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