diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp index 4cac6063..2bc4f205 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.cpp +++ b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -1,3 +1,4 @@ +#include // std::clamp #include #include #include @@ -67,7 +68,14 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) mStagedDSP(nullptr), mToneBass(), mToneMid(), - mToneTreble() + mToneTreble(), + mIR(), + mIRFileName(), + mIRPath(), + mFlagRemoveDSP(false), + mFlagRemoveIR(false), + mDefaultModelString("Select model..."), + mDefaultIRString("Select IR...") { GetParam(kInputLevel)->InitGain("Input", 0.0, -20.0, 20.0, 0.1); GetParam(kToneBass)->InitDouble("Bass", 5.0, 0.0, 10.0, 0.1); @@ -93,12 +101,14 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) pGraphics->EnableMouseOver(true); auto helpSVG = pGraphics->LoadSVG(HELP_FN); auto folderSVG = pGraphics->LoadSVG(FOLDER_FN); + auto closeButtonSVG = pGraphics->LoadSVG(CLOSE_BUTTON_FN); pGraphics->LoadFont("Roboto-Regular", ROBOTO_FN); const IRECT b = pGraphics->GetBounds(); const IRECT mainArea = b.GetPadded(-20); const auto content = mainArea.GetPadded(-10); const auto titleLabel = content.GetFromTop(50); + // Areas for knobs const float knobHalfPad = 10.0f; const float knobPad = 2.0f * knobHalfPad; const float knobHalfHeight = 70.0f; @@ -109,7 +119,14 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) 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); + // Areas for model and IR + const float fileWidth = 250.0f; + const float fileHeight = 30.0f; + const float fileSpace = 10.0f; + const auto modelArea = content.GetFromBottom(2.0f * fileHeight + fileSpace).GetFromTop(fileHeight).GetMidHPadded(fileWidth); + const auto irArea = content.GetFromBottom(fileHeight).GetMidHPadded(fileWidth); + + // Areas for meters 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); @@ -153,6 +170,7 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) // Model loader button auto loadModel = [&, pGraphics](IControl* pCaller) { + // TODO start from last directory on second load if possible. WDL_String dir; pGraphics->PromptForDirectory(dir, [&](const WDL_String& fileName, const WDL_String& path){ if (path.GetLength()) @@ -160,10 +178,36 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) }); }; - // Tells us what model is loaded - pGraphics->AttachControl(new IVPanelControl(modelArea, "", style.WithColor(kFG, PluginColors::NAM_1))); // .WithContrast(-0.75) - 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); + auto getIRPath = [&, pGraphics](IControl* pCaller) { + WDL_String fileName; + WDL_String path(this->mIRPath.Get()); + pGraphics->PromptForFile(fileName, path); + if (fileName.GetLength()) { + this->mIRPath = path; + this->_GetIR(fileName); + } + }; + + // Model-clearing function + auto ClearModel = [&, pGraphics](IControl* pCaller) { + this->mFlagRemoveDSP = true; + }; + // IR-clearing function + auto ClearIR = [&, pGraphics](IControl* pCaller) { + this->mFlagRemoveIR = true; + }; + + // Graphics objects for what model is loaded + const float iconWidth = fileHeight; // Square icon + pGraphics->AttachControl(new IVPanelControl(modelArea, "", style.WithColor(kFG, PluginColors::NAM_1))); + pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromLeft(iconWidth).GetPadded(-2.f), loadModel, folderSVG)); + pGraphics->AttachControl(new IRolloverSVGButtonControl(modelArea.GetFromRight(iconWidth).GetPadded(-2.f), ClearModel, closeButtonSVG)); + pGraphics->AttachControl(new IVUpdateableLabelControl(modelArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultModelString.Get(), style.WithDrawFrame(false).WithValueText(style.valueText.WithVAlign(EVAlign::Middle))), kCtrlTagModelName); + // IR + pGraphics->AttachControl(new IVPanelControl(irArea, "", style.WithColor(kFG, PluginColors::NAM_1))); + pGraphics->AttachControl(new IRolloverSVGButtonControl(irArea.GetFromLeft(iconWidth).GetPadded(-2.f), getIRPath, folderSVG)); + pGraphics->AttachControl(new IRolloverSVGButtonControl(irArea.GetFromRight(iconWidth).GetPadded(-2.f), ClearIR, closeButtonSVG)); + pGraphics->AttachControl(new IVUpdateableLabelControl(irArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultIRString.Get(), style.WithDrawFrame(false).WithValueText(style.valueText.WithVAlign(EVAlign::Middle))), kCtrlTagIRName); // The knobs pGraphics->AttachControl(new IVKnobControl(inputKnobArea, kInputLevel, "", style)); @@ -174,7 +218,7 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) // The meters const float meterMin = -60.0f; - const float meterMax = 12.0f; + const float meterMax = -0.01f; pGraphics->AttachControl(new IVPeakAvgMeterControl(inputMeterArea, "", style.WithWidgetFrac(0.5) .WithShowValue(false) .WithColor(kFG, PluginColors::NAM_2), EDirection::Vertical, {}, 0, meterMin, meterMax, {}), kCtrlTagInputMeter) @@ -231,17 +275,11 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp const int nChans = this->NOutChansConnected(); this->_PrepareBuffers(nFrames); this->_ProcessInput(inputs, nFrames); - if (mStagedDSP != nullptr) - { - // Move from staged to active DSP - mDSP = std::move(mStagedDSP); - mStagedDSP = nullptr; - } + this->_ApplyDSPStaging(); if (mDSP != nullptr) { // 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); @@ -269,16 +307,26 @@ void NeuralAmpModeler::ProcessBlock(iplug::sample** inputs, iplug::sample** outp // 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); + recursive_linear_filter::BiquadParams bassParams(sampleRate, bassFrequency, bassQuality, bassGainDB); + recursive_linear_filter::BiquadParams midParams(sampleRate, midFrequency, midQuality, midGainDB); + recursive_linear_filter::BiquadParams 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); + // Set parameters + this->mToneBass.SetParams(bassParams); + this->mToneMid.SetParams(midParams); + this->mToneTreble.SetParams(trebleParams); + const size_t numChannels = (size_t) nChans; + const size_t numFrames = (size_t) nFrames; + sample** bassPointers = this->mToneBass.Process(this->mOutputPointers, numChannels, numFrames); + sample** midPointers = this->mToneMid.Process(bassPointers, numChannels, numFrames); + sample** treblePointers = this->mToneTreble.Process(midPointers, numChannels, numFrames); + + sample** irPointers = treblePointers; + if (this->mIR != nullptr) + irPointers = this->mIR->Process(treblePointers, numChannels, numFrames); // Let's get outta here - this->_ProcessOutput(treblePointers, outputs, nFrames); + this->_ProcessOutput(irPointers, outputs, nFrames); // * Output of input leveling (inputs -> mInputPointers), // * Output of output leveling (mOutputPointers -> outputs) this->_UpdateMeters(this->mInputPointers, outputs, nFrames); @@ -288,6 +336,7 @@ 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()); + chunk.PutStr(this->mIRFileName.Get()); return SerializeParams(chunk); } @@ -295,10 +344,14 @@ int NeuralAmpModeler::UnserializeState(const IByteChunk& chunk, int startPos) { WDL_String dir; startPos = chunk.GetStr(mModelPath, startPos); - mDSP = nullptr; + startPos = chunk.GetStr(this->mIRFileName, startPos); + this->mDSP = nullptr; + this->mIR = nullptr; int retcode = UnserializeParams(chunk, startPos); if (this->mModelPath.GetLength()) this->_GetDSP(this->mModelPath); + if (this->mIRFileName.GetLength()) + this->_GetIR(this->mIRFileName); return retcode; } @@ -307,10 +360,38 @@ void NeuralAmpModeler::OnUIOpen() Plugin::OnUIOpen(); if (this->mModelPath.GetLength()) this->_SetModelMsg(this->mModelPath); + if (this->mIRFileName.GetLength()) + this->_SetIRMsg(this->mIRFileName); } // Private methods ============================================================ +void NeuralAmpModeler::_ApplyDSPStaging() +{ + // Move things from staged to live + if (this->mStagedDSP != nullptr) + { + // Move from staged to active DSP + this->mDSP = std::move(this->mStagedDSP); + this->mStagedDSP = nullptr; + } + if (this->mStagedIR != nullptr) { + this->mIR = std::move(this->mStagedIR); + this->mStagedIR = nullptr; + } + // Remove marked modules + if (this->mFlagRemoveDSP) { + this->mDSP = nullptr; + this->_UnsetModelMsg(); + this->mFlagRemoveDSP = false; + } + if (this->mFlagRemoveIR) { + this->mIR = nullptr; + this->_UnsetIRMsg(); + this->mFlagRemoveIR = false; + } +} + void NeuralAmpModeler::_FallbackDSP(const int nFrames) { const int nChans = this->NOutChansConnected(); @@ -341,6 +422,28 @@ void NeuralAmpModeler::_GetDSP(const WDL_String& modelPath) } } +void NeuralAmpModeler::_GetIR(const WDL_String& irFileName) +{ + WDL_String previousIRFileName; + try { + previousIRFileName = this->mIRFileName; + const double sampleRate = this->GetSampleRate(); + this->mStagedIR = std::make_unique(irFileName, sampleRate); + this->_SetIRMsg(irFileName); + } + catch (std::exception& e) { + std::stringstream ss; + ss << "FAILED to load IR"; + SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); + if (this->mStagedIR != nullptr) { + this->mStagedIR = nullptr; + } + this->mIRFileName = previousIRFileName; + std::cerr << "Failed to read IR" << std::endl; + std::cerr << e.what() << std::endl; + } +} + size_t NeuralAmpModeler::_GetBufferNumChannels() const { return this->mInputArray.size(); @@ -410,16 +513,40 @@ void NeuralAmpModeler::_ProcessOutput(iplug::sample** inputs, iplug::sample **ou // Assume _PrepareBuffers() was already called for (int c=0; cmIRFileName = irFileName; + auto dspPath = std::filesystem::path(irFileName.Get()); + std::stringstream ss; + ss << "Loaded " << dspPath.filename(); + SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); +} + +void NeuralAmpModeler::_UnsetModelMsg() +{ + this->_UnsetMsg(kCtrlTagModelName, this->mDefaultModelString); +} + +void NeuralAmpModeler::_UnsetIRMsg() +{ + this->_UnsetMsg(kCtrlTagIRName, this->mDefaultIRString); +} + +void NeuralAmpModeler::_UnsetMsg(const int tag, const WDL_String &msg) +{ + SendControlMsgFromDelegate(tag, 0, int(strlen(msg.Get())), msg.Get()); } void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, sample** outputPointer, const int nFrames) diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h index 0bd7fcf5..27eb2f83 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.h +++ b/NeuralAmpModeler/NeuralAmpModeler.h @@ -4,6 +4,7 @@ #include "dsp.h" #include "choc_ReenableAllWarnings.h" #include "dsp/RecursiveLinearFilter.h" +#include "dsp/ImpulseResponse.h" #include "IPlug_include_in_plug_hdr.h" @@ -24,6 +25,7 @@ enum EParams enum ECtrlTags { kCtrlTagModelName = 0, + kCtrlTagIRName, kCtrlTagInputMeter, kCtrlTagOutputMeter, kNumCtrlTags @@ -45,18 +47,25 @@ class NeuralAmpModeler final : public iplug::Plugin this->mOutputSender.TransmitData(*this); } - bool SerializeState(IByteChunk& chunk) const override; - int UnserializeState(const IByteChunk& chunk, int startPos) override; + bool SerializeState(iplug::IByteChunk& chunk) const override; + int UnserializeState(const iplug::IByteChunk& chunk, int startPos) override; void OnUIOpen() override; private: + // Moves DSP modules from staging area to the main area. + // Also deletes DSP modules that are flagged for removal. + // Exists so that we don't try to use a DSP module that's only + // partially-instantiated. + void _ApplyDSPStaging(); // 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; + // Gets a new DSP object and stores it to mStagedDSP + void _GetDSP(const WDL_String& dspPath); + // Gets the IR and stores to mStagedIR + void _GetIR(const WDL_String& irFileName); // Update the message about which model is loaded. void _SetModelMsg(const WDL_String& dspPath); bool _HaveModel() const { @@ -70,31 +79,46 @@ class NeuralAmpModeler final : public iplug::Plugin void _ProcessInput(iplug::sample** inputs, const int nFrames); // Copy the output to the output buffer, applying output level. void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const int nFrames); + // Update the text in the IR area to say what's loaded. + void _SetIRMsg(const WDL_String& modelPath); + void _UnsetModelMsg(); + void _UnsetIRMsg(); + void _UnsetMsg(const int tag, const WDL_String& msg); // Update level meters // Called within ProcessBlock(). // Assume _ProcessInput() and _ProcessOutput() were run immediately before. - void _UpdateMeters(sample** inputPointer, sample** outputPointer, const int nFrames); + void _UpdateMeters(iplug::sample** inputPointer, iplug::sample** outputPointer, const int nFrames); // Input arrays - std::vector> mInputArray; + std::vector> mInputArray; // Output arrays - std::vector> mOutputArray; + std::vector> mOutputArray; // Pointer versions - sample** mInputPointers; - sample** mOutputPointers; + iplug::sample** mInputPointers; + iplug::sample** mOutputPointers; - // The DSP actually being used: + // The DSPs actually being used: std::unique_ptr mDSP; + std::unique_ptr mIR; // Manages switching what DSP is being used. std::unique_ptr mStagedDSP; + std::unique_ptr mStagedIR; + // Flags to take away the modules at a safe time. + bool mFlagRemoveDSP; + bool mFlagRemoveIR; + const WDL_String mDefaultModelString; + const WDL_String mDefaultIRString; // Tone stack modules - // TODO low shelf, peaking, high shelf recursive_linear_filter::LowShelf mToneBass; recursive_linear_filter::Peaking mToneMid; recursive_linear_filter::HighShelf mToneTreble; + // Paths to model and IR WDL_String mModelPath; + WDL_String mIRFileName; + // Directory containing the file (for better file browsing on second load-in) + WDL_String mIRPath; // Do we need this, or can we manipualte mITFileName (".dirname") std::unordered_map mDSPParams = { { "Input", 0.0 }, diff --git a/NeuralAmpModeler/config.h b/NeuralAmpModeler/config.h index 73a56878..4e5f3bab 100644 --- a/NeuralAmpModeler/config.h +++ b/NeuralAmpModeler/config.h @@ -24,7 +24,7 @@ #define PLUG_DOES_STATE_CHUNKS 0 #define PLUG_HAS_UI 1 #define PLUG_WIDTH 600 -#define PLUG_HEIGHT 300 +#define PLUG_HEIGHT 400 #define PLUG_FPS 60 #define PLUG_SHARED_RESOURCES 0 #define PLUG_HOST_RESIZE 0 @@ -57,5 +57,6 @@ #define ROBOTO_FN "Roboto-Regular.ttf" #define HELP_FN "help.svg" #define FOLDER_FN "folder.svg" +#define CLOSE_BUTTON_FN "close-button.svg" #define TOLEX_FN "tolex.jpeg" #define TOLEX2X_FN "tolex@2x.jpeg" diff --git a/NeuralAmpModeler/dsp/ImpulseResponse.cpp b/NeuralAmpModeler/dsp/ImpulseResponse.cpp new file mode 100644 index 00000000..d91ec7df --- /dev/null +++ b/NeuralAmpModeler/dsp/ImpulseResponse.cpp @@ -0,0 +1,67 @@ +// +// ImpulseResponse.cpp +// NeuralAmpModeler-macOS +// +// Created by Steven Atkinson on 12/30/22. +// + +#include "Resample.h" +#include "wav.h" + +#include "ImpulseResponse.h" + +dsp::ImpulseResponse::ImpulseResponse(const WDL_String& fileName, + const double sampleRate) +{ + // Try to load the WAV + if (dsp::wav::Load(fileName, this->mRawAudio, this->mRawAudioSampleRate) != dsp::wav::RET_SUCCESS) { + std::stringstream ss; + ss << "Failed to load IR at " << fileName.Get() << std::endl; + throw std::runtime_error(ss.str()); + } + // Set the weights based on the raw audio. + this->_SetWeights(sampleRate); +} + +iplug::sample** dsp::ImpulseResponse::Process(iplug::sample** inputs, + const size_t numChannels, + const size_t numFrames) +{ + this->_PrepareBuffers(numChannels, numFrames); + this->_UpdateHistory(inputs, numChannels, numFrames); + + for (size_t i=0, j=this->mHistoryIndex - this->mHistoryRequired; i(&this->mHistory[j], this->mHistoryRequired+1); + this->mOutputs[0][i] = (double) this->mWeight.dot(input); + } + // Copy out for more-than-mono. + for (size_t c=1; cmOutputs[c][i] = this->mOutputs[0][i]; + + this->_AdvanceHistoryIndex(numFrames); + return this->_GetPointers(); +} + +void dsp::ImpulseResponse::_SetWeights(const double sampleRate) +{ + if (this->mRawAudioSampleRate == sampleRate) { + this->mResampled.resize(this->mRawAudio.size()); + memcpy(this->mResampled.data(), this->mRawAudio.data(), this->mResampled.size()); + } + else { + // Cubic resampling + std::vector padded; + padded.resize(this->mRawAudio.size() + 2); + padded[0] = 0.0f; + padded[padded.size()-1] = 0.0f; + memcpy(padded.data() + 1, this->mRawAudio.data(), this->mRawAudio.size()); + dsp::ResampleCubic(padded, this->mRawAudioSampleRate, sampleRate, 0.0, this->mResampled); + } + // Simple implementation w/ no resample... + const size_t irLength = std::min(this->mResampled.size(), this->mMaxLength); + this->mWeight.resize(irLength); + for (size_t i=0, j=irLength-1; imWeight[j] = this->mResampled[i]; + this->mHistoryRequired = irLength - 1; +} diff --git a/NeuralAmpModeler/dsp/ImpulseResponse.h b/NeuralAmpModeler/dsp/ImpulseResponse.h new file mode 100644 index 00000000..c3aa861a --- /dev/null +++ b/NeuralAmpModeler/dsp/ImpulseResponse.h @@ -0,0 +1,43 @@ +// +// ImpulseResponse.h +// NeuralAmpModeler-macOS +// +// Created by Steven Atkinson on 12/30/22. +// +// Impulse response processing + +#ifndef ImpulseResponse_h +#define ImpulseResponse_h + +#include + +#include + +#include "wdlstring.h" // WDL_String +#include "IPlugConstants.h" // sample +#include "dsp.h" + +namespace dsp { + class ImpulseResponse : public History { + public: + ImpulseResponse(const WDL_String& fileName, const double sampleRate); + iplug::sample** Process(iplug::sample** inputs, + const size_t numChannels, + const size_t numFrames) override; + private: + // Set the weights, given that the plugin is running at the provided sample rate. + void _SetWeights(const double sampleRate); + + // Keep a copy of the raw audio that was loaded so that it can be resampled + std::vector mRawAudio; + double mRawAudioSampleRate; + // Resampled to the required sample rate. + std::vector mResampled; + + const size_t mMaxLength = 8192; + // The weights + Eigen::VectorXf mWeight; + }; +}; + +#endif /* ImpulseResponse_h */ diff --git a/NeuralAmpModeler/dsp/RecursiveLinearFilter.cpp b/NeuralAmpModeler/dsp/RecursiveLinearFilter.cpp index 5afdf12e..fc477704 100644 --- a/NeuralAmpModeler/dsp/RecursiveLinearFilter.cpp +++ b/NeuralAmpModeler/dsp/RecursiveLinearFilter.cpp @@ -7,15 +7,13 @@ // See: https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html #include // std::fill -#include // pow, sin #include #include "RecursiveLinearFilter.h" recursive_linear_filter::Base::Base(const size_t inputDegree, const size_t outputDegree) : -mOutputPointers(nullptr), -mOutputPointersSize(0), +dsp::DSP(), mInputStart(inputDegree), // 1 is subtracted before first use mOutputStart(outputDegree) { @@ -24,12 +22,10 @@ mOutputStart(outputDegree) } iplug::sample** recursive_linear_filter::Base::Process(iplug::sample** inputs, - const int numChannels, - const int numFrames, - const Params *params) + const size_t numChannels, + const size_t numFrames) { this->_PrepareBuffers(numChannels, numFrames); - params->SetCoefficients(this->mInputCoefficients, this->mOutputCoefficients); long inputStart=0; long outputStart=0; // Degree = longest history @@ -65,55 +61,51 @@ iplug::sample** recursive_linear_filter::Base::Process(iplug::sample** inputs, } this->mInputStart = inputStart; this->mOutputStart = outputStart; - return this->GetPointers(); + return this->_GetPointers(); } -iplug::sample** recursive_linear_filter::Base::GetPointers() +void recursive_linear_filter::Base::_PrepareBuffers(const size_t numChannels, const size_t numFrames) { - for (auto c=0; c_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) { + // Check for new channel count *before* parent class ensures they match! + const bool newChannels = this->_GetNumChannels() != numChannels; + // Parent implementation takes care of mOutputs and mOutputPointers + this->dsp::DSP::_PrepareBuffers(numChannels, numFrames); + if (newChannels) { 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; cmInputHistory[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) +void recursive_linear_filter::Biquad::_AssignCoefficients(const double a0, + const double a1, + const double a2, + const double b0, + const double b1, + const double b2) { - 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; + this->mInputCoefficients[0] = b0 / a0; + this->mInputCoefficients[1] = b1 / a0; + this->mInputCoefficients[2] = b2 / a0; + // this->mOutputCoefficients[0] = 0.0; // Always + // Sign flip due so we add during main loop (cf Eq. (4)) + this->mOutputCoefficients[1] = -a1 / a0; + this->mOutputCoefficients[2] = -a2 / a0; } -void recursive_linear_filter::LowShelfParams::SetCoefficients(std::vector &inputCoefficients, - std::vector &outputCoefficients) const +void recursive_linear_filter::LowShelf::SetParams(const recursive_linear_filter::BiquadParams ¶ms) { - 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 a = params.GetA(); + const double omega_0 = params.GetOmega0(); + const double alpha = params.GetAlpha(omega_0); + const double cosw = params.GetCosW(omega_0); const double ap = a + 1.0; const double am = a - 1.0; @@ -126,22 +118,15 @@ void recursive_linear_filter::LowShelfParams::SetCoefficients(std::vector_AssignCoefficients(a0, a1, a2, b0, b1, b2); } -void recursive_linear_filter::PeakingParams::SetCoefficients(std::vector &inputCoefficients, - std::vector &outputCoefficients) const +void recursive_linear_filter::Peaking::SetParams(const recursive_linear_filter::BiquadParams ¶ms) { - 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 a = params.GetA(); + const double omega_0 = params.GetOmega0(); + const double alpha = params.GetAlpha(omega_0); + const double cosw = params.GetCosW(omega_0); const double b0 = 1.0 + alpha * a; const double b1 = -2.0 * cosw; @@ -150,24 +135,17 @@ void recursive_linear_filter::PeakingParams::SetCoefficients(std::vector 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; + this->_AssignCoefficients(a0, a1, a2, b0, b1, b2); } -void recursive_linear_filter::HighShelfParams::SetCoefficients(std::vector &inputCoefficients, - std::vector &outputCoefficients) const +void recursive_linear_filter::HighShelf::SetParams(const recursive_linear_filter::BiquadParams ¶ms) { - 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 a = params.GetA(); + const double omega_0 = params.GetOmega0(); + const double alpha = params.GetAlpha(omega_0); + const double cosw = params.GetCosW(omega_0); + const double roota2alpha = 2.0 * sqrt(a) * alpha; const double ap = a + 1.0; const double am = a - 1.0; @@ -178,12 +156,5 @@ void recursive_linear_filter::HighShelfParams::SetCoefficients(std::vector_AssignCoefficients(a0, a1, a2, b0, b1, b2); } diff --git a/NeuralAmpModeler/dsp/RecursiveLinearFilter.h b/NeuralAmpModeler/dsp/RecursiveLinearFilter.h index 324a1313..cf6469eb 100644 --- a/NeuralAmpModeler/dsp/RecursiveLinearFilter.h +++ b/NeuralAmpModeler/dsp/RecursiveLinearFilter.h @@ -9,53 +9,28 @@ #ifndef RecursiveLinearFilter_h #define RecursiveLinearFilter_h +#include // pow, sin #include #include "IPlugConstants.h" // sample +#include "dsp.h" #define MATH_PI 3.14159265358979323846 // TODO refactor base DSP into a common abstraction. namespace recursive_linear_filter { - class Params{ - public: - // Based on the parameters in this object, set the coefficients in the - // provided arrays for the filter. - virtual void SetCoefficients(std::vector& inputCoefficients, - std::vector& outputCoefficients) const=0; - }; - - class Base { // TODO: inherit from DSP + class Base : public dsp::DSP { public: Base(const size_t inputDegree, const size_t outputDegree); - - // Compute the DSP, saving to mOutputs, owned by this instance. - // Return a pointer-to-pointers of the output array - // TODO return const iplug::sample** Process(iplug::sample** inputs, - const int numChannels, - const int numFrames, - const Params *params); - - // Return a pointer-to-pointers for the DSP's output buffers (all channels) - // Assumes that ._PrepareBuffers() was called recently enough. - iplug::sample** GetPointers(); + const size_t numChannels, + const size_t numFrames) override; protected: - size_t _GetNumChannels() const {return this->mInputHistory.size();}; + // Methods size_t _GetInputDegree() const {return this->mInputCoefficients.size();}; size_t _GetOutputDegree() const {return this->mOutputCoefficients.size();}; - void _PrepareBuffers(const int numChannels, const int numFrames); - // Resizes the pointer-to-pointers for the vector-of-vectors. - void _ResizePointers(const size_t numChannels); - - // Where the output will get written... - // Indexing is [channel][sample] - std::vector> mOutputs; - // ...And a pointer to it. - // Only the first level is allocated; the second level are duplicate - // pointers to the data that are primarily owned by the vectors. - iplug::sample** mOutputPointers; - size_t mOutputPointersSize; // Keep track of its size. + // Additionally prepares mInputHistory and mOutputHistory. + void _PrepareBuffers(const size_t numChannels, const size_t numFrames) override; // Coefficients for the DSP filter std::vector mInputCoefficients; @@ -72,13 +47,10 @@ namespace recursive_linear_filter { long mOutputStart; }; - class LevelParams : public Params { + class LevelParams : public dsp::Params { public: LevelParams(const double gain) : Params(), mGain(gain) {}; - void SetCoefficients(std::vector& inputCoefficients, - std::vector& outputCoefficients) const { - inputCoefficients[0] = this->mGain; - }; + double GetGain() const {return this->mGain;}; private: // The gain (multiplicative, i.e. not dB) double mGain; @@ -87,75 +59,66 @@ namespace recursive_linear_filter { class Level : public Base { public: Level() : Base(1, 0) {}; + // Invalid usage: require a pointer to recursive_linear_filter::Params so + // that SetCoefficients() is defined. + void SetParams(const LevelParams ¶ms) { + this->mInputCoefficients[0] = params.GetGain(); + };; }; - class LowShelfParams : public Params { + // The same 3 params (frequency, quality, gain) describe a bunch of filters. + // (Low shelf, high shelf, peaking) + class BiquadParams : public dsp::Params { public: - LowShelfParams(const double sampleRate, const double frequency, const double quality, const double gainDB) : - Params(), - mSampleRate(sampleRate), + BiquadParams(const double sampleRate, + const double frequency, + const double quality, + const double gainDB) : + dsp::Params(), mFrequency(frequency), + mGainDB(gainDB), mQuality(quality), - mGainDB(gainDB) {}; + mSampleRate(sampleRate) {}; - void SetCoefficients(std::vector& inputCoefficients, - std::vector& outputCoefficients) const; + // Parameters defined in + // https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html + double GetA() const {return pow(10.0, this->mGainDB / 40.0);}; + double GetOmega0() const {return 2.0 * MATH_PI * this->mFrequency / this->mSampleRate;}; + double GetAlpha(const double omega_0) const {return sin(omega_0) / (2.0 * this->mQuality);}; + double GetCosW(const double omega_0) const {return cos(omega_0);}; private: - double mSampleRate; double mFrequency; - double mQuality; double mGainDB; + double mQuality; + double mSampleRate; }; - class LowShelf : public Base { - public: - LowShelf() : Base(3,3) {}; - }; - - class PeakingParams : public Params { + class Biquad : public Base { public: - PeakingParams(const double sampleRate, const double frequency, const double quality, const double gainDB) : - Params(), - mSampleRate(sampleRate), - mFrequency(frequency), - mQuality(quality), - mGainDB(gainDB) {}; - - void SetCoefficients(std::vector& inputCoefficients, - std::vector& outputCoefficients) const; - private: - double mSampleRate; - double mFrequency; - double mQuality; - double mGainDB; + Biquad() : Base(3, 3) {}; + virtual void SetParams(const BiquadParams ¶ms) = 0; + protected: + void _AssignCoefficients(const double a0, + const double a1, + const double a2, + const double b0, + const double b1, + const double b2); }; - class Peaking : public Base { + class LowShelf : public Biquad { public: - Peaking() : Base(3,3) {}; + void SetParams(const BiquadParams ¶ms) override; }; - class HighShelfParams : public Params { + class Peaking : public Biquad { public: - HighShelfParams(const double sampleRate, const double frequency, const double quality, const double gainDB) : - Params(), - mSampleRate(sampleRate), - mFrequency(frequency), - mQuality(quality), - mGainDB(gainDB) {}; - - void SetCoefficients(std::vector& inputCoefficients, - std::vector& outputCoefficients) const; - private: - double mSampleRate; - double mFrequency; - double mQuality; - double mGainDB; + void SetParams(const BiquadParams ¶ms) override; }; - class HighShelf : public Base { + class HighShelf : public Biquad { public: - HighShelf() : Base(3,3) {}; + void SetParams(const BiquadParams ¶ms) override; }; }; diff --git a/NeuralAmpModeler/dsp/Resample.h b/NeuralAmpModeler/dsp/Resample.h new file mode 100644 index 00000000..c696214d --- /dev/null +++ b/NeuralAmpModeler/dsp/Resample.h @@ -0,0 +1,81 @@ +// +// Resample.h +// NeuralAmpModeler-macOS +// +// Created by Steven Atkinson on 1/2/23. +// + +#ifndef Resample_h +#define Resample_h + +#include +#include +#include + +namespace dsp { + // Resample a provided vector in inputs to outputs. + // Creates an array of the required length to fill all points from the SECOND + // input to the SECOND-TO-LAST input point, exclusive. + // (Seconds bc cubic. and ew want to only interpoalte between points 2 and + // 3.) + // tOutputStart: location of first output point relative to the second input + // point (should be >=0.0) + template + void ResampleCubic(const std::vector& inputs, + const double originalSampleRate, + const double desiredSampleRate, + const double tOutputStart, + std::vector& outputs); + // Interpolate the 4 provided equispaced points to x in [-1,2] + template + T _CubicInterpolation(T p[4], T x) { + return p[1] + 0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); + }; +}; + +template +void dsp::ResampleCubic(const std::vector& inputs, + const double originalSampleRate, + const double desiredSampleRate, + const double tOutputStart, + std::vector& outputs) +{ + if (tOutputStart < 0.0) + throw std::runtime_error("Starting time must be non-negative"); + + // Time increment for each sample in the original audio file + const double timeIncrement = 1.0 / originalSampleRate; + + // Time increment for each sample in the resampled audio file + const double resampledTimeIncrement = 1.0 / desiredSampleRate; + + // Current time + double time = timeIncrement + tOutputStart; + + const double endTimeOriginal = (inputs.size() - 1) * timeIncrement; + while (time < endTimeOriginal) { + // Find the index of the sample in the original audio file that is just before the current time in the resampled audio file + int index = (long) std::floor(time / timeIncrement); + + // Calculate the time difference between the current time in the resampled audio file and the sample in the original audio file + double timeDifference = time - index * timeIncrement; + + // Get the four surrounding samples in the original audio file for cubic interpolation + double p[4]; + p[0] = (index == 0) ? inputs[0] : inputs[index - 1]; + p[1] = inputs[index]; + p[2] = (index == inputs.size() - 1) ? inputs[inputs.size() - 1] : inputs[index + 1]; + p[3] = (index == inputs.size() - 2) ? inputs[inputs.size() - 1] : inputs[index + 2]; + + // Use cubic interpolation to estimate the value of the audio signal at the current time in the resampled audio file + T resampledValue = dsp::_CubicInterpolation(p, timeDifference / timeIncrement); + + // Add the estimated value to the resampled audio file + outputs.push_back(resampledValue); + + // Update the current time in the resampled audio file + time += resampledTimeIncrement; + } +} + +#endif /* Resample_h */ diff --git a/NeuralAmpModeler/dsp/dsp.cpp b/NeuralAmpModeler/dsp/dsp.cpp index e8e5d0b5..fe461bfe 100644 --- a/NeuralAmpModeler/dsp/dsp.cpp +++ b/NeuralAmpModeler/dsp/dsp.cpp @@ -18,8 +18,8 @@ DSP::DSP() { this->_stale_params = true; } void DSP::process( - sample** inputs, - sample** outputs, + iplug::sample** inputs, + iplug::sample** outputs, const int num_channels, const int num_frames, const double input_gain, @@ -52,7 +52,7 @@ void DSP::_get_params_(const std::unordered_map& input_para } } -void DSP::_apply_input_level_(sample** inputs, const int num_channels, const int num_frames, const double gain) +void DSP::_apply_input_level_(iplug::sample** inputs, const int num_channels, const int num_frames, const double gain) { // Must match exactly; we're going to use the size of _input_post_gain later for num_frames. if (this->_input_post_gain.size() != num_frames) @@ -76,7 +76,7 @@ void DSP::_process_core_() this->_core_dsp_output[i] = this->_input_post_gain[i]; } -void DSP::_apply_output_level_(sample** outputs, const int num_channels, const int num_frames, const double gain) +void DSP::_apply_output_level_(iplug::sample** outputs, const int num_channels, const int num_frames, const double gain) { for (int c = 0; c < num_channels; c++) for (int s = 0; s < num_frames; s++) @@ -567,3 +567,94 @@ void convnet::ConvNet::_reset_anti_pop_() receptive_field += this->_blocks[i].conv.get_dilation(); this->_anti_pop_countdown = -receptive_field; } + +// ============================================================================ +// Implementation of Version 2 interface + +dsp::DSP::DSP() : +mOutputPointers(nullptr), +mOutputPointersSize(0) +{} + +dsp::DSP::~DSP() +{ + if (this->mOutputPointers != nullptr) + delete[] this->mOutputPointers; +}; + +iplug::sample** dsp::DSP::_GetPointers() +{ + for (auto c=0; c_GetNumChannels(); c++) + this->mOutputPointers[c] = this->mOutputs[c].data(); + return this->mOutputPointers; +} + + +void dsp::DSP::_PrepareBuffers(const size_t numChannels, const size_t numFrames) +{ + if (this->_GetNumChannels() != numChannels) { + this->mOutputs.resize(numChannels); + for (auto c=0; cmOutputs[c].resize(numFrames); + } + this->_ResizePointers(numChannels); + } +} + +void dsp::DSP::_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; +} + +dsp::History::History() : +DSP(), +mHistoryRequired(0), +mHistoryIndex(0) +{ +} + +void dsp::History::_AdvanceHistoryIndex(const size_t bufferSize) +{ + this->mHistoryIndex += bufferSize; +} + +void dsp::History::_EnsureHistorySize(const size_t bufferSize) +{ + const size_t repeatSize = std::max(bufferSize, this->mHistoryRequired); + const size_t requiredHistoryArraySize = 10 * repeatSize; // Just so we don't spend too much time copying back. + if (this->mHistory.size() < requiredHistoryArraySize) { + this->mHistory.resize(requiredHistoryArraySize); + std::fill(this->mHistory.begin(), this->mHistory.end(), 0.0f); + this->mHistoryIndex = this->mHistoryRequired; // Guaranteed to be less than requiredHistoryArraySize + } +} + +void dsp::History::_RewindHistory() +{ + // TODO memcpy? Should be fine w/ history array being >2x the history length. + for (size_t i=0, j=this->mHistoryIndex - this->mHistoryRequired; imHistoryRequired; i++, j++) + this->mHistory[i] = this->mHistory[j]; + this->mHistoryIndex = this->mHistoryRequired; +} + +void dsp::History::_UpdateHistory(iplug::sample **inputs, + const size_t numChannels, + const size_t numFrames) +{ + this->_EnsureHistorySize(numFrames); + if (numChannels < 1) + throw std::runtime_error("Zero channels?"); + if (this->mHistoryIndex + numFrames >= this->mHistory.size()) + this->_RewindHistory(); + // Grabs channel 1, drops hannel 2. + for (size_t i=0, j=this->mHistoryIndex; imHistory[j] = (float) inputs[0][i]; +} diff --git a/NeuralAmpModeler/dsp/dsp.h b/NeuralAmpModeler/dsp/dsp.h index fa2553f2..43c8ecec 100644 --- a/NeuralAmpModeler/dsp/dsp.h +++ b/NeuralAmpModeler/dsp/dsp.h @@ -23,9 +23,6 @@ enum EArchitectures kNumModels }; -// HACK -using namespace iplug; - // Class for providing params from the plugin to the DSP module // For now, we'll work with doubles. Later, we'll add other types. class DSPParam @@ -50,8 +47,8 @@ class DSP // overridden in subclasses). // 4. The output level is applied and the result stored to `output`. virtual void process( - sample** inputs, - sample** outputs, + iplug::sample** inputs, + iplug::sample** outputs, const int num_channels, const int num_frames, const double input_gain, @@ -85,7 +82,10 @@ class DSP // Apply the input gain // Result populates this->_input_post_gain - void _apply_input_level_(sample** inputs, const int num_channels, const int num_frames, const double gain); + void _apply_input_level_(iplug::sample** inputs, + const int num_channels, + const int num_frames, + const double gain); // i.e. ensure the size is correct. void _ensure_core_dsp_output_ready_(); @@ -96,7 +96,10 @@ class DSP virtual void _process_core_(); // Copy this->_core_dsp_output to output and apply the output volume - void _apply_output_level_(sample** outputs, const int num_channels, const int num_frames, const double gain); + void _apply_output_level_(iplug::sample** outputs, + const int num_channels, + const int num_frames, + const double gain); }; // Class where an input buffer is kept so that long-time effects can be captured. @@ -369,4 +372,89 @@ std::unique_ptr get_dsp(const std::filesystem::path dirname); // Hard-coded model: std::unique_ptr get_hard_dsp(); +// Version 2 DSP abstraction ================================================== + +namespace dsp { + class Params {}; + + class DSP { + public: + DSP(); + ~DSP(); + // The main interface for processing audio. + // The incoming audio is given as a raw pointer-to-pointers. + // The indexing is [channel][frame]. + // The output shall be a pointer-to-pointers of matching size. + // This object instance will own the data referenced by the pointers and be + // responsible for its allocation and deallocation. + virtual iplug::sample** Process(iplug::sample** inputs, const size_t numChannels, const size_t numFrames) = 0; + // Update the parameters of the DSP object according to the provided params. + // Not declaring a pure virtual bc there's no concrete definition that can + // use Params. + // But, use this name :) + // virtual void SetParams(Params* params) = 0; + + protected: + // Methods + + size_t _GetNumChannels() const {return this->mOutputs.size();}; + // Return a pointer-to-pointers for the DSP's output buffers (all channels) + // Assumes that ._PrepareBuffers() was called recently enough. + iplug::sample** _GetPointers(); + // Resize mOutputs to (numChannels, numFrames) and ensure that the raw + // pointers are also keeping up. + virtual void _PrepareBuffers(const size_t numChannels, const size_t numFrames); + // Resize the pointer-to-pointers for the vector-of-vectors. + void _ResizePointers(const size_t numChannels); + + + // Attributes + + // The output array into which the DSP module's calculations will be written. + // Pointers to this member's data will be returned by .Process(), and std + // Will ensure proper allocation. + std::vector> mOutputs; + // A pointer to pointers of which copies will be given out as the output of .Process(). + // This object will ensure proper allocation and deallocation of the first level; + // The second level points to .data() from mOutputs. + iplug::sample** mOutputPointers; + size_t mOutputPointersSize; + }; + + // A class where a longer buffer of history is needed to correctly calculate + // the DSP algorithm (e.g. algorithms involving convolution). + // + // Hacky stuff: + // * Mono + // * Single-precision floats. + class History : public DSP { + public: + History(); + protected: + // Called at the end of the DSP, advance the hsitory index to the next open + // spot. Does not ensure that it's at a valid address. + void _AdvanceHistoryIndex(const size_t bufferSize); + // Drop the new samples into the history array. + // Manages history array size + void _UpdateHistory(iplug::sample** inputs, + const size_t numChannels, + const size_t numFrames); + + // The history array that's used for DSP calculations. + std::vector mHistory; + // How many samples previous are required. + // Zero means that no history is required--only the current sample. + size_t mHistoryRequired; + // Location of the first sample in the current buffer. + // Shall always be in the range [mHistoryRequired, mHistory.size()). + size_t mHistoryIndex; + + private: + // Make sure that the history array is long enough. + void _EnsureHistorySize(const size_t bufferSize); + // Copy the end of the history back to the fron and reset mHistoryIndex + void _RewindHistory(); + }; +}; + #endif // IPLUG_DSP diff --git a/NeuralAmpModeler/dsp/wav.cpp b/NeuralAmpModeler/dsp/wav.cpp new file mode 100644 index 00000000..8985d26b --- /dev/null +++ b/NeuralAmpModeler/dsp/wav.cpp @@ -0,0 +1,204 @@ +// +// wav.cpp +// NeuralAmpModeler-macOS +// +// Created by Steven Atkinson on 12/31/22. +// + +#include // pow +#include +#include +#include + +#include "wav.h" + +bool idIsJunk(char* id) +{ + return (strncmp(id, "junk", 4) == 0) || (strncmp(id, "JUNK", 4) == 0); +} + +void ReadChunkAndSkipJunk(std::ifstream& file, char* chunkID) +{ + file.read(chunkID, 4); + if (idIsJunk(chunkID)) { + int junkSize; + file.read(reinterpret_cast(&junkSize), 4); + file.ignore(junkSize); + // Unused byte if junkSize is odd + if ((junkSize % 2) == 1) + file.ignore(1); + // And now we should be ready for data... + file.read(chunkID, 4); + } + if (idIsJunk(chunkID)) + throw std::runtime_error("Found more than 1 junk chunk"); +} + +int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double &sampleRate) +{ + // FYI: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + // Open the WAV file for reading + std::ifstream wavFile(fileName.Get(), std::ios::binary); + + // Check if the file was opened successfully + if (!wavFile.is_open()) { + std::cerr << "Error opening WAV file" << std::endl; + return dsp::wav::RET_ERROR_OPENING; + } + + // WAV file has 3 "chunks": RIFF ("RIFF"), format ("fmt ") and data ("data"). + // Read the WAV file header + char chunkId[4]; + ReadChunkAndSkipJunk(wavFile, chunkId); + if (strncmp(chunkId, "RIFF", 4) != 0) { + std::cerr << "Error: Not a WAV file" << std::endl; + return dsp::wav::RET_ERROR_NOT_WAV; + } + + int chunkSize; + wavFile.read(reinterpret_cast(&chunkSize), 4); + + char format[4]; + wavFile.read(format, 4); + if (strncmp(format, "WAVE", 4) != 0) { + std::cerr << "Error: Not a WAV file" << std::endl; + return dsp::wav::RET_ERROR_NOT_WAV; + } + + // Read the format chunk + char subchunk1Id[4]; + ReadChunkAndSkipJunk(wavFile, subchunk1Id); + if (strncmp(subchunk1Id, "fmt ", 4) != 0) { + std::cerr << "Error: Invalid WAV file" << std::endl; + return dsp::wav::RET_ERROR_INVALID_WAV; + } + + int subchunk1Size; + wavFile.read(reinterpret_cast(&subchunk1Size), 4); + + unsigned short audioFormat; + wavFile.read(reinterpret_cast(&audioFormat), 2); + if (audioFormat != 1) { + std::cerr << "Error: Only PCM format is supported" << std::endl; + return dsp::wav::RET_ERROR_NOT_PCM; + } + + short numChannels; + wavFile.read(reinterpret_cast(&numChannels), 2); + // HACK + if (numChannels != 1) { + std::cerr << "Require mono (using for IR loading)" << std::endl; + return dsp::wav::RET_ERROR_INVALID_WAV; + } + + int iSampleRate; + wavFile.read(reinterpret_cast(&iSampleRate), 4); + // Store in format we assume (SR is double) + sampleRate = (double) iSampleRate; + + int byteRate; + wavFile.read(reinterpret_cast(&byteRate), 4); + + short blockAlign; + wavFile.read(reinterpret_cast(&blockAlign), 2); + + short bitsPerSample; + wavFile.read(reinterpret_cast(&bitsPerSample), 2); + + // Read the data chunk + char subchunk2Id[4]; + ReadChunkAndSkipJunk(wavFile, subchunk2Id); + if (strncmp(subchunk2Id, "data", 4) != 0) { + std::cerr << "Error: Invalid WAV file" << std::endl; + return dsp::wav::RET_ERROR_INVALID_WAV; + } + + // Size of the data chunk, in bits. + int subchunk2Size; + wavFile.read(reinterpret_cast(&subchunk2Size), 4); + + if (bitsPerSample == 16) + dsp::wav::_LoadSamples16(wavFile, subchunk2Size, audio); + else if (bitsPerSample == 24) + dsp::wav::_LoadSamples24(wavFile, subchunk2Size, audio); + else if (bitsPerSample == 32) + dsp::wav::_LoadSamples32(wavFile, subchunk2Size, audio); + else { + std::cerr << "Error: Unsupported bits per sample: " << bitsPerSample << std::endl; + return 1; + } + + // Close the WAV file + wavFile.close(); + + // Print the number of samples + // std::cout << "Number of samples: " << samples.size() << std::endl; + + return dsp::wav::RET_SUCCESS; +} + +void dsp::wav::_LoadSamples16(std::ifstream &wavFile, + const int chunkSize, + std::vector& samples) +{ + // Allocate an array to hold the samples + std::vector tmp(chunkSize / 2); // 16 bits (2 bytes) per sample + + // Read the samples from the file into the array + wavFile.read(reinterpret_cast(tmp.data()), chunkSize); + + // Copy into the return array + const float scale = 1.0 / ((double) (1 << 15)); + samples.resize(tmp.size()); + for (auto i=0; i& samples) +{ + // Allocate an array to hold the samples + std::vector tmp(chunkSize / 3); // 24 bits (3 bytes) per sample + // Read in and convert the samples + for (int& x : tmp) { + x = dsp::wav::_ReadSigned24BitInt(wavFile); + } + + // Copy into the return array + const float scale = 1.0 / ((double) (1 << 23)); + samples.resize(tmp.size()); + for (auto i=0; i(bytes), 3); + + // Combine the three bytes into a single integer using bit shifting and masking. + // This works by isolating each byte using a bit mask (0xff) and then shifting + // the byte to the correct position in the final integer. + int value = bytes[0] | (bytes[1] << 8) | (bytes[2]<<16); + + // The value is stored in two's complement format, so if the most significant + // bit (the 24th bit) is set, then the value is negative. In this case, we + // need to extend the sign bit to get the correct negative value. + if (value & (1 << 23)) { + value |= ~((1 << 24) - 1); + } + + return value; +} + + +void dsp::wav::_LoadSamples32(std::ifstream &wavFile, + const int chunkSize, + std::vector& samples) +{ + // NOTE: 32-bit is float. + samples.resize(chunkSize / 4); // 32 bits (4 bytes) per sample + // Read the samples from the file into the array + wavFile.read(reinterpret_cast(samples.data()), chunkSize); +} diff --git a/NeuralAmpModeler/dsp/wav.h b/NeuralAmpModeler/dsp/wav.h new file mode 100644 index 00000000..c3265820 --- /dev/null +++ b/NeuralAmpModeler/dsp/wav.h @@ -0,0 +1,39 @@ +// +// wav.h +// NeuralAmpModeler-macOS +// +// Created by Steven Atkinson on 12/31/22. +// + +#ifndef wav_h +#define wav_h + +#include "wdlstring.h" // WDL_String + +namespace dsp { + namespace wav { + // Return cases + const int RET_SUCCESS = 0; + const int RET_ERROR_OPENING = 1; + const int RET_ERROR_NOT_WAV = 2; + const int RET_ERROR_INVALID_WAV = 3; + const int RET_ERROR_NOT_PCM = 4; + // Load a WAV file into a provided array of doubles, + // And note the sample rate. + // + // Returns: as per return cases above + int Load(const WDL_String& fileName, std::vector &audio, double& sampleRate); + + // Load samples, 16-bit + void _LoadSamples16(std::ifstream &wavFile, const int chunkSize, std::vector& samples); + // Load samples, 24-bit + void _LoadSamples24(std::ifstream &wavFile, const int chunkSize, std::vector& samples); + // Load samples, 32-bit + void _LoadSamples32(std::ifstream &wavFile, const int chunkSize, std::vector& samples); + + // Read in a 24-bit sample and convert it to an int + int _ReadSigned24BitInt(std::ifstream& stream); + }; +}; + +#endif /* wav_h */ diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj b/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj index 045f0cef..b826f3cb 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj @@ -412,10 +412,12 @@ + + @@ -503,10 +505,13 @@ + + + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj.filters b/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj.filters index 49d4b893..a70bec1a 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj.filters +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-aax.vcxproj.filters @@ -83,6 +83,12 @@ dsp + + dsp + + + dsp + @@ -262,6 +268,15 @@ dsp + + dsp + + + dsp + + + dsp + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj b/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj index 615c0abc..304f879e 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj @@ -322,10 +322,13 @@ + + + @@ -373,10 +376,12 @@ + + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj.filters b/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj.filters index 0cda6585..1cb5d81b 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj.filters +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-app.vcxproj.filters @@ -101,6 +101,12 @@ dsp + + dsp + + + dsp + @@ -310,6 +316,15 @@ dsp + + dsp + + + dsp + + + dsp + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-iOS.xcodeproj/project.pbxproj b/NeuralAmpModeler/projects/NeuralAmpModeler-iOS.xcodeproj/project.pbxproj index 9db95ff8..94de075a 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-iOS.xcodeproj/project.pbxproj +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-iOS.xcodeproj/project.pbxproj @@ -73,6 +73,9 @@ 4FDF6D7F2267CEBA0007B686 /* IPlugAUPlayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4FDF6D7D2267CEBA0007B686 /* IPlugAUPlayer.mm */; }; 91236D811B08F59300734C5E /* NeuralAmpModelerAppExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 91236D771B08F59300734C5E /* NeuralAmpModelerAppExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; AAD9F849295EC49D009DBBA2 /* RecursiveLinearFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F848295EC49D009DBBA2 /* RecursiveLinearFilter.cpp */; }; + AAD9F85D295F762B009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F85C295F762B009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F86E2960D4D0009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F86C2960D4D0009DBBA2 /* wav.cpp */; }; + AAD9F873296377A6009DBBA2 /* Resample.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F872296377A6009DBBA2 /* Resample.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -317,6 +320,11 @@ 91236D771B08F59300734C5E /* NeuralAmpModelerAppExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NeuralAmpModelerAppExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; AAD9F848295EC49D009DBBA2 /* RecursiveLinearFilter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = RecursiveLinearFilter.cpp; path = /Users/steve/src/NeuralAmpModelerPlugin/NeuralAmpModeler/dsp/RecursiveLinearFilter.cpp; sourceTree = ""; }; AAD9F84A295EC4A0009DBBA2 /* RecursiveLinearFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RecursiveLinearFilter.h; path = /Users/steve/src/NeuralAmpModelerPlugin/NeuralAmpModeler/dsp/RecursiveLinearFilter.h; sourceTree = ""; }; + AAD9F85B295F761C009DBBA2 /* ImpulseResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ImpulseResponse.h; sourceTree = ""; }; + AAD9F85C295F762B009DBBA2 /* ImpulseResponse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ImpulseResponse.cpp; sourceTree = ""; }; + AAD9F86C2960D4D0009DBBA2 /* wav.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = wav.cpp; sourceTree = ""; }; + AAD9F86D2960D4D0009DBBA2 /* wav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wav.h; sourceTree = ""; }; + AAD9F872296377A6009DBBA2 /* Resample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Resample.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -359,22 +367,27 @@ 4F3EF90528DE0BA7002972F2 /* dsp */ = { isa = PBXGroup; children = ( - AAD9F84A295EC4A0009DBBA2 /* RecursiveLinearFilter.h */, - AAD9F848295EC49D009DBBA2 /* RecursiveLinearFilter.cpp */, - 4F3EF90728DE0BA7002972F2 /* util.cpp */, + 4F3EF91028DE0BA7002972F2 /* activations.h */, + 4F3EF91428DE0BA7002972F2 /* cnpy.h */, + 4F3EF90A28DE0BA7002972F2 /* cnpy.cpp */, + 4F3EF90E28DE0BA7002972F2 /* dsp.h */, 4F3EF90828DE0BA7002972F2 /* dsp.cpp */, + 4F3EF91328DE0BA7002972F2 /* get_dsp.cpp */, + AAD9F85B295F761C009DBBA2 /* ImpulseResponse.h */, + AAD9F85C295F762B009DBBA2 /* ImpulseResponse.cpp */, 4F3EF90928DE0BA7002972F2 /* lstm.h */, - 4F3EF90A28DE0BA7002972F2 /* cnpy.cpp */, - 4F3EF90B28DE0BA7002972F2 /* wavenet.h */, 4F3EF90C28DE0BA7002972F2 /* lstm.cpp */, - 4F3EF90D28DE0BA7002972F2 /* util.h */, - 4F3EF90E28DE0BA7002972F2 /* dsp.h */, + 4F3EF91228DE0BA7002972F2 /* numpy_util.h */, 4F3EF90F28DE0BA7002972F2 /* numpy_util.cpp */, - 4F3EF91028DE0BA7002972F2 /* activations.h */, + AAD9F84A295EC4A0009DBBA2 /* RecursiveLinearFilter.h */, + AAD9F848295EC49D009DBBA2 /* RecursiveLinearFilter.cpp */, + AAD9F872296377A6009DBBA2 /* Resample.h */, + 4F3EF90D28DE0BA7002972F2 /* util.h */, + 4F3EF90728DE0BA7002972F2 /* util.cpp */, + 4F3EF90B28DE0BA7002972F2 /* wavenet.h */, 4F3EF91128DE0BA7002972F2 /* wavenet.cpp */, - 4F3EF91228DE0BA7002972F2 /* numpy_util.h */, - 4F3EF91328DE0BA7002972F2 /* get_dsp.cpp */, - 4F3EF91428DE0BA7002972F2 /* cnpy.h */, + AAD9F86D2960D4D0009DBBA2 /* wav.h */, + AAD9F86C2960D4D0009DBBA2 /* wav.cpp */, ); name = dsp; path = ../dsp; @@ -732,6 +745,7 @@ buildActionMask = 2147483647; files = ( 4F3EF91828DE0BA7002972F2 /* lstm.h in Headers */, + AAD9F873296377A6009DBBA2 /* Resample.h in Headers */, 4F4856882773C41E005BCF8E /* IPlugAUAudioUnit.h in Headers */, 4F3EF91D28DE0BA7002972F2 /* dsp.h in Headers */, 4F3EF92128DE0BA7002972F2 /* numpy_util.h in Headers */, @@ -946,6 +960,8 @@ 4F914A4C26B4911A00E19BD1 /* IPlugAUViewController.mm in Sources */, 4FA61F8E22E89B4300A92C58 /* IGraphicsIOS.mm in Sources */, 4FA61F9022E8A1F500A92C58 /* IGraphics.cpp in Sources */, + AAD9F85D295F762B009DBBA2 /* ImpulseResponse.cpp in Sources */, + AAD9F86E2960D4D0009DBBA2 /* wav.cpp in Sources */, 4F3EF91928DE0BA7002972F2 /* cnpy.cpp in Sources */, 4F3EF92028DE0BA7002972F2 /* wavenet.cpp in Sources */, 4FA61F7E22E89AFF00A92C58 /* IPlugPluginBase.cpp in Sources */, diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-macOS.xcodeproj/project.pbxproj b/NeuralAmpModeler/projects/NeuralAmpModeler-macOS.xcodeproj/project.pbxproj index fe63d84b..5640eaaa 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-macOS.xcodeproj/project.pbxproj +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-macOS.xcodeproj/project.pbxproj @@ -403,6 +403,32 @@ AAD9F844295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F83A295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp */; }; AAD9F846295E6CB7009DBBA2 /* RecursiveLinearFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F845295E6CB7009DBBA2 /* RecursiveLinearFilter.h */; }; AAD9F847295E6CB7009DBBA2 /* RecursiveLinearFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F845295E6CB7009DBBA2 /* RecursiveLinearFilter.h */; }; + AAD9F84F295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F850295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F851295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F852295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F853295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F854295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F855295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F856295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F857295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F858295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */; }; + AAD9F859295F75F6009DBBA2 /* ImpulseResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F84E295F75F6009DBBA2 /* ImpulseResponse.h */; }; + AAD9F85A295F75F6009DBBA2 /* ImpulseResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F84E295F75F6009DBBA2 /* ImpulseResponse.h */; }; + AAD9F85F2960D0C6009DBBA2 /* wav.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F85E2960D0C6009DBBA2 /* wav.h */; }; + AAD9F8602960D0C6009DBBA2 /* wav.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F85E2960D0C6009DBBA2 /* wav.h */; }; + AAD9F8622960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8632960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8642960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8652960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8662960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8672960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8682960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8692960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F86A2960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F86B2960D4BA009DBBA2 /* wav.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AAD9F8612960D4BA009DBBA2 /* wav.cpp */; }; + AAD9F8702963777E009DBBA2 /* Resample.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F86F2963777E009DBBA2 /* Resample.h */; }; + AAD9F8712963777E009DBBA2 /* Resample.h in Headers */ = {isa = PBXBuildFile; fileRef = AAD9F86F2963777E009DBBA2 /* Resample.h */; }; B885CBC52304AE7300D73128 /* IPlugProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4F8F61A8202807B9003F2573 /* IPlugProcessor.cpp */; }; B8E22A0C220268C4007CBF4C /* IPlugVST3_ProcessorBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B8E22A0A220268C4007CBF4C /* IPlugVST3_ProcessorBase.cpp */; }; B8E22A0D220268C4007CBF4C /* IPlugVST3_ProcessorBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B8E22A0A220268C4007CBF4C /* IPlugVST3_ProcessorBase.cpp */; }; @@ -956,6 +982,11 @@ AAD248502954339400F55DD4 /* AUv3AppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AUv3AppRelease.entitlements; sourceTree = ""; }; AAD9F83A295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RecursiveLinearFilter.cpp; sourceTree = ""; }; AAD9F845295E6CB7009DBBA2 /* RecursiveLinearFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecursiveLinearFilter.h; sourceTree = ""; }; + AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ImpulseResponse.cpp; sourceTree = ""; }; + AAD9F84E295F75F6009DBBA2 /* ImpulseResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ImpulseResponse.h; sourceTree = ""; }; + AAD9F85E2960D0C6009DBBA2 /* wav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wav.h; sourceTree = ""; }; + AAD9F8612960D4BA009DBBA2 /* wav.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = wav.cpp; sourceTree = ""; }; + AAD9F86F2963777E009DBBA2 /* Resample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Resample.h; sourceTree = ""; }; B8E22A0A220268C4007CBF4C /* IPlugVST3_ProcessorBase.cpp */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.cpp.cpp; name = IPlugVST3_ProcessorBase.cpp; path = ../../iPlug2/IPlug/VST3/IPlugVST3_ProcessorBase.cpp; sourceTree = ""; tabWidth = 2; }; B8E22A0B220268C4007CBF4C /* IPlugVST3_ProcessorBase.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = IPlugVST3_ProcessorBase.h; path = ../../iPlug2/IPlug/VST3/IPlugVST3_ProcessorBase.h; sourceTree = ""; tabWidth = 2; }; B8EA6B932203868500D23A86 /* IPlugVST3_Common.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; name = IPlugVST3_Common.h; path = ../../iPlug2/IPlug/VST3/IPlugVST3_Common.h; sourceTree = ""; tabWidth = 2; }; @@ -1239,22 +1270,27 @@ 4F3EF8AA28DE03ED002972F2 /* dsp */ = { isa = PBXGroup; children = ( - AAD9F845295E6CB7009DBBA2 /* RecursiveLinearFilter.h */, - AAD9F83A295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp */, - 4F3EF8AC28DE03ED002972F2 /* util.cpp */, + 4F3EF8B528DE03ED002972F2 /* activations.h */, + 4F3EF8B928DE03ED002972F2 /* cnpy.h */, + 4F3EF8AF28DE03ED002972F2 /* cnpy.cpp */, 4F3EF8B328DE03ED002972F2 /* dsp.h */, 4F3EF8AD28DE03ED002972F2 /* dsp.cpp */, + 4F3EF8B828DE03ED002972F2 /* get_dsp.cpp */, + AAD9F84E295F75F6009DBBA2 /* ImpulseResponse.h */, + AAD9F84D295F75F6009DBBA2 /* ImpulseResponse.cpp */, 4F3EF8AE28DE03ED002972F2 /* lstm.h */, - 4F3EF8AF28DE03ED002972F2 /* cnpy.cpp */, - 4F3EF8B028DE03ED002972F2 /* wavenet.h */, 4F3EF8B128DE03ED002972F2 /* lstm.cpp */, - 4F3EF8B228DE03ED002972F2 /* util.h */, + 4F3EF8B728DE03ED002972F2 /* numpy_util.h */, 4F3EF8B428DE03ED002972F2 /* numpy_util.cpp */, - 4F3EF8B528DE03ED002972F2 /* activations.h */, + AAD9F845295E6CB7009DBBA2 /* RecursiveLinearFilter.h */, + AAD9F83A295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp */, + AAD9F86F2963777E009DBBA2 /* Resample.h */, + 4F3EF8B228DE03ED002972F2 /* util.h */, + 4F3EF8AC28DE03ED002972F2 /* util.cpp */, + 4F3EF8B028DE03ED002972F2 /* wavenet.h */, 4F3EF8B628DE03ED002972F2 /* wavenet.cpp */, - 4F3EF8B728DE03ED002972F2 /* numpy_util.h */, - 4F3EF8B828DE03ED002972F2 /* get_dsp.cpp */, - 4F3EF8B928DE03ED002972F2 /* cnpy.h */, + AAD9F85E2960D0C6009DBBA2 /* wav.h */, + AAD9F8612960D4BA009DBBA2 /* wav.cpp */, ); name = dsp; path = ../dsp; @@ -1888,11 +1924,14 @@ 4F3EF8E228DE03ED002972F2 /* dsp.h in Headers */, 4F3EF8F628DE03ED002972F2 /* numpy_util.h in Headers */, 4F78BE1222E73DD900AD537E /* NeuralAmpModelerAU.h in Headers */, + AAD9F8602960D0C6009DBBA2 /* wav.h in Headers */, 4F4856852773C3B5005BCF8E /* IPlugAUViewController.h in Headers */, 4F3EF8E128DE03ED002972F2 /* util.h in Headers */, 4F3EF8EC28DE03ED002972F2 /* activations.h in Headers */, + AAD9F85A295F75F6009DBBA2 /* ImpulseResponse.h in Headers */, 4F3EF8D728DE03ED002972F2 /* wavenet.h in Headers */, AA355E2E295B688F0061AA3D /* Colors.h in Headers */, + AAD9F8712963777E009DBBA2 /* Resample.h in Headers */, AAD9F847295E6CB7009DBBA2 /* RecursiveLinearFilter.h in Headers */, 4F3EF90028DE03ED002972F2 /* cnpy.h in Headers */, ); @@ -1902,10 +1941,12 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + AAD9F85F2960D0C6009DBBA2 /* wav.h in Headers */, AA355E2D295B688F0061AA3D /* Colors.h in Headers */, 4FC3EFF02086CE5700BD11FA /* eventlist.h in Headers */, 4FC3EFF42086CE5700BD11FA /* module.h in Headers */, 4F03A58C20A4621100EBDFFB /* IGraphicsNanoVG.h in Headers */, + AAD9F8702963777E009DBBA2 /* Resample.h in Headers */, 4F03A5D320A4621100EBDFFB /* IGraphicsStructs.h in Headers */, 4FC3EFF82086CE5700BD11FA /* optional.h in Headers */, 4FC3F0012086CE5700BD11FA /* uid.h in Headers */, @@ -1923,6 +1964,7 @@ 4F03A5D220A4621100EBDFFB /* IGraphicsPopupMenu.h in Headers */, 4FC3EFFE2086CE5700BD11FA /* processdata.h in Headers */, 4FC3F0002086CE5700BD11FA /* stringconvert.h in Headers */, + AAD9F859295F75F6009DBBA2 /* ImpulseResponse.h in Headers */, 4F8C10E720BA2796006320CD /* IGraphicsEditorDelegate.h in Headers */, 4FC3EFFA2086CE5700BD11FA /* parameterchanges.h in Headers */, 4F03A5B320A4621100EBDFFB /* IGraphics_include_in_plug_src.h in Headers */, @@ -2481,6 +2523,7 @@ AAD9F83C295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp in Sources */, 4F3EF8D928DE03ED002972F2 /* lstm.cpp in Sources */, 4F3EF8E428DE03ED002972F2 /* numpy_util.cpp in Sources */, + AAD9F8632960D4BA009DBBA2 /* wav.cpp in Sources */, 4F5F344220C0226200487201 /* IPlugPaths.mm in Sources */, 4F3EF8C528DE03ED002972F2 /* dsp.cpp in Sources */, 4F6369EC20A466470022C370 /* IControl.cpp in Sources */, @@ -2496,6 +2539,7 @@ 4F78D9BB13B63BA50032E0F3 /* IPlugAPIBase.cpp in Sources */, 4FB1F59020E4B010004157C8 /* IGraphicsMac_view.mm in Sources */, 4F3EF8BC28DE03ED002972F2 /* util.cpp in Sources */, + AAD9F850295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F993F7223055C96000313AF /* IPlugProcessor.cpp in Sources */, 4F35DEAE207E5C5A00867D8F /* IPlugPluginBase.cpp in Sources */, 4F6FD2B222675B6300FC59E6 /* IGraphicsCoreText.mm in Sources */, @@ -2512,9 +2556,11 @@ 4F3EF8CA28DE03ED002972F2 /* dsp.cpp in Sources */, 4F4856892773CA76005BCF8E /* NeuralAmpModelerAUv3Appex.m in Sources */, 4F3EF8FD28DE03ED002972F2 /* get_dsp.cpp in Sources */, + AAD9F855295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F3EF8C128DE03ED002972F2 /* util.cpp in Sources */, 4F3EF8E928DE03ED002972F2 /* numpy_util.cpp in Sources */, 4F3EF8F328DE03ED002972F2 /* wavenet.cpp in Sources */, + AAD9F8682960D4BA009DBBA2 /* wav.cpp in Sources */, 4F3EF8DE28DE03ED002972F2 /* lstm.cpp in Sources */, 4F3EF8D428DE03ED002972F2 /* cnpy.cpp in Sources */, ); @@ -2535,6 +2581,8 @@ 4FD52131202A5B9B00A4D22A /* IPlugAU_view_factory.mm in Sources */, 4F993F7423055C96000313AF /* IPlugProcessor.cpp in Sources */, 4F8C10E320BA2796006320CD /* IGraphicsEditorDelegate.cpp in Sources */, + AAD9F852295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, + AAD9F8652960D4BA009DBBA2 /* wav.cpp in Sources */, 4F3EF8D128DE03ED002972F2 /* cnpy.cpp in Sources */, 4F3EF8F028DE03ED002972F2 /* wavenet.cpp in Sources */, 4F6FD2B422675B6300FC59E6 /* IGraphicsCoreText.mm in Sources */, @@ -2589,7 +2637,9 @@ AAD9F844295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp in Sources */, 4F3EF8C328DE03ED002972F2 /* util.cpp in Sources */, 4F3EE1D9231438D000004786 /* RtAudio.cpp in Sources */, + AAD9F86B2960D4BA009DBBA2 /* wav.cpp in Sources */, 4F3EE1DA231438D000004786 /* IGraphicsCoreText.mm in Sources */, + AAD9F858295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F3EE1DB231438D000004786 /* IPlugAPP_main.cpp in Sources */, 4F3EF8CC28DE03ED002972F2 /* dsp.cpp in Sources */, 4F3EE1DD231438D000004786 /* IGraphicsMac.mm in Sources */, @@ -2611,6 +2661,7 @@ 4F7C496A255DDFCB00DF7588 /* IControls.cpp in Sources */, 4F3EF8CB28DE03ED002972F2 /* dsp.cpp in Sources */, 4F78BE1422E7406D00AD537E /* NeuralAmpModeler.h in Sources */, + AAD9F86A2960D4BA009DBBA2 /* wav.cpp in Sources */, 4F0D965C23099F6900BFDED0 /* IPlugProcessor.cpp in Sources */, 4F78BE1522E7406D00AD537E /* NeuralAmpModeler.cpp in Sources */, 4F3EF8F428DE03ED002972F2 /* wavenet.cpp in Sources */, @@ -2632,6 +2683,7 @@ 4F3EF8DF28DE03ED002972F2 /* lstm.cpp in Sources */, 4F78BE2522E7406D00AD537E /* IPlugPluginBase.cpp in Sources */, 4F78BE2622E7406D00AD537E /* IPlugAPIBase.cpp in Sources */, + AAD9F857295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F78BE2822E7406D00AD537E /* IPlugParameter.cpp in Sources */, 4F78BE2922E7406D00AD537E /* IPlugTimer.cpp in Sources */, 4F3EF8EA28DE03ED002972F2 /* numpy_util.cpp in Sources */, @@ -2684,12 +2736,14 @@ 4F6369DF20A464BB0022C370 /* IGraphicsNanoVG_src.m in Sources */, 4F815991205D51F000393585 /* vstcomponentbase.cpp in Sources */, 4F03A5AE20A4621100EBDFFB /* IGraphics.cpp in Sources */, + AAD9F851295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F815987205D50EB00393585 /* conststringtable.cpp in Sources */, 4F722020225C1EB100FF0E7C /* commoniids.cpp in Sources */, 4FB1F59220E4B012004157C8 /* IGraphicsMac_view.mm in Sources */, 4F81598D205D51F000393585 /* vstaudioeffect.cpp in Sources */, 4F81598C205D51CF00393585 /* vstsinglecomponenteffect.cpp in Sources */, 4F815984205D50EB00393585 /* updatehandler.cpp in Sources */, + AAD9F8642960D4BA009DBBA2 /* wav.cpp in Sources */, 4F815990205D51F000393585 /* vstcomponent.cpp in Sources */, 4F815981205D50EB00393585 /* fstreamer.cpp in Sources */, 4F815996205D51F000393585 /* vstrepresentation.cpp in Sources */, @@ -2711,6 +2765,7 @@ 4F7C4961255DDFC600DF7588 /* IControls.cpp in Sources */, 4F3EF8C828DE03ED002972F2 /* dsp.cpp in Sources */, 4F993F7523055C97000313AF /* IPlugProcessor.cpp in Sources */, + AAD9F8662960D4BA009DBBA2 /* wav.cpp in Sources */, 4FDAC0EE207D76C600299363 /* IPlugTimer.cpp in Sources */, 4F8C10E420BA2796006320CD /* IGraphicsEditorDelegate.cpp in Sources */, 4F3EF8F128DE03ED002972F2 /* wavenet.cpp in Sources */, @@ -2732,6 +2787,7 @@ 4F3EF8DC28DE03ED002972F2 /* lstm.cpp in Sources */, 4FB600231567CB0A0020189A /* IPlugParameter.cpp in Sources */, 4FB600261567CB0A0020189A /* AAX_Exports.cpp in Sources */, + AAD9F853295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F6FD2B522675B6300FC59E6 /* IGraphicsCoreText.mm in Sources */, 4FB600281567CB0A0020189A /* IPlugAAX_Describe.cpp in Sources */, 4F3EF8E728DE03ED002972F2 /* numpy_util.cpp in Sources */, @@ -2746,6 +2802,7 @@ files = ( 4F7C4964255DDFC800DF7588 /* IControls.cpp in Sources */, 4F03A5B220A4621100EBDFFB /* IGraphics.cpp in Sources */, + AAD9F856295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F7C4966255DDFC800DF7588 /* ITextEntryControl.cpp in Sources */, AAD9F842295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp in Sources */, 4FB1F58F20E4B009004157C8 /* IGraphicsMac.mm in Sources */, @@ -2755,6 +2812,7 @@ 4FC3EFF92086CE5700BD11FA /* parameterchanges.cpp in Sources */, 4FC3EFCE2086C35D00BD11FA /* IPlugPluginBase.cpp in Sources */, 4F7C4965255DDFC800DF7588 /* IPopupMenuControl.cpp in Sources */, + AAD9F8692960D4BA009DBBA2 /* wav.cpp in Sources */, 4F722021225C1EB100FF0E7C /* commoniids.cpp in Sources */, 4FB1F59620E4B017004157C8 /* IGraphicsMac_view.mm in Sources */, 4F472103209B294400A0A0A8 /* IPlugVST3_Controller.cpp in Sources */, @@ -2797,7 +2855,9 @@ AAD9F83B295E6C8D009DBBA2 /* RecursiveLinearFilter.cpp in Sources */, 4F3EF8BB28DE03ED002972F2 /* util.cpp in Sources */, 4FAFFE5821495A4800A6E72D /* RtAudio.cpp in Sources */, + AAD9F8622960D4BA009DBBA2 /* wav.cpp in Sources */, 4F6FD2B122675B6300FC59E6 /* IGraphicsCoreText.mm in Sources */, + AAD9F84F295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4F690C9B203A345100A4A13E /* IPlugAPP_main.cpp in Sources */, 4F3EF8C428DE03ED002972F2 /* dsp.cpp in Sources */, 4FB1F58920E4B004004157C8 /* IGraphicsMac.mm in Sources */, @@ -2822,12 +2882,14 @@ 4FFBB90820863B0E00DDD0E7 /* pluginfactory.cpp in Sources */, 4FFBB90920863B0E00DDD0E7 /* vstinitiids.cpp in Sources */, 4FFBB90A20863B0E00DDD0E7 /* fdebug.cpp in Sources */, + AAD9F8672960D4BA009DBBA2 /* wav.cpp in Sources */, 4FFBB90C20863B0E00DDD0E7 /* IPlugAPIBase.cpp in Sources */, B8E22A0D220268C4007CBF4C /* IPlugVST3_ProcessorBase.cpp in Sources */, 4FFBB90D20863B0E00DDD0E7 /* fdynlib.cpp in Sources */, 4FFBB90E20863B0E00DDD0E7 /* memorystream.cpp in Sources */, 4F3EF8F228DE03ED002972F2 /* wavenet.cpp in Sources */, 4FFBB90F20863B0E00DDD0E7 /* IPlugParameter.cpp in Sources */, + AAD9F854295F75F6009DBBA2 /* ImpulseResponse.cpp in Sources */, 4FFBB91020863B0E00DDD0E7 /* pluginview.cpp in Sources */, 4FFBB91120863B0E00DDD0E7 /* fstring.cpp in Sources */, 4FFBB91320863B0E00DDD0E7 /* vstpresetfile.cpp in Sources */, diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj b/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj index 4426d5e4..7d532780 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj @@ -299,10 +299,13 @@ + + + @@ -341,10 +344,12 @@ + + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj.filters b/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj.filters index e965e4f7..39392fe1 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj.filters +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-vst2.vcxproj.filters @@ -72,6 +72,12 @@ dsp + + dsp + + + dsp + @@ -245,6 +251,15 @@ dsp + + dsp + + + dsp + + + dsp + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj b/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj index 8f7a0aa4..fc793c0c 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj @@ -331,10 +331,13 @@ + + + @@ -401,10 +404,12 @@ + + diff --git a/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj.filters b/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj.filters index d6967fc3..76f08772 100644 --- a/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj.filters +++ b/NeuralAmpModeler/projects/NeuralAmpModeler-vst3.vcxproj.filters @@ -158,6 +158,12 @@ dsp + + dsp + + + dsp + @@ -409,6 +415,15 @@ dsp + + dsp + + + dsp + + + dsp + diff --git a/NeuralAmpModeler/resources/img/close-button.svg b/NeuralAmpModeler/resources/img/close-button.svg new file mode 100644 index 00000000..834b1a7b --- /dev/null +++ b/NeuralAmpModeler/resources/img/close-button.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + Close Button + 2013-09-18T11:23:45 + A round black close button with a white, thick, X in the center. + https://openclipart.org/detail/183568/close-button-by-henrikhoff-183568 + + + henrikhoff + + + + + black + close button + cross + icon + white + x + + + + + + + + + + + diff --git a/NeuralAmpModeler/resources/main.rc b/NeuralAmpModeler/resources/main.rc index 18c177b9..98201bcc 100644 --- a/NeuralAmpModeler/resources/main.rc +++ b/NeuralAmpModeler/resources/main.rc @@ -233,6 +233,7 @@ ROBOTO_FN TTF ROBOTO_FN TOLEX_FN JPEG TOLEX_FN TOLEX2X_FN JPEG TOLEX2X_FN FOLDER_FN SVG FOLDER_FN +CLOSE_BUTTON_FN SVG CLOSE_BUTTON_FN HELP_FN SVG HELP_FN ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED