/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-7 by Raw Material Software ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License, as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU General Public License along with JUCE; if not, visit www.gnu.org/licenses or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ If you'd like to release a closed-source product which uses JUCE, commercial licenses are also available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../../../juce_core/basics/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "juce_AudioDeviceManager.h" #include "../../gui/components/juce_Desktop.h" #include "../../../juce_core/text/juce_LocalisedStrings.h" //============================================================================== AudioDeviceManager::AudioDeviceManager() : currentAudioDevice (0), currentCallback (0), numInputChansNeeded (0), numOutputChansNeeded (2), lastExplicitSettings (0), listNeedsScanning (true), useInputNames (false), enabledMidiInputs (4), midiCallbacks (4), midiCallbackDevices (4), defaultMidiOutput (0), cpuUsageMs (0), timeToCpuScale (0) { callbackHandler.owner = this; AudioIODeviceType::createDeviceTypes (availableDeviceTypes); } AudioDeviceManager::~AudioDeviceManager() { stopDevice(); deleteAndZero (currentAudioDevice); deleteAndZero (defaultMidiOutput); delete lastExplicitSettings; } //============================================================================== const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const int numOutputChannelsNeeded, const XmlElement* const e, const bool selectDefaultDeviceOnFailure) { if (listNeedsScanning) refreshDeviceList(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (e != 0 && e->hasTagName (T("DEVICESETUP"))) { lastExplicitSettings = new XmlElement (*e); BitArray ins, outs; ins.parseString (e->getStringAttribute (T("audioDeviceInChans"), T("11")), 2); outs.parseString (e->getStringAttribute (T("audioDeviceOutChans"), T("11")), 2); String error (setAudioDevice (e->getStringAttribute (T("audioDeviceName")), e->getIntAttribute (T("audioDeviceBufferSize")), e->getDoubleAttribute (T("audioDeviceRate")), e->hasAttribute (T("audioDeviceInChans")) ? &ins : 0, e->hasAttribute (T("audioDeviceOutChans")) ? &outs : 0, true)); StringArray enabledMidiIns; forEachXmlChildElementWithTagName (*e, c, T("MIDIINPUT")) enabledMidiIns.add (c->getStringAttribute (T("name"))); const StringArray allMidiIns (MidiInput::getDevices()); for (int i = allMidiIns.size(); --i >= 0;) setMidiInputEnabled (allMidiIns[i], enabledMidiIns.contains (allMidiIns[i])); if (error.isNotEmpty() && selectDefaultDeviceOnFailure) initialise (numInputChannelsNeeded, numOutputChannelsNeeded, 0, false); setDefaultMidiOutput (e->getStringAttribute (T("defaultMidiOutput"))); return error; } else { setInputDeviceNamesUsed (numOutputChannelsNeeded == 0); String defaultDevice; if (availableDeviceTypes [0] != 0) defaultDevice = availableDeviceTypes[0]->getDefaultDeviceName (numOutputChannelsNeeded == 0, numInputChannelsNeeded, numOutputChannelsNeeded); return setAudioDevice (defaultDevice, 0, 0, 0, 0, false); } } XmlElement* AudioDeviceManager::createStateXml() const { return lastExplicitSettings != 0 ? new XmlElement (*lastExplicitSettings) : 0; } //============================================================================== const StringArray AudioDeviceManager::getAvailableAudioDeviceNames() const { if (listNeedsScanning) refreshDeviceList(); StringArray names; for (int i = 0; i < availableDeviceTypes.size(); ++i) names.addArray (availableDeviceTypes[i]->getDeviceNames (useInputNames)); return names; } void AudioDeviceManager::refreshDeviceList() const { listNeedsScanning = false; for (int i = 0; i < availableDeviceTypes.size(); ++i) availableDeviceTypes[i]->scanForDevices(); } void AudioDeviceManager::setInputDeviceNamesUsed (const bool useInputNames_) { useInputNames = useInputNames_; sendChangeMessage (this); } void AudioDeviceManager::addDeviceNamesToComboBox (ComboBox& combo) const { int n = 0; for (int i = 0; i < availableDeviceTypes.size(); ++i) { AudioIODeviceType* const type = availableDeviceTypes[i]; if (availableDeviceTypes.size() > 1) combo.addSectionHeading (type->getTypeName() + T(" devices:")); const StringArray names (type->getDeviceNames (useInputNames)); for (int j = 0; j < names.size(); ++j) combo.addItem (names[j], ++n); combo.addSeparator(); } combo.addItem (TRANS("<< no audio device >>"), -1); } const String AudioDeviceManager::getCurrentAudioDeviceName() const { if (currentAudioDevice != 0) return currentAudioDevice->getName(); return String::empty; } const String AudioDeviceManager::setAudioDevice (const String& deviceNameToUse, int blockSizeToUse, double sampleRateToUse, const BitArray* inChans, const BitArray* outChans, const bool treatAsChosenDevice) { stopDevice(); String error; if (deviceNameToUse.isNotEmpty()) { const StringArray devNames (getAvailableAudioDeviceNames()); int index = devNames.indexOf (deviceNameToUse, true); if (index >= 0) { if (currentAudioDevice == 0 || currentAudioDevice->getLastError().isNotEmpty() || ! deviceNameToUse.equalsIgnoreCase (currentAudioDevice->getName())) { // change of device.. deleteAndZero (currentAudioDevice); int n = 0; for (int i = 0; i < availableDeviceTypes.size(); ++i) { AudioIODeviceType* const type = availableDeviceTypes[i]; const StringArray names (type->getDeviceNames (useInputNames)); if (index >= n && index < n + names.size()) { currentAudioDevice = type->createDevice (deviceNameToUse); break; } n += names.size(); } error = currentAudioDevice->getLastError(); if (error.isNotEmpty()) { deleteAndZero (currentAudioDevice); return error; } inputChannels.clear(); inputChannels.setRange (0, numInputChansNeeded, true); outputChannels.clear(); outputChannels.setRange (0, numOutputChansNeeded, true); } if (inChans != 0) inputChannels = *inChans; if (outChans != 0) outputChannels = *outChans; error = restartDevice (blockSizeToUse, sampleRateToUse, inputChannels, outputChannels); if (error.isNotEmpty()) { deleteAndZero (currentAudioDevice); } } else { deleteAndZero (currentAudioDevice); error << "No such device: " << deviceNameToUse; } } else { deleteAndZero (currentAudioDevice); } if (treatAsChosenDevice && error.isEmpty()) updateXml(); return error; } const String AudioDeviceManager::restartDevice (int blockSizeToUse, double sampleRateToUse, const BitArray& inChans, const BitArray& outChans) { stopDevice(); inputChannels = inChans; outputChannels = outChans; if (sampleRateToUse > 0) { bool ok = false; for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) { const double sr = currentAudioDevice->getSampleRate (i); if (sr == sampleRateToUse) ok = true; } if (! ok) sampleRateToUse = 0; } if (sampleRateToUse == 0) { double lowestAbove44 = 0.0; for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) { const double sr = currentAudioDevice->getSampleRate (i); if (sr >= 44100.0 && (lowestAbove44 == 0 || sr < lowestAbove44)) lowestAbove44 = sr; } if (lowestAbove44 == 0.0) sampleRateToUse = currentAudioDevice->getSampleRate (0); else sampleRateToUse = lowestAbove44; } const String error (currentAudioDevice->open (inChans, outChans, sampleRateToUse, blockSizeToUse)); if (error.isEmpty()) currentAudioDevice->start (&callbackHandler); sendChangeMessage (this); return error; } void AudioDeviceManager::stopDevice() { if (currentAudioDevice != 0) currentAudioDevice->stop(); } void AudioDeviceManager::setInputChannels (const BitArray& newEnabledChannels, const bool treatAsChosenDevice) { if (currentAudioDevice != 0 && newEnabledChannels != inputChannels) { setAudioDevice (currentAudioDevice->getName(), currentAudioDevice->getCurrentBufferSizeSamples(), currentAudioDevice->getCurrentSampleRate(), &newEnabledChannels, 0, treatAsChosenDevice); } } void AudioDeviceManager::setOutputChannels (const BitArray& newEnabledChannels, const bool treatAsChosenDevice) { if (currentAudioDevice != 0 && newEnabledChannels != outputChannels) { setAudioDevice (currentAudioDevice->getName(), currentAudioDevice->getCurrentBufferSizeSamples(), currentAudioDevice->getCurrentSampleRate(), 0, &newEnabledChannels, treatAsChosenDevice); } } void AudioDeviceManager::updateXml() { delete lastExplicitSettings; lastExplicitSettings = new XmlElement (T("DEVICESETUP")); lastExplicitSettings->setAttribute (T("audioDeviceName"), getCurrentAudioDeviceName()); if (currentAudioDevice != 0) { lastExplicitSettings->setAttribute (T("audioDeviceRate"), currentAudioDevice->getCurrentSampleRate()); if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) lastExplicitSettings->setAttribute (T("audioDeviceBufferSize"), currentAudioDevice->getCurrentBufferSizeSamples()); lastExplicitSettings->setAttribute (T("audioDeviceInChans"), inputChannels.toString (2)); lastExplicitSettings->setAttribute (T("audioDeviceOutChans"), outputChannels.toString (2)); } for (int i = 0; i < enabledMidiInputs.size(); ++i) { XmlElement* const m = new XmlElement (T("MIDIINPUT")); m->setAttribute (T("name"), enabledMidiInputs[i]->getName()); lastExplicitSettings->addChildElement (m); } if (defaultMidiOutputName.isNotEmpty()) lastExplicitSettings->setAttribute (T("defaultMidiOutput"), defaultMidiOutputName); } //============================================================================== void AudioDeviceManager::setAudioCallback (AudioIODeviceCallback* newCallback) { if (newCallback != currentCallback) { AudioIODeviceCallback* lastCallback = currentCallback; audioCallbackLock.enter(); currentCallback = 0; audioCallbackLock.exit(); if (currentAudioDevice != 0) { if (lastCallback != 0) lastCallback->audioDeviceStopped(); if (newCallback != 0) newCallback->audioDeviceAboutToStart (currentAudioDevice); } currentCallback = newCallback; } } void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels, float** outputChannelData, int totalNumOutputChannels, int numSamples) { const ScopedLock sl (audioCallbackLock); if (currentCallback != 0) { const double callbackStartTime = Time::getMillisecondCounterHiRes(); currentCallback->audioDeviceIOCallback (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples); const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; const double filterAmount = 0.2; cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); } } 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; } if (currentCallback != 0) currentCallback->audioDeviceAboutToStart (device); sendChangeMessage (this); } void AudioDeviceManager::audioDeviceStoppedInt() { cpuUsageMs = 0; timeToCpuScale = 0; sendChangeMessage (this); if (currentCallback != 0) currentCallback->audioDeviceStopped(); } 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) { MidiInput* const min = MidiInput::openDevice (index, &callbackHandler); if (min != 0) { enabledMidiInputs.add (min); min->start(); } } } else { for (int i = enabledMidiInputs.size(); --i >= 0;) if (enabledMidiInputs[i]->getName() == name) enabledMidiInputs.remove (i); } updateXml(); sendChangeMessage (this); } } 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* callback) { removeMidiInputCallback (callback); if (name.isEmpty()) { midiCallbacks.add (callback); midiCallbackDevices.add (0); } else { for (int i = enabledMidiInputs.size(); --i >= 0;) { if (enabledMidiInputs[i]->getName() == name) { const ScopedLock sl (midiCallbackLock); if (! midiCallbacks.contains (callback)) { midiCallbacks.add (callback); midiCallbackDevices.add (enabledMidiInputs[i]); } break; } } } } void AudioDeviceManager::removeMidiInputCallback (MidiInputCallback* callback) { const ScopedLock sl (midiCallbackLock); const int index = midiCallbacks.indexOf (callback); midiCallbacks.remove (index); midiCallbackDevices.remove (index); } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) { if (! message.isActiveSense()) { const bool isDefaultSource = (source == 0 || source == enabledMidiInputs.getFirst()); const ScopedLock sl (midiCallbackLock); for (int i = midiCallbackDevices.size(); --i >= 0;) { MidiInput* const md = midiCallbackDevices.getUnchecked(i); if (md == source || (md == 0 && isDefaultSource)) midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); } } } //============================================================================== void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) { if (defaultMidiOutputName != deviceName) { deleteAndZero (defaultMidiOutput); defaultMidiOutputName = deviceName; if (deviceName.isNotEmpty()) defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); updateXml(); sendChangeMessage (this); } } //============================================================================== void AudioDeviceManager::CallbackHandler::audioDeviceIOCallback (const float** inputChannelData, int totalNumInputChannels, float** outputChannelData, int totalNumOutputChannels, int numSamples) { owner->audioDeviceIOCallbackInt (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples); } void AudioDeviceManager::CallbackHandler::audioDeviceAboutToStart (AudioIODevice* device) { owner->audioDeviceAboutToStartInt (device); } void AudioDeviceManager::CallbackHandler::audioDeviceStopped() { owner->audioDeviceStoppedInt(); } void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) { owner->handleIncomingMidiMessageInt (source, message); } END_JUCE_NAMESPACE