/* ============================================================================== 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. ============================================================================== */ AudioDeviceManager::AudioDeviceSetup::AudioDeviceSetup() : sampleRate (0), bufferSize (0), useDefaultInputChannels (true), useDefaultOutputChannels (true) { } bool AudioDeviceManager::AudioDeviceSetup::operator== (const AudioDeviceManager::AudioDeviceSetup& other) const { return outputDeviceName == other.outputDeviceName && inputDeviceName == other.inputDeviceName && sampleRate == other.sampleRate && bufferSize == other.bufferSize && inputChannels == other.inputChannels && useDefaultInputChannels == other.useDefaultInputChannels && outputChannels == other.outputChannels && useDefaultOutputChannels == other.useDefaultOutputChannels; } //============================================================================== class AudioDeviceManager::CallbackHandler : public AudioIODeviceCallback, public MidiInputCallback, public AudioIODeviceType::Listener { public: CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} private: void audioDeviceIOCallback (const float** ins, int numIns, float** outs, int numOuts, int numSamples) override { owner.audioDeviceIOCallbackInt (ins, numIns, outs, numOuts, numSamples); } void audioDeviceAboutToStart (AudioIODevice* device) override { owner.audioDeviceAboutToStartInt (device); } void audioDeviceStopped() override { owner.audioDeviceStoppedInt(); } void audioDeviceError (const String& message) override { owner.audioDeviceErrorInt (message); } void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override { owner.handleIncomingMidiMessageInt (source, message); } void audioDeviceListChanged() override { owner.audioDeviceListChanged(); } AudioDeviceManager& owner; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) }; //============================================================================== AudioDeviceManager::AudioDeviceManager() : numInputChansNeeded (0), numOutputChansNeeded (2), listNeedsScanning (true), inputLevel (0), cpuUsageMs (0), timeToCpuScale (0) { callbackHandler = new CallbackHandler (*this); } AudioDeviceManager::~AudioDeviceManager() { currentAudioDevice = nullptr; defaultMidiOutput = nullptr; } //============================================================================== void AudioDeviceManager::createDeviceTypesIfNeeded() { if (availableDeviceTypes.size() == 0) { OwnedArray types; createAudioDeviceTypes (types); for (int i = 0; i < types.size(); ++i) addAudioDeviceType (types.getUnchecked(i)); types.clear (false); if (AudioIODeviceType* first = availableDeviceTypes.getFirst()) currentDeviceType = first->getTypeName(); } } const OwnedArray& AudioDeviceManager::getAvailableDeviceTypes() { scanDevicesIfNeeded(); return availableDeviceTypes; } void AudioDeviceManager::audioDeviceListChanged() { if (currentAudioDevice != nullptr) { currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); } sendChangeMessage(); } //============================================================================== static void addIfNotNull (OwnedArray& list, AudioIODeviceType* const device) { if (device != nullptr) list.add (device); } void AudioDeviceManager::createAudioDeviceTypes (OwnedArray& list) { addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (false)); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (true)); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); } void AudioDeviceManager::addAudioDeviceType (AudioIODeviceType* newDeviceType) { if (newDeviceType != nullptr) { jassert (lastDeviceTypeConfigs.size() == availableDeviceTypes.size()); availableDeviceTypes.add (newDeviceType); lastDeviceTypeConfigs.add (new AudioDeviceSetup()); newDeviceType->addListener (callbackHandler); } } static bool deviceListContains (AudioIODeviceType* type, bool isInput, const String& name) { StringArray devices (type->getDeviceNames (isInput)); for (int i = devices.size(); --i >= 0;) if (devices[i].trim().equalsIgnoreCase (name.trim())) return true; return false; } //============================================================================== String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const int numOutputChannelsNeeded, const XmlElement* const xml, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup* preferredSetupOptions) { scanDevicesIfNeeded(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (xml != nullptr && xml->hasTagName ("DEVICESETUP")) return initialiseFromXML (*xml, selectDefaultDeviceOnFailure, preferredDefaultDeviceName, preferredSetupOptions); return initialiseDefault (preferredDefaultDeviceName, preferredSetupOptions); } String AudioDeviceManager::initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup* preferredSetupOptions) { AudioDeviceSetup setup; if (preferredSetupOptions != nullptr) { setup = *preferredSetupOptions; } else if (preferredDefaultDeviceName.isNotEmpty()) { for (int j = availableDeviceTypes.size(); --j >= 0;) { AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); const StringArray outs (type->getDeviceNames (false)); for (int i = 0; i < outs.size(); ++i) { if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) { setup.outputDeviceName = outs[i]; break; } } const StringArray ins (type->getDeviceNames (true)); for (int i = 0; i < ins.size(); ++i) { if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) { setup.inputDeviceName = ins[i]; break; } } } } insertDefaultDeviceNames (setup); return setAudioDeviceSetup (setup, false); } String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup* preferredSetupOptions) { lastExplicitSettings = new XmlElement (xml); String error; AudioDeviceSetup setup; if (preferredSetupOptions != nullptr) setup = *preferredSetupOptions; if (xml.getStringAttribute ("audioDeviceName").isNotEmpty()) { setup.inputDeviceName = setup.outputDeviceName = xml.getStringAttribute ("audioDeviceName"); } else { setup.inputDeviceName = xml.getStringAttribute ("audioInputDeviceName"); setup.outputDeviceName = xml.getStringAttribute ("audioOutputDeviceName"); } currentDeviceType = xml.getStringAttribute ("deviceType"); if (findType (currentDeviceType) == nullptr) { if (AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName)) currentDeviceType = type->getTypeName(); else if (availableDeviceTypes.size() > 0) currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); } setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); setup.useDefaultInputChannels = ! xml.hasAttribute ("audioDeviceInChans"); setup.useDefaultOutputChannels = ! xml.hasAttribute ("audioDeviceOutChans"); error = setAudioDeviceSetup (setup, true); midiInsFromXml.clear(); forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") midiInsFromXml.add (c->getStringAttribute ("name")); const StringArray allMidiIns (MidiInput::getDevices()); for (int i = allMidiIns.size(); --i >= 0;) setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); if (error.isNotEmpty() && selectDefaultDeviceOnFailure) error = initialise (numInputChansNeeded, numOutputChansNeeded, nullptr, false, preferredDefaultDeviceName); setDefaultMidiOutput (xml.getStringAttribute ("defaultMidiOutput")); return error; } String AudioDeviceManager::initialiseWithDefaultDevices (int numInputChannelsNeeded, int numOutputChannelsNeeded) { lastExplicitSettings = nullptr; return initialise (numInputChannelsNeeded, numOutputChannelsNeeded, nullptr, false, String(), nullptr); } void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const { if (AudioIODeviceType* type = getCurrentDeviceTypeObject()) { if (setup.outputDeviceName.isEmpty()) setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; if (setup.inputDeviceName.isEmpty()) setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; } } XmlElement* AudioDeviceManager::createStateXml() const { return lastExplicitSettings.createCopy(); } //============================================================================== void AudioDeviceManager::scanDevicesIfNeeded() { if (listNeedsScanning) { listNeedsScanning = false; createDeviceTypesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) availableDeviceTypes.getUnchecked(i)->scanForDevices(); } } AudioIODeviceType* AudioDeviceManager::findType (const String& typeName) { scanDevicesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) if (availableDeviceTypes.getUnchecked(i)->getTypeName() == typeName) return availableDeviceTypes.getUnchecked(i); return nullptr; } AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) { scanDevicesIfNeeded(); for (int i = availableDeviceTypes.size(); --i >= 0;) { AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(i); if ((inputName.isNotEmpty() && deviceListContains (type, true, inputName)) || (outputName.isNotEmpty() && deviceListContains (type, false, outputName))) { return type; } } return nullptr; } void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { setup = currentSetup; } void AudioDeviceManager::deleteCurrentDevice() { currentAudioDevice = nullptr; currentSetup.inputDeviceName.clear(); currentSetup.outputDeviceName.clear(); } void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, const bool treatAsChosenDevice) { for (int i = 0; i < availableDeviceTypes.size(); ++i) { if (availableDeviceTypes.getUnchecked(i)->getTypeName() == type && currentDeviceType != type) { if (currentAudioDevice != nullptr) { closeAudioDevice(); Thread::sleep (1500); // allow a moment for OS devices to sort themselves out, to help // avoid things like DirectSound/ASIO clashes } currentDeviceType = type; AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); insertDefaultDeviceNames (s); setAudioDeviceSetup (s, treatAsChosenDevice); sendChangeMessage(); break; } } } AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const { for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes.getUnchecked(i)->getTypeName() == currentDeviceType) return availableDeviceTypes.getUnchecked(i); return availableDeviceTypes[0]; } String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, const bool treatAsChosenDevice) { jassert (&newSetup != ¤tSetup); // this will have no effect if (newSetup == currentSetup && currentAudioDevice != nullptr) return String(); if (! (newSetup == currentSetup)) sendChangeMessage(); stopDevice(); const String newInputDeviceName (numInputChansNeeded == 0 ? String() : newSetup.inputDeviceName); const String newOutputDeviceName (numOutputChansNeeded == 0 ? String() : newSetup.outputDeviceName); String error; AudioIODeviceType* type = getCurrentDeviceTypeObject(); if (type == nullptr || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) { deleteCurrentDevice(); if (treatAsChosenDevice) updateXml(); return String(); } if (currentSetup.inputDeviceName != newInputDeviceName || currentSetup.outputDeviceName != newOutputDeviceName || currentAudioDevice == nullptr) { deleteCurrentDevice(); scanDevicesIfNeeded(); if (newOutputDeviceName.isNotEmpty() && ! deviceListContains (type, false, newOutputDeviceName)) return "No such device: " + newOutputDeviceName; if (newInputDeviceName.isNotEmpty() && ! deviceListContains (type, true, newInputDeviceName)) return "No such device: " + newInputDeviceName; currentAudioDevice = type->createDevice (newOutputDeviceName, newInputDeviceName); if (currentAudioDevice == nullptr) error = "Can't open the audio device!\n\n" "This may be because another application is currently using the same device - " "if so, you should close any other applications and try again!"; else error = currentAudioDevice->getLastError(); if (error.isNotEmpty()) { deleteCurrentDevice(); return error; } if (newSetup.useDefaultInputChannels) { inputChannels.clear(); inputChannels.setRange (0, numInputChansNeeded, true); } if (newSetup.useDefaultOutputChannels) { outputChannels.clear(); outputChannels.setRange (0, numOutputChansNeeded, true); } if (newInputDeviceName.isEmpty()) inputChannels.clear(); if (newOutputDeviceName.isEmpty()) outputChannels.clear(); } if (! newSetup.useDefaultInputChannels) inputChannels = newSetup.inputChannels; if (! newSetup.useDefaultOutputChannels) outputChannels = newSetup.outputChannels; currentSetup = newSetup; currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); currentSetup.bufferSize = chooseBestBufferSize (newSetup.bufferSize); error = currentAudioDevice->open (inputChannels, outputChannels, currentSetup.sampleRate, currentSetup.bufferSize); if (error.isEmpty()) { currentDeviceType = currentAudioDevice->getTypeName(); currentAudioDevice->start (callbackHandler); currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); for (int i = 0; i < availableDeviceTypes.size(); ++i) if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) *(lastDeviceTypeConfigs.getUnchecked (i)) = currentSetup; if (treatAsChosenDevice) updateXml(); } else { deleteCurrentDevice(); } return error; } double AudioDeviceManager::chooseBestSampleRate (double rate) const { jassert (currentAudioDevice != nullptr); const Array rates (currentAudioDevice->getAvailableSampleRates()); if (rate > 0 && rates.contains (rate)) return rate; rate = currentAudioDevice->getCurrentSampleRate(); if (rate > 0 && rates.contains (rate)) return rate; double lowestAbove44 = 0.0; for (int i = rates.size(); --i >= 0;) { const double sr = rates[i]; if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) lowestAbove44 = sr; } if (lowestAbove44 > 0.0) return lowestAbove44; return rates[0]; } int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const { jassert (currentAudioDevice != nullptr); if (bufferSize > 0 && currentAudioDevice->getAvailableBufferSizes().contains (bufferSize)) return bufferSize; return currentAudioDevice->getDefaultBufferSize(); } void AudioDeviceManager::stopDevice() { if (currentAudioDevice != nullptr) currentAudioDevice->stop(); } void AudioDeviceManager::closeAudioDevice() { stopDevice(); currentAudioDevice = nullptr; } void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == nullptr) { if (currentSetup.inputDeviceName.isEmpty() && currentSetup.outputDeviceName.isEmpty()) { // This method will only reload the last device that was running // before closeAudioDevice() was called - you need to actually open // one first, with setAudioDevice(). jassertfalse; return; } AudioDeviceSetup s (currentSetup); setAudioDeviceSetup (s, false); } } void AudioDeviceManager::updateXml() { lastExplicitSettings = new XmlElement ("DEVICESETUP"); lastExplicitSettings->setAttribute ("deviceType", currentDeviceType); lastExplicitSettings->setAttribute ("audioOutputDeviceName", currentSetup.outputDeviceName); lastExplicitSettings->setAttribute ("audioInputDeviceName", currentSetup.inputDeviceName); if (currentAudioDevice != nullptr) { lastExplicitSettings->setAttribute ("audioDeviceRate", currentAudioDevice->getCurrentSampleRate()); if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) lastExplicitSettings->setAttribute ("audioDeviceBufferSize", currentAudioDevice->getCurrentBufferSizeSamples()); if (! currentSetup.useDefaultInputChannels) lastExplicitSettings->setAttribute ("audioDeviceInChans", currentSetup.inputChannels.toString (2)); if (! currentSetup.useDefaultOutputChannels) lastExplicitSettings->setAttribute ("audioDeviceOutChans", currentSetup.outputChannels.toString (2)); } for (int i = 0; i < enabledMidiInputs.size(); ++i) lastExplicitSettings->createNewChildElement ("MIDIINPUT") ->setAttribute ("name", enabledMidiInputs[i]->getName()); if (midiInsFromXml.size() > 0) { // Add any midi devices that have been enabled before, but which aren't currently // open because the device has been disconnected. const StringArray availableMidiDevices (MidiInput::getDevices()); for (int i = 0; i < midiInsFromXml.size(); ++i) if (! availableMidiDevices.contains (midiInsFromXml[i], true)) lastExplicitSettings->createNewChildElement ("MIDIINPUT") ->setAttribute ("name", midiInsFromXml[i]); } if (defaultMidiOutputName.isNotEmpty()) lastExplicitSettings->setAttribute ("defaultMidiOutput", defaultMidiOutputName); } //============================================================================== void AudioDeviceManager::addAudioCallback (AudioIODeviceCallback* newCallback) { { const ScopedLock sl (audioCallbackLock); if (callbacks.contains (newCallback)) return; } if (currentAudioDevice != nullptr && newCallback != nullptr) newCallback->audioDeviceAboutToStart (currentAudioDevice); const ScopedLock sl (audioCallbackLock); callbacks.add (newCallback); } void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callbackToRemove) { if (callbackToRemove != nullptr) { bool needsDeinitialising = currentAudioDevice != nullptr; { const ScopedLock sl (audioCallbackLock); needsDeinitialising = needsDeinitialising && callbacks.contains (callbackToRemove); callbacks.removeFirstMatchingValue (callbackToRemove); } if (needsDeinitialising) callbackToRemove->audioDeviceStopped(); } } void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, int numInputChannels, float** outputChannelData, int numOutputChannels, int numSamples) { const ScopedLock sl (audioCallbackLock); if (inputLevelMeasurementEnabledCount.get() > 0 && numInputChannels > 0) { for (int j = 0; j < numSamples; ++j) { float s = 0; for (int i = 0; i < numInputChannels; ++i) s += std::abs (inputChannelData[i][j]); s /= numInputChannels; const double decayFactor = 0.99992; if (s > inputLevel) inputLevel = s; else if (inputLevel > 0.001f) inputLevel *= decayFactor; else inputLevel = 0; } } else { inputLevel = 0; } if (callbacks.size() > 0) { const double callbackStartTime = Time::getMillisecondCounterHiRes(); tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); float** const tempChans = tempBuffer.getArrayOfWritePointers(); for (int i = callbacks.size(); --i > 0;) { callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, tempChans, numOutputChannels, numSamples); for (int chan = 0; chan < numOutputChannels; ++chan) { if (const float* const src = tempChans [chan]) if (float* const dst = outputChannelData [chan]) for (int j = 0; j < numSamples; ++j) dst[j] += src[j]; } } const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; const double filterAmount = 0.2; cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); } else { for (int i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); } } void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) { cpuUsageMs = 0; const double sampleRate = device->getCurrentSampleRate(); const int blockSize = device->getCurrentBufferSizeSamples(); if (sampleRate > 0.0 && blockSize > 0) { const double msPerBlock = 1000.0 * blockSize / sampleRate; timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; } { const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); } sendChangeMessage(); } void AudioDeviceManager::audioDeviceStoppedInt() { cpuUsageMs = 0; timeToCpuScale = 0; sendChangeMessage(); const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceStopped(); } void AudioDeviceManager::audioDeviceErrorInt (const String& message) { const ScopedLock sl (audioCallbackLock); for (int i = callbacks.size(); --i >= 0;) callbacks.getUnchecked(i)->audioDeviceError (message); } double AudioDeviceManager::getCpuUsage() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } //============================================================================== void AudioDeviceManager::setMidiInputEnabled (const String& name, const bool enabled) { if (enabled != isMidiInputEnabled (name)) { if (enabled) { const int index = MidiInput::getDevices().indexOf (name); if (index >= 0) { if (MidiInput* const midiIn = MidiInput::openDevice (index, callbackHandler)) { enabledMidiInputs.add (midiIn); midiIn->start(); } } } else { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) enabledMidiInputs.remove (i); } updateXml(); sendChangeMessage(); } } bool AudioDeviceManager::isMidiInputEnabled (const String& name) const { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) return true; return false; } void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callbackToAdd) { removeMidiInputCallback (name, callbackToAdd); if (name.isEmpty() || isMidiInputEnabled (name)) { const ScopedLock sl (midiCallbackLock); MidiCallbackInfo mc; mc.deviceName = name; mc.callback = callbackToAdd; midiCallbacks.add (mc); } } void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) { for (int i = midiCallbacks.size(); --i >= 0;) { const MidiCallbackInfo& mc = midiCallbacks.getReference(i); if (mc.callback == callbackToRemove && mc.deviceName == name) { const ScopedLock sl (midiCallbackLock); midiCallbacks.remove (i); } } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) { if (! message.isActiveSense()) { const ScopedLock sl (midiCallbackLock); for (int i = 0; i < midiCallbacks.size(); ++i) { const MidiCallbackInfo& mc = midiCallbacks.getReference(i); if (mc.deviceName.isEmpty() || mc.deviceName == source->getName()) mc.callback->handleIncomingMidiMessage (source, message); } } } //============================================================================== void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) { if (defaultMidiOutputName != deviceName) { Array oldCallbacks; { const ScopedLock sl (audioCallbackLock); oldCallbacks.swapWith (callbacks); } if (currentAudioDevice != nullptr) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceStopped(); defaultMidiOutput = nullptr; defaultMidiOutputName = deviceName; if (deviceName.isNotEmpty()) defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); if (currentAudioDevice != nullptr) for (int i = oldCallbacks.size(); --i >= 0;) oldCallbacks.getUnchecked(i)->audioDeviceAboutToStart (currentAudioDevice); { const ScopedLock sl (audioCallbackLock); oldCallbacks.swapWith (callbacks); } updateXml(); sendChangeMessage(); } } //============================================================================== // This is an AudioTransportSource which will own it's assigned source class AudioSourceOwningTransportSource : public AudioTransportSource { public: AudioSourceOwningTransportSource() {} ~AudioSourceOwningTransportSource() { setSource (nullptr); } void setSource (PositionableAudioSource* newSource) { if (src != newSource) { ScopedPointer oldSourceDeleter (src); src = newSource; // tell the base class about the new source before deleting the old one AudioTransportSource::setSource (newSource); } } private: ScopedPointer src; 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 { DeleteOnMessageThread (AutoRemovingSourcePlayer* p) : parent (p) {} void messageCallback() override { delete parent; } AutoRemovingSourcePlayer* parent; }; //============================================================================== AutoRemovingSourcePlayer (AudioDeviceManager& deviceManager, bool ownSource) : manager (deviceManager), deleteWhenDone (ownSource), hasAddedCallback (false), recursiveEntry (false) { } void changeListenerCallback (ChangeBroadcaster* newSource) override { if (AudioTransportSource* currentTransport = dynamic_cast (getCurrentSource())) { ignoreUnused (newSource); jassert (newSource == currentTransport); if (! currentTransport->isPlaying()) { // this will call audioDeviceStopped! manager.removeAudioCallback (this); } else if (! hasAddedCallback) { hasAddedCallback = true; manager.addAudioCallback (this); } } } void audioDeviceStopped() override { if (! recursiveEntry) { ScopedValueSetter s (recursiveEntry, true, false); 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 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) { 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) ++inputLevelMeasurementEnabledCount; else --inputLevelMeasurementEnabledCount; inputLevel = 0; } double AudioDeviceManager::getCurrentInputLevel() const { jassert (inputLevelMeasurementEnabledCount.get() > 0); // you need to call enableInputLevelMeasurement() before using this! return inputLevel; }