diff --git a/.gitignore b/.gitignore index 8a64b46..f755f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .vscode +build_arm64 +build_x86_64 build dist diff --git a/NeuralResonatorVST/CMakeLists.txt b/NeuralResonatorVST/CMakeLists.txt index da5cd9d..72e391c 100644 --- a/NeuralResonatorVST/CMakeLists.txt +++ b/NeuralResonatorVST/CMakeLists.txt @@ -121,7 +121,13 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") ${TORCH_INSTALL_PREFIX}/lib/libc10.dylib ) - # if the architecture is arm64, we need to add the arm64e library + # force set the architecture variable to the current architecture + # this is needed because the CMAKE_OSX_ARCHITECTURES variable is not set + if (NOT CMAKE_OSX_ARCHITECTURES) + set(CMAKE_OSX_ARCHITECTURES ${CMAKE_SYSTEM_PROCESSOR} CACHE STRING "" FORCE) + endif() + + # if the architecture is arm64, we need to add the libiomp5 library if (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64") list(APPEND TORCH_LIBS ${TORCH_INSTALL_PREFIX}/lib/libiomp5.dylib) endif() diff --git a/NeuralResonatorVST/Filterbank.cpp b/NeuralResonatorVST/Filterbank.cpp index 529fee6..342f839 100644 --- a/NeuralResonatorVST/Filterbank.cpp +++ b/NeuralResonatorVST/Filterbank.cpp @@ -1,6 +1,18 @@ #include "Filterbank.h" +#include "HelperFunctions.h" +Filterbank::Filterbank() +{ + mNumParallel = 0; + mNumBiquads = 0; + mStride = 0; +} Filterbank::Filterbank(int numParallel, int numBiquads) +{ + setup(numParallel, numBiquads); +} + +void Filterbank::setup(int numParallel, int numBiquads) { mNumParallel = numParallel; mNumBiquads = numBiquads; @@ -17,17 +29,28 @@ Filterbank::Filterbank(int numParallel, int numBiquads) { for (int j = 0; j < mNumBiquads; j++) { - mIIRFilters[i][j].set_coefficients(0.0, 0.0, 0.0, 0.0, 0.0); + mIIRFilters[i][j].setCoefficientValues(0.0, 0.0, 0.0, 0.0, 0.0); } } } -Filterbank::~Filterbank() {} +Filterbank::~Filterbank() +{ + cleanup(); +} + +void Filterbank::cleanup() +{ + JLOG("Filterbank::cleanup()"); + for (int i = 0; i < mNumParallel; i++) + { + for (int j = 0; j < mNumBiquads; j++) { mIIRFilters[i][j].cleanup(); } + } +} void Filterbank::setCoefficients(const std::vector& coeffs) { const juce::SpinLock::ScopedLockType lock(mProcessLock); - for (int i = 0; i < mNumParallel; i++) { for (int j = 0; j < mNumBiquads; j++) @@ -69,4 +92,15 @@ void Filterbank::processBuffer(juce::AudioBuffer& buffer) buffer.setSample(channel, sampleIdx, static_cast(out)); } } -} \ No newline at end of file +} +void Filterbank::setInterpolationDelta(unsigned int delta) +{ + mInterpolationDelta = delta; + for (int i = 0; i < mNumParallel; i++) + { + for (int j = 0; j < mNumBiquads; j++) + { + mIIRFilters[i][j].setDelta(mInterpolationDelta); + } + } +} diff --git a/NeuralResonatorVST/Filterbank.h b/NeuralResonatorVST/Filterbank.h index 006630a..cf1b1b3 100644 --- a/NeuralResonatorVST/Filterbank.h +++ b/NeuralResonatorVST/Filterbank.h @@ -1,6 +1,6 @@ #pragma once -#include "TwoPole.h" +#include "TwoPoleInterpolated.h" #include #include #include @@ -8,9 +8,13 @@ class Filterbank { public: - Filterbank(int numParallel, int numBiquads); + Filterbank(); ~Filterbank(); + Filterbank(int numParallel, int numBiquads); + void setup(int numParallel, int numBiquads); + + void cleanup(); /** * @brief Set the coefficients of the filterbank * @note The coefficients are expected to be in the following order: @@ -28,12 +32,15 @@ class Filterbank */ void processBuffer(juce::AudioBuffer& buffer); + void setInterpolationDelta(unsigned int delta); + private: - std::vector>> mIIRFilters; + std::vector> mIIRFilters; int mNumParallel; int mNumBiquads; int mStride; + unsigned int mInterpolationDelta; juce::SpinLock mProcessLock; diff --git a/NeuralResonatorVST/Lerp.h b/NeuralResonatorVST/Lerp.h new file mode 100644 index 0000000..8d3be14 --- /dev/null +++ b/NeuralResonatorVST/Lerp.h @@ -0,0 +1,128 @@ +#pragma once + +#include + +template +class Lerp +{ + public: + Lerp(); + ~Lerp(); + + Lerp(unsigned int delta); + void setup(unsigned int delta); + + void setTarget(T target); + void setValue(T value); + void setDelta(unsigned int delta); + + T process(bool bound=true); + + T getTarget() { return m_target; } + T getValue() { return m_val; } + T getDelta() { return m_delta; } + + bool isFinished() { return m_counter == 0; } + + private: + T m_val; + T m_increment; + int m_counter; + unsigned int m_delta; + T m_target; + + void calcIncrement(T target, unsigned int delta); + T boundValue(); +}; + +template +inline Lerp::Lerp() +{ + setup(0); +} + +template +inline Lerp::Lerp(unsigned int delta) +{ + setup(delta); +} + +template +inline void Lerp::setup(unsigned int delta) +{ + m_val = 0; + m_increment = 0; + m_counter = 0; + m_delta = delta; + m_target = 0; +} + +template +inline void Lerp::setTarget(T target) +{ + m_target = target; + calcIncrement(m_target, m_delta); +} + +template +inline void Lerp::setValue(T value) +{ + m_val = value; + m_counter = 0; + m_increment = 0; +} + +template +inline void Lerp::setDelta(unsigned int delta) +{ + m_delta = delta; + calcIncrement(m_target, m_delta); +} + +template +inline void Lerp::calcIncrement(T target, unsigned int delta) +{ + if(delta == 0) + m_increment = 0.0; + else + m_increment = (target - m_val) / static_cast(delta); + + if(m_increment == 0.0) + { + m_counter = 0; + m_val = target; + } + else + { + m_counter = delta; + } +} + +template +inline T Lerp::process(bool bound) +{ + if (m_counter > 0) + { + m_val += m_increment; + m_counter--; + } + if (bound) + m_val = boundValue(); + return m_val; +} + +template +inline T Lerp::boundValue() +{ + if (m_increment > 0 && m_val > m_target) + m_val = m_target; + else if (m_increment < 0 && m_val < m_target) + m_val = m_target; + return m_val; +} + +template +inline Lerp::~Lerp() +{ + juce::Logger::writeToLog("Lerp::~Lerp"); +} diff --git a/NeuralResonatorVST/ParallelLerp.h b/NeuralResonatorVST/ParallelLerp.h new file mode 100644 index 0000000..3489635 --- /dev/null +++ b/NeuralResonatorVST/ParallelLerp.h @@ -0,0 +1,283 @@ +#pragma once +#include + +#include "Lerp.h" +#include + +template +class ParallelLerp +{ +public: + ParallelLerp(){}; + ~ParallelLerp(); + + ParallelLerp(unsigned int nInterp, unsigned int delta); + + void cleanup(); + void setup(unsigned int nInterp, unsigned int delta); + + void setup(const T* values, unsigned int nValues, unsigned int delta); + template + void setup(std::vector const& values, unsigned int delta); + template + void setup(T const (&values)[N], unsigned int delta); + + bool setValues(const T* values, unsigned int nValues); + template + bool setValues(std::vector const& values); + template + bool setValues(T const (&values)[N]); + + bool setValue(unsigned int index, T value); + + bool setTargets(const T* targets, unsigned int nValues); + template + bool setTargets(std::vector const& targets); + template + bool setTargets(T const (&targets)[N]); + + bool setTarget(unsigned int index, T target); + + void setDelta(unsigned int delta); + unsigned int getDelta(); + + bool isFinished(); + + const T* process(); + std::vector getValues(); + const T* getValuesPtr(); + unsigned int getNValues(); + unsigned int getNInterpolators(); + + T getTarget(unsigned int index); + T getValue(unsigned int index); + + Lerp getInterpolator(unsigned int index); + +private: + std::vector> m_interpolators; + std::vector m_values; +}; + +template +inline ParallelLerp::ParallelLerp(unsigned int nInterp, unsigned int delta) +{ + setup(nInterp, delta); +} + +template +inline void ParallelLerp::cleanup() +{ + juce::Logger::writeToLog("ParallelLerp::cleanup()"); +} + +template +inline ParallelLerp::~ParallelLerp() +{ + cleanup(); +} + +template +inline void ParallelLerp::setup(unsigned int nInterp, unsigned int delta) +{ + m_interpolators.resize(nInterp); + m_values.resize(nInterp); + for (unsigned int i = 0; i < nInterp; i++) + { + m_interpolators[i].setup(delta); + } + juce::Logger::writeToLog("ParallelLerp::setup()"); + juce::Logger::writeToLog( + "Number of interpolators: " + std::to_string(m_interpolators.size()) + ); + juce::Logger::writeToLog( + "Number of values: " + std::to_string(m_values.size()) + ); +} + +template +inline void ParallelLerp::setup( + const T* values, + unsigned int nValues, + unsigned int delta +) +{ + m_interpolators.resize(nValues); + m_values.resize(nValues); + for (unsigned int i = 0; i < nValues; i++) + { + m_interpolators[i].setup(delta); + m_values[i] = values[i]; + m_interpolators[i].setValue(m_values[i]); + } +} + +template +template +inline void ParallelLerp::setup( + std::vector const& values, + unsigned int delta +) +{ + setup(values.data(), values.size(), delta); +} + +template +template +inline void ParallelLerp::setup(T const (&values)[N], unsigned int delta) +{ + setup(values, N * sizeof(T), delta); +} + +template +inline bool ParallelLerp::setValues(const T* values, unsigned int nValues) +{ + if (nValues != m_interpolators.size()) return false; + for (unsigned int i = 0; i < nValues; i++) + { + m_interpolators[i].setValues(values[i]); + m_values[i] = values[i]; + } + return true; +} + +template +template +inline bool ParallelLerp::setValues(std::vector const& values) +{ + return setValues(values.data(), values.size()); +} + +template +template +inline bool ParallelLerp::setValues(T const (&values)[N]) +{ + return setValues(values, N * sizeof(T)); +} + +template +inline bool + ParallelLerp::setTargets(const T* targets, unsigned int nTargets) +{ + if (nTargets != m_interpolators.size()) return false; + + for (unsigned int i = 0; i < nTargets; i++) + { + m_interpolators[i].setTarget(targets[i]); + } + return true; +} + +template +template +inline bool ParallelLerp::setTargets(std::vector const& targets) +{ + return setTargets(targets.data(), targets.size()); +} + +template +template +inline bool ParallelLerp::setTargets(T const (&targets)[N]) +{ + return setTargets(targets, N * sizeof(T)); +} + +template +inline void ParallelLerp::setDelta(unsigned int delta) +{ + juce::Logger::writeToLog( + "ParallelLerp::setDelta " + std::to_string(delta) + ); + for (unsigned int i = 0; i < m_interpolators.size(); i++) + { + m_interpolators[i].setDelta(delta); + } +} + +template +inline bool ParallelLerp::isFinished() +{ + for (unsigned int i = 0; i < m_interpolators.size(); i++) + { + if (!m_interpolators[i].isFinished()) return false; + } + return true; +} + +template +inline const T* ParallelLerp::process() +{ + for (unsigned int i = 0; i < m_interpolators.size(); i++) + { + m_values[i] = m_interpolators[i].process(); + } + return m_values.data(); +} + +template +inline std::vector ParallelLerp::getValues() +{ + return m_values; +} + +template +inline const T* ParallelLerp::getValuesPtr() +{ + return m_values.data(); +} + +template +inline unsigned int ParallelLerp::getNValues() +{ + return m_values.size(); +} + +template +inline Lerp ParallelLerp::getInterpolator(unsigned int index) +{ + return m_interpolators[index]; +} + +template +inline T ParallelLerp::getTarget(unsigned int index) +{ + if (index >= m_interpolators.size()) return T(); + return m_interpolators[index].getTarget(); +} + +template +inline bool ParallelLerp::setValue(unsigned int index, T value) +{ + if (index >= m_interpolators.size()) return false; + m_interpolators[index].setValue(value); + m_values[index] = value; + return true; +} + +template +inline bool ParallelLerp::setTarget(unsigned int index, T target) +{ + if (index >= m_interpolators.size()) { return false; } + m_interpolators[index].setTarget(target); + return true; +} + +template +inline T ParallelLerp::getValue(unsigned int index) +{ + if (index >= m_interpolators.size()) return T(); + return m_values[index]; +} + +template +inline unsigned int ParallelLerp::getNInterpolators() +{ + return m_interpolators.size(); +} + +template +inline unsigned int ParallelLerp::getDelta() +{ + if (m_interpolators.size() == 0) return 0; + return m_interpolators[0].getDelta(); +} diff --git a/NeuralResonatorVST/PluginProcessor.cpp b/NeuralResonatorVST/PluginProcessor.cpp index 4b5c10e..796c3cb 100644 --- a/NeuralResonatorVST/PluginProcessor.cpp +++ b/NeuralResonatorVST/PluginProcessor.cpp @@ -34,41 +34,45 @@ AudioPluginAudioProcessor::AudioPluginAudioProcessor() createAndAppendValueTree(); // location of the index.html file inside the plugin bundle - mIndexFile = - juce::File::getSpecialLocation( - juce::File::SpecialLocationType::currentApplicationFile) - .getChildFile("Contents") - .getChildFile("Resources") - .getChildFile("index.html") - .getFullPathName(); - + mIndexFile = juce::File::getSpecialLocation( + juce::File::SpecialLocationType::currentApplicationFile + ) + .getChildFile("Contents") + .getChildFile("Resources") + .getChildFile("index.html") + .getFullPathName(); + // check that the index file exists - if (!juce::File(mIndexFile).existsAsFile()) { + if (!juce::File(mIndexFile).existsAsFile()) + { JLOG("index.html not found"); } - // location of the pretrained models inside the plugin bundle + // location of the pretrained models inside the plugin bundle auto encoderPath = juce::File::getSpecialLocation( - juce::File::SpecialLocationType::currentApplicationFile) + juce::File::SpecialLocationType::currentApplicationFile + ) .getChildFile("Contents") .getChildFile("Resources") .getChildFile("encoder.pt") .getFullPathName(); - auto fcPath = - juce::File::getSpecialLocation( - juce::File::SpecialLocationType::currentApplicationFile) - .getChildFile("Contents") - .getChildFile("Resources") - .getChildFile("model_wrap.pt") - .getFullPathName(); - + auto fcPath = juce::File::getSpecialLocation( + juce::File::SpecialLocationType::currentApplicationFile + ) + .getChildFile("Contents") + .getChildFile("Resources") + .getChildFile("model_wrap.pt") + .getFullPathName(); + // check that the models exist - if (!juce::File(encoderPath).existsAsFile()) { + if (!juce::File(encoderPath).existsAsFile()) + { JLOG("encoder.pt not found"); } - if (!juce::File(fcPath).existsAsFile()) { + if (!juce::File(fcPath).existsAsFile()) + { JLOG("model_wrap.pt not found"); } @@ -173,6 +177,12 @@ void AudioPluginAudioProcessor::prepareToPlay( JLOG("prepareToPlay"); // Use this method as the place to do any pre-playback // initialisation that you need.. + + // Set interpolation delta for filterbank (0.05 seconds) + float interpolationDeltaSeconds = 0.05; + unsigned int interpolationDelta = + (unsigned int)(interpolationDeltaSeconds * sampleRate); + mFilterbank.setInterpolationDelta(interpolationDelta); } void AudioPluginAudioProcessor::releaseResources() @@ -237,12 +247,12 @@ void AudioPluginAudioProcessor::processBlock( // https://forum.juce.com/t/1-most-common-programming-mistake-that-we-see-on-the-forum/26013 // if we receive any midi message and the - // buffer is empty, then create a buffer + // buffer is empty, then create a buffer // with an impulse if (midiMessages.getNumEvents() > 0) { // get midi on - for (const auto &midi : midiMessages) + for (const auto& midi : midiMessages) { if (midi.getMessage().isNoteOn()) { @@ -284,7 +294,9 @@ void AudioPluginAudioProcessor::getStateInformation( // complex data. JLOG("AudioPluginAudioProcessor::getStateInformation"); juce::MemoryOutputStream stream(destData, false); - auto str = juce::JSON::toString(HelperFunctions::convertToVar(mParameters.state)); + auto str = + juce::JSON::toString(HelperFunctions::convertToVar(mParameters.state) + ); // JLOG("AudioPluginAudioProcessor::getStateInformation: " + str); stream.writeString(str); } @@ -299,11 +311,11 @@ void AudioPluginAudioProcessor::setStateInformation( // getStateInformation() call. //! Warning: this seems to delete the callbacks if not handled properly JLOG("AudioPluginAudioProcessor::setStateInformation"); - juce::MemoryInputStream stream(data, size_t (sizeInBytes), true); + juce::MemoryInputStream stream(data, size_t(sizeInBytes), true); auto jsonAsStr = stream.readEntireStreamAsString(); // JLOG(jsonAsStr); auto json = juce::JSON::parse(jsonAsStr); - mParameters.replaceState(HelperFunctions::convertToValueTree(json)); + mParameters.replaceState(HelperFunctions::convertToValueTree(json)); } void AudioPluginAudioProcessor::coefficentsChanged( @@ -368,7 +380,7 @@ juce::AudioProcessorValueTreeState::ParameterLayout "alpha", // parameterID "Alpha", // parameter name juce::NormalisableRange( // range - -0.5f, + -0.5f, 1.5f, 0.01f ), // min, max, interval diff --git a/NeuralResonatorVST/TwoPole.h b/NeuralResonatorVST/TwoPole.h index f014ca7..3c32a0e 100644 --- a/NeuralResonatorVST/TwoPole.h +++ b/NeuralResonatorVST/TwoPole.h @@ -3,7 +3,9 @@ #include #include #include +#include +#define N_COEFFICIENTS_TWO_POLE 5 template class TwoPole { @@ -12,34 +14,66 @@ class TwoPole void set_coefficients(T b0, T b1, T b2, T a1, T a2) { - m_a1 = a1; - m_a2 = a2; - m_b0 = b0; - m_b1 = b1; - m_b2 = b2; + m_coeff[0] = a1; + m_coeff[1] = a2; + m_coeff[2] = b0; + m_coeff[3] = b1; + m_coeff[4] = b2; } - std::vector get_coefficients() + void set_coefficients(const T* coefficients, unsigned int nCoefficients) { - std::vector coefficients = {m_b0, m_b1, m_b2, m_a1, m_a2}; + if (nCoefficients == N_COEFFICIENTS_TWO_POLE) + { + m_coeff[0] = coefficients[0]; + m_coeff[1] = coefficients[1]; + m_coeff[2] = coefficients[2]; + m_coeff[3] = coefficients[3]; + m_coeff[4] = coefficients[4]; + } + } + + template + void set_coefficients(const std::vector& coefficients) + { + if (coefficients.size() == N_COEFFICIENTS_TWO_POLE) + { + set_coefficients(coefficients.data(), coefficients.size()); + } + } + + std::vector get_coefficients_vector() + { + std::vector coefficients(std::begin(m_coeff), std::end(m_coeff)); return coefficients; } + T* get_coefficients_ptr() { return &m_coeff[0]; } + + const T& get_coefficients_ref() { return m_coeff; } + + T get_coefficient(unsigned int index) { return m_coeff[index]; } + T process(T x) { - T y = m_b0 * x + m_s0; - m_s0 = m_b1 * x - m_a1 * y + m_s1; - m_s1 = m_b2 * x - m_a2 * y; + T y = m_coeff[2] * x + m_s0; + m_s0 = m_coeff[3] * x - m_coeff[0] * y + m_s1; + m_s1 = m_coeff[4] * x - m_coeff[1] * y; return y; } std::string to_string() { std::stringstream ss; - ss << "a1: " << m_a1 << " a2: " << m_a2 << " b0: " << m_b0 << " b1: " << m_b1 << " b2: " << m_b2; + ss << "a0: " << m_coeff[0] << " a2: " << m_coeff[1] + << " b0: " << m_coeff[2] << " b1: " << m_coeff[3] + << " b2: " << m_coeff[4]; return ss.str(); } + + unsigned int get_n_coefficients() { return N_COEFFICIENTS_TWO_POLE; } + private: - T m_a1, m_a2, m_b0, m_b1, m_b2 = 0.0f; + T m_coeff[N_COEFFICIENTS_TWO_POLE]; // a1, a2, b0, b1, b2 T m_s0, m_s1 = 0.0f; -}; \ No newline at end of file +}; diff --git a/NeuralResonatorVST/TwoPoleInterpolated.h b/NeuralResonatorVST/TwoPoleInterpolated.h new file mode 100644 index 0000000..7b63892 --- /dev/null +++ b/NeuralResonatorVST/TwoPoleInterpolated.h @@ -0,0 +1,142 @@ +#pragma once +#include "HelperFunctions.h" +#include "TwoPole.h" +#include "ParallelLerp.h" + +// #define NO_INTERPOLATION + +class TwoPoleInterpolated : public TwoPole +{ +public: + TwoPoleInterpolated() + : m_interpolator(N_COEFFICIENTS_TWO_POLE, 0){}; + ~TwoPoleInterpolated(); + + TwoPoleInterpolated(unsigned int delta); + void setup(unsigned int delta); + + double process(double x); + + void setDelta(unsigned int delta); + + void setCoefficientValues( + double b0, + double b1, + double b2, + double a1, + double a2 + ); + void setCoefficientTargets( + double b0, + double b1, + double b2, + double a1, + double a2 + ); + + void set_coefficients( + double b0, + double b1, + double b2, + double a1, + double a2 + ); + + void cleanup(); + +private: + ParallelLerp m_interpolator; + bool m_interpolationFinished = false; +}; + +inline TwoPoleInterpolated::TwoPoleInterpolated(unsigned int delta) +{ + setup(delta); +} + +inline TwoPoleInterpolated::~TwoPoleInterpolated() +{ + cleanup(); +} + +inline void TwoPoleInterpolated::setup(unsigned int delta) +{ + // m_interpolator.setup(N_COEFFICIENTS_TWO_POLE, delta); + setDelta(delta); +} + +inline void TwoPoleInterpolated::setDelta(unsigned int delta) +{ + m_interpolator.setDelta(delta); +} + +inline void TwoPoleInterpolated::setCoefficientValues( + double b0, + double b1, + double b2, + double a1, + double a2 +) +{ + m_interpolator.setValue(0, b0); + m_interpolator.setValue(1, b1); + m_interpolator.setValue(2, b2); + m_interpolator.setValue(3, a1); + m_interpolator.setValue(4, a2); + + TwoPole::set_coefficients(b0, b1, b2, a1, a2); +} + +inline void TwoPoleInterpolated::setCoefficientTargets( + double b0, + double b1, + double b2, + double a1, + double a2 +) +{ + m_interpolator.setTarget(0, b0); + m_interpolator.setTarget(1, b1); + m_interpolator.setTarget(2, b2); + m_interpolator.setTarget(3, a1); + m_interpolator.setTarget(4, a2); +} + +inline double TwoPoleInterpolated::process(double x) +{ +#ifndef NO_INTERPOLATION + if (!m_interpolator.isFinished() || m_interpolator.getDelta() == 0) + { + m_interpolator.process(); + TwoPole::set_coefficients( + m_interpolator.getValue(0), + m_interpolator.getValue(1), + m_interpolator.getValue(2), + m_interpolator.getValue(3), + m_interpolator.getValue(4) + ); + } +#endif + return TwoPole::process(x); +} + +inline void TwoPoleInterpolated::cleanup() +{ + JLOG("TwoPoleInterpolated::cleanup()"); + m_interpolator.cleanup(); +} + +inline void TwoPoleInterpolated::set_coefficients( + double b0, + double b1, + double b2, + double a1, + double a2 +) +{ +#ifdef NO_INTERPOLATION + setCoefficientValues(b0, b1, b2, a1, a2); +#else + setCoefficientTargets(b0, b1, b2, a1, a2); +#endif +}