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.
432 lines
18 KiB
432 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))
|
|
|
|
|