Skip to content

Commit

Permalink
Treble/Middle/Bass EQ knobs (#30)
Browse files Browse the repository at this point in the history
* 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

* Make knobs for tone stack

* Implement filters

* Tune knob settings

* Add RecursiveLienarFilter to VS project files.
Add stdexcept to includes to define std::runtime_error
  • Loading branch information
sdatkinson authored Jan 2, 2023
1 parent c6a9c0d commit 3e204dc
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 6 deletions.
49 changes: 45 additions & 4 deletions NeuralAmpModeler/NeuralAmpModeler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,15 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
mInputPointers(nullptr),
mOutputPointers(nullptr),
mDSP(nullptr),
mStagedDSP(nullptr)
mStagedDSP(nullptr),
mToneBass(),
mToneMid(),
mToneTreble()
{
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, -20.0, 20.0, 0.1);

// try {
Expand Down Expand Up @@ -98,6 +104,9 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
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 bassKnobArea = knobs.GetGridCell(0, kToneBass, 1, kNumParams).GetPadded(-10);
IRECT middleKnobArea = knobs.GetGridCell(0, kToneMid, 1, kNumParams).GetPadded(-10);
IRECT trebleKnobArea = knobs.GetGridCell(0, kToneTreble, 1, kNumParams).GetPadded(-10);
IRECT outputKnobArea =knobs.GetGridCell(0, kOutputLevel, 1, kNumParams).GetPadded(-10);

const auto modelArea = content.GetFromBottom(30).GetMidHPadded(150);
Expand Down Expand Up @@ -158,6 +167,9 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)

// The knobs
pGraphics->AttachControl(new IVKnobControl(inputKnobArea, kInputLevel, "", style));
pGraphics->AttachControl(new IVKnobControl(bassKnobArea, kToneBass, "", style));
pGraphics->AttachControl(new IVKnobControl(middleKnobArea, kToneMid, "", style));
pGraphics->AttachControl(new IVKnobControl(trebleKnobArea, kToneTreble, "", style));
pGraphics->AttachControl(new IVKnobControl(outputKnobArea, kOutputLevel, "", style));

// The meters
Expand Down Expand Up @@ -216,6 +228,7 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)

void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outputs, int nFrames)
{
const int nChans = this->NOutChansConnected();
this->_PrepareBuffers(nFrames);
this->_ProcessInput(inputs, nFrames);
if (mStagedDSP != nullptr)
Expand All @@ -237,7 +250,35 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp
else {
this->_FallbackDSP(nFrames);
}
this->_ProcessOutput(outputs, nFrames);
// Tone stack
const double sampleRate = this->GetSampleRate();
// Translate params from knob 0-10 to dB.
// Tuned ranges based on my ear. E.g. seems treble doesn't need nearly as
// much swing as bass can use.
const double bassGainDB = 4.0 * (this->GetParam(kToneBass)->Value() - 5.0); // +/- 20
const double midGainDB = 3.0 * (this->GetParam(kToneMid)->Value() - 5.0); // +/- 15
const double trebleGainDB = 2.0 * (this->GetParam(kToneTreble)->Value() - 5.0); // +/- 10

const double bassFrequency = 150.0;
const double midFrequency = 425.0;
const double trebleFrequency = 1800.0;
const double bassQuality = 0.707;
// Wider EQ on mid bump up to sound less honky.
const double midQuality = midGainDB < 0.0 ? 1.5 : 0.7;
const double trebleQuality = 0.707;


// Define filter parameters
recursive_linear_filter::LowShelfParams bassParams(sampleRate, bassFrequency, bassQuality, bassGainDB);
recursive_linear_filter::PeakingParams midParams(sampleRate, midFrequency, midQuality, midGainDB);
recursive_linear_filter::HighShelfParams trebleParams(sampleRate, trebleFrequency, trebleQuality, trebleGainDB);
// Apply tone stack
sample** bassPointers = this->mToneBass.Process(this->mOutputPointers, nChans, nFrames, &bassParams);
sample** midPointers = this->mToneMid.Process(bassPointers, nChans, nFrames, &midParams);
sample** treblePointers = this->mToneTreble.Process(midPointers, nChans, nFrames, &trebleParams);

// Let's get outta here
this->_ProcessOutput(treblePointers, outputs, nFrames);
// * Output of input leveling (inputs -> mInputPointers),
// * Output of output leveling (mOutputPointers -> outputs)
this->_UpdateMeters(this->mInputPointers, outputs, nFrames);
Expand Down Expand Up @@ -362,14 +403,14 @@ void NeuralAmpModeler::_ProcessInput(iplug::sample **inputs, const int nFrames)
this->mInputArray[c][s] = gain * inputs[c][s];
}

void NeuralAmpModeler::_ProcessOutput(iplug::sample **outputs, const int nFrames)
void NeuralAmpModeler::_ProcessOutput(iplug::sample** inputs, 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];
outputs[c][s] = gain * inputs[c][s];
}

void NeuralAmpModeler::_SetModelMsg(const WDL_String& modelPath)
Expand Down
12 changes: 11 additions & 1 deletion NeuralAmpModeler/NeuralAmpModeler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "choc_DisableAllWarnings.h"
#include "dsp.h"
#include "choc_ReenableAllWarnings.h"
#include "dsp/RecursiveLinearFilter.h"

#include "IPlug_include_in_plug_hdr.h"

Expand All @@ -13,6 +14,9 @@ const int kNumPresets = 1;
enum EParams
{
kInputLevel = 0,
kToneBass,
kToneMid,
kToneTreble,
kOutputLevel,
kNumParams
};
Expand Down Expand Up @@ -65,7 +69,7 @@ class NeuralAmpModeler final : public iplug::Plugin
// 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);
void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const int nFrames);
// Update level meters
// Called within ProcessBlock().
// Assume _ProcessInput() and _ProcessOutput() were run immediately before.
Expand All @@ -84,6 +88,12 @@ class NeuralAmpModeler final : public iplug::Plugin
// Manages switching what DSP is being used.
std::unique_ptr<DSP> mStagedDSP;

// Tone stack modules
// TODO low shelf, peaking, high shelf
recursive_linear_filter::LowShelf mToneBass;
recursive_linear_filter::Peaking mToneMid;
recursive_linear_filter::HighShelf mToneTreble;

WDL_String mModelPath;

std::unordered_map<std::string, double> mDSPParams = {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion NeuralAmpModeler/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
#define PLUG_DOES_MPE 0
#define PLUG_DOES_STATE_CHUNKS 0
#define PLUG_HAS_UI 1
#define PLUG_WIDTH 500
#define PLUG_WIDTH 600
#define PLUG_HEIGHT 300
#define PLUG_FPS 60
#define PLUG_SHARED_RESOURCES 0
Expand Down
189 changes: 189 additions & 0 deletions NeuralAmpModeler/dsp/RecursiveLinearFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//
// RecursiveLinearFilter.cpp
//
//
// Created by Steven Atkinson on 12/28/22.
//
// See: https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html

#include <algorithm> // std::fill
#include <cmath> // pow, sin
#include <stdexcept>

#include "RecursiveLinearFilter.h"

recursive_linear_filter::Base::Base(const size_t inputDegree,
const size_t outputDegree) :
mOutputPointers(nullptr),
mOutputPointersSize(0),
mInputStart(inputDegree), // 1 is subtracted before first use
mOutputStart(outputDegree)
{
this->mInputCoefficients.resize(inputDegree);
this->mOutputCoefficients.resize(outputDegree);
}

iplug::sample** recursive_linear_filter::Base::Process(iplug::sample** inputs,
const int numChannels,
const int numFrames,
const Params *params)
{
this->_PrepareBuffers(numChannels, numFrames);
params->SetCoefficients(this->mInputCoefficients, this->mOutputCoefficients);
long inputStart=0;
long outputStart=0;
// Degree = longest history
// E.g. if y[n] explicitly depends on x[n-2], then input degree is 3 (n,n-1,n-2).
// NOTE: output degree is never 1 because y[n] is never used to explicitly calculate...itself!
// 0,2,3,... are fine.
const size_t inputDegree = this->_GetInputDegree();
const size_t outputDegree = this->_GetOutputDegree();
for (auto c=0; c<numChannels; c++) {
inputStart = this->mInputStart; // Should be plenty fine
outputStart = this->mOutputStart;
for (auto s=0; s<numFrames; s++) {
double out = 0.0;
// Compute input terms
inputStart -= 1;
if (inputStart < 0)
inputStart = inputDegree - 1;
this->mInputHistory[c][inputStart] = inputs[c][s]; // Store current input
for (auto i=0; i<inputDegree; i++)
out += this->mInputCoefficients[i] * this->mInputHistory[c][(inputStart + i) % inputDegree];

// Output terms
outputStart -= 1;
if (outputStart < 0)
outputStart = outputDegree - 1;
for (auto i=1; i<outputDegree; i++)
out += this->mOutputCoefficients[i] * this->mOutputHistory[c][(outputStart + i) % outputDegree];
// Store the output!
if (outputDegree >= 1)
this->mOutputHistory[c][outputStart] = out;
this->mOutputs[c][s] = out;
}
}
this->mInputStart = inputStart;
this->mOutputStart = outputStart;
return this->GetPointers();
}

iplug::sample** recursive_linear_filter::Base::GetPointers()
{
for (auto c=0; c<this->_GetNumChannels(); c++)
this->mOutputPointers[c] = this->mOutputs[c].data();
return this->mOutputPointers;
}


void recursive_linear_filter::Base::_PrepareBuffers(const int numChannels, const int numFrames)
{
if (this->_GetNumChannels() != numChannels) {
this->mInputHistory.resize(numChannels);
this->mOutputHistory.resize(numChannels);
this->mOutputs.resize(numChannels);
const size_t inputDegree = this->_GetInputDegree();
const size_t outputDegree = this->_GetOutputDegree();
for (auto c=0; c<numChannels; c++) {
this->mInputHistory[c].resize(inputDegree);
this->mOutputHistory[c].resize(outputDegree);
this->mOutputs[c].resize(numFrames);
std::fill(this->mInputHistory[c].begin(), this->mInputHistory[c].end(), 0.0);
std::fill(this->mOutputHistory[c].begin(), this->mOutputHistory[c].end(), 0.0);
}
this->_ResizePointers((size_t) numChannels);
}
}

void recursive_linear_filter::Base::_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;
}

void recursive_linear_filter::LowShelfParams::SetCoefficients(std::vector<double> &inputCoefficients,
std::vector<double> &outputCoefficients) const
{
const double a = pow(10.0, this->mGainDB / 40.0);
const double omega_0 = 2.0 * MATH_PI * this->mFrequency / this->mSampleRate;
const double alpha = sin(omega_0) / (2.0 * this->mQuality);
const double cosw = cos(omega_0);

const double ap = a + 1.0;
const double am = a - 1.0;
const double roota2alpha = 2.0 * sqrt(a) * alpha;

const double b0 = a * (ap - am * cosw + roota2alpha);
const double b1 = 2.0 * a * (am - ap * cosw);
const double b2 = a * (ap - am * cosw - roota2alpha);
const double a0 = ap + am * cosw + roota2alpha;
const double a1 = -2.0 * (am + ap * cosw);
const double a2 = ap + am * cosw - roota2alpha;

inputCoefficients[0] = b0 / a0;
inputCoefficients[1] = b1 / a0;
inputCoefficients[2] = b2 / a0;
// outputCoefficients[0] = 0.0; // Always
// Sign flip due so we add during main loop (cf Eq. (4))
outputCoefficients[1] = -a1 / a0;
outputCoefficients[2] = -a2 / a0;
}

void recursive_linear_filter::PeakingParams::SetCoefficients(std::vector<double> &inputCoefficients,
std::vector<double> &outputCoefficients) const
{
const double a = pow(10.0, this->mGainDB / 40.0);
const double omega_0 = 2.0 * MATH_PI * this->mFrequency / this->mSampleRate;
const double alpha = sin(omega_0) / (2.0 * this->mQuality);
const double cosw = cos(omega_0);

const double b0 = 1.0 + alpha * a;
const double b1 = -2.0 * cosw;
const double b2 = 1.0 - alpha * a;
const double a0 = 1.0 + alpha / a;
const double a1 = -2.0 * cosw;
const double a2 = 1.0 - alpha / a;

inputCoefficients[0] = b0 / a0;
inputCoefficients[1] = b1 / a0;
inputCoefficients[2] = b2 / a0;
// outputCoefficients[0] = 0.0; // Always
// Sign flip due so we add during main loop (cf Eq. (4))
outputCoefficients[1] = -a1 / a0;
outputCoefficients[2] = -a2 / a0;
}

void recursive_linear_filter::HighShelfParams::SetCoefficients(std::vector<double> &inputCoefficients,
std::vector<double> &outputCoefficients) const
{
const double a = pow(10.0, this->mGainDB / 40.0);
const double omega_0 = 2.0 * MATH_PI * this->mFrequency / this->mSampleRate;
const double alpha = sin(omega_0) / (2.0 * this->mQuality);
const double cosw = cos(omega_0);
const double roota2alpha = 2.0 * sqrt(a) * alpha;

const double ap = a + 1.0;
const double am = a - 1.0;

const double b0 = a * (ap + am * cosw + roota2alpha);
const double b1 = -2.0 * a * (am + ap * cosw);
const double b2 = a * (ap + am * cosw - roota2alpha);
const double a0 = ap - am * cosw + roota2alpha;
const double a1 = 2.0 * (am - ap * cosw);
const double a2 = ap - am * cosw - roota2alpha;


inputCoefficients[0] = b0 / a0;
inputCoefficients[1] = b1 / a0;
inputCoefficients[2] = b2 / a0;
// outputCoefficients[0] = 0.0; // Always
// Sign flip due so we add during main loop (cf Eq. (4))
outputCoefficients[1] = -a1 / a0;
outputCoefficients[2] = -a2 / a0;
}
Loading

0 comments on commit 3e204dc

Please sign in to comment.