A/B compare and undo history

I would like to have a separate undo history when switching between A and B state, so that each has its own and independent history.
A good solution would be to be able to save and restore history, but unless I am missing something it does not looks like this can be done with the UndoManager class.

Any idea on how to implement this kind of behavior?

You could have 2 different UndoManagers and switch between them…

That approach is likely to blow up. Mixing actions with undo and without undo (or even a different undo stack) will at least create inconsistencies, if it doesn’t even crash.

I would recommend to have two ValueTrees with each one having their own undo manager and swap the pair when switching between A and B.

1 Like

You can’t because the undoManager instance in the AudioProcessorValueTreeState is a const.

How do you switch them? My understanding is that the processor has a single apvts, and once created you can only alter its state.
Right now I do the A/B switch using setStateInformation / getStateInformation

Well, when the user clicks A/B you don’t necessarily get a setStateInformation, or is that the A/B switch from the host menu?

Either way, when you call replaceState you also switch an internal variable with it:

struct PluginState
{
    juce::ValueTree state;
    juce::UndoManager undo;
}

PluginState stateA, stateB;
PluginState* currentState = &stateA;

void toggleState()
{
    if (currentState.state == stateA.state)
        currentState = &stateB;
    else
        currentState = &stateA;

    apvts.replaceState (currentState.state); // or similar...
}

and subsequently only use currentState.

You might have to recreate the ParameterAttachments, since they keep the pointer to the undo manager…

Thank you for taking the time @daniel, I appreciate this!

I am currently calling get/setStateInformation myself to store and restore the state when switching. Ideally I would like to do the same with the UndoManager.

I am also using an AudioProcessorValueTreeState to store the state and undo manager, instantiated directly in my processor with a ParameterLayout and all.
I am not sure how I should proceed to replace this with the struct you propose.

Any idea on how to swap undo history when using an AudioProcessorValueTreeState?

Sorry to insist but am I at see here or is it impossible to manage with an AudioProcessorValueTreeState?

Having an A/B compare system with separate history basically means having two distinct ValueTree and UndoManager instances, but this seems impossible with an AudioProcessorValueTreeState.

Am I supposed to ditch the AudioProcessorValueTreeState class altogether and build my own state+undo+parameter system just for this? That seems a bit convoluted for such a common and reasonable requirement.

You always have the option to do necessary changes in the JUCE code base yourself, so you can keep APVTS as your model.
Depending on your context you may also just use two APVTS instances?

APVTS is in my opinion a very high level structure that is good in doing easy things, and not so good at doing complicated stuff. It is therefore a great starting place for beginners, but I replaced it relatively early with something I wrote myself. Also, I believe it has some thread-safety issues that would prevent me from changing anything about the internal value tree.

Concerning how reasonable the requirement is: I would rather expect my application to push the switch from A to B as an undoable action, rather than having two independent undo histories.

EDIT: just though of another way – you can pass your own UndoManager pointer to ParameterAttachments, if you don’t use the once APVTS supplies, but the broader version you can attach to an AudioParameterFloat for example (juce::SliderParameterAttachment).

2 Likes

Thank you for your insight!
I never though of the AudioProcessorValueTreeState as a beginner class meant to be replaced for serious projects, but I guess this is something I will have to consider.

I don’t think I can easily have two instances in one processor as they also handle parameters.

Thank you also for your suggestion regarding parameter attachment, I will have to try different approaches.

As for the A/B strategy, to me it makes more sense to have separate history, but looking at different plugins it looks like what you suggest is the more common approach.