GLUI: simplify inst edit window buttons
[spectmorph.git] / glui / sminsteditwindow.hh
1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2
3 #ifndef SPECTMORPH_INST_EDIT_WINDOW_HH
4 #define SPECTMORPH_INST_EDIT_WINDOW_HH
5
6 #include "sminstrument.hh"
7 #include "smsamplewidget.hh"
8 #include "smcombobox.hh"
9 #include "smtimer.hh"
10 #include "smwavsetbuilder.hh"
11 #include "sminsteditparams.hh"
12 #include "smbutton.hh"
13 #include "smcheckbox.hh"
14 #include "smshortcut.hh"
15
16 #include <thread>
17
18 namespace SpectMorph
19 {
20
21 enum class PlayMode
22 {
23   SPECTMORPH,
24   SAMPLE,
25   REFERENCE
26 };
27
28 class InstEditWindow;
29 class InstEditBackend
30 {
31   std::mutex              result_mutex;
32   std::unique_ptr<WavSet> result_wav_set;
33   PlayMode                result_play_mode;
34   SynthInterface         *synth_interface;
35
36   WavSetBuilder          *current_builder = nullptr;
37   WavSetBuilder          *next_builder = nullptr;
38
39   double
40   note_to_freq (int note)
41   {
42     return 440 * exp (log (2) * (note - 69) / 12.0);
43   }
44
45   inline std::string
46   smset_file()
47   {
48     return sm_get_user_dir (USER_DIR_DATA) + "/midi_synth.smset";
49   }
50
51 public:
52   InstEditBackend (SynthInterface *synth_interface) :
53     synth_interface (synth_interface)
54   {
55   }
56   void switch_to_sample (const Sample *sample, PlayMode play_mode, const Instrument *instrument);
57
58   void
59   add_builder (WavSetBuilder *builder, PlayMode play_mode)
60   {
61     std::lock_guard<std::mutex> lg (result_mutex);
62
63     result_play_mode = play_mode;
64
65     if (current_builder)
66       {
67         if (next_builder) /* kill and overwrite obsolete next builder */
68           delete next_builder;
69
70         next_builder = builder;
71       }
72     else
73       {
74         start_as_current (builder);
75       }
76   }
77   void
78   start_as_current (WavSetBuilder *builder)
79   {
80     // must have result_mutex lock
81     current_builder = builder;
82     new std::thread ([this] () {
83       WavSet *wav_set = current_builder->run();
84
85       finish_current_builder (wav_set);
86       });
87   }
88   void
89   finish_current_builder (WavSet *wav_set)
90   {
91     std::lock_guard<std::mutex> lg (result_mutex);
92
93     result_wav_set.reset (wav_set);
94
95     delete current_builder;
96     current_builder = nullptr;
97
98     if (next_builder)
99       {
100         WavSetBuilder *builder = next_builder;
101
102         next_builder = nullptr;
103         start_as_current (builder);
104       }
105   }
106   bool
107   have_builder()
108   {
109     std::lock_guard<std::mutex> lg (result_mutex);
110
111     return current_builder != nullptr;
112   }
113   int current_midi_note() {return 69;}
114
115   Signal<int, Audio *> signal_have_audio;
116   void on_timer();
117 };
118
119 class InstEditWindow : public Window
120 {
121   Instrument instrument;
122   InstEditBackend m_backend;
123   SynthInterface *synth_interface;
124
125   SampleWidget *sample_widget;
126   ComboBox *midi_note_combobox = nullptr;
127
128   std::string
129   note_to_text (int i)
130   {
131     std::vector<std::string> note_name { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
132     return string_printf ("%d  :  %s%d", i, note_name[i % 12].c_str(), i / 12 - 2);
133   }
134   void
135   load_sample (const std::string& filename)
136   {
137     if (filename != "")
138       instrument.add_sample (filename);
139   }
140   void
141   on_samples_changed()
142   {
143     sample_combobox->clear();
144     if (instrument.size() == 0)
145       {
146         sample_combobox->set_text ("");
147       }
148     for (size_t i = 0; i < instrument.size(); i++)
149       {
150         Sample *sample = instrument.sample (i);
151         std::string text = string_printf ("%s  :  %s", note_to_text (sample->midi_note()).c_str(), sample->short_name.c_str());
152
153         sample_combobox->add_item (text);
154
155         if (int (i) == instrument.selected())
156           sample_combobox->set_text (text);
157       }
158     Sample *sample = instrument.sample (instrument.selected());
159     sample_widget->set_sample (sample);
160     midi_note_combobox->set_enabled (sample != nullptr);
161     sample_combobox->set_enabled (sample != nullptr);
162     play_mode_combobox->set_enabled (sample != nullptr);
163     loop_combobox->set_enabled (sample != nullptr);
164     if (!sample)
165       {
166         midi_note_combobox->set_text ("");
167         loop_combobox->set_text ("");
168       }
169     else
170       {
171         midi_note_combobox->set_text (note_to_text (sample->midi_note()));
172         loop_combobox->set_text (loop_to_text (sample->loop()));
173       }
174     if (sample)
175       {
176         m_backend.switch_to_sample (sample, play_mode, &instrument);
177       }
178   }
179   void
180   on_marker_changed()
181   {
182     Sample *sample = instrument.sample (instrument.selected());
183
184     if (sample)
185       m_backend.switch_to_sample (sample, play_mode, &instrument);
186   }
187   void
188   on_global_changed()
189   {
190     auto_volume_checkbox->set_checked (instrument.auto_volume().enabled);
191
192     auto_tune_checkbox->set_checked (instrument.auto_tune().enabled);
193
194     Sample *sample = instrument.sample (instrument.selected());
195
196     if (sample)
197       m_backend.switch_to_sample (sample, play_mode, &instrument);
198   }
199   ComboBox *sample_combobox;
200   ScrollView *sample_scroll_view;
201   Label *hzoom_label;
202   Label *vzoom_label;
203   PlayMode play_mode = PlayMode::SPECTMORPH;
204   ComboBox *play_mode_combobox;
205   ComboBox *loop_combobox;
206   Led *led;
207   Label *playing_label;
208   CheckBox *auto_volume_checkbox = nullptr;
209   CheckBox *auto_tune_checkbox = nullptr;
210   Button   *play_button = nullptr;
211   bool      playing = false;
212
213   Sample::Loop
214   text_to_loop (const std::string& text)
215   {
216     for (int i = 0; ; i++)
217       {
218         std::string txt = loop_to_text (Sample::Loop (i));
219
220         if (txt == text)
221           return Sample::Loop (i);
222         if (txt == "")
223           return Sample::Loop (0);
224       }
225   }
226   std::string
227   loop_to_text (const Sample::Loop loop)
228   {
229     switch (loop)
230       {
231         case Sample::Loop::NONE:        return "None";
232         case Sample::Loop::FORWARD:     return "Forward";
233         case Sample::Loop::PING_PONG:   return "Ping Pong";
234         case Sample::Loop::SINGLE_FRAME:return "Single Frame";
235       }
236     return ""; /* not found */
237   }
238 public:
239   void
240   on_add_sample_clicked()
241   {
242     open_file_dialog ("Select Sample to load", "Wav Files", "*.wav", [=](std::string filename) {
243       load_sample (filename);
244     });
245   }
246 // morph plan window size
247   static const int win_width = 744;
248   static const int win_height = 560;
249
250   InstEditWindow (const std::string& test_sample, SynthInterface *synth_interface, Window *parent_window = nullptr) :
251     Window ("SpectMorph - Instrument Editor", win_width, win_height, 0, false, parent_window ? parent_window->native_window() : 0),
252     m_backend (synth_interface),
253     synth_interface (synth_interface)
254   {
255     /* attach to model */
256     connect (instrument.signal_samples_changed, this, &InstEditWindow::on_samples_changed);
257     connect (instrument.signal_marker_changed, this, &InstEditWindow::on_marker_changed);
258     connect (instrument.signal_global_changed, this, &InstEditWindow::on_global_changed);
259
260     /* attach to backend */
261     connect (m_backend.signal_have_audio, this, &InstEditWindow::on_have_audio);
262
263     FixedGrid grid;
264
265     MenuBar *menu_bar = new MenuBar (this);
266
267     fill_zoom_menu (menu_bar->add_menu ("Zoom"));
268     Menu *file_menu = menu_bar->add_menu ("File");
269
270     MenuItem *add_item = file_menu->add_item ("Add Sample...");
271     connect (add_item->signal_clicked, this, &InstEditWindow::on_add_sample_clicked);
272
273     MenuItem *load_item = file_menu->add_item ("Load Instrument...");
274     connect (load_item->signal_clicked, this, &InstEditWindow::on_load_clicked);
275
276     MenuItem *save_item = file_menu->add_item ("Save Instrument...");
277     connect (save_item->signal_clicked, this, &InstEditWindow::on_save_clicked);
278
279     grid.add_widget (menu_bar, 1, 1, 91, 3);
280
281     /*----- sample combobox -----*/
282     sample_combobox = new ComboBox (this);
283     grid.add_widget (sample_combobox, 1, 5, 91, 3);
284
285     connect (sample_combobox->signal_item_changed, this, &InstEditWindow::on_sample_changed);
286
287     Shortcut *sample_up = new Shortcut (this, PUGL_KEY_UP);
288     connect (sample_up->signal_activated, this, &InstEditWindow::on_sample_up);
289
290     Shortcut *sample_down = new Shortcut (this, PUGL_KEY_DOWN);
291     connect (sample_down->signal_activated, this, &InstEditWindow::on_sample_down);
292
293     /*----- sample view -----*/
294     sample_scroll_view = new ScrollView (this);
295     grid.add_widget (sample_scroll_view, 1, 8, 91, 46);
296
297     sample_widget = new SampleWidget (sample_scroll_view);
298
299     grid.add_widget (sample_widget, 1, 1, 100, 42);
300     sample_scroll_view->set_scroll_widget (sample_widget, true, false, /* center_zoom */ true);
301
302     /*----- hzoom -----*/
303     grid.add_widget (new Label (this, "HZoom"), 1, 54, 10, 3);
304     Slider *hzoom_slider = new Slider (this, 0.0);
305     grid.add_widget (hzoom_slider, 8, 54, 30, 3);
306     connect (hzoom_slider->signal_value_changed, this, &InstEditWindow::on_update_hzoom);
307
308     hzoom_label = new Label (this, "0");
309     grid.add_widget (hzoom_label, 40, 54, 10, 3);
310
311     /*----- vzoom -----*/
312     grid.add_widget (new Label (this, "VZoom"), 1, 57, 10, 3);
313     Slider *vzoom_slider = new Slider (this, 0.0);
314     grid.add_widget (vzoom_slider, 8, 57, 30, 3);
315     connect (vzoom_slider->signal_value_changed, this, &InstEditWindow::on_update_vzoom);
316
317     vzoom_label = new Label (this, "0");
318     grid.add_widget (vzoom_label, 40, 57, 10, 3);
319
320     /*---- midi_note ---- */
321     midi_note_combobox = new ComboBox (this);
322     connect (midi_note_combobox->signal_item_changed, this, &InstEditWindow::on_midi_note_changed);
323
324     for (int i = 127; i >= 0; i--)
325       midi_note_combobox->add_item (note_to_text (i));
326
327     grid.add_widget (new Label (this, "Midi Note"), 1, 60, 10, 3);
328     grid.add_widget (midi_note_combobox, 8, 60, 20, 3);
329
330     /*---- loop mode ----*/
331
332     loop_combobox = new ComboBox (this);
333     connect (loop_combobox->signal_item_changed, this, &InstEditWindow::on_loop_changed);
334
335     loop_combobox->add_item (loop_to_text (Sample::Loop::NONE));
336     loop_combobox->set_text (loop_to_text (Sample::Loop::NONE));
337     loop_combobox->add_item (loop_to_text (Sample::Loop::FORWARD));
338     loop_combobox->add_item (loop_to_text (Sample::Loop::PING_PONG));
339     loop_combobox->add_item (loop_to_text (Sample::Loop::SINGLE_FRAME));
340
341     grid.add_widget (new Label (this, "Loop"), 1, 63, 10, 3);
342     grid.add_widget (loop_combobox, 8, 63, 20, 3);
343
344     /*--- play mode ---*/
345     play_mode_combobox = new ComboBox (this);
346     connect (play_mode_combobox->signal_item_changed, this, &InstEditWindow::on_play_mode_changed);
347     grid.add_widget (new Label (this, "Play Mode"), 60, 54, 10, 3);
348     grid.add_widget (play_mode_combobox, 68, 54, 24, 3);
349     play_mode_combobox->add_item ("SpectMorph Instrument"); // default
350     play_mode_combobox->set_text ("SpectMorph Instrument");
351     play_mode_combobox->add_item ("Original Sample");
352     play_mode_combobox->add_item ("Reference Instrument");
353
354     /*--- play button ---*/
355     play_button = new Button (this, "Play");
356     connect (play_button->signal_pressed, this, &InstEditWindow::on_toggle_play);
357     grid.add_widget (play_button, 51, 54, 8, 3);
358
359     Shortcut *play_shortcut = new Shortcut (this, ' ');
360     connect (play_shortcut->signal_activated, this, &InstEditWindow::on_toggle_play);
361
362     /*--- led ---*/
363     led = new Led (this, false);
364     grid.add_widget (new Label (this, "Analyzing"), 60, 67, 10, 3);
365     grid.add_widget (led, 67, 67.5, 2, 2);
366
367     /*--- playing ---*/
368     playing_label = new Label (this, "");
369     grid.add_widget (new Label (this, "Playing"), 70, 67, 10, 3);
370     grid.add_widget (playing_label, 77, 67, 10, 3);
371
372     /* --- timer --- */
373     Timer *timer = new Timer (this);
374     connect (timer->signal_timeout, &m_backend, &InstEditBackend::on_timer);
375     connect (timer->signal_timeout, this, &InstEditWindow::on_update_led);
376     timer->start (0);
377
378     connect (synth_interface->signal_notify_event, [this](SynthNotifyEvent *ne) {
379       auto iev = dynamic_cast<InstEditVoice *> (ne);
380       if (iev)
381         {
382           std::vector<float> play_pointers;
383
384           Sample *sample = instrument.sample (instrument.selected());
385           if (sample && play_mode != PlayMode::REFERENCE)
386             {
387               for (size_t i = 0; i < iev->current_pos.size(); i++)
388                 {
389                   if (fabs (iev->fundamental_note[i] - sample->midi_note()) < 0.1)
390                     play_pointers.push_back (iev->current_pos[i]);
391                 }
392             }
393           sample_widget->set_play_pointers (play_pointers);
394
395           /* this is not 100% accurate if external midi events also affect
396            * the state, but it should be good enough */
397           bool new_playing = iev->note.size() > 0;
398           set_playing (new_playing);
399
400           std::string text = "---";
401           if (iev->note.size() > 0)
402             text = note_to_text (iev->note[0]);
403           playing_label->set_text (text);
404         }
405     });
406
407     instrument.load (test_sample);
408
409     /*--- auto volume ---*/
410     auto_volume_checkbox = new CheckBox (this, "Auto Volume");
411     connect (auto_volume_checkbox->signal_toggled, this, &InstEditWindow::on_auto_volume_changed);
412     grid.add_widget (auto_volume_checkbox, 60, 58.5, 20, 2);
413
414     auto_volume_checkbox->set_checked (instrument.auto_volume().enabled);
415
416     auto_tune_checkbox = new CheckBox (this, "Auto Tune");
417     grid.add_widget (auto_tune_checkbox, 60, 61.5, 20, 2);
418     connect (auto_tune_checkbox->signal_toggled, this, &InstEditWindow::on_auto_tune_changed);
419     auto_tune_checkbox->set_checked (instrument.auto_tune().enabled);
420
421     auto show_params_button = new Button (this, "Show All Parameters...");
422     connect (show_params_button->signal_clicked, [&]() {
423       auto ie_params = new InstEditParams (this, &instrument);
424       connect (ie_params->signal_toggle_play, this, &InstEditWindow::on_toggle_play);
425     });
426     grid.add_widget (show_params_button, 60, 64, 25, 3);
427
428     // show complete wave
429     on_update_hzoom (0);
430
431     on_update_vzoom (0);
432   }
433   void
434   on_update_hzoom (float value)
435   {
436     FixedGrid grid;
437     double factor = pow (2, value * 10);
438     grid.add_widget (sample_widget, 1, 1, 89 * factor, 42);
439     sample_scroll_view->on_widget_size_changed();
440     hzoom_label->set_text (string_printf ("%.1f %%", factor * 100));
441   }
442   void
443   on_update_vzoom (float value)
444   {
445     FixedGrid grid;
446     double factor = pow (10, value);
447     sample_widget->set_vzoom (factor);
448     vzoom_label->set_text (string_printf ("%.1f %%", factor * 100));
449   }
450   void
451   on_save_clicked()
452   {
453     instrument.save ("/tmp/x.sminst");
454   }
455   void
456   on_load_clicked()
457   {
458     instrument.load ("/tmp/x.sminst");
459   }
460   void
461   on_sample_changed()
462   {
463     int idx = sample_combobox->current_index();
464     if (idx >= 0)
465       instrument.set_selected (idx);
466   }
467   void
468   on_sample_up()
469   {
470     int selected = instrument.selected();
471
472     if (selected > 0)
473       instrument.set_selected (selected - 1);
474   }
475   void
476   on_sample_down()
477   {
478     int selected = instrument.selected();
479
480     if (selected >= 0 && size_t (selected + 1) < instrument.size())
481       instrument.set_selected (selected + 1);
482   }
483   void
484   on_midi_note_changed()
485   {
486     Sample *sample = instrument.sample (instrument.selected());
487
488     if (!sample)
489       return;
490     for (int i = 0; i < 128; i++)
491       {
492         if (midi_note_combobox->text() == note_to_text (i))
493           {
494             sample->set_midi_note (i);
495           }
496       }
497   }
498   void
499   on_auto_volume_changed (bool new_value)
500   {
501     Instrument::AutoVolume av = instrument.auto_volume();
502     av.enabled = new_value;
503
504     instrument.set_auto_volume (av);
505   }
506   void
507   on_auto_tune_changed (bool new_value)
508   {
509     Instrument::AutoTune at = instrument.auto_tune();
510     at.enabled = new_value;
511
512     instrument.set_auto_tune (at);
513   }
514   void
515   on_play_mode_changed()
516   {
517     int idx = play_mode_combobox->current_index();
518     if (idx >= 0)
519       {
520         play_mode = static_cast <PlayMode> (idx);
521
522         // this may do a little more than we need, but it updates play_mode
523         // in the backend
524         on_samples_changed();
525       }
526   }
527   void
528   on_loop_changed()
529   {
530     Sample *sample = instrument.sample (instrument.selected());
531
532     sample->set_loop (text_to_loop (loop_combobox->text()));
533     sample_widget->update_loop();
534   }
535   void
536   on_update_led()
537   {
538     led->set_on (m_backend.have_builder());
539   }
540   void
541   on_toggle_play()
542   {
543     Sample *sample = instrument.sample (instrument.selected());
544     if (sample)
545       synth_interface->synth_inst_edit_note (sample->midi_note(), !playing);
546   }
547   void
548   set_playing (bool new_playing)
549   {
550     if (playing == new_playing)
551       return;
552
553     playing = new_playing;
554     play_button->set_text (playing ? "Stop" : "Play");
555   }
556   void
557   on_have_audio (int note, Audio *audio)
558   {
559     if (!audio)
560       return;
561
562     for (size_t i = 0; i < instrument.size(); i++)
563       {
564         Sample *sample = instrument.sample (i);
565
566         if (sample->midi_note() == note)
567           sample->audio.reset (audio->clone());
568       }
569     sample_widget->update();
570   }
571 };
572
573 inline void
574 InstEditBackend::on_timer()
575 {
576   for (auto ev : synth_interface->notify_take_events())
577     {
578       SynthNotifyEvent *sn_event = SynthNotifyEvent::create (ev);
579       if (sn_event)
580         {
581           synth_interface->signal_notify_event (sn_event);
582           delete sn_event;
583         }
584     }
585
586   std::lock_guard<std::mutex> lg (result_mutex);
587   if (result_wav_set)
588     {
589       printf ("got result!\n");
590       result_wav_set->save (smset_file());
591       for (const auto& wave : result_wav_set->waves)
592         signal_have_audio (wave.midi_note, wave.audio);
593
594       if (result_play_mode == PlayMode::SPECTMORPH)
595         {
596           synth_interface->synth_inst_edit_update (true, smset_file(), false);
597         }
598       else if (result_play_mode == PlayMode::SAMPLE)
599         {
600           synth_interface->synth_inst_edit_update (true, smset_file(), true);
601         }
602       else
603         {
604           Index index;
605           index.load_file ("instruments:standard");
606
607           std::string smset_dir = index.smset_dir();
608           synth_interface->synth_inst_edit_update (true, smset_dir + "/synth-saw.smset", false);
609         }
610
611       // delete
612       result_wav_set.reset();
613     }
614 }
615
616 inline void
617 InstEditBackend::switch_to_sample (const Sample *sample, PlayMode play_mode, const Instrument *instrument)
618 {
619   WavSetBuilder *builder = new WavSetBuilder (instrument, /* keep_samples */ play_mode == PlayMode::SAMPLE);
620
621   add_builder (builder, play_mode);
622 }
623
624 }
625
626 #endif