Front-end: implement loading SFZ files directly, without adding config entries.
[calfbox.git] / py / instr_gui.py
blobea40f26e5e7e8cb6bf170c90f8dde73b427e6278
1 import cbox
2 from gui_tools import *
4 class StreamWindow(Gtk.VBox):
5 def __init__(self, instrument, iobj):
6 Gtk.Widget.__init__(self)
7 self.engine = iobj.engine
8 self.path = self.engine.path
10 panel = Gtk.VBox(spacing=5)
12 self.filebutton = Gtk.FileChooserButton("Streamed file")
13 self.filebutton.set_action(Gtk.FileChooserAction.OPEN)
14 self.filebutton.set_local_only(True)
15 self.filebutton.set_filename(self.engine.status().filename)
16 self.filebutton.add_filter(standard_filter(["*.wav", "*.WAV", "*.ogg", "*.OGG", "*.flac", "*.FLAC"], "All loadable audio files"))
17 self.filebutton.add_filter(standard_filter(["*.wav", "*.WAV"], "RIFF WAVE files"))
18 self.filebutton.add_filter(standard_filter(["*.ogg", "*.OGG"], "OGG container files"))
19 self.filebutton.add_filter(standard_filter(["*.flac", "*.FLAC"], "FLAC files"))
20 self.filebutton.add_filter(standard_filter(["*"], "All files"))
21 self.filebutton.connect('file-set', self.file_set)
22 hpanel = Gtk.HBox(spacing = 5)
23 hpanel.pack_start(Gtk.Label.new_with_mnemonic("_Play file:"), False, False, 5)
24 hpanel.pack_start(self.filebutton, True, True, 5)
25 panel.pack_start(hpanel, False, False, 5)
27 self.adjustment = Gtk.Adjustment()
28 self.adjustment_handler = self.adjustment.connect('value-changed', self.pos_slider_moved)
29 self.progress = standard_hslider(self.adjustment)
30 panel.pack_start(self.progress, False, False, 5)
32 self.play_button = Gtk.Button.new_with_mnemonic("_Play")
33 self.rewind_button = Gtk.Button.new_with_mnemonic("_Rewind")
34 self.stop_button = Gtk.Button.new_with_mnemonic("_Stop")
35 buttons = Gtk.HBox(spacing = 5)
36 buttons.add(self.play_button)
37 buttons.add(self.rewind_button)
38 buttons.add(self.stop_button)
39 panel.pack_start(buttons, False, False, 5)
41 self.add(panel)
42 self.play_button.connect('clicked', lambda x: self.engine.play())
43 self.rewind_button.connect('clicked', lambda x: self.engine.seek(0))
44 self.stop_button.connect('clicked', lambda x: self.engine.stop())
45 set_timer(self, 30, self.update)
47 def update(self):
48 attribs = cbox.GetThings("%s/status" % self.path, ['filename', 'pos', 'length', 'playing'], [])
49 self.progress.set_sensitive(attribs.length is not None)
50 if attribs.length is not None:
51 try:
52 self.adjustment.handler_block(self.adjustment_handler)
53 self.adjustment.set_properties(value = attribs.pos, lower = 0, upper = attribs.length)
54 #self.adjustment.set_all(attribs.pos, 0, attribs.length, 44100, 44100 * 10, 0)
55 finally:
56 self.adjustment.handler_unblock(self.adjustment_handler)
57 return True
59 def pos_slider_moved(self, adjustment):
60 self.engine.seek(adjustment.get_value())
62 def file_set(self, button):
63 self.engine.load(button.get_filename())
66 class WithPatchTable:
67 def __init__(self, attribs):
68 self.patches = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT)
69 self.patch_combos = []
71 self.table = Gtk.Table(2, 16)
72 self.table.set_col_spacings(5)
74 for i in range(16):
75 self.table.attach(bold_label("Channel %s" % (1 + i)), 0, 1, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
76 cb = standard_combo(self.patches, None)
77 cb.connect('changed', self.patch_combo_changed, i + 1)
78 self.table.attach(cb, 1, 2, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
79 self.patch_combos.append(cb)
81 self.update_model()
82 set_timer(self, 500, self.patch_combo_update)
84 def update_model(self):
85 self.patches.clear()
86 patches = self.engine.get_patches()
87 ch_patches = self.engine.status().patches
88 self.mapping = {}
89 for id in patches:
90 self.mapping[id] = len(self.mapping)
91 self.patches.append((self.fmt_patch_name(patches[id], id), id))
92 self.patch_combo_update()
94 def patch_combo_changed(self, combo, channel):
95 if combo.get_active() == -1:
96 return
97 self.engine.set_patch(channel, self.patches[combo.get_active()][1])
99 def patch_combo_update(self):
100 patch = self.engine.status().patches
101 for i in range(16):
102 cb = self.patch_combos[i]
103 old_patch_index = cb.get_active() if cb.get_active() >= 0 else -1
104 patch_id = patch[i + 1][0]
105 current_patch_index = self.mapping[patch_id] if (patch_id >= 0 and patch_id in self.mapping) else -1
106 if old_patch_index != current_patch_index:
107 cb.set_active(current_patch_index)
108 #self.status_label.set_markup(s)
109 return True
111 def fmt_patch_name(self, patch, id):
112 return "%s (%s)" % (patch, id)
114 class FluidsynthWindow(Gtk.VBox, WithPatchTable):
115 def __init__(self, instrument, iobj):
116 Gtk.VBox.__init__(self)
117 self.engine = iobj.engine
118 self.path = self.engine.path
119 print (iobj.path)
121 attribs = iobj.status()
123 panel = Gtk.VBox(spacing=5)
124 table = Gtk.Table(2, 1)
125 IntSliderRow("Polyphony", "polyphony", 2, 256).add_row(table, 0, cbox.VarPath(self.path), attribs)
127 WithPatchTable.__init__(self, attribs)
128 panel.pack_start(standard_vscroll_window(-1, 160, self.table), True, True, 5)
130 hpanel = Gtk.HBox(spacing = 5)
131 self.filebutton = Gtk.FileChooserButton("Soundfont")
132 self.filebutton.set_action(Gtk.FileChooserAction.OPEN)
133 self.filebutton.set_local_only(True)
134 self.filebutton.set_filename(cbox.GetThings("%s/status" % self.path, ['soundfont'], []).soundfont)
135 self.filebutton.add_filter(standard_filter(["*.sf2", "*.SF2"], "SF2 Soundfonts"))
136 self.filebutton.add_filter(standard_filter(["*"], "All files"))
137 hpanel.pack_start(Gtk.Label.new_with_mnemonic("_Load SF2:"), False, False, 5)
138 hpanel.pack_start(self.filebutton, True, True, 5)
139 unload = Gtk.Button.new_with_mnemonic("_Unload")
140 hpanel.pack_start(unload, False, False, 5)
141 unload.connect('clicked', self.unload)
142 panel.pack_start(hpanel, False, False, 5)
144 self.filebutton.connect('file-set', self.file_set)
146 self.add(panel)
147 def file_set(self, button):
148 self.engine.load_soundfont(button.get_filename())
149 self.update_model()
150 def unload(self, button):
151 self.filebutton.set_filename('')
153 class LoadProgramDialog(SelectObjectDialog):
154 title = "Load a sampler program"
155 def __init__(self, parent):
156 SelectObjectDialog.__init__(self, parent)
157 def update_model(self, model):
158 for s in cbox.Config.sections("spgm:"):
159 title = s["title"]
160 if s["sfz"] == None:
161 model.append((s.name[5:], "Program", s.name, title))
162 else:
163 model.append((s.name[5:], "SFZ", s.name, title))
165 class SamplerWindow(Gtk.VBox, WithPatchTable):
166 def __init__(self, instrument, iobj):
167 Gtk.VBox.__init__(self)
168 self.engine = iobj.engine
169 self.path = self.engine.path
171 attribs = self.engine.status()
173 panel = Gtk.VBox(spacing=5)
174 table = Gtk.Table(2, 2)
175 table.set_col_spacings(5)
176 IntSliderRow("Polyphony", "polyphony", 1, 128).add_row(table, 0, cbox.VarPath(self.path), attribs)
177 self.voices_widget = add_display_row(table, 1, "Voices in use", cbox.VarPath(self.path), attribs, "active_voices")
178 panel.pack_start(table, False, False, 5)
180 WithPatchTable.__init__(self, attribs)
181 panel.pack_start(standard_vscroll_window(-1, 160, self.table), True, True, 5)
182 self.add(panel)
183 hpanel = Gtk.HBox(spacing = 5)
185 hpanel.pack_start(Gtk.Label.new_with_mnemonic("Add from _SFZ:"), False, False, 5)
186 self.filebutton = Gtk.FileChooserButton("Soundfont")
187 self.filebutton.set_action(Gtk.FileChooserAction.OPEN)
188 self.filebutton.set_local_only(True)
189 #self.filebutton.set_filename(cbox.GetThings("%s/status" % self.path, ['soundfont'], []).soundfont)
190 self.filebutton.add_filter(standard_filter(["*.sfz", "*.SFZ"], "SFZ Programs"))
191 self.filebutton.add_filter(standard_filter(["*"], "All files"))
192 self.filebutton.connect('file-set', self.load_sfz)
193 hpanel.pack_start(self.filebutton, False, True, 5)
195 load_button = Gtk.Button.new_with_mnemonic("Add from _config")
196 load_button.connect('clicked', self.load_config)
197 hpanel.pack_start(load_button, False, True, 5)
198 panel.pack_start(hpanel, False, False, 5)
200 set_timer(self, 200, self.voices_update)
201 self.polyphony_labels = {}
202 for i in range(16):
203 button = Gtk.Button("Dump SFZ")
204 button.connect("clicked", self.dump_sfz, i + 1)
205 self.table.attach(button, 2, 3, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
206 label = Gtk.Label("")
207 self.table.attach(label, 3, 4, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
208 self.polyphony_labels[i + 1] = label
210 def dump_sfz(self, w, channel):
211 attribs = cbox.GetThings("%s/status" % self.path, ['%patch', 'polyphony', 'active_voices'], [])
212 prog_no, patch_name = attribs.patch[channel]
213 pname, uuid, in_use_cnt = cbox.GetThings("%s/patches" % self.path, ['%patch'], []).patch[prog_no]
214 print ("UUID=%s" % uuid)
215 patch = cbox.Document.map_uuid(uuid)
216 groups = patch.get_groups()
217 for r in groups[0].get_children():
218 print ("<region> %s" % (r.as_string()))
219 for grp in patch.get_groups()[1:]:
220 print ("<group> %s" % (grp.as_string()))
221 for r in grp.get_children():
222 print ("<region> %s" % (r.as_string()))
224 def load_config(self, event):
225 d = LoadProgramDialog(self.get_toplevel())
226 response = d.run()
227 try:
228 if response == Gtk.ResponseType.OK:
229 scene = d.get_selected_object()
230 pgm_id = self.engine.get_unused_program()
231 self.engine.load_patch_from_cfg(pgm_id, scene[2], scene[2][5:])
232 self.update_model()
233 finally:
234 d.destroy()
236 def load_sfz(self, button):
237 pgm_id = self.engine.get_unused_program()
238 self.engine.load_patch_from_file(pgm_id, self.filebutton.get_filename(), self.filebutton.get_filename())
239 self.update_model()
241 def voices_update(self):
242 status = self.engine.status()
243 self.voices_widget.set_text(str(status.active_voices))
244 for i in range(16):
245 self.polyphony_labels[i + 1].set_text("%d voices" % (status.channel_voices[i + 1],))
247 return True
249 def fmt_patch_name(self, patch, id):
250 return "%s (%s)" % (patch[0], id)
252 class TonewheelOrganWindow(Gtk.VBox):
253 combos = [
254 (1, 'Upper', 'upper_vibrato', [(0, 'Off'), (1, 'On')]),
255 (1, 'Lower', 'lower_vibrato', [(0, 'Off'), (1, 'On')]),
256 (1, 'Mode', 'vibrato_mode', [(0, '1'), (1, '2'), (2, '3')]),
257 (1, 'Chorus', 'vibrato_chorus', [(0, 'Off'), (1, 'On')]),
258 (2, 'Enable', 'percussion_enable', [(0, 'Off'), (1, 'On')]),
259 (2, 'Harmonic', 'percussion_3rd', [(0, '2nd'), (1, '3rd')]),
261 def __init__(self, instrument, iobj):
262 Gtk.VBox.__init__(self)
263 self.engine = iobj.engine
264 self.path = self.engine.path
265 panel = Gtk.VBox(spacing=10)
266 table = Gtk.Table(4, 10)
267 table.props.row_spacing = 10
268 table.set_col_spacings(5)
269 self.drawbars = {}
270 self.hboxes = {}
271 self.hboxes[1] = Gtk.HBox(spacing = 10)
272 self.hboxes[1].pack_start(Gtk.Label('Vibrato: '), False, False, 5)
273 self.hboxes[2] = Gtk.HBox(spacing = 10)
274 self.hboxes[2].pack_start(Gtk.Label('Percussion: '), False, False, 5)
275 self.combos = {}
276 for row, name, flag, options in TonewheelOrganWindow.combos:
277 label = Gtk.Label(name)
278 self.hboxes[row].pack_start(label, False, False, 5)
279 model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING)
280 for oval, oname in options:
281 model.append((oval, oname))
282 combo = standard_combo(model, column = 1)
283 self.hboxes[row].pack_start(combo, False, False, 5)
284 combo.update_handler = combo.connect('changed', lambda w, setter: setter(w.get_model()[w.get_active()][0]), getattr(self.engine, 'set_' + flag))
285 self.combos[flag] = combo
286 panel.pack_start(self.hboxes[1], False, False, 5)
287 panel.pack_start(self.hboxes[2], False, False, 5)
288 table.attach(Gtk.Label("Upper"), 0, 1, 0, 1)
289 table.attach(Gtk.Label("Lower"), 0, 1, 1, 2)
290 for i in range(9):
291 slider = Gtk.VScale(adjustment = Gtk.Adjustment(0, 0, 8, 1, 1))
292 slider.props.digits = 0
293 table.attach(slider, i + 1, i + 2, 0, 1)
294 self.drawbars['u%d' % i] = slider.get_adjustment()
295 slider.get_adjustment().connect('value-changed', lambda adj, drawbar: self.engine.set_upper_drawbar(drawbar, int(adj.get_value())), i)
296 slider = Gtk.VScale(adjustment = Gtk.Adjustment(0, 0, 8, 1, 1))
297 slider.props.digits = 0
298 table.attach(slider, i + 1, i + 2, 1, 2)
299 self.drawbars['l%d' % i] = slider.get_adjustment()
300 slider.get_adjustment().connect('value-changed', lambda adj, drawbar: self.engine.set_lower_drawbar(drawbar, int(adj.get_value())), i)
301 panel.add(table)
302 self.add(panel)
303 self.refresh()
305 def refresh(self):
306 attribs = self.engine.status()
307 for i in range(9):
308 self.drawbars['u%d' % i].set_value(attribs.upper_drawbar[i])
309 self.drawbars['l%d' % i].set_value(attribs.lower_drawbar[i])
310 for row, name, flag, options in TonewheelOrganWindow.combos:
311 combo = self.combos[flag]
312 combo.handler_block(combo.update_handler)
313 combo.set_active(ls_index(combo.get_model(), getattr(attribs, flag), 0))
314 combo.handler_unblock(combo.update_handler)
316 instrument_window_map = {
317 'stream_player' : StreamWindow,
318 'fluidsynth' : FluidsynthWindow,
319 'sampler' : SamplerWindow,
320 'tonewheel_organ' : TonewheelOrganWindow