diff --git a/src-ui/app/shared/HeaderRegion.cpp b/src-ui/app/shared/HeaderRegion.cpp index 32c7847f..3fce1c67 100644 --- a/src-ui/app/shared/HeaderRegion.cpp +++ b/src-ui/app/shared/HeaderRegion.cpp @@ -304,6 +304,42 @@ void HeaderRegion::doLoadMulti() }); } +void HeaderRegion::doSaveSelectedPart() +{ + fileChooser = std::make_unique( + "Save Selected Part", juce::File(editor->browser.patchIODirectory.u8string()), "*.scp"); + fileChooser->launchAsync(juce::FileBrowserComponent::canSelectFiles | + juce::FileBrowserComponent::saveMode | + juce::FileBrowserComponent::warnAboutOverwriting, + [w = juce::Component::SafePointer(this)](const juce::FileChooser &c) { + auto result = c.getResults(); + if (result.isEmpty() || result.size() > 1) + { + return; + } + // send a 'save multi' message + w->sendToSerialization(cmsg::SaveSelectedPart( + result[0].getFullPathName().toStdString())); + }); +} + +void HeaderRegion::doLoadIntoSelectedPart() +{ + fileChooser = std::make_unique( + "Load Part", juce::File(editor->browser.patchIODirectory.u8string()), "*.scp"); + fileChooser->launchAsync( + juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::openMode, + [w = juce::Component::SafePointer(this)](const juce::FileChooser &c) { + auto result = c.getResults(); + if (result.isEmpty() || result.size() > 1) + { + return; + } + w->sendToSerialization(cmsg::LoadPartInto( + {result[0].getFullPathName().toStdString(), w->editor->selectedPart})); + }); +} + void HeaderRegion::showSaveMenu() { auto p = juce::PopupMenu(); @@ -313,10 +349,22 @@ void HeaderRegion::showSaveMenu() if (w) w->doSaveMulti(); }); + p.addItem("Save Part " + std::to_string(editor->selectedPart + 1), + [w = juce::Component::SafePointer(this)]() { + if (w) + w->doSaveSelectedPart(); + }); + + p.addSeparator(); p.addItem("Load Multi", [w = juce::Component::SafePointer(this)]() { if (w) w->doLoadMulti(); }); + p.addItem("Load Part Into " + std::to_string(editor->selectedPart + 1), + [w = juce::Component::SafePointer(this)]() { + if (w) + w->doLoadIntoSelectedPart(); + }); p.addSeparator(); p.addItem("Reset Engine To Blank", [w = juce::Component::SafePointer(this)]() { if (w) diff --git a/src-ui/app/shared/HeaderRegion.h b/src-ui/app/shared/HeaderRegion.h index b7476573..7321c98d 100644 --- a/src-ui/app/shared/HeaderRegion.h +++ b/src-ui/app/shared/HeaderRegion.h @@ -109,6 +109,8 @@ struct HeaderRegion : juce::Component, HasEditor, juce::FileDragAndDropTarget void showSaveMenu(); void doSaveMulti(); void doLoadMulti(); + void doSaveSelectedPart(); + void doLoadIntoSelectedPart(); void showMultiSelectionMenu(); diff --git a/src/engine/engine.h b/src/engine/engine.h index 2ef5039b..25d6db38 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -350,6 +350,7 @@ struct Engine : MoveableOnly, SampleRateSupport { IN_PROCESS, FOR_MULTI, + FOR_PART, FOR_DAW }; static thread_local StreamReason streamReason; diff --git a/src/engine/part.cpp b/src/engine/part.cpp index 485ba82d..02233508 100644 --- a/src/engine/part.cpp +++ b/src/engine/part.cpp @@ -84,4 +84,20 @@ Part::zoneMappingSummary_t Part::getZoneMappingSummary() } return res; } + +std::vector Part::getSamplesUsedByPart() const +{ + std::unordered_set resSet; + for (const auto &g : groups) + { + for (const auto &z : g->getZones()) + { + for (const auto &var : z->variantData.variants) + { + resSet.insert(var.sampleID); + } + } + } + return std::vector(resSet.begin(), resSet.end()); +} } // namespace scxt::engine \ No newline at end of file diff --git a/src/engine/part.h b/src/engine/part.h index eda092e2..6cb91ce8 100644 --- a/src/engine/part.h +++ b/src/engine/part.h @@ -195,6 +195,8 @@ struct Part : MoveableOnly, SampleRateSupport groupContainer_t::iterator end() noexcept { return groups.end(); } groupContainer_t::const_iterator cend() const noexcept { return groups.cend(); } + std::vector getSamplesUsedByPart() const; + private: groupContainer_t groups; }; diff --git a/src/json/engine_traits.h b/src/json/engine_traits.h index 2d0cca20..6e7afa36 100644 --- a/src/json/engine_traits.h +++ b/src/json/engine_traits.h @@ -154,11 +154,39 @@ SC_STREAMDEF( scxt::engine::Part, SC_FROM({ // TODO: Do a non-empty part stream with the If variant v = {{"config", from.configuration}, {"groups", from.getGroups()}, {"macros", from.macros}}; + if (SC_STREAMING_FOR_PART) + { + addToObject(v, "streamingVersion", currentStreamingVersion); + addToObject(v, "streamedForPart", true); + assert(from.parentPatch->parentEngine); + addToObject( + v, "samplesUsedByPart", + from.parentPatch->parentEngine->getSampleManager()->getSampleAddressesFor( + from.getSamplesUsedByPart())); + SCLOG("ToDo - stream sample manager subset"); + } }), SC_TO({ auto &part = to; part.clearGroups(); + std::unique_ptr sg; + bool streamedForPart{false}; + findOrSet(v, "streamedForPart", false, streamedForPart); + if (streamedForPart) + { + uint64_t partStreamingVersion{0}; + findIf(v, "streamingVersion", partStreamingVersion); + SCLOG("Unstreaming part state. Stream version : " + << scxt::humanReadableVersion(partStreamingVersion)); + + scxt::sample::SampleManager::sampleAddressesAndIds_t samples; + findIf(v, "samplesUsedByPart", samples); + to.parentPatch->parentEngine->getSampleManager()->restoreFromSampleAddressesAndIDs(samples); + + sg = std::make_unique(partStreamingVersion); + } + if (SC_UNSTREAMING_FROM_PRIOR_TO(0x2024'08'18)) { findIf(v, "channel", part.configuration.channel); diff --git a/src/json/scxt_traits.h b/src/json/scxt_traits.h index 388eb623..388d9e59 100644 --- a/src/json/scxt_traits.h +++ b/src/json/scxt_traits.h @@ -210,6 +210,8 @@ void addUnlessDefault(V &v, const std::string &key, const R &defVal, const R &va #define SC_STREAMING_FOR_DAW (engine::Engine::streamReason == engine::Engine::StreamReason::FOR_DAW) #define SC_STREAMING_FOR_MULTI \ (engine::Engine::streamReason == engine::Engine::StreamReason::FOR_MULTI) +#define SC_STREAMING_FOR_PART \ + (engine::Engine::streamReason == engine::Engine::StreamReason::FOR_PART) #define SC_STREAMING_FOR_DAW_OR_MULTI (SC_STREAMING_FOR_DAW || SC_STREAMING_FOR_MULTI) diff --git a/src/json/stream.cpp b/src/json/stream.cpp index 5e53efbd..5774e204 100644 --- a/src/json/stream.cpp +++ b/src/json/stream.cpp @@ -101,4 +101,37 @@ void unstreamEngineState(engine::Engine &e, const std::string &data, bool msgPac e.sendFullRefreshToClient(); } + + +void unstreamPartState(engine::Engine &e, int part, const std::string &data, bool msgPack) +{ + e.clearAll(); + if (msgPack) + { + tao::json::events::transformer> consumer; + tao::json::msgpack::events::from_string(consumer, data); + auto jv = std::move(consumer.value); + jv.to(*(e.getPatch()->getPart(part))); + } + else + { + tao::json::events::transformer> consumer; + tao::json::events::from_string(consumer, data); + auto jv = std::move(consumer.value); + jv.to(*(e.getPatch()->getPart(part))); + } + + if (!e.getSampleManager()->missingList.empty()) + { + std::ostringstream oss; + oss << "On load, sample manager could not locate the following files:\n"; + for (const auto &p : e.getSampleManager()->missingList) + { + oss << " " << p.u8string() << "\n"; + } + e.getMessageController()->reportErrorToClient("Missing Samples", oss.str()); + } + + e.sendFullRefreshToClient(); +} } // namespace scxt::json \ No newline at end of file diff --git a/src/json/stream.h b/src/json/stream.h index 4cac5cb5..85bfb567 100644 --- a/src/json/stream.h +++ b/src/json/stream.h @@ -37,6 +37,7 @@ namespace scxt::json std::string streamPatch(const engine::Patch &p, bool pretty = false); std::string streamEngineState(const engine::Engine &e, bool pretty = false); void unstreamEngineState(engine::Engine &e, const std::string &jsonData, bool msgPack = false); +void unstreamPartState(engine::Engine &e, int part, const std::string &jsonData, bool msgPack = false); } // namespace scxt::json #endif // SHORTCIRCUIT_STREAM_H diff --git a/src/messaging/client/browser_messages.h b/src/messaging/client/browser_messages.h index 188531c0..4bd10f1d 100644 --- a/src/messaging/client/browser_messages.h +++ b/src/messaging/client/browser_messages.h @@ -46,7 +46,7 @@ inline void doAddBrowserDeviceLocation(const fs::path &p, const engine::Engine & serializationSendToClient(s2c_refresh_browser, true, cont); } CLIENT_TO_SERIAL(AddBrowserDeviceLocation, c2s_add_browser_device_location, std::string, - doAddBrowserDeviceLocation(fs::path{payload}, engine, cont)); + doAddBrowserDeviceLocation(fs::path(fs::u8path(payload)), engine, cont)); SERIAL_TO_CLIENT(RefreshBrowser, s2c_refresh_browser, bool, onBrowserRefresh) diff --git a/src/messaging/client/patch_io_messages.h b/src/messaging/client/patch_io_messages.h index a4fb0d8e..3451e5e6 100644 --- a/src/messaging/client/patch_io_messages.h +++ b/src/messaging/client/patch_io_messages.h @@ -34,14 +34,33 @@ namespace scxt::messaging::client { inline void doSaveMulti(const std::string &s, engine::Engine &engine, MessageController &cont) { - patch_io::saveMulti(fs::path{s}, engine); + patch_io::saveMulti(fs::path(fs::u8path(s)), engine); SCLOG("Remember to update the browser also"); // engine.getBrowser()->doSomething; } CLIENT_TO_SERIAL(SaveMulti, c2s_save_multi, std::string, doSaveMulti(payload, engine, cont)); - CLIENT_TO_SERIAL(LoadMulti, c2s_load_multi, std::string, - patch_io::loadMulti(fs::path{payload}, engine)); + patch_io::loadMulti(fs::path(fs::u8path(payload)), engine)); + +inline void doSaveSelectedPart(const std::string &s, engine::Engine &engine, + MessageController &cont) +{ + SCLOG("Saving part to " << s); + patch_io::savePart(fs::path(fs::u8path(s)), engine, engine.getSelectionManager()->selectedPart); + // engine.getBrowser()->doSomething; +} +CLIENT_TO_SERIAL(SaveSelectedPart, c2s_save_selected_part, std::string, + doSaveSelectedPart(payload, engine, cont)); + +using loadPartIntoPayload_t = std::tuple; +inline void doLoadPartInto(const loadPartIntoPayload_t &payload, engine::Engine &engine, + MessageController &cont) +{ + patch_io::loadPartInto(fs::path(fs::u8path(std::get<0>(payload))), engine, + std::get<1>(payload)); +} +CLIENT_TO_SERIAL(LoadPartInto, c2s_load_part_into, loadPartIntoPayload_t, + doLoadPartInto(payload, engine, cont)); } // namespace scxt::messaging::client #endif // SHORTCIRCUITXT_PATCH_IO_MESSAGES_H diff --git a/src/messaging/client/structure_messages.h b/src/messaging/client/structure_messages.h index 267888fd..a4e24f84 100644 --- a/src/messaging/client/structure_messages.h +++ b/src/messaging/client/structure_messages.h @@ -80,7 +80,7 @@ CLIENT_TO_SERIAL(RegisterClient, c2s_register_client, bool, doRegisterClient(eng inline void addSample(const std::string &payload, engine::Engine &engine, MessageController &cont) { assert(cont.threadingChecker.isSerialThread()); - auto p = fs::path{payload}; + auto p = fs::path(fs::u8path(payload)); engine.loadSampleIntoSelectedPartAndGroup(p); } CLIENT_TO_SERIAL(AddSample, c2s_add_sample, std::string, addSample(payload, engine, cont);) @@ -106,7 +106,7 @@ inline void addSampleInZone(const addSampleInZone_t &payload, engine::Engine &en MessageController &cont) { assert(cont.threadingChecker.isSerialThread()); - auto path = fs::path{std::get<0>(payload)}; + auto path = fs::path(fs::u8path(std::get<0>(payload))); auto part{std::get<1>(payload)}; auto group{std::get<2>(payload)}; auto zone{std::get<3>(payload)}; diff --git a/src/patch_io/patch_io.cpp b/src/patch_io/patch_io.cpp index 9b0be5eb..cdff2cad 100644 --- a/src/patch_io/patch_io.cpp +++ b/src/patch_io/patch_io.cpp @@ -90,7 +90,7 @@ std::string readSCDataChunk(const std::unique_ptr &f) bool saveMulti(const fs::path &p, const scxt::engine::Engine &e) { - SCLOG("Made it to the patch code " << p.u8string()); + SCLOG("Saving Multi to " << p.u8string()); try { @@ -112,6 +112,32 @@ bool saveMulti(const fs::path &p, const scxt::engine::Engine &e) return true; } +bool savePart(const fs::path &p, const scxt::engine::Engine &e, int part) +{ + SCLOG("Saving part " << part << " to " << p.u8string()); + + try + { + auto sg = scxt::engine::Engine::StreamGuard(engine::Engine::FOR_PART); + // auto msg = + // tao::json::msgpack::to_string(json::scxt_value(*(e.getPatch()->getPart(part)))); + auto msg = tao::json::to_string(json::scxt_value(*(e.getPatch()->getPart(part)))); + + auto f = std::make_unique('SCXT'); + f->SetByteOrder(RIFF::endian_little); + addSCManifest(f, "part"); + addSCDataChunk(f, msg); + + // TODO: If embeeding samples, add a list here with them + f->Save(p.u8string()); + } + catch (const RIFF::Exception &e) + { + SCLOG(e.Message); + } + return true; +} + bool initFromResourceBundle(scxt::engine::Engine &engine) { SCLOG("Init From Resource Bundle"); @@ -210,4 +236,54 @@ bool loadMulti(const fs::path &p, scxt::engine::Engine &engine) } return true; } + +bool loadPartInto(const fs::path &p, scxt::engine::Engine &engine, int part) +{ + SCLOG("loadPart " << p.u8string() << " " << part); + + std::string payload; + try + { + auto f = std::make_unique(p.u8string()); + auto manifest = readSCManifest(f); + payload = readSCDataChunk(f); + } + catch (const RIFF::Exception &e) + { + SCLOG("RIFF::Exception " << e.Message); + return false; + } + + auto &cont = engine.getMessageController(); + if (cont->isAudioRunning) + { + cont->stopAudioThreadThenRunOnSerial([payload, part, &nonconste = engine](auto &e) { + try + { + nonconste.stopAllSounds(); + scxt::json::unstreamPartState(nonconste, part, payload, false); + auto &cont = *e.getMessageController(); + cont.restartAudioThreadFromSerial(); + } + catch (std::exception &err) + { + SCLOG("Unable to load [" << err.what() << "]"); + } + }); + } + else + { + try + { + engine.stopAllSounds(); + scxt::json::unstreamPartState(engine,part, payload, false); + } + catch (std::exception &err) + { + SCLOG("Unable to load [" << err.what() << "]"); + } + } + + return true; +} } // namespace scxt::patch_io \ No newline at end of file diff --git a/src/patch_io/patch_io.h b/src/patch_io/patch_io.h index 97317a84..0448bff9 100644 --- a/src/patch_io/patch_io.h +++ b/src/patch_io/patch_io.h @@ -35,8 +35,8 @@ namespace scxt::patch_io { bool saveMulti(const fs::path &toFile, const scxt::engine::Engine &); bool loadMulti(const fs::path &fromFile, scxt::engine::Engine &); -bool streamPart(const fs::path &toFile, const scxt::engine::Part &); -bool unstreamPart(const fs::path &fromFile, scxt::engine::Part &); +bool savePart(const fs::path &toFile, const scxt::engine::Engine &, int part); +bool loadPartInto(const fs::path &fromFile, scxt::engine::Engine &, int part); bool initFromResourceBundle(scxt::engine::Engine &e); } // namespace scxt::patch_io diff --git a/src/sample/sample_manager.cpp b/src/sample/sample_manager.cpp index c7218cad..277e6304 100644 --- a/src/sample/sample_manager.cpp +++ b/src/sample/sample_manager.cpp @@ -246,4 +246,24 @@ void SampleManager::updateSampleMemory() } sampleMemoryInBytes = res; } + +SampleManager::sampleAddressesAndIds_t +SampleManager::getSampleAddressesFor(const std::vector &sids) const +{ + SampleManager::sampleAddressesAndIds_t res; + for (const auto &sid : sids) + { + auto smp = getSample(sid); + if (!smp) + { + SCLOG("WARNING: Requested non-existant sample at " << sid.to_string()); + } + else + { + res.emplace_back(sid, smp->getSampleFileAddress()); + } + } + return res; +} + } // namespace scxt::sample diff --git a/src/sample/sample_manager.h b/src/sample/sample_manager.h index ee5a58c2..5d9f25ec 100644 --- a/src/sample/sample_manager.h +++ b/src/sample/sample_manager.h @@ -114,6 +114,9 @@ struct SampleManager : MoveableOnly } return res; } + + sampleAddressesAndIds_t getSampleAddressesFor(const std::vector &) const; + void restoreFromSampleAddressesAndIDs(const sampleAddressesAndIds_t &); void purgeUnreferencedSamples();