AudioProcessorValueTreeState & UndoManager usage

UndoManager is very mature. I’m explicitly mentioning AudioProcessorValueTreeState usage of it.

I’ve made now another test on my personal machine with clean JUCE 5 (master 7e959).

Plain “plug-in” with only gain slider and undo redo buttons.
For the sake of keeping it short I’m only showing the important editor parts.

That’s my constructor:

addAndMakeVisible(undoBtn);
addAndMakeVisible(redoBtn);
addAndMakeVisible(gainSlider);

undoBtn.addListener(this);
redoBtn.addListener(this);

gainAttach = new AudioProcessorValueTreeState::SliderAttachment(p.params,"gain",gainSlider);

setSize (400, 200);

That’s my a simple listener for the buttons:

void AudioProcessorValueTreeUndoAudioProcessorEditor::buttonClicked (Button* btn)
{
    UndoManager* undoMgt = processor.params.undoManager;

    if (btn == &undoBtn)
        if (undoMgt->canUndo()) undoMgt->undo();

    if (btn == &redoBtn)
        if (undoMgt->canRedo()) undoMgt->redo();
}

On the processor side,
Constructor inits:

,params(*this, &undoManager)
{
    params.createAndAddParameter("gain", "Gain", "gain", NormalisableRange<float>(0.0,1.0), 1.0, nullptr, nullptr);
    params.state = ValueTree( Identifier("undoTest"));
} 

And you get those behaviors:

  1. Click Undo just when running it first time will assert (UndoManager::perform:126) as:

             jassertfalse;  // don't call perform() recursively from the UndoableAction::perform()
                        // or undo() methods, or else these actions will be discarded!
    

I can “overcome” those assertions by adding on our constructor:

undoManager.clearUndoHistory();
  1. Now it pretty much “works”. but there are more undoable/redoable actions than what I’d expect from the plug-in…
  • start the plugin
  • move slider
  • undo
  • Try to redo, it’ll fail.
  • start the plugin
  • move slider
  • move slider
  • Try undo, it’ll consider both moves as a single transaction…

I didn’t try equator but if you compare this to other plug-ins undo/redo. this isn’t a common workflow. or am I’m implementing it wrong?

1 Like