Don't know if this is the done thing really, but I wanted to show off:
https://youtu.be/1kMHloRQLcM

Best posts made by Orvillain
-
I wrote a reverb
-
I wrote a bbd delay
Again, wasn't sure where to put this. But I created my own node.
Modelled analog bucket brigade delay. I'm starting to build up quite a nice collection of delay and reverb utilities now!
-
RE: I wrote a reverb
@Chazrox I might do a video or two on everything I've learned!
-
RE: Need filmstrip animations
@d-healey I really like that UI. Very simple, accessible, and smooth looking - for lack of a better word!
-
RE: Can We PLEASE Just Get This Feature DONE
Free mankini with every commercial license???
-
RE: I wrote a reverb
@Chazrox said in I wrote a reverb:
@Orvillain Please.
I've been waiting for some dsp videos! I've been watching ADC's everyday on baby topics just to familiarize myself with the lingo and what nots. I think im ready to start diving in! There are some pretty wicked dsp guys in here for sure and I'd love to get some tutuorials for writing c++ nodes.
There's two guys who got me started in this. One is a dude called Geraint Luff aka SignalSmith. This is probably his most accessible video:
https://youtu.be/6ZK2GoiyotkThen the other guy of course is Sean Costello of ValhallaDSP fame:
https://valhalladsp.com/2021/09/22/getting-started-with-reverb-design-part-2-the-foundations/
https://valhalladsp.com/2021/09/23/getting-started-with-reverb-design-part-3-online-resources/In essence, here's the journey; assuming you know at least a little bit of C++
- Learn how to create a ring buffer (aka my Ring Delay thread)
- Learn how to create an all-pass filter using a ring buffer.
- Understand how fractional delays work, and the various types of interpolation.
- Learn how to manage feedback loops.
Loads of resources out there for sure!
-
RE: Orv's ScriptNode+SNEX Journey
Lesson 5 - SNEX code in a bit more detail.
So I'm by no means an expert in C or C++ - in fact I only just recently started learning it. But here's what I've sussed out in regards to the HISE template.... and template is exactly the right word, because the first line is:
template <int NV> struct audio_loader
Somewhere under the hood, HISE must be setup to send in an integer into any SNEX node, that integer corresponding to a voice. NV = new voice perhaps, or number of voices ????
The line above declares a template that takes this NV integer in, and creates a struct called audio_loader for each instance of NV. Indeed we can prove this by running the following code:
template <int NV> struct audio_loader { SNEX_NODE(audio_loader); ExternalData data; double note = 0.0; // Initialise the processing specs here void prepare(PrepareSpecs ps) { } // Reset the processing pipeline here void reset() { } // Process the signal here template <typename ProcessDataType> void process(ProcessDataType& data) { } // Process the signal as frame here template <int C> void processFrame(span<float, C>& data) { } // Process the MIDI events here void handleHiseEvent(HiseEvent& e) { double note = e.getNoteNumber(); Console.print(note); } // Use this function to setup the external data void setExternalData(const ExternalData& d, int index) { data = d; } // Set the parameters here template <int P> void setParameter(double v) { } };
There are only three things happening here:
- We set the ExternalData as in a previous post.
- We establish a variable with the datatype of double called 'note' and we initialise it as 0.0. But this value will never hold because....
- In the handleHiseEvent() method, we use e.getNoteNumber() and we assign this to the note variable. We then print the note variable out inside of the handleHiseEvent() method.
Now when we run this script, any time we play a midi note, the console will show us the note number that we pressed. This is even true if you play chords, or in a scenario where no note off events occur.
That's a long winded way of saying that a SNEX node is run for each active voice; at least when it is within a ScriptNode Synthesiser dsp network.
The next line in the script after the template is established is:
SNEX_NODE(audio_loader);
This is pretty straight forward. The text you pass here has to match the name of the script loaded inside your SNEX node - not the name of the SNEX node itself.
Here you can see my SNEX node is just called: snex_node.
But the script loaded into it is called audio_loader, and so the reference to SNEX_NODE inside the script has to also reference audio_loader.
-
RE: scriptAudioWaveForm and updating contents
@d-healey said in scriptAudioWaveForm and updating contents:
@Orvillain Did you try,
AudioWaveform.set("processorId", value);
?Yeah I did, and it does update it based on a follow up AudioWaveform.get('processorId') call - but the UI component doesn't seem to update, and still shows data from the previous processorId. When I compile the script, then the UI updates one time... but not on subsequent calls to the set method.
I figured I needed to call some kind of update() function after setting the processorId, but no such luck so far.
-
RE: Ring Buffer design
Here is a super contrived example. If you compile this as a node, and add it in your scriptnode layout... it will delay the right signal by 2 seconds.
#pragma once #include <JuceHeader.h> namespace project { using namespace juce; using namespace hise; using namespace scriptnode; static inline float cubic4(float s0, float s1, float s2, float s3, float f) { float a0 = -0.5f * s0 + 1.5f * s1 - 1.5f * s2 + 0.5f * s3; float a1 = s0 - 2.5f * s1 + 2.0f * s2 - 0.5f * s3; float a2 = -0.5f * s0 + 0.5f * s2; float a3 = s1; return ((a0 * f + a1) * f + a2) * f + a3; } struct RingDelay { // holds all of the sample data std::vector<float> buf; // write position int w = 0; // bitmask for fast wrap-around (which is why the buffer must always be power-of-2) int mask = 0; // sets the size of the buffer according to requested size in samples // will set the buffer to a power-of-two size above the requested capacity // for example - minCapacitySamples==3000, n==4096 void setSize(int minCapacitySamples) { // start off with n=1 int n = 1; // keep doubling n until it is greater or equal to minCapacitySamples while (n < minCapacitySamples) { n <<= 1; } // set the size of the buffer to n, and fill with zeros buf.assign(n, 0.0f); // mask is now n-1; 4095 in the example mask = n - 1; // reset the write pointer to zero w = 0; } // push a sample value into the buffer at the write position // this will always wrap around the capacity length because of the mask value being set prior void push(float x) { buf[w] = x; // set the current write position to the sample value w = (w + 1) & mask; // increment w by 1. Use bitwise 'AND' operator to perform a wrap } // Performs a cubic interpolation read operation on the buffer at the specified sample position // This can be a fractional number float readCubic(float delaySamples) const { // w is the next write position. Read back from that according to delaySamples. float rp = static_cast<float>(w) - delaySamples; // wrap this read pointer into the range 0-size, where size=mask+1 rp -= std::floor(rp / static_cast<float>(mask + 1)) * static_cast<float>(mask + 1); // the floor of rp - the integer part int i1 = static_cast<int>(rp); // the decimal part float f = rp - static_cast<float>(i1); // grab the neighbours around i1 int i0 = (i1 - 1) & mask; int i2 = (i1 + 1) & mask; int i3 = (i1 + 2) & mask; // feed those numbers into the cubic interpolator return cubic4(buf[i0], buf[i1 & mask], buf[i2], buf[i3], f); } // returns the size of the buffer int size() const { return mask + 1; } // clear the buffer without changing the size and reset the write pointer void clear() { std::fill(buf.begin(), buf.end(), 0.0f); w = 0; } }; // ==========================| The node class with all required callbacks |========================== template <int NV> struct RingBufferExp: public data::base { // Metadata Definitions ------------------------------------------------------------------------ SNEX_NODE(RingBufferExp); struct MetadataClass { SN_NODE_ID("RingBufferExp"); }; // set to true if you want this node to have a modulation dragger static constexpr bool isModNode() { return false; }; static constexpr bool isPolyphonic() { return NV > 1; }; // set to true if your node produces a tail static constexpr bool hasTail() { return false; }; // set to true if your doesn't generate sound from silence and can be suspended when the input signal is silent static constexpr bool isSuspendedOnSilence() { return false; }; // Undefine this method if you want a dynamic channel count static constexpr int getFixChannelAmount() { return 2; }; // Define the amount and types of external data slots you want to use 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; // components double sampleRate = 48000.0; RingDelay rd; // Helpers // Converts milliseconds to samples and returns as an integer static inline int msToSamplesInt(float ms, double fs) { return (int)std::ceil(ms * fs / 1000.0); } // Converts milliseconds to samples and returns as a float static inline float msToSamplesFloat(float ms, double fs) { return (float)(ms * fs / 1000.0); } // Scriptnode Callbacks ------------------------------------------------------------------------ void prepare(PrepareSpecs specs) { // update the sampleRate constant to be the current sample rate sampleRate = specs.sampleRate; // we arbitrarily invent a pad guard number to add to the length of the ring delay const int guard = 128; // set the size using msToSamplesInt because setSize expects an integer rd.setSize(msToSamplesInt(10000.0f, sampleRate) + guard); } void reset() { } void handleHiseEvent(HiseEvent& e) { } template <typename T> void process(T& data) { static constexpr int NumChannels = getFixChannelAmount(); // Cast the dynamic channel data to a fixed channel amount auto& fixData = data.template as<ProcessData<NumChannels>>(); // Create a FrameProcessor object auto fd = fixData.toFrameData(); while(fd.next()) { // Forward to frame processing processFrame(fd.toSpan()); } } template <typename T> void processFrame(T& data) { // Separate out the stereo input float L = data[0]; float R = data[1]; // Calculate the delay time we want in samples - expects float float dTime = msToSamplesFloat(2000.0f, sampleRate); // read the value according to the delay time we just setup float dR = rd.readCubic(dTime); // Push just the right channel into our ring delay // remember, this will auto-increment the write pointer rd.push(R); // left channel - Write the original audio back to the datastream data[0] = L; // Right channel - Write the delayed audio back to the datastream data[1] = dR; } int handleModulation(double& value) { return 0; } void setExternalData(const ExternalData& data, int index) { } // Parameter Functions ------------------------------------------------------------------------- template <int P> void setParameter(double v) { if (P == 0) { // This will be executed for MyParameter (see below) jassertfalse; } } void createParameters(ParameterDataList& data) { { // Create a parameter like this parameter::data p("MyParameter", { 0.0, 1.0 }); // The template parameter (<0>) will be forwarded to setParameter<P>() registerCallback<0>(p); p.setDefaultValue(0.5); data.add(std::move(p)); } } }; }
Latest posts made by Orvillain
-
RE: Please Increase parameter limit on Scriptnode custom nodes!
Seems to be a limit on the number of pages? If I move a control to an 8th page, I don't see it.
-
RE: Can I update a parameter on my node from inside the C++
Thanks ! I'll check it out.
-
Can I update a parameter on my node from inside the C++
Custom c++ node. I have loads of parameters. I have one parameter that changes a bunch of internal state variables, and also changes the values of the incoming UI parameters. Is there a way to update the scriptnode UI with the relevant new parameter?
-
RE: I wrote a bbd delay
@griffinboy said in I wrote a bbd delay:
It's not like the analog filters are the best ever
Oh yeah undoubtedly, but I reckon that's part of the magic and charm. I've got a few SVF models kicking around, and a Moog model too, that might be interesting to try too.
-
RE: I wrote a bbd delay
@griffinboy said in I wrote a bbd delay:
That's really comprehensive, more so than mine.
The only thing I noticed missing is possibly the fact that filters change shape in BBD delays depending on the delay time. They kind of warp into a lower order lower filter as time increases.If you take IR measurements from a real device you can recreate matching filters using vector fitting!
I'd love to hear more of your delay though. BBD delay is wonderful.
Interesting, I'll look into that! Right now my filters are biquads, and they do adjust based on what chip I select. I've also plumbed in more parameters this morning now, so I can actually select all my clipping types for each stage from the HISE UI. It is great!!
It runs in true stereo too. So I'm eventually going to add note selection options, and a spread control to offset the delay times by an amount.
-
RE: I wrote a bbd delay
I've tried to be as authentic as I can. The model can sound like a bunch of different styles, depending on the parameters.
This is what can be changed:
- Delay time (ms)
- Mix and Feedback
- Chip count
- Stage count (each chip)
- Input sample+hold
- Bit-depth (additional crunchy stage, optional)
- Feedback tone - lowpass, highpass, and two peak bands
- Compander - on/off, threshold, ratio, knee, time
- Input, feedback, output saturation types - none, hard, soft, asym, sym, tube, tape, exponential, and cubic curves.
- Noise level
- Clock whine level
- BBD Droop (level loss with longer times)
- Transient softener in the feedback path
- Micro blur effect in the feedback path
- Pre/De-emphasis on/off (shelving tilt)
- Chip voicing (MN3005, MN3205, MN3007, MN3207, SAD1024, R5106)
- wow rate and depth
- flutter rate and depth
- clock jitter
-
RE: Please Increase parameter limit on Scriptnode custom nodes!
@Christoph-Hart said in Please Increase parameter limit on Scriptnode custom nodes!:
@Orvillain have you tried the new pagination feature? That allows you to subgroup parameters into pages. It looks like this is just an UI problem and having a node with 100 knobs will look very ugly anyways.
Ahhh yeah, this seems like it will work. I did notice when you create a page, any hidden parameters end up going to it, rather than showing up on the unassigned page. But I can work with that.
-
RE: Please Increase parameter limit on Scriptnode custom nodes!
@Christoph-Hart said in Please Increase parameter limit on Scriptnode custom nodes!:
@Orvillain have you tried the new pagination feature? That allows you to subgroup parameters into pages. It looks like this is just an UI problem and having a node with 100 knobs will look very ugly anyways.
I haven't actually no. Maybe it will fix this issue. I'll check.
-
I wrote a bbd delay
Again, wasn't sure where to put this. But I created my own node.
Modelled analog bucket brigade delay. I'm starting to build up quite a nice collection of delay and reverb utilities now!