Safe to dynamic cast an AudioProcessor* into child / instantiate an AudioProcessorEditor outside of createEditor?

Hi there,

I’m using an AudioProcessorGraph to build an FX chain with a few processors with their own editor.

I want the GUI of the individual processors to appear in the main FX chain window when selected. Initially I returned a new processorEditor when the processor was instantiated in the graph, however no GUI appears (although the processor was definitely instantiated).

I am now trying another method, where the createEditor method returns a nullptr for each of the node processors in the graph, and instead am using this helper function in the AudioProcessorGraph:

 AudioProcessorEditor* getEditor(Node::Ptr node)
    {
        return node->getProcessor()->createEditor();
        
    }

Then, in my main plugin editor, I have the following helper functions:

juce::Array<AudioProcessorEditor*> getEditors()
 {
        juce::Array<AudioProcessorEditor*> editors;
        editors.add(processor.getEditor(processor.getNode1()));
        editors.add(processor.getEditor(processor.getNode2()));
        editors.add(processor.getEditor(processor.getNode3()));
        
        return editors;
        
}
 
void setCurrentEditors()
{
        currentEditors.clear();
        currentEditors.add(processor.getEditor(processor.getNode1()));
        currentEditors.add(processor.getEditor(processor.getNode2()));
        currentEditors.add(processor.getEditor(processor.getNode3()));
 }
void ObliqueConcreteV1AudioProcessorEditor::makeEditorsVisible(Rectangle<float>
                                                               box)
{
    setCurrentEditors();
    int prevWidth = 0;
    int currWidth = (getWidth()/3) - 10;
    for(int i = 0; i < getEditors().size(); i++)
    {
        auto editor = getEditors().getUnchecked(i);
        auto currentEditor = currentEditors.getUnchecked(i);
        
        if(editor->getName() == currentEditor->getName())
        {
            prevWidth = currWidth;
            continue;
        }
        
        addAndMakeVisible(editor);
        editor->setBounds((i+1) * prevWidth, box.getHeight() + 5, currWidth, box.getHeight() - 10);
        prevWidth = currWidth;
    }
}

calling setCurrentEditors causes the program to crash upon startup.

commenting out setCurrentEditors means the program runs fine, but means I cannot see the GUIs for the individual processors. Can anyone give insight into a better solution than what I’ve done or point out any errors with my logic?

Additional info: ThroughOutput is just a simple plugin that outputs the gain as it came in, implemented this as an alternative to an empty slot to ensure my Editor arrays never had nullptr values.

Any help appreciated.

Edit:

I should also add that each individual audio Processor works fine, the only difference being the instantiation of their Editors (using the above method instead of createEditor())

First of all you are likely to get more responses if you make it easy for people to read your code by formatting it properly – to be honest I stopped reading after the first lines of code. Better edit your post and format the code blocks by putting ``` in the line above and below the code – this will make the forum software display your code properly formatted. If you look close you’ll also see that e.g. some * are missing as they are interpreted as markers for italic text – which makes the code even less readable and confusing.

That being said, I never worked with the AudioProcessorGraph myself, but from my experience with single Plug-Ins which are also derived from AudioProcessor in JUCE, I know that creating the editor always works through calling createEditor on the processor instance. If I get it right, here

if(node->getProcessor()->getName() == “Filter”)
{
    return new FilterEditor (*dynamic_cast<FilterProcessor*>(node->getProcessor()));
}

you are calling the constructor of your editor with a reference to your processor instance as argument manually. If there is nothing special about the graphs I don’t know, then this is the wrong way to do it, as this will bypass all the stuff that may happen in the createEditor function.

So I think your function should basically look like

AudioProcessorEditor* getEditor (Node::Ptr node)
{
    return node->getProcessor()->createEditor();
}

as the createEditor overload of each individual processor type should manage to return the right type of procesor.

Hi Penguin. Thanks for your response here. I’ve reformatted my initial post for readability. My understanding of createEditor was that it was automatically called when an instantiation of the processor it is attached to is created, thus when I instantiate a new processor in the graph, a new editor, should, in theory, open for this processor in a new window… or at least so I thought. However, this wasn’t happening, hence my trying the alternative method. I’ve implemented it the way you advised and it still crashes on start up.

I’m still trying to figure out what’s causing this. Again, each processor works individually as its own plugin, and the graph works when I don’t try to instantiate editors for the individual nodes, so my problem must be to do with the instantiation of the sub-editors.

For reference, I call setBounds instead of setSize() in the constructors for each processor editor (although I have tried both), which is then over-written by the call to setBounds in the makeEditorsVisible function. This function is called in resized() in the main plugin Editor.

This piece of code is causing the crash on startup:

AudioProcessorEditor* getEditor(Node::Ptr node)
    {
        return node->getProcessor()->createEditor();
        
    }

I changed the function to createEditorIfNeeded() to see if this would work but it still crashed.

Hi PolityRecs,
I know if I say something it may get the conversation going for you. A static std::map<String, std::function> based on a shared valueTree model that returns the new UI item, is a great way to manage such items rather than using if else. The shared valueTree keeps the UI and the processor seperate. I also use a mapped destructor to delete the class.
Alternatively, Dave has a fully working version in the Tracktion Engine that will handle dynamic plug-ins, allow external plug-ins and all with delay compensation.
Cheers

1 Like

And just keep swimming.
I read some code and think wow that is amazing, but then I see the person’s products and can’t hear or see anything that great. The point is the programmers who know it all do not necessarily make the best products in the end. Great to see your perseverance.

Hey Iob, thank you so much for the response, would you mind elaborating on your suggestion a little bit / perhaps sharing some code? I’m not particularly familiar with certain kinds of data structures and their utility so, although your response is extremely helpful, I’m on a tight deadline to get a prototype of this done and would really appreciate a bit of a leg up here. But again, thank you so much for your response :slight_smile:

“A static std::map<String, std::function> based on a shared valueTree model that returns the new UI item, is a great way to manage such items rather than using if else. The shared valueTree keeps the UI and the processor seperate. I also use a mapped destructor to delete the class.”

Specifically, could you share some boilerplate code to implement this strategy? Again, thanks so much!

Edit: I’ve also been learning c++ alongside learning Juce for this so some of my c++ concepts are a little shaky, especially when it comes to destructors.

I should also add I have fixed the crash on startup - this was caused by me failing to check for non-AudioProcessor nodes when trying to instantiate editors based on the ProcessorGraph’s nodes; eliminating the code’s attempt to insantiate editors for the audio and midi IO nodes fixed the crash, but the UI still doth not appear :((

Not 100% sure if I get you right, but createEditor is called by whowever wants to create an editor for that specific processor. It returns a new AudioProcessorEditor object, its ownership has to be taken over by the caller. As AudioProcessorEditor inherits Component it can be treated like a regular Component and thus can be made visible in all the ways Components can be made visible, e.g. by adding them as a child to another component or through a new window that your code has to create, this doesn’t happen automatically.

It is really important to create the editor through this function, as this way, the processor has a chance to know that an editor has been created and therefore has the chance to prepare e.g. data structures for that.

I see that you are making the editors visible through the makeEditorsVisible function. At which point is it called? As you are setting the bounds there too, I’m wondering if this is a bit too early? Usually you separate adding components from setting their size and set the size in the parents components resized() callback. Is it possible that you do the resizing to early? Did you check the bounds of the box argument passed to that function when running your code under the debugger?

A propos debugger: Regarding crashes, I get the feeling that you should dig a bit deeper into debugging techniques to learn helping yourself. If you run your application under a debugger, it will stop when the crash happens, show you the exact line of code that crashed, it will show you the state of all variables at the point of the crash and it will show you the call stack, e.g. what functions called the function you are currently into, including the state of all variables in the call stack. Usually this gives you a good idea of either what exacly went wrong (like “Oops, I dereferenced a nullpointer” or “Oh, this variable is out of the allowed range here”) or you can set a breakpoint a bit above the function and step through the code line by line, looking what function calls do and maybe spot the point where something wrong happens that leads to the crash later). With that knowledge you might also be able to ask more specific questions here :wink:

Regarding the std::map approach @lob suggested: While there are good use-cases for a std::map I don’t think that this will help you here in any way and it doesn’t make your code any clearer or better working. Having said already that createEditor is the right approach as a general advice: A clean piece of code like that returning something specific based on the type of the element should not rely on string comparison at all but use the behaviour if the dynamic_cast to return a nullptr in case the cast fails, like e.g.

Foo* getFoo (Bar* bar)
{
    if (auto* b = dynamic_cast<BarSubclassA*>) return b->somethingSpecificToSubclassA();
    if (auto* b = dynamic_cast<BarSubclassB*>) return b-> somethingSpecificToSubclassB();
    if (auto* b = dynamic_cast<BarSubclassC*>) return b-> somethingSpecificToSubclassC();

    jassertfalse;
    return nullptr;
}

Hi Penguin, thanks for the detailed response. I think I understand the Editor object better now.

makeEditorsVisible is called in the resized() function of the Graph’s editor, after a box has been created representing the size of the graph’s box containing its parameters (containing the comboboxes and toggleboxes). The box that is passed to the makeEditorsVisible function is a reflection of this to give the bounds for the remainder of the plugin.

I am currently debugging - I had been lazy and have not been using the Juce plugin host, this is now corrected. With regards to addAndMakeVisible - must this be called in the constructor, and can it be called anywhere else?

Edit: I should also add that the editors, once created, are stored in an array of editor pointers. The array is a member of the PluginEditor of the graph. THe programme keeps track of the current editors loaded, and the makeEditors visible function makes a call to getEditors which returns an array of editor pointers, compares them with the current editors and makes a change to the UI depending on whether the two arrays are different.

This is not the right place. addAndMakeVisible is intended to be called once, e.g. after creating the component itself (–> calling createEditor). After adding it you have to make sure to keep that component alive as long as the component it was added to is alive or at least until you manually deleted the child component. Taking a close look at your code snippet above I see you violating this rule:

setCurrentEditors() calls processor.getEditor (which creates a new editor instance) and afterwards you create some more editors to the same processors by calling getEditors() multiple times in your loop. You never delete the instances allocated :grimacing: Beneath creating a great memory leak here, you are also working on different processor instances in every loop iteration in your makeEditorsVisible function. You create editors through setCurrentEditors and then set the size on different editors, created inside getEditors(). What you should do instead is:

First: Change your getEditor signature to return a std::unique_ptr which makes clear “Hey, this is an object you need to take over ownership for” like

std::unique_ptr<AudioProcessorEditor> getEditor (Node::Ptr node)
{
    return node->getProcessor()->createEditor();        
}

As soon as the unique pointer returned gets out of scope it deletes the editor.

Second: Make sure to use the same editor instances everywhere in your code. To do so, currentEditors has to be a some kind of container that manages ownership. JUCE has the OwnedArray class for that and you see that it has an add function that takes a unique pointer. So declare your currentEditors array as OwnedArray<AudioProcessorEditor> currentEditors and make it a member of your class. Call setCurrentEditors right after the graph has been set up and make sure to clear the array before the graph is deleted at the end of its lifetime.

Third: Add the graph editors to your main editor in the constructor of your main editor, e.g. by simply calling

for (auto* graphEditor : currentEditors)
    addAndMakeVisible (graphEditor);

Fourth: Create the layout of your editors in the resized function of your editor. Strip down your makeEditorsVisible to something like

void ObliqueConcreteV1AudioProcessorEditor::resizeGraphEditors (Rectangle<float> box)
{
    int prevWidth = 0;
    int currWidth = (getWidth()/3) - 10;
    for(int i = 0; i < currentEditors.size(); ++i)
    {
        auto& editor = *currentEditors[i];
        
        // Check if the bounds computation still makes sense this way, but I guess you'll manage to get this done right...
        editor->setBounds((i+1) * prevWidth, box.getHeight() + 5, currWidth, box.getHeight() - 10);
        prevWidth = currWidth;
    }
}

And then delete getEditors() completely.

For further reference you should look up memory management, unique_ptr etc. as this is some really important general knowledge. And check the JUCE tutorial on parent and child components!

1 Like

A side note on that: You can debug in (nearly) every host, you don’t have to use the juce host. I prefer Reaper for an example. Just attach the debugger to the DAWs process

Here is some code. Haven’t run it, but it is based on what I used to use. A function pointer is the address of an actual function defined in C++. I think a std::function is a slightly slower wrapper that can hold any type of reassignable callable object (objects that can be used like functions).

  //——function pointer method——
  typedef void/*return type*/ (*functionOnEditor)(AudioProcessorEditor* audioProcessorEditor);      
  static const std::map<String, functionOnEditor> functionOnEditorMap   =
  //————or use a std::function  method——
  //typedef std::function<void(AudioProcessorEditor* audioProcessorEditor)> functionOnEditor;      
  //static const std::map<String, functionOnEditor> functionOnEditorMap =
  {
    {
      "FilterEditor",
      [] (AudioProcessorEditor* audioProcessorEditor) -> void/*any return type like this…. -> void, not used here when using the std::function. So remove -> void*/
      {
        if ( auto* filterProcessor =  dynamic_cast<FilterProcessor*>(audioProcessorEditor) )
        {
          filterProcessor -> function();//return here
        }
				//failed return nullptr here …..
      }
    }
    ,
    next.....
  }
  //or with a ValueTree model
  typedef AudioProcessorEditor* (*getEditor)(const ValueTree&);
  static const std::map<String, getEditor> getEditorMap   =
  {
    {
      "FilterEditor",
      [] (const ValueTree& paramTree) -> AudioProcessorEditor*
      {
        return new FilterEditor( paramTree.getProperty("Property").operator int() );
      }
    }
    ,
    next.....
  }

As for destructors, I am not sure how dynamic you are going, but you can look at https://stackoverflow.com/questions/294927/does-delete-work-with-pointers-to-base-class
You can call filterProcessor -> ~filterProcessor(); to trigger a destructor function.

Now to call the function use

functionOnEditorMap[“FilterEditor”](audioProcessorEditor/*parameter*/); 

or sometimes map will not work unless you use

functionOnEditorMap.at(“FilterEditor”)(audioProcessorEditor);

Have fun. Check out Tracktion Engine. This can run a plugin.

“<PLUGIN type=“4osc” windowLocked=“1” id=“1069” enabled=“1” filterType=“1” presetDirty=“0” presetName=“4OSC: Organ” filterFreq=“127.00000000000000000000” ampAttack=“0.60000002384185791016” ampDecay=“10.00000000000000000000” ampSustain=“100.00000000000000000000” ampRelease=“0.40000000596046447754” waveShape1=“4” tune2=”-24.00000000000000000000" waveShape2=“4”> <MACROPARAMETERS id=“1069”/> ";

You can connect up a UI to this valueTree or connect a UI to it’s parameters. If later, you want to add playback in your plugin like Autotune, it is all synced.

Cheers

Reading PluginPenguin,
If you don’t plan on managing deletes, I would agree about using std::unique_ptr or std::shared_ptr. Generally, do not link editor shared_ptr to processor, or vice versa. My code was used for casting void* and is fully dynamic unlike c++, which would upset most programmers. std::unique_ptr exits in one scope unless you move it.
Cheers