mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
New methods in AudioDeviceManager to easily play sounds from files or audio buffers.
This commit is contained in:
parent
b1a8470514
commit
e440a83ad7
2 changed files with 337 additions and 38 deletions
|
|
@ -93,7 +93,6 @@ AudioDeviceManager::AudioDeviceManager()
|
|||
numOutputChansNeeded (2),
|
||||
listNeedsScanning (true),
|
||||
inputLevel (0),
|
||||
testSoundPosition (0),
|
||||
cpuUsageMs (0),
|
||||
timeToCpuScale (0)
|
||||
{
|
||||
|
|
@ -589,8 +588,6 @@ void AudioDeviceManager::stopDevice()
|
|||
{
|
||||
if (currentAudioDevice != nullptr)
|
||||
currentAudioDevice->stop();
|
||||
|
||||
testSound = nullptr;
|
||||
}
|
||||
|
||||
void AudioDeviceManager::closeAudioDevice()
|
||||
|
|
@ -762,20 +759,6 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat
|
|||
for (int i = 0; i < numOutputChannels; ++i)
|
||||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples);
|
||||
}
|
||||
|
||||
if (testSound != nullptr)
|
||||
{
|
||||
const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition);
|
||||
const float* const src = testSound->getReadPointer (0, testSoundPosition);
|
||||
|
||||
for (int i = 0; i < numOutputChannels; ++i)
|
||||
for (int j = 0; j < numSamps; ++j)
|
||||
outputChannelData [i][j] += src[j];
|
||||
|
||||
testSoundPosition += numSamps;
|
||||
if (testSoundPosition >= testSound->getNumSamples())
|
||||
testSound = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device)
|
||||
|
|
@ -944,42 +927,315 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName)
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioDeviceManager::playTestSound()
|
||||
// This is an AudioTransportSource which will own it's assigned source
|
||||
class AudioSourceOwningTransportSource : public AudioTransportSource
|
||||
{
|
||||
{ // cunningly nested to swap, unlock and delete in that order.
|
||||
ScopedPointer<AudioSampleBuffer> oldSound;
|
||||
public:
|
||||
AudioSourceOwningTransportSource() {}
|
||||
~AudioSourceOwningTransportSource() { setSource (nullptr); }
|
||||
|
||||
void setSource (PositionableAudioSource* newSource)
|
||||
{
|
||||
if (src != newSource)
|
||||
{
|
||||
const ScopedLock sl (audioCallbackLock);
|
||||
oldSound = testSound;
|
||||
ScopedPointer<PositionableAudioSource> oldSourceDeleter (src);
|
||||
src = newSource;
|
||||
|
||||
// tell the base class about the new source before deleting the old one
|
||||
AudioTransportSource::setSource (newSource);
|
||||
}
|
||||
}
|
||||
|
||||
testSoundPosition = 0;
|
||||
private:
|
||||
ScopedPointer<PositionableAudioSource> src;
|
||||
|
||||
if (currentAudioDevice != nullptr)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// An Audio player which will remove itself from the AudioDeviceManager's
|
||||
// callback list once it finishes playing its source
|
||||
class AutoRemovingSourcePlayer : public AudioSourcePlayer,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
struct DeleteOnMessageThread : public CallbackMessage
|
||||
{
|
||||
const double sampleRate = currentAudioDevice->getCurrentSampleRate();
|
||||
const int soundLength = (int) sampleRate;
|
||||
DeleteOnMessageThread (AutoRemovingSourcePlayer* p) : parent (p) {}
|
||||
void messageCallback() override { delete parent; }
|
||||
|
||||
const double frequency = 440.0;
|
||||
const float amplitude = 0.5f;
|
||||
AutoRemovingSourcePlayer* parent;
|
||||
};
|
||||
|
||||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency);
|
||||
//==============================================================================
|
||||
AutoRemovingSourcePlayer (AudioDeviceManager& deviceManager, bool ownSource)
|
||||
: manager (deviceManager),
|
||||
deleteWhenDone (ownSource),
|
||||
hasAddedCallback (false),
|
||||
recursiveEntry (false)
|
||||
{
|
||||
}
|
||||
|
||||
AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength);
|
||||
void changeListenerCallback (ChangeBroadcaster* source) override
|
||||
{
|
||||
if (AudioTransportSource* currentTransport
|
||||
= dynamic_cast<AudioTransportSource*> (getCurrentSource()))
|
||||
{
|
||||
ignoreUnused (source);
|
||||
jassert (source == currentTransport);
|
||||
|
||||
for (int i = 0; i < soundLength; ++i)
|
||||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample));
|
||||
if (! currentTransport->isPlaying())
|
||||
{
|
||||
// this will call audioDeviceStopped!
|
||||
manager.removeAudioCallback (this);
|
||||
}
|
||||
else if (! hasAddedCallback)
|
||||
{
|
||||
hasAddedCallback = true;
|
||||
manager.addAudioCallback (this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f);
|
||||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f);
|
||||
void audioDeviceStopped() override
|
||||
{
|
||||
if (! recursiveEntry)
|
||||
{
|
||||
ScopedValueSetter<bool> s (recursiveEntry, true, false);
|
||||
|
||||
const ScopedLock sl (audioCallbackLock);
|
||||
testSound = newSound;
|
||||
manager.removeAudioCallback (this);
|
||||
AudioSourcePlayer::audioDeviceStopped();
|
||||
|
||||
if (MessageManager* mm = MessageManager::getInstanceWithoutCreating())
|
||||
{
|
||||
if (mm->isThisTheMessageThread())
|
||||
delete this;
|
||||
else
|
||||
(new DeleteOnMessageThread (this))->post();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setSource (AudioTransportSource* newSource)
|
||||
{
|
||||
AudioSource* oldSource = getCurrentSource();
|
||||
|
||||
if (AudioTransportSource* oldTransport = dynamic_cast<AudioTransportSource*> (oldSource))
|
||||
oldTransport->removeChangeListener (this);
|
||||
|
||||
if (newSource != nullptr)
|
||||
newSource->addChangeListener (this);
|
||||
|
||||
AudioSourcePlayer::setSource (newSource);
|
||||
|
||||
if (deleteWhenDone)
|
||||
delete oldSource;
|
||||
}
|
||||
|
||||
private:
|
||||
// only allow myself to be deleted when my audio callback has been removed
|
||||
~AutoRemovingSourcePlayer()
|
||||
{
|
||||
setSource (nullptr);
|
||||
}
|
||||
|
||||
AudioDeviceManager& manager;
|
||||
bool deleteWhenDone, hasAddedCallback, recursiveEntry;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// An AudioSource which simply outputs a buffer
|
||||
class AudioSampleBufferSource : public PositionableAudioSource
|
||||
{
|
||||
public:
|
||||
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool shouldLoop, bool ownBuffer)
|
||||
: position (0),
|
||||
buffer (audioBuffer),
|
||||
looping (shouldLoop),
|
||||
deleteWhenDone (ownBuffer)
|
||||
{}
|
||||
|
||||
~AudioSampleBufferSource()
|
||||
{
|
||||
if (deleteWhenDone)
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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 prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
|
||||
{
|
||||
ignoreUnused (samplesPerBlockExpected, sampleRate);
|
||||
}
|
||||
|
||||
void releaseResources() override
|
||||
{}
|
||||
|
||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
|
||||
{
|
||||
int max = jmin (buffer->getNumSamples() - position, bufferToFill.numSamples);
|
||||
|
||||
jassert (max >= 0);
|
||||
{
|
||||
int ch;
|
||||
int maxInChannels = buffer->getNumChannels();
|
||||
int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(),
|
||||
jmax (maxInChannels, 2));
|
||||
|
||||
for (ch = 0; ch < maxOutChannels; ch++)
|
||||
{
|
||||
int inChannel = ch % maxInChannels;
|
||||
|
||||
if (max > 0)
|
||||
bufferToFill.buffer->copyFrom (ch, bufferToFill.startSample, *buffer, inChannel, position, max);
|
||||
}
|
||||
|
||||
for (; ch < bufferToFill.buffer->getNumChannels(); ++ch)
|
||||
bufferToFill.buffer->clear (ch, bufferToFill.startSample, bufferToFill.numSamples);
|
||||
}
|
||||
|
||||
position += max;
|
||||
|
||||
if (looping)
|
||||
position = position % buffer->getNumSamples();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int position;
|
||||
AudioSampleBuffer* buffer;
|
||||
bool looping, deleteWhenDone;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource)
|
||||
};
|
||||
|
||||
void AudioDeviceManager::playSound (const File& file)
|
||||
{
|
||||
if (file.existsAsFile())
|
||||
{
|
||||
AudioFormatManager formatManager;
|
||||
|
||||
formatManager.registerBasicFormats();
|
||||
playSound (formatManager.createReaderFor (file), true);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDeviceManager::playSound (const char* resourceData, int resourceSize)
|
||||
{
|
||||
if (resourceData != nullptr && resourceSize > 0)
|
||||
{
|
||||
AudioFormatManager formatManager;
|
||||
formatManager.registerBasicFormats();
|
||||
|
||||
MemoryInputStream* mem = new MemoryInputStream (resourceData,
|
||||
static_cast<size_t> (resourceSize),
|
||||
false);
|
||||
|
||||
playSound (formatManager.createReaderFor (mem), true);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished)
|
||||
{
|
||||
playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true);
|
||||
}
|
||||
|
||||
void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished)
|
||||
{
|
||||
if (audioSource != nullptr && currentAudioDevice != nullptr)
|
||||
{
|
||||
if (AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource))
|
||||
{
|
||||
AutoRemovingSourcePlayer* player = new AutoRemovingSourcePlayer (*this, deleteWhenFinished);
|
||||
player->setSource (transport);
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioTransportSource* transportSource;
|
||||
|
||||
if (deleteWhenFinished)
|
||||
{
|
||||
AudioSourceOwningTransportSource* owningTransportSource = new AudioSourceOwningTransportSource();
|
||||
owningTransportSource->setSource (audioSource);
|
||||
transportSource = owningTransportSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
transportSource = new AudioTransportSource;
|
||||
transportSource->setSource (audioSource);
|
||||
}
|
||||
|
||||
// recursively call myself
|
||||
playSound (transportSource, true);
|
||||
transportSource->start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (deleteWhenFinished)
|
||||
delete audioSource;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished)
|
||||
{
|
||||
playSound (new AudioSampleBufferSource (buffer, false, deleteWhenFinished), true);
|
||||
}
|
||||
|
||||
void AudioDeviceManager::playTestSound()
|
||||
{
|
||||
const double sampleRate = currentAudioDevice->getCurrentSampleRate();
|
||||
const int soundLength = (int) sampleRate;
|
||||
|
||||
const double frequency = 440.0;
|
||||
const float amplitude = 0.5f;
|
||||
|
||||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency);
|
||||
|
||||
AudioSampleBuffer* newSound = new AudioSampleBuffer (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);
|
||||
|
||||
playSound (newSound, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement)
|
||||
{
|
||||
if (enableMeasurement)
|
||||
|
|
|
|||
|
|
@ -404,6 +404,51 @@ public:
|
|||
*/
|
||||
void playTestSound();
|
||||
|
||||
/** Plays a sound from a file. */
|
||||
void playSound (const File& file);
|
||||
|
||||
/** Convenient method to play sound from a JUCE resource. */
|
||||
void playSound (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 playSound (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 deleteWhenFinished If this is true then the audio source will
|
||||
be deleted once the device manager has
|
||||
finished playing.
|
||||
*/
|
||||
void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false);
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
void playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished = false);
|
||||
|
||||
//==============================================================================
|
||||
/** Turns on level-measuring.
|
||||
|
||||
When enabled, the device manager will measure the peak input level
|
||||
|
|
@ -452,8 +497,6 @@ private:
|
|||
mutable bool listNeedsScanning;
|
||||
Atomic<int> inputLevelMeasurementEnabledCount;
|
||||
double inputLevel;
|
||||
ScopedPointer<AudioSampleBuffer> testSound;
|
||||
int testSoundPosition;
|
||||
AudioSampleBuffer tempBuffer;
|
||||
|
||||
struct MidiCallbackInfo
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue