From c562cfc3cc3595b88417bbada41412dfeaf3dfd5 Mon Sep 17 00:00:00 2001 From: jules Date: Mon, 2 Nov 2015 11:09:41 +0000 Subject: [PATCH] Converted AudioSampleBuffer into a templated class that can use either float or double types. Used this to implement 64-bit audio plugin support in VST and AU --- .../Source/PluginProcessor.cpp | 55 +- .../Source/PluginProcessor.h | 23 +- .../Source/GraphEditorPanel.cpp | 3 +- .../Source/GraphEditorPanel.h | 1 + .../Source/MainHostWindow.cpp | 37 + .../audio plugin host/Source/MainHostWindow.h | 4 + .../buffers/juce_AudioSampleBuffer.cpp | 673 ---------------- .../buffers/juce_AudioSampleBuffer.h | 727 +++++++++++++++--- .../juce_audio_basics/juce_audio_basics.cpp | 1 - modules/juce_audio_basics/juce_audio_basics.h | 2 +- .../synthesisers/juce_Synthesiser.cpp | 43 +- .../synthesisers/juce_Synthesiser.h | 28 +- .../native/juce_mac_CoreAudio.cpp | 4 +- .../codecs/juce_CoreAudioFormat.cpp | 2 +- .../AU/juce_AU_Wrapper.mm | 6 +- .../VST/juce_VST_Wrapper.cpp | 114 ++- .../VST3/juce_VST3_Wrapper.cpp | 178 +++-- .../juce_AudioUnitPluginFormat.mm | 2 +- .../format_types/juce_VST3Common.h | 62 +- .../format_types/juce_VST3PluginFormat.cpp | 78 +- .../format_types/juce_VSTPluginFormat.cpp | 249 +++--- .../processors/juce_AudioProcessor.cpp | 31 +- .../processors/juce_AudioProcessor.h | 115 ++- .../processors/juce_AudioProcessorGraph.cpp | 289 +++++-- .../processors/juce_AudioProcessorGraph.h | 25 +- .../players/juce_AudioProcessorPlayer.cpp | 43 +- .../players/juce_AudioProcessorPlayer.h | 22 +- 27 files changed, 1713 insertions(+), 1104 deletions(-) delete mode 100644 modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.cpp diff --git a/examples/audio plugin demo/Source/PluginProcessor.cpp b/examples/audio plugin demo/Source/PluginProcessor.cpp index fc73ae8b84..230038ab05 100644 --- a/examples/audio plugin demo/Source/PluginProcessor.cpp +++ b/examples/audio plugin demo/Source/PluginProcessor.cpp @@ -85,7 +85,20 @@ public: // not interested in controllers in this case. } - void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override + void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override + { + processBlock (outputBuffer, startSample, numSamples); + } + + void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override + { + processBlock (outputBuffer, startSample, numSamples); + } + +private: + + template + void processBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) { if (angleDelta != 0.0) { @@ -93,7 +106,8 @@ public: { while (--numSamples >= 0) { - const float currentSample = (float) (sin (currentAngle) * level * tailOff); + const FloatType currentSample = + static_cast (std::sin (currentAngle) * level * tailOff); for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); @@ -116,7 +130,7 @@ public: { while (--numSamples >= 0) { - const float currentSample = (float) (sin (currentAngle) * level); + const FloatType currentSample = static_cast (std::sin (currentAngle) * level); for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); @@ -128,7 +142,6 @@ public: } } -private: double currentAngle, angleDelta, level, tailOff; }; @@ -183,7 +196,6 @@ const float defaultDelay = 0.5f; //============================================================================== JuceDemoPluginAudioProcessor::JuceDemoPluginAudioProcessor() - : delayBuffer (2, 12000) { // Set up our parameters. The base class will delete them for us. addParameter (gain = new FloatParameter (defaultGain, "Gain")); @@ -213,7 +225,19 @@ void JuceDemoPluginAudioProcessor::prepareToPlay (double newSampleRate, int /*sa // initialisation that you need.. synth.setCurrentPlaybackSampleRate (newSampleRate); keyboardState.reset(); - delayBuffer.clear(); + + if (isUsingDoublePrecision()) + { + delayBufferDouble.setSize (2, 12000); + delayBufferFloat.setSize (1, 1); + } + else + { + delayBufferFloat.setSize (2, 12000); + delayBufferDouble.setSize (1, 1); + } + + reset(); } void JuceDemoPluginAudioProcessor::releaseResources() @@ -227,17 +251,21 @@ void JuceDemoPluginAudioProcessor::reset() { // Use this method as the place to clear any delay lines, buffers, etc, as it // means there's been a break in the audio's continuity. - delayBuffer.clear(); + delayBufferFloat.clear(); + delayBufferDouble.clear(); } -void JuceDemoPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) +template +void JuceDemoPluginAudioProcessor::process (AudioBuffer& buffer, + MidiBuffer& midiMessages, + AudioBuffer& delayBuffer) { const int numSamples = buffer.getNumSamples(); int channel, dp = 0; // Go through the incoming data, and apply our gain to it... for (channel = 0; channel < getNumInputChannels(); ++channel) - buffer.applyGain (channel, 0, buffer.getNumSamples(), gain->getValue()); + buffer.applyGain (channel, 0, buffer.getNumSamples(), static_cast (gain->getValue())); // Now pass any incoming midi messages to our keyboard state object, and let it // add messages to the buffer if the user is clicking on the on-screen keys @@ -249,15 +277,16 @@ void JuceDemoPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, Midi // Apply our delay effect to the new output.. for (channel = 0; channel < getNumInputChannels(); ++channel) { - float* channelData = buffer.getWritePointer (channel); - float* delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1)); + FloatType* channelData = buffer.getWritePointer (channel); + FloatType* delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1)); dp = delayPosition; for (int i = 0; i < numSamples; ++i) { - const float in = channelData[i]; + const FloatType in = channelData[i]; channelData[i] += delayData[dp]; - delayData[dp] = (delayData[dp] + in) * delay->getValue(); + delayData[dp] = (delayData[dp] + in) * static_cast (delay->getValue()); + if (++dp >= delayBuffer.getNumSamples()) dp = 0; } diff --git a/examples/audio plugin demo/Source/PluginProcessor.h b/examples/audio plugin demo/Source/PluginProcessor.h index ce9d47d5fb..1b43bc9c0d 100644 --- a/examples/audio plugin demo/Source/PluginProcessor.h +++ b/examples/audio plugin demo/Source/PluginProcessor.h @@ -28,13 +28,28 @@ public: //============================================================================== void prepareToPlay (double sampleRate, int samplesPerBlock) override; void releaseResources() override; - void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override; void reset() override; + //============================================================================== + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (! isUsingDoublePrecision()); + process (buffer, midiMessages, delayBufferFloat); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + process (buffer, midiMessages, delayBufferDouble); + } + //============================================================================== bool hasEditor() const override { return true; } AudioProcessorEditor* createEditor() override; + //============================================================================== + bool supportsDoublePrecisionProcessing() const override { return true; } + //============================================================================== const String getName() const override { return JucePlugin_Name; } @@ -83,7 +98,11 @@ public: private: //============================================================================== - AudioSampleBuffer delayBuffer; + template + void process (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioBuffer& delayBuffer); + + AudioBuffer delayBufferFloat; + AudioBuffer delayBufferDouble; int delayPosition; // the synth! diff --git a/examples/audio plugin host/Source/GraphEditorPanel.cpp b/examples/audio plugin host/Source/GraphEditorPanel.cpp index e37919a151..7b1ef8f681 100644 --- a/examples/audio plugin host/Source/GraphEditorPanel.cpp +++ b/examples/audio plugin host/Source/GraphEditorPanel.cpp @@ -1087,7 +1087,8 @@ private: //============================================================================== GraphDocumentComponent::GraphDocumentComponent (AudioPluginFormatManager& formatManager, AudioDeviceManager* deviceManager_) - : graph (formatManager), deviceManager (deviceManager_) + : graph (formatManager), deviceManager (deviceManager_), + graphPlayer (getAppProperties().getUserSettings()->getBoolValue ("doublePrecisionProcessing", false)) { addAndMakeVisible (graphPanel = new GraphEditorPanel (graph)); diff --git a/examples/audio plugin host/Source/GraphEditorPanel.h b/examples/audio plugin host/Source/GraphEditorPanel.h index 2f18bf4754..dde5654e2b 100644 --- a/examples/audio plugin host/Source/GraphEditorPanel.h +++ b/examples/audio plugin host/Source/GraphEditorPanel.h @@ -88,6 +88,7 @@ public: //============================================================================== void createNewPlugin (const PluginDescription* desc, int x, int y); + inline void setDoublePrecision (bool doublePrecision) { graphPlayer.setDoublePrecisionProcessing (doublePrecision); } //============================================================================== FilterGraph graph; diff --git a/examples/audio plugin host/Source/MainHostWindow.cpp b/examples/audio plugin host/Source/MainHostWindow.cpp index 774db9ec75..1d992b68c7 100644 --- a/examples/audio plugin host/Source/MainHostWindow.cpp +++ b/examples/audio plugin host/Source/MainHostWindow.cpp @@ -240,6 +240,7 @@ PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& menu.addSeparator(); menu.addCommandItem (&getCommandManager(), CommandIDs::showAudioSettings); + menu.addCommandItem (&getCommandManager(), CommandIDs::toggleDoublePrecision); menu.addSeparator(); menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox); @@ -331,6 +332,7 @@ void MainHostWindow::getAllCommands (Array & commands) CommandIDs::saveAs, CommandIDs::showPluginListEditor, CommandIDs::showAudioSettings, + CommandIDs::toggleDoublePrecision, CommandIDs::aboutBox, CommandIDs::allWindowsForward }; @@ -376,6 +378,10 @@ void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationComma result.addDefaultKeypress ('a', ModifierKeys::commandModifier); break; + case CommandIDs::toggleDoublePrecision: + updatePrecisionMenuItem (result); + break; + case CommandIDs::aboutBox: result.setInfo ("About...", String::empty, category, 0); break; @@ -427,6 +433,23 @@ bool MainHostWindow::perform (const InvocationInfo& info) showAudioSettings(); break; + case CommandIDs::toggleDoublePrecision: + if (PropertiesFile* props = getAppProperties().getUserSettings()) + { + bool newIsDoublePrecision = ! isDoublePrecisionProcessing(); + props->setValue ("doublePrecisionProcessing", var (newIsDoublePrecision)); + + { + ApplicationCommandInfo cmdInfo (info.commandID); + updatePrecisionMenuItem (cmdInfo); + menuItemsChanged(); + } + + if (graphEditor != nullptr) + graphEditor->setDoublePrecision (newIsDoublePrecision); + } + break; + case CommandIDs::aboutBox: // TODO break; @@ -524,3 +547,17 @@ GraphDocumentComponent* MainHostWindow::getGraphEditor() const { return dynamic_cast (getContentComponent()); } + +bool MainHostWindow::isDoublePrecisionProcessing() +{ + if (PropertiesFile* props = getAppProperties().getUserSettings()) + return props->getBoolValue ("doublePrecisionProcessing", false); + + return false; +} + +void MainHostWindow::updatePrecisionMenuItem (ApplicationCommandInfo& info) +{ + info.setInfo ("Double floating point precision rendering", String::empty, "General", 0); + info.setTicked (isDoublePrecisionProcessing()); +} diff --git a/examples/audio plugin host/Source/MainHostWindow.h b/examples/audio plugin host/Source/MainHostWindow.h index f97df2cdaf..6c1d05e9ba 100644 --- a/examples/audio plugin host/Source/MainHostWindow.h +++ b/examples/audio plugin host/Source/MainHostWindow.h @@ -40,6 +40,7 @@ namespace CommandIDs static const int showAudioSettings = 0x30200; static const int aboutBox = 0x30300; static const int allWindowsForward = 0x30400; + static const int toggleDoublePrecision = 0x30500; } ApplicationCommandManager& getCommandManager(); @@ -86,6 +87,9 @@ public: GraphDocumentComponent* getGraphEditor() const; + bool isDoublePrecisionProcessing(); + void updatePrecisionMenuItem (ApplicationCommandInfo& info); + private: //============================================================================== AudioDeviceManager deviceManager; diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.cpp b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.cpp deleted file mode 100644 index e9fd6e0024..0000000000 --- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.cpp +++ /dev/null @@ -1,673 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2015 - ROLI Ltd. - - Permission is granted to use this software under the terms of either: - a) the GPL v2 (or any later version) - b) the Affero GPL v3 - - Details of these licenses can be found at: www.gnu.org/licenses - - JUCE is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - ------------------------------------------------------------------------------ - - To release a closed-source product which uses JUCE, commercial licenses are - available: visit www.juce.com for more information. - - ============================================================================== -*/ - -AudioSampleBuffer::AudioSampleBuffer() noexcept - : numChannels (0), size (0), allocatedBytes (0), - channels (static_cast (preallocatedChannelSpace)), - isClear (false) -{ -} - -AudioSampleBuffer::AudioSampleBuffer (const int numChans, - const int numSamples) noexcept - : numChannels (numChans), - size (numSamples) -{ - jassert (numSamples >= 0); - jassert (numChans >= 0); - - allocateData(); -} - -AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept - : numChannels (other.numChannels), - size (other.size), - allocatedBytes (other.allocatedBytes) -{ - if (allocatedBytes == 0) - { - allocateChannels (other.channels, 0); - } - else - { - allocateData(); - - if (other.isClear) - { - clear(); - } - else - { - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::copy (channels[i], other.channels[i], size); - } - } -} - -void AudioSampleBuffer::allocateData() -{ - const size_t channelListSize = sizeof (float*) * (size_t) (numChannels + 1); - allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (float) + channelListSize + 32; - allocatedData.malloc (allocatedBytes); - channels = reinterpret_cast (allocatedData.getData()); - - float* chan = (float*) (allocatedData + channelListSize); - for (int i = 0; i < numChannels; ++i) - { - channels[i] = chan; - chan += size; - } - - channels [numChannels] = nullptr; - isClear = false; -} - -AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, - const int numChans, - const int numSamples) noexcept - : numChannels (numChans), - size (numSamples), - allocatedBytes (0) -{ - jassert (dataToReferTo != nullptr); - jassert (numChans >= 0 && numSamples >= 0); - allocateChannels (dataToReferTo, 0); -} - -AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, - const int numChans, - const int startSample, - const int numSamples) noexcept - : numChannels (numChans), - size (numSamples), - allocatedBytes (0), - isClear (false) -{ - jassert (dataToReferTo != nullptr); - jassert (numChans >= 0 && startSample >= 0 && numSamples >= 0); - allocateChannels (dataToReferTo, startSample); -} - -void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, - const int newNumChannels, - const int newNumSamples) noexcept -{ - jassert (dataToReferTo != nullptr); - jassert (newNumChannels >= 0 && newNumSamples >= 0); - - if (allocatedBytes != 0) - { - allocatedBytes = 0; - allocatedData.free(); - } - - numChannels = newNumChannels; - size = newNumSamples; - - allocateChannels (dataToReferTo, 0); - jassert (! isClear); -} - -void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset) -{ - jassert (offset >= 0); - - // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) - if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) - { - channels = static_cast (preallocatedChannelSpace); - } - else - { - allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*)); - channels = reinterpret_cast (allocatedData.getData()); - } - - for (int i = 0; i < numChannels; ++i) - { - // you have to pass in the same number of valid pointers as numChannels - jassert (dataToReferTo[i] != nullptr); - - channels[i] = dataToReferTo[i] + offset; - } - - channels [numChannels] = nullptr; - isClear = false; -} - -AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept -{ - if (this != &other) - { - setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); - - if (other.isClear) - { - clear(); - } - else - { - isClear = false; - - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::copy (channels[i], other.channels[i], size); - } - } - - return *this; -} - -AudioSampleBuffer::~AudioSampleBuffer() noexcept -{ -} - -void AudioSampleBuffer::setSize (const int newNumChannels, - const int newNumSamples, - const bool keepExistingContent, - const bool clearExtraSpace, - const bool avoidReallocating) noexcept -{ - jassert (newNumChannels >= 0); - jassert (newNumSamples >= 0); - - if (newNumSamples != size || newNumChannels != numChannels) - { - const size_t allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; - const size_t channelListSize = ((sizeof (float*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; - const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (float)) - + channelListSize + 32; - - if (keepExistingContent) - { - HeapBlock newData; - newData.allocate (newTotalBytes, clearExtraSpace || isClear); - - const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size); - - float** const newChannels = reinterpret_cast (newData.getData()); - float* newChan = reinterpret_cast (newData + channelListSize); - - for (int j = 0; j < newNumChannels; ++j) - { - newChannels[j] = newChan; - newChan += allocatedSamplesPerChannel; - } - - if (! isClear) - { - const int numChansToCopy = jmin (numChannels, newNumChannels); - for (int i = 0; i < numChansToCopy; ++i) - FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); - } - - allocatedData.swapWith (newData); - allocatedBytes = newTotalBytes; - channels = newChannels; - } - else - { - if (avoidReallocating && allocatedBytes >= newTotalBytes) - { - if (clearExtraSpace || isClear) - allocatedData.clear (newTotalBytes); - } - else - { - allocatedBytes = newTotalBytes; - allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); - channels = reinterpret_cast (allocatedData.getData()); - } - - float* chan = reinterpret_cast (allocatedData + channelListSize); - for (int i = 0; i < newNumChannels; ++i) - { - channels[i] = chan; - chan += allocatedSamplesPerChannel; - } - } - - channels [newNumChannels] = 0; - size = newNumSamples; - numChannels = newNumChannels; - } -} - -void AudioSampleBuffer::clear() noexcept -{ - if (! isClear) - { - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::clear (channels[i], size); - - isClear = true; - } -} - -void AudioSampleBuffer::clear (const int startSample, - const int numSamples) noexcept -{ - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (! isClear) - { - if (startSample == 0 && numSamples == size) - isClear = true; - - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::clear (channels[i] + startSample, numSamples); - } -} - -void AudioSampleBuffer::clear (const int channel, - const int startSample, - const int numSamples) noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (! isClear) - FloatVectorOperations::clear (channels [channel] + startSample, numSamples); -} - -float AudioSampleBuffer::getSample (int channel, int index) const noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (isPositiveAndBelow (index, size)); - return *(channels [channel] + index); -} - -void AudioSampleBuffer::setSample (int channel, int index, float newValue) noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (isPositiveAndBelow (index, size)); - *(channels [channel] + index) = newValue; - isClear = false; -} - -void AudioSampleBuffer::addSample (int channel, int index, float valueToAdd) noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (isPositiveAndBelow (index, size)); - *(channels [channel] + index) += valueToAdd; - isClear = false; -} - -void AudioSampleBuffer::applyGain (const int channel, - const int startSample, - int numSamples, - const float gain) noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (gain != 1.0f && ! isClear) - { - float* const d = channels [channel] + startSample; - - if (gain == 0.0f) - FloatVectorOperations::clear (d, numSamples); - else - FloatVectorOperations::multiply (d, gain, numSamples); - } -} - -void AudioSampleBuffer::applyGainRamp (const int channel, - const int startSample, - int numSamples, - float startGain, - float endGain) noexcept -{ - if (! isClear) - { - if (startGain == endGain) - { - applyGain (channel, startSample, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - const float increment = (endGain - startGain) / numSamples; - float* d = channels [channel] + startSample; - - while (--numSamples >= 0) - { - *d++ *= startGain; - startGain += increment; - } - } - } -} - -void AudioSampleBuffer::applyGain (int startSample, int numSamples, float gain) noexcept -{ - for (int i = 0; i < numChannels; ++i) - applyGain (i, startSample, numSamples, gain); -} - -void AudioSampleBuffer::applyGain (const float gain) noexcept -{ - applyGain (0, size, gain); -} - -void AudioSampleBuffer::applyGainRamp (int startSample, int numSamples, - float startGain, float endGain) noexcept -{ - for (int i = 0; i < numChannels; ++i) - applyGainRamp (i, startSample, numSamples, startGain, endGain); -} - -void AudioSampleBuffer::addFrom (const int destChannel, - const int destStartSample, - const AudioSampleBuffer& source, - const int sourceChannel, - const int sourceStartSample, - int numSamples, - const float gain) noexcept -{ - jassert (&source != this || sourceChannel != destChannel); - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); - jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); - - if (gain != 0.0f && numSamples > 0 && ! source.isClear) - { - float* const d = channels [destChannel] + destStartSample; - const float* const s = source.channels [sourceChannel] + sourceStartSample; - - if (isClear) - { - isClear = false; - - if (gain != 1.0f) - FloatVectorOperations::copyWithMultiply (d, s, gain, numSamples); - else - FloatVectorOperations::copy (d, s, numSamples); - } - else - { - if (gain != 1.0f) - FloatVectorOperations::addWithMultiply (d, s, gain, numSamples); - else - FloatVectorOperations::add (d, s, numSamples); - } - } -} - -void AudioSampleBuffer::addFrom (const int destChannel, - const int destStartSample, - const float* source, - int numSamples, - const float gain) noexcept -{ - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (gain != 0.0f && numSamples > 0) - { - float* const d = channels [destChannel] + destStartSample; - - if (isClear) - { - isClear = false; - - if (gain != 1.0f) - FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); - else - FloatVectorOperations::copy (d, source, numSamples); - } - else - { - if (gain != 1.0f) - FloatVectorOperations::addWithMultiply (d, source, gain, numSamples); - else - FloatVectorOperations::add (d, source, numSamples); - } - } -} - -void AudioSampleBuffer::addFromWithRamp (const int destChannel, - const int destStartSample, - const float* source, - int numSamples, - float startGain, - const float endGain) noexcept -{ - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (startGain == endGain) - { - addFrom (destChannel, destStartSample, source, numSamples, startGain); - } - else - { - if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) - { - isClear = false; - const float increment = (endGain - startGain) / numSamples; - float* d = channels [destChannel] + destStartSample; - - while (--numSamples >= 0) - { - *d++ += startGain * *source++; - startGain += increment; - } - } - } -} - -void AudioSampleBuffer::copyFrom (const int destChannel, - const int destStartSample, - const AudioSampleBuffer& source, - const int sourceChannel, - const int sourceStartSample, - int numSamples) noexcept -{ - jassert (&source != this || sourceChannel != destChannel); - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); - jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); - - if (numSamples > 0) - { - if (source.isClear) - { - if (! isClear) - FloatVectorOperations::clear (channels [destChannel] + destStartSample, numSamples); - } - else - { - isClear = false; - FloatVectorOperations::copy (channels [destChannel] + destStartSample, - source.channels [sourceChannel] + sourceStartSample, - numSamples); - } - } -} - -void AudioSampleBuffer::copyFrom (const int destChannel, - const int destStartSample, - const float* source, - int numSamples) noexcept -{ - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (numSamples > 0) - { - isClear = false; - FloatVectorOperations::copy (channels [destChannel] + destStartSample, source, numSamples); - } -} - -void AudioSampleBuffer::copyFrom (const int destChannel, - const int destStartSample, - const float* source, - int numSamples, - const float gain) noexcept -{ - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (numSamples > 0) - { - float* const d = channels [destChannel] + destStartSample; - - if (gain != 1.0f) - { - if (gain == 0) - { - if (! isClear) - FloatVectorOperations::clear (d, numSamples); - } - else - { - isClear = false; - FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); - } - } - else - { - isClear = false; - FloatVectorOperations::copy (d, source, numSamples); - } - } -} - -void AudioSampleBuffer::copyFromWithRamp (const int destChannel, - const int destStartSample, - const float* source, - int numSamples, - float startGain, - float endGain) noexcept -{ - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (startGain == endGain) - { - copyFrom (destChannel, destStartSample, source, numSamples, startGain); - } - else - { - if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) - { - isClear = false; - const float increment = (endGain - startGain) / numSamples; - float* d = channels [destChannel] + destStartSample; - - while (--numSamples >= 0) - { - *d++ = startGain * *source++; - startGain += increment; - } - } - } -} - -void AudioSampleBuffer::reverse (int channel, int startSample, int numSamples) const noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (! isClear) - std::reverse (channels[channel] + startSample, - channels[channel] + startSample + numSamples); -} - -void AudioSampleBuffer::reverse (int startSample, int numSamples) const noexcept -{ - for (int i = 0; i < numChannels; ++i) - reverse (i, startSample, numSamples); -} - -Range AudioSampleBuffer::findMinMax (const int channel, - const int startSample, - int numSamples) const noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (isClear) - return Range(); - - return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); -} - -float AudioSampleBuffer::getMagnitude (const int channel, - const int startSample, - const int numSamples) const noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (isClear) - return 0.0f; - - const Range r (findMinMax (channel, startSample, numSamples)); - - return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); -} - -float AudioSampleBuffer::getMagnitude (int startSample, int numSamples) const noexcept -{ - float mag = 0.0f; - - if (! isClear) - for (int i = 0; i < numChannels; ++i) - mag = jmax (mag, getMagnitude (i, startSample, numSamples)); - - return mag; -} - -float AudioSampleBuffer::getRMSLevel (const int channel, - const int startSample, - const int numSamples) const noexcept -{ - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && startSample + numSamples <= size); - - if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) - return 0.0f; - - const float* const data = channels [channel] + startSample; - double sum = 0.0; - - for (int i = 0; i < numSamples; ++i) - { - const float sample = data [i]; - sum += sample * sample; - } - - return (float) std::sqrt (sum / numSamples); -} diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 12e2de807c..19e91bb889 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -28,15 +28,22 @@ //============================================================================== /** - A multi-channel buffer of 32-bit floating point audio samples. + A multi-channel buffer of floating point audio samples. + @see AudioSampleBuffer */ -class JUCE_API AudioSampleBuffer +template +class AudioBuffer { public: //============================================================================== /** Creates an empty buffer with 0 channels and 0 length. */ - AudioSampleBuffer() noexcept; + AudioBuffer() noexcept + : numChannels (0), size (0), allocatedBytes (0), + channels (static_cast (preallocatedChannelSpace)), + isClear (false) + { + } //============================================================================== /** Creates a buffer with a specified number of channels and samples. @@ -48,8 +55,16 @@ public: when the buffer is deleted. If the memory can't be allocated, this will throw a std::bad_alloc exception. */ - AudioSampleBuffer (int numChannels, - int numSamples) noexcept; + AudioBuffer (int numChannelsToAllocate, + int numSamplesToAllocate) noexcept + : numChannels (numChannelsToAllocate), + size (numSamplesToAllocate) + { + jassert (size >= 0); + jassert (numChannels >= 0); + + allocateData(); + } /** Creates a buffer using a pre-allocated block of memory. @@ -61,14 +76,22 @@ public: for each channel that should be used by this buffer. The buffer will only refer to this memory, it won't try to delete it when the buffer is deleted or resized. - @param numChannels the number of channels to use - this must correspond to the + @param numChannelsToUse the number of channels to use - this must correspond to the number of elements in the array passed in @param numSamples the number of samples to use - this must correspond to the size of the arrays passed in */ - AudioSampleBuffer (float* const* dataToReferTo, - int numChannels, - int numSamples) noexcept; + AudioBuffer (Type* const* dataToReferTo, + int numChannelsToUse, + int numSamples) noexcept + : numChannels (numChannelsToUse), + size (numSamples), + allocatedBytes (0) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, 0); + } /** Creates a buffer using a pre-allocated block of memory. @@ -80,16 +103,25 @@ public: for each channel that should be used by this buffer. The buffer will only refer to this memory, it won't try to delete it when the buffer is deleted or resized. - @param numChannels the number of channels to use - this must correspond to the + @param numChannelsToUse the number of channels to use - this must correspond to the number of elements in the array passed in @param startSample the offset within the arrays at which the data begins @param numSamples the number of samples to use - this must correspond to the size of the arrays passed in */ - AudioSampleBuffer (float* const* dataToReferTo, - int numChannels, - int startSample, - int numSamples) noexcept; + AudioBuffer (Type* const* dataToReferTo, + int numChannelsToUse, + int startSample, + int numSamples) noexcept + : numChannels (numChannelsToUse), + size (numSamples), + allocatedBytes (0), + isClear (false) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && startSample >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, startSample); + } /** Copies another buffer. @@ -97,28 +129,71 @@ public: using an external data buffer, in which case boths buffers will just point to the same shared block of data. */ - AudioSampleBuffer (const AudioSampleBuffer&) noexcept; + AudioBuffer (const AudioBuffer& other) noexcept + : numChannels (other.numChannels), + size (other.size), + allocatedBytes (other.allocatedBytes) + { + if (allocatedBytes == 0) + { + allocateChannels (other.channels, 0); + } + else + { + allocateData(); + + if (other.isClear) + { + clear(); + } + else + { + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::copy (channels[i], other.channels[i], size); + } + } + } /** Copies another buffer onto this one. This buffer's size will be changed to that of the other buffer. */ - AudioSampleBuffer& operator= (const AudioSampleBuffer&) noexcept; + AudioBuffer& operator= (const AudioBuffer& other) noexcept + { + if (this != &other) + { + setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); + + if (other.isClear) + { + clear(); + } + else + { + isClear = false; + + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::copy (channels[i], other.channels[i], size); + } + } + + return *this; + } /** Destructor. This will free any memory allocated by the buffer. */ - ~AudioSampleBuffer() noexcept; + ~AudioBuffer() noexcept {} //============================================================================== /** Returns the number of channels of audio data that this buffer contains. @see getSampleData */ - int getNumChannels() const noexcept { return numChannels; } + int getNumChannels() const noexcept { return numChannels; } /** Returns the number of samples allocated in each of the buffer's channels. @see getSampleData */ - int getNumSamples() const noexcept { return size; } + int getNumSamples() const noexcept { return size; } /** Returns a pointer to an array of read-only samples in one of the buffer's channels. For speed, this doesn't check whether the channel number is out of range, @@ -127,7 +202,7 @@ public: result! Instead, you must call getWritePointer so that the buffer knows you're planning on modifying the data. */ - const float* getReadPointer (int channelNumber) const noexcept + const Type* getReadPointer (int channelNumber) const noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); return channels [channelNumber]; @@ -140,7 +215,7 @@ public: result! Instead, you must call getWritePointer so that the buffer knows you're planning on modifying the data. */ - const float* getReadPointer (int channelNumber, int sampleIndex) const noexcept + const Type* getReadPointer (int channelNumber, int sampleIndex) const noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); jassert (isPositiveAndBelow (sampleIndex, size)); @@ -153,7 +228,7 @@ public: Note that if you're not planning on writing to the data, you should always use getReadPointer instead. */ - float* getWritePointer (int channelNumber) noexcept + Type* getWritePointer (int channelNumber) noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); isClear = false; @@ -166,7 +241,7 @@ public: Note that if you're not planning on writing to the data, you should use getReadPointer instead. */ - float* getWritePointer (int channelNumber, int sampleIndex) noexcept + Type* getWritePointer (int channelNumber, int sampleIndex) noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); jassert (isPositiveAndBelow (sampleIndex, size)); @@ -179,14 +254,14 @@ public: Don't modify any of the pointers that are returned, and bear in mind that these will become invalid if the buffer is resized. */ - const float** getArrayOfReadPointers() const noexcept { return const_cast (channels); } + const Type** getArrayOfReadPointers() const noexcept { return const_cast (channels); } /** Returns an array of pointers to the channels in the buffer. Don't modify any of the pointers that are returned, and bear in mind that these will become invalid if the buffer is resized. */ - float** getArrayOfWritePointers() noexcept { isClear = false; return channels; } + Type** getArrayOfWritePointers() noexcept { isClear = false; return channels; } //============================================================================== /** Changes the buffer's size or number of channels. @@ -212,8 +287,72 @@ public: int newNumSamples, bool keepExistingContent = false, bool clearExtraSpace = false, - bool avoidReallocating = false) noexcept; + bool avoidReallocating = false) noexcept + { + jassert (newNumChannels >= 0); + jassert (newNumSamples >= 0); + if (newNumSamples != size || newNumChannels != numChannels) + { + const size_t allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; + const size_t channelListSize = ((sizeof (Type*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; + const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (Type)) + + channelListSize + 32; + + if (keepExistingContent) + { + HeapBlock newData; + newData.allocate (newTotalBytes, clearExtraSpace || isClear); + + const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size); + + Type** const newChannels = reinterpret_cast (newData.getData()); + Type* newChan = reinterpret_cast (newData + channelListSize); + + for (int j = 0; j < newNumChannels; ++j) + { + newChannels[j] = newChan; + newChan += allocatedSamplesPerChannel; + } + + if (! isClear) + { + const int numChansToCopy = jmin (numChannels, newNumChannels); + for (int i = 0; i < numChansToCopy; ++i) + FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); + } + + allocatedData.swapWith (newData); + allocatedBytes = newTotalBytes; + channels = newChannels; + } + else + { + if (avoidReallocating && allocatedBytes >= newTotalBytes) + { + if (clearExtraSpace || isClear) + allocatedData.clear (newTotalBytes); + } + else + { + allocatedBytes = newTotalBytes; + allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); + channels = reinterpret_cast (allocatedData.getData()); + } + + Type* chan = reinterpret_cast (allocatedData + channelListSize); + for (int i = 0; i < newNumChannels; ++i) + { + channels[i] = chan; + chan += allocatedSamplesPerChannel; + } + } + + channels [newNumChannels] = 0; + size = newNumSamples; + numChannels = newNumChannels; + } + } /** Makes this buffer point to a pre-allocated set of channel data arrays. @@ -228,18 +367,69 @@ public: for each channel that should be used by this buffer. The buffer will only refer to this memory, it won't try to delete it when the buffer is deleted or resized. - @param numChannels the number of channels to use - this must correspond to the + @param newNumChannels the number of channels to use - this must correspond to the number of elements in the array passed in - @param numSamples the number of samples to use - this must correspond to the + @param newNumSamples the number of samples to use - this must correspond to the size of the arrays passed in */ - void setDataToReferTo (float** dataToReferTo, - int numChannels, - int numSamples) noexcept; + void setDataToReferTo (Type** dataToReferTo, + const int newNumChannels, + const int newNumSamples) noexcept + { + jassert (dataToReferTo != nullptr); + jassert (newNumChannels >= 0 && newNumSamples >= 0); + + if (allocatedBytes != 0) + { + allocatedBytes = 0; + allocatedData.free(); + } + + numChannels = newNumChannels; + size = newNumSamples; + + allocateChannels (dataToReferTo, 0); + jassert (! isClear); + } + + /** Resizes this buffer to match the given one, and copies all of its content across. + The source buffer can contain a different floating point type, so this can be used to + convert between 32 and 64 bit float buffer types. + */ + template + void makeCopyOf (const AudioBuffer& other) + { + setSize (other.getNumChannels(), other.getNumSamples()); + + if (other.hasBeenCleared()) + { + clear(); + } + else + { + for (int chan = 0; chan < numChannels; ++chan) + { + Type* const dest = channels[chan]; + const OtherType* const src = other.getReadPointer (chan); + + for (int i = 0; i < size; ++i) + dest[i] = static_cast (src[i]); + } + } + } //============================================================================== /** Clears all the samples in all channels. */ - void clear() noexcept; + void clear() noexcept + { + if (! isClear) + { + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i], size); + + isClear = true; + } + } /** Clears a specified region of all the channels. @@ -247,7 +437,19 @@ public: are in-range, so be careful! */ void clear (int startSample, - int numSamples) noexcept; + int numSamples) noexcept + { + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + { + if (startSample == 0 && numSamples == size) + isClear = true; + + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i] + startSample, numSamples); + } + } /** Clears a specified region of just one channel. @@ -256,7 +458,14 @@ public: */ void clear (int channel, int startSample, - int numSamples) noexcept; + int numSamples) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + FloatVectorOperations::clear (channels [channel] + startSample, numSamples); + } /** Returns true if the buffer has been entirely cleared. Note that this does not actually measure the contents of the buffer - it simply @@ -272,21 +481,38 @@ public: an assertion will be thrown, but in a release build, you're into 'undefined behaviour' territory. */ - float getSample (int channel, int sampleIndex) const noexcept; + Type getSample (int channel, int sampleIndex) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + return *(channels [channel] + sampleIndex); + } /** Sets a sample in the buffer. The channel and index are not checked - they are expected to be in-range. If not, an assertion will be thrown, but in a release build, you're into 'undefined behaviour' territory. */ - void setSample (int destChannel, int destSample, float newValue) noexcept; + void setSample (int destChannel, int destSample, Type newValue) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels [destChannel] + destSample) = newValue; + isClear = false; + } /** Adds a value to a sample in the buffer. The channel and index are not checked - they are expected to be in-range. If not, an assertion will be thrown, but in a release build, you're into 'undefined behaviour' territory. */ - void addSample (int destChannel, int destSample, float valueToAdd) noexcept; + void addSample (int destChannel, int destSample, Type valueToAdd) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels [destChannel] + destSample) += valueToAdd; + isClear = false; + } /** Applies a gain multiple to a region of one channel. @@ -296,7 +522,21 @@ public: void applyGain (int channel, int startSample, int numSamples, - float gain) noexcept; + Type gain) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (gain != 1.0f && ! isClear) + { + Type* const d = channels [channel] + startSample; + + if (gain == 0.0f) + FloatVectorOperations::clear (d, numSamples); + else + FloatVectorOperations::multiply (d, gain, numSamples); + } + } /** Applies a gain multiple to a region of all the channels. @@ -305,10 +545,17 @@ public: */ void applyGain (int startSample, int numSamples, - float gain) noexcept; + Type gain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGain (i, startSample, numSamples, gain); + } /** Applies a gain multiple to all the audio data. */ - void applyGain (float gain) noexcept; + void applyGain (Type gain) noexcept + { + applyGain (0, size, gain); + } /** Applies a range of gains to a region of a channel. @@ -322,8 +569,31 @@ public: void applyGainRamp (int channel, int startSample, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + if (! isClear) + { + if (startGain == endGain) + { + applyGain (channel, startSample, numSamples, startGain); + } + else + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + const Type increment = (endGain - startGain) / numSamples; + Type* d = channels [channel] + startSample; + + while (--numSamples >= 0) + { + *d++ *= startGain; + startGain += increment; + } + } + } + } /** Applies a range of gains to a region of all channels. @@ -336,8 +606,12 @@ public: */ void applyGainRamp (int startSample, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGainRamp (i, startSample, numSamples, startGain, endGain); + } /** Adds samples from another buffer to this one. @@ -354,11 +628,42 @@ public: */ void addFrom (int destChannel, int destStartSample, - const AudioSampleBuffer& source, + const AudioBuffer& source, int sourceChannel, int sourceStartSample, int numSamples, - float gainToApplyToSource = 1.0f) noexcept; + Type gainToApplyToSource = (Type) 1) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (gainToApplyToSource != 0.0f && numSamples > 0 && ! source.isClear) + { + Type* const d = channels [destChannel] + destStartSample; + const Type* const s = source.channels [sourceChannel] + sourceStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); + else + FloatVectorOperations::copy (d, s, numSamples); + } + else + { + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); + else + FloatVectorOperations::add (d, s, numSamples); + } + } + } + /** Adds samples from an array of floats to one of the channels. @@ -373,9 +678,37 @@ public: */ void addFrom (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float gainToApplyToSource = 1.0f) noexcept; + Type gainToApplyToSource = (Type) 1) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (gainToApplyToSource != 0.0f && numSamples > 0) + { + Type* const d = channels [destChannel] + destStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); + else + FloatVectorOperations::copy (d, source, numSamples); + } + else + { + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); + else + FloatVectorOperations::add (d, source, numSamples); + } + } + } + /** Adds samples from an array of floats, applying a gain ramp to them. @@ -390,10 +723,35 @@ public: */ void addFromWithRamp (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (startGain == endGain) + { + addFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) + { + isClear = false; + const Type increment = (endGain - startGain) / numSamples; + Type* d = channels [destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ += startGain * *source++; + startGain += increment; + } + } + } + } /** Copies samples from another buffer to this one. @@ -408,10 +766,33 @@ public: */ void copyFrom (int destChannel, int destStartSample, - const AudioSampleBuffer& source, + const AudioBuffer& source, int sourceChannel, int sourceStartSample, - int numSamples) noexcept; + int numSamples) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (numSamples > 0) + { + if (source.isClear) + { + if (! isClear) + FloatVectorOperations::clear (channels [destChannel] + destStartSample, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copy (channels [destChannel] + destStartSample, + source.channels [sourceChannel] + sourceStartSample, + numSamples); + } + } + } /** Copies samples from an array of floats into one of the channels. @@ -424,8 +805,19 @@ public: */ void copyFrom (int destChannel, int destStartSample, - const float* source, - int numSamples) noexcept; + const Type* source, + int numSamples) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + isClear = false; + FloatVectorOperations::copy (channels [destChannel] + destStartSample, source, numSamples); + } + } /** Copies samples from an array of floats into one of the channels, applying a gain to it. @@ -439,9 +831,38 @@ public: */ void copyFrom (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float gain) noexcept; + Type gain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + Type* const d = channels [destChannel] + destStartSample; + + if (gain != 1.0f) + { + if (gain == 0) + { + if (! isClear) + FloatVectorOperations::clear (d, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); + } + } + else + { + isClear = false; + FloatVectorOperations::copy (d, source, numSamples); + } + } + } /** Copies samples from an array of floats into one of the channels, applying a gain ramp. @@ -458,11 +879,35 @@ public: */ void copyFromWithRamp (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + if (startGain == endGain) + { + copyFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) + { + isClear = false; + const Type increment = (endGain - startGain) / numSamples; + Type* d = channels [destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ = startGain * *source++; + startGain += increment; + } + } + } + } /** Returns a Range indicating the lowest and highest sample values in a given section. @@ -470,57 +915,159 @@ public: @param startSample the start sample within the channel @param numSamples the number of samples to check */ - Range findMinMax (int channel, - int startSample, - int numSamples) const noexcept; + Range findMinMax (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (isClear) + return Range(); + + return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); + } + /** Finds the highest absolute sample value within a region of a channel. */ - float getMagnitude (int channel, - int startSample, - int numSamples) const noexcept; + Type getMagnitude (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (isClear) + return 0.0f; + + const Range r (findMinMax (channel, startSample, numSamples)); + + return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); + } /** Finds the highest absolute sample value within a region on all channels. */ - float getMagnitude (int startSample, - int numSamples) const noexcept; + Type getMagnitude (int startSample, + int numSamples) const noexcept + { + Type mag = 0.0f; + + if (! isClear) + for (int i = 0; i < numChannels; ++i) + mag = jmax (mag, getMagnitude (i, startSample, numSamples)); + + return mag; + } /** Returns the root mean squared level for a region of a channel. */ - float getRMSLevel (int channel, - int startSample, - int numSamples) const noexcept; + Type getRMSLevel (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) + return 0.0f; + + const Type* const data = channels [channel] + startSample; + double sum = 0.0; + + for (int i = 0; i < numSamples; ++i) + { + const Type sample = data [i]; + sum += sample * sample; + } + + return (Type) std::sqrt (sum / numSamples); + } /** Reverses a part of a channel. */ - void reverse (int channel, int startSample, int numSamples) const noexcept; + void reverse (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + std::reverse (channels[channel] + startSample, + channels[channel] + startSample + numSamples); + } /** Reverses a part of the buffer. */ - void reverse (int startSample, int numSamples) const noexcept; + void reverse (int startSample, int numSamples) const noexcept + { + for (int i = 0; i < numChannels; ++i) + reverse (i, startSample, numSamples); + } - //============================================================================== - #ifndef DOXYGEN - // Note that these methods have now been replaced by getReadPointer() and getWritePointer() - JUCE_DEPRECATED_WITH_BODY (const float* getSampleData (int channel) const, { return getReadPointer (channel); }) - JUCE_DEPRECATED_WITH_BODY (const float* getSampleData (int channel, int index) const, { return getReadPointer (channel, index); }) - JUCE_DEPRECATED_WITH_BODY (float* getSampleData (int channel), { return getWritePointer (channel); }) - JUCE_DEPRECATED_WITH_BODY (float* getSampleData (int channel, int index), { return getWritePointer (channel, index); }) - - // These have been replaced by getArrayOfReadPointers() and getArrayOfWritePointers() - JUCE_DEPRECATED_WITH_BODY (const float** getArrayOfChannels() const, { return getArrayOfReadPointers(); }) - JUCE_DEPRECATED_WITH_BODY (float** getArrayOfChannels(), { return getArrayOfWritePointers(); }) - #endif private: //============================================================================== int numChannels, size; size_t allocatedBytes; - float** channels; + Type** channels; HeapBlock allocatedData; - float* preallocatedChannelSpace [32]; + Type* preallocatedChannelSpace [32]; bool isClear; - void allocateData(); - void allocateChannels (float* const*, int offset); + void allocateData() + { + const size_t channelListSize = sizeof (Type*) * (size_t) (numChannels + 1); + allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; + allocatedData.malloc (allocatedBytes); + channels = reinterpret_cast (allocatedData.getData()); - JUCE_LEAK_DETECTOR (AudioSampleBuffer) + Type* chan = (Type*) (allocatedData + channelListSize); + for (int i = 0; i < numChannels; ++i) + { + channels[i] = chan; + chan += size; + } + + channels [numChannels] = nullptr; + isClear = false; + } + + void allocateChannels (Type* const* const dataToReferTo, int offset) + { + jassert (offset >= 0); + + // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) + if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) + { + channels = static_cast (preallocatedChannelSpace); + } + else + { + allocatedData.malloc ((size_t) numChannels + 1, sizeof (Type*)); + channels = reinterpret_cast (allocatedData.getData()); + } + + for (int i = 0; i < numChannels; ++i) + { + // you have to pass in the same number of valid pointers as numChannels + jassert (dataToReferTo[i] != nullptr); + + channels[i] = dataToReferTo[i] + offset; + } + + channels [numChannels] = nullptr; + isClear = false; + } + + JUCE_LEAK_DETECTOR (AudioBuffer) }; +//============================================================================== +/** + A multi-channel buffer of 32-bit floating point audio samples. + + This typedef is here for backwards compatibility with the older AudioSampleBuffer + class, which was fixed for 32-bit data, but is otherwise the same as the new + templated AudioBuffer class. + + @see AudioBuffer +*/ +typedef AudioBuffer AudioSampleBuffer; + #endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index 9d9d193219..5072031bb7 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -80,7 +80,6 @@ namespace juce { #include "buffers/juce_AudioDataConverters.cpp" -#include "buffers/juce_AudioSampleBuffer.cpp" #include "buffers/juce_FloatVectorOperations.cpp" #include "effects/juce_IIRFilter.cpp" #include "effects/juce_LagrangeInterpolator.cpp" diff --git a/modules/juce_audio_basics/juce_audio_basics.h b/modules/juce_audio_basics/juce_audio_basics.h index bffa1ef50b..e34d4faae7 100644 --- a/modules/juce_audio_basics/juce_audio_basics.h +++ b/modules/juce_audio_basics/juce_audio_basics.h @@ -35,8 +35,8 @@ namespace juce #undef Factor #include "buffers/juce_AudioDataConverters.h" -#include "buffers/juce_AudioSampleBuffer.h" #include "buffers/juce_FloatVectorOperations.h" +#include "buffers/juce_AudioSampleBuffer.h" #include "effects/juce_Decibels.h" #include "effects/juce_IIRFilter.h" #include "effects/juce_LagrangeInterpolator.h" diff --git a/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index 16ef359ac5..2aa10fb7ce 100644 --- a/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -71,6 +71,18 @@ bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const no return noteOnTime < other.noteOnTime; } +void SynthesiserVoice::renderNextBlock (AudioBuffer& outputBuffer, + int startSample, int numSamples) +{ + AudioBuffer subBuffer (outputBuffer.getArrayOfWritePointers(), + outputBuffer.getNumChannels(), + startSample, numSamples); + + tempBuffer.makeCopyOf (subBuffer); + renderNextBlock (tempBuffer, 0, numSamples); + subBuffer.makeCopyOf (tempBuffer); +} + //============================================================================== Synthesiser::Synthesiser() : sampleRate (0), @@ -156,8 +168,11 @@ void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) } } -void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBuffer& midiData, - int startSample, int numSamples) +template +void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples) { // must set the sample rate before using this! jassert (sampleRate != 0); @@ -174,7 +189,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu { if (! midiIterator.getNextEvent (m, midiEventPos)) { - renderVoices (outputBuffer, startSample, numSamples); + renderVoices (outputAudio, startSample, numSamples); return; } @@ -182,7 +197,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu if (samplesToNextMidiMessage >= numSamples) { - renderVoices (outputBuffer, startSample, numSamples); + renderVoices (outputAudio, startSample, numSamples); handleMidiEvent (m); break; } @@ -193,7 +208,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu continue; } - renderVoices (outputBuffer, startSample, samplesToNextMidiMessage); + renderVoices (outputAudio, startSample, samplesToNextMidiMessage); handleMidiEvent (m); startSample += samplesToNextMidiMessage; numSamples -= samplesToNextMidiMessage; @@ -203,7 +218,23 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu handleMidiEvent (m); } -void Synthesiser::renderVoices (AudioSampleBuffer& buffer, int startSample, int numSamples) +// explicit template instantiation +template void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples); +template void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples); + +void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) +{ + for (int i = voices.size(); --i >= 0;) + voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); +} + +void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) { for (int i = voices.size(); --i >= 0;) voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); diff --git a/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h b/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h index f255fa3604..0cf92dc7d4 100644 --- a/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h +++ b/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h @@ -182,9 +182,12 @@ public: involve rendering as little as 1 sample at a time. In between rendering callbacks, the voice's methods will be called to tell it about note and controller events. */ - virtual void renderNextBlock (AudioSampleBuffer& outputBuffer, + virtual void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) = 0; + virtual void renderNextBlock (AudioBuffer& outputBuffer, + int startSample, + int numSamples); /** Changes the voice's reference sample rate. @@ -255,6 +258,8 @@ private: SynthesiserSound::Ptr currentlyPlayingSound; bool keyIsDown, sustainPedalDown, sostenutoPedalDown; + AudioBuffer tempBuffer; + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE // Note the new parameters for this method. virtual int stopNote (bool) { return 0; } @@ -504,10 +509,17 @@ public: both to the audio output buffer and the midi input buffer, so any midi events with timestamps outside the specified region will be ignored. */ - void renderNextBlock (AudioSampleBuffer& outputAudio, + inline void renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, int startSample, - int numSamples); + int numSamples) + { processNextBlock (outputAudio, inputMidi, startSample, numSamples); } + + inline void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples) + { processNextBlock (outputAudio, inputMidi, startSample, numSamples); } /** Returns the current target sample rate at which rendering is being done. Subclasses may need to know this so that they can pitch things correctly. @@ -545,7 +557,9 @@ protected: By default this just calls renderNextBlock() on each voice, but you may need to override it to handle custom cases. */ - virtual void renderVoices (AudioSampleBuffer& outputAudio, + virtual void renderVoices (AudioBuffer& outputAudio, + int startSample, int numSamples); + virtual void renderVoices (AudioBuffer& outputAudio, int startSample, int numSamples); /** Searches through the voices to find one that's not currently playing, and @@ -592,6 +606,12 @@ protected: private: //============================================================================== + template + void processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); + //============================================================================== double sampleRate; uint32 lastNoteOnCounter; int minimumSubBlockSize; diff --git a/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 601fbf2c05..763634e265 100644 --- a/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -233,7 +233,7 @@ public: for (int i = 0; i < numStreams; ++i) { - const AudioBuffer& b = bufList->mBuffers[i]; + const ::AudioBuffer& b = bufList->mBuffers[i]; for (unsigned int j = 0; j < b.mNumberChannels; ++j) { @@ -1945,7 +1945,7 @@ private: for (int i = 0; i < numStreams; ++i) { - const AudioBuffer& b = bufList->mBuffers[i]; + const ::AudioBuffer& b = bufList->mBuffers[i]; total += b.mNumberChannels; } } diff --git a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp index 9b4d556d04..d11b17b34b 100644 --- a/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp +++ b/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp @@ -380,7 +380,7 @@ public: &destinationAudioFormat); if (status == noErr) { - bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (AudioBuffer)); + bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (::AudioBuffer)); bufferList->mNumberBuffers = numChannels; ok = true; } diff --git a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index 418a991a16..238aca8a1f 100644 --- a/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -886,7 +886,7 @@ public: for (unsigned int i = 0; i < outBuffer.mNumberBuffers; ++i) { - AudioBuffer& buf = outBuffer.mBuffers[i]; + ::AudioBuffer& buf = outBuffer.mBuffers[i]; if (buf.mNumberChannels == 1) { @@ -908,7 +908,7 @@ public: for (unsigned int i = 0; i < inBuffer.mNumberBuffers; ++i) { - const AudioBuffer& buf = inBuffer.mBuffers[i]; + const ::AudioBuffer& buf = inBuffer.mBuffers[i]; if (buf.mNumberChannels == 1) { @@ -1027,7 +1027,7 @@ public: for (unsigned int i = 0; i < outBuffer.mNumberBuffers; ++i) { - AudioBuffer& buf = outBuffer.mBuffers[i]; + ::AudioBuffer& buf = outBuffer.mBuffers[i]; if (buf.mNumberChannels > 1) { diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 39b21667a8..93076f3fca 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -243,6 +243,27 @@ class JuceVSTWrapper : public AudioEffectX, private Timer, private AsyncUpdater { +private: + //============================================================================== + template + struct VstTempBuffers + { + VstTempBuffers() {} + ~VstTempBuffers() { release(); } + + void release() noexcept + { + for (int i = tempChannels.size(); --i >= 0;) + delete[] (tempChannels.getUnchecked(i)); + + tempChannels.clear(); + } + + HeapBlock channels; + Array tempChannels; // see note in processReplacing() + juce::AudioBuffer processTempBuffer; + }; + public: //============================================================================== JuceVSTWrapper (audioMasterCallback audioMasterCB, AudioProcessor* const af) @@ -264,7 +285,6 @@ public: #else useNSView (false), #endif - processTempBuffer (1, 1), hostWindow (0) { filter->setPlayConfigDetails (numInChans, numOutChans, 0, 0); @@ -280,6 +300,7 @@ public: setNumOutputs (numOutChans); canProcessReplacing (true); + canDoubleReplacing (filter->supportsDoublePrecisionProcessing()); isSynth ((JucePlugin_IsSynth) != 0); setInitialDelay (filter->getLatencySamples()); @@ -310,7 +331,6 @@ public: jassert (editorComp == 0); - channels.free(); deleteTempChannels(); jassert (activePlugins.contains (this)); @@ -500,23 +520,27 @@ public: void process (float** inputs, float** outputs, VstInt32 numSamples) { + VstTempBuffers& tmpBuffers = floatTempBuffers; + const int numIn = numInChans; const int numOut = numOutChans; - processTempBuffer.setSize (numIn, numSamples, false, false, true); + tmpBuffers.processTempBuffer.setSize (numIn, numSamples, false, false, true); for (int i = numIn; --i >= 0;) - processTempBuffer.copyFrom (i, 0, outputs[i], numSamples); + tmpBuffers.processTempBuffer.copyFrom (i, 0, outputs[i], numSamples); processReplacing (inputs, outputs, numSamples); AudioSampleBuffer dest (outputs, numOut, numSamples); for (int i = jmin (numIn, numOut); --i >= 0;) - dest.addFrom (i, 0, processTempBuffer, i, 0, numSamples); + dest.addFrom (i, 0, tmpBuffers.processTempBuffer, i, 0, numSamples); } - void processReplacing (float** inputs, float** outputs, VstInt32 numSamples) override + template + void internalProcessReplacing (FloatType** inputs, FloatType** outputs, + VstInt32 numSamples, VstTempBuffers& tmpBuffers) { if (firstProcessCallback) { @@ -561,7 +585,7 @@ public: int i; for (i = 0; i < numOut; ++i) { - float* chan = tempChannels.getUnchecked(i); + FloatType* chan = tmpBuffers.tempChannels.getUnchecked(i); if (chan == nullptr) { @@ -574,24 +598,24 @@ public: { if (outputs[j] == chan) { - chan = new float [blockSize * 2]; - tempChannels.set (i, chan); + chan = new FloatType [blockSize * 2]; + tmpBuffers.tempChannels.set (i, chan); break; } } } if (i < numIn && chan != inputs[i]) - memcpy (chan, inputs[i], sizeof (float) * (size_t) numSamples); + memcpy (chan, inputs[i], sizeof (FloatType) * (size_t) numSamples); - channels[i] = chan; + tmpBuffers.channels[i] = chan; } for (; i < numIn; ++i) - channels[i] = inputs[i]; + tmpBuffers.channels[i] = inputs[i]; { - AudioSampleBuffer chans (channels, jmax (numIn, numOut), numSamples); + AudioBuffer chans (tmpBuffers.channels, jmax (numIn, numOut), numSamples); if (isBypassed) filter->processBlockBypassed (chans, midiEvents); @@ -601,8 +625,8 @@ public: // copy back any temp channels that may have been used.. for (i = 0; i < numOut; ++i) - if (const float* const chan = tempChannels.getUnchecked(i)) - memcpy (outputs[i], chan, sizeof (float) * (size_t) numSamples); + if (const FloatType* const chan = tmpBuffers.tempChannels.getUnchecked(i)) + memcpy (outputs[i], chan, sizeof (FloatType) * (size_t) numSamples); } } @@ -648,16 +672,47 @@ public: } } + void processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames) override + { + jassert (! filter->isUsingDoublePrecision()); + internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); + } + + void processDoubleReplacing (double** inputs, double** outputs, VstInt32 sampleFrames) override + { + jassert (filter->isUsingDoublePrecision()); + internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); + } + //============================================================================== VstInt32 startProcess() override { return 0; } VstInt32 stopProcess() override { return 0; } + //============================================================================== + bool setProcessPrecision (VstInt32 vstPrecision) override + { + if (! isProcessing) + { + if (filter != nullptr) + { + filter->setProcessingPrecision (vstPrecision == kVstProcessPrecision64 && filter->supportsDoublePrecisionProcessing() + ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision); + + return true; + } + } + + return false; + } + void resume() override { if (filter != nullptr) { isProcessing = true; - channels.calloc ((size_t) (numInChans + numOutChans)); + floatTempBuffers.channels.calloc ((size_t) (numInChans + numOutChans)); + doubleTempBuffers.channels.calloc ((size_t) (numInChans + numOutChans)); double rate = getSampleRate(); jassert (rate > 0); @@ -699,7 +754,8 @@ public: outgoingEvents.freeEvents(); isProcessing = false; - channels.free(); + floatTempBuffers.channels.free(); + doubleTempBuffers.channels.free(); deleteTempChannels(); } @@ -1457,9 +1513,8 @@ private: int numInChans, numOutChans; bool isProcessing, isBypassed, hasShutdown, isInSizeWindow, firstProcessCallback; bool shouldDeleteEditor, useNSView; - HeapBlock channels; - Array tempChannels; // see note in processReplacing() - AudioSampleBuffer processTempBuffer; + VstTempBuffers floatTempBuffers; + VstTempBuffers doubleTempBuffers; #if JUCE_MAC void* hostWindow; @@ -1517,15 +1572,22 @@ private: #endif //============================================================================== - void deleteTempChannels() + template + void deleteTempChannels (VstTempBuffers& tmpBuffers) { - for (int i = tempChannels.size(); --i >= 0;) - delete[] (tempChannels.getUnchecked(i)); - - tempChannels.clear(); + tmpBuffers.release(); if (filter != nullptr) - tempChannels.insertMultiple (0, nullptr, filter->getNumInputChannels() + filter->getNumOutputChannels()); + { + int numChannels = filter->getNumInputChannels() + filter->getNumOutputChannels(); + tmpBuffers.tempChannels.insertMultiple (0, nullptr, numChannels); + } + } + + void deleteTempChannels() + { + deleteTempChannels (floatTempBuffers); + deleteTempChannels (doubleTempBuffers); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 1f909fb9b9..e9490b183b 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -743,6 +743,14 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) }; +namespace +{ + template struct AudioBusPointerHelper {}; + template <> struct AudioBusPointerHelper { static inline float** impl (Vst::AudioBusBuffers& data) noexcept { return data.channelBuffers32; } }; + template <> struct AudioBusPointerHelper { static inline double** impl (Vst::AudioBusBuffers& data) noexcept { return data.channelBuffers64; } }; +} + + //============================================================================== class JuceVST3Component : public Vst::IComponent, public Vst::IAudioProcessor, @@ -956,8 +964,8 @@ public: ? (int) processSetup.maxSamplesPerBlock : bufferSize; - channelList.clear(); - channelList.insertMultiple (0, nullptr, jmax (JucePlugin_MaxNumInputChannels, JucePlugin_MaxNumOutputChannels) + 1); + allocateChannelLists (channelListFloat); + allocateChannelLists (channelListDouble); preparePlugin (sampleRate, bufferSize); } @@ -1440,7 +1448,9 @@ public: tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override { - return symbolicSampleSize == Vst::kSample32 ? kResultTrue : kResultFalse; + return (symbolicSampleSize == Vst::kSample32 + || (getPluginInstance().supportsDoublePrecisionProcessing() + && symbolicSampleSize == Vst::kSample64)) ? kResultTrue : kResultFalse; } Steinberg::uint32 PLUGIN_API getLatencySamples() override @@ -1456,6 +1466,10 @@ public: processSetup = newSetup; processContext.sampleRate = processSetup.sampleRate; + getPluginInstance().setProcessingPrecision (newSetup.symbolicSampleSize == Vst::kSample64 + ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision); + preparePlugin (processSetup.sampleRate, processSetup.maxSamplesPerBlock); return kResultTrue; @@ -1532,9 +1546,13 @@ public: tresult PLUGIN_API process (Vst::ProcessData& data) override { + if (pluginInstance == nullptr) return kResultFalse; + if ((processSetup.symbolicSampleSize == Vst::kSample64) != pluginInstance->isUsingDoublePrecision()) + return kResultFalse; + if (data.processContext != nullptr) processContext = *data.processContext; else @@ -1551,60 +1569,19 @@ public: const int numMidiEventsComingIn = midiBuffer.getNumEvents(); #endif - const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; - const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; - - int totalChans = 0; - - while (totalChans < numInputChans) + if (getHostType().isWavelab()) { - channelList.set (totalChans, data.inputs[0].channelBuffers32[totalChans]); - ++totalChans; + const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; + const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; + + if ((pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels()) > 0 + && (numInputChans + numOutputChans) == 0) + return kResultFalse; } - while (totalChans < numOutputChans) - { - channelList.set (totalChans, data.outputs[0].channelBuffers32[totalChans]); - ++totalChans; - } - - AudioSampleBuffer buffer; - - if (totalChans != 0) - buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); - else if (getHostType().isWavelab() - && pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels() > 0) - return kResultFalse; - - { - const ScopedLock sl (pluginInstance->getCallbackLock()); - - pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); - - if (data.inputParameterChanges != nullptr) - processParameterChanges (*data.inputParameterChanges); - - if (pluginInstance->isSuspended()) - { - buffer.clear(); - } - else - { - if (isBypassed()) - pluginInstance->processBlockBypassed (buffer, midiBuffer); - else - pluginInstance->processBlock (buffer, midiBuffer); - } - } - - for (int i = 0; i < numOutputChans; ++i) - FloatVectorOperations::copy (data.outputs[0].channelBuffers32[i], buffer.getReadPointer (i), (int) data.numSamples); - - // clear extra busses.. - if (data.outputs != nullptr) - for (int i = 1; i < data.numOutputs; ++i) - for (int f = 0; f < data.outputs[i].numChannels; ++f) - FloatVectorOperations::clear (data.outputs[i].channelBuffers32[f], (int) data.numSamples); + if (processSetup.symbolicSampleSize == Vst::kSample32) processAudio (data, channelListFloat); + else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio (data, channelListDouble); + else jassertfalse; #if JucePlugin_ProducesMidiOutput if (data.outputEvents != nullptr) @@ -1648,7 +1625,8 @@ private: Vst::BusList audioInputs, audioOutputs, eventInputs, eventOutputs; MidiBuffer midiBuffer; - Array channelList; + Array channelListFloat; + Array channelListDouble; ScopedJuceInitialiser_GUI libraryInitialiser; @@ -1656,6 +1634,52 @@ private: static const char* kJucePrivateDataIdentifier; + //============================================================================== + template + void processAudio (Vst::ProcessData& data, Array& channelList) + { + const int totalChans = prepareChannelLists (channelList, data); + + AudioBuffer buffer; + + if (totalChans != 0) + buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); + + { + const ScopedLock sl (pluginInstance->getCallbackLock()); + + pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); + + if (data.inputParameterChanges != nullptr) + processParameterChanges (*data.inputParameterChanges); + + if (pluginInstance->isSuspended()) + { + buffer.clear(); + } + else + { + if (isBypassed()) + pluginInstance->processBlockBypassed (buffer, midiBuffer); + else + pluginInstance->processBlock (buffer, midiBuffer); + } + } + + if (data.outputs != nullptr) + { + for (int i = 0; i < data.numOutputs; ++i) + FloatVectorOperations::copy (getPointerForAudioBus (data.outputs[0])[i], + buffer.getReadPointer (i), (int) data.numSamples); + } + + // clear extra busses.. + if (data.outputs != nullptr) + for (int i = 1; i < data.numOutputs; ++i) + for (int f = 0; f < data.outputs[i].numChannels; ++f) + FloatVectorOperations::clear (getPointerForAudioBus (data.outputs[i])[f], (int) data.numSamples); + } + //============================================================================== void addBusTo (Vst::BusList& busList, Vst::Bus* newBus) { @@ -1680,6 +1704,50 @@ private: return nullptr; } + //============================================================================== + template + void allocateChannelLists (Array& channelList) + { + channelList.clear(); + channelList.insertMultiple (0, nullptr, jmax (JucePlugin_MaxNumInputChannels, JucePlugin_MaxNumOutputChannels) + 1); + } + + template + static FloatType** getPointerForAudioBus (Vst::AudioBusBuffers& data) noexcept + { + return AudioBusPointerHelper::impl (data); + } + + template + static int prepareChannelLists (Array& channelList, Vst::ProcessData& data) noexcept + { + int totalChans = 0; + FloatType** inChannelBuffers = + data.inputs != nullptr ? getPointerForAudioBus (data.inputs[0]) : nullptr; + + FloatType** outChannelBuffers = + data.outputs != nullptr ? getPointerForAudioBus (data.outputs[0]) : nullptr; + + const int numInputChans = (data.inputs != nullptr && inChannelBuffers != nullptr) ? (int) data.inputs[0].numChannels : 0; + const int numOutputChans = (data.outputs != nullptr && outChannelBuffers != nullptr) ? (int) data.outputs[0].numChannels : 0; + + for (int idx = 0; totalChans < numInputChans; ++idx) + { + channelList.set (totalChans, inChannelBuffers[idx]); + ++totalChans; + } + + // note that the loop bounds are correct: as VST-3 is always process replacing + // we already know the output channel buffers of the first numInputChans channels + for (int idx = 0; totalChans < numOutputChans; ++idx) + { + channelList.set (totalChans, outChannelBuffers[idx]); + ++totalChans; + } + + return totalChans; + } + //============================================================================== enum InternalParameters { diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 214853b5b1..ebdec5f9d6 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -1223,7 +1223,7 @@ private: //============================================================================== size_t getAudioBufferSizeInBytes() const noexcept { - return offsetof (AudioBufferList, mBuffers) + (sizeof (AudioBuffer) * numOutputBusChannels); + return offsetof (AudioBufferList, mBuffers) + (sizeof (::AudioBuffer) * numOutputBusChannels); } AudioBufferList* getAudioBufferListForBus (AudioUnitElement busIndex) const noexcept diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index e4769807cc..ec33c618b2 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -344,21 +344,25 @@ private: }; //============================================================================== -namespace VST3BufferExchange +template +struct VST3BufferExchange { - typedef Array Bus; + typedef Array Bus; typedef Array BusMap; + static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw) { vstBuffers.channelBuffers32 = raw; } + static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; } + /** Assigns a series of AudioSampleBuffer's channels to an AudioBusBuffers' @warning For speed, does not check the channel count and offsets according to the AudioSampleBuffer */ - void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, - Bus& bus, - AudioSampleBuffer& buffer, - int numChannels, int channelStartOffset, - int sampleOffset = 0) + static void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, + Bus& bus, + AudioBuffer& buffer, + int numChannels, int channelStartOffset, + int sampleOffset = 0) { const int channelEnd = numChannels + channelStartOffset; jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); @@ -368,7 +372,7 @@ namespace VST3BufferExchange for (int i = channelStartOffset; i < channelEnd; ++i) bus.add (buffer.getWritePointer (i, sampleOffset)); - vstBuffers.channelBuffers32 = bus.getRawDataPointer(); + assignRawPointer (vstBuffers, bus.getRawDataPointer()); vstBuffers.numChannels = numChannels; vstBuffers.silenceFlags = 0; } @@ -376,7 +380,7 @@ namespace VST3BufferExchange static void mapArrangementToBusses (int& channelIndexOffset, int index, Array& result, BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, - AudioSampleBuffer& source) + AudioBuffer& source) { const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); @@ -387,18 +391,16 @@ namespace VST3BufferExchange busMapToUse.add (Bus()); if (numChansForBus > 0) - { associateBufferTo (result.getReference (index), busMapToUse.getReference (index), source, numChansForBus, channelIndexOffset); - } channelIndexOffset += numChansForBus; } - inline void mapBufferToBusses (Array& result, BusMap& busMapToUse, - const Array& arrangements, - AudioSampleBuffer& source) + static inline void mapBufferToBusses (Array& result, BusMap& busMapToUse, + const Array& arrangements, + AudioBuffer& source) { int channelIndexOffset = 0; @@ -407,10 +409,10 @@ namespace VST3BufferExchange arrangements.getUnchecked (i), source); } - inline void mapBufferToBusses (Array& result, - Steinberg::Vst::IAudioProcessor& processor, - BusMap& busMapToUse, bool isInput, int numBusses, - AudioSampleBuffer& source) + static inline void mapBufferToBusses (Array& result, + Steinberg::Vst::IAudioProcessor& processor, + BusMap& busMapToUse, bool isInput, int numBusses, + AudioBuffer& source) { int channelIndexOffset = 0; @@ -420,6 +422,28 @@ namespace VST3BufferExchange getArrangementForBus (&processor, isInput, i), source); } -} +}; +template +struct VST3FloatAndDoubleBusMapCompositeHelper {}; + +struct VST3FloatAndDoubleBusMapComposite +{ + VST3BufferExchange::BusMap floatVersion; + VST3BufferExchange::BusMap doubleVersion; + + template + inline typename VST3BufferExchange::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper::get (*this); } +}; + + +template <> struct VST3FloatAndDoubleBusMapCompositeHelper +{ + static inline VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; } +}; + +template <> struct VST3FloatAndDoubleBusMapCompositeHelper +{ + static inline VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; } +}; #endif // JUCE_VST3COMMON_H_INCLUDED diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 440aaedf26..5f7ca8d2e4 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -1701,7 +1701,7 @@ public: using namespace Vst; ProcessSetup setup; - setup.symbolicSampleSize = kSample32; + setup.symbolicSampleSize = isUsingDoublePrecision() ? kSample64 : kSample32; setup.maxSamplesPerBlock = estimatedSamplesPerBlock; setup.sampleRate = newSampleRate; setup.processMode = isNonRealtime() ? kOffline : kRealtime; @@ -1768,39 +1768,56 @@ public: JUCE_CATCH_ALL_ASSERT } - void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override + bool supportsDoublePrecisionProcessing() const override + { + return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (! isUsingDoublePrecision()); + + if (isActive && processor != nullptr) + processAudio (buffer, midiMessages, Vst::kSample32); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + + if (isActive && processor != nullptr) + processAudio (buffer, midiMessages, Vst::kSample64); + } + + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, + Vst::SymbolicSampleSizes sampleSize) { using namespace Vst; + const int numSamples = buffer.getNumSamples(); - if (isActive - && processor != nullptr - && processor->canProcessSampleSize (kSample32) == kResultTrue) - { - const int numSamples = buffer.getNumSamples(); + ProcessData data; + data.processMode = isNonRealtime() ? kOffline : kRealtime; + data.symbolicSampleSize = sampleSize; + data.numInputs = numInputAudioBusses; + data.numOutputs = numOutputAudioBusses; + data.inputParameterChanges = inputParameterChanges; + data.outputParameterChanges = outputParameterChanges; + data.numSamples = (Steinberg::int32) numSamples; - ProcessData data; - data.processMode = isNonRealtime() ? kOffline : kRealtime; - data.symbolicSampleSize = kSample32; - data.numInputs = numInputAudioBusses; - data.numOutputs = numOutputAudioBusses; - data.inputParameterChanges = inputParameterChanges; - data.outputParameterChanges = outputParameterChanges; - data.numSamples = (Steinberg::int32) numSamples; + updateTimingInformation (data, getSampleRate()); - updateTimingInformation (data, getSampleRate()); + for (int i = getNumInputChannels(); i < buffer.getNumChannels(); ++i) + buffer.clear (i, 0, numSamples); - for (int i = getNumInputChannels(); i < buffer.getNumChannels(); ++i) - buffer.clear (i, 0, numSamples); + associateTo (data, buffer); + associateTo (data, midiMessages); - associateTo (data, buffer); - associateTo (data, midiMessages); + processor->process (data); - processor->process (data); + MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); - MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); - - inputParameterChanges->clearAllQueues(); - } + inputParameterChanges->clearAllQueues(); } //============================================================================== @@ -2150,7 +2167,7 @@ private: */ int numInputAudioBusses, numOutputAudioBusses; Array inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). - VST3BufferExchange::BusMap inputBusMap, outputBusMap; + VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap; Array inputBusses, outputBusses; //============================================================================== @@ -2392,12 +2409,11 @@ private: } //============================================================================== - void associateTo (Vst::ProcessData& destination, AudioSampleBuffer& buffer) + template + void associateTo (Vst::ProcessData& destination, AudioBuffer& buffer) { - using namespace VST3BufferExchange; - - mapBufferToBusses (inputBusses, inputBusMap, inputArrangements, buffer); - mapBufferToBusses (outputBusses, outputBusMap, outputArrangements, buffer); + VST3BufferExchange::mapBufferToBusses (inputBusses, inputBusMap.get(), inputArrangements, buffer); + VST3BufferExchange::mapBufferToBusses (outputBusses, outputBusMap.get(), outputArrangements, buffer); destination.inputs = inputBusses.getRawDataPointer(); destination.outputs = outputBusses.getRawDataPointer(); diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index c86b08c5ba..b37993fc81 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -715,8 +715,7 @@ public: name (mh->pluginName), wantsMidiMessages (false), initialised (false), - isPowerOn (false), - tempBuffer (1, 1) + isPowerOn (false) { try { @@ -938,6 +937,16 @@ public: dispatch (effSetSampleRate, 0, 0, 0, (float) rate); dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); + if (supportsDoublePrecisionProcessing()) + { + VstInt32 vstPrecision = isUsingDoublePrecision() ? kVstProcessPrecision64 + : kVstProcessPrecision32; + + // if you get an assertion here then your plug-in claims it supports double precision + // but returns an error when we try to change the precision + jassert (dispatch (effSetProcessPrecision, 0, (VstIntPtr) vstPrecision, 0, 0) > 0); + } + tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); if (! isPowerOn) @@ -980,110 +989,22 @@ public: } } - void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override { - const int numSamples = buffer.getNumSamples(); + jassert (! isUsingDoublePrecision()); + processAudio (buffer, midiMessages); + } - if (initialised) - { - if (AudioPlayHead* const currentPlayHead = getPlayHead()) - { - AudioPlayHead::CurrentPositionInfo position; - if (currentPlayHead->getCurrentPosition (position)) - { + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + processAudio (buffer, midiMessages); + } - vstHostTime.samplePos = (double) position.timeInSamples; - vstHostTime.tempo = position.bpm; - vstHostTime.timeSigNumerator = position.timeSigNumerator; - vstHostTime.timeSigDenominator = position.timeSigDenominator; - vstHostTime.ppqPos = position.ppqPosition; - vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; - vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; - - VstInt32 newTransportFlags = 0; - if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; - if (position.isRecording) newTransportFlags |= kVstTransportRecording; - - if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) - vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; - else - vstHostTime.flags &= ~kVstTransportChanged; - - switch (position.frameRate) - { - case AudioPlayHead::fps24: setHostTimeFrameRate (0, 24.0, position.timeInSeconds); break; - case AudioPlayHead::fps25: setHostTimeFrameRate (1, 25.0, position.timeInSeconds); break; - case AudioPlayHead::fps2997: setHostTimeFrameRate (2, 29.97, position.timeInSeconds); break; - case AudioPlayHead::fps30: setHostTimeFrameRate (3, 30.0, position.timeInSeconds); break; - case AudioPlayHead::fps2997drop: setHostTimeFrameRate (4, 29.97, position.timeInSeconds); break; - case AudioPlayHead::fps30drop: setHostTimeFrameRate (5, 29.97, position.timeInSeconds); break; - default: break; - } - - if (position.isLooping) - { - vstHostTime.cycleStartPos = position.ppqLoopStart; - vstHostTime.cycleEndPos = position.ppqLoopEnd; - vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); - } - else - { - vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); - } - } - } - - vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); - - if (wantsMidiMessages) - { - midiEventsToSend.clear(); - midiEventsToSend.ensureSize (1); - - MidiBuffer::Iterator iter (midiMessages); - const uint8* midiData; - int numBytesOfMidiData, samplePosition; - - while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) - { - midiEventsToSend.addEvent (midiData, numBytesOfMidiData, - jlimit (0, numSamples - 1, samplePosition)); - } - - effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); - } - - _clearfp(); - - if ((effect->flags & effFlagsCanReplacing) != 0) - { - effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), numSamples); - } - else - { - tempBuffer.setSize (effect->numOutputs, numSamples); - tempBuffer.clear(); - - effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), numSamples); - - for (int i = effect->numOutputs; --i >= 0;) - buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), numSamples); - } - } - else - { - // Not initialised, so just bypass.. - for (int i = 0; i < getNumOutputChannels(); ++i) - buffer.clear (i, 0, buffer.getNumSamples()); - } - - { - // copy any incoming midi.. - const ScopedLock sl (midiInLock); - - midiMessages.swapWith (incomingMidi); - incomingMidi.clear(); - } + bool supportsDoublePrecisionProcessing() const override + { + return ((effect->flags & effFlagsCanReplacing) != 0 + && (effect->flags & effFlagsCanDoubleReplacing) != 0); } //============================================================================== @@ -1673,12 +1594,132 @@ private: CriticalSection lock; bool wantsMidiMessages, initialised, isPowerOn; mutable StringArray programNames; - AudioSampleBuffer tempBuffer; + AudioBuffer tempBuffer; CriticalSection midiInLock; MidiBuffer incomingMidi; VSTMidiEventList midiEventsToSend; VstTimeInfo vstHostTime; + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + const int numSamples = buffer.getNumSamples(); + + if (initialised) + { + if (AudioPlayHead* const currentPlayHead = getPlayHead()) + { + AudioPlayHead::CurrentPositionInfo position; + + if (currentPlayHead->getCurrentPosition (position)) + { + + vstHostTime.samplePos = (double) position.timeInSamples; + vstHostTime.tempo = position.bpm; + vstHostTime.timeSigNumerator = position.timeSigNumerator; + vstHostTime.timeSigDenominator = position.timeSigDenominator; + vstHostTime.ppqPos = position.ppqPosition; + vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; + vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; + + VstInt32 newTransportFlags = 0; + if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; + if (position.isRecording) newTransportFlags |= kVstTransportRecording; + + if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) + vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; + else + vstHostTime.flags &= ~kVstTransportChanged; + + switch (position.frameRate) + { + case AudioPlayHead::fps24: setHostTimeFrameRate (0, 24.0, position.timeInSeconds); break; + case AudioPlayHead::fps25: setHostTimeFrameRate (1, 25.0, position.timeInSeconds); break; + case AudioPlayHead::fps2997: setHostTimeFrameRate (2, 29.97, position.timeInSeconds); break; + case AudioPlayHead::fps30: setHostTimeFrameRate (3, 30.0, position.timeInSeconds); break; + case AudioPlayHead::fps2997drop: setHostTimeFrameRate (4, 29.97, position.timeInSeconds); break; + case AudioPlayHead::fps30drop: setHostTimeFrameRate (5, 29.97, position.timeInSeconds); break; + default: break; + } + + if (position.isLooping) + { + vstHostTime.cycleStartPos = position.ppqLoopStart; + vstHostTime.cycleEndPos = position.ppqLoopEnd; + vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); + } + else + { + vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); + } + } + } + + vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); + + if (wantsMidiMessages) + { + midiEventsToSend.clear(); + midiEventsToSend.ensureSize (1); + + MidiBuffer::Iterator iter (midiMessages); + const uint8* midiData; + int numBytesOfMidiData, samplePosition; + + while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) + { + midiEventsToSend.addEvent (midiData, numBytesOfMidiData, + jlimit (0, numSamples - 1, samplePosition)); + } + + effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); + } + + _clearfp(); + + invokeProcessFunction (buffer, numSamples); + } + else + { + // Not initialised, so just bypass.. + for (int i = 0; i < getNumOutputChannels(); ++i) + buffer.clear (i, 0, buffer.getNumSamples()); + } + + { + // copy any incoming midi.. + const ScopedLock sl (midiInLock); + + midiMessages.swapWith (incomingMidi); + incomingMidi.clear(); + } + } + + //============================================================================== + inline void invokeProcessFunction (AudioBuffer& buffer, VstInt32 sampleFrames) + { + if ((effect->flags & effFlagsCanReplacing) != 0) + { + effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); + } + else + { + tempBuffer.setSize (effect->numOutputs, sampleFrames); + tempBuffer.clear(); + + effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), sampleFrames); + + for (int i = effect->numOutputs; --i >= 0;) + buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), sampleFrames); + } + } + + inline void invokeProcessFunction (AudioBuffer& buffer, VstInt32 sampleFrames) + { + effect->processDoubleReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); + } + //============================================================================== void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept { diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index fe1c7fd1c2..a3f7630a3e 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -38,7 +38,8 @@ AudioProcessor::AudioProcessor() numOutputChannels (0), latencySamples (0), suspended (false), - nonRealtime (false) + nonRealtime (false), + processingPrecision (singlePrecision) { } @@ -323,7 +324,33 @@ void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) } void AudioProcessor::reset() {} -void AudioProcessor::processBlockBypassed (AudioSampleBuffer&, MidiBuffer&) {} +void AudioProcessor::processBlockBypassed (AudioBuffer&, MidiBuffer&) {} +void AudioProcessor::processBlockBypassed (AudioBuffer&, MidiBuffer&) {} + +void AudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + ignoreUnused (buffer, midiMessages); + + // If you hit this assertion then either the caller called the double + // precision version of processBlock on a processor which does not support it + // (i.e. supportsDoublePrecisionProcessing() returns false), or the implementation + // of the AudioProcessor forgot to override the double precision version of this method + jassertfalse; +} + +void AudioProcessor::setProcessingPrecision (ProcessingPrecision precision) noexcept +{ + // If you hit this assertion then you're trying to use double precision + // processing on a processor which does not support it! + jassert (precision != doublePrecision || supportsDoublePrecisionProcessing()); + + processingPrecision = precision; +} + +bool AudioProcessor::supportsDoublePrecisionProcessing() const +{ + return false; +} //============================================================================== void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 226d927f49..c1df51e409 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -48,6 +48,14 @@ protected: AudioProcessor(); public: + //============================================================================== + enum ProcessingPrecision + { + singlePrecision, + doublePrecision + }; + + //============================================================================== /** Destructor. */ virtual ~AudioProcessor(); @@ -125,9 +133,63 @@ public: processBlock() method to send out an asynchronous message. You could also use the AsyncUpdater class in a similar way. */ - virtual void processBlock (AudioSampleBuffer& buffer, + virtual void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) = 0; + /** Renders the next block. + + When this method is called, the buffer contains a number of channels which is + at least as great as the maximum number of input and output channels that + this filter is using. It will be filled with the filter's input data and + should be replaced with the filter's output. + + So for example if your filter has 2 input channels and 4 output channels, then + the buffer will contain 4 channels, the first two being filled with the + input data. Your filter should read these, do its processing, and replace + the contents of all 4 channels with its output. + + Or if your filter has 5 inputs and 2 outputs, the buffer will have 5 channels, + all filled with data, and your filter should overwrite the first 2 of these + with its output. But be VERY careful not to write anything to the last 3 + channels, as these might be mapped to memory that the host assumes is read-only! + + Note that if you have more outputs than inputs, then only those channels that + correspond to an input channel are guaranteed to contain sensible data - e.g. + in the case of 2 inputs and 4 outputs, the first two channels contain the input, + but the last two channels may contain garbage, so you should be careful not to + let this pass through without being overwritten or cleared. + + Also note that the buffer may have more channels than are strictly necessary, + but you should only read/write from the ones that your filter is supposed to + be using. + + The number of samples in these buffers is NOT guaranteed to be the same for every + callback, and may be more or less than the estimated value given to prepareToPlay(). + Your code must be able to cope with variable-sized blocks, or you're going to get + clicks and crashes! + + Also note that some hosts will occasionally decide to pass a buffer containing + zero samples, so make sure that your algorithm can deal with that! + + If the filter is receiving a midi input, then the midiMessages array will be filled + with the midi messages for this block. Each message's timestamp will indicate the + message's time, as a number of samples from the start of the block. + + Any messages left in the midi buffer when this method has finished are assumed to + be the filter's midi output. This means that your filter should be careful to + clear any incoming messages from the array if it doesn't want them to be passed-on. + + Be very careful about what you do in this callback - it's going to be called by + the audio thread, so any kind of interaction with the UI is absolutely + out of the question. If you change a parameter in here and need to tell your UI to + update itself, the best way is probably to inherit from a ChangeBroadcaster, let + the UI components register as listeners, and then call sendChangeMessage() inside the + processBlock() method to send out an asynchronous message. You could also use + the AsyncUpdater class in a similar way. + */ + virtual void processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages); + /** Renders the next block when the processor is being bypassed. The default implementation of this method will pass-through any incoming audio, but you may override this method e.g. to add latency compensation to the data to match @@ -136,9 +198,57 @@ public: Another use for this method would be to cross-fade or morph between the wet (not bypassed) and dry (bypassed) signals. */ - virtual void processBlockBypassed (AudioSampleBuffer& buffer, + virtual void processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midiMessages); + /** Renders the next block when the processor is being bypassed. + The default implementation of this method will pass-through any incoming audio, but + you may override this method e.g. to add latency compensation to the data to match + the processor's latency characteristics. This will avoid situations where bypassing + will shift the signal forward in time, possibly creating pre-echo effects and odd timings. + Another use for this method would be to cross-fade or morph between the wet (not bypassed) + and dry (bypassed) signals. + */ + virtual void processBlockBypassed (AudioBuffer& buffer, + MidiBuffer& midiMessages); + + //============================================================================== + /** Returns true if the Audio processor supports double precision floating point processing. + The default implementation will always return false. + If you return true here then you must override the double precision versions + of processBlock. Additionally, you must call getProcessingPrecision() in + your prepareToPlay method to determine the precision with which you need to + allocate your internal buffers. + @see getProcessingPrecision, setProcessingPrecision + */ + virtual bool supportsDoublePrecisionProcessing() const; + + /** Returns the precision-mode of the processor. + Depending on the result of this method you MUST call the corresponding version + of processBlock. The default processing precision is single precision. + @see setProcessingPrecision, supportsDoublePrecisionProcessing + */ + ProcessingPrecision getProcessingPrecision() const noexcept { return processingPrecision; } + + /** Returns true if the current precision is set to doublePrecision. */ + bool isUsingDoublePrecision() const noexcept { return processingPrecision == doublePrecision; } + + /** Changes the processing precision of the receiver. A client of the AudioProcessor + calls this function to indicate which version of processBlock (single or double + precision) it intends to call. The client MUST call this function before calling + the prepareToPlay method so that the receiver can do any necessary allocations + in the prepareToPlay() method. An implementation of prepareToPlay() should call + getProcessingPrecision() to determine with which precision it should allocate + it's internal buffers. + + Note that setting the processing precision to double floating point precision + on a receiver which does not support double precision processing (i.e. + supportsDoublePrecisionProcessing() returns false) will result in an assertion. + + @see getProcessingPrecision, supportsDoublePrecisionProcessing + */ + void setProcessingPrecision (ProcessingPrecision precision) noexcept; + //============================================================================== /** Returns the current AudioPlayHead object that should be used to find out the state and position of the playhead. @@ -731,6 +841,7 @@ private: double sampleRate; int blockSize, numInputChannels, numOutputChannels, latencySamples; bool suspended, nonRealtime; + ProcessingPrecision processingPrecision; CriticalSection callbackLock, listenerLock; String inputSpeakerArrangement, outputSpeakerArrangement; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index d4a672c7fe..5d5afec54d 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -24,29 +24,82 @@ const int AudioProcessorGraph::midiChannelIndex = 0x1000; +//============================================================================== +template struct FloatDoubleUtil {}; +template struct FloatDoubleType {}; + +template +struct FloatAndDoubleComposition +{ + typedef typename FloatDoubleType::Type FloatType; + typedef typename FloatDoubleType::Type DoubleType; + + template + inline typename FloatDoubleType::Type& get() noexcept + { + return FloatDoubleUtil >::get (*this); + } + + FloatType floatVersion; + DoubleType doubleVersion; +}; + +template struct FloatDoubleUtil { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } }; +template struct FloatDoubleUtil { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } }; + +struct FloatPlaceholder; + +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef AudioBuffer Type; }; +template struct FloatDoubleType*, FloatingType> { typedef AudioBuffer* Type; }; + //============================================================================== namespace GraphRenderingOps { -//============================================================================== -struct AudioGraphRenderingOp +struct AudioGraphRenderingOpBase { - AudioGraphRenderingOp() noexcept {} - virtual ~AudioGraphRenderingOp() {} + AudioGraphRenderingOpBase() noexcept {} + virtual ~AudioGraphRenderingOpBase() {} - virtual void perform (AudioSampleBuffer& sharedBufferChans, + virtual void perform (AudioBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) = 0; - JUCE_LEAK_DETECTOR (AudioGraphRenderingOp) + virtual void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) = 0; + + JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase) +}; + +// use CRTP +template +struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase +{ + void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } + + void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } }; //============================================================================== -struct ClearChannelOp : public AudioGraphRenderingOp +struct ClearChannelOp : public AudioGraphRenderingOp { ClearChannelOp (const int channel) noexcept : channelNum (channel) {} - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { sharedBufferChans.clear (channelNum, 0, numSamples); } @@ -57,13 +110,14 @@ struct ClearChannelOp : public AudioGraphRenderingOp }; //============================================================================== -struct CopyChannelOp : public AudioGraphRenderingOp +struct CopyChannelOp : public AudioGraphRenderingOp { CopyChannelOp (const int srcChan, const int dstChan) noexcept : srcChannelNum (srcChan), dstChannelNum (dstChan) {} - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } @@ -74,13 +128,14 @@ struct CopyChannelOp : public AudioGraphRenderingOp }; //============================================================================== -struct AddChannelOp : public AudioGraphRenderingOp +struct AddChannelOp : public AudioGraphRenderingOp { AddChannelOp (const int srcChan, const int dstChan) noexcept : srcChannelNum (srcChan), dstChannelNum (dstChan) {} - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } @@ -91,11 +146,12 @@ struct AddChannelOp : public AudioGraphRenderingOp }; //============================================================================== -struct ClearMidiBufferOp : public AudioGraphRenderingOp +struct ClearMidiBufferOp : public AudioGraphRenderingOp { ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} - void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) { sharedMidiBuffers.getUnchecked (bufferNum)->clear(); } @@ -106,13 +162,14 @@ struct ClearMidiBufferOp : public AudioGraphRenderingOp }; //============================================================================== -struct CopyMidiBufferOp : public AudioGraphRenderingOp +struct CopyMidiBufferOp : public AudioGraphRenderingOp { CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) {} - void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) { *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); } @@ -123,13 +180,14 @@ struct CopyMidiBufferOp : public AudioGraphRenderingOp }; //============================================================================== -struct AddMidiBufferOp : public AudioGraphRenderingOp +struct AddMidiBufferOp : public AudioGraphRenderingOp { AddMidiBufferOp (const int srcBuffer, const int dstBuffer) : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) {} - void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) { sharedMidiBuffers.getUnchecked (dstBufferNum) ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); @@ -141,24 +199,27 @@ struct AddMidiBufferOp : public AudioGraphRenderingOp }; //============================================================================== -struct DelayChannelOp : public AudioGraphRenderingOp +struct DelayChannelOp : public AudioGraphRenderingOp { DelayChannelOp (const int chan, const int delaySize) : channel (chan), bufferSize (delaySize + 1), readIndex (0), writeIndex (delaySize) { - buffer.calloc ((size_t) bufferSize); + buffer.floatVersion. calloc ((size_t) bufferSize); + buffer.doubleVersion.calloc ((size_t) bufferSize); } - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { - float* data = sharedBufferChans.getWritePointer (channel, 0); + FloatType* data = sharedBufferChans.getWritePointer (channel, 0); + HeapBlock& block = buffer.get(); for (int i = numSamples; --i >= 0;) { - buffer [writeIndex] = *data; - *data++ = buffer [readIndex]; + block [writeIndex] = *data; + *data++ = block [readIndex]; if (++readIndex >= bufferSize) readIndex = 0; if (++writeIndex >= bufferSize) writeIndex = 0; @@ -166,41 +227,67 @@ struct DelayChannelOp : public AudioGraphRenderingOp } private: - HeapBlock buffer; + FloatAndDoubleComposition > buffer; const int channel, bufferSize; int readIndex, writeIndex; JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) }; - //============================================================================== -struct ProcessBufferOp : public AudioGraphRenderingOp +struct ProcessBufferOp : public AudioGraphRenderingOp { ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, - const Array& audioChannels, + const Array& audioChannelsUsed, const int totalNumChans, const int midiBuffer) : node (n), processor (n->getProcessor()), - audioChannelsToUse (audioChannels), + audioChannelsToUse (audioChannelsUsed), totalChans (jmax (1, totalNumChans)), midiBufferToUse (midiBuffer) { - channels.calloc ((size_t) totalChans); + audioChannels.floatVersion. calloc ((size_t) totalChans); + audioChannels.doubleVersion.calloc ((size_t) totalChans); while (audioChannelsToUse.size() < totalChans) audioChannelsToUse.add (0); } - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) { + HeapBlock& channels = audioChannels.get(); + for (int i = totalChans; --i >= 0;) channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); - AudioSampleBuffer buffer (channels, totalChans, numSamples); + AudioBuffer buffer (channels, totalChans, numSamples); - processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + } + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + processor->processBlock (buffer, midiMessages); + } + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + if (processor->isUsingDoublePrecision()) + { + processor->processBlock (buffer, midiMessages); + } + else + { + // if the processor is in single precision mode but the graph in double + // precision then we need to convert between buffer formats. Note, that + // this will only happen if the processor does not support double + // precision processing. + tempBuffer.makeCopyOf (buffer); + processor->processBlock (tempBuffer, midiMessages); + buffer.makeCopyOf (tempBuffer); + } } const AudioProcessorGraph::Node::Ptr node; @@ -208,7 +295,8 @@ struct ProcessBufferOp : public AudioGraphRenderingOp private: Array audioChannelsToUse; - HeapBlock channels; + FloatAndDoubleComposition > audioChannels; + AudioBuffer tempBuffer; const int totalChans; const int midiBufferToUse; @@ -861,13 +949,17 @@ AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) n } void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, - AudioProcessorGraph* const graph) + AudioProcessorGraph* const graph, ProcessingPrecision precision) { if (! isPrepared) { isPrepared = true; setParentGraph (graph); + // try to align the precision of the processor and the graph + processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision + : singlePrecision); + processor->setPlayConfigDetails (processor->getNumInputChannels(), processor->getNumOutputChannels(), newSampleRate, newBlockSize); @@ -892,10 +984,53 @@ void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph ioProc->setParentGraph (graph); } +//============================================================================== +struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers +{ + AudioProcessorGraphBufferHelpers() + { + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + } + + void setRenderingBufferSize (int newNumChannels, int newNumSamples) + { + renderingBuffers.floatVersion. setSize (newNumChannels, newNumSamples); + renderingBuffers.doubleVersion.setSize (newNumChannels, newNumSamples); + + renderingBuffers.floatVersion. clear(); + renderingBuffers.doubleVersion.clear(); + } + + void release() + { + renderingBuffers.floatVersion. setSize (1, 1); + renderingBuffers.doubleVersion.setSize (1, 1); + + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + + currentAudioOutputBuffer.floatVersion. setSize (1, 1); + currentAudioOutputBuffer.doubleVersion.setSize (1, 1); + } + + void prepareInOutBuffers(int newNumChannels, int newNumSamples) + { + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + + currentAudioOutputBuffer.floatVersion. setSize (newNumChannels, newNumSamples); + currentAudioOutputBuffer.doubleVersion.setSize (newNumChannels, newNumSamples); + } + + FloatAndDoubleComposition > renderingBuffers; + FloatAndDoubleComposition*> currentAudioInputBuffer; + FloatAndDoubleComposition > currentAudioOutputBuffer; +}; + //============================================================================== AudioProcessorGraph::AudioProcessorGraph() - : lastNodeId (0), - currentAudioInputBuffer (nullptr), + : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), currentMidiInputBuffer (nullptr) { } @@ -1140,7 +1275,7 @@ bool AudioProcessorGraph::removeIllegalConnections() static void deleteRenderOpArray (Array& ops) { for (int i = ops.size(); --i >= 0;) - delete static_cast (ops.getUnchecked(i)); + delete static_cast (ops.getUnchecked(i)); } void AudioProcessorGraph::clearRenderingSequence() @@ -1193,7 +1328,7 @@ void AudioProcessorGraph::buildRenderingSequence() { Node* const node = nodes.getUnchecked(i); - node->prepare (getSampleRate(), getBlockSize(), this); + node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); int j = 0; for (; j < orderedNodes.size(); ++j) @@ -1214,8 +1349,7 @@ void AudioProcessorGraph::buildRenderingSequence() // swap over to the new rendering sequence.. const ScopedLock sl (getCallbackLock()); - renderingBuffers.setSize (numRenderingBuffersNeeded, getBlockSize()); - renderingBuffers.clear(); + audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); for (int i = midiBuffers.size(); --i >= 0;) midiBuffers.getUnchecked(i)->clear(); @@ -1238,8 +1372,8 @@ void AudioProcessorGraph::handleAsyncUpdate() //============================================================================== void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) { - currentAudioInputBuffer = nullptr; - currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); + audioBuffers->prepareInOutBuffers (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); + currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); @@ -1247,16 +1381,19 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam buildRenderingSequence(); } +bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const +{ + return true; +} + void AudioProcessorGraph::releaseResources() { for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->unprepare(); - renderingBuffers.setSize (1, 1); + audioBuffers->release(); midiBuffers.clear(); - currentAudioInputBuffer = nullptr; - currentAudioOutputBuffer.setSize (1, 1); currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); } @@ -1287,8 +1424,13 @@ void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); } -void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) +template +void AudioProcessorGraph::processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) { + AudioBuffer& renderingBuffers = audioBuffers->renderingBuffers.get(); + AudioBuffer*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer.get(); + AudioBuffer& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get(); + const int numSamples = buffer.getNumSamples(); currentAudioInputBuffer = &buffer; @@ -1299,8 +1441,8 @@ void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& m for (int i = 0; i < renderingOps.size(); ++i) { - GraphRenderingOps::AudioGraphRenderingOp* const op - = (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); + GraphRenderingOps::AudioGraphRenderingOpBase* const op + = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); op->perform (renderingBuffers, midiBuffers, numSamples); } @@ -1331,6 +1473,21 @@ bool AudioProcessorGraph::producesMidi() const { return tru void AudioProcessorGraph::getStateInformation (juce::MemoryBlock&) {} void AudioProcessorGraph::setStateInformation (const void*, int) {} +void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +// explicit template instantiation +template void AudioProcessorGraph::processAudio ( AudioBuffer& buffer, + MidiBuffer& midiMessages); +template void AudioProcessorGraph::processAudio (AudioBuffer& buffer, + MidiBuffer& midiMessages); //============================================================================== AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) @@ -1384,19 +1541,31 @@ void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() { } -void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, +bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessing() const +{ + return true; +} + +template +void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) { + AudioBuffer*& currentAudioInputBuffer = + graph->audioBuffers->currentAudioInputBuffer.get(); + + AudioBuffer& currentAudioOutputBuffer = + graph->audioBuffers->currentAudioOutputBuffer.get(); + jassert (graph != nullptr); switch (type) { case audioOutputNode: { - for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(), + for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), buffer.getNumChannels()); --i >= 0;) { - graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); + currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); } break; @@ -1404,10 +1573,10 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer case audioInputNode: { - for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(), + for (int i = jmin (currentAudioInputBuffer->getNumChannels(), buffer.getNumChannels()); --i >= 0;) { - buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples()); + buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); } break; @@ -1426,6 +1595,18 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer } } +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + bool AudioProcessorGraph::AudioGraphIOProcessor::silenceInProducesSilenceOut() const { return isOutput(); diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 5ffba3c638..3fa03ea041 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -25,7 +25,6 @@ #ifndef JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED #define JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED - //============================================================================== /** A type of AudioProcessor which plays back a graph of other AudioProcessors. @@ -92,7 +91,7 @@ public: Node (uint32 nodeId, AudioProcessor*) noexcept; void setParentGraph (AudioProcessorGraph*) const; - void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*); + void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*, ProcessingPrecision); void unprepare(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) @@ -308,7 +307,9 @@ public: void fillInPluginDescription (PluginDescription&) const override; void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; void releaseResources() override; - void processBlock (AudioSampleBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer& , MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + bool supportsDoublePrecisionProcessing() const override; const String getInputChannelName (int channelIndex) const override; const String getOutputChannelName (int channelIndex) const override; @@ -338,6 +339,10 @@ public: const IODeviceType type; AudioProcessorGraph* graph; + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) }; @@ -345,7 +350,9 @@ public: const String getName() const override; void prepareToPlay (double, int) override; void releaseResources() override; - void processBlock (AudioSampleBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + bool supportsDoublePrecisionProcessing() const override; void reset() override; void setNonRealtime (bool) noexcept override; @@ -372,17 +379,21 @@ public: void setStateInformation (const void* data, int sizeInBytes) override; private: + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + //============================================================================== ReferenceCountedArray nodes; OwnedArray connections; uint32 lastNodeId; - AudioSampleBuffer renderingBuffers; OwnedArray midiBuffers; Array renderingOps; friend class AudioGraphIOProcessor; - AudioSampleBuffer* currentAudioInputBuffer; - AudioSampleBuffer currentAudioOutputBuffer; + struct AudioProcessorGraphBufferHelpers; + ScopedPointer audioBuffers; + MidiBuffer* currentMidiInputBuffer; MidiBuffer currentMidiOutputBuffer; diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp index a8bfc440c4..8d25c1a9f0 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp @@ -22,11 +22,12 @@ ============================================================================== */ -AudioProcessorPlayer::AudioProcessorPlayer() +AudioProcessorPlayer::AudioProcessorPlayer(bool doDoublePrecisionProcessing) : processor (nullptr), sampleRate (0), blockSize (0), isPrepared (false), + isDoublePrecision (doDoublePrecisionProcessing), numInputChans (0), numOutputChans (0) { @@ -45,6 +46,12 @@ void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0) { processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize); + + const bool supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision; + AudioProcessor::ProcessingPrecision precision = supportsDouble ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision; + + processorToPlay->setProcessingPrecision (precision); processorToPlay->prepareToPlay (sampleRate, blockSize); } @@ -62,6 +69,28 @@ void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) } } +void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision) +{ + if (doublePrecision != isDoublePrecision) + { + const ScopedLock sl (lock); + + if (processor != nullptr) + { + processor->releaseResources(); + + const bool supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision; + AudioProcessor::ProcessingPrecision precision = supportsDouble ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision; + + processor->setProcessingPrecision (precision); + processor->prepareToPlay (sampleRate, blockSize); + } + + isDoublePrecision = doublePrecision; + } +} + //============================================================================== void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData, const int numInputChannels, @@ -126,7 +155,17 @@ void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChann if (! processor->isSuspended()) { - processor->processBlock (buffer, incomingMidi); + if (processor->isUsingDoublePrecision()) + { + conversionBuffer.makeCopyOf (buffer); + processor->processBlock (conversionBuffer, incomingMidi); + buffer.makeCopyOf (conversionBuffer); + } + else + { + processor->processBlock (buffer, incomingMidi); + } + return; } } diff --git a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h index ee729fe476..9225c95c4e 100644 --- a/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h +++ b/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h @@ -25,7 +25,6 @@ #ifndef JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED #define JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED - //============================================================================== /** An AudioIODeviceCallback object which streams audio through an AudioProcessor. @@ -43,7 +42,7 @@ class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback, { public: //============================================================================== - AudioProcessorPlayer(); + AudioProcessorPlayer(bool doDoublePrecisionProcessing = false); /** Destructor. */ virtual ~AudioProcessorPlayer(); @@ -65,6 +64,20 @@ public: */ MidiMessageCollector& getMidiMessageCollector() noexcept { return messageCollector; } + /** Switch between double and single floating point precisions processing. + The audio IO callbacks will still operate in single floating point + precision, however, all internal processing including the + AudioProcessor will be processed in double floating point precision if + the AudioProcessor supports it (see + AudioProcessor::supportsDoublePrecisionProcessing()). + Otherwise, the processing will remain single precision irrespective of + the parameter doublePrecision. */ + void setDoublePrecisionProcessing (bool doublePrecision); + + /** Returns true if this player processes internally processes the samples with + double floating point precision. */ + inline bool getDoublePrecisionProcessing() { return isDoublePrecision; } + //============================================================================== /** @internal */ void audioDeviceIOCallback (const float**, int, float**, int, int) override; @@ -81,11 +94,12 @@ private: CriticalSection lock; double sampleRate; int blockSize; - bool isPrepared; + bool isPrepared, isDoublePrecision; int numInputChans, numOutputChans; HeapBlock channels; - AudioSampleBuffer tempBuffer; + AudioBuffer tempBuffer; + AudioBuffer conversionBuffer; MidiBuffer incomingMidi; MidiMessageCollector messageCollector;