1 /*
2 ** Surge Synthesizer is Free and Open Source Software
3 **
4 ** Surge is made available under the Gnu General Public License, v3.0
5 ** https://www.gnu.org/licenses/gpl-3.0.en.html
6 **
7 ** Copyright 2004-2020 by various individuals as described by the Git transaction log
8 **
9 ** All source at: https://github.com/surge-synthesizer/surge.git
10 **
11 ** Surge was a commercial product from 2004-2018, with Copyright and ownership
12 ** in that period held by Claes Johanson at Vember Audio. Claes made Surge
13 ** open source in September 2018.
14 */
15
16 #include "SurgeGUIEditor.h"
17 #include "resource.h"
18 #include "CSurgeSlider.h"
19 #include "CHSwitch2.h"
20 #include "CSwitchControl.h"
21 #include "CParameterTooltip.h"
22 #include "CPatchBrowser.h"
23 #include "COscillatorDisplay.h"
24 #include "CVerticalLabel.h"
25 #include "CModulationSourceButton.h"
26 #include "CSnapshotMenu.h"
27 #include "CLFOGui.h"
28 #include "CEffectSettings.h"
29 #include "CSurgeVuMeter.h"
30 #include "CMenuAsSlider.h"
31 #include "CEffectLabel.h"
32 #include "CTextButtonWithHover.h"
33 #include "CAboutBox.h"
34 #include "vstcontrols.h"
35 #include "SurgeBitmaps.h"
36 #include "CScalableBitmap.h"
37 #include "CNumberField.h"
38 #include "UserInteractions.h"
39 #include "DisplayInfo.h"
40 #include "UserDefaults.h"
41 #include "SkinSupport.h"
42 #include "SkinColors.h"
43 #include "UIInstrumentation.h"
44 #include "guihelpers.h"
45 #include "DebugHelpers.h"
46 #include "StringOps.h"
47 #include "ModulatorPresetManager.h"
48
49 #include <iostream>
50 #include <iomanip>
51 #include <sstream>
52 #include <stack>
53 #include <numeric>
54 #include <unordered_map>
55 #include <codecvt>
56 #include "MSEGEditor.h"
57 #include "version.h"
58 #include "CMidiLearnOverlay.h"
59 #include "ModernOscillator.h"
60 #include "libMTSClient.h"
61
62 #if TARGET_VST3
63 #include "pluginterfaces/vst/ivstcontextmenu.h"
64 #include "pluginterfaces/base/ustring.h"
65
66 #include "vstgui/lib/cvstguitimer.h"
67
68 #include "SurgeVst3Processor.h"
69
70 template <typename T> struct RememberForgetGuard
71 {
RememberForgetGuardRememberForgetGuard72 RememberForgetGuard(T *tg)
73 {
74 t = tg;
75
76 int rc = -1;
77 if (t)
78 rc = t->addRef();
79 }
RememberForgetGuardRememberForgetGuard80 RememberForgetGuard(const RememberForgetGuard &other)
81 {
82 int rc = -1;
83 if (t)
84 {
85 rc = t->release();
86 }
87 t = other.t;
88 if (t)
89 {
90 rc = t->addRef();
91 }
92 }
~RememberForgetGuardRememberForgetGuard93 ~RememberForgetGuard()
94 {
95 if (t)
96 {
97 t->release();
98 }
99 }
100 T *t = nullptr;
101 };
102
103 DEF_CLASS_IID(IPlugViewContentScaleSupport);
104
105 #endif
106
107 #if TARGET_AUDIOUNIT
108 #include "aulayer.h"
109 #endif
110
111 #include "filesystem/import.h"
112
113 #if LINUX && !TARGET_JUCE_UI
114 #include "vstgui/lib/platform/platform_x11.h"
115 #include "vstgui/lib/platform/linux/x11platform.h"
116 #endif
117
118 #if LINUX && TARGET_LV2
119 namespace SurgeLv2
120 {
121 VSTGUI::SharedPointer<VSTGUI::X11::IRunLoop> createRunLoop(void *ui);
122 }
123 #endif
124
125 #include "RuntimeFont.h"
126
127 const int yofs = 10;
128
129 using namespace VSTGUI;
130 using namespace std;
131
132 #if LINUX && TARGET_VST3
133 extern void LinuxVST3Init(Steinberg::Linux::IRunLoop *pf);
134 extern void LinuxVST3Detatch();
135 extern void LinuxVST3FrameOpen(CFrame *that, void *, const VSTGUI::PlatformType &pt);
136 extern void LinuxVST3Idle();
137 #endif
138
139 CFontRef displayFont = NULL;
140 CFontRef patchNameFont = NULL;
141 CFontRef lfoTypeFont = NULL;
142 CFontRef aboutFont = NULL;
143
144 enum special_tags
145 {
146 tag_scene_select = 1,
147 tag_osc_select,
148 tag_osc_menu,
149 tag_fx_select,
150 tag_fx_menu,
151 tag_patchname,
152 tag_mp_category,
153 tag_mp_patch,
154 tag_store,
155 tag_store_cancel,
156 tag_store_ok,
157 tag_store_name,
158 tag_store_category,
159 tag_store_creator,
160 tag_store_comments,
161 tag_store_tuning,
162 tag_mod_source0,
163 tag_mod_source_end = tag_mod_source0 + n_modsources,
164 tag_settingsmenu,
165 tag_mp_jogfx,
166 tag_value_typein,
167 tag_editor_overlay_close,
168 tag_miniedit_ok,
169 tag_miniedit_cancel,
170
171 tag_status_mpe,
172 tag_status_zoom,
173 tag_status_tune,
174
175 tag_mseg_edit,
176 tag_lfo_menu,
177 // tag_metaparam,
178 // tag_metaparam_end = tag_metaparam+n_customcontrollers,
179 start_paramtags,
180 };
181
182 #if !TARGET_JUCE_UI
183 // TODO: CHECK REFERENCE COUNTING
184 struct SGEDropAdapter : public VSTGUI::IDropTarget, public VSTGUI::ReferenceCounted<int>
185 {
186 SurgeGUIEditor *buddy = nullptr;
SGEDropAdapterSGEDropAdapter187 SGEDropAdapter(SurgeGUIEditor *buddy)
188 {
189 this->buddy = buddy;
190 // std::cout << "Adapter created" << std::endl;
191 }
~SGEDropAdapterSGEDropAdapter192 ~SGEDropAdapter()
193 {
194 // std::cout << "Adapter Destroyed" << std::endl;
195 }
196
singleFileFNameSGEDropAdapter197 std::string singleFileFName(VSTGUI::DragEventData data)
198 {
199 auto drag = data.drag;
200 auto where = data.pos;
201 uint32_t ct = drag->getCount();
202 if (ct == 1)
203 {
204 IDataPackage::Type t = drag->getDataType(0);
205 if (t == IDataPackage::kFilePath)
206 {
207 const void *fn;
208 drag->getData(0, fn, t);
209 const char *fName = static_cast<const char *>(fn);
210 return fName;
211 }
212 }
213 return "";
214 }
215
onDragEnterSGEDropAdapter216 virtual VSTGUI::DragOperation onDragEnter(VSTGUI::DragEventData data) override
217 {
218 auto fn = singleFileFName(data);
219
220 if (buddy && buddy->canDropTarget(fn))
221 return VSTGUI::DragOperation::Copy;
222 // Hand this decision off to SGE
223 return VSTGUI::DragOperation::None;
224 }
onDragMoveSGEDropAdapter225 virtual VSTGUI::DragOperation onDragMove(VSTGUI::DragEventData data) override
226 {
227 auto fn = singleFileFName(data);
228
229 if (buddy && buddy->canDropTarget(fn))
230 return VSTGUI::DragOperation::Copy;
231
232 return VSTGUI::DragOperation::None;
233 }
onDragLeaveSGEDropAdapter234 virtual void onDragLeave(VSTGUI::DragEventData data) override {}
onDropSGEDropAdapter235 virtual bool onDrop(VSTGUI::DragEventData data) override
236 {
237 auto fn = singleFileFName(data);
238 if (buddy)
239 return buddy->onDrop(fn);
240 return false;
241 }
242 };
243 #endif
244
245 int SurgeGUIEditor::start_paramtag_value = start_paramtags;
246
fromSynthGUITag(SurgeSynthesizer * synth,int tag,SurgeSynthesizer::ID & q)247 bool SurgeGUIEditor::fromSynthGUITag(SurgeSynthesizer *synth, int tag, SurgeSynthesizer::ID &q)
248 {
249 // This is wrong for macros and params but is close
250 return synth->fromSynthSideIdWithGuiOffset(tag, start_paramtags, tag_mod_source0 + ms_ctrl1, q);
251 }
252
SurgeGUIEditor(PARENT_PLUGIN_TYPE * effect,SurgeSynthesizer * synth,void * userdata)253 SurgeGUIEditor::SurgeGUIEditor(PARENT_PLUGIN_TYPE *effect, SurgeSynthesizer *synth, void *userdata)
254 : super(effect)
255 {
256 #ifdef INSTRUMENT_UI
257 Surge::Debug::record("SurgeGUIEditor::SurgeGUIEditor");
258 #endif
259 frame = 0;
260
261 #if TARGET_VST3
262 // setIdleRate(25);
263 // synth = ((SurgeProcessor*)effect)->getSurge();
264 #endif
265
266 patchname = 0;
267 blinktimer = 0.f;
268 blinkstate = false;
269 aboutbox = nullptr;
270 midiLearnOverlay = nullptr;
271 patchCountdown = -1;
272
273 mod_editor = false;
274
275 // init the size of the plugin
276 initialZoomFactor = Surge::Storage::getUserDefaultValue(&(synth->storage), "defaultZoom", 100);
277 int instanceZoomFactor = synth->storage.getPatch().dawExtraState.editor.instanceZoomFactor;
278 if (instanceZoomFactor > 0)
279 {
280 // dawExtraState zoomFactor wins defaultZoom
281 initialZoomFactor = instanceZoomFactor;
282 }
283
284 rect.left = 0;
285 rect.top = 0;
286 #if TARGET_VST2 || TARGET_VST3
287 rect.right = getWindowSizeX() * initialZoomFactor * 0.01;
288 rect.bottom = getWindowSizeY() * initialZoomFactor * 0.01;
289 #else
290 rect.right = getWindowSizeX();
291 rect.bottom = getWindowSizeY();
292 #endif
293 editor_open = false;
294 queue_refresh = false;
295 memset(param, 0, n_paramslots * sizeof(void *));
296 polydisp =
297 0; // FIXME - when changing skins and rebuilding we need to reset these state variables too
298 splitpointControl = 0;
299 clear_infoview_countdown = -1;
300 vu[0] = 0;
301 vu[1] = 0;
302 vu[2] = 0;
303 vu[3] = 0;
304 vu[4] = 0;
305 vu[5] = 0;
306 vu[6] = 0;
307 vu[7] = 0;
308 vu[8] = 0;
309 vu[9] = 0;
310 vu[10] = 0;
311 vu[11] = 0;
312 vu[12] = 0;
313 vu[13] = 0;
314 vu[14] = 0;
315 vu[15] = 0;
316 lfodisplay = 0;
317 fxmenu = 0;
318 for (int i = 0; i < n_fx_slots; ++i)
319 {
320 selectedFX[i] = -1;
321 fxPresetName[i] = "";
322 }
323
324 _effect = effect;
325 _userdata = userdata;
326 this->synth = synth;
327
328 minimumZoom = 50;
329 #if LINUX
330 minimumZoom = 100; // See github issue #628
331 #endif
332
333 zoom_callback = [](SurgeGUIEditor *f, bool) {};
334 setZoomFactor(initialZoomFactor);
335 zoomInvalid = (initialZoomFactor != 100);
336
337 /*
338 ** As documented in RuntimeFonts.h, the contract of this function is to side-effect
339 ** onto globals displayFont and patchNameFont with valid fonts from the runtime
340 ** distribution
341 */
342 Surge::GUI::initializeRuntimeFont();
343
344 if (displayFont == NULL)
345 {
346 /*
347 ** OK the runtime load didn't work. Fall back to
348 ** the old defaults
349 **
350 ** FIXME: One day we will be confident enough in
351 ** our dyna loader to make this a Surge::UserInteraction::promptError
352 ** warning also.
353 **
354 ** For now, copy the defaults from above. (Don't factor this into
355 ** a function since the above defaults are initialized as dll
356 ** statics if we are not runtime).
357 */
358 #if !TARGET_JUCE_UI
359 #if MAC
360 SharedPointer<CFontDesc> minifont = new CFontDesc("Lucida Grande", 9);
361 SharedPointer<CFontDesc> patchfont = new CFontDesc("Lucida Grande", 14);
362 SharedPointer<CFontDesc> lfofont = new CFontDesc("Lucida Grande", 8);
363 SharedPointer<CFontDesc> aboutfont = new CFontDesc("Lucida Grande", 10);
364 #elif LINUX
365 SharedPointer<CFontDesc> minifont = new CFontDesc("sans-serif", 9);
366 SharedPointer<CFontDesc> patchfont = new CFontDesc("sans-serif", 14);
367 SharedPointer<CFontDesc> lfofont = new CFontDesc("sans-serif", 8);
368 SharedPointer<CFontDesc> aboutfont = new CFontDesc("sans-serif", 10);
369 #else
370 /*
371 * Choose to only warn on windows since (1) mac includes Lato in the bundle so this
372 * never happens there and (2) linux has all sorts of font noise
373 */
374 static bool warnedAboutLato =
375 Surge::Storage::getUserDefaultValue(&(synth->storage), "warnedAboutLato", 0);
376 if (!warnedAboutLato)
377 {
378 Surge::UserInteractions::promptError(
379 std::string("Surge was unable to resolve the Lato font. ") +
380 "Install Lato or re-run the Surge installer to resolve this. " +
381 "Surge will run anwyay, but some of your labels may clip or be mis-rendered.",
382 "Unable to resolve Lato Font");
383 warnedAboutLato = true;
384 Surge::Storage::updateUserDefaultValue(&(synth->storage), "warnedAboutLato", 1);
385 }
386 SharedPointer<CFontDesc> minifont = new CFontDesc("Microsoft Sans Serif", 9);
387 SharedPointer<CFontDesc> patchfont = new CFontDesc("Arial", 14);
388 SharedPointer<CFontDesc> lfofont = new CFontDesc("Microsoft Sans Serif", 8);
389 SharedPointer<CFontDesc> aboutfont = new CFontDesc("Microsoft Sans Serif", 10);
390 #endif
391
392 displayFont = minifont;
393 patchNameFont = patchfont;
394 lfoTypeFont = lfofont;
395 aboutFont = aboutfont;
396 #endif
397 }
398
399 /*
400 ** See the comment in SurgeParamConfig.h
401 */
402 if ((int)Surge::ParamConfig::kHorizontal != (int)VSTGUI::CSlider::kHorizontal ||
403 (int)Surge::ParamConfig::kVertical != (int)VSTGUI::CSlider::kVertical)
404 {
405 throw std::runtime_error("Software Error: Param Mismatch");
406 }
407
408 for (int i = 0; i < n_modsources; ++i)
409 modsource_is_alternate[i] = false;
410
411 currentSkin = Surge::UI::SkinDB::get().defaultSkin(&(this->synth->storage));
412 reloadFromSkin();
413
414 auto des = &(synth->storage.getPatch().dawExtraState);
415 if (des->isPopulated)
416 loadFromDAWExtraState(synth);
417 }
418
~SurgeGUIEditor()419 SurgeGUIEditor::~SurgeGUIEditor()
420 {
421 auto isPop = synth->storage.getPatch().dawExtraState.isPopulated;
422 populateDawExtraState(synth); // If I must die, leave my state for future generations
423 synth->storage.getPatch().dawExtraState.isPopulated = isPop;
424 if (frame)
425 {
426 #if !TARGET_JUCE_UI
427 getFrame()->unregisterKeyboardHook(this);
428 #endif
429 frame->close();
430 }
431 #if !TARGET_JUCE_UI
432 if (dropAdapter)
433 {
434 dropAdapter->buddy = nullptr;
435 dropAdapter->forget();
436 dropAdapter = nullptr;
437 }
438 #endif
439
440 #if TARGET_JUCE_UI
441 frame->forget();
442 #endif
443 }
444
idle()445 void SurgeGUIEditor::idle()
446 {
447 #if TARGET_VST2 && LINUX
448 if (!super::idle2())
449 return;
450 #endif
451 #if TARGET_VST3 && LINUX
452 LinuxVST3Idle();
453 #endif
454 #if TARGET_VST3
455 if (_effect)
456 _effect->uithreadIdleActivity();
457 #endif
458
459 if (!synth)
460 return;
461
462 if (pause_idle_updates)
463 return;
464
465 if (editor_open && frame && !synth->halt_engine)
466 {
467 /*
468 * USEFUL for testing stress patch changes
469 *
470 static int runct = 0;
471 if( runct++ == 5 )
472 {
473 synth->patchid_queue = rand() % 1800;
474 runct = 0;
475 }
476 */
477 hasIdleRun = true;
478 if (firstIdleCountdown)
479 {
480 // Linux VST3 in JUCE Hosts (maybe others?) sets up the run loop out of order, it seems
481 // sometimes missing the very first invalidation. Force a redraw on the first idle
482 // isFirstIdle = false;
483 firstIdleCountdown--;
484
485 frame->invalid();
486 }
487 if (synth->learn_param < 0 && synth->learn_custom < 0 && midiLearnOverlay != nullptr)
488 {
489 hideMidiLearnOverlay();
490 }
491 for (auto c : removeFromFrame)
492 {
493 if (frame->isChild(c))
494 {
495 frame->removeView(c);
496 }
497 }
498 removeFromFrame.clear();
499
500 if (clearOffscreenCachesAtZero == 0)
501 {
502 bitmapStore->clearAllBitmapOffscreenCaches();
503 frame->invalid();
504 }
505 if (clearOffscreenCachesAtZero >= 0)
506 {
507 clearOffscreenCachesAtZero--;
508 }
509
510 {
511 bool expected = true;
512 if (synth->rawLoadNeedsUIDawExtraState.compare_exchange_weak(expected, true) &&
513 expected)
514 {
515 std::lock_guard<std::mutex> g(synth->rawLoadQueueMutex);
516 synth->rawLoadNeedsUIDawExtraState = false;
517 loadFromDAWExtraState(synth);
518 }
519 }
520
521 if (patchCountdown >= 0 && !pause_idle_updates)
522 {
523 patchCountdown--;
524 if (patchCountdown < 0 && synth->patchid_queue >= 0)
525 {
526 std::ostringstream oss;
527
528 oss << "Loading patch " << synth->patchid_queue
529 << " has not occured after 200 idle cycles. This means"
530 << "that the audio system is not running in your host, or that your system is "
531 "delayed while"
532 << " loading many patches in a row. The audio system has to be "
533 << "running in order to load Surge patches. If the audio system is working, "
534 "you can probably"
535 << " ignore this message and continue once Surge catches up.";
536 Surge::UserInteractions::promptError(oss.str(), "Patch Loading Error");
537 }
538 }
539
540 if (zoomInvalid)
541 {
542 setZoomFactor(getZoomFactor());
543 zoomInvalid = false;
544 }
545
546 #if TARGET_VST3
547 resizeFromIdleSentinel();
548 #endif
549
550 if (showMSEGEditorOnNextIdleOrOpen)
551 {
552 showMSEGEditor();
553 showMSEGEditorOnNextIdleOrOpen = false;
554 }
555
556 /*static CDrawContext drawContext
557 (frame, NULL, systemWindow);*/
558 // CDrawContext *drawContext = frame->createDrawContext();
559
560 CView *v = frame->getFocusView();
561 if (v && dynamic_cast<CControl *>(v) != nullptr)
562 {
563 int ptag = ((CControl *)v)->getTag() - start_paramtags;
564 if (ptag >= 0)
565 {
566 synth->storage.modRoutingMutex.lock();
567
568 for (int i = 1; i < n_modsources; i++)
569 {
570 if (gui_modsrc[i])
571 ((CModulationSourceButton *)gui_modsrc[i])
572 ->update_rt_vals(synth->isActiveModulation(ptag, (modsources)i), 0,
573 synth->isModsourceUsed((modsources)i));
574 }
575 synth->storage.modRoutingMutex.unlock();
576 }
577 }
578 else
579 {
580 synth->storage.modRoutingMutex.lock();
581 for (int i = 1; i < n_modsources; i++)
582 {
583 if (gui_modsrc[i])
584 ((CModulationSourceButton *)gui_modsrc[i])
585 ->update_rt_vals(false, 0, synth->isModsourceUsed((modsources)i));
586 }
587 synth->storage.modRoutingMutex.unlock();
588 }
589
590 if (synth->storage.getPatch()
591 .scene[current_scene]
592 .osc[current_osc[current_scene]]
593 .wt.refresh_display)
594 {
595 synth->storage.getPatch()
596 .scene[current_scene]
597 .osc[current_osc[current_scene]]
598 .wt.refresh_display = false;
599 if (oscdisplay)
600 {
601 oscdisplay->setDirty(true);
602 oscdisplay->invalid();
603 }
604 }
605
606 if (typeinResetCounter > 0)
607 {
608 typeinResetCounter--;
609 if (typeinResetCounter <= 0 && typeinDialog)
610 {
611 typeinLabel->setText(typeinResetLabel.c_str());
612 typeinLabel->setFontColor(currentSkin->getColor(Colors::Slider::Label::Light));
613 typeinLabel->invalid();
614 }
615 }
616
617 #if OSC_MOD_ANIMATION
618 if (mod_editor && oscdisplay)
619 {
620 ((COscillatorDisplay *)oscdisplay)->tickModTime();
621 oscdisplay->setDirty(true);
622 oscdisplay->invalid();
623 }
624 #endif
625
626 if (polydisp)
627 {
628 CNumberField *cnpd = static_cast<CNumberField *>(polydisp);
629 int prior = cnpd->getPoly();
630 cnpd->setPoly(synth->polydisplay);
631 if (prior != synth->polydisplay)
632 cnpd->invalid();
633 }
634
635 bool patchChanged = false;
636 if (patchname)
637 {
638 patchChanged = ((CPatchBrowser *)patchname)->sel_id != synth->patchid;
639 }
640
641 if (statusMPE)
642 {
643 auto v = statusMPE->getValue();
644 if ((v < 0.5 && synth->mpeEnabled) || (v > 0.5 && !synth->mpeEnabled))
645 {
646 statusMPE->setValue(synth->mpeEnabled ? 1 : 0);
647 statusMPE->invalid();
648 }
649 }
650
651 if (statusTune)
652 {
653 auto v = statusTune->getValue();
654 if ((v < 0.5 && !synth->storage.isStandardTuning) ||
655 (v > 0.5 && synth->storage.isStandardTuning))
656 {
657 bool hasmts =
658 synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active;
659 statusTune->setValue(!synth->storage.isStandardTuning || hasmts);
660 statusTune->invalid();
661 }
662 }
663
664 if (patchChanged)
665 {
666 #if TARGET_AUDIOUNIT
667 synth->getParent()->setPresetByID(synth->patchid);
668 #endif
669 for (int i = 0; i < n_fx_slots; ++i)
670 {
671 fxPresetName[i] = "";
672 }
673 }
674
675 if (queue_refresh || synth->refresh_editor || patchChanged)
676 {
677 queue_refresh = false;
678 synth->refresh_editor = false;
679
680 if (frame)
681 {
682 if (synth->patch_loaded)
683 mod_editor = false;
684 synth->patch_loaded = false;
685
686 openOrRecreateEditor();
687 }
688 if (patchname)
689 {
690 ((CPatchBrowser *)patchname)->sel_id = synth->patchid;
691 ((CPatchBrowser *)patchname)->setLabel(synth->storage.getPatch().name);
692 ((CPatchBrowser *)patchname)->setCategory(synth->storage.getPatch().category);
693 ((CPatchBrowser *)patchname)->setAuthor(synth->storage.getPatch().author);
694 patchname->invalid();
695 }
696 }
697
698 bool vuInvalid = false;
699 if (synth->vu_peak[0] != vu[0]->getValue())
700 {
701 vuInvalid = true;
702 vu[0]->setValue(synth->vu_peak[0]);
703 }
704 if (synth->vu_peak[1] != ((CSurgeVuMeter *)vu[0])->getValueR())
705 {
706 ((CSurgeVuMeter *)vu[0])->setValueR(synth->vu_peak[1]);
707 vuInvalid = true;
708 }
709 if (vuInvalid)
710 vu[0]->invalid();
711
712 for (int i = 0; i < n_fx_slots; i++)
713 {
714 assert(i + 1 < Effect::KNumVuSlots);
715 if (vu[i + 1] && synth->fx[current_fx])
716 {
717 // there's seems to be a bug here that overwrites either this or the vu-pointer
718 // try to catch it earlier to retrieve more info
719
720 // assert(!((int)vu[i+1] & 0xffff0000));
721
722 // check so it doesn't overlap with the infowindow
723 CRect iw = ((CParameterTooltip *)infowindow)->getViewSize();
724 CRect vur = vu[i + 1]->getViewSize();
725
726 if (!((CParameterTooltip *)infowindow)->isVisible() || !vur.rectOverlap(iw))
727 {
728 vu[i + 1]->setValue(synth->fx[current_fx]->vu[(i << 1)]);
729 ((CSurgeVuMeter *)vu[i + 1])
730 ->setValueR(synth->fx[current_fx]->vu[(i << 1) + 1]);
731 vu[i + 1]->invalid();
732 }
733 }
734 }
735
736 for (int i = 0; i < 8; i++)
737 {
738 if (synth->refresh_ctrl_queue[i] >= 0)
739 {
740 int j = synth->refresh_ctrl_queue[i];
741 synth->refresh_ctrl_queue[i] = -1;
742
743 if (param[j])
744 {
745 char pname[256], pdisp[256];
746 SurgeSynthesizer::ID jid;
747 if (synth->fromSynthSideId(j, jid))
748 {
749 synth->getParameterName(jid, pname);
750 synth->getParameterDisplay(jid, pdisp);
751 }
752
753 /*if(i == 0)
754 {
755 ((gui_pdisplay*)infowindow)->setLabel(pname,pdisp);
756 draw_infowindow(j, param[j], false, true);
757 clear_infoview_countdown = 40;
758 }*/
759
760 param[j]->setValue(synth->refresh_ctrl_queue_value[i]);
761 frame->invalidRect(param[j]->getViewSize());
762 // oscdisplay->invalid();
763
764 if (oscdisplay)
765 {
766 ((COscillatorDisplay *)oscdisplay)->invalidateIfIdIsInRange(j);
767 }
768
769 if (lfodisplay)
770 {
771 ((CLFOGui *)lfodisplay)->invalidateIfIdIsInRange(j);
772 }
773 }
774 }
775 }
776
777 if (lastTempo != synth->time_data.tempo || lastTSNum != synth->time_data.timeSigNumerator ||
778 lastTSDen != synth->time_data.timeSigDenominator)
779 {
780 lastTempo = synth->time_data.tempo;
781 lastTSNum = synth->time_data.timeSigNumerator;
782 lastTSDen = synth->time_data.timeSigDenominator;
783 if (lfodisplay)
784 {
785 ((CLFOGui *)lfodisplay)
786 ->setTimeSignature(synth->time_data.timeSigNumerator,
787 synth->time_data.timeSigDenominator);
788 ((CLFOGui *)lfodisplay)->invalidateIfAnythingIsTemposynced();
789 }
790 }
791
792 std::vector<int> refreshIndices;
793 if (synth->refresh_overflow)
794 {
795 refreshIndices.resize(n_total_params);
796 std::iota(std::begin(refreshIndices), std::end(refreshIndices), 0);
797 frame->invalid();
798 }
799 else
800 {
801 for (int i = 0; i < 8; ++i)
802 if (synth->refresh_parameter_queue[i] >= 0)
803 refreshIndices.push_back(synth->refresh_parameter_queue[i]);
804 }
805
806 synth->refresh_overflow = false;
807 for (int i = 0; i < 8; ++i)
808 synth->refresh_parameter_queue[i] = -1;
809
810 for (auto j : refreshIndices)
811 {
812 if ((j < n_total_params) && param[j])
813 {
814 SurgeSynthesizer::ID jid;
815 if (synth->fromSynthSideId(j, jid))
816 param[j]->setValue(synth->getParameter01(jid));
817 frame->invalidRect(param[j]->getViewSize());
818
819 if (oscdisplay)
820 {
821 ((COscillatorDisplay *)oscdisplay)->invalidateIfIdIsInRange(j);
822 }
823
824 if (lfodisplay)
825 {
826 ((CLFOGui *)lfodisplay)->invalidateIfIdIsInRange(j);
827 }
828 }
829 else if ((j >= metaparam_offset) && (j < (metaparam_offset + n_customcontrollers)))
830 {
831 int cc = j - metaparam_offset;
832 gui_modsrc[ms_ctrl1 + cc]->setValue(
833 ((ControllerModulationSource *)synth->storage.getPatch()
834 .scene[current_scene]
835 .modsources[ms_ctrl1 + cc])
836 ->get_target01());
837 }
838 else if ((j >= 0) && (j < n_total_params) && nonmod_param[j])
839 {
840 /*
841 ** What the heck is this NONMOD_PARAM thing?
842 **
843 ** There are a set of params - like discrete things like
844 ** octave and filter type - which are not LFO modulatable
845 ** and aren't in the params[] array. But they are exposed
846 ** properties, so you can control them from a DAW. The
847 ** DAW control works - everything up to this path (as described
848 ** in #160) works fine and sets the value but since there's
849 ** no CControl in param the above bails out. But ading
850 ** all these controls to param[] would have the unintended
851 ** side effect of giving them all the other param[] behaviours.
852 ** So have a second array and drop select items in here so we
853 ** can actually get them redrawing when an external param set occurs.
854 */
855 CControl *cc = nonmod_param[j];
856
857 /*
858 ** Some state changes enable and disable sliders. If this is one of those state
859 *changes and a value has changed, then
860 ** we need to invalidate them. See #2056.
861 */
862 auto tag = cc->getTag();
863 SurgeSynthesizer::ID jid;
864
865 auto sv = 0.f;
866 if (synth->fromSynthSideId(j, jid))
867 sv = synth->getParameter01(jid);
868 auto cv = cc->getValue();
869
870 if ((sv != cv) && ((tag == fmconfig_tag || tag == filterblock_tag)))
871 {
872 std::unordered_map<int, bool> resetMap;
873
874 if (tag == fmconfig_tag)
875 {
876 auto targetTag =
877 synth->storage.getPatch().scene[current_scene].fm_depth.id +
878 start_paramtags;
879 auto targetState =
880 (Parameter::intUnscaledFromFloat(sv, n_fm_routings - 1) == fm_off);
881 resetMap[targetTag] = targetState;
882 }
883
884 if (tag == filterblock_tag)
885 {
886 auto pval = Parameter::intUnscaledFromFloat(sv, n_filter_configs - 1);
887
888 auto targetTag =
889 synth->storage.getPatch().scene[current_scene].feedback.id +
890 start_paramtags;
891 auto targetState = (pval == fc_serial1);
892 resetMap[targetTag] = targetState;
893
894 targetTag = synth->storage.getPatch().scene[current_scene].width.id +
895 start_paramtags;
896 targetState = (pval != fc_stereo && pval != fc_wide);
897 resetMap[targetTag] = targetState;
898 }
899
900 for (int i = 0; i < n_paramslots; i++)
901 {
902 if (param[i] && (resetMap.find(param[i]->getTag()) != resetMap.end()))
903 {
904 auto css = dynamic_cast<CSurgeSlider *>(param[i]);
905
906 if (css)
907 {
908 css->disabled = resetMap[param[i]->getTag()];
909 css->invalid();
910 }
911 }
912 }
913 }
914
915 if (synth->storage.getPatch().param_ptr[j]->ctrltype == ct_scenemode)
916 {
917 /*
918 * This is gross hack for our reordering of scenemode. Basically take the
919 * automation value and turn it into the UI value
920 */
921 auto pval = Parameter::intUnscaledFromFloat(sv, n_scene_modes - 1);
922 if (pval == sm_dual)
923 pval = sm_chsplit;
924 else if (pval == sm_chsplit)
925 pval = sm_dual;
926 sv = Parameter::intScaledToFloat(pval, n_scene_modes - 1);
927 }
928
929 if (sv != cv)
930 {
931 cc->setValue(sv);
932 }
933
934 // Integer switches also work differently
935 auto assw = dynamic_cast<CSwitchControl *>(cc);
936 if (assw)
937 {
938 if (assw->is_itype)
939 {
940 assw->ivalue = synth->storage.getPatch().param_ptr[j]->val.i + 1;
941 }
942 }
943
944 cc->setDirty();
945 cc->invalid();
946 }
947 #if 0
948 /*
949 ** A set of special things which invalidate the entire UI.
950 ** See #2226 for why this is currently off.
951 */
952 else if( j == synth->storage.getPatch().scene_active.id )
953 {
954 if( current_scene != synth->storage.getPatch().scene_active.val.i )
955 {
956 synth->refresh_editor = true;
957 current_scene = synth->storage.getPatch().scene_active.val.i;
958 }
959
960 }
961 #endif
962 else
963 {
964 // printf( "Bailing out of all possible refreshes on %d\n", j );
965 // This is not really a problem
966 }
967 }
968 for (int i = 0; i < n_customcontrollers; i++)
969 {
970 if (((ControllerModulationSource *)synth->storage.getPatch()
971 .scene[current_scene]
972 .modsources[ms_ctrl1 + i])
973 ->has_changed(true))
974 {
975 gui_modsrc[ms_ctrl1 + i]->setValue(
976 ((ControllerModulationSource *)synth->storage.getPatch()
977 .scene[current_scene]
978 .modsources[ms_ctrl1 + i])
979 ->get_target01());
980 }
981 }
982 clear_infoview_countdown += clear_infoview_peridle;
983 if (clear_infoview_countdown == 0)
984 {
985 ((CParameterTooltip *)infowindow)->Hide();
986 // infowindow->getViewSize();
987 // ctnvg frame->redrawRect(drawContext,r);
988 }
989
990 // frame->update(&drawContext);
991 // frame->idle();
992 }
993
994 #if BUILD_IS_DEBUG
995 if (debugLabel)
996 {
997 /*
998 * We can do debuggy stuff here to get an indea about internal state on the screen
999 */
1000
1001 /*
1002 auto t = std::to_string( synth->storage.songpos );
1003 debugLabel->setText(t.c_str());
1004 debugLabel->invalid();
1005 */
1006 }
1007 #endif
1008 }
1009
toggle_mod_editing()1010 void SurgeGUIEditor::toggle_mod_editing()
1011 {
1012 bool doModEditing = true;
1013
1014 if (currentSkin->getVersion() >= 2)
1015 {
1016 auto skinCtrl = currentSkin->controlForUIID("controls.modulation.panel");
1017
1018 if (!skinCtrl)
1019 {
1020 skinCtrl = currentSkin->getOrCreateControlForConnector(
1021 Surge::Skin::Connector::connectorByID("controls.modulation.panel"));
1022 }
1023
1024 if (skinCtrl->classname == Surge::UI::NoneClassName)
1025 {
1026 doModEditing = false;
1027 }
1028 }
1029
1030 if (doModEditing)
1031 {
1032 mod_editor = !mod_editor;
1033 refresh_mod();
1034
1035 auto iw = dynamic_cast<CParameterTooltip *>(infowindow);
1036
1037 if (iw && iw->isVisible())
1038 {
1039 iw->Hide();
1040 }
1041 }
1042 }
1043
refresh_mod()1044 void SurgeGUIEditor::refresh_mod()
1045 {
1046 CModulationSourceButton *cms = gui_modsrc[modsource];
1047
1048 modsources thisms = modsource;
1049 if (cms->hasAlternate && cms->useAlternate)
1050 thisms = (modsources)cms->alternateId;
1051
1052 synth->storage.modRoutingMutex.lock();
1053 for (int i = 0; i < n_paramslots; i++)
1054 {
1055 if (param[i])
1056 {
1057 auto *s = dynamic_cast<CSurgeSlider *>(param[i]);
1058 if (s)
1059 {
1060 if (s->is_mod)
1061 {
1062 s->setModMode(mod_editor ? 1 : 0);
1063 s->setModPresent(synth->isModDestUsed(i));
1064 s->setModCurrent(synth->isActiveModulation(i, thisms),
1065 synth->isBipolarModulation(thisms));
1066 }
1067 // s->setDirty();
1068 s->setModValue(synth->getModulation(i, thisms));
1069 s->invalid();
1070 }
1071 }
1072 }
1073 #if OSC_MOD_ANIMATION
1074 if (oscdisplay)
1075 {
1076 ((COscillatorDisplay *)oscdisplay)->setIsMod(mod_editor);
1077 ((COscillatorDisplay *)oscdisplay)->setModSource(thisms);
1078 oscdisplay->invalid();
1079 oscdisplay->setDirty(true);
1080 }
1081 #endif
1082
1083 synth->storage.modRoutingMutex.unlock();
1084
1085 for (int i = 1; i < n_modsources; i++)
1086 {
1087 int state = 0;
1088
1089 if (i == modsource)
1090 state = mod_editor ? 2 : 1;
1091
1092 if (i == modsource_editor[current_scene] && lfoNameLabel)
1093 {
1094 state |= 4;
1095
1096 // update the LFO title label
1097 std::string modname = modulatorName(modsource_editor[current_scene], true);
1098
1099 lfoNameLabel->setText(modname.c_str());
1100 }
1101
1102 if (gui_modsrc[i])
1103 {
1104 // this could change if I cleared the last one
1105 gui_modsrc[i]->used = synth->isModsourceUsed((modsources)i);
1106 gui_modsrc[i]->state = state;
1107
1108 if (i < ms_ctrl1 || i > ms_ctrl8)
1109 {
1110 auto mn = modulatorName(i, true);
1111 gui_modsrc[i]->setlabel(mn.c_str());
1112 }
1113 gui_modsrc[i]->invalid();
1114 }
1115 }
1116 // ctnvg frame->redraw();
1117 // frame->setDirty();
1118 #if LINUX
1119 frame->invalid();
1120 // Turns out linux is very bad with lots of little invalid rects in vstgui
1121 // see github issue 1103
1122 #endif
1123 }
1124
onKeyDown(const VstKeyCode & code,CFrame * frame)1125 int32_t SurgeGUIEditor::onKeyDown(const VstKeyCode &code, CFrame *frame)
1126 {
1127 #if !TARGET_JUCE_UI
1128 if (code.virt != 0)
1129 {
1130 switch (code.virt)
1131 {
1132 case VKEY_F5:
1133 if (Surge::Storage::getUserDefaultValue(&(this->synth->storage), "skinReloadViaF5", 0))
1134 {
1135 bitmapStore.reset(new SurgeBitmaps());
1136 bitmapStore->setupBitmapsForFrame(frame);
1137
1138 if (!currentSkin->reloadSkin(bitmapStore))
1139 {
1140 auto &db = Surge::UI::SkinDB::get();
1141 auto msg = std::string("Unable to load skin! Reverting the skin to Surge "
1142 "Classic.\n\nSkin error:\n") +
1143 db.getAndResetErrorString();
1144 currentSkin = db.defaultSkin(&(synth->storage));
1145 currentSkin->reloadSkin(bitmapStore);
1146 Surge::UserInteractions::promptError(msg, "Skin Loading Error");
1147 }
1148
1149 reloadFromSkin();
1150 synth->refresh_editor = true;
1151 }
1152
1153 return 1;
1154 break;
1155 case VKEY_TAB:
1156 if ((topmostEditorTag() == STORE_PATCH) || (typeinDialog && typeinDialog->isVisible()))
1157 {
1158 /*
1159 ** SaveDialog gets access to the tab key to switch between fields if it is open
1160 */
1161 return -1;
1162 }
1163 toggle_mod_editing();
1164 return 1;
1165 break;
1166 case VKEY_ESCAPE:
1167 if (typeinDialog && typeinDialog->isVisible())
1168 {
1169 // important to do this first since it basically stops us listening to changes
1170 typeinEditTarget = nullptr;
1171
1172 typeinDialog->setVisible(false);
1173 removeFromFrame.push_back(typeinDialog);
1174 typeinDialog = nullptr;
1175 typeinMode = Inactive;
1176 typeinResetCounter = -1;
1177
1178 return 1;
1179 }
1180 break;
1181 case VKEY_RETURN:
1182 if (typeinDialog && typeinDialog->isVisible())
1183 {
1184 typeinDialog->setVisible(false);
1185 }
1186 break;
1187 }
1188 }
1189 #endif
1190 return -1;
1191 }
1192
onKeyUp(const VstKeyCode & keyCode,CFrame * frame)1193 int32_t SurgeGUIEditor::onKeyUp(const VstKeyCode &keyCode, CFrame *frame) { return -1; }
1194
isControlVisible(ControlGroup controlGroup,int controlGroupEntry)1195 bool SurgeGUIEditor::isControlVisible(ControlGroup controlGroup, int controlGroupEntry)
1196 {
1197 switch (controlGroup)
1198 {
1199 case cg_OSC:
1200 return (controlGroupEntry == current_osc[current_scene]);
1201 case cg_LFO:
1202 return (controlGroupEntry == modsource_editor[current_scene]);
1203 case cg_FX:
1204 return (controlGroupEntry == current_fx);
1205 default:
1206 return true;
1207 }
1208 }
1209
positionForModulationGrid(modsources entry)1210 CRect SurgeGUIEditor::positionForModulationGrid(modsources entry)
1211 {
1212 bool isMacro = isCustomController(entry);
1213 int gridX = modsource_grid_xy[entry][0];
1214 int gridY = modsource_grid_xy[entry][1];
1215 int width = isMacro ? 93 : 74;
1216
1217 // to ensure the same gap between the modbuttons,
1218 // make the first and last non-macro button wider 2px
1219 if ((!isMacro) && ((gridX == 0) || (gridX == 9)))
1220 width += 2;
1221
1222 auto skinCtrl = currentSkin->controlForUIID("controls.modulation.panel");
1223
1224 if (!skinCtrl)
1225 {
1226 skinCtrl = currentSkin->getOrCreateControlForConnector(
1227 Surge::Skin::Connector::connectorByID("controls.modulation.panel"));
1228 }
1229
1230 if (skinCtrl->classname == Surge::UI::NoneClassName && currentSkin->getVersion() >= 2)
1231 {
1232 return CRect();
1233 }
1234
1235 CRect r(CPoint(skinCtrl->x, skinCtrl->y), CPoint(width - 1, 14));
1236
1237 if (isMacro)
1238 r.bottom += 8;
1239
1240 int offsetX = 1;
1241
1242 for (int i = 0; i < gridX; i++)
1243 {
1244 if ((!isMacro) && (i == 0))
1245 offsetX += 2;
1246
1247 // gross hack for accumulated 2 px horizontal offsets from the previous if clause
1248 // needed to align the last column nicely
1249 if ((!isMacro) && (i == 8))
1250 offsetX -= 18;
1251
1252 offsetX += width;
1253 }
1254
1255 r.offset(offsetX, (8 * gridY));
1256
1257 return r;
1258 }
1259
setDisabledForParameter(Parameter * p,CSurgeSlider * s)1260 void SurgeGUIEditor::setDisabledForParameter(Parameter *p, CSurgeSlider *s)
1261 {
1262 if (p->id == synth->storage.getPatch().scene[current_scene].fm_depth.id)
1263 {
1264 s->disabled = !(synth->storage.getPatch().scene[current_scene].fm_switch.val.i);
1265 }
1266 }
1267
1268 class LastChanceEventCapture : public CControl
1269 {
1270 public:
LastChanceEventCapture(const CPoint & sz,SurgeGUIEditor * ed)1271 LastChanceEventCapture(const CPoint &sz, SurgeGUIEditor *ed)
1272 : CControl(CRect(CPoint(0, 0), sz)), editor(ed)
1273 {
1274 }
draw(CDrawContext * pContext)1275 void draw(CDrawContext *pContext) override { return; }
1276
onMouseDown(CPoint & where,const CButtonState & buttons)1277 CMouseEventResult onMouseDown(CPoint &where, const CButtonState &buttons) override
1278 {
1279 if (buttons & (kMButton | kButton4 | kButton5))
1280 {
1281 if (editor)
1282 {
1283 editor->toggle_mod_editing();
1284 }
1285
1286 return kMouseEventHandled;
1287 }
1288
1289 if (buttons & kRButton)
1290 {
1291 if (editor)
1292 {
1293 CRect menuRect;
1294 menuRect.offset(where.x, where.y);
1295
1296 editor->useDevMenu = false;
1297 editor->showSettingsMenu(menuRect);
1298 }
1299
1300 return kMouseEventHandled;
1301 }
1302
1303 return kMouseEventNotHandled;
1304 }
1305
1306 private:
1307 SurgeGUIEditor *editor = nullptr;
1308 CLASS_METHODS(LastChanceEventCapture, CControl);
1309 };
1310
openOrRecreateEditor()1311 void SurgeGUIEditor::openOrRecreateEditor()
1312 {
1313 #ifdef INSTRUMENT_UI
1314 Surge::Debug::record("SurgeGUIEditor::openOrRecreateEditor");
1315 #endif
1316 if (!synth)
1317 return;
1318 assert(frame);
1319
1320 removeFromFrame.clear();
1321
1322 editorOverlayTagAtClose = "";
1323 if (editor_open)
1324 {
1325 editorOverlayTagAtClose = topmostEditorTag();
1326 for (auto el : editorOverlay)
1327 {
1328 el.second->remember();
1329 frame->removeView(el.second);
1330 }
1331
1332 close_editor();
1333 }
1334 CPoint nopoint(0, 0);
1335 CPoint sz(getWindowSizeX(), getWindowSizeY());
1336 auto lcb = new LastChanceEventCapture(sz, this);
1337 frame->addView(lcb);
1338
1339 clear_infoview_peridle = -1;
1340
1341 std::unordered_map<std::string, std::string> uiidToSliderLabel;
1342
1343 /*
1344 ** There are a collection of member states we need to reset
1345 */
1346 polydisp = nullptr;
1347 lfodisplay = nullptr;
1348 fxmenu = nullptr;
1349 typeinDialog = nullptr;
1350 minieditOverlay = nullptr;
1351 msegEditSwitch = nullptr;
1352 lfoNameLabel = nullptr;
1353 midiLearnOverlay = nullptr;
1354
1355 for (int i = 0; i < 16; ++i)
1356 vu[i] = nullptr;
1357
1358 current_scene = synth->storage.getPatch().scene_active.val.i;
1359
1360 /*
1361 * In Surge 1.8, the skin engine can change the position of this panel as a whole
1362 * but not anything else about it. The skin query happens inside positionForModulationGrid
1363 */
1364 for (int k = 1; k < n_modsources; k++)
1365 {
1366 if ((k != ms_random_unipolar) && (k != ms_alternate_unipolar))
1367 {
1368 modsources ms = (modsources)k;
1369
1370 CRect r = positionForModulationGrid(ms);
1371
1372 int state = 0;
1373
1374 if (ms == modsource)
1375 state = mod_editor ? 2 : 1;
1376 if (ms == modsource_editor[current_scene])
1377 state |= 4;
1378
1379 gui_modsrc[ms] = new CModulationSourceButton(r, this, tag_mod_source0 + ms, state, ms,
1380 bitmapStore, &(synth->storage));
1381
1382 gui_modsrc[ms]->setSkin(currentSkin);
1383 gui_modsrc[ms]->update_rt_vals(false, 0, synth->isModsourceUsed(ms));
1384
1385 if ((ms >= ms_ctrl1) && (ms <= ms_ctrl8))
1386 {
1387 // std::cout << synth->learn_custom << std::endl;
1388 gui_modsrc[ms]->setlabel(
1389 synth->storage.getPatch().CustomControllerLabel[ms - ms_ctrl1]);
1390 gui_modsrc[ms]->set_ismeta(true);
1391 gui_modsrc[ms]->setBipolar(
1392 synth->storage.getPatch().scene[current_scene].modsources[ms]->is_bipolar());
1393 gui_modsrc[ms]->setValue(((ControllerModulationSource *)synth->storage.getPatch()
1394 .scene[current_scene]
1395 .modsources[ms])
1396 ->get_target01());
1397 }
1398 else
1399 {
1400 gui_modsrc[ms]->setlabel(modulatorName(ms, true).c_str());
1401
1402 if (ms == ms_random_bipolar)
1403 {
1404 gui_modsrc[ms]->setAlternate(ms_random_unipolar,
1405 modsource_names_button[ms_random_unipolar]);
1406 gui_modsrc[ms]->setUseAlternate(modsource_is_alternate[ms]);
1407 }
1408
1409 if (ms == ms_alternate_bipolar)
1410 {
1411 gui_modsrc[ms]->setAlternate(ms_alternate_unipolar,
1412 modsource_names_button[ms_alternate_unipolar]);
1413 gui_modsrc[ms]->setUseAlternate(modsource_is_alternate[ms]);
1414 }
1415 }
1416
1417 frame->addView(gui_modsrc[ms]);
1418 if (ms >= ms_ctrl1 && ms <= ms_ctrl8 && synth->learn_custom == ms - ms_ctrl1)
1419 {
1420 showMidiLearnOverlay(r);
1421 }
1422 }
1423 }
1424
1425 /*// Comments
1426 {
1427 CRect CommentsRect(6 + 150*4,528, getWindowSizeX(), getWindowSizeY());
1428 CTextLabel *Comments = new
1429 CTextLabel(CommentsRect,synth->storage.getPatch().comment.c_str());
1430 Comments->setTransparency(true);
1431 Comments->setFont(displayFont);
1432 Comments->setHoriAlign(kMultiLineCenterText);
1433 frame->addView(Comments);
1434 }*/
1435
1436 // fx vu-meters & labels. This is all a bit hacky still
1437 {
1438 std::lock_guard<std::mutex> g(synth->fxSpawnMutex);
1439
1440 if (synth->fx[current_fx])
1441 {
1442 auto fxpp = currentSkin->getOrCreateControlForConnector("fx.param.panel");
1443 CRect fxRect = CRect(CPoint(fxpp->x, fxpp->y), CPoint(123, 13));
1444 for (int i = 0; i < 15; i++)
1445 {
1446 int t = synth->fx[current_fx]->vu_type(i);
1447 if (t)
1448 {
1449 CRect vr(fxRect); // FIXME (vurect);
1450 vr.offset(6, yofs * synth->fx[current_fx]->vu_ypos(i));
1451 vr.offset(0, -14);
1452 vu[i + 1] = new CSurgeVuMeter(vr, this);
1453 ((CSurgeVuMeter *)vu[i + 1])->setSkin(currentSkin, bitmapStore);
1454 ((CSurgeVuMeter *)vu[i + 1])->setType(t);
1455 frame->addView(vu[i + 1]);
1456 }
1457 else
1458 {
1459 vu[i + 1] = 0;
1460 }
1461
1462 const char *label = synth->fx[current_fx]->group_label(i);
1463
1464 if (label)
1465 {
1466 CRect vr(fxRect); // (vurect);
1467 vr.top += 1;
1468 vr.right += 5;
1469 vr.offset(5, -12);
1470 vr.offset(0, yofs * synth->fx[current_fx]->group_label_ypos(i));
1471 CEffectLabel *lb = new CEffectLabel(vr);
1472 lb->setLabel(label);
1473 lb->setSkin(currentSkin, bitmapStore);
1474 frame->addView(lb);
1475 }
1476 }
1477 }
1478 }
1479
1480 /*
1481 * Loop over the non-associated controls
1482 */
1483 for (auto i = Surge::Skin::Connector::NonParameterConnection::PARAMETER_CONNECTED + 1;
1484 i < Surge::Skin::Connector::NonParameterConnection::N_NONCONNECTED; ++i)
1485 {
1486 Surge::Skin::Connector::NonParameterConnection npc =
1487 (Surge::Skin::Connector::NonParameterConnection)i;
1488 auto conn = Surge::Skin::Connector::connectorByNonParameterConnection(npc);
1489 auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn);
1490 currentSkin->resolveBaseParentOffsets(skinCtrl);
1491
1492 if (!skinCtrl)
1493 {
1494 std::cout << "Unable to find SkinCtrl" << std::endl;
1495 continue;
1496 }
1497 if (skinCtrl->classname == Surge::UI::NoneClassName)
1498 continue;
1499
1500 /*
1501 * Many of the controls are special and so require non-generalizable constructors
1502 * handled here. Some are standard and so once we know the tag we can use
1503 * layoutComponentForSkin but it's not worth generalizing the OSCILLATOR_DISPLAY beyond
1504 * this, say.
1505 */
1506 switch (npc)
1507 {
1508 case Surge::Skin::Connector::NonParameterConnection::OSCILLATOR_DISPLAY:
1509 {
1510 auto od = new COscillatorDisplay(
1511 CRect(CPoint(skinCtrl->x, skinCtrl->y), CPoint(skinCtrl->w, skinCtrl->h)), this,
1512 &synth->storage.getPatch()
1513 .scene[synth->storage.getPatch().scene_active.val.i]
1514 .osc[current_osc[current_scene]],
1515 &synth->storage);
1516 od->setSkin(currentSkin, bitmapStore);
1517 frame->addView(od);
1518 oscdisplay = od;
1519 break;
1520 }
1521 case Surge::Skin::Connector::NonParameterConnection::SURGE_MENU:
1522 {
1523 auto q = layoutComponentForSkin(skinCtrl, tag_settingsmenu);
1524 break;
1525 }
1526 case Surge::Skin::Connector::NonParameterConnection::OSCILLATOR_SELECT:
1527 {
1528 auto oscswitch = layoutComponentForSkin(skinCtrl, tag_osc_select);
1529 oscswitch->setValue((float)current_osc[current_scene] / 2.0f);
1530 break;
1531 }
1532 case Surge::Skin::Connector::NonParameterConnection::JOG_PATCHCATEGORY:
1533 {
1534 layoutComponentForSkin(skinCtrl, tag_mp_category);
1535 break;
1536 }
1537 case Surge::Skin::Connector::NonParameterConnection::JOG_PATCH:
1538 {
1539 layoutComponentForSkin(skinCtrl, tag_mp_patch);
1540 break;
1541 }
1542 case Surge::Skin::Connector::NonParameterConnection::JOG_FX:
1543 {
1544 layoutComponentForSkin(skinCtrl, tag_mp_jogfx);
1545 break;
1546 }
1547 case Surge::Skin::Connector::NonParameterConnection::STATUS_MPE:
1548 {
1549 statusMPE = layoutComponentForSkin(skinCtrl, tag_status_mpe);
1550 statusMPE->setValue(synth->mpeEnabled ? 1 : 0);
1551 break;
1552 }
1553 case Surge::Skin::Connector::NonParameterConnection::STATUS_TUNE:
1554 {
1555 // FIXME - drag and drop onto this?
1556 statusTune = layoutComponentForSkin(skinCtrl, tag_status_tune);
1557 auto hasmts = synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active;
1558 statusTune->setValue(synth->storage.isStandardTuning ? hasmts : 1);
1559
1560 auto csc = dynamic_cast<CSwitchControl *>(statusTune);
1561 if (csc && synth->storage.isStandardTuning)
1562 {
1563 csc->unValueClickable = !synth->storage.isToggledToCache;
1564 }
1565 break;
1566 }
1567 case Surge::Skin::Connector::NonParameterConnection::STATUS_ZOOM:
1568 {
1569 statusZoom = layoutComponentForSkin(skinCtrl, tag_status_zoom);
1570 break;
1571 }
1572 case Surge::Skin::Connector::NonParameterConnection::STORE_PATCH:
1573 {
1574 layoutComponentForSkin(skinCtrl, tag_store);
1575 break;
1576 }
1577 case Surge::Skin::Connector::NonParameterConnection::MSEG_EDITOR_OPEN:
1578 {
1579 msegEditSwitch = layoutComponentForSkin(skinCtrl, tag_mseg_edit);
1580 msegEditSwitch->setVisible(false);
1581 msegEditSwitch->setValue(isAnyOverlayPresent(MSEG_EDITOR));
1582 auto q = modsource_editor[current_scene];
1583 if ((q >= ms_lfo1 && q <= ms_lfo6) || (q >= ms_slfo1 && q <= ms_slfo6))
1584 {
1585 auto *lfodata = &(synth->storage.getPatch().scene[current_scene].lfo[q - ms_lfo1]);
1586 if (lfodata->shape.val.i == lt_mseg)
1587 msegEditSwitch->setVisible(true);
1588 }
1589 break;
1590 }
1591 case Surge::Skin::Connector::NonParameterConnection::LFO_MENU:
1592 {
1593 layoutComponentForSkin(skinCtrl, tag_lfo_menu);
1594
1595 break;
1596 }
1597 case Surge::Skin::Connector::NonParameterConnection::LFO_LABEL:
1598 {
1599 // Room for improvement, obviously
1600 lfoNameLabel = new CVerticalLabel(skinCtrl->getRect(), "");
1601 lfoNameLabel->setTransparency(true);
1602 #if !TARGET_JUCE_UI
1603 lfoNameLabel->setFont(Surge::GUI::getLatoAtSize(10, kBoldFace));
1604 #endif
1605 lfoNameLabel->setFontColor(currentSkin->getColor(Colors::LFO::Title::Text));
1606 lfoNameLabel->setHoriAlign(kCenterText);
1607 frame->addView(lfoNameLabel);
1608
1609 break;
1610 }
1611
1612 case Surge::Skin::Connector::NonParameterConnection::FXPRESET_LABEL:
1613 {
1614 // Room for improvement, obviously
1615 fxPresetLabel = new CTextLabel(skinCtrl->getRect(), "Preset");
1616 fxPresetLabel->setFontColor(currentSkin->getColor(Colors::Effect::Preset::Name));
1617 fxPresetLabel->setTransparency(true);
1618 fxPresetLabel->setFont(displayFont);
1619 fxPresetLabel->setHoriAlign(kRightText);
1620 #if !TARGET_JUCE_UI
1621 fxPresetLabel->setTextTruncateMode(CTextLabel::TextTruncateMode::kTruncateTail);
1622 #endif
1623 frame->addView(fxPresetLabel);
1624 break;
1625 }
1626 case Surge::Skin::Connector::NonParameterConnection::PATCH_BROWSER:
1627 {
1628 patchname =
1629 new CPatchBrowser(skinCtrl->getRect(), this, tag_patchname, &synth->storage);
1630 ((CPatchBrowser *)patchname)->setSkin(currentSkin, bitmapStore);
1631 ((CPatchBrowser *)patchname)->setLabel(synth->storage.getPatch().name);
1632 ((CPatchBrowser *)patchname)->setCategory(synth->storage.getPatch().category);
1633 ((CPatchBrowser *)patchname)->setIDs(synth->current_category_id, synth->patchid);
1634 ((CPatchBrowser *)patchname)->setAuthor(synth->storage.getPatch().author);
1635 frame->addView(patchname);
1636 break;
1637 }
1638 case Surge::Skin::Connector::NonParameterConnection::FX_SELECTOR:
1639 {
1640 CEffectSettings *fc = new CEffectSettings(skinCtrl->getRect(), this, tag_fx_select,
1641 current_fx, bitmapStore);
1642 fc->setSkin(currentSkin, bitmapStore);
1643 ccfxconf = fc;
1644 for (int i = 0; i < n_fx_slots; i++)
1645 {
1646 fc->set_type(i, synth->storage.getPatch().fx[i].type.val.i);
1647 }
1648 fc->set_bypass(synth->storage.getPatch().fx_bypass.val.i);
1649 fc->set_disable(synth->storage.getPatch().fx_disable.val.i);
1650 frame->addView(fc);
1651 break;
1652 }
1653 case Surge::Skin::Connector::NonParameterConnection::MAIN_VU_METER:
1654 { // main vu-meter
1655 vu[0] = new CSurgeVuMeter(skinCtrl->getRect(), this);
1656 ((CSurgeVuMeter *)vu[0])->setSkin(currentSkin, bitmapStore);
1657 ((CSurgeVuMeter *)vu[0])->setType(vut_vu_stereo);
1658 frame->addView(vu[0]);
1659 break;
1660 }
1661 case Surge::Skin::Connector::NonParameterConnection::PARAMETER_CONNECTED:
1662 case Surge::Skin::Connector::NonParameterConnection::STORE_PATCH_DIALOG:
1663 case Surge::Skin::Connector::NonParameterConnection::MSEG_EDITOR_WINDOW:
1664 case Surge::Skin::Connector::NonParameterConnection::N_NONCONNECTED:
1665 break;
1666 }
1667 }
1668
1669 memset(param, 0, n_paramslots * sizeof(void *));
1670 memset(nonmod_param, 0, n_paramslots * sizeof(void *));
1671 int i = 0;
1672 vector<Parameter *>::iterator iter;
1673 for (iter = synth->storage.getPatch().param_ptr.begin();
1674 iter != synth->storage.getPatch().param_ptr.end(); iter++)
1675 {
1676 if (i == n_paramslots)
1677 {
1678 // This would only happen if a dev added params.
1679 Surge::UserInteractions::promptError(
1680 "INTERNAL ERROR: List of parameters is larger than maximum number of parameter "
1681 "slots. Increase n_paramslots in SurgeGUIEditor.h!",
1682 "Error");
1683 }
1684 Parameter *p = *iter;
1685 bool paramIsVisible = ((p->scene == (current_scene + 1)) || (p->scene == 0)) &&
1686 isControlVisible(p->ctrlgroup, p->ctrlgroup_entry) &&
1687 (p->ctrltype != ct_none);
1688
1689 auto conn = Surge::Skin::Connector::connectorByID(p->ui_identifier);
1690 std::string uiid = p->ui_identifier;
1691
1692 long style = p->ctrlstyle;
1693
1694 if (p->ctrltype == ct_fmratio)
1695 {
1696 if (p->extend_range || p->absolute)
1697 p->val_default.f = 16.f;
1698 else
1699 p->val_default.f = 1.f;
1700 }
1701
1702 if (p->hasSkinConnector &&
1703 conn.payload->defaultComponent != Surge::Skin::Components::None && paramIsVisible)
1704 {
1705 /*
1706 * Some special cases where we don't add a control
1707 */
1708 bool addControl = true;
1709
1710 // Case: Analog envelopes have no shapers
1711 if (p->ctrltype == ct_envshape || p->ctrltype == ct_envshape_attack)
1712 {
1713 addControl = (synth->storage.getPatch()
1714 .scene[current_scene]
1715 .adsr[p->ctrlgroup_entry]
1716 .mode.val.i == emt_digital);
1717 }
1718
1719 if (addControl)
1720 {
1721 auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn);
1722 currentSkin->resolveBaseParentOffsets(skinCtrl);
1723 layoutComponentForSkin(skinCtrl, p->id + start_paramtags, i, p,
1724 style | conn.payload->controlStyleFlags);
1725
1726 uiidToSliderLabel[p->ui_identifier] = p->get_name();
1727 if (p->id == synth->learn_param)
1728 {
1729 showMidiLearnOverlay(param[p->id]->getViewSize());
1730 }
1731 }
1732 }
1733 i++;
1734 }
1735
1736 // resonance link mode
1737 if (synth->storage.getPatch().scene[current_scene].f2_link_resonance.val.b)
1738 {
1739 i = synth->storage.getPatch().scene[current_scene].filterunit[1].resonance.id;
1740 if (param[i] && dynamic_cast<CSurgeSlider *>(param[i]) != nullptr)
1741 ((CSurgeSlider *)param[i])->disabled = true;
1742 }
1743
1744 // feedback control
1745 if (synth->storage.getPatch().scene[current_scene].filterblock_configuration.val.i ==
1746 fc_serial1)
1747 {
1748 i = synth->storage.getPatch().scene[current_scene].feedback.id;
1749 if (param[i] && dynamic_cast<CSurgeSlider *>(param[i]) != nullptr)
1750 {
1751 bool curr = ((CSurgeSlider *)param[i])->disabled;
1752 ((CSurgeSlider *)param[i])->disabled = true;
1753 if (!curr)
1754 {
1755 param[i]->setDirty();
1756 param[i]->invalid();
1757 }
1758 }
1759 }
1760
1761 // pan2 control
1762 if ((synth->storage.getPatch().scene[current_scene].filterblock_configuration.val.i !=
1763 fc_stereo) &&
1764 (synth->storage.getPatch().scene[current_scene].filterblock_configuration.val.i != fc_wide))
1765 {
1766 i = synth->storage.getPatch().scene[current_scene].width.id;
1767 if (param[i] && dynamic_cast<CSurgeSlider *>(param[i]) != nullptr)
1768 {
1769 bool curr = ((CSurgeSlider *)param[i])->disabled;
1770 ((CSurgeSlider *)param[i])->disabled = true;
1771 if (!curr)
1772 {
1773 param[i]->setDirty();
1774 param[i]->invalid();
1775 }
1776 }
1777 }
1778
1779 int menuX = 844, menuY = 550, menuW = 50, menuH = 15;
1780 CRect menurect(menuX, menuY, menuX + menuW, menuY + menuH);
1781
1782 infowindow = new CParameterTooltip(CRect(0, 0, 0, 0));
1783 ((CParameterTooltip *)infowindow)->setSkin(currentSkin);
1784 frame->addView(infowindow);
1785
1786 // Mouse behavior
1787 if (CSurgeSlider::sliderMoveRateState == CSurgeSlider::kUnInitialized)
1788 CSurgeSlider::sliderMoveRateState =
1789 (CSurgeSlider::MoveRateState)Surge::Storage::getUserDefaultValue(
1790 &(synth->storage), "sliderMoveRateState", (int)CSurgeSlider::kLegacy);
1791
1792 /*
1793 ** Skin Labels
1794 */
1795 auto labels = currentSkin->getLabels();
1796
1797 for (auto &l : labels)
1798 {
1799 auto mtext = currentSkin->propertyValue(l, Surge::Skin::Component::TEXT);
1800 auto ctext = currentSkin->propertyValue(l, Surge::Skin::Component::CONTROL_TEXT);
1801 if (ctext.isJust() && uiidToSliderLabel.find(ctext.fromJust()) != uiidToSliderLabel.end())
1802 {
1803
1804 mtext = Surge::Maybe<std::string>(uiidToSliderLabel[ctext.fromJust()]);
1805 }
1806
1807 if (mtext.isJust())
1808 {
1809 VSTGUI::CHoriTxtAlign txtalign = Surge::UI::Skin::setTextAlignProperty(
1810 currentSkin->propertyValue(l, Surge::Skin::Component::TEXT_ALIGN, "left"));
1811
1812 auto fs = currentSkin->propertyValue(l, Surge::Skin::Component::FONT_SIZE, "12");
1813 auto fsize = std::atof(fs.c_str());
1814
1815 VSTGUI::CTxtFace fstyle = Surge::UI::Skin::setFontStyleProperty(
1816 currentSkin->propertyValue(l, Surge::Skin::Component::FONT_STYLE, "normal"));
1817
1818 auto coln =
1819 currentSkin->propertyValue(l, Surge::Skin::Component::TEXT_COLOR, "#FF0000");
1820 auto col = currentSkin->getColor(coln, kRedCColor);
1821
1822 auto dcol = VSTGUI::CColor(255, 255, 255, 0);
1823 auto bgcoln = currentSkin->propertyValue(l, Surge::Skin::Component::BACKGROUND_COLOR,
1824 "#FFFFFF00");
1825 auto bgcol = currentSkin->getColor(bgcoln, dcol);
1826
1827 auto frcoln =
1828 currentSkin->propertyValue(l, Surge::Skin::Component::FRAME_COLOR, "#FFFFFF00");
1829 auto frcol = currentSkin->getColor(frcoln, dcol);
1830
1831 auto lb = new CTextLabel(CRect(CPoint(l->x, l->y), CPoint(l->w, l->h)),
1832 mtext.fromJust().c_str());
1833 lb->setTransparency((bgcol == dcol && frcol == dcol));
1834 lb->setHoriAlign(txtalign);
1835
1836 #if !TARGET_JUCE_UI
1837 lb->setAntialias(true);
1838 lb->setFont(Surge::GUI::getLatoAtSize(fsize, fstyle));
1839 #endif
1840
1841 lb->setFontColor(col);
1842 lb->setBackColor(bgcol);
1843 lb->setFrameColor(frcol);
1844
1845 frame->addView(lb);
1846 }
1847 else
1848 {
1849 auto image = currentSkin->propertyValue(l, Surge::Skin::Component::IMAGE);
1850 if (image.isJust())
1851 {
1852 auto bmp = bitmapStore->getBitmapByStringID(image.fromJust());
1853 if (bmp)
1854 {
1855 auto lb =
1856 new CTextLabel(CRect(CPoint(l->x, l->y), CPoint(l->w, l->h)), nullptr, bmp);
1857 frame->addView(lb);
1858 }
1859 }
1860 }
1861 }
1862 #if BUILD_IS_DEBUG
1863 /*
1864 * This code is here JUST because baconpaul keeps developing surge and then swapping
1865 * to make music and wondering why LPX is stuttering. Please don't remove it!
1866 *
1867 * UPDATE: Might as well keep a reference to the object though so we can touch it in idle
1868 */
1869 auto dl = std::string("D ") + Surge::Build::BuildTime + " " + Surge::Build::GitBranch;
1870 auto lb = new CTextLabel(CRect(CPoint(310, 39), CPoint(195, 15)), dl.c_str());
1871 lb->setTransparency(false);
1872 lb->setBackColor(kRedCColor);
1873 lb->setFontColor(kWhiteCColor);
1874 lb->setFont(displayFont);
1875 lb->setHoriAlign(VSTGUI::kCenterText);
1876 lb->setAntialias(true);
1877 frame->addView(lb);
1878 debugLabel = lb;
1879
1880 #if TARGET_JUCE_UI
1881 struct TestC : public juce::Component
1882 {
1883 ~TestC() { std::cout << "TestC Cleaned Up" << std::endl; }
1884 juce::Colour bg = juce::Colour(255, 0, 255);
1885 void paint(juce::Graphics &g) override
1886 {
1887 g.fillAll(bg);
1888 g.setColour(juce::Colour(255, 255, 255));
1889 g.drawText("JUCE BUILD", getLocalBounds(), juce::Justification::centred);
1890 }
1891 void mouseDown(const juce::MouseEvent &e) override
1892 {
1893 auto q = rand() % 255;
1894 bg = juce::Colour(255, q, 255 - q);
1895 repaint();
1896 }
1897 };
1898 auto pt = std::make_unique<TestC>();
1899 frame->juceComponent()->addAndMakeVisible(*pt);
1900 pt->setBounds(150, 2, 100, 10);
1901 frame->takeOwnership(std::move(pt));
1902 #endif
1903 #endif
1904 for (auto el : editorOverlay)
1905 {
1906 frame->addView(el.second);
1907 auto contents = editorOverlayContentsWeakReference[el.second];
1908
1909 /*
1910 * This is a hack for 1.8 which we have to clean up in 1.9 when we do #3223
1911 */
1912 auto mse = dynamic_cast<RefreshableOverlay *>(contents);
1913 if (mse)
1914 {
1915 mse->forceRefresh();
1916 }
1917 }
1918
1919 if (showMSEGEditorOnNextIdleOrOpen)
1920 {
1921 showMSEGEditor();
1922 showMSEGEditorOnNextIdleOrOpen = false;
1923 }
1924
1925 // We need this here in case we rebuild when opening a new patch.
1926 // closeMSEGEditor does nothing if the mseg editor isn't open
1927 auto lfoidx = modsource_editor[current_scene] - ms_lfo1;
1928 if (lfoidx >= 0 && lfoidx <= n_lfos)
1929 {
1930 auto ld = &(synth->storage.getPatch().scene[current_scene].lfo[lfoidx]);
1931 if (ld->shape.val.i != lt_mseg)
1932 {
1933 closeMSEGEditor();
1934 }
1935 }
1936
1937 refresh_mod();
1938
1939 editor_open = true;
1940 queue_refresh = false;
1941
1942 /* When we rebuild, the onMouseEntered event is not re-sent to the new component by VSTGui;
1943 * Maybe this is a bug in VSTG? But it screws up our hover states so force an onMouseEntered
1944 * if we are over a coponent
1945 */
1946 CPoint tr;
1947 frame->getCurrentMouseLocation(tr);
1948
1949 // OK to *find* the component we need to look in transformed coordinates
1950 CPoint ttr = tr;
1951 frame->getTransform().transform(ttr);
1952 auto v = frame->getViewAt(ttr);
1953
1954 if (v)
1955 {
1956 // but to *hover* the component we need to do it in 100-scale coordinates
1957 v->onMouseEntered(tr, 0);
1958 }
1959
1960 frame->invalid();
1961 }
1962
close_editor()1963 void SurgeGUIEditor::close_editor()
1964 {
1965 editor_open = false;
1966 lfodisplay = 0;
1967 frame->removeAll(true);
1968 setzero(param);
1969 }
1970
1971 #if !TARGET_VST3
open(void * parent)1972 bool SurgeGUIEditor::open(void *parent)
1973 #else
1974 bool PLUGIN_API SurgeGUIEditor::open(void *parent, const PlatformType &platformType)
1975 #endif
1976 {
1977 if (samplerate == 0)
1978 {
1979 std::cout << "Sample rate was not set when editor opened. Defaulting to 44.1k" << std::endl;
1980
1981 /*
1982 ** The oscillator displays need a sample rate; some test hosts don't call
1983 ** setSampleRate so if we are in this state make the bad but reasonable
1984 ** default choice
1985 */
1986 synth->setSamplerate(44100);
1987 }
1988 #if !TARGET_VST3
1989 // !!! always call this !!!
1990 super::open(parent);
1991
1992 #if !TARGET_JUCE_UI
1993 PlatformType platformType = kDefaultNative;
1994 #else
1995 int platformType = 0;
1996 #endif
1997 #endif
1998
1999 #if TARGET_VST3
2000 #if LINUX
2001 Steinberg::Linux::IRunLoop *l = nullptr;
2002 if (getIPlugFrame()->queryInterface(Steinberg::Linux::IRunLoop::iid, (void **)&l) ==
2003 Steinberg::kResultOk)
2004 {
2005 std::cout << "IPlugFrame is a runloop " << l << std::endl;
2006 }
2007 else
2008 {
2009 std::cout << "IPlugFrame is not a runloop " << l << std::endl;
2010 }
2011 if (l == nullptr)
2012 {
2013 Surge::UserInteractions::promptError("Starting VST3 with null runloop", "Software Error");
2014 }
2015 LinuxVST3Init(l);
2016 #endif
2017 #endif
2018
2019 float fzf = getZoomFactor() / 100.0;
2020 #if TARGET_VST2
2021 CRect wsize(0, 0, currentSkin->getWindowSizeX() * fzf, currentSkin->getWindowSizeY() * fzf);
2022 #else
2023 CRect wsize(0, 0, currentSkin->getWindowSizeX(), currentSkin->getWindowSizeY());
2024 #endif
2025
2026 CFrame *nframe = new CFrame(wsize, this);
2027
2028 #if TARGET_JUCE_UI
2029 nframe->remember();
2030 #endif
2031
2032 bitmapStore.reset(new SurgeBitmaps());
2033 bitmapStore->setupBitmapsForFrame(nframe);
2034 currentSkin->reloadSkin(bitmapStore);
2035 nframe->setZoom(fzf);
2036 /*
2037 * OK so WTF is this? Well:
2038 * CFrame is a CView so it can be a drop thing
2039 * but CFrame is final so you can't subclass it
2040 * You could call setDropTarget on it but that's not what CViewContainers use
2041 * They instead use this attribute, kCViewContainerDropTargetAttribute
2042 * but that's not public so copy its value here
2043 * and add a comment
2044 * and think - sigh. VSTGUI.
2045 */
2046
2047 #if !TARGET_JUCE_UI
2048 dropAdapter = new SGEDropAdapter(this);
2049 dropAdapter->remember();
2050
2051 VSTGUI::IDropTarget *dt = nullptr;
2052 nframe->getAttribute('vcdt', dt);
2053 if (dt)
2054 dt->forget();
2055 nframe->setAttribute('vcdt', dropAdapter);
2056 #endif
2057
2058 frame = nframe;
2059
2060 #if LINUX && TARGET_VST3
2061 LinuxVST3FrameOpen(frame, parent, platformType);
2062 #elif LINUX && TARGET_LV2
2063 VSTGUI::X11::FrameConfig frameConfig;
2064 frameConfig.runLoop = SurgeLv2::createRunLoop(_userdata);
2065 frame->open(parent, platformType, &frameConfig);
2066 #else
2067 frame->open(parent, platformType);
2068 #endif
2069
2070 #if TARGET_VST3 || TARGET_LV2
2071 _idleTimer = VSTGUI::SharedPointer<VSTGUI::CVSTGUITimer>(
2072 new CVSTGUITimer([this](CVSTGUITimer *timer) { idle(); }, 50, false), false);
2073 _idleTimer->start();
2074 #endif
2075
2076 #if TARGET_VST3 && LINUX
2077 firstIdleCountdown = 2;
2078 #endif
2079
2080 /*#if TARGET_AUDIOUNIT
2081 synth = (sub3_synth*)_effect;
2082 #elif TARGET_VST3
2083 //synth = (sub3_synth*)_effect; ??
2084 #else
2085 vstlayer *plug = (vstlayer*)_effect;
2086 if(!plug->initialized) plug->init();
2087 synth = (sub3_synth*)plug->plugin_instance;
2088 #endif*/
2089
2090 /*
2091 ** Register only once (when we open)
2092 */
2093 #if !TARGET_JUCE_UI
2094 frame->registerKeyboardHook(this);
2095 #endif
2096 reloadFromSkin();
2097 openOrRecreateEditor();
2098
2099 if (getZoomFactor() != 100)
2100 {
2101 zoom_callback(this, true);
2102 zoomInvalid = true;
2103 }
2104
2105 // HEREHERE
2106 auto *des = &(synth->storage.getPatch().dawExtraState);
2107
2108 if (des->isPopulated && des->editor.isMSEGOpen)
2109 showMSEGEditor();
2110
2111 return true;
2112 }
2113
close()2114 void SurgeGUIEditor::close()
2115 {
2116 populateDawExtraState(synth);
2117 for (auto el : editorOverlay)
2118 {
2119 frame->removeView(el.second);
2120 auto f = editorOverlayOnClose[el.second];
2121 f();
2122 editorOverlayTagAtClose = el.first;
2123 }
2124 #if TARGET_VST2 // && WINDOWS
2125 // We may need this in other hosts also; but for now
2126 if (frame)
2127 {
2128 getFrame()->unregisterKeyboardHook(this);
2129 frame->close();
2130 frame = nullptr;
2131 }
2132
2133 if (dropAdapter)
2134 {
2135 dropAdapter->buddy = nullptr;
2136 dropAdapter->forget();
2137 dropAdapter = nullptr;
2138 }
2139
2140 #endif
2141
2142 #if !TARGET_VST3
2143 super::close();
2144 #endif
2145
2146 #if TARGET_VST3 || TARGET_LV2
2147 _idleTimer->stop();
2148 _idleTimer = nullptr;
2149 #endif
2150 hasIdleRun = false;
2151 firstIdleCountdown = 0;
2152
2153 #if TARGET_VST3
2154 #if LINUX
2155 LinuxVST3Detatch();
2156 #endif
2157 #endif
2158 }
2159
setParameter(long index,float value)2160 void SurgeGUIEditor::setParameter(long index, float value)
2161 {
2162 if (!frame)
2163 return;
2164 if (!editor_open)
2165 return;
2166 if (index > synth->storage.getPatch().param_ptr.size())
2167 return;
2168
2169 // if(param[index])
2170 {
2171 // param[index]->setValue(value);
2172 int j = 0;
2173 while (j < 7)
2174 {
2175 if ((synth->refresh_ctrl_queue[j] > -1) && (synth->refresh_ctrl_queue[j] != index))
2176 j++;
2177 else
2178 break;
2179 }
2180 synth->refresh_ctrl_queue[j] = index;
2181 synth->refresh_ctrl_queue_value[j] = value;
2182 }
2183 }
2184
decode_controllerid(char * txt,int id)2185 void decode_controllerid(char *txt, int id)
2186 {
2187 int type = (id & 0xffff0000) >> 16;
2188 int num = (id & 0xffff);
2189 switch (type)
2190 {
2191 case 1:
2192 snprintf(txt, TXT_SIZE, "NRPN %i", num);
2193 break;
2194 case 2:
2195 snprintf(txt, TXT_SIZE, "RPN %i", num);
2196 break;
2197 default:
2198 snprintf(txt, TXT_SIZE, "CC %i", num);
2199 break;
2200 };
2201 }
2202
controlModifierClicked(CControl * control,CButtonState button)2203 int32_t SurgeGUIEditor::controlModifierClicked(CControl *control, CButtonState button)
2204 {
2205 if (!synth)
2206 return 0;
2207 if (!frame)
2208 return 0;
2209 if (!editor_open)
2210 return 0;
2211
2212 if (button & (kMButton | kButton4 | kButton5))
2213 {
2214 toggle_mod_editing();
2215 return 1;
2216 }
2217
2218 long tag = control->getTag();
2219
2220 if ((button & kControl) && (tag == tag_mp_patch || tag == tag_mp_category))
2221 {
2222 synth->selectRandomPatch();
2223 return 1;
2224 }
2225
2226 // In these cases just move along with success. RMB does nothing on these switches
2227 if (tag == tag_mp_jogfx || tag == tag_mp_category || tag == tag_mp_patch || tag == tag_store ||
2228 tag == tag_store_cancel || tag == tag_store_ok)
2229 return 1;
2230
2231 if (tag == tag_lfo_menu)
2232 {
2233 control->setValue(0);
2234 CPoint where;
2235 frame->getCurrentMouseLocation(where);
2236 frame->localToFrame(where);
2237
2238 showLfoMenu(where);
2239 return 1;
2240 }
2241
2242 if (tag == tag_status_zoom)
2243 {
2244 CPoint where;
2245 frame->getCurrentMouseLocation(where);
2246 frame->localToFrame(where);
2247
2248 showZoomMenu(where);
2249 return 1;
2250 }
2251 if (tag == tag_status_mpe)
2252 {
2253 CPoint where;
2254 frame->getCurrentMouseLocation(where);
2255 frame->localToFrame(where);
2256
2257 showMPEMenu(where);
2258 return 1;
2259 }
2260 if (tag == tag_status_tune)
2261 {
2262 CPoint where;
2263 frame->getCurrentMouseLocation(where);
2264 frame->localToFrame(where);
2265
2266 showTuningMenu(where);
2267 return 1;
2268 }
2269
2270 std::vector<std::string> clearControlTargetNames;
2271
2272 auto cmensl = dynamic_cast<CMenuAsSlider *>(control);
2273
2274 if (cmensl && cmensl->deactivated)
2275 return 0;
2276
2277 if (button & kRButton)
2278 {
2279 if (tag == tag_settingsmenu)
2280 {
2281 CRect r = control->getViewSize();
2282 CRect menuRect;
2283 CPoint where;
2284 frame->getCurrentMouseLocation(where);
2285 frame->localToFrame(where);
2286
2287 menuRect.offset(where.x, where.y);
2288
2289 useDevMenu = true;
2290 showSettingsMenu(menuRect);
2291 return 1;
2292 }
2293
2294 if (tag == tag_osc_select)
2295 {
2296 CRect r = control->getViewSize();
2297 CRect menuRect;
2298
2299 CPoint where;
2300 frame->getCurrentMouseLocation(where);
2301 frame->localToFrame(where);
2302
2303 int a = limit_range((int)((3 * (where.x - r.left)) / r.getWidth()), 0, 2);
2304 menuRect.offset(where.x, where.y);
2305
2306 COptionMenu *contextMenu =
2307 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
2308 int eid = 0;
2309
2310 char txt[256];
2311 auto hu = helpURLForSpecial("osc-select");
2312 if (hu != "")
2313 {
2314 snprintf(txt, TXT_SIZE, "[?] Osc %i", a + 1);
2315 auto lurl = fullyResolvedHelpURL(hu);
2316 addCallbackMenu(contextMenu, txt,
2317 [lurl]() { Surge::UserInteractions::openURL(lurl); });
2318 eid++;
2319 }
2320 else
2321 {
2322 snprintf(txt, TXT_SIZE, "Osc %i", a + 1);
2323 contextMenu->addEntry(txt, eid++);
2324 }
2325
2326 contextMenu->addSeparator(eid++);
2327 addCallbackMenu(contextMenu, "Copy", [this, a]() {
2328 synth->storage.clipboard_copy(cp_osc, current_scene, a);
2329 });
2330 eid++;
2331
2332 addCallbackMenu(
2333 contextMenu, Surge::UI::toOSCaseForMenu("Copy With Modulation"),
2334 [this, a]() { synth->storage.clipboard_copy(cp_oscmod, current_scene, a); });
2335 eid++;
2336
2337 if (synth->storage.get_clipboard_type() == cp_osc)
2338 {
2339 addCallbackMenu(contextMenu, "Paste", [this, a]() {
2340 synth->clear_osc_modulation(current_scene, a);
2341 synth->storage.clipboard_paste(cp_osc, current_scene, a);
2342 queue_refresh = true;
2343 });
2344 eid++;
2345 }
2346
2347 frame->addView(contextMenu); // add to frame
2348 contextMenu->setDirty();
2349 contextMenu->popup();
2350 frame->removeView(contextMenu, true); // remove from frame and forget
2351
2352 return 1;
2353 }
2354
2355 if (tag == tag_scene_select)
2356 {
2357 CRect r = control->getViewSize();
2358 CRect menuRect;
2359 CPoint where;
2360 frame->getCurrentMouseLocation(where);
2361 frame->localToFrame(where);
2362
2363 // Assume vertical alignment of the scene buttons
2364 int a = limit_range((int)((2 * (where.y - r.top)) / r.getHeight()), 0, n_scenes);
2365
2366 if (auto aschsw = dynamic_cast<CHSwitch2 *>(control))
2367 {
2368 if (aschsw->columns == n_scenes)
2369 {
2370 // We are horizontal due to skins or whatever so
2371 a = limit_range((int)((2 * (where.x - r.left)) / r.getWidth()), 0, n_scenes);
2372 }
2373 }
2374
2375 menuRect.offset(where.x, where.y);
2376
2377 COptionMenu *contextMenu =
2378 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
2379 int eid = 0;
2380
2381 char txt[256];
2382 auto hu = helpURLForSpecial("scene-select");
2383 if (hu != "")
2384 {
2385 snprintf(txt, TXT_SIZE, "[?] Scene %c", 'A' + a);
2386 auto lurl = fullyResolvedHelpURL(hu);
2387 addCallbackMenu(contextMenu, txt,
2388 [lurl]() { Surge::UserInteractions::openURL(lurl); });
2389 eid++;
2390 }
2391 else
2392 {
2393 snprintf(txt, TXT_SIZE, "Scene %c", 'A' + a);
2394 contextMenu->addEntry(txt, eid++);
2395 }
2396
2397 contextMenu->addSeparator(eid++);
2398
2399 addCallbackMenu(contextMenu, "Copy",
2400 [this, a]() { synth->storage.clipboard_copy(cp_scene, a, -1); });
2401 eid++;
2402 if (synth->storage.get_clipboard_type() == cp_scene)
2403 {
2404 addCallbackMenu(contextMenu, "Paste", [this, a]() {
2405 synth->storage.clipboard_paste(cp_scene, a, -1);
2406 queue_refresh = true;
2407 });
2408 eid++;
2409 }
2410
2411 frame->addView(contextMenu); // add to frame
2412 contextMenu->setDirty();
2413 contextMenu->popup();
2414 frame->removeView(contextMenu, true); // remove from frame and forget
2415
2416 return 1;
2417 }
2418 }
2419
2420 if ((tag >= tag_mod_source0) && (tag < tag_mod_source_end))
2421 {
2422 modsources modsource = (modsources)(tag - tag_mod_source0);
2423
2424 if (button & kRButton)
2425 {
2426 CModulationSourceButton *cms = (CModulationSourceButton *)control;
2427 CRect menuRect;
2428 CPoint where;
2429 frame->getCurrentMouseLocation(where);
2430 frame->localToFrame(where);
2431
2432 menuRect.offset(where.x, where.y);
2433 COptionMenu *contextMenu = new COptionMenu(
2434 menuRect, 0, 0, 0, 0,
2435 VSTGUI::COptionMenu::kNoDrawStyle | VSTGUI::COptionMenu::kMultipleCheckStyle);
2436 int eid = 0;
2437
2438 std::string hu;
2439 if (modsource >= ms_ctrl1 && modsource <= ms_ctrl8)
2440 {
2441 hu = helpURLForSpecial("macro-modbutton");
2442 }
2443 else if (modsource >= ms_lfo1 && modsource <= ms_slfo6)
2444 {
2445 hu = helpURLForSpecial("lfo-modbutton");
2446 }
2447 else if ((modsource >= ms_ampeg && modsource <= ms_filtereg) ||
2448 (modsource >= ms_random_bipolar && modsource <= ms_alternate_unipolar))
2449 {
2450 hu = helpURLForSpecial("internalmod-modbutton");
2451 }
2452
2453 else
2454 {
2455 hu = helpURLForSpecial("other-modbutton");
2456 }
2457
2458 if (cms->hasAlternate)
2459 {
2460 int idOn = modsource;
2461 int idOff = cms->alternateId;
2462 if (cms->useAlternate)
2463 {
2464 auto t = idOn;
2465 idOn = idOff;
2466 idOff = t;
2467 }
2468
2469 if (hu != "")
2470 {
2471 auto lurl = fullyResolvedHelpURL(hu);
2472 std::string hs = std::string("[?] ") + (char *)modsource_names[idOn];
2473 addCallbackMenu(contextMenu, hs,
2474 [lurl]() { Surge::UserInteractions::openURL(lurl); });
2475 eid++;
2476 }
2477 else
2478 {
2479 contextMenu->addEntry((char *)modsource_names[idOn], eid++);
2480 }
2481 std::string offLab = "Switch to ";
2482 offLab += modsource_names[idOff];
2483 bool activeMod = (cms->state & 3) == 2;
2484
2485 auto *mi = addCallbackMenu(contextMenu, offLab, [this, modsource, cms]() {
2486 cms->setUseAlternate(!cms->useAlternate);
2487 modsource_is_alternate[modsource] = cms->useAlternate;
2488 this->refresh_mod();
2489 });
2490 if (activeMod)
2491 mi->setEnabled(false);
2492 eid++;
2493 }
2494 else
2495 {
2496 if (hu != "")
2497 {
2498 auto lurl = fullyResolvedHelpURL(hu);
2499 std::string hs = std::string("[?] ") + modulatorName(modsource, false);
2500 addCallbackMenu(contextMenu, hs,
2501 [lurl]() { Surge::UserInteractions::openURL(lurl); });
2502 eid++;
2503 }
2504 else
2505 {
2506 contextMenu->addEntry((char *)modulatorName(modsource, false).c_str(), eid++);
2507 }
2508 }
2509
2510 int n_total_md = synth->storage.getPatch().param_ptr.size();
2511
2512 const int max_md = 4096;
2513 assert(max_md >= n_total_md);
2514
2515 bool cancellearn = false;
2516 int ccid = 0;
2517
2518 int detailedMode = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
2519 "highPrecisionReadouts", 0);
2520
2521 // should start at 0, but started at 1 before.. might be a reason but don't remember
2522 // why...
2523 std::vector<modsources> possibleSources;
2524 possibleSources.push_back(modsource);
2525 if (cms->hasAlternate)
2526 {
2527 possibleSources.push_back((modsources)(cms->alternateId));
2528 }
2529
2530 for (auto thisms : possibleSources)
2531 {
2532 bool first_destination = true;
2533 int n_md = 0;
2534
2535 for (int md = 0; md < n_total_md; md++)
2536 {
2537 auto activeScene = synth->storage.getPatch().scene_active.val.i;
2538 Parameter *parameter = synth->storage.getPatch().param_ptr[md];
2539
2540 if (((md < n_global_params) || ((parameter->scene - 1) == activeScene)) &&
2541 synth->isActiveModulation(md, thisms))
2542 {
2543 char modtxt[TXT_SIZE];
2544 auto pmd = synth->storage.getPatch().param_ptr[md];
2545 pmd->get_display_of_modulation_depth(modtxt, synth->getModDepth(md, thisms),
2546 synth->isBipolarModulation(thisms),
2547 Parameter::Menu);
2548 char tmptxt[1024]; // leave room for that ubuntu 20.0 error
2549 if (pmd->ctrlgroup == cg_LFO)
2550 {
2551 char pname[TXT_SIZE];
2552 pmd->create_fullname(pmd->get_name(), pname, pmd->ctrlgroup,
2553 pmd->ctrlgroup_entry,
2554 modulatorName(pmd->ctrlgroup_entry, true).c_str());
2555 snprintf(tmptxt, TXT_SIZE, "Edit %s -> %s: %s",
2556 (char *)modulatorName(thisms, true).c_str(), pname, modtxt);
2557 }
2558 else
2559 {
2560 snprintf(tmptxt, TXT_SIZE, "Edit %s -> %s: %s",
2561 (char *)modulatorName(thisms, true).c_str(),
2562 pmd->get_full_name(), modtxt);
2563 }
2564
2565 auto clearOp = [this, parameter, control, thisms]() {
2566 this->promptForUserValueEntry(parameter, control, thisms);
2567 };
2568
2569 if (first_destination)
2570 {
2571 contextMenu->addSeparator(eid++);
2572 first_destination = false;
2573 }
2574
2575 addCallbackMenu(contextMenu, tmptxt, clearOp);
2576 eid++;
2577 }
2578 }
2579
2580 first_destination = true;
2581 for (int md = 0; md < n_total_md; md++)
2582 {
2583 auto activeScene = synth->storage.getPatch().scene_active.val.i;
2584 Parameter *parameter = synth->storage.getPatch().param_ptr[md];
2585
2586 if (((md < n_global_params) || ((parameter->scene - 1) == activeScene)) &&
2587 synth->isActiveModulation(md, thisms))
2588 {
2589 char tmptxt[1024];
2590 if (parameter->ctrlgroup == cg_LFO)
2591 {
2592 char pname[512];
2593 parameter->create_fullname(
2594 parameter->get_name(), pname, parameter->ctrlgroup,
2595 parameter->ctrlgroup_entry,
2596 modulatorName(parameter->ctrlgroup_entry, true).c_str());
2597 snprintf(tmptxt, TXT_SIZE, "Clear %s -> %s",
2598 (char *)modulatorName(thisms, true).c_str(), pname);
2599 }
2600 else
2601 {
2602 snprintf(tmptxt, TXT_SIZE, "Clear %s -> %s",
2603 (char *)modulatorName(thisms, true).c_str(),
2604 parameter->get_full_name());
2605 }
2606
2607 auto clearOp = [this, first_destination, md, n_total_md, thisms,
2608 control]() {
2609 bool resetName = false; // Should I reset the name?
2610 std::string newName = ""; // And to what?
2611 int ccid = thisms - ms_ctrl1;
2612
2613 if (first_destination)
2614 {
2615 if (strncmp(synth->storage.getPatch().CustomControllerLabel[ccid],
2616 synth->storage.getPatch().param_ptr[md]->get_name(),
2617 15) == 0)
2618 {
2619 // So my modulator is named after my short name. I haven't been
2620 // renamed. So I want to reset at least to "-" unless someone is
2621 // after me
2622 resetName = true;
2623 newName = "-";
2624
2625 // Now we have to find if there's another modulation below me
2626 int nextmd = md + 1;
2627 while (nextmd < n_total_md &&
2628 !synth->isActiveModulation(nextmd, thisms))
2629 nextmd++;
2630 if (nextmd < n_total_md && strlen(synth->storage.getPatch()
2631 .param_ptr[nextmd]
2632 ->get_name()) > 1)
2633 newName =
2634 synth->storage.getPatch().param_ptr[nextmd]->get_name();
2635 }
2636 }
2637
2638 synth->clearModulation(md, thisms);
2639 refresh_mod();
2640 control->setDirty();
2641 control->invalid();
2642
2643 if (resetName)
2644 {
2645 // And this is where we apply the name refresh, of course.
2646 strxcpy(synth->storage.getPatch().CustomControllerLabel[ccid],
2647 newName.c_str(), 15);
2648 synth->storage.getPatch().CustomControllerLabel[ccid][15] = 0;
2649 ((CModulationSourceButton *)control)
2650 ->setlabel(
2651 synth->storage.getPatch().CustomControllerLabel[ccid]);
2652 control->setDirty();
2653 control->invalid();
2654 synth->updateDisplay();
2655 }
2656 };
2657
2658 if (first_destination)
2659 {
2660 contextMenu->addSeparator(eid++);
2661 first_destination = false;
2662 }
2663
2664 addCallbackMenu(contextMenu, tmptxt, clearOp);
2665 eid++;
2666
2667 n_md++;
2668 }
2669 }
2670 if (n_md)
2671 {
2672 std::string clearLab;
2673 std::string modName;
2674
2675 // hacky, but works - we want to retain the capitalization for modulator names
2676 // regardless of OS!
2677 modName = modulatorName(thisms, false);
2678 clearLab = Surge::UI::toOSCaseForMenu("Clear All ") + modName +
2679 Surge::UI::toOSCaseForMenu(" Routings");
2680
2681 addCallbackMenu(contextMenu, clearLab, [this, n_total_md, thisms, control]() {
2682 for (int md = 1; md < n_total_md; md++)
2683 synth->clearModulation(md, thisms);
2684 refresh_mod();
2685
2686 // Also blank out the name and rebuild the UI
2687 if (within_range(ms_ctrl1, thisms, ms_ctrl1 + n_customcontrollers - 1))
2688 {
2689 int ccid = thisms - ms_ctrl1;
2690
2691 synth->storage.getPatch().CustomControllerLabel[ccid][0] = '-';
2692 synth->storage.getPatch().CustomControllerLabel[ccid][1] = 0;
2693 ((CModulationSourceButton *)control)
2694 ->setlabel(synth->storage.getPatch().CustomControllerLabel[ccid]);
2695 }
2696 control->setDirty(true);
2697 control->invalid();
2698 synth->updateDisplay();
2699 });
2700 eid++;
2701 }
2702 }
2703 int sc = limit_range(synth->storage.getPatch().scene_active.val.i, 0, n_scenes - 1);
2704 #if TARGET_VST3
2705 Steinberg::Vst::IContextMenu *hostMenu = nullptr;
2706 #endif
2707
2708 if (within_range(ms_ctrl1, modsource, ms_ctrl1 + n_customcontrollers - 1))
2709 {
2710 /*
2711 ** This is the menu for the controls
2712 */
2713
2714 ccid = modsource - ms_ctrl1;
2715
2716 auto cms = ((ControllerModulationSource *)synth->storage.getPatch()
2717 .scene[current_scene]
2718 .modsources[modsource]);
2719
2720 contextMenu->addSeparator(eid++);
2721 char vtxt[1024];
2722 snprintf(vtxt, 1024, "%s: %.*f %%",
2723 Surge::UI::toOSCaseForMenu("Edit Value").c_str(), (detailedMode ? 6 : 2),
2724 100 * cms->get_output());
2725 addCallbackMenu(contextMenu, vtxt, [this, control, modsource]() {
2726 promptForUserValueEntry(nullptr, control, modsource);
2727 });
2728 eid++;
2729
2730 contextMenu->addSeparator(eid++);
2731
2732 char txt[TXT_SIZE];
2733
2734 // Construct submenus for explicit controller mapping
2735 COptionMenu *midiSub = new COptionMenu(
2736 menuRect, 0, 0, 0, 0,
2737 VSTGUI::COptionMenu::kNoDrawStyle | VSTGUI::COptionMenu::kMultipleCheckStyle);
2738 COptionMenu *currentSub = nullptr;
2739
2740 for (int subs = 0; subs < 7; ++subs)
2741 {
2742 if (currentSub)
2743 {
2744 currentSub->forget();
2745 currentSub = nullptr;
2746 }
2747 currentSub = new COptionMenu(menuRect, 0, 0, 0, 0,
2748 VSTGUI::COptionMenu::kNoDrawStyle |
2749 VSTGUI::COptionMenu::kMultipleCheckStyle);
2750
2751 char name[16];
2752
2753 snprintf(name, 16, "CC %d ... %d", subs * 20, min((subs * 20) + 20, 128) - 1);
2754
2755 auto added_to_menu = midiSub->addEntry(currentSub, name);
2756
2757 for (int item = 0; item < 20; ++item)
2758 {
2759 int mc = (subs * 20) + item;
2760 int disabled = 0;
2761
2762 // these CCs cannot be used for MIDI learn (see
2763 // SurgeSynthesizer::channelController)
2764 if (mc == 0 || mc == 6 || mc == 32 || mc == 38 || mc == 64 ||
2765 (mc == 74 && synth->mpeEnabled) || (mc >= 98 && mc <= 101) ||
2766 mc == 120 || mc == 123)
2767 disabled = 1;
2768
2769 // we don't have any more CCs to cover, so break the loop
2770 if (mc > 127)
2771 break;
2772
2773 char name[128];
2774
2775 snprintf(name, 128, "CC %d (%s) %s", mc, midicc_names[mc],
2776 (disabled == 1 ? "- RESERVED" : ""));
2777
2778 CCommandMenuItem *cmd = new CCommandMenuItem(CCommandMenuItem::Desc(name));
2779
2780 cmd->setActions([this, ccid, mc](CCommandMenuItem *men) {
2781 synth->storage.controllers[ccid] = mc;
2782 });
2783 cmd->setEnabled(!disabled);
2784
2785 auto added = currentSub->addEntry(cmd);
2786
2787 if (synth->storage.controllers[ccid] == mc)
2788 {
2789 added->setChecked();
2790 added_to_menu->setChecked();
2791 }
2792 }
2793 }
2794
2795 if (currentSub)
2796 {
2797 currentSub->forget();
2798 currentSub = nullptr;
2799 }
2800
2801 contextMenu->addEntry(midiSub, Surge::UI::toOSCaseForMenu("Assign Macro To..."));
2802
2803 eid++;
2804
2805 if (synth->learn_custom > -1 && synth->learn_custom == ccid)
2806 cancellearn = true;
2807
2808 std::string learnTag =
2809 cancellearn ? "Abort Macro MIDI Learn" : "MIDI Learn Macro...";
2810 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu(learnTag),
2811 [this, cancellearn, control, ccid] {
2812 if (cancellearn)
2813 {
2814 hideMidiLearnOverlay();
2815 synth->learn_custom = -1;
2816 }
2817 else
2818 {
2819 showMidiLearnOverlay(control->getViewSize());
2820 synth->learn_custom = ccid;
2821 }
2822 });
2823 eid++;
2824
2825 if (synth->storage.controllers[ccid] >= 0)
2826 {
2827 char txt4[16];
2828 decode_controllerid(txt4, synth->storage.controllers[ccid]);
2829 snprintf(txt, TXT_SIZE, "Clear Learned MIDI (%s ", txt4);
2830 addCallbackMenu(contextMenu,
2831 Surge::UI::toOSCaseForMenu(txt) +
2832 midicc_names[synth->storage.controllers[ccid]] + ")",
2833 [this, ccid]() { synth->storage.controllers[ccid] = -1; });
2834 eid++;
2835 }
2836
2837 contextMenu->addSeparator(eid++);
2838
2839 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Bipolar Mode"),
2840 [this, control, ccid]() {
2841 bool bp = !synth->storage.getPatch()
2842 .scene[current_scene]
2843 .modsources[ms_ctrl1 + ccid]
2844 ->is_bipolar();
2845 synth->storage.getPatch()
2846 .scene[current_scene]
2847 .modsources[ms_ctrl1 + ccid]
2848 ->set_bipolar(bp);
2849
2850 float f = synth->storage.getPatch()
2851 .scene[current_scene]
2852 .modsources[ms_ctrl1 + ccid]
2853 ->get_output01();
2854 control->setValue(f);
2855 ((CModulationSourceButton *)control)->setBipolar(bp);
2856 refresh_mod();
2857 });
2858 contextMenu->checkEntry(eid, synth->storage.getPatch()
2859 .scene[current_scene]
2860 .modsources[ms_ctrl1 + ccid]
2861 ->is_bipolar());
2862 eid++;
2863
2864 addCallbackMenu(
2865 contextMenu, Surge::UI::toOSCaseForMenu("Rename Macro..."),
2866 [this, control, ccid, menuRect]() {
2867 std::string pval = synth->storage.getPatch().CustomControllerLabel[ccid];
2868 if (pval == "-")
2869 pval = "";
2870 promptForMiniEdit(
2871 pval, "Enter a new name for the macro:", "Rename Macro",
2872 menuRect.getTopLeft(), [this, control, ccid](const std::string &s) {
2873 auto useS = s;
2874 if (useS == "")
2875 useS = "-";
2876 strxcpy(synth->storage.getPatch().CustomControllerLabel[ccid],
2877 useS.c_str(), 16);
2878 synth->storage.getPatch().CustomControllerLabel[ccid][15] =
2879 0; // to be sure
2880 ((CModulationSourceButton *)control)
2881 ->setlabel(
2882 synth->storage.getPatch().CustomControllerLabel[ccid]);
2883
2884 control->setDirty();
2885 control->invalid();
2886 synth->refresh_editor = true;
2887 // synth->updateDisplay();
2888 });
2889 });
2890 eid++;
2891
2892 #if TARGET_VST3
2893 SurgeSynthesizer::ID mid;
2894 if (synth->fromSynthSideId(modsource - ms_ctrl1 + metaparam_offset, mid))
2895 hostMenu = addVst3MenuForParams(contextMenu, mid, eid);
2896 #endif
2897
2898 midiSub->forget();
2899 }
2900
2901 int lfo_id = isLFO(modsource) ? modsource - ms_lfo1 : -1;
2902
2903 if (lfo_id >= 0)
2904 {
2905 contextMenu->addSeparator(eid++);
2906 addCallbackMenu(contextMenu, "Copy", [this, sc, lfo_id]() {
2907 if (lfo_id >= 0)
2908 synth->storage.clipboard_copy(cp_lfo, sc, lfo_id);
2909 mostRecentCopiedMSEGState = msegEditState[sc][lfo_id];
2910 });
2911 eid++;
2912
2913 if (synth->storage.get_clipboard_type() == cp_lfo)
2914 {
2915 addCallbackMenu(contextMenu, "Paste", [this, sc, lfo_id]() {
2916 if (lfo_id >= 0)
2917 synth->storage.clipboard_paste(cp_lfo, sc, lfo_id);
2918 msegEditState[sc][lfo_id] = mostRecentCopiedMSEGState;
2919 queue_refresh = true;
2920 });
2921 eid++;
2922 }
2923 }
2924 frame->addView(contextMenu); // add to frame
2925 contextMenu->setDirty();
2926 contextMenu->popup();
2927 frame->removeView(contextMenu, true); // remove from frame and forget
2928
2929 #if TARGET_VST3
2930 if (hostMenu)
2931 hostMenu->release();
2932 #endif
2933
2934 return 1;
2935 }
2936 return 0;
2937 }
2938
2939 if (tag < start_paramtags)
2940 return 0;
2941
2942 if (!(button & (kDoubleClick | kRButton | kControl)))
2943 return 0;
2944
2945 int ptag = tag - start_paramtags;
2946
2947 if ((ptag >= 0) && (ptag < synth->storage.getPatch().param_ptr.size()))
2948 {
2949 Parameter *p = synth->storage.getPatch().param_ptr[ptag];
2950
2951 // don't show RMB context menu for filter subtype if it's hidden/not applicable
2952 auto f1type = synth->storage.getPatch().scene[current_scene].filterunit[0].type.val.i;
2953 auto f2type = synth->storage.getPatch().scene[current_scene].filterunit[1].type.val.i;
2954
2955 if (tag == f1subtypetag && (f1type == fut_none || f1type == fut_SNH))
2956 return 1;
2957 if (tag == f2subtypetag && (f2type == fut_none || f2type == fut_SNH))
2958 return 1;
2959
2960 bool blockForLFO = false;
2961 if (p->ctrltype == ct_lfotype)
2962 {
2963 blockForLFO = true;
2964 auto *clfo = dynamic_cast<CLFOGui *>(control);
2965 if (clfo)
2966 {
2967 CPoint where;
2968 frame->getCurrentMouseLocation(where);
2969 frame->localToFrame(where);
2970
2971 blockForLFO = !clfo->insideTypeSelector(where);
2972 }
2973 }
2974
2975 // all the RMB context menus
2976 if ((button & kRButton) && !blockForLFO)
2977 {
2978 CRect menuRect;
2979 CPoint where;
2980 frame->getCurrentMouseLocation(where);
2981 frame->localToFrame(where);
2982
2983 menuRect.offset(where.x, where.y);
2984
2985 COptionMenu *contextMenu = new COptionMenu(
2986 menuRect, 0, 0, 0, 0,
2987 VSTGUI::COptionMenu::kNoDrawStyle | VSTGUI::COptionMenu::kMultipleCheckStyle);
2988 int eid = 0;
2989
2990 // FIXME - only fail at this once
2991 /* if( bitmapStore->getBitmapByPath( synth->storage.datapath +
2992 "skins/shared/help-24.svg" ) == nullptr )
2993 {
2994 std::cout << "LOADING BMP" << std::endl;
2995 bitmapStore->loadBitmapByPath( synth->storage.datapath + "skins/shared/help-24.svg"
2996 );
2997 }
2998 auto helpbmp = bitmapStore->getBitmapByPath( synth->storage.datapath +
2999 "skins/shared/help-24.svg" ); std::cout << "HELPBMP is " << helpbmp << std::endl; */
3000 // auto pp = IPlatformBitmap::createFromPath( (synth->storage.datapath +
3001 // "skins/shared/help-14.png" ).c_str() ); auto helpbmp = new CBitmap( pp );
3002
3003 std::string helpurl = helpURLFor(p);
3004
3005 if (helpurl == "")
3006 {
3007 contextMenu->addEntry((char *)p->get_full_name(), eid++);
3008 }
3009 else
3010 {
3011 std::string helpstr = "[?] ";
3012 auto lurl = fullyResolvedHelpURL(helpurl);
3013 addCallbackMenu(contextMenu, std::string(helpstr + p->get_full_name()).c_str(),
3014 [lurl]() { Surge::UserInteractions::openURL(lurl); });
3015 eid++;
3016 }
3017
3018 contextMenu->addSeparator(eid++);
3019
3020 char txt[TXT_SIZE], txt2[512];
3021 p->get_display(txt);
3022 snprintf(txt2, 512, "%s: %s", Surge::UI::toOSCaseForMenu("Edit Value").c_str(), txt);
3023
3024 if (p->valtype == vt_float)
3025 {
3026 if (p->can_temposync() && p->temposync)
3027 {
3028 COptionMenu *tsMenuR =
3029 new COptionMenu(menuRect, 0, 0, 0, 0,
3030 VSTGUI::COptionMenu::kNoDrawStyle |
3031 VSTGUI::COptionMenu::kMultipleCheckStyle);
3032 COptionMenu *tsMenuD =
3033 new COptionMenu(menuRect, 0, 0, 0, 0,
3034 VSTGUI::COptionMenu::kNoDrawStyle |
3035 VSTGUI::COptionMenu::kMultipleCheckStyle);
3036 COptionMenu *tsMenuT =
3037 new COptionMenu(menuRect, 0, 0, 0, 0,
3038 VSTGUI::COptionMenu::kNoDrawStyle |
3039 VSTGUI::COptionMenu::kMultipleCheckStyle);
3040
3041 // have 0 1 2 then 0 + log2 1.5 1 + log2 1.5 then 0 + log2 1.33 and so on
3042 for (int i = p->val_min.f; i <= p->val_max.f; ++i)
3043 {
3044 float mul = 1.0;
3045 float triplaboff = log2(1.33333333);
3046 float tripoff = triplaboff;
3047 float dotlaboff = log2(1.5);
3048 float dotoff = dotlaboff;
3049 if (p->ctrltype == ct_lforate || p->ctrltype == ct_lforate_deactivatable)
3050 {
3051 mul = -1.0;
3052 triplaboff = log2(1.5);
3053 tripoff = triplaboff;
3054 dotlaboff = log2(1.3333333333);
3055 dotoff = dotlaboff;
3056 }
3057 addCallbackMenu(tsMenuR, p->tempoSyncNotationValue(mul * ((float)i)),
3058 [p, i, this]() {
3059 p->val.f = (float)i;
3060 p->bound_value();
3061 this->synth->refresh_editor = true;
3062 });
3063 addCallbackMenu(tsMenuD,
3064 p->tempoSyncNotationValue(mul * ((float)i + dotlaboff)),
3065 [p, i, dotoff, this]() {
3066 p->val.f = (float)i + dotoff;
3067 p->bound_value();
3068 this->synth->refresh_editor = true;
3069 });
3070 addCallbackMenu(tsMenuT,
3071 p->tempoSyncNotationValue(mul * ((float)i + triplaboff)),
3072 [p, i, tripoff, this]() {
3073 p->val.f = (float)i + tripoff;
3074 p->bound_value();
3075 this->synth->refresh_editor = true;
3076 });
3077 }
3078 contextMenu->addEntry(txt2);
3079 eid++;
3080 contextMenu->addSeparator();
3081 eid++;
3082 contextMenu->addEntry(tsMenuR, "Straight");
3083 tsMenuR->forget();
3084 eid++;
3085 contextMenu->addEntry(tsMenuD, "Dotted");
3086 tsMenuD->forget();
3087 eid++;
3088 contextMenu->addEntry(tsMenuT, "Triplet");
3089 tsMenuT->forget();
3090 eid++;
3091 contextMenu->addSeparator();
3092 eid++;
3093 }
3094 else
3095 {
3096 addCallbackMenu(contextMenu, txt2, [this, p, control]() {
3097 this->promptForUserValueEntry(p, control);
3098 });
3099 eid++;
3100 }
3101 }
3102 else if (p->valtype == vt_bool)
3103 {
3104 // FIXME - make this a checked toggle
3105 auto b = addCallbackMenu(contextMenu, txt2, [this, p, control]() {
3106 SurgeSynthesizer::ID pid;
3107 if (synth->fromSynthSideId(p->id, pid))
3108 {
3109 synth->setParameter01(pid, !p->val.b, false, false);
3110 repushAutomationFor(p);
3111 synth->refresh_editor = true;
3112 }
3113 });
3114
3115 eid++;
3116
3117 if (p->val.b)
3118 {
3119 b->setChecked(true);
3120 }
3121
3122 if (p->ctrltype == ct_bool_keytrack || p->ctrltype == ct_bool_retrigger)
3123 {
3124 std::vector<Parameter *> impactedParms;
3125 std::vector<std::string> tgltxt = {"Enable", "Disable"};
3126 std::string parname = "Keytrack";
3127 if (p->ctrltype == ct_bool_retrigger)
3128 {
3129 parname = "Retrigger";
3130 }
3131
3132 int currvals = 0;
3133 // There is surely a more efficient way but this is fine
3134 for (auto iter = this->synth->storage.getPatch().param_ptr.begin();
3135 iter != this->synth->storage.getPatch().param_ptr.end(); iter++)
3136 {
3137 Parameter *pl = *iter;
3138 if (pl->ctrltype == p->ctrltype && pl->scene == p->scene)
3139 {
3140 currvals += pl->val.b;
3141 impactedParms.push_back(pl);
3142 }
3143 }
3144
3145 int ktsw = (currvals == n_oscs);
3146
3147 auto txt3 = Surge::UI::toOSCaseForMenu(tgltxt[ktsw] + " " + parname +
3148 " for All Oscillators");
3149
3150 auto b =
3151 addCallbackMenu(contextMenu, txt3.c_str(), [this, ktsw, impactedParms]() {
3152 for (auto *p : impactedParms)
3153 {
3154 SurgeSynthesizer::ID pid;
3155
3156 if (synth->fromSynthSideId(p->id, pid))
3157 {
3158 synth->setParameter01(pid, 1 - ktsw, false, false);
3159 repushAutomationFor(p);
3160 }
3161 }
3162
3163 synth->refresh_editor = true;
3164 });
3165
3166 eid++;
3167 }
3168 }
3169 else if (p->valtype == vt_int)
3170 {
3171 if (p->can_setvalue_from_string())
3172 {
3173 addCallbackMenu(contextMenu, txt2, [this, p, control]() {
3174 this->promptForUserValueEntry(p, control);
3175 });
3176 eid++;
3177 }
3178 else
3179 {
3180 int incr = 1;
3181
3182 if (p->ctrltype == ct_vocoder_bandcount)
3183 incr = 4;
3184
3185 // we have a case where the number of menu entries to be generated depends on
3186 // another parameter so instead of using val_max.i directly, store it to local
3187 // var and modify its value when required
3188 int max = p->val_max.i;
3189
3190 // currently we only have this case with filter subtypes - different filter
3191 // types have a different number of them so let's do this!
3192 bool isCombOnSubtype = false;
3193 if (p->ctrltype == ct_filtersubtype)
3194 {
3195 auto ftype = synth->storage.getPatch()
3196 .scene[current_scene]
3197 .filterunit[p->ctrlgroup_entry]
3198 .type.val.i;
3199 if (ftype == fut_comb_pos || ftype == fut_comb_neg)
3200 isCombOnSubtype = true;
3201 max = fut_subcount[ftype] - 1;
3202 }
3203
3204 auto dm = dynamic_cast<ParameterDiscreteIndexRemapper *>(p->user_data);
3205 if (dm)
3206 {
3207 std::unordered_map<std::string, std::map<int, int>> reorderMap;
3208 std::vector<std::string> groupAppearanceOrder;
3209 for (int i = p->val_min.i; i <= max; i += incr)
3210 {
3211 int idx = dm->remapStreamedIndexToDisplayIndex(i);
3212 if (idx >= 0)
3213 {
3214 auto gn = dm->groupNameAtStreamedIndex(i);
3215 if (reorderMap.find(gn) == reorderMap.end())
3216 {
3217 groupAppearanceOrder.push_back(gn);
3218 }
3219
3220 reorderMap[gn][idx] = i;
3221 }
3222 }
3223
3224 if (dm->sortGroupNames())
3225 std::sort(groupAppearanceOrder.begin(), groupAppearanceOrder.end());
3226 else if (dm->useRemappedOrderingForGroupsIfNotSorted())
3227 {
3228 groupAppearanceOrder.clear();
3229 std::map<int, std::string> gnr;
3230 for (int i = p->val_min.i; i <= max; i += incr)
3231 {
3232 int idx = dm->remapStreamedIndexToDisplayIndex(i);
3233 if (idx >= 0)
3234 {
3235 gnr[idx] = dm->groupNameAtStreamedIndex(i);
3236 }
3237 }
3238 std::unordered_set<std::string> dealt;
3239 for (const auto &p : gnr)
3240 {
3241 if (dealt.find(p.second) == dealt.end())
3242 {
3243 groupAppearanceOrder.push_back(p.second);
3244 dealt.insert(p.second);
3245 }
3246 }
3247 }
3248
3249 bool useSubMenus = dm->hasGroupNames();
3250 COptionMenu *sub = nullptr;
3251 for (auto grpN : groupAppearanceOrder)
3252 {
3253 auto grp = reorderMap[grpN];
3254 bool checkSub = false;
3255 if (useSubMenus)
3256 {
3257 sub = new COptionMenu(menuRect, 0, 0, 0, 0,
3258 VSTGUI::COptionMenu::kNoDrawStyle |
3259 VSTGUI::COptionMenu::kMultipleCheckStyle);
3260 }
3261 for (auto rp : grp)
3262 {
3263 int i = rp.second;
3264 std::string displaytxt = dm->nameAtStreamedIndex(i);
3265 auto addTo = useSubMenus ? sub : contextMenu;
3266 if (useSubMenus && grpN == "")
3267 addTo = contextMenu;
3268
3269 #if WINDOWS
3270 Surge::Storage::findReplaceSubstring(displaytxt, std::string("&"),
3271 std::string("&&"));
3272 #endif
3273
3274 auto b =
3275 addCallbackMenu(addTo, displaytxt.c_str(), [this, p, i, tag]() {
3276 float ef = Parameter::intScaledToFloat(i, p->val_max.i,
3277 p->val_min.i);
3278 synth->setParameter01(synth->idForParameter(p), ef, false,
3279 false);
3280 repushAutomationFor(p);
3281 synth->refresh_editor = true;
3282 });
3283 eid++;
3284 if (i == p->val.i)
3285 {
3286 b->setChecked(true);
3287 checkSub = true;
3288 }
3289 }
3290 if (useSubMenus && grpN != "")
3291 {
3292 auto e = contextMenu->addEntry(sub, grpN.c_str());
3293 sub->forget();
3294 sub = nullptr;
3295 e->setChecked(checkSub);
3296 }
3297 }
3298 }
3299 else
3300 {
3301 for (int i = p->val_min.i; i <= max; i += incr)
3302 {
3303 char txt[256];
3304 float ef = (1.0f * i - p->val_min.i) / (p->val_max.i - p->val_min.i);
3305 p->get_display(txt, true, ef);
3306
3307 std::string displaytxt = txt;
3308
3309 #if WINDOWS
3310 Surge::Storage::findReplaceSubstring(displaytxt, std::string("&"),
3311 std::string("&&"));
3312 #endif
3313
3314 auto b = addCallbackMenu(
3315 contextMenu, displaytxt.c_str(), [this, ef, p, i]() {
3316 synth->setParameter01(synth->idForParameter(p), ef, false,
3317 false);
3318 repushAutomationFor(p);
3319 synth->refresh_editor = true;
3320 });
3321 eid++;
3322 if (i == p->val.i)
3323 b->setChecked(true);
3324 }
3325 if (isCombOnSubtype)
3326 {
3327 contextMenu->addSeparator();
3328 auto m = addCallbackMenu(
3329 contextMenu, Surge::UI::toOSCaseForMenu("Precise Tuning"),
3330 [this]() {
3331 synth->storage.getPatch().correctlyTuneCombFilter =
3332 !synth->storage.getPatch().correctlyTuneCombFilter;
3333 });
3334 m->setChecked(synth->storage.getPatch().correctlyTuneCombFilter);
3335 }
3336
3337 if (p->ctrltype == ct_polymode &&
3338 (p->val.i == pm_mono || p->val.i == pm_mono_st ||
3339 p->val.i == pm_mono_fp || p->val.i == pm_mono_st_fp))
3340 {
3341 std::vector<std::string> labels = {"Last", "High", "Low", "Legacy"};
3342 std::vector<MonoVoicePriorityMode> vals = {
3343 ALWAYS_LATEST, ALWAYS_HIGHEST, ALWAYS_LOWEST,
3344 NOTE_ON_LATEST_RETRIGGER_HIGHEST};
3345 contextMenu->addSeparator();
3346 for (int i = 0; i < 4; ++i)
3347 {
3348 auto m = addCallbackMenu(
3349 contextMenu,
3350 Surge::UI::toOSCaseForMenu(labels[i] + " Note Priority"),
3351 [this, vals, i]() {
3352 synth->storage.getPatch()
3353 .scene[current_scene]
3354 .monoVoicePriorityMode = vals[i];
3355 });
3356 if (vals[i] == synth->storage.getPatch()
3357 .scene[current_scene]
3358 .monoVoicePriorityMode)
3359 m->setChecked(true);
3360 }
3361 contextMenu->addSeparator();
3362 contextMenu->addEntry(
3363 makeMonoModeOptionsMenu(menuRect, false),
3364 Surge::UI::toOSCaseForMenu("Sustain Pedal In Mono Mode"));
3365 }
3366 }
3367 }
3368 }
3369 bool cancellearn = false;
3370
3371 // Modulation and Learn semantics only apply to vt_float types in Surge right now
3372 if (p->valtype == vt_float)
3373 {
3374 // if(p->can_temposync() || p->can_extend_range()) contextMenu->addSeparator(eid++);
3375 if (p->can_temposync())
3376 {
3377 auto r = addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Tempo Sync"),
3378 [this, p, control]() {
3379 p->temposync = !p->temposync;
3380 if (p->temposync)
3381 p->bound_value();
3382 else if (control)
3383 p->set_value_f01(control->getValue());
3384
3385 if (this->lfodisplay)
3386 this->lfodisplay->invalid();
3387 auto *css = dynamic_cast<CSurgeSlider *>(control);
3388 if (css)
3389 {
3390 css->setTempoSync(p->temposync);
3391 css->invalid();
3392 }
3393 });
3394 if (p->temposync)
3395 r->setChecked(true);
3396 eid++;
3397
3398 if (p->ctrlgroup == cg_ENV || p->ctrlgroup == cg_LFO)
3399 {
3400 char label[256];
3401 char prefix[128];
3402 char pars[32];
3403
3404 int a = p->ctrlgroup_entry;
3405
3406 if (p->ctrlgroup == cg_ENV)
3407 {
3408 if (a == 0)
3409 sprintf(prefix, "Amp EG");
3410 else
3411 sprintf(prefix, "Filter EG");
3412 }
3413 else if (p->ctrlgroup == cg_LFO)
3414 {
3415 if (a >= ms_lfo1 && a <= ms_lfo1 + n_lfos_voice)
3416 sprintf(prefix, "Voice LFO %i", a - ms_lfo1 + 1);
3417 else if (a >= ms_slfo1 && a <= ms_slfo1 + n_lfos_scene)
3418 sprintf(prefix, "Scene LFO %i", a - ms_slfo1 + 1);
3419 }
3420
3421 bool setTSTo;
3422
3423 #if WINDOWS
3424 snprintf(pars, 32, "parameters");
3425 #else
3426 snprintf(pars, 32, "Parameters");
3427 #endif
3428
3429 if (p->temposync)
3430 {
3431 snprintf(
3432 label, 256, "%s %s %s",
3433 Surge::UI::toOSCaseForMenu("Disable Tempo Sync for All").c_str(),
3434 prefix, pars);
3435 setTSTo = false;
3436 }
3437 else
3438 {
3439 snprintf(
3440 label, 256, "%s %s %s",
3441 Surge::UI::toOSCaseForMenu("Enable Tempo Sync for All").c_str(),
3442 prefix, pars);
3443 setTSTo = true;
3444 }
3445
3446 addCallbackMenu(contextMenu, label, [this, p, setTSTo]() {
3447 // There is surely a more efficient way but this is fine
3448 for (auto iter = this->synth->storage.getPatch().param_ptr.begin();
3449 iter != this->synth->storage.getPatch().param_ptr.end(); iter++)
3450 {
3451 Parameter *pl = *iter;
3452 if (pl->ctrlgroup_entry == p->ctrlgroup_entry &&
3453 pl->ctrlgroup == p->ctrlgroup && pl->can_temposync())
3454 {
3455 pl->temposync = setTSTo;
3456 if (setTSTo)
3457 pl->bound_value();
3458 }
3459 }
3460 this->synth->refresh_editor = true;
3461 });
3462 eid++;
3463 }
3464 }
3465
3466 switch (p->ctrltype)
3467 {
3468 case ct_freq_audible_with_tunability:
3469 case ct_freq_audible_with_very_low_lowerbound:
3470 addCallbackMenu(
3471 contextMenu,
3472 Surge::UI::toOSCaseForMenu("Reset Filter Cutoff To Keytrack Root"),
3473 [this, p, control] {
3474 auto kr = this->synth->storage.getPatch()
3475 .scene[current_scene]
3476 .keytrack_root.val.i;
3477 p->set_value_f01(p->value_to_normalized(kr - 69));
3478 control->setValue(p->get_value_f01());
3479 });
3480 eid++;
3481 break;
3482
3483 default:
3484 break;
3485 }
3486
3487 if (p->has_portaoptions())
3488 {
3489 contextMenu->addSeparator(eid++);
3490
3491 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Constant Rate"),
3492 [this, p]() { p->porta_constrate = !p->porta_constrate; });
3493 contextMenu->checkEntry(eid, p->porta_constrate);
3494 eid++;
3495
3496 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Glissando"),
3497 [this, p]() { p->porta_gliss = !p->porta_gliss; });
3498 contextMenu->checkEntry(eid, p->porta_gliss);
3499 eid++;
3500
3501 addCallbackMenu(contextMenu,
3502 Surge::UI::toOSCaseForMenu("Retrigger at Scale Degrees"),
3503 [this, p]() { p->porta_retrigger = !p->porta_retrigger; });
3504 contextMenu->checkEntry(eid, p->porta_retrigger);
3505 eid++;
3506
3507 contextMenu->addSeparator(eid++);
3508
3509 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Logarithmic Curve"),
3510 [this, p]() { p->porta_curve = -1; });
3511 contextMenu->checkEntry(eid, (p->porta_curve == -1));
3512 eid++;
3513
3514 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Linear Curve"),
3515 [this, p]() { p->porta_curve = 0; });
3516 contextMenu->checkEntry(eid, (p->porta_curve == 0));
3517 eid++;
3518
3519 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Exponential Curve"),
3520 [this, p]() { p->porta_curve = 1; });
3521 contextMenu->checkEntry(eid, (p->porta_curve == 1));
3522 eid++;
3523 }
3524
3525 if (p->has_deformoptions())
3526 {
3527 switch (p->ctrltype)
3528 {
3529
3530 case ct_lfodeform:
3531 {
3532 auto q = modsource_editor[current_scene];
3533 auto *lfodata =
3534 &(synth->storage.getPatch().scene[current_scene].lfo[q - ms_lfo1]);
3535
3536 if (lt_num_deforms[lfodata->shape.val.i] > 1)
3537 {
3538 contextMenu->addSeparator(eid++);
3539
3540 for (int i = 0; i < lt_num_deforms[lfodata->shape.val.i]; i++)
3541 {
3542 char title[32];
3543 sprintf(title, "Deform Type %d", (i + 1));
3544
3545 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu(title),
3546 [this, p, i]() {
3547 p->deform_type = i;
3548 if (frame)
3549 frame->invalid();
3550 });
3551 contextMenu->checkEntry(eid, (p->deform_type == i));
3552 eid++;
3553 }
3554 }
3555
3556 break;
3557 }
3558 case ct_modern_trimix:
3559 {
3560 contextMenu->addSeparator();
3561 eid++;
3562
3563 std::vector<int> waves = {ModernOscillator::mo_multitypes::momt_triangle,
3564 ModernOscillator::mo_multitypes::momt_sine,
3565 ModernOscillator::mo_multitypes::momt_square};
3566
3567 for (int m : waves)
3568 {
3569 auto mtm =
3570 addCallbackMenu(contextMenu, mo_multitype_names[m], [p, m, this]() {
3571 // p->deform_type = m;
3572 p->deform_type = (p->deform_type & 0xFFF0) | m;
3573 synth->refresh_editor = true;
3574 });
3575
3576 mtm->setChecked((p->deform_type & 0x0F) == m);
3577 eid++;
3578 }
3579
3580 contextMenu->addSeparator();
3581 eid++;
3582
3583 int subosc = ModernOscillator::mo_submask::mo_subone;
3584
3585 auto mtm = addCallbackMenu(
3586 contextMenu, Surge::UI::toOSCaseForMenu("Sub-oscillator Mode"),
3587 [p, subosc, this]() {
3588 auto usubosc = subosc;
3589 int usubskipsync =
3590 p->deform_type & ModernOscillator::mo_submask::mo_subskipsync;
3591
3592 if (p->deform_type & subosc)
3593 {
3594 usubosc = 0;
3595 }
3596
3597 p->deform_type = (p->deform_type & 0xF) | usubosc | usubskipsync;
3598
3599 synth->refresh_editor = true;
3600 });
3601
3602 mtm->setChecked((p->deform_type & subosc));
3603 eid++;
3604
3605 int subskipsync = ModernOscillator::mo_submask::mo_subskipsync;
3606
3607 auto skp = addCallbackMenu(
3608 contextMenu,
3609 Surge::UI::toOSCaseForMenu("Disable Hardsync in Sub-oscillator Mode"),
3610 [p, subskipsync, this]() {
3611 auto usubskipsync = subskipsync;
3612 int usubosc =
3613 p->deform_type & ModernOscillator::mo_submask::mo_subone;
3614
3615 if (p->deform_type & subskipsync)
3616 {
3617 usubskipsync = 0;
3618 }
3619
3620 p->deform_type = (p->deform_type & 0xF) | usubosc | usubskipsync;
3621
3622 synth->refresh_editor = true;
3623 });
3624
3625 skp->setChecked((p->deform_type & subskipsync));
3626 eid++;
3627
3628 break;
3629 }
3630 case ct_alias_mask:
3631 {
3632 contextMenu->addSeparator();
3633 eid++;
3634
3635 auto trimask = addCallbackMenu(
3636 contextMenu,
3637 Surge::UI::toOSCaseForMenu("Triangle not masked after threshold point"),
3638 [p, this]() {
3639 p->deform_type = !p->deform_type;
3640
3641 synth->refresh_editor = true;
3642 });
3643
3644 trimask->setChecked(p->deform_type);
3645 eid++;
3646
3647 break;
3648 }
3649 default:
3650 {
3651 break;
3652 }
3653 }
3654 }
3655
3656 if (p->can_extend_range())
3657 {
3658 if (!(p->ctrltype == ct_fmratio && p->can_be_absolute() && p->absolute))
3659 {
3660 bool enable = true;
3661 bool visible = true;
3662 std::string txt = "Extend Range";
3663 switch (p->ctrltype)
3664 {
3665 case ct_reson_res_extendable:
3666 txt = "Modulation Extends into Self-oscillation";
3667 break;
3668 case ct_freq_audible_with_tunability:
3669 case ct_freq_audible_with_very_low_lowerbound:
3670 txt = "Apply SCL/KBM Tuning to Filter Cutoff";
3671 visible =
3672 synth->storage.tuningApplicationMode == SurgeStorage::RETUNE_ALL;
3673 break;
3674 case ct_percent_oscdrift:
3675 txt = "Randomize Initial Drift Phase";
3676 break;
3677 case ct_twist_aux_mix:
3678 txt = "Pan Main and Auxilliary Signals";
3679 break;
3680 default:
3681 break;
3682 }
3683
3684 if (visible)
3685 {
3686 auto ee = addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu(txt),
3687 [this, p]() {
3688 p->extend_range = !p->extend_range;
3689 this->synth->refresh_editor = true;
3690 });
3691 contextMenu->checkEntry(eid, p->extend_range);
3692 ee->setEnabled(enable);
3693 eid++;
3694 }
3695 }
3696 }
3697
3698 if (p->can_be_absolute())
3699 {
3700 addCallbackMenu(contextMenu, "Absolute", [this, p]() {
3701 p->absolute = !p->absolute;
3702
3703 // FIXME : What's a better aprpoach?
3704 if (p->ctrltype == ct_fmratio)
3705 {
3706 char txt[256], ntxt[256];
3707 memset(txt, 0, 256);
3708 strxcpy(txt, p->get_name(), 256);
3709 if (p->absolute)
3710 {
3711 snprintf(ntxt, 256, "M%c Frequency", txt[1]);
3712 }
3713 else
3714 {
3715 snprintf(ntxt, 256, "M%c Ratio",
3716 txt[1]); // Ladies and gentlemen, MC Ratio!
3717 }
3718 p->set_name(ntxt);
3719 synth->refresh_editor = true;
3720 }
3721 });
3722 contextMenu->checkEntry(eid, p->absolute);
3723 eid++;
3724 }
3725
3726 if (p->can_deactivate() || p->get_primary_deactivation_driver())
3727 {
3728 auto q = p->get_primary_deactivation_driver();
3729 if (!q)
3730 {
3731 q = p;
3732 }
3733
3734 std::string txt;
3735
3736 if (q->deactivated)
3737 {
3738 if (q->ctrltype == ct_envtime_linkable_delay)
3739 {
3740 txt = Surge::UI::toOSCaseForMenu("Unlink from Left Channel");
3741 }
3742 else
3743 {
3744 txt = Surge::UI::toOSCaseForMenu("Activate");
3745 }
3746
3747 addCallbackMenu(contextMenu, txt.c_str(), [this, q]() {
3748 q->deactivated = false;
3749 this->synth->refresh_editor = true;
3750 });
3751 }
3752 else
3753 {
3754 if (q->ctrltype == ct_envtime_linkable_delay)
3755 {
3756 txt = Surge::UI::toOSCaseForMenu("Link to Left Channel");
3757 }
3758 else
3759 {
3760 txt = Surge::UI::toOSCaseForMenu("Deactivate");
3761 }
3762
3763 addCallbackMenu(contextMenu, txt.c_str(), [this, q]() {
3764 q->deactivated = true;
3765 this->synth->refresh_editor = true;
3766 });
3767 }
3768
3769 eid++;
3770 }
3771
3772 contextMenu->addSeparator(eid++);
3773
3774 // Construct submenus for explicit controller mapping
3775 COptionMenu *midiSub = new COptionMenu(
3776 menuRect, 0, 0, 0, 0,
3777 VSTGUI::COptionMenu::kNoDrawStyle | VSTGUI::COptionMenu::kMultipleCheckStyle);
3778 COptionMenu *currentSub = nullptr;
3779
3780 for (int subs = 0; subs < 7; ++subs)
3781 {
3782 if (currentSub)
3783 {
3784 currentSub->forget();
3785 currentSub = nullptr;
3786 }
3787 currentSub = new COptionMenu(menuRect, 0, 0, 0, 0,
3788 VSTGUI::COptionMenu::kNoDrawStyle |
3789 VSTGUI::COptionMenu::kMultipleCheckStyle);
3790
3791 char name[16];
3792
3793 sprintf(name, "CC %d ... %d", subs * 20, min((subs * 20) + 20, 128) - 1);
3794
3795 auto added_to_menu = midiSub->addEntry(currentSub, name);
3796
3797 for (int item = 0; item < 20; ++item)
3798 {
3799 int mc = (subs * 20) + item;
3800 int disabled = 0;
3801
3802 // these CCs cannot be used for MIDI learn (see
3803 // SurgeSynthesizer::channelController)
3804 if (mc == 0 || mc == 6 || mc == 32 || mc == 38 || mc == 64 ||
3805 (mc == 74 && synth->mpeEnabled) || (mc >= 98 && mc <= 101) ||
3806 mc == 120 || mc == 123)
3807 disabled = 1;
3808
3809 // we don't have any more CCs to cover, so break the loop
3810 if (mc > 127)
3811 break;
3812
3813 char name[128];
3814
3815 sprintf(name, "CC %d (%s) %s", mc, midicc_names[mc],
3816 (disabled == 1 ? "- RESERVED" : ""));
3817
3818 CCommandMenuItem *cmd = new CCommandMenuItem(CCommandMenuItem::Desc(name));
3819
3820 cmd->setActions([this, p, ptag, mc](CCommandMenuItem *men) {
3821 if (ptag < n_global_params)
3822 p->midictrl = mc;
3823 else
3824 {
3825 int a = ptag;
3826 if (ptag >= (n_global_params + n_scene_params))
3827 a -= ptag;
3828
3829 synth->storage.getPatch().param_ptr[a]->midictrl = mc;
3830 synth->storage.getPatch().param_ptr[a + n_scene_params]->midictrl =
3831 mc;
3832 }
3833 });
3834 cmd->setEnabled(!disabled);
3835
3836 auto added = currentSub->addEntry(cmd);
3837
3838 if ((ptag < n_global_params && p->midictrl == mc) ||
3839 (ptag > n_global_params &&
3840 synth->storage.getPatch().param_ptr[ptag]->midictrl == mc))
3841 {
3842 added->setChecked();
3843 added_to_menu->setChecked();
3844 }
3845 }
3846 }
3847
3848 if (currentSub)
3849 {
3850 currentSub->forget();
3851 currentSub = nullptr;
3852 }
3853
3854 contextMenu->addEntry(midiSub,
3855 Surge::UI::toOSCaseForMenu("Assign Parameter To..."));
3856
3857 eid++;
3858
3859 if (synth->learn_param > -1 && synth->learn_param == p->id)
3860 cancellearn = true;
3861
3862 std::string learnTag =
3863 cancellearn ? "Abort Parameter MIDI Learn" : "MIDI Learn Parameter...";
3864 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu(learnTag),
3865 [this, cancellearn, control, p] {
3866 if (cancellearn)
3867 {
3868 hideMidiLearnOverlay();
3869 synth->learn_param = -1;
3870 }
3871 else
3872 {
3873 showMidiLearnOverlay(control->getViewSize());
3874 synth->learn_param = p->id;
3875 }
3876 });
3877 eid++;
3878
3879 if (p->midictrl >= 0)
3880 {
3881 char txt4[16];
3882 decode_controllerid(txt4, p->midictrl);
3883 sprintf(txt, "Clear Learned MIDI (%s ", txt4);
3884 addCallbackMenu(
3885 contextMenu,
3886 Surge::UI::toOSCaseForMenu(txt) + midicc_names[p->midictrl] + ")",
3887 [this, p, ptag]() {
3888 if (ptag < n_global_params)
3889 {
3890 p->midictrl = -1;
3891 }
3892 else
3893 {
3894 int a = ptag;
3895 if (ptag >= (n_global_params + n_scene_params))
3896 a -= n_scene_params;
3897
3898 synth->storage.getPatch().param_ptr[a]->midictrl = -1;
3899 synth->storage.getPatch().param_ptr[a + n_scene_params]->midictrl =
3900 -1;
3901 }
3902 });
3903 eid++;
3904 }
3905
3906 int n_ms = 0;
3907
3908 for (int ms = 1; ms < n_modsources; ms++)
3909 if (synth->isActiveModulation(ptag, (modsources)ms))
3910 n_ms++;
3911
3912 // see if we have any modulators that are unassigned, then create "Add Modulation
3913 // from..." menu
3914 if (n_ms != n_modsources)
3915 {
3916 COptionMenu *addModSub =
3917 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3918
3919 COptionMenu *addMacroSub =
3920 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3921 COptionMenu *addVLFOSub =
3922 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3923 COptionMenu *addSLFOSub =
3924 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3925 COptionMenu *addEGSub =
3926 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3927 COptionMenu *addMIDISub =
3928 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3929 COptionMenu *addMiscSub =
3930 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
3931
3932 for (int k = 1; k < n_modsources; k++)
3933 {
3934 modsources ms = (modsources)modsource_display_order[k];
3935
3936 if (!synth->isActiveModulation(ptag, ms) &&
3937 synth->isValidModulation(ptag, ms))
3938 {
3939 char tmptxt[512];
3940 sprintf(tmptxt, "%s", modulatorName(ms, false).c_str());
3941
3942 // TODO FIXME: Try not to be gross!
3943 if (ms >= ms_ctrl1 && ms <= ms_ctrl1 + n_customcontrollers - 1)
3944 {
3945 addCallbackMenu(addMacroSub, tmptxt, [this, p, control, ms]() {
3946 this->promptForUserValueEntry(p, control, ms);
3947 });
3948 }
3949 else if (ms >= ms_lfo1 && ms <= ms_lfo1 + n_lfos_voice - 1)
3950 {
3951 addCallbackMenu(addVLFOSub, tmptxt, [this, p, control, ms]() {
3952 this->promptForUserValueEntry(p, control, ms);
3953 });
3954 }
3955 else if (ms >= ms_slfo1 && ms <= ms_slfo1 + n_lfos_scene - 1)
3956 {
3957 addCallbackMenu(addSLFOSub, tmptxt, [this, p, control, ms]() {
3958 this->promptForUserValueEntry(p, control, ms);
3959 });
3960 }
3961 else if (ms >= ms_ampeg && ms <= ms_filtereg)
3962 {
3963 addCallbackMenu(addEGSub, tmptxt, [this, p, control, ms]() {
3964 this->promptForUserValueEntry(p, control, ms);
3965 });
3966 }
3967 else if (ms >= ms_random_bipolar && ms <= ms_alternate_unipolar)
3968 {
3969 addCallbackMenu(addMiscSub, tmptxt, [this, p, control, ms]() {
3970 this->promptForUserValueEntry(p, control, ms);
3971 });
3972 }
3973 else
3974 {
3975 addCallbackMenu(addMIDISub, tmptxt, [this, p, control, ms]() {
3976 this->promptForUserValueEntry(p, control, ms);
3977 });
3978 }
3979 }
3980 }
3981
3982 if (addMacroSub->getNbEntries())
3983 {
3984 addModSub->addEntry(addMacroSub, "Macros");
3985 }
3986 if (addVLFOSub->getNbEntries())
3987 {
3988 addModSub->addEntry(addVLFOSub, "Voice LFOs");
3989 }
3990 if (addSLFOSub->getNbEntries())
3991 {
3992 addModSub->addEntry(addSLFOSub, "Scene LFOs");
3993 }
3994 if (addEGSub->getNbEntries())
3995 {
3996 addModSub->addEntry(addEGSub, "Envelopes");
3997 }
3998 if (addMIDISub->getNbEntries())
3999 {
4000 addModSub->addEntry(addMIDISub, "MIDI");
4001 }
4002 if (addMiscSub->getNbEntries())
4003 {
4004 addModSub->addEntry(addMiscSub, "Internal");
4005 }
4006
4007 if (addModSub->getNbEntries())
4008 {
4009 contextMenu->addSeparator(eid++);
4010 contextMenu->addEntry(addModSub,
4011 Surge::UI::toOSCaseForMenu("Add Modulation from..."));
4012 }
4013
4014 if (addMacroSub)
4015 {
4016 addMacroSub->forget();
4017 addMacroSub = nullptr;
4018 }
4019 if (addVLFOSub)
4020 {
4021 addVLFOSub->forget();
4022 addVLFOSub = nullptr;
4023 }
4024 if (addSLFOSub)
4025 {
4026 addSLFOSub->forget();
4027 addSLFOSub = nullptr;
4028 }
4029 if (addEGSub)
4030 {
4031 addEGSub->forget();
4032 addEGSub = nullptr;
4033 }
4034 if (addMIDISub)
4035 {
4036 addMIDISub->forget();
4037 addMIDISub = nullptr;
4038 }
4039 if (addMiscSub)
4040 {
4041 addMiscSub->forget();
4042 addMiscSub = nullptr;
4043 }
4044
4045 if (addModSub)
4046 {
4047 addModSub->forget();
4048 addModSub = nullptr;
4049 }
4050
4051 eid++;
4052 }
4053
4054 if (n_ms)
4055 {
4056 contextMenu->addSeparator(eid++);
4057
4058 for (int k = 1; k < n_modsources; k++)
4059 {
4060 modsources ms = (modsources)k;
4061 if (synth->isActiveModulation(ptag, ms))
4062 {
4063 char modtxt[256];
4064 p->get_display_of_modulation_depth(modtxt, synth->getModDepth(ptag, ms),
4065 synth->isBipolarModulation(ms),
4066 Parameter::Menu);
4067
4068 char tmptxt[512];
4069 sprintf(tmptxt, "Edit %s -> %s: %s",
4070 (char *)modulatorName(ms, true).c_str(), p->get_full_name(),
4071 modtxt);
4072 addCallbackMenu(contextMenu, tmptxt, [this, p, control, ms]() {
4073 this->promptForUserValueEntry(p, control, ms);
4074 });
4075 eid++;
4076 }
4077 }
4078 contextMenu->addSeparator(eid++);
4079 for (int k = 1; k < n_modsources; k++)
4080 {
4081 modsources ms = (modsources)k;
4082 if (synth->isActiveModulation(ptag, ms))
4083 {
4084 char tmptxt[256];
4085 snprintf(tmptxt, 256, "Clear %s -> %s",
4086 (char *)modulatorName(ms, true).c_str(), p->get_full_name());
4087 // clear_ms[ms] = eid;
4088 // contextMenu->addEntry(tmptxt, eid++);
4089 addCallbackMenu(contextMenu, tmptxt, [this, ms, ptag]() {
4090 synth->clearModulation(ptag, (modsources)ms);
4091 refresh_mod();
4092 /*
4093 ** FIXME - this is a pretty big hammer to deal with
4094 ** #1477 - can we be more parsimonious?
4095 */
4096 synth->refresh_editor = true;
4097 });
4098 eid++;
4099 }
4100 }
4101 if (n_ms > 1)
4102 {
4103 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu("Clear All"),
4104 [this, ptag]() {
4105 for (int ms = 1; ms < n_modsources; ms++)
4106 {
4107 synth->clearModulation(ptag, (modsources)ms);
4108 }
4109 refresh_mod();
4110 synth->refresh_editor = true;
4111 });
4112 eid++;
4113 }
4114 }
4115 } // end vt_float if statement
4116
4117 if (p->ctrltype == ct_amplitude_clipper)
4118 {
4119 std::string sc = std::string("Scene ") + (char)('A' + current_scene);
4120 contextMenu->addSeparator(eid++);
4121 // FIXME - add unified menu here
4122 addCallbackMenu(contextMenu, Surge::UI::toOSCaseForMenu(sc + " Hard Clip Disabled"),
4123 [this]() {
4124 synth->storage.sceneHardclipMode[current_scene] =
4125 SurgeStorage::BYPASS_HARDCLIP;
4126 });
4127 contextMenu->checkEntry(eid, synth->storage.sceneHardclipMode[current_scene] ==
4128 SurgeStorage::BYPASS_HARDCLIP);
4129 eid++;
4130
4131 addCallbackMenu(contextMenu,
4132 Surge::UI::toOSCaseForMenu(sc + " Hard Clip at 0 dBFS"), [this]() {
4133 synth->storage.sceneHardclipMode[current_scene] =
4134 SurgeStorage::HARDCLIP_TO_0DBFS;
4135 });
4136 contextMenu->checkEntry(eid, synth->storage.sceneHardclipMode[current_scene] ==
4137 SurgeStorage::HARDCLIP_TO_0DBFS);
4138 eid++;
4139
4140 addCallbackMenu(contextMenu,
4141 Surge::UI::toOSCaseForMenu(sc + " Hard Clip at +18 dBFS"),
4142 [this]() {
4143 synth->storage.sceneHardclipMode[current_scene] =
4144 SurgeStorage::HARDCLIP_TO_18DBFS;
4145 });
4146 contextMenu->checkEntry(eid, synth->storage.sceneHardclipMode[current_scene] ==
4147 SurgeStorage::HARDCLIP_TO_18DBFS);
4148 eid++;
4149 }
4150
4151 if (p->ctrltype == ct_decibel_attenuation_clipper)
4152 {
4153 contextMenu->addSeparator(eid++);
4154 // FIXME - add unified menu here
4155
4156 addCallbackMenu(
4157 contextMenu, Surge::UI::toOSCaseForMenu("Global Hard Clip Disabled"),
4158 [this]() { synth->storage.hardclipMode = SurgeStorage::BYPASS_HARDCLIP; });
4159 contextMenu->checkEntry(eid, synth->storage.hardclipMode ==
4160 SurgeStorage::BYPASS_HARDCLIP);
4161 eid++;
4162
4163 addCallbackMenu(
4164 contextMenu, Surge::UI::toOSCaseForMenu("Global Hard Clip at 0 dBFS"),
4165 [this]() { synth->storage.hardclipMode = SurgeStorage::HARDCLIP_TO_0DBFS; });
4166 contextMenu->checkEntry(eid, synth->storage.hardclipMode ==
4167 SurgeStorage::HARDCLIP_TO_0DBFS);
4168 eid++;
4169
4170 addCallbackMenu(
4171 contextMenu, Surge::UI::toOSCaseForMenu("Global Hard Clip at +18 dBFS"),
4172 [this]() { synth->storage.hardclipMode = SurgeStorage::HARDCLIP_TO_18DBFS; });
4173 contextMenu->checkEntry(eid, synth->storage.hardclipMode ==
4174 SurgeStorage::HARDCLIP_TO_18DBFS);
4175 eid++;
4176 }
4177
4178 #if TARGET_VST3
4179 auto hostMenu = addVst3MenuForParams(contextMenu, synth->idForParameter(p), eid);
4180 #endif
4181
4182 frame->addView(contextMenu); // add to frame
4183 contextMenu->popup();
4184 frame->removeView(contextMenu, true); // remove from frame and forget
4185
4186 #if TARGET_VST3
4187 if (hostMenu)
4188 hostMenu->release();
4189 #endif
4190
4191 return 1;
4192 }
4193 // reset to default value
4194 else if (button & kDoubleClick)
4195 {
4196 if (synth->isValidModulation(ptag, modsource) && mod_editor)
4197 {
4198 CModulationSourceButton *cms = (CModulationSourceButton *)gui_modsrc[modsource];
4199 auto thisms = modsource;
4200 if (cms && cms->hasAlternate && cms->useAlternate)
4201 thisms = (modsources)cms->alternateId;
4202
4203 synth->clearModulation(ptag, thisms);
4204 ((CSurgeSlider *)control)->setModValue(synth->getModulation(p->id, thisms));
4205 ((CSurgeSlider *)control)->setModPresent(synth->isModDestUsed(p->id));
4206 ((CSurgeSlider *)control)
4207 ->setModCurrent(synth->isActiveModulation(p->id, thisms),
4208 synth->isBipolarModulation(thisms));
4209 // control->setGhostValue(p->get_value_f01());
4210 oscdisplay->invalid();
4211 return 0;
4212 }
4213 else
4214 {
4215 switch (p->ctrltype)
4216 {
4217 case ct_lfotype:
4218 /*
4219 ** This code resets you to default if you double click on control
4220 ** but on the lfoshape UI this is undesirable; it means if you accidentally
4221 ** control click on step sequencer, say, you go back to sin and lose your
4222 ** edits. So supress
4223 */
4224 break;
4225 case ct_freq_audible_with_tunability:
4226 case ct_freq_audible_with_very_low_lowerbound:
4227 {
4228 if (p->extend_range || button & VSTGUI::CButton::kAlt)
4229 {
4230 auto kr = this->synth->storage.getPatch()
4231 .scene[current_scene]
4232 .keytrack_root.val.i;
4233 p->set_value_f01(p->value_to_normalized(kr - 69));
4234 control->setValue(p->get_value_f01());
4235 }
4236 else
4237 {
4238 p->set_value_f01(p->get_default_value_f01());
4239 control->setValue(p->get_value_f01());
4240 }
4241 return 0;
4242 }
4243 default:
4244 {
4245 p->set_value_f01(p->get_default_value_f01());
4246 control->setValue(p->get_value_f01());
4247 if (oscdisplay && (p->ctrlgroup == cg_OSC))
4248 oscdisplay->invalid();
4249 if (lfodisplay && (p->ctrlgroup == cg_LFO))
4250 lfodisplay->invalid();
4251 control->invalid();
4252 return 0;
4253 }
4254 }
4255 }
4256 }
4257 // exclusive mute/solo in the mixer
4258 else if (button & kControl)
4259 {
4260 if (p->ctrltype == ct_bool_mute)
4261 {
4262 Parameter *o1 = &(synth->storage.getPatch().scene[current_scene].mute_o1);
4263 Parameter *r23 = &(synth->storage.getPatch().scene[current_scene].mute_ring_23);
4264
4265 auto curr = o1;
4266
4267 while (curr <= r23)
4268 {
4269 if (curr->id == p->id)
4270 curr->val.b = true;
4271 else
4272 curr->val.b = false;
4273
4274 curr++;
4275 }
4276
4277 synth->refresh_editor = true;
4278 }
4279 else if (p->ctrltype == ct_bool_solo)
4280 {
4281 Parameter *o1 = &(synth->storage.getPatch().scene[current_scene].solo_o1);
4282 Parameter *r23 = &(synth->storage.getPatch().scene[current_scene].solo_ring_23);
4283
4284 auto curr = o1;
4285
4286 while (curr <= r23)
4287 {
4288 if (curr->id == p->id)
4289 curr->val.b = true;
4290 else
4291 curr->val.b = false;
4292
4293 curr++;
4294 }
4295
4296 synth->refresh_editor = true;
4297 }
4298 else
4299 p->bound_value();
4300 }
4301 }
4302 return 0;
4303 }
4304
effectSettingsBackgroundClick(int whichScene)4305 void SurgeGUIEditor::effectSettingsBackgroundClick(int whichScene)
4306 {
4307 CPoint where;
4308 CRect menuRect;
4309 frame->getCurrentMouseLocation(where);
4310 frame->localToFrame(where);
4311
4312 menuRect.offset(where.x, where.y);
4313
4314 auto effmen = new COptionMenu(menuRect, 0, 0, 0, 0,
4315 VSTGUI::COptionMenu::kNoDrawStyle |
4316 VSTGUI::COptionMenu::kMultipleCheckStyle);
4317
4318 auto msurl = SurgeGUIEditor::helpURLForSpecial("fx-selector");
4319 auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl);
4320 std::string txt;
4321
4322 addCallbackMenu(effmen, "[?] Effect Settings",
4323 [hurl]() { Surge::UserInteractions::openURL(hurl); });
4324
4325 effmen->addSeparator();
4326
4327 std::string sc = std::string("Scene ") + (char)('A' + whichScene);
4328
4329 txt = sc + Surge::UI::toOSCaseForMenu(" Hard Clip Disabled");
4330 auto hcmen = addCallbackMenu(effmen, txt.c_str(), [this, whichScene]() {
4331 this->synth->storage.sceneHardclipMode[whichScene] = SurgeStorage::BYPASS_HARDCLIP;
4332 });
4333 hcmen->setChecked(synth->storage.sceneHardclipMode[whichScene] ==
4334 SurgeStorage::BYPASS_HARDCLIP);
4335
4336 txt = sc + Surge::UI::toOSCaseForMenu(" Hard Clip at 0 dBFS");
4337 hcmen = addCallbackMenu(effmen, txt.c_str(), [this, whichScene]() {
4338 this->synth->storage.sceneHardclipMode[whichScene] = SurgeStorage::HARDCLIP_TO_0DBFS;
4339 });
4340 hcmen->setChecked(synth->storage.sceneHardclipMode[whichScene] ==
4341 SurgeStorage::HARDCLIP_TO_0DBFS);
4342
4343 txt = sc + Surge::UI::toOSCaseForMenu(" Hard Clip at +18 dBFS");
4344 hcmen = addCallbackMenu(effmen, txt.c_str(), [this, whichScene]() {
4345 this->synth->storage.sceneHardclipMode[whichScene] = SurgeStorage::HARDCLIP_TO_18DBFS;
4346 });
4347 hcmen->setChecked(synth->storage.sceneHardclipMode[whichScene] ==
4348 SurgeStorage::HARDCLIP_TO_18DBFS);
4349
4350 frame->addView(effmen);
4351 effmen->popup();
4352 frame->removeView(effmen, true);
4353 }
4354
valueChanged(CControl * control)4355 void SurgeGUIEditor::valueChanged(CControl *control)
4356 {
4357 if (!frame)
4358 return;
4359 if (!editor_open)
4360 return;
4361 long tag = control->getTag();
4362
4363 if (typeinDialog != nullptr && tag != tag_value_typein)
4364 {
4365 typeinDialog->setVisible(false);
4366 removeFromFrame.push_back(typeinDialog);
4367 typeinDialog = nullptr;
4368 typeinResetCounter = -1;
4369 typeinEditTarget = nullptr;
4370 typeinMode = Inactive;
4371 }
4372
4373 if (tag == tag_status_zoom)
4374 {
4375 control->setValue(0);
4376 CPoint where;
4377 frame->getCurrentMouseLocation(where);
4378 frame->localToFrame(where);
4379
4380 showZoomMenu(where);
4381 return;
4382 }
4383
4384 if (tag == tag_lfo_menu)
4385 {
4386 control->setValue(0);
4387 CPoint where;
4388 frame->getCurrentMouseLocation(where);
4389 frame->localToFrame(where);
4390
4391 showLfoMenu(where);
4392 return;
4393 }
4394
4395 if (tag == tag_status_mpe)
4396 {
4397 toggleMPE();
4398 return;
4399 }
4400 if (tag == tag_status_tune)
4401 {
4402 toggleTuning();
4403 return;
4404 }
4405
4406 if (tag == tag_mseg_edit)
4407 {
4408 if (control->getValue() > 0.5)
4409 {
4410 showMSEGEditor();
4411 }
4412 else
4413 {
4414 closeMSEGEditor();
4415 }
4416 return;
4417 }
4418
4419 if ((tag >= tag_mod_source0) && (tag < tag_mod_source_end))
4420 {
4421 if (((CModulationSourceButton *)control)->event_is_drag)
4422 {
4423 int t = (tag - tag_mod_source0);
4424 ((ControllerModulationSource *)synth->storage.getPatch()
4425 .scene[current_scene]
4426 .modsources[t])
4427 ->set_target01(control->getValue(), false);
4428
4429 SurgeSynthesizer::ID tid;
4430 if (synth->fromSynthSideId(t + metaparam_offset - ms_ctrl1, tid))
4431 {
4432 synth->sendParameterAutomation(tid, control->getValue());
4433 }
4434 return;
4435 }
4436 else
4437 {
4438 CModulationSourceButton *cms = (CModulationSourceButton *)control;
4439 int state = cms->get_state();
4440 modsources newsource = (modsources)(tag - tag_mod_source0);
4441 long buttons = 0; // context->getMouseButtons(); // temp fix vstgui 3.5
4442 bool ciep =
4443 ((CModulationSourceButton *)control)->click_is_editpart && (newsource >= ms_lfo1);
4444 if (!ciep)
4445 {
4446 switch (state & 3)
4447 {
4448 case 0:
4449 modsource = newsource;
4450 if (mod_editor)
4451 mod_editor = true;
4452 else
4453 mod_editor = false;
4454 queue_refresh = true;
4455 refresh_mod();
4456 break;
4457 case 1:
4458 modsource = newsource;
4459 mod_editor = true;
4460 refresh_mod();
4461 break;
4462 case 2:
4463 modsource = newsource;
4464 mod_editor = false;
4465 refresh_mod();
4466 break;
4467 };
4468 }
4469
4470 if (isLFO(newsource) && !(buttons & kShift))
4471 {
4472 if (modsource_editor[current_scene] != newsource)
4473 {
4474 auto tabPosMem = Surge::Storage::getUserDefaultValue(
4475 &(this->synth->storage), "rememberTabPositionsPerScene", 0);
4476
4477 if (tabPosMem)
4478 modsource_editor[current_scene] = newsource;
4479 else
4480 {
4481 for (int i = 0; i < n_scenes; i++)
4482 {
4483 modsource_editor[i] = newsource;
4484 }
4485 }
4486
4487 if (isAnyOverlayPresent(MSEG_EDITOR))
4488 {
4489 auto ld = &(synth->storage.getPatch().scene[current_scene].lfo[newsource -
4490 ms_lfo1]);
4491 if (ld->shape.val.i == lt_mseg)
4492 {
4493 showMSEGEditor();
4494 }
4495 else
4496 {
4497 closeMSEGEditor();
4498 }
4499 }
4500
4501 queue_refresh = true;
4502 }
4503 }
4504 }
4505
4506 return;
4507 }
4508
4509 if ((tag == f1subtypetag) || (tag == f2subtypetag))
4510 {
4511 int idx = (tag == f2subtypetag) ? 1 : 0;
4512 auto csc = dynamic_cast<CSwitchControl *>(control);
4513 int valdir = csc->value_direction;
4514 csc->value_direction = 0;
4515
4516 int a =
4517 synth->storage.getPatch().scene[current_scene].filterunit[idx].subtype.val.i + valdir;
4518 int t = synth->storage.getPatch().scene[current_scene].filterunit[idx].type.val.i;
4519 int nn =
4520 fut_subcount[synth->storage.getPatch().scene[current_scene].filterunit[idx].type.val.i];
4521 if (a >= nn)
4522 a = 0;
4523 if (a < 0)
4524 a = nn - 1;
4525 synth->storage.getPatch().scene[current_scene].filterunit[idx].subtype.val.i = a;
4526 synth->storage.subtypeMemory[current_scene][idx][t] = a;
4527 if (csc)
4528 {
4529 if (nn == 0)
4530 csc->ivalue = 0;
4531 else
4532 csc->ivalue = a + 1;
4533 }
4534 control->invalid();
4535 synth->switch_toggled_queued = true;
4536 return;
4537 }
4538
4539 switch (tag)
4540 {
4541 case tag_scene_select:
4542 {
4543 current_scene = (int)(control->getValue() * 1.f) + 0.5f;
4544 synth->release_if_latched[synth->storage.getPatch().scene_active.val.i] = true;
4545 synth->storage.getPatch().scene_active.val.i = current_scene;
4546 // synth->storage.getPatch().param_ptr[scene_select_pid]->set_value_f01(control->getValue());
4547
4548 if (isAnyOverlayPresent(MSEG_EDITOR))
4549 {
4550 auto ld = &(synth->storage.getPatch()
4551 .scene[current_scene]
4552 .lfo[modsource_editor[current_scene] - ms_lfo1]);
4553 if (ld->shape.val.i == lt_mseg)
4554 {
4555 showMSEGEditor();
4556 }
4557 else
4558 {
4559 closeMSEGEditor();
4560 }
4561 }
4562
4563 queue_refresh = true;
4564 return;
4565 }
4566 break;
4567 case tag_patchname:
4568 {
4569 int id = ((CPatchBrowser *)control)->enqueue_sel_id;
4570 // synth->load_patch(id);
4571 enqueuePatchId = id;
4572
4573 #if LINUX || TARGET_JUCE_UI
4574 // On linux the popup menu will be gone so we gotta process
4575 flushEnqueuedPatchId();
4576 #endif
4577 return;
4578 }
4579 break;
4580 case tag_mp_category:
4581 {
4582 if (isAnyOverlayPresent(STORE_PATCH))
4583 {
4584 closeStorePatchDialog();
4585 }
4586
4587 if (control->getValue() > 0.5f)
4588 synth->incrementCategory(true);
4589 else
4590 synth->incrementCategory(false);
4591 return;
4592 }
4593 break;
4594 case tag_mp_patch:
4595 {
4596 if (isAnyOverlayPresent(STORE_PATCH))
4597 {
4598 closeStorePatchDialog();
4599 }
4600
4601 auto insideCategory =
4602 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "patchJogWraparound", 1);
4603
4604 if (control->getValue() > 0.5f)
4605 synth->incrementPatch(true, insideCategory);
4606 else
4607 synth->incrementPatch(false, insideCategory);
4608 return;
4609 }
4610 break;
4611 case tag_mp_jogfx:
4612 {
4613 CFxMenu *fxm = dynamic_cast<CFxMenu *>(fxmenu);
4614 auto jog = [this, fxm](int byThis) {
4615 this->selectedFX[this->current_fx] =
4616 std::max(this->selectedFX[this->current_fx] + byThis, -1);
4617 if (!fxm->loadSnapshotByIndex(this->selectedFX[this->current_fx]))
4618 {
4619 // Try and go back to 0. This is the wrong behavior for negative jog
4620 this->selectedFX[this->current_fx] = 0;
4621 fxm->loadSnapshotByIndex(0);
4622 }
4623 };
4624
4625 if (fxm)
4626 {
4627 if (fxm->selectedIdx >= 0 && fxm->selectedIdx != selectedFX[current_fx])
4628 selectedFX[current_fx] = fxm->selectedIdx;
4629
4630 if (control->getValue() > 0.5f)
4631 {
4632 jog(+1);
4633 }
4634 else
4635 {
4636 jog(-1);
4637 }
4638
4639 if (fxPresetLabel)
4640 {
4641 fxPresetLabel->setText(fxm->selectedName.c_str());
4642 fxPresetName[this->current_fx] = fxm->selectedName;
4643 }
4644 }
4645 else
4646 {
4647 }
4648 return;
4649 }
4650 break;
4651 case tag_settingsmenu:
4652 {
4653 CRect r = control->getViewSize();
4654 CRect menuRect;
4655 CPoint where;
4656 frame->getCurrentMouseLocation(where);
4657 frame->localToFrame(where);
4658
4659 menuRect.offset(where.x, where.y);
4660
4661 useDevMenu = false;
4662 showSettingsMenu(menuRect);
4663 }
4664 break;
4665 case tag_osc_select:
4666 {
4667 auto tabPosMem = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
4668 "rememberTabPositionsPerScene", 0);
4669
4670 if (tabPosMem)
4671 current_osc[current_scene] = (int)(control->getValue() * 2.f + 0.5f);
4672 else
4673 {
4674 for (int i = 0; i < n_scenes; i++)
4675 {
4676 current_osc[i] = (int)(control->getValue() * 2.f + 0.5f);
4677 }
4678 }
4679
4680 queue_refresh = true;
4681 return;
4682 }
4683 break;
4684 case tag_fx_select:
4685 {
4686 auto fxc = ((CEffectSettings *)control);
4687 int d = fxc->get_disable();
4688 synth->fx_suspend_bitmask = synth->storage.getPatch().fx_disable.val.i ^ d;
4689 synth->storage.getPatch().fx_disable.val.i = d;
4690 fxc->set_disable(d);
4691
4692 int nfx = fxc->get_current();
4693 if (current_fx != nfx)
4694 {
4695 current_fx = nfx;
4696 queue_refresh = true;
4697 }
4698
4699 return;
4700 }
4701 break;
4702 case tag_osc_menu:
4703 {
4704 synth->switch_toggled_queued = true;
4705 queue_refresh = true;
4706 synth->processThreadunsafeOperations();
4707 return;
4708 }
4709 break;
4710 case tag_fx_menu:
4711 {
4712 synth->load_fx_needed = true;
4713 // queue_refresh = true;
4714 synth->fx_reload[current_fx & 7] = true;
4715 synth->processThreadunsafeOperations();
4716
4717 CFxMenu *fxm = dynamic_cast<CFxMenu *>(fxmenu);
4718 if (fxm && fxm->selectedIdx >= 0)
4719 {
4720 selectedFX[current_fx] = fxm->selectedIdx;
4721 fxPresetName[current_fx] = fxm->selectedName;
4722 }
4723 else if (fxPresetLabel)
4724 {
4725 fxPresetLabel->setText(fxm->selectedName.c_str());
4726 fxPresetName[this->current_fx] = fxm->selectedName;
4727 }
4728
4729 return;
4730 }
4731 break;
4732 case tag_store:
4733 {
4734 patchdata p;
4735
4736 p.name = synth->storage.getPatch().name;
4737 p.category = synth->storage.getPatch().category;
4738 p.author = synth->storage.getPatch().author;
4739 p.comments = synth->storage.getPatch().comment;
4740
4741 string defaultAuthor =
4742 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "defaultPatchAuthor", "");
4743 string defaultComment =
4744 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "defaultPatchComment", "");
4745 string oldAuthor = "";
4746
4747 if (!Surge::Storage::isValidUTF8(defaultAuthor))
4748 {
4749 defaultAuthor = "";
4750 }
4751 if (!Surge::Storage::isValidUTF8(defaultComment))
4752 {
4753 defaultComment = "";
4754 }
4755
4756 if (p.author == "" && defaultAuthor != "")
4757 {
4758 p.author = defaultAuthor;
4759 }
4760
4761 if (p.author != "" && defaultAuthor != "")
4762 {
4763 if (_stricmp(p.author.c_str(), defaultAuthor.c_str()))
4764 {
4765 oldAuthor = p.author;
4766 p.author = defaultAuthor;
4767 }
4768 }
4769
4770 if (p.comments == "" && defaultComment != "")
4771 {
4772 p.comments = defaultComment;
4773 }
4774
4775 if (oldAuthor != "")
4776 {
4777 if (p.comments == "")
4778 p.comments += "Original patch by " + oldAuthor;
4779 else
4780 p.comments += " (Original patch by " + oldAuthor + ")";
4781 }
4782
4783 showStorePatchDialog();
4784
4785 patchName->setText(p.name.c_str());
4786 patchCategory->setText(p.category.c_str());
4787 patchCreator->setText(p.author.c_str());
4788 patchComment->setText(p.comments.c_str());
4789 }
4790 break;
4791 case tag_store_ok:
4792 {
4793 // prevent duplicate execution of savePatch() by detecting if the Store Patch dialog is
4794 // displayed or not
4795 // FIXME: baconpaul will know a better and more correct way to fix this
4796 if (isAnyOverlayPresent(STORE_PATCH))
4797 {
4798 synth->storage.getPatch().name = patchName->getText();
4799 synth->storage.getPatch().author = patchCreator->getText();
4800 synth->storage.getPatch().category = patchCategory->getText();
4801 synth->storage.getPatch().comment = patchComment->getText();
4802
4803 synth->storage.getPatch().patchTuning.tuningStoredInPatch =
4804 patchTuning->getValue() > 0.5;
4805 if (synth->storage.getPatch().patchTuning.tuningStoredInPatch)
4806 {
4807 if (synth->storage.isStandardScale)
4808 {
4809 synth->storage.getPatch().patchTuning.scaleContents = "";
4810 }
4811 else
4812 {
4813 synth->storage.getPatch().patchTuning.scaleContents =
4814 synth->storage.currentScale.rawText;
4815 }
4816 if (synth->storage.isStandardMapping)
4817 {
4818 synth->storage.getPatch().patchTuning.mappingContents = "";
4819 }
4820 else
4821 {
4822 synth->storage.getPatch().patchTuning.mappingContents =
4823 synth->storage.currentMapping.rawText;
4824 synth->storage.getPatch().patchTuning.mappingName =
4825 synth->storage.currentMapping.name;
4826 }
4827 }
4828
4829 // Ignore whatever comes from the DAW
4830 synth->storage.getPatch().dawExtraState.isPopulated = false;
4831
4832 synth->savePatch();
4833
4834 closeStorePatchDialog();
4835 frame->setDirty();
4836 }
4837 }
4838 break;
4839 case tag_store_cancel:
4840 {
4841 closeStorePatchDialog();
4842 frame->setDirty();
4843 }
4844 break;
4845 case tag_editor_overlay_close:
4846 {
4847 // So can I find an editor overlay parent
4848 VSTGUI::CView *p = control;
4849 auto tagToNuke = NO_EDITOR;
4850 while (p)
4851 {
4852 p = p->getParentView();
4853 for (auto el : editorOverlay)
4854 {
4855 if (el.second == p)
4856 {
4857 tagToNuke = el.first;
4858 p = nullptr;
4859 break;
4860 }
4861 }
4862 }
4863 if (tagToNuke != NO_EDITOR)
4864 {
4865 dismissEditorOfType(tagToNuke);
4866 }
4867 }
4868 break;
4869 case tag_miniedit_ok:
4870 case tag_miniedit_cancel:
4871 {
4872 if (minieditOverlay != nullptr)
4873 {
4874 if (minieditTypein)
4875 {
4876 auto q = minieditTypein->getText().getString();
4877 if (tag == tag_miniedit_ok)
4878 {
4879 minieditOverlayDone(q.c_str());
4880 }
4881 minieditTypein->looseFocus();
4882 }
4883 minieditOverlay->setVisible(false);
4884 removeFromFrame.push_back(minieditOverlay);
4885 minieditOverlay = nullptr;
4886 }
4887 }
4888 break;
4889 case tag_value_typein:
4890 {
4891 if (typeinDialog && typeinMode != Inactive)
4892 {
4893 std::string t = typeinValue->getText().getString();
4894 bool isInvalid = false;
4895 if (typeinMode == Param && typeinEditTarget && typeinModSource > 0)
4896 {
4897 bool valid = false;
4898 auto mv = typeinEditTarget->calculate_modulation_value_from_string(t, valid);
4899
4900 if (!valid)
4901 {
4902 isInvalid = true;
4903 }
4904 else
4905 {
4906 synth->setModulation(typeinEditTarget->id, (modsources)typeinModSource, mv);
4907 synth->refresh_editor = true;
4908
4909 typeinDialog->setVisible(false);
4910 removeFromFrame.push_back(typeinDialog);
4911 typeinDialog = nullptr;
4912 typeinResetCounter = -1;
4913 typeinEditTarget = nullptr;
4914 typeinMode = Inactive;
4915 }
4916 }
4917 else if (typeinMode == Param && typeinEditTarget &&
4918 typeinEditTarget->set_value_from_string(t))
4919 {
4920 repushAutomationFor(typeinEditTarget);
4921 isInvalid = false;
4922 synth->refresh_editor = true;
4923 typeinDialog->setVisible(false);
4924 removeFromFrame.push_back(typeinDialog);
4925 typeinDialog = nullptr;
4926 typeinResetCounter = -1;
4927 typeinEditTarget = nullptr;
4928 typeinMode = Inactive;
4929 }
4930 else if (typeinMode == Control)
4931 {
4932 auto cms = ((ControllerModulationSource *)synth->storage.getPatch()
4933 .scene[current_scene]
4934 .modsources[typeinModSource]);
4935 bool bp = cms->is_bipolar();
4936 float val = std::atof(t.c_str()) / 100.0;
4937 if ((bp && val >= -1 && val <= 1) || (val >= 0 && val <= 1))
4938 {
4939 cms->output = val;
4940 cms->target = val;
4941 // This doesn't seem to work so hammer away
4942 if (typeinEditControl)
4943 {
4944 typeinEditControl->invalid();
4945 }
4946 synth->refresh_editor = true;
4947 }
4948 else
4949 {
4950 isInvalid = true;
4951 }
4952 }
4953 else
4954 {
4955 isInvalid = true;
4956 }
4957
4958 if (isInvalid)
4959 {
4960 auto l = typeinLabel->getText().getString();
4961 promptForUserValueEntry(typeinEditTarget, typeinEditControl, typeinModSource);
4962 typeinResetCounter = 20;
4963 typeinResetLabel = l;
4964 typeinLabel->setText("Invalid Entry");
4965 typeinValue->setText(t.c_str());
4966 typeinLabel->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Error));
4967 }
4968 }
4969 }
4970 break;
4971 default:
4972 {
4973 int ptag = tag - start_paramtags;
4974
4975 if ((ptag >= 0) && (ptag < synth->storage.getPatch().param_ptr.size()))
4976 {
4977 Parameter *p = synth->storage.getPatch().param_ptr[ptag];
4978 if (p->is_nonlocal_on_change())
4979 frame->invalid();
4980
4981 char pname[256], pdisp[128], txt[128];
4982 bool modulate = false;
4983
4984 // This allows us to turn on and off the editor. FIXME mseg check it
4985 if (p->ctrltype == ct_lfotype)
4986 synth->refresh_editor = true;
4987
4988 if ((p->ctrlstyle & kNoPopup))
4989 {
4990 auto iw = dynamic_cast<CParameterTooltip *>(infowindow);
4991 if (iw && iw->isVisible())
4992 iw->Hide();
4993 }
4994
4995 if (modsource && mod_editor && synth->isValidModulation(p->id, modsource) &&
4996 dynamic_cast<CSurgeSlider *>(control) != nullptr)
4997 {
4998 modsources thisms = modsource;
4999 if (gui_modsrc[modsource])
5000 {
5001 CModulationSourceButton *cms = (CModulationSourceButton *)gui_modsrc[modsource];
5002 if (cms->hasAlternate && cms->useAlternate)
5003 thisms = (modsources)cms->alternateId;
5004 }
5005 bool quantize_mod = frame->getCurrentMouseButtons() & kControl;
5006 float mv = ((CSurgeSlider *)control)->getModValue();
5007 if (quantize_mod)
5008 {
5009 mv = p->quantize_modulation(mv);
5010 // maybe setModValue here
5011 }
5012
5013 synth->setModulation(ptag, thisms, mv);
5014 ((CSurgeSlider *)control)->setModPresent(synth->isModDestUsed(p->id));
5015 ((CSurgeSlider *)control)
5016 ->setModCurrent(synth->isActiveModulation(p->id, thisms),
5017 synth->isBipolarModulation(thisms));
5018
5019 SurgeSynthesizer::ID ptagid;
5020 if (synth->fromSynthSideId(ptag, ptagid))
5021 synth->getParameterName(ptagid, txt);
5022 sprintf(pname, "%s -> %s", modulatorName(thisms, true).c_str(), txt);
5023 ModulationDisplayInfoWindowStrings mss;
5024 p->get_display_of_modulation_depth(pdisp, synth->getModDepth(ptag, thisms),
5025 synth->isBipolarModulation(thisms),
5026 Parameter::InfoWindow, &mss);
5027 if (mss.val != "")
5028 {
5029 ((CParameterTooltip *)infowindow)->setLabel(pname, pdisp);
5030 ((CParameterTooltip *)infowindow)->setMDIWS(mss);
5031 }
5032 else
5033 {
5034 ((CParameterTooltip *)infowindow)->setLabel(pname, pdisp);
5035 ((CParameterTooltip *)infowindow)->clearMDIWS();
5036 }
5037 modulate = true;
5038
5039 if (isCustomController(modsource))
5040 {
5041 int ccid = modsource - ms_ctrl1;
5042 char *lbl = synth->storage.getPatch().CustomControllerLabel[ccid];
5043
5044 if ((lbl[0] == '-') && !lbl[1])
5045 {
5046 strxcpy(lbl, p->get_name(), 15);
5047 synth->storage.getPatch().CustomControllerLabel[ccid][15] = 0;
5048 ((CModulationSourceButton *)gui_modsrc[modsource])->setlabel(lbl);
5049 ((CModulationSourceButton *)gui_modsrc[modsource])->invalid();
5050 }
5051 }
5052 }
5053 else
5054 {
5055 auto val = control->getValue();
5056 if (p->ctrltype == ct_scenemode)
5057 {
5058
5059 /*
5060 ** See the comment in the constructor of ct_scenemode above
5061 */
5062 auto cs2 = dynamic_cast<CHSwitch2 *>(control);
5063 auto im = 0;
5064 if (cs2)
5065 {
5066 im = cs2->getIValue();
5067 if (im == 3)
5068 im = 2;
5069 else if (im == 2)
5070 im = 3;
5071 val = Parameter::intScaledToFloat(im, n_scene_modes - 1);
5072 }
5073
5074 /*
5075 ** Now I also need to toggle the split key state
5076 */
5077 auto nf = dynamic_cast<CNumberField *>(splitpointControl);
5078 if (nf)
5079 {
5080 int cm = nf->getControlMode();
5081 if (im == sm_chsplit && cm != cm_midichannel_from_127)
5082 {
5083 nf->setControlMode(cm_midichannel_from_127);
5084 nf->invalid();
5085 }
5086 else if (im == sm_split && cm != cm_notename)
5087 {
5088 nf->setControlMode(cm_notename);
5089 nf->invalid();
5090 }
5091 else if ((im == sm_single || im == sm_dual) && cm != cm_none)
5092 {
5093 nf->setControlMode(cm_none);
5094 nf->invalid();
5095 }
5096 }
5097 }
5098
5099 bool force_integer = frame->getCurrentMouseButtons() & kControl;
5100 SurgeSynthesizer::ID ptagid;
5101 synth->fromSynthSideId(ptag, ptagid);
5102 if (synth->setParameter01(ptagid, val, false, force_integer))
5103 {
5104 queue_refresh = true;
5105 return;
5106 }
5107 else
5108 {
5109 synth->sendParameterAutomation(ptagid, synth->getParameter01(ptagid));
5110
5111 if (dynamic_cast<CSurgeSlider *>(control) != nullptr)
5112 ((CSurgeSlider *)control)->SetQuantitizedDispValue(p->get_value_f01());
5113 else
5114 control->invalid();
5115 synth->getParameterName(ptagid, pname);
5116 synth->getParameterDisplay(ptagid, pdisp);
5117 char pdispalt[256];
5118 synth->getParameterDisplayAlt(ptagid, pdispalt);
5119 ((CParameterTooltip *)infowindow)->setLabel(0, pdisp, pdispalt);
5120 ((CParameterTooltip *)infowindow)->clearMDIWS();
5121 if (p->ctrltype == ct_polymode)
5122 modulate = true;
5123 }
5124
5125 if (p->ctrltype == ct_bool_unipolar || p->ctrltype == ct_lfotype)
5126 {
5127 // The green line might change so...
5128 refresh_mod();
5129 }
5130 }
5131 if (!queue_refresh)
5132 {
5133 if (!(p->ctrlstyle & kNoPopup))
5134 {
5135 draw_infowindow(ptag, control, modulate);
5136 }
5137
5138 if (oscdisplay && ((p->ctrlgroup == cg_OSC) || (p->ctrltype == ct_character)))
5139 {
5140 oscdisplay->setDirty();
5141 oscdisplay->invalid();
5142 }
5143 if (lfodisplay && (p->ctrlgroup == cg_LFO))
5144 {
5145 lfodisplay->setDirty();
5146 lfodisplay->invalid();
5147 }
5148 for (auto el : editorOverlay)
5149 {
5150 el.second->invalid();
5151 }
5152 }
5153 if (p->ctrltype == ct_filtertype)
5154 {
5155 auto *subsw = dynamic_cast<CSwitchControl *>(filtersubtype[p->ctrlgroup_entry]);
5156 if (subsw)
5157 {
5158 int sc = fut_subcount[p->val.i];
5159
5160 subsw->imax = sc;
5161 }
5162 }
5163 }
5164
5165 break;
5166 }
5167 }
5168
5169 if ((tag == (f1subtypetag - 1)) || (tag == (f2subtypetag - 1)))
5170 {
5171 int idx = (tag == (f2subtypetag - 1)) ? 1 : 0;
5172
5173 int a = synth->storage.getPatch().scene[current_scene].filterunit[idx].subtype.val.i;
5174 int nn =
5175 fut_subcount[synth->storage.getPatch().scene[current_scene].filterunit[idx].type.val.i];
5176 if (a >= nn)
5177 a = 0;
5178 synth->storage.getPatch().scene[current_scene].filterunit[idx].subtype.val.i = a;
5179 if (!nn)
5180 ((CSwitchControl *)filtersubtype[idx])->ivalue = 0;
5181 else
5182 ((CSwitchControl *)filtersubtype[idx])->ivalue = a + 1;
5183
5184 filtersubtype[idx]->setDirty();
5185 filtersubtype[idx]->invalid();
5186 }
5187
5188 if (tag == fmconfig_tag)
5189 {
5190 // FM depth control
5191 int i = synth->storage.getPatch().scene[current_scene].fm_depth.id;
5192 if (param[i] && dynamic_cast<CSurgeSlider *>(param[i]) != nullptr)
5193 ((CSurgeSlider *)param[i])->disabled =
5194 (synth->storage.getPatch().scene[current_scene].fm_switch.val.i == fm_off);
5195
5196 param[i]->setDirty();
5197 param[i]->invalid();
5198 }
5199
5200 if (tag == filterblock_tag)
5201 {
5202 // pan2 control
5203 int i = synth->storage.getPatch().scene[current_scene].width.id;
5204 if (param[i] && dynamic_cast<CSurgeSlider *>(param[i]) != nullptr)
5205 ((CSurgeSlider *)param[i])->disabled =
5206 (synth->storage.getPatch().scene[current_scene].filterblock_configuration.val.i !=
5207 fc_stereo) &&
5208 (synth->storage.getPatch().scene[current_scene].filterblock_configuration.val.i !=
5209 fc_wide);
5210
5211 param[i]->setDirty();
5212 param[i]->invalid();
5213
5214 // feedback control
5215 i = synth->storage.getPatch().scene[current_scene].feedback.id;
5216 if (param[i] && dynamic_cast<CSurgeSlider *>(param[i]) != nullptr)
5217 ((CSurgeSlider *)param[i])->disabled =
5218 (synth->storage.getPatch().scene[current_scene].filterblock_configuration.val.i ==
5219 fc_serial1);
5220
5221 param[i]->setDirty();
5222 param[i]->invalid();
5223 }
5224
5225 if (tag == fxbypass_tag) // still do the normal operation, that's why it's outside the
5226 // switch-statement
5227 {
5228 if (ccfxconf)
5229 {
5230 ((CEffectSettings *)ccfxconf)->set_bypass(synth->storage.getPatch().fx_bypass.val.i);
5231 ccfxconf->invalid();
5232 }
5233
5234 switch (synth->storage.getPatch().fx_bypass.val.i)
5235 {
5236 case fxb_no_fx:
5237 synth->fx_suspend_bitmask = synth->fx_suspend_bitmask | 0xff;
5238 break;
5239 case fxb_scene_fx_only:
5240 synth->fx_suspend_bitmask = synth->fx_suspend_bitmask | 0xf0;
5241 break;
5242 case fxb_no_sends:
5243 synth->fx_suspend_bitmask = synth->fx_suspend_bitmask | 0x30;
5244 break;
5245 case fxb_all_fx:
5246 default:
5247 break;
5248 }
5249 }
5250 }
5251
5252 //------------------------------------------------------------------------------------------------
5253
beginEdit(long tag)5254 void SurgeGUIEditor::beginEdit(long tag)
5255 {
5256 if (tag < start_paramtags)
5257 {
5258 return;
5259 }
5260
5261 int synthidx = tag - start_paramtags;
5262 SurgeSynthesizer::ID did;
5263
5264 if (synth->fromSynthSideId(synthidx, did))
5265 {
5266 super::beginEdit(did.getDawSideId());
5267 }
5268 }
5269
5270 //------------------------------------------------------------------------------------------------
5271
endEdit(long tag)5272 void SurgeGUIEditor::endEdit(long tag)
5273 {
5274 if (tag < start_paramtags)
5275 {
5276 return;
5277 }
5278
5279 int synthidx = tag - start_paramtags;
5280 SurgeSynthesizer::ID did;
5281
5282 if (synth->fromSynthSideId(synthidx, did))
5283 {
5284 super::endEdit(did.getDawSideId());
5285 }
5286 }
5287
5288 //------------------------------------------------------------------------------------------------
5289
controlBeginEdit(VSTGUI::CControl * control)5290 void SurgeGUIEditor::controlBeginEdit(VSTGUI::CControl *control)
5291 {
5292 #if TARGET_AUDIOUNIT
5293 long tag = control->getTag();
5294 int ptag = tag - start_paramtags;
5295
5296 if (tag >= tag_mod_source0 + ms_ctrl1 && tag <= tag_mod_source0 + ms_ctrl8)
5297 {
5298 ptag = metaparam_offset + tag - tag_mod_source0 - ms_ctrl1;
5299 SurgeSynthesizer::ID did;
5300 if (synth->fromSynthSideId(ptag, did))
5301 {
5302 synth->getParent()->ParameterBeginEdit(did.getDawSideIndex());
5303 }
5304 }
5305 else if ((ptag >= 0) && (ptag < synth->storage.getPatch().param_ptr.size()))
5306 {
5307 SurgeSynthesizer::ID did;
5308
5309 if (synth->fromSynthSideId(ptag, did))
5310 {
5311 synth->getParent()->ParameterBeginEdit(did.getDawSideIndex());
5312 }
5313 }
5314
5315 #endif
5316
5317 #if TARGET_VST3
5318 /*
5319 * I am sure this is us doing something wrong, but the first time through
5320 * of an edit of a discrete value (the digital/analog for instance) the
5321 * beignEdit starts sending me the old value over and over which isn't what
5322 * I want, and that old value is gotten when beginEdit is called, not when
5323 * endEdit is called. So this code, in LIve, establishes the value as correct
5324 * at the beginning of beginEdit. Which is odd but fixes issue #3283.
5325 */
5326 if (synth->hostProgram.find("Ableton") != string::npos)
5327 {
5328 long tag = control->getTag();
5329 int ptag = tag - start_paramtags;
5330 if (ptag >= 0 && ptag < synth->storage.getPatch().param_ptr.size())
5331 {
5332 auto id = synth->idForParameter(synth->storage.getPatch().param_ptr[ptag]);
5333 synth->setParameter01(id, control->getValue(), false);
5334 }
5335 }
5336 #endif
5337
5338 #if TARGET_JUCE_UI
5339 long tag = control->getTag();
5340 int ptag = tag - start_paramtags;
5341 if (ptag >= 0 && ptag < synth->storage.getPatch().param_ptr.size())
5342 {
5343 _effect->beginParameterEdit(synth->storage.getPatch().param_ptr[ptag]);
5344 }
5345 #endif
5346 }
5347
5348 //------------------------------------------------------------------------------------------------
5349
controlEndEdit(VSTGUI::CControl * control)5350 void SurgeGUIEditor::controlEndEdit(VSTGUI::CControl *control)
5351 {
5352 #if TARGET_AUDIOUNIT
5353 long tag = control->getTag();
5354 int ptag = tag - start_paramtags;
5355 if (tag >= tag_mod_source0 + ms_ctrl1 && tag <= tag_mod_source0 + ms_ctrl8)
5356 {
5357 ptag = metaparam_offset + tag - tag_mod_source0 - ms_ctrl1;
5358 SurgeSynthesizer::ID did;
5359 if (synth->fromSynthSideId(ptag, did))
5360 synth->getParent()->ParameterEndEdit(did.getDawSideIndex());
5361 }
5362 else if ((ptag >= 0) && (ptag < synth->storage.getPatch().param_ptr.size()))
5363 {
5364 SurgeSynthesizer::ID did;
5365
5366 if (synth->fromSynthSideId(ptag, did))
5367 {
5368 synth->getParent()->ParameterEndEdit(did.getDawSideIndex());
5369 }
5370 }
5371 #endif
5372
5373 #if TARGET_JUCE_UI
5374 long tag = control->getTag();
5375 int ptag = tag - start_paramtags;
5376 if (ptag >= 0 && ptag < synth->storage.getPatch().param_ptr.size())
5377 {
5378 _effect->endParameterEdit(synth->storage.getPatch().param_ptr[ptag]);
5379 }
5380 #endif
5381
5382 if (((CParameterTooltip *)infowindow)->isVisible())
5383 {
5384 auto cs = dynamic_cast<CSurgeSlider *>(control);
5385 if (cs == nullptr || cs->hasBeenDraggedDuringMouseGesture)
5386 ((CParameterTooltip *)infowindow)->Hide();
5387 else
5388 {
5389 clear_infoview_countdown = 15;
5390 }
5391 }
5392 }
5393
5394 //------------------------------------------------------------------------------------------------
5395
draw_infowindow(int ptag,CControl * control,bool modulate,bool forceMB)5396 void SurgeGUIEditor::draw_infowindow(int ptag, CControl *control, bool modulate, bool forceMB)
5397 {
5398 long buttons = 1; // context?context->getMouseButtons():1;
5399
5400 if (buttons && forceMB)
5401 return; // don't draw via CC if MB is down
5402
5403 // A heuristic
5404 auto ml = ((CParameterTooltip *)infowindow)->getMaxLabelLen();
5405 auto iff = 148;
5406 // This is just empirical. It would be lovely to use the actual string width but that needs a
5407 // draw context of for these to be TextLabels so we can call sizeToFit
5408 if (ml > 24)
5409 iff += (ml - 24) * 5;
5410
5411 auto modValues =
5412 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "modWindowShowsValues", 0);
5413
5414 ((CParameterTooltip *)infowindow)->setExtendedMDIWS(modValues);
5415 CRect r(0, 0, iff, 18);
5416 if (modulate)
5417 {
5418 int hasMDIWS = ((CParameterTooltip *)infowindow)->hasMDIWS();
5419 r.bottom += (hasMDIWS & modValues ? 36 : 18);
5420 if (modValues)
5421 r.right += 20;
5422 }
5423
5424 CRect r2 = control->getViewSize();
5425
5426 // OK this is a heuristic to stop deform overpainting and stuff
5427 if (r2.bottom > getWindowSizeY() - r.getHeight() - 2)
5428 {
5429 // stick myself on top please
5430 r.offset((r2.left / 150) * 150, r2.top - r.getHeight() - 2);
5431 }
5432 else
5433 {
5434 r.offset((r2.left / 150) * 150, r2.bottom);
5435 }
5436
5437 if (r.bottom > getWindowSizeY() - 2)
5438 {
5439 int ao = (getWindowSizeY() - 2 - r.bottom);
5440 r.offset(0, ao);
5441 }
5442
5443 if (r.right > getWindowSizeX() - 2)
5444 {
5445 int ao = (getWindowSizeX() - 2 - r.right);
5446 r.offset(ao, 0);
5447 }
5448
5449 if (r.left < 0)
5450 {
5451 int ao = 2 - r.left;
5452 r.offset(ao, 0);
5453 }
5454
5455 if (r.top < 0)
5456 {
5457 int ao = 2 - r.top;
5458 r.offset(0, ao);
5459 }
5460
5461 if (buttons || forceMB)
5462 {
5463 // make sure an infowindow doesn't appear twice
5464 if (((CParameterTooltip *)infowindow)->isVisible())
5465 {
5466 ((CParameterTooltip *)infowindow)->Hide();
5467 ((CParameterTooltip *)infowindow)->invalid();
5468 }
5469
5470 ((CParameterTooltip *)infowindow)->setViewSize(r);
5471 ((CParameterTooltip *)infowindow)->Show();
5472 infowindow->invalid();
5473 // on Linux the infoview closes too soon
5474 #if LINUX
5475 clear_infoview_countdown = 100;
5476 #else
5477 clear_infoview_countdown = 40;
5478 #endif
5479 }
5480 else
5481 {
5482 ((CParameterTooltip *)infowindow)->Hide();
5483 frame->invalidRect(r);
5484 }
5485 }
5486
applyParameterOffset(long id)5487 long SurgeGUIEditor::applyParameterOffset(long id) { return id - start_paramtags; }
5488
unapplyParameterOffset(long id)5489 long SurgeGUIEditor::unapplyParameterOffset(long id) { return id + start_paramtags; }
5490
5491 // Status Panel Callbacks
toggleMPE()5492 void SurgeGUIEditor::toggleMPE()
5493 {
5494 this->synth->mpeEnabled = !this->synth->mpeEnabled;
5495 if (statusMPE)
5496 {
5497 statusMPE->setValue(this->synth->mpeEnabled ? 1 : 0);
5498 statusMPE->invalid();
5499 }
5500 }
showZoomMenu(VSTGUI::CPoint & where)5501 void SurgeGUIEditor::showZoomMenu(VSTGUI::CPoint &where)
5502 {
5503 CRect menuRect;
5504 menuRect.offset(where.x, where.y);
5505 auto m = makeZoomMenu(menuRect, true);
5506
5507 frame->addView(m);
5508 m->setDirty();
5509 m->popup();
5510 frame->removeView(m, true);
5511 }
5512
showMPEMenu(VSTGUI::CPoint & where)5513 void SurgeGUIEditor::showMPEMenu(VSTGUI::CPoint &where)
5514 {
5515 CRect menuRect;
5516 menuRect.offset(where.x, where.y);
5517 auto m = makeMpeMenu(menuRect, true);
5518
5519 frame->addView(m);
5520 m->setDirty();
5521 m->popup();
5522 frame->removeView(m, true);
5523 }
showLfoMenu(VSTGUI::CPoint & where)5524 void SurgeGUIEditor::showLfoMenu(VSTGUI::CPoint &where)
5525 {
5526 CRect menuRect;
5527 menuRect.offset(where.x, where.y);
5528 auto m = makeLfoMenu(menuRect);
5529
5530 frame->addView(m);
5531 m->setDirty();
5532 m->popup();
5533 frame->removeView(m, true);
5534 }
5535
toggleTuning()5536 void SurgeGUIEditor::toggleTuning()
5537 {
5538 synth->storage.toggleTuningToCache();
5539
5540 if (statusTune)
5541 {
5542 bool hasmts = synth->storage.oddsound_mts_client && synth->storage.oddsound_mts_active;
5543 statusTune->setValue(this->synth->storage.isStandardTuning ? hasmts : 1);
5544 }
5545
5546 this->synth->refresh_editor = true;
5547 }
showTuningMenu(VSTGUI::CPoint & where)5548 void SurgeGUIEditor::showTuningMenu(VSTGUI::CPoint &where)
5549 {
5550 CRect menuRect;
5551 menuRect.offset(where.x, where.y);
5552 auto m = makeTuningMenu(menuRect, true);
5553
5554 frame->addView(m);
5555 m->setDirty();
5556 m->popup();
5557 frame->removeView(m, true);
5558 }
5559
scaleFileDropped(std::string fn)5560 void SurgeGUIEditor::scaleFileDropped(std::string fn)
5561 {
5562 try
5563 {
5564 this->synth->storage.retuneToScale(Tunings::readSCLFile(fn));
5565 this->synth->refresh_editor = true;
5566 }
5567 catch (Tunings::TuningError &e)
5568 {
5569 Surge::UserInteractions::promptError(e.what(), "SCL Error");
5570 }
5571 }
5572
mappingFileDropped(std::string fn)5573 void SurgeGUIEditor::mappingFileDropped(std::string fn)
5574 {
5575 try
5576 {
5577 this->synth->storage.remapToKeyboard(Tunings::readKBMFile(fn));
5578 this->synth->refresh_editor = true;
5579 }
5580 catch (Tunings::TuningError &e)
5581 {
5582 Surge::UserInteractions::promptError(e.what(), "KBM Error");
5583 }
5584 }
5585
doesZoomFitToScreen(float zf,float & correctedZf)5586 bool SurgeGUIEditor::doesZoomFitToScreen(float zf, float &correctedZf)
5587 {
5588 #if !LINUX
5589 correctedZf = zf;
5590 return true;
5591 #endif
5592
5593 CRect screenDim = Surge::GUI::getScreenDimensions(getFrame());
5594
5595 float baseW = getWindowSizeX();
5596 float baseH = getWindowSizeY();
5597
5598 /*
5599 ** Window decoration takes up some of the screen so don't zoom to full screen dimensions.
5600 ** This heuristic seems to work on Windows 10 and macOS 10.14 well enough.
5601 ** Keep these as integers to be consistent with the other zoom factors, and to make
5602 ** the error message cleaner.
5603 */
5604 int maxScreenUsage = 90;
5605
5606 /*
5607 ** In the startup path we may not have a clean window yet to give us a trustworthy
5608 ** screen dimension; so allow callers to suppress this check with an optional
5609 ** variable and set it only in the constructor of SurgeGUIEditor
5610 */
5611 if (zf != 100.0 && zf > 100 && screenDim.getHeight() > 0 && screenDim.getWidth() > 0 &&
5612 ((baseW * zf / 100.0) > maxScreenUsage * screenDim.getWidth() / 100.0 ||
5613 (baseH * zf / 100.0) > maxScreenUsage * screenDim.getHeight() / 100.0))
5614 {
5615 correctedZf = findLargestFittingZoomBetween(100.0, zf, 5, maxScreenUsage, baseW, baseH);
5616 return false;
5617 }
5618 else
5619 {
5620 correctedZf = zf;
5621 return true;
5622 }
5623 }
5624
resizeWindow(float zf)5625 void SurgeGUIEditor::resizeWindow(float zf) { setZoomFactor(zf, true); }
5626
setZoomFactor(float zf)5627 void SurgeGUIEditor::setZoomFactor(float zf) { setZoomFactor(zf, false); }
5628
setZoomFactor(float zf,bool resizeWindow)5629 void SurgeGUIEditor::setZoomFactor(float zf, bool resizeWindow)
5630 {
5631 #if TARGET_JUCE_UI
5632 zoomFactor = zf;
5633 if (currentSkin && resizeWindow)
5634 parentEd->setSize(currentSkin->getWindowSizeX(), currentSkin->getWindowSizeY());
5635 parentEd->setScaleFactor(zf * 0.01);
5636 #else
5637
5638 if (zf < minimumZoom)
5639 {
5640 zf = minimumZoom;
5641 showMinimumZoomError();
5642 }
5643
5644 CRect screenDim = Surge::GUI::getScreenDimensions(getFrame());
5645
5646 /*
5647 ** If getScreenDimensions() can't determine a size on all platforms it now
5648 ** returns a size 0 screen. In that case we will skip the min check but
5649 ** need to remember the zoom is invalid
5650 */
5651 if (screenDim.getWidth() == 0 || screenDim.getHeight() == 0)
5652 {
5653 zoomInvalid = true;
5654 }
5655
5656 float newZf;
5657 if (doesZoomFitToScreen(zf, newZf))
5658 {
5659 zoomFactor = zf;
5660 }
5661 else
5662 {
5663 zoomFactor = newZf;
5664 showTooLargeZoomError(screenDim.getWidth(), screenDim.getHeight(), zoomFactor);
5665 }
5666
5667 /*
5668 * Fixed zoom: zoom factor constraint
5669 */
5670 if (currentSkin && currentSkin->hasFixedZooms())
5671 {
5672 int nzf = 100;
5673 for (auto z : currentSkin->getFixedZooms())
5674 {
5675 if (z <= zoomFactor)
5676 nzf = z;
5677 }
5678 zoomFactor = nzf;
5679 }
5680
5681 // update zoom level stored in DAW extra state
5682 synth->storage.getPatch().dawExtraState.editor.instanceZoomFactor = zoomFactor;
5683
5684 zoom_callback(this, resizeWindow);
5685
5686 setBitmapZoomFactor(zoomFactor);
5687 #endif
5688 }
5689
setBitmapZoomFactor(float zf)5690 void SurgeGUIEditor::setBitmapZoomFactor(float zf)
5691 {
5692 float dbs = Surge::GUI::getDisplayBackingScaleFactor(getFrame());
5693 int fullPhysicalZoomFactor = (int)(zf * dbs);
5694 if (bitmapStore != nullptr)
5695 bitmapStore->setPhysicalZoomFactor(fullPhysicalZoomFactor);
5696 }
5697
showMinimumZoomError() const5698 void SurgeGUIEditor::showMinimumZoomError() const
5699 {
5700 std::ostringstream oss;
5701 oss << "The smallest zoom level possible on your platform is " << minimumZoom
5702 << "%. Sorry, you cannot make Surge any smaller!";
5703 Surge::UserInteractions::promptError(oss.str(), "Zoom Level Error");
5704 }
5705
showTooLargeZoomError(double width,double height,float zf) const5706 void SurgeGUIEditor::showTooLargeZoomError(double width, double height, float zf) const
5707 {
5708 #if !LINUX
5709 std::ostringstream msg;
5710 msg << "Surge adjusts the maximum zoom level in order to prevent the interface becoming larger "
5711 "than available screen area. "
5712 << "Your screen resolution is " << width << "x" << height << " "
5713 << "for which the target zoom level of " << zf << "% would be too large." << std::endl
5714 << std::endl;
5715 if (currentSkin && currentSkin->hasFixedZooms())
5716 {
5717 msg << "Surge chose the largest fitting fixed zoom which is provided by this skin.";
5718 }
5719 else
5720 {
5721 msg << "Surge chose the largest fitting zoom level of " << zf << "%.";
5722 }
5723 Surge::UserInteractions::promptError(msg.str(), "Zoom Level Adjusted");
5724 #endif
5725 }
5726
showSettingsMenu(CRect & menuRect)5727 void SurgeGUIEditor::showSettingsMenu(CRect &menuRect)
5728 {
5729 COptionMenu *settingsMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
5730 VSTGUI::COptionMenu::kNoDrawStyle |
5731 VSTGUI::COptionMenu::kMultipleCheckStyle);
5732
5733 int eid = 0;
5734
5735 auto mpeSubMenu = makeMpeMenu(menuRect, false);
5736 settingsMenu->addEntry(mpeSubMenu, Surge::UI::toOSCaseForMenu("MPE Options"));
5737 eid++;
5738 mpeSubMenu->forget();
5739
5740 auto tuningSubMenu = makeTuningMenu(menuRect, false);
5741 settingsMenu->addEntry(tuningSubMenu, "Tuning");
5742 eid++;
5743 tuningSubMenu->forget();
5744
5745 auto zoomMenu = makeZoomMenu(menuRect, false);
5746 settingsMenu->addEntry(zoomMenu, "Zoom");
5747 eid++;
5748 zoomMenu->forget();
5749
5750 auto skinSubMenu = makeSkinMenu(menuRect);
5751 settingsMenu->addEntry(skinSubMenu, "Skins");
5752 eid++;
5753 skinSubMenu->forget();
5754
5755 auto uiOptionsMenu = makeUserSettingsMenu(menuRect);
5756 settingsMenu->addEntry(uiOptionsMenu, Surge::UI::toOSCaseForMenu("User Settings"));
5757 uiOptionsMenu->forget();
5758 eid++;
5759
5760 auto dataSubMenu = makeDataMenu(menuRect);
5761 settingsMenu->addEntry(dataSubMenu, Surge::UI::toOSCaseForMenu("Data Folders"));
5762 eid++;
5763 dataSubMenu->forget();
5764
5765 auto midiSubMenu = makeMidiMenu(menuRect);
5766 settingsMenu->addEntry(midiSubMenu, Surge::UI::toOSCaseForMenu("MIDI Settings"));
5767 eid++;
5768 midiSubMenu->forget();
5769
5770 if (useDevMenu)
5771 {
5772 auto devSubMenu = makeDevMenu(menuRect);
5773 settingsMenu->addEntry(devSubMenu, Surge::UI::toOSCaseForMenu("Developer Options"));
5774 eid++;
5775 devSubMenu->forget();
5776 }
5777
5778 settingsMenu->addSeparator(eid++);
5779
5780 addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Reach the Developers..."), []() {
5781 Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/feedback");
5782 });
5783 eid++;
5784
5785 addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Read the Code..."), []() {
5786 Surge::UserInteractions::openURL("https://github.com/surge-synthesizer/surge/");
5787 });
5788 eid++;
5789
5790 addCallbackMenu(
5791 settingsMenu, Surge::UI::toOSCaseForMenu("Download Additional Content..."), []() {
5792 Surge::UserInteractions::openURL("https://github.com/surge-synthesizer/"
5793 "surge-synthesizer.github.io/wiki/Additional-Content");
5794 });
5795 eid++;
5796
5797 addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Surge Website..."), []() {
5798 Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/");
5799 });
5800 eid++;
5801
5802 addCallbackMenu(settingsMenu, Surge::UI::toOSCaseForMenu("Surge Manual..."), []() {
5803 Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/manual/");
5804 });
5805 eid++;
5806
5807 settingsMenu->addSeparator(eid++);
5808
5809 addCallbackMenu(settingsMenu, "About Surge", [this]() { this->showAboutBox(); });
5810 eid++;
5811
5812 frame->addView(settingsMenu);
5813 settingsMenu->setDirty();
5814 settingsMenu->popup();
5815 frame->removeView(settingsMenu, true);
5816 }
5817
makeLfoMenu(VSTGUI::CRect & menuRect)5818 VSTGUI::COptionMenu *SurgeGUIEditor::makeLfoMenu(VSTGUI::CRect &menuRect)
5819 {
5820 int currentLfoId = modsource_editor[current_scene] - ms_lfo1;
5821
5822 int shapev = synth->storage.getPatch().scene[current_scene].lfo[currentLfoId].shape.val.i;
5823 std::string what = "LFO";
5824 if (lt_mseg == shapev)
5825 what = "MSEG";
5826 if (lt_stepseq == shapev)
5827 what = "Step Seq";
5828 if (lt_envelope == shapev)
5829 what = "Envelope";
5830 if (lt_function == shapev)
5831 // TODO FIXME: When function LFO type is added, adjust this condition!
5832 what = "Envelope";
5833
5834 auto msurl = SurgeGUIEditor::helpURLForSpecial("lfo-presets");
5835 auto hurl = SurgeGUIEditor::fullyResolvedHelpURL(msurl);
5836
5837 COptionMenu *lfoSubMenu =
5838 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
5839 addCallbackMenu(lfoSubMenu, "[?] LFO Presets",
5840 [hurl]() { Surge::UserInteractions::openURL(hurl); }),
5841
5842 lfoSubMenu->addSeparator();
5843
5844 addCallbackMenu(lfoSubMenu, Surge::UI::toOSCaseForMenu("Save " + what + " As..."),
5845 [this, currentLfoId, what]() {
5846 // Prompt for a name
5847 promptForMiniEdit("preset", "Enter the name for " + what + " preset:",
5848 what + " Preset Name", CPoint(-1, -1),
5849 [this, currentLfoId](const std::string &s) {
5850 Surge::ModulatorPreset::savePresetToUser(
5851 string_to_path(s), &(this->synth->storage),
5852 current_scene, currentLfoId);
5853 });
5854 // and save
5855 });
5856
5857 auto presetCategories = Surge::ModulatorPreset::getPresets(&(synth->storage));
5858
5859 if (!presetCategories.empty())
5860 lfoSubMenu->addSeparator();
5861
5862 std::unordered_map<std::string, std::pair<COptionMenu *, bool>> subMenuMaps;
5863 subMenuMaps[""] = std::make_pair(lfoSubMenu, true);
5864 for (auto const &cat : presetCategories)
5865 {
5866 COptionMenu *catSubMenu =
5867 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
5868 subMenuMaps[cat.path] = std::make_pair(catSubMenu, false);
5869 }
5870 for (auto const &cat : presetCategories)
5871 {
5872 if (subMenuMaps.find(cat.path) == subMenuMaps.end())
5873 {
5874 // should never happen
5875 continue;
5876 }
5877 auto catSubMenu = subMenuMaps[cat.path].first;
5878
5879 auto parentMenu = lfoSubMenu;
5880 if (subMenuMaps.find(cat.parentPath) != subMenuMaps.end())
5881 {
5882 parentMenu = subMenuMaps[cat.parentPath].first;
5883 if (!subMenuMaps[cat.parentPath].second)
5884 {
5885 subMenuMaps[cat.parentPath].second = true;
5886 }
5887 }
5888
5889 auto catname = cat.name;
5890
5891 #if WINDOWS
5892 Surge::Storage::findReplaceSubstring(catname, std::string("&"), std::string("&&"));
5893 #endif
5894
5895 parentMenu->addEntry(catSubMenu, catname.c_str());
5896 catSubMenu->forget();
5897 }
5898
5899 for (auto const &cat : presetCategories)
5900 {
5901 if (subMenuMaps.find(cat.path) == subMenuMaps.end())
5902 {
5903 // should never happen
5904 continue;
5905 }
5906
5907 auto catSubMenu = subMenuMaps[cat.path].first;
5908
5909 if (catSubMenu->getNbEntries() > 0 && cat.presets.size() > 0)
5910 {
5911 catSubMenu->addSeparator();
5912 }
5913
5914 for (auto const &p : cat.presets)
5915 {
5916 auto pname = p.name;
5917
5918 #if WINDOWS
5919 Surge::Storage::findReplaceSubstring(pname, std::string("&"), std::string("&&"));
5920 #endif
5921
5922 addCallbackMenu(catSubMenu, pname, [this, p, currentLfoId]() {
5923 Surge::ModulatorPreset::loadPresetFrom(p.path, &(this->synth->storage),
5924 current_scene, currentLfoId);
5925
5926 auto newshape = this->synth->storage.getPatch()
5927 .scene[current_scene]
5928 .lfo[currentLfoId]
5929 .shape.val.i;
5930
5931 if (isAnyOverlayPresent(MSEG_EDITOR))
5932 {
5933 closeMSEGEditor();
5934 if (newshape == lt_mseg)
5935 showMSEGEditor();
5936 }
5937
5938 this->synth->refresh_editor = true;
5939 });
5940 }
5941 }
5942
5943 lfoSubMenu->addSeparator();
5944
5945 addCallbackMenu(lfoSubMenu, Surge::UI::toOSCaseForMenu("Rescan Presets"),
5946 []() { Surge::ModulatorPreset::forcePresetRescan(); });
5947 return lfoSubMenu;
5948 }
5949
makeMpeMenu(VSTGUI::CRect & menuRect,bool showhelp)5950 VSTGUI::COptionMenu *SurgeGUIEditor::makeMpeMenu(VSTGUI::CRect &menuRect, bool showhelp)
5951 {
5952
5953 COptionMenu *mpeSubMenu =
5954 new COptionMenu(menuRect, 0, 0, 0, 0, VSTGUI::COptionMenu::kNoDrawStyle);
5955
5956 auto hu = helpURLForSpecial("mpe-menu");
5957 if (hu != "" && showhelp)
5958 {
5959 auto lurl = fullyResolvedHelpURL(hu);
5960 addCallbackMenu(mpeSubMenu, "[?] MPE",
5961 [lurl]() { Surge::UserInteractions::openURL(lurl); });
5962 mpeSubMenu->addSeparator();
5963 }
5964
5965 std::string endis = "Enable MPE";
5966 if (synth->mpeEnabled)
5967 endis = "Disable MPE";
5968 addCallbackMenu(mpeSubMenu, endis.c_str(),
5969 [this]() { this->synth->mpeEnabled = !this->synth->mpeEnabled; });
5970 mpeSubMenu->addSeparator();
5971
5972 std::ostringstream oss;
5973 oss << "Change MPE Pitch Bend Range (Current: " << synth->storage.mpePitchBendRange
5974 << " Semitones)";
5975 addCallbackMenu(mpeSubMenu, Surge::UI::toOSCaseForMenu(oss.str().c_str()), [this, menuRect]() {
5976 // FIXME! This won't work on linux
5977 const auto c{std::to_string(int(synth->storage.mpePitchBendRange))};
5978 promptForMiniEdit(c, "Enter new MPE pitch bend range:", "MPE Pitch Bend Range",
5979 menuRect.getTopLeft(), [this](const std::string &c) {
5980 int newVal = ::atoi(c.c_str());
5981 this->synth->storage.mpePitchBendRange = newVal;
5982 });
5983 });
5984
5985 std::ostringstream oss2;
5986 int def = Surge::Storage::getUserDefaultValue(&(synth->storage), "mpePitchBendRange", 48);
5987 oss2 << "Change Default MPE Pitch Bend Range (Current: " << def << " Semitones)";
5988 addCallbackMenu(mpeSubMenu, Surge::UI::toOSCaseForMenu(oss2.str().c_str()), [this, menuRect]() {
5989 // FIXME! This won't work on linux
5990 const auto c{std::to_string(int(synth->storage.mpePitchBendRange))};
5991 promptForMiniEdit(c, "Enter default MPE pitch bend range:", "Default MPE Pitch Bend Range",
5992 menuRect.getTopLeft(), [this](const std::string &s) {
5993 int newVal = ::atoi(s.c_str());
5994 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
5995 "mpePitchBendRange", newVal);
5996 this->synth->storage.mpePitchBendRange = newVal;
5997 });
5998 });
5999
6000 auto men = makeSmoothMenu(menuRect, "pitchSmoothingMode",
6001 (int)ControllerModulationSource::SmoothingMode::DIRECT,
6002 [this](auto md) { this->resetPitchSmoothing(md); });
6003 mpeSubMenu->addEntry(men, Surge::UI::toOSCaseForMenu("MPE Pitch Bend Smoothing"));
6004 men->forget();
6005
6006 return mpeSubMenu;
6007 }
6008
makeMonoModeOptionsMenu(VSTGUI::CRect & menuRect,bool updateDefaults)6009 VSTGUI::COptionMenu *SurgeGUIEditor::makeMonoModeOptionsMenu(VSTGUI::CRect &menuRect,
6010 bool updateDefaults)
6011 {
6012 COptionMenu *monoSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6013 VSTGUI::COptionMenu::kNoDrawStyle |
6014 VSTGUI::COptionMenu::kMultipleCheckStyle);
6015
6016 auto mode = synth->storage.monoPedalMode;
6017 if (updateDefaults)
6018 mode = (MonoPedalMode)Surge::Storage::getUserDefaultValue(
6019 &(this->synth->storage), "monoPedalMode", (int)HOLD_ALL_NOTES);
6020
6021 auto cb = addCallbackMenu(
6022 monoSubMenu,
6023 Surge::UI::toOSCaseForMenu("Sustain Pedal Holds All Notes (No Note Off Retrigger)"),
6024 [this, updateDefaults]() {
6025 this->synth->storage.monoPedalMode = HOLD_ALL_NOTES;
6026 if (updateDefaults)
6027 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "monoPedalMode",
6028 (int)HOLD_ALL_NOTES);
6029 });
6030 if (mode == HOLD_ALL_NOTES)
6031 cb->setChecked(true);
6032
6033 cb = addCallbackMenu(
6034 monoSubMenu, Surge::UI::toOSCaseForMenu("Sustain Pedal Allows Note Off Retrigger"),
6035 [this, updateDefaults]() {
6036 this->synth->storage.monoPedalMode = RELEASE_IF_OTHERS_HELD;
6037 if (updateDefaults)
6038 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "monoPedalMode",
6039 (int)RELEASE_IF_OTHERS_HELD);
6040 });
6041 if (mode == RELEASE_IF_OTHERS_HELD)
6042 cb->setChecked(true);
6043
6044 return monoSubMenu;
6045 }
6046
makeTuningMenu(VSTGUI::CRect & menuRect,bool showhelp)6047 VSTGUI::COptionMenu *SurgeGUIEditor::makeTuningMenu(VSTGUI::CRect &menuRect, bool showhelp)
6048 {
6049 int tid = 0;
6050 COptionMenu *tuningSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6051 VSTGUI::COptionMenu::kNoDrawStyle |
6052 VSTGUI::COptionMenu::kMultipleCheckStyle);
6053
6054 auto hu = helpURLForSpecial("tun-menu");
6055
6056 if (hu != "" && showhelp)
6057 {
6058 auto lurl = fullyResolvedHelpURL(hu);
6059 addCallbackMenu(tuningSubMenu, "[?] Tuning ",
6060 [lurl]() { Surge::UserInteractions::openURL(lurl); });
6061 tid++;
6062 tuningSubMenu->addSeparator();
6063 }
6064
6065 bool isTuningEnabled = !synth->storage.isStandardTuning;
6066 bool isScaleEnabled = !synth->storage.isStandardScale;
6067 bool isMappingEnabled = !synth->storage.isStandardMapping;
6068
6069 if (isScaleEnabled)
6070 {
6071 auto tuningLabel = Surge::UI::toOSCaseForMenu("Current Tuning: ");
6072
6073 if (synth->storage.currentScale.description.empty())
6074 {
6075 tuningLabel += path_to_string(fs::path(synth->storage.currentScale.name).stem());
6076 }
6077 else
6078 {
6079 tuningLabel += synth->storage.currentScale.description;
6080 }
6081 auto curTuning = new CCommandMenuItem(CCommandMenuItem::Desc(tuningLabel.c_str()));
6082 curTuning->setEnabled(false);
6083 tuningSubMenu->addEntry(curTuning);
6084 tid++;
6085 }
6086
6087 if (isMappingEnabled)
6088 {
6089 auto mappingLabel = Surge::UI::toOSCaseForMenu("Current Keyboard Mapping: ");
6090 mappingLabel += path_to_string(fs::path(synth->storage.currentMapping.name).stem());
6091 auto curMapping = new CCommandMenuItem(CCommandMenuItem::Desc(mappingLabel.c_str()));
6092 curMapping->setEnabled(false);
6093 tuningSubMenu->addEntry(curMapping);
6094 tid++;
6095 }
6096
6097 if (isTuningEnabled || isMappingEnabled)
6098 {
6099 tuningSubMenu->addSeparator();
6100 tid++;
6101 }
6102
6103 auto *st = addCallbackMenu(tuningSubMenu, Surge::UI::toOSCaseForMenu("Set to Standard Tuning"),
6104 [this]() {
6105 this->synth->storage.retuneTo12TETScaleC261Mapping();
6106 this->synth->refresh_editor = true;
6107 });
6108 st->setEnabled(!this->synth->storage.isStandardTuning);
6109 tid++;
6110
6111 st = addCallbackMenu(tuningSubMenu,
6112 Surge::UI::toOSCaseForMenu("Set to Standard Scale (12-TET)"), [this]() {
6113 this->synth->storage.retuneTo12TETScale();
6114 this->synth->refresh_editor = true;
6115 });
6116 st->setEnabled(!this->synth->storage.isStandardScale);
6117 tid++;
6118 auto *kst = addCallbackMenu(
6119 tuningSubMenu, Surge::UI::toOSCaseForMenu("Set to Standard Mapping (Concert C)"), [this]() {
6120 this->synth->storage.remapToConcertCKeyboard();
6121 this->synth->refresh_editor = true;
6122 });
6123 kst->setEnabled(!this->synth->storage.isStandardMapping);
6124 tid++;
6125
6126 tuningSubMenu->addSeparator();
6127 tid++;
6128
6129 addCallbackMenu(tuningSubMenu, Surge::UI::toOSCaseForMenu("Load .scl Scale..."), [this]() {
6130 auto cb = [this](std::string sf) {
6131 std::string sfx = ".scl";
6132 if (sf.length() >= sfx.length())
6133 {
6134 if (sf.compare(sf.length() - sfx.length(), sfx.length(), sfx) != 0)
6135 {
6136 Surge::UserInteractions::promptError("Please select only .scl files!",
6137 "Invalid Choice");
6138 std::cout << "FILE is [" << sf << "]" << std::endl;
6139 return;
6140 }
6141 }
6142 try
6143 {
6144 auto sc = Tunings::readSCLFile(sf);
6145
6146 if (!this->synth->storage.retuneToScale(sc))
6147 {
6148 Surge::UserInteractions::promptError("This .scl file is not valid!",
6149 "File Format Error");
6150 return;
6151 }
6152 this->synth->refresh_editor = true;
6153 }
6154 catch (Tunings::TuningError &e)
6155 {
6156 Surge::UserInteractions::promptError(e.what(), "Loading Error");
6157 }
6158 };
6159
6160 auto scl_path =
6161 Surge::Storage::appendDirectory(this->synth->storage.datapath, "tuning-library", "SCL");
6162
6163 Surge::UserInteractions::promptFileOpenDialog(scl_path, ".scl",
6164 "Scala microtuning files (*.scl)", cb);
6165 });
6166 tid++;
6167
6168 addCallbackMenu(
6169 tuningSubMenu, Surge::UI::toOSCaseForMenu("Load .kbm Keyboard Mapping..."), [this]() {
6170 auto cb = [this](std::string sf) {
6171 std::string sfx = ".kbm";
6172 if (sf.length() >= sfx.length())
6173 {
6174 if (sf.compare(sf.length() - sfx.length(), sfx.length(), sfx) != 0)
6175 {
6176 Surge::UserInteractions::promptError("Please select only .kbm files!",
6177 "Invalid Choice");
6178 std::cout << "FILE is [" << sf << "]" << std::endl;
6179 return;
6180 }
6181 }
6182 try
6183 {
6184 auto kb = Tunings::readKBMFile(sf);
6185
6186 if (!this->synth->storage.remapToKeyboard(kb))
6187 {
6188 Surge::UserInteractions::promptError("This .kbm file is not valid!",
6189 "File Format Error");
6190 return;
6191 }
6192
6193 this->synth->refresh_editor = true;
6194 }
6195 catch (Tunings::TuningError &e)
6196 {
6197 Surge::UserInteractions::promptError(e.what(), "Loading Error");
6198 }
6199 };
6200
6201 auto kbm_path = Surge::Storage::appendDirectory(this->synth->storage.datapath,
6202 "tuning-library", "KBM Concert Pitch");
6203
6204 Surge::UserInteractions::promptFileOpenDialog(
6205 kbm_path, ".kbm", "Scala keyboard mapping files (*.kbm)", cb);
6206 });
6207 tid++;
6208
6209 int oct = 5 - Surge::Storage::getUserDefaultValue(&(this->synth->storage), "middleC", 1);
6210 string middle_A = "A" + to_string(oct);
6211
6212 addCallbackMenu(
6213 tuningSubMenu,
6214 Surge::UI::toOSCaseForMenu("Remap " + middle_A + " (MIDI Note 69) Directly to..."),
6215 [this, middle_A, menuRect]() {
6216 char ma[256];
6217 sprintf(ma, "Remap %s Frequency", middle_A.c_str());
6218
6219 char c[256];
6220 snprintf(c, 256, "440.0");
6221 promptForMiniEdit(c, "Remap MIDI note 69 frequency to: ", ma, menuRect.getTopLeft(),
6222 [this](const std::string &s) {
6223 float freq = ::atof(s.c_str());
6224 auto kb = Tunings::tuneA69To(freq);
6225 kb.name = "Note 69 Retuned 440 to " + std::to_string(freq);
6226 if (!this->synth->storage.remapToKeyboard(kb))
6227 {
6228 Surge::UserInteractions::promptError(
6229 "This .kbm file is not valid!", "File Format Error");
6230 return;
6231 }
6232 });
6233 });
6234
6235 tuningSubMenu->addSeparator();
6236
6237 auto mod = addCallbackMenu(
6238 tuningSubMenu, Surge::UI::toOSCaseForMenu("Apply Tuning at MIDI Input"), [this]() {
6239 this->synth->storage.setTuningApplicationMode(SurgeStorage::RETUNE_MIDI_ONLY);
6240 });
6241 mod->setChecked(synth->storage.tuningApplicationMode == SurgeStorage::RETUNE_MIDI_ONLY);
6242 tid++;
6243
6244 mod = addCallbackMenu(
6245 tuningSubMenu, Surge::UI::toOSCaseForMenu("Apply Tuning After Modulation"),
6246 [this]() { this->synth->storage.setTuningApplicationMode(SurgeStorage::RETUNE_ALL); });
6247 mod->setChecked(synth->storage.tuningApplicationMode == SurgeStorage::RETUNE_ALL);
6248 tid++;
6249
6250 tuningSubMenu->addSeparator();
6251
6252 bool tsMode = Surge::Storage::getUserDefaultValue(&(this->synth->storage), "useODDMTS", false);
6253 std::string txt = "Use ODDSound" + Surge::UI::toOSCaseForMenu(" MTS-ESP (if Loaded in DAW)");
6254
6255 auto menuItem = addCallbackMenu(tuningSubMenu, txt, [this, tsMode]() {
6256 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "useODDMTS", !tsMode);
6257 if (tsMode)
6258 {
6259 // We toggled to false
6260 this->synth->storage.deinitialize_oddsound();
6261 }
6262 else
6263 {
6264 this->synth->storage.initialize_oddsound();
6265 }
6266 });
6267
6268 if (tsMode && !this->synth->storage.oddsound_mts_client)
6269 {
6270 addCallbackMenu(tuningSubMenu, Surge::UI::toOSCaseForMenu("Reconnect to MTS-ESP"),
6271 [this]() { this->synth->storage.initialize_oddsound(); });
6272 }
6273 menuItem->setChecked(tsMode);
6274
6275 if (this->synth->storage.oddsound_mts_active && this->synth->storage.oddsound_mts_client)
6276 {
6277 // a 'turn MTS off' menu
6278 addCallbackMenu(tuningSubMenu, Surge::UI::toOSCaseForMenu("Disconnect from MTS-ESP"),
6279 [this]() {
6280 auto q = this->synth->storage.oddsound_mts_client;
6281 this->synth->storage.oddsound_mts_active = false;
6282 this->synth->storage.oddsound_mts_client = nullptr;
6283 MTS_DeregisterClient(q);
6284 });
6285
6286 // an MTS tuning toggle
6287 auto mm = addCallbackMenu(
6288 tuningSubMenu, Surge::UI::toOSCaseForMenu("Query Tuning at Note On Only"), [this]() {
6289 if (this->synth->storage.oddsoundRetuneMode == SurgeStorage::RETUNE_CONSTANT)
6290 {
6291 this->synth->storage.oddsoundRetuneMode = SurgeStorage::RETUNE_NOTE_ON_ONLY;
6292 }
6293 else
6294 {
6295 this->synth->storage.oddsoundRetuneMode = SurgeStorage::RETUNE_CONSTANT;
6296 }
6297 });
6298 mm->setEnabled(true);
6299 mm->setChecked(this->synth->storage.oddsoundRetuneMode ==
6300 SurgeStorage::RETUNE_NOTE_ON_ONLY);
6301
6302 // an 'MTS is on' disabled menu
6303 mm = addCallbackMenu(tuningSubMenu, Surge::UI::toOSCaseForMenu("MTS-ESP is Active"),
6304 []() {});
6305 mm->setEnabled(false);
6306
6307 // an 'MTS scale name' disabled menu
6308 std::string mtsScale = MTS_GetScaleName(synth->storage.oddsound_mts_client);
6309 mm = addCallbackMenu(tuningSubMenu, mtsScale, []() {});
6310 mm->setEnabled(false);
6311
6312 return tuningSubMenu;
6313 }
6314
6315 tuningSubMenu->addSeparator();
6316
6317 tid++;
6318 auto *sct = addCallbackMenu(
6319 tuningSubMenu, Surge::UI::toOSCaseForMenu("Show Current Tuning Information..."),
6320 [this]() { Surge::UserInteractions::showHTML(this->tuningToHtml()); });
6321 sct->setEnabled(!this->synth->storage.isStandardTuning);
6322
6323 addCallbackMenu(
6324 tuningSubMenu, Surge::UI::toOSCaseForMenu("Factory Tuning Library..."), [this]() {
6325 auto dpath =
6326 Surge::Storage::appendDirectory(this->synth->storage.datapath, "tuning-library");
6327
6328 Surge::UserInteractions::openFolderInFileBrowser(dpath);
6329 });
6330
6331 return tuningSubMenu;
6332 }
6333
makeZoomMenu(VSTGUI::CRect & menuRect,bool showhelp)6334 VSTGUI::COptionMenu *SurgeGUIEditor::makeZoomMenu(VSTGUI::CRect &menuRect, bool showhelp)
6335 {
6336 COptionMenu *zoomSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6337 VSTGUI::COptionMenu::kNoDrawStyle |
6338 VSTGUI::COptionMenu::kMultipleCheckStyle);
6339
6340 int zid = 0;
6341
6342 auto hu = helpURLForSpecial("zoom-menu");
6343 if (hu != "" && showhelp)
6344 {
6345 auto lurl = fullyResolvedHelpURL(hu);
6346 addCallbackMenu(zoomSubMenu, "[?] Zoom",
6347 [lurl]() { Surge::UserInteractions::openURL(lurl); });
6348 zid++;
6349
6350 zoomSubMenu->addSeparator(zid++);
6351 }
6352
6353 std::vector<int> zoomTos = {{100, 125, 150, 175, 200, 300, 400}};
6354 bool isFixed = false;
6355 if (currentSkin->hasFixedZooms())
6356 {
6357 isFixed = true;
6358 zoomTos = currentSkin->getFixedZooms();
6359 }
6360
6361 for (auto s : zoomTos) // These are somewhat arbitrary reasonable defaults
6362 {
6363 std::ostringstream lab;
6364 lab << "Zoom to " << s << "%";
6365 CCommandMenuItem *zcmd = new CCommandMenuItem(CCommandMenuItem::Desc(lab.str()));
6366 zcmd->setActions([this, s](CCommandMenuItem *m) { resizeWindow(s); });
6367 zoomSubMenu->addEntry(zcmd);
6368 if (s == zoomFactor)
6369 zcmd->setChecked(true);
6370 zid++;
6371 }
6372 zoomSubMenu->addSeparator(zid++);
6373
6374 if (isFixed)
6375 {
6376 /*
6377 * DO WE WANT SOMETHING LIKE THIS?
6378 addCallbackMenu( zoomSubMenu, Surge::UI::toOSCaseForMenu( "About fixed zoom skins..." ),
6379 [](){});
6380 */
6381 }
6382 else
6383 {
6384 for (auto jog : {-25, -10, 10, 25}) // These are somewhat arbitrary reasonable defaults also
6385 {
6386 std::ostringstream lab;
6387 if (jog > 0)
6388 lab << "Grow by " << jog << "%";
6389 else
6390 lab << "Shrink by " << -jog << "%";
6391
6392 CCommandMenuItem *zcmd = new CCommandMenuItem(CCommandMenuItem::Desc(lab.str()));
6393 zcmd->setActions(
6394 [this, jog](CCommandMenuItem *m) { resizeWindow(getZoomFactor() + jog); });
6395 zoomSubMenu->addEntry(zcmd);
6396 zid++;
6397 }
6398
6399 zoomSubMenu->addSeparator(zid++);
6400
6401 CCommandMenuItem *biggestZ = new CCommandMenuItem(
6402 CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Zoom to Largest")));
6403 biggestZ->setActions([this](CCommandMenuItem *m) {
6404 int newZF = findLargestFittingZoomBetween(100.0, 500.0, 5,
6405 90, // See comment in setZoomFactor
6406 getWindowSizeX(), getWindowSizeY());
6407 resizeWindow(newZF);
6408 });
6409 zoomSubMenu->addEntry(biggestZ);
6410 zid++;
6411
6412 CCommandMenuItem *smallestZ = new CCommandMenuItem(
6413 CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Zoom to Smallest")));
6414 smallestZ->setActions([this](CCommandMenuItem *m) { resizeWindow(minimumZoom); });
6415 zoomSubMenu->addEntry(smallestZ);
6416 zid++;
6417
6418 zoomSubMenu->addSeparator(zid++);
6419
6420 auto dzf =
6421 Surge::Storage::getUserDefaultValue(&(synth->storage), "defaultZoom", zoomFactor);
6422 std::ostringstream dss;
6423 dss << "Zoom to Default (" << dzf << "%)";
6424 CCommandMenuItem *todefaultZ = new CCommandMenuItem(
6425 CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu(dss.str().c_str())));
6426 todefaultZ->setActions([this, dzf](CCommandMenuItem *m) { resizeWindow(dzf); });
6427 zoomSubMenu->addEntry(todefaultZ);
6428 zid++;
6429 }
6430
6431 CCommandMenuItem *defaultZ = new CCommandMenuItem(
6432 CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Set Current Zoom Level as Default")));
6433 defaultZ->setActions([this](CCommandMenuItem *m) {
6434 Surge::Storage::updateUserDefaultValue(&(synth->storage), "defaultZoom", zoomFactor);
6435 });
6436 zoomSubMenu->addEntry(defaultZ);
6437 zid++;
6438
6439 if (!isFixed)
6440 {
6441 addCallbackMenu(zoomSubMenu, Surge::UI::toOSCaseForMenu("Set Default Zoom Level to..."),
6442 [this, menuRect]() {
6443 // FIXME! This won't work on linux
6444 char c[256];
6445 snprintf(c, 256, "%d", (int)zoomFactor);
6446 promptForMiniEdit(
6447 c, "Enter a default zoom level value:", "Set Default Zoom Level",
6448 menuRect.getTopLeft(), [this](const std::string &s) {
6449 int newVal = ::atoi(s.c_str());
6450 Surge::Storage::updateUserDefaultValue(&(synth->storage),
6451 "defaultZoom", newVal);
6452 resizeWindow(newVal);
6453 });
6454 });
6455 zid++;
6456 }
6457
6458 #if TARGET_VST3
6459 if (!isFixed)
6460 {
6461 zoomSubMenu->addSeparator(zid++);
6462
6463 bool dragResize =
6464 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "dragResizeVST3", true);
6465
6466 auto menuItem = addCallbackMenu(
6467 zoomSubMenu, Surge::UI::toOSCaseForMenu("Resize by Dragging the Bottom Right Corner"),
6468 [this, dragResize]() {
6469 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "dragResizeVST3",
6470 !dragResize);
6471 });
6472 menuItem->setChecked(dragResize);
6473
6474 zid++;
6475 }
6476 #endif
6477
6478 return zoomSubMenu;
6479 }
6480
makeUserSettingsMenu(VSTGUI::CRect & menuRect)6481 VSTGUI::COptionMenu *SurgeGUIEditor::makeUserSettingsMenu(VSTGUI::CRect &menuRect)
6482 {
6483 COptionMenu *uiOptionsMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6484 VSTGUI::COptionMenu::kNoDrawStyle |
6485 VSTGUI::COptionMenu::kMultipleCheckStyle);
6486
6487 #if WINDOWS
6488 #define SUPPORTS_TOUCH_MENU 1
6489 #else
6490 #define SUPPORTS_TOUCH_MENU 0
6491 #endif
6492
6493 #if SUPPORTS_TOUCH_MENU
6494 bool touchMode =
6495 Surge::Storage::getUserDefaultValue(&(synth->storage), "touchMouseMode", false);
6496 #else
6497 bool touchMode = false;
6498 #endif
6499
6500 // Mouse behavior submenu
6501 int mid = 0;
6502 COptionMenu *mouseSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6503 VSTGUI::COptionMenu::kNoDrawStyle |
6504 VSTGUI::COptionMenu::kMultipleCheckStyle);
6505
6506 std::string mouseLegacy = "Legacy";
6507 std::string mouseSlow = "Slow";
6508 std::string mouseMedium = "Medium";
6509 std::string mouseExact = "Exact";
6510
6511 VSTGUI::CCommandMenuItem *menuItem = nullptr;
6512
6513 menuItem = addCallbackMenu(mouseSubMenu, mouseLegacy.c_str(), [this]() {
6514 CSurgeSlider::sliderMoveRateState = CSurgeSlider::kLegacy;
6515 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "sliderMoveRateState",
6516 CSurgeSlider::sliderMoveRateState);
6517 });
6518 menuItem->setChecked((CSurgeSlider::sliderMoveRateState == CSurgeSlider::kLegacy));
6519 menuItem->setEnabled(!touchMode);
6520 mid++;
6521
6522 menuItem = addCallbackMenu(mouseSubMenu, mouseSlow.c_str(), [this]() {
6523 CSurgeSlider::sliderMoveRateState = CSurgeSlider::kSlow;
6524 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "sliderMoveRateState",
6525 CSurgeSlider::sliderMoveRateState);
6526 });
6527 menuItem->setChecked((CSurgeSlider::sliderMoveRateState == CSurgeSlider::kSlow));
6528 menuItem->setEnabled(!touchMode);
6529 mid++;
6530
6531 menuItem = addCallbackMenu(mouseSubMenu, mouseMedium.c_str(), [this]() {
6532 CSurgeSlider::sliderMoveRateState = CSurgeSlider::kMedium;
6533 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "sliderMoveRateState",
6534 CSurgeSlider::sliderMoveRateState);
6535 });
6536 menuItem->setChecked((CSurgeSlider::sliderMoveRateState == CSurgeSlider::kMedium));
6537 menuItem->setEnabled(!touchMode);
6538 mid++;
6539
6540 menuItem = addCallbackMenu(mouseSubMenu, mouseExact.c_str(), [this]() {
6541 CSurgeSlider::sliderMoveRateState = CSurgeSlider::kExact;
6542 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "sliderMoveRateState",
6543 CSurgeSlider::sliderMoveRateState);
6544 });
6545 menuItem->setChecked((CSurgeSlider::sliderMoveRateState == CSurgeSlider::kExact));
6546 menuItem->setEnabled(!touchMode);
6547 mid++;
6548
6549 mouseSubMenu->addSeparator(mid++);
6550
6551 bool tsMode = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6552 "showCursorWhileEditing", true);
6553
6554 menuItem = addCallbackMenu(
6555 mouseSubMenu, Surge::UI::toOSCaseForMenu("Show Cursor While Editing"), [this, tsMode]() {
6556 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6557 "showCursorWhileEditing", !tsMode);
6558 });
6559 menuItem->setChecked(tsMode);
6560 menuItem->setEnabled(!touchMode);
6561
6562 #if SUPPORTS_TOUCH_MENU
6563 mouseSubMenu->addSeparator();
6564 menuItem = addCallbackMenu(mouseSubMenu, Surge::UI::toOSCaseForMenu("Touchscreen Mode"),
6565 [this, touchMode]() {
6566 Surge::Storage::updateUserDefaultValue(
6567 &(this->synth->storage), "touchMouseMode", !touchMode);
6568 });
6569 menuItem->setChecked(touchMode);
6570 #endif
6571
6572 std::string mouseMenuName = Surge::UI::toOSCaseForMenu("Mouse Behavior");
6573
6574 uiOptionsMenu->addEntry(mouseSubMenu, mouseMenuName.c_str());
6575 mouseSubMenu->forget();
6576
6577 // patch defaults
6578 COptionMenu *patchDefMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6579 VSTGUI::COptionMenu::kNoDrawStyle |
6580 VSTGUI::COptionMenu::kMultipleCheckStyle);
6581
6582 VSTGUI::CCommandMenuItem *pdItem = nullptr;
6583
6584 pdItem = addCallbackMenu(
6585 patchDefMenu, Surge::UI::toOSCaseForMenu("Set Default Patch Author..."),
6586 [this, menuRect]() {
6587 string s = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6588 "defaultPatchAuthor", "");
6589 char txt[256];
6590 txt[0] = 0;
6591 if (Surge::Storage::isValidUTF8(s))
6592 strxcpy(txt, s.c_str(), 256);
6593 promptForMiniEdit(txt, "Enter default patch author name:", "Set Default Patch Author",
6594 menuRect.getTopLeft(), [this](const std::string &s) {
6595 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6596 "defaultPatchAuthor", s);
6597 });
6598 });
6599
6600 pdItem = addCallbackMenu(
6601 patchDefMenu, Surge::UI::toOSCaseForMenu("Set Default Patch Comment..."),
6602 [this, menuRect]() {
6603 string s = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6604 "defaultPatchComment", "");
6605 char txt[256];
6606 txt[0] = 0;
6607 if (Surge::Storage::isValidUTF8(s))
6608 strxcpy(txt, s.c_str(), 256);
6609 promptForMiniEdit(txt, "Enter default patch comment text:", "Set Default Patch Comment",
6610 menuRect.getTopLeft(), [this](const std::string &s) {
6611 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6612 "defaultPatchComment", s);
6613 });
6614 });
6615
6616 uiOptionsMenu->addEntry(patchDefMenu, Surge::UI::toOSCaseForMenu("Patch Defaults"));
6617 patchDefMenu->forget();
6618
6619 auto *dispDefMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6620 VSTGUI::COptionMenu::kNoDrawStyle |
6621 VSTGUI::COptionMenu::kMultipleCheckStyle);
6622
6623 // high precision value readouts
6624 bool precReadout = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6625 "highPrecisionReadouts", false);
6626
6627 menuItem =
6628 addCallbackMenu(dispDefMenu, Surge::UI::toOSCaseForMenu("High Precision Value Readouts"),
6629 [this, precReadout]() {
6630 Surge::Storage::updateUserDefaultValue(
6631 &(this->synth->storage), "highPrecisionReadouts", !precReadout);
6632 });
6633 menuItem->setChecked(precReadout);
6634
6635 // modulation value readout shows bounds
6636 bool modValues =
6637 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "modWindowShowsValues", false);
6638
6639 menuItem = addCallbackMenu(dispDefMenu,
6640 Surge::UI::toOSCaseForMenu("Modulation Value Readout Shows Bounds"),
6641 [this, modValues]() {
6642 Surge::Storage::updateUserDefaultValue(
6643 &(this->synth->storage), "modWindowShowsValues", !modValues);
6644 });
6645 menuItem->setChecked(modValues);
6646
6647 // lfoone. I think this is a display thing. But could be workflowalso?
6648 bool lfoone = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6649 "showGhostedLFOWaveReference", true);
6650
6651 menuItem = addCallbackMenu(
6652 dispDefMenu, Surge::UI::toOSCaseForMenu("Show Ghosted LFO Waveform Reference"),
6653 [this, lfoone]() {
6654 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6655 "showGhostedLFOWaveReference", !lfoone);
6656 this->frame->invalid();
6657 });
6658 menuItem->setChecked(lfoone);
6659
6660 // Middle C submenu
6661 COptionMenu *middleCSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6662 VSTGUI::COptionMenu::kNoDrawStyle |
6663 VSTGUI::COptionMenu::kMultipleCheckStyle);
6664
6665 VSTGUI::CCommandMenuItem *mcItem = nullptr;
6666
6667 auto mcValue = Surge::Storage::getUserDefaultValue(&(this->synth->storage), "middleC", 1);
6668
6669 mcItem = addCallbackMenu(middleCSubMenu, "C3", [this]() {
6670 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "middleC", 2);
6671 synth->refresh_editor = true;
6672 });
6673 mcItem->setChecked(mcValue == 2);
6674
6675 mcItem = addCallbackMenu(middleCSubMenu, "C4", [this]() {
6676 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "middleC", 1);
6677 synth->refresh_editor = true;
6678 });
6679 mcItem->setChecked(mcValue == 1);
6680
6681 mcItem = addCallbackMenu(middleCSubMenu, "C5", [this]() {
6682 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "middleC", 0);
6683 synth->refresh_editor = true;
6684 });
6685 mcItem->setChecked(mcValue == 0);
6686
6687 dispDefMenu->addEntry(middleCSubMenu, "Middle C");
6688 middleCSubMenu->forget();
6689
6690 uiOptionsMenu->addEntry(dispDefMenu, Surge::UI::toOSCaseForMenu("Value Displays"));
6691 dispDefMenu->forget();
6692
6693 auto *wfMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6694 VSTGUI::COptionMenu::kNoDrawStyle |
6695 VSTGUI::COptionMenu::kMultipleCheckStyle);
6696
6697 // activate individual scene outputs
6698 menuItem = addCallbackMenu(
6699 wfMenu, Surge::UI::toOSCaseForMenu("Activate Individual Scene Outputs"), [this]() {
6700 this->synth->activateExtraOutputs = !this->synth->activateExtraOutputs;
6701 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "activateExtraOutputs",
6702 this->synth->activateExtraOutputs ? 1 : 0);
6703 });
6704 menuItem->setChecked(synth->activateExtraOutputs);
6705
6706 bool msegSnapMem = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6707 "restoreMSEGSnapFromPatch", true);
6708
6709 menuItem =
6710 addCallbackMenu(wfMenu, Surge::UI::toOSCaseForMenu("Load MSEG Snap State from Patch"),
6711 [this, msegSnapMem]() {
6712 Surge::Storage::updateUserDefaultValue(
6713 &(this->synth->storage), "restoreMSEGSnapFromPatch", !msegSnapMem);
6714 });
6715 menuItem->setChecked(msegSnapMem);
6716
6717 // remember tab positions per scene
6718 bool tabPosMem = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
6719 "rememberTabPositionsPerScene", false);
6720
6721 menuItem = addCallbackMenu(
6722 wfMenu, Surge::UI::toOSCaseForMenu("Remember Tab Positions Per Scene"),
6723 [this, tabPosMem]() {
6724 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6725 "rememberTabPositionsPerScene", !tabPosMem);
6726 });
6727 menuItem->setChecked(tabPosMem);
6728
6729 // wrap around browsing patches within current category
6730 bool patchJogWrap =
6731 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "patchJogWraparound", true);
6732
6733 menuItem = addCallbackMenu(
6734 wfMenu, Surge::UI::toOSCaseForMenu("Previous/Next Patch Constrained to Current Category"),
6735 [this, patchJogWrap]() {
6736 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "patchJogWraparound",
6737 !patchJogWrap);
6738 });
6739 menuItem->setChecked(patchJogWrap);
6740
6741 uiOptionsMenu->addEntry(wfMenu, Surge::UI::toOSCaseForMenu("Workflow"));
6742 wfMenu->forget();
6743
6744 return uiOptionsMenu;
6745 }
6746
makeSkinMenu(VSTGUI::CRect & menuRect)6747 VSTGUI::COptionMenu *SurgeGUIEditor::makeSkinMenu(VSTGUI::CRect &menuRect)
6748 {
6749 int tid = 0;
6750 COptionMenu *skinSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6751 VSTGUI::COptionMenu::kNoDrawStyle |
6752 VSTGUI::COptionMenu::kMultipleCheckStyle);
6753
6754 auto &db = Surge::UI::SkinDB::get();
6755 bool hasTests = false;
6756
6757 // TODO: Later allow nesting
6758 std::map<std::string, std::vector<Surge::UI::SkinDB::Entry>> entryByCategory;
6759 for (auto &entry : db.getAvailableSkins())
6760 {
6761 entryByCategory[entry.category].push_back(entry);
6762 }
6763
6764 for (auto pr : entryByCategory)
6765 {
6766 auto addToThis = skinSubMenu;
6767 auto cat = pr.first;
6768 if (cat != "")
6769 {
6770 addToThis = new COptionMenu(menuRect, 0, 0, 0, 0,
6771 VSTGUI::COptionMenu::kNoDrawStyle |
6772 VSTGUI::COptionMenu::kMultipleCheckStyle);
6773 }
6774 for (auto &entry : pr.second)
6775 {
6776 auto dname = entry.displayName;
6777 if (useDevMenu)
6778 {
6779 dname += " (";
6780 if (entry.root.find(synth->storage.datapath) != std::string::npos)
6781 {
6782 dname += "factory";
6783 }
6784 else if (entry.root.find(synth->storage.userDataPath) != std::string::npos)
6785 {
6786 dname += "user";
6787 }
6788 else
6789 {
6790 dname += "other";
6791 }
6792
6793 dname += PATH_SEPARATOR + entry.name + ")";
6794 }
6795
6796 auto cb = addCallbackMenu(addToThis, dname, [this, entry]() {
6797 setupSkinFromEntry(entry);
6798 this->synth->refresh_editor = true;
6799 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "defaultSkin",
6800 entry.name);
6801 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6802 "defaultSkinRootType", entry.rootType);
6803 });
6804 cb->setChecked(entry.matchesSkin(currentSkin));
6805 tid++;
6806 }
6807 if (cat != "")
6808 {
6809 skinSubMenu->addEntry(addToThis, cat.c_str());
6810 addToThis->forget();
6811 }
6812 }
6813 skinSubMenu->addSeparator();
6814
6815 if (useDevMenu)
6816 {
6817 auto f5Value =
6818 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "skinReloadViaF5", 0);
6819 VSTGUI::CCommandMenuItem *valItem = nullptr;
6820
6821 valItem = addCallbackMenu(
6822 skinSubMenu, Surge::UI::toOSCaseForMenu("Use F5 To Reload Current Skin"),
6823 [this, f5Value]() {
6824 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "skinReloadViaF5",
6825 f5Value ? 0 : 1);
6826 });
6827 valItem->setChecked(f5Value == 1);
6828
6829 tid++;
6830
6831 int pxres =
6832 Surge::Storage::getUserDefaultValue(&(synth->storage), "layoutGridResolution", 16);
6833
6834 auto m = std::string("Show Layout Grid (") + std::to_string(pxres) + " px)";
6835
6836 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu(m),
6837 [this, pxres]() { this->showAboutBox(pxres); });
6838
6839 tid++;
6840
6841 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Change Layout Grid Resolution..."),
6842 [this, pxres]() {
6843 this->promptForMiniEdit(
6844 std::to_string(pxres),
6845 "Enter new resolution:", "Layout Grid Resolution", CPoint(400, 400),
6846 [this](const std::string &s) {
6847 Surge::Storage::updateUserDefaultValue(&(this->synth->storage),
6848 "layoutGridResolution",
6849 std::atoi(s.c_str()));
6850 });
6851 });
6852
6853 skinSubMenu->addSeparator();
6854 }
6855
6856 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Reload Current Skin"), [this]() {
6857 this->bitmapStore.reset(new SurgeBitmaps());
6858 this->bitmapStore->setupBitmapsForFrame(frame);
6859 if (!this->currentSkin->reloadSkin(this->bitmapStore))
6860 {
6861 auto &db = Surge::UI::SkinDB::get();
6862 auto msg =
6863 std::string(
6864 "Unable to load skin! Reverting the skin to Surge Classic.\n\nSkin error:\n") +
6865 db.getAndResetErrorString();
6866 this->currentSkin = db.defaultSkin(&(this->synth->storage));
6867 this->currentSkin->reloadSkin(this->bitmapStore);
6868 Surge::UserInteractions::promptError(msg, "Skin Loading Error");
6869 }
6870
6871 reloadFromSkin();
6872 this->synth->refresh_editor = true;
6873 });
6874 tid++;
6875
6876 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Rescan Skins"), [this]() {
6877 auto r = this->currentSkin->root;
6878 auto n = this->currentSkin->name;
6879
6880 auto &db = Surge::UI::SkinDB::get();
6881 db.rescanForSkins(&(this->synth->storage));
6882
6883 // So go find the skin
6884 auto e = db.getEntryByRootAndName(r, n);
6885 if (e.isJust())
6886 {
6887 setupSkinFromEntry(e.fromJust());
6888 }
6889 else
6890 {
6891 setupSkinFromEntry(db.getDefaultSkinEntry());
6892 }
6893 this->synth->refresh_editor = true;
6894 });
6895
6896 tid++;
6897
6898 skinSubMenu->addSeparator(tid++);
6899
6900 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Open Current Skin Folder..."),
6901 [this]() {
6902 Surge::UserInteractions::openFolderInFileBrowser(this->currentSkin->root +
6903 this->currentSkin->name);
6904 });
6905 tid++;
6906
6907 skinSubMenu->addSeparator();
6908
6909 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Show Skin Inspector..."),
6910 [this]() { Surge::UserInteractions::showHTML(skinInspectorHtml()); });
6911
6912 tid++;
6913
6914 addCallbackMenu(skinSubMenu, Surge::UI::toOSCaseForMenu("Skin Development Guide..."), []() {
6915 Surge::UserInteractions::openURL("https://surge-synthesizer.github.io/skin-manual.html");
6916 });
6917
6918 tid++;
6919
6920 return skinSubMenu;
6921 }
6922
makeDataMenu(VSTGUI::CRect & menuRect)6923 VSTGUI::COptionMenu *SurgeGUIEditor::makeDataMenu(VSTGUI::CRect &menuRect)
6924 {
6925 int did = 0;
6926 COptionMenu *dataSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
6927 VSTGUI::COptionMenu::kNoDrawStyle |
6928 VSTGUI::COptionMenu::kMultipleCheckStyle);
6929
6930 addCallbackMenu(
6931 dataSubMenu, Surge::UI::toOSCaseForMenu("Open Factory Data Folder..."), [this]() {
6932 Surge::UserInteractions::openFolderInFileBrowser(this->synth->storage.datapath);
6933 });
6934 did++;
6935
6936 addCallbackMenu(dataSubMenu, Surge::UI::toOSCaseForMenu("Open User Data Folder..."), [this]() {
6937 // make it if it isn't there
6938 fs::create_directories(string_to_path(this->synth->storage.userDataPath));
6939 Surge::UserInteractions::openFolderInFileBrowser(this->synth->storage.userDataPath);
6940 });
6941 did++;
6942
6943 addCallbackMenu(
6944 dataSubMenu, Surge::UI::toOSCaseForMenu("Set Custom User Data Folder..."), [this]() {
6945 auto cb = [this](std::string f) {
6946 // FIXME - check if f is a path
6947 this->synth->storage.userDataPath = f;
6948 Surge::Storage::updateUserDefaultValue(&(this->synth->storage), "userDataPath", f);
6949 this->synth->storage.refresh_wtlist();
6950 this->synth->storage.refresh_patchlist();
6951 };
6952 Surge::UserInteractions::promptFileOpenDialog(this->synth->storage.userDataPath, "", "",
6953 cb, true, true);
6954 });
6955 did++;
6956
6957 dataSubMenu->addSeparator(did++);
6958
6959 addCallbackMenu(dataSubMenu, Surge::UI::toOSCaseForMenu("Rescan All Data Folders"), [this]() {
6960 this->synth->storage.refresh_wtlist();
6961 this->synth->storage.refresh_patchlist();
6962 this->scannedForMidiPresets = false;
6963 CFxMenu::scanForUserPresets =
6964 true; // that's annoying now I see it side by side. But you know.
6965
6966 // Rescan for skins
6967 auto r = this->currentSkin->root;
6968 auto n = this->currentSkin->name;
6969
6970 auto &db = Surge::UI::SkinDB::get();
6971 db.rescanForSkins(&(this->synth->storage));
6972
6973 // So go find the skin
6974 auto e = db.getEntryByRootAndName(r, n);
6975 if (e.isJust())
6976 {
6977 setupSkinFromEntry(e.fromJust());
6978 }
6979 else
6980 {
6981 setupSkinFromEntry(db.getDefaultSkinEntry());
6982 }
6983
6984 // Will need to rebuild the FX menu also so...
6985 this->synth->refresh_editor = true;
6986 });
6987
6988 return dataSubMenu;
6989 }
6990
6991 // builds a menu for setting controller smoothing, used in ::makeMidiMenu and ::makeMpeMenu
6992 // key is the key given to getUserDefaultValue,
6993 // default is a value to default to,
6994 // setSmooth is a function called to set the smoothing value
makeSmoothMenu(VSTGUI::CRect & menuRect,const std::string & key,int defaultValue,std::function<void (ControllerModulationSource::SmoothingMode)> setSmooth)6995 VSTGUI::COptionMenu *SurgeGUIEditor::makeSmoothMenu(
6996 VSTGUI::CRect &menuRect, const std::string &key, int defaultValue,
6997 std::function<void(ControllerModulationSource::SmoothingMode)> setSmooth)
6998 {
6999 COptionMenu *smoothMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
7000 VSTGUI::COptionMenu::kNoDrawStyle |
7001 VSTGUI::COptionMenu::kMultipleCheckStyle);
7002
7003 int smoothing = Surge::Storage::getUserDefaultValue(&(synth->storage), key, defaultValue);
7004
7005 auto asmt = [this, smoothMenu, smoothing,
7006 setSmooth](const char *label, ControllerModulationSource::SmoothingMode md) {
7007 auto me = addCallbackMenu(smoothMenu, label, [setSmooth, md]() { setSmooth(md); });
7008 me->setChecked(smoothing == md);
7009 };
7010 asmt("Legacy", ControllerModulationSource::SmoothingMode::LEGACY);
7011 asmt("Slow Exponential", ControllerModulationSource::SmoothingMode::SLOW_EXP);
7012 asmt("Fast Exponential", ControllerModulationSource::SmoothingMode::FAST_EXP);
7013 asmt("Fast Linear", ControllerModulationSource::SmoothingMode::FAST_LINE);
7014 asmt("No Smoothing", ControllerModulationSource::SmoothingMode::DIRECT);
7015 return smoothMenu;
7016 };
7017
makeMidiMenu(VSTGUI::CRect & menuRect)7018 VSTGUI::COptionMenu *SurgeGUIEditor::makeMidiMenu(VSTGUI::CRect &menuRect)
7019 {
7020 COptionMenu *midiSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
7021 VSTGUI::COptionMenu::kNoDrawStyle |
7022 VSTGUI::COptionMenu::kMultipleCheckStyle);
7023
7024 auto smen = makeSmoothMenu(menuRect, "smoothingMode",
7025 (int)ControllerModulationSource::SmoothingMode::LEGACY,
7026 [this](auto md) { this->resetSmoothing(md); });
7027 midiSubMenu->addEntry(smen, Surge::UI::toOSCaseForMenu("Controller Smoothing"));
7028 smen->forget();
7029
7030 auto mmom = makeMonoModeOptionsMenu(menuRect, true);
7031 midiSubMenu->addEntry(mmom, Surge::UI::toOSCaseForMenu("Sustain Pedal In Mono Mode"));
7032 mmom->forget();
7033 midiSubMenu->addSeparator();
7034
7035 addCallbackMenu(midiSubMenu, Surge::UI::toOSCaseForMenu("Save MIDI Mapping As..."),
7036 [this, menuRect]() {
7037 this->scannedForMidiPresets = false; // force a rescan
7038 char msn[256];
7039 msn[0] = 0;
7040 promptForMiniEdit(msn, "MIDI Mapping Name", "Save MIDI Mapping",
7041 menuRect.getTopLeft(), [this](const std::string &s) {
7042 this->synth->storage.storeMidiMappingToName(s);
7043 });
7044 });
7045
7046 auto menuItem = addCallbackMenu(
7047 midiSubMenu, Surge::UI::toOSCaseForMenu("Set Current MIDI Mapping as Default"),
7048 [this]() { this->synth->storage.write_midi_controllers_to_user_default(); });
7049
7050 addCallbackMenu(
7051 midiSubMenu, Surge::UI::toOSCaseForMenu("Clear Current MIDI Mapping"), [this]() {
7052 int n = n_global_params + n_scene_params;
7053
7054 for (int i = 0; i < n; i++)
7055 {
7056 this->synth->storage.getPatch().param_ptr[i]->midictrl = -1;
7057 if (i > n_global_params)
7058 this->synth->storage.getPatch().param_ptr[i + n_scene_params]->midictrl = -1;
7059 }
7060 });
7061
7062 midiSubMenu->addSeparator();
7063
7064 addCallbackMenu(midiSubMenu, Surge::UI::toOSCaseForMenu("Show Current MIDI Mapping..."),
7065 [this]() { Surge::UserInteractions::showHTML(this->midiMappingToHtml()); });
7066
7067 if (!scannedForMidiPresets)
7068 {
7069 scannedForMidiPresets = true;
7070 synth->storage.rescanUserMidiMappings();
7071 }
7072
7073 bool gotOne = false;
7074 for (const auto &p : synth->storage.userMidiMappingsXMLByName)
7075 {
7076 if (!gotOne)
7077 {
7078 gotOne = true;
7079 midiSubMenu->addSeparator();
7080 }
7081 addCallbackMenu(midiSubMenu, p.first,
7082 [this, p] { this->synth->storage.loadMidiMappingByName(p.first); });
7083 }
7084
7085 return midiSubMenu;
7086 }
7087
reloadFromSkin()7088 void SurgeGUIEditor::reloadFromSkin()
7089 {
7090 if (!frame || !bitmapStore.get())
7091 return;
7092
7093 float dbs = Surge::GUI::getDisplayBackingScaleFactor(getFrame());
7094 bitmapStore->setPhysicalZoomFactor(getZoomFactor() * dbs);
7095
7096 auto bg = currentSkin->customBackgroundImage();
7097
7098 if (bg != "")
7099 {
7100 CScalableBitmap *cbm = bitmapStore->getBitmapByStringID(bg);
7101 if (cbm)
7102 {
7103 cbm->setExtraScaleFactor(getZoomFactor());
7104 frame->setBackground(cbm);
7105 }
7106 }
7107 else
7108 {
7109 CScalableBitmap *cbm = bitmapStore->getBitmap(IDB_MAIN_BG);
7110 cbm->setExtraScaleFactor(getZoomFactor());
7111 frame->setBackground(cbm);
7112 }
7113
7114 auto c = currentSkin->getColor(Colors::Dialog::Entry::Focus);
7115 frame->setFocusColor(c);
7116
7117 wsx = currentSkin->getWindowSizeX();
7118 wsy = currentSkin->getWindowSizeY();
7119
7120 float sf = 1;
7121 #if TARGET_VST2
7122 sf = getZoomFactor() / 100.0;
7123 #endif
7124 frame->setSize(wsx * sf, wsy * sf);
7125
7126 #if TARGET_VST3
7127 float uzf = getZoomFactor();
7128
7129 if (currentSkin->hasFixedZooms())
7130 {
7131 for (auto z : currentSkin->getFixedZooms())
7132 {
7133 if (z <= zoomFactor)
7134 {
7135 uzf = z;
7136 }
7137 }
7138 }
7139
7140 resizeToOnIdle = VSTGUI::CPoint(wsx * uzf / 100.0, wsy * uzf / 100.0);
7141
7142 sf = uzf / 100.0;
7143 #endif
7144
7145 rect.right = wsx * sf;
7146 rect.bottom = wsy * sf;
7147
7148 #if TARGET_JUCE_UI
7149 setZoomFactor(getZoomFactor(), true);
7150 #else
7151 setZoomFactor(getZoomFactor());
7152 #endif
7153 clearOffscreenCachesAtZero = 1;
7154
7155 // update MSEG editor if opened
7156 if (isAnyOverlayPresent(MSEG_EDITOR))
7157 {
7158 showMSEGEditor();
7159 }
7160
7161 // update Store Patch dialog if opened
7162 if (isAnyOverlayPresent(STORE_PATCH))
7163 {
7164 auto pname = patchName->getText();
7165 auto pcat = patchCategory->getText();
7166 auto pauth = patchCreator->getText();
7167 auto pcom = patchComment->getText();
7168
7169 showStorePatchDialog();
7170
7171 patchName->setText(pname);
7172 patchCategory->setText(pcat);
7173 patchCreator->setText(pauth);
7174 patchComment->setText(pcom);
7175 }
7176 }
7177
makeDevMenu(VSTGUI::CRect & menuRect)7178 VSTGUI::COptionMenu *SurgeGUIEditor::makeDevMenu(VSTGUI::CRect &menuRect)
7179 {
7180 int tid = 0;
7181
7182 COptionMenu *devSubMenu = new COptionMenu(menuRect, 0, 0, 0, 0,
7183 VSTGUI::COptionMenu::kNoDrawStyle |
7184 VSTGUI::COptionMenu::kMultipleCheckStyle);
7185
7186 #if WINDOWS
7187 VSTGUI::CCommandMenuItem *conItem = nullptr;
7188
7189 static bool consoleState;
7190
7191 conItem = addCallbackMenu(devSubMenu, Surge::UI::toOSCaseForMenu("Show Debug Console..."),
7192 []() { consoleState = Surge::Debug::toggleConsole(); });
7193 conItem->setChecked(consoleState);
7194 tid++;
7195 #endif
7196
7197 #ifdef INSTRUMENT_UI
7198 addCallbackMenu(devSubMenu, Surge::UI::toOSCaseForMenu("Show UI Instrumentation..."),
7199 []() { Surge::Debug::report(); });
7200 tid++;
7201 #endif
7202
7203 return devSubMenu;
7204 }
7205
findLargestFittingZoomBetween(int zoomLow,int zoomHigh,int zoomQuanta,int percentageOfScreenAvailable,float baseW,float baseH)7206 int SurgeGUIEditor::findLargestFittingZoomBetween(
7207 int zoomLow, // bottom of range
7208 int zoomHigh, // top of range
7209 int zoomQuanta, // step size
7210 int percentageOfScreenAvailable, // How much to shrink actual screen
7211 float baseW, float baseH)
7212 {
7213 // Here is a very crude implementation
7214 int result = zoomHigh;
7215 CRect screenDim = Surge::GUI::getScreenDimensions(getFrame());
7216 float sx = screenDim.getWidth() * percentageOfScreenAvailable / 100.0;
7217 float sy = screenDim.getHeight() * percentageOfScreenAvailable / 100.0;
7218
7219 while (result > zoomLow)
7220 {
7221 if (result * baseW / 100.0 <= sx && result * baseH / 100.0 <= sy)
7222 break;
7223 result -= zoomQuanta;
7224 }
7225 if (result < zoomLow)
7226 result = zoomLow;
7227
7228 return result;
7229 }
7230
addCallbackMenu(VSTGUI::COptionMenu * toThis,std::string label,std::function<void ()> op)7231 VSTGUI::CCommandMenuItem *SurgeGUIEditor::addCallbackMenu(VSTGUI::COptionMenu *toThis,
7232 std::string label,
7233 std::function<void()> op)
7234 {
7235 CCommandMenuItem *menu = new CCommandMenuItem(CCommandMenuItem::Desc(label.c_str()));
7236 menu->setActions([op](CCommandMenuItem *m) { op(); });
7237 toThis->addEntry(menu);
7238 return menu;
7239 }
7240
7241 #if TARGET_VST3
initialZoom()7242 bool SurgeGUIEditor::initialZoom()
7243 {
7244 if (initialZoomFactor != 100)
7245 {
7246 // Daw does not necessarily have any idea what size to draw on init. We need to tell it.
7247 setZoomFactor(initialZoomFactor);
7248 initialZoomFactor = 100;
7249
7250 // Important: this is the one and only window resize operation allowed inside onSize.
7251 // Infinite recursion warning!
7252 zoom_callback(this, true);
7253 return true;
7254 }
7255 return false;
7256 }
7257
resizeFromIdleSentinel()7258 void SurgeGUIEditor::resizeFromIdleSentinel()
7259 {
7260 if (resizeToOnIdle.x > 10 && resizeToOnIdle.y > 10)
7261 {
7262 Steinberg::IPlugFrame *ipf = getIPlugFrame();
7263 if (ipf)
7264 {
7265 Steinberg::ViewRect vr(0, 0, resizeToOnIdle.x, resizeToOnIdle.y);
7266 Steinberg::tresult res = ipf->resizeView(this, &vr);
7267 resizeToOnIdle = VSTGUI::CPoint(-1, -1);
7268 }
7269 }
7270 }
7271
onSize(Steinberg::ViewRect * newSize)7272 Steinberg::tresult PLUGIN_API SurgeGUIEditor::onSize(Steinberg::ViewRect *newSize)
7273 {
7274 if (!initialZoom())
7275 {
7276 float width = newSize->getWidth();
7277 float height = newSize->getHeight();
7278
7279 // resolve current zoomFactor
7280 float izfx = ceil(width / getWindowSizeX() * 100.0);
7281 float izfy = ceil(height / getWindowSizeY() * 100.0);
7282 float currentZoomFactor = std::min(izfx, izfy);
7283 currentZoomFactor = std::max(currentZoomFactor, 1.0f * minimumZoom);
7284
7285 // Don't allow me to set a zoom which will pop a dialog from this drag event. See #1212
7286 float correctedZoomFactor;
7287
7288 if (!doesZoomFitToScreen(currentZoomFactor, correctedZoomFactor))
7289 {
7290 currentZoomFactor = correctedZoomFactor;
7291 }
7292
7293 // If the resolved currentZoomFactor changes size by more than a pixel, store new size
7294 auto zfdx = std::fabs((currentZoomFactor - zoomFactor) * getWindowSizeX()) / 100.0;
7295 auto zfdy = std::fabs((currentZoomFactor - zoomFactor) * getWindowSizeY()) / 100.0;
7296 bool isFixed = currentSkin->hasFixedZooms();
7297 bool windowDragResize = std::max(zfdx, zfdy) > 1;
7298 bool allowDragResize =
7299 Surge::Storage::getUserDefaultValue(&(this->synth->storage), "dragResizeVST3", true);
7300
7301 if (allowDragResize && windowDragResize && !isFixed)
7302 {
7303 setZoomFactor(currentZoomFactor);
7304 }
7305 }
7306
7307 return Steinberg::Vst::VSTGUIEditor::onSize(newSize);
7308 }
7309
checkSizeConstraint(Steinberg::ViewRect * newSize)7310 Steinberg::tresult PLUGIN_API SurgeGUIEditor::checkSizeConstraint(Steinberg::ViewRect *newSize)
7311 {
7312 // we want cratio == tration by adjusting height so
7313 // WSX / WSY = gW / gH
7314 // gH = gW * WSY / WSX
7315 if (synth->hostProgram.find("Fruit") != std::string::npos) // see #2466
7316 {
7317 return EditorType::checkSizeConstraint(newSize);
7318 }
7319 else
7320 {
7321 float tratio = 1.0 * getWindowSizeX() / getWindowSizeY();
7322 float cratio = 1.0 * newSize->getWidth() / newSize->getHeight();
7323 if (cratio < tratio)
7324 {
7325 float newHeight = newSize->getWidth() / tratio;
7326 newSize->bottom = newSize->top + std::ceil(newHeight);
7327 }
7328 else
7329 {
7330 float newWidth = newSize->getHeight() * tratio;
7331 newSize->right = newSize->left + std::ceil(newWidth);
7332 }
7333
7334 return Steinberg::kResultTrue;
7335 }
7336 }
7337
7338 #endif
7339
forceautomationchangefor(Parameter * p)7340 void SurgeGUIEditor::forceautomationchangefor(Parameter *p)
7341 {
7342 #if TARGET_LV2
7343 // revisit this for non-LV2 outside 1.6.6
7344 synth->sendParameterAutomation(synth->idForParameter(p),
7345 synth->getParameter01(synth->idForParameter(p)));
7346 #endif
7347 }
7348 //------------------------------------------------------------------------------------------------
7349
promptForUserValueEntry(Parameter * p,CControl * c,int ms)7350 void SurgeGUIEditor::promptForUserValueEntry(Parameter *p, CControl *c, int ms)
7351 {
7352 if (typeinDialog)
7353 {
7354 typeinDialog->setVisible(false);
7355 removeFromFrame.push_back(typeinDialog);
7356 typeinDialog = nullptr;
7357 typeinResetCounter = -1;
7358 }
7359
7360 if (p)
7361 {
7362 typeinMode = Param;
7363 }
7364 else
7365 {
7366 typeinMode = Control;
7367 }
7368
7369 bool ismod = p && ms > 0;
7370 int boxht = 56;
7371 auto cp = c->getViewSize();
7372
7373 if (ismod)
7374 boxht += 22;
7375
7376 CRect typeinSize(cp.left, cp.top - boxht, cp.left + 120, cp.top - boxht + boxht);
7377
7378 if (cp.top - boxht < 0)
7379 {
7380 typeinSize =
7381 CRect(cp.left, cp.top + c->getHeight(), cp.left + 120, cp.top + c->getHeight() + boxht);
7382 }
7383
7384 typeinModSource = ms;
7385 typeinEditControl = c;
7386 typeinResetCounter = -1;
7387
7388 typeinDialog = new CViewContainer(typeinSize);
7389 typeinDialog->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Border));
7390 typeinDialog->setVisible(false);
7391 frame->addView(typeinDialog);
7392
7393 CRect innerSize(0, 0, typeinSize.getWidth(), typeinSize.getHeight());
7394 innerSize.inset(1, 1);
7395 auto inner = new CViewContainer(innerSize);
7396 CColor bggr = currentSkin->getColor(Colors::Dialog::Background);
7397 inner->setBackgroundColor(bggr);
7398 typeinDialog->addView(inner);
7399
7400 std::string lab = "";
7401 if (p)
7402 {
7403 if (p->ctrlgroup == cg_LFO)
7404 {
7405 char pname[1024];
7406 p->create_fullname(p->get_name(), pname, p->ctrlgroup, p->ctrlgroup_entry,
7407 modulatorName(p->ctrlgroup_entry, true).c_str());
7408 lab = pname;
7409 }
7410 else
7411 {
7412 lab = p->get_full_name();
7413 }
7414 }
7415 else
7416 {
7417 lab = modulatorName(ms, false);
7418 }
7419
7420 typeinLabel = new CTextLabel(CRect(2, 2, 114, 14), lab.c_str());
7421 typeinLabel->setFontColor(currentSkin->getColor(Colors::Slider::Label::Dark));
7422 typeinLabel->setTransparency(true);
7423 typeinLabel->setFont(displayFont);
7424 inner->addView(typeinLabel);
7425
7426 char txt[256];
7427 char ptext[1024], ptext2[1024];
7428 ptext2[0] = 0;
7429 if (p)
7430 {
7431 if (ismod)
7432 {
7433 char txt2[256];
7434 p->get_display_of_modulation_depth(txt, synth->getModDepth(p->id, (modsources)ms),
7435 synth->isBipolarModulation((modsources)ms),
7436 Parameter::TypeIn);
7437 p->get_display(txt2);
7438 sprintf(ptext, "mod: %s", txt);
7439 sprintf(ptext2, "current: %s", txt2);
7440 }
7441 else
7442 {
7443 p->get_display(txt);
7444 sprintf(ptext, "current: %s", txt);
7445 }
7446 }
7447 else
7448 {
7449 int detailedMode = Surge::Storage::getUserDefaultValue(&(this->synth->storage),
7450 "highPrecisionReadouts", 0);
7451 auto cms = ((ControllerModulationSource *)synth->storage.getPatch()
7452 .scene[current_scene]
7453 .modsources[ms]);
7454 sprintf(txt, "%.*f %%", (detailedMode ? 6 : 2), 100.0 * cms->get_output());
7455 sprintf(ptext, "current: %s", txt);
7456 }
7457
7458 if (ismod)
7459 {
7460 std::string mls = std::string("by ") + (char *)modulatorName(ms, true).c_str();
7461 auto ml = new CTextLabel(CRect(2, 10, 114, 27), mls.c_str());
7462 ml->setFontColor(currentSkin->getColor(Colors::Slider::Label::Dark));
7463 ml->setTransparency(true);
7464 ml->setFont(displayFont);
7465 inner->addView(ml);
7466 }
7467
7468 typeinPriorValueLabel = new CTextLabel(CRect(2, 29 - (ismod ? 0 : 23), 116, 36 + ismod), ptext);
7469 typeinPriorValueLabel->setFontColor(currentSkin->getColor(Colors::Slider::Label::Dark));
7470 typeinPriorValueLabel->setTransparency(true);
7471 typeinPriorValueLabel->setFont(displayFont);
7472 inner->addView(typeinPriorValueLabel);
7473
7474 if (ismod)
7475 {
7476 auto sl = new CTextLabel(CRect(2, 29 + 9, 116, 36 + 13), ptext2);
7477 sl->setFontColor(currentSkin->getColor(Colors::Slider::Label::Dark));
7478 sl->setTransparency(true);
7479 sl->setFont(displayFont);
7480 inner->addView(sl);
7481 }
7482
7483 typeinValue = new CTextEdit(CRect(4, 31 + (ismod ? 22 : 0), 114, 50 + (ismod ? 22 : 0)), this,
7484 tag_value_typein, txt);
7485 typeinValue->setBackColor(currentSkin->getColor(Colors::Dialog::Entry::Background));
7486 typeinValue->setFontColor(currentSkin->getColor(Colors::Dialog::Entry::Text));
7487
7488 // fix the text selection rectangle background overhanging the borders on Windows
7489 #if WINDOWS
7490 typeinValue->setTextInset(CPoint(3, 0));
7491 #endif
7492
7493 if (p)
7494 {
7495 if (!p->can_setvalue_from_string())
7496 {
7497 typeinValue->setFontColor(VSTGUI::kRedCColor);
7498 typeinValue->setText("Not available");
7499 }
7500 }
7501
7502 inner->addView(typeinValue);
7503 typeinEditTarget = p;
7504 typeinDialog->setVisible(true);
7505 typeinValue->takeFocus();
7506 }
7507
modulatorName(int i,bool button)7508 std::string SurgeGUIEditor::modulatorName(int i, bool button)
7509 {
7510 if ((i >= ms_lfo1 && i <= ms_slfo6))
7511 {
7512 int idx = i - ms_lfo1;
7513 bool isS = idx >= 6;
7514 int fnum = idx % 6;
7515 auto *lfodata = &(synth->storage.getPatch().scene[current_scene].lfo[i - ms_lfo1]);
7516
7517 if (lfodata->shape.val.i == lt_envelope)
7518 {
7519 char txt[64];
7520 if (button)
7521 sprintf(txt, "%sENV %d", (isS ? "S-" : ""), fnum + 1);
7522 else
7523 sprintf(txt, "%s Envelope %d", (isS ? "Scene" : "Voice"), fnum + 1);
7524 return std::string(txt);
7525 }
7526 else if (lfodata->shape.val.i == lt_stepseq)
7527 {
7528 char txt[64];
7529 if (button)
7530 sprintf(txt, "%sSEQ %d", (isS ? "S-" : ""), fnum + 1);
7531 else
7532 sprintf(txt, "%s Step Sequencer %d", (isS ? "Scene" : "Voice"), fnum + 1);
7533 return std::string(txt);
7534 }
7535 else if (lfodata->shape.val.i == lt_mseg)
7536 {
7537 char txt[64];
7538 if (button)
7539 sprintf(txt, "%sMSEG %d", (isS ? "S-" : ""), fnum + 1);
7540 else
7541 sprintf(txt, "%s MSEG %d", (isS ? "Scene" : "Voice"), fnum + 1);
7542 return std::string(txt);
7543 }
7544 else if (lfodata->shape.val.i == lt_function)
7545 {
7546 char txt[64];
7547
7548 // TODO FIXME: When function LFO type is added, uncomment the second sprintf and remove
7549 // the first one!
7550 if (button)
7551 sprintf(txt, "%sENV %d", (isS ? "S-" : ""), fnum + 1);
7552 // sprintf( txt, "%sFUN %d", (isS ? "S-" : "" ), fnum + 1 );
7553 else
7554 sprintf(txt, "%s Envelope %d", (isS ? "Scene" : "Voice"), fnum + 1);
7555 // sprintf( txt, "%s Function %d", (isS ? "Scene" : "Voice" ), fnum + 1 );
7556 return std::string(txt);
7557 }
7558 }
7559
7560 if (i >= ms_ctrl1 && i <= ms_ctrl8)
7561 {
7562 std::string ccl =
7563 std::string(synth->storage.getPatch().CustomControllerLabel[i - ms_ctrl1]);
7564 if (ccl == "-")
7565 {
7566 return std::string(modsource_names[i]);
7567 }
7568 else
7569 {
7570 return ccl + " (" + modsource_names[i] + ")";
7571 }
7572 }
7573 if (button)
7574 return std::string(modsource_names_button[i]);
7575 else
7576 return std::string(modsource_names[i]);
7577 }
7578
7579 #if TARGET_VST3
addVst3MenuForParams(VSTGUI::COptionMenu * contextMenu,const SurgeSynthesizer::ID & pid,int & eid)7580 Steinberg::Vst::IContextMenu *SurgeGUIEditor::addVst3MenuForParams(VSTGUI::COptionMenu *contextMenu,
7581 const SurgeSynthesizer::ID &pid,
7582 int &eid)
7583 {
7584 CRect menuRect;
7585 Steinberg::Vst::IComponentHandler *componentHandler = getController()->getComponentHandler();
7586 Steinberg::FUnknownPtr<Steinberg::Vst::IComponentHandler3> componentHandler3(componentHandler);
7587 Steinberg::Vst::IContextMenu *hostMenu = nullptr;
7588 if (componentHandler3)
7589 {
7590 std::stack<COptionMenu *> menuStack;
7591 menuStack.push(contextMenu);
7592 std::stack<int> eidStack;
7593 eidStack.push(eid);
7594
7595 Steinberg::Vst::ParamID param = pid.getDawSideId();
7596 hostMenu = componentHandler3->createContextMenu(this, ¶m);
7597
7598 int N = hostMenu ? hostMenu->getItemCount() : 0;
7599 if (N > 0)
7600 {
7601 contextMenu->addSeparator();
7602 eid++;
7603 }
7604
7605 std::deque<COptionMenu *> parentMenus;
7606 for (int i = 0; i < N; i++)
7607 {
7608 Steinberg::Vst::IContextMenu::Item item = {0};
7609 Steinberg::Vst::IContextMenuTarget *target = {0};
7610
7611 hostMenu->getItem(i, item, &target);
7612
7613 // char nm[1024];
7614 // Steinberg::UString128(item.name, 128).toAscii(nm, 1024);
7615 #if WINDOWS
7616 // https://stackoverflow.com/questions/32055357/visual-studio-c-2015-stdcodecvt-with-char16-t-or-char32-t
7617 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion;
7618 std::string nm = conversion.to_bytes((wchar_t *)(item.name));
7619 #else
7620 std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> conversion;
7621 std::string nm = conversion.to_bytes((char16_t *)(item.name));
7622 #endif
7623
7624 if (nm[0] ==
7625 '-') // FL sends us this as a separator with no VST indication so just strip the '-'
7626 {
7627 int pos = 1;
7628 while (nm[pos] == ' ' && nm[pos] != 0)
7629 pos++;
7630 nm = nm.substr(pos);
7631 }
7632
7633 auto itag = item.tag;
7634 /*
7635 ** Leave this here so we can debug if another vst3 problem comes up
7636 std::cout << nm << " FL=" << item.flags << " jGS=" <<
7637 Steinberg::Vst::IContextMenuItem::kIsGroupStart
7638 << " and=" << ( item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupStart )
7639 << " IGS="
7640 << ( ( item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupStart ) ==
7641 Steinberg::Vst::IContextMenuItem::kIsGroupStart ) << " IGE="
7642 << ( ( item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupEnd ) ==
7643 Steinberg::Vst::IContextMenuItem::kIsGroupEnd ) << " "
7644 << std::endl;
7645
7646 if( item.flags != 0 )
7647 printf( "FLAG %d IGS %d IGE %d SEP %d\n",
7648 item.flags,
7649 item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupStart,
7650 item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupEnd,
7651 item.flags & Steinberg::Vst::IContextMenuItem::kIsSeparator
7652 );
7653 */
7654 if ((item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupStart) ==
7655 Steinberg::Vst::IContextMenuItem::kIsGroupStart)
7656 {
7657 COptionMenu *subMenu = new COptionMenu(
7658 menuRect, 0, 0, 0, 0,
7659 VSTGUI::COptionMenu::kNoDrawStyle | VSTGUI::COptionMenu::kMultipleCheckStyle);
7660 menuStack.top()->addEntry(subMenu, nm.c_str());
7661 menuStack.push(subMenu);
7662 subMenu->forget();
7663 eidStack.push(0);
7664
7665 /*
7666 VSTGUI doesn't seem to allow a disabled or checked grouping menu.
7667 if( item.flags & Steinberg::Vst::IContextMenuItem::kIsDisabled )
7668 {
7669 subMenu->setEnabled(false);
7670 }
7671 if( item.flags & Steinberg::Vst::IContextMenuItem::kIsChecked )
7672 {
7673 subMenu->setChecked(true);
7674 }
7675 */
7676 }
7677 else if ((item.flags & Steinberg::Vst::IContextMenuItem::kIsGroupEnd) ==
7678 Steinberg::Vst::IContextMenuItem::kIsGroupEnd)
7679 {
7680 menuStack.pop();
7681 eidStack.pop();
7682 }
7683 else if (item.flags & Steinberg::Vst::IContextMenuItem::kIsSeparator)
7684 // separator not group end. Thanks for the insane definition of these constants VST3!
7685 // (See #3090)
7686 {
7687 menuStack.top()->addSeparator();
7688 }
7689 else
7690 {
7691 RememberForgetGuard<Steinberg::Vst::IContextMenuTarget> tg(target);
7692 RememberForgetGuard<Steinberg::Vst::IContextMenu> hm(hostMenu);
7693
7694 auto menu = addCallbackMenu(
7695 menuStack.top(), nm, [this, hm, tg, itag]() { tg.t->executeMenuItem(itag); });
7696 eidStack.top()++;
7697 if (item.flags & Steinberg::Vst::IContextMenuItem::kIsDisabled)
7698 {
7699 menu->setEnabled(false);
7700 }
7701 if (item.flags & Steinberg::Vst::IContextMenuItem::kIsChecked)
7702 {
7703 menu->setChecked(true);
7704 }
7705 }
7706 // hostMenu->addItem(item, &target);
7707 }
7708 eid = eidStack.top();
7709 }
7710 return hostMenu;
7711 }
7712 #endif
7713
helpURLFor(Parameter * p)7714 std::string SurgeGUIEditor::helpURLFor(Parameter *p)
7715 {
7716 auto storage = &(synth->storage);
7717 #if 0 // useful debug
7718 static bool once = false;
7719 if( ! once )
7720 {
7721 once = true;
7722 for( auto hp : storage->helpURL_paramidentifier )
7723 {
7724 auto k = hp.first;
7725 bool found = false;
7726 for (auto iter = synth->storage.getPatch().param_ptr.begin();
7727 iter != synth->storage.getPatch().param_ptr.end(); iter++)
7728 {
7729 Parameter* q = *iter;
7730 if( ! q ) break;
7731 if( q->ui_identifier == k )
7732 {
7733 found = true;
7734 break;
7735 }
7736 }
7737 if( ! found )
7738 std::cout << "UNFOUND : " << k << std::endl;
7739 }
7740 }
7741 #endif
7742 std::string id = p->ui_identifier;
7743 int type = -1;
7744 if (p->ctrlgroup == cg_OSC)
7745 {
7746 type = storage->getPatch().scene[current_scene].osc[current_osc[current_scene]].type.val.i;
7747 }
7748 if (p->ctrlgroup == cg_FX)
7749 {
7750 type = storage->getPatch().fx[current_fx].type.val.i;
7751 }
7752 if (type >= 0)
7753 {
7754 auto key = std::make_pair(id, type);
7755 if (storage->helpURL_paramidentifier_typespecialized.find(key) !=
7756 storage->helpURL_paramidentifier_typespecialized.end())
7757 {
7758 auto r = storage->helpURL_paramidentifier_typespecialized[key];
7759 if (r != "")
7760 return r;
7761 }
7762 }
7763 if (storage->helpURL_paramidentifier.find(id) != storage->helpURL_paramidentifier.end())
7764 {
7765 auto r = storage->helpURL_paramidentifier[id];
7766 if (r != "")
7767 return r;
7768 }
7769 if (storage->helpURL_controlgroup.find(p->ctrlgroup) != storage->helpURL_controlgroup.end())
7770 {
7771 auto r = storage->helpURL_controlgroup[p->ctrlgroup];
7772 if (r != "")
7773 return r;
7774 }
7775 return "";
7776 }
7777
helpURLForSpecial(std::string key)7778 std::string SurgeGUIEditor::helpURLForSpecial(std::string key)
7779 {
7780 auto storage = &(synth->storage);
7781 return helpURLForSpecial(storage, key);
7782 }
7783
helpURLForSpecial(SurgeStorage * storage,std::string key)7784 std::string SurgeGUIEditor::helpURLForSpecial(SurgeStorage *storage, std::string key)
7785 {
7786 if (storage->helpURL_specials.find(key) != storage->helpURL_specials.end())
7787 {
7788 auto r = storage->helpURL_specials[key];
7789 if (r != "")
7790 return r;
7791 }
7792 return "";
7793 }
fullyResolvedHelpURL(std::string helpurl)7794 std::string SurgeGUIEditor::fullyResolvedHelpURL(std::string helpurl)
7795 {
7796 std::string lurl = helpurl;
7797 if (helpurl[0] == '#')
7798 {
7799 lurl = "https://surge-synthesizer.github.io/manual/" + helpurl;
7800 }
7801 return lurl;
7802 }
7803
setupSkinFromEntry(const Surge::UI::SkinDB::Entry & entry)7804 void SurgeGUIEditor::setupSkinFromEntry(const Surge::UI::SkinDB::Entry &entry)
7805 {
7806 auto &db = Surge::UI::SkinDB::get();
7807 auto s = db.getSkin(entry);
7808 this->currentSkin = s;
7809 this->bitmapStore.reset(new SurgeBitmaps());
7810 this->bitmapStore->setupBitmapsForFrame(frame);
7811 if (!this->currentSkin->reloadSkin(this->bitmapStore))
7812 {
7813 std::ostringstream oss;
7814 oss << "Unable to load " << entry.root << entry.name
7815 << " skin! Reverting the skin to Surge Classic.\n\nSkin Error:\n"
7816 << db.getAndResetErrorString();
7817
7818 auto msg = std::string(oss.str());
7819 this->currentSkin = db.defaultSkin(&(this->synth->storage));
7820 this->currentSkin->reloadSkin(this->bitmapStore);
7821 Surge::UserInteractions::promptError(msg, "Skin Loading Error");
7822 }
7823 reloadFromSkin();
7824 }
7825
sliderHoverStart(int tag)7826 void SurgeGUIEditor::sliderHoverStart(int tag)
7827 {
7828 int ptag = tag - start_paramtags;
7829 for (int k = 1; k < n_modsources; k++)
7830 {
7831 modsources ms = (modsources)k;
7832 if (synth->isActiveModulation(ptag, ms))
7833 {
7834 auto gms = dynamic_cast<CModulationSourceButton *>(gui_modsrc[k]);
7835 if (gms)
7836 {
7837 gms->setSecondaryHover(true);
7838 }
7839 }
7840 };
7841 }
sliderHoverEnd(int tag)7842 void SurgeGUIEditor::sliderHoverEnd(int tag)
7843 {
7844 for (int k = 1; k < n_modsources; k++)
7845 {
7846 auto gms = dynamic_cast<CModulationSourceButton *>(gui_modsrc[k]);
7847 if (gms)
7848 {
7849 gms->setSecondaryHover(false);
7850 }
7851 }
7852 }
7853
dismissEditorOfType(OverlayTags ofType)7854 void SurgeGUIEditor::dismissEditorOfType(OverlayTags ofType)
7855 {
7856 if (editorOverlay.size() == 0)
7857 return;
7858
7859 auto newO = editorOverlay;
7860 newO.clear();
7861 for (auto el : editorOverlay)
7862 {
7863 if (el.first == ofType)
7864 {
7865 el.second->setVisible(false);
7866 auto f = editorOverlayOnClose[el.second];
7867 f();
7868
7869 editorOverlayOnClose.erase(el.second);
7870 editorOverlayContentsWeakReference.erase(el.second);
7871
7872 removeFromFrame.push_back(el.second);
7873 }
7874 else
7875 {
7876 newO.push_back(el);
7877 }
7878 }
7879 editorOverlay = newO;
7880 }
7881
addEditorOverlay(VSTGUI::CView * c,std::string editorTitle,OverlayTags editorTag,const VSTGUI::CPoint & topLeft,bool modalOverlay,bool hasCloseButton,std::function<void ()> onClose)7882 void SurgeGUIEditor::addEditorOverlay(VSTGUI::CView *c, std::string editorTitle,
7883 OverlayTags editorTag, const VSTGUI::CPoint &topLeft,
7884 bool modalOverlay, bool hasCloseButton,
7885 std::function<void()> onClose)
7886 {
7887 dismissEditorOfType(editorTag);
7888
7889 const int header = 18;
7890 const int buttonwidth = 18;
7891
7892 if (!c)
7893 return;
7894
7895 auto vs = c->getViewSize();
7896
7897 // add a solid outline thing which is bigger than the control with the title to that
7898 auto containerSize = vs;
7899 containerSize.top -= header;
7900
7901 if (!modalOverlay)
7902 containerSize.offset(-containerSize.left + topLeft.x, -containerSize.top + topLeft.y);
7903
7904 auto fs = CRect(0, 0, getWindowSizeX(), getWindowSizeY());
7905
7906 if (!modalOverlay)
7907 fs = containerSize;
7908
7909 // add a screen size transparent thing into the editorOverlay
7910 auto editorOverlayC = new CViewContainer(fs);
7911 editorOverlayC->setBackgroundColor(currentSkin->getColor(Colors::Overlay::Background));
7912 editorOverlayC->setVisible(true);
7913 frame->addView(editorOverlayC);
7914
7915 if (modalOverlay)
7916 containerSize = containerSize.centerInside(fs);
7917 else
7918 containerSize.moveTo(CPoint(0, 0));
7919
7920 auto headerFont = Surge::GUI::getLatoAtSize(9, kBoldFace);
7921 auto btnFont = Surge::GUI::getLatoAtSize(8);
7922
7923 auto outerc = new CViewContainer(containerSize);
7924 outerc->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Border));
7925 editorOverlayC->addView(outerc);
7926
7927 auto csz = containerSize;
7928 csz.bottom = csz.top + header;
7929 auto innerc = new CViewContainer(csz);
7930 innerc->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background));
7931 editorOverlayC->addView(innerc);
7932
7933 auto tl = new CTextLabel(csz, editorTitle.c_str());
7934 tl->setBackColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background));
7935 tl->setFrameColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background));
7936 tl->setFontColor(currentSkin->getColor(Colors::Dialog::Titlebar::Text));
7937 tl->setFont(headerFont);
7938 innerc->addView(tl);
7939
7940 auto iconrect = CRect(CPoint(3, 2), CPoint(15, 15));
7941 auto icon = new CViewContainer(iconrect);
7942 icon->setBackground(bitmapStore->getBitmap(IDB_SURGE_ICON));
7943 icon->setVisible(true);
7944 innerc->addView(icon);
7945
7946 if (hasCloseButton)
7947 {
7948 auto btnbg = currentSkin->getColor(Colors::Dialog::Button::Background);
7949 auto btnborder = currentSkin->getColor(Colors::Dialog::Button::Border);
7950 auto btntext = currentSkin->getColor(Colors::Dialog::Button::Text);
7951
7952 auto hovbtnbg = currentSkin->getColor(Colors::Dialog::Button::BackgroundHover);
7953 auto hovbtnborder = currentSkin->getColor(Colors::Dialog::Button::BorderHover);
7954 auto hovbtntext = currentSkin->getColor(Colors::Dialog::Button::TextHover);
7955
7956 auto pressbtnbg = currentSkin->getColor(Colors::Dialog::Button::BackgroundPressed);
7957 auto pressbtnborder = currentSkin->getColor(Colors::Dialog::Button::BorderPressed);
7958 auto pressbtntext = currentSkin->getColor(Colors::Dialog::Button::TextPressed);
7959
7960 VSTGUI::CGradient::ColorStopMap csm;
7961 VSTGUI::CGradient *cg = VSTGUI::CGradient::create(csm);
7962 cg->addColorStop(0, btnbg);
7963
7964 VSTGUI::CGradient::ColorStopMap hovcsm;
7965 VSTGUI::CGradient *hovcg = VSTGUI::CGradient::create(hovcsm);
7966 hovcg->addColorStop(0, hovbtnbg);
7967
7968 VSTGUI::CGradient::ColorStopMap presscsm;
7969 VSTGUI::CGradient *presscg = VSTGUI::CGradient::create(presscsm);
7970 presscg->addColorStop(0, pressbtnbg);
7971
7972 csz.left = csz.right - buttonwidth;
7973 csz.inset(2, 3);
7974 csz.top--;
7975 csz.bottom += 1;
7976 auto b = new CTextButtonWithHover(csz, this, tag_editor_overlay_close, "X");
7977 b->setVisible(true);
7978 b->setFont(btnFont);
7979 b->setGradient(cg);
7980 b->setFrameColor(btnborder);
7981 b->setTextColor(btntext);
7982 b->setHoverGradient(hovcg);
7983 b->setHoverFrameColor(hovbtnborder);
7984 b->setHoverTextColor(hovbtntext);
7985 b->setGradientHighlighted(presscg);
7986 b->setFrameColorHighlighted(pressbtnborder);
7987 b->setTextColorHighlighted(pressbtntext);
7988 b->setRoundRadius(CCoord(3.f));
7989
7990 innerc->addView(b);
7991 }
7992
7993 // add the control inside that in an outline
7994 if (modalOverlay)
7995 {
7996 containerSize = vs;
7997 containerSize.top -= header;
7998 containerSize = containerSize.centerInside(fs);
7999 containerSize.top += header;
8000 containerSize.inset(3, 3);
8001 }
8002 else
8003 {
8004 containerSize = vs.moveTo(0, header).inset(2, 0);
8005 containerSize.bottom -= 2;
8006 }
8007
8008 c->setViewSize(containerSize);
8009 c->setMouseableArea(containerSize); // sigh
8010 editorOverlayC->addView(c);
8011
8012 // save the onClose function
8013 editorOverlay.push_back(std::make_pair(editorTag, editorOverlayC));
8014 editorOverlayOnClose[editorOverlayC] = onClose;
8015 editorOverlayContentsWeakReference[editorOverlayC] = c;
8016 }
8017
getDisplayForTag(long tag)8018 std::string SurgeGUIEditor::getDisplayForTag(long tag)
8019 {
8020 if (tag < start_paramtags)
8021 return "Non-param tag";
8022
8023 int ptag = tag - start_paramtags;
8024 if ((ptag >= 0) && (ptag < synth->storage.getPatch().param_ptr.size()))
8025 {
8026 Parameter *p = synth->storage.getPatch().param_ptr[ptag];
8027 if (p)
8028 {
8029 char txt[1024];
8030 p->get_display(txt);
8031 return txt;
8032 }
8033 }
8034
8035 return "Unknown";
8036 }
8037
promptForMiniEdit(const std::string & value,const std::string & prompt,const std::string & title,const VSTGUI::CPoint & iwhere,std::function<void (const std::string &)> onOK)8038 void SurgeGUIEditor::promptForMiniEdit(const std::string &value, const std::string &prompt,
8039 const std::string &title, const VSTGUI::CPoint &iwhere,
8040 std::function<void(const std::string &)> onOK)
8041 {
8042 auto fs = CRect(0, 0, getWindowSizeX(), getWindowSizeY());
8043 minieditOverlay = new CViewContainer(fs);
8044 minieditOverlay->setBackgroundColor(currentSkin->getColor(Colors::Overlay::Background));
8045 minieditOverlay->setVisible(true);
8046 frame->addView(minieditOverlay);
8047
8048 auto where = iwhere;
8049 if (where.x < 0 || where.y < 0)
8050 {
8051 frame->getCurrentMouseLocation(where);
8052 frame->localToFrame(where);
8053 }
8054
8055 int wd = 160;
8056 int ht = 80;
8057
8058 // We want to center the text on where. The 0.4 just 'feels better' than 0.5
8059 where = where.offset(-wd * 0.4, -ht * 0.5);
8060
8061 auto rr = CRect(CPoint(where.x - fs.left, where.y - fs.top), CPoint(wd, ht));
8062
8063 if (rr.top < 0)
8064 rr = rr.offset(0, 10 - rr.top);
8065 if (rr.bottom > fs.getHeight())
8066
8067 rr.offset(0, -rr.bottom + fs.getHeight() - 10);
8068 if (rr.left < 0)
8069 rr = rr.offset(10 - rr.left, 0);
8070 if (rr.right > fs.getWidth())
8071
8072 rr.offset(-rr.right + fs.getWidth() - 10, 0);
8073
8074 auto fnt = Surge::GUI::getLatoAtSize(11);
8075 auto fnts = Surge::GUI::getLatoAtSize(9);
8076 auto fntt = Surge::GUI::getLatoAtSize(9, kBoldFace);
8077
8078 auto window = new CViewContainer(rr);
8079 window->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Border));
8080 window->setVisible(true);
8081 minieditOverlay->addView(window);
8082
8083 auto titlebar = new CViewContainer(CRect(0, 0, wd, 18));
8084 titlebar->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Titlebar::Background));
8085 titlebar->setVisible(true);
8086 window->addView(titlebar);
8087
8088 auto titlerect = CRect(CPoint(0, 0), CPoint(wd, 16));
8089 auto titletxt = new CTextLabel(titlerect, title.c_str());
8090 titletxt->setTransparency(true);
8091 titletxt->setFontColor(currentSkin->getColor(Colors::Dialog::Titlebar::Text));
8092 titletxt->setFont(fntt);
8093 titlebar->addView(titletxt);
8094
8095 auto iconrect = CRect(CPoint(3, 2), CPoint(15, 15));
8096 auto icon = new CViewContainer(iconrect);
8097 icon->setBackground(bitmapStore->getBitmap(IDB_SURGE_ICON));
8098 icon->setVisible(true);
8099 titlebar->addView(icon);
8100
8101 auto bgrect = CRect(CPoint(1, 18), CPoint(wd - 2, ht - 19));
8102 auto bg = new CViewContainer(bgrect);
8103 bg->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Background));
8104 bg->setVisible(true);
8105 window->addView(bg);
8106
8107 auto msgrect = CRect(CPoint(0, 2), CPoint(wd, 14));
8108 msgrect.inset(5, 0);
8109 auto msgtxt = new CTextLabel(msgrect, prompt.c_str());
8110 msgtxt->setTransparency(true);
8111 msgtxt->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Text));
8112 msgtxt->setFont(fnts);
8113 msgtxt->setHoriAlign(kLeftText);
8114 bg->addView(msgtxt);
8115
8116 auto mer = CRect(CPoint(0, 18), CPoint(wd - 2, 22));
8117 mer.inset(4, 2);
8118 minieditTypein = new CTextEdit(mer, this, tag_miniedit_ok, value.c_str());
8119 minieditTypein->setBackColor(currentSkin->getColor(Colors::Dialog::Entry::Background));
8120 minieditTypein->setFrameColor(currentSkin->getColor(Colors::Dialog::Entry::Border));
8121 minieditTypein->setFontColor(currentSkin->getColor(Colors::Dialog::Entry::Text));
8122 minieditTypein->setFont(fnt);
8123 #if WINDOWS
8124 minieditTypein->setTextInset(CPoint(3, 0));
8125 #endif
8126 bg->addView(minieditTypein);
8127 minieditTypein->takeFocus();
8128
8129 minieditOverlayDone = onOK;
8130
8131 int bw = 44;
8132 auto b1r = CRect(CPoint(wd - (bw * 2) - 9, bgrect.bottom - 36), CPoint(bw, 13));
8133 auto b2r = CRect(CPoint(wd - bw - 6, bgrect.bottom - 36), CPoint(bw, 13));
8134
8135 auto btnbg = currentSkin->getColor(Colors::Dialog::Button::Background);
8136 auto btnborder = currentSkin->getColor(Colors::Dialog::Button::Border);
8137 auto btntext = currentSkin->getColor(Colors::Dialog::Button::Text);
8138
8139 auto hovbtnbg = currentSkin->getColor(Colors::Dialog::Button::BackgroundHover);
8140 auto hovbtnborder = currentSkin->getColor(Colors::Dialog::Button::BorderHover);
8141 auto hovbtntext = currentSkin->getColor(Colors::Dialog::Button::TextHover);
8142
8143 auto pressbtnbg = currentSkin->getColor(Colors::Dialog::Button::BackgroundPressed);
8144 auto pressbtnborder = currentSkin->getColor(Colors::Dialog::Button::BorderPressed);
8145 auto pressbtntext = currentSkin->getColor(Colors::Dialog::Button::TextPressed);
8146
8147 VSTGUI::CGradient::ColorStopMap csm;
8148 VSTGUI::CGradient *cg = VSTGUI::CGradient::create(csm);
8149 cg->addColorStop(0, btnbg);
8150
8151 VSTGUI::CGradient::ColorStopMap hovcsm;
8152 VSTGUI::CGradient *hovcg = VSTGUI::CGradient::create(hovcsm);
8153 hovcg->addColorStop(0, hovbtnbg);
8154
8155 VSTGUI::CGradient::ColorStopMap presscsm;
8156 VSTGUI::CGradient *presscg = VSTGUI::CGradient::create(presscsm);
8157 presscg->addColorStop(0, pressbtnbg);
8158
8159 auto cb = new CTextButtonWithHover(b1r, this, tag_miniedit_cancel, "Cancel");
8160 cb->setVisible(true);
8161 cb->setFont(fnts);
8162 cb->setGradient(cg);
8163 cb->setFrameColor(btnborder);
8164 cb->setTextColor(btntext);
8165 cb->setHoverGradient(hovcg);
8166 cb->setHoverFrameColor(hovbtnborder);
8167 cb->setHoverTextColor(hovbtntext);
8168 cb->setGradientHighlighted(presscg);
8169 cb->setFrameColorHighlighted(pressbtnborder);
8170 cb->setTextColorHighlighted(pressbtntext);
8171 cb->setRoundRadius(CCoord(3.f));
8172 bg->addView(cb);
8173
8174 auto kb = new CTextButtonWithHover(b2r, this, tag_miniedit_ok, "OK");
8175 kb->setVisible(true);
8176 kb->setFont(fnts);
8177 kb->setGradient(cg);
8178 kb->setFrameColor(btnborder);
8179 kb->setTextColor(btntext);
8180 kb->setHoverGradient(hovcg);
8181 kb->setHoverFrameColor(hovbtnborder);
8182 kb->setHoverTextColor(hovbtntext);
8183 kb->setGradientHighlighted(presscg);
8184 kb->setFrameColorHighlighted(pressbtnborder);
8185 kb->setTextColorHighlighted(pressbtntext);
8186 kb->setRoundRadius(CCoord(3.f));
8187 bg->addView(kb);
8188 }
8189
swapControllers(int t1,int t2)8190 void SurgeGUIEditor::swapControllers(int t1, int t2)
8191 {
8192 synth->swapMetaControllers(t1 - tag_mod_source0 - ms_ctrl1, t2 - tag_mod_source0 - ms_ctrl1);
8193 }
8194
openModTypeinOnDrop(int modt,CControl * sl,int slidertag)8195 void SurgeGUIEditor::openModTypeinOnDrop(int modt, CControl *sl, int slidertag)
8196 {
8197 auto p = synth->storage.getPatch().param_ptr[slidertag - start_paramtags];
8198 int ms = modt - tag_mod_source0;
8199
8200 if (synth->isValidModulation(p->id, (modsources)ms))
8201 promptForUserValueEntry(p, sl, ms);
8202 }
8203
resetSmoothing(ControllerModulationSource::SmoothingMode t)8204 void SurgeGUIEditor::resetSmoothing(ControllerModulationSource::SmoothingMode t)
8205 {
8206 // Reset the default value and tell the synth it is updated
8207 Surge::Storage::updateUserDefaultValue(&(synth->storage), "smoothingMode", (int)t);
8208 synth->changeModulatorSmoothing(t);
8209 }
8210
resetPitchSmoothing(ControllerModulationSource::SmoothingMode t)8211 void SurgeGUIEditor::resetPitchSmoothing(ControllerModulationSource::SmoothingMode t)
8212 {
8213 // Reset the default value and update it in storage for newly created voices to use
8214 Surge::Storage::updateUserDefaultValue(&(synth->storage), "pitchSmoothingMode", (int)t);
8215 synth->storage.pitchSmoothingMode = t;
8216 }
8217
makeStorePatchDialog()8218 void SurgeGUIEditor::makeStorePatchDialog()
8219 {
8220 auto npc = Surge::Skin::Connector::NonParameterConnection::STORE_PATCH_DIALOG;
8221 auto conn = Surge::Skin::Connector::connectorByNonParameterConnection(npc);
8222 auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn);
8223
8224 // TODO: add skin connectors for all Store Patch dialog widgets
8225 // let's have fixed dialog size for now, once TODO is done use the commented out line instead
8226 CRect dialogSize(CPoint(0, 0), CPoint(390, 143));
8227 // CRect dialogSize(CPoint(0, 0), CPoint(skinCtrl->w, skinCtrl->h));
8228
8229 auto saveDialog = new CViewContainer(dialogSize);
8230 saveDialog->setBackgroundColor(currentSkin->getColor(Colors::Dialog::Background));
8231
8232 auto btnbg = currentSkin->getColor(Colors::Dialog::Button::Background);
8233 auto btnborder = currentSkin->getColor(Colors::Dialog::Button::Border);
8234 auto btntext = currentSkin->getColor(Colors::Dialog::Button::Text);
8235
8236 auto hovbtnbg = currentSkin->getColor(Colors::Dialog::Button::BackgroundHover);
8237 auto hovbtnborder = currentSkin->getColor(Colors::Dialog::Button::BorderHover);
8238 auto hovbtntext = currentSkin->getColor(Colors::Dialog::Button::TextHover);
8239
8240 auto pressbtnbg = currentSkin->getColor(Colors::Dialog::Button::BackgroundPressed);
8241 auto pressbtnborder = currentSkin->getColor(Colors::Dialog::Button::BorderPressed);
8242 auto pressbtntext = currentSkin->getColor(Colors::Dialog::Button::TextPressed);
8243
8244 VSTGUI::CGradient::ColorStopMap csm;
8245 VSTGUI::CGradient *cg = VSTGUI::CGradient::create(csm);
8246 cg->addColorStop(0, btnbg);
8247
8248 VSTGUI::CGradient::ColorStopMap hovcsm;
8249 VSTGUI::CGradient *hovcg = VSTGUI::CGradient::create(hovcsm);
8250 hovcg->addColorStop(0, hovbtnbg);
8251
8252 VSTGUI::CGradient::ColorStopMap presscsm;
8253 VSTGUI::CGradient *presscg = VSTGUI::CGradient::create(presscsm);
8254 presscg->addColorStop(0, pressbtnbg);
8255
8256 auto fnt = Surge::GUI::getLatoAtSize(11);
8257
8258 auto label = CRect(CPoint(10, 10), CPoint(47, 19));
8259 auto pnamelbl = new CTextLabel(label, "Name");
8260 pnamelbl->setTransparency(true);
8261 pnamelbl->setFont(fnt);
8262 pnamelbl->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Text));
8263 pnamelbl->setHoriAlign(kRightText);
8264
8265 label.offset(0, 24);
8266 auto pcatlbl = new CTextLabel(label, "Category");
8267 pcatlbl->setTransparency(true);
8268 pcatlbl->setFont(fnt);
8269 pcatlbl->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Text));
8270 pcatlbl->setHoriAlign(kRightText);
8271
8272 label.offset(0, 24);
8273 auto pauthlbl = new CTextLabel(label, "Author");
8274 pauthlbl->setTransparency(true);
8275 pauthlbl->setFont(fnt);
8276 pauthlbl->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Text));
8277 pauthlbl->setHoriAlign(kRightText);
8278
8279 label.offset(0, 24);
8280 auto pcomlbl = new CTextLabel(label, "Comment");
8281 pcomlbl->setTransparency(true);
8282 pcomlbl->setFont(fnt);
8283 pcomlbl->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Text));
8284 pcomlbl->setHoriAlign(kRightText);
8285
8286 patchName = new CTextEdit(CRect(CPoint(67, 10), CPoint(309, 19)), this, tag_store_name);
8287 patchCategory = new CTextEdit(CRect(CPoint(67, 34), CPoint(309, 19)), this, tag_store_category);
8288 patchCreator = new CTextEdit(CRect(CPoint(67, 58), CPoint(309, 19)), this, tag_store_creator);
8289 patchComment = new CTextEdit(CRect(CPoint(67, 82), CPoint(309, 19)), this, tag_store_comments);
8290 patchTuning = new CCheckBox(CRect(CPoint(67, 111), CPoint(200, 20)), this, tag_store_tuning,
8291 "Save With Tuning");
8292 patchTuning->setFont(fnt);
8293 patchTuning->setFontColor(currentSkin->getColor(Colors::Dialog::Label::Text));
8294 patchTuning->setBoxFrameColor(currentSkin->getColor(Colors::Dialog::Checkbox::Border));
8295 patchTuning->setBoxFillColor(currentSkin->getColor(Colors::Dialog::Checkbox::Background));
8296 patchTuning->setCheckMarkColor(currentSkin->getColor(Colors::Dialog::Checkbox::Tick));
8297 patchTuning->sizeToFit();
8298 patchTuning->setValue(0);
8299 patchTuning->setMouseEnabled(true);
8300 patchTuning->setVisible(!synth->storage.isStandardTuning);
8301
8302 // fix the text selection rectangle background overhanging the borders on Windows
8303 #if WINDOWS
8304 patchName->setTextInset(CPoint(3, 0));
8305 patchCategory->setTextInset(CPoint(3, 0));
8306 patchCreator->setTextInset(CPoint(3, 0));
8307 patchComment->setTextInset(CPoint(3, 0));
8308 #endif
8309
8310 /*
8311 * There is, apparently, a bug in VSTGui that focus events don't fire reliably on some mac
8312 * hosts. This leads to the odd behaviour when you click out of a box that in some hosts - Logic
8313 * Pro for instance - there is no looseFocus event and so the value doesn't update. We could fix
8314 * that a variety of ways I imagine, but since we don't really mind the value being updated as
8315 * we go, we can just set the editors to immediate and correct the problem.
8316 *
8317 * See GitHub Issue #231 for an explanation of the behaviour without these changes as of Jan
8318 * 2019.
8319 */
8320 patchName->setImmediateTextChange(true);
8321 patchCategory->setImmediateTextChange(true);
8322 patchCreator->setImmediateTextChange(true);
8323 patchComment->setImmediateTextChange(true);
8324
8325 patchName->setBackColor(currentSkin->getColor(Colors::Dialog::Entry::Background));
8326 patchCategory->setBackColor(currentSkin->getColor(Colors::Dialog::Entry::Background));
8327 patchCreator->setBackColor(currentSkin->getColor(Colors::Dialog::Entry::Background));
8328 patchComment->setBackColor(currentSkin->getColor(Colors::Dialog::Entry::Background));
8329
8330 patchName->setFontColor(currentSkin->getColor(Colors::Dialog::Entry::Text));
8331 patchCategory->setFontColor(currentSkin->getColor(Colors::Dialog::Entry::Text));
8332 patchCreator->setFontColor(currentSkin->getColor(Colors::Dialog::Entry::Text));
8333 patchComment->setFontColor(currentSkin->getColor(Colors::Dialog::Entry::Text));
8334
8335 patchName->setFrameColor(currentSkin->getColor(Colors::Dialog::Entry::Border));
8336 patchCategory->setFrameColor(currentSkin->getColor(Colors::Dialog::Entry::Border));
8337 patchCreator->setFrameColor(currentSkin->getColor(Colors::Dialog::Entry::Border));
8338 patchComment->setFrameColor(currentSkin->getColor(Colors::Dialog::Entry::Border));
8339
8340 auto b1r = CRect(CPoint(266, 111), CPoint(50, 20));
8341 auto cb = new CTextButtonWithHover(b1r, this, tag_store_cancel, "Cancel");
8342 cb->setFont(aboutFont);
8343 cb->setGradient(cg);
8344 cb->setFrameColor(btnborder);
8345 cb->setTextColor(btntext);
8346 cb->setHoverGradient(hovcg);
8347 cb->setHoverFrameColor(hovbtnborder);
8348 cb->setHoverTextColor(hovbtntext);
8349 cb->setGradientHighlighted(presscg);
8350 cb->setFrameColorHighlighted(pressbtnborder);
8351 cb->setTextColorHighlighted(pressbtntext);
8352 cb->setRoundRadius(CCoord(3.f));
8353
8354 auto b2r = CRect(CPoint(326, 111), CPoint(50, 20));
8355 auto kb = new CTextButtonWithHover(b2r, this, tag_store_ok, "OK");
8356 kb->setFont(aboutFont);
8357 kb->setGradient(cg);
8358 kb->setFrameColor(btnborder);
8359 kb->setTextColor(btntext);
8360 kb->setHoverGradient(hovcg);
8361 kb->setHoverFrameColor(hovbtnborder);
8362 kb->setHoverTextColor(hovbtntext);
8363 kb->setGradientHighlighted(presscg);
8364 kb->setFrameColorHighlighted(pressbtnborder);
8365 kb->setTextColorHighlighted(pressbtntext);
8366 kb->setRoundRadius(CCoord(3.f));
8367
8368 saveDialog->addView(pnamelbl);
8369 saveDialog->addView(pcatlbl);
8370 saveDialog->addView(pauthlbl);
8371 saveDialog->addView(pcomlbl);
8372 saveDialog->addView(patchName);
8373 saveDialog->addView(patchCategory);
8374 saveDialog->addView(patchCreator);
8375 saveDialog->addView(patchComment);
8376 saveDialog->addView(patchTuning);
8377 saveDialog->addView(patchTuningLabel);
8378 saveDialog->addView(cb);
8379 saveDialog->addView(kb);
8380
8381 addEditorOverlay(saveDialog, "Store Patch", STORE_PATCH, CPoint(skinCtrl->x, skinCtrl->y),
8382 false, false, [this]() {});
8383 }
8384
8385 VSTGUI::CControl *
layoutComponentForSkin(std::shared_ptr<Surge::UI::Skin::Control> skinCtrl,long tag,int paramIndex,Parameter * p,int style)8386 SurgeGUIEditor::layoutComponentForSkin(std::shared_ptr<Surge::UI::Skin::Control> skinCtrl, long tag,
8387 int paramIndex, Parameter *p, int style)
8388 {
8389 // Special cases to preserve things
8390 if (p && p->ctrltype == ct_fmconfig)
8391 {
8392 fmconfig_tag = tag;
8393 }
8394 if (p && p->ctrltype == ct_fbconfig)
8395 {
8396 filterblock_tag = tag;
8397 }
8398 if (p && p->ctrltype == ct_fxbypass)
8399 {
8400 fxbypass_tag = tag;
8401 }
8402 // Basically put this in a function
8403 if (skinCtrl->ultimateparentclassname == "CSurgeSlider")
8404 {
8405 if (!p)
8406 {
8407 // FIXME ERROR
8408 return nullptr;
8409 }
8410
8411 CPoint loc(skinCtrl->x, skinCtrl->y + p->posy_offset * yofs);
8412
8413 if (p->is_discrete_selection())
8414 {
8415 loc.offset(2, 4);
8416 auto hs = new CMenuAsSlider(loc, this, p->id + start_paramtags, bitmapStore,
8417 &(synth->storage));
8418
8419 auto *parm = dynamic_cast<ParameterDiscreteIndexRemapper *>(p->user_data);
8420 if (parm && parm->supportsTotalIndexOrdering())
8421 hs->intOrdering = parm->totalIndexOrdering();
8422
8423 hs->setSkin(currentSkin, bitmapStore);
8424 hs->setValue(p->get_value_f01());
8425 hs->setMinMax(p->val_min.i, p->val_max.i);
8426 hs->setLabel(p->get_name());
8427 p->ctrlstyle = p->ctrlstyle | kNoPopup;
8428 frame->addView(hs);
8429 if (p->can_deactivate())
8430 hs->setDeactivated(p->deactivated);
8431
8432 param[paramIndex] = hs;
8433 return hs;
8434 }
8435 else
8436 {
8437 p->ctrlstyle = p->ctrlstyle & ~kNoPopup;
8438 }
8439 bool is_mod = p && synth->isValidModulation(p->id, modsource);
8440
8441 auto hs = new CSurgeSlider(loc, style, this, tag, is_mod, bitmapStore, &(synth->storage));
8442 hs->setSkin(currentSkin, bitmapStore, skinCtrl);
8443 hs->setMoveRate(p->moverate);
8444
8445 if (p->can_temposync())
8446 hs->setTempoSync(p->temposync);
8447 hs->setValue(p->get_value_f01());
8448
8449 if (p->supportsDynamicName() && p->dynamicName)
8450 {
8451 hs->setDynamicLabel([p]() { return std::string(p->get_name()); });
8452 }
8453 else
8454 {
8455 hs->setLabel(p->get_name());
8456 }
8457
8458 hs->setBipolarFunction([p]() { return p->is_bipolar(); });
8459 hs->setModPresent(synth->isModDestUsed(p->id));
8460 hs->setDefaultValue(p->get_default_value_f01());
8461 hs->font_style = Surge::UI::Skin::setFontStyleProperty(
8462 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::FONT_STYLE, "normal"));
8463
8464 hs->text_align = Surge::UI::Skin::setTextAlignProperty(
8465 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_ALIGN, "right"));
8466
8467 // CSurgeSlider is using labfont = displayFont, which is currently 9 pt in size
8468 // TODO: Pull the default font size from some central location at a later date
8469 hs->font_size = std::atoi(
8470 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::FONT_SIZE, "9").c_str());
8471
8472 hs->text_hoffset = std::atoi(
8473 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_HOFFSET, "0")
8474 .c_str());
8475
8476 hs->text_voffset = std::atoi(
8477 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_VOFFSET, "0")
8478 .c_str());
8479
8480 hs->setDeactivatedFn([p]() { return p->appears_deactivated(); });
8481
8482 #if !TARGET_JUCE_UI && 0
8483 auto ff = currentSkin->propertyValue(skinCtrl, "font-family", "");
8484 if (ff.size() > 0)
8485 {
8486 hs->setFont(new CFontDesc(ff.c_str(), 9));
8487 }
8488 #endif
8489
8490 if (p->valtype == vt_int || p->valtype == vt_bool)
8491 {
8492 hs->isStepped = true;
8493 hs->intRange = p->val_max.i - p->val_min.i;
8494 }
8495 else
8496 {
8497 hs->isStepped = false;
8498 }
8499
8500 setDisabledForParameter(p, hs);
8501
8502 if (synth->isValidModulation(p->id, modsource))
8503 {
8504 hs->setModMode(mod_editor ? 1 : 0);
8505 hs->setModValue(synth->getModulation(p->id, modsource));
8506 hs->setModCurrent(synth->isActiveModulation(p->id, modsource),
8507 synth->isBipolarModulation(modsource));
8508 }
8509 else
8510 {
8511 // Even if current modsource isn't modulating me, something else may be
8512 }
8513 frame->addView(hs);
8514 if (paramIndex >= 0)
8515 param[paramIndex] = hs;
8516 return hs;
8517 }
8518 if (skinCtrl->ultimateparentclassname == "CHSwitch2")
8519 {
8520 CRect rect(0, 0, skinCtrl->w, skinCtrl->h);
8521 rect.offset(skinCtrl->x, skinCtrl->y);
8522
8523 // Make this a function on skin
8524 auto bmp = currentSkin->backgroundBitmapForControl(skinCtrl, bitmapStore);
8525
8526 if (bmp)
8527 {
8528 // Special case that scene select parameter is "odd"
8529 if (p && p->ctrltype == ct_scenesel)
8530 tag = tag_scene_select;
8531
8532 auto frames = currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::FRAMES, "1");
8533 auto rows = currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::ROWS, "1");
8534 auto cols = currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::COLUMNS, "1");
8535 auto frameoffset =
8536 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::FRAME_OFFSET, "0");
8537 auto drgb = currentSkin->propertyValue(skinCtrl,
8538 Surge::Skin::Component::DRAGGABLE_HSWITCH, "1");
8539 auto hsw = new CHSwitch2(rect, this, tag, std::atoi(frames.c_str()), skinCtrl->h,
8540 std::atoi(rows.c_str()), std::atoi(cols.c_str()), bmp,
8541 CPoint(0, 0), std::atoi(drgb.c_str()));
8542 if (p)
8543 {
8544 auto fval = p->get_value_f01();
8545
8546 if (p->ctrltype == ct_scenemode)
8547 {
8548 /*
8549 ** SceneMode is special now because we have a streaming vs UI difference.
8550 ** The streamed integer value is 0, 1, 2, 3 which matches the scene_mode
8551 ** SurgeStorage enum. But our display would look gross in that order, so
8552 ** our display order is single, split, channel split, dual which is 0, 1, 3, 2.
8553 ** Fine. So just deal with that here.
8554 */
8555 auto guiscenemode = p->val.i;
8556 if (guiscenemode == 3)
8557 guiscenemode = 2;
8558 else if (guiscenemode == 2)
8559 guiscenemode = 3;
8560 fval = Parameter::intScaledToFloat(guiscenemode, n_scene_modes - 1);
8561 }
8562 hsw->setValue(fval);
8563 }
8564 hsw->setSkin(currentSkin, bitmapStore, skinCtrl);
8565 hsw->setMouseableArea(rect);
8566 hsw->frameOffset = std::atoi(frameoffset.c_str());
8567
8568 frame->addView(hsw);
8569 if (paramIndex >= 0)
8570 nonmod_param[paramIndex] = hsw;
8571
8572 return hsw;
8573 }
8574 else
8575 {
8576 std::cout << "Can't get a CHSwitch2 BG" << std::endl;
8577 }
8578 }
8579 if (skinCtrl->ultimateparentclassname == "CSwitchControl")
8580 {
8581 CRect rect(CPoint(skinCtrl->x, skinCtrl->y), CPoint(skinCtrl->w, skinCtrl->h));
8582 auto bmp = currentSkin->backgroundBitmapForControl(skinCtrl, bitmapStore);
8583 if (bmp)
8584 {
8585 CSwitchControl *hsw = new CSwitchControl(rect, this, tag, bmp);
8586 hsw->setSkin(currentSkin, bitmapStore, skinCtrl);
8587 hsw->setMouseableArea(rect);
8588 frame->addView(hsw);
8589 if (paramIndex >= 0)
8590 nonmod_param[paramIndex] = hsw;
8591 if (p)
8592 {
8593 hsw->setValue(p->get_value_f01());
8594
8595 // Carry over this filter type special case from the default control path
8596 if (p->ctrltype == ct_filtersubtype)
8597 {
8598 auto filttype = synth->storage.getPatch()
8599 .scene[current_scene]
8600 .filterunit[p->ctrlgroup_entry]
8601 .type.val.i;
8602 auto stc = fut_subcount[filttype];
8603 hsw->is_itype = true;
8604 hsw->imax = stc;
8605 hsw->ivalue = std::min(p->val.i + 1, stc);
8606 if (fut_subcount[filttype] == 0)
8607 hsw->ivalue = 0;
8608
8609 if (p->ctrlgroup_entry == 1)
8610 {
8611 f2subtypetag = p->id + start_paramtags;
8612 filtersubtype[1] = hsw;
8613 }
8614 else
8615 {
8616 f1subtypetag = p->id + start_paramtags;
8617 filtersubtype[0] = hsw;
8618 }
8619 }
8620 }
8621 return hsw;
8622 }
8623 }
8624 if (skinCtrl->ultimateparentclassname == "CLFOGui")
8625 {
8626 if (!p)
8627 return nullptr;
8628 if (p->ctrltype != ct_lfotype)
8629 {
8630 // FIXME - warning?
8631 }
8632 CRect rect(0, 0, skinCtrl->w, skinCtrl->h);
8633 rect.offset(skinCtrl->x, skinCtrl->y);
8634 int lfo_id = p->ctrlgroup_entry - ms_lfo1;
8635 if ((lfo_id >= 0) && (lfo_id < n_lfos))
8636 {
8637 CLFOGui *slfo = new CLFOGui(
8638 rect, lfo_id >= 0 && lfo_id <= (ms_lfo6 - ms_lfo1), this, p->id + start_paramtags,
8639 &synth->storage.getPatch().scene[current_scene].lfo[lfo_id], &synth->storage,
8640 &synth->storage.getPatch().stepsequences[current_scene][lfo_id],
8641 &synth->storage.getPatch().msegs[current_scene][lfo_id],
8642 &synth->storage.getPatch().formulamods[current_scene][lfo_id], bitmapStore);
8643 slfo->setSkin(currentSkin, bitmapStore, skinCtrl);
8644 lfodisplay = slfo;
8645 frame->addView(slfo);
8646 nonmod_param[paramIndex] = slfo;
8647 return slfo;
8648 }
8649 }
8650 if (skinCtrl->ultimateparentclassname == "COSCMenu")
8651 {
8652 CRect rect(0, 0, skinCtrl->w, skinCtrl->h);
8653 rect.offset(skinCtrl->x, skinCtrl->y);
8654 auto hsw = new COscMenu(
8655 rect, this, tag_osc_menu, &synth->storage,
8656 &synth->storage.getPatch().scene[current_scene].osc[current_osc[current_scene]],
8657 bitmapStore);
8658
8659 hsw->setSkin(currentSkin, bitmapStore);
8660 hsw->setMouseableArea(rect);
8661
8662 hsw->text_allcaps = Surge::UI::Skin::setAllCapsProperty(
8663 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_ALL_CAPS, "false"));
8664 hsw->font_style = Surge::UI::Skin::setFontStyleProperty(
8665 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::FONT_STYLE, "normal"));
8666 hsw->text_align = Surge::UI::Skin::setTextAlignProperty(
8667 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_ALIGN, "center"));
8668 hsw->font_size = std::atoi(
8669 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::FONT_SIZE, "8").c_str());
8670 hsw->text_hoffset = std::atoi(
8671 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_HOFFSET, "0")
8672 .c_str());
8673 hsw->text_voffset = std::atoi(
8674 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::TEXT_VOFFSET, "0")
8675 .c_str());
8676
8677 if (p)
8678 hsw->setValue(p->get_value_f01());
8679 // TODO: This was not on before skinnification. Why?
8680 // if( paramIndex >= 0 ) nonmod_param[paramIndex] = hsw;
8681
8682 frame->addView(hsw);
8683 return hsw;
8684 }
8685 if (skinCtrl->ultimateparentclassname == "CFXMenu")
8686 {
8687 CRect rect(0, 0, skinCtrl->w, skinCtrl->h);
8688 rect.offset(skinCtrl->x, skinCtrl->y);
8689 // CControl *m = new
8690 // CFxMenu(rect,this,tag_fx_menu,&synth->storage,&synth->storage.getPatch().fx[current_fx],current_fx);
8691 CControl *m = new CFxMenu(rect, this, tag_fx_menu, &synth->storage,
8692 &synth->storage.getPatch().fx[current_fx],
8693 &synth->fxsync[current_fx], current_fx);
8694 m->setMouseableArea(rect);
8695 ((CFxMenu *)m)->setSkin(currentSkin, bitmapStore);
8696 ((CFxMenu *)m)->selectedIdx = this->selectedFX[current_fx];
8697 fxPresetLabel->setText(this->fxPresetName[current_fx].c_str());
8698 m->setValue(p->get_value_f01());
8699 frame->addView(m);
8700 fxmenu = m;
8701 return m;
8702 }
8703 if (skinCtrl->ultimateparentclassname == "CNumberField")
8704 {
8705 CRect rect(0, 0, skinCtrl->w, skinCtrl->h);
8706 rect.offset(skinCtrl->x, skinCtrl->y);
8707 CNumberField *pbd = new CNumberField(rect, this, tag, nullptr, &(synth->storage));
8708 pbd->setSkin(currentSkin, bitmapStore, skinCtrl);
8709 pbd->setMouseableArea(rect);
8710
8711 // TODO extra from properties
8712 auto nfcm = currentSkin->propertyValue(
8713 skinCtrl, Surge::Skin::Component::NUMBERFIELD_CONTROLMODE, std::to_string(cm_none));
8714 pbd->setControlMode(std::atoi(nfcm.c_str()));
8715
8716 pbd->setValue(p->get_value_f01());
8717 frame->addView(pbd);
8718 nonmod_param[paramIndex] = pbd;
8719
8720 if (p && p->ctrltype == ct_midikey_or_channel)
8721 {
8722 auto sm = this->synth->storage.getPatch().scenemode.val.i;
8723
8724 switch (sm)
8725 {
8726 case sm_single:
8727 case sm_dual:
8728 pbd->setControlMode(cm_none);
8729 break;
8730 case sm_split:
8731 pbd->setControlMode(cm_notename);
8732 break;
8733 case sm_chsplit:
8734 pbd->setControlMode(cm_midichannel_from_127);
8735 break;
8736 }
8737 }
8738
8739 // Save some of these for later reference
8740 switch (p->ctrltype)
8741 {
8742 case ct_polylimit:
8743 polydisp = pbd;
8744 break;
8745 case ct_midikey_or_channel:
8746 splitpointControl = pbd;
8747 break;
8748 default:
8749 break;
8750 }
8751 return pbd;
8752 }
8753 if (skinCtrl->ultimateparentclassname == "FilterSelector")
8754 {
8755 // Obviously exposing this widget as a controllable widget would be better
8756 if (!p)
8757 return nullptr;
8758
8759 auto rect = skinCtrl->getRect();
8760 auto hsw = new CMenuAsSlider(rect.getTopLeft(), rect.getSize(), this,
8761 p->id + start_paramtags, bitmapStore, &(synth->storage));
8762
8763 auto *parm = dynamic_cast<ParameterDiscreteIndexRemapper *>(p->user_data);
8764 if (parm && parm->supportsTotalIndexOrdering())
8765 hsw->intOrdering = parm->totalIndexOrdering();
8766
8767 hsw->setMinMax(0, n_fu_types - 1);
8768 hsw->setMouseableArea(rect);
8769 hsw->setLabel(p->get_name());
8770 hsw->setDeactivated(false);
8771 hsw->setBackgroundID(IDB_FILTER_MENU);
8772
8773 bool activeGlyph = true;
8774 if (currentSkin->getVersion() >= 2)
8775 {
8776 auto pv =
8777 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::GLPYH_ACTIVE, "true");
8778 if (pv == "false")
8779 activeGlyph = false;
8780 }
8781
8782 hsw->setFilterMode(true);
8783
8784 if (activeGlyph)
8785 {
8786 for (int i = 0; i < n_fu_types; i++)
8787 hsw->glyphIndexMap.push_back(
8788 std::make_pair(fut_glyph_index[i][0], fut_glyph_index[i][1]));
8789 if (currentSkin->getVersion() == 1)
8790 {
8791 auto drr = rect;
8792 drr.right = drr.left + 18;
8793 hsw->setDragRegion(drr);
8794 hsw->setDragGlyph(IDB_FILTER_ICONS, 18);
8795 }
8796 }
8797
8798 p->ctrlstyle = p->ctrlstyle | kNoPopup;
8799
8800 hsw->setValue(p->get_value_f01());
8801 hsw->setSkin(currentSkin, bitmapStore, skinCtrl);
8802
8803 if (currentSkin->getVersion() > 1)
8804 {
8805 if (activeGlyph)
8806 {
8807 auto glpc = currentSkin->propertyValue(
8808 skinCtrl, Surge::Skin::Component::GLYPH_PLACEMENT, "left");
8809 auto glw = std::atoi(
8810 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::GLYPH_W, "18")
8811 .c_str());
8812 auto glh = std::atoi(
8813 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::GLYPH_H, "18")
8814 .c_str());
8815 auto gli =
8816 currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::GLYPH_IMAGE, "");
8817 auto glih = currentSkin->propertyValue(
8818 skinCtrl, Surge::Skin::Component::GLYPH_HOVER_IMAGE, "");
8819
8820 // These are the V1 hardcoded defaults
8821 if (glw == 18 && glh == 18 && glpc == "left" && gli == "")
8822 {
8823 auto drr = rect;
8824 drr.right = drr.left + 18;
8825 hsw->setDragRegion(drr);
8826 hsw->setDragGlyph(IDB_FILTER_ICONS, 18);
8827 }
8828 else
8829 {
8830 hsw->setGlyphSettings(gli, glih, glpc, glw, glh);
8831 }
8832 }
8833
8834 auto pv = currentSkin->propertyValue(skinCtrl, Surge::Skin::Component::BACKGROUND);
8835 if (pv.isJust())
8836 {
8837 hsw->setBackgroundBitmapResource(pv.fromJust());
8838 }
8839 }
8840
8841 frame->addView(hsw);
8842 nonmod_param[paramIndex] = hsw;
8843 return hsw;
8844 }
8845 if (skinCtrl->ultimateparentclassname != Surge::UI::NoneClassName)
8846 std::cout << "Unable to make control with upc " << skinCtrl->ultimateparentclassname
8847 << std::endl;
8848 return nullptr;
8849 }
8850
canDropTarget(const std::string & fname)8851 bool SurgeGUIEditor::canDropTarget(const std::string &fname)
8852 {
8853 static std::unordered_set<std::string> extensions;
8854 if (extensions.empty())
8855 {
8856 extensions.insert(".scl");
8857 extensions.insert(".kbm");
8858 extensions.insert(".wav");
8859 extensions.insert(".wt");
8860 extensions.insert(".fxp");
8861 }
8862
8863 fs::path fPath(fname);
8864 std::string fExt(path_to_string(fPath.extension()));
8865 std::transform(fExt.begin(), fExt.end(), fExt.begin(),
8866 [](unsigned char c) { return std::tolower(c); });
8867 if (extensions.find(fExt) != extensions.end())
8868 return true;
8869 return false;
8870 }
onDrop(const std::string & fname)8871 bool SurgeGUIEditor::onDrop(const std::string &fname)
8872 {
8873 fs::path fPath(fname);
8874 std::string fExt(path_to_string(fPath.extension()));
8875 std::transform(fExt.begin(), fExt.end(), fExt.begin(),
8876 [](unsigned char c) { return std::tolower(c); });
8877 if (fExt == ".wav" || fExt == ".wt")
8878 {
8879 strxcpy(synth->storage.getPatch()
8880 .scene[current_scene]
8881 .osc[current_osc[current_scene]]
8882 .wt.queue_filename,
8883 fname.c_str(), 255);
8884 }
8885 else if (fExt == ".scl")
8886 {
8887 scaleFileDropped(fname);
8888 }
8889 else if (fExt == ".kbm")
8890 {
8891 mappingFileDropped(fname);
8892 }
8893 else if (fExt == ".fxp")
8894 {
8895 queuePatchFileLoad(fname);
8896 }
8897
8898 return true;
8899 }
8900
swapFX(int source,int target,SurgeSynthesizer::FXReorderMode m)8901 void SurgeGUIEditor::swapFX(int source, int target, SurgeSynthesizer::FXReorderMode m)
8902 {
8903 if (source < 0 || source >= n_fx_slots || target < 0 || target >= n_fx_slots)
8904 return;
8905
8906 auto t = fxPresetName[target];
8907 fxPresetName[target] = fxPresetName[source];
8908 if (m == SurgeSynthesizer::SWAP)
8909 fxPresetName[source] = t;
8910 if (m == SurgeSynthesizer::MOVE)
8911 fxPresetName[source] = "";
8912
8913 synth->reorderFx(source, target, m);
8914 }
8915
lfoShapeChanged(int prior,int curr)8916 void SurgeGUIEditor::lfoShapeChanged(int prior, int curr)
8917 {
8918 if (prior != curr || prior == lt_mseg || curr == lt_mseg)
8919 {
8920 if (msegEditSwitch)
8921 {
8922 msegEditSwitch->setVisible(curr == lt_mseg);
8923 }
8924 }
8925
8926 if (curr == lt_mseg && isAnyOverlayPresent(MSEG_EDITOR))
8927 {
8928 // We have the MSEGEditor open and have swapped to the MSEG here
8929 showMSEGEditor();
8930 }
8931 else if (prior == lt_mseg && curr != lt_mseg && isAnyOverlayPresent(MSEG_EDITOR))
8932 {
8933 // We can choose to not do this too; if we do we are editing an MSEG which isn't used though
8934 closeMSEGEditor();
8935 }
8936
8937 // update the LFO title label
8938 std::string modname = modulatorName(modsource_editor[current_scene], true);
8939 lfoNameLabel->setText(modname.c_str());
8940 lfoNameLabel->invalid();
8941
8942 // And now we have dynamic labels really anything
8943 frame->invalid();
8944 }
8945
closeStorePatchDialog()8946 void SurgeGUIEditor::closeStorePatchDialog()
8947 {
8948 dismissEditorOfType(STORE_PATCH);
8949
8950 // Have to update all that state too for the newly orphaned items
8951 patchName = nullptr;
8952 patchCategory = nullptr;
8953 patchCreator = nullptr;
8954 patchComment = nullptr;
8955 }
8956
showStorePatchDialog()8957 void SurgeGUIEditor::showStorePatchDialog() { makeStorePatchDialog(); }
8958
closeMSEGEditor()8959 void SurgeGUIEditor::closeMSEGEditor()
8960 {
8961 if (isAnyOverlayPresent(MSEG_EDITOR))
8962 {
8963 broadcastMSEGState();
8964 dismissEditorOfType(MSEG_EDITOR);
8965 }
8966 }
toggleMSEGEditor()8967 void SurgeGUIEditor::toggleMSEGEditor()
8968 {
8969 if (isAnyOverlayPresent(MSEG_EDITOR))
8970 {
8971 closeMSEGEditor();
8972 }
8973 else
8974 {
8975 showMSEGEditor();
8976 }
8977 }
8978
8979 /*
8980 * The edit state is independent per LFO. We want to sync some of ti as if it is not
8981 * so this is called at the appropriate time.
8982 */
broadcastMSEGState()8983 void SurgeGUIEditor::broadcastMSEGState()
8984 {
8985 if (msegIsOpenFor >= 0 && msegIsOpenInScene >= 0)
8986 {
8987 for (int s = 0; s < n_scenes; ++s)
8988 {
8989 for (int lf = 0; lf < n_lfos; ++lf)
8990 {
8991 msegEditState[s][lf].timeEditMode =
8992 msegEditState[msegIsOpenInScene][msegIsOpenFor].timeEditMode;
8993 }
8994 }
8995 }
8996 msegIsOpenFor = -1;
8997 msegIsOpenInScene = -1;
8998 }
showMSEGEditor()8999 void SurgeGUIEditor::showMSEGEditor()
9000 {
9001 broadcastMSEGState();
9002
9003 auto lfo_id = modsource_editor[current_scene] - ms_lfo1;
9004 msegIsOpenFor = lfo_id;
9005 msegIsOpenInScene = current_scene;
9006
9007 auto lfodata = &synth->storage.getPatch().scene[current_scene].lfo[lfo_id];
9008 auto ms = &synth->storage.getPatch().msegs[current_scene][lfo_id];
9009 auto mse = new MSEGEditor(&(synth->storage), lfodata, ms, &msegEditState[current_scene][lfo_id],
9010 currentSkin, bitmapStore);
9011 auto vs = mse->getViewSize().getWidth();
9012 float xp = (currentSkin->getWindowSizeX() - (vs + 8)) * 0.5;
9013
9014 std::string title = modsource_names[modsource_editor[current_scene]];
9015 title += " Editor";
9016 Surge::Storage::findReplaceSubstring(title, std::string("LFO"), std::string("MSEG"));
9017
9018 auto npc = Surge::Skin::Connector::NonParameterConnection::MSEG_EDITOR_WINDOW;
9019 auto conn = Surge::Skin::Connector::connectorByNonParameterConnection(npc);
9020 auto skinCtrl = currentSkin->getOrCreateControlForConnector(conn);
9021
9022 addEditorOverlay(mse, title, MSEG_EDITOR, CPoint(skinCtrl->x, skinCtrl->y), false, true,
9023 [this]() {
9024 if (msegEditSwitch)
9025 {
9026 msegEditSwitch->setValue(0.0);
9027 msegEditSwitch->invalid();
9028 }
9029 });
9030
9031 if (msegEditSwitch)
9032 {
9033 msegEditSwitch->setValue(1.0);
9034 msegEditSwitch->invalid();
9035 }
9036 }
9037
repushAutomationFor(Parameter * p)9038 void SurgeGUIEditor::repushAutomationFor(Parameter *p)
9039 {
9040 auto id = synth->idForParameter(p);
9041 synth->sendParameterAutomation(id, synth->getParameter01(id));
9042
9043 #if TARGET_AUDIOUNIT
9044 synth->getParent()->ParameterBeginEdit(id.getDawSideIndex());
9045 synth->getParent()->ParameterUpdate(id.getDawSideIndex());
9046 synth->getParent()->ParameterEndEdit(id.getDawSideIndex());
9047 #endif
9048 }
9049
showAboutBox(int devModeGrid)9050 void SurgeGUIEditor::showAboutBox(int devModeGrid)
9051 {
9052 CRect wsize(0, 0, getWindowSizeX(), getWindowSizeY());
9053 aboutbox = new CAboutBox(wsize, this, &(synth->storage), synth->hostProgram, currentSkin,
9054 bitmapStore, devModeGrid);
9055 aboutbox->setVisible(true);
9056 getFrame()->addView(aboutbox);
9057 }
9058
hideAboutBox()9059 void SurgeGUIEditor::hideAboutBox()
9060 {
9061 if (aboutbox)
9062 {
9063 aboutbox->setVisible(false);
9064 removeFromFrame.push_back(aboutbox);
9065 aboutbox = nullptr;
9066 }
9067 }
9068
showMidiLearnOverlay(const VSTGUI::CRect & r)9069 void SurgeGUIEditor::showMidiLearnOverlay(const VSTGUI::CRect &r)
9070 {
9071 if (midiLearnOverlay)
9072 hideMidiLearnOverlay();
9073
9074 midiLearnOverlay = new CMidiLearnOverlay(r, bitmapStore);
9075 midiLearnOverlay->setVisible(true);
9076 getFrame()->addView(midiLearnOverlay);
9077 }
9078
hideMidiLearnOverlay()9079 void SurgeGUIEditor::hideMidiLearnOverlay()
9080 {
9081 if (midiLearnOverlay)
9082 {
9083 midiLearnOverlay->setVisible(false);
9084 removeFromFrame.push_back(midiLearnOverlay);
9085 midiLearnOverlay = nullptr;
9086 }
9087 }
9088
toggleAlternateFor(VSTGUI::CControl * c)9089 void SurgeGUIEditor::toggleAlternateFor(VSTGUI::CControl *c)
9090 {
9091 auto cms = dynamic_cast<CModulationSourceButton *>(c);
9092 if (cms)
9093 {
9094 modsource = (modsources)(cms->getTag() - tag_mod_source0);
9095 cms->setUseAlternate(!cms->useAlternate);
9096 modsource_is_alternate[modsource] = cms->useAlternate;
9097 this->refresh_mod();
9098 }
9099 }
9100