mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
AudioProcessorGraph: Make prepareToPlay and releaseResources truly synchronous
Previously, these functions would only do a synchronous rebuild of the graph when called from the main thread. However, in scenarios like offline rendering, the graph *must* be ready to process after prepareToPlay() returns, even if the prepare call is made on a background thread.
This commit is contained in:
parent
a9a99a0a08
commit
a38fd6b477
1 changed files with 95 additions and 13 deletions
|
|
@ -1851,24 +1851,18 @@ public:
|
|||
|
||||
nodeStates.setState (settings);
|
||||
|
||||
topologyChanged (UpdateKind::sync);
|
||||
topologyChanged (RebuildKind::immediate);
|
||||
}
|
||||
|
||||
void releaseResources()
|
||||
{
|
||||
nodeStates.setState (nullopt);
|
||||
topologyChanged (UpdateKind::sync);
|
||||
topologyChanged (RebuildKind::immediate);
|
||||
}
|
||||
|
||||
void rebuild (UpdateKind updateKind)
|
||||
{
|
||||
if (updateKind == UpdateKind::none)
|
||||
return;
|
||||
|
||||
if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
|
||||
handleAsyncUpdate();
|
||||
else
|
||||
updater.triggerAsyncUpdate();
|
||||
rebuild (getRebuildKind (updateKind));
|
||||
}
|
||||
|
||||
void reset()
|
||||
|
|
@ -1918,16 +1912,67 @@ public:
|
|||
auto* getAudioThreadState() const { return renderSequenceExchange.getAudioThreadState(); }
|
||||
|
||||
private:
|
||||
enum class RebuildKind
|
||||
{
|
||||
none, // no rebuild
|
||||
async, // always async on main thread
|
||||
syncIfMainThread, // sync if the rebuild request is on the main thread, async otherwise
|
||||
immediate, // synchronous regardless of the thread making the rebuild request
|
||||
};
|
||||
|
||||
static RebuildKind getRebuildKind (UpdateKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case UpdateKind::async:
|
||||
return RebuildKind::async;
|
||||
|
||||
case UpdateKind::sync:
|
||||
return RebuildKind::syncIfMainThread;
|
||||
|
||||
case UpdateKind::none:
|
||||
return RebuildKind::none;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return RebuildKind::syncIfMainThread;
|
||||
}
|
||||
|
||||
void setParentGraph (AudioProcessor* p) const
|
||||
{
|
||||
if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (p))
|
||||
ioProc->setParentGraph (owner);
|
||||
}
|
||||
|
||||
void topologyChanged (UpdateKind updateKind)
|
||||
void topologyChanged (UpdateKind kind)
|
||||
{
|
||||
topologyChanged (getRebuildKind (kind));
|
||||
}
|
||||
|
||||
void topologyChanged (RebuildKind kind)
|
||||
{
|
||||
owner->sendChangeMessage();
|
||||
rebuild (updateKind);
|
||||
rebuild (kind);
|
||||
}
|
||||
|
||||
void rebuild (RebuildKind kind)
|
||||
{
|
||||
if (kind == RebuildKind::none)
|
||||
return;
|
||||
|
||||
const auto immediate = kind == RebuildKind::immediate
|
||||
|| (kind == RebuildKind::syncIfMainThread
|
||||
&& MessageManager::getInstance()->isThisTheMessageThread());
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
updater.cancelPendingUpdate();
|
||||
handleAsyncUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
updater.triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void handleAsyncUpdate()
|
||||
|
|
@ -2399,6 +2444,40 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
beginTest ("graph can be prepared and unprepared from a background thread");
|
||||
{
|
||||
using UK = AudioProcessorGraph::UpdateKind;
|
||||
AudioProcessorGraph graph;
|
||||
auto nodeA = BasicProcessor::make ({}, MidiIn::no, MidiOut::yes);
|
||||
auto nodeB = BasicProcessor::make ({}, MidiIn::yes, MidiOut::no);
|
||||
|
||||
auto* ptrA = nodeA.get();
|
||||
auto* ptrB = nodeB.get();
|
||||
|
||||
const auto idA = graph.addNode (std::move (nodeA), {}, UK::none)->nodeID;
|
||||
const auto idB = graph.addNode (std::move (nodeB), {}, UK::none)->nodeID;
|
||||
expect (graph.addConnection ({ { idA, midiChannel }, { idB, midiChannel } }, UK::none));
|
||||
|
||||
expect (! ptrA->isPrepared());
|
||||
expect (! ptrB->isPrepared());
|
||||
|
||||
std::ignore = std::async (std::launch::async, [&]
|
||||
{
|
||||
expect (! ptrA->isPrepared());
|
||||
expect (! ptrB->isPrepared());
|
||||
|
||||
graph.prepareToPlay (44100, 512);
|
||||
|
||||
expect (ptrA->isPrepared());
|
||||
expect (ptrB->isPrepared());
|
||||
|
||||
graph.releaseResources();
|
||||
|
||||
expect (! ptrA->isPrepared());
|
||||
expect (! ptrB->isPrepared());
|
||||
});
|
||||
}
|
||||
|
||||
beginTest ("large render sequence can be built");
|
||||
{
|
||||
AudioProcessorGraph graph;
|
||||
|
|
@ -2453,8 +2532,8 @@ private:
|
|||
void changeProgramName (int, const String&) override {}
|
||||
void getStateInformation (MemoryBlock&) override {}
|
||||
void setStateInformation (const void*, int) override {}
|
||||
void prepareToPlay (double, int) override {}
|
||||
void releaseResources() override {}
|
||||
void prepareToPlay (double, int) override { prepared = true; }
|
||||
void releaseResources() override { prepared = false; }
|
||||
bool supportsDoublePrecisionProcessing() const override { return doublePrecisionSupported; }
|
||||
bool isMidiEffect() const override { return {}; }
|
||||
void reset() override {}
|
||||
|
|
@ -2510,11 +2589,14 @@ private:
|
|||
|
||||
ProcessingPrecision getLastBlockPrecision() const { return blockPrecision; }
|
||||
|
||||
bool isPrepared() const { return prepared; }
|
||||
|
||||
private:
|
||||
MidiIn midiIn;
|
||||
MidiOut midiOut;
|
||||
ProcessingPrecision blockPrecision = ProcessingPrecision (-1); // initially invalid
|
||||
bool doublePrecisionSupported = true;
|
||||
bool prepared = false;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue