mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
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
This commit is contained in:
parent
ba672f03fb
commit
c562cfc3cc
27 changed files with 1713 additions and 1104 deletions
|
|
@ -85,7 +85,20 @@ public:
|
|||
// not interested in controllers in this case.
|
||||
}
|
||||
|
||||
void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override
|
||||
void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
|
||||
{
|
||||
processBlock (outputBuffer, startSample, numSamples);
|
||||
}
|
||||
|
||||
void renderNextBlock (AudioBuffer<double>& outputBuffer, int startSample, int numSamples) override
|
||||
{
|
||||
processBlock (outputBuffer, startSample, numSamples);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <typename FloatType>
|
||||
void processBlock (AudioBuffer<FloatType>& 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<FloatType> (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<FloatType> (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 <typename FloatType>
|
||||
void JuceDemoPluginAudioProcessor::process (AudioBuffer<FloatType>& buffer,
|
||||
MidiBuffer& midiMessages,
|
||||
AudioBuffer<FloatType>& 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<FloatType> (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<FloatType> (delay->getValue());
|
||||
|
||||
if (++dp >= delayBuffer.getNumSamples())
|
||||
dp = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<float>& buffer, MidiBuffer& midiMessages) override
|
||||
{
|
||||
jassert (! isUsingDoublePrecision());
|
||||
process (buffer, midiMessages, delayBufferFloat);
|
||||
}
|
||||
|
||||
void processBlock (AudioBuffer<double>& 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 <typename floatType>
|
||||
void process (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages, AudioBuffer<floatType>& delayBuffer);
|
||||
|
||||
AudioBuffer<float> delayBufferFloat;
|
||||
AudioBuffer<double> delayBufferDouble;
|
||||
int delayPosition;
|
||||
|
||||
// the synth!
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ public:
|
|||
|
||||
//==============================================================================
|
||||
void createNewPlugin (const PluginDescription* desc, int x, int y);
|
||||
inline void setDoublePrecision (bool doublePrecision) { graphPlayer.setDoublePrecisionProcessing (doublePrecision); }
|
||||
|
||||
//==============================================================================
|
||||
FilterGraph graph;
|
||||
|
|
|
|||
|
|
@ -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 <CommandID>& 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 <GraphDocumentComponent*> (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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<float**> (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<float**> (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<float**> (preallocatedChannelSpace);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*));
|
||||
channels = reinterpret_cast<float**> (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<char, true> newData;
|
||||
newData.allocate (newTotalBytes, clearExtraSpace || isClear);
|
||||
|
||||
const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size);
|
||||
|
||||
float** const newChannels = reinterpret_cast<float**> (newData.getData());
|
||||
float* newChan = reinterpret_cast<float*> (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<float**> (allocatedData.getData());
|
||||
}
|
||||
|
||||
float* chan = reinterpret_cast<float*> (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<float> 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<float>();
|
||||
|
||||
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<float> 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);
|
||||
}
|
||||
|
|
@ -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 <typename Type>
|
||||
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<Type**> (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<const float**> (channels); }
|
||||
const Type** getArrayOfReadPointers() const noexcept { return const_cast<const Type**> (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<char, true> newData;
|
||||
newData.allocate (newTotalBytes, clearExtraSpace || isClear);
|
||||
|
||||
const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size);
|
||||
|
||||
Type** const newChannels = reinterpret_cast<Type**> (newData.getData());
|
||||
Type* newChan = reinterpret_cast<Type*> (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<Type**> (allocatedData.getData());
|
||||
}
|
||||
|
||||
Type* chan = reinterpret_cast<Type*> (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 <typename OtherType>
|
||||
void makeCopyOf (const AudioBuffer<OtherType>& 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<Type> (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<float> findMinMax (int channel,
|
||||
int startSample,
|
||||
int numSamples) const noexcept;
|
||||
Range<Type> findMinMax (int channel,
|
||||
int startSample,
|
||||
int numSamples) const noexcept
|
||||
{
|
||||
jassert (isPositiveAndBelow (channel, numChannels));
|
||||
jassert (startSample >= 0 && startSample + numSamples <= size);
|
||||
|
||||
if (isClear)
|
||||
return Range<Type>();
|
||||
|
||||
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<Type> 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<char, true> 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<Type**> (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<Type**> (preallocatedChannelSpace);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocatedData.malloc ((size_t) numChannels + 1, sizeof (Type*));
|
||||
channels = reinterpret_cast<Type**> (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<float> AudioSampleBuffer;
|
||||
|
||||
|
||||
#endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -71,6 +71,18 @@ bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const no
|
|||
return noteOnTime < other.noteOnTime;
|
||||
}
|
||||
|
||||
void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer,
|
||||
int startSample, int numSamples)
|
||||
{
|
||||
AudioBuffer<double> 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 <typename floatType>
|
||||
void Synthesiser::processNextBlock (AudioBuffer<floatType>& 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<float> (AudioBuffer<float>& outputAudio,
|
||||
const MidiBuffer& midiData,
|
||||
int startSample,
|
||||
int numSamples);
|
||||
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>& outputAudio,
|
||||
const MidiBuffer& midiData,
|
||||
int startSample,
|
||||
int numSamples);
|
||||
|
||||
void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples)
|
||||
{
|
||||
for (int i = voices.size(); --i >= 0;)
|
||||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples);
|
||||
}
|
||||
|
||||
void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples)
|
||||
{
|
||||
for (int i = voices.size(); --i >= 0;)
|
||||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples);
|
||||
|
|
|
|||
|
|
@ -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<float>& outputBuffer,
|
||||
int startSample,
|
||||
int numSamples) = 0;
|
||||
virtual void renderNextBlock (AudioBuffer<double>& 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<float> 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<float>& outputAudio,
|
||||
const MidiBuffer& inputMidi,
|
||||
int startSample,
|
||||
int numSamples);
|
||||
int numSamples)
|
||||
{ processNextBlock (outputAudio, inputMidi, startSample, numSamples); }
|
||||
|
||||
inline void renderNextBlock (AudioBuffer<double>& 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<float>& outputAudio,
|
||||
int startSample, int numSamples);
|
||||
virtual void renderVoices (AudioBuffer<double>& 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 <typename floatType>
|
||||
void processNextBlock (AudioBuffer<floatType>& outputAudio,
|
||||
const MidiBuffer& inputMidi,
|
||||
int startSample,
|
||||
int numSamples);
|
||||
//==============================================================================
|
||||
double sampleRate;
|
||||
uint32 lastNoteOnCounter;
|
||||
int minimumSubBlockSize;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -243,6 +243,27 @@ class JuceVSTWrapper : public AudioEffectX,
|
|||
private Timer,
|
||||
private AsyncUpdater
|
||||
{
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename FloatType>
|
||||
struct VstTempBuffers
|
||||
{
|
||||
VstTempBuffers() {}
|
||||
~VstTempBuffers() { release(); }
|
||||
|
||||
void release() noexcept
|
||||
{
|
||||
for (int i = tempChannels.size(); --i >= 0;)
|
||||
delete[] (tempChannels.getUnchecked(i));
|
||||
|
||||
tempChannels.clear();
|
||||
}
|
||||
|
||||
HeapBlock<FloatType*> channels;
|
||||
Array<FloatType*> tempChannels; // see note in processReplacing()
|
||||
juce::AudioBuffer<FloatType> 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<float>& 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 <typename FloatType>
|
||||
void internalProcessReplacing (FloatType** inputs, FloatType** outputs,
|
||||
VstInt32 numSamples, VstTempBuffers<FloatType>& 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<FloatType> 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<float*> channels;
|
||||
Array<float*> tempChannels; // see note in processReplacing()
|
||||
AudioSampleBuffer processTempBuffer;
|
||||
VstTempBuffers<float> floatTempBuffers;
|
||||
VstTempBuffers<double> doubleTempBuffers;
|
||||
|
||||
#if JUCE_MAC
|
||||
void* hostWindow;
|
||||
|
|
@ -1517,15 +1572,22 @@ private:
|
|||
#endif
|
||||
|
||||
//==============================================================================
|
||||
void deleteTempChannels()
|
||||
template <typename FloatType>
|
||||
void deleteTempChannels (VstTempBuffers<FloatType>& 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)
|
||||
|
|
|
|||
|
|
@ -743,6 +743,14 @@ private:
|
|||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController)
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename FloatType> struct AudioBusPointerHelper {};
|
||||
template <> struct AudioBusPointerHelper<float> { static inline float** impl (Vst::AudioBusBuffers& data) noexcept { return data.channelBuffers32; } };
|
||||
template <> struct AudioBusPointerHelper<double> { 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<float> (data, channelListFloat);
|
||||
else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio<double> (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<float*> channelList;
|
||||
Array<float*> channelListFloat;
|
||||
Array<double*> channelListDouble;
|
||||
|
||||
ScopedJuceInitialiser_GUI libraryInitialiser;
|
||||
|
||||
|
|
@ -1656,6 +1634,52 @@ private:
|
|||
|
||||
static const char* kJucePrivateDataIdentifier;
|
||||
|
||||
//==============================================================================
|
||||
template <typename FloatType>
|
||||
void processAudio (Vst::ProcessData& data, Array<FloatType*>& channelList)
|
||||
{
|
||||
const int totalChans = prepareChannelLists (channelList, data);
|
||||
|
||||
AudioBuffer<FloatType> 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<FloatType> (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<FloatType> (data.outputs[i])[f], (int) data.numSamples);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void addBusTo (Vst::BusList& busList, Vst::Bus* newBus)
|
||||
{
|
||||
|
|
@ -1680,6 +1704,50 @@ private:
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename FloatType>
|
||||
void allocateChannelLists (Array<FloatType*>& channelList)
|
||||
{
|
||||
channelList.clear();
|
||||
channelList.insertMultiple (0, nullptr, jmax (JucePlugin_MaxNumInputChannels, JucePlugin_MaxNumOutputChannels) + 1);
|
||||
}
|
||||
|
||||
template <typename FloatType>
|
||||
static FloatType** getPointerForAudioBus (Vst::AudioBusBuffers& data) noexcept
|
||||
{
|
||||
return AudioBusPointerHelper<FloatType>::impl (data);
|
||||
}
|
||||
|
||||
template <typename FloatType>
|
||||
static int prepareChannelLists (Array<FloatType*>& channelList, Vst::ProcessData& data) noexcept
|
||||
{
|
||||
int totalChans = 0;
|
||||
FloatType** inChannelBuffers =
|
||||
data.inputs != nullptr ? getPointerForAudioBus<FloatType> (data.inputs[0]) : nullptr;
|
||||
|
||||
FloatType** outChannelBuffers =
|
||||
data.outputs != nullptr ? getPointerForAudioBus<FloatType> (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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -344,21 +344,25 @@ private:
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
namespace VST3BufferExchange
|
||||
template <typename FloatType>
|
||||
struct VST3BufferExchange
|
||||
{
|
||||
typedef Array<float*> Bus;
|
||||
typedef Array<FloatType*> Bus;
|
||||
typedef Array<Bus> 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<FloatType>& 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<Steinberg::Vst::AudioBusBuffers>& result,
|
||||
BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement,
|
||||
AudioSampleBuffer& source)
|
||||
AudioBuffer<FloatType>& 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<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse,
|
||||
const Array<Steinberg::Vst::SpeakerArrangement>& arrangements,
|
||||
AudioSampleBuffer& source)
|
||||
static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse,
|
||||
const Array<Steinberg::Vst::SpeakerArrangement>& arrangements,
|
||||
AudioBuffer<FloatType>& source)
|
||||
{
|
||||
int channelIndexOffset = 0;
|
||||
|
||||
|
|
@ -407,10 +409,10 @@ namespace VST3BufferExchange
|
|||
arrangements.getUnchecked (i), source);
|
||||
}
|
||||
|
||||
inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result,
|
||||
Steinberg::Vst::IAudioProcessor& processor,
|
||||
BusMap& busMapToUse, bool isInput, int numBusses,
|
||||
AudioSampleBuffer& source)
|
||||
static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result,
|
||||
Steinberg::Vst::IAudioProcessor& processor,
|
||||
BusMap& busMapToUse, bool isInput, int numBusses,
|
||||
AudioBuffer<FloatType>& source)
|
||||
{
|
||||
int channelIndexOffset = 0;
|
||||
|
||||
|
|
@ -420,6 +422,28 @@ namespace VST3BufferExchange
|
|||
getArrangementForBus (&processor, isInput, i),
|
||||
source);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FloatType>
|
||||
struct VST3FloatAndDoubleBusMapCompositeHelper {};
|
||||
|
||||
struct VST3FloatAndDoubleBusMapComposite
|
||||
{
|
||||
VST3BufferExchange<float>::BusMap floatVersion;
|
||||
VST3BufferExchange<double>::BusMap doubleVersion;
|
||||
|
||||
template <typename FloatType>
|
||||
inline typename VST3BufferExchange<FloatType>::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper<FloatType>::get (*this); }
|
||||
};
|
||||
|
||||
|
||||
template <> struct VST3FloatAndDoubleBusMapCompositeHelper<float>
|
||||
{
|
||||
static inline VST3BufferExchange<float>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; }
|
||||
};
|
||||
|
||||
template <> struct VST3FloatAndDoubleBusMapCompositeHelper<double>
|
||||
{
|
||||
static inline VST3BufferExchange<double>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; }
|
||||
};
|
||||
#endif // JUCE_VST3COMMON_H_INCLUDED
|
||||
|
|
|
|||
|
|
@ -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<float>& buffer, MidiBuffer& midiMessages) override
|
||||
{
|
||||
jassert (! isUsingDoublePrecision());
|
||||
|
||||
if (isActive && processor != nullptr)
|
||||
processAudio (buffer, midiMessages, Vst::kSample32);
|
||||
}
|
||||
|
||||
void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
|
||||
{
|
||||
jassert (isUsingDoublePrecision());
|
||||
|
||||
if (isActive && processor != nullptr)
|
||||
processAudio (buffer, midiMessages, Vst::kSample64);
|
||||
}
|
||||
|
||||
template <typename FloatType>
|
||||
void processAudio (AudioBuffer<FloatType>& 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<Vst::SpeakerArrangement> inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements().
|
||||
VST3BufferExchange::BusMap inputBusMap, outputBusMap;
|
||||
VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap;
|
||||
Array<Vst::AudioBusBuffers> inputBusses, outputBusses;
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -2392,12 +2409,11 @@ private:
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void associateTo (Vst::ProcessData& destination, AudioSampleBuffer& buffer)
|
||||
template <typename FloatType>
|
||||
void associateTo (Vst::ProcessData& destination, AudioBuffer<FloatType>& buffer)
|
||||
{
|
||||
using namespace VST3BufferExchange;
|
||||
|
||||
mapBufferToBusses (inputBusses, inputBusMap, inputArrangements, buffer);
|
||||
mapBufferToBusses (outputBusses, outputBusMap, outputArrangements, buffer);
|
||||
VST3BufferExchange<FloatType>::mapBufferToBusses (inputBusses, inputBusMap.get<FloatType>(), inputArrangements, buffer);
|
||||
VST3BufferExchange<FloatType>::mapBufferToBusses (outputBusses, outputBusMap.get<FloatType>(), outputArrangements, buffer);
|
||||
|
||||
destination.inputs = inputBusses.getRawDataPointer();
|
||||
destination.outputs = outputBusses.getRawDataPointer();
|
||||
|
|
|
|||
|
|
@ -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<float>& 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<double>& 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<float> tempBuffer;
|
||||
CriticalSection midiInLock;
|
||||
MidiBuffer incomingMidi;
|
||||
VSTMidiEventList midiEventsToSend;
|
||||
VstTimeInfo vstHostTime;
|
||||
|
||||
//==============================================================================
|
||||
template <typename FloatType>
|
||||
void processAudio (AudioBuffer<FloatType>& 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<float>& 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<double>& buffer, VstInt32 sampleFrames)
|
||||
{
|
||||
effect->processDoubleReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<float>&, MidiBuffer&) {}
|
||||
void AudioProcessor::processBlockBypassed (AudioBuffer<double>&, MidiBuffer&) {}
|
||||
|
||||
void AudioProcessor::processBlock (AudioBuffer<double>& 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
|
||||
|
|
|
|||
|
|
@ -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<float>& 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<double>& 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<float>& 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<double>& 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,29 +24,82 @@
|
|||
|
||||
const int AudioProcessorGraph::midiChannelIndex = 0x1000;
|
||||
|
||||
//==============================================================================
|
||||
template <typename FloatType, typename Impl> struct FloatDoubleUtil {};
|
||||
template <typename Tag, typename Type> struct FloatDoubleType {};
|
||||
|
||||
template <typename Tag>
|
||||
struct FloatAndDoubleComposition
|
||||
{
|
||||
typedef typename FloatDoubleType<Tag, float>::Type FloatType;
|
||||
typedef typename FloatDoubleType<Tag, double>::Type DoubleType;
|
||||
|
||||
template <typename FloatingType>
|
||||
inline typename FloatDoubleType<Tag, FloatingType>::Type& get() noexcept
|
||||
{
|
||||
return FloatDoubleUtil<FloatingType, FloatAndDoubleComposition<Tag> >::get (*this);
|
||||
}
|
||||
|
||||
FloatType floatVersion;
|
||||
DoubleType doubleVersion;
|
||||
};
|
||||
|
||||
template <typename Impl> struct FloatDoubleUtil<float, Impl> { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } };
|
||||
template <typename Impl> struct FloatDoubleUtil<double, Impl> { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } };
|
||||
|
||||
struct FloatPlaceholder;
|
||||
|
||||
template <typename FloatingType> struct FloatDoubleType<HeapBlock<FloatPlaceholder>, FloatingType> { typedef HeapBlock<FloatingType> Type; };
|
||||
template <typename FloatingType> struct FloatDoubleType<HeapBlock<FloatPlaceholder*>, FloatingType> { typedef HeapBlock<FloatingType*> Type; };
|
||||
template <typename FloatingType> struct FloatDoubleType<AudioBuffer<FloatPlaceholder>, FloatingType> { typedef AudioBuffer<FloatingType> Type; };
|
||||
template <typename FloatingType> struct FloatDoubleType<AudioBuffer<FloatPlaceholder>*, FloatingType> { typedef AudioBuffer<FloatingType>* Type; };
|
||||
|
||||
//==============================================================================
|
||||
namespace GraphRenderingOps
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
struct AudioGraphRenderingOp
|
||||
struct AudioGraphRenderingOpBase
|
||||
{
|
||||
AudioGraphRenderingOp() noexcept {}
|
||||
virtual ~AudioGraphRenderingOp() {}
|
||||
AudioGraphRenderingOpBase() noexcept {}
|
||||
virtual ~AudioGraphRenderingOpBase() {}
|
||||
|
||||
virtual void perform (AudioSampleBuffer& sharedBufferChans,
|
||||
virtual void perform (AudioBuffer<float>& sharedBufferChans,
|
||||
const OwnedArray<MidiBuffer>& sharedMidiBuffers,
|
||||
const int numSamples) = 0;
|
||||
|
||||
JUCE_LEAK_DETECTOR (AudioGraphRenderingOp)
|
||||
virtual void perform (AudioBuffer<double>& sharedBufferChans,
|
||||
const OwnedArray<MidiBuffer>& sharedMidiBuffers,
|
||||
const int numSamples) = 0;
|
||||
|
||||
JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase)
|
||||
};
|
||||
|
||||
// use CRTP
|
||||
template <class Child>
|
||||
struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase
|
||||
{
|
||||
void perform (AudioBuffer<float>& sharedBufferChans,
|
||||
const OwnedArray<MidiBuffer>& sharedMidiBuffers,
|
||||
const int numSamples) override
|
||||
{
|
||||
static_cast<Child*> (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples);
|
||||
}
|
||||
|
||||
void perform (AudioBuffer<double>& sharedBufferChans,
|
||||
const OwnedArray<MidiBuffer>& sharedMidiBuffers,
|
||||
const int numSamples) override
|
||||
{
|
||||
static_cast<Child*> (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples);
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ClearChannelOp : public AudioGraphRenderingOp
|
||||
struct ClearChannelOp : public AudioGraphRenderingOp<ClearChannelOp>
|
||||
{
|
||||
ClearChannelOp (const int channel) noexcept : channelNum (channel) {}
|
||||
|
||||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, 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>
|
||||
{
|
||||
CopyChannelOp (const int srcChan, const int dstChan) noexcept
|
||||
: srcChannelNum (srcChan), dstChannelNum (dstChan)
|
||||
{}
|
||||
|
||||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, 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>
|
||||
{
|
||||
AddChannelOp (const int srcChan, const int dstChan) noexcept
|
||||
: srcChannelNum (srcChan), dstChannelNum (dstChan)
|
||||
{}
|
||||
|
||||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, 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>
|
||||
{
|
||||
ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {}
|
||||
|
||||
void perform (AudioSampleBuffer&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int)
|
||||
{
|
||||
sharedMidiBuffers.getUnchecked (bufferNum)->clear();
|
||||
}
|
||||
|
|
@ -106,13 +162,14 @@ struct ClearMidiBufferOp : public AudioGraphRenderingOp
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
struct CopyMidiBufferOp : public AudioGraphRenderingOp
|
||||
struct CopyMidiBufferOp : public AudioGraphRenderingOp<CopyMidiBufferOp>
|
||||
{
|
||||
CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept
|
||||
: srcBufferNum (srcBuffer), dstBufferNum (dstBuffer)
|
||||
{}
|
||||
|
||||
void perform (AudioSampleBuffer&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>&, const OwnedArray<MidiBuffer>& 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>
|
||||
{
|
||||
AddMidiBufferOp (const int srcBuffer, const int dstBuffer)
|
||||
: srcBufferNum (srcBuffer), dstBufferNum (dstBuffer)
|
||||
{}
|
||||
|
||||
void perform (AudioSampleBuffer&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int numSamples)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>&, const OwnedArray<MidiBuffer>& 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>
|
||||
{
|
||||
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<MidiBuffer>&, const int numSamples)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples)
|
||||
{
|
||||
float* data = sharedBufferChans.getWritePointer (channel, 0);
|
||||
FloatType* data = sharedBufferChans.getWritePointer (channel, 0);
|
||||
HeapBlock<FloatType>& block = buffer.get<FloatType>();
|
||||
|
||||
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<float> buffer;
|
||||
FloatAndDoubleComposition<HeapBlock<FloatPlaceholder> > buffer;
|
||||
const int channel, bufferSize;
|
||||
int readIndex, writeIndex;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (DelayChannelOp)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
struct ProcessBufferOp : public AudioGraphRenderingOp
|
||||
struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp>
|
||||
{
|
||||
ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n,
|
||||
const Array<int>& audioChannels,
|
||||
const Array<int>& 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<MidiBuffer>& sharedMidiBuffers, const int numSamples)
|
||||
template <typename FloatType>
|
||||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int numSamples)
|
||||
{
|
||||
HeapBlock<FloatType*>& channels = audioChannels.get<FloatType>();
|
||||
|
||||
for (int i = totalChans; --i >= 0;)
|
||||
channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0);
|
||||
|
||||
AudioSampleBuffer buffer (channels, totalChans, numSamples);
|
||||
AudioBuffer<FloatType> buffer (channels, totalChans, numSamples);
|
||||
|
||||
processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse));
|
||||
callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse));
|
||||
}
|
||||
|
||||
void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
|
||||
{
|
||||
processor->processBlock (buffer, midiMessages);
|
||||
}
|
||||
|
||||
void callProcess (AudioBuffer<double>& 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<int> audioChannelsToUse;
|
||||
HeapBlock<float*> channels;
|
||||
FloatAndDoubleComposition<HeapBlock<FloatPlaceholder*> > audioChannels;
|
||||
AudioBuffer<float> 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<AudioBuffer<FloatPlaceholder> > renderingBuffers;
|
||||
FloatAndDoubleComposition<AudioBuffer<FloatPlaceholder>*> currentAudioInputBuffer;
|
||||
FloatAndDoubleComposition<AudioBuffer<FloatPlaceholder> > 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<void*>& ops)
|
||||
{
|
||||
for (int i = ops.size(); --i >= 0;)
|
||||
delete static_cast<GraphRenderingOps::AudioGraphRenderingOp*> (ops.getUnchecked(i));
|
||||
delete static_cast<GraphRenderingOps::AudioGraphRenderingOpBase*> (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 <typename FloatType>
|
||||
void AudioProcessorGraph::processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages)
|
||||
{
|
||||
AudioBuffer<FloatType>& renderingBuffers = audioBuffers->renderingBuffers.get<FloatType>();
|
||||
AudioBuffer<FloatType>*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer.get<FloatType>();
|
||||
AudioBuffer<FloatType>& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get<FloatType>();
|
||||
|
||||
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<float>& buffer, MidiBuffer& midiMessages)
|
||||
{
|
||||
processAudio (buffer, midiMessages);
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages)
|
||||
{
|
||||
processAudio (buffer, midiMessages);
|
||||
}
|
||||
|
||||
// explicit template instantiation
|
||||
template void AudioProcessorGraph::processAudio<float> ( AudioBuffer<float>& buffer,
|
||||
MidiBuffer& midiMessages);
|
||||
template void AudioProcessorGraph::processAudio<double> (AudioBuffer<double>& 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 <typename FloatType>
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer<FloatType>& buffer,
|
||||
MidiBuffer& midiMessages)
|
||||
{
|
||||
AudioBuffer<FloatType>*& currentAudioInputBuffer =
|
||||
graph->audioBuffers->currentAudioInputBuffer.get<FloatType>();
|
||||
|
||||
AudioBuffer<FloatType>& currentAudioOutputBuffer =
|
||||
graph->audioBuffers->currentAudioOutputBuffer.get<FloatType>();
|
||||
|
||||
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<float>& buffer,
|
||||
MidiBuffer& midiMessages)
|
||||
{
|
||||
processAudio (buffer, midiMessages);
|
||||
}
|
||||
|
||||
void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer<double>& buffer,
|
||||
MidiBuffer& midiMessages)
|
||||
{
|
||||
processAudio (buffer, midiMessages);
|
||||
}
|
||||
|
||||
bool AudioProcessorGraph::AudioGraphIOProcessor::silenceInProducesSilenceOut() const
|
||||
{
|
||||
return isOutput();
|
||||
|
|
|
|||
|
|
@ -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<float>& , MidiBuffer&) override;
|
||||
void processBlock (AudioBuffer<double>&, 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 <typename floatType>
|
||||
void processAudio (AudioBuffer<floatType>& 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<float>&, MidiBuffer&) override;
|
||||
void processBlock (AudioBuffer<double>&, 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 <typename floatType>
|
||||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages);
|
||||
|
||||
//==============================================================================
|
||||
ReferenceCountedArray<Node> nodes;
|
||||
OwnedArray<Connection> connections;
|
||||
uint32 lastNodeId;
|
||||
AudioSampleBuffer renderingBuffers;
|
||||
OwnedArray<MidiBuffer> midiBuffers;
|
||||
Array<void*> renderingOps;
|
||||
|
||||
friend class AudioGraphIOProcessor;
|
||||
AudioSampleBuffer* currentAudioInputBuffer;
|
||||
AudioSampleBuffer currentAudioOutputBuffer;
|
||||
struct AudioProcessorGraphBufferHelpers;
|
||||
ScopedPointer<AudioProcessorGraphBufferHelpers> audioBuffers;
|
||||
|
||||
MidiBuffer* currentMidiInputBuffer;
|
||||
MidiBuffer currentMidiOutputBuffer;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<float*> channels;
|
||||
AudioSampleBuffer tempBuffer;
|
||||
AudioBuffer<float> tempBuffer;
|
||||
AudioBuffer<double> conversionBuffer;
|
||||
|
||||
MidiBuffer incomingMidi;
|
||||
MidiMessageCollector messageCollector;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue