diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 25451fed92..bd5237d5f4 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -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 oldSound; +public: + AudioSourceOwningTransportSource() {} + ~AudioSourceOwningTransportSource() { setSource (nullptr); } + void setSource (PositionableAudioSource* newSource) + { + if (src != newSource) { - const ScopedLock sl (audioCallbackLock); - oldSound = testSound; + ScopedPointer oldSourceDeleter (src); + src = newSource; + + // tell the base class about the new source before deleting the old one + AudioTransportSource::setSource (newSource); } } - testSoundPosition = 0; +private: + ScopedPointer 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 (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 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 (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 (buffer->getNumSamples()); + + position = jmin (buffer->getNumSamples(), static_cast (newPosition)); + } + + int64 getNextReadPosition() const override + { + return static_cast (position); + } + + int64 getTotalLength() const override + { + return static_cast (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 (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 (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) diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index 978ed3508f..1e59dbf97e 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -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 inputLevelMeasurementEnabledCount; double inputLevel; - ScopedPointer testSound; - int testSoundPosition; AudioSampleBuffer tempBuffer; struct MidiCallbackInfo