/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2013 - Raw Material Software 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), useInputNames (false), inputLevel (0), testSoundPosition (0), tempBuffer (2, 2), 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()); 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); } } //============================================================================== String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const int numOutputChannelsNeeded, const XmlElement* const e, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup* preferredSetupOptions) { scanDevicesIfNeeded(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (e != nullptr && e->hasTagName ("DEVICESETUP")) { lastExplicitSettings = new XmlElement (*e); String error; AudioDeviceSetup setup; if (preferredSetupOptions != nullptr) setup = *preferredSetupOptions; if (e->getStringAttribute ("audioDeviceName").isNotEmpty()) { setup.inputDeviceName = setup.outputDeviceName = e->getStringAttribute ("audioDeviceName"); } else { setup.inputDeviceName = e->getStringAttribute ("audioInputDeviceName"); setup.outputDeviceName = e->getStringAttribute ("audioOutputDeviceName"); } currentDeviceType = e->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 = e->getIntAttribute ("audioDeviceBufferSize"); setup.sampleRate = e->getDoubleAttribute ("audioDeviceRate"); setup.inputChannels .parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); setup.outputChannels.parseString (e->getStringAttribute ("audioDeviceOutChans", "11"), 2); setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); setup.useDefaultOutputChannels = ! e->hasAttribute ("audioDeviceOutChans"); error = setAudioDeviceSetup (setup, true); midiInsFromXml.clear(); forEachXmlChildElementWithTagName (*e, 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 (numInputChannelsNeeded, numOutputChannelsNeeded, 0, false, preferredDefaultDeviceName); setDefaultMidiOutput (e->getStringAttribute ("defaultMidiOutput")); return error; } else { 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); } } 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() && type->getDeviceNames (true).contains (inputName, true)) || (outputName.isNotEmpty() && type->getDeviceNames (false).contains (outputName, true))) { return type; } } return nullptr; } void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { setup = currentSetup; } void AudioDeviceManager::deleteCurrentDevice() { currentAudioDevice = nullptr; currentSetup.inputDeviceName = String::empty; currentSetup.outputDeviceName = String::empty; } 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::empty; if (! (newSetup == currentSetup)) sendChangeMessage(); stopDevice(); const String newInputDeviceName (numInputChansNeeded == 0 ? String::empty : newSetup.inputDeviceName); const String newOutputDeviceName (numOutputChansNeeded == 0 ? String::empty : newSetup.outputDeviceName); String error; AudioIODeviceType* type = getCurrentDeviceTypeObject(); if (type == nullptr || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) { deleteCurrentDevice(); if (treatAsChosenDevice) updateXml(); return String::empty; } if (currentSetup.inputDeviceName != newInputDeviceName || currentSetup.outputDeviceName != newOutputDeviceName || currentAudioDevice == nullptr) { deleteCurrentDevice(); scanDevicesIfNeeded(); if (newOutputDeviceName.isNotEmpty() && ! type->getDeviceNames (false).contains (newOutputDeviceName)) { return "No such device: " + newOutputDeviceName; } if (newInputDeviceName.isNotEmpty() && ! type->getDeviceNames (true).contains (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); if (rate > 0) for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) if (currentAudioDevice->getSampleRate (i) == rate) return rate; double lowestAbove44 = 0.0; for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) { const double sr = currentAudioDevice->getSampleRate (i); if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) lowestAbove44 = sr; } if (lowestAbove44 > 0.0) return lowestAbove44; return currentAudioDevice->getSampleRate (0); } int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const { jassert (currentAudioDevice != nullptr); if (bufferSize > 0) for (int i = currentAudioDevice->getNumBufferSizesAvailable(); --i >= 0;) if (currentAudioDevice->getBufferSizeSamples(i) == bufferSize) return bufferSize; return currentAudioDevice->getDefaultBufferSize(); } void AudioDeviceManager::stopDevice() { if (currentAudioDevice != nullptr) currentAudioDevice->stop(); testSound = nullptr; } 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.getArrayOfChannels(); 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); } if (testSound != nullptr) { const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); const float* const src = testSound->getSampleData (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) { 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); midiCallbacks.add (callbackToAdd); midiCallbackDevices.add (name); } } void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) { for (int i = midiCallbacks.size(); --i >= 0;) { if (midiCallbackDevices[i] == name && midiCallbacks.getUnchecked(i) == callbackToRemove) { const ScopedLock sl (midiCallbackLock); midiCallbacks.remove (i); midiCallbackDevices.remove (i); } } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) { if (! message.isActiveSense()) { const bool isDefaultSource = (source == nullptr || source == enabledMidiInputs.getFirst()); const ScopedLock sl (midiCallbackLock); for (int i = midiCallbackDevices.size(); --i >= 0;) { const String name (midiCallbackDevices[i]); if ((isDefaultSource && name.isEmpty()) || (name.isNotEmpty() && name == source->getName())) midiCallbacks.getUnchecked(i)->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(); } } //============================================================================== void AudioDeviceManager::playTestSound() { { // cunningly nested to swap, unlock and delete in that order. ScopedPointer oldSound; { const ScopedLock sl (audioCallbackLock); oldSound = testSound; } } testSoundPosition = 0; if (currentAudioDevice != nullptr) { const double sampleRate = currentAudioDevice->getCurrentSampleRate(); const int soundLength = (int) sampleRate; AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); float* samples = newSound->getSampleData (0); const double frequency = 440.0; const float amplitude = 0.5f; const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); for (int i = 0; i < soundLength; ++i) samples[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; } } 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; }