HISE Logo Forum
    • Categories
    • Register
    • Login

    Finding Sample Rate, and Other C++ scriptnode questions

    Scheduled Pinned Locked Moved Solved ScriptNode
    8 Posts 2 Posters 367 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • griffinboyG
      griffinboy
      last edited by griffinboy

      There are a few things that have been tripping me up. which are missing from the default c++ external 3rd party node template.

      I've been trying to discover:

      • What is the correct method to find the sample rate ?
      • What is the best practice to get data out of a c++ node and into the main hise interface script?
      • Are there are any tools already in place for time based effects (think LFO / Envelope)?
      • It is possible (to easily) change the size of a frame and process very specific frame lengths (eg. in 0.4s frames)?

      To have these things written up for reference, or included in a template would be incredibly useful!

      I would be really appreciative if anyone knows the answers to any of these already because I've been making a mess trying to figure it out : )

      griffinboyG 1 Reply Last reply Reply Quote 0
      • griffinboyG griffinboy marked this topic as a question on
      • griffinboyG
        griffinboy @griffinboy
        last edited by

        @griffinboy

        For reference, here is my current template.

        // ==================================| 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 NodeName: public data::base
        {
        	// Metadata Definitions ------------------------------------------------------------------------
        	
        	SNEX_NODE(NodeName);
        	
        	struct MetadataClass
        	{
        		SN_NODE_ID("NodeName");
        	};
        	
        	// 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;
        	
        	// Scriptnode Callbacks ------------------------------------------------------------------------
        	
                // User Controllable Variables
        		float GAIN = 1.0f;
        
                // Internal Variables
                double sr = 0.0; // Sample rate, initialized here and set properly in the prepare function
        
        	// Initialise the processing specs here
        	void prepare(PrepareSpecs specs)
        	{
        	}
        	
        	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());
        		}
        		
        	}
        
        
        
        	float processSampleL(float s) {
        		float shapedSample = s * GAIN;
        		return shapedSample;
        	}
        
        	float processSampleR(float s) {
        		float shapedSample = s * GAIN;
        		return shapedSample;
        	}
        
        	
        
        		template <typename T> void processFrame(T& data)
        	{
        		// Assuming `data` is a contiguous block with interleaved channels
        		const size_t numChannels = 2; // We only have two channels
        		const size_t numSamples = data.size() / numChannels;
        
        		// Process each channel differently
        		for (size_t channel = 0; channel < numChannels; ++channel)
        		{
        			for (size_t i = 0; i < numSamples; ++i)
        			{
        				size_t index = i * numChannels + channel;
        
        				if (channel == 0)
        				{
        					// Process left channel
        					data[index] = processSampleL(data[index]);
        				}
        				else if (channel == 1)
        				{
        					// Process right channel
        					data[index] = processSampleR(data[index]);  //processSampleR(data[index]);
        				}
        			}
        		}
        	}
        
        
        	
        	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)
        		{
        			// Update the GAIN parameter
        			GAIN = static_cast<float>(v);
        		}
        		if (P == 1)
        		{
        		}
        	}
        	
        	void createParameters(ParameterDataList& data)
        	{
        		{
        			parameter::data p("GAIN", { 0.0, 1.0 });
        			registerCallback<0>(p);
        			p.setDefaultValue(1.0);
        			data.add(std::move(p));
        		}
        		{
        			parameter::data p("ParameterName", { 0.0, 1.0 });
        			registerCallback<0>(p);
        			p.setDefaultValue(0.5);
        			data.add(std::move(p));
        		}
        	}
        };
        }
        
        
        
        
        griffinboyG 1 Reply Last reply Reply Quote 0
        • griffinboyG
          griffinboy @griffinboy
          last edited by

          @griffinboy
          bump (srry)

          Christoph HartC 1 Reply Last reply Reply Quote 0
          • Christoph HartC
            Christoph Hart @griffinboy
            last edited by Christoph Hart

            What is the correct method to find the sample rate ?

            Grab it in the prepare callback (just as in SNEX). You get a PrepareSpecs object that contains all processing information (channel count, sample rate, max blocksize, etc).

            What is the best practice to get data out of a c++ node and into the main hise interface script?

            Depends on the data type, but it's way more complicated than it should be :)

            Are there are any tools already in place for time based effects (think LFO / Envelope)?

            No, but you can use every scriptnode node as C++ class as a member of your node class, eg. envelope::ahdsr. That's basically how the C++ generator works, but you can obviously use them yourself too. If you do so, you need to forward all callbacks to the member, just like you would with any external C++ DSP code.

            It is possible (to easily) change the size of a frame and process very specific frame lengths (eg. in 0.4s frames)?

            Similar to the last answer, the tools for slicing up buffers that you have in scriptnode are also available as C++ templates, so wrap::fix_block<ahdsr::envelope, 32>. Prepare for waves of weird template compilation errors if you go down that route, but that's the tradeoff for getting a fast end product :)

            griffinboyG 1 Reply Last reply Reply Quote 0
            • griffinboyG
              griffinboy @Christoph Hart
              last edited by griffinboy

              @Christoph-Hart

              Thank you for your answers I appreciate it.

              What is the best practice to get data out of a c++ node and into the main hise interface script?

              Unfortunately this is one is quite important.

              My requirement is that main interface script in my project needs to be able to grab one specific variable out of my node.

              My first idea was to turn the node into a modulator and then pass the value to a global cable in scriptnode. But I've been having issues with compiled networks not really working as they should with global cable.
              I'm not sure if there are any alternatives to this.

              Christoph HartC 1 Reply Last reply Reply Quote 0
              • Christoph HartC
                Christoph Hart @griffinboy
                last edited by

                @griffinboy said in Finding Sample Rate, and Other C++ scriptnode questions:

                My first idea was to turn the node into a modulator and then pass the value to a global cable in scriptnode.

                Actually that's what I would recommend too in that use case:

                1. Create a C++ node my_node with a modulation output
                2. Load the project.my_node in a DSP network and connect the mod output to a global_cable with a fixed ID
                3. Export the DSP network
                4. Use the global cable in your interface script.

                I'll do a quick test to see where it gets stuck, but that should theoretically work.

                Christoph HartC 1 Reply Last reply Reply Quote 0
                • Christoph HartC
                  Christoph Hart @Christoph Hart
                  last edited by

                  Just checked the procedure and it works fine. You have to create the global routing manager and the cable you connect to in the interface script before you load the hardcoded master FX (because the DLL cannot create anything because of DLL heap memory issues) and that might become a problem if you load a real project but the basic functionality works.

                  griffinboyG 1 Reply Last reply Reply Quote 1
                  • griffinboyG
                    griffinboy @Christoph Hart
                    last edited by griffinboy

                    @Christoph-Hart

                    Thanks I'll try it out!

                    I was previously having issues where when I compile the FX vst the cable would no longer function, but I shall see

                    1 Reply Last reply Reply Quote 0
                    • griffinboyG griffinboy has marked this topic as solved on
                    • First post
                      Last post

                    11

                    Online

                    1.7k

                    Users

                    11.9k

                    Topics

                    103.4k

                    Posts