Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Output calibration #531

Merged
merged 4 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 53 additions & 38 deletions NeuralAmpModeler/NeuralAmpModeler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ const IVStyle style =
DEFAULT_SHADOW_OFFSET,
DEFAULT_WIDGET_FRAC,
DEFAULT_WIDGET_ANGLE};

const IVStyle titleStyle =
DEFAULT_STYLE.WithValueText(IText(30, COLOR_WHITE, "Michroma-Regular")).WithDrawFrame(false).WithShadowOffset(2.f);
const IVStyle radioButtonStyle =
style
.WithColor(EVColor::kON, PluginColors::NAM_THEMECOLOR) // Pressed buttons and their labels
.WithColor(EVColor::kOFF, PluginColors::NAM_THEMECOLOR.WithOpacity(0.1f)) // Unpressed buttons
.WithColor(EVColor::kX1, PluginColors::NAM_THEMECOLOR.WithOpacity(0.6f)); // Unpressed buttons' labels

EMsgBoxResult _ShowMessageBox(iplug::igraphics::IGraphics* pGraphics, const char* str, const char* caption,
EMsgBoxType type)
Expand All @@ -79,10 +83,9 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
GetParam(kNoiseGateThreshold)->InitGain("Threshold", -80.0, -100.0, 0.0, 0.1);
GetParam(kNoiseGateActive)->InitBool("NoiseGateActive", true);
GetParam(kEQActive)->InitBool("ToneStack", true);
GetParam(kOutNorm)->InitBool("OutNorm", true);
GetParam(kOutputMode)->InitEnum("OutputMode", 1, {"Raw", "Normalized", "Calibrated"}); // TODO DRY w/ control
GetParam(kIRToggle)->InitBool("IRToggle", true);
GetParam(kCalibrateInput)->InitBool("CalibrateInput", false);
// TODO Double, label "dBu"
GetParam(kInputCalibrationLevel)->InitDouble("InputCalibrationLevel", 12.5, -30.0, 30.0, 0.1, "dBu");

mNoiseGateTrigger.AddListener(&mNoiseGateGain);
Expand Down Expand Up @@ -227,8 +230,6 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
pGraphics->AttachControl(
new NAMSwitchControl(ngToggleArea, kNoiseGateActive, "Noise Gate", style, switchHandleBitmap));
pGraphics->AttachControl(new NAMSwitchControl(eqToggleArea, kEQActive, "EQ", style, switchHandleBitmap));
pGraphics->AttachControl(
new NAMSwitchControl(outNormToggleArea, kOutNorm, "Normalize", style, switchHandleBitmap), kCtrlTagOutNorm);

// The knobs
pGraphics->AttachControl(new NAMKnobControl(inputKnobArea, kInputLevel, "", style, knobBackgroundBitmap));
Expand All @@ -254,8 +255,8 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
gearSVG));

pGraphics
->AttachControl(new NAMSettingsPageControl(
b, backgroundBitmap, inputLevelBackgroundBitmap, switchHandleBitmap, crossSVG, style),
->AttachControl(new NAMSettingsPageControl(b, backgroundBitmap, inputLevelBackgroundBitmap, switchHandleBitmap,
crossSVG, style, radioButtonStyle),
kCtrlTagSettingsBox)
->Hide(true);

Expand Down Expand Up @@ -312,20 +313,13 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp

if (mModel != nullptr)
{
// TODO multi-channel processing; Issue
// Make sure it's multi-threaded or else this won't perform well!
mModel->process(triggerOutput[0], mOutputPointers[0], nFrames);
// Normalize loudness
if (GetParam(kOutNorm)->Value())
{
_NormalizeModelOutput(mOutputPointers, numChannelsInternal, numFrames);
}
}
else
{
_FallbackDSP(triggerOutput, mOutputPointers, numChannelsInternal, numFrames);
}
// Apply the noise gate
// Apply the noise gate after the NAM
sample** gateGainOutput =
noiseGateActive ? mNoiseGateGain.Process(mOutputPointers, numChannelsInternal, numFrames) : mOutputPointers;

Expand Down Expand Up @@ -386,7 +380,6 @@ void NeuralAmpModeler::OnIdle()
{
if (auto* pGraphics = GetUI())
{
pGraphics->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(!mModel->HasLoudness());
_UpdateControlsFromModel();
mNewModelLoadedInDSP = false;
}
Expand All @@ -395,7 +388,8 @@ void NeuralAmpModeler::OnIdle()
{
if (auto* pGraphics = GetUI())
{
pGraphics->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(false);
// FIXME -- need to disable only the "normalized" model
// pGraphics->GetControlWithTag(kCtrlTagOutputMode)->SetDisabled(false);
static_cast<NAMSettingsPageControl*>(pGraphics->GetControlWithTag(kCtrlTagSettingsBox))->ClearModelInfo();
mModelCleared = false;
}
Expand Down Expand Up @@ -478,6 +472,9 @@ void NeuralAmpModeler::OnParamChange(int paramIdx)
case kCalibrateInput:
case kInputCalibrationLevel:
case kInputLevel: _SetInputGain(); break;
// Changes to the output gain
case kOutputLevel:
case kOutputMode: _SetOutputGain(); break;
// Tone stack:
case kToneBass: mToneStack->SetParam("bass", GetParam(paramIdx)->Value()); break;
case kToneMid: mToneStack->SetParam("middle", GetParam(paramIdx)->Value()); break;
Expand Down Expand Up @@ -563,6 +560,7 @@ void NeuralAmpModeler::_ApplyDSPStaging()
mModelCleared = true;
_UpdateLatency();
_SetInputGain();
_SetOutputGain();
}
if (mShouldRemoveIR)
{
Expand All @@ -573,12 +571,12 @@ void NeuralAmpModeler::_ApplyDSPStaging()
// Move things from staged to live
if (mStagedModel != nullptr)
{
// Move from staged to active DSP
mModel = std::move(mStagedModel);
mStagedModel = nullptr;
mNewModelLoadedInDSP = true;
_UpdateLatency();
_SetInputGain();
_SetOutputGain();
}
if (mStagedIR != nullptr)
{
Expand Down Expand Up @@ -613,24 +611,6 @@ void NeuralAmpModeler::_FallbackDSP(iplug::sample** inputs, iplug::sample** outp
mOutputArray[c][s] = mInputArray[c][s];
}

void NeuralAmpModeler::_NormalizeModelOutput(iplug::sample** buffer, const size_t numChannels, const size_t numFrames)
{
if (!mModel)
return;
if (!mModel->HasLoudness())
return;
const double loudness = mModel->GetLoudness();
const double targetLoudness = -18.0;
const double gain = pow(10.0, (targetLoudness - loudness) / 20.0);
for (size_t c = 0; c < numChannels; c++)
{
for (size_t f = 0; f < numFrames; f++)
{
buffer[c][f] *= gain;
}
}
}

void NeuralAmpModeler::_ResetModelAndIR(const double sampleRate, const int maxBlockSize)
{
// Model
Expand Down Expand Up @@ -675,6 +655,37 @@ void NeuralAmpModeler::_SetInputGain()
mInputGain = DBToAmp(inputGainDB);
}

void NeuralAmpModeler::_SetOutputGain()
{
double gainDB = GetParam(kOutputLevel)->Value();
if (mModel != nullptr)
{
const int outputMode = GetParam(kOutputMode)->Int();
switch (outputMode)
{
case 1: // Normalized
if (mModel->HasLoudness())
{
const double loudness = mModel->GetLoudness();
const double targetLoudness = -18.0;
gainDB += (targetLoudness - loudness);
}
break;
case 2: // Calibrated
if (mModel->HasOutputLevel())
{
const double inputLevel = GetParam(kInputCalibrationLevel)->Value();
const double outputLevel = mModel->GetOutputLevel();
gainDB += (outputLevel - inputLevel);
}
break;
case 0: // Raw
default: break;
}
}
mOutputGain = DBToAmp(gainDB);
}

std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath)
{
WDL_String previousNAMPath = mNAMPath;
Expand Down Expand Up @@ -830,7 +841,7 @@ void NeuralAmpModeler::_ProcessInput(iplug::sample** inputs, const size_t nFrame
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 double gain = mOutputGain;
// Assume _PrepareBuffers() was already called
if (nChansIn != 1)
throw std::runtime_error("Plugin is supposed to process in mono.");
Expand Down Expand Up @@ -940,9 +951,13 @@ void NeuralAmpModeler::_UpdateControlsFromModel()
static_cast<NAMSettingsPageControl*>(pGraphics->GetControlWithTag(kCtrlTagSettingsBox))->SetModelInfo(modelInfo);

const bool disableInputCalibrationControls = !mModel->HasInputLevel();
pGraphics->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(!mModel->HasLoudness());
pGraphics->GetControlWithTag(kCtrlTagCalibrateInput)->SetDisabled(disableInputCalibrationControls);
pGraphics->GetControlWithTag(kCtrlTagInputCalibrationLevel)->SetDisabled(disableInputCalibrationControls);
{
auto* c = static_cast<OutputModeControl*>(pGraphics->GetControlWithTag(kCtrlTagOutputMode));
c->SetNormalizedDisable(!mModel->HasLoudness());
c->SetCalibratedDisable(!mModel->HasOutputLevel());
}
}
}

Expand Down
12 changes: 6 additions & 6 deletions NeuralAmpModeler/NeuralAmpModeler.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ enum EParams
// The rest is fine though.
kNoiseGateActive,
kEQActive,
kOutNorm,
kIRToggle,
// Input calibration
kCalibrateInput,
kInputCalibrationLevel,
kOutputMode,
kNumParams
};

Expand All @@ -56,7 +56,7 @@ enum ECtrlTags
kCtrlTagInputMeter,
kCtrlTagOutputMeter,
kCtrlTagSettingsBox,
kCtrlTagOutNorm,
kCtrlTagOutputMode,
kCtrlTagCalibrateInput,
kCtrlTagInputCalibrationLevel,
kNumCtrlTags
Expand Down Expand Up @@ -216,8 +216,6 @@ class NeuralAmpModeler final : public iplug::Plugin
size_t _GetBufferNumChannels() const;
size_t _GetBufferNumFrames() const;
void _InitToneStack();
// Apply the normalization for the model output (if possible)
void _NormalizeModelOutput(iplug::sample** buffer, const size_t numChannels, const size_t numFrames);
// Loads a NAM model and stores it to mStagedNAM
// Returns an empty string on success, or an error message on failure.
std::string _StageModel(const WDL_String& dspFile);
Expand All @@ -244,6 +242,7 @@ class NeuralAmpModeler final : public iplug::Plugin
void _ResetModelAndIR(const double sampleRate, const int maxBlockSize);

void _SetInputGain();
void _SetOutputGain();

// Unserialize current-version plug-in data:
int _UnserializeStateCurrent(const iplug::IByteChunk& chunk, int startPos);
Expand Down Expand Up @@ -273,8 +272,9 @@ class NeuralAmpModeler final : public iplug::Plugin
iplug::sample** mInputPointers = nullptr;
iplug::sample** mOutputPointers = nullptr;

// Input and (soon) output gain
iplug::sample mInputGain = 1.0;
// Input and output gain
double mInputGain = 1.0;
double mOutputGain = 1.0;

// Noise gates
dsp::noise_gate::Trigger mNoiseGateTrigger;
Expand Down
49 changes: 45 additions & 4 deletions NeuralAmpModeler/NeuralAmpModelerControls.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ class NAMFileBrowserControl : public IDirBrowseControlBase
auto clearFileFunc = [&](IControl* pCaller) {
pCaller->GetDelegate()->SendArbitraryMsgFromUI(mClearMsgTag);
mFileNameControl->SetLabelAndTooltip(mDefaultLabelStr.Get());
pCaller->GetUI()->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(false);
// FIXME disabling output mode...
// pCaller->GetUI()->GetControlWithTag(kCtrlTagOutputMode)->SetDisabled(false);
};

auto chooseFileFunc = [&, loadFileFunc](IControl* pCaller) {
Expand Down Expand Up @@ -555,17 +556,50 @@ class ModelInfoControl : public IContainerBaseWithNamedChildren
bool mHasInfo = false;
};

class OutputModeControl : public IVRadioButtonControl
{
public:
OutputModeControl(const IRECT& bounds, int paramIdx, const IVStyle& style, float buttonSize)
: IVRadioButtonControl(
bounds, paramIdx, {}, "Output Mode", style, EVShape::Ellipse, EDirection::Vertical, buttonSize) {};

void SetNormalizedDisable(const bool disable)
{
// HACK non-DRY string and hard-coded indices
std::stringstream ss;
ss << "Normalized";
if (disable)
{
ss << " [Not supported by model]";
}
mTabLabels.Get(1)->Set(ss.str().c_str());
};
void SetCalibratedDisable(const bool disable)
{
// HACK non-DRY string and hard-coded indices
std::stringstream ss;
ss << "Calibrated";
if (disable)
{
ss << " [Not supported by model]";
}
mTabLabels.Get(2)->Set(ss.str().c_str());
};
};

class NAMSettingsPageControl : public IContainerBaseWithNamedChildren
{
public:
NAMSettingsPageControl(const IRECT& bounds, const IBitmap& bitmap, const IBitmap& inputLevelBackgroundBitmap,
const IBitmap& switchBitmap, ISVG closeSVG, const IVStyle& style)
const IBitmap& switchBitmap, ISVG closeSVG, const IVStyle& style,
const IVStyle& radioButtonStyle)
: IContainerBaseWithNamedChildren(bounds)
, mAnimationTime(0)
, mBitmap(bitmap)
, mInputLevelBackgroundBitmap(inputLevelBackgroundBitmap)
, mSwitchBitmap(switchBitmap)
, mStyle(style)
, mRadioButtonStyle(radioButtonStyle)
, mCloseSVG(closeSVG)
{
mIgnoreMouse = false;
Expand Down Expand Up @@ -645,7 +679,7 @@ class NAMSettingsPageControl : public IContainerBaseWithNamedChildren
const float width = titleArea.W();
const auto inputOutputArea = titleArea.GetFromBottom(height).GetTranslated(0.0f, height);
const auto inputArea = inputOutputArea.GetFromLeft(0.5f * width);
// const auto outputArea = inputOutputArea.GetFromRight(0.5f * width);
const auto outputArea = inputOutputArea.GetFromRight(0.5f * width);

const float knobWidth = 87.0f; // HACK based on looking at the main page knobs.
const auto inputLevelArea =
Expand All @@ -662,7 +696,12 @@ class NAMSettingsPageControl : public IContainerBaseWithNamedChildren
new NAMSwitchControl(inputSwitchArea, kCalibrateInput, "Calibrate Input", mStyle, mSwitchBitmap),
mControlNames.calibrateInput, kCtrlTagCalibrateInput);

// TODO output--raw, normalized, calibrated
// Same-ish height & width as input controls
const auto outputRadioArea = outputArea.GetFromBottom(
1.1f * (inputLevelArea.H() + inputSwitchArea.H())); // .GetMidHPadded(0.55f * knobWidth);
const float buttonSize = 10.0f;
AddNamedChildControl(new OutputModeControl(outputRadioArea, kOutputMode, mRadioButtonStyle, buttonSize),
mControlNames.outputMode, kCtrlTagOutputMode);
}

const float halfWidth = PLUG_WIDTH / 2.0f - pad;
Expand Down Expand Up @@ -694,6 +733,7 @@ class NAMSettingsPageControl : public IContainerBaseWithNamedChildren
IBitmap mInputLevelBackgroundBitmap;
IBitmap mSwitchBitmap;
IVStyle mStyle;
IVStyle mRadioButtonStyle;
ISVG mCloseSVG;
int mAnimationTime = 200;
bool mWillHide = false;
Expand All @@ -708,6 +748,7 @@ class NAMSettingsPageControl : public IContainerBaseWithNamedChildren
const std::string close = "Close";
const std::string inputCalibrationLevel = "InputCalibrationLevel";
const std::string modelInfo = "ModelInfo";
const std::string outputMode = "OutputMode";
const std::string title = "Title";
} mControlNames;

Expand Down
Loading