Skip to content

Commit a38fd6b

Browse files
committed
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.
1 parent a9a99a0 commit a38fd6b

File tree

1 file changed

+95
-13
lines changed

1 file changed

+95
-13
lines changed

modules/juce_audio_processors_headless/processors/juce_AudioProcessorGraph.cpp

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,24 +1851,18 @@ class AudioProcessorGraph::Pimpl
18511851

18521852
nodeStates.setState (settings);
18531853

1854-
topologyChanged (UpdateKind::sync);
1854+
topologyChanged (RebuildKind::immediate);
18551855
}
18561856

18571857
void releaseResources()
18581858
{
18591859
nodeStates.setState (nullopt);
1860-
topologyChanged (UpdateKind::sync);
1860+
topologyChanged (RebuildKind::immediate);
18611861
}
18621862

18631863
void rebuild (UpdateKind updateKind)
18641864
{
1865-
if (updateKind == UpdateKind::none)
1866-
return;
1867-
1868-
if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
1869-
handleAsyncUpdate();
1870-
else
1871-
updater.triggerAsyncUpdate();
1865+
rebuild (getRebuildKind (updateKind));
18721866
}
18731867

18741868
void reset()
@@ -1918,16 +1912,67 @@ class AudioProcessorGraph::Pimpl
19181912
auto* getAudioThreadState() const { return renderSequenceExchange.getAudioThreadState(); }
19191913

19201914
private:
1915+
enum class RebuildKind
1916+
{
1917+
none, // no rebuild
1918+
async, // always async on main thread
1919+
syncIfMainThread, // sync if the rebuild request is on the main thread, async otherwise
1920+
immediate, // synchronous regardless of the thread making the rebuild request
1921+
};
1922+
1923+
static RebuildKind getRebuildKind (UpdateKind kind)
1924+
{
1925+
switch (kind)
1926+
{
1927+
case UpdateKind::async:
1928+
return RebuildKind::async;
1929+
1930+
case UpdateKind::sync:
1931+
return RebuildKind::syncIfMainThread;
1932+
1933+
case UpdateKind::none:
1934+
return RebuildKind::none;
1935+
}
1936+
1937+
jassertfalse;
1938+
return RebuildKind::syncIfMainThread;
1939+
}
1940+
19211941
void setParentGraph (AudioProcessor* p) const
19221942
{
19231943
if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (p))
19241944
ioProc->setParentGraph (owner);
19251945
}
19261946

1927-
void topologyChanged (UpdateKind updateKind)
1947+
void topologyChanged (UpdateKind kind)
1948+
{
1949+
topologyChanged (getRebuildKind (kind));
1950+
}
1951+
1952+
void topologyChanged (RebuildKind kind)
19281953
{
19291954
owner->sendChangeMessage();
1930-
rebuild (updateKind);
1955+
rebuild (kind);
1956+
}
1957+
1958+
void rebuild (RebuildKind kind)
1959+
{
1960+
if (kind == RebuildKind::none)
1961+
return;
1962+
1963+
const auto immediate = kind == RebuildKind::immediate
1964+
|| (kind == RebuildKind::syncIfMainThread
1965+
&& MessageManager::getInstance()->isThisTheMessageThread());
1966+
1967+
if (immediate)
1968+
{
1969+
updater.cancelPendingUpdate();
1970+
handleAsyncUpdate();
1971+
}
1972+
else
1973+
{
1974+
updater.triggerAsyncUpdate();
1975+
}
19311976
}
19321977

19331978
void handleAsyncUpdate()
@@ -2399,6 +2444,40 @@ class AudioProcessorGraphTests final : public UnitTest
23992444
}
24002445
}
24012446

2447+
beginTest ("graph can be prepared and unprepared from a background thread");
2448+
{
2449+
using UK = AudioProcessorGraph::UpdateKind;
2450+
AudioProcessorGraph graph;
2451+
auto nodeA = BasicProcessor::make ({}, MidiIn::no, MidiOut::yes);
2452+
auto nodeB = BasicProcessor::make ({}, MidiIn::yes, MidiOut::no);
2453+
2454+
auto* ptrA = nodeA.get();
2455+
auto* ptrB = nodeB.get();
2456+
2457+
const auto idA = graph.addNode (std::move (nodeA), {}, UK::none)->nodeID;
2458+
const auto idB = graph.addNode (std::move (nodeB), {}, UK::none)->nodeID;
2459+
expect (graph.addConnection ({ { idA, midiChannel }, { idB, midiChannel } }, UK::none));
2460+
2461+
expect (! ptrA->isPrepared());
2462+
expect (! ptrB->isPrepared());
2463+
2464+
std::ignore = std::async (std::launch::async, [&]
2465+
{
2466+
expect (! ptrA->isPrepared());
2467+
expect (! ptrB->isPrepared());
2468+
2469+
graph.prepareToPlay (44100, 512);
2470+
2471+
expect (ptrA->isPrepared());
2472+
expect (ptrB->isPrepared());
2473+
2474+
graph.releaseResources();
2475+
2476+
expect (! ptrA->isPrepared());
2477+
expect (! ptrB->isPrepared());
2478+
});
2479+
}
2480+
24022481
beginTest ("large render sequence can be built");
24032482
{
24042483
AudioProcessorGraph graph;
@@ -2453,8 +2532,8 @@ class AudioProcessorGraphTests final : public UnitTest
24532532
void changeProgramName (int, const String&) override {}
24542533
void getStateInformation (MemoryBlock&) override {}
24552534
void setStateInformation (const void*, int) override {}
2456-
void prepareToPlay (double, int) override {}
2457-
void releaseResources() override {}
2535+
void prepareToPlay (double, int) override { prepared = true; }
2536+
void releaseResources() override { prepared = false; }
24582537
bool supportsDoublePrecisionProcessing() const override { return doublePrecisionSupported; }
24592538
bool isMidiEffect() const override { return {}; }
24602539
void reset() override {}
@@ -2510,11 +2589,14 @@ class AudioProcessorGraphTests final : public UnitTest
25102589

25112590
ProcessingPrecision getLastBlockPrecision() const { return blockPrecision; }
25122591

2592+
bool isPrepared() const { return prepared; }
2593+
25132594
private:
25142595
MidiIn midiIn;
25152596
MidiOut midiOut;
25162597
ProcessingPrecision blockPrecision = ProcessingPrecision (-1); // initially invalid
25172598
bool doublePrecisionSupported = true;
2599+
bool prepared = false;
25182600
};
25192601
};
25202602

0 commit comments

Comments
 (0)