Making an audio App in JUCE using Max Gen~ export code

Hello!

I’m trying to make an app out of gen~ (MaxMSP) exported c++ code. The reason I’m writing here is that the subject is more oriented on JUCE implementation neither Max one.

  1. So, the first question is how to reference gen~ parameters in Projucer?

I wan’t to made a custom UI for this app in Projucer, for this I created several rotary sliders and comboboxes. Do I need to name them the same way as my gen~ parameters named in gen code?

So for example in gen code I got a parameter:

// initialize parameter 5 ("m_bl_64")
	pi = self->__commonstate.params + 5;
	pi->name = "bl";
	pi->paramtype = GENLIB_PARAMTYPE_FLOAT;
	pi->defaultvalue = self->m_bl_64;
	pi->defaultref = 0;
	pi->hasinputminmax = false;
	pi->inputmin = 0;
	pi->inputmax = 1;
	pi->hasminmax = true;
	pi->outputmin = 6798.014921;
	pi->outputmax = 17657.181612;
	pi->exp = 0;
	pi->units = "";		// no units defined

And in my MainComponent.cpp

I got this code:

	void addSliders()
	{
		Component *sliderHolder = m_uiComponent->getSliderHolder();
		
		if (sliderHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Slider *slider = new Slider();
				double min = 0;
				double max = 1;
				
				if (C74_GENPLUGIN::getparameterhasminmax(m_C74PluginState, i)) {
					min = C74_GENPLUGIN::getparametermin(m_C74PluginState, i);
					max = C74_GENPLUGIN::getparametermax(m_C74PluginState, i);
				}

				slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
				slider->setRange(min, max);
				slider->setName(String((int)i));
				slider->addListener(this);
				
				sliderHolder->addAndMakeVisible(slider);
			}
		}
		
		Component *sliderLabelHolder = m_uiComponent->getSliderLabelHolder();
		
		if (sliderLabelHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Label *sliderLabel = new Label();
				sliderLabel->setText(String(C74_GENPLUGIN::getparametername(m_C74PluginState, i)), NotificationType::dontSendNotification);
				sliderLabelHolder->addAndMakeVisible(sliderLabel);
			}
		}
		
		resized();
	}
	
	void sliderValueChanged (Slider* slider)
	{
		long index = atoi(slider->getName().getCharPointer());
		C74_GENPLUGIN::setparameter(m_C74PluginState, index, slider->getValue(), NULL);
	}

Which references some automatically created UI. How can I link both my interface and automatically created UI?

  1. I also got some compilation errors, after trying to compile this MainComponent.cpp:
/*
  ==============================================================================

    This file was auto-generated!

  ==============================================================================
*/

#ifndef MAINCOMPONENT_H_INCLUDED
#define MAINCOMPONENT_H_INCLUDED

#include <JuceHeader.h>

#include "C74_GENPLUGIN.h"
#include "UIComponent.h"

//==============================================================================
/*
    This component lives inside our window, and this is where you should put all
    your controls and content.
*/
class MainContentComponent   : public AudioAppComponent, Slider::Listener
{
public:
    //==============================================================================
    MainContentComponent()
	:m_CurrentBufferSize(0)
	{
		// use a default samplerate and vector size here, reset it later
		m_C74PluginState = (CommonState *)C74_GENPLUGIN::create(44100, 64);
		C74_GENPLUGIN::reset(m_C74PluginState);

		m_InputBuffers = new t_sample *[C74_GENPLUGIN::num_inputs()];
		m_OutputBuffers = new t_sample *[C74_GENPLUGIN::num_outputs()];
		
		for (int i = 0; i < C74_GENPLUGIN::num_inputs(); i++) {
			m_InputBuffers[i] = NULL;
		}
		for (int i = 0; i < C74_GENPLUGIN::num_outputs(); i++) {
			m_OutputBuffers[i] = NULL;
		}
		
        // specify the number of input and output channels that we want to open
        setAudioChannels (getNumInputChannels(), getNumOutputChannels());
		
		m_uiComponent = new UIComponent();
		addAndMakeVisible(m_uiComponent);
		addSliders();
		
		Rectangle<int> r = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
		setSize (r.getWidth(), r.getHeight());
    }

    ~MainContentComponent()
    {
        shutdownAudio();
		C74_GENPLUGIN::destroy(m_C74PluginState);
    }

    //=======================================================================
	
	long getNumInputChannels() const { return 2; }
	long getNumOutputChannels() const { return 2; }
	
    void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
    {
        // This function will be called when the audio device is started, or when
        // its settings (i.e. sample rate, block size, etc) are changed.

        // You can use this function to initialise any resources you might need,
        // but be careful - it will be called on the audio thread, not the GUI thread.

        // For more details, see the help for AudioProcessor::prepareToPlay()
		
		// initialize samplerate and vectorsize with the correct values
		m_C74PluginState->sr = sampleRate;
		m_C74PluginState->vs = samplesPerBlockExpected;
		
		assureBufferSize(samplesPerBlockExpected);
    }

    void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
    {
        // Your audio-processing code goes here!

        // For more details, see the help for AudioProcessor::getNextAudioBlock()
		
		AudioSampleBuffer *buffer = bufferToFill.buffer;
		
		assureBufferSize(buffer->getNumSamples());
		
		// fill input buffers
		for (int i = 0; i < C74_GENPLUGIN::num_inputs(); i++) {
			if (i < getNumInputChannels()) {
				for (int j = 0; j < buffer->getNumSamples(); j++) {
					m_InputBuffers[i][j] = buffer->getReadPointer(i)[j];
				}
			} else {
				memset(m_InputBuffers[i], 0, m_CurrentBufferSize *  sizeof(double));
			}
		}
		
		// process audio
		C74_GENPLUGIN::perform(m_C74PluginState,
								  m_InputBuffers,
								  C74_GENPLUGIN::num_inputs(),
								  m_OutputBuffers,
								  C74_GENPLUGIN::num_outputs(),
								  buffer->getNumSamples());
		
		// fill output buffers
		for (int i = 0; i < getNumOutputChannels(); i++) {
			if (i < C74_GENPLUGIN::num_outputs()) {
				for (int j = 0; j < buffer->getNumSamples(); j++) {
					buffer->getWritePointer(i)[j] = m_OutputBuffers[i][j];
				}
			} else {
				buffer->clear (i, 0, buffer->getNumSamples());
			}
		}
    }

    void releaseResources() override
    {
        // This will be called when the audio device stops, or when it is being
        // restarted due to a setting change.

        // For more details, see the help for AudioProcessor::releaseResources()
    }

    //=======================================================================
    void paint (Graphics& g) override
    {
        // (Our component is opaque, so we must completely fill the background with a solid colour)
        g.fillAll (Colours::black);


        // You can add your drawing code here!
    }

    void resized() override
    {
        // This is called when the MainContentComponent is resized.
        // If you add any child components, this is where you should
        // update their positions.

		m_uiComponent->setBounds(getBounds());
    }

	void addSliders()
	{
		Component *sliderHolder = m_uiComponent->getSliderHolder();
		
		if (sliderHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Slider *slider = new Slider();
				double min = 0;
				double max = 1;
				
				if (C74_GENPLUGIN::getparameterhasminmax(m_C74PluginState, i)) {
					min = C74_GENPLUGIN::getparametermin(m_C74PluginState, i);
					max = C74_GENPLUGIN::getparametermax(m_C74PluginState, i);
				}

				slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
				slider->setRange(min, max);
				slider->setName(String((int)i));
				slider->addListener(this);
				
				sliderHolder->addAndMakeVisible(slider);
			}
		}
		
		Component *sliderLabelHolder = m_uiComponent->getSliderLabelHolder();
		
		if (sliderLabelHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Label *sliderLabel = new Label();
				sliderLabel->setText(String(C74_GENPLUGIN::getparametername(m_C74PluginState, i)), NotificationType::dontSendNotification);
				sliderLabelHolder->addAndMakeVisible(sliderLabel);
			}
		}
		
		resized();
	}
	
	void sliderValueChanged (Slider* slider)
	{
		long index = atoi(slider->getName().getCharPointer());
		C74_GENPLUGIN::setparameter(m_C74PluginState, index, slider->getValue(), NULL);
	}
	

protected:
	// c74: since Juce does float sample processing and Gen offers double sample
	// processing, we need to go through input and output buffers
	void assureBufferSize(long bufferSize)
	{
		if (bufferSize > m_CurrentBufferSize) {
			for (int i = 0; i < C74_GENPLUGIN::num_inputs(); i++) {
				if (m_InputBuffers[i]) delete m_InputBuffers[i];
				m_InputBuffers[i] = new t_sample[bufferSize];
			}
			for (int i = 0; i < C74_GENPLUGIN::num_outputs(); i++) {
				if (m_OutputBuffers[i]) delete m_OutputBuffers[i];
				m_OutputBuffers[i] = new t_sample[bufferSize];
			}
			
			m_CurrentBufferSize = bufferSize;
		}
	}
private:
    //==============================================================================

    // Your private member variables go here...

	UIComponent				*m_uiComponent;
	
	CommonState				*m_C74PluginState;
	
	long					m_CurrentBufferSize;
	t_sample				**m_InputBuffers;
	t_sample				**m_OutputBuffers;
	
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};


// (This function is called by the app startup code to create our main component)
Component* createMainContentComponent()     { return new MainContentComponent(); }


#endif  // MAINCOMPONENT_H_INCLUDED

class MainContentComponent   : public AudioAppComponent, Slider::Listener  // error: Expected class name
setAudioChannels (getNumInputChannels(), getNumOutputChannels());  // error: use of undeclared identifier 'setAudioChannels'
addAndMakeVisible(m_uiComponent);  // error: use of undeclared identifier 'addAndMakeVisible'
setSize (r.getWidth(), r.getHeight());  // error: use of undeclared identifier 'setSize'
shutdownAudio();  // error: use of undeclared identifier 'shutdownAudio'

And a number of override errors:

void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override  // error: Only virtual member functions can be marked 'override'

I have a novice experience in C++, I read Bjarne Stroustrup book up to the classes chapter and watched some video courses, so I can understand but, unfortunately, cannot write code myself yet.

Best regards,
Ruslan

Setting to:

#include "../JuceLibraryCode/JuceHeader.h"

Doesn’t solve the thing. I know that MainContentComponent is a user class with an object AudioAppComponent, which in fact JUCE class. I don’t understand, why Xcode compiler keep asking to enter a class name. For some reason it doesn’t see JUCE classes, maybe I should #include other objects as well?

Setting class name to:

class MainContentComponent : public Component, Slider::Listener

removes the first error but this way I’m unable to use AudioAppComponent methods, which I need for my application.

So here is the solution to a first problem:

AudioAppComponent

Ok, I was able to compile the code, heard sound but parameters doesn’t work by their name reference and this part of code also resulted in compilation error:

	void addSliders()
	{
		Component *sliderHolder = m_uiComponent->getSliderHolder();
		
		if (sliderHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Slider *slider = new Slider();
				double min = 0;
				double max = 1;
				
				if (C74_GENPLUGIN::getparameterhasminmax(m_C74PluginState, i)) {
					min = C74_GENPLUGIN::getparametermin(m_C74PluginState, i);
					max = C74_GENPLUGIN::getparametermax(m_C74PluginState, i);
				}

				slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
				slider->setRange(min, max);
				slider->setName(String((int)i));
				slider->addListener(this);
				
				sliderHolder->addAndMakeVisible(slider);
			}
		}
		
		Component *sliderLabelHolder = m_uiComponent->getSliderLabelHolder();
		
		if (sliderLabelHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Label *sliderLabel = new Label();
				sliderLabel->setText(String(C74_GENPLUGIN::getparametername(m_C74PluginState, i)), NotificationType::dontSendNotification);
				sliderLabelHolder->addAndMakeVisible(sliderLabel);
			}
		}
		
		resized();
	}
	
	void sliderValueChanged (Slider* slider)
	{
		long index = atoi(slider->getName().getCharPointer());
		C74_GENPLUGIN::setparameter(m_C74PluginState, index, slider->getValue(), NULL);
	}

It seems I need to return values for Gen~ parameters to JUCE params, for this I need a line of code for each parameter. Can anyone who faced similar issue help me to write this part of code?

There is a guide for RNBO, which is a new lower level language inside Max:

https://github.com/Cycling74/rnbo.example.juce/blob/main/CUSTOM_UI.md

Where the same technic used to inject parameters:

//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
void RootComponent::setAudioProcessor(RNBO::JuceAudioProcessor *p)
{
    processor = p;

    RNBO::ParameterInfo parameterInfo;
    RNBO::CoreObject& coreObject = processor->getRnboObject();

    for (unsigned long i = 0; i < coreObject.getNumParameters(); i++) {
        auto parameterName = coreObject.getParameterId(i);
        RNBO::ParameterValue value = coreObject.getParameterValue(i);
        Slider *slider = nullptr;
        if (juce::String(parameterName) == juce__slider.get()->getName()) {
            slider = juce__slider.get();
        } else if (juce::String(parameterName) == juce__slider2.get()->getName()) {
            slider = juce__slider2.get();
        } else if (juce::String(parameterName) == juce__slider3.get()->getName()) {
            slider = juce__slider3.get();
        }

        if (slider) {
            slidersByParameterIndex.set(i, slider);
            coreObject.getParameterInfo(i, &parameterInfo);
            slider->setRange(parameterInfo.min, parameterInfo.max);
            slider->setValue(value);
        }
    }
}
//[/MiscUserCode]

And in Gen~ example:

void addSliders()
	{
		Component *sliderHolder = m_uiComponent->getSliderHolder();
		
		if (sliderHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Slider *slider = new Slider();
				double min = 0;
				double max = 1;
				
				if (C74_GENPLUGIN::getparameterhasminmax(m_C74PluginState, i)) {
					min = C74_GENPLUGIN::getparametermin(m_C74PluginState, i);
					max = C74_GENPLUGIN::getparametermax(m_C74PluginState, i);
				}

				slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
				slider->setRange(min, max);
				slider->setName(String((int)i));
				slider->addListener(this);
				
				sliderHolder->addAndMakeVisible(slider);
			}
		}
		
		Component *sliderLabelHolder = m_uiComponent->getSliderLabelHolder();
		
		if (sliderLabelHolder) {
			for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
				Label *sliderLabel = new Label();
				sliderLabel->setText(String(C74_GENPLUGIN::getparametername(m_C74PluginState, i)), NotificationType::dontSendNotification);
				sliderLabelHolder->addAndMakeVisible(sliderLabel);
			}
		}
		
		resized();
	}

The only difference I see is in implementation, for example there is no Gen~ tutorial and I don’t know how to realize this parts of code from RNBO example:

//[UserMethods]     -- You can add your own custom methods in this section.
void setAudioProcessor(RNBO::JuceAudioProcessor *p);
void updateSliderForParam(unsigned long index, double value);
//[/UserMethods]

And

//[UserVariables]   -- You can add your own custom variables in this section.
RNBO::JuceAudioProcessor *processor = nullptr;
HashMap<int, Slider *> slidersByParameterIndex; // used to map parameter index to slider we want to control
//[/UserVariables]
void RootComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved)
{
    //[UsersliderValueChanged_Pre]
    if (processor == nullptr) return;
    RNBO::CoreObject& coreObject = processor->getRnboObject();
    auto parameters = processor->getParameters();
    //[/UsersliderValueChanged_Pre]

    if (sliderThatWasMoved == juce__slider.get())
    {
        //[UserSliderCode_juce__slider] -- add your slider handling code here..
        //[/UserSliderCode_juce__slider]
    }
    else if (sliderThatWasMoved == juce__slider2.get())
    {
        //[UserSliderCode_juce__slider2] -- add your slider handling code here..
        //[/UserSliderCode_juce__slider2]
    }
    else if (sliderThatWasMoved == juce__slider3.get())
    {
        //[UserSliderCode_juce__slider3] -- add your slider handling code here..
        //[/UserSliderCode_juce__slider3]
    }

    //[UsersliderValueChanged_Post]
    RNBO::ParameterIndex index = coreObject.getParameterIndexForID(sliderThatWasMoved->getName().toRawUTF8());
    if (index != -1) {
        const auto param = processor->getParameters()[index];
        auto newVal = sliderThatWasMoved->getValue();

        if (param && param->getValue() != newVal)
        {
            param->beginChangeGesture();
            param->setValueNotifyingHost(newVal);
            param->endChangeGesture();
        }
    }
    //[/UsersliderValueChanged_Post]
}

Instead of:

void sliderValueChanged (Slider* slider)
	{
		long index = atoi(slider->getName().getCharPointer());
		C74_GENPLUGIN::setparameter(m_C74PluginState, index, slider->getValue(), NULL);
	}

As you see the technic is essentially the same, the only difference I see is that they do not provide a reference code for UI to match Gen~ parameters to the created GUI in Projucer.

I have no experience with gen~ but here is an example which uses APVTS (juce::AudioProcessorValueTreeState) to connect the gen~ parameters to the JUCE UI elements.

If you re-structure you project as a standalone plugin (instead of a GUI application) you should be able to follow the example. Hope this helps!

ok, thank you!

I come across this function:

void setParameter (int index, float newValue)
{
	t_param min = C74_GENPLUGIN::getparametermin(m_C74PluginState, index);
	t_param range = fabs(C74_GENPLUGIN::getparametermax(m_C74PluginState, index) - min);
	t_param value = newValue * range + min;
	
	C74_GENPLUGIN::setparameter(m_C74PluginState, index, value, NULL);
}

Which I defined inside MainContentComponent class. Then I need to refer to it from this code:

void UIComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved)
{
    //[UsersliderValueChanged_Pre]
    //[/UsersliderValueChanged_Pre]

    if (sliderThatWasMoved == tempo__slider.get())
    {
        //[UserSliderCode_tempo__slider] -- add your slider handling code here..
        sliderThatWasMoved->getValue();
        setParameter(6, sliderThatWasMoved->getValue());

        //[/UserSliderCode_tempo__slider]
    }
    else if (sliderThatWasMoved == lambda__slider.get())
    {
        //[UserSliderCode_lambda__slider] -- add your slider handling code here..
        sliderThatWasMoved->getValue();
        setParameter(5, sliderThatWasMoved->getValue());
        //[/UserSliderCode_lambda__slider]
    }

I need to put a pointer before setParameter, but don’t know how to reference the class from this object. Can you help me in this? I believe this is the only thing I need to connect my sliders :slight_smile:

You could declare a std::function in UIComponent:
std::function<void (int, float)> setParameter;

And in the constructor of your MainContentComponent:
m_uiComponent.setParameter = [this] (int index, float value) { setParameter (index, value); };

Now calling setParameter() within m_uiComponent calls setParameter() of MainContentComponent.

Of course there are other solutions, such as passing a pointer or a reference: GitHub - Xenakios/JuceComponentCommunication

Is this an example of how I should call this object in this code:

void UIComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved)
{
    //[UsersliderValueChanged_Pre]
    //[/UsersliderValueChanged_Pre]

    if (sliderThatWasMoved == tempo__slider.get())
    {
        //[UserSliderCode_tempo__slider] -- add your slider handling code here..
        sliderThatWasMoved->getValue();
        m_uiComponent.setParameter = [this] (6, sliderThatWasMoved->getValue()) { setParameter (index, value); };

        //[/UserSliderCode_tempo__slider]
    }

No, put that at the end of the constructor of MainContentComponent.
You call setParameter just as you had it previously, no change needed.

I got two errors, while compiling in Xcode:

  1. Cannot use dot operator on a type
  2. Unknown type name ‘m_uiComponent’

Ok, I will set this project aside until I enhance my c++ skills, will read book, watch videos… there are a number of conceptions misunderstood here.

If you give it another go, immediately after:
addAndMakeVisible(m_uiComponent);
put:
m_uiComponent->setParameter = [this] (int index, float value) { setParameter (index, value); };

But yes, learning some C++ is a good idea. Good luck!

Thank you! this removes the previous error, but gives a new one:

Reference to non-static member function must be called.

Now I need to figure out where to put the function declaration:

UIComponent::UIComponent ()
{
    //[Constructor_pre] You can add your own custom stuff here..
    std::function<void (int, float)> setParameter;
    //[/Constructor_pre]

    setName ("UIComponent");
    sliderHolder.reset (new Component());
    addAndMakeVisible (sliderHolder.get());
    sliderHolder->setName ("sliderHolder");

Is it a good spot?

I put it in .h file:

public:
    //==============================================================================
    UIComponent ();
    ~UIComponent() override;

    //==============================================================================
    //[UserMethods]     -- You can add your own custom methods in this section.
	Component *getSliderHolder();
	Component *getSliderLabelHolder();
	int getNumParameters();
    float getParameter (int index);
    const String getParameterName (int index);
    const String getParameterText (int index);
    std::function<void (int, float)> setParameter;
    //[/UserMethods]

OK Great! It compiled!

1 Like

Ok, checked this code works, but when parameters are from 0 to 5000 say so, it gives the maximum value at 1, and when the parameter starts from 3000 it doesn’t change at all. Other parameters, which are from 0 to 1 work as expected!

Now I just need to invert getToggleState() and I’m done.

Is there an easy way to do so just like !getToggleState() - gives a compilation error!?

Ok, just made an int variable to store an intermediate value

1 - buttonThatWasClicked->getToggleState()

Ok, now beta-testing the app!