diff --git a/CMakeLists.txt b/CMakeLists.txt index bf514be8faf..f203fed8bd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ juce_add_binary_data(surge-shared-binary HEADER_NAME SurgeCoreBinary.h SOURCES resources/data/configuration.xml + resources/surge-xt/README_UserArea.txt resources/data/windows.wt resources/data/paramdocumentation.xml) set_target_properties(surge-shared-binary PROPERTIES diff --git a/resources/surge-xt/README_UserArea.txt b/resources/surge-xt/README_UserArea.txt new file mode 100644 index 00000000000..386bd2560af --- /dev/null +++ b/resources/surge-xt/README_UserArea.txt @@ -0,0 +1,17 @@ +This is your Surge XT user area! + +With Surge XT, we are using a more structured user area which +separates the storage of patches, wavetables, FX settings, +MIDI settings, and modulator settings. Each of these types of +assets now have a parent level directory. + +Most importantly, you can read the Surge manual at: + +https://surge-synthesizer.github.io/ + +but if you want to import patches, drag them into the Patches +directory, and so on. + +Before we release Surge XT, we will also add some sort of rudimentary +migration assistant. Until then, you are kinda on your own. +Hop on Discord if you get stuck during the alpha and early beta stage! diff --git a/src/common/PatchDB.cpp b/src/common/PatchDB.cpp index 7f26ddfda3e..50bb3e4f114 100644 --- a/src/common/PatchDB.cpp +++ b/src/common/PatchDB.cpp @@ -175,7 +175,7 @@ struct TxnGuard struct PatchDB::workerS { - static constexpr const char *schema_version = "3"; // I will rebuild if this is not my verion + static constexpr const char *schema_version = "4"; // I will rebuild if this is not my verion /* * Obviously a lot of thought needs to go into this @@ -265,7 +265,6 @@ CREATE TABLE Category ( explicit workerS(SurgeStorage *storage) : storage(storage) { - fs::create_directories(string_to_path(storage->userDataPath)); auto dbname = storage->userDataPath + "/SurgePatches.db"; auto flag = SQLITE_OPEN_FULLMUTEX; // basically lock flag |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; diff --git a/src/common/SurgeStorage.cpp b/src/common/SurgeStorage.cpp index dfbbfa5bfef..c3baf8ab534 100644 --- a/src/common/SurgeStorage.cpp +++ b/src/common/SurgeStorage.cpp @@ -268,15 +268,15 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) if (lstat == 0 && (linfo.st_mtime > rinfo.st_mtime || rstat != 0)) datapath = localpath; // use the local else - datapath = rootpath; // else use the root. If both are missing we will blow up later. + datapath = rootpath; // else use the root. If both are missing we will blow up later } else { datapath = suppliedDataPath; } - // ~/Documents/Surge in full name - sprintf(path, "%s/Documents/Surge", homePath); + // ~/Documents/Surge XT in full name + sprintf(path, "%s/Documents/Surge XT", homePath); userDataPath = path; #elif LINUX if (!hasSuppliedDataPath) @@ -326,8 +326,8 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) if (buildOverrideDataPath) { datapath = std::string(buildOverrideDataPath); - std::cout << "WARNING: Surge Overriding DataPath to " << datapath << std::endl; - std::cout << " Only use this in build pipelines please" << std::endl; + std::cout << "WARNING: Surge overriding data path to " << datapath << std::endl; + std::cout << " Only use this in build pipelines please!" << std::endl; } } else @@ -337,15 +337,15 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) /* ** See the discussion in github issue #930. Basically - ** if ~/Documents/Surge exists use that - ** else if ~/.Surge exists use that - ** else if ~/.Documents exists, use ~/Documents/Surge - ** else use ~/.Surge + ** if ~/Documents/Surge XT exists use that + ** else if ~/.Surge XT exists use that + ** else if ~/.Documents exists, use ~/Documents/Surge XT + ** else use ~/.Surge XT ** Compensating for whether your distro makes you a ~/Documents or not */ - std::string documentsSurge = std::string(homePath) + "/Documents/Surge"; - std::string dotSurge = std::string(homePath) + "/.Surge"; + std::string documentsSurge = std::string(homePath) + "/Documents/Surge XT"; + std::string dotSurge = std::string(homePath) + "/.Surge XT"; std::string documents = std::string(homePath) + "/Documents/"; if (fs::is_directory(string_to_path(documentsSurge))) @@ -364,8 +364,8 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) { userDataPath = dotSurge; } - // std::cout << "DataPath is " << datapath << std::endl; - // std::cout << "UserDataPath is " << userDataPath << std::endl; + // std::cout << "Data path is " << datapath << std::endl; + // std::cout << "User data path is " << userDataPath << std::endl; #elif WINDOWS #if TARGET_RACK @@ -435,10 +435,10 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) } } - // Portable - first check for dllPath\\SurgeUserData - if (!dllPath.empty() && fs::is_directory(dllPath / L"SurgeUserData")) + // Portable - first check for dllPath\\SurgeXTUserData + if (!dllPath.empty() && fs::is_directory(dllPath / L"SurgeXTUserData")) { - userDataPath = path_to_string(dllPath / L"SurgeUserData"); + userDataPath = path_to_string(dllPath / L"SurgeXTUserData"); } else { @@ -446,7 +446,7 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) if (!SHGetKnownFolderPath(FOLDERID_Documents, 0, nullptr, &documentsFolder)) { fs::path path(documentsFolder); - path /= L"Surge"; + path /= L"Surge XT"; userDataPath = path_to_string(path); } } @@ -464,10 +464,14 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) // append separator if not present datapath = Surge::Storage::appendDirectory(datapath, std::string()); - - userFXPath = Surge::Storage::appendDirectory(userDataPath, "FXSettings"); - - userMidiMappingsPath = Surge::Storage::appendDirectory(userDataPath, "MIDIMappings"); + userPatchesPath = Surge::Storage::appendDirectory(userDataPath, "Patches"); + userWavetablesPath = Surge::Storage::appendDirectory(userDataPath, "Wavetables"); + userWavetablesExportPath = Surge::Storage::appendDirectory(userWavetablesPath, "Exported"); + userFXPath = Surge::Storage::appendDirectory(userDataPath, "FX Presets"); + userMidiMappingsPath = Surge::Storage::appendDirectory(userDataPath, "MIDI Mappings"); + userModulatorSettingsPath = Surge::Storage::appendDirectory(userDataPath, "Modulator Presets"); + userSkinsPath = Surge::Storage::appendDirectory(userDataPath, "Skins"); + createUserDirectory(); /* const auto snapshotmenupath{string_to_path(datapath + "configuration.xml")}; @@ -487,8 +491,8 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) if (!snapshotloader.Parse(cxmlData.c_str())) { reportError("Cannot parse 'configuration.xml' in path '" + datapath + - "'. Please reinstall surge.", - "Surge is not properly installed."); + "'. Please reinstall Surge!", + "Surge Incorrectly Installed"); } load_midi_controllers(); @@ -508,16 +512,15 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) getPatch().scene[0].osc[0].wt.dt = 1.0f / 512.f; load_wt(0, &getPatch().scene[0].osc[0].wt, &getPatch().scene[0].osc[0]); - // WindowWT is a WaveTable which now has a constructor so don't do this + // WindowWT is a Wavetable which now has a constructor so don't do this // memset(&WindowWT, 0, sizeof(WindowWT)); if (loadWtAndPatch && !load_wt_wt_mem(SurgeCoreBinary::windows_wt, SurgeCoreBinary::windows_wtSize, &WindowWT)) { WindowWT.size = 0; std::ostringstream oss; - oss << "Unable to load 'windows.wt'. from memory. " - << "This is a usually fatal internal software error in Surge XT which should" - << " never occur!"; + oss << "Unable to load 'windows.wt' from memory. " + << "This is a fatal internal software error which should never occur!"; reportError(oss.str(), "Surge Resources Loading Error"); } @@ -652,6 +655,40 @@ SurgeStorage::SurgeStorage(std::string suppliedDataPath) : otherscene_clients(0) this, Surge::Storage::InitialPatchCategory, "Templates"); } +void SurgeStorage::createUserDirectory() +{ + auto p = string_to_path(userDataPath); + auto needToBuild = false; + if (!fs::is_directory(p)) + { + needToBuild = true; + } + + if (needToBuild) + { + try + { + for (auto &s : {userDataPath, userDefaultFilePath, userPatchesPath, userWavetablesPath, + userModulatorSettingsPath, userFXPath, userWavetablesExportPath, + userSkinsPath, userMidiMappingsPath}) + fs::create_directories(string_to_path(s)); + + auto rd = std::string(SurgeCoreBinary::README_UserArea_txt, + SurgeCoreBinary::README_UserArea_txtSize) + + "\n"; + auto of = + std::ofstream(string_to_path(userDataPath) / "README.txt", std::ofstream::out); + if (of.is_open()) + of << rd << std::endl; + of.close(); + } + catch (const fs::filesystem_error &e) + { + reportError(e.what(), "Unable to set up User Directory"); + } + } +} + void SurgeStorage::initializePatchDb() { patchDB = std::make_unique(this); @@ -715,7 +752,7 @@ void SurgeStorage::refresh_patchlist() refreshPatchlistAddDir(false, "patches_3rdparty"); firstUserCategory = patch_category.size(); - refreshPatchlistAddDir(true, ""); + refreshPatchlistAddDir(true, "Patches"); patchOrdering = std::vector(patch_list.size()); std::iota(patchOrdering.begin(), patchOrdering.end(), 0); @@ -945,7 +982,7 @@ void SurgeStorage::refresh_wtlist() firstThirdPartyWTCategory = wt_category.size(); refresh_wtlistAddDir(false, "wavetables_3rdparty"); firstUserWTCategory = wt_category.size(); - refresh_wtlistAddDir(true, ""); + refresh_wtlistAddDir(true, "Wavetables"); wtCategoryOrdering = std::vector(wt_category.size()); std::iota(wtCategoryOrdering.begin(), wtCategoryOrdering.end(), 0); diff --git a/src/common/SurgeStorage.h b/src/common/SurgeStorage.h index 2d612d45af4..335b760eb44 100644 --- a/src/common/SurgeStorage.h +++ b/src/common/SurgeStorage.h @@ -962,6 +962,8 @@ class alignas(16) SurgeStorage float modsource_vu[n_modsources]; void setSamplerate(float sr); + void createUserDirectory(); + void refresh_wtlist(); void refresh_wtlistAddDir(bool userDir, std::string subdir); void refresh_patchlist(); @@ -1005,12 +1007,18 @@ class alignas(16) SurgeStorage std::string wtpath; std::string datapath; - std::string userDataPath; std::string userDefaultFilePath; + std::string userDataPath; + std::string userPatchesPath; + std::string userWavetablesPath; + std::string userModulatorSettingsPath; std::string userFXPath; + std::string userWavetablesExportPath; + std::string userSkinsPath; + std::string userMidiMappingsPath; + std::string installedPath; - std::string userMidiMappingsPath; std::map userMidiMappingsXMLByName; void rescanUserMidiMappings(); void loadMidiMappingByName(std::string name); diff --git a/src/common/SurgeSynthesizerIO.cpp b/src/common/SurgeSynthesizerIO.cpp index 7849cad34b7..154c42b5223 100644 --- a/src/common/SurgeSynthesizerIO.cpp +++ b/src/common/SurgeSynthesizerIO.cpp @@ -489,7 +489,8 @@ void SurgeSynthesizer::savePatch() if (storage.getPatch().category.empty()) storage.getPatch().category = "Default"; - fs::path savepath = string_to_path(getUserPatchDirectory()); + fs::path savepath = + string_to_path(getUserPatchDirectory() + PATH_SEPARATOR + "Patches" + PATH_SEPARATOR); try { diff --git a/src/common/WAVFileSupport.cpp b/src/common/WAVFileSupport.cpp index c01b61ca3ce..ee92e340704 100644 --- a/src/common/WAVFileSupport.cpp +++ b/src/common/WAVFileSupport.cpp @@ -469,7 +469,7 @@ bool SurgeStorage::load_wt_wav_portable(std::string fn, Wavetable *wt) std::string SurgeStorage::export_wt_wav_portable(std::string fbase, Wavetable *wt) { std::string path; - path = Surge::Storage::appendDirectory(userDataPath, "Exported Wavetables"); + path = Surge::Storage::appendDirectory(userDataPath, "Wavetables", "Exported"); fs::create_directories(string_to_path(path)); auto fnamePre = fbase + ".wav"; diff --git a/src/gui/SkinSupport.cpp b/src/gui/SkinSupport.cpp index 568021eb72d..51887ca793d 100644 --- a/src/gui/SkinSupport.cpp +++ b/src/gui/SkinSupport.cpp @@ -89,7 +89,7 @@ void SkinDB::rescanForSkins(SurgeStorage *storage) availableSkins.clear(); std::array paths = {string_to_path(storage->datapath), - string_to_path(storage->userDataPath)}; + string_to_path(storage->userSkinsPath)}; for (auto &source : paths) { diff --git a/src/gui/SurgeGUIEditor.cpp b/src/gui/SurgeGUIEditor.cpp index d5a49059357..649c43af466 100644 --- a/src/gui/SurgeGUIEditor.cpp +++ b/src/gui/SurgeGUIEditor.cpp @@ -3027,11 +3027,7 @@ juce::PopupMenu SurgeGUIEditor::makeSkinMenu(const juce::Point &where) else { skinSubMenu.addItem(Surge::GUI::toOSCaseForMenu("Install a New Skin..."), [this]() { - std::string skinspath = - Surge::Storage::appendDirectory(this->synth->storage.userDataPath, "Skins"); - // make it if it isn't there - fs::create_directories(string_to_path(skinspath)); - Surge::GUI::openFileOrFolder(skinspath); + Surge::GUI::openFileOrFolder(this->synth->storage.userSkinsPath); }); } diff --git a/src/gui/widgets/OscillatorWaveformDisplay.cpp b/src/gui/widgets/OscillatorWaveformDisplay.cpp index 8e2ea067e0d..64d369589e8 100644 --- a/src/gui/widgets/OscillatorWaveformDisplay.cpp +++ b/src/gui/widgets/OscillatorWaveformDisplay.cpp @@ -339,13 +339,7 @@ void OscillatorWaveformDisplay::populateMenu(juce::PopupMenu &contextMenu, int s message); } }; - contextMenu.addItem(Surge::GUI::toOSCaseForMenu("Save Wavetable to File..."), exportAction); - - contextMenu.addItem(Surge::GUI::toOSCaseForMenu("Open Exported Wavetables Folder..."), - [this]() { - Surge::GUI::openFileOrFolder(Surge::Storage::appendDirectory( - this->storage->userDataPath, "Exported Wavetables")); - }); + contextMenu.addItem(Surge::GUI::toOSCaseForMenu("Export Wavetable to File..."), exportAction); if (sge) { diff --git a/src/gui/widgets/PatchSelector.cpp b/src/gui/widgets/PatchSelector.cpp index b7e9a065f01..a347dd35d4e 100644 --- a/src/gui/widgets/PatchSelector.cpp +++ b/src/gui/widgets/PatchSelector.cpp @@ -114,7 +114,6 @@ void PatchSelector::showClassicMenu(bool single_category) int main_e = 0; bool has_3rdparty = false; int last_category = current_category; - int root_count = 0, usercat_pos = 0, col_breakpoint = 0; auto patch_cat_size = storage->patch_category.size(); if (single_category) @@ -171,43 +170,27 @@ void PatchSelector::showClassicMenu(bool single_category) for (int i = 0; i < patch_cat_size; i++) { - if ((!single_category) || (i == last_category)) + if (i == storage->firstThirdPartyCategory || i == storage->firstUserCategory) { - if (!single_category && - (i == storage->firstThirdPartyCategory || i == storage->firstUserCategory)) + std::string txt; + + if (i == storage->firstThirdPartyCategory && storage->firstUserCategory != i) { - std::string txt; - - if (i == storage->firstThirdPartyCategory && storage->firstUserCategory != i) - { - has_3rdparty = true; - txt = "THIRD PARTY PATCHES"; - } - else - { - txt = "USER PATCHES"; - } - - contextMenu.addColumnBreak(); - contextMenu.addSectionHeader(txt); + txt = "THIRD PARTY PATCHES"; } - - // Remap index to the corresponding category in alphabetical order. - int c = storage->patchCategoryOrdering[i]; - - // find at which position the first user category root folder shows up - if (storage->patch_category[i].isRoot) + else { - root_count++; - - if (i == storage->firstUserCategory) - { - usercat_pos = root_count; - } + txt = "USER PATCHES"; } - populatePatchMenuForCategory(c, contextMenu, single_category, main_e, true); + contextMenu.addColumnBreak(); + contextMenu.addSectionHeader(txt); } + + // remap index to the corresponding category in alphabetical order. + int c = storage->patchCategoryOrdering[i]; + + populatePatchMenuForCategory(c, contextMenu, single_category, main_e, true); } } @@ -251,6 +234,8 @@ void PatchSelector::showClassicMenu(bool single_category) } }); + contextMenu.addSeparator(); + contextMenu.addItem(Surge::GUI::toOSCaseForMenu("Refresh Patch List"), [this]() { this->storage->refresh_patchlist(); });