Polyphonic Custom Filters (scriptnode) how?
-
You need to wrap your single filter class into a PolyData container:
struct GriffinBoyFilter { void process(float* data, int numSamples); }; // in the node: template <int NV> struct node { PolyData<GriffinBoyFilter, NV> filters; };
See:
https://docs.hise.dev/scriptnode/snex_api/containers/polydata.html
This basically creates one filter per voice and automatically selects the one that is assigned to the currently rendered voice.
-
Haha yeah whoops. Sorry.
I assumed it would have to be a question for christoph, maybe I should've addressed him directly xD -
Thank you I missed that in the docs!
-
-
Sorry, I'm still not having any luck!
#pragma once #include <JuceHeader.h> #include "src/ScopedValue.h" namespace project { using namespace juce; using namespace hise; using namespace scriptnode; template <int NV> struct Moog_Filter : public data::base { SNEX_NODE(Moog_Filter); struct MetadataClass { SN_NODE_ID("Moog_Filter"); }; // Node properties 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 1; } // Mono processing 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; // Define AudioEffect class here class AudioEffect { public: AudioEffect() = default; void prepare(float sampleRate) { fs = sampleRate; reset(); } void reset() { // Initialize state variables to zero s1 = s2 = s3 = s4 = 0.0f; } void updateCoefficients(float fc, float resonanceValue) { // Limit cutoff frequency to Nyquist frequency float fcClamped = std::clamp(fc, 20.0f, 0.49f * fs); // Pre-warp the cutoff frequency float g = std::tan(MathConstants<float>::pi * fcClamped / fs); // Set the resonance parameter (k) for the Moog ladder filter k = 4.0f * resonanceValue; // Max resonance at k = 4 // Compute filter coefficients G = g / (1.0f + g); // Calculate gain compensation float dbGain = quadratic_curve(resonanceValue, a, b, c); gainCompensation = std::pow(10.0f, dbGain / 20.0f); } void process(float* samples, int numSamples) { for (int i = 0; i < numSamples; ++i) { samples[i] = processSample(samples[i]); } } private: // Moog ladder filter processing variables float fs = 44100.0f; // Sample rate float k = 0.0f; // Resonance parameter float G = 0.0f; // Filter coefficient float gainCompensation = 1.0f; // Gain compensation factor // State variables for each stage float s1 = 0.0f; float s2 = 0.0f; float s3 = 0.0f; float s4 = 0.0f; // Curve fit parameters static constexpr float a = -8.0f; static constexpr float b = 18.2f; static constexpr float c = 1.5f; inline float processSample(float input) { // Input with feedback float u = input - k * s4; // Four cascaded one-pole filters s1 = G * (u - s1) + s1; s2 = G * (s1 - s2) + s2; s3 = G * (s2 - s3) + s3; s4 = G * (s3 - s4) + s4; // Output is the last stage, apply gain compensation return s4 * gainCompensation; } static float quadratic_curve(float x, float a, float b, float c) { return a * x * x + b * x + c; } }; // Wrap AudioEffect in a PolyData container for polyphonic handling PolyData<AudioEffect, NV> leftChannelEffect; // Prepare: Called on init, and when sample rate changes void prepare(PrepareSpecs specs) { // Forward prepare call to PolyData container leftChannelEffect.prepare(specs); } // Reset: Called when the plugin is reloaded void reset() { // Use iterator to reset each voice for (auto& effect : leftChannelEffect) { effect.reset(); } } // Process: Audio blocks enter the script here template <typename ProcessDataType> void process(ProcessDataType& data) { auto& fixData = data.template as<ProcessData<1>>(); // Process only one channel auto audioBlock = fixData.toAudioBlock(); // Get pointer to left channel data auto* leftChannelData = audioBlock.getChannelPointer(0); // Correctly get the number of samples int numSamples = (int)data.getNumSamples(); // Process each voice in the PolyData container for (auto& effect : leftChannelEffect) { effect.process(leftChannelData, numSamples); } } template <int P> void setParameter(double v) { // Use iterator to update coefficients for each voice for (auto& effect : leftChannelEffect) { if (P == 0) { cutoffFrequency = static_cast<float>(v); effect.updateCoefficients(cutoffFrequency, resonance); } else if (P == 1) { resonance = static_cast<float>(v); effect.updateCoefficients(cutoffFrequency, resonance); } } } // Create parameters on the GUI void createParameters(ParameterDataList& data) { { parameter::data p("Cutoff Frequency", { 20.0, 20000.0, 1.0 }); registerCallback<0>(p); p.setDefaultValue(1000.0); data.add(std::move(p)); } { parameter::data p("Resonance", { 0.0, 1.0, 0.01 }); registerCallback<1>(p); p.setDefaultValue(0.0); data.add(std::move(p)); } } // Interact with external data (e.g., an external buffer) void setExternalData(const ExternalData& data, int index) {} // Handle HISE events: Process MIDI or other events void handleHiseEvent(HiseEvent& e) {} // processFrame: Needed for compiler, does nothing template <typename FrameDataType> void processFrame(FrameDataType& data) {} private: // Filter parameters float cutoffFrequency = 1000.0f; float resonance = 0.0f; }; }
The audio samples aren't being affected at all. I don't think I understand how to use polydata.
Looking at it myself I've been confused, and as you can see, AI hasn't been particularly useful either : (
-
-
@griffinboy Might be silly because I'm sure you thought about this 100 times already, but are you sure you're in a Polyphonic Script FX context?
-
Yep haha, thank you though.
I wish I didn't have to ask such questions, but I am slow to understand the Hise source code : (
-
@griffinboy Yep tested and working here
-
@griffinboy Don't be harsh on yourself, no one has publicly advanced on the C++ topic as you did!
I wouldn't have made my first steps without you! -
Ah my code will probably not work when run it has a dependency, I should probably include it in this post.
I might rewrite this post to make it easier for christoph to help me out
I just need to figure out how to create an instance of a class for each voice, and process audio through that -
@griffinboy said in Polyphonic Custom Filters (scriptnode) how?:
Ah my code will probably not work when run it has a dependency
Well I just commented out the dependancy and it kinda works...
I just need to figure out how to create an instance of a class for each voice
But that's what the wrapper does with
NV
, isn't it? -
I don't believe so, or at least not for my node. It was not producing any audible effect, wheras inside a regular scriptfx (non poly) it was working.
I have just realised that I may have solved this before, I wrote a pretty bad sampler a few months ago using ai, and had unwittingly used polydata, which I stole the usage of from the regular sampler inside of hise. I'm going to investigate that now.
-
Code looks OKish on the first glance, but you need to call
updateCoefficients
in your prepare callback too though so that it can initialize the coefficients when the sample rate is set (otherwise it might be zero because the parameters might or might not be set before or after the call). -
I'll try with a more simple effect.
-
@griffinboy Well in my video above it works, and it is just your pasted code, node inserted in a poly FX
@Christoph-Hart noticed that the coeff weren't updated as they should but I got lucky having the filter working straight ahead
-
Okay maybe I should update my version of hise.
I'm very surprised that everything seems to be in order lol.For me it still only works when used in monophonic.
-
@Christoph-Hart Jus noticed that when two+ notes are held, changing the resonance makes the freq to be updated to last note value for all notes. What could be done to prevent this?
-
@griffinboy Strange indeed...
-
The fact that it works without dependencies means that the ai has removed some of my optimisations, I'll release a proper version of this filter when it's done lol.
I'm amused that it's (somewhat) working for you
-
Yup it works here too, but you definitely need to put the function call into prepare().
us noticed that when two+ notes are held, changing the resonance makes the freq to be updated to last note value for all notes.
That's intended - any UI interaction will be applied to all voices. If you want to use different values, you have to modulate it, then it will be applied for each voice:
HiseSnippet 1763.3oc2Y0zSiaDFdbfIrjkVs6psRcukC8.aEBk.rvh1CD9JaQcCDQnnsmPC1SHSi8Lt1N.YqpTkZOr2548V+azSs+ApTk5uf9Of+AsuiGarcrARLeraaPBxLuy63G+9wy7XSSGgN00U3fzJsWeaJRaJbq9buNq2gv3ns1.o8w3FDWOpSY0Tq02l35RMPZZi8R4DZSNNx+yYqrFwjv0oQSgP6KX5zWwrXdQy1r1WxLMqSLn6wrhs5EpsktfutvTzCvyX3JHahdWxQzsIxkU.i9BhaGj1miq7rEozklusgAY94MVbwEW94KuvxUIsqr.4vpKt7BysvRy87kZizJtoAyS3zxi3QcgMcMgQ+VcDmvUWf8YtrCMoxAUQsfqrZZz5cXlFMCCNtHjFtYTnZLUn5w3FLC14yGExdfugxQdDOnoU3xfT0Q.RZwfz3JH8PbKcGlsWjEIdtOdKNjAaSfbSbnnVKR62wqKfEv8l0hzkV2AFbtCSuXkJyTF90SeQ6dbcOlfWVv2V3Q2gO8SK8cklrz2Wp7flZ2NSaxKiivzj5joYY4fyk43z7dVGRclo7wDydzyWHb6mLlVb3ho5p65XKTv2hy71wlFLttvzPFqjeOcF.ED1fu8UasAwiHSJAyAqyl53wjvQaC5wPafJEMIdCpaWOgMzHjJ+AUNBidlDujkSxFs.CP7HQNTln3tLu9waDGgZrJWZM1vBwGhax7z6jMFKjAFgH0sAFC5L+H7lsaS08h.3335uNusgUF41vRAsgv0TdwmJXX45utZlMfuEGUra6PsINz8DMMI8m1kXYaR2Ev3LkOzTn2sE6MzzcH1JHrlbESq2gv4TS27zHU7FhbBsqnmGieTChmC6Tf9b6dVs.dcc55AnClSqfrGRMthbrrtnEka3O3efOAFqJGqEXrZnwXseaS8NQ3z0OcD7cj1DpXuqeP9f1mBvm53JKN0l.WYV3GzpllhSVWXYyBJagjh+bMEl8s6H3Lc4TpUDB8UsD8fqZ.9gyj1ivLk04s54BLGF6vaAK1+fPYc11BC3aSTmnCA19MIdcj8KRtGn3j5Lq94EoIvZ75D0AfILKSJMINfAfn1UkFjWoviOUAS.3VxHj7vSXa.69MLQ9IKeCGANoNsHZlFL99xJDvjLhgZPN87wUgwIFrAsMomoWh474C4T+xufd0PZQUmibDvKTPc+uk69Rq5DyvsFhwqIDcsHpragvAAtLAFJt61Ob0OB6xNZNKgwKHFFuvlR5hPC3QIrcOGZ4cb0CcZJrvUGTjHozt9L4SFRAoZt8US4mceRvLbHKT1eZpKyk5T8RzT8mCqlJ6gVSUHmyL3qAuv3CGuPxyshs9LnQK9dPf0MygeuGOeNwgOSFhwVL4gFaxOlZBkt9X7Q3ftyvYSV00PvEgzcQEZ6RAx6iNRxLDg8LugV0yCjpGMyiqsK0jRbiUI9Y0dEP2QbZ3SGlqXwnqGNy70mhUvsrrUo7+c0UM1+q0UMrgn6g27TOGR0O.ydAPat6Rn8ghtqmDHVQdZ2AtwOsa3zbU3Zn4JkLuPQXEGBQXWLvSqH6hW6UIEKO5fBjuozwA2LpkFS6Rp6NG5rwLmkhxXlGEHKMAGwbZnToMXt1viqrVOfdPwEmXF48E2fdZXEzlfHCC3Aq8cOrpJgpzhwTkNQjJzHZtHkngy8fZs7n1xmOJdmUqtzSTwkDGhAgthX+ihFXWNakjxXithnKGRKTKEjd6eMHj9k28teMMjp8iV+wJJo20cneaOnNt+.35G5VKSbAyeE35rURGppLhgpo7wU4ck8qCa.6rUPidND.wHlCeIPXdmCIfP7BgzD3lcjBeRs04rt5l.SE8UnNBgoXLLkvviSonGSvu73XrmmuhzTLwb+1jSTKFhKhg3ocRzdeEanugzfLviq.fimfALRS1dDmindtnDOravYNaYHUM0PHN5f5LSYN97rszzivq2ySztc4n99acp0wtj5Me9oT0a094giGKFeQSpCSX3+rno1qrq595Z2AMB2G+JgvFTY438AWCZ93wjkhwp8m.aAqmgtPgNJyoaABcbjTAD18UHABxfrHsTqrPvcKMAb8fGQc.USSgsf+NqxRlXz2xUfwBIhRW3qa6V6DH0FjiB7AplRxbkHP8IXaGw2.bcyFeIoiXI2h7G2JDKtcubp95JDSjAYbxcrvukM2EvosJ3IT4n9+cbSPXT6ueyOsxkQjsK0Uv8e0f4LOmXPr2wZ9dCn2EO2oEQ2Qbft5UUJKJtm+L.m.2uVaRbC43xUQGO3stEyfcftdxsJkiykWGmOuNtPdc7Y40wEyqiKkWGe9U6n704FzBI0NAcHM2TofQaSN4PSEiB5eoxNyhG
-
@griffinboy Well, if you could develop things that are working for me and not for you, I can be nothing less than grateful...