NeuralAmpModelerPlugin

Plugin for Neural Amp Modeler
Log | Files | Refs | Submodules | README | LICENSE

commit a92fe974f04ef41fffd2b322d9675f5e83373967
parent ad6e5568550c2d1e4d7e826085a0c014bb3b0aa9
Author: Steven Atkinson <steven@atkinson.mn>
Date:   Sat,  4 Feb 2023 16:56:11 -0800

Plugin works with VST3 SDK's VST3PluginTestHost (#54)

* Initialize tone stack param as bool

* Fix some uninitialized members, set deleted pointers to null explicitly.

* Tighter allocation & deallocation of raw pointers, fix a memory leak

* Force internal processing to mono, expand out at the end only.

* More careful pointer code

* Change VST3 sub-category from Effect to Fx, now shows up in VST3PluginTestHost
Diffstat:
MNeuralAmpModeler/NeuralAmpModeler.cpp | 187++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
MNeuralAmpModeler/NeuralAmpModeler.h | 31+++++++++++++++++++++++++++----
MNeuralAmpModeler/config.h | 2+-
MNeuralAmpModeler/dsp/dsp.cpp | 35+++++++++++++++++++++++++++--------
MNeuralAmpModeler/dsp/dsp.h | 6++++++
5 files changed, 185 insertions(+), 76 deletions(-)

diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -107,24 +107,28 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) mInputPointers(nullptr), mOutputPointers(nullptr), mNAM(nullptr), + mIR(nullptr), mStagedNAM(nullptr), + mStagedIR(nullptr), + mFlagRemoveNAM(false), + mFlagRemoveIR(false), + mDefaultNAMString("Select model..."), + mDefaultIRString("Select IR..."), mToneBass(), mToneMid(), mToneTreble(), - mIR(), mNAMPath(), mNAMLegacyPath(), mIRPath(), - mFlagRemoveNAM(false), - mFlagRemoveIR(false), - mDefaultNAMString("Select model..."), - mDefaultIRString("Select IR...") + mInputSender(), + mOutputSender() { GetParam(kInputLevel)->InitGain("Input", 0.0, -20.0, 20.0, 0.1); GetParam(kToneBass)->InitDouble("Bass", 5.0, 0.0, 10.0, 0.1); GetParam(kToneMid)->InitDouble("Middle", 5.0, 0.0, 10.0, 0.1); GetParam(kToneTreble)->InitDouble("Treble", 5.0, 0.0, 10.0, 0.1); GetParam(kOutputLevel)->InitGain("Output", 0.0, -40.0, 40.0, 0.1); + GetParam(kEQActive)->InitBool("ToneStack", false); // try { // this->mDSP = get_hard_dsp(); @@ -307,7 +311,7 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) // The knobs pGraphics->AttachControl(new IVKnobControl(inputKnobArea, kInputLevel, "", style)); - const bool toneStackIsActive = this->GetParam(kEQActive)->Value() > 0; + const bool toneStackIsActive = this->GetParam(kEQActive)->Value(); const IVStyle toneStackInitialStyle = toneStackIsActive ? style : styleInactive; IVKnobControl* bassControl = new IVKnobControl(bassKnobArea, kToneBass, "", toneStackInitialStyle); IVKnobControl* middleControl = new IVKnobControl(middleKnobArea, kToneMid, "", toneStackInitialStyle); @@ -396,23 +400,30 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) }; } +NeuralAmpModeler::~NeuralAmpModeler() +{ + this->_DeallocateIOPointers(); +} + void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outputs, int nFrames) { - // TODO clean up the types here - const int nChans = this->NOutChansConnected(); - const size_t numChannels = (size_t)nChans; + const size_t numChannelsExternalIn = (size_t) this->NInChansConnected(); + const size_t numChannelsExternalOut = (size_t) this->NOutChansConnected(); + const size_t numChannelsInternal = this->mNUM_INTERNAL_CHANNELS; const size_t numFrames = (size_t)nFrames; - this->_PrepareBuffers(nFrames); - this->_ProcessInput(inputs, nFrames); + this->_PrepareBuffers(numChannelsInternal, numFrames); + // Input is collapsed to mono in preparation for the NAM. + this->_ProcessInput(inputs, numFrames, numChannelsExternalIn, numChannelsInternal); this->_ApplyDSPStaging(); - const bool toneStackActive = this->GetParam(kEQActive)->Value() > 0; + const bool toneStackActive = this->GetParam(kEQActive)->Value(); if (mNAM != nullptr) { // TODO remove input / output gains from here. const double inputGain = 1.0; const double outputGain = 1.0; + const int nChans = (int) numChannelsInternal; mNAM->process(this->mInputPointers, this->mOutputPointers, nChans, nFrames, inputGain, outputGain, mNAMParams); mNAM->finalize_(nFrames); } @@ -449,21 +460,22 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp this->mToneBass.SetParams(bassParams); this->mToneMid.SetParams(midParams); this->mToneTreble.SetParams(trebleParams); - sample** bassPointers = this->mToneBass.Process(this->mOutputPointers, numChannels, numFrames); - sample** midPointers = this->mToneMid.Process(bassPointers, numChannels, numFrames); - sample** treblePointers = this->mToneTreble.Process(midPointers, numChannels, numFrames); + sample** bassPointers = this->mToneBass.Process(this->mOutputPointers, numChannelsInternal, numFrames); + sample** midPointers = this->mToneMid.Process(bassPointers, numChannelsInternal, numFrames); + sample** treblePointers = this->mToneTreble.Process(midPointers, numChannelsInternal, numFrames); toneStackOutPointers = treblePointers; } sample** irPointers = toneStackOutPointers; if (this->mIR != nullptr) - irPointers = this->mIR->Process(toneStackOutPointers, numChannels, numFrames); + irPointers = this->mIR->Process(toneStackOutPointers, numChannelsInternal, numFrames); // Let's get outta here - this->_ProcessOutput(irPointers, outputs, nFrames); + // This is where we exit mono for whatever the output requires. + this->_ProcessOutput(irPointers, outputs, numFrames, numChannelsInternal, numChannelsExternalOut); // * Output of input leveling (inputs -> mInputPointers), // * Output of output leveling (mOutputPointers -> outputs) - this->_UpdateMeters(this->mInputPointers, outputs, nFrames); + this->_UpdateMeters(this->mInputPointers, outputs, numFrames, numChannelsInternal, numChannelsExternalOut); } bool NeuralAmpModeler::SerializeState(IByteChunk& chunk) const @@ -506,6 +518,20 @@ void NeuralAmpModeler::OnUIOpen() // Private methods ============================================================ +void NeuralAmpModeler::_AllocateIOPointers(const size_t nChans) +{ + if (this->mInputPointers != nullptr) + throw std::runtime_error("Tried to re-allocate mInputPointers without freeing"); + this->mInputPointers = new sample*[nChans]; + if (this->mInputPointers == nullptr) + throw std::runtime_error("Failed to allocate pointer to input buffer!\n"); + if (this->mOutputPointers != nullptr) + throw std::runtime_error("Tried to re-allocate mOutputPointers without freeing"); + this->mOutputPointers = new sample*[nChans]; + if (this->mOutputPointers == nullptr) + throw std::runtime_error("Failed to allocate pointer to output buffer!\n"); +} + void NeuralAmpModeler::_ApplyDSPStaging() { // Move things from staged to live @@ -535,11 +561,27 @@ void NeuralAmpModeler::_ApplyDSPStaging() } } +void NeuralAmpModeler::_DeallocateIOPointers() +{ + if (this->mInputPointers != nullptr) { + delete[] this->mInputPointers; + this->mInputPointers = nullptr; + } + if (this->mInputPointers != nullptr) + throw std::runtime_error("Failed to deallocate pointer to input buffer!\n"); + if (this->mOutputPointers != nullptr) { + delete[] this->mOutputPointers; + this->mOutputPointers = nullptr; + } + if (this->mOutputPointers != nullptr) + throw std::runtime_error("Failed to deallocate pointer to output buffer!\n"); +} + void NeuralAmpModeler::_FallbackDSP(const int nFrames) { - const int nChans = this->NOutChansConnected(); - for (int c=0; c<nChans; c++) - for (int s=0; s<nFrames; s++) + const size_t nChans = this->mNUM_INTERNAL_CHANNELS; + for (auto c=0; c<nChans; c++) + for (auto s=0; s<nFrames; s++) this->mOutputArray[c][s] = this->mInputArray[c][s]; } @@ -633,6 +675,7 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irPath) size_t NeuralAmpModeler::_GetBufferNumChannels() const { + // Assumes input=output (no mono->stereo effects) return this->mInputArray.size(); } @@ -643,64 +686,70 @@ size_t NeuralAmpModeler::_GetBufferNumFrames() const return this->mInputArray[0].size(); } -void NeuralAmpModeler::_PrepareBuffers(const int nFrames) +void NeuralAmpModeler::_PrepareBuffers(const size_t numChannels, const size_t numFrames) { - const size_t nChans = this->NOutChansConnected(); - const bool updateChannels = nChans != this->_GetBufferNumChannels(); - const bool updateFrames = updateChannels || (this->_GetBufferNumFrames() != nFrames); -// if (!updateChannels && !updateFrames) + const bool updateChannels = numChannels != this->_GetBufferNumChannels(); + const bool updateFrames = updateChannels || (this->_GetBufferNumFrames() != numFrames); +// if (!updateChannels && !updateFrames) // Could we do this? // return; - if (updateChannels) { // Does channels *and* frames just to be safe. - this->_PrepareIOPointers(nChans); - this->mInputArray.resize(nChans); - this->mOutputArray.resize(nChans); + if (updateChannels) { + this->_PrepareIOPointers(numChannels); + this->mInputArray.resize(numChannels); + this->mOutputArray.resize(numChannels); } - if (updateFrames) { // and not update channels - for (int c=0; c<nChans; c++) { - this->mInputArray[c].resize(nFrames); - this->mOutputArray[c].resize(nFrames); - } + if (updateFrames) { + for (auto c=0; c<this->mInputArray.size(); c++) + this->mInputArray[c].resize(numFrames); + for (auto c=0; c<this->mOutputArray.size(); c++) + this->mOutputArray[c].resize(numFrames); } - // Would these ever change? - for (auto c=0; c<this->mInputArray.size(); c++) { + // Would these ever get changed by something? + for (auto c=0; c<this->mInputArray.size(); c++) this->mInputPointers[c] = this->mInputArray[c].data(); + for (auto c=0; c<this->mOutputArray.size(); c++) this->mOutputPointers[c] = this->mOutputArray[c].data(); - } } -void NeuralAmpModeler::_PrepareIOPointers(const size_t nChans) +void NeuralAmpModeler::_PrepareIOPointers(const size_t numChannels) { - if (this->mInputPointers != nullptr) - delete[] this->mInputPointers; - if (this->mOutputPointers != nullptr) - delete[] this->mOutputPointers; - this->mInputPointers = new sample*[nChans]; - if (this->mInputPointers == nullptr) - throw std::runtime_error("Failed to allocate pointer to input buffer!\n"); - this->mOutputPointers = new sample*[nChans]; - if (this->mOutputPointers == nullptr) - throw std::runtime_error("Failed to allocate pointer to output buffer!\n"); + this->_DeallocateIOPointers(); + this->_AllocateIOPointers(numChannels); } -void NeuralAmpModeler::_ProcessInput(iplug::sample **inputs, const int nFrames) +void NeuralAmpModeler::_ProcessInput(iplug::sample **inputs, + const size_t nFrames, + const size_t nChansIn, + const size_t nChansOut) { - const double gain = pow(10.0, GetParam(kInputLevel)->Value() / 20.0); - const size_t nChans = this->NOutChansConnected(); // Assume _PrepareBuffers() was already called - for (int c=0; c<nChans; c++) - for (int s=0; s<nFrames; s++) - this->mInputArray[c][s] = gain * inputs[c][s]; + const double gain = pow(10.0, GetParam(kInputLevel)->Value() / 20.0); + if (nChansOut <= nChansIn) // Many->few: Drop additional channels + for (size_t c=0; c<nChansOut; c++) + for (size_t s=0; s<nFrames; s++) + this->mInputArray[c][s] = gain * inputs[c][s]; + else { + // Something is wrong--this is a mono plugin. How could there be fewer + // incoming channels? + throw std::runtime_error("Unexpected input processing--sees fewer than 1 incoming channel?"); + } } -void NeuralAmpModeler::_ProcessOutput(iplug::sample** inputs, iplug::sample **outputs, const int nFrames) +void NeuralAmpModeler::_ProcessOutput(iplug::sample** inputs, + iplug::sample **outputs, + const size_t nFrames, + const size_t nChansIn, + const size_t nChansOut) { const double gain = pow(10.0, GetParam(kOutputLevel)->Value() / 20.0); - const size_t nChans = this->NOutChansConnected(); // Assume _PrepareBuffers() was already called - for (int c=0; c<nChans; c++) - for (int s=0; s<nFrames; s++) - outputs[c][s] = std::clamp(gain * inputs[c][s], -1.0, 1.0); + if (nChansIn != 1) + throw std::runtime_error("Plugin is supposed to process in mono."); + // Broadcast the internal mono stream to all output channels. + const size_t cin = 0; + for (auto cout=0; cout<nChansOut; cout++) + for (auto s=0; s<nFrames; s++) + outputs[cout][s] = std::clamp(gain * inputs[cin][s], -1.0, 1.0); } void NeuralAmpModeler::_SetModelMsg(const WDL_String& modelPath) @@ -739,8 +788,20 @@ void NeuralAmpModeler::_UnsetMsg(const int tag, const WDL_String &msg) SendControlMsgFromDelegate(tag, 0, int(strlen(msg.Get())), msg.Get()); } -void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, sample** outputPointer, const int nFrames) +void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, + sample** outputPointer, + const size_t nFrames, + const size_t nChansIn, + const size_t nChansOut) { - this->mInputSender.ProcessBlock(inputPointer, nFrames, kCtrlTagInputMeter); - this->mOutputSender.ProcessBlock(outputPointer, nFrames, kCtrlTagOutputMeter); + // Right now, we didn't specify MAXNC when we initialized these, so it's 1. + const int nChansHack = 1; + this->mInputSender.ProcessBlock(inputPointer, + (int) nFrames, + kCtrlTagInputMeter, + nChansHack); + this->mOutputSender.ProcessBlock(outputPointer, + (int) nFrames, + kCtrlTagOutputMeter, + nChansHack); } diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h @@ -41,6 +41,7 @@ class NeuralAmpModeler final : public iplug::Plugin { public: NeuralAmpModeler(const iplug::InstanceInfo& info); + ~NeuralAmpModeler(); void ProcessBlock(iplug::sample** inputs, iplug::sample** outputs, int nFrames) override; void OnReset() override { @@ -58,11 +59,15 @@ public: void OnUIOpen() override; private: + // Allocates mInputPointers and mOutputPointers + void _AllocateIOPointers(const size_t nChans); // Moves DSP modules from staging area to the main area. // Also deletes DSP modules that are flagged for removal. // Exists so that we don't try to use a DSP module that's only // partially-instantiated. void _ApplyDSPStaging(); + // Deallocates mInputPointers and mOutputPointers + void _DeallocateIOPointers(); // Fallback that just copies inputs to outputs if mDSP doesn't hold a model. void _FallbackDSP(const int nFrames); // Sizes based on mInputArray @@ -83,13 +88,24 @@ private: return this->mNAM != nullptr; }; // Prepare the input & output buffers - void _PrepareBuffers(const int nFrames); + void _PrepareBuffers(const size_t numChannels, const size_t numFrames); // Manage pointers void _PrepareIOPointers(const size_t nChans); // Copy the input buffer to the object, applying input level. - void _ProcessInput(iplug::sample** inputs, const int nFrames); + // :param nChansIn: In from external + // :param nChansOut: Out to the internal of the DSP routine + void _ProcessInput(iplug::sample** inputs, + const size_t nFrames, + const size_t nChansIn, + const size_t nChansOut); // Copy the output to the output buffer, applying output level. - void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const int nFrames); + // :param nChansIn: In from internal + // :param nChansOut: Out to external + void _ProcessOutput(iplug::sample** inputs, + iplug::sample** outputs, + const size_t nFrames, + const size_t nChansIn, + const size_t nChansOut); // Update the text in the IR area to say what's loaded. void _SetIRMsg(const WDL_String& irPath); void _UnsetModelMsg(); @@ -98,9 +114,16 @@ private: // Update level meters // Called within ProcessBlock(). // Assume _ProcessInput() and _ProcessOutput() were run immediately before. - void _UpdateMeters(iplug::sample** inputPointer, iplug::sample** outputPointer, const int nFrames); + void _UpdateMeters(iplug::sample** inputPointer, + iplug::sample** outputPointer, + const size_t nFrames, + const size_t nChansIn, + const size_t nChansOut); // Member data + + // The plugin is mono inside + const size_t mNUM_INTERNAL_CHANNELS = 1; // Input arrays to NAM std::vector<std::vector<iplug::sample>> mInputArray; diff --git a/NeuralAmpModeler/config.h b/NeuralAmpModeler/config.h @@ -46,7 +46,7 @@ #define AAX_PLUG_CATEGORY_STR "Effect" #define AAX_DOES_AUDIOSUITE 1 -#define VST3_SUBCATEGORY "Effect" +#define VST3_SUBCATEGORY "Fx" #define APP_NUM_CHANNELS 2 #define APP_N_VECTOR_WAIT 0 diff --git a/NeuralAmpModeler/dsp/dsp.cpp b/NeuralAmpModeler/dsp/dsp.cpp @@ -52,7 +52,10 @@ void DSP::_get_params_(const std::unordered_map<std::string, double>& input_para } } -void DSP::_apply_input_level_(iplug::sample** inputs, const int num_channels, const int num_frames, const double gain) +void DSP::_apply_input_level_(iplug::sample** inputs, + const int num_channels, + const int num_frames, + const double gain) { // Must match exactly; we're going to use the size of _input_post_gain later for num_frames. if (this->_input_post_gain.size() != num_frames) @@ -578,9 +581,29 @@ mOutputPointersSize(0) dsp::DSP::~DSP() { + this->_DeallocateOutputPointers(); +}; + +void dsp::DSP::_AllocateOutputPointers(const size_t numChannels) +{ if (this->mOutputPointers != nullptr) + throw std::runtime_error("Tried to re-allocate over non-null mOutputPointers"); + this->mOutputPointers = new iplug::sample*[numChannels]; + if (this->mOutputPointers == nullptr) + throw std::runtime_error("Failed to allocate pointer to output buffer!\n"); + this->mOutputPointersSize = numChannels; +} + +void dsp::DSP::_DeallocateOutputPointers() +{ + if (this->mOutputPointers != nullptr) { delete[] this->mOutputPointers; -}; + this->mOutputPointers = nullptr; + } + if (this->mOutputPointers != nullptr) + throw std::runtime_error("Failed to deallocate output pointer!"); + this->mOutputPointersSize = 0; +} iplug::sample** dsp::DSP::_GetPointers() { @@ -605,12 +628,8 @@ void dsp::DSP::_ResizePointers(const size_t numChannels) { if (this->mOutputPointersSize == numChannels) return; - if (this->mOutputPointers != nullptr) - delete[] this->mOutputPointers; - this->mOutputPointers = new iplug::sample*[numChannels]; - if (this->mOutputPointers == nullptr) - throw std::runtime_error("Failed to allocate pointer to output buffer!\n"); - this->mOutputPointersSize = numChannels; + this->_DeallocateOutputPointers(); + this->_AllocateOutputPointers(numChannels); } dsp::History::History() : diff --git a/NeuralAmpModeler/dsp/dsp.h b/NeuralAmpModeler/dsp/dsp.h @@ -398,6 +398,12 @@ namespace dsp { protected: // Methods + // Allocate mOutputPointers. + // Assumes it's already null (Use _DeallocateOutputPointers()). + void _AllocateOutputPointers(const size_t numChannels); + // Ensure mOutputPointers is freed. + void _DeallocateOutputPointers(); + size_t _GetNumChannels() const {return this->mOutputs.size();}; // Return a pointer-to-pointers for the DSP's output buffers (all channels) // Assumes that ._PrepareBuffers() was called recently enough.