Skip to content

Commit 13f066e

Browse files
authored
[ENHANCEMENT] Support for different sample rates (#397)
* Update on core, NAM models expose expected sample rates * Add resample files to XCode project * Implement structure of the resampler. Dummy algorithm in place * Implement placeholder for finalize_() * Implement dummy resampling that transfers data to encapsulated buffer arrays. * Rearrange files * Format * Update iPlug2 * Implement resampling using iPlug2 tools * Fix XCode project * Format * Fix iPlug2 commit, add aax and app filters to VS solution * Update core
1 parent a5725c1 commit 13f066e

10 files changed

+200
-20
lines changed

NeuralAmpModeler/NeuralAmpModeler.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const IVStyle style =
5353
const IVStyle titleStyle =
5454
DEFAULT_STYLE.WithValueText(IText(30, COLOR_WHITE, "Michroma-Regular")).WithDrawFrame(false).WithShadowOffset(2.f);
5555

56+
5657
NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info)
5758
: Plugin(info, MakeConfig(kNumParams, kNumPresets))
5859
{
@@ -382,7 +383,7 @@ void NeuralAmpModeler::OnReset()
382383
mOutputSender.Reset(sampleRate);
383384
mCheckSampleRateWarning = true;
384385
// If there is a model or IR loaded, they need to be checked for resampling.
385-
_ResampleModelAndIR();
386+
_ResetModelAndIR(sampleRate, GetBlockSize());
386387
}
387388

388389
void NeuralAmpModeler::OnIdle()
@@ -612,11 +613,17 @@ void NeuralAmpModeler::_NormalizeModelOutput(iplug::sample** buffer, const size_
612613
}
613614
}
614615

615-
void NeuralAmpModeler::_ResampleModelAndIR()
616+
void NeuralAmpModeler::_ResetModelAndIR(const double sampleRate, const int maxBlockSize)
616617
{
617-
const auto sampleRate = GetSampleRate();
618618
// Model
619-
// TODO
619+
if (mStagedModel != nullptr)
620+
{
621+
mStagedModel->Reset(sampleRate, maxBlockSize);
622+
}
623+
else if (mModel != nullptr)
624+
{
625+
mModel->Reset(sampleRate, maxBlockSize);
626+
}
620627

621628
// IR
622629
if (mStagedIR != nullptr)
@@ -645,7 +652,10 @@ std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath)
645652
try
646653
{
647654
auto dspPath = std::filesystem::u8path(modelPath.Get());
648-
mStagedModel = nam::get_dsp(dspPath);
655+
std::unique_ptr<nam::DSP> model = nam::get_dsp(dspPath);
656+
std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move(model), GetSampleRate());
657+
temp->Reset(GetSampleRate(), GetBlockSize());
658+
mStagedModel = std::move(temp);
649659
mNAMPath = modelPath;
650660
SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, mNAMPath.GetLength(), mNAMPath.Get());
651661
}

NeuralAmpModeler/NeuralAmpModeler.h

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include "IPlug_include_in_plug_hdr.h"
1111
#include "ISender.h"
12+
#define M_PI 3.141592653589793238462643383279 // Needed by NonIntegerResampler.h
13+
#include "NonIntegerResampler.h"
1214

1315
const int kNumPresets = 1;
1416
// The plugin is mono inside
@@ -68,6 +70,137 @@ enum EMsgTags
6870
kNumMsgTags
6971
};
7072

73+
// Get the sample rate of a NAM model.
74+
// Sometimes, the model doesn't know its own sample rate; this wrapper guesses 48k based on the way that most
75+
// people have used NAM in the past.
76+
double GetNAMSampleRate(const std::unique_ptr<nam::DSP>& model)
77+
{
78+
// Some models are from when we didn't have sample rate in the model.
79+
// For those, this wraps with the assumption that they're 48k models, which is probably true.
80+
const double assumedSampleRate = 48000.0;
81+
const double reportedEncapsulatedSampleRate = model->GetExpectedSampleRate();
82+
const double encapsulatedSampleRate =
83+
reportedEncapsulatedSampleRate <= 0.0 ? assumedSampleRate : reportedEncapsulatedSampleRate;
84+
return encapsulatedSampleRate;
85+
};
86+
87+
class ResamplingNAM : public nam::DSP
88+
{
89+
public:
90+
// Resampling wrapper around the NAM models
91+
ResamplingNAM(std::unique_ptr<nam::DSP> encapsulated, const double expected_sample_rate)
92+
: nam::DSP(expected_sample_rate)
93+
, mEncapsulated(std::move(encapsulated))
94+
, mResampler(GetNAMSampleRate(mEncapsulated), iplug::ESRCMode::kLancsoz)
95+
{
96+
// Assign the encapsulated object's processing function to this object's member so that the resampler can use it:
97+
auto ProcessBlockFunc = [&](NAM_SAMPLE** input, NAM_SAMPLE** output, int numFrames) {
98+
mEncapsulated->process(input[0], output[0], numFrames);
99+
mEncapsulated->finalize_(numFrames);
100+
};
101+
mBlockProcessFunc = ProcessBlockFunc;
102+
103+
// Get the other information from the encapsulated NAM so that we can tell the outside world about what we're
104+
// holding.
105+
if (mEncapsulated->HasLoudness())
106+
SetLoudness(mEncapsulated->GetLoudness());
107+
108+
// NOTE: prewarm samples doesn't mean anything--we can prewarm the encapsulated model as it likes and be good to
109+
// go.
110+
// _prewarm_samples = 0;
111+
112+
// And be ready
113+
int maxBlockSize = 2048; // Conservative
114+
Reset(expected_sample_rate, maxBlockSize);
115+
};
116+
117+
~ResamplingNAM() = default;
118+
119+
void prewarm() override { mEncapsulated->prewarm(); };
120+
121+
void process(NAM_SAMPLE* input, NAM_SAMPLE* output, const int num_frames) override
122+
{
123+
if (!mFinalized)
124+
throw std::runtime_error("Processing was called before the last block was finalized!");
125+
if (num_frames > mMaxExternalBlockSize)
126+
// We can afford to be careful
127+
throw std::runtime_error("More frames were provided than the max expected!");
128+
129+
if (GetExpectedSampleRate() == GetEncapsulatedSampleRate())
130+
{
131+
mEncapsulated->process(input, output, num_frames);
132+
mEncapsulated->finalize_(num_frames);
133+
}
134+
else
135+
{
136+
mResampler.ProcessBlock(&input, &output, num_frames, mBlockProcessFunc);
137+
}
138+
139+
// Prepare for external call to .finalize_()
140+
lastNumExternalFramesProcessed = num_frames;
141+
mFinalized = false;
142+
};
143+
144+
void finalize_(const int num_frames)
145+
{
146+
if (mFinalized)
147+
throw std::runtime_error("Call to ResamplingNAM.finalize_() when the object is already in a finalized state!");
148+
if (num_frames != lastNumExternalFramesProcessed)
149+
throw std::runtime_error(
150+
"finalize_() called on ResamplingNAM with a different number of frames from what was just processed. Something "
151+
"is probably going wrong.");
152+
153+
// We don't actually do anything--it was taken care of during BlockProcessFunc()!
154+
155+
// prepare for next call to `.process()`
156+
mFinalized = true;
157+
};
158+
159+
void Reset(const double sampleRate, const int maxBlockSize)
160+
{
161+
mExpectedSampleRate = sampleRate;
162+
mMaxExternalBlockSize = maxBlockSize;
163+
mResampler.Reset(sampleRate, maxBlockSize);
164+
165+
// Allocations in the encapsulated model (HACK)
166+
// Stolen some code from the resampler; it'd be nice to have these exposed as methods? :)
167+
const double mUpRatio = sampleRate / GetEncapsulatedSampleRate();
168+
const auto maxEncapsulatedBlockSize = static_cast<int>(std::ceil(static_cast<double>(maxBlockSize) / mUpRatio));
169+
std::vector<NAM_SAMPLE> input, output;
170+
for (int i = 0; i < maxEncapsulatedBlockSize; i++)
171+
input.push_back((NAM_SAMPLE)0.0);
172+
output.resize(maxEncapsulatedBlockSize); // Doesn't matter what's in here
173+
mEncapsulated->process(input.data(), output.data(), maxEncapsulatedBlockSize);
174+
mEncapsulated->finalize_(maxEncapsulatedBlockSize);
175+
176+
mFinalized = true; // prepare for `.process()`
177+
};
178+
179+
// So that we can let the world know if we're resampling (useful for debugging)
180+
double GetEncapsulatedSampleRate() const { return GetNAMSampleRate(mEncapsulated); };
181+
182+
private:
183+
// The encapsulated NAM
184+
std::unique_ptr<nam::DSP> mEncapsulated;
185+
// The processing for NAM is a little weird--there's a call to .finalize_() that's expected.
186+
// This flag makes sure that the NAM sees alternating instances of .process() and .finalize_()
187+
// A value of `true` means that we expect the ResamplingNAM object to see .process() next;
188+
// `false` means we expect .finalize_() next.
189+
bool mFinalized = true;
190+
191+
// The resampling wrapper
192+
iplug::NonIntegerResampler<NAM_SAMPLE, 1> mResampler;
193+
194+
// Used to check that we don't get too large a block to process.
195+
int mMaxExternalBlockSize = 0;
196+
// Keep track of how many frames were processed so that we can be sure that finalize_() is being used correctly.
197+
// This is kind of hacky, but I'm not sure I want to rethink the core right now.
198+
int lastNumExternalFramesProcessed = -1;
199+
200+
// This function is defined to conform to the interface expected by the iPlug2 resampler.
201+
std::function<void(NAM_SAMPLE**, NAM_SAMPLE**, int)> mBlockProcessFunc;
202+
};
203+
71204
class NeuralAmpModeler final : public iplug::Plugin
72205
{
73206
public:
@@ -128,8 +261,8 @@ class NeuralAmpModeler final : public iplug::Plugin
128261
// :param nChansOut: Out to external
129262
void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const size_t nFrames, const size_t nChansIn,
130263
const size_t nChansOut);
131-
// Checks the loaded model and IR against the current sample rate and resamples them if needed
132-
void _ResampleModelAndIR();
264+
// Resetting for models and IRs, called by OnReset
265+
void _ResetModelAndIR(const double sampleRate, const int maxBlockSize);
133266

134267
// Update level meters
135268
// Called within ProcessBlock().
@@ -151,11 +284,11 @@ class NeuralAmpModeler final : public iplug::Plugin
151284
dsp::noise_gate::Trigger mNoiseGateTrigger;
152285
dsp::noise_gate::Gain mNoiseGateGain;
153286
// The model actually being used:
154-
std::unique_ptr<nam::DSP> mModel;
287+
std::unique_ptr<ResamplingNAM> mModel;
155288
// And the IR
156289
std::unique_ptr<dsp::ImpulseResponse> mIR;
157290
// Manages switching what DSP is being used.
158-
std::unique_ptr<nam::DSP> mStagedModel;
291+
std::unique_ptr<ResamplingNAM> mStagedModel;
159292
std::unique_ptr<dsp::ImpulseResponse> mStagedIR;
160293
// Flags to take away the modules at a safe time.
161294
std::atomic<bool> mShouldRemoveModel = false;

NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,8 @@
491491
<ClInclude Include="..\..\iPlug2\IPlug\AAX\IPlugAAX_Parameters.h" />
492492
<ClInclude Include="..\..\iPlug2\IPlug\AAX\IPlugAAX_TaperDelegate.h" />
493493
<ClInclude Include="..\..\iPlug2\IPlug\AAX\IPlugAAX.h" />
494+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\LanczosResampler.h" />
495+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\NonIntegerResampler.h" />
494496
<ClInclude Include="..\..\iPlug2\IPlug\IPlugAPIBase.h" />
495497
<ClInclude Include="..\..\iPlug2\IPlug\IPlugConstants.h" />
496498
<ClInclude Include="..\..\iPlug2\IPlug\IPlugDelegate_select.h" />

NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj.filters

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,12 @@
290290
<ClInclude Include="..\AudioDSPTools\dsp\wav.h">
291291
<Filter>dsp</Filter>
292292
</ClInclude>
293+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\NonIntegerResampler.h">
294+
<Filter>IPlug\Extras</Filter>
295+
</ClInclude>
296+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\LanczosResampler.h">
297+
<Filter>IPlug\Extras</Filter>
298+
</ClInclude>
293299
</ItemGroup>
294300
<ItemGroup>
295301
<Filter Include="resources">
@@ -322,6 +328,9 @@
322328
<Filter Include="dsp">
323329
<UniqueIdentifier>{29388c48-c6f4-4b62-9284-b09d44ad1e40}</UniqueIdentifier>
324330
</Filter>
331+
<Filter Include="IPlug\Extras">
332+
<UniqueIdentifier>{db86886b-3f03-4c59-acdb-b4a137147b3c}</UniqueIdentifier>
333+
</Filter>
325334
</ItemGroup>
326335
<ItemGroup>
327336
<ResourceCompile Include="..\resources\main.rc">

NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@
302302
<ClInclude Include="..\..\iPlug2\IGraphics\Platforms\IGraphicsWin.h" />
303303
<ClInclude Include="..\..\iPlug2\IPlug\APP\IPlugAPP.h" />
304304
<ClInclude Include="..\..\iPlug2\IPlug\APP\IPlugAPP_host.h" />
305+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\LanczosResampler.h" />
306+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\NonIntegerResampler.h" />
305307
<ClInclude Include="..\..\iPlug2\IPlug\IPlugAPIBase.h" />
306308
<ClInclude Include="..\..\iPlug2\IPlug\IPlugConstants.h" />
307309
<ClInclude Include="..\..\iPlug2\IPlug\IPlugEditorDelegate.h" />

NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj.filters

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,12 @@
338338
<ClInclude Include="..\AudioDSPTools\dsp\wav.h">
339339
<Filter>dsp</Filter>
340340
</ClInclude>
341+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\NonIntegerResampler.h">
342+
<Filter>IPlug\Extras</Filter>
343+
</ClInclude>
344+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\LanczosResampler.h">
345+
<Filter>IPlug\Extras</Filter>
346+
</ClInclude>
341347
</ItemGroup>
342348
<ItemGroup>
343349
<Filter Include="resources">
@@ -376,6 +382,9 @@
376382
<Filter Include="NAM">
377383
<UniqueIdentifier>{3adb1e7a-68f8-4b41-8563-9bcf2bb0c8da}</UniqueIdentifier>
378384
</Filter>
385+
<Filter Include="IPlug\Extras">
386+
<UniqueIdentifier>{91ab74a9-d5f2-42db-9c8f-3bef7ee22bd3}</UniqueIdentifier>
387+
</Filter>
379388
</ItemGroup>
380389
<ItemGroup>
381390
<ResourceCompile Include="..\resources\main.rc">

NeuralAmpModeler/projects/NeuralAmpModeler-iOS.xcodeproj/project.pbxproj

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
4FE0DEE829A183B700DDBCC8 /* NeuralAmpModelerAU.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FC6982F293BA47F0076EC33 /* NeuralAmpModelerAU.framework */; };
8585
4FE0DEF029A2E0F100DDBCC8 /* IPlugAUViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4FFF105A20A0E57100D3092F /* IPlugAUViewController.mm */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
8686
91236D811B08F59300734C5E /* NeuralAmpModelerAppExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 91236D771B08F59300734C5E /* NeuralAmpModelerAppExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
87+
AA8CA7772A452EF500F5BEF0 /* resample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AA8CA7752A452EF500F5BEF0 /* resample.cpp */; };
8788
/* End PBXBuildFile section */
8889

8990
/* Begin PBXContainerItemProxy section */
@@ -335,6 +336,8 @@
335336
4FFF108820A1036200D3092F /* NeuralAmpModeler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NeuralAmpModeler.h; path = ../NeuralAmpModeler.h; sourceTree = "<group>"; };
336337
91236D0D1B08F42B00734C5E /* NeuralAmpModeler.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NeuralAmpModeler.app; sourceTree = BUILT_PRODUCTS_DIR; };
337338
91236D771B08F59300734C5E /* NeuralAmpModelerAppExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NeuralAmpModelerAppExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
339+
AA8CA7752A452EF500F5BEF0 /* resample.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resample.cpp; sourceTree = "<group>"; };
340+
AA8CA7762A452EF500F5BEF0 /* resample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resample.h; sourceTree = "<group>"; };
338341
/* End PBXFileReference section */
339342

340343
/* Begin PBXFrameworksBuildPhase section */
@@ -636,20 +639,20 @@
636639
4FBDC93E29FFF143004FF203 /* NAM */ = {
637640
isa = PBXGroup;
638641
children = (
639-
4FBDC93F29FFF143004FF203 /* util.cpp */,
640-
4FBDC94029FFF143004FF203 /* version.h */,
641-
4FBDC94129FFF143004FF203 /* dsp.cpp */,
642-
4FBDC94229FFF143004FF203 /* convnet.h */,
643-
4FBDC94329FFF143004FF203 /* lstm.h */,
642+
4FBDC94A29FFF143004FF203 /* activations.cpp */,
643+
4FBDC94929FFF143004FF203 /* activations.h */,
644644
4FBDC94429FFF143004FF203 /* convnet.cpp */,
645-
4FBDC94529FFF143004FF203 /* wavenet.h */,
645+
4FBDC94229FFF143004FF203 /* convnet.h */,
646+
4FBDC94129FFF143004FF203 /* dsp.cpp */,
647+
4FBDC94829FFF143004FF203 /* dsp.h */,
648+
4FBDC94C29FFF143004FF203 /* get_dsp.cpp */,
646649
4FBDC94629FFF143004FF203 /* lstm.cpp */,
650+
4FBDC94329FFF143004FF203 /* lstm.h */,
651+
4FBDC93F29FFF143004FF203 /* util.cpp */,
647652
4FBDC94729FFF143004FF203 /* util.h */,
648-
4FBDC94829FFF143004FF203 /* dsp.h */,
649-
4FBDC94929FFF143004FF203 /* activations.h */,
650-
4FBDC94A29FFF143004FF203 /* activations.cpp */,
653+
4FBDC94029FFF143004FF203 /* version.h */,
651654
4FBDC94B29FFF143004FF203 /* wavenet.cpp */,
652-
4FBDC94C29FFF143004FF203 /* get_dsp.cpp */,
655+
4FBDC94529FFF143004FF203 /* wavenet.h */,
653656
);
654657
name = NAM;
655658
path = ../NeuralAmpModelerCore/NAM;
@@ -1003,6 +1006,7 @@
10031006
4FBDC95329FFF143004FF203 /* ImpulseResponse.cpp in Sources */,
10041007
4FC69843293BA5C50076EC33 /* IPlugParameter.cpp in Sources */,
10051008
4FC69844293BA5C50076EC33 /* IPlugTimer.cpp in Sources */,
1009+
AA8CA7772A452EF500F5BEF0 /* resample.cpp in Sources */,
10061010
4FC69845293BA5C50076EC33 /* IPlugPaths.mm in Sources */,
10071011
4FBDC96329FFF143004FF203 /* activations.cpp in Sources */,
10081012
4FC69847293BA5F90076EC33 /* IPopupMenuControl.cpp in Sources */,

NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@
304304
<ClInclude Include="..\..\iPlug2\IGraphics\Platforms\IGraphicsMac_view.h" />
305305
<ClInclude Include="..\..\iPlug2\IGraphics\Platforms\IGraphicsWeb.h" />
306306
<ClInclude Include="..\..\iPlug2\IGraphics\Platforms\IGraphicsWin.h" />
307+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\LanczosResampler.h" />
308+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\NonIntegerResampler.h" />
307309
<ClInclude Include="..\..\iPlug2\IPlug\IPlugAPIBase.h" />
308310
<ClInclude Include="..\..\iPlug2\IPlug\IPlugConstants.h" />
309311
<ClInclude Include="..\..\iPlug2\IPlug\IPlugEditorDelegate.h" />

NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj.filters

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@
437437
<ClInclude Include="..\AudioDSPTools\dsp\wav.h">
438438
<Filter>dsp</Filter>
439439
</ClInclude>
440+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\NonIntegerResampler.h">
441+
<Filter>IPlug\Extras</Filter>
442+
</ClInclude>
443+
<ClInclude Include="..\..\iPlug2\IPlug\Extras\LanczosResampler.h">
444+
<Filter>IPlug\Extras</Filter>
445+
</ClInclude>
440446
</ItemGroup>
441447
<ItemGroup>
442448
<Filter Include="resources">
@@ -502,6 +508,9 @@
502508
<Filter Include="dsp">
503509
<UniqueIdentifier>{ed745419-3921-4abe-8286-170fa316a5ac}</UniqueIdentifier>
504510
</Filter>
511+
<Filter Include="IPlug\Extras">
512+
<UniqueIdentifier>{4b6a01ff-796f-4098-a581-3b22035cc8be}</UniqueIdentifier>
513+
</Filter>
505514
</ItemGroup>
506515
<ItemGroup>
507516
<ResourceCompile Include="..\resources\main.rc">

0 commit comments

Comments
 (0)