// native_bridge/include/NativeInstrumentEngine.h #ifndef NATIVE_INSTRUMENT_ENGINE_H #define NATIVE_INSTRUMENT_ENGINE_H #include "INativeInstrument.h" #include #include #include #include #include #include #include #include // FluidSynth (.sf2 / .sf3) class FluidSynthInstrument : public INativeInstrument { public: FluidSynthInstrument(); ~FluidSynthInstrument() override; bool loadSoundFontFile(const std::string& path, double sampleRate); bool init(double sampleRate, uint32_t maxBlockSize) override; void selectProgram(uint32_t channel, uint32_t bank, uint32_t program) override; void noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) override; void noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) override; void controlChange(uint32_t channel, uint32_t cc, uint32_t value) override; void programChange(uint32_t channel, uint32_t program) override; void pitchBend(uint32_t channel, uint32_t bend14) override; bool openGUI(void* parentWindowHandle) override; void closeGUI() override; void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) override; private: void* settings; // fluid_settings_t* void* synth; // fluid_synth_t* int sfontId; // Per-channel bank select (CC0<<7 | CC32) — programChange phai dung bank // that (truoc day hardcode bank 0 -> preset o bank != 0 khong chon duoc). uint32_t bank_[16]; }; // sfizz (.sfz) class SfizzInstrument : public INativeInstrument { public: bool loadSfzFile(const std::string& path, double sampleRate); bool init(double sampleRate, uint32_t maxBlockSize) override; void selectProgram(uint32_t channel, uint32_t bank, uint32_t program) override; void noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) override; void noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) override; void controlChange(uint32_t channel, uint32_t cc, uint32_t value) override; void programChange(uint32_t channel, uint32_t program) override; void pitchBend(uint32_t channel, uint32_t bend14) override; bool openGUI(void* parentWindowHandle) override; void closeGUI() override; void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) override; private: sfz::Sfizz sfizzSynth; }; // Multi-instrument host (A10): one engine instance per MIDI channel, so two // tracks can play two different soundfonts/VSTs simultaneously. All channels // render into the same output block (mixed), keyed by evt.channel. class InstrumentEngineManager { public: // Create (or replace) the instrument on `channel` for `path`. // Returns false if the type is unsupported or the file fails to load. bool assign(uint32_t channel, InstrumentType type, const std::string& path, double sampleRate, uint32_t blockSize); INativeInstrument* get(uint32_t channel); // Real-time MIDI dispatch (audio loop thread). Each call holds mu_ for the // WHOLE call — lookup + reloading check + instrument call under one lock — // so a worker thread's assign()/unload() can never swap the map and destroy // the old instance while dispatch is inside a method on it (use-after-free // when loading a new VSTi while others play). Events for unassigned or // reloading channels are dropped (a mid-reload instance must not be touched // while its worker rebuilds state_ in place). void noteOn(uint32_t channel, uint32_t pitch, float velocity); void noteOff(uint32_t channel, uint32_t pitch); void controlChange(uint32_t channel, uint32_t cc, uint32_t value); void programChange(uint32_t channel, uint32_t program); void pitchBend(uint32_t channel, uint32_t bend14); // Remove and destroy the instrument on `channel` (its destructor may call // VST terminate — MUST run on the channel worker thread, caller's duty). // Used by Option B: two live instances of the same plugin DLL (Nexus) // hang createView on the second — unload the other channel's copy. void unload(uint32_t channel); // Path last assigned to a channel (empty if none). Windows paths are // case-insensitive — caller must lowercase before comparing. std::string pathOf(uint32_t channel); // Mark a channel as rebuilding its VST instance (GUI reopen): the // real-time loop then skips processAudioBlock for that channel so VST // teardown never races the audio thread. void setReloading(uint32_t channel, bool on); // True while a channel is being rebuilt (instance teardown/reload on its // worker thread): real-time code drops MIDI for it. Channel-level flag — // survives the instance swap (the per-instance flag dies with the object). bool isReloading(uint32_t channel) const; // Editor-open predicate: while any VST editor is attached, channels whose // plugin DLL path has an open editor count as quiet (same-plugin channels // too -- the worker pumps the editor's window proc inside the DLL while the // audio loop process()es other instances of the same DLL). void setEditorOpenPredicate(std::function fn); // Flush every sounding note on every assigned channel. void allNotesOff(); // Zero L/R then sum each assigned instrument into it. void renderAll(float* outputL, float* outputR, uint32_t numSamples); size_t count() const { std::lock_guard lock(mu_); return channels_.size(); } void setBypassed(bool on) { isBypassed_.store(on); } bool isBypassed() const { return isBypassed_.load(); } private: static std::unique_ptr create_instrument(InstrumentType type); mutable std::mutex mu_; std::map> channels_; std::map paths_; // last assigned path per channel // Per-instrument scratch so engines that overwrite (not mix) stay additive. std::vector scratchL_, scratchR_; // Channel-level reload flag (see isReloading) — indexed by MIDI channel. bool reloadingCh_[16] = {}; // True when the channel's plugin DLL has an attached editor (or its reload // is in progress): real-time code drops MIDI and skips rendering. Lowercased // compare inside -- Windows plugin paths are case-insensitive. bool channelQuiet(uint32_t ch) const; std::function editorOpenFn_; std::atomic isBypassed_{false}; }; #endif // NATIVE_INSTRUMENT_ENGINE_H