(ScriptNode) Smeary Reverb / Washy Cymbals?
-
I'm noodling at physically modeled drums, specifically cymbals and I need an efficient way to "smear" the excitation impulse
I know the typical route is to use allpass filters in the delay line, but I find I have to use a
math.mulat 0.5 just to keep the audio from blowing up.I currently have 5 delays with different fractional delay times and I'm also passing a long noise tail into it but it just sounds like a noisy sawtooth buzz rather than a nice chorusy wash.
Not sure how to proceed towards that metallic ringing without overloading the delay lines or the CPU
-
-
@Lindon Yep that's the one I'm following :)
he's using an airwindows plugin for the allpass but the HISE allpass node sounds nothing like that, not sure how all the coefficient stuff works
I ended up going a harmonic + stochastic approach which is showings its obvious limitations
-
@iamlamprey you could always import the Airwindows code.. Chris is pretty open to that.
-
@Lindon That's a good idea, I'll keep it in the back pocket for now because I want to keep everything in scriptnode but if I can't figure it out I'll make a wrapper
-
Haha yeah I‘ve seen that video too and thought damn thats not what a DAW is made for.
-
@Christoph-Hart said in (ScriptNode) Smeary Reverb / Washy Cymbals?:
Haha yeah I‘ve seen that video too and thought damn thats not what a DAW is made for.
yeah its extreme .. but to be admired in some ways....
-
Okie, here's the wrapper if anyone else needs it:
// ==================================| Third Party Node Template |================================== #pragma once #include <JuceHeader.h> namespace project { using namespace juce; using namespace hise; using namespace scriptnode; // ==========================| The node class with all required callbacks |========================== template <int NV> struct phaseNudge: public data::base { // Metadata Definitions ------------------------------------------------------------------------ SNEX_NODE(phaseNudge); struct MetadataClass { SN_NODE_ID("phaseNudge"); }; 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; void prepare(PrepareSpecs specs) { } void reset() { } void handleHiseEvent(HiseEvent& e) { (void)e; } double dL[1503]; double dR[1503]; int one = 1; int maxdelay = 9001; double A = 0.0; double B = 1.0; uint32_t fpdL; uint32_t fpdR; int allpasstemp; double outallpass = 0.618033988749894848204586; int maxdelayTarget = (int)(pow(A,3)*1501.0); double wet = B; double bridgerectifier; double inputSampleL; double inputSampleR; double drySampleL; double drySampleR; template <typename T> void process(T& data) { const int numSamples = data.getNumSamples(); const int numChannels = jmin(2, (int)data.getNumChannels()); maxdelayTarget = (int)(pow(A, 3) * 1501.0); wet = B; dyn<float> chL, chR; int idx = 0; for (auto ch : data) { if (idx == 0) chL = data.toChannelData(ch); else if (idx == 1) chR = data.toChannelData(ch); ++idx; } if (numChannels == 1) chR = chL; for (int s = 0; s < numSamples; ++s) { double dryL = chL[s]; double dryR = numChannels > 1 ? chR[s] : dryL; double xL = chL[s]; double xR = numChannels > 1 ? chR[s] : xL; // might add dithering later // bridge rectifier xL /= 4.0; xR /= 4.0; double br = std::fabs(xL); br = std::sin(br); if (xL > 0) xL = br; else xL = -br; br = std::fabs(xR); br = std::sin(br); if (xR > 0) xR = br; else xR = -br; // allpass if (std::fabs(maxdelay - maxdelayTarget) > 1500) maxdelay = maxdelayTarget; if (maxdelay < maxdelayTarget) { maxdelay++; dL[maxdelay] = (dL[0] + dL[maxdelay-1]) / 2.0; dR[maxdelay] = (dR[0] + dR[maxdelay-1]) / 2.0; } if (maxdelay > maxdelayTarget) { maxdelay--; dL[maxdelay] = (dL[0] + dL[maxdelay]) / 2.0; dR[maxdelay] = (dR[0] + dR[maxdelay]) / 2.0; } allpasstemp = one - 1; if (allpasstemp < 0 || allpasstemp > maxdelay) allpasstemp = maxdelay; xL -= dL[allpasstemp] * outallpass; xR -= dR[allpasstemp] * outallpass; dL[one] = xL; dR[one] = xR; xL *= outallpass; xR *= outallpass; one--; if (one < 0 || one > maxdelay) { one = maxdelay; } xL += (dL[one]); xR += (dR[one]); // second bridge rectifier stage br = std::fabs(xL); br = 1.0 - std::cos(br); if (xL > 0) xL -= br; else xL += br; br = std::fabs(xR); br = 1.0 - std::cos(br); if (xR > 0) xR -= br; else xR += br; xL *= 4.0; xR *= 4.0; // apply dry / wet if (wet < 1.0) { xL = (dryL * (1.0 - wet)) + (xL * wet); xR = (dryR * (1.0 - wet)) + (xR * wet); } // apply dither goes here // write back to channelData chL[s] = xL; chR[s] = xR; } } template <typename T> void processFrame(T& data) { } int handleModulation(double& value) { return 0; } void setExternalData(const ExternalData& data, int index) { } template <int P> void setParameter(double v) { switch (P) { case 0: { A = v; break; } case 1: { B = v; break; } } } void createParameters(ParameterDataList& data) { { parameter::data p("PhaseNudge", { 0.0, 1.0 }); registerCallback<0>(p); p.setDefaultValue(0.5); data.add(std::move(p)); } { parameter::data p("Dry/Wet", { 0.0, 1.0 }); registerCallback<1>(p); p.setDefaultValue(0.5); data.add(std::move(p)); } } }; // end struct } // end namespaceIt's missing the dithering stuff, but it sounds basically identical to the original implementation, just make sure to set up the third party node using the template and call it "phaseNudge.h"
-
@iamlamprey haha everybody always rips out the dither stuff from air windows :)
-
@Christoph-Hart said in (ScriptNode) Smeary Reverb / Washy Cymbals?:
@iamlamprey haha everybody always rips out the dither stuff from air windows :)
i assume he has it there for a reason but most DAWs apply dithering now anyway and I can't hear a difference lol