Skip to content

Commit

Permalink
Plugins: Avoided taking an audio callback lock when initialising plug…
Browse files Browse the repository at this point in the history
…ins to avoid hangs in the audio thread (sfztools#145)
  • Loading branch information
drowaudio authored Mar 15, 2023
1 parent 91bd1b8 commit d90724f
Show file tree
Hide file tree
Showing 13 changed files with 63 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class LevelMeasurerProcessingNode final : public tracktion::graph::Node
void initialisePlugin()
{
// N.B. This is deliberately zeroed as it (correctly) assumes the LevelMeterPlugin doesn't need the info during initialisation
meterPlugin.baseClassInitialise ({ TimePosition(), 0.0, 0 });
meterPlugin.baseClassInitialise ({ 0_tp, 0.0, 0 });
isInitialised = true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,14 @@ juce::String NodeRenderContext::renderMidi (Renderer::RenderTask& owner,
const auto blockLength = TimeDuration::fromSamples (samplesPerBlock, sampleRate);
auto streamTime = r.time.getStart();

auto nodePlayer = std::make_unique<TracktionNodePlayer> (std::move (n), *processState,
sampleRate, samplesPerBlock,
getPoolCreatorFunction (static_cast<tracktion::graph::ThreadPoolStrategy> (EditPlaybackContext::getThreadPoolStrategy())));
std::unique_ptr<TracktionNodePlayer> nodePlayer;
callBlocking ([&]
{
nodePlayer = std::make_unique<TracktionNodePlayer> (std::move (n), *processState,
sampleRate, samplesPerBlock,
getPoolCreatorFunction (static_cast<tracktion::graph::ThreadPoolStrategy> (EditPlaybackContext::getThreadPoolStrategy())));
});

nodePlayer->setNumThreads ((size_t) r.engine->getEngineBehaviour().getNumberOfCPUsToUseForAudio() - 1);

//TODO: Should really purge any non-MIDI nodes here then return if no MIDI has been found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ AuxSendPlugin::~AuxSendPlugin()

bool AuxSendPlugin::shouldProcess()
{
const juce::ScopedLock sl (ownerTrackLock);

if (ownerTrack != nullptr)
{
// If this track gets disabled when muted,
Expand All @@ -46,6 +48,7 @@ bool AuxSendPlugin::shouldProcess()

return ! ownerTrack->isMuted (true);
}

return true;
}

Expand Down Expand Up @@ -80,7 +83,11 @@ void AuxSendPlugin::initialise (const PluginInitialisationInfo& info)

void AuxSendPlugin::initialiseWithoutStopping (const PluginInitialisationInfo&)
{
ownerTrack = getOwnerTrack();
TRACKTION_ASSERT_MESSAGE_THREAD
auto newOwnerTrack = getOwnerTrack();

const juce::ScopedLock sl (ownerTrackLock);
ownerTrack = newOwnerTrack;
}

void AuxSendPlugin::deinitialise()
Expand Down
2 changes: 2 additions & 0 deletions modules/tracktion_engine/plugins/internal/tracktion_AuxSend.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class AuxSendPlugin : public Plugin
//==============================================================================
juce::CachedValue<float> lastVolumeBeforeMute;
float lastGain = 1.0f;

juce::CriticalSection ownerTrackLock;
Track* ownerTrack = nullptr;

//==============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ void InsertPlugin::initialise (const PluginInitialisationInfo& info)

void InsertPlugin::initialiseWithoutStopping (const PluginInitialisationInfo& info)
{
TRACKTION_ASSERT_MESSAGE_THREAD
// This latency number is from trial and error, may need more testing
latencySeconds = manualAdjustMs / 1000.0 + (double)info.blockSizeSamples / info.sampleRate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class InsertPlugin : public Plugin
MidiMessageArray sendMidiBuffer, returnMidiBuffer;
juce::CriticalSection bufferLock;

double latencySeconds = 0.0;
std::atomic<double> latencySeconds { 0.0 };
DeviceType sendDeviceType = noDevice, returnDeviceType = noDevice;

void valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier&) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ void LevelMeterPlugin::initialise (const PluginInitialisationInfo& info)

void LevelMeterPlugin::initialiseWithoutStopping (const PluginInitialisationInfo&)
{
TRACKTION_ASSERT_MESSAGE_THREAD

if (auto t = getOwnerTrack())
{
controllerTrack = t->getIndexInEditTrackList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,17 +291,6 @@ void RackInstance::initialise (const PluginInitialisationInfo& info)
{
if (type != nullptr)
type->registerInstance (this, info);

initialiseWithoutStopping (info);
}

void RackInstance::initialiseWithoutStopping (const PluginInitialisationInfo&)
{
const float wet = wetGain->getCurrentValue();
lastLeftIn = dbToGain (leftInDb->getCurrentValue());
lastRightIn = dbToGain (linkInputs ? leftInDb->getCurrentValue() : rightInDb->getCurrentValue());
lastLeftOut = wet * dbToGain (leftOutDb->getCurrentValue());
lastRightOut = wet * dbToGain (linkOutputs ? leftOutDb->getCurrentValue() : rightOutDb->getCurrentValue());
}

void RackInstance::deinitialise()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class RackInstance : public Plugin
juce::String getTooltip() override;

void initialise (const PluginInitialisationInfo&) override;
void initialiseWithoutStopping (const PluginInitialisationInfo&) override;
void deinitialise() override;

bool takesAudioInput() override { return true; }
Expand Down Expand Up @@ -78,8 +77,6 @@ class RackInstance : public Plugin
static constexpr double rackMaxDb = 12.0;

private:
float lastLeftIn = 0.0f, lastRightIn = 0.0f, lastLeftOut = 0.0f, lastRightOut = 0.0f;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RackInstance)
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,21 @@ const char* VolumeAndPanPlugin::xmlTypeName = "volume";
void VolumeAndPanPlugin::initialise (const PluginInitialisationInfo&)
{
refreshVCATrack();

auto sliderPos = getSliderPos();
getGainsFromVolumeFaderPositionAndPan (sliderPos, getPan(), getPanLaw(), lastGainL, lastGainR);
lastGainS = volumeFaderPositionToGain (sliderPos);
}

void VolumeAndPanPlugin::initialiseWithoutStopping (const PluginInitialisationInfo&)
{
TRACKTION_ASSERT_MESSAGE_THREAD
refreshVCATrack();
}

void VolumeAndPanPlugin::deinitialise()
{
const juce::ScopedLock sl (vcaTrackLock);
vcaTrack = nullptr;
}

Expand Down Expand Up @@ -195,10 +198,15 @@ void VolumeAndPanPlugin::applyToBuffer (const PluginRenderContext& fc)
if (fc.destBuffer != nullptr)
{
const int numChansIn = fc.destBuffer->getNumChannels();
const float vcaPosDelta = vcaTrack != nullptr
? decibelsToVolumeFaderPosition (getParentVcaDb (*vcaTrack, fc.editTime.getStart()))
- decibelsToVolumeFaderPosition (0.0f)
: 0.0f;
float vcaPosDelta = 0.0f;

{
const juce::ScopedLock sl (vcaTrackLock);
vcaPosDelta = vcaTrack != nullptr
? decibelsToVolumeFaderPosition (getParentVcaDb (*vcaTrack, fc.editTime.getStart()))
- decibelsToVolumeFaderPosition (0.0f)
: 0.0f;
}

float lgain, rgain;
getGainsFromVolumeFaderPositionAndPan (getSliderPos() + vcaPosDelta, getPan(), getPanLaw(), lgain, rgain);
Expand Down Expand Up @@ -232,6 +240,7 @@ void VolumeAndPanPlugin::applyToBuffer (const PluginRenderContext& fc)

void VolumeAndPanPlugin::refreshVCATrack()
{
const juce::ScopedLock sl (vcaTrackLock);
vcaTrack = ignoreVca ? nullptr : dynamic_cast<AudioTrack*> (getOwnerTrack());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class VolumeAndPanPlugin : public Plugin
private:
float lastGainL = 0.0f, lastGainR = 0.0f, lastGainS = 0.0f, lastVolumeBeforeMute = 0.0f;

juce::CriticalSection vcaTrackLock;
juce::ReferenceCountedObjectPtr<AudioTrack> vcaTrack;
const bool isMasterVolume = false;

Expand Down
19 changes: 16 additions & 3 deletions modules/tracktion_engine/plugins/tracktion_Plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ void Plugin::reset()
//==============================================================================
void Plugin::baseClassInitialise (const PluginInitialisationInfo& info)
{
TRACKTION_ASSERT_MESSAGE_THREAD
const bool sampleRateOrBlockSizeChanged = (sampleRate != info.sampleRate) || (blockSizeSamples != info.blockSizeSamples);
bool isUpdatingWithoutStopping = false;
sampleRate = info.sampleRate;
Expand All @@ -459,20 +460,29 @@ void Plugin::baseClassInitialise (const PluginInitialisationInfo& info)
}

{
auto& dm = engine.getDeviceManager();
const juce::ScopedLock sl (dm.deviceManager.getAudioCallbackLock());

if (initialiseCount++ == 0 || sampleRateOrBlockSizeChanged)
{
CRASH_TRACER
#if JUCE_DEBUG
isInitialisingFlag = true;
#endif

initialise (info);

#if JUCE_DEBUG
isInitialisingFlag = true;
#endif
}
else
{
CRASH_TRACER
initialiseWithoutStopping (info);
isUpdatingWithoutStopping = true;
}

#if JUCE_DEBUG
isInitialisingFlag = false;
#endif
}

{
Expand Down Expand Up @@ -658,6 +668,9 @@ void Plugin::applyToBufferWithAutomation (const PluginRenderContext& pc)

auto& arm = edit.getAutomationRecordManager();
jassert (initialiseCount > 0);
#if JUCE_DEBUG
jassert (! isInitialisingFlag);
#endif

updateLastPlaybackTime();

Expand Down
11 changes: 10 additions & 1 deletion modules/tracktion_engine/plugins/tracktion_Plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,18 +162,23 @@ class Plugin : public Selectable,

//==============================================================================
/** Gives the plugin a chance to set itself up before being played.
This won't be called concurrently with the process thread.
The sample rate and the average block size - although the blocks
won't always be the same, and may be bigger.
Don't call this directly or the initialise count will become out of sync.
@see baseClassInitialise
[[ message_thread ]]
*/
virtual void initialise (const PluginInitialisationInfo&) = 0;

/** Tells the plugin that the audio graph has changed but the plugin isn't being
re-initialised - i.e. it's being re-used, maybe by being moved to a different
track, etc.
This can be called concurrently whilst the plugin is being processed so
implementations of it must be thread safe.
[[ message_thread ]]
*/
virtual void initialiseWithoutStopping (const PluginInitialisationInfo&) {}

Expand Down Expand Up @@ -410,7 +415,7 @@ class Plugin : public Selectable,
private:
mutable AutomatableParameter::Ptr quickControlParameter;

int initialiseCount = 0;
std::atomic<int> initialiseCount { 0 };
double timeToCpuScale = 0;
std::atomic<double> cpuUsageMs { 0 };
std::atomic<bool> isClipEffect { false };
Expand All @@ -419,6 +424,10 @@ class Plugin : public Selectable,
struct WireList;
std::unique_ptr<WireList> sidechainWireList;

#if JUCE_DEBUG
std::atomic<bool> isInitialisingFlag { false };
#endif

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Plugin)
};

Expand Down

0 comments on commit d90724f

Please sign in to comment.