1 #include "globals.h"
2 #include "SurgeGUIEditor.h"
3 #include "DspUtilities.h"
4 #include "CSnapshotMenu.h"
5 #include "effect/Effect.h"
6 #include "CScalableBitmap.h"
7 #include "SurgeBitmaps.h"
8 #include "SurgeStorage.h"
9 #include "filesystem/import.h"
10 #include "SkinColors.h"
11 #include "RuntimeFont.h"
12 #include "guihelpers.h"
13 #include "StringOps.h"
14
15 #include <iostream>
16 #include <iomanip>
17 #include <queue>
18
19 using namespace VSTGUI;
20
21 extern CFontRef displayFont;
22
23 // CSnapshotMenu
24
CSnapshotMenu(const CRect & size,IControlListener * listener,long tag,SurgeStorage * storage)25 CSnapshotMenu::CSnapshotMenu(const CRect &size, IControlListener *listener, long tag,
26 SurgeStorage *storage)
27 : COptionMenu(size, nullptr, tag, 0)
28 {
29 this->storage = storage;
30 this->listenerNotForParent = listener;
31 }
~CSnapshotMenu()32 CSnapshotMenu::~CSnapshotMenu() {}
33
onMouseDown(CPoint & where,const CButtonState & button)34 CMouseEventResult CSnapshotMenu::onMouseDown(CPoint &where, const CButtonState &button)
35 {
36 if (listenerNotForParent && (button & (kMButton | kButton4 | kButton5)))
37 {
38 listenerNotForParent->controlModifierClicked(this, button);
39 return kMouseDownEventHandledButDontNeedMovedOrUpEvents;
40 }
41 return COptionMenu::onMouseDown(where, button);
42 }
43
draw(CDrawContext * dc)44 void CSnapshotMenu::draw(CDrawContext *dc) { setDirty(false); }
45
canSave()46 bool CSnapshotMenu::canSave() { return false; }
47
populate()48 void CSnapshotMenu::populate()
49 {
50 int main = 0, sub = 0;
51 const long max_main = 128, max_sub = 256;
52
53 int idx = 0;
54 TiXmlElement *sect = storage->getSnapshotSection(mtype);
55 if (sect)
56 {
57 TiXmlElement *type = sect->FirstChildElement();
58
59 while (type)
60 {
61 if (type->Value() && strcmp(type->Value(), "type") == 0)
62 {
63 auto sm = populateSubmenuFromTypeElement(type, this, main, sub, max_sub, idx);
64
65 if (sm)
66 {
67 addToTopLevelTypeMenu(type, sm, idx);
68 }
69 }
70 else if (type->Value() && strcmp(type->Value(), "separator") == 0)
71 {
72 addSeparator();
73 }
74
75 type = type->NextSiblingElement();
76 main++;
77
78 if (main >= max_main)
79 {
80 break;
81 }
82 }
83 }
84 maxIdx = idx;
85 }
86
loadSnapshotByIndex(int idx)87 bool CSnapshotMenu::loadSnapshotByIndex(int idx)
88 {
89 int cidx = 0;
90 // This isn't that efficient but you know
91 TiXmlElement *sect = storage->getSnapshotSection(mtype);
92 if (sect)
93 {
94 std::queue<TiXmlElement *> typeD;
95 typeD.push(TINYXML_SAFE_TO_ELEMENT(sect->FirstChild("type")));
96 while (!typeD.empty())
97 {
98 auto type = typeD.front();
99 typeD.pop();
100 int type_id = 0;
101 type->Attribute("i", &type_id);
102 TiXmlElement *snapshot = TINYXML_SAFE_TO_ELEMENT(type->FirstChild("snapshot"));
103 while (snapshot)
104 {
105 int snapshotTypeID = type_id, tmpI = 0;
106 if (snapshot->Attribute("i", &tmpI) != nullptr)
107 {
108 snapshotTypeID = tmpI;
109 }
110
111 if (cidx == idx)
112 {
113 selectedIdx = cidx;
114 loadSnapshot(snapshotTypeID, snapshot, cidx);
115 if (listenerNotForParent)
116 listenerNotForParent->valueChanged(this);
117 return true;
118 }
119 snapshot = TINYXML_SAFE_TO_ELEMENT(snapshot->NextSibling("snapshot"));
120 cidx++;
121 }
122
123 auto subType = TINYXML_SAFE_TO_ELEMENT(type->FirstChild("type"));
124 while (subType)
125 {
126 typeD.push(subType);
127 subType = TINYXML_SAFE_TO_ELEMENT(subType->NextSibling("type"));
128 }
129
130 auto next = TINYXML_SAFE_TO_ELEMENT(type->NextSibling("type"));
131 if (next)
132 typeD.push(next);
133 }
134 }
135 if (idx < 0 && cidx + idx > 0)
136 {
137 // I've gone off the end
138 return (loadSnapshotByIndex(cidx + idx));
139 }
140 return false;
141 }
142
populateSubmenuFromTypeElement(TiXmlElement * type,VSTGUI::COptionMenu * parent,int & main,int & sub,const long & max_sub,int & idx)143 VSTGUI::COptionMenu *CSnapshotMenu::populateSubmenuFromTypeElement(TiXmlElement *type,
144 VSTGUI::COptionMenu *parent,
145 int &main, int &sub,
146 const long &max_sub, int &idx)
147 {
148 /*
149 ** Begin by grabbing all the snapshot elements
150 */
151 std::string txt;
152 int type_id = 0;
153 type->Attribute("i", &type_id);
154 sub = 0;
155 COptionMenu *subMenu = new COptionMenu(getViewSize(), 0, main, 0, 0, kNoDrawStyle);
156 subMenu->setNbItemsPerColumn(20);
157 TiXmlElement *snapshot = TINYXML_SAFE_TO_ELEMENT(type->FirstChild("snapshot"));
158 while (snapshot)
159 {
160 txt = snapshot->Attribute("name");
161
162 #if WINDOWS
163 Surge::Storage::findReplaceSubstring(txt, std::string("&"), std::string("&&"));
164 #endif
165
166 int snapshotTypeID = type_id;
167 int tmpI;
168 if (snapshot->Attribute("i", &tmpI) != nullptr)
169 {
170 snapshotTypeID = tmpI;
171 }
172
173 if (firstSnapshotByType.find(snapshotTypeID) == firstSnapshotByType.end())
174 firstSnapshotByType[snapshotTypeID] = idx;
175 auto actionItem = new CCommandMenuItem(CCommandMenuItem::Desc(txt.c_str()));
176 auto action = [this, snapshot, snapshotTypeID, idx](CCommandMenuItem *item) {
177 this->selectedIdx = idx;
178 this->loadSnapshot(snapshotTypeID, snapshot, idx);
179 if (this->listenerNotForParent)
180 this->listenerNotForParent->valueChanged(this);
181 };
182 loadArgsByIndex.push_back(std::make_pair(snapshotTypeID, snapshot));
183 idx++;
184
185 actionItem->setActions(action, nullptr);
186 subMenu->addEntry(actionItem);
187
188 snapshot = TINYXML_SAFE_TO_ELEMENT(snapshot->NextSibling("snapshot"));
189 sub++;
190 if (sub >= max_sub)
191 break;
192 }
193
194 /*
195 ** Next see if we have any subordinate types
196 */
197 TiXmlElement *subType = TINYXML_SAFE_TO_ELEMENT(type->FirstChild("type"));
198 while (subType)
199 {
200 populateSubmenuFromTypeElement(subType, subMenu, main, sub, max_sub, idx);
201 subType = TINYXML_SAFE_TO_ELEMENT(subType->NextSibling("type"));
202 }
203
204 /*
205 ** Then add myself to parent
206 */
207 txt = type->Attribute("name");
208
209 #if WINDOWS
210 Surge::Storage::findReplaceSubstring(txt, std::string("&"), std::string("&&"));
211 #endif
212
213 if (sub)
214 {
215 parent->addEntry(subMenu, txt.c_str());
216 }
217 else
218 {
219 auto actionItem = new CCommandMenuItem(CCommandMenuItem::Desc(txt.c_str()));
220
221 if (firstSnapshotByType.find(type_id) == firstSnapshotByType.end())
222 firstSnapshotByType[type_id] = idx;
223
224 auto action = [this, type_id, type, idx](CCommandMenuItem *item) {
225 this->selectedIdx = 0;
226 this->loadSnapshot(type_id, type, idx);
227 if (this->listenerNotForParent)
228 this->listenerNotForParent->valueChanged(this);
229 };
230
231 loadArgsByIndex.push_back(std::make_pair(type_id, type));
232 idx++;
233 actionItem->setActions(action, nullptr);
234 parent->addEntry(actionItem);
235 }
236
237 subMenu->forget();
238 if (sub)
239 return subMenu; // OK to return forgoten since it has lifetime of parent
240 else
241 return nullptr;
242 }
243
244 // COscMenu
245
COscMenu(const CRect & size,IControlListener * listener,long tag,SurgeStorage * storage,OscillatorStorage * osc,std::shared_ptr<SurgeBitmaps> bitmapStore)246 COscMenu::COscMenu(const CRect &size, IControlListener *listener, long tag, SurgeStorage *storage,
247 OscillatorStorage *osc, std::shared_ptr<SurgeBitmaps> bitmapStore)
248 : CSnapshotMenu(size, listener, tag, storage)
249 {
250 strcpy(mtype, "osc");
251 this->osc = osc;
252 this->storage = storage;
253 auto tb = bitmapStore->getBitmap(IDB_OSC_MENU);
254 bmp = tb;
255 populate();
256 }
257
draw(CDrawContext * dc)258 void COscMenu::draw(CDrawContext *dc)
259 {
260 if (!attemptedHoverLoad)
261 {
262 hoverBmp = skin->hoverBitmapOverlayForBackgroundBitmap(
263 skinControl, dynamic_cast<CScalableBitmap *>(bmp), associatedBitmapStore,
264 Surge::UI::Skin::HOVER);
265 attemptedHoverLoad = true;
266 }
267 CRect size = getViewSize();
268 int i = osc->type.val.i;
269 int y = i * size.getHeight();
270
271 // we're using code generated text for this menu from skin version 2 onwards
272 // so there's no need to slice the graphics asset
273 if (skin->getVersion() >= 2)
274 {
275 y = 0;
276 }
277
278 if (bmp)
279 bmp->draw(dc, size, CPoint(0, y), 0xff);
280
281 if (isHovered && hoverBmp)
282 hoverBmp->draw(dc, size, CPoint(0, y), 0xff);
283
284 if (skin->getVersion() >= 2)
285 {
286 dc->saveGlobalState();
287
288 dc->setDrawMode(VSTGUI::kAntiAliasing | VSTGUI::kNonIntegralMode);
289 dc->setFont(Surge::GUI::getLatoAtSize(font_size, font_style));
290
291 if (isHovered)
292 {
293 dc->setFontColor(skin->getColor(Colors::Osc::Type::TextHover));
294 }
295 else
296 {
297 dc->setFontColor(skin->getColor(Colors::Osc::Type::Text));
298 }
299
300 std::string txt = osc_type_shortnames[i];
301
302 if (text_allcaps)
303 {
304 std::transform(txt.begin(), txt.end(), txt.begin(), ::toupper);
305 }
306
307 dc->drawString(txt.c_str(), size.offset(text_hoffset, text_voffset), text_align, true);
308
309 dc->restoreGlobalState();
310 }
311
312 setDirty(false);
313 }
314
loadSnapshot(int type,TiXmlElement * e,int idx)315 void COscMenu::loadSnapshot(int type, TiXmlElement *e, int idx)
316 {
317 assert(within_range(0, type, n_osc_types));
318 auto sge = dynamic_cast<SurgeGUIEditor *>(listenerNotForParent);
319 if (sge)
320 {
321 auto sc = sge->current_scene;
322 sge->oscilatorMenuIndex[sc][sge->current_osc[sc]] = idx;
323 }
324 osc->queue_type = type;
325 osc->queue_xmldata = e;
326 }
327
onWheel(const VSTGUI::CPoint & where,const float & distance,const VSTGUI::CButtonState & buttons)328 bool COscMenu::onWheel(const VSTGUI::CPoint &where, const float &distance,
329 const VSTGUI::CButtonState &buttons)
330 {
331 accumWheel += distance;
332 #if WINDOWS // rough hack but it still takes too many mousewheel clicks to get outside of Classic
333 // osc subfolder onto other osc types!
334 accumWheel += distance;
335 #endif
336
337 auto *sge = dynamic_cast<SurgeGUIEditor *>(listenerNotForParent);
338 if (sge)
339 {
340 auto sc = sge->current_scene;
341 int currentIdx = sge->oscilatorMenuIndex[sc][sge->current_osc[sc]];
342 if (accumWheel < -1)
343 {
344 currentIdx = currentIdx + 1;
345 if (currentIdx >= maxIdx)
346 currentIdx = 0;
347 accumWheel = 0;
348 auto args = loadArgsByIndex[currentIdx];
349 loadSnapshot(args.first, args.second, currentIdx);
350 }
351 else if (accumWheel > 1)
352 {
353 currentIdx = currentIdx - 1;
354 if (currentIdx < 0)
355 currentIdx = maxIdx - 1;
356 accumWheel = 0;
357 auto args = loadArgsByIndex[currentIdx];
358 loadSnapshot(args.first, args.second, currentIdx);
359 }
360 }
361 return true;
362 }
363
364 /*void COscMenu::load_snapshot(int type, TiXmlElement *e)
365 {
366 assert(within_range(0,type,n_osc_types));
367 osc->type.val.i = type;
368 //osc->retrigger.val.i =
369 storage->patch.update_controls(false, osc);
370 if(e)
371 {
372 for(int i=0; i<n_osc_params; i++)
373 {
374 double d; int j;
375 char lbl[TXT_SIZE];
376 snprintf(lbl, TXT_SIZE, "p%i",i);
377 if (osc->p[i].valtype == vt_float)
378 {
379 if(e->QueryDoubleAttribute(lbl,&d) == TIXML_SUCCESS) osc->p[i].val.f
380 = (float)d;
381 }
382 else
383 {
384 if(e->QueryIntAttribute(lbl,&j) == TIXML_SUCCESS) osc->p[i].val.i =
385 j;
386 }
387 }
388 }
389 }*/
390
391 // CFxMenu
392
393 std::vector<float> CFxMenu::fxCopyPaste;
394
CFxMenu(const CRect & size,IControlListener * listener,long tag,SurgeStorage * storage,FxStorage * fx,FxStorage * fxbuffer,int slot)395 CFxMenu::CFxMenu(const CRect &size, IControlListener *listener, long tag, SurgeStorage *storage,
396 FxStorage *fx, FxStorage *fxbuffer, int slot)
397 : CSnapshotMenu(size, listener, tag, storage)
398 {
399 strcpy(mtype, "fx");
400 this->fx = fx;
401 this->fxbuffer = fxbuffer;
402 this->slot = slot;
403 selectedName = "";
404 populate();
405 }
406
draw(CDrawContext * dc)407 void CFxMenu::draw(CDrawContext *dc)
408 {
409 CRect lbox = getViewSize();
410 lbox.right--;
411 lbox.bottom--;
412
413 if (!triedToLoadBmp)
414 {
415 triedToLoadBmp = true;
416 pBackground = associatedBitmapStore->getBitmap(IDB_MENU_AS_SLIDER);
417 pBackgroundHover = skin->hoverBitmapOverlayForBackgroundBitmap(
418 skinControl, dynamic_cast<CScalableBitmap *>(pBackground), associatedBitmapStore,
419 Surge::UI::Skin::HoverType::HOVER);
420 }
421
422 if (pBackground)
423 {
424 pBackground->draw(dc, getViewSize(), CPoint(0, 0), 0xff);
425 }
426
427 if (isHovered && pBackgroundHover)
428 {
429 pBackgroundHover->draw(dc, getViewSize(), CPoint(0, 0), 0xff);
430 }
431
432 // hover color and position
433 auto fgc = skin->getColor(Colors::Effect::Menu::Text);
434
435 if (isHovered)
436 fgc = skin->getColor(Colors::Effect::Menu::TextHover);
437
438 dc->setFontColor(fgc);
439 dc->setFont(displayFont);
440
441 CRect txtbox(getViewSize());
442 txtbox.inset(2, 2);
443 txtbox.left += 4;
444 txtbox.right -= 12;
445 dc->drawString(fxslot_names[slot], txtbox, kLeftText, true);
446
447 char fxname[NAMECHARS];
448 snprintf(fxname, NAMECHARS, "%s", fx_type_names[fx->type.val.i]);
449 dc->drawString(fxname, txtbox, kRightText, true);
450
451 setDirty(false);
452 }
453
loadSnapshot(int type,TiXmlElement * e,int idx)454 void CFxMenu::loadSnapshot(int type, TiXmlElement *e, int idx)
455 {
456 if (!type)
457 fxbuffer->type.val.i = type;
458
459 if (e)
460 {
461 fxbuffer->type.val.i = type;
462 // storage->patch.update_controls();
463 selectedName = e->Attribute("name");
464
465 Effect *t_fx = spawn_effect(type, storage, fxbuffer, 0);
466 if (t_fx)
467 {
468 t_fx->init_ctrltypes();
469 t_fx->init_default_values();
470 delete t_fx;
471 }
472
473 for (int i = 0; i < n_fx_params; i++)
474 {
475 double d;
476 int j;
477 char lbl[TXT_SIZE], sublbl[TXT_SIZE];
478 snprintf(lbl, TXT_SIZE, "p%i", i);
479 if (fxbuffer->p[i].valtype == vt_float)
480 {
481 if (e->QueryDoubleAttribute(lbl, &d) == TIXML_SUCCESS)
482 fxbuffer->p[i].set_storage_value((float)d);
483 }
484 else
485 {
486 if (e->QueryIntAttribute(lbl, &j) == TIXML_SUCCESS)
487 fxbuffer->p[i].set_storage_value(j);
488 }
489
490 snprintf(sublbl, TXT_SIZE, "p%i_temposync", i);
491 fxbuffer->p[i].temposync =
492 ((e->QueryIntAttribute(sublbl, &j) == TIXML_SUCCESS) && (j == 1));
493 snprintf(sublbl, TXT_SIZE, "p%i_extend_range", i);
494 fxbuffer->p[i].extend_range =
495 ((e->QueryIntAttribute(sublbl, &j) == TIXML_SUCCESS) && (j == 1));
496 snprintf(sublbl, TXT_SIZE, "p%i_deactivated", i);
497 fxbuffer->p[i].deactivated =
498 ((e->QueryIntAttribute(sublbl, &j) == TIXML_SUCCESS) && (j == 1));
499 }
500 }
501 #if TARGET_LV2
502 auto *sge = dynamic_cast<SurgeGUIEditor *>(listenerNotForParent);
503 if (sge)
504 {
505 sge->forceautomationchangefor(&(fxbuffer->type));
506 for (int i = 0; i < n_fx_params; ++i)
507 sge->forceautomationchangefor(&(fxbuffer->p[i]));
508 }
509 #endif
510 }
saveSnapshot(TiXmlElement * e,const char * name)511 void CFxMenu::saveSnapshot(TiXmlElement *e, const char *name)
512 {
513 if (fx->type.val.i == 0)
514 {
515 return;
516 }
517
518 TiXmlElement *t = TINYXML_SAFE_TO_ELEMENT(e->FirstChild("type"));
519
520 while (t)
521 {
522 int ii;
523
524 if ((t->QueryIntAttribute("i", &ii) == TIXML_SUCCESS) && (ii == fx->type.val.i))
525 {
526 // if name already exists, delete old entry
527 TiXmlElement *sn = TINYXML_SAFE_TO_ELEMENT(t->FirstChild("snapshot"));
528 while (sn)
529 {
530 if (sn->Attribute("name") && !strcmp(sn->Attribute("name"), name))
531 {
532 t->RemoveChild(sn);
533 break;
534 }
535 sn = TINYXML_SAFE_TO_ELEMENT(sn->NextSibling("snapshot"));
536 }
537
538 TiXmlElement neu("snapshot");
539
540 for (int p = 0; p < n_fx_params; p++)
541 {
542 char lbl[TXT_SIZE], txt[TXT_SIZE], sublbl[TXT_SIZE];
543 snprintf(lbl, TXT_SIZE, "p%i", p);
544
545 if (fx->p[p].ctrltype != ct_none)
546 {
547 neu.SetAttribute(lbl, fx->p[p].get_storage_value(txt));
548
549 if (fx->p[p].temposync)
550 {
551 snprintf(sublbl, TXT_SIZE, "p%i_temposync", p);
552 neu.SetAttribute(sublbl, "1");
553 }
554 if (fx->p[p].extend_range)
555 {
556 snprintf(sublbl, TXT_SIZE, "p%i_extend_range", p);
557 neu.SetAttribute(sublbl, "1");
558 }
559 if (fx->p[p].deactivated)
560 {
561 snprintf(sublbl, TXT_SIZE, "p%i_deactivated", p);
562 neu.SetAttribute(sublbl, "1");
563 }
564 }
565 }
566
567 neu.SetAttribute("name", name);
568 t->InsertEndChild(neu);
569
570 return;
571 }
572
573 t = TINYXML_SAFE_TO_ELEMENT(t->NextSibling("type"));
574 }
575 }
576
577 bool CFxMenu::scanForUserPresets = true;
578 std::unordered_map<int, std::vector<CFxMenu::UserPreset>> CFxMenu::userPresets;
579
rescanUserPresets()580 void CFxMenu::rescanUserPresets()
581 {
582 userPresets.clear();
583 scanForUserPresets = false;
584
585 auto ud = storage->userFXPath;
586
587 std::vector<fs::path> sfxfiles;
588
589 std::deque<fs::path> workStack;
590 workStack.push_back(fs::path(ud));
591
592 try
593 {
594 while (!workStack.empty())
595 {
596 auto top = workStack.front();
597 workStack.pop_front();
598 if (fs::is_directory(top))
599 {
600 for (auto &d : fs::directory_iterator(top))
601 {
602 if (fs::is_directory(d))
603 {
604 workStack.push_back(d);
605 }
606 else if (path_to_string(d.path().extension()) == ".srgfx")
607 {
608 sfxfiles.push_back(d.path());
609 }
610 }
611 }
612 }
613 }
614 catch (const fs::filesystem_error &e)
615 {
616 std::ostringstream oss;
617 oss << "Experienced file system error when scanning user FX. " << e.what();
618 Surge::UserInteractions::promptError(oss.str(), "FileSystem Error");
619 }
620
621 for (const auto &f : sfxfiles)
622 {
623 {
624 UserPreset preset;
625 preset.file = path_to_string(f);
626 TiXmlDocument d;
627 int t;
628
629 if (!d.LoadFile(f))
630 goto badPreset;
631
632 auto r = TINYXML_SAFE_TO_ELEMENT(d.FirstChild("single-fx"));
633
634 if (!r)
635 goto badPreset;
636
637 auto s = TINYXML_SAFE_TO_ELEMENT(r->FirstChild("snapshot"));
638
639 if (!s)
640 goto badPreset;
641
642 if (!s->Attribute("name"))
643 goto badPreset;
644
645 preset.name = s->Attribute("name");
646
647 if (s->QueryIntAttribute("type", &t) != TIXML_SUCCESS)
648 goto badPreset;
649
650 preset.type = t;
651
652 for (int i = 0; i < n_fx_params; ++i)
653 {
654 double fl;
655 std::string p = "p";
656
657 if (s->QueryDoubleAttribute((p + std::to_string(i)).c_str(), &fl) == TIXML_SUCCESS)
658 {
659 preset.p[i] = fl;
660 }
661
662 if (s->QueryDoubleAttribute((p + std::to_string(i) + "_temposync").c_str(), &fl) ==
663 TIXML_SUCCESS &&
664 fl != 0)
665 {
666 preset.ts[i] = true;
667 }
668
669 if (s->QueryDoubleAttribute((p + std::to_string(i) + "_extend_range").c_str(),
670 &fl) == TIXML_SUCCESS &&
671 fl != 0)
672 {
673 preset.er[i] = true;
674 }
675
676 if (s->QueryDoubleAttribute((p + std::to_string(i) + "_deactivated").c_str(),
677 &fl) == TIXML_SUCCESS &&
678 fl != 0)
679 {
680 preset.da[i] = true;
681 }
682 }
683
684 if (userPresets.find(preset.type) == userPresets.end())
685 {
686 userPresets[preset.type] = std::vector<UserPreset>();
687 }
688
689 userPresets[preset.type].push_back(preset);
690 }
691
692 badPreset:;
693 }
694
695 for (auto &a : userPresets)
696 {
697 std::sort(a.second.begin(), a.second.end(), [](UserPreset a, UserPreset b) {
698 if (a.type == b.type)
699 {
700 return _stricmp(a.name.c_str(), b.name.c_str()) < 0;
701 }
702 else
703 {
704 return a.type < b.type;
705 }
706 });
707 }
708 }
709
populate()710 void CFxMenu::populate()
711 {
712 /*
713 ** Are there user presets
714 */
715 if (scanForUserPresets || true) // for now
716 {
717 rescanUserPresets();
718 }
719
720 CSnapshotMenu::populate();
721
722 /*
723 ** Add copy/paste/save
724 */
725
726 this->addSeparator();
727
728 auto copyItem = new CCommandMenuItem(CCommandMenuItem::Desc("Copy"));
729 auto copy = [this](CCommandMenuItem *item) { this->copyFX(); };
730 copyItem->setActions(copy, nullptr);
731 this->addEntry(copyItem);
732
733 auto pasteItem = new CCommandMenuItem(CCommandMenuItem::Desc("Paste"));
734 auto paste = [this](CCommandMenuItem *item) { this->pasteFX(); };
735 pasteItem->setActions(paste, nullptr);
736 this->addEntry(pasteItem);
737
738 this->addSeparator();
739
740 if (fx->type.val.i != fxt_off)
741 {
742 auto saveItem = new CCommandMenuItem(
743 CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Save FX Preset")));
744 saveItem->setActions([this](CCommandMenuItem *item) { this->saveFX(); });
745 this->addEntry(saveItem);
746 }
747
748 auto rescanItem = new CCommandMenuItem(
749 CCommandMenuItem::Desc(Surge::UI::toOSCaseForMenu("Refresh FX Preset List")));
750 rescanItem->setActions([this](CCommandMenuItem *item) {
751 scanForUserPresets = true;
752 auto *sge = dynamic_cast<SurgeGUIEditor *>(listenerNotForParent);
753 if (sge)
754 sge->queueRebuildUI();
755 });
756 this->addEntry(rescanItem);
757 }
758
copyFX()759 void CFxMenu::copyFX()
760 {
761 /*
762 ** This is a junky implementation until I figure out save and load which will require me to
763 *stream this
764 */
765 if (fxCopyPaste.size() == 0)
766 {
767 fxCopyPaste.resize(n_fx_params * 4 + 1); // type then (val; ts; extend; deact)
768 }
769
770 fxCopyPaste[0] = fx->type.val.i;
771 for (int i = 0; i < n_fx_params; ++i)
772 {
773 int vp = i * 4 + 1;
774 int tp = i * 4 + 2;
775 int xp = i * 4 + 3;
776 int dp = i * 4 + 4;
777
778 switch (fx->p[i].valtype)
779 {
780 case vt_float:
781 fxCopyPaste[vp] = fx->p[i].val.f;
782 break;
783 case vt_int:
784 fxCopyPaste[vp] = fx->p[i].val.i;
785 break;
786 }
787
788 fxCopyPaste[tp] = fx->p[i].temposync;
789 fxCopyPaste[xp] = fx->p[i].extend_range;
790 fxCopyPaste[dp] = fx->p[i].deactivated;
791 }
792 memcpy((void *)fxbuffer, (void *)fx, sizeof(FxStorage));
793 }
794
pasteFX()795 void CFxMenu::pasteFX()
796 {
797 if (fxCopyPaste.size() == 0)
798 {
799 return;
800 }
801
802 fxbuffer->type.val.i = (int)fxCopyPaste[0];
803
804 Effect *t_fx = spawn_effect(fxbuffer->type.val.i, storage, fxbuffer, 0);
805 if (t_fx)
806 {
807 t_fx->init_ctrltypes();
808 t_fx->init_default_values();
809 delete t_fx;
810 }
811
812 for (int i = 0; i < n_fx_params; i++)
813 {
814 int vp = i * 4 + 1;
815 int tp = i * 4 + 2;
816 int xp = i * 4 + 3;
817 int dp = i * 4 + 4;
818
819 switch (fxbuffer->p[i].valtype)
820 {
821 case vt_float:
822 {
823 fxbuffer->p[i].val.f = fxCopyPaste[vp];
824 if (fxbuffer->p[i].val.f < fxbuffer->p[i].val_min.f)
825 {
826 fxbuffer->p[i].val.f = fxbuffer->p[i].val_min.f;
827 }
828 if (fxbuffer->p[i].val.f > fxbuffer->p[i].val_max.f)
829 {
830 fxbuffer->p[i].val.f = fxbuffer->p[i].val_max.f;
831 }
832 }
833 break;
834 case vt_int:
835 fxbuffer->p[i].val.i = (int)fxCopyPaste[vp];
836 break;
837 default:
838 break;
839 }
840 fxbuffer->p[i].temposync = (int)fxCopyPaste[tp];
841 fxbuffer->p[i].extend_range = (int)fxCopyPaste[xp];
842 fxbuffer->p[i].deactivated = (int)fxCopyPaste[dp];
843 }
844
845 selectedName = std::string("Copied ") + fx_type_names[fxbuffer->type.val.i];
846
847 if (listenerNotForParent)
848 listenerNotForParent->valueChanged(this);
849 }
850
saveFX()851 void CFxMenu::saveFX()
852 {
853 auto *sge = dynamic_cast<SurgeGUIEditor *>(listenerNotForParent);
854 if (sge)
855 {
856 sge->promptForMiniEdit("", "Enter a name for the FX preset:", "Save FX Preset",
857 CPoint(-1, -1), [this](const std::string &s) { this->saveFXIn(s); });
858 }
859 }
saveFXIn(const std::string & s)860 void CFxMenu::saveFXIn(const std::string &s)
861 {
862 char fxName[TXT_SIZE];
863 fxName[0] = 0;
864 strxcpy(fxName, s.c_str(), TXT_SIZE);
865
866 if (strlen(fxName) == 0)
867 {
868 return;
869 }
870
871 if (!Surge::Storage::isValidName(fxName))
872 {
873 return;
874 }
875
876 int ti = fx->type.val.i;
877
878 std::ostringstream oss;
879 oss << storage->userFXPath << PATH_SEPARATOR << fx_type_names[ti] << PATH_SEPARATOR;
880
881 auto pn = oss.str();
882 fs::create_directories(string_to_path(pn));
883
884 auto fn = pn + fxName + ".srgfx";
885 std::ofstream pfile(fn, std::ios::out);
886 if (!pfile.is_open())
887 {
888 Surge::UserInteractions::promptError(
889 std::string("Unable to open FX preset file '") + fn + "' for writing!", "Error");
890 return;
891 }
892
893 // this used to say streaming_versio before (was a typo)
894 // make sure both variants are checked when checking sv in the future on patch load
895 pfile << "<single-fx streaming_version=\"" << ff_revision << "\">\n";
896
897 // take care of 5 special XML characters
898 std::string fxNameSub(fxName);
899 Surge::Storage::findReplaceSubstring(fxNameSub, std::string("&"), std::string("&"));
900 Surge::Storage::findReplaceSubstring(fxNameSub, std::string("<"), std::string("<"));
901 Surge::Storage::findReplaceSubstring(fxNameSub, std::string(">"), std::string(">"));
902 Surge::Storage::findReplaceSubstring(fxNameSub, std::string("\""), std::string("""));
903 Surge::Storage::findReplaceSubstring(fxNameSub, std::string("'"), std::string("'"));
904
905 pfile << " <snapshot name=\"" << fxNameSub.c_str() << "\" \n";
906
907 pfile << " type=\"" << fx->type.val.i << "\"\n";
908 for (int i = 0; i < n_fx_params; ++i)
909 {
910 if (fx->p[i].ctrltype != ct_none)
911 {
912 switch (fx->p[i].valtype)
913 {
914 case vt_float:
915 pfile << " p" << i << "=\"" << fx->p[i].val.f << "\"\n";
916 break;
917 case vt_int:
918 pfile << " p" << i << "=\"" << fx->p[i].val.i << "\"\n";
919 break;
920 }
921
922 if (fx->p[i].can_temposync() && fx->p[i].temposync)
923 {
924 pfile << " p" << i << "_temposync=\"1\"\n";
925 }
926 if (fx->p[i].can_extend_range() && fx->p[i].extend_range)
927 {
928 pfile << " p" << i << "_extend_range=\"1\"\n";
929 }
930 if (fx->p[i].can_deactivate() && fx->p[i].deactivated)
931 {
932 pfile << " p" << i << "_deactivated=\"1\"\n";
933 }
934 }
935 }
936
937 pfile << " />\n";
938 pfile << "</single-fx>\n";
939 pfile.close();
940
941 scanForUserPresets = true;
942 auto *sge = dynamic_cast<SurgeGUIEditor *>(listenerNotForParent);
943 if (sge)
944 sge->queueRebuildUI();
945 }
946
loadUserPreset(const UserPreset & p)947 void CFxMenu::loadUserPreset(const UserPreset &p)
948 {
949 fxbuffer->type.val.i = p.type;
950
951 Effect *t_fx = spawn_effect(fxbuffer->type.val.i, storage, fxbuffer, 0);
952
953 if (t_fx)
954 {
955 t_fx->init_ctrltypes();
956 t_fx->init_default_values();
957 delete t_fx;
958 }
959
960 for (int i = 0; i < n_fx_params; i++)
961 {
962 switch (fxbuffer->p[i].valtype)
963 {
964 case vt_float:
965 {
966 fxbuffer->p[i].val.f = p.p[i];
967 }
968 break;
969 case vt_int:
970 fxbuffer->p[i].val.i = (int)p.p[i];
971 break;
972 default:
973 break;
974 }
975 fxbuffer->p[i].temposync = (int)p.ts[i];
976 fxbuffer->p[i].extend_range = (int)p.er[i];
977 fxbuffer->p[i].deactivated = (int)p.da[i];
978 }
979
980 selectedIdx = -1;
981 selectedName = p.name;
982
983 if (listenerNotForParent)
984 {
985 listenerNotForParent->valueChanged(this);
986 }
987 }
988
addToTopLevelTypeMenu(TiXmlElement * type,VSTGUI::COptionMenu * subMenu,int & idx)989 void CFxMenu::addToTopLevelTypeMenu(TiXmlElement *type, VSTGUI::COptionMenu *subMenu, int &idx)
990 {
991 if (!type || !subMenu)
992 return;
993
994 int type_id = 0;
995 type->Attribute("i", &type_id);
996
997 if (userPresets.find(type_id) == userPresets.end() || userPresets[type_id].size() == 0)
998 return;
999
1000 auto factory_add = subMenu->addEntry("FACTORY PRESETS", 0);
1001 factory_add->setEnabled(0);
1002
1003 auto user_add = subMenu->addEntry("USER PRESETS");
1004 user_add->setEnabled(0);
1005
1006 for (auto &ps : userPresets[type_id])
1007 {
1008 auto fxName = ps.name;
1009
1010 #if WINDOWS
1011 Surge::Storage::findReplaceSubstring(fxName, std::string("&"), std::string("&&"));
1012 #endif
1013
1014 auto i = new CCommandMenuItem(CCommandMenuItem::Desc(fxName.c_str()));
1015 i->setActions([this, ps](CCommandMenuItem *item) { this->loadUserPreset(ps); });
1016 subMenu->addEntry(i);
1017 }
1018 }
1019