mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Moved simple sound player to audio_utils module
This commit is contained in:
parent
1fcae3675c
commit
a347689d96
7 changed files with 462 additions and 268 deletions
269
modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
Executable file → Normal file
269
modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp
Executable file → Normal file
|
|
@ -86,71 +86,12 @@ private:
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler)
|
||||||
};
|
};
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
// This is an AudioTransportSource which will own it's assigned source
|
|
||||||
struct AudioSourceOwningTransportSource : public AudioTransportSource
|
|
||||||
{
|
|
||||||
AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s)
|
|
||||||
{
|
|
||||||
AudioTransportSource::setSource (s);
|
|
||||||
}
|
|
||||||
|
|
||||||
~AudioSourceOwningTransportSource()
|
|
||||||
{
|
|
||||||
setSource (nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ScopedPointer<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 AutoRemovingSourcePlayer : public AudioSourcePlayer,
|
|
||||||
private Timer
|
|
||||||
{
|
|
||||||
AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource)
|
|
||||||
: manager (dm), transportSource (ts, ownSource)
|
|
||||||
{
|
|
||||||
jassert (ts != nullptr);
|
|
||||||
manager.addAudioCallback (this);
|
|
||||||
AudioSourcePlayer::setSource (transportSource);
|
|
||||||
startTimerHz (10);
|
|
||||||
}
|
|
||||||
|
|
||||||
~AutoRemovingSourcePlayer()
|
|
||||||
{
|
|
||||||
setSource (nullptr);
|
|
||||||
manager.removeAudioCallback (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void timerCallback() override
|
|
||||||
{
|
|
||||||
if (getCurrentSource() == nullptr || ! transportSource->isPlaying())
|
|
||||||
delete this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void audioDeviceStopped() override
|
|
||||||
{
|
|
||||||
AudioSourcePlayer::audioDeviceStopped();
|
|
||||||
setSource (nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
AudioDeviceManager& manager;
|
|
||||||
OptionalScopedPointer<AudioTransportSource> transportSource;
|
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer)
|
|
||||||
};
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
AudioDeviceManager::AudioDeviceManager()
|
AudioDeviceManager::AudioDeviceManager()
|
||||||
: numInputChansNeeded (0),
|
: numInputChansNeeded (0),
|
||||||
numOutputChansNeeded (2),
|
numOutputChansNeeded (2),
|
||||||
listNeedsScanning (true),
|
listNeedsScanning (true),
|
||||||
|
testSoundPosition (0),
|
||||||
cpuUsageMs (0),
|
cpuUsageMs (0),
|
||||||
timeToCpuScale (0)
|
timeToCpuScale (0)
|
||||||
{
|
{
|
||||||
|
|
@ -161,10 +102,6 @@ AudioDeviceManager::~AudioDeviceManager()
|
||||||
{
|
{
|
||||||
currentAudioDevice = nullptr;
|
currentAudioDevice = nullptr;
|
||||||
defaultMidiOutput = nullptr;
|
defaultMidiOutput = nullptr;
|
||||||
|
|
||||||
for (int i = 0; i < callbacks.size(); ++i)
|
|
||||||
if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i)))
|
|
||||||
delete p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -649,6 +586,8 @@ void AudioDeviceManager::stopDevice()
|
||||||
{
|
{
|
||||||
if (currentAudioDevice != nullptr)
|
if (currentAudioDevice != nullptr)
|
||||||
currentAudioDevice->stop();
|
currentAudioDevice->stop();
|
||||||
|
|
||||||
|
testSound = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDeviceManager::closeAudioDevice()
|
void AudioDeviceManager::closeAudioDevice()
|
||||||
|
|
@ -797,6 +736,20 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat
|
||||||
for (int i = 0; i < numOutputChannels; ++i)
|
for (int i = 0; i < numOutputChannels; ++i)
|
||||||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples);
|
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)
|
void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device)
|
||||||
|
|
@ -964,158 +917,6 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
// An AudioSource which simply outputs a buffer
|
|
||||||
class AudioSampleBufferSource : public PositionableAudioSource
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels)
|
|
||||||
: buffer (audioBuffer, ownBuffer),
|
|
||||||
position (0), looping (false), playAcrossAllChannels (playOnAllChannels)
|
|
||||||
{}
|
|
||||||
|
|
||||||
//==============================================================================
|
|
||||||
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, double) override {}
|
|
||||||
void releaseResources() override {}
|
|
||||||
|
|
||||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
|
|
||||||
{
|
|
||||||
bufferToFill.clearActiveBufferRegion();
|
|
||||||
|
|
||||||
const int bufferSize = buffer->getNumSamples();
|
|
||||||
const int samplesNeeded = bufferToFill.numSamples;
|
|
||||||
const int samplesToCopy = jmin (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 += samplesNeeded;
|
|
||||||
|
|
||||||
if (looping)
|
|
||||||
position %= bufferSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
//==============================================================================
|
|
||||||
OptionalScopedPointer<AudioSampleBuffer> buffer;
|
|
||||||
int position;
|
|
||||||
bool looping, playAcrossAllChannels;
|
|
||||||
|
|
||||||
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 void* resourceData, size_t resourceSize)
|
|
||||||
{
|
|
||||||
if (resourceData != nullptr && resourceSize > 0)
|
|
||||||
{
|
|
||||||
AudioFormatManager formatManager;
|
|
||||||
formatManager.registerBasicFormats();
|
|
||||||
MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false);
|
|
||||||
playSound (formatManager.createReaderFor (mem), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished)
|
|
||||||
{
|
|
||||||
if (reader != nullptr)
|
|
||||||
playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels)
|
|
||||||
{
|
|
||||||
if (buffer != nullptr)
|
|
||||||
playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished)
|
|
||||||
{
|
|
||||||
if (audioSource != nullptr && currentAudioDevice != nullptr)
|
|
||||||
{
|
|
||||||
AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource);
|
|
||||||
|
|
||||||
if (transport == nullptr)
|
|
||||||
{
|
|
||||||
if (deleteWhenFinished)
|
|
||||||
{
|
|
||||||
transport = new AudioSourceOwningTransportSource (audioSource);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
transport = new AudioTransportSource();
|
|
||||||
transport->setSource (audioSource);
|
|
||||||
deleteWhenFinished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transport->start();
|
|
||||||
new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (deleteWhenFinished)
|
|
||||||
delete audioSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {}
|
AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {}
|
||||||
|
|
||||||
|
|
@ -1160,6 +961,42 @@ double AudioDeviceManager::LevelMeter::getCurrentLevel() const noexcept
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioDeviceManager::playTestSound()
|
||||||
|
{
|
||||||
|
{ // cunningly nested to swap, unlock and delete in that order.
|
||||||
|
ScopedPointer<AudioSampleBuffer> oldSound;
|
||||||
|
|
||||||
|
{
|
||||||
|
const ScopedLock sl (audioCallbackLock);
|
||||||
|
oldSound = testSound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testSoundPosition = 0;
|
||||||
|
|
||||||
|
if (currentAudioDevice != nullptr)
|
||||||
|
{
|
||||||
|
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* const 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);
|
||||||
|
|
||||||
|
const ScopedLock sl (audioCallbackLock);
|
||||||
|
testSound = newSound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); }
|
double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); }
|
||||||
double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); }
|
double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -404,57 +404,6 @@ public:
|
||||||
*/
|
*/
|
||||||
void playTestSound();
|
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 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.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
|
|
||||||
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 playSound (AudioSampleBuffer* buffer,
|
|
||||||
bool deleteWhenFinished = false,
|
|
||||||
bool playOnAllOutputChannels = false);
|
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
/** Turns on level-measuring for input channels.
|
/** Turns on level-measuring for input channels.
|
||||||
@see getCurrentInputLevel()
|
@see getCurrentInputLevel()
|
||||||
|
|
@ -519,6 +468,9 @@ private:
|
||||||
ScopedPointer<MidiOutput> defaultMidiOutput;
|
ScopedPointer<MidiOutput> defaultMidiOutput;
|
||||||
CriticalSection audioCallbackLock, midiCallbackLock;
|
CriticalSection audioCallbackLock, midiCallbackLock;
|
||||||
|
|
||||||
|
ScopedPointer<AudioSampleBuffer> testSound;
|
||||||
|
int testSoundPosition;
|
||||||
|
|
||||||
double cpuUsageMs, timeToCpuScale;
|
double cpuUsageMs, timeToCpuScale;
|
||||||
|
|
||||||
struct LevelMeter
|
struct LevelMeter
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
website: http://www.juce.com/juce
|
website: http://www.juce.com/juce
|
||||||
license: GPL/Commercial
|
license: GPL/Commercial
|
||||||
|
|
||||||
dependencies: juce_audio_basics, juce_audio_formats, juce_events
|
dependencies: juce_audio_basics, juce_events
|
||||||
OSXFrameworks: CoreAudio CoreMIDI DiscRecording
|
OSXFrameworks: CoreAudio CoreMIDI DiscRecording
|
||||||
iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation
|
iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation
|
||||||
linuxPackages: alsa
|
linuxPackages: alsa
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ namespace juce
|
||||||
#include "gui/juce_AudioVisualiserComponent.cpp"
|
#include "gui/juce_AudioVisualiserComponent.cpp"
|
||||||
#include "gui/juce_MidiKeyboardComponent.cpp"
|
#include "gui/juce_MidiKeyboardComponent.cpp"
|
||||||
#include "gui/juce_AudioAppComponent.cpp"
|
#include "gui/juce_AudioAppComponent.cpp"
|
||||||
|
#include "players/juce_SoundPlayer.cpp"
|
||||||
#include "players/juce_AudioProcessorPlayer.cpp"
|
#include "players/juce_AudioProcessorPlayer.cpp"
|
||||||
|
|
||||||
#if JUCE_MAC
|
#if JUCE_MAC
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ namespace juce
|
||||||
#include "gui/juce_MidiKeyboardComponent.h"
|
#include "gui/juce_MidiKeyboardComponent.h"
|
||||||
#include "gui/juce_AudioAppComponent.h"
|
#include "gui/juce_AudioAppComponent.h"
|
||||||
#include "gui/juce_BluetoothMidiDevicePairingDialogue.h"
|
#include "gui/juce_BluetoothMidiDevicePairingDialogue.h"
|
||||||
|
#include "players/juce_SoundPlayer.h"
|
||||||
#include "players/juce_AudioProcessorPlayer.h"
|
#include "players/juce_AudioProcessorPlayer.h"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
273
modules/juce_audio_utils/players/juce_SoundPlayer.cpp
Normal file
273
modules/juce_audio_utils/players/juce_SoundPlayer.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// This is an AudioTransportSource which will own it's assigned source
|
||||||
|
struct AudioSourceOwningTransportSource : public AudioTransportSource
|
||||||
|
{
|
||||||
|
AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s)
|
||||||
|
{
|
||||||
|
AudioTransportSource::setSource (s);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AudioSourceOwningTransportSource()
|
||||||
|
{
|
||||||
|
setSource (nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ScopedPointer<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 sampleRate)
|
||||||
|
: mixer (mixerToUse), transportSource (ts, ownSource)
|
||||||
|
{
|
||||||
|
jassert (ts != nullptr);
|
||||||
|
|
||||||
|
setSource (transportSource);
|
||||||
|
|
||||||
|
prepareToPlay (samplesPerBlock, sampleRate);
|
||||||
|
start();
|
||||||
|
|
||||||
|
mixer.addInputSource (this, true);
|
||||||
|
startTimerHz (10);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AutoRemovingTransportSource()
|
||||||
|
{
|
||||||
|
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 AudioSampleBufferSource : public PositionableAudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels)
|
||||||
|
: buffer (audioBuffer, ownBuffer),
|
||||||
|
position (0), looping (false),
|
||||||
|
playAcrossAllChannels (playOnAllChannels)
|
||||||
|
{}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
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, double) override {}
|
||||||
|
void releaseResources() override {}
|
||||||
|
|
||||||
|
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
|
||||||
|
{
|
||||||
|
bufferToFill.clearActiveBufferRegion();
|
||||||
|
|
||||||
|
const int bufferSize = buffer->getNumSamples();
|
||||||
|
const int samplesNeeded = bufferToFill.numSamples;
|
||||||
|
const int samplesToCopy = jmin (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 += samplesNeeded;
|
||||||
|
|
||||||
|
if (looping)
|
||||||
|
position %= bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
//==============================================================================
|
||||||
|
OptionalScopedPointer<AudioSampleBuffer> buffer;
|
||||||
|
int position;
|
||||||
|
bool looping, playAcrossAllChannels;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource)
|
||||||
|
};
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false);
|
||||||
|
play (formatManager.createReaderFor (mem), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPlayer::play (AudioFormatReader* reader, bool deleteWhenFinished)
|
||||||
|
{
|
||||||
|
if (reader != nullptr)
|
||||||
|
play (new AudioFormatReaderSource (reader, deleteWhenFinished), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPlayer::play (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels)
|
||||||
|
{
|
||||||
|
if (buffer != nullptr)
|
||||||
|
play (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPlayer::play (PositionableAudioSource* audioSource, bool deleteWhenFinished)
|
||||||
|
{
|
||||||
|
if (audioSource != nullptr)
|
||||||
|
{
|
||||||
|
AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource);
|
||||||
|
|
||||||
|
if (transport == nullptr)
|
||||||
|
{
|
||||||
|
if (deleteWhenFinished)
|
||||||
|
{
|
||||||
|
transport = new AudioSourceOwningTransportSource (audioSource);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transport = new AudioTransportSource();
|
||||||
|
transport->setSource (audioSource);
|
||||||
|
deleteWhenFinished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transport->start();
|
||||||
|
transport->prepareToPlay (bufferSize, sampleRate);
|
||||||
|
|
||||||
|
new AutoRemovingTransportSource (mixer, transport, deleteWhenFinished, bufferSize, sampleRate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (deleteWhenFinished)
|
||||||
|
delete audioSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPlayer::playTestSound()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
130
modules/juce_audio_utils/players/juce_SoundPlayer.h
Normal file
130
modules/juce_audio_utils/players/juce_SoundPlayer.h
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef JUCE_SOUNDPLAYER_H_INCLUDED
|
||||||
|
#define JUCE_SOUNDPLAYER_H_INCLUDED
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/**
|
||||||
|
A simple sound player that you can add to the AudioDeviceManager to play
|
||||||
|
simple sounds.
|
||||||
|
|
||||||
|
@see AudioProcessor, AudioProcessorGraph
|
||||||
|
*/
|
||||||
|
class JUCE_API SoundPlayer : public AudioIODeviceCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//==============================================================================
|
||||||
|
SoundPlayer ();
|
||||||
|
|
||||||
|
/** Destructor. */
|
||||||
|
virtual ~SoundPlayer();
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
/** 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.
|
||||||
|
*/
|
||||||
|
void play (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.
|
||||||
|
|
||||||
|
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 (AudioSampleBuffer* 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)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // JUCE_SOUNDPLAYER_H_INCLUDED
|
||||||
Loading…
Add table
Add a link
Reference in a new issue