Sampled Instrument Player with static and monolithic design. All instruments are built-in.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

433 lines
18 KiB

import cbox
from gui_tools import *
#################################################################################################################################
class EffectWindow(Gtk.Window):
engine_name = None
def __init__(self, location, main_window, path):
Gtk.Window.__init__(self, Gtk.WindowType.TOPLEVEL)
self.set_type_hint(Gdk.WindowTypeHint.UTILITY)
self.set_transient_for(main_window)
self.main_window = main_window
self.path = path
self.vpath = cbox.VarPath(path)
self.set_title("%s - %s" % (self.effect_name, location))
self.vbox = Gtk.VBox()
menu_bar = Gtk.MenuBar()
menu_bar.append(create_menu("_Effect", [
("_Save as...", self.on_effect_save_as if self.engine_name is not None else None),
("_Close", lambda w: self.destroy()),
]))
self.vbox.pack_start(menu_bar, False, False, 0)
if hasattr(self, 'params'):
values = cbox.GetThings(self.path + "/status", [p.name for p in self.params], [])
self.refreshers = []
t = Gtk.Table(2, len(self.params))
for i in range(len(self.params)):
p = self.params[i]
t.attach(p.create_label(), 0, 1, i, i+1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
widget, refresher = p.create_widget(self.vpath)
refresher(values)
t.attach(widget, 1, 2, i, i+1, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK)
self.refreshers.append(refresher)
self.vbox.pack_start(t, True, True, 5)
self.add(self.vbox)
def create_param_table(self, cols, rows, values, extra_rows = 0):
t = Gtk.Table(4, rows + 1 + extra_rows)
self.cols = cols
self.table_refreshers = []
for i in range(len(self.cols)):
par = self.cols[i]
t.attach(par.create_label(), i, i + 1, 0, 1, Gtk.AttachOptions.SHRINK | Gtk.AttachOptions.FILL)
for j in range(rows):
widget, refresher = par.create_widget(self.vpath.plus(None, j))
t.attach(widget, i, i + 1, j + 1, j + 2, Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL)
refresher(values)
self.table_refreshers.append(refresher)
return t
def get_save_params(self):
if hasattr(self, 'params'):
values = cbox.GetThings(self.path + "/status", [p.name for p in self.params], [])
result = {'engine' : self.engine_name}
for p in self.params:
result[p.name] = str(getattr(values, p.name))
return result
return None
def refresh(self):
if hasattr(self, 'params'):
values = cbox.GetThings(self.path + "/status", [p.name for p in self.params], [])
for refresher in self.refreshers:
refresher(values)
def on_effect_save_as(self, w):
data = self.get_save_params()
if data is None:
print ("Save not implemented for this effect")
return
dlg = SaveConfigObjectDialog(self, "Select name for effect preset")
try:
if dlg.run() == Gtk.ResponseType.OK and dlg.get_name() != "":
cs = cbox.CfgSection("fxpreset:" + dlg.get_name())
for name in sorted(data.keys()):
cs[name] = data[name]
cbox.Config.save()
finally:
dlg.destroy()
#################################################################################################################################
class PhaserWindow(EffectWindow):
params = [
MappedSliderRow("Center", "center_freq", LogMapper(100, 2000, freq_format)),
SliderRow("Mod depth", "mod_depth", 0, 7200),
SliderRow("Feedback", "fb_amt", -1, 1),
MappedSliderRow("LFO frequency", "lfo_freq", lfo_freq_mapper),
SliderRow("Stereo", "stereo_phase", 0, 360),
SliderRow("Wet/dry", "wet_dry", 0, 1),
IntSliderRow("Stages", "stages", 1, 12)
]
engine_name = "phaser"
effect_name = "Phaser"
class ChorusWindow(EffectWindow):
params = [
SliderRow("Min. delay", "min_delay", 1, 20),
SliderRow("Mod depth", "mod_depth", 1, 20),
MappedSliderRow("LFO frequency", "lfo_freq", lfo_freq_mapper),
SliderRow("Stereo", "stereo_phase", 0, 360),
SliderRow("Wet/dry", "wet_dry", 0, 1)
]
engine_name = "chorus"
effect_name = "Chorus"
class DelayWindow(EffectWindow):
params = [
SliderRow("Delay time (ms)", "time", 1, 1000),
SliderRow("Feedback", "fb_amt", 0, 1),
SliderRow("Wet/dry", "wet_dry", 0, 1)
]
engine_name = "delay"
effect_name = "Delay"
class ReverbWindow(EffectWindow):
params = [
SliderRow("Decay time", "decay_time", 500, 5000),
SliderRow("Dry amount", "dry_amt", -100, 12),
SliderRow("Wet amount", "wet_amt", -100, 12),
MappedSliderRow("Lowpass", "lowpass", LogMapper(300, 20000, freq_format)),
MappedSliderRow("Highpass", "highpass", LogMapper(30, 2000, freq_format))
]
engine_name = "reverb"
effect_name = "Reverb"
class ToneControlWindow(EffectWindow):
params = [
MappedSliderRow("Lowpass", "lowpass", LogMapper(300, 20000, freq_format)),
MappedSliderRow("Highpass", "highpass", LogMapper(30, 2000, freq_format))
]
engine_name = "tone_control"
effect_name = "Tone Control"
class CompressorWindow(EffectWindow):
params = [
SliderRow("Threshold", "threshold", -100, 12),
MappedSliderRow("Ratio", "ratio", LogMapper(1, 100, "%0.2f")),
MappedSliderRow("Attack", "attack", LogMapper(1, 1000, ms_format)),
MappedSliderRow("Release", "release", LogMapper(1, 1000, ms_format)),
SliderRow("Make-up gain", "makeup", -48, 48),
]
engine_name = "compressor"
effect_name = "Compressor"
class GateWindow(EffectWindow):
params = [
SliderRow("Threshold", "threshold", -100, 12),
MappedSliderRow("Ratio", "ratio", LogMapper(1, 100, "%0.2f")),
MappedSliderRow("Attack", "attack", LogMapper(1, 1000, ms_format)),
MappedSliderRow("Hold", "hold", LogMapper(1, 1000, ms_format)),
MappedSliderRow("Release", "release", LogMapper(1, 1000, ms_format)),
]
engine_name = "gate"
effect_name = "Gate"
class DistortionWindow(EffectWindow):
params = [
SliderRow("Drive", "drive", -36, 36),
SliderRow("Shape", "shape", -1, 2),
]
engine_name = "distortion"
effect_name = "Distortion"
class FuzzWindow(EffectWindow):
params = [
SliderRow("Drive", "drive", -36, 36),
SliderRow("Wet/dry", "wet_dry", 0, 1),
SliderRow("Rectify", "rectify", 0, 1),
MappedSliderRow("Pre freq", "band", LogMapper(100, 5000, freq_format)),
SliderRow("Pre width", "bandwidth", 0.25, 4),
MappedSliderRow("Post freq", "band2", LogMapper(100, 5000, freq_format)),
SliderRow("Post width", "bandwidth2", 0.25, 4),
]
engine_name = "fuzz"
effect_name = "Fuzz"
class LimiterWindow(EffectWindow):
params = [
SliderRow("Threshold", "threshold", -100, 12),
MappedSliderRow("Attack", "attack", LogMapper(1, 1000, ms_format)),
MappedSliderRow("Release", "release", LogMapper(1, 5000, ms_format)),
]
engine_name = "limiter"
effect_name = "Limiter"
class EQCommon(object):
columns = [
CheckBoxRow("Active", "active"),
MappedSliderRow("Center Freq", "center", filter_freq_mapper),
MappedSliderRow("Filter Q", "q", LogMapper(0.01, 100, "%f")),
SliderRow("Gain", "gain", -36, 36),
]
def get_save_params(self):
values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
result = {'engine':self.engine_name}
for row in range(self.bands):
row2 = 1 + row
result['band%s_active' % row2] = values.active[row]
result['band%s_center' % row2] = values.center[row]
result['band%s_q' % row2] = values.q[row]
result['band%s_gain' % row2] = values.gain[row]
return result
class EQWindow(EffectWindow, EQCommon):
def __init__(self, instrument, main_window, path):
EffectWindow.__init__(self, instrument, main_window, path)
values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
self.vbox.add(self.create_param_table(self.columns, 4, values))
def get_save_params(self):
return EQCommon.get_save_params(self)
effect_name = "Equalizer"
engine_name = "parametric_eq"
bands = 4
class FBRWindow(EffectWindow, EQCommon):
effect_name = "Feedback Reduction"
engine_name = "feedback_reducer"
bands = 16
def __init__(self, instrument, main_window, path):
EffectWindow.__init__(self, instrument, main_window, path)
values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
t = self.create_param_table(self.columns, 16, values, 1)
self.vbox.add(t)
self.ready_label = Gtk.Label("-")
t.attach(self.ready_label, 0, 2, 17, 18)
set_timer(self, 100, self.update)
sbutton = Gtk.Button.new_with_mnemonic("_Start")
sbutton.connect("clicked", lambda button, path: cbox.do_cmd(path + "/start", None, []), self.path)
t.attach(sbutton, 2, 4, 17, 18)
def refresh_table(self):
values = cbox.GetThings(self.path + "/status", ["%active", "%center", "%q", "%gain"], [])
for refresher in self.table_refreshers:
refresher(values)
def update(self):
values = cbox.GetThings(self.path + "/status", ["finished", "refresh"], [])
if values.refresh:
self.refresh_table()
if values.finished > 0:
self.ready_label.set_text("Ready")
else:
self.ready_label.set_text("Not Ready")
return True
def get_save_params(self):
return EQCommon.get_save_params(self)
class FXChainWindow(EffectWindow):
effect_name = "Effect chain"
def __init__(self, instrument, main_window, path):
EffectWindow.__init__(self, instrument, main_window, path)
self.fx_table = None
self.choosers = []
self.refresh_table()
def refresh_table(self):
res = cbox.GetThings(self.path + "/status", ["*module", "%bypass"], [])
values = res.module
bypass = res.bypass
fx_count = len(values)
t = Gtk.Table(fx_count + 2, 9)
for c in self.choosers:
c.close_popup()
self.choosers = []
for i in range(1, fx_count + 1):
engine, preset = values[i - 1]
chooser = InsertEffectChooser("%s/module/%s" % (self.path, i), "%s: slot %s" % (self.get_title(), i), engine, preset, bypass[i], self.main_window)
t.attach(chooser.fx_engine, 0, 1, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
t.attach(chooser.fx_preset, 1, 2, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
t.attach(chooser.fx_edit, 2, 3, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
t.attach(chooser.fx_bypass, 3, 4, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
buttons = [
("+", self.on_add_clicked, lambda pos: True),
("-", self.on_delete_clicked, lambda pos: True),
("Up", self.on_up_clicked, lambda pos: pos > 1),
("Down", self.on_down_clicked, lambda pos: pos < fx_count)
]
for j in range(len(buttons)):
label, method, cond = buttons[j]
if not cond(i):
continue
button = Gtk.Button(label)
button.connect('clicked', lambda button, method, pos: method(pos), method, i)
t.attach(button, 4 + j, 5 + j, i, i + 1, 0, Gtk.AttachOptions.SHRINK)
self.choosers.append(chooser)
button = Gtk.Button("+")
button.connect('clicked', lambda button, pos: self.on_add_clicked(pos), fx_count + 1)
t.attach(button, 3, 4, fx_count + 1, fx_count + 2, 0, Gtk.AttachOptions.SHRINK)
if self.fx_table is not None:
self.vbox.remove(self.fx_table)
self.vbox.pack_start(t, True, True, 5)
t.show_all()
self.fx_table = t
def on_add_clicked(self, pos):
cbox.do_cmd(self.path + "/insert", None, [pos])
self.refresh_table()
def on_delete_clicked(self, pos):
cbox.do_cmd(self.path + "/delete", None, [pos])
self.refresh_table()
def on_up_clicked(self, pos):
cbox.do_cmd(self.path + "/move", None, [pos, pos - 1])
self.refresh_table()
def on_down_clicked(self, pos):
cbox.do_cmd(self.path + "/move", None, [pos, pos + 1])
self.refresh_table()
#################################################################################################################################
effect_engines = ['', 'phaser', 'reverb', 'chorus', 'feedback_reducer', 'tone_control', 'delay', 'parametric_eq', 'compressor', 'gate', 'distortion', 'fuzz', 'fxchain', 'limiter']
effect_window_map = {
'phaser': PhaserWindow,
'chorus': ChorusWindow,
'delay': DelayWindow,
'reverb' : ReverbWindow,
'feedback_reducer': FBRWindow,
'parametric_eq': EQWindow,
'tone_control': ToneControlWindow,
'compressor': CompressorWindow,
'gate': GateWindow,
'distortion': DistortionWindow,
'fuzz': FuzzWindow,
'limiter': LimiterWindow,
'fxchain': FXChainWindow,
}
#################################################################################################################################
class EffectListModel(Gtk.ListStore):
def __init__(self):
self.presets = {}
Gtk.ListStore.__init__(self, GObject.TYPE_STRING)
for engine in effect_engines:
self.presets[engine] = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
self.append((engine,))
for preset in cbox.Config.sections("fxpreset:"):
engine = preset["engine"]
if engine in self.presets:
title = preset.title if hasattr(preset, 'title') else preset.name[9:]
self.presets[engine].append((preset.name[9:], title))
def get_model_for_engine(self, engine):
return self.presets[engine]
effect_list_model = EffectListModel()
#################################################################################################################################
class InsertEffectChooser(object):
def __init__(self, opath, location, engine, preset, bypass, main_window):
self.opath = opath
self.location = location
self.main_window = main_window
self.popup = None
self.fx_engine = standard_combo(effect_list_model, ls_index(effect_list_model, engine, 0), width = 120)
self.fx_engine.connect('changed', self.fx_engine_changed)
if engine in effect_engines:
model = effect_list_model.get_model_for_engine(engine)
self.fx_preset = standard_combo(model, active_item_lookup = preset, column = 1, lookup_column = 0, width = 120)
else:
self.fx_preset = standard_combo(None, active_item = 0, column = 1, width = 120)
self.fx_preset.connect('changed', self.fx_preset_changed)
self.fx_edit = Gtk.Button.new_with_mnemonic("_Edit")
self.fx_edit.connect("clicked", self.edit_effect_clicked)
self.fx_edit.set_sensitive(engine in effect_window_map)
self.fx_bypass = Gtk.ToggleButton.new_with_mnemonic("_Bypass")
self.fx_bypass.set_active(bypass > 0)
self.fx_bypass.connect("clicked", self.bypass_effect_clicked)
def edit_effect_clicked(self, button):
if self.popup is not None:
self.popup.present()
return
engine = cbox.GetThings(self.opath + "/status", ['insert_engine'], []).insert_engine
wclass = effect_window_map[engine]
popup = wclass(self.location, self.main_window, "%s/engine" % self.opath)
popup.show_all()
popup.present()
popup.connect('delete_event', self.on_popup_closed)
self.popup = popup
def fx_engine_changed(self, combo):
if self.popup is not None:
self.popup.destroy()
self.popup = None
engine = combo.get_model()[combo.get_active()][0]
cbox.do_cmd(self.opath + '/insert_engine', None, [engine])
self.fx_preset.set_model(effect_list_model.get_model_for_engine(engine))
self.fx_preset.set_active(0)
self.fx_edit.set_sensitive(engine in effect_window_map)
def fx_preset_changed(self, combo):
if combo.get_active() >= 0:
cbox.do_cmd(self.opath + '/insert_preset', None, [combo.get_model()[combo.get_active()][0]])
if self.popup is not None:
self.popup.refresh()
def on_popup_closed(self, popup, event):
self.popup = None
def close_popup(self):
if self.popup is not None:
self.popup.destroy();
def bypass_effect_clicked(self, button):
cbox.do_cmd(self.opath + "/set_bypass", None, [1 if button.get_active() else 0])
#################################################################################################################################
class LoadEffectDialog(SelectObjectDialog):
title = "Load an aux effect"
def __init__(self, parent):
SelectObjectDialog.__init__(self, parent)
def update_model(self, model):
for s in cbox.Config.sections("fxpreset:"):
title = s["title"]
model.append((s.name[9:], s['engine'], s.name, title))