HISE Logo Forum
    • Categories
    • Register
    • Login
    1. HISE
    2. Orvillain
    • Profile
    • Following 1
    • Followers 0
    • Topics 73
    • Posts 588
    • Groups 0

    Orvillain

    @Orvillain

    128
    Reputation
    75
    Profile views
    588
    Posts
    0
    Followers
    1
    Following
    Joined
    Last Online

    Orvillain Unfollow Follow

    Best posts made by Orvillain

    • I wrote a reverb

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

      posted in C++ Development
      OrvillainO
      Orvillain
    • I wrote a bbd delay

      Again, wasn't sure where to put this. But I created my own node.

      https://youtu.be/-siB1UJfrD0

      Modelled analog bucket brigade delay. I'm starting to build up quite a nice collection of delay and reverb utilities now!

      posted in C++ Development
      OrvillainO
      Orvillain
    • RE: I wrote a reverb

      @Chazrox I might do a video or two on everything I've learned!

      posted in C++ Development
      OrvillainO
      Orvillain
    • RE: Need filmstrip animations

      @d-healey I really like that UI. Very simple, accessible, and smooth looking - for lack of a better word!

      posted in General Questions
      OrvillainO
      Orvillain
    • RE: Can We PLEASE Just Get This Feature DONE

      Free mankini with every commercial license???

      posted in Feature Requests
      OrvillainO
      Orvillain
    • 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/6ZK2Goiyotk

      Then 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++

      1. Learn how to create a ring buffer (aka my Ring Delay thread)
      2. Learn how to create an all-pass filter using a ring buffer.
      3. Understand how fractional delays work, and the various types of interpolation.
      4. Learn how to manage feedback loops.

      Loads of resources out there for sure!

      posted in C++ Development
      OrvillainO
      Orvillain
    • 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:

      1. We set the ExternalData as in a previous post.
      2. 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....
      3. 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.

      2aa2dbe0-4ea2-40b8-8a45-49d96ada26ec-image.png

      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.

      posted in ScriptNode
      OrvillainO
      Orvillain
    • 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.

      posted in General Questions
      OrvillainO
      Orvillain
    • RE: UI Design - AI?

      They're all crap quite honestly.

      posted in Presets / Scripts / Ideas
      OrvillainO
      Orvillain
    • 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));
      		}
      	}
      };
      }
      
      
      
      
      posted in C++ Development
      OrvillainO
      Orvillain

    Latest posts made by Orvillain

    • RE: Please Increase parameter limit on Scriptnode custom nodes!

      @Christoph-Hart
      761cefe2-fcfc-4f87-bde6-7fc2113e831b-image.png

      Seems to be a limit on the number of pages? If I move a control to an 8th page, I don't see it.

      posted in Feature Requests
      OrvillainO
      Orvillain
    • RE: Can I update a parameter on my node from inside the C++

      Thanks ! I'll check it out.

      posted in C++ Development
      OrvillainO
      Orvillain
    • 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?

      posted in C++ Development
      OrvillainO
      Orvillain
    • 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.

      posted in C++ Development
      OrvillainO
      Orvillain
    • RE: I wrote a bbd delay

      @griffinboy said in I wrote a bbd delay:

      @Orvillain

      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.

      posted in C++ Development
      OrvillainO
      Orvillain
    • 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
      posted in C++ Development
      OrvillainO
      Orvillain
    • 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.

      posted in Feature Requests
      OrvillainO
      Orvillain
    • RE: I wrote a bbd delay

      @griffinboy Sounds great!

      posted in C++ Development
      OrvillainO
      Orvillain
    • 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.

      posted in Feature Requests
      OrvillainO
      Orvillain
    • I wrote a bbd delay

      Again, wasn't sure where to put this. But I created my own node.

      https://youtu.be/-siB1UJfrD0

      Modelled analog bucket brigade delay. I'm starting to build up quite a nice collection of delay and reverb utilities now!

      posted in C++ Development
      OrvillainO
      Orvillain