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