1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2 
3 #include "sminsteditwindow.hh"
4 #include "sminsteditparams.hh"
5 
6 #include "smprogressbar.hh"
7 #include "smmessagebox.hh"
8 #include "smsamplewidget.hh"
9 #include "smtimer.hh"
10 #include "smmenubar.hh"
11 #include "smslider.hh"
12 #include "smsimplelines.hh"
13 #include "smwavsetbuilder.hh"
14 #include "smsynthinterface.hh"
15 #include "smscrollview.hh"
16 #include "sminstenccache.hh"
17 #include "smzip.hh"
18 
19 using namespace SpectMorph;
20 
21 using std::string;
22 using std::vector;
23 
24 namespace SpectMorph
25 {
26 
27 class IconButton : public Button
28 {
29 public:
30   enum Icon { PLAY, STOP } m_icon;
31 
IconButton(Widget * parent,Icon icon)32   IconButton (Widget *parent, Icon icon) :
33     Button (parent, ""),
34     m_icon (icon)
35   {
36   }
37   void
set_icon(Icon icon)38   set_icon (Icon icon)
39   {
40     if (icon != m_icon)
41       {
42         m_icon = icon;
43         update();
44       }
45   }
46   void
draw(const DrawEvent & devent)47   draw (const DrawEvent& devent) override
48   {
49     Button::draw (devent);
50 
51     cairo_t *cr = devent.cr;
52     DrawUtils du (cr);
53 
54     double space = 4;
55     double size = std::min (width() * 0.55, height() * 0.55) - 2 * space;
56 
57     if (m_icon == PLAY)
58       {
59         const double left = width() / 2 - size / 2 + size * 0.1;
60         cairo_move_to (cr, left, height() / 2 - size / 2);
61         cairo_line_to (cr, left, height() / 2 + size / 2);
62         cairo_line_to (cr, left + size * 0.8, height() / 2);
63         //cairo_line_to (cr, width / 2 - size / 2 + size, height / 2);
64 
65         cairo_close_path (cr);
66         cairo_stroke_preserve (cr);
67         cairo_fill (cr);
68       }
69     else if (m_icon == STOP)
70       {
71         du.rect_fill (width() / 2 - size / 2, height() / 2 - size / 2, size, size, Color (1, 1, 1));
72       }
73   }
74 };
75 
76 }
77 
78 // ---------------- InstEditBackend ----------------
79 //
InstEditBackend(SynthInterface * synth_interface)80 InstEditBackend::InstEditBackend (SynthInterface *synth_interface) :
81   synth_interface (synth_interface),
82   cache_group (InstEncCache::the()->create_group())
83 {
84 }
85 
86 void
switch_to_sample(const Sample * sample,const Instrument * instrument)87 InstEditBackend::switch_to_sample (const Sample *sample, const Instrument *instrument)
88 {
89   WavSetBuilder *builder = new WavSetBuilder (instrument, true);
90   builder->set_cache_group (cache_group.get());
91 
92   builder_thread.kill_all_jobs();
93 
94   std::lock_guard<std::mutex> lg (result_mutex);
95   result_updated = true;
96   result_wav_set.reset (nullptr);
97 
98   builder_thread.add_job (builder, /* unused: object_id */ 0,
99     [this] (WavSet *wav_set)
100       {
101         std::lock_guard<std::mutex> lg (result_mutex);
102         result_updated = true;
103         result_wav_set.reset (wav_set);
104       }
105     );
106 }
107 
108 bool
have_builder()109 InstEditBackend::have_builder()
110 {
111   return builder_thread.job_count() > 0;
112 }
113 
114 void
on_timer()115 InstEditBackend::on_timer()
116 {
117   /* FIXME: event handling should probably not be done here */
118   for (auto ev : synth_interface->get_project()->notify_take_events())
119     {
120       SynthNotifyEvent *sn_event = SynthNotifyEvent::create (ev);
121       if (sn_event)
122         {
123           synth_interface->signal_notify_event (sn_event);
124           delete sn_event;
125         }
126     }
127 
128   std::lock_guard<std::mutex> lg (result_mutex);
129   if (result_updated)
130     {
131       result_updated = false;
132       if (result_wav_set)
133         {
134           for (const auto& wave : result_wav_set->waves)
135             signal_have_audio (wave.midi_note, wave.audio);
136         }
137 
138       Index index;
139       index.load_file ("instruments:standard");
140 
141       WavSet *ref_wav_set = new WavSet();
142       ref_wav_set->load (index.smset_dir() + "/synth-saw.smset");
143 
144       synth_interface->synth_inst_edit_update (true, result_wav_set.release(), ref_wav_set);
145     }
146 }
147 
148 // ---------------- InstEditWindow ----------------
149 //
InstEditWindow(EventLoop & event_loop,Instrument * edit_instrument,SynthInterface * synth_interface,Window * parent_window)150 InstEditWindow::InstEditWindow (EventLoop& event_loop, Instrument *edit_instrument, SynthInterface *synth_interface, Window *parent_window) :
151   Window (event_loop, "SpectMorph - Instrument Editor", win_width, win_height, 0, false, parent_window ? parent_window->native_window() : 0),
152   m_backend (synth_interface),
153   synth_interface (synth_interface)
154 {
155   assert (edit_instrument != nullptr);
156   instrument = edit_instrument;
157 
158   /* make a backup to be able to revert */
159   ZipWriter writer;
160   instrument->save (writer);
161   revert_instrument_data = writer.data();
162 
163   /* attach to model */
164   connect (instrument->signal_samples_changed, this, &InstEditWindow::on_samples_changed);
165   connect (instrument->signal_marker_changed, this, &InstEditWindow::on_marker_changed);
166   connect (instrument->signal_global_changed, this, &InstEditWindow::on_global_changed);
167 
168   /* attach to backend */
169   connect (m_backend.signal_have_audio, this, &InstEditWindow::on_have_audio);
170 
171   FixedGrid grid;
172 
173   MenuBar *menu_bar = new MenuBar (this);
174 
175   fill_zoom_menu (menu_bar->add_menu ("Zoom"));
176   Menu *file_menu = menu_bar->add_menu ("File");
177 
178   MenuItem *add_item = file_menu->add_item ("Add Sample...");
179   connect (add_item->signal_clicked, this, &InstEditWindow::on_add_sample_clicked);
180 
181   MenuItem *import_item = file_menu->add_item ("Import Instrument...");
182   connect (import_item->signal_clicked, this, &InstEditWindow::on_import_clicked);
183 
184   MenuItem *export_item = file_menu->add_item ("Export Instrument...");
185   connect (export_item->signal_clicked, this, &InstEditWindow::on_export_clicked);
186 
187   MenuItem *clear_item = file_menu->add_item ("Clear Instrument");
188   connect (clear_item->signal_clicked, this, &InstEditWindow::on_clear);
189 
190   MenuItem *revert_item = file_menu->add_item ("Revert Instrument");
191   connect (revert_item->signal_clicked, this, &InstEditWindow::on_revert);
192 
193   grid.add_widget (menu_bar, 1, 1, 91, 3);
194 
195   /*----- sample combobox -----*/
196   sample_combobox = new ComboBox (this);
197   grid.add_widget (sample_combobox, 1, 5, 72, 3);
198 
199   connect (sample_combobox->signal_item_changed, this, &InstEditWindow::on_sample_changed);
200 
201   Shortcut *sample_up = new Shortcut (this, PUGL_KEY_UP);
202   connect (sample_up->signal_activated, this, &InstEditWindow::on_sample_up);
203 
204   Shortcut *sample_down = new Shortcut (this, PUGL_KEY_DOWN);
205   connect (sample_down->signal_activated, this, &InstEditWindow::on_sample_down);
206 
207   /*----- add/remove ----- */
208   add_sample_button = new Button (this, "Add...");
209   grid.add_widget (add_sample_button, 74, 5, 8, 3);
210   connect (add_sample_button->signal_clicked, this, &InstEditWindow::on_add_sample_clicked);
211 
212   remove_sample_button = new Button (this, "Remove");
213   grid.add_widget (remove_sample_button, 83, 5, 9, 3);
214   connect (remove_sample_button->signal_clicked, this, &InstEditWindow::on_remove_sample_clicked);
215 
216   /*----- sample view -----*/
217   sample_scroll_view = new ScrollView (this);
218   grid.add_widget (sample_scroll_view, 1, 8, 91, 46);
219 
220   sample_widget = new SampleWidget (sample_scroll_view);
221 
222   grid.add_widget (sample_widget, 1, 1, 100, 42);
223   sample_scroll_view->set_scroll_widget (sample_widget, true, false, /* center_zoom */ true);
224 
225   /*----- hzoom -----*/
226   grid.add_widget (new Label (this, "HZoom"), 1, 54, 6, 3);
227   Slider *hzoom_slider = new Slider (this, 0.0);
228   grid.add_widget (hzoom_slider, 7, 54, 28, 3);
229   connect (hzoom_slider->signal_value_changed, this, &InstEditWindow::on_update_hzoom);
230 
231   hzoom_label = new Label (this, "0");
232   grid.add_widget (hzoom_label, 36, 54, 10, 3);
233 
234   /*----- vzoom -----*/
235   grid.add_widget (new Label (this, "VZoom"), 51, 54, 6, 3);
236   Slider *vzoom_slider = new Slider (this, 0.0);
237   grid.add_widget (vzoom_slider, 57, 54, 28, 3);
238   connect (vzoom_slider->signal_value_changed, this, &InstEditWindow::on_update_vzoom);
239 
240   vzoom_label = new Label (this, "0");
241   grid.add_widget (vzoom_label, 86, 54, 10, 3);
242 
243   /******************* INSTRUMENT *********************/
244   grid.dx = 1;
245   grid.dy = 60.5;
246 
247   /*---- Instrument name ----*/
248 
249   name_line_edit = new LineEdit (this, "untitled");
250   name_line_edit->set_click_to_focus (true);
251   connect (name_line_edit->signal_text_changed, [this] (const string& name) { instrument->set_name (name); });
252 
253   grid.add_widget (new Label (this, "Name"), 0, 0, 6, 3);
254   grid.add_widget (name_line_edit, 6, 0, 21, 3);
255 
256   /*--- Instrument: auto volume ---*/
257   auto_volume_checkbox = new CheckBox (this, "Auto Volume");
258   connect (auto_volume_checkbox->signal_toggled, this, &InstEditWindow::on_auto_volume_changed);
259   grid.add_widget (auto_volume_checkbox, 0, 3.5, 21, 2);
260 
261   /*--- Instrument: auto tune ---*/
262   auto_tune_checkbox = new CheckBox (this, "Auto Tune");
263   grid.add_widget (auto_tune_checkbox, 0, 6.5, 21, 2);
264   connect (auto_tune_checkbox->signal_toggled, this, &InstEditWindow::on_auto_tune_changed);
265 
266   /*--- Instrument: edit ---*/
267   show_params_button = new Button (this, "Edit");
268   connect (show_params_button->signal_clicked, this, &InstEditWindow::on_show_hide_params);
269   grid.add_widget (show_params_button, 21, 4, 6, 3);
270 
271   /******************* SAMPLE *********************/
272   grid.dx = 31;
273   grid.dy = 60.5;
274 
275   /*---- Sample: midi_note ---- */
276   midi_note_combobox = new ComboBox (this);
277   connect (midi_note_combobox->signal_item_changed, this, &InstEditWindow::on_midi_note_changed);
278 
279   for (int i = 127; i >= 0; i--)
280     midi_note_combobox->add_item (note_to_text (i));
281 
282   grid.add_widget (new Label (this, "Midi Note"), 0, 0, 7, 3);
283   grid.add_widget (midi_note_combobox, 8, 0, 12, 3);
284 
285   /*--- Sample: edit ---*/
286   show_pitch_button = new Button (this, "Edit");
287   connect (show_pitch_button->signal_clicked, this, &InstEditWindow::on_show_hide_note);
288   grid.add_widget (show_pitch_button, 21, 0, 7, 3);
289 
290   /*---- Sample: loop mode ----*/
291 
292   loop_combobox = new ComboBox (this);
293   connect (loop_combobox->signal_item_changed, this, &InstEditWindow::on_loop_changed);
294 
295   loop_combobox->add_item (loop_to_text (Sample::Loop::NONE));
296   loop_combobox->set_text (loop_to_text (Sample::Loop::NONE));
297   loop_combobox->add_item (loop_to_text (Sample::Loop::FORWARD));
298   loop_combobox->add_item (loop_to_text (Sample::Loop::PING_PONG));
299   loop_combobox->add_item (loop_to_text (Sample::Loop::SINGLE_FRAME));
300 
301   grid.add_widget (new Label (this, "Loop"), 0, 3, 7, 3);
302   grid.add_widget (loop_combobox, 8, 3, 20, 3);
303 
304   /*--- Sample: time --- */
305   time_label = new Label (this, "");
306 
307   grid.add_widget (new Label (this, "Time"), 0, 6, 10, 3);
308   grid.add_widget (time_label, 8, 6, 10, 3);
309 
310   /******************* PLAYBACK *********************/
311   grid.dx = 62;
312   grid.dy = 60.5;
313 
314   /*--- Playback: play mode ---*/
315   play_mode_combobox = new ComboBox (this);
316   connect (play_mode_combobox->signal_item_changed, this, &InstEditWindow::on_play_mode_changed);
317   //grid.add_widget (new Label (this, "Play Mode"), 60, 60, 10, 3);
318   grid.add_widget (play_mode_combobox, 7.5, 0, 22.5, 3);
319   play_mode_combobox->add_item ("SpectMorph Instrument"); // default
320   play_mode_combobox->set_text ("SpectMorph Instrument");
321   play_mode_combobox->add_item ("Original Sample");
322   play_mode_combobox->add_item ("Reference Instrument");
323 
324   /*--- Playback: play button ---*/
325   play_button = new IconButton (this, IconButton::PLAY);
326   connect (play_button->signal_pressed, this, &InstEditWindow::on_toggle_play);
327   grid.add_widget (play_button, 0, 0, 6, 6);
328 
329   Shortcut *play_shortcut = new Shortcut (this, ' ');
330   connect (play_shortcut->signal_activated, this, &InstEditWindow::on_toggle_play);
331 
332   /*--- Playback: playing ---*/
333   playing_label = new Label (this, "");
334   grid.add_widget (new Label (this, "Playing"), 7.5, 3, 10, 3);
335   grid.add_widget (playing_label, 15, 3, 10, 3);
336 
337   /*--- Playback: progress ---*/
338   progress_bar = new ProgressBar (this);
339   progress_label = new Label (this, "Analyzing");
340   grid.add_widget (progress_label, 0, 6, 10, 3);
341   grid.add_widget (progress_bar, 7.5, 6.25, 22.5, 2.5);
342 
343   /* --- Playback: timer --- */
344   Timer *timer = new Timer (this);
345   connect (timer->signal_timeout, &m_backend, &InstEditBackend::on_timer);
346   connect (timer->signal_timeout, this, &InstEditWindow::on_update_led);
347   timer->start (0);
348 
349   connect (synth_interface->signal_notify_event, [this](SynthNotifyEvent *ne) {
350     auto iev = dynamic_cast<InstEditVoice *> (ne);
351     if (iev)
352       {
353         vector<float> play_pointers;
354 
355         Sample *sample = instrument->sample (instrument->selected());
356         if (sample)
357           {
358             for (size_t i = 0; i < iev->current_pos.size(); i++)
359               {
360                 if (fabs (iev->fundamental_note[i] - sample->midi_note()) < 0.1 &&
361                     iev->layer[i] < 2) /* no play position pointer for reference */
362                   {
363                     double ppos = iev->current_pos[i];
364                     if (iev->layer[i] == 0)
365                       {
366                         const double clip_start_ms = sample->get_marker (MARKER_CLIP_START);
367                         if (clip_start_ms > 0)
368                           ppos += clip_start_ms;
369                       }
370                     play_pointers.push_back (ppos);
371                   }
372               }
373           }
374         sample_widget->set_play_pointers (play_pointers);
375 
376         /* this is not 100% accurate if external midi events also affect
377          * the state, but it should be good enough */
378         bool new_playing = iev->note.size() > 0;
379         set_playing (new_playing);
380 
381         string text = "---";
382         if (iev->note.size() > 0)
383           text = note_to_text (iev->note[0]);
384         playing_label->set_text (text);
385         if (inst_edit_note)
386           inst_edit_note->set_active_notes (iev->note);
387       }
388   });
389 
390   // use global coordinates again
391   grid.dx = 0;
392   grid.dy = 0;
393 
394   const double vline1 = 29 + 0.5;
395   const double vline2 = 60 + 0.5;
396   const double vline3 = 93;
397 
398   // need to be below other widgets
399   auto hdr_bg = new Widget (this);
400   hdr_bg->set_background_color (Color (0.3, 0.3, 0.3));
401   grid.add_widget (hdr_bg, 0, 57.25, 93, 2.5);
402 
403   auto hdr_inst = new Label (this, "Instrument");
404   hdr_inst->set_align (TextAlign::CENTER);
405   hdr_inst->set_bold (true);
406   grid.add_widget (hdr_inst, 0, 57, vline1, 3);
407 
408   auto hdr_sample = new Label (this, "Sample");
409   hdr_sample->set_align (TextAlign::CENTER);
410   hdr_sample->set_bold (true);
411   grid.add_widget (hdr_sample, vline1, 57, vline2 - vline1, 3);
412 
413   auto hdr_play = new Label (this, "Playback");
414   hdr_play->set_align (TextAlign::CENTER);
415   hdr_play->set_bold (true);
416   grid.add_widget (hdr_play, vline2, 57, vline3 - vline2, 3);
417 
418   grid.add_widget (new VLine (this, Color (0.8, 0.8, 0.8), 2), vline1 - 0.5, 57.25, 1, 12.75);
419   grid.add_widget (new VLine (this, Color (0.8, 0.8, 0.8), 2), vline2 - 0.5, 57.25, 1, 12.75);
420 
421   on_samples_changed();
422   on_global_changed();
423 
424   // show complete wave
425   on_update_hzoom (0);
426 
427   on_update_vzoom (0);
428 }
429 
~InstEditWindow()430 InstEditWindow::~InstEditWindow()
431 {
432   if (inst_edit_params)
433     {
434       delete inst_edit_params;
435       inst_edit_params = nullptr;
436     }
437   if (inst_edit_note)
438     {
439       delete inst_edit_note;
440       inst_edit_note = nullptr;
441     }
442 }
443 
444 string
note_to_text(int i)445 InstEditWindow::note_to_text (int i)
446 {
447   vector<string> note_name { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
448   return string_printf ("%d  :  %s%d", i, note_name[i % 12].c_str(), i / 12 - 2);
449 }
450 
451 void
load_sample(const string & filename)452 InstEditWindow::load_sample (const string& filename)
453 {
454   if (filename != "")
455     {
456       Error error = instrument->add_sample (filename, nullptr);
457       if (error)
458         {
459           MessageBox::critical (this, "Error",
460                                 string_locale_printf ("Loading sample failed:\n'%s'\n%s.", filename.c_str(), error.message()));
461         }
462     }
463 }
464 
465 void
on_samples_changed()466 InstEditWindow::on_samples_changed()
467 {
468   sample_combobox->clear();
469   if (instrument->size() == 0)
470     {
471       sample_combobox->set_text ("");
472     }
473   auto used_count = instrument->used_count();
474   for (size_t i = 0; i < instrument->size(); i++)
475     {
476       Sample *sample = instrument->sample (i);
477       string text = string_printf ("%s  :  %s", note_to_text (sample->midi_note()).c_str(), sample->short_name.c_str());
478 
479       int c = used_count[sample->midi_note()];
480       if (c > 1)
481         text += string_printf ("  :  ** error: midi note used %d times **", c);
482 
483       sample_combobox->add_item (text);
484 
485       if (int (i) == instrument->selected())
486         sample_combobox->set_text (text);
487     }
488   Sample *sample = instrument->sample (instrument->selected());
489   sample_widget->set_sample (sample);
490   midi_note_combobox->set_enabled (sample != nullptr);
491   sample_combobox->set_enabled (sample != nullptr);
492   play_mode_combobox->set_enabled (sample != nullptr);
493   loop_combobox->set_enabled (sample != nullptr);
494   if (!sample)
495     {
496       midi_note_combobox->set_text ("");
497       loop_combobox->set_text ("");
498       time_label->set_text ("---");
499     }
500   else
501     {
502       midi_note_combobox->set_text (note_to_text (sample->midi_note()));
503       loop_combobox->set_text (loop_to_text (sample->loop()));
504 
505       const double time_s = sample->wav_data().samples().size() / sample->wav_data().mix_freq();
506       time_label->set_text (string_printf ("%.3f s", time_s));
507     }
508   if (sample)
509     m_backend.switch_to_sample (sample, instrument);
510 }
511 
512 void
on_marker_changed()513 InstEditWindow::on_marker_changed()
514 {
515   Sample *sample = instrument->sample (instrument->selected());
516 
517   if (sample)
518     m_backend.switch_to_sample (sample, instrument);
519 }
520 
521 void
update_auto_checkboxes()522 InstEditWindow::update_auto_checkboxes()
523 {
524   /* update auto volume checkbox */
525   const auto auto_volume = instrument->auto_volume();
526 
527   auto_volume_checkbox->set_checked (auto_volume.enabled);
528 
529   string av_text = "Auto Volume";
530   if (auto_volume.enabled)
531     {
532       switch (auto_volume.method)
533       {
534         case Instrument::AutoVolume::FROM_LOOP: av_text += " - From Loop";
535                                                 break;
536         case Instrument::AutoVolume::GLOBAL:    av_text += " - Global";
537                                                 break;
538       }
539     }
540   auto_volume_checkbox->set_text (av_text);
541 
542   /* update auto tune checkbox */
543   const auto auto_tune = instrument->auto_tune();
544 
545   auto_tune_checkbox->set_checked (auto_tune.enabled);
546   string at_text = "Auto Tune";
547   if (auto_tune.enabled)
548     {
549       switch (auto_tune.method)
550       {
551         case Instrument::AutoTune::SIMPLE:      at_text += " - Simple";
552                                                 break;
553         case Instrument::AutoTune::ALL_FRAMES:  at_text += " - All Frames";
554                                                 break;
555         case Instrument::AutoTune::SMOOTH:      at_text += " - Smooth";
556                                                 break;
557       }
558     }
559   auto_tune_checkbox->set_text (at_text);
560 }
561 
562 void
on_global_changed()563 InstEditWindow::on_global_changed()
564 {
565   update_auto_checkboxes();
566 
567   name_line_edit->set_text (instrument->name());
568 
569   Sample *sample = instrument->sample (instrument->selected());
570 
571   if (sample)
572     m_backend.switch_to_sample (sample, instrument);
573 }
574 
575 Sample::Loop
text_to_loop(const string & text)576 InstEditWindow::text_to_loop (const string& text)
577 {
578   for (int i = 0; ; i++)
579     {
580       string txt = loop_to_text (Sample::Loop (i));
581 
582       if (txt == text)
583         return Sample::Loop (i);
584       if (txt == "")
585         return Sample::Loop (0);
586     }
587 }
588 
589 string
loop_to_text(const Sample::Loop loop)590 InstEditWindow::loop_to_text (const Sample::Loop loop)
591 {
592   switch (loop)
593     {
594       case Sample::Loop::NONE:        return "None";
595       case Sample::Loop::FORWARD:     return "Forward";
596       case Sample::Loop::PING_PONG:   return "Ping Pong";
597       case Sample::Loop::SINGLE_FRAME:return "Single Frame";
598     }
599   return ""; /* not found */
600 }
601 
602 void
on_clear()603 InstEditWindow::on_clear()
604 {
605   instrument->clear();
606 }
607 
608 void
on_revert()609 InstEditWindow::on_revert()
610 {
611   ZipReader reader (revert_instrument_data);
612   instrument->load (reader);
613 }
614 
615 void
on_add_sample_clicked()616 InstEditWindow::on_add_sample_clicked()
617 {
618   FileDialogFormats formats;
619   formats.add ("Supported Audio Files", { "wav", "flac", "ogg", "aiff" });
620   formats.add ("All Files", { "*" });
621   open_file_dialog ("Select Sample to load", formats, [=](string filename) {
622     load_sample (filename);
623   });
624 }
625 
626 void
on_remove_sample_clicked()627 InstEditWindow::on_remove_sample_clicked()
628 {
629   instrument->remove_sample();
630 }
631 
632 void
on_update_hzoom(float value)633 InstEditWindow::on_update_hzoom (float value)
634 {
635   FixedGrid grid;
636   double factor = pow (2, value * 10);
637   grid.add_widget (sample_widget, 1, 1, 89 * factor, 42);
638   sample_scroll_view->on_widget_size_changed();
639   hzoom_label->set_text (string_printf ("%.1f %%", factor * 100));
640 }
641 
642 void
on_update_vzoom(float value)643 InstEditWindow::on_update_vzoom (float value)
644 {
645   FixedGrid grid;
646   double factor = pow (10, value);
647   sample_widget->set_vzoom (factor);
648   vzoom_label->set_text (string_printf ("%.1f %%", factor * 100));
649 }
650 
651 void
on_show_hide_params()652 InstEditWindow::on_show_hide_params()
653 {
654   if (inst_edit_params)
655     {
656       inst_edit_params->delete_later();
657       inst_edit_params = nullptr;
658     }
659   else
660     {
661       inst_edit_params = new InstEditParams (this, instrument, sample_widget);
662       connect (inst_edit_params->signal_toggle_play, this, &InstEditWindow::on_toggle_play);
663       connect (inst_edit_params->signal_closed, [this]() {
664         inst_edit_params = nullptr;
665       });
666     }
667 }
668 
669 void
on_show_hide_note()670 InstEditWindow::on_show_hide_note()
671 {
672   if (inst_edit_note)
673     {
674       inst_edit_note->delete_later();
675       inst_edit_note = nullptr;
676     }
677   else
678     {
679       inst_edit_note = new InstEditNote (this, instrument, synth_interface);
680       connect (inst_edit_note->signal_toggle_play, this, &InstEditWindow::on_toggle_play);
681       connect (inst_edit_note->signal_closed, [this]() {
682         inst_edit_note = nullptr;
683       });
684     }
685 }
686 
687 void
on_export_clicked()688 InstEditWindow::on_export_clicked()
689 {
690   FileDialogFormats formats ("SpectMorph Instrument files", "sminst");
691   save_file_dialog ("Select SpectMorph Instrument export filename", formats, [=](string filename) {
692     if (filename != "")
693       {
694         ZipWriter zip_writer (filename);
695 
696         Error error = instrument->save (zip_writer);
697         if (error)
698           {
699             MessageBox::critical (this, "Error",
700                                   string_locale_printf ("Exporting instrument failed:\n'%s'\n%s.", filename.c_str(), error.message()));
701           }
702       }
703   });
704 }
705 
706 void
on_import_clicked()707 InstEditWindow::on_import_clicked()
708 {
709   FileDialogFormats formats ("SpectMorph Instrument files", "sminst");
710   window()->open_file_dialog ("Select SpectMorph Instrument to import", formats, [=](string filename) {
711     if (filename != "")
712       {
713         Error error = instrument->load (filename);
714         if (error)
715           {
716             MessageBox::critical (this, "Error",
717                                   string_locale_printf ("Importing instrument failed:\n'%s'\n%s.", filename.c_str(), error.message()));
718           }
719       }
720   });
721 }
722 
723 void
on_sample_changed()724 InstEditWindow::on_sample_changed()
725 {
726   int idx = sample_combobox->current_index();
727   if (idx >= 0)
728     instrument->set_selected (idx);
729 }
730 
731 void
on_sample_up()732 InstEditWindow::on_sample_up()
733 {
734   int selected = instrument->selected();
735 
736   if (selected > 0)
737     instrument->set_selected (selected - 1);
738 }
739 
740 void
on_sample_down()741 InstEditWindow::on_sample_down()
742 {
743   int selected = instrument->selected();
744 
745   if (selected >= 0 && size_t (selected + 1) < instrument->size())
746     instrument->set_selected (selected + 1);
747 }
748 
749 void
on_midi_note_changed()750 InstEditWindow::on_midi_note_changed()
751 {
752   Sample *sample = instrument->sample (instrument->selected());
753 
754   if (!sample)
755     return;
756   for (int i = 0; i < 128; i++)
757     {
758       if (midi_note_combobox->text() == note_to_text (i))
759         {
760           sample->set_midi_note (i);
761         }
762     }
763 }
764 
765 void
on_auto_volume_changed(bool new_value)766 InstEditWindow::on_auto_volume_changed (bool new_value)
767 {
768   Instrument::AutoVolume av = instrument->auto_volume();
769   av.enabled = new_value;
770 
771   instrument->set_auto_volume (av);
772 }
773 
774 void
on_auto_tune_changed(bool new_value)775 InstEditWindow::on_auto_tune_changed (bool new_value)
776 {
777   Instrument::AutoTune at = instrument->auto_tune();
778   at.enabled = new_value;
779 
780   instrument->set_auto_tune (at);
781 }
782 
783 void
on_play_mode_changed()784 InstEditWindow::on_play_mode_changed()
785 {
786   int idx = play_mode_combobox->current_index();
787   if (idx >= 0)
788     {
789       play_mode = static_cast <PlayMode> (idx);
790 
791       // this may do a little more than we need, but it updates play_mode
792       // in the backend
793       on_samples_changed();
794     }
795 }
796 
797 void
on_loop_changed()798 InstEditWindow::on_loop_changed()
799 {
800   Sample *sample = instrument->sample (instrument->selected());
801 
802   sample->set_loop (text_to_loop (loop_combobox->text()));
803   sample_widget->update_loop();
804 }
805 
806 void
on_update_led()807 InstEditWindow::on_update_led()
808 {
809   if (m_backend.have_builder())
810     {
811       progress_label->set_text ("Analyzing");
812       progress_bar->set_value (-1.0);
813     }
814   else
815     {
816       progress_label->set_text ("Ready.");
817       progress_bar->set_value (1.0);
818     }
819 }
820 
821 void
on_toggle_play()822 InstEditWindow::on_toggle_play()
823 {
824   Sample *sample = instrument->sample (instrument->selected());
825   if (sample)
826     {
827       uint layer = 0;
828       if (play_mode == PlayMode::SAMPLE)
829         layer = 1;
830       if (play_mode == PlayMode::REFERENCE)
831         layer = 2;
832 
833       synth_interface->synth_inst_edit_note (sample->midi_note(), !playing, layer);
834     }
835 }
836 
837 void
set_playing(bool new_playing)838 InstEditWindow::set_playing (bool new_playing)
839 {
840   if (playing == new_playing)
841     return;
842 
843   playing = new_playing;
844   play_button->set_icon (playing ? IconButton::STOP : IconButton::PLAY);
845 }
846 
847 void
on_have_audio(int note,Audio * audio)848 InstEditWindow::on_have_audio (int note, Audio *audio)
849 {
850   if (!audio)
851     return;
852 
853   for (size_t i = 0; i < instrument->size(); i++)
854     {
855       Sample *sample = instrument->sample (i);
856 
857       if (sample->midi_note() == note)
858         sample->audio.reset (audio->clone());
859     }
860   sample_widget->update();
861 }
862