Final Cut - Automation Bug

I started to use AudioValueTreeState::SliderAttachments in a plugin for Final Cut. At first glance it looks ok, but…
a) This assert fires (I don’t implement any callbacks myself, only the SliderAttachment), so I had to turn on the JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING define:

b) When I skim the timeline, the sliders are moved with the value changes. However, sometimes this leads to killing the automations, because outdated values are sent, without even having the mouse in the custom view. There needs to be a check, if there is a mouse pressed on the slider, otherwise it is not ok to send values.

c) If I have keyframes in my automation and I move the sliders, the value change is applied to the whole graph, instead of adding a new keyframe.

Is it possible to change that? If not, I have to deactivate the sliders completely, as just having them present destroys user data without even touching them.

Thanks for advice!

This problem can be reproduced in a vanilla plugin, even without a SliderAttachment:

  1. Create an empty plugin, select AU (I used deployment target 10.7, probably doesn’t matter)
  2. select legacy parameter ids so you can see the automation graph JUCE_FORCE_USE_LEGACY_PARAM_IDS=1
  3. Add an AudioProcessorValueTreeState and one parameter
AutomatedParameterAudioProcessor::AutomatedParameterAudioProcessor()
#ifndef JucePlugin_PreferredChannelConfigurations
     : AudioProcessor (BusesProperties()
                     #if ! JucePlugin_IsMidiEffect
                      #if ! JucePlugin_IsSynth
                       .withInput  ("Input",  AudioChannelSet::stereo(), true)
                      #endif
                       .withOutput ("Output", AudioChannelSet::stereo(), true)
                     #endif
                       )
#endif
, state (*this, nullptr)
{
    state.createAndAddParameter ("test", "test", "test",
                                 NormalisableRange<float> (0.0, 1.0), 0.0, nullptr, nullptr);

    state.state = ValueTree (getName());
}
  1. Add save and load:
void AutomatedParameterAudioProcessor::getStateInformation (MemoryBlock& destData)
{
    MemoryOutputStream stream(destData, false);
    state.state.writeToStream (stream);
}

void AutomatedParameterAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
    ValueTree tree = ValueTree::readFromData (data, sizeInBytes);
    if (tree.isValid()) {
        state.state = tree;
    }
}
  1. Run in FinalCutProX (here: 10.3.4 -> latest)
  2. Drag plugin onto an audio clip
  3. Open audio animation by selecting the clip and hit Ctrl-A
  4. Expand the parameter and add a ramp
  5. Place the cursor somewhere over the ramped part and hit space to play: result all good
  6. Open the custom UI in the HUD
  7. Repeat step 9 and see the whole graph is moved up or down

Please have a look, this is the last thing that needs to be fixed for our release, and seems to be a quite general problem.

Next finding: if I leave getStateInformation() and setStateInformation() empty, the error does not happen. I’ll see if I simply avoid saving the parameters in the state and rely on the parameters sent live during playback/rendering…

It looks like

At one point this issue was fixed. Maybe it re-appeared.

I have a custom updateHostDisplay() in my processor which helps dealing with Final Cut.

// Call it to inform the host about changes. Especially about changes of non-pluginParameters.
void MyAudioProcessor::updateHostDisplay()
{
    // Ableton disables all pluginParameters as soon as updateHostDisplay() gets called.
    // Luckily it doesn't reset non-pluginParameters after a stop & start.
    // So don't call it.
    if (!pluginHostType.isAbletonLive())
        AudioProcessor::updateHostDisplay();
        
    // Ugly Final Cut Pro X hack.
    // Because FCP X does not react to the plain updateHostDisplay.
    // To thin out the data to the host, a timer is used.
    if (pluginHostType.isFinalCut() && !finalcutTimer.isTimerRunning())
    {
        aPluginParameter->setNonNormalisedValueNotifyingHost (aPluginParameter->getNonNormalisedValue());
            
        finalcutTimer.startTimer (100);
            // The timer gets stopped in its own callback.
    }
}

/* To thin out the data to the host. */
class FinalCutTimer : public Timer
{
    void timerCallback () override
    {
        stopTimer();
    }
};
FinalCutTimer finalcutTimer;

Good news for you, I found that, documented here:

So you should be able to remove that workaround now, Fabian added the correct flag for the AU wrapper in develop

Thanks for the notice, @daniel ! That one slipped my radar.

After a lot of testing and digging, I am convinced, that it is bad to save automated parameters in the getStateInformation() / setStateInformation():

  1. Parameters are stored by the host outside the private stateInformation chunk. That is, because a parameter is only valid for a given timepoint
  2. The host will set the parameter outside of each processBlock call, so setting a value yourself is counter productive
  3. At least in final cut setting a value during playback will move the whole graph to fit the value at that time point. This is the reason, why the graph jumps, if you press play hovering a position with a different value).
  4. Because the state might reflect a different snapshot, it must not restore automated parameters. So not setting a value from setStateInformation avoids that problem in FCPX. Maybe it does as well for other hosts…

Can anybody please help me think, are there any reasons or situations, when one should set parameters from setStateInformation? @fabian?

This is really odd as Apple’s own public CoreAudio Utility classes, they also save/restore the parameter values on getState/setState calls. See here! AFAIK all of Apple’s AudioUnits use the CoreAudio Utility classes internally and we just mimic this behaviour in JUCE.

I also wanted to know, if I was the only one with the problem, so I checked various Brainworx and Waves plug-ins. But I realised, that none of the ones I tested allowed any automatisation in Final Cut Pro. I stopped testing after 10 plugins…

I think because of the multiple instances Final Cut is very sensitive with that. All other hosts that I am aware of only read and restore the state when you open the project. So they don’t have any problems with that situation.

So should we check if it is FCP and then just not load/store the parameters? Would that be a viable workaround. Or should I report this to Apple?

I solved it for me by just not saving the whole state tree, but only my non-automated branch of it. We have full control, what we include in the data blob in the setStateInformation, so actually the juce code needs no change, and neither does apple.
One just needs to be aware, that the nice generic 2-line save and 2-line load method that I use all the time leads to that problem (see above, 2nd post in step 4).

I just hope, that I didn’t forget something, but it seems to work well, possible problems I double checked:

  • even with no value set, the default value is correctly applied
  • the value is set before the first processBlock is called
  • the GUI is up to date, when opened

It just feels like I forgot a good reason, not to leave the values out in the state information (like if automations are turned off in Logic…?) Like solving the problem here but generating tons of problems somewhere else…

But apart from that I am settled with that now. Maybe as many others as possible try that routine as well and give me notice, if that leads to trouble in other hosts, please…