Handle remaining samples < 4 correctly(?).
[calfbox.git] / py / instr_gui.py
blobc9608c6a4de19ba009221d95f58f1aec4315caa3
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 iattribs = iobj.status()
172 attribs = self.engine.status()
174 panel = Gtk.VBox(spacing=5)
175 table = Gtk.Table(2, 2)
176 table.set_col_spacings(5)
177 IntSliderRow("Polyphony", "polyphony", 1, 128).add_row(table, 0, cbox.VarPath(self.path), attribs)
178 self.voices_widget = add_display_row(table, 1, "Voices in use", cbox.VarPath(self.path), attribs, "active_voices")
179 panel.pack_start(table, False, False, 5)
181 WithPatchTable.__init__(self, attribs)
182 panel.pack_start(standard_vscroll_window(-1, 160, self.table), True, True, 5)
183 self.add(panel)
184 hpanel = Gtk.HBox(spacing = 5)
186 hpanel.pack_start(Gtk.Label.new_with_mnemonic("Add from _SFZ:"), False, False, 5)
187 self.filebutton = Gtk.FileChooserButton("Soundfont")
188 self.filebutton.set_action(Gtk.FileChooserAction.OPEN)
189 self.filebutton.set_local_only(True)
190 #self.filebutton.set_filename(cbox.GetThings("%s/status" % self.path, ['soundfont'], []).soundfont)
191 self.filebutton.add_filter(standard_filter(["*.sfz", "*.SFZ"], "SFZ Programs"))
192 self.filebutton.add_filter(standard_filter(["*"], "All files"))
193 self.filebutton.connect('file-set', self.load_sfz)
194 hpanel.pack_start(self.filebutton, False, True, 5)
196 load_button = Gtk.Button.new_with_mnemonic("Add from _config")
197 load_button.connect('clicked', self.load_config)
198 hpanel.pack_start(load_button, False, True, 5)
199 panel.pack_start(hpanel, False, False, 5)
201 set_timer(self, 200, self.voices_update)
202 self.output_model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING)
203 for i in range(iattribs.outputs):
204 self.output_model.append((i + 1, "Out %d" % (i + 1)))
206 self.polyphony_labels = {}
207 self.output_combos = {}
208 for i in range(16):
209 button = Gtk.Button("Dump SFZ")
210 button.connect("clicked", self.dump_sfz, i + 1)
211 self.table.attach(button, 2, 3, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
212 label = Gtk.Label("")
213 self.table.attach(label, 3, 4, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
214 self.polyphony_labels[i + 1] = label
215 combo = standard_combo(self.output_model, column = 1)
216 combo.connect('changed', self.output_combo_changed, i + 1)
217 self.table.attach(combo, 4, 5, i, i + 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK)
218 self.output_combos[i + 1] = combo
219 self.output_combo_update()
221 def output_combo_update(self):
222 output = self.engine.status().output
223 for i in range(16):
224 cb = self.output_combos[i + 1]
225 old_channel_index = cb.get_active() if cb.get_active() >= 0 else -1
226 if old_channel_index != output[1 + i]:
227 cb.set_active(output[1 + i])
228 #self.status_label.set_markup(s)
229 return True
231 def output_combo_changed(self, combo, channel):
232 if combo.get_active() == -1:
233 return
234 self.engine.set_output(channel, combo.get_active())
236 def dump_sfz(self, w, channel):
237 attribs = cbox.GetThings("%s/status" % self.path, ['%patch', 'polyphony', 'active_voices'], [])
238 prog_no, patch_name = attribs.patch[channel]
239 pname, uuid, in_use_cnt = cbox.GetThings("%s/patches" % self.path, ['%patch'], []).patch[prog_no]
240 print ("UUID=%s" % uuid)
241 patch = cbox.Document.map_uuid(uuid)
242 groups = patch.get_groups()
243 for r in groups[0].get_children():
244 print ("<region> %s" % (r.as_string()))
245 for grp in patch.get_groups()[1:]:
246 print ("<group> %s" % (grp.as_string()))
247 for r in grp.get_children():
248 print ("<region> %s" % (r.as_string()))
250 def load_config(self, event):
251 d = LoadProgramDialog(self.get_toplevel())
252 response = d.run()
253 try:
254 if response == Gtk.ResponseType.OK:
255 scene = d.get_selected_object()
256 pgm_id = self.engine.get_unused_program()
257 self.engine.load_patch_from_cfg(pgm_id, scene[2], scene[2][5:])
258 self.update_model()
259 finally:
260 d.destroy()
262 def load_sfz(self, button):
263 pgm_id = self.engine.get_unused_program()
264 self.engine.load_patch_from_file(pgm_id, self.filebutton.get_filename(), self.filebutton.get_filename())
265 self.update_model()
267 def voices_update(self):
268 status = self.engine.status()
269 self.voices_widget.set_text("%s voices, %s pipes" % (status.active_voices, status.active_pipes))
270 for i in range(16):
271 self.polyphony_labels[i + 1].set_text("%d voices" % (status.channel_voices[i + 1],))
273 return True
275 def fmt_patch_name(self, patch, id):
276 return "%s (%s)" % (patch[0], id)
278 class TonewheelOrganWindow(Gtk.VBox):
279 combos = [
280 (1, 'Upper', 'upper_vibrato', [(0, 'Off'), (1, 'On')]),
281 (1, 'Lower', 'lower_vibrato', [(0, 'Off'), (1, 'On')]),
282 (1, 'Mode', 'vibrato_mode', [(0, '1'), (1, '2'), (2, '3')]),
283 (1, 'Chorus', 'vibrato_chorus', [(0, 'Off'), (1, 'On')]),
284 (2, 'Enable', 'percussion_enable', [(0, 'Off'), (1, 'On')]),
285 (2, 'Harmonic', 'percussion_3rd', [(0, '2nd'), (1, '3rd')]),
287 def __init__(self, instrument, iobj):
288 Gtk.VBox.__init__(self)
289 self.engine = iobj.engine
290 self.path = self.engine.path
291 panel = Gtk.VBox(spacing=10)
292 table = Gtk.Table(4, 10)
293 table.props.row_spacing = 10
294 table.set_col_spacings(5)
295 self.drawbars = {}
296 self.hboxes = {}
297 self.hboxes[1] = Gtk.HBox(spacing = 10)
298 self.hboxes[1].pack_start(Gtk.Label('Vibrato: '), False, False, 5)
299 self.hboxes[2] = Gtk.HBox(spacing = 10)
300 self.hboxes[2].pack_start(Gtk.Label('Percussion: '), False, False, 5)
301 self.combos = {}
302 for row, name, flag, options in TonewheelOrganWindow.combos:
303 label = Gtk.Label(name)
304 self.hboxes[row].pack_start(label, False, False, 5)
305 model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING)
306 for oval, oname in options:
307 model.append((oval, oname))
308 combo = standard_combo(model, column = 1)
309 self.hboxes[row].pack_start(combo, False, False, 5)
310 combo.update_handler = combo.connect('changed', lambda w, setter: setter(w.get_model()[w.get_active()][0]), getattr(self.engine, 'set_' + flag))
311 self.combos[flag] = combo
312 panel.pack_start(self.hboxes[1], False, False, 5)
313 panel.pack_start(self.hboxes[2], False, False, 5)
314 table.attach(Gtk.Label("Upper"), 0, 1, 0, 1)
315 table.attach(Gtk.Label("Lower"), 0, 1, 1, 2)
316 for i in range(9):
317 slider = Gtk.VScale(adjustment = Gtk.Adjustment(0, 0, 8, 1, 1))
318 slider.props.digits = 0
319 table.attach(slider, i + 1, i + 2, 0, 1)
320 self.drawbars['u%d' % i] = slider.get_adjustment()
321 slider.get_adjustment().connect('value-changed', lambda adj, drawbar: self.engine.set_upper_drawbar(drawbar, int(adj.get_value())), i)
322 slider = Gtk.VScale(adjustment = Gtk.Adjustment(0, 0, 8, 1, 1))
323 slider.props.digits = 0
324 table.attach(slider, i + 1, i + 2, 1, 2)
325 self.drawbars['l%d' % i] = slider.get_adjustment()
326 slider.get_adjustment().connect('value-changed', lambda adj, drawbar: self.engine.set_lower_drawbar(drawbar, int(adj.get_value())), i)
327 panel.add(table)
328 self.add(panel)
329 self.refresh()
331 def refresh(self):
332 attribs = self.engine.status()
333 for i in range(9):
334 self.drawbars['u%d' % i].set_value(attribs.upper_drawbar[i])
335 self.drawbars['l%d' % i].set_value(attribs.lower_drawbar[i])
336 for row, name, flag, options in TonewheelOrganWindow.combos:
337 combo = self.combos[flag]
338 combo.handler_block(combo.update_handler)
339 combo.set_active(ls_index(combo.get_model(), getattr(attribs, flag), 0))
340 combo.handler_unblock(combo.update_handler)
342 instrument_window_map = {
343 'stream_player' : StreamWindow,
344 'fluidsynth' : FluidsynthWindow,
345 'sampler' : SamplerWindow,
346 'tonewheel_organ' : TonewheelOrganWindow