[Free Dsp] Oberheim-8 Analog Filter
-
Polyphonic 12db/oct Filter with keytracking.
Created from a linear (non saturating) analysis of the OB8 lowpass.
It's not a match for high resonance settings, but it has good intuition at low resonance. You are welcome to tune it further by modifying the code.CPU usage: ≈1%
Instructions for setup included inside the script.
The "Eigen" header library is required:
https://github.com/PX4/eigenDownload the Filter Script here:
https://1drv.ms/u/c/6c19b197e5968b36/ER8vuJpevI9Htt_WY8Mqs8sBBJ15n8JLf-MJdSNgwjZX5g?e=zXd7gZRaw Code:
#pragma once #include <JuceHeader.h> #include <cmath> #include "src\eigen-master\Eigen\Dense" /** ==============| by GriffinBoy (2025) |============== This node implements an OB8 2-SVF (State-Variable Filter) with global feedback using the Delay-Free method as described in DAFx-2020. Features: - Parameter smoothing via Juce's SmoothedValue class - Bilinear (TPT) transform implementation - Optimized 4x4 matrix inversion using the Eigen library - Recalculates matrices only when parameters change (dirty flags) - Keytracking support for cutoff frequency - (rough) Resonance compensation for frequency shift - (rough) Gain compensation for resonance control Integration Steps for HISE: 1. Eigen Setup: Download and place the Eigen library under: ProjectName/DspNetworks/ThirdParty/src/eigen-master 2. Create a 3rd party C++ node in HISE named "Griffin_OBFilter" 3. Compile the initial DLL in HISE using "Compile dsp networks as .dll" 4. Replace the generated "Griffin_OBFilter.h" (in ProjectName/DspNetworks/ThirdParty) with this header file you are reading now 5. Re-compile the final DLL in HISE using "Compile dsp networks as .dll" Continuous-Time System Definition: x1'(t) = -2*r*x1 - x2 - kf*x4 + input x2'(t) = x1 x3'(t) = -2*r*x3 - x4 + x2 x4'(t) = x3 (Output is taken from state x4) Bilinear Transform: M = I - g*A, N = I + g*A, where g = 2 * tan(pi * fc / fs) (M is inverted using Eigen's optimized fixed-size matrix inversion) Optimizations: - Precomputed reciprocals and constants - Eigen fixed-size matrices for 4x4 operations - Dirty flags for matrix recalculation only when parameters change Author: GriffinBoy (2025) License: UwU */ namespace project { using namespace juce; using namespace hise; using namespace scriptnode; template <int NV> // NV = Number of Voices struct Griffin_OBFilter : public data::base { SNEX_NODE(Griffin_OBFilter); struct MetadataClass { SN_NODE_ID("Griffin_OBFilter"); }; static constexpr bool isModNode() { return false; } static constexpr bool isPolyphonic() { return NV > 1; } static constexpr bool hasTail() { return false; } static constexpr bool isSuspendedOnSilence() { return false; } static constexpr int getFixChannelAmount() { return 2; } static constexpr int NumTables = 0; static constexpr int NumSliderPacks = 0; static constexpr int NumAudioFiles = 0; static constexpr int NumFilters = 0; static constexpr int NumDisplayBuffers = 0; //========================================================================= // Parameters (Raw Values) //========================================================================= float cutoffFrequency = 1000.0f; float resonance = 0.0f; // Range: [0, 35] float keytrackAmount = 1.0f; float sampleRate = 44100.0f; //========================================================================= // Smoothing Objects for Per-Sample Smoothing //========================================================================= SmoothedValue<float> cutoffSmooth; SmoothedValue<float> resonanceSmooth; SmoothedValue<float> keytrackSmooth; //========================================================================= // Prepare Function // - Sets up voices and initializes smoothers. //========================================================================= void prepare(PrepareSpecs specs) { sampleRate = specs.sampleRate; filtersLeft.prepare(specs); filtersRight.prepare(specs); for (auto& v : filtersLeft) v.prepare(sampleRate); for (auto& v : filtersRight) v.prepare(sampleRate); // Set smoothing time (10ms) cutoffSmooth.reset(sampleRate, 0.01); resonanceSmooth.reset(sampleRate, 0.01); keytrackSmooth.reset(sampleRate, 0.01); cutoffSmooth.setCurrentAndTargetValue(cutoffFrequency); resonanceSmooth.setCurrentAndTargetValue(resonance); keytrackSmooth.setCurrentAndTargetValue(keytrackAmount); } void reset() { for (auto& v : filtersLeft) v.reset(); for (auto& v : filtersRight) v.reset(); } //========================================================================= // Process Audio // - Updates parameters per sample using smoothed values. //========================================================================= template <typename ProcessDataType> void process(ProcessDataType& data) { auto& fixData = data.template as<ProcessData<getFixChannelAmount()>>(); auto audioBlock = fixData.toAudioBlock(); float* leftCh = audioBlock.getChannelPointer(0); float* rightCh = audioBlock.getChannelPointer(1); int numSamples = static_cast<int>(data.getNumSamples()); for (int i = 0; i < numSamples; ++i) { // Get per-sample smoothed parameter values float cVal = cutoffSmooth.getNextValue(); float rVal = resonanceSmooth.getNextValue(); float kVal = keytrackSmooth.getNextValue(); // Update each voice with new smoothed parameters for (auto& v : filtersLeft) { v.setCutoff(cVal); v.setResonance(rVal); v.setKeytrack(kVal); v.applyChangesIfNeeded(); } for (auto& v : filtersRight) { v.setCutoff(cVal); v.setResonance(rVal); v.setKeytrack(kVal); v.applyChangesIfNeeded(); } // Process sample for each voice in series float outL = leftCh[i]; float outR = rightCh[i]; for (auto& v : filtersLeft) outL = v.processSample(outL); for (auto& v : filtersRight) outR = v.processSample(outR); leftCh[i] = outL; rightCh[i] = outR; } } template <typename FrameDataType> void processFrame(FrameDataType& data) {} //========================================================================= // AudioEffect Class: OB8-Style Voice-Level Filter // - Implements the filter logic with Eigen optimizations. //========================================================================= class AudioEffect { public: AudioEffect() = default; inline void prepare(float fs) { sampleRate = fs; baseCutoff = 1000.0f; resonance = 0.0f; rDamping = 3.4f; // Fixed damping value keytrackAmount = 1.0f; storedNote = 60; reset(); dirtyFlags = 0; updateAll(); // Initial matrix calculations } inline void reset() { x.setZero(); } //===================================================================== // Parameter Setting Functions //===================================================================== enum Dirty : uint32_t { changedCutoff = 1 << 0, changedResonance = 1 << 1, changedKeytrack = 1 << 2, changedNote = 1 << 3 }; inline void setCutoff(float c) { baseCutoff = c; dirtyFlags |= changedCutoff; } inline void setResonance(float r) { resonance = r; dirtyFlags |= changedResonance; } inline void setKeytrack(float kt) { keytrackAmount = kt; dirtyFlags |= changedKeytrack; } inline void setNoteNumber(int n) { storedNote = n; dirtyFlags |= changedNote; } inline void applyChangesIfNeeded() { if (dirtyFlags != 0) updateAll(); } //===================================================================== // Process Sample Function // - Processes a single sample and applies the filter. //===================================================================== inline float processSample(float input) { constexpr float noiseFloor = 0.005f; input += noiseFloor * (noiseGen.nextFloat() - 0.5f); // Add slight noise for stability // State update and output calculation (Eigen optimized) Eigen::Vector4f temp = N * x + gB * input; Eigen::Vector4f newX = MInv * temp; float out = C.dot(newX) * gainComp; x = newX; return out; } private: //===================================================================== // Update All Parameters Function // - Recalculates matrices and inversion when parameters change. //===================================================================== inline void updateAll() { // Keytracking calculation float semitones = (static_cast<float>(storedNote) - 60.0f) * keytrackAmount; float noteFactor = std::exp2f(0.0833333f * semitones); float fc = baseCutoff * noteFactor; // Cutoff frequency clamping [20 Hz, 20 kHz] if (fc < 20.0f) fc = 20.0f; if (fc > 20000.0f) fc = 20000.0f; // Compensation offset for frequency shift (empirical) float compensationOffset = 0.44f * fc - 30.0f; if (compensationOffset < 0.0f) compensationOffset = 0.0f; // Resonance compensation fc -= (resonance / 35.0f) * compensationOffset; // Re-clamp cutoff after compensation if (fc < 20.0f) fc = 20.0f; if (fc > 20000.0f) fc = 20000.0f; // TPT Warped frequency and g parameter const float fsRecip = 1.0f / sampleRate; const float factor = MathConstants<float>::pi * fc * fsRecip; const float warped = std::tan(factor); g = 2.0f * warped; // Matrix construction and inversion buildContinuousTimeSystem(); buildDiscreteTimeMatrices(); MInv = M.inverse(); C = Ccont; // Set output vector // Gain Compensation (Resonance dependent) if (dirtyFlags & changedResonance) { gainComp = std::pow(10.0f, (std::sqrt(resonance / 35.0f) * 22.0f) / 20.0f); } dirtyFlags = 0; // Clear dirty flags } //===================================================================== // Build Continuous-Time System Function // - Defines the continuous-time state-space matrices (Acont, Bcont, Ccont). //===================================================================== inline void buildContinuousTimeSystem() { const float twoR = 2.0f * rDamping; Acont.setZero(); Bcont.setZero(); Ccont.setZero(); // State equations (matrix A) Acont(0, 0) = -twoR; Acont(0, 1) = -1.0f; Acont(0, 3) = -resonance; Acont(1, 0) = 1.0f; Acont(2, 1) = 1.0f; Acont(2, 2) = -twoR; Acont(2, 3) = -1.0f; Acont(3, 2) = 1.0f; // Input matrix B (input to x1') Bcont(0) = 1.0f; // Output matrix C (output from x4) Ccont(3) = 1.0f; } //===================================================================== // Build Discrete-Time Matrices Function // - Discretizes the continuous-time system using TPT transform (M, N, gB). //===================================================================== inline void buildDiscreteTimeMatrices() { Eigen::Matrix4f gA = g * Acont; M = Eigen::Matrix4f::Identity() - gA; N = Eigen::Matrix4f::Identity() + gA; gB = g * Bcont; } //===================================================================== // Member Variables (AudioEffect) //===================================================================== float sampleRate = 44100.0f; float baseCutoff = 1000.0f; float resonance = 0.0f; float rDamping = 3.4f; // Fixed damping parameter float keytrackAmount = 1.0f; int storedNote = 60; float g = 0.0f; // Warped frequency parameter Eigen::Vector4f x = Eigen::Vector4f::Zero(); // State vector Eigen::Matrix4f Acont = Eigen::Matrix4f::Zero(); // Continuous-time A matrix Eigen::Vector4f Bcont = Eigen::Vector4f::Zero(); // Continuous-time B matrix Eigen::Vector4f Ccont = Eigen::Vector4f::Zero(); // Continuous-time C matrix Eigen::Matrix4f M = Eigen::Matrix4f::Zero(); // Discrete-time M matrix Eigen::Matrix4f N = Eigen::Matrix4f::Zero(); // Discrete-time N matrix Eigen::Matrix4f MInv = Eigen::Matrix4f::Zero(); // Inverted M matrix Eigen::Vector4f gB = Eigen::Vector4f::Zero(); // Discrete-time gB matrix Eigen::Vector4f C = Eigen::Vector4f::Zero(); // Discrete-time C matrix (output) float gainComp = 1.0f; // Gain compensation factor uint32_t dirtyFlags = 0; // Flags to track parameter changes juce::Random noiseGen; // Random number generator for noise }; //========================================================================= // Parameter Setting (Per Sample Update) //========================================================================= template <int P> void setParameter(double val) { if (P == 0) { cutoffFrequency = static_cast<float>(val); cutoffSmooth.setTargetValue(cutoffFrequency); } else if (P == 1) { resonance = static_cast<float>(val); if (resonance < 0.0f) resonance = 0.0f; if (resonance > 35.0f) resonance = 35.0f; resonanceSmooth.setTargetValue(resonance); } else if (P == 2) { keytrackAmount = static_cast<float>(val); keytrackSmooth.setTargetValue(keytrackAmount); } } void createParameters(ParameterDataList& data) { { parameter::data p("Cutoff Frequency", { 100.0, 20000.0, 1.0 }); registerCallback<0>(p); p.setDefaultValue(1000.0f); data.add(std::move(p)); } { parameter::data p("Resonance", { 0.0, 35.0, 0.01 }); registerCallback<1>(p); p.setDefaultValue(0.0f); data.add(std::move(p)); } { parameter::data p("Keytrack Amount", { -1.0, 1.0, 0.5 }); registerCallback<2>(p); p.setDefaultValue(0.0f); data.add(std::move(p)); } } void setExternalData(const ExternalData& data, int index) {} //========================================================================= // Note Handling //========================================================================= void handleHiseEvent(HiseEvent& e) { if (e.isNoteOn()) { filtersLeft.get().setNoteNumber(e.getNoteNumber()); filtersLeft.get().applyChangesIfNeeded(); filtersRight.get().setNoteNumber(e.getNoteNumber()); filtersRight.get().applyChangesIfNeeded(); } } private: PolyData<AudioEffect, NV> filtersLeft; PolyData<AudioEffect, NV> filtersRight; }; }
*edit: As an extra, I include a resonant ladder filter, which has an extremely wide range for resonance, from ultra soft to spiky. Setup is the same as the OB Filter, this script replaces the same effect. Rename the node if you want to use it separately.
#pragma once #include <JuceHeader.h> #include <cmath> #include "src\eigen-master\Eigen\Dense" // Werner Filter namespace project { using namespace juce; using namespace hise; using namespace scriptnode; template <int NV> // NV = number of voices struct Griffin_OBFilter : public data::base { SNEX_NODE(Griffin_OBFilter); struct MetadataClass { SN_NODE_ID("Griffin_OBFilter"); }; static constexpr bool isModNode() { return false; } static constexpr bool isPolyphonic() { return NV > 1; } static constexpr bool hasTail() { return false; } static constexpr bool isSuspendedOnSilence() { return false; } static constexpr int getFixChannelAmount() { return 2; } static constexpr int NumTables = 0; static constexpr int NumSliderPacks = 0; static constexpr int NumAudioFiles = 0; static constexpr int NumFilters = 0; static constexpr int NumDisplayBuffers = 0; // Outer-level parameters and smoothing objects float cutoffFrequency = 1000.0f; // Combined resonance/damping control float resonance = 0.0f; float keytrackAmount = 1.0f; float rDamp = 1.06f; // SVF damping (r) float sampleRate = 44100.0f; SmoothedValue<float> cutoffSmooth; SmoothedValue<float> resonanceSmooth; SmoothedValue<float> keytrackSmooth; SmoothedValue<float> dampingSmooth; void prepare(PrepareSpecs specs) { sampleRate = specs.sampleRate; // Prepare voice-level filters filtersLeft.prepare(specs); filtersRight.prepare(specs); for (auto& fl : filtersLeft) fl.prepare(sampleRate); for (auto& fr : filtersRight) fr.prepare(sampleRate); // Initialize per-sample smoothing (10ms ramp time) cutoffSmooth.reset(sampleRate, 0.01); resonanceSmooth.reset(sampleRate, 0.01); keytrackSmooth.reset(sampleRate, 0.01); dampingSmooth.reset(sampleRate, 0.01); cutoffSmooth.setCurrentAndTargetValue(cutoffFrequency); resonanceSmooth.setCurrentAndTargetValue(resonance); keytrackSmooth.setCurrentAndTargetValue(keytrackAmount); dampingSmooth.setCurrentAndTargetValue(rDamp); } void reset() { for (auto& fl : filtersLeft) fl.reset(); for (auto& fr : filtersRight) fr.reset(); } // Per-sample processing with parameter smoothing template <typename ProcessDataType> void process(ProcessDataType& data) { auto& fixData = data.template as<ProcessData<getFixChannelAmount()>>(); auto audioBlock = fixData.toAudioBlock(); float* leftChannelData = audioBlock.getChannelPointer(0); float* rightChannelData = audioBlock.getChannelPointer(1); int numSamples = static_cast<int>(data.getNumSamples()); for (int i = 0; i < numSamples; ++i) { // Get per-sample smoothed parameters float cVal = cutoffSmooth.getNextValue(); float rVal = resonanceSmooth.getNextValue(); float ktVal = keytrackSmooth.getNextValue(); float dVal = dampingSmooth.getNextValue(); // Update all voices with the current smoothed values for (auto& fl : filtersLeft) { fl.setCutoff(cVal); fl.setResonance(rVal); fl.setKeytrack(ktVal); fl.setDamping(dVal); fl.applyChangesIfNeeded(); } for (auto& fr : filtersRight) { fr.setCutoff(cVal); fr.setResonance(rVal); fr.setKeytrack(ktVal); fr.setDamping(dVal); fr.applyChangesIfNeeded(); } // Process the sample for each voice in series float inL = leftChannelData[i]; float inR = rightChannelData[i]; for (auto& fl : filtersLeft) inL = fl.processSample(inL); for (auto& fr : filtersRight) inR = fr.processSample(inR); leftChannelData[i] = inL; rightChannelData[i] = inR; } } template <typename FrameDataType> void processFrame(FrameDataType& data) {} // Voice-level effect: Two 2nd-order SVFs + global feedback, EXACT delay-free method class AudioEffect { public: AudioEffect() = default; void prepare(float fs) { sampleRate = fs; baseCutoff = 1000.0f; resonance = 0.0f; rDamping = 1.06f; keytrackAmount = 1.0f; storedNote = 60; reset(); dirtyFlags = 0; updateAll(); // Build A, B, C, compute g, discretize & invert, etc. } void reset() { x = Eigen::Vector4f::Zero(); } // Dirty flag enum for parameter changes enum Dirty : uint32_t { changedCutoff = 1 << 0, changedResonance = 1 << 1, changedDamping = 1 << 2, changedKeytrack = 1 << 3, changedNote = 1 << 4 }; inline void setCutoff(float c) { baseCutoff = c; dirtyFlags |= changedCutoff; } inline void setResonance(float r) { resonance = r; dirtyFlags |= changedResonance; } inline void setDamping(float d) { rDamping = d; dirtyFlags |= changedDamping; } inline void setKeytrack(float kt) { keytrackAmount = kt; dirtyFlags |= changedKeytrack; } inline void setNoteNumber(int n) { storedNote = n; dirtyFlags |= changedNote; } inline void applyChangesIfNeeded() { if (dirtyFlags != 0) updateAll(); } // Process a single sample using the discrete-time state update inline float processSample(float input) { Eigen::Vector4f temp = N * x + gB * input; Eigen::Vector4f newX = MInv * temp; float out = C.dot(newX) * gainComp; x = newX; return out; } private: inline void updateAll() { // Compute effective cutoff with keytracking float semitones = (static_cast<float>(storedNote) - 60.0f) * keytrackAmount; float noteFactor = std::exp2f(0.0833333f * semitones); float fc = baseCutoff * noteFactor; if (fc < 20.0f) fc = 20.0f; float limit = 0.49f * sampleRate; if (fc > limit) fc = limit; // Compute TPT warp coefficient: g = 2 * tan(pi * (fc / fs)) float norm = fc / sampleRate; float warped = std::tan(MathConstants<float>::pi * norm); g = 2.0f * warped; // Build continuous-time state-space (Acont, Bcont, Ccont) buildContinuousTimeSystem(); // Build discrete-time matrices via TPT: M = I - g*Acont, N = I + g*Acont, and gB = g*Bcont buildDiscreteTimeMatrices(); // Invert M using Eigen's fixed-size matrix inversion MInv = M.inverse(); // For output, C (discrete-time) equals Ccont C = Ccont; // Apply gain compensation: design so that resonance=3 produces an 11 dB boost. gainComp = std::pow(10.0f, (std::sqrt(resonance / 3.0f) * 11.0f) / 20.0f); dirtyFlags = 0; } inline void buildContinuousTimeSystem() { // Using damping (rDamping) and feedback gain (resonance) const float r = rDamping; const float kf = resonance; Acont << -2.0f * r, -1.0f, 0.0f, -kf, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, -2.0f * r, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f; Bcont << 1.0f, 0.0f, 0.0f, 0.0f; Ccont << 0.0f, 0.0f, 0.0f, 1.0f; } inline void buildDiscreteTimeMatrices() { M = Eigen::Matrix4f::Identity() - g * Acont; N = Eigen::Matrix4f::Identity() + g * Acont; gB = g * Bcont; } float sampleRate = 44100.0f; float baseCutoff = 1000.0f; float resonance = 0.0f; float rDamping = 1.06f; float keytrackAmount = 1.0f; int storedNote = 60; float g = 0.0f; float gainComp = 1.0f; uint32_t dirtyFlags = 0; Eigen::Matrix4f Acont = Eigen::Matrix4f::Zero(); Eigen::Vector4f Bcont = Eigen::Vector4f::Zero(); Eigen::Vector4f Ccont = Eigen::Vector4f::Zero(); Eigen::Matrix4f M = Eigen::Matrix4f::Zero(); Eigen::Matrix4f N = Eigen::Matrix4f::Zero(); Eigen::Matrix4f MInv = Eigen::Matrix4f::Zero(); Eigen::Vector4f gB = Eigen::Vector4f::Zero(); Eigen::Vector4f C = Eigen::Vector4f::Zero(); Eigen::Vector4f x = Eigen::Vector4f::Zero(); }; // External parameter setters with combined resonance/damping control. template <int P> void setParameter(double val) { if (P == 0) { cutoffFrequency = static_cast<float>(val); cutoffSmooth.setTargetValue(cutoffFrequency); for (auto& fl : filtersLeft) { fl.setCutoff(cutoffFrequency); fl.applyChangesIfNeeded(); } for (auto& fr : filtersRight) { fr.setCutoff(cutoffFrequency); fr.applyChangesIfNeeded(); } } else if (P == 1) { float extRes = static_cast<float>(val); // Using a threshold of 1.0 within the control range [0, 1.3] if (extRes >= 1.0f) { float t = (extRes - 1.0f) / 0.3f; // t in [0,1] for extRes in [1.0,1.3] resonance = t * 2.0f; // Map from 0 to 2.0 rDamp = 0.6f; } else { resonance = 0.0f; // Hold resonance at its lowest value // Map extRes in [0,1] to rDamp in [2.0,0.6] rDamp = 0.6f + ((1.0f - extRes) / 1.0f) * (2.0f - 0.6f); } resonanceSmooth.setTargetValue(resonance); dampingSmooth.setTargetValue(rDamp); for (auto& fl : filtersLeft) { fl.setResonance(resonance); fl.setDamping(rDamp); fl.applyChangesIfNeeded(); } for (auto& fr : filtersRight) { fr.setResonance(resonance); fr.setDamping(rDamp); fr.applyChangesIfNeeded(); } } else if (P == 2) { keytrackAmount = static_cast<float>(val); keytrackSmooth.setTargetValue(keytrackAmount); for (auto& fl : filtersLeft) { fl.setKeytrack(keytrackAmount); fl.applyChangesIfNeeded(); } for (auto& fr : filtersRight) { fr.setKeytrack(keytrackAmount); fr.applyChangesIfNeeded(); } } } // Parameter definitions for the UI (SVF Damping removed) void createParameters(ParameterDataList& data) { { parameter::data p("Cutoff Frequency", { 20.0, 20000.0, 1.0 }); registerCallback<0>(p); p.setDefaultValue(1000.0f); data.add(std::move(p)); } { parameter::data p("Resonance", { 0.0, 1.2, 0.01 }); registerCallback<1>(p); p.setDefaultValue(1.0f); data.add(std::move(p)); } { parameter::data p("Keytrack Amount", { -1.0, 1.0, 0.01 }); registerCallback<2>(p); p.setDefaultValue(0.0f); data.add(std::move(p)); } } void setExternalData(const ExternalData& data, int index) {} // Handle note on events for keytracking void handleHiseEvent(HiseEvent& e) { if (e.isNoteOn()) { filtersLeft.get().setNoteNumber(e.getNoteNumber()); filtersLeft.get().applyChangesIfNeeded(); filtersRight.get().setNoteNumber(e.getNoteNumber()); filtersRight.get().applyChangesIfNeeded(); } } private: PolyData<AudioEffect, NV> filtersLeft; PolyData<AudioEffect, NV> filtersRight; }; }
-
@griffinboy said in [Free Dsp] Oberheim-8 Analog Filter:
The "Eigen" header library is required.
Where do I get it ? How to set it?
-
Download the zipped code for the eigen library here:
https://github.com/PX4/eigenExtract it into the hise project folder such that the file structure reads:
ProjectName/DspNetworks/ThirdParty/src/eigen-master
See the whole code for complete instructions:
/* Integration Steps for HISE: 1. Eigen Setup: Download and place the Eigen library under: ProjectName/DspNetworks/ThirdParty/src/eigen-master 2. Create a 3rd party C++ node in HISE named "Griffin_OBFilter" 3. Compile the initial DLL in HISE using "Compile dsp networks as .dll" 4. Replace the generated "Griffin_OBFilter.h" (in ProjectName/DspNetworks/ThirdParty) with this header file you are reading now 5. Re-compile the final DLL in HISE using "Compile dsp networks as .dll" */
-
@griffinboy great job, thank you! I can't right now, but i'm very interested in listening to how it sounds.
-
This post is deleted! -
Thank you!!!