From 5297df999598b59de3fbff5cbde80fea20877ed9 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 31 Mar 2025 12:39:49 +0100 Subject: [PATCH] AudioProcessorGraph: Remove unnecessary precision conversion buffer The double-precision buffer is unnecessary because internal nodes should be prepared using the same precision as the graph itself, and all AudioProcessors *must* support single-precision processing. Therefore, if the graph is prepared to use single-precision, then all inner nodes *must* also use single-precision. This change also shares the remaining temporary buffer. --- .../processors/juce_AudioProcessorGraph.cpp | 118 ++++++++++++++++-- 1 file changed, 105 insertions(+), 13 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 0a353903ad..73203bbcd2 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -788,7 +788,11 @@ struct GraphRenderSequence } } - return std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer); + return std::make_unique (node, + audioChannelsUsed, + totalNumChans, + midiBuffer, + *precisionConversionBuffer); }(); renderOps.push_back (std::move (op)); @@ -801,6 +805,8 @@ struct GraphRenderSequence currentAudioOutputBuffer.setSize (numBuffersNeeded + 1, blockSize); currentAudioOutputBuffer.clear(); + precisionConversionBuffer->setSize (numBuffersNeeded, blockSize); + currentMidiOutputBuffer.clear(); midiBuffers.clearQuick(); @@ -898,7 +904,14 @@ private: struct ProcessOp final : public NodeOp { - using NodeOp::NodeOp; + ProcessOp (const Node::Ptr& n, + const Array& audioChannelsUsed, + int totalNumChans, + int midiBufferIndex, + AudioBuffer& tempBuffer) + : NodeOp (n, audioChannelsUsed, totalNumChans, midiBufferIndex), + temporaryBuffer (tempBuffer) + {} void processWithBuffer (const GlobalIO&, bool bypass, AudioBuffer& audio, MidiBuffer& midi) final { @@ -909,9 +922,14 @@ private: { if (this->processor.isUsingDoublePrecision()) { - tempBufferDouble.makeCopyOf (buffer, true); - processImpl (bypass, this->processor, tempBufferDouble, midi); - buffer.makeCopyOf (tempBufferDouble, true); + // The graph is processing in single-precision, but this node is expecting a + // double-precision buffer. If the graph is using single-precision, it + // should also have set its internal nodes to use single-precision + // during prepareToPlay(). You should avoid calling setProcessingPrecision() + // directly on processors within an AudioProcessorGraph. + jassertfalse; + buffer.clear(); + midi.clear(); } else { @@ -927,9 +945,11 @@ private: } else { - tempBufferFloat.makeCopyOf (buffer, true); - processImpl (bypass, this->processor, tempBufferFloat, midi); - buffer.makeCopyOf (tempBufferFloat, true); + // This branch will be taken if the graph is configured for double-precision but + // this node only supports single-precision. + temporaryBuffer.makeCopyOf (buffer, true); + processImpl (bypass, this->processor, temporaryBuffer, midi); + buffer.makeCopyOf (temporaryBuffer, true); } } @@ -942,7 +962,7 @@ private: p.processBlock (audio, midi); } - AudioBuffer tempBufferFloat, tempBufferDouble; + AudioBuffer& temporaryBuffer; }; struct MidiInOp final : public NodeOp @@ -996,6 +1016,8 @@ private: }; std::vector> renderOps; + + std::unique_ptr> precisionConversionBuffer = std::make_unique>(); }; //============================================================================== @@ -2246,6 +2268,63 @@ public: expect (graph.getLatencySamples() == nodeALatency + nodeBLatency + finalLatency); } + beginTest ("nodes use double precision if supported"); + { + AudioProcessorGraph graph; + constexpr auto blockSize = 512; + AudioBuffer bufferFloat (2, blockSize); + AudioBuffer bufferDouble (2, blockSize); + MidiBuffer midi; + + auto processorOwner = BasicProcessor::make (BasicProcessor::getStereoProperties(), MidiIn::no, MidiOut::no); + auto* processor = processorOwner.get(); + graph.addNode (std::move (processorOwner)); + + // Process in single-precision + { + graph.setProcessingPrecision (AudioProcessor::singlePrecision); + graph.prepareToPlay (44100.0, blockSize); + + graph.processBlock (bufferFloat, midi); + expect (processor->getProcessingPrecision() == AudioProcessor::singlePrecision); + expect (processor->getLastBlockPrecision() == AudioProcessor::singlePrecision); + + graph.releaseResources(); + } + + // Process in double-precision + { + graph.setProcessingPrecision (AudioProcessor::doublePrecision); + graph.prepareToPlay (44100.0, blockSize); + + graph.processBlock (bufferDouble, midi); + expect (processor->getProcessingPrecision() == AudioProcessor::doublePrecision); + expect (processor->getLastBlockPrecision() == AudioProcessor::doublePrecision); + + graph.releaseResources(); + } + + // Process in double-precision when node only supports single-precision + { + processor->setSupportsDoublePrecisionProcessing (false); + + graph.setProcessingPrecision (AudioProcessor::doublePrecision); + graph.prepareToPlay (44100.0, blockSize); + + graph.processBlock (bufferDouble, midi); + expect (processor->getProcessingPrecision() == AudioProcessor::singlePrecision); + expect (processor->getLastBlockPrecision() == AudioProcessor::singlePrecision); + + graph.releaseResources(); + } + + // It's not possible for the node to *only* support double-precision. + // It's also not possible to prepare the graph in single-precision mode, and then + // to set an individual node into double-precision mode. This would require calling + // prepareToPlay() on an individual node after preparing the graph as a whole, which is + // not a supported usage pattern. + } + beginTest ("large render sequence can be built"); { AudioProcessorGraph graph; @@ -2302,15 +2381,22 @@ private: void setStateInformation (const void*, int) override {} void prepareToPlay (double, int) override {} void releaseResources() override {} - void processBlock (AudioBuffer&, MidiBuffer&) override {} - bool supportsDoublePrecisionProcessing() const override { return true; } + bool supportsDoublePrecisionProcessing() const override { return doublePrecisionSupported; } bool isMidiEffect() const override { return {}; } void reset() override {} void setNonRealtime (bool) noexcept override {} - using AudioProcessor::processBlock; + void processBlock (AudioBuffer&, MidiBuffer&) override + { + blockPrecision = singlePrecision; + } - static std::unique_ptr make (const BusesProperties& layout, + void processBlock (AudioBuffer&, MidiBuffer&) override + { + blockPrecision = doublePrecision; + } + + static std::unique_ptr make (const BusesProperties& layout, MidiIn midiIn, MidiOut midiOut) { @@ -2334,9 +2420,15 @@ private: .withOutput ("out", AudioChannelSet::discreteChannels (numChannels)); } + void setSupportsDoublePrecisionProcessing (bool x) { doublePrecisionSupported = x; } + + ProcessingPrecision getLastBlockPrecision() const { return blockPrecision; } + private: MidiIn midiIn; MidiOut midiOut; + ProcessingPrecision blockPrecision = ProcessingPrecision (-1); // initially invalid + bool doublePrecisionSupported = true; }; };