git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
436
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp
vendored
Normal file
436
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.cpp
vendored
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
template <typename Value>
|
||||
struct ChannelInfo
|
||||
{
|
||||
ChannelInfo() = default;
|
||||
ChannelInfo (Value** dataIn, int numChannelsIn)
|
||||
: data (dataIn), numChannels (numChannelsIn) {}
|
||||
|
||||
Value** data = nullptr;
|
||||
int numChannels = 0;
|
||||
};
|
||||
|
||||
/** Sets up `channels` so that it contains channel pointers suitable for passing to
|
||||
an AudioProcessor's processBlock.
|
||||
|
||||
On return, `channels` will hold `max (processorIns, processorOuts)` entries.
|
||||
The first `processorIns` entries will point to buffers holding input data.
|
||||
Any entries after the first `processorIns` entries will point to zeroed buffers.
|
||||
|
||||
In the case that the system only provides a single input channel, but the processor
|
||||
has been initialised with multiple input channels, the system input will be copied
|
||||
to all processor inputs.
|
||||
|
||||
In the case that the system provides no input channels, but the processor has
|
||||
been initialise with multiple input channels, the processor's input channels will
|
||||
all be zeroed.
|
||||
|
||||
@param ins the system inputs.
|
||||
@param outs the system outputs.
|
||||
@param numSamples the number of samples in the system buffers.
|
||||
@param processorIns the number of input channels requested by the processor.
|
||||
@param processorOuts the number of output channels requested by the processor.
|
||||
@param tempBuffer temporary storage for inputs that don't have a corresponding output.
|
||||
@param channels holds pointers to each of the processor's audio channels.
|
||||
*/
|
||||
static void initialiseIoBuffers (ChannelInfo<const float> ins,
|
||||
ChannelInfo<float> outs,
|
||||
const int numSamples,
|
||||
int processorIns,
|
||||
int processorOuts,
|
||||
AudioBuffer<float>& tempBuffer,
|
||||
std::vector<float*>& channels)
|
||||
{
|
||||
jassert ((int) channels.size() >= jmax (processorIns, processorOuts));
|
||||
|
||||
size_t totalNumChans = 0;
|
||||
const auto numBytes = (size_t) numSamples * sizeof (float);
|
||||
|
||||
const auto prepareInputChannel = [&] (int index)
|
||||
{
|
||||
if (ins.numChannels == 0)
|
||||
zeromem (channels[totalNumChans], numBytes);
|
||||
else
|
||||
memcpy (channels[totalNumChans], ins.data[index % ins.numChannels], numBytes);
|
||||
};
|
||||
|
||||
if (processorIns > processorOuts)
|
||||
{
|
||||
// If there aren't enough output channels for the number of
|
||||
// inputs, we need to use some temporary extra ones (can't
|
||||
// use the input data in case it gets written to).
|
||||
jassert (tempBuffer.getNumChannels() >= processorIns - processorOuts);
|
||||
jassert (tempBuffer.getNumSamples() >= numSamples);
|
||||
|
||||
for (int i = 0; i < processorOuts; ++i)
|
||||
{
|
||||
channels[totalNumChans] = outs.data[i];
|
||||
prepareInputChannel (i);
|
||||
++totalNumChans;
|
||||
}
|
||||
|
||||
for (auto i = processorOuts; i < processorIns; ++i)
|
||||
{
|
||||
channels[totalNumChans] = tempBuffer.getWritePointer (i - outs.numChannels);
|
||||
prepareInputChannel (i);
|
||||
++totalNumChans;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < processorIns; ++i)
|
||||
{
|
||||
channels[totalNumChans] = outs.data[i];
|
||||
prepareInputChannel (i);
|
||||
++totalNumChans;
|
||||
}
|
||||
|
||||
for (auto i = processorIns; i < processorOuts; ++i)
|
||||
{
|
||||
channels[totalNumChans] = outs.data[i];
|
||||
zeromem (channels[totalNumChans], (size_t) numSamples * sizeof (float));
|
||||
++totalNumChans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorPlayer::AudioProcessorPlayer (bool doDoublePrecisionProcessing)
|
||||
: isDoublePrecision (doDoublePrecisionProcessing)
|
||||
{
|
||||
}
|
||||
|
||||
AudioProcessorPlayer::~AudioProcessorPlayer()
|
||||
{
|
||||
setProcessor (nullptr);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorPlayer::NumChannels AudioProcessorPlayer::findMostSuitableLayout (const AudioProcessor& proc) const
|
||||
{
|
||||
if (proc.isMidiEffect())
|
||||
return {};
|
||||
|
||||
std::vector<NumChannels> layouts { deviceChannels };
|
||||
|
||||
if (deviceChannels.ins == 0 || deviceChannels.ins == 1)
|
||||
{
|
||||
layouts.emplace_back (defaultProcessorChannels.ins, deviceChannels.outs);
|
||||
layouts.emplace_back (deviceChannels.outs, deviceChannels.outs);
|
||||
}
|
||||
|
||||
const auto it = std::find_if (layouts.begin(), layouts.end(), [&] (const NumChannels& chans)
|
||||
{
|
||||
return proc.checkBusesLayoutSupported (chans.toLayout());
|
||||
});
|
||||
|
||||
return it != std::end (layouts) ? *it : layouts[0];
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::resizeChannels()
|
||||
{
|
||||
const auto maxChannels = jmax (deviceChannels.ins,
|
||||
deviceChannels.outs,
|
||||
actualProcessorChannels.ins,
|
||||
actualProcessorChannels.outs);
|
||||
channels.resize ((size_t) maxChannels);
|
||||
tempBuffer.setSize (maxChannels, blockSize);
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (processor == processorToPlay)
|
||||
return;
|
||||
|
||||
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0)
|
||||
{
|
||||
defaultProcessorChannels = NumChannels { processorToPlay->getBusesLayout() };
|
||||
actualProcessorChannels = findMostSuitableLayout (*processorToPlay);
|
||||
|
||||
if (processorToPlay->isMidiEffect())
|
||||
processorToPlay->setRateAndBufferSizeDetails (sampleRate, blockSize);
|
||||
else
|
||||
processorToPlay->setPlayConfigDetails (actualProcessorChannels.ins,
|
||||
actualProcessorChannels.outs,
|
||||
sampleRate,
|
||||
blockSize);
|
||||
|
||||
auto supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision;
|
||||
|
||||
processorToPlay->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
|
||||
: AudioProcessor::singlePrecision);
|
||||
processorToPlay->prepareToPlay (sampleRate, blockSize);
|
||||
}
|
||||
|
||||
AudioProcessor* oldOne = nullptr;
|
||||
|
||||
oldOne = isPrepared ? processor : nullptr;
|
||||
processor = processorToPlay;
|
||||
isPrepared = true;
|
||||
resizeChannels();
|
||||
|
||||
if (oldOne != nullptr)
|
||||
oldOne->releaseResources();
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision)
|
||||
{
|
||||
if (doublePrecision != isDoublePrecision)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (processor != nullptr)
|
||||
{
|
||||
processor->releaseResources();
|
||||
|
||||
auto supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision;
|
||||
|
||||
processor->setProcessingPrecision (supportsDouble ? AudioProcessor::doublePrecision
|
||||
: AudioProcessor::singlePrecision);
|
||||
processor->prepareToPlay (sampleRate, blockSize);
|
||||
}
|
||||
|
||||
isDoublePrecision = doublePrecision;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::setMidiOutput (MidiOutput* midiOutputToUse)
|
||||
{
|
||||
if (midiOutput != midiOutputToUse)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
midiOutput = midiOutputToUse;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData,
|
||||
const int numInputChannels,
|
||||
float** const outputChannelData,
|
||||
const int numOutputChannels,
|
||||
const int numSamples)
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
// These should have been prepared by audioDeviceAboutToStart()...
|
||||
jassert (sampleRate > 0 && blockSize > 0);
|
||||
|
||||
incomingMidi.clear();
|
||||
messageCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
|
||||
|
||||
initialiseIoBuffers ({ inputChannelData, numInputChannels },
|
||||
{ outputChannelData, numOutputChannels },
|
||||
numSamples,
|
||||
actualProcessorChannels.ins,
|
||||
actualProcessorChannels.outs,
|
||||
tempBuffer,
|
||||
channels);
|
||||
|
||||
const auto totalNumChannels = jmax (actualProcessorChannels.ins, actualProcessorChannels.outs);
|
||||
AudioBuffer<float> buffer (channels.data(), (int) totalNumChannels, numSamples);
|
||||
|
||||
if (processor != nullptr)
|
||||
{
|
||||
// The processor should be prepared to deal with the same number of output channels
|
||||
// as our output device.
|
||||
jassert (processor->isMidiEffect() || numOutputChannels == actualProcessorChannels.outs);
|
||||
|
||||
const ScopedLock sl2 (processor->getCallbackLock());
|
||||
|
||||
if (! processor->isSuspended())
|
||||
{
|
||||
if (processor->isUsingDoublePrecision())
|
||||
{
|
||||
conversionBuffer.makeCopyOf (buffer, true);
|
||||
processor->processBlock (conversionBuffer, incomingMidi);
|
||||
buffer.makeCopyOf (conversionBuffer, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
processor->processBlock (buffer, incomingMidi);
|
||||
}
|
||||
|
||||
if (midiOutput != nullptr)
|
||||
{
|
||||
if (midiOutput->isBackgroundThreadRunning())
|
||||
{
|
||||
midiOutput->sendBlockOfMessages (incomingMidi,
|
||||
Time::getMillisecondCounterHiRes(),
|
||||
sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
midiOutput->sendBlockOfMessagesNow (incomingMidi);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < numOutputChannels; ++i)
|
||||
FloatVectorOperations::clear (outputChannelData[i], numSamples);
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::audioDeviceAboutToStart (AudioIODevice* const device)
|
||||
{
|
||||
auto newSampleRate = device->getCurrentSampleRate();
|
||||
auto newBlockSize = device->getCurrentBufferSizeSamples();
|
||||
auto numChansIn = device->getActiveInputChannels().countNumberOfSetBits();
|
||||
auto numChansOut = device->getActiveOutputChannels().countNumberOfSetBits();
|
||||
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
sampleRate = newSampleRate;
|
||||
blockSize = newBlockSize;
|
||||
deviceChannels = { numChansIn, numChansOut };
|
||||
|
||||
resizeChannels();
|
||||
|
||||
messageCollector.reset (sampleRate);
|
||||
|
||||
if (processor != nullptr)
|
||||
{
|
||||
if (isPrepared)
|
||||
processor->releaseResources();
|
||||
|
||||
auto* oldProcessor = processor;
|
||||
setProcessor (nullptr);
|
||||
setProcessor (oldProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::audioDeviceStopped()
|
||||
{
|
||||
const ScopedLock sl (lock);
|
||||
|
||||
if (processor != nullptr && isPrepared)
|
||||
processor->releaseResources();
|
||||
|
||||
sampleRate = 0.0;
|
||||
blockSize = 0;
|
||||
isPrepared = false;
|
||||
tempBuffer.setSize (1, 1);
|
||||
}
|
||||
|
||||
void AudioProcessorPlayer::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message)
|
||||
{
|
||||
messageCollector.addMessageToQueue (message);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct AudioProcessorPlayerTests : public UnitTest
|
||||
{
|
||||
AudioProcessorPlayerTests()
|
||||
: UnitTest ("AudioProcessorPlayer", UnitTestCategories::audio) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
struct Layout
|
||||
{
|
||||
int numIns, numOuts;
|
||||
};
|
||||
|
||||
const Layout processorLayouts[] { Layout { 0, 0 },
|
||||
Layout { 1, 1 },
|
||||
Layout { 4, 4 },
|
||||
Layout { 4, 8 },
|
||||
Layout { 8, 4 } };
|
||||
|
||||
beginTest ("Buffers are prepared correctly for a variety of channel layouts");
|
||||
{
|
||||
for (const auto& layout : processorLayouts)
|
||||
{
|
||||
for (const auto numSystemInputs : { 0, 1, layout.numIns })
|
||||
{
|
||||
const int numSamples = 256;
|
||||
const auto systemIns = getTestBuffer (numSystemInputs, numSamples);
|
||||
auto systemOuts = getTestBuffer (layout.numOuts, numSamples);
|
||||
AudioBuffer<float> tempBuffer (jmax (layout.numIns, layout.numOuts), numSamples);
|
||||
std::vector<float*> channels ((size_t) jmax (layout.numIns, layout.numOuts), nullptr);
|
||||
|
||||
initialiseIoBuffers ({ systemIns.getArrayOfReadPointers(), systemIns.getNumChannels() },
|
||||
{ systemOuts.getArrayOfWritePointers(), systemOuts.getNumChannels() },
|
||||
numSamples,
|
||||
layout.numIns,
|
||||
layout.numOuts,
|
||||
tempBuffer,
|
||||
channels);
|
||||
|
||||
int channelIndex = 0;
|
||||
|
||||
for (const auto& channel : channels)
|
||||
{
|
||||
const auto value = [&]
|
||||
{
|
||||
// Any channels past the number of inputs should be silent.
|
||||
if (layout.numIns <= channelIndex)
|
||||
return 0.0f;
|
||||
|
||||
// If there's no input, all input channels should be silent.
|
||||
if (numSystemInputs == 0) return 0.0f;
|
||||
|
||||
// If there's one input, all input channels should copy from that input.
|
||||
if (numSystemInputs == 1) return 1.0f;
|
||||
|
||||
// Otherwise, each processor input should match the corresponding system input.
|
||||
return (float) (channelIndex + 1);
|
||||
}();
|
||||
|
||||
expect (FloatVectorOperations::findMinAndMax (channel, numSamples) == Range<float> (value, value));
|
||||
|
||||
channelIndex += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AudioBuffer<float> getTestBuffer (int numChannels, int numSamples)
|
||||
{
|
||||
AudioBuffer<float> result (numChannels, numSamples);
|
||||
|
||||
for (int i = 0; i < result.getNumChannels(); ++i)
|
||||
FloatVectorOperations::fill (result.getWritePointer (i), (float) i + 1, result.getNumSamples());
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
static AudioProcessorPlayerTests audioProcessorPlayerTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
144
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h
vendored
Normal file
144
deps/juce/modules/juce_audio_utils/players/juce_AudioProcessorPlayer.h
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An AudioIODeviceCallback object which streams audio through an AudioProcessor.
|
||||
|
||||
To use one of these, just make it the callback used by your AudioIODevice, and
|
||||
give it a processor to use by calling setProcessor().
|
||||
|
||||
It's also a MidiInputCallback, so you can connect it to both an audio and midi
|
||||
input to send both streams through the processor. To set a MidiOutput for the processor,
|
||||
use the setMidiOutput() method.
|
||||
|
||||
@see AudioProcessor, AudioProcessorGraph
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback,
|
||||
public MidiInputCallback
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AudioProcessorPlayer (bool doDoublePrecisionProcessing = false);
|
||||
|
||||
/** Destructor. */
|
||||
~AudioProcessorPlayer() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the processor that should be played.
|
||||
|
||||
The processor that is passed in will not be deleted or owned by this object.
|
||||
To stop anything playing, pass a nullptr to this method.
|
||||
*/
|
||||
void setProcessor (AudioProcessor* processorToPlay);
|
||||
|
||||
/** Returns the current audio processor that is being played. */
|
||||
AudioProcessor* getCurrentProcessor() const noexcept { return processor; }
|
||||
|
||||
/** Returns a midi message collector that you can pass midi messages to if you
|
||||
want them to be injected into the midi stream that is being sent to the
|
||||
processor.
|
||||
*/
|
||||
MidiMessageCollector& getMidiMessageCollector() noexcept { return messageCollector; }
|
||||
|
||||
/** Sets the MIDI output that should be used, if required.
|
||||
|
||||
The MIDI output will not be deleted or owned by this object. If the MIDI output is
|
||||
deleted, pass a nullptr to this method.
|
||||
*/
|
||||
void setMidiOutput (MidiOutput* midiOutputToUse);
|
||||
|
||||
/** 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;
|
||||
/** @internal */
|
||||
void audioDeviceAboutToStart (AudioIODevice*) override;
|
||||
/** @internal */
|
||||
void audioDeviceStopped() override;
|
||||
/** @internal */
|
||||
void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override;
|
||||
|
||||
private:
|
||||
struct NumChannels
|
||||
{
|
||||
NumChannels() = default;
|
||||
NumChannels (int numIns, int numOuts) : ins (numIns), outs (numOuts) {}
|
||||
|
||||
explicit NumChannels (const AudioProcessor::BusesLayout& layout)
|
||||
: ins (layout.getNumChannels (true, 0)), outs (layout.getNumChannels (false, 0)) {}
|
||||
|
||||
AudioProcessor::BusesLayout toLayout() const
|
||||
{
|
||||
return { { AudioChannelSet::canonicalChannelSet (ins) },
|
||||
{ AudioChannelSet::canonicalChannelSet (outs) } };
|
||||
}
|
||||
|
||||
int ins = 0, outs = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
NumChannels findMostSuitableLayout (const AudioProcessor&) const;
|
||||
void resizeChannels();
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessor* processor = nullptr;
|
||||
CriticalSection lock;
|
||||
double sampleRate = 0;
|
||||
int blockSize = 0;
|
||||
bool isPrepared = false, isDoublePrecision = false;
|
||||
|
||||
NumChannels deviceChannels, defaultProcessorChannels, actualProcessorChannels;
|
||||
std::vector<float*> channels;
|
||||
AudioBuffer<float> tempBuffer;
|
||||
AudioBuffer<double> conversionBuffer;
|
||||
|
||||
MidiBuffer incomingMidi;
|
||||
MidiMessageCollector messageCollector;
|
||||
MidiOutput* midiOutput = nullptr;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorPlayer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
306
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.cpp
vendored
Normal file
306
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.cpp
vendored
Normal file
@ -0,0 +1,306 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
// This is an AudioTransportSource which will own it's assigned source
|
||||
struct AudioSourceOwningTransportSource : public AudioTransportSource
|
||||
{
|
||||
AudioSourceOwningTransportSource (PositionableAudioSource* s, double sr) : source (s)
|
||||
{
|
||||
AudioTransportSource::setSource (s, 0, nullptr, sr);
|
||||
}
|
||||
|
||||
~AudioSourceOwningTransportSource()
|
||||
{
|
||||
setSource (nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<PositionableAudioSource> source;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// An AudioSourcePlayer which will remove itself from the AudioDeviceManager's
|
||||
// callback list once it finishes playing its source
|
||||
struct AutoRemovingTransportSource : public AudioTransportSource,
|
||||
private Timer
|
||||
{
|
||||
AutoRemovingTransportSource (MixerAudioSource& mixerToUse, AudioTransportSource* ts, bool ownSource,
|
||||
int samplesPerBlock, double requiredSampleRate)
|
||||
: mixer (mixerToUse), transportSource (ts, ownSource)
|
||||
{
|
||||
jassert (ts != nullptr);
|
||||
|
||||
setSource (transportSource);
|
||||
|
||||
prepareToPlay (samplesPerBlock, requiredSampleRate);
|
||||
start();
|
||||
|
||||
mixer.addInputSource (this, true);
|
||||
startTimerHz (10);
|
||||
}
|
||||
|
||||
~AutoRemovingTransportSource() override
|
||||
{
|
||||
setSource (nullptr);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (! transportSource->isPlaying())
|
||||
mixer.removeInputSource (this);
|
||||
}
|
||||
|
||||
private:
|
||||
MixerAudioSource& mixer;
|
||||
OptionalScopedPointer<AudioTransportSource> transportSource;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingTransportSource)
|
||||
};
|
||||
|
||||
// An AudioSource which simply outputs a buffer
|
||||
class AudioBufferSource : public PositionableAudioSource
|
||||
{
|
||||
public:
|
||||
AudioBufferSource (AudioBuffer<float>* audioBuffer, bool ownBuffer, bool playOnAllChannels)
|
||||
: buffer (audioBuffer, ownBuffer),
|
||||
playAcrossAllChannels (playOnAllChannels),
|
||||
loopLen(buffer->getNumSamples())
|
||||
{}
|
||||
|
||||
//==============================================================================
|
||||
void setNextReadPosition (int64 newPosition) override
|
||||
{
|
||||
jassert (newPosition >= 0);
|
||||
|
||||
if (looping)
|
||||
newPosition = newPosition % static_cast<int64> (buffer->getNumSamples());
|
||||
|
||||
position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition));
|
||||
}
|
||||
|
||||
int64 getNextReadPosition() const override { return static_cast<int64> (position); }
|
||||
int64 getTotalLength() const override { return static_cast<int64> (buffer->getNumSamples()); }
|
||||
|
||||
bool isLooping() const override { return looping; }
|
||||
void setLooping (bool shouldLoop) override { looping = shouldLoop; }
|
||||
|
||||
void setLoopRange (int64 loopStart, int64 loopLength) override {
|
||||
loopStartPos = jmax(0, jmin(static_cast<int>(loopStart), static_cast<int>(buffer->getNumSamples()) - 1));
|
||||
loopLen = jmax(1, jmin(static_cast<int>(buffer->getNumSamples()) - loopStartPos, static_cast<int>(loopLength)));
|
||||
}
|
||||
void getLoopRange(int64 & loopStart, int64 & loopLength) const override {
|
||||
loopStart = loopStartPos; loopLength = loopLen;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void prepareToPlay (int, double) override {}
|
||||
void releaseResources() override {}
|
||||
|
||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
|
||||
{
|
||||
bufferToFill.clearActiveBufferRegion();
|
||||
|
||||
const int bufferSize = buffer->getNumSamples();
|
||||
int samplesNeeded = bufferToFill.numSamples;
|
||||
|
||||
while (samplesNeeded > 0) {
|
||||
|
||||
const int samplesToCopy = jmin (looping ? (loopStartPos + loopLen) - position : bufferSize - position, samplesNeeded);
|
||||
|
||||
if (samplesToCopy > 0)
|
||||
{
|
||||
int maxInChannels = buffer->getNumChannels();
|
||||
int maxOutChannels = bufferToFill.buffer->getNumChannels();
|
||||
|
||||
if (! playAcrossAllChannels) {
|
||||
maxOutChannels = jmin (maxOutChannels, maxInChannels);
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxOutChannels; ++i) {
|
||||
bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer,
|
||||
i % maxInChannels, position, samplesToCopy);
|
||||
}
|
||||
|
||||
position += samplesToCopy;
|
||||
samplesNeeded -= samplesToCopy;
|
||||
}
|
||||
else {
|
||||
position += samplesNeeded;
|
||||
samplesNeeded = 0;
|
||||
}
|
||||
|
||||
if (looping) {
|
||||
int posdelta = position - (loopStartPos + loopLen);
|
||||
if (posdelta >= 0) {
|
||||
position = loopStartPos + posdelta;
|
||||
}
|
||||
}
|
||||
else {
|
||||
position += samplesNeeded - samplesToCopy;
|
||||
samplesNeeded = 0; // force to be done
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OptionalScopedPointer<AudioBuffer<float>> buffer;
|
||||
int position = 0;
|
||||
bool looping = false, playAcrossAllChannels;
|
||||
int loopStartPos = 0, loopLen;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioBufferSource)
|
||||
};
|
||||
|
||||
SoundPlayer::SoundPlayer()
|
||||
: sampleRate (44100.0), bufferSize (512)
|
||||
{
|
||||
formatManager.registerBasicFormats();
|
||||
player.setSource (&mixer);
|
||||
}
|
||||
|
||||
SoundPlayer::~SoundPlayer()
|
||||
{
|
||||
mixer.removeAllInputs();
|
||||
player.setSource (nullptr);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (const File& file)
|
||||
{
|
||||
if (file.existsAsFile())
|
||||
play (formatManager.createReaderFor (file), true);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (const void* resourceData, size_t resourceSize)
|
||||
{
|
||||
if (resourceData != nullptr && resourceSize > 0)
|
||||
{
|
||||
auto mem = std::make_unique<MemoryInputStream> (resourceData, resourceSize, false);
|
||||
play (formatManager.createReaderFor (std::move (mem)), true);
|
||||
}
|
||||
}
|
||||
|
||||
void SoundPlayer::play (AudioFormatReader* reader, bool deleteWhenFinished)
|
||||
{
|
||||
if (reader != nullptr)
|
||||
play (new AudioFormatReaderSource (reader, deleteWhenFinished), true, reader->sampleRate);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (AudioBuffer<float>* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels)
|
||||
{
|
||||
if (buffer != nullptr)
|
||||
play (new AudioBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true);
|
||||
}
|
||||
|
||||
void SoundPlayer::play (PositionableAudioSource* audioSource, bool deleteWhenFinished, double fileSampleRate)
|
||||
{
|
||||
if (audioSource != nullptr)
|
||||
{
|
||||
AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource);
|
||||
|
||||
if (transport == nullptr)
|
||||
{
|
||||
if (deleteWhenFinished)
|
||||
{
|
||||
transport = new AudioSourceOwningTransportSource (audioSource, fileSampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
transport = new AudioTransportSource();
|
||||
transport->setSource (audioSource, 0, nullptr, fileSampleRate);
|
||||
deleteWhenFinished = true;
|
||||
}
|
||||
}
|
||||
|
||||
transport->start();
|
||||
transport->prepareToPlay (bufferSize, sampleRate);
|
||||
|
||||
new AutoRemovingTransportSource (mixer, transport, deleteWhenFinished, bufferSize, sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (deleteWhenFinished)
|
||||
delete audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundPlayer::playTestSound()
|
||||
{
|
||||
auto soundLength = (int) sampleRate;
|
||||
double frequency = 440.0;
|
||||
float amplitude = 0.5f;
|
||||
|
||||
auto phasePerSample = MathConstants<double>::twoPi / (sampleRate / frequency);
|
||||
|
||||
auto* newSound = new AudioBuffer<float> (1, soundLength);
|
||||
|
||||
for (int i = 0; i < soundLength; ++i)
|
||||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample));
|
||||
|
||||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f);
|
||||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f);
|
||||
|
||||
play (newSound, true, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SoundPlayer::audioDeviceIOCallback (const float** inputChannelData,
|
||||
int numInputChannels,
|
||||
float** outputChannelData,
|
||||
int numOutputChannels,
|
||||
int numSamples)
|
||||
{
|
||||
player.audioDeviceIOCallback (inputChannelData, numInputChannels,
|
||||
outputChannelData, numOutputChannels,
|
||||
numSamples);
|
||||
}
|
||||
|
||||
void SoundPlayer::audioDeviceAboutToStart (AudioIODevice* device)
|
||||
{
|
||||
if (device != nullptr)
|
||||
{
|
||||
sampleRate = device->getCurrentSampleRate();
|
||||
bufferSize = device->getCurrentBufferSizeSamples();
|
||||
}
|
||||
|
||||
player.audioDeviceAboutToStart (device);
|
||||
}
|
||||
|
||||
void SoundPlayer::audioDeviceStopped()
|
||||
{
|
||||
player.audioDeviceStopped();
|
||||
}
|
||||
|
||||
void SoundPlayer::audioDeviceError (const String& errorMessage)
|
||||
{
|
||||
player.audioDeviceError (errorMessage);
|
||||
}
|
||||
|
||||
} // namespace juce
|
136
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.h
vendored
Normal file
136
deps/juce/modules/juce_audio_utils/players/juce_SoundPlayer.h
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A simple sound player that you can add to the AudioDeviceManager to play
|
||||
simple sounds.
|
||||
|
||||
@see AudioProcessor, AudioProcessorGraph
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API SoundPlayer : public AudioIODeviceCallback
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
SoundPlayer();
|
||||
|
||||
/** Destructor. */
|
||||
~SoundPlayer() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Plays a sound from a file. */
|
||||
void play (const File& file);
|
||||
|
||||
/** Convenient method to play sound from a JUCE resource. */
|
||||
void play (const void* resourceData, size_t resourceSize);
|
||||
|
||||
/** Plays the sound from an audio format reader.
|
||||
|
||||
If deleteWhenFinished is true then the format reader will be
|
||||
automatically deleted once the sound has finished playing.
|
||||
*/
|
||||
void play (AudioFormatReader* buffer, bool deleteWhenFinished = false);
|
||||
|
||||
/** Plays the sound from a positionable audio source.
|
||||
|
||||
This will output the sound coming from a positionable audio source.
|
||||
This gives you slightly more control over the sound playback compared
|
||||
to the other playSound methods. For example, if you would like to
|
||||
stop the sound prematurely you can call this method with a
|
||||
TransportAudioSource and then call audioSource->stop. Note that,
|
||||
you must call audioSource->start to start the playback, if your
|
||||
audioSource is a TransportAudioSource.
|
||||
|
||||
The audio device manager will not hold any references to this audio
|
||||
source once the audio source has stopped playing for any reason,
|
||||
for example when the sound has finished playing or when you have
|
||||
called audioSource->stop. Therefore, calling audioSource->start() on
|
||||
a finished audioSource will not restart the sound again. If this is
|
||||
desired simply call playSound with the same audioSource again.
|
||||
|
||||
@param audioSource the audio source to play
|
||||
@param deleteWhenFinished If this is true then the audio source will
|
||||
be deleted once the device manager has finished
|
||||
playing.
|
||||
@param sampleRateOfSource The sample rate of the source. If this is zero, JUCE
|
||||
will assume that the sample rate is the same as the
|
||||
audio output device.
|
||||
*/
|
||||
void play (PositionableAudioSource* audioSource, bool deleteWhenFinished = false,
|
||||
double sampleRateOfSource = 0.0);
|
||||
|
||||
/** Plays the sound from an audio sample buffer.
|
||||
|
||||
This will output the sound contained in an audio sample buffer. If
|
||||
deleteWhenFinished is true then the audio sample buffer will be
|
||||
automatically deleted once the sound has finished playing.
|
||||
|
||||
If playOnAllOutputChannels is true, then if there are more output channels
|
||||
than buffer channels, then the ones that are available will be re-used on
|
||||
multiple outputs so that something is sent to all output channels. If it
|
||||
is false, then the buffer will just be played on the first output channels.
|
||||
*/
|
||||
void play (AudioBuffer<float>* buffer,
|
||||
bool deleteWhenFinished = false,
|
||||
bool playOnAllOutputChannels = false);
|
||||
|
||||
/** Plays a beep through the current audio device.
|
||||
|
||||
This is here to allow the audio setup UI panels to easily include a "test"
|
||||
button so that the user can check where the audio is coming from.
|
||||
*/
|
||||
void playTestSound();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void audioDeviceIOCallback (const float**, int, float**, int, int) override;
|
||||
/** @internal */
|
||||
void audioDeviceAboutToStart (AudioIODevice*) override;
|
||||
/** @internal */
|
||||
void audioDeviceStopped() override;
|
||||
/** @internal */
|
||||
void audioDeviceError (const String& errorMessage) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioFormatManager formatManager;
|
||||
AudioSourcePlayer player;
|
||||
MixerAudioSource mixer;
|
||||
OwnedArray<AudioSource> sources;
|
||||
|
||||
//==============================================================================
|
||||
double sampleRate;
|
||||
int bufferSize;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SoundPlayer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user