commit c6a9c0d1794961252d3d1d25de7054902ddd552f
parent 96df3ce30432115ce7e86ac8f3885d61a9381211
Author: Steven Atkinson <[email protected]>
Date: Mon, 2 Jan 2023 10:58:45 -0600
Input level meter (#29)
* Refactor colors to separate header
* Refactor to be ready for second meter. Seems to still work.
* numframes and channels as functions
* Add in input meter control (not upating yet)
* ProcessBlock on inputs
* Update OnReset()
* Transmit data
* Rearrange meters to be vertical on sides of the plugin
Diffstat:
2 files changed, 203 insertions(+), 61 deletions(-)
diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp
@@ -60,12 +60,14 @@ public:
};
NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
-: Plugin(info, MakeConfig(kNumParams, kNumPresets))
+: Plugin(info, MakeConfig(kNumParams, kNumPresets)),
+ mInputPointers(nullptr),
+ mOutputPointers(nullptr),
+ mDSP(nullptr),
+ mStagedDSP(nullptr)
{
- this->mDSP = nullptr;
- this->mStagedDSP = nullptr;
- GetParam(kInputGain)->InitGain("Input", 0.0, -20.0, 20.0, 0.1);
- GetParam(kOutputGain)->InitGain("Output", 0.0, -20.0, 20.0, 0.1);
+ GetParam(kInputLevel)->InitGain("Input", 0.0, -20.0, 20.0, 0.1);
+ GetParam(kOutputLevel)->InitGain("Output", 0.0, -20.0, 20.0, 0.1);
// try {
// this->mDSP = get_hard_dsp();
@@ -90,9 +92,18 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
const IRECT mainArea = b.GetPadded(-20);
const auto content = mainArea.GetPadded(-10);
const auto titleLabel = content.GetFromTop(50);
- const auto knobs = content.GetReducedFromLeft(10).GetReducedFromRight(10).GetMidVPadded(70);
+
+ const float knobHalfPad = 10.0f;
+ const float knobPad = 2.0f * knobHalfPad;
+ const float knobHalfHeight = 70.0f;
+ const auto knobs = content.GetReducedFromLeft(knobPad).GetReducedFromRight(knobPad).GetMidVPadded(knobHalfHeight);
+ IRECT inputKnobArea = knobs.GetGridCell(0, kInputLevel, 1, kNumParams).GetPadded(-10);
+ IRECT outputKnobArea =knobs.GetGridCell(0, kOutputLevel, 1, kNumParams).GetPadded(-10);
+
const auto modelArea = content.GetFromBottom(30).GetMidHPadded(150);
- const auto meterArea = content.GetFromBottom(50).GetFromTop(20);
+ const float meterHalfHeight = 0.5f * 250.0f;
+ const auto inputMeterArea = inputKnobArea.GetFromLeft(knobHalfPad).GetMidHPadded(knobHalfPad).GetMidVPadded(meterHalfHeight).GetTranslated(-knobPad, 0.0f);
+ const auto outputMeterArea = outputKnobArea.GetFromRight(knobHalfPad).GetMidHPadded(knobHalfPad).GetMidVPadded(meterHalfHeight).GetTranslated(knobPad, 0.0f);
const IVStyle style {
true, // Show label
@@ -129,14 +140,14 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
.WithDrawFrame(false)
.WithValueText({30, EAlign::Center, PluginColors::NAM_3})));
-// pGraphics->AttachControl(new IVBakedPresetManagerControl(modelArea, style.WithValueText({DEFAULT_TEXT_SIZE, EVAlign::Middle, COLOR_WHITE})));
+ // pGraphics->AttachControl(new IVBakedPresetManagerControl(modelArea, style.WithValueText({DEFAULT_TEXT_SIZE, EVAlign::Middle, COLOR_WHITE})));
// Model loader button
auto loadModel = [&, pGraphics](IControl* pCaller) {
WDL_String dir;
pGraphics->PromptForDirectory(dir, [&](const WDL_String& fileName, const WDL_String& path){
if (path.GetLength())
- GetDSP(path);
+ _GetDSP(path);
});
};
@@ -145,14 +156,23 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromLeft(30).GetPadded(-2.f), loadModel, folderSVG));
pGraphics->AttachControl(new IVUpdateableLabelControl(modelArea.GetReducedFromLeft(30), "Select model...", style.WithDrawFrame(false).WithValueText(style.valueText.WithVAlign(EVAlign::Middle))), kCtrlTagModelName);
- pGraphics->AttachControl(new IVKnobControl(knobs.GetGridCell(0, 0, 1, kNumParams).GetPadded(-10), kInputGain, "", style));
- pGraphics->AttachControl(new IVKnobControl(knobs.GetGridCell(0, 1, 1, kNumParams).GetPadded(-10), kOutputGain, "", style));
+ // The knobs
+ pGraphics->AttachControl(new IVKnobControl(inputKnobArea, kInputLevel, "", style));
+ pGraphics->AttachControl(new IVKnobControl(outputKnobArea, kOutputLevel, "", style));
- pGraphics->AttachControl(new IVPeakAvgMeterControl(meterArea, "", style.WithWidgetFrac(0.5)
+ // The meters
+ const float meterMin = -60.0f;
+ const float meterMax = 12.0f;
+ pGraphics->AttachControl(new IVPeakAvgMeterControl(inputMeterArea, "", style.WithWidgetFrac(0.5)
+ .WithShowValue(false)
+ .WithColor(kFG, PluginColors::NAM_2), EDirection::Vertical, {}, 0, meterMin, meterMax, {}), kCtrlTagInputMeter)
+ ->As<IVPeakAvgMeterControl<>>()->SetPeakSize(2.0f);
+ pGraphics->AttachControl(new IVPeakAvgMeterControl(outputMeterArea, "", style.WithWidgetFrac(0.5)
.WithShowValue(false)
- .WithColor(kFG, PluginColors::NAM_2), EDirection::Horizontal, {}, 0, -60.f, 12.f, {}), kCtrlTagMeter)
+ .WithColor(kFG, PluginColors::NAM_2), EDirection::Vertical, {}, 0, meterMin, meterMax, {}), kCtrlTagOutputMeter)
->As<IVPeakAvgMeterControl<>>()->SetPeakSize(2.0f);
+ // Help/about box
pGraphics->AttachControl(new IVAboutBoxControl(
new IRolloverCircleSVGButtonControl(mainArea.GetFromTRHC(50, 50).GetCentredInside(20, 20), DefaultClickActionFunc, helpSVG),
new IPanelControl(IRECT(),
@@ -196,37 +216,69 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outputs, int nFrames)
{
- const int nChans = NOutChansConnected();
- const double inputGain = pow(10.0, GetParam(kInputGain)->Value() / 10.0);
- const double outputGain = pow(10.0, GetParam(kOutputGain)->Value() / 10.0);
-
- if (mStagedDSP)
+ this->_PrepareBuffers(nFrames);
+ this->_ProcessInput(inputs, nFrames);
+ if (mStagedDSP != nullptr)
{
// Move from staged to active DSP
mDSP = std::move(mStagedDSP);
mStagedDSP = nullptr;
}
- if (mDSP)
+ if (mDSP != nullptr)
{
- mDSP->process(inputs, outputs, nChans, nFrames, inputGain, outputGain, mDSPParams);
+ // TODO remove input / output gains from here.
+ const int nChans = this->NOutChansConnected();
+ const double inputGain = 1.0;
+ const double outputGain = 1.0;
+ mDSP->process(this->mInputPointers, this->mOutputPointers, nChans, nFrames, inputGain, outputGain, mDSPParams);
mDSP->finalize_(nFrames);
}
- else
- {
- for (auto c = 0; c < nChans; c++)
- {
- for (auto s = 0; s < nFrames; s++)
- {
- outputs[c][s] = inputs[c][s];
- }
- }
+ else {
+ this->_FallbackDSP(nFrames);
}
-
- mSender.ProcessBlock(outputs, nFrames, kCtrlTagMeter);
+ this->_ProcessOutput(outputs, nFrames);
+ // * Output of input leveling (inputs -> mInputPointers),
+ // * Output of output leveling (mOutputPointers -> outputs)
+ this->_UpdateMeters(this->mInputPointers, outputs, nFrames);
+}
+
+bool NeuralAmpModeler::SerializeState(IByteChunk& chunk) const
+{
+ // Model directory (don't serialize the model itself; we'll just load it again when we unserialize)
+ chunk.PutStr(mModelPath.Get());
+ return SerializeParams(chunk);
}
-void NeuralAmpModeler::GetDSP(const WDL_String& modelPath)
+int NeuralAmpModeler::UnserializeState(const IByteChunk& chunk, int startPos)
+{
+ WDL_String dir;
+ startPos = chunk.GetStr(mModelPath, startPos);
+ mDSP = nullptr;
+ int retcode = UnserializeParams(chunk, startPos);
+ if (this->mModelPath.GetLength())
+ this->_GetDSP(this->mModelPath);
+ return retcode;
+}
+
+void NeuralAmpModeler::OnUIOpen()
+{
+ Plugin::OnUIOpen();
+ if (this->mModelPath.GetLength())
+ this->_SetModelMsg(this->mModelPath);
+}
+
+// Private methods ============================================================
+
+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++)
+ this->mOutputArray[c][s] = this->mInputArray[c][s];
+}
+
+void NeuralAmpModeler::_GetDSP(const WDL_String& modelPath)
{
WDL_String previousModelPath;
try {
@@ -248,36 +300,89 @@ void NeuralAmpModeler::GetDSP(const WDL_String& modelPath)
}
}
-void NeuralAmpModeler::_SetModelMsg(const WDL_String& modelPath)
+size_t NeuralAmpModeler::_GetBufferNumChannels() const
{
- auto dspPath = std::filesystem::path(modelPath.Get());
- mModelPath = modelPath;
- std::stringstream ss;
- ss << "Loaded " << dspPath.parent_path().filename();
- SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str());
+ return this->mInputArray.size();
}
-bool NeuralAmpModeler::SerializeState(IByteChunk& chunk) const
+size_t NeuralAmpModeler::_GetBufferNumFrames() const
{
- // Model directory (don't serialize the model itself; we'll just load it again when we unserialize)
- chunk.PutStr(mModelPath.Get());
- return SerializeParams(chunk);
+ if (this->_GetBufferNumChannels() == 0)
+ return 0;
+ return this->mInputArray[0].size();
}
-int NeuralAmpModeler::UnserializeState(const IByteChunk& chunk, int startPos)
+void NeuralAmpModeler::_PrepareBuffers(const int nFrames)
+{
+ const size_t nChans = this->NOutChansConnected();
+ const bool updateChannels = nChans != this->_GetBufferNumChannels();
+ const bool updateFrames = updateChannels || (this->_GetBufferNumFrames() != nFrames);
+// if (!updateChannels && !updateFrames)
+// return;
+
+ if (updateChannels) { // Does channels *and* frames just to be safe.
+ this->_PrepareIOPointers(nChans);
+ this->mInputArray.resize(nChans);
+ this->mOutputArray.resize(nChans);
+ }
+ if (updateFrames) { // and not update channels
+ for (int c=0; c<nChans; c++) {
+ this->mInputArray[c].resize(nFrames);
+ this->mOutputArray[c].resize(nFrames);
+ }
+ }
+ // Would these ever change?
+ for (auto c=0; c<this->mInputArray.size(); c++) {
+ this->mInputPointers[c] = this->mInputArray[c].data();
+ this->mOutputPointers[c] = this->mOutputArray[c].data();
+ }
+}
+
+void NeuralAmpModeler::_PrepareIOPointers(const size_t nChans)
{
- WDL_String dir;
- startPos = chunk.GetStr(mModelPath, startPos);
- mDSP = nullptr;
- int retcode = UnserializeParams(chunk, startPos);
- if (this->mModelPath.GetLength())
- this->GetDSP(this->mModelPath);
- return retcode;
+ 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");
}
-void NeuralAmpModeler::OnUIOpen()
+void NeuralAmpModeler::_ProcessInput(iplug::sample **inputs, const int nFrames)
+{
+ const double gain = pow(10.0, GetParam(kInputLevel)->Value() / 10.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];
+}
+
+void NeuralAmpModeler::_ProcessOutput(iplug::sample **outputs, const int nFrames)
+{
+ const double gain = pow(10.0, GetParam(kOutputLevel)->Value() / 10.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] = gain * this->mOutputArray[c][s];
+}
+
+void NeuralAmpModeler::_SetModelMsg(const WDL_String& modelPath)
+{
+ auto dspPath = std::filesystem::path(modelPath.Get());
+ mModelPath = modelPath;
+ std::stringstream ss;
+ ss << "Loaded " << dspPath.parent_path().filename();
+ SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str());
+}
+
+void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, sample** outputPointer, const int nFrames)
{
- Plugin::OnUIOpen();
- if (this->mModelPath.GetLength())
- this->_SetModelMsg(this->mModelPath);
+ this->mInputSender.ProcessBlock(inputPointer, nFrames, kCtrlTagInputMeter);
+ this->mOutputSender.ProcessBlock(outputPointer, nFrames, kCtrlTagOutputMeter);
}
diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h
@@ -12,15 +12,16 @@ const int kNumPresets = 1;
enum EParams
{
- kInputGain = 0,
- kOutputGain,
+ kInputLevel = 0,
+ kOutputLevel,
kNumParams
};
enum ECtrlTags
{
kCtrlTagModelName = 0,
- kCtrlTagMeter,
+ kCtrlTagInputMeter,
+ kCtrlTagOutputMeter,
kNumCtrlTags
};
@@ -30,19 +31,54 @@ public:
NeuralAmpModeler(const iplug::InstanceInfo& info);
void ProcessBlock(iplug::sample** inputs, iplug::sample** outputs, int nFrames) override;
- void OnReset() override { mSender.Reset(GetSampleRate()); }
- void OnIdle() override { mSender.TransmitData(*this); }
+ void OnReset() override {
+ const auto sampleRate = this->GetSampleRate();
+ this->mInputSender.Reset(sampleRate);
+ this->mOutputSender.Reset(sampleRate);
+ }
+ void OnIdle() override {
+ this->mInputSender.TransmitData(*this);
+ this->mOutputSender.TransmitData(*this);
+ }
bool SerializeState(IByteChunk& chunk) const override;
int UnserializeState(const IByteChunk& chunk, int startPos) override;
void OnUIOpen() override;
private:
- void GetDSP(const WDL_String& dspPath);
+ // Fallback that just copies inputs to outputs if mDSP doesn't hold a model.
+ void _FallbackDSP(const int nFrames);
+ // Gets a new DSP object and stores it to mStagedDSP
+ void _GetDSP(const WDL_String& dspPath);
+ // Sizes based on mInputArray
+ size_t _GetBufferNumChannels() const;
+ size_t _GetBufferNumFrames() const;
+ // Update the message about which model is loaded.
void _SetModelMsg(const WDL_String& dspPath);
bool _HaveModel() const {
return this->mDSP != nullptr;
};
+ // Prepare the input & output buffers
+ void _PrepareBuffers(const int nFrames);
+ // 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);
+ // Copy the output to the output buffer, applying output level.
+ void _ProcessOutput(iplug::sample** outputs, const int nFrames);
+ // Update level meters
+ // Called within ProcessBlock().
+ // Assume _ProcessInput() and _ProcessOutput() were run immediately before.
+ void _UpdateMeters(sample** inputPointer, sample** outputPointer, const int nFrames);
+
+ // Input arrays
+ std::vector<std::vector<sample>> mInputArray;
+ // Output arrays
+ std::vector<std::vector<sample>> mOutputArray;
+ // Pointer versions
+ sample** mInputPointers;
+ sample** mOutputPointers;
+
// The DSP actually being used:
std::unique_ptr<DSP> mDSP;
// Manages switching what DSP is being used.
@@ -55,5 +91,6 @@ private:
{ "Output", 0.0 }
};
- iplug::IPeakAvgSender<> mSender;
+ iplug::IPeakAvgSender<> mInputSender;
+ iplug::IPeakAvgSender<> mOutputSender;
};