G2.3: auto-reload instruments from state file after bridge respawn
This commit is contained in:
@@ -587,6 +587,67 @@ std::string Vst3Instrument::serializeState() const {
|
||||
#endif
|
||||
}
|
||||
|
||||
// G2.3: base64 decode (RFC 4648). Empty / invalid -> false.
|
||||
static int b64val(unsigned char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
static bool base64Decode(const std::string& in, std::vector<uint8_t>& out) {
|
||||
out.clear();
|
||||
out.reserve((in.size() / 4) * 3);
|
||||
uint32_t acc = 0;
|
||||
int bits = 0;
|
||||
for (unsigned char ch : in) {
|
||||
if (ch == '=' || ch == '\n' || ch == '\r' || ch == ' ') continue;
|
||||
int v = b64val(ch);
|
||||
if (v < 0) return false;
|
||||
acc = (acc << 6) | (uint32_t)v;
|
||||
bits += 6;
|
||||
if (bits >= 8) {
|
||||
bits -= 8;
|
||||
out.push_back((uint8_t)((acc >> bits) & 0xFF));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// G2.3: restore a preset previously saved by serializeState() (base64 of
|
||||
// [4B BE componentLen][component][4B BE controllerLen][controller]).
|
||||
// Decodes into the saved-state buffers and applies them to the live instance.
|
||||
void Vst3Instrument::loadSerializedState(const std::string& base64) {
|
||||
#ifndef HAVE_VST3SDK
|
||||
(void)base64;
|
||||
return;
|
||||
#else
|
||||
std::vector<uint8_t> blob;
|
||||
if (!base64Decode(base64, blob) || blob.size() < 8) {
|
||||
std::cerr << "[Vst3Instrument] loadSerializedState: bad base64 ch=" << channel_ << std::endl;
|
||||
return;
|
||||
}
|
||||
auto read32 = [&blob](size_t off) -> uint32_t {
|
||||
return ((uint32_t)blob[off] << 24) | ((uint32_t)blob[off + 1] << 16) |
|
||||
((uint32_t)blob[off + 2] << 8) | (uint32_t)blob[off + 3];
|
||||
};
|
||||
size_t off = 0;
|
||||
uint32_t clen = read32(off); off += 4;
|
||||
if (off + clen > blob.size()) { std::cerr << "[Vst3Instrument] loadSerializedState: bad compLen" << std::endl; return; }
|
||||
savedComponentState_.assign(blob.begin() + off, blob.begin() + off + clen);
|
||||
off += clen;
|
||||
if (off + 4 > blob.size()) { std::cerr << "[Vst3Instrument] loadSerializedState: bad ctrlLen" << std::endl; return; }
|
||||
uint32_t klen = read32(off); off += 4;
|
||||
if (off + klen > blob.size()) { std::cerr << "[Vst3Instrument] loadSerializedState: bad ctrlLen" << std::endl; return; }
|
||||
savedControllerState_.assign(blob.begin() + off, blob.begin() + off + klen);
|
||||
std::cout << "[Vst3Instrument] loaded serialized state ch=" << channel_
|
||||
<< " component=" << savedComponentState_.size()
|
||||
<< " controller=" << savedControllerState_.size() << std::endl;
|
||||
restoreState();
|
||||
#endif
|
||||
}
|
||||
|
||||
// G0.4: restore the captured state onto the freshly loaded instance.
|
||||
void Vst3Instrument::restoreState() {
|
||||
#ifndef HAVE_VST3SDK
|
||||
|
||||
@@ -1045,6 +1045,63 @@ int main(int argc, char* argv[]) {
|
||||
std::cerr << "[NativeBridge] state save FAILED: " << stateFile << std::endl;
|
||||
};
|
||||
|
||||
// G2.3: auto-reload instruments from the state file after a respawn.
|
||||
// Runs before the audio loop: read the file once, post one LOAD job per
|
||||
// saved channel to the UiThread (assign + preset restore), then resume
|
||||
// transport if the session was PLAYing. Only restores channels that are
|
||||
// empty (the frontend may already have loaded some by the time the state
|
||||
// file becomes visible). All jobs serialize on g_loadMutex; file IO here
|
||||
// is startup-only, never in the audio loop.
|
||||
{
|
||||
BridgeStateSnapshot snap;
|
||||
if (StateStore::load(snap, stateFile)) {
|
||||
std::cout << "[NativeBridge] restoring " << snap.instruments.size()
|
||||
<< " instruments from " << stateFile << std::endl;
|
||||
for (const auto& e : snap.instruments) {
|
||||
uint32_t ch = e.channel & 0xF;
|
||||
if (e.path.empty() || instruments.get(ch)) continue; // guard: empty channel only
|
||||
InstrumentType t = (InstrumentType)e.type;
|
||||
std::string path = e.path;
|
||||
std::string preset = e.presetBase64;
|
||||
uint32_t bank = e.bank, program = e.program;
|
||||
uiWorker.post([&instruments, t, ch, path, preset, bank, program, sampleRate, block]() {
|
||||
std::lock_guard<std::mutex> lg(g_loadMutex);
|
||||
bool ok = false;
|
||||
try {
|
||||
ok = instruments.assign(ch, t, path, sampleRate, block);
|
||||
} catch (...) {
|
||||
std::cerr << "[NativeBridge] restore assign EXCEPTION ch=" << ch
|
||||
<< " type=" << (int)t << " path=" << path << std::endl;
|
||||
ok = false;
|
||||
}
|
||||
if (ok) {
|
||||
if (auto* inst = instruments.get(ch)) {
|
||||
if (t == InstrumentType::VST3 && !preset.empty())
|
||||
inst->loadSerializedState(preset);
|
||||
else
|
||||
inst->selectProgram(ch, bank, program);
|
||||
}
|
||||
std::cout << "[NativeBridge] restored ch=" << ch
|
||||
<< " type=" << (int)t << " " << path << std::endl;
|
||||
} else {
|
||||
std::cerr << "[NativeBridge] restore FAILED ch=" << ch
|
||||
<< " type=" << (int)t << " " << path << std::endl;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (snap.playing) {
|
||||
playheadSamples = (uint64_t)snap.playhead;
|
||||
transportStopped = false;
|
||||
g_transportPlaying = true;
|
||||
g_stateDirty = true;
|
||||
std::cout << "[NativeBridge] restoring transport PLAY playhead="
|
||||
<< playheadSamples.load() << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "[NativeBridge] no state file to restore (" << stateFile << ")" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. REAL-TIME AUDIO PROCESSING ENGINE LOOP
|
||||
while (true) {
|
||||
// CRASH FIX (0xc000041d): last-resort net — any C++ exception that
|
||||
|
||||
Reference in New Issue
Block a user