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 "DspUtilities.h"
17 #include "SurgeStorage.h"
18 #include "UserInteractions.h"
19 #include <set>
20 #include <numeric>
21 #include <cctype>
22 #include <map>
23 #include <queue>
24 #include <vt_dsp/vt_dsp_endian.h>
25 #include "UserDefaults.h"
26 #if MAC
27 #include <cstdlib>
28 #include <sys/stat.h>
29 //#include <MoreFilesX.h>
30 //#include <MacErrorHandling.h>
31 #include <CoreFoundation/CFBundle.h>
32 #include <CoreServices/CoreServices.h>
33 #elif LINUX
34 #include <stdlib.h>
35 #else
36 #include <windows.h>
37 #include <shellapi.h>
38 #include <shlobj.h>
39 #endif
40 
41 #include <iostream>
42 #include <iomanip>
43 #include <sstream>
44 
45 #include "UserDefaults.h"
46 #include "version.h"
47 
48 #include "strnatcmp.h"
49 #include "libMTSClient.h"
50 
51 // FIXME probably remove this when we remove the hardcoded hack below
52 #include "MSEGModulationHelper.h"
53 // FIXME
54 
55 #if __cplusplus < 201703L
56 constexpr float MSEGStorage::minimumDuration;
57 #endif
58 
59 float sinctable alignas(16)[(FIRipol_M + 1) * FIRipol_N * 2];
60 float sinctable1X alignas(16)[(FIRipol_M + 1) * FIRipol_N];
61 short sinctableI16 alignas(16)[(FIRipol_M + 1) * FIRipolI16_N];
62 float table_dB alignas(16)[512], table_envrate_lpf alignas(16)[512],
63     table_envrate_linear alignas(16)[512], table_glide_exp alignas(16)[512],
64     table_glide_log alignas(16)[512];
65 float waveshapers alignas(16)[n_ws_types][1024];
66 float samplerate = 0, samplerate_inv;
67 double dsamplerate, dsamplerate_inv;
68 double dsamplerate_os, dsamplerate_os_inv;
69 
70 using namespace std;
71 
72 #if WINDOWS
dummyExportedWindowsToLookupDLL()73 void dummyExportedWindowsToLookupDLL() {}
74 #else
75 #include <dlfcn.h>
getDLLPath()76 std::string getDLLPath()
77 {
78     Dl_info info;
79     if (dladdr((const void *)getDLLPath, &info))
80     {
81         auto res = std::string(info.dli_fname);
82 #if MAC
83         for (int i = 0; i < 3; i++)
84         {
85             size_t delPos = res.find_last_of('/');
86             if (delPos == std::string::npos)
87             {
88                 return "<error>"; // unexpected
89             }
90             res.erase(delPos, res.length() - delPos);
91         }
92 #endif
93         return res;
94     }
95     return "";
96 }
97 #endif
98 
SurgeStorage(std::string suppliedDataPath)99 SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0)
100 {
101     _patch.reset(new SurgePatch(this));
102 
103     float cutoff = 0.455f;
104     float cutoff1X = 0.85f;
105     float cutoffI16 = 1.0f;
106     int j;
107     for (j = 0; j < FIRipol_M + 1; j++)
108     {
109         for (int i = 0; i < FIRipol_N; i++)
110         {
111             double t = -double(i) + double(FIRipol_N / 2.0) + double(j) / double(FIRipol_M) - 1.0;
112             double val = (float)(symmetric_blackman(t, FIRipol_N) * cutoff * sincf(cutoff * t));
113             double val1X =
114                 (float)(symmetric_blackman(t, FIRipol_N) * cutoff1X * sincf(cutoff1X * t));
115             sinctable[j * FIRipol_N * 2 + i] = (float)val;
116             sinctable1X[j * FIRipol_N + i] = (float)val1X;
117         }
118     }
119     for (j = 0; j < FIRipol_M; j++)
120     {
121         for (int i = 0; i < FIRipol_N; i++)
122         {
123             sinctable[j * FIRipol_N * 2 + FIRipol_N + i] =
124                 (float)((sinctable[(j + 1) * FIRipol_N * 2 + i] -
125                          sinctable[j * FIRipol_N * 2 + i]) /
126                         65536.0);
127         }
128     }
129 
130     for (j = 0; j < FIRipol_M + 1; j++)
131     {
132         for (int i = 0; i < FIRipolI16_N; i++)
133         {
134             double t =
135                 -double(i) + double(FIRipolI16_N / 2.0) + double(j) / double(FIRipol_M) - 1.0;
136             double val =
137                 (float)(symmetric_blackman(t, FIRipolI16_N) * cutoffI16 * sincf(cutoffI16 * t));
138 
139             sinctableI16[j * FIRipolI16_N + i] = (short)((float)val * 16384.f);
140         }
141     }
142     /*for(j=0; j<FIRipolI16_N; j++){
143             for(int i=0; i<FIRipol_N; i++){
144                     sinctable[j*FIRipolI16_N*2 + FIRipolI16_N + i] = sinctable[(j+1)*FIRipolI16_N*2
145     + i] - sinctable[j*FIRipolI16_N*2 + i]));
146             }
147     }*/
148 
149     for (int s = 0; s < n_scenes; s++)
150         for (int o = 0; o < n_oscs; o++)
151         {
152             for (int i = 0; i < max_mipmap_levels; i++)
153                 for (int j = 0; j < max_subtables; j++)
154                 {
155                     getPatch().scene[s].osc[o].wt.TableF32WeakPointers[i][j] = 0;
156                     getPatch().scene[s].osc[o].wt.TableI16WeakPointers[i][j] = 0;
157                 }
158             getPatch().scene[s].osc[o].extraConfig.nData = 0;
159             memset(getPatch().scene[s].osc[0].extraConfig.data, 0,
160                    sizeof(float) * OscillatorStorage::ExtraConfigurationData::max_config);
161         }
162 
163     for (int s = 0; s < n_scenes; ++s)
164         for (int fu = 0; fu < n_filterunits_per_scene; ++fu)
165             for (int t = 0; t < n_fu_types; ++t)
166             {
167                 switch (t)
168                 {
169                 case fut_lpmoog:
170                 case fut_obxd_4pole:
171                 case fut_diode:
172                     subtypeMemory[s][fu][t] = 3;
173                     break;
174                 default:
175                     subtypeMemory[s][fu][t] = 0;
176                     break;
177                 }
178             }
179 
180     init_tables();
181     pitch_bend = 0;
182     last_key[0] = 60;
183     last_key[1] = 60;
184     temposyncratio = 1.f;
185     temposyncratio_inv = 0.0f; // Use this as a sentinel (since it was not initialized prior
186                                // to 1.6.5 this was the value at least win and mac had). #1444
187 
188     songpos = 0;
189 
190     for (int i = 0; i < n_customcontrollers; i++)
191     {
192         controllers[i] = 41 + i;
193     }
194     for (int i = 0; i < n_modsources; i++)
195         modsource_vu[i] = 0.f; // remove?
196 
197     for (int s = 0; s < n_scenes; s++)
198         for (int cc = 0; cc < 128; cc++)
199             poly_aftertouch[s][cc] = 0.f;
200 
201     memset(&audio_in[0][0], 0, 2 * BLOCK_SIZE_OS * sizeof(float));
202 
203     bool hasSuppliedDataPath = false;
204     if (suppliedDataPath.size() != 0)
205     {
206         hasSuppliedDataPath = true;
207     }
208 
209 #if MAC || LINUX
210     const char *homePath = getenv("HOME");
211     if (!homePath)
212         throw std::runtime_error("The environment variable HOME does not exist");
213 
214     installedPath = getDLLPath();
215 #endif
216 
217 #if MAC
218     char path[1024];
219     if (!hasSuppliedDataPath)
220     {
221         FSRef foundRef;
222         OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, false, &foundRef);
223         FSRefMakePath(&foundRef, (UInt8 *)path, 1024);
224         std::string localpath = path;
225         localpath += "/Surge/";
226 
227         err = FSFindFolder(kLocalDomain, kApplicationSupportFolderType, false, &foundRef);
228         FSRefMakePath(&foundRef, (UInt8 *)path, 1024);
229         std::string rootpath = path;
230         rootpath += "/Surge/";
231 
232         struct stat linfo;
233         auto lxml = localpath + "configuration.xml";
234         int lstat = stat(lxml.c_str(), &linfo);
235 
236         struct stat rinfo;
237         auto rxml = rootpath + "configuration.xml";
238         int rstat = stat(rxml.c_str(), &rinfo);
239 
240         // if the local one is here, and either is newer than the root one, or the root one is
241         // missing
242         if (lstat == 0 && (linfo.st_mtime > rinfo.st_mtime || rstat != 0))
243             datapath = localpath; // use the local
244         else
245             datapath = rootpath; // else use the root. If both are missing we will blow up later.
246     }
247     else
248     {
249         datapath = suppliedDataPath;
250     }
251 
252     // ~/Documents/Surge in full name
253     sprintf(path, "%s/Documents/Surge", homePath);
254     userDataPath = path;
255 #elif LINUX
256     if (!hasSuppliedDataPath)
257     {
258         const char *xdgDataPath = getenv("XDG_DATA_HOME");
259         std::string localDataPath = std::string(homePath) + "/.local/share/surge/";
260         if (xdgDataPath)
261         {
262             datapath = std::string(xdgDataPath) + "/surge/";
263         }
264         else if (fs::is_directory(string_to_path(localDataPath)))
265         {
266             datapath = localDataPath;
267         }
268         else
269         {
270             datapath = std::string(homePath) + "/.local/share/Surge/";
271         }
272 
273         /*
274         ** If local directory doesn't exists - we probably came here through an installer -
275         ** check for /usr/share/surge and use /usr/share/Surge as our last guess
276         */
277         if (!fs::is_directory(string_to_path(datapath)))
278         {
279             if (fs::is_directory(string_to_path(std::string(Surge::Build::CMAKE_INSTALL_PREFIX) +
280                                                 "/share/surge")))
281             {
282                 datapath = std::string() + Surge::Build::CMAKE_INSTALL_PREFIX + "/share/surge";
283             }
284             else if (fs::is_directory(string_to_path(
285                          std::string(Surge::Build::CMAKE_INSTALL_PREFIX) + "/share/Surge")))
286             {
287                 datapath = std::string() + Surge::Build::CMAKE_INSTALL_PREFIX + "/share/Surge";
288             }
289             else
290             {
291                 std::string systemDataPath = "/usr/share/surge/";
292                 if (fs::is_directory(string_to_path(systemDataPath)))
293                     datapath = systemDataPath;
294                 else
295                     datapath = "/usr/local/share/surge-synthesizer/";
296             }
297         }
298     }
299     else
300     {
301         datapath = suppliedDataPath;
302     }
303 
304     /*
305     ** See the discussion in github issue #930. Basically
306     ** if ~/Documents/Surge exists use that
307     ** else if ~/.Surge exists use that
308     ** else if ~/.Documents exists, use ~/Documents/Surge
309     ** else use ~/.Surge
310     ** Compensating for whether your distro makes you a ~/Documents or not
311     */
312 
313     std::string documentsSurge = std::string(homePath) + "/Documents/Surge";
314     std::string dotSurge = std::string(homePath) + "/.Surge";
315     std::string documents = std::string(homePath) + "/Documents/";
316 
317     if (fs::is_directory(string_to_path(documentsSurge)))
318     {
319         userDataPath = documentsSurge;
320     }
321     else if (fs::is_directory(string_to_path(dotSurge)))
322     {
323         userDataPath = dotSurge;
324     }
325     else if (fs::is_directory(string_to_path(documents)))
326     {
327         userDataPath = documentsSurge;
328     }
329     else
330     {
331         userDataPath = dotSurge;
332     }
333     // std::cout << "DataPath is " << datapath << std::endl;
334     // std::cout << "UserDataPath is " << userDataPath << std::endl;
335 
336 #elif WINDOWS
337 #if TARGET_RACK
338     datapath = suppliedDataPath;
339 #else
340     fs::path dllPath;
341 
342     // First check the portable mode sitting beside me
343     {
344         WCHAR pathBuf[MAX_PATH];
345         HMODULE hm = NULL;
346 
347         if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
348                                   GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
349                               reinterpret_cast<LPCTSTR>(&dummyExportedWindowsToLookupDLL),
350                               &hm) == 0)
351         {
352             int ret = GetLastError();
353             printf("GetModuleHandle failed, error = %d\n", ret);
354             // Return or however you want to handle an error.
355             goto bailOnPortable;
356         }
357         if (GetModuleFileName(hm, pathBuf, std::size(pathBuf)) == 0)
358         {
359             int ret = GetLastError();
360             printf("GetModuleFileName failed, error = %d\n", ret);
361             // Return or however you want to handle an error.
362             goto bailOnPortable;
363         }
364 
365         // The pathBuf variable should now contain the full filepath for this DLL.
366         fs::path path(pathBuf);
367         installedPath = path_to_string(path);
368 
369         path.remove_filename();
370         dllPath = path;
371         path /= L"SurgeData";
372         if (fs::is_directory(path))
373         {
374             datapath = path_to_string(path);
375         }
376     }
377 bailOnPortable:
378 
379     if (datapath.empty())
380     {
381         PWSTR commonAppData;
382         if (!SHGetKnownFolderPath(FOLDERID_ProgramData, 0, nullptr, &commonAppData))
383         {
384             fs::path path(commonAppData);
385             path /= L"Surge";
386             if (fs::is_directory(path))
387             {
388                 datapath = path_to_string(path);
389             }
390         }
391     }
392 
393     if (datapath.empty())
394     {
395         PWSTR localAppData;
396         if (!SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localAppData))
397         {
398             fs::path path(localAppData);
399             path /= L"Surge";
400             datapath = path_to_string(path);
401         }
402     }
403 
404     // Portable - first check for dllPath\\SurgeUserData
405     if (!dllPath.empty() && fs::is_directory(dllPath / L"SurgeUserData"))
406     {
407         userDataPath = path_to_string(dllPath / L"SurgeUserData");
408     }
409     else
410     {
411         PWSTR documentsFolder;
412         if (!SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &documentsFolder))
413         {
414             fs::path path(documentsFolder);
415             path /= L"Surge";
416             userDataPath = path_to_string(path);
417         }
418     }
419 #endif
420 #endif
421 
422     userDefaultFilePath = userDataPath;
423 
424     std::string userSpecifiedDataPath =
425         Surge::Storage::getUserDefaultValue(this, "userDataPath", "UNSPEC");
426     if (userSpecifiedDataPath != "UNSPEC")
427     {
428         userDataPath = userSpecifiedDataPath;
429     }
430 
431     // append separator if not present
432     datapath = Surge::Storage::appendDirectory(datapath, std::string());
433 
434     userFXPath = Surge::Storage::appendDirectory(userDataPath, "FXSettings");
435 
436     userMidiMappingsPath = Surge::Storage::appendDirectory(userDataPath, "MIDIMappings");
437 
438     const auto snapshotmenupath{string_to_path(datapath + "configuration.xml")};
439 
440     if (!snapshotloader.LoadFile(snapshotmenupath)) // load snapshots (& config-stuff)
441     {
442         Surge::UserInteractions::promptError("Cannot find 'configuration.xml' in path '" +
443                                                  datapath + "'. Please reinstall surge.",
444                                              "Surge is not properly installed.");
445     }
446 
447     load_midi_controllers();
448 
449     bool loadWtAndPatch = true;
450 
451 #if !TARGET_RACK
452     // skip loading during export, it pops up an irrelevant error dialog. Only used by LV2
453     loadWtAndPatch = !skipLoadWtAndPatch;
454     if (loadWtAndPatch)
455     {
456         refresh_wtlist();
457         refresh_patchlist();
458     }
459 #endif
460 
461     getPatch().scene[0].osc[0].wt.dt = 1.0f / 512.f;
462     load_wt(0, &getPatch().scene[0].osc[0].wt, &getPatch().scene[0].osc[0]);
463 
464     // WindowWT is a WaveTable which now has a constructor so don't do this
465     // memset(&WindowWT, 0, sizeof(WindowWT));
466     if (loadWtAndPatch && !load_wt_wt(datapath + "windows.wt", &WindowWT))
467     {
468         WindowWT.size = 0;
469         std::ostringstream oss;
470         oss << "Unable to load '" << datapath
471             << "windows.wt'. This file is required for Surge to work "
472             << "properly. This occurs when Surge is incorrectly installed and its resources are "
473                "not found at "
474 #if MAC
475             << "the global or local user Library/Application Support/Surge directory."
476 #endif
477 #if WINDOWS
478             << "%ProgramData%\\Surge directory."
479 #endif
480 #if LINUX
481             << "/usr/share/Surge or ~/.local/share/Surge."
482 #endif
483             << " Please reinstall Surge and try again!";
484         Surge::UserInteractions::promptError(oss.str(), "Surge Resources Loading Error");
485     }
486 
487     // Tunings Library Support
488     currentScale = Tunings::evenTemperament12NoteScale();
489     currentMapping = Tunings::KeyboardMapping();
490     cachedToggleOffScale = currentScale;
491     cachedToggleOffMapping = currentMapping;
492     twelveToneStandardMapping =
493         Tunings::Tuning(Tunings::evenTemperament12NoteScale(), Tunings::KeyboardMapping());
494     isStandardTuning = true;
495     isToggledToCache = false;
496     for (int q = 0; q < 3; ++q)
497         togglePriorState[q] = false;
498 
499     // Load the XML DocStrings if we are loading startup data
500     if (loadWtAndPatch)
501     {
502         auto dsf = string_to_path(datapath + "paramdocumentation.xml");
503         TiXmlDocument doc;
504         if (!doc.LoadFile(dsf) || doc.Error())
505         {
506             std::cout << "Unable to load  '" << dsf << "'!" << std::endl;
507             std::cout << "Unable to parse!\nError is:\n"
508                       << doc.ErrorDesc() << " at row " << doc.ErrorRow() << ", column "
509                       << doc.ErrorCol() << std::endl;
510         }
511         else
512         {
513             TiXmlElement *pdoc = TINYXML_SAFE_TO_ELEMENT(doc.FirstChild("param-doc"));
514             if (!pdoc)
515             {
516                 Surge::UserInteractions::promptError(
517                     "Unknown top element in paramdocumentation.xml - not a parameter documentation "
518                     "XML file!",
519                     "Error");
520             }
521             else
522             {
523                 for (auto pchild = pdoc->FirstChildElement(); pchild;
524                      pchild = pchild->NextSiblingElement())
525                 {
526                     if (strcmp(pchild->Value(), "ctrl_group") == 0)
527                     {
528                         int g = 0;
529                         if (pchild->QueryIntAttribute("group", &g) == TIXML_SUCCESS)
530                         {
531                             std::string help_url = pchild->Attribute("help_url");
532                             if (help_url.size() > 0)
533                                 helpURL_controlgroup[g] = help_url;
534                         }
535                     }
536                     else if (strcmp(pchild->Value(), "param") == 0)
537                     {
538                         std::string id = pchild->Attribute("id");
539                         std::string help_url = pchild->Attribute("help_url");
540                         int t = 0;
541                         if (help_url.size() > 0)
542                         {
543                             if (pchild->QueryIntAttribute("type", &t) == TIXML_SUCCESS)
544                             {
545                                 helpURL_paramidentifier_typespecialized[std::make_pair(id, t)] =
546                                     help_url;
547                             }
548                             else
549                             {
550                                 helpURL_paramidentifier[id] = help_url;
551                             }
552                         }
553                     }
554                     else if (strcmp(pchild->Value(), "special") == 0)
555                     {
556                         std::string id = pchild->Attribute("id");
557                         std::string help_url = pchild->Attribute("help_url");
558                         if (help_url.size() > 0)
559                         {
560                             helpURL_specials[id] = help_url;
561                         }
562                     }
563                     else
564                     {
565                         std::cout << "UNKNOWN " << pchild->Value() << std::endl;
566                     }
567                 }
568             }
569         }
570     }
571 
572     for (int s = 0; s < n_scenes; ++s)
573     {
574         for (int i = 0; i < n_lfos; ++i)
575         {
576             auto ms = &(_patch->msegs[s][i]);
577             if (ms_lfo1 + i >= ms_slfo1 && ms_lfo1 + i <= ms_slfo6)
578             {
579                 Surge::MSEG::createInitSceneMSEG(ms);
580             }
581             else
582             {
583                 Surge::MSEG::createInitVoiceMSEG(ms);
584             }
585             Surge::MSEG::rebuildCache(ms);
586         }
587     }
588 
589     monoPedalMode = (MonoPedalMode)Surge::Storage::getUserDefaultValue(
590         this, "monoPedalMode", MonoPedalMode::HOLD_ALL_NOTES);
591 
592     for (int s = 0; s < n_scenes; ++s)
593     {
594         getPatch().scene[s].drift.extend_range = true;
595     }
596 
597     bool mtsMode = Surge::Storage::getUserDefaultValue(this, "useODDMTS", false);
598     if (mtsMode)
599     {
600         initialize_oddsound();
601     }
602     else
603     {
604         oddsound_mts_client = nullptr;
605         oddsound_mts_active = false;
606     }
607 }
608 
getPatch()609 SurgePatch &SurgeStorage::getPatch() { return *_patch.get(); }
610 
611 struct PEComparer
612 {
operator ()PEComparer613     bool operator()(const Patch &a, const Patch &b) { return a.name.compare(b.name) < 0; }
614 };
615 
refresh_patchlist()616 void SurgeStorage::refresh_patchlist()
617 {
618     patch_category.clear();
619     patch_list.clear();
620 
621     refreshPatchlistAddDir(false, "patches_factory");
622     firstThirdPartyCategory = patch_category.size();
623 
624     refreshPatchlistAddDir(false, "patches_3rdparty");
625     firstUserCategory = patch_category.size();
626     refreshPatchlistAddDir(true, "");
627 
628     patchOrdering = std::vector<int>(patch_list.size());
629     std::iota(patchOrdering.begin(), patchOrdering.end(), 0);
630 
631     auto patchCompare = [this](const int &i1, const int &i2) -> bool {
632         return strnatcasecmp(patch_list[i1].name.c_str(), patch_list[i2].name.c_str()) < 0;
633     };
634 
635     std::sort(patchOrdering.begin(), patchOrdering.end(), patchCompare);
636 
637     patchCategoryOrdering = std::vector<int>(patch_category.size());
638     std::iota(patchCategoryOrdering.begin(), patchCategoryOrdering.end(), 0);
639 
640     for (int i = 0; i < patch_list.size(); i++)
641     {
642         patch_list[patchOrdering[i]].order = i;
643     }
644 
645     auto categoryCompare = [this](const int &i1, const int &i2) -> bool {
646         return strnatcasecmp(patch_category[i1].name.c_str(), patch_category[i2].name.c_str()) < 0;
647     };
648 
649     int groups[4] = {0, firstThirdPartyCategory, firstUserCategory, (int)patch_category.size()};
650 
651     for (int i = 0; i < 3; i++)
652     {
653         std::sort(std::next(patchCategoryOrdering.begin(), groups[i]),
654                   std::next(patchCategoryOrdering.begin(), groups[i + 1]), categoryCompare);
655     }
656 
657     for (int i = 0; i < patch_category.size(); i++)
658     {
659         patch_category[patchCategoryOrdering[i]].order = i;
660     }
661 }
662 
refreshPatchlistAddDir(bool userDir,string subdir)663 void SurgeStorage::refreshPatchlistAddDir(bool userDir, string subdir)
664 {
665     refreshPatchOrWTListAddDir(
666         userDir, subdir, [](std::string s) -> bool { return _stricmp(s.c_str(), ".fxp") == 0; },
667         patch_list, patch_category);
668 }
669 
refreshPatchOrWTListAddDir(bool userDir,string subdir,std::function<bool (std::string)> filterOp,std::vector<Patch> & items,std::vector<PatchCategory> & categories)670 void SurgeStorage::refreshPatchOrWTListAddDir(bool userDir, string subdir,
671                                               std::function<bool(std::string)> filterOp,
672                                               std::vector<Patch> &items,
673                                               std::vector<PatchCategory> &categories)
674 {
675     int category = categories.size();
676 
677     // See issue 4200. In some cases this can throw a filesystem exception so:
678     std::vector<PatchCategory> local_categories;
679 
680     try
681     {
682         fs::path patchpath = string_to_path(userDir ? userDataPath : datapath);
683         if (!subdir.empty())
684             patchpath /= string_to_path(subdir);
685 
686         if (!fs::is_directory(patchpath))
687         {
688             return;
689         }
690 
691         /*
692         ** std::filesystem has a recursive_directory_iterator, but between the
693         ** hand rolled ipmmlementation on mac, expermiental on windows, and
694         ** ostensibly standard on linux it isn't consistent enough to warrant
695         ** using yet, so build my own recursive directory traversal with a simple
696         ** stack
697         */
698         std::vector<fs::path> alldirs;
699         std::deque<fs::path> workStack;
700         workStack.push_back(patchpath);
701         while (!workStack.empty())
702         {
703             auto top = workStack.front();
704             workStack.pop_front();
705             for (auto &d : fs::directory_iterator(top))
706             {
707                 if (fs::is_directory(d))
708                 {
709                     alldirs.push_back(d);
710                     workStack.push_back(d);
711                 }
712             }
713         }
714 
715         /*
716         ** We want to remove parent directory /user/foo or c:\\users\\bar\\
717         ** with a substr in the main loop, so get the length once
718         */
719         auto patchpathStr(path_to_string(patchpath));
720         auto patchpathSubstrLength = patchpathStr.size() + 1;
721         if (patchpathStr.back() == '/' || patchpathStr.back() == '\\')
722             patchpathSubstrLength--;
723 
724         for (auto &p : alldirs)
725         {
726             PatchCategory c;
727             c.name = path_to_string(p).substr(patchpathSubstrLength);
728             c.internalid = category;
729 
730             c.numberOfPatchesInCatgory = 0;
731             for (auto &f : fs::directory_iterator(p))
732             {
733                 std::string xtn = path_to_string(f.path().extension());
734                 if (filterOp(xtn))
735                 {
736                     Patch e;
737                     e.category = category;
738                     e.path = f.path();
739                     e.name = path_to_string(f.path().filename());
740                     e.name = e.name.substr(0, e.name.size() - xtn.length());
741                     items.push_back(e);
742 
743                     c.numberOfPatchesInCatgory++;
744                 }
745             }
746 
747             c.numberOfPatchesInCategoryAndChildren = c.numberOfPatchesInCatgory;
748             local_categories.push_back(c);
749             category++;
750         }
751     }
752     catch (const fs::filesystem_error &e)
753     {
754         std::ostringstream oss;
755         oss << "Experienced file system error when building patches. " << e.what();
756         Surge::UserInteractions::promptError(oss.str(), "FileSystem Error");
757     }
758 
759     /*
760     ** Now establish parent child relationships between patch categories. Do this by
761     ** scanning for names; setting the 'root' to everything without a slash
762     ** and finding the parent in the name map for everything with a slash
763     */
764 
765     std::map<std::string, int> nameToLocalIndex;
766     int idx = 0;
767     for (auto &pc : local_categories)
768         nameToLocalIndex[pc.name] = idx++;
769 
770     for (auto &pc : local_categories)
771     {
772         if (pc.name.find(PATH_SEPARATOR) == std::string::npos)
773         {
774             pc.isRoot = true;
775         }
776         else
777         {
778             pc.isRoot = false;
779             std::string parent = pc.name.substr(0, pc.name.find_last_of(PATH_SEPARATOR));
780             local_categories[nameToLocalIndex[parent]].children.push_back(pc);
781         }
782     }
783 
784     /*
785     ** We need to sort the local patch category child to make sure subfolders remain
786     ** sorted when displayed using the child data structure in the menu view.
787     */
788 
789     auto catCompare = [this](const PatchCategory &c1, const PatchCategory &c2) -> bool {
790         return strnatcasecmp(c1.name.c_str(), c2.name.c_str()) < 0;
791     };
792     for (auto &pc : local_categories)
793     {
794         std::sort(pc.children.begin(), pc.children.end(), catCompare);
795     }
796 
797     /*
798     ** Now we need to prune categories with nothing in their children.
799     ** Start by updating the numberOfPatchesInCatgoryAndChildren from the root.
800     ** This is complicated because the child list is a copy of values which
801     ** I should fix one day. FIXME fix that to avoid this sort of double copy
802     ** nonsense. But this keeps it consistent. At a price. Sorry!
803     */
804     std::function<void(PatchCategory &)> recCorrect = [&recCorrect, &nameToLocalIndex,
805                                                        &local_categories](PatchCategory &c) {
806         local_categories[nameToLocalIndex[c.name]].numberOfPatchesInCategoryAndChildren =
807             c.numberOfPatchesInCatgory;
808         for (auto &ckid : c.children)
809         {
810             recCorrect(local_categories[nameToLocalIndex[ckid.name]]);
811             ckid.numberOfPatchesInCategoryAndChildren =
812                 local_categories[nameToLocalIndex[ckid.name]].numberOfPatchesInCategoryAndChildren;
813 
814             local_categories[nameToLocalIndex[c.name]].numberOfPatchesInCategoryAndChildren +=
815                 local_categories[nameToLocalIndex[ckid.name]].numberOfPatchesInCategoryAndChildren;
816         }
817         c.numberOfPatchesInCategoryAndChildren =
818             local_categories[nameToLocalIndex[c.name]].numberOfPatchesInCategoryAndChildren;
819     };
820 
821     for (auto &c : local_categories)
822     {
823         if (c.isRoot)
824         {
825             recCorrect(c);
826         }
827     }
828 
829     /*
830     ** Then copy our local patch category onto the member and be done
831     */
832     for (auto &pc : local_categories)
833     {
834         categories.push_back(pc);
835     }
836 }
837 
refresh_wtlist()838 void SurgeStorage::refresh_wtlist()
839 {
840     wt_category.clear();
841     wt_list.clear();
842 
843     refresh_wtlistAddDir(false, "wavetables");
844 
845     if (wt_category.size() == 0 || wt_list.size() == 0)
846     {
847         std::ostringstream ss;
848         ss << "Surge was unable to load wavetables from '" << datapath
849            << "'. Please reinstall Surge!";
850         Surge::UserInteractions::promptError(ss.str(), "Surge Installation Error");
851     }
852 
853     firstThirdPartyWTCategory = wt_category.size();
854     refresh_wtlistAddDir(false, "wavetables_3rdparty");
855     firstUserWTCategory = wt_category.size();
856     refresh_wtlistAddDir(true, "");
857 
858     wtCategoryOrdering = std::vector<int>(wt_category.size());
859     std::iota(wtCategoryOrdering.begin(), wtCategoryOrdering.end(), 0);
860 
861     // This nonsense deals with the fact that \ < ' ' but ' ' < / and we want "foo bar/h" and
862     // "foo/bar" to sort consistently on mac and win. See #1218
863     auto categoryCompare = [this](const int &i1, const int &i2) -> bool {
864         auto n1 = wt_category[i1].name;
865         for (auto i = 0; i < n1.length(); ++i)
866             if (n1[i] == '\\')
867                 n1[i] = '/';
868 
869         auto n2 = wt_category[i2].name;
870         for (auto i = 0; i < n2.length(); ++i)
871             if (n2[i] == '\\')
872                 n2[i] = '/';
873 
874         return strnatcasecmp(n1.c_str(), n2.c_str()) < 0;
875     };
876 
877     int groups[4] = {0, firstThirdPartyWTCategory, firstUserWTCategory, (int)wt_category.size()};
878 
879     for (int i = 0; i < 3; i++)
880     {
881         std::sort(std::next(wtCategoryOrdering.begin(), groups[i]),
882                   std::next(wtCategoryOrdering.begin(), groups[i + 1]), categoryCompare);
883     }
884 
885     for (int i = 0; i < wt_category.size(); i++)
886         wt_category[wtCategoryOrdering[i]].order = i;
887 
888     wtOrdering = std::vector<int>();
889 
890     auto wtCompare = [this](const int &i1, const int &i2) -> bool {
891         return strnatcasecmp(wt_list[i1].name.c_str(), wt_list[i2].name.c_str()) < 0;
892     };
893 
894     // Sort wavetables per category in the category order.
895     for (auto c : wtCategoryOrdering)
896     {
897         int start = wtOrdering.size();
898 
899         for (int i = 0; i < wt_list.size(); i++)
900             if (wt_list[i].category == c)
901                 wtOrdering.push_back(i);
902 
903         int end = wtOrdering.size();
904 
905         std::sort(std::next(wtOrdering.begin(), start), std::next(wtOrdering.begin(), end),
906                   wtCompare);
907     }
908 
909     for (int i = 0; i < wt_list.size(); i++)
910         wt_list[wtOrdering[i]].order = i;
911 }
912 
refresh_wtlistAddDir(bool userDir,std::string subdir)913 void SurgeStorage::refresh_wtlistAddDir(bool userDir, std::string subdir)
914 {
915     std::vector<std::string> supportedTableFileTypes;
916     supportedTableFileTypes.push_back(".wt");
917     supportedTableFileTypes.push_back(".wav");
918 
919     refreshPatchOrWTListAddDir(
920         userDir, subdir,
921         [supportedTableFileTypes](std::string in) -> bool {
922             for (auto q : supportedTableFileTypes)
923             {
924                 if (_stricmp(q.c_str(), in.c_str()) == 0)
925                     return true;
926             }
927             return false;
928         },
929         wt_list, wt_category);
930 }
931 
perform_queued_wtloads()932 void SurgeStorage::perform_queued_wtloads()
933 {
934     SurgePatch &patch =
935         getPatch(); // Change here is for performance and ease of debugging, simply not calling
936                     // getPatch so many times. Code should behave identically.
937     for (int sc = 0; sc < n_scenes; sc++)
938     {
939         for (int o = 0; o < n_oscs; o++)
940         {
941             if (patch.scene[sc].osc[o].wt.queue_id != -1)
942             {
943                 load_wt(patch.scene[sc].osc[o].wt.queue_id, &patch.scene[sc].osc[o].wt,
944                         &patch.scene[sc].osc[o]);
945                 patch.scene[sc].osc[o].wt.refresh_display = true;
946             }
947             else if (patch.scene[sc].osc[o].wt.queue_filename[0])
948             {
949                 if (!(patch.scene[sc].osc[o].type.val.i == ot_wavetable ||
950                       patch.scene[sc].osc[o].type.val.i == ot_window))
951                 {
952                     patch.scene[sc].osc[o].queue_type = ot_wavetable;
953                 }
954                 int wtidx = -1, ct = 0;
955                 for (const auto &wti : wt_list)
956                 {
957                     if (path_to_string(wti.path) == patch.scene[sc].osc[0].wt.queue_filename)
958                     {
959                         wtidx = ct;
960                     }
961                     ct++;
962                 }
963 
964                 patch.scene[sc].osc[o].wt.current_id = wtidx;
965                 load_wt(patch.scene[sc].osc[o].wt.queue_filename, &patch.scene[sc].osc[o].wt,
966                         &patch.scene[sc].osc[o]);
967                 patch.scene[sc].osc[o].wt.refresh_display = true;
968             }
969         }
970     }
971 }
972 
load_wt(int id,Wavetable * wt,OscillatorStorage * osc)973 void SurgeStorage::load_wt(int id, Wavetable *wt, OscillatorStorage *osc)
974 {
975     wt->current_id = id;
976     wt->queue_id = -1;
977     if (id < 0)
978         return;
979     if (id >= wt_list.size())
980         return;
981     if (!wt)
982         return;
983 
984     load_wt(path_to_string(wt_list[id].path), wt, osc);
985 
986     if (osc)
987     {
988         auto n = wt_list.at(id).name;
989         strncpy(osc->wavetable_display_name, n.c_str(), 256);
990     }
991 }
992 
load_wt(string filename,Wavetable * wt,OscillatorStorage * osc)993 void SurgeStorage::load_wt(string filename, Wavetable *wt, OscillatorStorage *osc)
994 {
995     wt->queue_filename[0] = 0;
996     string extension = filename.substr(filename.find_last_of('.'), filename.npos);
997     for (unsigned int i = 0; i < extension.length(); i++)
998         extension[i] = tolower(extension[i]);
999     bool loaded = false;
1000     if (extension.compare(".wt") == 0)
1001         loaded = load_wt_wt(filename, wt);
1002     else if (extension.compare(".wav") == 0)
1003         loaded = load_wt_wav_portable(filename, wt);
1004     else
1005     {
1006         std::ostringstream oss;
1007         oss << "Unable to load file with extension " << extension
1008             << "! Surge only supports .wav and .wt wavetable files!";
1009         Surge::UserInteractions::promptError(oss.str(), "Error");
1010     }
1011 
1012     if (osc && loaded)
1013     {
1014         char sep = PATH_SEPARATOR;
1015         auto fn = filename.substr(filename.find_last_of(sep) + 1, filename.npos);
1016         std::string fnnoext = fn.substr(0, fn.find_last_of('.'));
1017 
1018         if (fnnoext.length() > 0)
1019         {
1020             strncpy(osc->wavetable_display_name, fnnoext.c_str(), 256);
1021         }
1022     }
1023 }
1024 
load_wt_wt(string filename,Wavetable * wt)1025 bool SurgeStorage::load_wt_wt(string filename, Wavetable *wt)
1026 {
1027     std::filebuf f;
1028     if (!f.open(string_to_path(filename), std::ios::binary | std::ios::in))
1029         return false;
1030     wt_header wh;
1031     memset(&wh, 0, sizeof(wt_header));
1032 
1033     size_t read = f.sgetn(reinterpret_cast<char *>(&wh), sizeof(wh));
1034     // I'm not sure why this ever worked but it is checking the 4 bytes against vawt so...
1035     // if (wh.tag != vt_read_int32BE('vawt'))
1036     if (!(wh.tag[0] == 'v' && wh.tag[1] == 'a' && wh.tag[2] == 'w' && wh.tag[3] == 't'))
1037     {
1038         // SOME sort of error reporting is appropriate
1039         return false;
1040     }
1041 
1042     size_t ds;
1043     if (vt_read_int16LE(wh.flags) & wtf_int16)
1044         ds = sizeof(short) * vt_read_int16LE(wh.n_tables) * vt_read_int32LE(wh.n_samples);
1045     else
1046         ds = sizeof(float) * vt_read_int16LE(wh.n_tables) * vt_read_int32LE(wh.n_samples);
1047 
1048     const std::unique_ptr<char[]> data{new char[ds]};
1049     read = f.sgetn(data.get(), ds);
1050     // FIXME - error if read != ds
1051 
1052     waveTableDataMutex.lock();
1053     bool wasBuilt = wt->BuildWT(data.get(), wh, false);
1054     waveTableDataMutex.unlock();
1055 
1056     if (!wasBuilt)
1057     {
1058         std::ostringstream oss;
1059         oss << "Wavetable could not be built, which means it has too many samples or frames."
1060             << " You provided " << wh.n_tables << " frames of " << wh.n_samples
1061             << "samples, while limit is " << max_subtables << " frames and " << max_wtable_size
1062             << " samples."
1063             << " In some cases, Surge detects this situation inconsistently. Surge is now in a "
1064                "potentially "
1065             << " inconsistent state. It is recommended to restart Surge and not load the "
1066                "problematic wavetable again."
1067             << " If you would like, please attach the wavetable which caused this message to a new "
1068                "GitHub issue at "
1069             << " https://github.com/surge-synthesizer/surge/";
1070         Surge::UserInteractions::promptError(oss.str(), "Wavetable Loading Error");
1071     }
1072     return wasBuilt;
1073 }
get_clipboard_type()1074 int SurgeStorage::get_clipboard_type() { return clipboard_type; }
1075 
getAdjacentWaveTable(int id,bool nextPrev)1076 int SurgeStorage::getAdjacentWaveTable(int id, bool nextPrev)
1077 {
1078     int n = wt_list.size();
1079     if (!n)
1080         return -1;
1081 
1082     // See comment in SurgeSynthesizerIO::incrementPatch and #319
1083     if (id < 0 || id > n - 1)
1084     {
1085         return wtOrdering[0];
1086     }
1087     else
1088     {
1089         int order = wt_list[id].order;
1090 
1091         if (nextPrev)
1092             order = (order >= (n - 1))
1093                         ? 0
1094                         : order + 1; // see comment in incrementPatch for that >= vs ==
1095         else
1096             order = (order <= 0) ? n - 1 : order - 1;
1097 
1098         return wtOrdering[order];
1099     }
1100 }
1101 
clipboard_copy(int type,int scene,int entry)1102 void SurgeStorage::clipboard_copy(int type, int scene, int entry)
1103 {
1104     bool includemod = false, includeall = false;
1105     if (type == cp_oscmod)
1106     {
1107         type = cp_osc;
1108         includemod = true;
1109     }
1110     int cgroup = -1;
1111     int cgroup_e = -1;
1112     int id = -1;
1113 
1114     clipboard_type = type;
1115     switch (type)
1116     {
1117     case cp_osc:
1118         cgroup = 2;
1119         cgroup_e = entry;
1120         id = getPatch().scene[scene].osc[entry].type.id; // first parameter id
1121         if (uses_wavetabledata(getPatch().scene[scene].osc[entry].type.val.i))
1122         {
1123             clipboard_wt[0].Copy(&getPatch().scene[scene].osc[entry].wt);
1124             strncpy(clipboard_wt_names[0],
1125                     getPatch().scene[scene].osc[entry].wavetable_display_name, 256);
1126         }
1127         memcpy(&clipboard_extraconfig[0], &getPatch().scene[scene].osc[entry].extraConfig,
1128                sizeof(OscillatorStorage::ExtraConfigurationData));
1129         break;
1130     case cp_lfo:
1131         cgroup = 6;
1132         cgroup_e = entry + ms_lfo1;
1133         id = getPatch().scene[scene].lfo[entry].shape.id;
1134         if (getPatch().scene[scene].lfo[entry].shape.val.i == lt_stepseq)
1135             memcpy(&clipboard_stepsequences[0], &getPatch().stepsequences[scene][entry],
1136                    sizeof(StepSequencerStorage));
1137         if (getPatch().scene[scene].lfo[entry].shape.val.i == lt_mseg)
1138             clipboard_msegs[0] = getPatch().msegs[scene][entry];
1139         break;
1140     case cp_scene:
1141     {
1142         includemod = true;
1143         includeall = true;
1144         id = getPatch().scene[scene].octave.id;
1145         for (int i = 0; i < n_lfos; i++)
1146         {
1147             memcpy(&clipboard_stepsequences[i], &getPatch().stepsequences[scene][i],
1148                    sizeof(StepSequencerStorage));
1149             clipboard_msegs[i] = getPatch().msegs[scene][i];
1150         }
1151         for (int i = 0; i < n_oscs; i++)
1152         {
1153             clipboard_wt[i].Copy(&getPatch().scene[scene].osc[i].wt);
1154             strncpy(clipboard_wt_names[i], getPatch().scene[scene].osc[i].wavetable_display_name,
1155                     256);
1156             memcpy(&clipboard_extraconfig[i], &getPatch().scene[scene].osc[i].extraConfig,
1157                    sizeof(OscillatorStorage::ExtraConfigurationData));
1158         }
1159         clipboard_primode = getPatch().scene[scene].monoVoicePriorityMode;
1160     }
1161     break;
1162     default:
1163         return;
1164     }
1165 
1166     modRoutingMutex.lock();
1167     {
1168 
1169         clipboard_p.clear();
1170         clipboard_modulation_scene.clear();
1171         clipboard_modulation_voice.clear();
1172 
1173         std::set<int> used_entries;
1174 
1175         int n = getPatch().param_ptr.size();
1176         for (int i = 0; i < n; i++)
1177         {
1178             Parameter p = *getPatch().param_ptr[i];
1179             if (((p.ctrlgroup == cgroup) || (cgroup < 0)) &&
1180                 ((p.ctrlgroup_entry == cgroup_e) || (cgroup_e < 0)) && (p.scene == (scene + 1)))
1181             {
1182                 p.id = p.id - id;
1183                 used_entries.insert(p.id);
1184                 clipboard_p.push_back(p);
1185             }
1186         }
1187 
1188         if (includemod)
1189         {
1190             int idoffset = 0;
1191             if (!includeall)
1192                 idoffset = -id + n_global_params;
1193             n = getPatch().scene[scene].modulation_voice.size();
1194             for (int i = 0; i < n; i++)
1195             {
1196                 ModulationRouting m;
1197                 m.source_id = getPatch().scene[scene].modulation_voice[i].source_id;
1198                 m.depth = getPatch().scene[scene].modulation_voice[i].depth;
1199                 m.destination_id =
1200                     getPatch().scene[scene].modulation_voice[i].destination_id + idoffset;
1201                 if (includeall || (used_entries.find(m.destination_id) != used_entries.end()))
1202                     clipboard_modulation_voice.push_back(m);
1203             }
1204             n = getPatch().scene[scene].modulation_scene.size();
1205             for (int i = 0; i < n; i++)
1206             {
1207                 ModulationRouting m;
1208                 m.source_id = getPatch().scene[scene].modulation_scene[i].source_id;
1209                 m.depth = getPatch().scene[scene].modulation_scene[i].depth;
1210                 m.destination_id =
1211                     getPatch().scene[scene].modulation_scene[i].destination_id + idoffset;
1212                 if (includeall || (used_entries.find(m.destination_id) != used_entries.end()))
1213                     clipboard_modulation_scene.push_back(m);
1214             }
1215         }
1216     }
1217     modRoutingMutex.unlock();
1218 }
1219 
clipboard_paste(int type,int scene,int entry)1220 void SurgeStorage::clipboard_paste(int type, int scene, int entry)
1221 {
1222     assert(scene < n_scenes);
1223     if (type != clipboard_type)
1224         return;
1225 
1226     int cgroup = -1;
1227     int cgroup_e = -1;
1228     int id = -1;
1229     int n = clipboard_p.size();
1230     int start = 0;
1231 
1232     if (!n)
1233         return;
1234 
1235     switch (type)
1236     {
1237     case cp_osc:
1238         cgroup = 2;
1239         cgroup_e = entry;
1240         id = getPatch().scene[scene].osc[entry].type.id; // first parameter id
1241         getPatch().scene[scene].osc[entry].type.val.i = clipboard_p[0].val.i;
1242         start = 1;
1243         getPatch().update_controls(false, &getPatch().scene[scene].osc[entry]);
1244 
1245         memcpy(&getPatch().scene[scene].osc[entry].extraConfig, &clipboard_extraconfig[0],
1246                sizeof(OscillatorStorage::ExtraConfigurationData));
1247         break;
1248     case cp_lfo:
1249         cgroup = 6;
1250         cgroup_e = entry + ms_lfo1;
1251         id = getPatch().scene[scene].lfo[entry].shape.id;
1252         break;
1253     case cp_scene:
1254     {
1255         id = getPatch().scene[scene].octave.id;
1256 
1257         for (int i = 0; i < n_lfos; i++)
1258         {
1259             memcpy(&getPatch().stepsequences[scene][i], &clipboard_stepsequences[i],
1260                    sizeof(StepSequencerStorage));
1261             getPatch().msegs[scene][i] = clipboard_msegs[i];
1262         }
1263 
1264         for (int i = 0; i < n_oscs; i++)
1265         {
1266             memcpy(&getPatch().scene[scene].osc[i].extraConfig, &clipboard_extraconfig[i],
1267                    sizeof(OscillatorStorage::ExtraConfigurationData));
1268             getPatch().scene[scene].osc[i].wt.Copy(&clipboard_wt[i]);
1269             strncpy(getPatch().scene[scene].osc[i].wavetable_display_name, clipboard_wt_names[i],
1270                     256);
1271         }
1272 
1273         getPatch().scene[scene].monoVoicePriorityMode = clipboard_primode;
1274     }
1275     break;
1276     default:
1277         return;
1278     }
1279 
1280     modRoutingMutex.lock();
1281     {
1282 
1283         for (int i = start; i < n; i++)
1284         {
1285             Parameter p = clipboard_p[i];
1286             int pid = p.id + id;
1287             getPatch().param_ptr[pid]->val.i = p.val.i;
1288             getPatch().param_ptr[pid]->temposync = p.temposync;
1289             getPatch().param_ptr[pid]->extend_range = p.extend_range;
1290             getPatch().param_ptr[pid]->deactivated = p.deactivated;
1291             getPatch().param_ptr[pid]->porta_constrate = p.porta_constrate;
1292             getPatch().param_ptr[pid]->porta_gliss = p.porta_gliss;
1293             getPatch().param_ptr[pid]->porta_retrigger = p.porta_retrigger;
1294             getPatch().param_ptr[pid]->porta_curve = p.porta_curve;
1295             getPatch().param_ptr[pid]->deform_type = p.deform_type;
1296         }
1297 
1298         switch (type)
1299         {
1300         case cp_osc:
1301         {
1302             if (uses_wavetabledata(getPatch().scene[scene].osc[entry].type.val.i))
1303             {
1304                 getPatch().scene[scene].osc[entry].wt.Copy(&clipboard_wt[0]);
1305                 strncpy(getPatch().scene[scene].osc[entry].wavetable_display_name,
1306                         clipboard_wt_names[0], 256);
1307             }
1308 
1309             // copy modroutings
1310             n = clipboard_modulation_voice.size();
1311             for (int i = 0; i < n; i++)
1312             {
1313                 ModulationRouting m;
1314                 m.source_id = clipboard_modulation_voice[i].source_id;
1315                 m.depth = clipboard_modulation_voice[i].depth;
1316                 m.destination_id =
1317                     clipboard_modulation_voice[i].destination_id + id - n_global_params;
1318                 getPatch().scene[scene].modulation_voice.push_back(m);
1319             }
1320             n = clipboard_modulation_scene.size();
1321             for (int i = 0; i < n; i++)
1322             {
1323                 ModulationRouting m;
1324                 m.source_id = clipboard_modulation_scene[i].source_id;
1325                 m.depth = clipboard_modulation_scene[i].depth;
1326                 m.destination_id =
1327                     clipboard_modulation_scene[i].destination_id + id - n_global_params;
1328                 getPatch().scene[scene].modulation_scene.push_back(m);
1329             }
1330         }
1331         break;
1332         case cp_lfo:
1333             if (getPatch().scene[scene].lfo[entry].shape.val.i == lt_stepseq)
1334                 memcpy(&getPatch().stepsequences[scene][entry], &clipboard_stepsequences[0],
1335                        sizeof(StepSequencerStorage));
1336             if (getPatch().scene[scene].lfo[entry].shape.val.i == lt_mseg)
1337                 getPatch().msegs[scene][entry] = clipboard_msegs[0];
1338 
1339             break;
1340         case cp_scene:
1341         {
1342             getPatch().scene[scene].modulation_voice.clear();
1343             getPatch().scene[scene].modulation_scene.clear();
1344             getPatch().update_controls(false);
1345 
1346             n = clipboard_modulation_voice.size();
1347             for (int i = 0; i < n; i++)
1348             {
1349                 ModulationRouting m;
1350                 m.source_id = clipboard_modulation_voice[i].source_id;
1351                 m.depth = clipboard_modulation_voice[i].depth;
1352                 m.destination_id = clipboard_modulation_voice[i].destination_id;
1353                 getPatch().scene[scene].modulation_voice.push_back(m);
1354             }
1355             n = clipboard_modulation_scene.size();
1356             for (int i = 0; i < n; i++)
1357             {
1358                 ModulationRouting m;
1359                 m.source_id = clipboard_modulation_scene[i].source_id;
1360                 m.depth = clipboard_modulation_scene[i].depth;
1361                 m.destination_id = clipboard_modulation_scene[i].destination_id;
1362                 getPatch().scene[scene].modulation_scene.push_back(m);
1363             }
1364         }
1365         }
1366     }
1367 
1368     modRoutingMutex.unlock();
1369 }
1370 
getSnapshotSection(const char * name)1371 TiXmlElement *SurgeStorage::getSnapshotSection(const char *name)
1372 {
1373     TiXmlElement *e = TINYXML_SAFE_TO_ELEMENT(snapshotloader.FirstChild(name));
1374     if (e)
1375         return e;
1376 
1377     // ok, create a new one then
1378     TiXmlElement ne(name);
1379     snapshotloader.InsertEndChild(ne);
1380     return TINYXML_SAFE_TO_ELEMENT(snapshotloader.FirstChild(name));
1381 }
1382 
save_snapshots()1383 void SurgeStorage::save_snapshots() { snapshotloader.SaveFile(); }
1384 
write_midi_controllers_to_user_default()1385 void SurgeStorage::write_midi_controllers_to_user_default()
1386 {
1387     TiXmlDocument doc;
1388 
1389     TiXmlElement root("midiconfig");
1390     TiXmlElement mc("midictrl");
1391 
1392     int n = n_global_params + n_scene_params; // only store midictrl's for scene A (scene A -> scene
1393                                               // B will be duplicated on load)
1394     for (int i = 0; i < n; i++)
1395     {
1396         if (getPatch().param_ptr[i]->midictrl >= 0)
1397         {
1398             TiXmlElement mc_e("entry");
1399             mc_e.SetAttribute("p", i);
1400             mc_e.SetAttribute("ctrl", getPatch().param_ptr[i]->midictrl);
1401             mc.InsertEndChild(mc_e);
1402         }
1403     }
1404     root.InsertEndChild(mc);
1405 
1406     TiXmlElement cc("customctrl");
1407 
1408     for (int i = 0; i < n_customcontrollers; i++)
1409     {
1410         TiXmlElement cc_e("entry");
1411         cc_e.SetAttribute("p", i);
1412         cc_e.SetAttribute("ctrl", controllers[i]);
1413         cc.InsertEndChild(cc_e);
1414     }
1415     root.InsertEndChild(cc);
1416     doc.InsertEndChild(root);
1417 
1418     try
1419     {
1420         auto dir = string_to_path(userDataPath);
1421         fs::create_directories(dir);
1422         auto f = string_to_path(userDataPath) / "SurgeMIDIDefaults.xml";
1423         doc.SaveFile(f);
1424     }
1425     catch (const fs::filesystem_error &e)
1426     {
1427         // Oh well.
1428     }
1429     // save_snapshots();
1430 }
1431 
setSamplerate(float sr)1432 void SurgeStorage::setSamplerate(float sr)
1433 {
1434     // If I am changing my sample rate I will change my internal tables, so this
1435     // needs to be tuning aware and reapply tuning if needed
1436     auto s = currentScale;
1437     bool wasST = isStandardTuning;
1438 
1439     samplerate = sr;
1440     dsamplerate = sr;
1441     samplerate_inv = 1.0 / sr;
1442     dsamplerate_inv = 1.0 / sr;
1443     dsamplerate_os = dsamplerate * OSC_OVERSAMPLING;
1444     dsamplerate_os_inv = 1.0 / dsamplerate_os;
1445     init_tables();
1446 
1447     if (!wasST)
1448     {
1449         retuneToScale(s);
1450     }
1451 }
1452 
load_midi_controllers()1453 void SurgeStorage::load_midi_controllers()
1454 {
1455     auto mcp = string_to_path(userDataPath) / "SurgeMIDIDefaults.xml";
1456     TiXmlDocument mcd;
1457     TiXmlElement *midiRoot = nullptr;
1458     if (mcd.LoadFile(mcp))
1459     {
1460         midiRoot = mcd.FirstChildElement("midiconfig");
1461     }
1462 
1463     auto get = [this, midiRoot](const char *n) {
1464         if (midiRoot)
1465         {
1466             auto q = TINYXML_SAFE_TO_ELEMENT(midiRoot->FirstChild(n));
1467             if (q)
1468                 return q;
1469         }
1470         return getSnapshotSection(n);
1471     };
1472 
1473     TiXmlElement *mc = get("midictrl");
1474     assert(mc);
1475 
1476     TiXmlElement *entry = TINYXML_SAFE_TO_ELEMENT(mc->FirstChild("entry"));
1477     while (entry)
1478     {
1479         int id, ctrl;
1480         if ((entry->QueryIntAttribute("p", &id) == TIXML_SUCCESS) &&
1481             (entry->QueryIntAttribute("ctrl", &ctrl) == TIXML_SUCCESS))
1482         {
1483             getPatch().param_ptr[id]->midictrl = ctrl;
1484             if (id >= n_global_params)
1485                 getPatch().param_ptr[id + n_scene_params]->midictrl = ctrl;
1486         }
1487         entry = TINYXML_SAFE_TO_ELEMENT(entry->NextSibling("entry"));
1488     }
1489 
1490     TiXmlElement *cc = get("customctrl");
1491     assert(cc);
1492 
1493     entry = TINYXML_SAFE_TO_ELEMENT(cc->FirstChild("entry"));
1494     while (entry)
1495     {
1496         int id, ctrl;
1497         if ((entry->QueryIntAttribute("p", &id) == TIXML_SUCCESS) &&
1498             (entry->QueryIntAttribute("ctrl", &ctrl) == TIXML_SUCCESS) &&
1499             (id < n_customcontrollers))
1500         {
1501             controllers[id] = ctrl;
1502         }
1503         entry = TINYXML_SAFE_TO_ELEMENT(entry->NextSibling("entry"));
1504     }
1505 }
1506 
~SurgeStorage()1507 SurgeStorage::~SurgeStorage() { deinitialize_oddsound(); }
1508 
shafted_tanh(double x)1509 double shafted_tanh(double x) { return (exp(x) - exp(-x * 1.2)) / (exp(x) + exp(-x)); }
1510 
init_tables()1511 void SurgeStorage::init_tables()
1512 {
1513     isStandardTuning = true;
1514     float db60 = powf(10.f, 0.05f * -60.f);
1515     float _512th = 1.f / 512.f;
1516 
1517     for (int i = 0; i < tuning_table_size; i++)
1518     {
1519         table_dB[i] = powf(10.f, 0.05f * ((float)i - 384.f));
1520         table_pitch[i] = powf(2.f, ((float)i - 256.f) * (1.f / 12.f));
1521         table_pitch_ignoring_tuning[i] = table_pitch[i];
1522         table_pitch_inv[i] = 1.f / table_pitch[i];
1523         table_pitch_inv_ignoring_tuning[i] = table_pitch_inv[i];
1524         table_note_omega[0][i] =
1525             (float)sin(2 * M_PI * min(0.5, 440 * table_pitch[i] * dsamplerate_os_inv));
1526         table_note_omega[1][i] =
1527             (float)cos(2 * M_PI * min(0.5, 440 * table_pitch[i] * dsamplerate_os_inv));
1528         table_note_omega_ignoring_tuning[0][i] = table_note_omega[0][i];
1529         table_note_omega_ignoring_tuning[1][i] = table_note_omega[1][i];
1530         double k = dsamplerate_os * pow(2.0, (((double)i - 256.0) / 16.0)) / (double)BLOCK_SIZE_OS;
1531         table_envrate_linear[i] = (float)(1.f / k);
1532         table_envrate_lpf[i] = (float)(1.f - exp(log(db60) / k));
1533         table_glide_log[i] = log2(1.0 + (i * _512th * 10.f)) / log2(1.f + 10.f);
1534         table_glide_exp[511 - i] = 1.0 - table_glide_log[i];
1535     }
1536 
1537     for (int i = 0; i < 1001; ++i)
1538     {
1539         double twelths = i * 1.0 / 12.0 / 1000.0;
1540         table_two_to_the[i] = pow(2.0, twelths);
1541         table_two_to_the_minus[i] = pow(2.0, -twelths);
1542     }
1543 
1544     double mult = 1.0 / 32.0;
1545     for (int i = 0; i < 1024; i++)
1546     {
1547         double x = ((double)i - 512.0) * mult;
1548 
1549         waveshapers[wst_soft][i] = (float)tanh(x);
1550         waveshapers[wst_hard][i] = (float)pow(tanh(pow(::abs(x), 5.0)), 0.2);
1551         if (x < 0)
1552             waveshapers[wst_hard][i] = -waveshapers[wst_hard][i];
1553         waveshapers[wst_asym][i] = (float)shafted_tanh(x + 0.5) - shafted_tanh(0.5);
1554         waveshapers[wst_sine][i] = (float)sin((double)((double)i - 512.0) * M_PI / 512.0);
1555         waveshapers[wst_digital][i] = (float)tanh(x);
1556     }
1557 
1558     // from 1.2.2
1559     // nyquist_pitch = (float)12.f*log((0.49999*M_PI) / (dsamplerate_os_inv */
1560     // 2*M_PI*440.0))/log(2.0);	// include some margin for error (and to avoid denormals in IIR
1561     // filter clamping)
1562     // 1.3
1563     nyquist_pitch =
1564         (float)12.f * log((0.75 * M_PI) / (dsamplerate_os_inv * 2 * M_PI * 440.0)) /
1565         log(2.0); // include some margin for error (and to avoid denormals in IIR filter clamping)
1566     vu_falloff =
1567         0.997f; // TODO should be sample rate-dependent (this is per 32-sample block at 44.1k)
1568 }
1569 
note_to_pitch(float x)1570 float SurgeStorage::note_to_pitch(float x)
1571 {
1572 
1573     if (tuningTableIs12TET())
1574     {
1575         return note_to_pitch_ignoring_tuning(x);
1576     }
1577     else
1578     {
1579         x = limit_range(x + 256, 0.f, tuning_table_size - (float)1.e-4);
1580         int e = (int)x;
1581         float a = x - (float)e;
1582 
1583         if (e > 0x1fe)
1584             e = 0x1fe;
1585 
1586         return (1 - a) * table_pitch[e & 0x1ff] + a * table_pitch[(e + 1) & 0x1ff];
1587     }
1588 }
1589 
note_to_pitch_inv(float x)1590 float SurgeStorage::note_to_pitch_inv(float x)
1591 {
1592     if (tuningTableIs12TET())
1593     {
1594         return note_to_pitch_inv_ignoring_tuning(x);
1595     }
1596     else
1597     {
1598         x = limit_range(x + 256, 0.f, tuning_table_size - (float)1.e-4);
1599         // x += 256;
1600         int e = (int)x;
1601         float a = x - (float)e;
1602 
1603         if (e > 0x1fe)
1604             e = 0x1fe;
1605 
1606         return (1 - a) * table_pitch_inv[e & 0x1ff] + a * table_pitch_inv[(e + 1) & 0x1ff];
1607     }
1608 }
1609 
note_to_pitch_ignoring_tuning(float x)1610 float SurgeStorage::note_to_pitch_ignoring_tuning(float x)
1611 {
1612     x = limit_range(x + 256, 0.f, tuning_table_size - (float)1.e-4);
1613     // x += 256;
1614     int e = (int)x;
1615     float a = x - (float)e;
1616 
1617     if (e > 0x1fe)
1618         e = 0x1fe;
1619 
1620     float pow2pos = a * 1000.0;
1621     int pow2idx = (int)pow2pos;
1622     float pow2frac = pow2pos - pow2idx;
1623     float pow2v =
1624         (1 - pow2frac) * table_two_to_the[pow2idx] + pow2frac * table_two_to_the[pow2idx + 1];
1625     return table_pitch_ignoring_tuning[e & 0x1ff] * pow2v;
1626 }
1627 
note_to_pitch_inv_ignoring_tuning(float x)1628 float SurgeStorage::note_to_pitch_inv_ignoring_tuning(float x)
1629 {
1630     x = limit_range(x + 256, 0.f, tuning_table_size - (float)1.e-4);
1631     // x += 256;
1632     int e = (int)x;
1633     float a = x - (float)e;
1634 
1635     if (e > 0x1fe)
1636         e = 0x1fe;
1637 
1638     float pow2pos = a * 1000.0;
1639     int pow2idx = (int)pow2pos;
1640     float pow2frac = pow2pos - pow2idx;
1641     float pow2v = (1 - pow2frac) * table_two_to_the_minus[pow2idx] +
1642                   pow2frac * table_two_to_the_minus[pow2idx + 1];
1643     return table_pitch_inv_ignoring_tuning[e & 0x1ff] * pow2v;
1644 }
1645 
note_to_omega(float x,float & sinu,float & cosi)1646 void SurgeStorage::note_to_omega(float x, float &sinu, float &cosi)
1647 {
1648     x = limit_range(x + 256, 0.f, tuning_table_size - (float)1.e-4);
1649     // x += 256;
1650     int e = (int)x;
1651     float a = x - (float)e;
1652 
1653     if (e > 0x1fe)
1654         e = 0x1fe;
1655     else if (e < 0)
1656         e = 0;
1657 
1658     sinu = (1 - a) * table_note_omega[0][e & 0x1ff] + a * table_note_omega[0][(e + 1) & 0x1ff];
1659     cosi = (1 - a) * table_note_omega[1][e & 0x1ff] + a * table_note_omega[1][(e + 1) & 0x1ff];
1660 }
1661 
note_to_omega_ignoring_tuning(float x,float & sinu,float & cosi)1662 void SurgeStorage::note_to_omega_ignoring_tuning(float x, float &sinu, float &cosi)
1663 {
1664     x = limit_range(x + 256, 0.f, tuning_table_size - (float)1.e-4);
1665     // x += 256;
1666     int e = (int)x;
1667     float a = x - (float)e;
1668 
1669     if (e > 0x1fe)
1670         e = 0x1fe;
1671     else if (e < 0)
1672         e = 0;
1673 
1674     sinu = (1 - a) * table_note_omega_ignoring_tuning[0][e & 0x1ff] +
1675            a * table_note_omega_ignoring_tuning[0][(e + 1) & 0x1ff];
1676     cosi = (1 - a) * table_note_omega_ignoring_tuning[1][e & 0x1ff] +
1677            a * table_note_omega_ignoring_tuning[1][(e + 1) & 0x1ff];
1678 }
1679 
db_to_linear(float x)1680 float db_to_linear(float x)
1681 {
1682     x += 384;
1683     int e = (int)x;
1684     float a = x - (float)e;
1685 
1686     return (1 - a) * table_dB[e & 0x1ff] + a * table_dB[(e + 1) & 0x1ff];
1687 }
1688 
lookup_waveshape(int entry,float x)1689 float lookup_waveshape(int entry, float x)
1690 {
1691     x *= 32.f;
1692     x += 512.f;
1693     int e = (int)x;
1694     float a = x - (float)e;
1695 
1696     if (e > 0x3fd)
1697         return 1;
1698     if (e < 1)
1699         return -1;
1700 
1701     return (1 - a) * waveshapers[entry][e & 0x3ff] + a * waveshapers[entry][(e + 1) & 0x3ff];
1702 }
1703 
lookup_waveshape_warp(int entry,float x)1704 float lookup_waveshape_warp(int entry, float x)
1705 {
1706     x *= 256.f;
1707     x += 512.f;
1708 
1709     int e = (int)x;
1710     float a = x - (float)e;
1711 
1712     return (1 - a) * waveshapers[entry][e & 0x3ff] + a * waveshapers[entry][(e + 1) & 0x3ff];
1713 }
1714 
envelope_rate_lpf(float x)1715 float envelope_rate_lpf(float x)
1716 {
1717     x *= 16.f;
1718     x += 256.f;
1719     int e = (int)x;
1720     float a = x - (float)e;
1721 
1722     return (1 - a) * table_envrate_lpf[e & 0x1ff] + a * table_envrate_lpf[(e + 1) & 0x1ff];
1723 }
1724 
envelope_rate_linear(float x)1725 float envelope_rate_linear(float x)
1726 {
1727     x *= 16.f;
1728     x += 256.f;
1729     int e = (int)x;
1730     float a = x - (float)e;
1731 
1732     return (1 - a) * table_envrate_linear[e & 0x1ff] + a * table_envrate_linear[(e + 1) & 0x1ff];
1733 }
1734 
envelope_rate_linear_nowrap(float x)1735 float envelope_rate_linear_nowrap(float x)
1736 {
1737     x *= 16.f;
1738     x += 256.f;
1739     int e = limit_range((int)x, 0, 0x1ff - 1);
1740     ;
1741     float a = x - (float)e;
1742 
1743     return (1 - a) * table_envrate_linear[e & 0x1ff] + a * table_envrate_linear[(e + 1) & 0x1ff];
1744 }
1745 
1746 // this function is only valid for x = {0, 1}
glide_exp(float x)1747 float glide_exp(float x)
1748 {
1749     x *= 511.f;
1750     int e = (int)x;
1751     float a = x - (float)e;
1752 
1753     return (1 - a) * table_glide_exp[e & 0x1ff] + a * table_glide_exp[(e + 1) & 0x1ff];
1754 }
1755 
1756 // this function is only valid for x = {0, 1}
glide_log(float x)1757 float glide_log(float x)
1758 {
1759     x *= 511.f;
1760     int e = (int)x;
1761     float a = x - (float)e;
1762 
1763     return (1 - a) * table_glide_log[e & 0x1ff] + a * table_glide_log[(e + 1) & 0x1ff];
1764 }
1765 
resetToCurrentScaleAndMapping()1766 bool SurgeStorage::resetToCurrentScaleAndMapping()
1767 {
1768     currentTuning = Tunings::Tuning(currentScale, currentMapping);
1769 
1770     auto t = currentTuning;
1771 
1772     if (tuningApplicationMode == RETUNE_MIDI_ONLY)
1773     {
1774         tuningPitch = 32.0;
1775         tuningPitchInv = 1.0 / 32.0;
1776         t = twelveToneStandardMapping;
1777     }
1778     else
1779     {
1780         tuningPitch = currentMapping.tuningFrequency / Tunings::MIDI_0_FREQ;
1781         tuningPitchInv = 1.0 / tuningPitch;
1782     }
1783 
1784     for (int i = 0; i < 512; ++i)
1785     {
1786         table_pitch[i] = t.frequencyForMidiNoteScaledByMidi0(i - 256);
1787         table_pitch_inv[i] = 1.f / table_pitch[i];
1788         table_note_omega[0][i] =
1789             (float)sin(2 * M_PI * min(0.5, 440 * table_pitch[i] * dsamplerate_os_inv));
1790         table_note_omega[1][i] =
1791             (float)cos(2 * M_PI * min(0.5, 440 * table_pitch[i] * dsamplerate_os_inv));
1792     }
1793     return true;
1794 }
1795 
setTuningApplicationMode(const TuningApplicationMode m)1796 void SurgeStorage::setTuningApplicationMode(const TuningApplicationMode m)
1797 {
1798     tuningApplicationMode = m;
1799     resetToCurrentScaleAndMapping();
1800 }
1801 
1802 bool SurgeStorage::skipLoadWtAndPatch = false;
1803 
rescanUserMidiMappings()1804 void SurgeStorage::rescanUserMidiMappings()
1805 {
1806     userMidiMappingsXMLByName.clear();
1807     std::error_code ec;
1808     const auto extension{fs::path{".srgmid"}.native()};
1809     try
1810     {
1811         for (const fs::path &d : fs::directory_iterator{string_to_path(userMidiMappingsPath), ec})
1812         {
1813             if (d.extension().native() == extension)
1814             {
1815                 TiXmlDocument doc;
1816                 if (!doc.LoadFile(d))
1817                     continue;
1818                 const auto r{TINYXML_SAFE_TO_ELEMENT(doc.FirstChild("surge-midi"))};
1819                 if (!r)
1820                     continue;
1821                 const auto a{r->Attribute("name")};
1822                 if (!a)
1823                     continue;
1824                 userMidiMappingsXMLByName.emplace(a, std::move(doc));
1825             }
1826         }
1827     }
1828     catch (const fs::filesystem_error &e)
1829     {
1830         std::ostringstream oss;
1831         oss << "Experienced file system error when loading MIDI settings. " << e.what();
1832         Surge::UserInteractions::promptError(oss.str(), "FileSystem Error");
1833     }
1834 }
1835 
loadMidiMappingByName(std::string name)1836 void SurgeStorage::loadMidiMappingByName(std::string name)
1837 {
1838     if (userMidiMappingsXMLByName.find(name) == userMidiMappingsXMLByName.end())
1839     {
1840         // FIXME - why would this ever happen? Probably show an error
1841         return;
1842     }
1843 
1844     auto doc = userMidiMappingsXMLByName[name];
1845     auto sm = TINYXML_SAFE_TO_ELEMENT(doc.FirstChild("surge-midi"));
1846     // We can do revisio nstuff here later if we need to
1847     if (!sm)
1848     {
1849         // Invalid XML Document. Show an error?
1850         Surge::UserInteractions::promptError(
1851             "Unable to locate surge-midi element in XML. Not a valid MIDI mapping!", "Surge MIDI");
1852         return;
1853     }
1854 
1855     auto mc = TINYXML_SAFE_TO_ELEMENT(sm->FirstChild("midictrl"));
1856 
1857     if (mc)
1858     {
1859         // Clear the current control mapping
1860         for (int i = 0; i < n_total_params; i++)
1861         {
1862             getPatch().param_ptr[i]->midictrl = -1;
1863         }
1864 
1865         // Apply the new control mapping
1866 
1867         auto map = mc->FirstChildElement("map");
1868         while (map)
1869         {
1870             int i, c;
1871             if (map->QueryIntAttribute("p", &i) == TIXML_SUCCESS &&
1872                 map->QueryIntAttribute("cc", &c) == TIXML_SUCCESS)
1873             {
1874                 getPatch().param_ptr[i]->midictrl = c;
1875                 if (i >= n_global_params)
1876                 {
1877                     getPatch().param_ptr[i + n_scene_params]->midictrl = c;
1878                 }
1879             }
1880             map = map->NextSiblingElement("map");
1881         }
1882     }
1883 
1884     auto cc = TINYXML_SAFE_TO_ELEMENT(sm->FirstChild("customctrl"));
1885     if (cc)
1886     {
1887         auto ctrl = cc->FirstChildElement("ctrl");
1888         while (ctrl)
1889         {
1890             int i, cc;
1891             if (ctrl->QueryIntAttribute("i", &i) == TIXML_SUCCESS &&
1892                 ctrl->QueryIntAttribute("cc", &cc) == TIXML_SUCCESS)
1893             {
1894                 controllers[i] = cc;
1895             }
1896             ctrl = ctrl->NextSiblingElement("ctrl");
1897         }
1898     }
1899 }
1900 
storeMidiMappingToName(std::string name)1901 void SurgeStorage::storeMidiMappingToName(std::string name)
1902 {
1903     TiXmlDocument doc;
1904     TiXmlElement sm("surge-midi");
1905     sm.SetAttribute("revision", ff_revision);
1906     sm.SetAttribute("name", name);
1907 
1908     // Build the XML here
1909     int n = n_global_params + n_scene_params; // only store midictrl's for scene A (scene A -> scene
1910                                               // B will be duplicated on load)
1911     TiXmlElement mc("midictrl");
1912     for (int i = 0; i < n; i++)
1913     {
1914         if (getPatch().param_ptr[i]->midictrl >= 0)
1915         {
1916             TiXmlElement p("map");
1917             p.SetAttribute("p", i);
1918             p.SetAttribute("cc", getPatch().param_ptr[i]->midictrl);
1919             mc.InsertEndChild(p);
1920         }
1921     }
1922     sm.InsertEndChild(mc);
1923 
1924     TiXmlElement cc("customctrl");
1925     for (int i = 0; i < n_customcontrollers; ++i)
1926     {
1927         TiXmlElement p("ctrl");
1928         p.SetAttribute("i", i);
1929         p.SetAttribute("cc", controllers[i]);
1930         cc.InsertEndChild(p);
1931     }
1932     sm.InsertEndChild(cc);
1933 
1934     doc.InsertEndChild(sm);
1935 
1936     fs::create_directories(string_to_path(userMidiMappingsPath));
1937     std::string fn = Surge::Storage::appendDirectory(userMidiMappingsPath, name + ".srgmid");
1938 
1939     if (!doc.SaveFile(string_to_path(fn)))
1940     {
1941         std::ostringstream oss;
1942         oss << "Unable to save MIDI settings to '" << fn << "'!";
1943         Surge::UserInteractions::promptError(oss.str(), "Error");
1944     }
1945 }
1946 
initialize_oddsound()1947 void SurgeStorage::initialize_oddsound()
1948 {
1949     if (oddsound_mts_client)
1950     {
1951         deinitialize_oddsound();
1952     }
1953     oddsound_mts_client = MTS_RegisterClient();
1954     if (oddsound_mts_client)
1955     {
1956         oddsound_mts_active = MTS_HasMaster(oddsound_mts_client);
1957     }
1958 }
1959 
deinitialize_oddsound()1960 void SurgeStorage::deinitialize_oddsound()
1961 {
1962     if (oddsound_mts_client)
1963     {
1964         MTS_DeregisterClient(oddsound_mts_client);
1965     }
1966     oddsound_mts_client = nullptr;
1967     oddsound_mts_active = false;
1968 }
1969 
toggleTuningToCache()1970 void SurgeStorage::toggleTuningToCache()
1971 {
1972     if (isToggledToCache)
1973     {
1974         currentScale = cachedToggleOffScale;
1975         currentMapping = cachedToggleOffMapping;
1976 
1977         isStandardTuning = togglePriorState[0];
1978         isStandardScale = togglePriorState[1];
1979         isStandardMapping = togglePriorState[2];
1980 
1981         resetToCurrentScaleAndMapping();
1982         isToggledToCache = false;
1983     }
1984     else
1985     {
1986         cachedToggleOffScale = currentScale;
1987         cachedToggleOffMapping = currentMapping;
1988         togglePriorState[0] = isStandardTuning;
1989         togglePriorState[1] = isStandardScale;
1990         togglePriorState[2] = isStandardMapping;
1991 
1992         retuneTo12TETScaleC261Mapping();
1993         isToggledToCache = true;
1994     }
1995 }
1996 
1997 namespace Surge
1998 {
1999 namespace Storage
2000 {
isValidName(const std::string & patchName)2001 bool isValidName(const std::string &patchName)
2002 {
2003     bool valid = false;
2004 
2005     // No need to validate size separately as an empty string won't have visible characters.
2006     for (const char &c : patchName)
2007         if (std::isalnum(c) || std::ispunct(c))
2008             valid = true;
2009         else if (c != ' ')
2010             return false;
2011 
2012     return valid;
2013 }
2014 
isValidUTF8(const std::string & testThis)2015 bool isValidUTF8(const std::string &testThis)
2016 {
2017     int pos = 0;
2018     const char *data = testThis.c_str();
2019 
2020     // https://helloacm.com/how-to-validate-utf-8-encoding-the-simple-utf-8-validation-algorithm/
2021     // Valid UTF8 has a specific binary format. If it's a single byte UTF8 character, then it is
2022     // always of form '0xxxxxxx', where 'x' is any binary digit. If it's a two byte UTF8 character,
2023     // then it's always of form '110xxxxx10xxxxxx'. Similarly for three and four byte UTF8
2024     // characters it starts with '1110xxxx' and '11110xxx' followed by '10xxxxxx' one less times as
2025     // there are bytes. This tool will locate mistakes in the encoding and tell you where they
2026     // occured.
2027 
2028     //    https://helloacm.com/how-to-validate-utf-8-encoding-the-simple-utf-8-validation-algorithm/
2029 
2030     auto is10x = [](int a) {
2031         int bit1 = (a >> 7) & 1;
2032         int bit2 = (a >> 6) & 1;
2033         return (bit1 == 1) && (bit2 == 0);
2034     };
2035     size_t dsz = testThis.size();
2036     for (int i = 0; i < dsz; ++i)
2037     {
2038         // 0xxxxxxx
2039         int bit1 = (data[i] >> 7) & 1;
2040         if (bit1 == 0)
2041             continue;
2042         // 110xxxxx 10xxxxxx
2043         int bit2 = (data[i] >> 6) & 1;
2044         if (bit2 == 0)
2045             return false;
2046         // 11
2047         int bit3 = (data[i] >> 5) & 1;
2048         if (bit3 == 0)
2049         {
2050             // 110xxxxx 10xxxxxx
2051             if ((++i) < dsz)
2052             {
2053                 if (is10x(data[i]))
2054                 {
2055                     continue;
2056                 }
2057                 return false;
2058             }
2059             else
2060             {
2061                 return false;
2062             }
2063         }
2064         int bit4 = (data[i] >> 4) & 1;
2065         if (bit4 == 0)
2066         {
2067             // 1110xxxx 10xxxxxx 10xxxxxx
2068             if (i + 2 < dsz)
2069             {
2070                 if (is10x(data[i + 1]) && is10x(data[i + 2]))
2071                 {
2072                     i += 2;
2073                     continue;
2074                 }
2075                 return false;
2076             }
2077             else
2078             {
2079                 return false;
2080             }
2081         }
2082         int bit5 = (data[i] >> 3) & 1;
2083         if (bit5 == 1)
2084             return false;
2085         if (i + 3 < dsz)
2086         {
2087             if (is10x(data[i + 1]) && is10x(data[i + 2]) && is10x(data[i + 3]))
2088             {
2089                 i += 3;
2090                 continue;
2091             }
2092             return false;
2093         }
2094         else
2095         {
2096             return false;
2097         }
2098     }
2099     return true;
2100 }
2101 
findReplaceSubstring(string & source,const string & from,const string & to)2102 string findReplaceSubstring(string &source, const string &from, const string &to)
2103 {
2104     string newString;
2105     newString.reserve(source.length()); // avoids a few memory allocations
2106 
2107     string::size_type lastPos = 0;
2108     string::size_type findPos;
2109 
2110     while (string::npos != (findPos = source.find(from, lastPos)))
2111     {
2112         newString.append(source, lastPos, findPos - lastPos);
2113         newString += to;
2114         lastPos = findPos + from.length();
2115     }
2116 
2117     // care for the rest after last occurrence
2118     newString += source.substr(lastPos);
2119 
2120     source.swap(newString);
2121 
2122     return newString;
2123 }
2124 
appendDirectory(const std::string & root,const std::string & path1)2125 std::string appendDirectory(const std::string &root, const std::string &path1)
2126 {
2127     if (root[root.size() - 1] == PATH_SEPARATOR)
2128         return root + path1;
2129     else
2130         return root + PATH_SEPARATOR + path1;
2131 }
appendDirectory(const std::string & root,const std::string & path1,const std::string & path2)2132 std::string appendDirectory(const std::string &root, const std::string &path1,
2133                             const std::string &path2)
2134 {
2135     return appendDirectory(appendDirectory(root, path1), path2);
2136 }
2137 
2138 } // namespace Storage
2139 } // namespace Surge
2140