G2.1: save full instrument state to disk (path/type/preset/transport)

This commit is contained in:
2026-08-16 07:28:17 +07:00
parent 8d9011222a
commit 28441566c2
9 changed files with 212 additions and 2 deletions
@@ -43,6 +43,7 @@ bool FluidSynthInstrument::loadSoundFontFile(const std::string& path, double sam
// Reset all channels to font preset 0 (spec §VII: bank0/prog0 piano)
for (uint32_t ch = 0; ch < 16; ++ch) {
bank_[ch] = 0;
program_[ch] = 0;
fluid_synth_program_select(FS_SYNTH, ch, sfontId, 0, 0);
}
return true;
@@ -55,6 +56,7 @@ bool FluidSynthInstrument::init(double sampleRate, uint32_t maxBlockSize) {
void FluidSynthInstrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {
if (!synth || channel >= 16) return;
bank_[channel] = bank;
program_[channel] = program;
fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank, program);
}
@@ -81,6 +83,7 @@ void FluidSynthInstrument::programChange(uint32_t channel, uint32_t program) {
if (!synth || channel >= 16) return;
// Dung bank da nhan tu CC0/CC32 — bank hardcode 0 lam preset o bank != 0
// khong duoc chon (fluid giu preset cu -> ra piano sai).
program_[channel] = program;
fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank_[channel], program);
}
@@ -96,6 +99,15 @@ bool FluidSynthInstrument::openGUI(void* parentWindowHandle) {
void FluidSynthInstrument::closeGUI() {}
// G2.1: expose last bank/program for the state snapshot.
uint32_t FluidSynthInstrument::bankOf(uint32_t channel) const {
return channel < 16 ? bank_[channel] : 0;
}
uint32_t FluidSynthInstrument::programOf(uint32_t channel) const {
return channel < 16 ? program_[channel] : 0;
}
void FluidSynthInstrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
if (!synth) return;
fluid_synth_write_float(FS_SYNTH, numSamples, outputL, 0, 1, outputR, 0, 1);
@@ -191,6 +203,7 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
if (it != channels_.end()) oldInst = std::move(it->second);
channels_[channel] = std::move(inst);
paths_[channel] = path;
types_[channel] = type;
// Fresh instance is by construction not reloading — clear the channel
// flag so real-time MIDI dispatch (which drops reloading channels)
// flows to it again.
@@ -221,6 +234,7 @@ void InstrumentEngineManager::unload(uint32_t channel) {
if (it != channels_.end()) oldInst = std::move(it->second);
channels_.erase(channel);
paths_.erase(channel);
types_.erase(channel);
reloadingCh_[channel] = false;
}
if (oldInst) {
@@ -242,6 +256,32 @@ std::string InstrumentEngineManager::pathOf(uint32_t channel) {
return it == paths_.end() ? std::string() : it->second;
}
bool InstrumentEngineManager::snapshot(BridgeStateSnapshot& out) {
std::lock_guard<std::mutex> lock(mu_);
out.instruments.clear();
for (auto& kv : channels_) {
uint32_t ch = kv.first;
if (ch < 16 && reloadingCh_[ch]) continue; // old instance dying
InstrumentStateEntry e;
e.channel = ch;
auto tp = types_.find(ch);
e.type = tp == types_.end() ? 0 : (int)tp->second;
auto pp = paths_.find(ch);
e.path = pp == paths_.end() ? std::string() : pp->second;
if (INativeInstrument* i = kv.second.get()) {
// VST3: refresh the saved buffers then serialize them. Safe
// here: mu_ is held, so the audio loop cannot be inside
// process() on this instance while getState runs.
i->captureState();
e.presetBase64 = i->serializeState();
e.bank = i->bankOf(ch);
e.program = i->programOf(ch);
}
out.instruments.push_back(std::move(e));
}
return true;
}
INativeInstrument* InstrumentEngineManager::get(uint32_t channel) {
std::lock_guard<std::mutex> lock(mu_);
auto it = channels_.find(channel);