NeuralAmpModelerPlugin

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

NeuralAmpModeler.h (10838B)


      1 #pragma once
      2 
      3 #include "NeuralAmpModelerCore/NAM/dsp.h"
      4 #include "AudioDSPTools/dsp/ImpulseResponse.h"
      5 #include "AudioDSPTools/dsp/NoiseGate.h"
      6 #include "AudioDSPTools/dsp/dsp.h"
      7 #include "AudioDSPTools/dsp/wav.h"
      8 #include "AudioDSPTools/dsp/ResamplingContainer/ResamplingContainer.h"
      9 
     10 #include "Colors.h"
     11 #include "ToneStack.h"
     12 
     13 #include "IPlug_include_in_plug_hdr.h"
     14 #include "ISender.h"
     15 
     16 
     17 const int kNumPresets = 1;
     18 // The plugin is mono inside
     19 constexpr size_t kNumChannelsInternal = 1;
     20 
     21 class NAMSender : public iplug::IPeakAvgSender<>
     22 {
     23 public:
     24   NAMSender()
     25   : iplug::IPeakAvgSender<>(-90.0, true, 5.0f, 1.0f, 300.0f, 500.0f)
     26   {
     27   }
     28 };
     29 
     30 enum EParams
     31 {
     32   // These need to be the first ones because I use their indices to place
     33   // their rects in the GUI.
     34   kInputLevel = 0,
     35   kNoiseGateThreshold,
     36   kToneBass,
     37   kToneMid,
     38   kToneTreble,
     39   kOutputLevel,
     40   // The rest is fine though.
     41   kNoiseGateActive,
     42   kEQActive,
     43   kIRToggle,
     44   // Input calibration
     45   kCalibrateInput,
     46   kInputCalibrationLevel,
     47   kOutputMode,
     48   kNumParams
     49 };
     50 
     51 const int numKnobs = 6;
     52 
     53 enum ECtrlTags
     54 {
     55   kCtrlTagModelFileBrowser = 0,
     56   kCtrlTagIRFileBrowser,
     57   kCtrlTagInputMeter,
     58   kCtrlTagOutputMeter,
     59   kCtrlTagSettingsBox,
     60   kCtrlTagOutputMode,
     61   kCtrlTagCalibrateInput,
     62   kCtrlTagInputCalibrationLevel,
     63   kNumCtrlTags
     64 };
     65 
     66 enum EMsgTags
     67 {
     68   // These tags are used from UI -> DSP
     69   kMsgTagClearModel = 0,
     70   kMsgTagClearIR,
     71   kMsgTagHighlightColor,
     72   // The following tags are from DSP -> UI
     73   kMsgTagLoadFailed,
     74   kMsgTagLoadedModel,
     75   kMsgTagLoadedIR,
     76   kNumMsgTags
     77 };
     78 
     79 // Get the sample rate of a NAM model.
     80 // Sometimes, the model doesn't know its own sample rate; this wrapper guesses 48k based on the way that most
     81 // people have used NAM in the past.
     82 double GetNAMSampleRate(const std::unique_ptr<nam::DSP>& model)
     83 {
     84   // Some models are from when we didn't have sample rate in the model.
     85   // For those, this wraps with the assumption that they're 48k models, which is probably true.
     86   const double assumedSampleRate = 48000.0;
     87   const double reportedEncapsulatedSampleRate = model->GetExpectedSampleRate();
     88   const double encapsulatedSampleRate =
     89     reportedEncapsulatedSampleRate <= 0.0 ? assumedSampleRate : reportedEncapsulatedSampleRate;
     90   return encapsulatedSampleRate;
     91 };
     92 
     93 class ResamplingNAM : public nam::DSP
     94 {
     95 public:
     96   // Resampling wrapper around the NAM models
     97   ResamplingNAM(std::unique_ptr<nam::DSP> encapsulated, const double expected_sample_rate)
     98   : nam::DSP(expected_sample_rate)
     99   , mEncapsulated(std::move(encapsulated))
    100   , mResampler(GetNAMSampleRate(mEncapsulated))
    101   {
    102     // Assign the encapsulated object's processing function  to this object's member so that the resampler can use it:
    103     auto ProcessBlockFunc = [&](NAM_SAMPLE** input, NAM_SAMPLE** output, int numFrames) {
    104       mEncapsulated->process(input[0], output[0], numFrames);
    105     };
    106     mBlockProcessFunc = ProcessBlockFunc;
    107 
    108     // Get the other information from the encapsulated NAM so that we can tell the outside world about what we're
    109     // holding.
    110     if (mEncapsulated->HasLoudness())
    111     {
    112       SetLoudness(mEncapsulated->GetLoudness());
    113     }
    114     if (mEncapsulated->HasInputLevel())
    115     {
    116       SetInputLevel(mEncapsulated->GetInputLevel());
    117     }
    118     if (mEncapsulated->HasOutputLevel())
    119     {
    120       SetOutputLevel(mEncapsulated->GetOutputLevel());
    121     }
    122 
    123     // NOTE: prewarm samples doesn't mean anything--we can prewarm the encapsulated model as it likes and be good to
    124     // go.
    125     // _prewarm_samples = 0;
    126 
    127     // And be ready
    128     int maxBlockSize = 2048; // Conservative
    129     Reset(expected_sample_rate, maxBlockSize);
    130   };
    131 
    132   ~ResamplingNAM() = default;
    133 
    134   void prewarm() override { mEncapsulated->prewarm(); };
    135 
    136   void process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) override
    137   {
    138     if (num_frames > mMaxExternalBlockSize)
    139       // We can afford to be careful
    140       throw std::runtime_error("More frames were provided than the max expected!");
    141 
    142     if (!NeedToResample())
    143     {
    144       mEncapsulated->process(input, output, num_frames);
    145     }
    146     else
    147     {
    148       mResampler.ProcessBlock(&input, &output, num_frames, mBlockProcessFunc);
    149     }
    150   };
    151 
    152   int GetLatency() const { return NeedToResample() ? mResampler.GetLatency() : 0; };
    153 
    154   void Reset(const double sampleRate, const int maxBlockSize) override
    155   {
    156     mExpectedSampleRate = sampleRate;
    157     mMaxExternalBlockSize = maxBlockSize;
    158     mResampler.Reset(sampleRate, maxBlockSize);
    159 
    160     // Allocations in the encapsulated model (HACK)
    161     // Stolen some code from the resampler; it'd be nice to have these exposed as methods? :)
    162     const double mUpRatio = sampleRate / GetEncapsulatedSampleRate();
    163     const auto maxEncapsulatedBlockSize = static_cast<int>(std::ceil(static_cast<double>(maxBlockSize) / mUpRatio));
    164     mEncapsulated->ResetAndPrewarm(sampleRate, maxEncapsulatedBlockSize);
    165   };
    166 
    167   // So that we can let the world know if we're resampling (useful for debugging)
    168   double GetEncapsulatedSampleRate() const { return GetNAMSampleRate(mEncapsulated); };
    169 
    170 private:
    171   bool NeedToResample() const { return GetExpectedSampleRate() != GetEncapsulatedSampleRate(); };
    172   // The encapsulated NAM
    173   std::unique_ptr<nam::DSP> mEncapsulated;
    174 
    175   // The resampling wrapper
    176   dsp::ResamplingContainer<NAM_SAMPLE, 1, 12> mResampler;
    177 
    178   // Used to check that we don't get too large a block to process.
    179   int mMaxExternalBlockSize = 0;
    180 
    181   // This function is defined to conform to the interface expected by the iPlug2 resampler.
    182   std::function<void(NAM_SAMPLE**, NAM_SAMPLE**, int)> mBlockProcessFunc;
    183 };
    184 
    185 class NeuralAmpModeler final : public iplug::Plugin
    186 {
    187 public:
    188   NeuralAmpModeler(const iplug::InstanceInfo& info);
    189   ~NeuralAmpModeler();
    190 
    191   void ProcessBlock(iplug::sample** inputs, iplug::sample** outputs, int nFrames) override;
    192   void OnReset() override;
    193   void OnIdle() override;
    194 
    195   bool SerializeState(iplug::IByteChunk& chunk) const override;
    196   int UnserializeState(const iplug::IByteChunk& chunk, int startPos) override;
    197   void OnUIOpen() override;
    198   bool OnHostRequestingSupportedViewConfiguration(int width, int height) override { return true; }
    199 
    200   void OnParamChange(int paramIdx) override;
    201   void OnParamChangeUI(int paramIdx, iplug::EParamSource source) override;
    202   bool OnMessage(int msgTag, int ctrlTag, int dataSize, const void* pData) override;
    203 
    204 private:
    205   // Allocates mInputPointers and mOutputPointers
    206   void _AllocateIOPointers(const size_t nChans);
    207   // Moves DSP modules from staging area to the main area.
    208   // Also deletes DSP modules that are flagged for removal.
    209   // Exists so that we don't try to use a DSP module that's only
    210   // partially-instantiated.
    211   void _ApplyDSPStaging();
    212   // Deallocates mInputPointers and mOutputPointers
    213   void _DeallocateIOPointers();
    214   // Fallback that just copies inputs to outputs if mDSP doesn't hold a model.
    215   void _FallbackDSP(iplug::sample** inputs, iplug::sample** outputs, const size_t numChannels, const size_t numFrames);
    216   // Sizes based on mInputArray
    217   size_t _GetBufferNumChannels() const;
    218   size_t _GetBufferNumFrames() const;
    219   void _InitToneStack();
    220   // Loads a NAM model and stores it to mStagedNAM
    221   // Returns an empty string on success, or an error message on failure.
    222   std::string _StageModel(const WDL_String& dspFile);
    223   // Loads an IR and stores it to mStagedIR.
    224   // Return status code so that error messages can be relayed if
    225   // it wasn't successful.
    226   dsp::wav::LoadReturnCode _StageIR(const WDL_String& irPath);
    227 
    228   bool _HaveModel() const { return this->mModel != nullptr; };
    229   // Prepare the input & output buffers
    230   void _PrepareBuffers(const size_t numChannels, const size_t numFrames);
    231   // Manage pointers
    232   void _PrepareIOPointers(const size_t nChans);
    233   // Copy the input buffer to the object, applying input level.
    234   // :param nChansIn: In from external
    235   // :param nChansOut: Out to the internal of the DSP routine
    236   void _ProcessInput(iplug::sample** inputs, const size_t nFrames, const size_t nChansIn, const size_t nChansOut);
    237   // Copy the output to the output buffer, applying output level.
    238   // :param nChansIn: In from internal
    239   // :param nChansOut: Out to external
    240   void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const size_t nFrames, const size_t nChansIn,
    241                       const size_t nChansOut);
    242   // Resetting for models and IRs, called by OnReset
    243   void _ResetModelAndIR(const double sampleRate, const int maxBlockSize);
    244 
    245   void _SetInputGain();
    246   void _SetOutputGain();
    247 
    248   // See: Unserialization.cpp
    249   void _UnserializeApplyConfig(nlohmann::json& config);
    250   // 0.7.9 and later
    251   int _UnserializeStateWithKnownVersion(const iplug::IByteChunk& chunk, int startPos);
    252   // Hopefully 0.7.3-0.7.8, but no gurantees
    253   int _UnserializeStateWithUnknownVersion(const iplug::IByteChunk& chunk, int startPos);
    254 
    255   // Update all controls that depend on a model
    256   void _UpdateControlsFromModel();
    257 
    258   // Make sure that the latency is reported correctly.
    259   void _UpdateLatency();
    260 
    261   // Update level meters
    262   // Called within ProcessBlock().
    263   // Assume _ProcessInput() and _ProcessOutput() were run immediately before.
    264   void _UpdateMeters(iplug::sample** inputPointer, iplug::sample** outputPointer, const size_t nFrames,
    265                      const size_t nChansIn, const size_t nChansOut);
    266 
    267   // Member data
    268 
    269   // Input arrays to NAM
    270   std::vector<std::vector<iplug::sample>> mInputArray;
    271   // Output from NAM
    272   std::vector<std::vector<iplug::sample>> mOutputArray;
    273   // Pointer versions
    274   iplug::sample** mInputPointers = nullptr;
    275   iplug::sample** mOutputPointers = nullptr;
    276 
    277   // Input and output gain
    278   double mInputGain = 1.0;
    279   double mOutputGain = 1.0;
    280 
    281   // Noise gates
    282   dsp::noise_gate::Trigger mNoiseGateTrigger;
    283   dsp::noise_gate::Gain mNoiseGateGain;
    284   // The model actually being used:
    285   std::unique_ptr<ResamplingNAM> mModel;
    286   // And the IR
    287   std::unique_ptr<dsp::ImpulseResponse> mIR;
    288   // Manages switching what DSP is being used.
    289   std::unique_ptr<ResamplingNAM> mStagedModel;
    290   std::unique_ptr<dsp::ImpulseResponse> mStagedIR;
    291   // Flags to take away the modules at a safe time.
    292   std::atomic<bool> mShouldRemoveModel = false;
    293   std::atomic<bool> mShouldRemoveIR = false;
    294 
    295   std::atomic<bool> mNewModelLoadedInDSP = false;
    296   std::atomic<bool> mModelCleared = false;
    297 
    298   // Tone stack modules
    299   std::unique_ptr<dsp::tone_stack::AbstractToneStack> mToneStack;
    300 
    301   // Post-IR filters
    302   recursive_linear_filter::HighPass mHighPass;
    303   //  recursive_linear_filter::LowPass mLowPass;
    304 
    305   // Path to model's config.json or model.nam
    306   WDL_String mNAMPath;
    307   // Path to IR (.wav file)
    308   WDL_String mIRPath;
    309 
    310   WDL_String mHighLightColor{PluginColors::NAM_THEMECOLOR.ToColorCode()};
    311 
    312   std::unordered_map<std::string, double> mNAMParams = {{"Input", 0.0}, {"Output", 0.0}};
    313 
    314   NAMSender mInputSender, mOutputSender;
    315 };