Skip to content

Commit

Permalink
Save and Load individual parts.
Browse files Browse the repository at this point in the history
- Streaming with a FOR_PART option includes version and samples
  with associated API changes
- Menu to save a part.
- Load part plumbing in place through to engine.
- Restore sample manager state *but* there's an ID colliusion issue
  here. See #1370

Addresses #1014 some more

Actually do the unstream (but still need sample side)

done-one
  • Loading branch information
baconpaul committed Sep 23, 2024
1 parent 9d31d05 commit e1dfed1
Show file tree
Hide file tree
Showing 16 changed files with 260 additions and 9 deletions.
48 changes: 48 additions & 0 deletions src-ui/app/shared/HeaderRegion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,42 @@ void HeaderRegion::doLoadMulti()
});
}

void HeaderRegion::doSaveSelectedPart()
{
fileChooser = std::make_unique<juce::FileChooser>(
"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<juce::FileChooser>(
"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();
Expand All @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src-ui/app/shared/HeaderRegion.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ struct HeaderRegion : juce::Component, HasEditor, juce::FileDragAndDropTarget
void showSaveMenu();
void doSaveMulti();
void doLoadMulti();
void doSaveSelectedPart();
void doLoadIntoSelectedPart();

void showMultiSelectionMenu();

Expand Down
1 change: 1 addition & 0 deletions src/engine/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ struct Engine : MoveableOnly<Engine>, SampleRateSupport
{
IN_PROCESS,
FOR_MULTI,
FOR_PART,
FOR_DAW
};
static thread_local StreamReason streamReason;
Expand Down
16 changes: 16 additions & 0 deletions src/engine/part.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,20 @@ Part::zoneMappingSummary_t Part::getZoneMappingSummary()
}
return res;
}

std::vector<SampleID> Part::getSamplesUsedByPart() const
{
std::unordered_set<SampleID> 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<SampleID>(resSet.begin(), resSet.end());
}
} // namespace scxt::engine
2 changes: 2 additions & 0 deletions src/engine/part.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ struct Part : MoveableOnly<Part>, SampleRateSupport
groupContainer_t::iterator end() noexcept { return groups.end(); }
groupContainer_t::const_iterator cend() const noexcept { return groups.cend(); }

std::vector<SampleID> getSamplesUsedByPart() const;

private:
groupContainer_t groups;
};
Expand Down
28 changes: 28 additions & 0 deletions src/json/engine_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<val_t>(v, "streamingVersion", currentStreamingVersion);
addToObject<val_t>(v, "streamedForPart", true);
assert(from.parentPatch->parentEngine);
addToObject<val_t>(
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<engine::Engine::UnstreamGuard> 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<engine::Engine::UnstreamGuard>(partStreamingVersion);
}

if (SC_UNSTREAMING_FROM_PRIOR_TO(0x2024'08'18))
{
findIf(v, "channel", part.configuration.channel);
Expand Down
2 changes: 2 additions & 0 deletions src/json/scxt_traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
33 changes: 33 additions & 0 deletions src/json/stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<tao::json::events::to_basic_value<scxt_traits>> 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<tao::json::events::to_basic_value<scxt_traits>> 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
1 change: 1 addition & 0 deletions src/json/stream.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion src/messaging/client/browser_messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
25 changes: 22 additions & 3 deletions src/messaging/client/patch_io_messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, int16_t>;
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
4 changes: 2 additions & 2 deletions src/messaging/client/structure_messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);)
Expand All @@ -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)};
Expand Down
78 changes: 77 additions & 1 deletion src/patch_io/patch_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ std::string readSCDataChunk(const std::unique_ptr<RIFF::File> &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
{
Expand All @@ -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<RIFF::File>('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");
Expand Down Expand Up @@ -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<RIFF::File>(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
4 changes: 2 additions & 2 deletions src/patch_io/patch_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit e1dfed1

Please sign in to comment.