diff --git a/build/linux/platform_specific_code/juce_linux_Audio.cpp b/build/linux/platform_specific_code/juce_linux_Audio.cpp index db4b1ee4cb..3a592f7d0d 100644 --- a/build/linux/platform_specific_code/juce_linux_Audio.cpp +++ b/build/linux/platform_specific_code/juce_linux_Audio.cpp @@ -78,7 +78,7 @@ static void getDeviceSampleRates (snd_pcm_t* handle, Array & rates) if (snd_pcm_hw_params_any (handle, hwParams) >= 0 && snd_pcm_hw_params_test_rate (handle, hwParams, ratesToTry[i], 0) == 0) { - rates.add (ratesToTry[i]); + rates.addIfNotAlreadyThere (ratesToTry[i]); } } } @@ -102,6 +102,9 @@ static void getDeviceProperties (const String& id, unsigned int& maxChansIn, Array & rates) { + if (id.isEmpty()) + return; + snd_ctl_t* handle; if (snd_ctl_open (&handle, id.upToLastOccurrenceOf (T(","), false, false), SND_CTL_NONBLOCK) >= 0) @@ -149,7 +152,7 @@ static void getDeviceProperties (const String& id, class ALSADevice { public: - ALSADevice (const String& deviceName, + ALSADevice (const String& id, const bool forInput) : handle (0), bitDepth (16), @@ -157,7 +160,7 @@ public: isInput (forInput), sampleFormat (AudioDataConverters::int16LE) { - failed (snd_pcm_open (&handle, deviceName, + failed (snd_pcm_open (&handle, id, forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC)); } @@ -364,12 +367,14 @@ private: class ALSAThread : public Thread { public: - ALSAThread (const String& deviceName_) + ALSAThread (const String& inputId_, + const String& outputId_) : Thread ("Juce ALSA"), sampleRate (0), bufferSize (0), callback (0), - deviceName (deviceName_), + inputId (inputId_), + outputId (outputId_), outputDevice (0), inputDevice (0), numCallbacks (0), @@ -402,16 +407,9 @@ public: currentInputChans.clear(); currentOutputChans.clear(); - numChannelsRunning = jmax (inputChannels.getHighestBit(), - outputChannels.getHighestBit()) + 1; - - numChannelsRunning = jmin (maxNumChans, jlimit ((int) minChansIn, - (int) maxChansIn, - numChannelsRunning)); - if (inputChannels.getHighestBit() >= 0) { - for (int i = 0; i < numChannelsRunning; ++i) + for (int i = 0; i <= inputChannels.getHighestBit(); ++i) { inputChannelData [i] = (float*) juce_calloc (sizeof (float) * bufferSize); @@ -425,7 +423,7 @@ public: if (outputChannels.getHighestBit() >= 0) { - for (int i = 0; i < numChannelsRunning; ++i) + for (int i = 0; i <= outputChannels.getHighestBit(); ++i) { outputChannelData [i] = (float*) juce_calloc (sizeof (float) * bufferSize); @@ -437,9 +435,9 @@ public: } } - if (totalNumOutputChannels > 0) + if (totalNumOutputChannels > 0 && outputId.isNotEmpty()) { - outputDevice = new ALSADevice (deviceName, false); + outputDevice = new ALSADevice (outputId, false); if (outputDevice->error.isNotEmpty()) { @@ -448,7 +446,9 @@ public: return; } - if (! outputDevice->setParameters ((unsigned int) sampleRate, numChannelsRunning, bufferSize)) + if (! outputDevice->setParameters ((unsigned int) sampleRate, + currentOutputChans.getHighestBit() + 1, + bufferSize)) { error = outputDevice->error; deleteAndZero (outputDevice); @@ -456,9 +456,9 @@ public: } } - if (totalNumInputChannels > 0) + if (totalNumInputChannels > 0 && inputId.isNotEmpty()) { - inputDevice = new ALSADevice (deviceName, true); + inputDevice = new ALSADevice (inputId, true); if (inputDevice->error.isNotEmpty()) { @@ -467,7 +467,9 @@ public: return; } - if (! inputDevice->setParameters ((unsigned int) sampleRate, numChannelsRunning, bufferSize)) + if (! inputDevice->setParameters ((unsigned int) sampleRate, + currentInputChans.getHighestBit() + 1, + bufferSize)) { error = inputDevice->error; deleteAndZero (inputDevice); @@ -527,7 +529,6 @@ public: zeromem (inputChannelDataForCallback, sizeof (inputChannelDataForCallback)); totalNumOutputChannels = 0; totalNumInputChannels = 0; - numChannelsRunning = 0; numCallbacks = 0; } @@ -544,8 +545,6 @@ public: { if (inputDevice != 0) { - jassert (numChannelsRunning >= inputDevice->numChannelsRunning); - if (! inputDevice->read (inputChannelData, bufferSize)) { DBG ("ALSA: read failure"); @@ -584,7 +583,6 @@ public: failed (snd_pcm_avail_update (outputDevice->handle)); - jassert (numChannelsRunning >= outputDevice->numChannelsRunning); if (! outputDevice->write (outputChannelData, bufferSize)) { DBG ("ALSA: write failure"); @@ -619,7 +617,7 @@ public: private: //============================================================================== - const String deviceName; + const String inputId, outputId; ALSADevice* outputDevice; ALSADevice* inputDevice; int numCallbacks; @@ -632,7 +630,6 @@ private: float* inputChannelData [maxNumChans]; float* inputChannelDataForCallback [maxNumChans]; int totalNumOutputChannels; - int numChannelsRunning; unsigned int minChansOut, maxChansOut; unsigned int minChansIn, maxChansIn; @@ -656,8 +653,10 @@ private: maxChansOut = 0; minChansIn = 0; maxChansIn = 0; + unsigned int dummy = 0; - getDeviceProperties (deviceName, minChansOut, maxChansOut, minChansIn, maxChansIn, sampleRates); + getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates); + getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates); unsigned int i; for (i = 0; i < maxChansOut; ++i) @@ -674,13 +673,16 @@ class ALSAAudioIODevice : public AudioIODevice { public: ALSAAudioIODevice (const String& deviceName, - const String& deviceId) + const String& inputId_, + const String& outputId_) : AudioIODevice (deviceName, T("ALSA")), + inputId (inputId_), + outputId (outputId_), isOpen_ (false), isStarted (false), internal (0) { - internal = new ALSAThread (deviceId); + internal = new ALSAThread (inputId, outputId); } ~ALSAAudioIODevice() @@ -839,6 +841,8 @@ public: return internal->error; } + String inputId, outputId; + private: bool isOpen_, isStarted; ALSAThread* internal; @@ -863,10 +867,14 @@ public: //============================================================================== void scanForDevices() { - hasScanned = true; + if (hasScanned) + return; - names.clear(); - ids.clear(); + hasScanned = true; + inputNames.clear(); + inputIds.clear(); + outputNames.clear(); + outputIds.clear(); snd_ctl_t* handle; snd_ctl_card_info_t* info; @@ -874,7 +882,7 @@ public: int cardNum = -1; - while (ids.size() <= 24) + while (outputIds.size() + inputIds.size() <= 32) { snd_card_next (&cardNum); @@ -900,18 +908,26 @@ public: String id, name; id << "hw:" << cardId << ',' << device; - if (testDevice (id)) + bool isInput, isOutput; + + if (testDevice (id, isInput, isOutput)) { name << snd_ctl_card_info_get_name (info); if (name.isEmpty()) name = id; - if (device > 0) - name << " (" << (device + 1) << ')'; + if (isInput) + { + inputNames.add (name); + inputIds.add (id); + } - ids.add (id); - names.add (name); + if (isOutput) + { + outputNames.add (name); + outputIds.add (id); + } } } } @@ -919,35 +935,54 @@ public: snd_ctl_close (handle); } } + + inputNames.appendNumbersToDuplicates (false, true); + outputNames.appendNumbersToDuplicates (false, true); } - const StringArray getDeviceNames (const bool /*preferInputNames*/) const + const StringArray getDeviceNames (const bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this - StringArray namesCopy (names); - namesCopy.removeDuplicates (true); - - return namesCopy; + return wantInputNames ? inputNames : outputNames; } - const String getDefaultDeviceName (const bool /*preferInputNames*/, - const int /*numInputChannelsNeeded*/, - const int /*numOutputChannelsNeeded*/) const + int getDefaultDeviceIndex (const bool forInput) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + return 0; + } + + bool hasSeparateInputsAndOutputs() const { return true; } + + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - return names[0]; + ALSAAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; + + return asInput ? inputIds.indexOf (d->inputId) + : outputIds.indexOf (d->outputId); } - AudioIODevice* createDevice (const String& deviceName) + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) { jassert (hasScanned); // need to call scanForDevices() before doing this - const int index = names.indexOf (deviceName); + const int inputIndex = inputNames.indexOf (inputDeviceName); + const int outputIndex = outputNames.indexOf (outputDeviceName); + + String deviceName (outputDeviceName); + if (deviceName.isEmpty()) + deviceName = inputDeviceName; if (index >= 0) - return new ALSAAudioIODevice (deviceName, ids [index]); + return new ALSAAudioIODevice (deviceName, + inputIds [inputIndex], + outputIds [outputIndex]); return 0; } @@ -956,10 +991,10 @@ public: juce_UseDebuggingNewOperator private: - StringArray names, ids; + StringArray inputNames, outputNames, inputIds, outputIds; bool hasScanned; - static bool testDevice (const String& id) + static bool testDevice (const String& id, bool& isInput, bool& isOutput) { unsigned int minChansOut = 0, maxChansOut = 0; unsigned int minChansIn = 0, maxChansIn = 0; @@ -972,7 +1007,10 @@ private: + T(" ins=") + String ((int) minChansIn) + T("-") + String ((int) maxChansIn) + T(" rates=") + String (rates.size())); - return (maxChansOut > 0 || maxChansIn > 0) && rates.size() > 0; + isInput = maxChansIn > 0; + isOutput = maxChansOut > 0; + + return (isInput || isOutput) && rates.size() > 0; } ALSAAudioIODeviceType (const ALSAAudioIODeviceType&); diff --git a/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp b/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp index 1af1458ee4..3844805e27 100644 --- a/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp +++ b/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp @@ -40,6 +40,11 @@ BEGIN_JUCE_NAMESPACE #include "../../../src/juce_core/threads/juce_ScopedLock.h" #include "../../../src/juce_core/threads/juce_Thread.h" #include "../../../src/juce_core/text/juce_LocalisedStrings.h" +#include "../../../src/juce_appframework/gui/components/buttons/juce_TextButton.h" +#include "../../../src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h" +#include "../../../src/juce_appframework/gui/components/controls/juce_ComboBox.h" +#include "../../../src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h" +#include "../../../src/juce_appframework/gui/components/windows/juce_AlertWindow.h" //============================================================================== @@ -613,7 +618,7 @@ public: for (i = numOutputChans; --i >= 0;) { const CallbackDetailsForChannel& info = outputChannelInfo[i]; - const float* src = tempOutputBuffers [info.sourceChannelNum]; + const float* src = tempOutputBuffers [i]; float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples; const int stride = info.dataStrideSamples; @@ -833,31 +838,52 @@ class CoreAudioIODevice : public AudioIODevice { public: CoreAudioIODevice (const String& deviceName, - AudioDeviceID deviceId1) + AudioDeviceID inputDeviceId, + const int inputIndex_, + AudioDeviceID outputDeviceId, + const int outputIndex_) : AudioIODevice (deviceName, "CoreAudio"), + inputIndex (inputIndex_), + outputIndex (outputIndex_), isOpen_ (false), isStarted (false) { internal = 0; + CoreAudioInternal* device = 0; - CoreAudioInternal* device = new CoreAudioInternal (deviceId1); - lastError = device->error; - - if (lastError.isNotEmpty()) + if (outputDeviceId == 0 || outputDeviceId == inputDeviceId) { - deleteAndZero (device); + jassert (inputDeviceId != 0); + + device = new CoreAudioInternal (inputDeviceId); + lastError = device->error; + + if (lastError.isNotEmpty()) + deleteAndZero (device); } else { - CoreAudioInternal* secondDevice = device->getRelatedDevice(); + device = new CoreAudioInternal (outputDeviceId); + lastError = device->error; - if (secondDevice != 0) + if (lastError.isNotEmpty()) { - if (device->inChanNames.size() > secondDevice->inChanNames.size()) - swapVariables (device, secondDevice); + deleteAndZero (device); + } + else if (inputDeviceId != 0) + { + CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId); + lastError = device->error; - device->inputDevice = secondDevice; - secondDevice->isSlaveDevice = true; + if (lastError.isNotEmpty()) + { + delete secondDevice; + } + else + { + device->inputDevice = secondDevice; + secondDevice->isSlaveDevice = true; + } } } @@ -1035,6 +1061,8 @@ public: return lastError; } + int inputIndex, outputIndex; + juce_UseDebuggingNewOperator private: @@ -1065,6 +1093,326 @@ private: const CoreAudioIODevice& operator= (const CoreAudioIODevice&); }; +//============================================================================== +class CoreAudioDevicePanel : public Component, + public ComboBoxListener, + public ChangeListener, + public ButtonListener +{ +public: + CoreAudioDevicePanel (AudioIODeviceType* type_, + AudioIODeviceType::DeviceSetupDetails& setup_) + : type (type_), + setup (setup_) + { + sampleRateDropDown = 0; + sampleRateLabel = 0; + bufferSizeDropDown = 0; + bufferSizeLabel = 0; + outputDeviceDropDown = 0; + outputDeviceLabel = 0; + inputDeviceDropDown = 0; + inputDeviceLabel = 0; + testButton = 0; + inputLevelMeter = 0; + + type->scanForDevices(); + + if (setup.maxNumOutputChannels > 0) + { + outputDeviceDropDown = new ComboBox (String::empty); + addNamesToDeviceBox (*outputDeviceDropDown, false); + outputDeviceDropDown->addListener (this); + addAndMakeVisible (outputDeviceDropDown); + + outputDeviceLabel = new Label (String::empty, TRANS ("output:")); + outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); + + addAndMakeVisible (testButton = new TextButton (TRANS ("Test"))); + testButton->addButtonListener (this); + } + + if (setup.maxNumInputChannels > 0) + { + inputDeviceDropDown = new ComboBox (String::empty); + addNamesToDeviceBox (*inputDeviceDropDown, true); + inputDeviceDropDown->addListener (this); + addAndMakeVisible (inputDeviceDropDown); + + inputDeviceLabel = new Label (String::empty, TRANS ("input:")); + inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); + + addAndMakeVisible (inputLevelMeter = AudioDeviceSelectorComponent::createSimpleLevelMeterComponent (setup_.manager)); + } + + setup.manager->addChangeListener (this); + changeListenerCallback (0); + } + + ~CoreAudioDevicePanel() + { + setup.manager->removeChangeListener (this); + + deleteAndZero (outputDeviceLabel); + deleteAndZero (inputDeviceLabel); + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAllChildren(); + } + + void resized() + { + const int lx = proportionOfWidth (0.35f); + const int w = proportionOfWidth (0.5f); + const int h = 24; + const int space = 6; + const int dh = h + space; + int y = 0; + + if (outputDeviceDropDown != 0) + { + outputDeviceDropDown->setBounds (lx, y, w, h); + testButton->setBounds (outputDeviceDropDown->getRight() + 8, + outputDeviceDropDown->getY(), + getWidth() - outputDeviceDropDown->getRight() - 10, + h); + y += dh; + } + + if (inputDeviceDropDown != 0) + { + inputDeviceDropDown->setBounds (lx, y, w, h); + + inputLevelMeter->setBounds (inputDeviceDropDown->getRight() + 8, + inputDeviceDropDown->getY(), + getWidth() - inputDeviceDropDown->getRight() - 10, + h); + y += dh; + } + + y += space * 2; + + if (sampleRateDropDown != 0) + { + sampleRateDropDown->setBounds (lx, y, w, h); + y += dh; + } + + if (bufferSizeDropDown != 0) + { + bufferSizeDropDown->setBounds (lx, y, w, h); + y += dh; + } + } + + void comboBoxChanged (ComboBox* comboBoxThatHasChanged) + { + if (comboBoxThatHasChanged == 0) + return; + + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + String error; + + if (comboBoxThatHasChanged == outputDeviceDropDown + || comboBoxThatHasChanged == inputDeviceDropDown) + { + config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty + : outputDeviceDropDown->getText(); + config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty + : inputDeviceDropDown->getText(); + + if (comboBoxThatHasChanged == inputDeviceDropDown) + config.useDefaultInputChannels = true; + else + config.useDefaultOutputChannels = true; + + error = setup.manager->setAudioDeviceSetup (config, true); + + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + } + else if (comboBoxThatHasChanged == sampleRateDropDown) + { + if (sampleRateDropDown->getSelectedId() > 0) + { + config.sampleRate = sampleRateDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + else if (comboBoxThatHasChanged == bufferSizeDropDown) + { + if (bufferSizeDropDown->getSelectedId() > 0) + { + config.bufferSize = bufferSizeDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + + if (error.isNotEmpty()) + { + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + T("Error when trying to open audio device!"), + error); + } + } + + void buttonClicked (Button*) + { + setup.manager->playTestSound(); + } + + void changeListenerCallback (void*) + { + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + if (currentDevice != 0) + { + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + + // sample rate.. + { + if (sampleRateDropDown == 0) + { + addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty)); + sampleRateDropDown->addListener (this); + + delete sampleRateLabel; + sampleRateLabel = new Label (String::empty, TRANS ("sample rate:")); + sampleRateLabel->attachToComponent (sampleRateDropDown, true); + } + else + { + sampleRateDropDown->clear(); + sampleRateDropDown->removeListener (this); + } + + const int numRates = currentDevice->getNumSampleRates(); + + for (int i = 0; i < numRates; ++i) + { + const int rate = roundDoubleToInt (currentDevice->getSampleRate (i)); + sampleRateDropDown->addItem (String (rate) + T(" Hz"), rate); + } + + sampleRateDropDown->setSelectedId (roundDoubleToInt (currentDevice->getCurrentSampleRate()), true); + sampleRateDropDown->addListener (this); + } + + // buffer size + { + if (bufferSizeDropDown == 0) + { + addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty)); + bufferSizeDropDown->addListener (this); + + delete bufferSizeLabel; + bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:")); + bufferSizeLabel->attachToComponent (bufferSizeDropDown, true); + } + else + { + bufferSizeDropDown->clear(); + } + + const int numBufferSizes = currentDevice->getNumBufferSizesAvailable(); + double currentRate = currentDevice->getCurrentSampleRate(); + if (currentRate == 0) + currentRate = 44100.0; + + for (int i = 0; i < numBufferSizes; ++i) + { + const int bs = currentDevice->getBufferSizeSamples (i); + bufferSizeDropDown->addItem (String (bs) + + T(" samples (") + + String (bs * 1000.0 / currentRate, 1) + + T(" ms)"), + bs); + } + + bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), true); + } + } + else + { + jassert (setup.manager->getCurrentAudioDevice() == 0); // not the correct device type! + + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAndZero (sampleRateDropDown); + deleteAndZero (bufferSizeDropDown); + + if (outputDeviceDropDown != 0) + outputDeviceDropDown->setSelectedId (-1, true); + + if (inputDeviceDropDown != 0) + inputDeviceDropDown->setSelectedId (-1, true); + } + + resized(); + setSize (getWidth(), getLowestY() + 4); + } + +private: + AudioIODeviceType* const type; + const AudioIODeviceType::DeviceSetupDetails setup; + + ComboBox* outputDeviceDropDown; + ComboBox* inputDeviceDropDown; + ComboBox* sampleRateDropDown; + ComboBox* bufferSizeDropDown; + Label* outputDeviceLabel; + Label* inputDeviceLabel; + Label* sampleRateLabel; + Label* bufferSizeLabel; + TextButton* testButton; + Component* inputLevelMeter; + + void showCorrectDeviceName (ComboBox* const box, const bool isInput) + { + if (box != 0) + { + CoreAudioIODevice* const currentDevice = dynamic_cast (setup.manager->getCurrentAudioDevice()); + + const int index = (currentDevice == 0) ? -1 + : (isInput ? currentDevice->inputIndex + : currentDevice->outputIndex); + + if (index >= 0) + box->setText (type->getDeviceNames (isInput) [index], true); + else + box->setSelectedId (-1, true); + + if (! isInput) + testButton->setEnabled (index >= 0); + } + } + + void addNamesToDeviceBox (ComboBox& combo, bool isInputs) + { + const StringArray devs (type->getDeviceNames (isInputs)); + + for (int i = 0; i < devs.size(); ++i) + combo.addItem (devs[i], i + 1); + + combo.addItem (TRANS("<< none >>"), -1); + combo.setSelectedId (-1, true); + } + + int getLowestY() const + { + int y = 0; + + for (int i = getNumChildComponents(); --i >= 0;) + y = jmax (y, getChildComponent (i)->getBottom()); + + return y; + } + + CoreAudioDevicePanel (const CoreAudioDevicePanel&); + const CoreAudioDevicePanel& operator= (const CoreAudioDevicePanel&); +}; //============================================================================== class CoreAudioIODeviceType : public AudioIODeviceType @@ -1086,8 +1434,10 @@ public: { hasScanned = true; - names.clear(); - ids.clear(); + inputDeviceNames.clear(); + outputDeviceNames.clear(); + inputIds.clear(); + outputIds.clear(); UInt32 size; if (OK (AudioHardwareGetPropertyInfo (kAudioHardwarePropertyDevices, &size, 0))) @@ -1109,8 +1459,20 @@ public: if (! alreadyLogged) log (T("CoreAudio device: ") + nameString); - names.add (nameString); - ids.add (devs[i]); + const int numIns = getNumChannels (devs[i], true); + const int numOuts = getNumChannels (devs[i], false); + + if (numIns > 0) + { + inputDeviceNames.add (nameString); + inputIds.add (devs[i]); + } + + if (numOuts > 0) + { + outputDeviceNames.add (nameString); + outputIds.add (devs[i]); + } } } @@ -1119,52 +1481,83 @@ public: juce_free (devs); } + + inputDeviceNames.appendNumbersToDuplicates (false, true); + outputDeviceNames.appendNumbersToDuplicates (false, true); } - const StringArray getDeviceNames (const bool /*preferInputNames*/) const + const StringArray getDeviceNames (const bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this - StringArray namesCopy (names); - namesCopy.removeDuplicates (true); - - return namesCopy; + if (wantInputNames) + return inputDeviceNames; + else + return outputDeviceNames; } - const String getDefaultDeviceName (const bool preferInputNames, - const int numInputChannelsNeeded, - const int numOutputChannelsNeeded) const + int getDefaultDeviceIndex (const bool forInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - String result (names[0]); - AudioDeviceID deviceID; UInt32 size = sizeof (deviceID); // if they're asking for any input channels at all, use the default input, so we // get the built-in mic rather than the built-in output with no inputs.. - if (AudioHardwareGetProperty (numInputChannelsNeeded > 0 - ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, + if (AudioHardwareGetProperty (forInput ? kAudioHardwarePropertyDefaultInputDevice + : kAudioHardwarePropertyDefaultOutputDevice, &size, &deviceID) == noErr) { - for (int i = ids.size(); --i >= 0;) - if (ids[i] == deviceID) - result = names[i]; + if (forInput) + { + for (int i = inputIds.size(); --i >= 0;) + if (inputIds[i] == deviceID) + return i; + } + else + { + for (int i = outputIds.size(); --i >= 0;) + if (outputIds[i] == deviceID) + return i; + } } - return result; + return 0; } - AudioIODevice* createDevice (const String& deviceName) + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - const int index = names.indexOf (deviceName); + CoreAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; + + return asInput ? d->inputIndex + : d->outputIndex; + } + + bool hasSeparateInputsAndOutputs() const { return true; } + + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); + const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); + + String deviceName (outputDeviceName); + if (deviceName.isEmpty()) + deviceName = inputDeviceName; if (index >= 0) - return new CoreAudioIODevice (deviceName, ids [index]); + return new CoreAudioIODevice (deviceName, + inputIds [inputIndex], + inputIndex, + outputIds [outputIndex], + outputIndex); return 0; } @@ -1173,11 +1566,37 @@ public: juce_UseDebuggingNewOperator private: - StringArray names; - Array ids; + StringArray inputDeviceNames, outputDeviceNames; + Array inputIds, outputIds; bool hasScanned; + static int getNumChannels (AudioDeviceID deviceID, bool input) + { + int total = 0; + UInt32 size; + + if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, 0))) + { + AudioBufferList* const bufList = (AudioBufferList*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, bufList))) + { + const int numStreams = bufList->mNumberBuffers; + + for (int i = 0; i < numStreams; ++i) + { + const AudioBuffer& b = bufList->mBuffers[i]; + total += b.mNumberChannels; + } + } + + juce_free (bufList); + } + + return total; + } + CoreAudioIODeviceType (const CoreAudioIODeviceType&); const CoreAudioIODeviceType& operator= (const CoreAudioIODeviceType&); }; diff --git a/build/win32/platform_specific_code/juce_win32_ASIO.cpp b/build/win32/platform_specific_code/juce_win32_ASIO.cpp index b2ff61e75a..7b96e76700 100644 --- a/build/win32/platform_specific_code/juce_win32_ASIO.cpp +++ b/build/win32/platform_specific_code/juce_win32_ASIO.cpp @@ -66,12 +66,17 @@ BEGIN_JUCE_NAMESPACE #include "../../../src/juce_appframework/audio/devices/juce_AudioIODeviceType.h" +#include "../../../src/juce_appframework/audio/devices/juce_AudioDeviceManager.h" +#include "../../../src/juce_appframework/gui/components/buttons/juce_TextButton.h" +#include "../../../src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h" #include "../../../src/juce_core/threads/juce_ScopedLock.h" -#include "../../../src/juce_appframework/gui/components/juce_Component.h" #include "../../../src/juce_core/basics/juce_Time.h" #include "../../../src/juce_core/threads/juce_Thread.h" #include "../../../src/juce_appframework/events/juce_Timer.h" #include "../../../src/juce_appframework/events/juce_MessageManager.h" +#include "../../../src/juce_appframework/gui/components/windows/juce_AlertWindow.h" +#include "../../../src/juce_appframework/gui/components/controls/juce_ListBox.h" +#include "../../../src/juce_core/text/juce_LocalisedStrings.h" //============================================================================== @@ -120,7 +125,6 @@ static const int maxASIOChannels = 160; //============================================================================== class JUCE_API ASIOAudioIODevice : public AudioIODevice, - private Thread, private Timer { public: @@ -128,7 +132,6 @@ public: ASIOAudioIODevice (const String& name_, const CLSID classId_, const int slotNumber) : AudioIODevice (name_, T("ASIO")), - Thread ("Juce ASIO"), asioObject (0), classId (classId_), currentBitDepth (16), @@ -496,8 +499,6 @@ public: { buffersCreated = true; - jassert (! isThreadRunning()); - juce_free (tempBuffer); tempBuffer = (float*) juce_calloc (totalBuffers * currentBlockSizeSamples * sizeof (float) + 128); @@ -601,7 +602,6 @@ public: } isOpen_ = true; - isThreadReady = false; log ("starting ASIO"); calledback = false; @@ -808,26 +808,6 @@ public: return done; } - void run() - { - isThreadReady = true; - - for (;;) - { - event1.wait(); - - if (threadShouldExit()) - break; - - processBuffer(); - } - - if (bufferIndex < 0) - { - log ("! ASIO callback never called"); - } - } - void resetRequest() throw() { needToReset = true; @@ -912,7 +892,7 @@ private: bool isOpen_, isStarted; bool volatile isASIOOpen; bool volatile calledback; - bool volatile littleEndian, postOutput, needToReset, isReSync, isThreadReady; + bool volatile littleEndian, postOutput, needToReset, isReSync; bool volatile insideControlPanelModalLoop; bool volatile shouldUsePreferredSize; @@ -1809,46 +1789,65 @@ public: } } - const StringArray getDeviceNames (const bool /*preferInputNames*/) const + const StringArray getDeviceNames (const bool /*wantInputNames*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this return deviceNames; } - const String getDefaultDeviceName (const bool /*preferInputNames*/, - const int /*numInputChannelsNeeded*/, - const int /*numOutputChannelsNeeded*/) const + int getDefaultDeviceIndex (const bool) const { jassert (hasScanned); // need to call scanForDevices() before doing this - return deviceNames [0]; + for (int i = deviceNames.size(); --i >= 0;) + if (deviceNames[i].containsIgnoreCase (T("asio4all"))) + return i; // asio4all is a safe choice for a default.. + +#if JUCE_DEBUG + if (deviceNames.size() > 1 && deviceNames[0].containsIgnoreCase (T("digidesign"))) + return 1; // (the digi m-box driver crashes the app when you run + // it in the debugger, which can be a bit annoying) +#endif + + return 0; } - AudioIODevice* createDevice (const String& deviceName) + static int findFreeSlot() + { + for (int i = 0; i < numElementsInArray (currentASIODev); ++i) + if (currentASIODev[i] == 0) + return i; + + jassertfalse; // unfortunately you can only have a finite number + // of ASIO devices open at the same time.. + return -1; + } + + int getIndexOfDevice (AudioIODevice* d, const bool /*asInput*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this - const int index = deviceNames.indexOf (deviceName); + return d == 0 ? -1 : deviceNames.indexOf (d->getName()); + } + + bool hasSeparateInputsAndOutputs() const { return false; } + + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (inputDeviceName == outputDeviceName); + (void) inputDeviceName; + jassert (hasScanned); // need to call scanForDevices() before doing this + + const int index = deviceNames.indexOf (outputDeviceName); if (index >= 0) { - int freeSlot = -1; - - for (int i = 0; i < numElementsInArray (currentASIODev); ++i) - { - if (currentASIODev[i] == 0) - { - freeSlot = i; - break; - } - } - - jassert (freeSlot >= 0); // unfortunately you can only have a finite number - // of ASIO devices open at the same time.. + const int freeSlot = findFreeSlot(); if (freeSlot >= 0) - return new ASIOAudioIODevice (deviceName, *(classIds [index]), freeSlot); + return new ASIOAudioIODevice (outputDeviceName, *(classIds [index]), freeSlot); } return 0; @@ -1923,6 +1922,7 @@ private: return ok; } + //============================================================================== void addDriverInfo (const String& keyName, HKEY hk) { HKEY subKey; @@ -1974,6 +1974,17 @@ AudioIODeviceType* juce_createASIOAudioIODeviceType() return new ASIOAudioIODeviceType(); } +AudioIODevice* juce_createASIOAudioIODeviceForGUID (const String& name, + void* guid) +{ + const int freeSlot = ASIOAudioIODeviceType::findFreeSlot(); + + if (freeSlot < 0) + return 0; + + return new ASIOAudioIODevice (name, *(CLSID*) guid, freeSlot); +} + END_JUCE_NAMESPACE diff --git a/build/win32/platform_specific_code/juce_win32_DirectSound.cpp b/build/win32/platform_specific_code/juce_win32_DirectSound.cpp index 05aef1baac..7627567813 100644 --- a/build/win32/platform_specific_code/juce_win32_DirectSound.cpp +++ b/build/win32/platform_specific_code/juce_win32_DirectSound.cpp @@ -141,12 +141,16 @@ DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown) BEGIN_JUCE_NAMESPACE #include "../../../src/juce_appframework/audio/devices/juce_AudioIODeviceType.h" +#include "../../../src/juce_appframework/audio/devices/juce_AudioDeviceManager.h" #include "../../../src/juce_appframework/application/juce_Application.h" +#include "../../../src/juce_appframework/gui/components/windows/juce_AlertWindow.h" #include "../../../src/juce_core/threads/juce_Thread.h" #include "../../../src/juce_core/basics/juce_Singleton.h" #include "../../../src/juce_core/basics/juce_Time.h" #include "../../../src/juce_core/containers/juce_OwnedArray.h" #include "../../../src/juce_core/text/juce_LocalisedStrings.h" +#include "../../../src/juce_appframework/gui/components/buttons/juce_TextButton.h" +#include "../../../src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h" static const String getDSErrorMessage (HRESULT hr) @@ -949,7 +953,7 @@ public: }; //============================================================================== -static int findBestMatchForName (const String& name, const StringArray& names) +/*static int findBestMatchForName (const String& name, const StringArray& names) { int i = names.indexOf (name); @@ -978,21 +982,21 @@ static int findBestMatchForName (const String& name, const StringArray& names) } return bestResult; -} +}*/ class DSoundAudioIODevice : public AudioIODevice, public Thread { public: DSoundAudioIODevice (const String& deviceName, - const int index, - const int inputIndex_) + const int outputDeviceIndex_, + const int inputDeviceIndex_) : AudioIODevice (deviceName, "DirectSound"), Thread ("Juce DSound"), isOpen_ (false), isStarted (false), - deviceIndex (index), - inputIndex (inputIndex_), + outputDeviceIndex (outputDeviceIndex_), + inputDeviceIndex (inputDeviceIndex_), inChans (4), outChans (4), numInputBuffers (0), @@ -1004,6 +1008,17 @@ public: callback (0), bufferSizeSamples (0) { + if (outputDeviceIndex_ >= 0) + { + outChannels.add (TRANS("Left")); + outChannels.add (TRANS("Right")); + } + + if (inputDeviceIndex_ >= 0) + { + inChannels.add (TRANS("Left")); + inChannels.add (TRANS("Right")); + } } ~DSoundAudioIODevice() @@ -1176,13 +1191,13 @@ public: juce_UseDebuggingNewOperator StringArray inChannels, outChannels; + int outputDeviceIndex, inputDeviceIndex; private: bool isOpen_; bool isStarted; String lastError; - int deviceIndex, inputIndex; OwnedArray inChans; OwnedArray outChans; WaitableEvent startEvent; @@ -1381,6 +1396,7 @@ public: } }; + //============================================================================== class DSoundAudioIODeviceType : public AudioIODeviceType { @@ -1413,72 +1429,46 @@ public: } } - const StringArray getDeviceNames (const bool preferInputNames) const + const StringArray getDeviceNames (const bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this - return preferInputNames ? inputDeviceNames - : outputDeviceNames; + return wantInputNames ? inputDeviceNames + : outputDeviceNames; } - const String getDefaultDeviceName (const bool preferInputNames, - const int /*numInputChannelsNeeded*/, - const int /*numOutputChannelsNeeded*/) const + int getDefaultDeviceIndex (const bool /*forInput*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this - - return getDeviceNames (preferInputNames) [0]; + return 0; } - AudioIODevice* createDevice (const String& deviceName) + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - if (deviceName.isEmpty() || deviceName.equalsIgnoreCase (T("DirectSound"))) - { - DSoundAudioIODevice* device = new DSoundAudioIODevice (deviceName, -1, -1); + DSoundAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; - int i; - for (i = 0; i < outputDeviceNames.size(); ++i) - { - device->outChannels.add (outputDeviceNames[i] + TRANS(" (left)")); - device->outChannels.add (outputDeviceNames[i] + TRANS(" (right)")); - } + return asInput ? d->inputDeviceIndex + : d->outputDeviceIndex; + } - for (i = 0; i < inputDeviceNames.size(); ++i) - { - device->inChannels.add (inputDeviceNames[i] + TRANS(" (left)")); - device->inChannels.add (inputDeviceNames[i] + TRANS(" (right)")); - } + bool hasSeparateInputsAndOutputs() const { return true; } - return device; - } - else if (outputDeviceNames.contains (deviceName) - || inputDeviceNames.contains (deviceName)) - { - int outputIndex = outputDeviceNames.indexOf (deviceName); - int inputIndex = findBestMatchForName (deviceName, inputDeviceNames); + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (hasScanned); // need to call scanForDevices() before doing this - if (outputIndex < 0) - { - // using an input device name instead.. - inputIndex = inputDeviceNames.indexOf (deviceName); - outputIndex = jmax (0, findBestMatchForName (deviceName, outputDeviceNames)); - } + const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); + const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); - DSoundAudioIODevice* device = new DSoundAudioIODevice (deviceName, outputIndex, inputIndex); - - device->outChannels.add (TRANS("Left")); - device->outChannels.add (TRANS("Right")); - - if (inputIndex >= 0) - { - device->inChannels.add (TRANS("Left")); - device->inChannels.add (TRANS("Right")); - } - - return device; - } + if (outputIndex >= 0 || inputIndex >= 0) + return new DSoundAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName + : inputDeviceName, + outputIndex, inputIndex); return 0; } @@ -1619,8 +1609,8 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, right = inputBuffers[numIns++] = (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)); if (left != 0 || right != 0) - inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [i / 2], - dlh.inputGuids [i / 2], + inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [inputDeviceIndex], + dlh.inputGuids [inputDeviceIndex], (int) sampleRate, bufferSizeSamples, left, right)); } @@ -1647,8 +1637,8 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, right = outputBuffers[numOuts++] = (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)); if (left != 0 || right != 0) - outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[i / 2], - dlh.outputGuids [i / 2], + outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[outputDeviceIndex], + dlh.outputGuids [outputDeviceIndex], (int) sampleRate, bufferSizeSamples, left, right)); } @@ -1714,6 +1704,7 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, return error; } + #undef log END_JUCE_NAMESPACE diff --git a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp index 6c1ae6063f..89662f7fd4 100644 --- a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp +++ b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp @@ -154,7 +154,7 @@ void AudioFilterStreamingDeviceManager::setFilter (AudioProcessor* filterToStrea { if (streamer != 0) { - removeMidiInputCallback (streamer); + removeMidiInputCallback (String::empty, streamer); setAudioCallback (0); delete streamer; diff --git a/extras/juce demo/src/demos/AudioDemo.cpp b/extras/juce demo/src/demos/AudioDemo.cpp index ee410345b8..ce92b1f7c7 100644 --- a/extras/juce demo/src/demos/AudioDemo.cpp +++ b/extras/juce demo/src/demos/AudioDemo.cpp @@ -479,7 +479,7 @@ public: ~AudioDemo() { - audioDeviceManager.removeMidiInputCallback (&synthSource.midiCollector); + audioDeviceManager.removeMidiInputCallback (String::empty, &synthSource.midiCollector); audioDeviceManager.setAudioCallback (0); transportSource.removeChangeListener (this); diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 34c2ed6c82..8c08fc711c 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -801,14 +801,16 @@ Time::Time (const int year, const int hours, const int minutes, const int seconds, - const int milliseconds) throw() + const int milliseconds, + const bool useLocalTime) throw() { jassert (year > 100); // year must be a 4-digit version - if (year < 1971 || year >= 2038) + if (year < 1971 || year >= 2038 || ! useLocalTime) { // use extended maths for dates beyond 1970 to 2037.. - const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); + const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) + : 0; const int a = (13 - month) / 12; const int y = year + 4800 - a; const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 @@ -14820,11 +14822,11 @@ int JUCEApplication::shutdownAppAndClearUp (const bool useMaximumForce) int JUCEApplication::main (int argc, char* argv[], JUCEApplication* const newApp) { - juce_setCurrentExecutableFileName (argv[0]); + juce_setCurrentExecutableFileName (String::fromUTF8 ((const uint8*) argv[0])); String cmd; for (int i = 1; i < argc; ++i) - cmd << argv[i] << T(' '); + cmd << String::fromUTF8 ((const uint8*) argv[i]) << T(' '); return JUCEApplication::main (cmd, newApp); } @@ -20802,6 +20804,26 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE +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; +} + AudioDeviceManager::AudioDeviceManager() : currentAudioDevice (0), currentCallback (0), @@ -20810,6 +20832,9 @@ AudioDeviceManager::AudioDeviceManager() lastExplicitSettings (0), listNeedsScanning (true), useInputNames (false), + inputLevelMeasurementEnabled (false), + inputLevel (0), + testSound (0), enabledMidiInputs (4), midiCallbacks (4), midiCallbackDevices (4), @@ -20818,16 +20843,54 @@ AudioDeviceManager::AudioDeviceManager() timeToCpuScale (0) { callbackHandler.owner = this; - - AudioIODeviceType::createDeviceTypes (availableDeviceTypes); } AudioDeviceManager::~AudioDeviceManager() { - stopDevice(); deleteAndZero (currentAudioDevice); deleteAndZero (defaultMidiOutput); delete lastExplicitSettings; + delete testSound; +} + +void AudioDeviceManager::createDeviceTypesIfNeeded() +{ + if (availableDeviceTypes.size() == 0) + { + createAudioDeviceTypes (availableDeviceTypes); + + while (lastDeviceTypeConfigs.size() < availableDeviceTypes.size()) + lastDeviceTypeConfigs.add (new AudioDeviceSetup()); + + if (availableDeviceTypes.size() > 0) + currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); + } +} + +extern AudioIODeviceType* juce_createDefaultAudioIODeviceType(); + +#if JUCE_WIN32 && JUCE_ASIO + extern AudioIODeviceType* juce_createASIOAudioIODeviceType(); +#endif + +#if JUCE_WIN32 && JUCE_WDM_AUDIO + extern AudioIODeviceType* juce_createWDMAudioIODeviceType(); +#endif + +void AudioDeviceManager::createAudioDeviceTypes (OwnedArray & list) +{ + AudioIODeviceType* const defaultDeviceType = juce_createDefaultAudioIODeviceType(); + + if (defaultDeviceType != 0) + list.add (defaultDeviceType); + +#if JUCE_WIN32 && JUCE_ASIO + list.add (juce_createASIOAudioIODeviceType()); +#endif + +#if JUCE_WIN32 && JUCE_WDM_AUDIO + list.add (juce_createWDMAudioIODeviceType()); +#endif } const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, @@ -20836,26 +20899,51 @@ const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName) { - if (listNeedsScanning) - refreshDeviceList(); + scanDevicesIfNeeded(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (e != 0 && e->hasTagName (T("DEVICESETUP"))) { + delete lastExplicitSettings; 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; + AudioDeviceSetup setup; - 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)); + if (e->getStringAttribute (T("audioDeviceName")).isNotEmpty()) + { + setup.inputDeviceName = setup.outputDeviceName + = e->getStringAttribute (T("audioDeviceName")); + } + else + { + setup.inputDeviceName = e->getStringAttribute (T("audioInputDeviceName")); + setup.outputDeviceName = e->getStringAttribute (T("audioOutputDeviceName")); + } + + currentDeviceType = e->getStringAttribute (T("deviceType")); + if (currentDeviceType.isEmpty()) + { + AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName); + + if (type != 0) + currentDeviceType = type->getTypeName(); + else if (availableDeviceTypes.size() > 0) + currentDeviceType = availableDeviceTypes[0]->getTypeName(); + } + + setup.bufferSize = e->getIntAttribute (T("audioDeviceBufferSize")); + setup.sampleRate = e->getDoubleAttribute (T("audioDeviceRate")); + + setup.inputChannels.parseString (e->getStringAttribute (T("audioDeviceInChans"), T("11")), 2); + setup.outputChannels.parseString (e->getStringAttribute (T("audioDeviceOutChans"), T("11")), 2); + + setup.useDefaultInputChannels = ! e->hasAttribute (T("audioDeviceInChans")); + setup.useDefaultOutputChannels = ! e->hasAttribute (T("audioDeviceOutChans")); + + error = setAudioDeviceSetup (setup, true); midiInsFromXml.clear(); forEachXmlChildElementWithTagName (*e, c, T("MIDIINPUT")) @@ -20876,33 +20964,54 @@ const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, } else { - setInputDeviceNamesUsed (numOutputChannelsNeeded == 0); - - String defaultDevice; + AudioDeviceSetup setup; if (preferredDefaultDeviceName.isNotEmpty()) { - for (int i = 0; i < availableDeviceTypes.size(); ++i) + for (int j = availableDeviceTypes.size(); --j >= 0;) { - const StringArray devs (availableDeviceTypes.getUnchecked(i)->getDeviceNames()); + AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); - for (int j = 0; j < devs.size(); ++j) + StringArray outs (type->getDeviceNames (false)); + + int i; + for (i = 0; i < outs.size(); ++i) { - if (devs[j].matchesWildcard (preferredDefaultDeviceName, true)) + if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) { - defaultDevice = devs[j]; + setup.outputDeviceName = outs[i]; + break; + } + } + + StringArray ins (type->getDeviceNames (true)); + + for (i = 0; i < ins.size(); ++i) + { + if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) + { + setup.inputDeviceName = ins[i]; break; } } } } - if (defaultDevice.isEmpty() && availableDeviceTypes [0] != 0) - defaultDevice = availableDeviceTypes[0]->getDefaultDeviceName (numOutputChannelsNeeded == 0, - numInputChannelsNeeded, - numOutputChannelsNeeded); + insertDefaultDeviceNames (setup); + return setAudioDeviceSetup (setup, false); + } +} - return setAudioDevice (defaultDevice, 0, 0, 0, 0, false); +void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const +{ + AudioIODeviceType* type = getCurrentDeviceTypeObject(); + if (type != 0) + { + if (setup.outputDeviceName.isEmpty()) + setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; + + if (setup.inputDeviceName.isEmpty()) + setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; } } @@ -20911,144 +21020,372 @@ XmlElement* AudioDeviceManager::createStateXml() const return lastExplicitSettings != 0 ? new XmlElement (*lastExplicitSettings) : 0; } -const StringArray AudioDeviceManager::getAvailableAudioDeviceNames() const +void AudioDeviceManager::scanDevicesIfNeeded() { 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]; + listNeedsScanning = false; - if (availableDeviceTypes.size() > 1) - combo.addSectionHeading (type->getTypeName() + T(" devices:")); + createDeviceTypesIfNeeded(); - const StringArray names (type->getDeviceNames (useInputNames)); + for (int i = availableDeviceTypes.size(); --i >= 0;) + availableDeviceTypes.getUnchecked(i)->scanForDevices(); + } +} - for (int j = 0; j < names.size(); ++j) - combo.addItem (names[j], ++n); +AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) +{ + scanDevicesIfNeeded(); - combo.addSeparator(); + 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; + } } - combo.addItem (TRANS("<< no audio device >>"), -1); + return 0; } -const String AudioDeviceManager::getCurrentAudioDeviceName() const +void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { - if (currentAudioDevice != 0) - return currentAudioDevice->getName(); - - return String::empty; + setup = currentSetup; } -const String AudioDeviceManager::setAudioDevice (const String& deviceNameToUse, - int blockSizeToUse, - double sampleRateToUse, - const BitArray* inChans, - const BitArray* outChans, - const bool treatAsChosenDevice) +void AudioDeviceManager::deleteCurrentDevice() +{ + deleteAndZero (currentAudioDevice); + 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) + { + currentDeviceType = type; + + AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); + insertDefaultDeviceNames (s); + + setAudioDeviceSetup (s, treatAsChosenDevice); + + sendChangeMessage (this); + break; + } + } +} + +AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const +{ + for (int i = 0; i < availableDeviceTypes.size(); ++i) + if (availableDeviceTypes[i]->getTypeName() == currentDeviceType) + return availableDeviceTypes[i]; + + return availableDeviceTypes[0]; +} + +const String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, + const bool treatAsChosenDevice) +{ + jassert (&newSetup != ¤tSetup); // this will have no effect + + if (newSetup == currentSetup && currentAudioDevice != 0) + return String::empty; + + if (! (newSetup == currentSetup)) + sendChangeMessage (this); + + stopDevice(); + String error; + AudioIODeviceType* type = getCurrentDeviceTypeObject(); + + if (type == 0 || (newSetup.inputDeviceName.isEmpty() + && newSetup.outputDeviceName.isEmpty())) + { + deleteCurrentDevice(); + + if (treatAsChosenDevice) + updateXml(); + + return String::empty; + } + + if (currentSetup.inputDeviceName != newSetup.inputDeviceName + || currentSetup.outputDeviceName != newSetup.outputDeviceName + || currentAudioDevice == 0) + { + deleteCurrentDevice(); + scanDevicesIfNeeded(); + + if (newSetup.outputDeviceName.isNotEmpty() + && ! type->getDeviceNames (false).contains (newSetup.outputDeviceName)) + { + return "No such device: " + newSetup.outputDeviceName; + } + + if (newSetup.inputDeviceName.isNotEmpty() + && ! type->getDeviceNames (true).contains (newSetup.inputDeviceName)) + { + return "No such device: " + newSetup.outputDeviceName; + } + + currentAudioDevice = type->createDevice (newSetup.outputDeviceName, + newSetup.inputDeviceName); + + if (currentAudioDevice == 0) + error = "Can't open device"; + else + error = currentAudioDevice->getLastError(); + + if (error.isNotEmpty()) + { + deleteCurrentDevice(); + return error; + } + + inputChannels.clear(); + inputChannels.setRange (0, numInputChansNeeded, true); + outputChannels.clear(); + outputChannels.setRange (0, numOutputChansNeeded, true); + } + else + { + if (! newSetup.useDefaultInputChannels) + inputChannels = newSetup.inputChannels; + + if (! newSetup.useDefaultOutputChannels) + outputChannels = newSetup.outputChannels; + } + + currentSetup = newSetup; + + currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); + + 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 != 0); + + if (rate > 0) + { + bool ok = false; + + for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) + { + const double sr = currentAudioDevice->getSampleRate (i); + + if (sr == rate) + ok = true; + } + + if (! ok) + rate = 0; + } + + if (rate == 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) + rate = currentAudioDevice->getSampleRate (0); + else + rate = lowestAbove44; + } + + return rate; +} + +/*const String AudioDeviceManager::setAudioDevices (const String& outputDeviceName, + const String& inputDeviceName, + int blockSizeToUse, + double sampleRateToUse, + const BitArray* inChans, + const BitArray* outChans, + const bool treatAsChosenDevice) { stopDevice(); String error; - if (deviceNameToUse.isNotEmpty()) + if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) { - const StringArray devNames (getAvailableAudioDeviceNames()); + const StringArray outputNames (getAvailableAudioOutputDeviceNames()); + int outputIndex = outputNames.indexOf (outputDeviceName); - int index = devNames.indexOf (deviceNameToUse, true); - - if (index >= 0) + if (outputIndex < 0 && outputDeviceName.isNotEmpty()) { - if (currentAudioDevice == 0 - || currentAudioDevice->getLastError().isNotEmpty() - || ! deviceNameToUse.equalsIgnoreCase (currentAudioDevice->getName())) + deleteDevices(); + error << "No such device: " << outputDeviceName; + return error; + } + + const StringArray inputNames (getAvailableAudioInputDeviceNames()); + int inputIndex = inputNames.indexOf (inputDeviceName); + + if (inputIndex < 0 && inputDeviceName.isNotEmpty()) + { + deleteDevices(); + error << "No such device: " << inputDeviceName; + return error; + } + + if (currentAudioInputDevice == 0 + || currentAudioInputDevice->getLastError().isNotEmpty() + || ! inputDeviceName.equalsIgnoreCase (currentAudioInputDevice->getName()) + || currentAudioOutputDevice == 0 + || currentAudioOutputDevice->getLastError().isNotEmpty() + || ! outputDeviceName.equalsIgnoreCase (currentAudioOutputDevice->getName())) + { + // change of device.. + deleteDevices(); + + int n = 0; + + for (int i = 0; i < availableDeviceTypes.size(); ++i) { - // change of device.. - deleteAndZero (currentAudioDevice); + AudioIODeviceType* const type = availableDeviceTypes[i]; + const StringArray names (type->getDeviceNames (true)); - int n = 0; + if (inputIndex >= n && inputIndex < n + names.size()) + { + currentAudioInputDevice = type->createDevice (inputDeviceName); + if (currentAudioInputDevice == 0) + error = "Can't open device: " + inputDeviceName; + else + error = currentAudioInputDevice->getLastError(); + + if (error.isNotEmpty()) + { + deleteDevices(); + return error; + } + + break; + } + + n += names.size(); + } + + //xxx + if (true)//inputDeviceName != outputDeviceName) + { + // Using different input and output devices.. + n = 0; for (int i = 0; i < availableDeviceTypes.size(); ++i) { AudioIODeviceType* const type = availableDeviceTypes[i]; - const StringArray names (type->getDeviceNames (useInputNames)); + const StringArray names (type->getDeviceNames (false)); - if (index >= n && index < n + names.size()) + if (outputIndex >= n && outputIndex < n + names.size()) { - currentAudioDevice = type->createDevice (deviceNameToUse); + currentAudioOutputDevice = type->createDevice (outputDeviceName); + + if (currentAudioOutputDevice == 0) + error = "Can't open device: " + outputDeviceName; + else + error = currentAudioOutputDevice->getLastError(); + + if (error.isNotEmpty()) + { + deleteDevices(); + return error; + } + break; } n += names.size(); } - error = currentAudioDevice->getLastError(); - - if (error.isNotEmpty()) + if (currentAudioInputDevice != 0 && currentAudioOutputDevice != 0) { - deleteAndZero (currentAudioDevice); - return error; + //xxx enable this optim } - inputChannels.clear(); - inputChannels.setRange (0, numInputChansNeeded, true); - outputChannels.clear(); - outputChannels.setRange (0, numOutputChansNeeded, true); + ConglomeratingAudioIODevice* const combiner = new ConglomeratingAudioIODevice(); + combiner->addDevice (currentAudioInputDevice, false, true); + combiner->addDevice (currentAudioOutputDevice, true, false); + + currentAudioDevice = combiner; } - - if (inChans != 0) - inputChannels = *inChans; - - if (outChans != 0) - outputChannels = *outChans; - - error = restartDevice (blockSizeToUse, - sampleRateToUse, - inputChannels, - outputChannels); - - if (error.isNotEmpty()) + else { - deleteAndZero (currentAudioDevice); + // Using a single device for in + out.. + currentAudioOutputDevice = currentAudioInputDevice; + currentAudioDevice = currentAudioInputDevice; } + + inputChannels.clear(); + inputChannels.setRange (0, numInputChansNeeded, true); + outputChannels.clear(); + outputChannels.setRange (0, numOutputChansNeeded, true); } - else - { - deleteAndZero (currentAudioDevice); - error << "No such device: " << deviceNameToUse; - } + + if (inChans != 0) + inputChannels = *inChans; + + if (outChans != 0) + outputChannels = *outChans; + + error = restartDevice (blockSizeToUse, + sampleRateToUse, + inputChannels, + outputChannels); + + if (error.isNotEmpty()) + deleteDevices(); } else { - deleteAndZero (currentAudioDevice); + deleteDevices(); } if (treatAsChosenDevice && error.isEmpty()) @@ -21102,7 +21439,7 @@ const String AudioDeviceManager::restartDevice (int blockSizeToUse, } const String error (currentAudioDevice->open (inChans, outChans, - sampleRateToUse, blockSizeToUse)); + sampleRateToUse, blockSizeToUse)); if (error.isEmpty()) currentAudioDevice->start (&callbackHandler); @@ -21110,7 +21447,7 @@ const String AudioDeviceManager::restartDevice (int blockSizeToUse, sendChangeMessage (this); return error; } - +*/ void AudioDeviceManager::stopDevice() { if (currentAudioDevice != 0) @@ -21119,25 +21456,16 @@ void AudioDeviceManager::stopDevice() void AudioDeviceManager::closeAudioDevice() { - if (currentAudioDevice != 0) - { - lastRunningDevice = currentAudioDevice->getName(); - lastRunningBlockSize = currentAudioDevice->getCurrentBufferSizeSamples(); - lastRunningSampleRate = currentAudioDevice->getCurrentSampleRate(); - lastRunningIns = inputChannels; - lastRunningOuts = outputChannels; - - stopDevice(); - - setAudioDevice (String::empty, 0, 0, 0, 0, false); - } + stopDevice(); + deleteAndZero (currentAudioDevice); } void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == 0) { - if (lastRunningDevice.isEmpty()) + 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 @@ -21146,50 +21474,19 @@ void AudioDeviceManager::restartLastAudioDevice() return; } - setAudioDevice (lastRunningDevice, - lastRunningBlockSize, - lastRunningSampleRate, - &lastRunningIns, - &lastRunningOuts, - false); - } -} - -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); + AudioDeviceSetup s (currentSetup); + setAudioDeviceSetup (s, false); } } void AudioDeviceManager::updateXml() { delete lastExplicitSettings; - lastExplicitSettings = new XmlElement (T("DEVICESETUP")); - lastExplicitSettings->setAttribute (T("audioDeviceName"), getCurrentAudioDeviceName()); + lastExplicitSettings->setAttribute (T("deviceType"), currentDeviceType); + lastExplicitSettings->setAttribute (T("audioOutputDeviceName"), currentSetup.outputDeviceName); + lastExplicitSettings->setAttribute (T("audioInputDeviceName"), currentSetup.inputDeviceName); if (currentAudioDevice != 0) { @@ -21198,8 +21495,11 @@ void AudioDeviceManager::updateXml() 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)); + if (! currentSetup.useDefaultInputChannels) + lastExplicitSettings->setAttribute (T("audioDeviceInChans"), currentSetup.inputChannels.toString (2)); + + if (! currentSetup.useDefaultOutputChannels) + lastExplicitSettings->setAttribute (T("audioDeviceOutChans"), currentSetup.outputChannels.toString (2)); } for (int i = 0; i < enabledMidiInputs.size(); ++i) @@ -21256,21 +21556,43 @@ void AudioDeviceManager::setAudioCallback (AudioIODeviceCallback* newCallback) } void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, - int totalNumInputChannels, + int numInputChannels, float** outputChannelData, - int totalNumOutputChannels, + int numOutputChannels, int numSamples) { const ScopedLock sl (audioCallbackLock); + if (inputLevelMeasurementEnabled) + { + for (int j = 0; j < numSamples; ++j) + { + float s = 0; + + for (int i = 0; i < numInputChannels; ++i) + s += fabsf (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; + } + } + if (currentCallback != 0) { const double callbackStartTime = Time::getMillisecondCounterHiRes(); currentCallback->audioDeviceIOCallback (inputChannelData, - totalNumInputChannels, + numInputChannels, outputChannelData, - totalNumOutputChannels, + numOutputChannels, numSamples); const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; @@ -21279,9 +21601,25 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat } else { - for (int i = 0; i < totalNumOutputChannels; ++i) - if (outputChannelData [i] != 0) - zeromem (outputChannelData[i], sizeof (float) * numSamples); + for (int i = 0; i < numOutputChannels; ++i) + zeromem (outputChannelData[i], sizeof (float) * numSamples); + } + + if (testSound != 0) + { + 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()) + { + delete testSound; + testSound = 0; + } } } @@ -21363,7 +21701,7 @@ bool AudioDeviceManager::isMidiInputEnabled (const String& name) const void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callback) { - removeMidiInputCallback (callback); + removeMidiInputCallback (name, callback); if (name.isEmpty()) { @@ -21390,13 +21728,24 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, } } -void AudioDeviceManager::removeMidiInputCallback (MidiInputCallback* callback) +void AudioDeviceManager::removeMidiInputCallback (const String& name, + MidiInputCallback* /*callback*/) { const ScopedLock sl (midiCallbackLock); - const int index = midiCallbacks.indexOf (callback); - midiCallbacks.remove (index); - midiCallbackDevices.remove (index); + for (int i = midiCallbacks.size(); --i >= 0;) + { + String devName; + + if (midiCallbackDevices.getUnchecked(i) != 0) + devName = midiCallbackDevices.getUnchecked(i)->getName(); + + if (devName == name) + { + midiCallbacks.remove (i); + midiCallbackDevices.remove (i); + } + } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, @@ -21434,12 +21783,12 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) } void AudioDeviceManager::CallbackHandler::audioDeviceIOCallback (const float** inputChannelData, - int totalNumInputChannels, + int numInputChannels, float** outputChannelData, - int totalNumOutputChannels, + int numOutputChannels, int numSamples) { - owner->audioDeviceIOCallbackInt (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples); + owner->audioDeviceIOCallbackInt (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); } void AudioDeviceManager::CallbackHandler::audioDeviceAboutToStart (AudioIODevice* device) @@ -21457,6 +21806,56 @@ void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* owner->handleIncomingMidiMessageInt (source, message); } +void AudioDeviceManager::playTestSound() +{ + audioCallbackLock.enter(); + AudioSampleBuffer* oldSound = testSound; + testSound = 0; + audioCallbackLock.exit(); + delete oldSound; + + testSoundPosition = 0; + + if (currentAudioDevice != 0) + { + 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 = MidiMessage::getMidiNoteInHertz (80); + 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) 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 (inputLevelMeasurementEnabled != enableMeasurement) + { + const ScopedLock sl (audioCallbackLock); + inputLevelMeasurementEnabled = enableMeasurement; + inputLevel = 0; + } +} + +double AudioDeviceManager::getCurrentInputLevel() const +{ + jassert (inputLevelMeasurementEnabled); // you need to call enableInputLevelMeasurement() before using this! + return inputLevel; +} + END_JUCE_NAMESPACE /********* End of inlined file: juce_AudioDeviceManager.cpp *********/ @@ -21502,32 +21901,6 @@ AudioIODeviceType::~AudioIODeviceType() { } -extern AudioIODeviceType* juce_createDefaultAudioIODeviceType(); - -#if JUCE_WIN32 && JUCE_ASIO - extern AudioIODeviceType* juce_createASIOAudioIODeviceType(); -#endif - -#if JUCE_WIN32 && JUCE_WDM_AUDIO - extern AudioIODeviceType* juce_createWDMAudioIODeviceType(); -#endif - -void AudioIODeviceType::createDeviceTypes (OwnedArray & list) -{ - AudioIODeviceType* const defaultDeviceType = juce_createDefaultAudioIODeviceType(); - - if (defaultDeviceType != 0) - list.add (defaultDeviceType); - -#if JUCE_WIN32 && JUCE_ASIO - list.add (juce_createASIOAudioIODeviceType()); -#endif - -#if JUCE_WIN32 && JUCE_WDM_AUDIO - list.add (juce_createWDMAudioIODeviceType()); -#endif -} - END_JUCE_NAMESPACE /********* End of inlined file: juce_AudioIODeviceType.cpp *********/ @@ -47181,6 +47554,11 @@ void TextEditor::focusGained (FocusChangeType) if (caretVisible) textHolder->startTimer (flashSpeedIntervalMs); + + ComponentPeer* const peer = getPeer(); + if (peer != 0) + peer->textInputRequired (getScreenX() - peer->getScreenX(), + getScreenY() - peer->getScreenY()); } void TextEditor::focusLost (FocusChangeType) @@ -47830,7 +48208,6 @@ public: { tc->setVisible (false); const int index = oldIndexes.remove (i); - owner.items.insert (index, tc); owner.addChildComponent (tc, index); --i; } @@ -51250,8 +51627,8 @@ BEGIN_JUCE_NAMESPACE Image* juce_createIconForFile (const File& file); FileListComponent::FileListComponent (DirectoryContentsList& listToShow) - : DirectoryContentsDisplayComponent (listToShow), - ListBox (String::empty, 0) + : ListBox (String::empty, 0), + DirectoryContentsDisplayComponent (listToShow) { setModel (this); fileList.addChangeListener (this); @@ -64417,53 +64794,28 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE -class AudioDeviceSelectorComponentListBox : public ListBox, - public ListBoxModel +class MidiInputSelectorComponentListBox : public ListBox, + public ListBoxModel { public: - enum BoxType - { - midiInputType, - audioInputType, - audioOutputType - }; - AudioDeviceSelectorComponentListBox (AudioDeviceManager& deviceManager_, - const BoxType type_, - const String& noItemsMessage_, - const int minNumber_, - const int maxNumber_) + MidiInputSelectorComponentListBox (AudioDeviceManager& deviceManager_, + const String& noItemsMessage_, + const int minNumber_, + const int maxNumber_) : ListBox (String::empty, 0), deviceManager (deviceManager_), - type (type_), noItemsMessage (noItemsMessage_), minNumber (minNumber_), maxNumber (maxNumber_) { - AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice(); - - if (type_ == midiInputType) - { - items = MidiInput::getDevices(); - } - else if (type_ == audioInputType) - { - items = currentDevice->getInputChannelNames(); - } - else if (type_ == audioOutputType) - { - items = currentDevice->getOutputChannelNames(); - } - else - { - jassertfalse - } + items = MidiInput::getDevices(); setModel (this); setOutlineThickness (1); } - ~AudioDeviceSelectorComponentListBox() + ~MidiInputSelectorComponentListBox() { } @@ -64484,20 +64836,7 @@ public: .withMultipliedAlpha (0.3f)); const String item (items [row]); - bool enabled = false; - - if (type == midiInputType) - { - enabled = deviceManager.isMidiInputEnabled (item); - } - else if (type == audioInputType) - { - enabled = deviceManager.getInputChannels() [row]; - } - else if (type == audioOutputType) - { - enabled = deviceManager.getOutputChannels() [row]; - } + bool enabled = deviceManager.isMidiInputEnabled (item); const int x = getTickX(); const int tickW = height - height / 4; @@ -64556,7 +64895,6 @@ public: private: AudioDeviceManager& deviceManager; - const BoxType type; const String noItemsMessage; StringArray items; int minNumber, maxNumber; @@ -64565,53 +64903,8 @@ private: { if (((unsigned int) row) < (unsigned int) items.size()) { - AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice(); - const String item (items [row]); - - if (type == midiInputType) - { - deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); - } - else - { - jassert (type == audioInputType || type == audioOutputType); - - if (audioDevice != 0) - { - BitArray chans (type == audioInputType ? deviceManager.getInputChannels() - : deviceManager.getOutputChannels()); - - const BitArray oldChans (chans); - - const bool newVal = ! chans[row]; - const int numActive = chans.countNumberOfSetBits(); - - if (! newVal) - { - if (numActive > minNumber) - chans.setBit (row, false); - } - else - { - if (numActive >= maxNumber) - { - const int firstActiveChan = chans.findNextSetBit(); - - chans.setBit (row > firstActiveChan - ? firstActiveChan : chans.getHighestBit(), - false); - } - - chans.setBit (row, true); - } - - if (type == audioInputType) - deviceManager.setInputChannels (chans, true); - else - deviceManager.setOutputChannels (chans, true); - } - } + deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); } } @@ -64620,8 +64913,707 @@ private: return getRowHeight() + 5; } - AudioDeviceSelectorComponentListBox (const AudioDeviceSelectorComponentListBox&); - const AudioDeviceSelectorComponentListBox& operator= (const AudioDeviceSelectorComponentListBox&); + MidiInputSelectorComponentListBox (const MidiInputSelectorComponentListBox&); + const MidiInputSelectorComponentListBox& operator= (const MidiInputSelectorComponentListBox&); +}; + +class AudioDeviceSettingsPanel : public Component, + public ComboBoxListener, + public ChangeListener, + public ButtonListener +{ +public: + AudioDeviceSettingsPanel (AudioIODeviceType* type_, + AudioIODeviceType::DeviceSetupDetails& setup_) + : type (type_), + setup (setup_) + { + sampleRateDropDown = 0; + sampleRateLabel = 0; + bufferSizeDropDown = 0; + bufferSizeLabel = 0; + outputDeviceDropDown = 0; + outputDeviceLabel = 0; + inputDeviceDropDown = 0; + inputDeviceLabel = 0; + testButton = 0; + inputLevelMeter = 0; + showUIButton = 0; + inputChanList = 0; + outputChanList = 0; + inputChanLabel = 0; + outputChanLabel = 0; + + type->scanForDevices(); + + setup.manager->addChangeListener (this); + changeListenerCallback (0); + } + + ~AudioDeviceSettingsPanel() + { + setup.manager->removeChangeListener (this); + + deleteAndZero (outputDeviceLabel); + deleteAndZero (inputDeviceLabel); + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAndZero (showUIButton); + deleteAndZero (inputChanLabel); + deleteAndZero (outputChanLabel); + + deleteAllChildren(); + } + + void resized() + { + const int lx = proportionOfWidth (0.35f); + const int w = proportionOfWidth (0.4f); + const int h = 24; + const int space = 6; + const int dh = h + space; + int y = 0; + + if (outputDeviceDropDown != 0) + { + outputDeviceDropDown->setBounds (lx, y, w, h); + testButton->setBounds (proportionOfWidth (0.77f), + outputDeviceDropDown->getY(), + proportionOfWidth (0.18f), + h); + y += dh; + } + + if (inputDeviceDropDown != 0) + { + inputDeviceDropDown->setBounds (lx, y, w, h); + + inputLevelMeter->setBounds (proportionOfWidth (0.77f), + inputDeviceDropDown->getY(), + proportionOfWidth (0.18f), + h); + y += dh; + } + + const int maxBoxHeight = 100;//(getHeight() - y - dh * 2) / numBoxes; + + if (outputChanList != 0) + { + const int bh = outputChanList->getBestHeight (maxBoxHeight); + outputChanList->setBounds (lx, y, proportionOfWidth (0.55f), bh); + y += bh + space; + } + + if (inputChanList != 0) + { + const int bh = inputChanList->getBestHeight (maxBoxHeight); + inputChanList->setBounds (lx, y, proportionOfWidth (0.55f), bh); + y += bh + space; + } + + y += space * 2; + + if (sampleRateDropDown != 0) + { + sampleRateDropDown->setBounds (lx, y, w, h); + y += dh; + } + + if (bufferSizeDropDown != 0) + { + bufferSizeDropDown->setBounds (lx, y, w, h); + y += dh; + } + + if (showUIButton != 0) + { + showUIButton->changeWidthToFitText (h); + showUIButton->setTopLeftPosition (lx, y); + } + } + + void comboBoxChanged (ComboBox* comboBoxThatHasChanged) + { + if (comboBoxThatHasChanged == 0) + return; + + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + String error; + + if (comboBoxThatHasChanged == outputDeviceDropDown + || comboBoxThatHasChanged == inputDeviceDropDown) + { + if (outputDeviceDropDown != 0) + config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty + : outputDeviceDropDown->getText(); + + if (inputDeviceDropDown != 0) + config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty + : inputDeviceDropDown->getText(); + + if (! type->hasSeparateInputsAndOutputs()) + config.inputDeviceName = config.outputDeviceName; + + if (comboBoxThatHasChanged == inputDeviceDropDown) + config.useDefaultInputChannels = true; + else + config.useDefaultOutputChannels = true; + + error = setup.manager->setAudioDeviceSetup (config, true); + + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + + updateControlPanelButton(); + resized(); + } + else if (comboBoxThatHasChanged == sampleRateDropDown) + { + if (sampleRateDropDown->getSelectedId() > 0) + { + config.sampleRate = sampleRateDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + else if (comboBoxThatHasChanged == bufferSizeDropDown) + { + if (bufferSizeDropDown->getSelectedId() > 0) + { + config.bufferSize = bufferSizeDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + + if (error.isNotEmpty()) + { + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + T("Error when trying to open audio device!"), + error); + } + } + + void buttonClicked (Button* button) + { + if (button == showUIButton) + { + AudioIODevice* const device = setup.manager->getCurrentAudioDevice(); + + if (device != 0 && device->showControlPanel()) + { + setup.manager->closeAudioDevice(); + setup.manager->restartLastAudioDevice(); + getTopLevelComponent()->toFront (true); + } + } + else if (button == testButton) + { + setup.manager->playTestSound(); + } + } + + void updateControlPanelButton() + { + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + deleteAndZero (showUIButton); + + if (currentDevice != 0 && currentDevice->hasControlPanel()) + { + addAndMakeVisible (showUIButton = new TextButton (TRANS ("show this device's control panel"), + TRANS ("opens the device's own control panel"))); + showUIButton->addButtonListener (this); + } + + resized(); + } + + void changeListenerCallback (void*) + { + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + if (setup.maxNumOutputChannels > 0 || ! type->hasSeparateInputsAndOutputs()) + { + if (outputDeviceDropDown == 0) + { + outputDeviceDropDown = new ComboBox (String::empty); + outputDeviceDropDown->addListener (this); + addAndMakeVisible (outputDeviceDropDown); + + outputDeviceLabel = new Label (String::empty, + type->hasSeparateInputsAndOutputs() ? TRANS ("output:") + : TRANS ("device:")); + outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); + + addAndMakeVisible (testButton = new TextButton (TRANS ("Test"))); + testButton->addButtonListener (this); + } + + addNamesToDeviceBox (*outputDeviceDropDown, false); + } + + if (setup.maxNumInputChannels > 0 && type->hasSeparateInputsAndOutputs()) + { + if (inputDeviceDropDown == 0) + { + inputDeviceDropDown = new ComboBox (String::empty); + inputDeviceDropDown->addListener (this); + addAndMakeVisible (inputDeviceDropDown); + + inputDeviceLabel = new Label (String::empty, TRANS ("input:")); + inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); + + addAndMakeVisible (inputLevelMeter = AudioDeviceSelectorComponent::createSimpleLevelMeterComponent (setup.manager)); + } + + addNamesToDeviceBox (*inputDeviceDropDown, true); + } + + updateControlPanelButton(); + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + + if (currentDevice != 0) + { + if (setup.maxNumOutputChannels > 0 + && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size()) + { + if (outputChanList == 0) + { + addAndMakeVisible (outputChanList + = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, + TRANS ("(no audio output channels found)"))); + outputChanLabel = new Label (String::empty, TRANS ("active output channels:")); + outputChanLabel->attachToComponent (outputChanList, true); + } + + outputChanList->updateContent(); + outputChanList->repaint(); + } + else + { + deleteAndZero (outputChanLabel); + deleteAndZero (outputChanList); + } + + if (setup.maxNumInputChannels > 0 + && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size()) + { + if (inputChanList == 0) + { + addAndMakeVisible (inputChanList + = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, + TRANS ("(no audio input channels found)"))); + inputChanLabel = new Label (String::empty, TRANS ("active input channels:")); + inputChanLabel->attachToComponent (inputChanList, true); + } + + inputChanList->updateContent(); + inputChanList->repaint(); + } + else + { + deleteAndZero (inputChanLabel); + deleteAndZero (inputChanList); + } + + // sample rate.. + { + if (sampleRateDropDown == 0) + { + addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty)); + sampleRateDropDown->addListener (this); + + delete sampleRateLabel; + sampleRateLabel = new Label (String::empty, TRANS ("sample rate:")); + sampleRateLabel->attachToComponent (sampleRateDropDown, true); + } + else + { + sampleRateDropDown->clear(); + sampleRateDropDown->removeListener (this); + } + + const int numRates = currentDevice->getNumSampleRates(); + + for (int i = 0; i < numRates; ++i) + { + const int rate = roundDoubleToInt (currentDevice->getSampleRate (i)); + sampleRateDropDown->addItem (String (rate) + T(" Hz"), rate); + } + + sampleRateDropDown->setSelectedId (roundDoubleToInt (currentDevice->getCurrentSampleRate()), true); + sampleRateDropDown->addListener (this); + } + + // buffer size + { + if (bufferSizeDropDown == 0) + { + addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty)); + bufferSizeDropDown->addListener (this); + + delete bufferSizeLabel; + bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:")); + bufferSizeLabel->attachToComponent (bufferSizeDropDown, true); + } + else + { + bufferSizeDropDown->clear(); + } + + const int numBufferSizes = currentDevice->getNumBufferSizesAvailable(); + double currentRate = currentDevice->getCurrentSampleRate(); + if (currentRate == 0) + currentRate = 48000.0; + + for (int i = 0; i < numBufferSizes; ++i) + { + const int bs = currentDevice->getBufferSizeSamples (i); + bufferSizeDropDown->addItem (String (bs) + + T(" samples (") + + String (bs * 1000.0 / currentRate, 1) + + T(" ms)"), + bs); + } + + bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), true); + } + } + else + { + jassert (setup.manager->getCurrentAudioDevice() == 0); // not the correct device type! + + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAndZero (sampleRateDropDown); + deleteAndZero (bufferSizeDropDown); + + if (outputDeviceDropDown != 0) + outputDeviceDropDown->setSelectedId (-1, true); + + if (inputDeviceDropDown != 0) + inputDeviceDropDown->setSelectedId (-1, true); + } + + resized(); + setSize (getWidth(), getLowestY() + 4); + } + +private: + AudioIODeviceType* const type; + const AudioIODeviceType::DeviceSetupDetails setup; + + ComboBox* outputDeviceDropDown; + ComboBox* inputDeviceDropDown; + ComboBox* sampleRateDropDown; + ComboBox* bufferSizeDropDown; + Label* outputDeviceLabel; + Label* inputDeviceLabel; + Label* sampleRateLabel; + Label* bufferSizeLabel; + Label* inputChanLabel; + Label* outputChanLabel; + TextButton* testButton; + Component* inputLevelMeter; + TextButton* showUIButton; + + void showCorrectDeviceName (ComboBox* const box, const bool isInput) + { + if (box != 0) + { + AudioIODevice* const currentDevice = dynamic_cast (setup.manager->getCurrentAudioDevice()); + + const int index = type->getIndexOfDevice (currentDevice, isInput); + + box->setSelectedId (index + 1, true); + + if (! isInput) + testButton->setEnabled (index >= 0); + } + } + + void addNamesToDeviceBox (ComboBox& combo, bool isInputs) + { + const StringArray devs (type->getDeviceNames (isInputs)); + + combo.clear(); + + for (int i = 0; i < devs.size(); ++i) + combo.addItem (devs[i], i + 1); + + combo.addItem (TRANS("<< none >>"), -1); + combo.setSelectedId (-1, true); + } + + int getLowestY() const + { + int y = 0; + + for (int i = getNumChildComponents(); --i >= 0;) + y = jmax (y, getChildComponent (i)->getBottom()); + + return y; + } + + class ChannelSelectorListBox : public ListBox, + public ListBoxModel + { + public: + enum BoxType + { + audioInputType, + audioOutputType + }; + + ChannelSelectorListBox (const AudioIODeviceType::DeviceSetupDetails& setup_, + const BoxType type_, + const String& noItemsMessage_) + : ListBox (String::empty, 0), + setup (setup_), + type (type_), + noItemsMessage (noItemsMessage_) + { + refresh(); + setModel (this); + setOutlineThickness (1); + } + + ~ChannelSelectorListBox() + { + } + + void refresh() + { + items.clear(); + + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + if (currentDevice != 0) + { + if (type == audioInputType) + items = currentDevice->getInputChannelNames(); + else if (type == audioOutputType) + items = currentDevice->getOutputChannelNames(); + + if (setup.useStereoPairs) + { + StringArray pairs; + + for (int i = 0; i < items.size(); i += 2) + { + String name (items[i]); + String name2 (items[i + 1]); + + String commonBit; + + for (int j = 0; j < name.length(); ++j) + if (name.substring (0, j).equalsIgnoreCase (name2.substring (0, j))) + commonBit = name.substring (0, j); + + pairs.add (name.trim() + + " + " + + name2.substring (commonBit.length()).trim()); + } + + items = pairs; + } + } + + updateContent(); + repaint(); + } + + int getNumRows() + { + return items.size(); + } + + void paintListBoxItem (int row, + Graphics& g, + int width, int height, + bool rowIsSelected) + { + if (((unsigned int) row) < (unsigned int) items.size()) + { + if (rowIsSelected) + g.fillAll (findColour (TextEditor::highlightColourId) + .withMultipliedAlpha (0.3f)); + + const String item (items [row]); + bool enabled = false; + + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + + if (setup.useStereoPairs) + { + if (type == audioInputType) + enabled = config.inputChannels [row * 2] || config.inputChannels [row * 2 + 1]; + else if (type == audioOutputType) + enabled = config.outputChannels [row * 2] || config.outputChannels [row * 2 + 1]; + } + else + { + if (type == audioInputType) + enabled = config.inputChannels [row]; + else if (type == audioOutputType) + enabled = config.outputChannels [row]; + } + + const int x = getTickX(); + const int tickW = height - height / 4; + + getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW, + enabled, true, true, false); + + g.setFont (height * 0.6f); + g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f)); + g.drawText (item, x, 0, width - x - 2, height, Justification::centredLeft, true); + } + } + + void listBoxItemClicked (int row, const MouseEvent& e) + { + selectRow (row); + + if (e.x < getTickX()) + flipEnablement (row); + } + + void listBoxItemDoubleClicked (int row, const MouseEvent&) + { + flipEnablement (row); + } + + void returnKeyPressed (int row) + { + flipEnablement (row); + } + + void paint (Graphics& g) + { + ListBox::paint (g); + + if (items.size() == 0) + { + g.setColour (Colours::grey); + g.setFont (13.0f); + g.drawText (noItemsMessage, + 0, 0, getWidth(), getHeight() / 2, + Justification::centred, true); + } + } + + int getBestHeight (int maxHeight) + { + return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()), + getNumRows()) + + getOutlineThickness() * 2; + } + + juce_UseDebuggingNewOperator + + private: + const AudioIODeviceType::DeviceSetupDetails setup; + const BoxType type; + const String noItemsMessage; + StringArray items; + + void flipEnablement (const int row) + { + jassert (type == audioInputType || type == audioOutputType); + + if (((unsigned int) row) < (unsigned int) items.size()) + { + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + + if (setup.useStereoPairs) + { + BitArray bits; + BitArray& original = (type == audioInputType ? config.inputChannels + : config.outputChannels); + + int i; + for (i = 0; i < 256; i += 2) + bits.setBit (i / 2, original [i] || original [i + 1]); + + if (type == audioInputType) + { + config.useDefaultInputChannels = false; + flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2); + } + else + { + config.useDefaultOutputChannels = false; + flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2); + } + + for (i = 0; i < 256; ++i) + original.setBit (i, bits [i / 2]); + } + else + { + if (type == audioInputType) + { + config.useDefaultInputChannels = false; + flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels); + } + else + { + config.useDefaultOutputChannels = false; + flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels); + } + } + + String error (setup.manager->setAudioDeviceSetup (config, true)); + + if (! error.isEmpty()) + { + //xxx + } + } + } + + static void flipBit (BitArray& chans, int index, int minNumber, int maxNumber) + { + const int numActive = chans.countNumberOfSetBits(); + + if (chans [index]) + { + if (numActive > minNumber) + chans.setBit (index, false); + } + else + { + if (numActive >= maxNumber) + { + const int firstActiveChan = chans.findNextSetBit(); + + chans.setBit (index > firstActiveChan + ? firstActiveChan : chans.getHighestBit(), + false); + } + + chans.setBit (index, true); + } + } + + int getTickX() const throw() + { + return getRowHeight() + 5; + } + + ChannelSelectorListBox (const ChannelSelectorListBox&); + const ChannelSelectorListBox& operator= (const ChannelSelectorListBox&); + }; + + ChannelSelectorListBox* inputChanList; + ChannelSelectorListBox* outputChanList; + + AudioDeviceSettingsPanel (const AudioDeviceSettingsPanel&); + const AudioDeviceSettingsPanel& operator= (const AudioDeviceSettingsPanel&); }; AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& deviceManager_, @@ -64636,41 +65628,40 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& maxOutputChannels (maxOutputChannels_), minInputChannels (minInputChannels_), maxInputChannels (maxInputChannels_), - sampleRateDropDown (0), - inputChansBox (0), - inputsLabel (0), - outputChansBox (0), - outputsLabel (0), - sampleRateLabel (0), - bufferSizeDropDown (0), - bufferSizeLabel (0), - launchUIButton (0) + deviceTypeDropDown (0), + deviceTypeDropDownLabel (0), + audioDeviceSettingsComp (0) { jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels); jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels); - audioDeviceDropDown = new ComboBox ("device"); - deviceManager_.addDeviceNamesToComboBox (*audioDeviceDropDown); - audioDeviceDropDown->setSelectedId (-1, true); + if (deviceManager_.getAvailableDeviceTypes().size() > 1) + { + deviceTypeDropDown = new ComboBox (String::empty); - if (deviceManager_.getCurrentAudioDeviceName().isNotEmpty()) - audioDeviceDropDown->setText (deviceManager_.getCurrentAudioDeviceName(), true); + for (int i = 0; i < deviceManager_.getAvailableDeviceTypes().size(); ++i) + { + deviceTypeDropDown + ->addItem (deviceManager_.getAvailableDeviceTypes().getUnchecked(i)->getTypeName(), + i + 1); + } - audioDeviceDropDown->addListener (this); - addAndMakeVisible (audioDeviceDropDown); + addAndMakeVisible (deviceTypeDropDown); + deviceTypeDropDown->addListener (this); - Label* label = new Label ("l1", TRANS ("audio device:")); - label->attachToComponent (audioDeviceDropDown, true); + deviceTypeDropDownLabel = new Label (String::empty, TRANS ("audio device type:")); + deviceTypeDropDownLabel->setJustificationType (Justification::centredRight); + deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown, true); + } if (showMidiInputOptions) { addAndMakeVisible (midiInputsList - = new AudioDeviceSelectorComponentListBox (deviceManager, - AudioDeviceSelectorComponentListBox::midiInputType, - TRANS("(no midi inputs available)"), - 0, 0)); + = new MidiInputSelectorComponentListBox (deviceManager, + TRANS("(no midi inputs available)"), + 0, 0)); - midiInputsLabel = new Label ("lm", TRANS ("active midi inputs:")); + midiInputsLabel = new Label (String::empty, TRANS ("active midi inputs:")); midiInputsLabel->setJustificationType (Justification::topRight); midiInputsLabel->attachToComponent (midiInputsList, true); } @@ -64707,42 +65698,26 @@ AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent() void AudioDeviceSelectorComponent::resized() { const int lx = proportionOfWidth (0.35f); - const int w = proportionOfWidth (0.55f); + const int w = proportionOfWidth (0.4f); const int h = 24; const int space = 6; const int dh = h + space; int y = 15; - audioDeviceDropDown->setBounds (lx, y, w, h); - y += dh; - - if (sampleRateDropDown != 0) + if (deviceTypeDropDown != 0) { - sampleRateDropDown->setBounds (lx, y, w, h); - y += dh; + deviceTypeDropDown->setBounds (lx, y, proportionOfWidth (0.3f), h); + y += dh + space * 2; } - if (bufferSizeDropDown != 0) + if (audioDeviceSettingsComp != 0) { - bufferSizeDropDown->setBounds (lx, y, w, h); - y += dh; - } - - if (launchUIButton != 0) - { - launchUIButton->setBounds (lx, y, 150, h); - ((TextButton*) launchUIButton)->changeWidthToFitText(); - y += dh; + audioDeviceSettingsComp->setBounds (0, y, getWidth(), audioDeviceSettingsComp->getHeight()); + y += audioDeviceSettingsComp->getHeight() + space; } VoidArray boxes; - if (outputChansBox != 0) - boxes.add (outputChansBox); - - if (inputChansBox != 0) - boxes.add (inputChansBox); - if (midiInputsList != 0) boxes.add (midiInputsList); @@ -64750,7 +65725,7 @@ void AudioDeviceSelectorComponent::resized() for (int i = 0; i < boxes.size(); ++i) { - AudioDeviceSelectorComponentListBox* const box = (AudioDeviceSelectorComponentListBox*) boxes.getUnchecked (i); + MidiInputSelectorComponentListBox* const box = (MidiInputSelectorComponentListBox*) boxes.getUnchecked (i); const int bh = box->getBestHeight (jmin (h * 8, boxSpace / boxes.size()) - space); box->setBounds (lx, y, w, bh); @@ -64767,13 +65742,8 @@ void AudioDeviceSelectorComponent::buttonClicked (Button*) if (device != 0 && device->hasControlPanel()) { - const String lastDevice (device->getName()); - if (device->showControlPanel()) - { - deviceManager.setAudioDevice (String::empty, 0, 0, 0, 0, false); - deviceManager.setAudioDevice (lastDevice, 0, 0, 0, 0, false); - } + deviceManager.restartLastAudioDevice(); getTopLevelComponent()->toFront (true); } @@ -64781,18 +65751,28 @@ void AudioDeviceSelectorComponent::buttonClicked (Button*) void AudioDeviceSelectorComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged) { - AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice(); +// AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice(); - if (comboBoxThatHasChanged == audioDeviceDropDown) + if (comboBoxThatHasChanged == deviceTypeDropDown) { - if (audioDeviceDropDown->getSelectedId() < 0) + AudioIODeviceType* const type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1]; + + if (type != 0) { - deviceManager.setAudioDevice (String::empty, 0, 0, 0, 0, true); + deleteAndZero (audioDeviceSettingsComp); + + deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true); + + changeListenerCallback (0); // needed in case the type hasn't actally changed + } + +/* if (outputDeviceDropDown->getSelectedId() < 0) + { + deviceManager.setAudioDevices (String::empty, deviceManager.getCurrentAudioInputDeviceName(), 0, 0, 0, 0, true); } else { - String error (deviceManager.setAudioDevice (audioDeviceDropDown->getText(), - 0, 0, 0, 0, true)); + String error (deviceManager.setAudioDevices (deviceName, deviceManager.getCurrentAudioInputDeviceName(), 0, 0, 0, 0, true)); if (error.isNotEmpty()) { @@ -64804,52 +65784,96 @@ void AudioDeviceSelectorComponent::comboBoxChanged (ComboBox* comboBoxThatHasCha // is removed, and this also buggers up our attempt at opening an output // device, so this is a workaround that doesn't fail in that case. BitArray noInputs; - error = deviceManager.setAudioDevice (audioDeviceDropDown->getText(), - 0, 0, &noInputs, 0, false); + error = deviceManager.setAudioDevices (deviceName, deviceManager.getCurrentAudioInputDeviceName(), 0, 0, &noInputs, 0, false); } #endif if (error.isNotEmpty()) AlertWindow::showMessageBox (AlertWindow::WarningIcon, T("Error while opening \"") - + audioDeviceDropDown->getText() + + deviceName + T("\""), error); } } - if (deviceManager.getCurrentAudioDeviceName().isNotEmpty()) - audioDeviceDropDown->setText (deviceManager.getCurrentAudioDeviceName(), true); + deviceName = deviceManager.getCurrentAudioOutputDeviceName(); + + if (deviceName.isNotEmpty()) + outputDeviceDropDown->setText (deviceName, true); else - audioDeviceDropDown->setSelectedId (-1, true); + outputDeviceDropDown->setSelectedId (-1, true); + } + else if (comboBoxThatHasChanged == inputDeviceDropDown) + { + String deviceName (inputDeviceDropDown->getText()); + + if (inputDeviceDropDown->getSelectedId() < 0) + { + deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), String::empty, 0, 0, 0, 0, true); + } + else + { + String error (deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), deviceName, 0, 0, 0, 0, true)); + + if (error.isNotEmpty()) + { +#if JUCE_WIN32 + if (deviceManager.getInputChannels().countNumberOfSetBits() > 0 + && deviceManager.getOutputChannels().countNumberOfSetBits() > 0) + { + // in DSound, some machines lose their primary input device when a mic + // is removed, and this also buggers up our attempt at opening an output + // device, so this is a workaround that doesn't fail in that case. + BitArray noInputs; + error = deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), deviceName, 0, 0, &noInputs, 0, false); + } +#endif + if (error.isNotEmpty()) + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + T("Error while opening \"") + + deviceName + + T("\""), + error); + } + } + + deviceName = deviceManager.getCurrentAudioOutputDeviceName(); + + if (deviceName.isNotEmpty()) + inputDeviceDropDown->setText (deviceName, true); + else + inputDeviceDropDown->setSelectedId (-1, true);*/ } else if (comboBoxThatHasChanged == midiOutputSelector) { deviceManager.setDefaultMidiOutput (midiOutputSelector->getText()); } - else if (audioDevice != 0) + /*else if (audioDevice != 0) { if (bufferSizeDropDown != 0 && comboBoxThatHasChanged == bufferSizeDropDown) { if (bufferSizeDropDown->getSelectedId() > 0) - deviceManager.setAudioDevice (audioDevice->getName(), - bufferSizeDropDown->getSelectedId(), - audioDevice->getCurrentSampleRate(), - 0, 0, true); + deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), + deviceManager.getCurrentAudioInputDeviceName(), + bufferSizeDropDown->getSelectedId(), + audioDevice->getCurrentSampleRate(), + 0, 0, true); } else if (sampleRateDropDown != 0 && comboBoxThatHasChanged == sampleRateDropDown) { if (sampleRateDropDown->getSelectedId() > 0) - deviceManager.setAudioDevice (audioDevice->getName(), - audioDevice->getCurrentBufferSizeSamples(), - sampleRateDropDown->getSelectedId(), - 0, 0, true); + deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), + deviceManager.getCurrentAudioInputDeviceName(), + audioDevice->getCurrentBufferSizeSamples(), + sampleRateDropDown->getSelectedId(), + 0, 0, true); } - } + }*/ } void AudioDeviceSelectorComponent::changeListenerCallback (void*) { - deleteAndZero (sampleRateDropDown); + /*deleteAndZero (sampleRateDropDown); deleteAndZero (inputChansBox); deleteAndZero (inputsLabel); deleteAndZero (outputChansBox); @@ -64857,13 +65881,57 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) deleteAndZero (sampleRateLabel); deleteAndZero (bufferSizeDropDown); deleteAndZero (bufferSizeLabel); - deleteAndZero (launchUIButton); + deleteAndZero (launchUIButton);*/ - AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice(); + if (deviceTypeDropDown != 0) + { + deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), false); + } + + if (audioDeviceSettingsComp == 0 + || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType()) + { + audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType(); + + deleteAndZero (audioDeviceSettingsComp); + + AudioIODeviceType* const type + = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == 0 + ? 0 : deviceTypeDropDown->getSelectedId() - 1]; + + if (type != 0) + { + AudioIODeviceType::DeviceSetupDetails details; + details.manager = &deviceManager; + details.minNumInputChannels = minInputChannels; + details.maxNumInputChannels = maxInputChannels; + details.minNumOutputChannels = minOutputChannels; + details.maxNumOutputChannels = maxOutputChannels; + details.useStereoPairs = true; + + audioDeviceSettingsComp = new AudioDeviceSettingsPanel (type, details); + + if (audioDeviceSettingsComp != 0) + { + addAndMakeVisible (audioDeviceSettingsComp); + audioDeviceSettingsComp->resized(); + } + } + } + +/* AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice(); if (currentDevice != 0) { - audioDeviceDropDown->setText (currentDevice->getName(), true); + if (deviceManager.getCurrentAudioOutputDevice() == 0) + outputDeviceDropDown->setSelectedId (-1, true); + else + outputDeviceDropDown->setText (deviceManager.getCurrentAudioOutputDeviceName(), true); + + if (deviceManager.getCurrentAudioInputDevice() == 0) + inputDeviceDropDown->setSelectedId (-1, true); + else + inputDeviceDropDown->setText (deviceManager.getCurrentAudioInputDeviceName(), true); // sample rate addAndMakeVisible (sampleRateDropDown = new ComboBox ("samplerate")); @@ -64939,8 +66007,9 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) } else { - audioDeviceDropDown->setSelectedId (-1, true); - } + outputDeviceDropDown->setSelectedId (-1, true); + inputDeviceDropDown->setSelectedId (-1, true); + }*/ if (midiInputsList != 0) { @@ -64954,7 +66023,7 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) const StringArray midiOuts (MidiOutput::getDevices()); - midiOutputSelector->addItem (TRANS("<< no audio device >>"), -1); + midiOutputSelector->addItem (TRANS("<< none >>"), -1); midiOutputSelector->addSeparator(); for (int i = 0; i < midiOuts.size(); ++i) @@ -64971,6 +66040,66 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) resized(); } +class SimpleDeviceManagerInputLevelMeter : public Component, + public Timer +{ +public: + SimpleDeviceManagerInputLevelMeter (AudioDeviceManager* const manager_) + : manager (manager_), + totalBlocks (7) + { + numBlocks = 0; + startTimer (50); + manager->enableInputLevelMeasurement (true); + } + + ~SimpleDeviceManagerInputLevelMeter() + { + manager->enableInputLevelMeasurement (false); + } + + void timerCallback() + { + const int newNumBlocks = roundDoubleToInt (manager->getCurrentInputLevel() * totalBlocks); + if (newNumBlocks != numBlocks) + { + numBlocks = newNumBlocks; + repaint(); + } + } + + void paint (Graphics& g) + { + g.setColour (Colours::white.withAlpha (0.8f)); + g.fillRoundedRectangle (0.0f, 0.0f, (float) getWidth(), (float) getHeight(), 3.0f); + g.setColour (Colours::black.withAlpha (0.2f)); + g.drawRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 3.0f, 1.0f); + + const float w = (getWidth() - 6.0f) / (float) totalBlocks; + + for (int i = 0; i < totalBlocks; ++i) + { + if (i >= numBlocks) + g.setColour (Colours::lightblue.withAlpha (0.6f)); + else + g.setColour (i < totalBlocks - 1 ? Colours::blue.withAlpha (0.5f) + : Colours::red); + + g.fillRoundedRectangle (3.0f + i * w + w * 0.1f, 3.0f, w * 0.8f, getHeight() - 6.0f, w * 0.4f); + } + } + +private: + AudioDeviceManager* const manager; + const int totalBlocks; + int numBlocks; +}; + +Component* AudioDeviceSelectorComponent::createSimpleLevelMeterComponent (AudioDeviceManager* managerToDisplay) +{ + return new SimpleDeviceManagerInputLevelMeter (managerToDisplay); +} + END_JUCE_NAMESPACE /********* End of inlined file: juce_AudioDeviceSelectorComponent.cpp *********/ @@ -66220,6 +67349,13 @@ public: peer->grabFocus(); } + void textInputRequired (int x, int y) + { + ComponentPeer* peer = magnifierComp->getPeer(); + if (peer != 0) + peer->textInputRequired (x, y); + } + void getBounds (int& x, int& y, int& w, int& h) const { x = magnifierComp->getScreenX(); @@ -230147,6 +231283,16 @@ bool juce_IsRunningInWine() throw() return false; } +const String JUCE_CALLTYPE PlatformUtilities::getCurrentCommandLineParams() throw() +{ + String s (::GetCommandLineW()); + + StringArray tokens; + tokens.addTokens (s, true); // tokenise so that we can remove the initial filename argument + + return tokens.joinIntoString (T(" "), 1); +} + static void* currentModuleHandle = 0; void* PlatformUtilities::getCurrentModuleInstanceHandle() throw() @@ -231101,7 +232247,6 @@ static ASIOAudioIODevice* volatile currentASIODev[3] = { 0, 0, 0 }; static const int maxASIOChannels = 160; class JUCE_API ASIOAudioIODevice : public AudioIODevice, - private Thread, private Timer { public: @@ -231109,7 +232254,6 @@ public: ASIOAudioIODevice (const String& name_, const CLSID classId_, const int slotNumber) : AudioIODevice (name_, T("ASIO")), - Thread ("Juce ASIO"), asioObject (0), classId (classId_), currentBitDepth (16), @@ -231477,8 +232621,6 @@ public: { buffersCreated = true; - jassert (! isThreadRunning()); - juce_free (tempBuffer); tempBuffer = (float*) juce_calloc (totalBuffers * currentBlockSizeSamples * sizeof (float) + 128); @@ -231582,7 +232724,6 @@ public: } isOpen_ = true; - isThreadReady = false; log ("starting ASIO"); calledback = false; @@ -231789,26 +232930,6 @@ public: return done; } - void run() - { - isThreadReady = true; - - for (;;) - { - event1.wait(); - - if (threadShouldExit()) - break; - - processBuffer(); - } - - if (bufferIndex < 0) - { - log ("! ASIO callback never called"); - } - } - void resetRequest() throw() { needToReset = true; @@ -231892,7 +233013,7 @@ private: bool isOpen_, isStarted; bool volatile isASIOOpen; bool volatile calledback; - bool volatile littleEndian, postOutput, needToReset, isReSync, isThreadReady; + bool volatile littleEndian, postOutput, needToReset, isReSync; bool volatile insideControlPanelModalLoop; bool volatile shouldUsePreferredSize; @@ -232779,46 +233900,65 @@ public: } } - const StringArray getDeviceNames (const bool /*preferInputNames*/) const + const StringArray getDeviceNames (const bool /*wantInputNames*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this return deviceNames; } - const String getDefaultDeviceName (const bool /*preferInputNames*/, - const int /*numInputChannelsNeeded*/, - const int /*numOutputChannelsNeeded*/) const + int getDefaultDeviceIndex (const bool) const { jassert (hasScanned); // need to call scanForDevices() before doing this - return deviceNames [0]; + for (int i = deviceNames.size(); --i >= 0;) + if (deviceNames[i].containsIgnoreCase (T("asio4all"))) + return i; // asio4all is a safe choice for a default.. + +#if JUCE_DEBUG + if (deviceNames.size() > 1 && deviceNames[0].containsIgnoreCase (T("digidesign"))) + return 1; // (the digi m-box driver crashes the app when you run + // it in the debugger, which can be a bit annoying) +#endif + + return 0; } - AudioIODevice* createDevice (const String& deviceName) + static int findFreeSlot() + { + for (int i = 0; i < numElementsInArray (currentASIODev); ++i) + if (currentASIODev[i] == 0) + return i; + + jassertfalse; // unfortunately you can only have a finite number + // of ASIO devices open at the same time.. + return -1; + } + + int getIndexOfDevice (AudioIODevice* d, const bool /*asInput*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this - const int index = deviceNames.indexOf (deviceName); + return d == 0 ? -1 : deviceNames.indexOf (d->getName()); + } + + bool hasSeparateInputsAndOutputs() const { return false; } + + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (inputDeviceName == outputDeviceName); + (void) inputDeviceName; + jassert (hasScanned); // need to call scanForDevices() before doing this + + const int index = deviceNames.indexOf (outputDeviceName); if (index >= 0) { - int freeSlot = -1; - - for (int i = 0; i < numElementsInArray (currentASIODev); ++i) - { - if (currentASIODev[i] == 0) - { - freeSlot = i; - break; - } - } - - jassert (freeSlot >= 0); // unfortunately you can only have a finite number - // of ASIO devices open at the same time.. + const int freeSlot = findFreeSlot(); if (freeSlot >= 0) - return new ASIOAudioIODevice (deviceName, *(classIds [index]), freeSlot); + return new ASIOAudioIODevice (outputDeviceName, *(classIds [index]), freeSlot); } return 0; @@ -232942,6 +234082,17 @@ AudioIODeviceType* juce_createASIOAudioIODeviceType() return new ASIOAudioIODeviceType(); } +AudioIODevice* juce_createASIOAudioIODeviceForGUID (const String& name, + void* guid) +{ + const int freeSlot = ASIOAudioIODeviceType::findFreeSlot(); + + if (freeSlot < 0) + return 0; + + return new ASIOAudioIODevice (name, *(CLSID*) guid, freeSlot); +} + END_JUCE_NAMESPACE #undef log @@ -235644,7 +236795,7 @@ public: totalBytesPerBuffer = (3 * bytesPerBuffer) & ~15; const int numChannels = 2; - hr = pDirectSound->SetCooperativeLevel (GetDesktopWindow(), 3 /* DSSCL_EXCLUSIVE */); + hr = pDirectSound->SetCooperativeLevel (GetDesktopWindow(), 2 /* DSSCL_PRIORITY */); logError (hr); if (hr == S_OK) @@ -235783,6 +236934,16 @@ public: (void**) &lpbuf1, &dwSize1, (void**) &lpbuf2, &dwSize2, 0); + if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST + { + pOutputBuffer->Restore(); + + hr = pOutputBuffer->Lock (writeOffset, + bytesPerBuffer, + (void**) &lpbuf1, &dwSize1, + (void**) &lpbuf2, &dwSize2, 0); + } + if (hr == S_OK) { if (bitDepth == 16) @@ -236208,7 +237369,7 @@ public: } }; -static int findBestMatchForName (const String& name, const StringArray& names) +/*static int findBestMatchForName (const String& name, const StringArray& names) { int i = names.indexOf (name); @@ -236237,21 +237398,21 @@ static int findBestMatchForName (const String& name, const StringArray& names) } return bestResult; -} +}*/ class DSoundAudioIODevice : public AudioIODevice, public Thread { public: DSoundAudioIODevice (const String& deviceName, - const int index, - const int inputIndex_) + const int outputDeviceIndex_, + const int inputDeviceIndex_) : AudioIODevice (deviceName, "DirectSound"), Thread ("Juce DSound"), isOpen_ (false), isStarted (false), - deviceIndex (index), - inputIndex (inputIndex_), + outputDeviceIndex (outputDeviceIndex_), + inputDeviceIndex (inputDeviceIndex_), inChans (4), outChans (4), numInputBuffers (0), @@ -236260,8 +237421,20 @@ public: sampleRate (0.0), inputBuffers (0), outputBuffers (0), - callback (0) + callback (0), + bufferSizeSamples (0) { + if (outputDeviceIndex_ >= 0) + { + outChannels.add (TRANS("Left")); + outChannels.add (TRANS("Right")); + } + + if (inputDeviceIndex_ >= 0) + { + inChannels.add (TRANS("Left")); + inChannels.add (TRANS("Right")); + } } ~DSoundAudioIODevice() @@ -236433,13 +237606,13 @@ public: juce_UseDebuggingNewOperator StringArray inChannels, outChannels; + int outputDeviceIndex, inputDeviceIndex; private: bool isOpen_; bool isStarted; String lastError; - int deviceIndex, inputIndex; OwnedArray inChans; OwnedArray outChans; WaitableEvent startEvent; @@ -236668,72 +237841,46 @@ public: } } - const StringArray getDeviceNames (const bool preferInputNames) const + const StringArray getDeviceNames (const bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this - return preferInputNames ? inputDeviceNames - : outputDeviceNames; + return wantInputNames ? inputDeviceNames + : outputDeviceNames; } - const String getDefaultDeviceName (const bool preferInputNames, - const int /*numInputChannelsNeeded*/, - const int /*numOutputChannelsNeeded*/) const + int getDefaultDeviceIndex (const bool /*forInput*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this - - return getDeviceNames (preferInputNames) [0]; + return 0; } - AudioIODevice* createDevice (const String& deviceName) + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - if (deviceName.isEmpty() || deviceName.equalsIgnoreCase (T("DirectSound"))) - { - DSoundAudioIODevice* device = new DSoundAudioIODevice (deviceName, -1, -1); + DSoundAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; - int i; - for (i = 0; i < outputDeviceNames.size(); ++i) - { - device->outChannels.add (outputDeviceNames[i] + TRANS(" (left)")); - device->outChannels.add (outputDeviceNames[i] + TRANS(" (right)")); - } + return asInput ? d->inputDeviceIndex + : d->outputDeviceIndex; + } - for (i = 0; i < inputDeviceNames.size(); ++i) - { - device->inChannels.add (inputDeviceNames[i] + TRANS(" (left)")); - device->inChannels.add (inputDeviceNames[i] + TRANS(" (right)")); - } + bool hasSeparateInputsAndOutputs() const { return true; } - return device; - } - else if (outputDeviceNames.contains (deviceName) - || inputDeviceNames.contains (deviceName)) - { - int outputIndex = outputDeviceNames.indexOf (deviceName); - int inputIndex = findBestMatchForName (deviceName, inputDeviceNames); + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (hasScanned); // need to call scanForDevices() before doing this - if (outputIndex < 0) - { - // using an input device name instead.. - inputIndex = inputDeviceNames.indexOf (deviceName); - outputIndex = jmax (0, findBestMatchForName (deviceName, outputDeviceNames)); - } + const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); + const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); - DSoundAudioIODevice* device = new DSoundAudioIODevice (deviceName, outputIndex, inputIndex); - - device->outChannels.add (TRANS("Left")); - device->outChannels.add (TRANS("Right")); - - if (inputIndex >= 0) - { - device->inChannels.add (TRANS("Left")); - device->inChannels.add (TRANS("Right")); - } - - return device; - } + if (outputIndex >= 0 || inputIndex >= 0) + return new DSoundAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName + : inputDeviceName, + outputIndex, inputIndex); return 0; } @@ -236868,8 +238015,8 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, right = inputBuffers[numIns++] = (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)); if (left != 0 || right != 0) - inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [i / 2], - dlh.inputGuids [i / 2], + inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [inputDeviceIndex], + dlh.inputGuids [inputDeviceIndex], (int) sampleRate, bufferSizeSamples, left, right)); } @@ -236896,8 +238043,8 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, right = outputBuffers[numOuts++] = (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)); if (left != 0 || right != 0) - outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[i / 2], - dlh.outputGuids [i / 2], + outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[outputDeviceIndex], + dlh.outputGuids [outputDeviceIndex], (int) sampleRate, bufferSizeSamples, left, right)); } @@ -239298,6 +240445,7 @@ public: fullScreen (false), isDragging (false), isMouseOver (false), + hasCreatedCaret (false), currentWindowIcon (0), taskBarIcon (0), dropTarget (0) @@ -239632,6 +240780,18 @@ public: shouldDeactivateTitleBar = oldDeactivate; } + void textInputRequired (int x, int y) + { + if (! hasCreatedCaret) + { + hasCreatedCaret = true; + CreateCaret (hwnd, 0, 0, 0); + } + + ShowCaret (hwnd); + SetCaretPos (x, y); + } + void repaint (int x, int y, int w, int h) { const RECT r = { x, y, x + w, y + h }; @@ -239711,7 +240871,7 @@ public: private: HWND hwnd; DropShadower* shadower; - bool fullScreen, isDragging, isMouseOver; + bool fullScreen, isDragging, isMouseOver, hasCreatedCaret; BorderSize windowBorder; HICON currentWindowIcon; NOTIFYICONDATA* taskBarIcon; @@ -240770,6 +241930,12 @@ private: break; case WM_KILLFOCUS: + if (hasCreatedCaret) + { + hasCreatedCaret = false; + DestroyCaret(); + } + handleFocusLoss(); break; @@ -243695,7 +244861,7 @@ void NamedPipe::cancelPendingReads() int timeout = 2000; while (intern->blocked && --timeout >= 0) - sleep (2); + Thread::sleep (2); intern->stopReadOperation = false; } @@ -243910,8 +245076,26 @@ public: if (! decomposeURL (url, hostName, hostPath, hostPort)) return false; - struct hostent* const host - = gethostbyname ((const char*) hostName.toUTF8()); + const struct hostent* host = 0; + int port = 0; + + String proxyName, proxyPath; + int proxyPort = 0; + + String proxyURL (getenv ("http_proxy")); + if (proxyURL.startsWithIgnoreCase (T("http://"))) + { + if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) + return false; + + host = gethostbyname ((const char*) proxyName.toUTF8()); + port = proxyPort; + } + else + { + host = gethostbyname ((const char*) hostName.toUTF8()); + port = hostPort; + } if (host == 0) return false; @@ -243920,7 +245104,7 @@ public: zerostruct (address); memcpy ((void*) &address.sin_addr, (const void*) host->h_addr, host->h_length); address.sin_family = host->h_addrtype; - address.sin_port = htons (hostPort); + address.sin_port = htons (port); socketHandle = socket (host->h_addrtype, SOCK_STREAM, 0); @@ -243941,17 +245125,11 @@ public: return false; } - String proxyURL (getenv ("http_proxy")); - - if (! proxyURL.startsWithIgnoreCase (T("http://"))) - proxyURL = String::empty; - - const MemoryBlock requestHeader (createRequestHeader (hostName, hostPath, - proxyURL, url, - hostPort, + const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, + proxyName, proxyPort, + hostPath, url, headers, postData, isPost)); - int totalHeaderSent = 0; while (totalHeaderSent < requestHeader.getSize()) @@ -244051,37 +245229,26 @@ private: } const MemoryBlock createRequestHeader (const String& hostName, - const String& hostPath, - const String& proxyURL, - const String& originalURL, const int hostPort, + const String& proxyName, + const int proxyPort, + const String& hostPath, + const String& originalURL, const String& headers, const MemoryBlock& postData, const bool isPost) { String header (isPost ? "POST " : "GET "); - if (proxyURL.isEmpty()) + if (proxyName.isEmpty()) { header << hostPath << " HTTP/1.0\r\nHost: " << hostName << ':' << hostPort; } else { - String proxyName, proxyPath; - int proxyPort; - - if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) - return MemoryBlock(); - header << originalURL << " HTTP/1.0\r\nHost: " << proxyName << ':' << proxyPort; - - /* xxx needs finishing - const char* proxyAuth = getenv ("http_proxy_auth"); - if (proxyAuth != 0) - header << T("\r\nProxy-Authorization: ") << Base64Encode (proxyAuth); - */ } header << "\r\nUser-Agent: JUCE/" @@ -244792,7 +245959,7 @@ static void getDeviceSampleRates (snd_pcm_t* handle, Array & rates) if (snd_pcm_hw_params_any (handle, hwParams) >= 0 && snd_pcm_hw_params_test_rate (handle, hwParams, ratesToTry[i], 0) == 0) { - rates.add (ratesToTry[i]); + rates.addIfNotAlreadyThere (ratesToTry[i]); } } } @@ -244816,6 +245983,9 @@ static void getDeviceProperties (const String& id, unsigned int& maxChansIn, Array & rates) { + if (id.isEmpty()) + return; + snd_ctl_t* handle; if (snd_ctl_open (&handle, id.upToLastOccurrenceOf (T(","), false, false), SND_CTL_NONBLOCK) >= 0) @@ -244862,7 +246032,7 @@ static void getDeviceProperties (const String& id, class ALSADevice { public: - ALSADevice (const String& deviceName, + ALSADevice (const String& id, const bool forInput) : handle (0), bitDepth (16), @@ -244870,7 +246040,7 @@ public: isInput (forInput), sampleFormat (AudioDataConverters::int16LE) { - failed (snd_pcm_open (&handle, deviceName, + failed (snd_pcm_open (&handle, id, forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC)); } @@ -245072,12 +246242,14 @@ private: class ALSAThread : public Thread { public: - ALSAThread (const String& deviceName_) + ALSAThread (const String& inputId_, + const String& outputId_) : Thread ("Juce ALSA"), sampleRate (0), bufferSize (0), callback (0), - deviceName (deviceName_), + inputId (inputId_), + outputId (outputId_), outputDevice (0), inputDevice (0), numCallbacks (0), @@ -245110,16 +246282,9 @@ public: currentInputChans.clear(); currentOutputChans.clear(); - numChannelsRunning = jmax (inputChannels.getHighestBit(), - outputChannels.getHighestBit()) + 1; - - numChannelsRunning = jmin (maxNumChans, jlimit ((int) minChansIn, - (int) maxChansIn, - numChannelsRunning)); - if (inputChannels.getHighestBit() >= 0) { - for (int i = 0; i < numChannelsRunning; ++i) + for (int i = 0; i <= inputChannels.getHighestBit(); ++i) { inputChannelData [i] = (float*) juce_calloc (sizeof (float) * bufferSize); @@ -245133,7 +246298,7 @@ public: if (outputChannels.getHighestBit() >= 0) { - for (int i = 0; i < numChannelsRunning; ++i) + for (int i = 0; i <= outputChannels.getHighestBit(); ++i) { outputChannelData [i] = (float*) juce_calloc (sizeof (float) * bufferSize); @@ -245145,9 +246310,9 @@ public: } } - if (totalNumOutputChannels > 0) + if (totalNumOutputChannels > 0 && outputId.isNotEmpty()) { - outputDevice = new ALSADevice (deviceName, false); + outputDevice = new ALSADevice (outputId, false); if (outputDevice->error.isNotEmpty()) { @@ -245156,7 +246321,9 @@ public: return; } - if (! outputDevice->setParameters ((unsigned int) sampleRate, numChannelsRunning, bufferSize)) + if (! outputDevice->setParameters ((unsigned int) sampleRate, + currentOutputChans.getHighestBit() + 1, + bufferSize)) { error = outputDevice->error; deleteAndZero (outputDevice); @@ -245164,9 +246331,9 @@ public: } } - if (totalNumInputChannels > 0) + if (totalNumInputChannels > 0 && inputId.isNotEmpty()) { - inputDevice = new ALSADevice (deviceName, true); + inputDevice = new ALSADevice (inputId, true); if (inputDevice->error.isNotEmpty()) { @@ -245175,7 +246342,9 @@ public: return; } - if (! inputDevice->setParameters ((unsigned int) sampleRate, numChannelsRunning, bufferSize)) + if (! inputDevice->setParameters ((unsigned int) sampleRate, + currentInputChans.getHighestBit() + 1, + bufferSize)) { error = inputDevice->error; deleteAndZero (inputDevice); @@ -245235,7 +246404,6 @@ public: zeromem (inputChannelDataForCallback, sizeof (inputChannelDataForCallback)); totalNumOutputChannels = 0; totalNumInputChannels = 0; - numChannelsRunning = 0; numCallbacks = 0; } @@ -245252,8 +246420,6 @@ public: { if (inputDevice != 0) { - jassert (numChannelsRunning >= inputDevice->numChannelsRunning); - if (! inputDevice->read (inputChannelData, bufferSize)) { DBG ("ALSA: read failure"); @@ -245292,7 +246458,6 @@ public: failed (snd_pcm_avail_update (outputDevice->handle)); - jassert (numChannelsRunning >= outputDevice->numChannelsRunning); if (! outputDevice->write (outputChannelData, bufferSize)) { DBG ("ALSA: write failure"); @@ -245326,7 +246491,7 @@ public: private: - const String deviceName; + const String inputId, outputId; ALSADevice* outputDevice; ALSADevice* inputDevice; int numCallbacks; @@ -245339,7 +246504,6 @@ private: float* inputChannelData [maxNumChans]; float* inputChannelDataForCallback [maxNumChans]; int totalNumOutputChannels; - int numChannelsRunning; unsigned int minChansOut, maxChansOut; unsigned int minChansIn, maxChansIn; @@ -245363,8 +246527,10 @@ private: maxChansOut = 0; minChansIn = 0; maxChansIn = 0; + unsigned int dummy = 0; - getDeviceProperties (deviceName, minChansOut, maxChansOut, minChansIn, maxChansIn, sampleRates); + getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates); + getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates); unsigned int i; for (i = 0; i < maxChansOut; ++i) @@ -245379,13 +246545,16 @@ class ALSAAudioIODevice : public AudioIODevice { public: ALSAAudioIODevice (const String& deviceName, - const String& deviceId) + const String& inputId_, + const String& outputId_) : AudioIODevice (deviceName, T("ALSA")), + inputId (inputId_), + outputId (outputId_), isOpen_ (false), isStarted (false), internal (0) { - internal = new ALSAThread (deviceId); + internal = new ALSAThread (inputId, outputId); } ~ALSAAudioIODevice() @@ -245544,6 +246713,8 @@ public: return internal->error; } + String inputId, outputId; + private: bool isOpen_, isStarted; ALSAThread* internal; @@ -245565,10 +246736,14 @@ public: void scanForDevices() { - hasScanned = true; + if (hasScanned) + return; - names.clear(); - ids.clear(); + hasScanned = true; + inputNames.clear(); + inputIds.clear(); + outputNames.clear(); + outputIds.clear(); snd_ctl_t* handle; snd_ctl_card_info_t* info; @@ -245576,7 +246751,7 @@ public: int cardNum = -1; - while (ids.size() <= 24) + while (outputIds.size() + inputIds.size() <= 32) { snd_card_next (&cardNum); @@ -245602,18 +246777,26 @@ public: String id, name; id << "hw:" << cardId << ',' << device; - if (testDevice (id)) + bool isInput, isOutput; + + if (testDevice (id, isInput, isOutput)) { name << snd_ctl_card_info_get_name (info); if (name.isEmpty()) name = id; - if (device > 0) - name << " (" << (device + 1) << ')'; + if (isInput) + { + inputNames.add (name); + inputIds.add (id); + } - ids.add (id); - names.add (name); + if (isOutput) + { + outputNames.add (name); + outputIds.add (id); + } } } } @@ -245621,35 +246804,54 @@ public: snd_ctl_close (handle); } } + + inputNames.appendNumbersToDuplicates (false, true); + outputNames.appendNumbersToDuplicates (false, true); } - const StringArray getDeviceNames (const bool /*preferInputNames*/) const + const StringArray getDeviceNames (const bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this - StringArray namesCopy (names); - namesCopy.removeDuplicates (true); - - return namesCopy; + return wantInputNames ? inputNames : outputNames; } - const String getDefaultDeviceName (const bool /*preferInputNames*/, - const int /*numInputChannelsNeeded*/, - const int /*numOutputChannelsNeeded*/) const + int getDefaultDeviceIndex (const bool forInput) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + return 0; + } + + bool hasSeparateInputsAndOutputs() const { return true; } + + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - return names[0]; + ALSAAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; + + return asInput ? inputIds.indexOf (d->inputId) + : outputIds.indexOf (d->outputId); } - AudioIODevice* createDevice (const String& deviceName) + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) { jassert (hasScanned); // need to call scanForDevices() before doing this - const int index = names.indexOf (deviceName); + const int inputIndex = inputNames.indexOf (inputDeviceName); + const int outputIndex = outputNames.indexOf (outputDeviceName); + + String deviceName (outputDeviceName); + if (deviceName.isEmpty()) + deviceName = inputDeviceName; if (index >= 0) - return new ALSAAudioIODevice (deviceName, ids [index]); + return new ALSAAudioIODevice (deviceName, + inputIds [inputIndex], + outputIds [outputIndex]); return 0; } @@ -245657,10 +246859,10 @@ public: juce_UseDebuggingNewOperator private: - StringArray names, ids; + StringArray inputNames, outputNames, inputIds, outputIds; bool hasScanned; - static bool testDevice (const String& id) + static bool testDevice (const String& id, bool& isInput, bool& isOutput) { unsigned int minChansOut = 0, maxChansOut = 0; unsigned int minChansIn = 0, maxChansIn = 0; @@ -245673,7 +246875,10 @@ private: + T(" ins=") + String ((int) minChansIn) + T("-") + String ((int) maxChansIn) + T(" rates=") + String (rates.size())); - return (maxChansOut > 0 || maxChansIn > 0) && rates.size() > 0; + isInput = maxChansIn > 0; + isOutput = maxChansOut > 0; + + return (isInput || isOutput) && rates.size() > 0; } ALSAAudioIODeviceType (const ALSAAudioIODeviceType&); @@ -246443,6 +247648,8 @@ struct MessageThreadFuncCall }; static bool errorCondition = false; +static XErrorHandler oldErrorHandler = (XErrorHandler) 0; +static XIOErrorHandler oldIOErrorHandler = (XIOErrorHandler) 0; // (defined in another file to avoid problems including certain headers in this one) extern bool juce_isRunningAsApplication(); @@ -246454,7 +247661,7 @@ static int ioErrorHandler (Display* display) errorCondition = true; - if (! juce_isRunningAsApplication()) + if (juce_isRunningAsApplication()) Process::terminate(); return 0; @@ -246518,20 +247725,29 @@ static void sig_handler (int sig) void MessageManager::doPlatformSpecificInitialisation() { // Initialise xlib for multiple thread support - if (! XInitThreads()) - { - // This is fatal! Print error and closedown - Logger::outputDebugString ("Failed to initialise xlib thread support."); + static bool initThreadCalled = false; - if (juce_isRunningAsApplication()) - Process::terminate(); + if (! initThreadCalled) + { + if (! XInitThreads()) + { + // This is fatal! Print error and closedown + Logger::outputDebugString ("Failed to initialise xlib thread support."); + + if (juce_isRunningAsApplication()) + Process::terminate(); + + return; + } + + initThreadCalled = true; } // This is called if the client/server connection is broken - XSetIOErrorHandler (ioErrorHandler); + oldIOErrorHandler = XSetIOErrorHandler (ioErrorHandler); // This is called if a protocol error occurs - XSetErrorHandler (errorHandler); + oldErrorHandler = XSetErrorHandler (errorHandler); // Install signal handler for break-in struct sigaction saction; @@ -246564,6 +247780,8 @@ void MessageManager::doPlatformSpecificInitialisation() if (juce_isRunningAsApplication()) Process::terminate(); + + return; } // Get defaults for various properties @@ -246597,6 +247815,16 @@ void MessageManager::doPlatformSpecificShutdown() { XDestroyWindow (display, juce_messageWindowHandle); XCloseDisplay (display); + + // reset pointers + juce_messageWindowHandle = 0; + display = 0; + + // Restore original error handlers + XSetIOErrorHandler (oldIOErrorHandler); + oldIOErrorHandler = 0; + XSetErrorHandler (oldErrorHandler); + oldErrorHandler = 0; } } @@ -246690,6 +247918,8 @@ bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) if (juce_isRunningAsApplication()) Process::terminate(); + + return false; } if (returnIfNoPendingMessages && ! XPending (display)) @@ -248307,6 +249537,10 @@ public: } } + void textInputRequired (int /*x*/, int /*y*/) + { + } + void repaint (int x, int y, int w, int h) { if (Rectangle::intersectRectangles (x, y, w, h, @@ -251709,7 +252943,7 @@ void NamedPipe::cancelPendingReads() int timeout = 2000; while (intern->blocked && --timeout >= 0) - sleep (2); + Thread::sleep (2); intern->stopReadOperation = false; } @@ -252340,8 +253574,26 @@ public: if (! decomposeURL (url, hostName, hostPath, hostPort)) return false; - struct hostent* const host - = gethostbyname ((const char*) hostName.toUTF8()); + const struct hostent* host = 0; + int port = 0; + + String proxyName, proxyPath; + int proxyPort = 0; + + String proxyURL (getenv ("http_proxy")); + if (proxyURL.startsWithIgnoreCase (T("http://"))) + { + if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) + return false; + + host = gethostbyname ((const char*) proxyName.toUTF8()); + port = proxyPort; + } + else + { + host = gethostbyname ((const char*) hostName.toUTF8()); + port = hostPort; + } if (host == 0) return false; @@ -252350,7 +253602,7 @@ public: zerostruct (address); memcpy ((void*) &address.sin_addr, (const void*) host->h_addr, host->h_length); address.sin_family = host->h_addrtype; - address.sin_port = htons (hostPort); + address.sin_port = htons (port); socketHandle = socket (host->h_addrtype, SOCK_STREAM, 0); @@ -252371,17 +253623,11 @@ public: return false; } - String proxyURL (getenv ("http_proxy")); - - if (! proxyURL.startsWithIgnoreCase (T("http://"))) - proxyURL = String::empty; - - const MemoryBlock requestHeader (createRequestHeader (hostName, hostPath, - proxyURL, url, - hostPort, + const MemoryBlock requestHeader (createRequestHeader (hostName, hostPort, + proxyName, proxyPort, + hostPath, url, headers, postData, isPost)); - int totalHeaderSent = 0; while (totalHeaderSent < requestHeader.getSize()) @@ -252481,37 +253727,26 @@ private: } const MemoryBlock createRequestHeader (const String& hostName, - const String& hostPath, - const String& proxyURL, - const String& originalURL, const int hostPort, + const String& proxyName, + const int proxyPort, + const String& hostPath, + const String& originalURL, const String& headers, const MemoryBlock& postData, const bool isPost) { String header (isPost ? "POST " : "GET "); - if (proxyURL.isEmpty()) + if (proxyName.isEmpty()) { header << hostPath << " HTTP/1.0\r\nHost: " << hostName << ':' << hostPort; } else { - String proxyName, proxyPath; - int proxyPort; - - if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) - return MemoryBlock(); - header << originalURL << " HTTP/1.0\r\nHost: " << proxyName << ':' << proxyPort; - - /* xxx needs finishing - const char* proxyAuth = getenv ("http_proxy_auth"); - if (proxyAuth != 0) - header << T("\r\nProxy-Authorization: ") << Base64Encode (proxyAuth); - */ } header << "\r\nUser-Agent: JUCE/" @@ -253794,7 +255029,7 @@ public: for (i = numOutputChans; --i >= 0;) { const CallbackDetailsForChannel& info = outputChannelInfo[i]; - const float* src = tempOutputBuffers [info.sourceChannelNum]; + const float* src = tempOutputBuffers [i]; float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + info.dataOffsetSamples; const int stride = info.dataStrideSamples; @@ -254009,31 +255244,52 @@ class CoreAudioIODevice : public AudioIODevice { public: CoreAudioIODevice (const String& deviceName, - AudioDeviceID deviceId1) + AudioDeviceID inputDeviceId, + const int inputIndex_, + AudioDeviceID outputDeviceId, + const int outputIndex_) : AudioIODevice (deviceName, "CoreAudio"), + inputIndex (inputIndex_), + outputIndex (outputIndex_), isOpen_ (false), isStarted (false) { internal = 0; + CoreAudioInternal* device = 0; - CoreAudioInternal* device = new CoreAudioInternal (deviceId1); - lastError = device->error; - - if (lastError.isNotEmpty()) + if (outputDeviceId == 0 || outputDeviceId == inputDeviceId) { - deleteAndZero (device); + jassert (inputDeviceId != 0); + + device = new CoreAudioInternal (inputDeviceId); + lastError = device->error; + + if (lastError.isNotEmpty()) + deleteAndZero (device); } else { - CoreAudioInternal* secondDevice = device->getRelatedDevice(); + device = new CoreAudioInternal (outputDeviceId); + lastError = device->error; - if (secondDevice != 0) + if (lastError.isNotEmpty()) { - if (device->inChanNames.size() > secondDevice->inChanNames.size()) - swapVariables (device, secondDevice); + deleteAndZero (device); + } + else if (inputDeviceId != 0) + { + CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId); + lastError = device->error; - device->inputDevice = secondDevice; - secondDevice->isSlaveDevice = true; + if (lastError.isNotEmpty()) + { + delete secondDevice; + } + else + { + device->inputDevice = secondDevice; + secondDevice->isSlaveDevice = true; + } } } @@ -254211,6 +255467,8 @@ public: return lastError; } + int inputIndex, outputIndex; + juce_UseDebuggingNewOperator private: @@ -254241,6 +255499,326 @@ private: const CoreAudioIODevice& operator= (const CoreAudioIODevice&); }; +class CoreAudioDevicePanel : public Component, + public ComboBoxListener, + public ChangeListener, + public ButtonListener +{ +public: + CoreAudioDevicePanel (AudioIODeviceType* type_, + AudioIODeviceType::DeviceSetupDetails& setup_) + : type (type_), + setup (setup_) + { + sampleRateDropDown = 0; + sampleRateLabel = 0; + bufferSizeDropDown = 0; + bufferSizeLabel = 0; + outputDeviceDropDown = 0; + outputDeviceLabel = 0; + inputDeviceDropDown = 0; + inputDeviceLabel = 0; + testButton = 0; + inputLevelMeter = 0; + + type->scanForDevices(); + + if (setup.maxNumOutputChannels > 0) + { + outputDeviceDropDown = new ComboBox (String::empty); + addNamesToDeviceBox (*outputDeviceDropDown, false); + outputDeviceDropDown->addListener (this); + addAndMakeVisible (outputDeviceDropDown); + + outputDeviceLabel = new Label (String::empty, TRANS ("output:")); + outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); + + addAndMakeVisible (testButton = new TextButton (TRANS ("Test"))); + testButton->addButtonListener (this); + } + + if (setup.maxNumInputChannels > 0) + { + inputDeviceDropDown = new ComboBox (String::empty); + addNamesToDeviceBox (*inputDeviceDropDown, true); + inputDeviceDropDown->addListener (this); + addAndMakeVisible (inputDeviceDropDown); + + inputDeviceLabel = new Label (String::empty, TRANS ("input:")); + inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); + + addAndMakeVisible (inputLevelMeter = AudioDeviceSelectorComponent::createSimpleLevelMeterComponent (setup_.manager)); + } + + setup.manager->addChangeListener (this); + changeListenerCallback (0); + } + + ~CoreAudioDevicePanel() + { + setup.manager->removeChangeListener (this); + + deleteAndZero (outputDeviceLabel); + deleteAndZero (inputDeviceLabel); + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAllChildren(); + } + + void resized() + { + const int lx = proportionOfWidth (0.35f); + const int w = proportionOfWidth (0.5f); + const int h = 24; + const int space = 6; + const int dh = h + space; + int y = 0; + + if (outputDeviceDropDown != 0) + { + outputDeviceDropDown->setBounds (lx, y, w, h); + testButton->setBounds (outputDeviceDropDown->getRight() + 8, + outputDeviceDropDown->getY(), + getWidth() - outputDeviceDropDown->getRight() - 10, + h); + y += dh; + } + + if (inputDeviceDropDown != 0) + { + inputDeviceDropDown->setBounds (lx, y, w, h); + + inputLevelMeter->setBounds (inputDeviceDropDown->getRight() + 8, + inputDeviceDropDown->getY(), + getWidth() - inputDeviceDropDown->getRight() - 10, + h); + y += dh; + } + + y += space * 2; + + if (sampleRateDropDown != 0) + { + sampleRateDropDown->setBounds (lx, y, w, h); + y += dh; + } + + if (bufferSizeDropDown != 0) + { + bufferSizeDropDown->setBounds (lx, y, w, h); + y += dh; + } + } + + void comboBoxChanged (ComboBox* comboBoxThatHasChanged) + { + if (comboBoxThatHasChanged == 0) + return; + + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + String error; + + if (comboBoxThatHasChanged == outputDeviceDropDown + || comboBoxThatHasChanged == inputDeviceDropDown) + { + config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty + : outputDeviceDropDown->getText(); + config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty + : inputDeviceDropDown->getText(); + + if (comboBoxThatHasChanged == inputDeviceDropDown) + config.useDefaultInputChannels = true; + else + config.useDefaultOutputChannels = true; + + error = setup.manager->setAudioDeviceSetup (config, true); + + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + } + else if (comboBoxThatHasChanged == sampleRateDropDown) + { + if (sampleRateDropDown->getSelectedId() > 0) + { + config.sampleRate = sampleRateDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + else if (comboBoxThatHasChanged == bufferSizeDropDown) + { + if (bufferSizeDropDown->getSelectedId() > 0) + { + config.bufferSize = bufferSizeDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + + if (error.isNotEmpty()) + { + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + T("Error when trying to open audio device!"), + error); + } + } + + void buttonClicked (Button*) + { + setup.manager->playTestSound(); + } + + void changeListenerCallback (void*) + { + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + if (currentDevice != 0) + { + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + + // sample rate.. + { + if (sampleRateDropDown == 0) + { + addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty)); + sampleRateDropDown->addListener (this); + + delete sampleRateLabel; + sampleRateLabel = new Label (String::empty, TRANS ("sample rate:")); + sampleRateLabel->attachToComponent (sampleRateDropDown, true); + } + else + { + sampleRateDropDown->clear(); + sampleRateDropDown->removeListener (this); + } + + const int numRates = currentDevice->getNumSampleRates(); + + for (int i = 0; i < numRates; ++i) + { + const int rate = roundDoubleToInt (currentDevice->getSampleRate (i)); + sampleRateDropDown->addItem (String (rate) + T(" Hz"), rate); + } + + sampleRateDropDown->setSelectedId (roundDoubleToInt (currentDevice->getCurrentSampleRate()), true); + sampleRateDropDown->addListener (this); + } + + // buffer size + { + if (bufferSizeDropDown == 0) + { + addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty)); + bufferSizeDropDown->addListener (this); + + delete bufferSizeLabel; + bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:")); + bufferSizeLabel->attachToComponent (bufferSizeDropDown, true); + } + else + { + bufferSizeDropDown->clear(); + } + + const int numBufferSizes = currentDevice->getNumBufferSizesAvailable(); + double currentRate = currentDevice->getCurrentSampleRate(); + if (currentRate == 0) + currentRate = 44100.0; + + for (int i = 0; i < numBufferSizes; ++i) + { + const int bs = currentDevice->getBufferSizeSamples (i); + bufferSizeDropDown->addItem (String (bs) + + T(" samples (") + + String (bs * 1000.0 / currentRate, 1) + + T(" ms)"), + bs); + } + + bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), true); + } + } + else + { + jassert (setup.manager->getCurrentAudioDevice() == 0); // not the correct device type! + + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAndZero (sampleRateDropDown); + deleteAndZero (bufferSizeDropDown); + + if (outputDeviceDropDown != 0) + outputDeviceDropDown->setSelectedId (-1, true); + + if (inputDeviceDropDown != 0) + inputDeviceDropDown->setSelectedId (-1, true); + } + + resized(); + setSize (getWidth(), getLowestY() + 4); + } + +private: + AudioIODeviceType* const type; + const AudioIODeviceType::DeviceSetupDetails setup; + + ComboBox* outputDeviceDropDown; + ComboBox* inputDeviceDropDown; + ComboBox* sampleRateDropDown; + ComboBox* bufferSizeDropDown; + Label* outputDeviceLabel; + Label* inputDeviceLabel; + Label* sampleRateLabel; + Label* bufferSizeLabel; + TextButton* testButton; + Component* inputLevelMeter; + + void showCorrectDeviceName (ComboBox* const box, const bool isInput) + { + if (box != 0) + { + CoreAudioIODevice* const currentDevice = dynamic_cast (setup.manager->getCurrentAudioDevice()); + + const int index = (currentDevice == 0) ? -1 + : (isInput ? currentDevice->inputIndex + : currentDevice->outputIndex); + + if (index >= 0) + box->setText (type->getDeviceNames (isInput) [index], true); + else + box->setSelectedId (-1, true); + + if (! isInput) + testButton->setEnabled (index >= 0); + } + } + + void addNamesToDeviceBox (ComboBox& combo, bool isInputs) + { + const StringArray devs (type->getDeviceNames (isInputs)); + + for (int i = 0; i < devs.size(); ++i) + combo.addItem (devs[i], i + 1); + + combo.addItem (TRANS("<< none >>"), -1); + combo.setSelectedId (-1, true); + } + + int getLowestY() const + { + int y = 0; + + for (int i = getNumChildComponents(); --i >= 0;) + y = jmax (y, getChildComponent (i)->getBottom()); + + return y; + } + + CoreAudioDevicePanel (const CoreAudioDevicePanel&); + const CoreAudioDevicePanel& operator= (const CoreAudioDevicePanel&); +}; + class CoreAudioIODeviceType : public AudioIODeviceType { public: @@ -254259,8 +255837,10 @@ public: { hasScanned = true; - names.clear(); - ids.clear(); + inputDeviceNames.clear(); + outputDeviceNames.clear(); + inputIds.clear(); + outputIds.clear(); UInt32 size; if (OK (AudioHardwareGetPropertyInfo (kAudioHardwarePropertyDevices, &size, 0))) @@ -254282,8 +255862,20 @@ public: if (! alreadyLogged) log (T("CoreAudio device: ") + nameString); - names.add (nameString); - ids.add (devs[i]); + const int numIns = getNumChannels (devs[i], true); + const int numOuts = getNumChannels (devs[i], false); + + if (numIns > 0) + { + inputDeviceNames.add (nameString); + inputIds.add (devs[i]); + } + + if (numOuts > 0) + { + outputDeviceNames.add (nameString); + outputIds.add (devs[i]); + } } } @@ -254292,52 +255884,83 @@ public: juce_free (devs); } + + inputDeviceNames.appendNumbersToDuplicates (false, true); + outputDeviceNames.appendNumbersToDuplicates (false, true); } - const StringArray getDeviceNames (const bool /*preferInputNames*/) const + const StringArray getDeviceNames (const bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this - StringArray namesCopy (names); - namesCopy.removeDuplicates (true); - - return namesCopy; + if (wantInputNames) + return inputDeviceNames; + else + return outputDeviceNames; } - const String getDefaultDeviceName (const bool preferInputNames, - const int numInputChannelsNeeded, - const int numOutputChannelsNeeded) const + int getDefaultDeviceIndex (const bool forInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - String result (names[0]); - AudioDeviceID deviceID; UInt32 size = sizeof (deviceID); // if they're asking for any input channels at all, use the default input, so we // get the built-in mic rather than the built-in output with no inputs.. - if (AudioHardwareGetProperty (numInputChannelsNeeded > 0 - ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, + if (AudioHardwareGetProperty (forInput ? kAudioHardwarePropertyDefaultInputDevice + : kAudioHardwarePropertyDefaultOutputDevice, &size, &deviceID) == noErr) { - for (int i = ids.size(); --i >= 0;) - if (ids[i] == deviceID) - result = names[i]; + if (forInput) + { + for (int i = inputIds.size(); --i >= 0;) + if (inputIds[i] == deviceID) + return i; + } + else + { + for (int i = outputIds.size(); --i >= 0;) + if (outputIds[i] == deviceID) + return i; + } } - return result; + return 0; } - AudioIODevice* createDevice (const String& deviceName) + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this - const int index = names.indexOf (deviceName); + CoreAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; + + return asInput ? d->inputIndex + : d->outputIndex; + } + + bool hasSeparateInputsAndOutputs() const { return true; } + + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); + const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); + + String deviceName (outputDeviceName); + if (deviceName.isEmpty()) + deviceName = inputDeviceName; if (index >= 0) - return new CoreAudioIODevice (deviceName, ids [index]); + return new CoreAudioIODevice (deviceName, + inputIds [inputIndex], + inputIndex, + outputIds [outputIndex], + outputIndex); return 0; } @@ -254345,11 +255968,37 @@ public: juce_UseDebuggingNewOperator private: - StringArray names; - Array ids; + StringArray inputDeviceNames, outputDeviceNames; + Array inputIds, outputIds; bool hasScanned; + static int getNumChannels (AudioDeviceID deviceID, bool input) + { + int total = 0; + UInt32 size; + + if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, 0))) + { + AudioBufferList* const bufList = (AudioBufferList*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, bufList))) + { + const int numStreams = bufList->mNumberBuffers; + + for (int i = 0; i < numStreams; ++i) + { + const AudioBuffer& b = bufList->mBuffers[i]; + total += b.mNumberChannels; + } + } + + juce_free (bufList); + } + + return total; + } + CoreAudioIODeviceType (const CoreAudioIODeviceType&); const CoreAudioIODeviceType& operator= (const CoreAudioIODeviceType&); }; @@ -257076,6 +258725,10 @@ public: } } + void textInputRequired (int /*x*/, int /*y*/) + { + } + void repaint (int x, int y, int w, int h) { if (Rectangle::intersectRectangles (x, y, w, h, diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 8a4da91796..4ded6d90a4 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -196,7 +196,7 @@ /** Enabling this builds support for VST audio plugins. @see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU */ -#define JUCE_PLUGINHOST_VST 1 +//#define JUCE_PLUGINHOST_VST 1 /** Enabling this builds support for AudioUnit audio plugins. @see AudioUnitPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_VST @@ -4138,6 +4138,8 @@ public: @param minutes minutes 0 to 59 @param seconds seconds 0 to 59 @param milliseconds milliseconds 0 to 999 + @param useLocalTime if true, encode using the current machine's local time; if + false, it will always work in GMT. */ Time (const int year, const int month, @@ -4145,7 +4147,8 @@ public: const int hours, const int minutes, const int seconds = 0, - const int milliseconds = 0) throw(); + const int milliseconds = 0, + const bool useLocalTime = true) throw(); /** Destructor. */ ~Time() throw(); @@ -12994,6 +12997,11 @@ public: */ static void JUCE_CALLTYPE setCurrentModuleInstanceHandle (void* newHandle) throw(); + /** WIN32 ONLY - Gets the command-line params as a string. + + This is needed to avoid unicode problems with the argc type params. + */ + static const String JUCE_CALLTYPE getCurrentCommandLineParams() throw(); #endif /** Clears the floating point unit's flags. @@ -20315,6 +20323,13 @@ public: /** Tries to give the window keyboard focus. */ virtual void grabFocus() = 0; + /** Tells the window that text input may be required at the given position. + + This may cause things like a virtual on-screen keyboard to appear, depending + on the OS. + */ + virtual void textInputRequired (int x, int y) = 0; + /** Called when the window gains keyboard focus. */ void handleFocusGain(); /** Called when the window loses keyboard focus. */ @@ -31673,6 +31688,9 @@ private: #ifndef __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ #define __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ +class AudioDeviceManager; +class Component; + /** Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc. @@ -31709,19 +31727,6 @@ class JUCE_API AudioIODeviceType { public: - /** Creates a list of available types. - - This will add a set of new AudioIODeviceType objects to the specified list, to - represent each available types of device. - - The objects that are created should be managed by the caller (the OwnedArray - will delete them when the array is itself deleted). - - When created, the objects are uninitialised, so you should call scanForDevices() - on each one before getting its list of devices. - */ - static void createDeviceTypes (OwnedArray & list); - /** Returns the name of this type of driver that this object manages. This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc. @@ -31739,34 +31744,46 @@ public: The scanForDevices() method must have been called to create this list. - @param preferInputNames only really used by DirectSound where devices are split up - into inputs and outputs, this indicates whether to use - the input or output name to refer to a pair of devices. + @param wantInputNames only really used by DirectSound where devices are split up + into inputs and outputs, this indicates whether to use + the input or output name to refer to a pair of devices. */ - virtual const StringArray getDeviceNames (const bool preferInputNames = false) const = 0; + virtual const StringArray getDeviceNames (const bool wantInputNames = false) const = 0; /** Returns the name of the default device. This will be one of the names from the getDeviceNames() list. - @param preferInputNames only really used by DirectSound where devices are split up - into inputs and outputs, this indicates whether to use - the input or output name to refer to a pair of devices. - @param numInputChannelsNeeded the number of input channels the user is expecting to need - this - may be used to help decide which device would be most suitable - @param numOutputChannelsNeeded the number of output channels the user is expecting to need - this - may be used to help decide which device would be most suitable + @param forInput if true, this means that a default input device should be + returned; if false, it should return the default output */ - virtual const String getDefaultDeviceName (const bool preferInputNames, - const int numInputChannelsNeeded, - const int numOutputChannelsNeeded) const = 0; + virtual int getDefaultDeviceIndex (const bool forInput) const = 0; + + /** Returns the index of a given device in the list of device names. + If asInput is true, it shows the index in the inputs list, otherwise it + looks for it in the outputs list. + */ + virtual int getIndexOfDevice (AudioIODevice* device, const bool asInput) const = 0; + + /** Returns true if two different devices can be used for the input and output. + */ + virtual bool hasSeparateInputsAndOutputs() const = 0; /** Creates one of the devices of this type. The deviceName must be one of the strings returned by getDeviceNames(), and scanForDevices() must have been called before this method is used. */ - virtual AudioIODevice* createDevice (const String& deviceName) = 0; + virtual AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) = 0; + + struct DeviceSetupDetails + { + AudioDeviceManager* manager; + int minNumInputChannels, maxNumInputChannels; + int minNumOutputChannels, maxNumOutputChannels; + bool useStereoPairs; + }; /** Destructor. */ virtual ~AudioIODeviceType(); @@ -33561,52 +33578,41 @@ public: */ XmlElement* createStateXml() const; - /** Returns a list of the audio devices that can be used. - - On Windows, this will include both DSound and ASIO devices if they are available. On - the Mac, it'll be a list of the CoreAudio devices. - - These names are used by setAudioDevice() when changing devices. + /** */ - const StringArray getAvailableAudioDeviceNames() const; + struct AudioDeviceSetup + { + AudioDeviceSetup(); - /** Rescans the list of known audio devices, in case it's changed. */ - void refreshDeviceList() const; + bool operator== (const AudioDeviceSetup& other) const; - /** Sets a flag to indicate that when listing audio device names, it should treat them - as inputs rather than outputs. + /** + */ + String outputDeviceName; + /** + */ + String inputDeviceName; + /** + */ + double sampleRate; + /** + */ + int bufferSize; + /** + */ + BitArray inputChannels; + bool useDefaultInputChannels; + /** + */ + BitArray outputChannels; + bool useDefaultOutputChannels; + }; - This only really applies to DirectSound, where input and output devices are - separate. On ASIO and CoreAudio this makes no difference. - - @see getAvailableAudioDeviceNames + /** */ - void setInputDeviceNamesUsed (const bool useInputNames); + void getAudioDeviceSetup (AudioDeviceSetup& setup); - /** Just adds the list of device names to a combo box. - - The only reason this is in this class is so that it can divide DSound - and ASIO devices into labelled sections, which makes it look much neater. - */ - void addDeviceNamesToComboBox (ComboBox& combo) const; - - /** Changes the audio device that should be used. - - If deviceName is empty or not a valid name returned by getAvailableAudioDeviceNames(), - it will disable the current device. - - @param deviceName the name of the device you want to use (or an empty string to - deselect the current device) - @param blockSizeToUse the samples-per-block you want to use, or 0 to use a default - value - @param sampleRateToUse the target sample-rate, or 0 to use a default that the device - is capable of - @param inputChans which of the audio device's input channels to open - pass 0 to - open as many of the the first ones as are needed for the number - of input channels that the app has requested - @param outputChans which of the audio device's input channels to open - pass 0 to - open as many of the the first ones as are needed for the number - of output channels that the app has requested + /** @param treatAsChosenDevice if this is true and if the device opens correctly, these new settings will be taken as having been explicitly chosen by the user, and the next time createStateXml() is called, these settings @@ -33614,15 +33620,17 @@ public: temporary or default device, and a call to createStateXml() will return either the last settings that were made with treatAsChosenDevice as true, or the last XML settings that were passed into initialise(). - @returns an error message if anything went wrong, or an empty string if it worked ok. */ - const String setAudioDevice (const String& deviceName, - int blockSizeToUse, - double sampleRateToUse, - const BitArray* inputChans, - const BitArray* outputChans, - const bool treatAsChosenDevice); + const String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, + const bool treatAsChosenDevice); + + /** Returns the currently-active audio device. */ + AudioIODevice* getCurrentAudioDevice() const throw() { return currentAudioDevice; } + + const String getCurrentAudioDeviceType() const throw() { return currentDeviceType; } + void setCurrentAudioDeviceType (const String& type, + const bool treatAsChosenDevice); /** Closes the currently-open device. @@ -33641,59 +33649,6 @@ public: */ void restartLastAudioDevice(); - /** Returns the name of the currently selected audio device. - - This will be an empty string if none is active. - */ - const String getCurrentAudioDeviceName() const; - - /** Returns the currently-active audio device. */ - AudioIODevice* getCurrentAudioDevice() const throw() { return currentAudioDevice; } - - /** Returns the set of audio input channels currently being used. - - To select different channels, use setInputChannels(), or call setAudioDevice() to - reopen the device with a different set of channels. - */ - const BitArray getInputChannels() const throw() { return inputChannels; } - - /** Changes the active set of input channels. - - @param newEnabledChannels the set of channels to enable - @param treatAsChosenDevice if this is true and if the device opens correctly, these new - settings will be taken as having been explicitly chosen by the - user, and the next time createStateXml() is called, these settings - will be returned. If it's false, then the device is treated as a - temporary or default device, and a call to createStateXml() will - return either the last settings that were made with treatAsChosenDevice - as true, or the last XML settings that were passed into initialise(). - @see getInputChannels - */ - void setInputChannels (const BitArray& newEnabledChannels, - const bool treatAsChosenDevice); - - /** Returns the set of audio output channels currently being used. - - To select different channels, use setOutputChannels(), or call setAudioDevice() to - reopen the device with a different set of channels. - */ - const BitArray getOutputChannels() const throw() { return outputChannels; } - - /** Changes the active set of output channels. - - @param newEnabledChannels the set of channels to enable - @param treatAsChosenDevice if this is true and if the device opens correctly, these new - settings will be taken as having been explicitly chosen by the - user, and the next time createStateXml() is called, these settings - will be returned. If it's false, then the device is treated as a - temporary or default device, and a call to createStateXml() will - return either the last settings that were made with treatAsChosenDevice - as true, or the last XML settings that were passed into initialise(). - @see getOutputChannels - */ - void setOutputChannels (const BitArray& newEnabledChannels, - const bool treatAsChosenDevice); - /** Gives the manager an audio callback to use. The manager will redirect callbacks from whatever audio device is currently @@ -33751,7 +33706,8 @@ public: /** Removes a listener that was previously registered with addMidiInputCallback(). */ - void removeMidiInputCallback (MidiInputCallback* callback); + void removeMidiInputCallback (const String& midiInputDeviceName, + MidiInputCallback* callback); /** Sets a midi output device to use as the default. @@ -33782,19 +33738,69 @@ public: */ MidiOutput* getDefaultMidiOutput() const throw() { return defaultMidiOutput; } + /** + */ + const OwnedArray & getAvailableDeviceTypes() const throw() { return availableDeviceTypes; } + + /** Creates a list of available types. + + This will add a set of new AudioIODeviceType objects to the specified list, to + represent each available types of device. + + You can override this if your app needs to do something specific, like avoid + using DirectSound devices, etc. + */ + virtual void createAudioDeviceTypes (OwnedArray & types); + + /** 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(); + + /** Turns on level-measuring. + + When enabled, the device manager will measure the peak input level + across all channels, and you can get this level by calling getCurrentInputLevel(). + + This is mainly intended for audio setup UI panels to use to create a mic + level display, so that the user can check that they've selected the right + device. + + A simple filter is used to make the level decay smoothly, but this is + only intended for giving rough feedback, and not for any kind of accurate + measurement. + */ + void enableInputLevelMeasurement (const bool enableMeasurement); + + /** Returns the current input level. + + To use this, you must first enable it by calling enableInputLevelMeasurement(). + + See enableInputLevelMeasurement() for more info. + */ + double getCurrentInputLevel() const; + juce_UseDebuggingNewOperator private: OwnedArray availableDeviceTypes; + OwnedArray lastDeviceTypeConfigs; + AudioDeviceSetup currentSetup; AudioIODevice* currentAudioDevice; AudioIODeviceCallback* currentCallback; int numInputChansNeeded, numOutputChansNeeded; + String currentDeviceType; BitArray inputChannels, outputChannels; XmlElement* lastExplicitSettings; mutable bool listNeedsScanning; - bool useInputNames; + bool useInputNames, inputLevelMeasurementEnabled; + double inputLevel; + AudioSampleBuffer* testSound; + int testSoundPosition; StringArray midiInsFromXml; OwnedArray enabledMidiInputs; @@ -33826,11 +33832,6 @@ private: }; CallbackHandler callbackHandler; - String lastRunningDevice; - int lastRunningBlockSize; - double lastRunningSampleRate; - BitArray lastRunningIns, lastRunningOuts; - friend class CallbackHandler; void audioDeviceIOCallbackInt (const float** inputChannelData, @@ -33849,6 +33850,15 @@ private: void updateXml(); + void createDeviceTypesIfNeeded(); + void scanDevicesIfNeeded(); + void deleteCurrentDevice(); + double chooseBestSampleRate (double preferred) const; + AudioIODeviceType* getCurrentDeviceTypeObject() const; + void insertDefaultDeviceNames (AudioDeviceSetup& setup) const; + + AudioIODeviceType* findType (const String& inputName, const String& outputName); + AudioDeviceManager (const AudioDeviceManager&); const AudioDeviceManager& operator= (const AudioDeviceManager&); }; @@ -49093,8 +49103,8 @@ private: @see DirectoryContentsList, FileTreeComponent */ -class JUCE_API FileListComponent : public DirectoryContentsDisplayComponent, - public ListBox, +class JUCE_API FileListComponent : public ListBox, + public DirectoryContentsDisplayComponent, private ListBoxModel, private ChangeListener { @@ -49454,8 +49464,8 @@ private: @see DirectoryContentsList, FileListComponent */ -class JUCE_API FileTreeComponent : public DirectoryContentsDisplayComponent, - public TreeView +class JUCE_API FileTreeComponent : public TreeView, + public DirectoryContentsDisplayComponent { public: @@ -50416,7 +50426,7 @@ private: #ifndef __JUCE_AUDIODEVICESELECTORCOMPONENT_JUCEHEADER__ #define __JUCE_AUDIODEVICESELECTORCOMPONENT_JUCEHEADER__ -class AudioDeviceSelectorComponentListBox; +class MidiInputSelectorComponentListBox; /** A component containing controls to let the user change the audio settings of @@ -50467,23 +50477,22 @@ public: /** @internal */ void changeListenerCallback (void*); + /** Called by the device-specific displays to create a little level meter that + just displays the current total input levels from the given device manager. + */ + static Component* createSimpleLevelMeterComponent (AudioDeviceManager* managerToDisplay); + juce_UseDebuggingNewOperator private: AudioDeviceManager& deviceManager; - ComboBox* audioDeviceDropDown; + ComboBox* deviceTypeDropDown; + Label* deviceTypeDropDownLabel; + Component* audioDeviceSettingsComp; + String audioDeviceSettingsCompType; const int minOutputChannels, maxOutputChannels, minInputChannels, maxInputChannels; - ComboBox* sampleRateDropDown; - AudioDeviceSelectorComponentListBox* inputChansBox; - Label* inputsLabel; - AudioDeviceSelectorComponentListBox* outputChansBox; - Label* outputsLabel; - Label* sampleRateLabel; - ComboBox* bufferSizeDropDown; - Label* bufferSizeLabel; - Button* launchUIButton; - AudioDeviceSelectorComponentListBox* midiInputsList; + MidiInputSelectorComponentListBox* midiInputsList; Label* midiInputsLabel; ComboBox* midiOutputSelector; Label* midiOutputLabel; @@ -51562,7 +51571,7 @@ public: @see newOpenGLContextCreated() */ - OpenGLContext* getCurrentContext() const throw(); + OpenGLContext* getCurrentContext() const throw() { return context; } /** Makes this component the current openGL context. @@ -53340,23 +53349,24 @@ END_JUCE_NAMESPACE #ifdef _CONSOLE #define START_JUCE_APPLICATION(AppClass) \ - int main (int argc, char* argv[]) \ + int main (int, char* argv[]) \ { \ - return JUCE_NAMESPACE::JUCEApplication::main (argc, argv, new AppClass()); \ + JUCE_NAMESPACE::String commandLineString (JUCE_NAMESPACE::PlatformUtilities::getCurrentCommandLineParams()); \ + return JUCE_NAMESPACE::JUCEApplication::main (commandLineString, new AppClass()); \ } #elif ! defined (_AFXDLL) #ifdef _WINDOWS_ #define START_JUCE_APPLICATION(AppClass) \ - int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR commandLine, int) \ + int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) \ { \ - JUCE_NAMESPACE::String commandLineString (commandLine); \ + JUCE_NAMESPACE::String commandLineString (JUCE_NAMESPACE::PlatformUtilities::getCurrentCommandLineParams()); \ return JUCE_NAMESPACE::JUCEApplication::main (commandLineString, new AppClass()); \ } #else #define START_JUCE_APPLICATION(AppClass) \ - int __stdcall WinMain (int, int, const char* commandLine, int) \ + int __stdcall WinMain (int, int, const char*, int) \ { \ - JUCE_NAMESPACE::String commandLineString (commandLine); \ + JUCE_NAMESPACE::String commandLineString (JUCE_NAMESPACE::PlatformUtilities::getCurrentCommandLineParams()); \ return JUCE_NAMESPACE::JUCEApplication::main (commandLineString, new AppClass()); \ } #endif diff --git a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp index fd0e1be538..0ea123d7b6 100644 --- a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp +++ b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp @@ -36,8 +36,30 @@ BEGIN_JUCE_NAMESPACE #include "juce_AudioDeviceManager.h" #include "../../gui/components/juce_Desktop.h" #include "../../../juce_core/text/juce_LocalisedStrings.h" +#include "../dsp/juce_AudioSampleBuffer.h" +//============================================================================== +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; +} + //============================================================================== AudioDeviceManager::AudioDeviceManager() : currentAudioDevice (0), @@ -47,6 +69,9 @@ AudioDeviceManager::AudioDeviceManager() lastExplicitSettings (0), listNeedsScanning (true), useInputNames (false), + inputLevelMeasurementEnabled (false), + inputLevel (0), + testSound (0), enabledMidiInputs (4), midiCallbacks (4), midiCallbackDevices (4), @@ -55,19 +80,59 @@ AudioDeviceManager::AudioDeviceManager() timeToCpuScale (0) { callbackHandler.owner = this; - - AudioIODeviceType::createDeviceTypes (availableDeviceTypes); } AudioDeviceManager::~AudioDeviceManager() { - stopDevice(); deleteAndZero (currentAudioDevice); deleteAndZero (defaultMidiOutput); delete lastExplicitSettings; + delete testSound; } +//============================================================================== +void AudioDeviceManager::createDeviceTypesIfNeeded() +{ + if (availableDeviceTypes.size() == 0) + { + createAudioDeviceTypes (availableDeviceTypes); + + while (lastDeviceTypeConfigs.size() < availableDeviceTypes.size()) + lastDeviceTypeConfigs.add (new AudioDeviceSetup()); + + if (availableDeviceTypes.size() > 0) + currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); + } +} + +//============================================================================== +extern AudioIODeviceType* juce_createDefaultAudioIODeviceType(); + +#if JUCE_WIN32 && JUCE_ASIO + extern AudioIODeviceType* juce_createASIOAudioIODeviceType(); +#endif + +#if JUCE_WIN32 && JUCE_WDM_AUDIO + extern AudioIODeviceType* juce_createWDMAudioIODeviceType(); +#endif + +void AudioDeviceManager::createAudioDeviceTypes (OwnedArray & list) +{ + AudioIODeviceType* const defaultDeviceType = juce_createDefaultAudioIODeviceType(); + + if (defaultDeviceType != 0) + list.add (defaultDeviceType); + +#if JUCE_WIN32 && JUCE_ASIO + list.add (juce_createASIOAudioIODeviceType()); +#endif + +#if JUCE_WIN32 && JUCE_WDM_AUDIO + list.add (juce_createWDMAudioIODeviceType()); +#endif +} + //============================================================================== const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const int numOutputChannelsNeeded, @@ -75,26 +140,51 @@ const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, const bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName) { - if (listNeedsScanning) - refreshDeviceList(); + scanDevicesIfNeeded(); numInputChansNeeded = numInputChannelsNeeded; numOutputChansNeeded = numOutputChannelsNeeded; if (e != 0 && e->hasTagName (T("DEVICESETUP"))) { + delete lastExplicitSettings; 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; + AudioDeviceSetup setup; - 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)); + if (e->getStringAttribute (T("audioDeviceName")).isNotEmpty()) + { + setup.inputDeviceName = setup.outputDeviceName + = e->getStringAttribute (T("audioDeviceName")); + } + else + { + setup.inputDeviceName = e->getStringAttribute (T("audioInputDeviceName")); + setup.outputDeviceName = e->getStringAttribute (T("audioOutputDeviceName")); + } + + currentDeviceType = e->getStringAttribute (T("deviceType")); + if (currentDeviceType.isEmpty()) + { + AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName); + + if (type != 0) + currentDeviceType = type->getTypeName(); + else if (availableDeviceTypes.size() > 0) + currentDeviceType = availableDeviceTypes[0]->getTypeName(); + } + + setup.bufferSize = e->getIntAttribute (T("audioDeviceBufferSize")); + setup.sampleRate = e->getDoubleAttribute (T("audioDeviceRate")); + + setup.inputChannels.parseString (e->getStringAttribute (T("audioDeviceInChans"), T("11")), 2); + setup.outputChannels.parseString (e->getStringAttribute (T("audioDeviceOutChans"), T("11")), 2); + + setup.useDefaultInputChannels = ! e->hasAttribute (T("audioDeviceInChans")); + setup.useDefaultOutputChannels = ! e->hasAttribute (T("audioDeviceOutChans")); + + error = setAudioDeviceSetup (setup, true); midiInsFromXml.clear(); forEachXmlChildElementWithTagName (*e, c, T("MIDIINPUT")) @@ -115,33 +205,54 @@ const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, } else { - setInputDeviceNamesUsed (numOutputChannelsNeeded == 0); - - String defaultDevice; + AudioDeviceSetup setup; if (preferredDefaultDeviceName.isNotEmpty()) { - for (int i = 0; i < availableDeviceTypes.size(); ++i) + for (int j = availableDeviceTypes.size(); --j >= 0;) { - const StringArray devs (availableDeviceTypes.getUnchecked(i)->getDeviceNames()); + AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); - for (int j = 0; j < devs.size(); ++j) + StringArray outs (type->getDeviceNames (false)); + + int i; + for (i = 0; i < outs.size(); ++i) { - if (devs[j].matchesWildcard (preferredDefaultDeviceName, true)) + if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) { - defaultDevice = devs[j]; + setup.outputDeviceName = outs[i]; + break; + } + } + + StringArray ins (type->getDeviceNames (true)); + + for (i = 0; i < ins.size(); ++i) + { + if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) + { + setup.inputDeviceName = ins[i]; break; } } } } - if (defaultDevice.isEmpty() && availableDeviceTypes [0] != 0) - defaultDevice = availableDeviceTypes[0]->getDefaultDeviceName (numOutputChannelsNeeded == 0, - numInputChannelsNeeded, - numOutputChannelsNeeded); + insertDefaultDeviceNames (setup); + return setAudioDeviceSetup (setup, false); + } +} - return setAudioDevice (defaultDevice, 0, 0, 0, 0, false); +void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const +{ + AudioIODeviceType* type = getCurrentDeviceTypeObject(); + if (type != 0) + { + if (setup.outputDeviceName.isEmpty()) + setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; + + if (setup.inputDeviceName.isEmpty()) + setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; } } @@ -151,144 +262,372 @@ XmlElement* AudioDeviceManager::createStateXml() const } //============================================================================== -const StringArray AudioDeviceManager::getAvailableAudioDeviceNames() const +void AudioDeviceManager::scanDevicesIfNeeded() { 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]; + listNeedsScanning = false; - if (availableDeviceTypes.size() > 1) - combo.addSectionHeading (type->getTypeName() + T(" devices:")); + createDeviceTypesIfNeeded(); - const StringArray names (type->getDeviceNames (useInputNames)); + for (int i = availableDeviceTypes.size(); --i >= 0;) + availableDeviceTypes.getUnchecked(i)->scanForDevices(); + } +} - for (int j = 0; j < names.size(); ++j) - combo.addItem (names[j], ++n); +AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) +{ + scanDevicesIfNeeded(); - combo.addSeparator(); + 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; + } } - combo.addItem (TRANS("<< no audio device >>"), -1); + return 0; } -const String AudioDeviceManager::getCurrentAudioDeviceName() const +void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) { - if (currentAudioDevice != 0) - return currentAudioDevice->getName(); - - return String::empty; + setup = currentSetup; } -const String AudioDeviceManager::setAudioDevice (const String& deviceNameToUse, - int blockSizeToUse, - double sampleRateToUse, - const BitArray* inChans, - const BitArray* outChans, - const bool treatAsChosenDevice) +void AudioDeviceManager::deleteCurrentDevice() +{ + deleteAndZero (currentAudioDevice); + 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) + { + currentDeviceType = type; + + AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); + insertDefaultDeviceNames (s); + + setAudioDeviceSetup (s, treatAsChosenDevice); + + sendChangeMessage (this); + break; + } + } +} + +AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const +{ + for (int i = 0; i < availableDeviceTypes.size(); ++i) + if (availableDeviceTypes[i]->getTypeName() == currentDeviceType) + return availableDeviceTypes[i]; + + return availableDeviceTypes[0]; +} + +const String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, + const bool treatAsChosenDevice) +{ + jassert (&newSetup != ¤tSetup); // this will have no effect + + if (newSetup == currentSetup && currentAudioDevice != 0) + return String::empty; + + if (! (newSetup == currentSetup)) + sendChangeMessage (this); + + stopDevice(); + String error; + AudioIODeviceType* type = getCurrentDeviceTypeObject(); + + if (type == 0 || (newSetup.inputDeviceName.isEmpty() + && newSetup.outputDeviceName.isEmpty())) + { + deleteCurrentDevice(); + + if (treatAsChosenDevice) + updateXml(); + + return String::empty; + } + + if (currentSetup.inputDeviceName != newSetup.inputDeviceName + || currentSetup.outputDeviceName != newSetup.outputDeviceName + || currentAudioDevice == 0) + { + deleteCurrentDevice(); + scanDevicesIfNeeded(); + + if (newSetup.outputDeviceName.isNotEmpty() + && ! type->getDeviceNames (false).contains (newSetup.outputDeviceName)) + { + return "No such device: " + newSetup.outputDeviceName; + } + + if (newSetup.inputDeviceName.isNotEmpty() + && ! type->getDeviceNames (true).contains (newSetup.inputDeviceName)) + { + return "No such device: " + newSetup.outputDeviceName; + } + + currentAudioDevice = type->createDevice (newSetup.outputDeviceName, + newSetup.inputDeviceName); + + if (currentAudioDevice == 0) + error = "Can't open device"; + else + error = currentAudioDevice->getLastError(); + + if (error.isNotEmpty()) + { + deleteCurrentDevice(); + return error; + } + + inputChannels.clear(); + inputChannels.setRange (0, numInputChansNeeded, true); + outputChannels.clear(); + outputChannels.setRange (0, numOutputChansNeeded, true); + } + else + { + if (! newSetup.useDefaultInputChannels) + inputChannels = newSetup.inputChannels; + + if (! newSetup.useDefaultOutputChannels) + outputChannels = newSetup.outputChannels; + } + + currentSetup = newSetup; + + currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); + + 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 != 0); + + if (rate > 0) + { + bool ok = false; + + for (int i = currentAudioDevice->getNumSampleRates(); --i >= 0;) + { + const double sr = currentAudioDevice->getSampleRate (i); + + if (sr == rate) + ok = true; + } + + if (! ok) + rate = 0; + } + + if (rate == 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) + rate = currentAudioDevice->getSampleRate (0); + else + rate = lowestAbove44; + } + + return rate; +} + +/*const String AudioDeviceManager::setAudioDevices (const String& outputDeviceName, + const String& inputDeviceName, + int blockSizeToUse, + double sampleRateToUse, + const BitArray* inChans, + const BitArray* outChans, + const bool treatAsChosenDevice) { stopDevice(); String error; - if (deviceNameToUse.isNotEmpty()) + if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) { - const StringArray devNames (getAvailableAudioDeviceNames()); + const StringArray outputNames (getAvailableAudioOutputDeviceNames()); + int outputIndex = outputNames.indexOf (outputDeviceName); - int index = devNames.indexOf (deviceNameToUse, true); - - if (index >= 0) + if (outputIndex < 0 && outputDeviceName.isNotEmpty()) { - if (currentAudioDevice == 0 - || currentAudioDevice->getLastError().isNotEmpty() - || ! deviceNameToUse.equalsIgnoreCase (currentAudioDevice->getName())) + deleteDevices(); + error << "No such device: " << outputDeviceName; + return error; + } + + const StringArray inputNames (getAvailableAudioInputDeviceNames()); + int inputIndex = inputNames.indexOf (inputDeviceName); + + if (inputIndex < 0 && inputDeviceName.isNotEmpty()) + { + deleteDevices(); + error << "No such device: " << inputDeviceName; + return error; + } + + if (currentAudioInputDevice == 0 + || currentAudioInputDevice->getLastError().isNotEmpty() + || ! inputDeviceName.equalsIgnoreCase (currentAudioInputDevice->getName()) + || currentAudioOutputDevice == 0 + || currentAudioOutputDevice->getLastError().isNotEmpty() + || ! outputDeviceName.equalsIgnoreCase (currentAudioOutputDevice->getName())) + { + // change of device.. + deleteDevices(); + + int n = 0; + + for (int i = 0; i < availableDeviceTypes.size(); ++i) { - // change of device.. - deleteAndZero (currentAudioDevice); + AudioIODeviceType* const type = availableDeviceTypes[i]; + const StringArray names (type->getDeviceNames (true)); - int n = 0; + if (inputIndex >= n && inputIndex < n + names.size()) + { + currentAudioInputDevice = type->createDevice (inputDeviceName); + if (currentAudioInputDevice == 0) + error = "Can't open device: " + inputDeviceName; + else + error = currentAudioInputDevice->getLastError(); + + if (error.isNotEmpty()) + { + deleteDevices(); + return error; + } + + break; + } + + n += names.size(); + } + + //xxx + if (true)//inputDeviceName != outputDeviceName) + { + // Using different input and output devices.. + n = 0; for (int i = 0; i < availableDeviceTypes.size(); ++i) { AudioIODeviceType* const type = availableDeviceTypes[i]; - const StringArray names (type->getDeviceNames (useInputNames)); + const StringArray names (type->getDeviceNames (false)); - if (index >= n && index < n + names.size()) + if (outputIndex >= n && outputIndex < n + names.size()) { - currentAudioDevice = type->createDevice (deviceNameToUse); + currentAudioOutputDevice = type->createDevice (outputDeviceName); + + if (currentAudioOutputDevice == 0) + error = "Can't open device: " + outputDeviceName; + else + error = currentAudioOutputDevice->getLastError(); + + if (error.isNotEmpty()) + { + deleteDevices(); + return error; + } + break; } n += names.size(); } - error = currentAudioDevice->getLastError(); - - if (error.isNotEmpty()) + if (currentAudioInputDevice != 0 && currentAudioOutputDevice != 0) { - deleteAndZero (currentAudioDevice); - return error; + //xxx enable this optim } - inputChannels.clear(); - inputChannels.setRange (0, numInputChansNeeded, true); - outputChannels.clear(); - outputChannels.setRange (0, numOutputChansNeeded, true); + ConglomeratingAudioIODevice* const combiner = new ConglomeratingAudioIODevice(); + combiner->addDevice (currentAudioInputDevice, false, true); + combiner->addDevice (currentAudioOutputDevice, true, false); + + currentAudioDevice = combiner; } - - if (inChans != 0) - inputChannels = *inChans; - - if (outChans != 0) - outputChannels = *outChans; - - error = restartDevice (blockSizeToUse, - sampleRateToUse, - inputChannels, - outputChannels); - - if (error.isNotEmpty()) + else { - deleteAndZero (currentAudioDevice); + // Using a single device for in + out.. + currentAudioOutputDevice = currentAudioInputDevice; + currentAudioDevice = currentAudioInputDevice; } + + inputChannels.clear(); + inputChannels.setRange (0, numInputChansNeeded, true); + outputChannels.clear(); + outputChannels.setRange (0, numOutputChansNeeded, true); } - else - { - deleteAndZero (currentAudioDevice); - error << "No such device: " << deviceNameToUse; - } + + if (inChans != 0) + inputChannels = *inChans; + + if (outChans != 0) + outputChannels = *outChans; + + error = restartDevice (blockSizeToUse, + sampleRateToUse, + inputChannels, + outputChannels); + + if (error.isNotEmpty()) + deleteDevices(); } else { - deleteAndZero (currentAudioDevice); + deleteDevices(); } if (treatAsChosenDevice && error.isEmpty()) @@ -342,7 +681,7 @@ const String AudioDeviceManager::restartDevice (int blockSizeToUse, } const String error (currentAudioDevice->open (inChans, outChans, - sampleRateToUse, blockSizeToUse)); + sampleRateToUse, blockSizeToUse)); if (error.isEmpty()) currentAudioDevice->start (&callbackHandler); @@ -350,7 +689,7 @@ const String AudioDeviceManager::restartDevice (int blockSizeToUse, sendChangeMessage (this); return error; } - +*/ void AudioDeviceManager::stopDevice() { if (currentAudioDevice != 0) @@ -359,25 +698,16 @@ void AudioDeviceManager::stopDevice() void AudioDeviceManager::closeAudioDevice() { - if (currentAudioDevice != 0) - { - lastRunningDevice = currentAudioDevice->getName(); - lastRunningBlockSize = currentAudioDevice->getCurrentBufferSizeSamples(); - lastRunningSampleRate = currentAudioDevice->getCurrentSampleRate(); - lastRunningIns = inputChannels; - lastRunningOuts = outputChannels; - - stopDevice(); - - setAudioDevice (String::empty, 0, 0, 0, 0, false); - } + stopDevice(); + deleteAndZero (currentAudioDevice); } void AudioDeviceManager::restartLastAudioDevice() { if (currentAudioDevice == 0) { - if (lastRunningDevice.isEmpty()) + 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 @@ -386,50 +716,19 @@ void AudioDeviceManager::restartLastAudioDevice() return; } - setAudioDevice (lastRunningDevice, - lastRunningBlockSize, - lastRunningSampleRate, - &lastRunningIns, - &lastRunningOuts, - false); - } -} - -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); + AudioDeviceSetup s (currentSetup); + setAudioDeviceSetup (s, false); } } void AudioDeviceManager::updateXml() { delete lastExplicitSettings; - lastExplicitSettings = new XmlElement (T("DEVICESETUP")); - lastExplicitSettings->setAttribute (T("audioDeviceName"), getCurrentAudioDeviceName()); + lastExplicitSettings->setAttribute (T("deviceType"), currentDeviceType); + lastExplicitSettings->setAttribute (T("audioOutputDeviceName"), currentSetup.outputDeviceName); + lastExplicitSettings->setAttribute (T("audioInputDeviceName"), currentSetup.inputDeviceName); if (currentAudioDevice != 0) { @@ -438,8 +737,11 @@ void AudioDeviceManager::updateXml() 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)); + if (! currentSetup.useDefaultInputChannels) + lastExplicitSettings->setAttribute (T("audioDeviceInChans"), currentSetup.inputChannels.toString (2)); + + if (! currentSetup.useDefaultOutputChannels) + lastExplicitSettings->setAttribute (T("audioDeviceOutChans"), currentSetup.outputChannels.toString (2)); } for (int i = 0; i < enabledMidiInputs.size(); ++i) @@ -497,21 +799,43 @@ void AudioDeviceManager::setAudioCallback (AudioIODeviceCallback* newCallback) } void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, - int totalNumInputChannels, + int numInputChannels, float** outputChannelData, - int totalNumOutputChannels, + int numOutputChannels, int numSamples) { const ScopedLock sl (audioCallbackLock); + if (inputLevelMeasurementEnabled) + { + for (int j = 0; j < numSamples; ++j) + { + float s = 0; + + for (int i = 0; i < numInputChannels; ++i) + s += fabsf (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; + } + } + if (currentCallback != 0) { const double callbackStartTime = Time::getMillisecondCounterHiRes(); currentCallback->audioDeviceIOCallback (inputChannelData, - totalNumInputChannels, + numInputChannels, outputChannelData, - totalNumOutputChannels, + numOutputChannels, numSamples); const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; @@ -520,9 +844,25 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat } else { - for (int i = 0; i < totalNumOutputChannels; ++i) - if (outputChannelData [i] != 0) - zeromem (outputChannelData[i], sizeof (float) * numSamples); + for (int i = 0; i < numOutputChannels; ++i) + zeromem (outputChannelData[i], sizeof (float) * numSamples); + } + + if (testSound != 0) + { + 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()) + { + delete testSound; + testSound = 0; + } } } @@ -605,7 +945,7 @@ bool AudioDeviceManager::isMidiInputEnabled (const String& name) const void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callback) { - removeMidiInputCallback (callback); + removeMidiInputCallback (name, callback); if (name.isEmpty()) { @@ -632,13 +972,24 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, } } -void AudioDeviceManager::removeMidiInputCallback (MidiInputCallback* callback) +void AudioDeviceManager::removeMidiInputCallback (const String& name, + MidiInputCallback* /*callback*/) { const ScopedLock sl (midiCallbackLock); - const int index = midiCallbacks.indexOf (callback); - midiCallbacks.remove (index); - midiCallbackDevices.remove (index); + for (int i = midiCallbacks.size(); --i >= 0;) + { + String devName; + + if (midiCallbackDevices.getUnchecked(i) != 0) + devName = midiCallbackDevices.getUnchecked(i)->getName(); + + if (devName == name) + { + midiCallbacks.remove (i); + midiCallbackDevices.remove (i); + } + } } void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, @@ -678,12 +1029,12 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) //============================================================================== void AudioDeviceManager::CallbackHandler::audioDeviceIOCallback (const float** inputChannelData, - int totalNumInputChannels, + int numInputChannels, float** outputChannelData, - int totalNumOutputChannels, + int numOutputChannels, int numSamples) { - owner->audioDeviceIOCallbackInt (inputChannelData, totalNumInputChannels, outputChannelData, totalNumOutputChannels, numSamples); + owner->audioDeviceIOCallbackInt (inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples); } void AudioDeviceManager::CallbackHandler::audioDeviceAboutToStart (AudioIODevice* device) @@ -701,5 +1052,56 @@ void AudioDeviceManager::CallbackHandler::handleIncomingMidiMessage (MidiInput* owner->handleIncomingMidiMessageInt (source, message); } +//============================================================================== +void AudioDeviceManager::playTestSound() +{ + audioCallbackLock.enter(); + AudioSampleBuffer* oldSound = testSound; + testSound = 0; + audioCallbackLock.exit(); + delete oldSound; + + testSoundPosition = 0; + + if (currentAudioDevice != 0) + { + 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 = MidiMessage::getMidiNoteInHertz (80); + 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) 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 (inputLevelMeasurementEnabled != enableMeasurement) + { + const ScopedLock sl (audioCallbackLock); + inputLevelMeasurementEnabled = enableMeasurement; + inputLevel = 0; + } +} + +double AudioDeviceManager::getCurrentInputLevel() const +{ + jassert (inputLevelMeasurementEnabled); // you need to call enableInputLevelMeasurement() before using this! + return inputLevel; +} + END_JUCE_NAMESPACE diff --git a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.h b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.h index a5700f3458..dfe752939a 100644 --- a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.h +++ b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.h @@ -37,6 +37,7 @@ #include "juce_MidiOutput.h" #include "../../../juce_core/text/juce_XmlElement.h" #include "../../gui/components/controls/juce_ComboBox.h" +#include "../dsp/juce_AudioSampleBuffer.h" //============================================================================== @@ -130,54 +131,42 @@ public: */ XmlElement* createStateXml() const; - //============================================================================== - /** Returns a list of the audio devices that can be used. - - On Windows, this will include both DSound and ASIO devices if they are available. On - the Mac, it'll be a list of the CoreAudio devices. - - These names are used by setAudioDevice() when changing devices. + /** */ - const StringArray getAvailableAudioDeviceNames() const; + struct AudioDeviceSetup + { + AudioDeviceSetup(); - /** Rescans the list of known audio devices, in case it's changed. */ - void refreshDeviceList() const; + bool operator== (const AudioDeviceSetup& other) const; - /** Sets a flag to indicate that when listing audio device names, it should treat them - as inputs rather than outputs. + /** + */ + String outputDeviceName; + /** + */ + String inputDeviceName; + /** + */ + double sampleRate; + /** + */ + int bufferSize; + /** + */ + BitArray inputChannels; + bool useDefaultInputChannels; + /** + */ + BitArray outputChannels; + bool useDefaultOutputChannels; + }; - This only really applies to DirectSound, where input and output devices are - separate. On ASIO and CoreAudio this makes no difference. - - @see getAvailableAudioDeviceNames + /** */ - void setInputDeviceNamesUsed (const bool useInputNames); + void getAudioDeviceSetup (AudioDeviceSetup& setup); - /** Just adds the list of device names to a combo box. - - The only reason this is in this class is so that it can divide DSound - and ASIO devices into labelled sections, which makes it look much neater. - */ - void addDeviceNamesToComboBox (ComboBox& combo) const; - - /** Changes the audio device that should be used. - - If deviceName is empty or not a valid name returned by getAvailableAudioDeviceNames(), - it will disable the current device. - - @param deviceName the name of the device you want to use (or an empty string to - deselect the current device) - @param blockSizeToUse the samples-per-block you want to use, or 0 to use a default - value - @param sampleRateToUse the target sample-rate, or 0 to use a default that the device - is capable of - @param inputChans which of the audio device's input channels to open - pass 0 to - open as many of the the first ones as are needed for the number - of input channels that the app has requested - @param outputChans which of the audio device's input channels to open - pass 0 to - open as many of the the first ones as are needed for the number - of output channels that the app has requested + /** @param treatAsChosenDevice if this is true and if the device opens correctly, these new settings will be taken as having been explicitly chosen by the user, and the next time createStateXml() is called, these settings @@ -185,15 +174,18 @@ public: temporary or default device, and a call to createStateXml() will return either the last settings that were made with treatAsChosenDevice as true, or the last XML settings that were passed into initialise(). - @returns an error message if anything went wrong, or an empty string if it worked ok. */ - const String setAudioDevice (const String& deviceName, - int blockSizeToUse, - double sampleRateToUse, - const BitArray* inputChans, - const BitArray* outputChans, - const bool treatAsChosenDevice); + const String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, + const bool treatAsChosenDevice); + + + /** Returns the currently-active audio device. */ + AudioIODevice* getCurrentAudioDevice() const throw() { return currentAudioDevice; } + + const String getCurrentAudioDeviceType() const throw() { return currentDeviceType; } + void setCurrentAudioDeviceType (const String& type, + const bool treatAsChosenDevice); /** Closes the currently-open device. @@ -212,59 +204,7 @@ public: */ void restartLastAudioDevice(); - /** Returns the name of the currently selected audio device. - - This will be an empty string if none is active. - */ - const String getCurrentAudioDeviceName() const; - - /** Returns the currently-active audio device. */ - AudioIODevice* getCurrentAudioDevice() const throw() { return currentAudioDevice; } - - /** Returns the set of audio input channels currently being used. - - To select different channels, use setInputChannels(), or call setAudioDevice() to - reopen the device with a different set of channels. - */ - const BitArray getInputChannels() const throw() { return inputChannels; } - - /** Changes the active set of input channels. - - @param newEnabledChannels the set of channels to enable - @param treatAsChosenDevice if this is true and if the device opens correctly, these new - settings will be taken as having been explicitly chosen by the - user, and the next time createStateXml() is called, these settings - will be returned. If it's false, then the device is treated as a - temporary or default device, and a call to createStateXml() will - return either the last settings that were made with treatAsChosenDevice - as true, or the last XML settings that were passed into initialise(). - @see getInputChannels - */ - void setInputChannels (const BitArray& newEnabledChannels, - const bool treatAsChosenDevice); - - /** Returns the set of audio output channels currently being used. - - To select different channels, use setOutputChannels(), or call setAudioDevice() to - reopen the device with a different set of channels. - */ - const BitArray getOutputChannels() const throw() { return outputChannels; } - - /** Changes the active set of output channels. - - @param newEnabledChannels the set of channels to enable - @param treatAsChosenDevice if this is true and if the device opens correctly, these new - settings will be taken as having been explicitly chosen by the - user, and the next time createStateXml() is called, these settings - will be returned. If it's false, then the device is treated as a - temporary or default device, and a call to createStateXml() will - return either the last settings that were made with treatAsChosenDevice - as true, or the last XML settings that were passed into initialise(). - @see getOutputChannels - */ - void setOutputChannels (const BitArray& newEnabledChannels, - const bool treatAsChosenDevice); - + //============================================================================== /** Gives the manager an audio callback to use. The manager will redirect callbacks from whatever audio device is currently @@ -274,6 +214,7 @@ public: */ void setAudioCallback (AudioIODeviceCallback* newCallback); + //============================================================================== /** Returns the average proportion of available CPU being spent inside the audio callbacks. Returns a value between 0 and 1.0 @@ -323,7 +264,8 @@ public: /** Removes a listener that was previously registered with addMidiInputCallback(). */ - void removeMidiInputCallback (MidiInputCallback* callback); + void removeMidiInputCallback (const String& midiInputDeviceName, + MidiInputCallback* callback); //============================================================================== /** Sets a midi output device to use as the default. @@ -355,6 +297,51 @@ public: */ MidiOutput* getDefaultMidiOutput() const throw() { return defaultMidiOutput; } + /** + */ + const OwnedArray & getAvailableDeviceTypes() const throw() { return availableDeviceTypes; } + + //============================================================================== + /** Creates a list of available types. + + This will add a set of new AudioIODeviceType objects to the specified list, to + represent each available types of device. + + You can override this if your app needs to do something specific, like avoid + using DirectSound devices, etc. + */ + virtual void createAudioDeviceTypes (OwnedArray & types); + + //============================================================================== + /** 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(); + + /** Turns on level-measuring. + + When enabled, the device manager will measure the peak input level + across all channels, and you can get this level by calling getCurrentInputLevel(). + + This is mainly intended for audio setup UI panels to use to create a mic + level display, so that the user can check that they've selected the right + device. + + A simple filter is used to make the level decay smoothly, but this is + only intended for giving rough feedback, and not for any kind of accurate + measurement. + */ + void enableInputLevelMeasurement (const bool enableMeasurement); + + /** Returns the current input level. + + To use this, you must first enable it by calling enableInputLevelMeasurement(). + + See enableInputLevelMeasurement() for more info. + */ + double getCurrentInputLevel() const; //============================================================================== juce_UseDebuggingNewOperator @@ -362,14 +349,20 @@ public: private: //============================================================================== OwnedArray availableDeviceTypes; + OwnedArray lastDeviceTypeConfigs; + AudioDeviceSetup currentSetup; AudioIODevice* currentAudioDevice; AudioIODeviceCallback* currentCallback; int numInputChansNeeded, numOutputChansNeeded; + String currentDeviceType; BitArray inputChannels, outputChannels; XmlElement* lastExplicitSettings; mutable bool listNeedsScanning; - bool useInputNames; + bool useInputNames, inputLevelMeasurementEnabled; + double inputLevel; + AudioSampleBuffer* testSound; + int testSoundPosition; StringArray midiInsFromXml; OwnedArray enabledMidiInputs; @@ -402,11 +395,6 @@ private: }; CallbackHandler callbackHandler; - String lastRunningDevice; - int lastRunningBlockSize; - double lastRunningSampleRate; - BitArray lastRunningIns, lastRunningOuts; - friend class CallbackHandler; void audioDeviceIOCallbackInt (const float** inputChannelData, @@ -425,6 +413,15 @@ private: void updateXml(); + void createDeviceTypesIfNeeded(); + void scanDevicesIfNeeded(); + void deleteCurrentDevice(); + double chooseBestSampleRate (double preferred) const; + AudioIODeviceType* getCurrentDeviceTypeObject() const; + void insertDefaultDeviceNames (AudioDeviceSetup& setup) const; + + AudioIODeviceType* findType (const String& inputName, const String& outputName); + AudioDeviceManager (const AudioDeviceManager&); const AudioDeviceManager& operator= (const AudioDeviceManager&); }; diff --git a/src/juce_appframework/audio/devices/juce_AudioIODevice.h b/src/juce_appframework/audio/devices/juce_AudioIODevice.h index 00a5fc05f6..0c5607aec8 100644 --- a/src/juce_appframework/audio/devices/juce_AudioIODevice.h +++ b/src/juce_appframework/audio/devices/juce_AudioIODevice.h @@ -307,6 +307,7 @@ public: */ virtual int getInputLatencyInSamples() = 0; + //============================================================================== /** True if this device can show a pop-up control panel for editing its settings. diff --git a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp index 8575617643..2ac7b7d449 100644 --- a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp +++ b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp @@ -46,33 +46,5 @@ AudioIODeviceType::~AudioIODeviceType() { } -//============================================================================== -extern AudioIODeviceType* juce_createDefaultAudioIODeviceType(); - -#if JUCE_WIN32 && JUCE_ASIO - extern AudioIODeviceType* juce_createASIOAudioIODeviceType(); -#endif - -#if JUCE_WIN32 && JUCE_WDM_AUDIO - extern AudioIODeviceType* juce_createWDMAudioIODeviceType(); -#endif - -//============================================================================== -void AudioIODeviceType::createDeviceTypes (OwnedArray & list) -{ - AudioIODeviceType* const defaultDeviceType = juce_createDefaultAudioIODeviceType(); - - if (defaultDeviceType != 0) - list.add (defaultDeviceType); - -#if JUCE_WIN32 && JUCE_ASIO - list.add (juce_createASIOAudioIODeviceType()); -#endif - -#if JUCE_WIN32 && JUCE_WDM_AUDIO - list.add (juce_createWDMAudioIODeviceType()); -#endif -} - END_JUCE_NAMESPACE diff --git a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h index df85bf8836..eb71ac7057 100644 --- a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h +++ b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h @@ -33,7 +33,8 @@ #define __JUCE_AUDIOIODEVICETYPE_JUCEHEADER__ #include "juce_AudioIODevice.h" - +class AudioDeviceManager; +class Component; //============================================================================== /** @@ -71,20 +72,6 @@ class JUCE_API AudioIODeviceType { public: - //============================================================================== - /** Creates a list of available types. - - This will add a set of new AudioIODeviceType objects to the specified list, to - represent each available types of device. - - The objects that are created should be managed by the caller (the OwnedArray - will delete them when the array is itself deleted). - - When created, the objects are uninitialised, so you should call scanForDevices() - on each one before getting its list of devices. - */ - static void createDeviceTypes (OwnedArray & list); - //============================================================================== /** Returns the name of this type of driver that this object manages. @@ -104,35 +91,47 @@ public: The scanForDevices() method must have been called to create this list. - @param preferInputNames only really used by DirectSound where devices are split up - into inputs and outputs, this indicates whether to use - the input or output name to refer to a pair of devices. + @param wantInputNames only really used by DirectSound where devices are split up + into inputs and outputs, this indicates whether to use + the input or output name to refer to a pair of devices. */ - virtual const StringArray getDeviceNames (const bool preferInputNames = false) const = 0; + virtual const StringArray getDeviceNames (const bool wantInputNames = false) const = 0; /** Returns the name of the default device. This will be one of the names from the getDeviceNames() list. - @param preferInputNames only really used by DirectSound where devices are split up - into inputs and outputs, this indicates whether to use - the input or output name to refer to a pair of devices. - @param numInputChannelsNeeded the number of input channels the user is expecting to need - this - may be used to help decide which device would be most suitable - @param numOutputChannelsNeeded the number of output channels the user is expecting to need - this - may be used to help decide which device would be most suitable + @param forInput if true, this means that a default input device should be + returned; if false, it should return the default output */ - virtual const String getDefaultDeviceName (const bool preferInputNames, - const int numInputChannelsNeeded, - const int numOutputChannelsNeeded) const = 0; + virtual int getDefaultDeviceIndex (const bool forInput) const = 0; + + /** Returns the index of a given device in the list of device names. + If asInput is true, it shows the index in the inputs list, otherwise it + looks for it in the outputs list. + */ + virtual int getIndexOfDevice (AudioIODevice* device, const bool asInput) const = 0; + + /** Returns true if two different devices can be used for the input and output. + */ + virtual bool hasSeparateInputsAndOutputs() const = 0; /** Creates one of the devices of this type. The deviceName must be one of the strings returned by getDeviceNames(), and scanForDevices() must have been called before this method is used. */ - virtual AudioIODevice* createDevice (const String& deviceName) = 0; + virtual AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) = 0; + //============================================================================== + struct DeviceSetupDetails + { + AudioDeviceManager* manager; + int minNumInputChannels, maxNumInputChannels; + int minNumOutputChannels, maxNumOutputChannels; + bool useStereoPairs; + }; //============================================================================== /** Destructor. */ diff --git a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp index ca32f4cfd7..7cce044337 100644 --- a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp +++ b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.cpp @@ -42,54 +42,28 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -class AudioDeviceSelectorComponentListBox : public ListBox, - public ListBoxModel +class MidiInputSelectorComponentListBox : public ListBox, + public ListBoxModel { public: - enum BoxType - { - midiInputType, - audioInputType, - audioOutputType - }; - //============================================================================== - AudioDeviceSelectorComponentListBox (AudioDeviceManager& deviceManager_, - const BoxType type_, - const String& noItemsMessage_, - const int minNumber_, - const int maxNumber_) + MidiInputSelectorComponentListBox (AudioDeviceManager& deviceManager_, + const String& noItemsMessage_, + const int minNumber_, + const int maxNumber_) : ListBox (String::empty, 0), deviceManager (deviceManager_), - type (type_), noItemsMessage (noItemsMessage_), minNumber (minNumber_), maxNumber (maxNumber_) { - AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice(); - - if (type_ == midiInputType) - { - items = MidiInput::getDevices(); - } - else if (type_ == audioInputType) - { - items = currentDevice->getInputChannelNames(); - } - else if (type_ == audioOutputType) - { - items = currentDevice->getOutputChannelNames(); - } - else - { - jassertfalse - } + items = MidiInput::getDevices(); setModel (this); setOutlineThickness (1); } - ~AudioDeviceSelectorComponentListBox() + ~MidiInputSelectorComponentListBox() { } @@ -110,20 +84,7 @@ public: .withMultipliedAlpha (0.3f)); const String item (items [row]); - bool enabled = false; - - if (type == midiInputType) - { - enabled = deviceManager.isMidiInputEnabled (item); - } - else if (type == audioInputType) - { - enabled = deviceManager.getInputChannels() [row]; - } - else if (type == audioOutputType) - { - enabled = deviceManager.getOutputChannels() [row]; - } + bool enabled = deviceManager.isMidiInputEnabled (item); const int x = getTickX(); const int tickW = height - height / 4; @@ -183,7 +144,6 @@ public: private: AudioDeviceManager& deviceManager; - const BoxType type; const String noItemsMessage; StringArray items; int minNumber, maxNumber; @@ -192,53 +152,8 @@ private: { if (((unsigned int) row) < (unsigned int) items.size()) { - AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice(); - const String item (items [row]); - - if (type == midiInputType) - { - deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); - } - else - { - jassert (type == audioInputType || type == audioOutputType); - - if (audioDevice != 0) - { - BitArray chans (type == audioInputType ? deviceManager.getInputChannels() - : deviceManager.getOutputChannels()); - - const BitArray oldChans (chans); - - const bool newVal = ! chans[row]; - const int numActive = chans.countNumberOfSetBits(); - - if (! newVal) - { - if (numActive > minNumber) - chans.setBit (row, false); - } - else - { - if (numActive >= maxNumber) - { - const int firstActiveChan = chans.findNextSetBit(); - - chans.setBit (row > firstActiveChan - ? firstActiveChan : chans.getHighestBit(), - false); - } - - chans.setBit (row, true); - } - - if (type == audioInputType) - deviceManager.setInputChannels (chans, true); - else - deviceManager.setOutputChannels (chans, true); - } - } + deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); } } @@ -247,10 +162,715 @@ private: return getRowHeight() + 5; } - AudioDeviceSelectorComponentListBox (const AudioDeviceSelectorComponentListBox&); - const AudioDeviceSelectorComponentListBox& operator= (const AudioDeviceSelectorComponentListBox&); + MidiInputSelectorComponentListBox (const MidiInputSelectorComponentListBox&); + const MidiInputSelectorComponentListBox& operator= (const MidiInputSelectorComponentListBox&); }; + +//============================================================================== +class AudioDeviceSettingsPanel : public Component, + public ComboBoxListener, + public ChangeListener, + public ButtonListener +{ +public: + AudioDeviceSettingsPanel (AudioIODeviceType* type_, + AudioIODeviceType::DeviceSetupDetails& setup_) + : type (type_), + setup (setup_) + { + sampleRateDropDown = 0; + sampleRateLabel = 0; + bufferSizeDropDown = 0; + bufferSizeLabel = 0; + outputDeviceDropDown = 0; + outputDeviceLabel = 0; + inputDeviceDropDown = 0; + inputDeviceLabel = 0; + testButton = 0; + inputLevelMeter = 0; + showUIButton = 0; + inputChanList = 0; + outputChanList = 0; + inputChanLabel = 0; + outputChanLabel = 0; + + type->scanForDevices(); + + setup.manager->addChangeListener (this); + changeListenerCallback (0); + } + + ~AudioDeviceSettingsPanel() + { + setup.manager->removeChangeListener (this); + + deleteAndZero (outputDeviceLabel); + deleteAndZero (inputDeviceLabel); + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAndZero (showUIButton); + deleteAndZero (inputChanLabel); + deleteAndZero (outputChanLabel); + + deleteAllChildren(); + } + + void resized() + { + const int lx = proportionOfWidth (0.35f); + const int w = proportionOfWidth (0.4f); + const int h = 24; + const int space = 6; + const int dh = h + space; + int y = 0; + + if (outputDeviceDropDown != 0) + { + outputDeviceDropDown->setBounds (lx, y, w, h); + testButton->setBounds (proportionOfWidth (0.77f), + outputDeviceDropDown->getY(), + proportionOfWidth (0.18f), + h); + y += dh; + } + + if (inputDeviceDropDown != 0) + { + inputDeviceDropDown->setBounds (lx, y, w, h); + + inputLevelMeter->setBounds (proportionOfWidth (0.77f), + inputDeviceDropDown->getY(), + proportionOfWidth (0.18f), + h); + y += dh; + } + + const int maxBoxHeight = 100;//(getHeight() - y - dh * 2) / numBoxes; + + if (outputChanList != 0) + { + const int bh = outputChanList->getBestHeight (maxBoxHeight); + outputChanList->setBounds (lx, y, proportionOfWidth (0.55f), bh); + y += bh + space; + } + + if (inputChanList != 0) + { + const int bh = inputChanList->getBestHeight (maxBoxHeight); + inputChanList->setBounds (lx, y, proportionOfWidth (0.55f), bh); + y += bh + space; + } + + y += space * 2; + + if (sampleRateDropDown != 0) + { + sampleRateDropDown->setBounds (lx, y, w, h); + y += dh; + } + + if (bufferSizeDropDown != 0) + { + bufferSizeDropDown->setBounds (lx, y, w, h); + y += dh; + } + + if (showUIButton != 0) + { + showUIButton->changeWidthToFitText (h); + showUIButton->setTopLeftPosition (lx, y); + } + } + + void comboBoxChanged (ComboBox* comboBoxThatHasChanged) + { + if (comboBoxThatHasChanged == 0) + return; + + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + String error; + + if (comboBoxThatHasChanged == outputDeviceDropDown + || comboBoxThatHasChanged == inputDeviceDropDown) + { + if (outputDeviceDropDown != 0) + config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String::empty + : outputDeviceDropDown->getText(); + + if (inputDeviceDropDown != 0) + config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String::empty + : inputDeviceDropDown->getText(); + + if (! type->hasSeparateInputsAndOutputs()) + config.inputDeviceName = config.outputDeviceName; + + if (comboBoxThatHasChanged == inputDeviceDropDown) + config.useDefaultInputChannels = true; + else + config.useDefaultOutputChannels = true; + + error = setup.manager->setAudioDeviceSetup (config, true); + + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + + updateControlPanelButton(); + resized(); + } + else if (comboBoxThatHasChanged == sampleRateDropDown) + { + if (sampleRateDropDown->getSelectedId() > 0) + { + config.sampleRate = sampleRateDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + else if (comboBoxThatHasChanged == bufferSizeDropDown) + { + if (bufferSizeDropDown->getSelectedId() > 0) + { + config.bufferSize = bufferSizeDropDown->getSelectedId(); + error = setup.manager->setAudioDeviceSetup (config, true); + } + } + + if (error.isNotEmpty()) + { + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + T("Error when trying to open audio device!"), + error); + } + } + + void buttonClicked (Button* button) + { + if (button == showUIButton) + { + AudioIODevice* const device = setup.manager->getCurrentAudioDevice(); + + if (device != 0 && device->showControlPanel()) + { + setup.manager->closeAudioDevice(); + setup.manager->restartLastAudioDevice(); + getTopLevelComponent()->toFront (true); + } + } + else if (button == testButton) + { + setup.manager->playTestSound(); + } + } + + void updateControlPanelButton() + { + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + deleteAndZero (showUIButton); + + if (currentDevice != 0 && currentDevice->hasControlPanel()) + { + addAndMakeVisible (showUIButton = new TextButton (TRANS ("show this device's control panel"), + TRANS ("opens the device's own control panel"))); + showUIButton->addButtonListener (this); + } + + resized(); + } + + void changeListenerCallback (void*) + { + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + if (setup.maxNumOutputChannels > 0 || ! type->hasSeparateInputsAndOutputs()) + { + if (outputDeviceDropDown == 0) + { + outputDeviceDropDown = new ComboBox (String::empty); + outputDeviceDropDown->addListener (this); + addAndMakeVisible (outputDeviceDropDown); + + outputDeviceLabel = new Label (String::empty, + type->hasSeparateInputsAndOutputs() ? TRANS ("output:") + : TRANS ("device:")); + outputDeviceLabel->attachToComponent (outputDeviceDropDown, true); + + addAndMakeVisible (testButton = new TextButton (TRANS ("Test"))); + testButton->addButtonListener (this); + } + + addNamesToDeviceBox (*outputDeviceDropDown, false); + } + + if (setup.maxNumInputChannels > 0 && type->hasSeparateInputsAndOutputs()) + { + if (inputDeviceDropDown == 0) + { + inputDeviceDropDown = new ComboBox (String::empty); + inputDeviceDropDown->addListener (this); + addAndMakeVisible (inputDeviceDropDown); + + inputDeviceLabel = new Label (String::empty, TRANS ("input:")); + inputDeviceLabel->attachToComponent (inputDeviceDropDown, true); + + addAndMakeVisible (inputLevelMeter = AudioDeviceSelectorComponent::createSimpleLevelMeterComponent (setup.manager)); + } + + addNamesToDeviceBox (*inputDeviceDropDown, true); + } + + updateControlPanelButton(); + showCorrectDeviceName (inputDeviceDropDown, true); + showCorrectDeviceName (outputDeviceDropDown, false); + + if (currentDevice != 0) + { + if (setup.maxNumOutputChannels > 0 + && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size()) + { + if (outputChanList == 0) + { + addAndMakeVisible (outputChanList + = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, + TRANS ("(no audio output channels found)"))); + outputChanLabel = new Label (String::empty, TRANS ("active output channels:")); + outputChanLabel->attachToComponent (outputChanList, true); + } + + outputChanList->updateContent(); + outputChanList->repaint(); + } + else + { + deleteAndZero (outputChanLabel); + deleteAndZero (outputChanList); + } + + if (setup.maxNumInputChannels > 0 + && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size()) + { + if (inputChanList == 0) + { + addAndMakeVisible (inputChanList + = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, + TRANS ("(no audio input channels found)"))); + inputChanLabel = new Label (String::empty, TRANS ("active input channels:")); + inputChanLabel->attachToComponent (inputChanList, true); + } + + inputChanList->updateContent(); + inputChanList->repaint(); + } + else + { + deleteAndZero (inputChanLabel); + deleteAndZero (inputChanList); + } + + // sample rate.. + { + if (sampleRateDropDown == 0) + { + addAndMakeVisible (sampleRateDropDown = new ComboBox (String::empty)); + sampleRateDropDown->addListener (this); + + delete sampleRateLabel; + sampleRateLabel = new Label (String::empty, TRANS ("sample rate:")); + sampleRateLabel->attachToComponent (sampleRateDropDown, true); + } + else + { + sampleRateDropDown->clear(); + sampleRateDropDown->removeListener (this); + } + + const int numRates = currentDevice->getNumSampleRates(); + + for (int i = 0; i < numRates; ++i) + { + const int rate = roundDoubleToInt (currentDevice->getSampleRate (i)); + sampleRateDropDown->addItem (String (rate) + T(" Hz"), rate); + } + + sampleRateDropDown->setSelectedId (roundDoubleToInt (currentDevice->getCurrentSampleRate()), true); + sampleRateDropDown->addListener (this); + } + + // buffer size + { + if (bufferSizeDropDown == 0) + { + addAndMakeVisible (bufferSizeDropDown = new ComboBox (String::empty)); + bufferSizeDropDown->addListener (this); + + delete bufferSizeLabel; + bufferSizeLabel = new Label (String::empty, TRANS ("audio buffer size:")); + bufferSizeLabel->attachToComponent (bufferSizeDropDown, true); + } + else + { + bufferSizeDropDown->clear(); + } + + const int numBufferSizes = currentDevice->getNumBufferSizesAvailable(); + double currentRate = currentDevice->getCurrentSampleRate(); + if (currentRate == 0) + currentRate = 48000.0; + + for (int i = 0; i < numBufferSizes; ++i) + { + const int bs = currentDevice->getBufferSizeSamples (i); + bufferSizeDropDown->addItem (String (bs) + + T(" samples (") + + String (bs * 1000.0 / currentRate, 1) + + T(" ms)"), + bs); + } + + bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), true); + } + } + else + { + jassert (setup.manager->getCurrentAudioDevice() == 0); // not the correct device type! + + deleteAndZero (sampleRateLabel); + deleteAndZero (bufferSizeLabel); + deleteAndZero (sampleRateDropDown); + deleteAndZero (bufferSizeDropDown); + + if (outputDeviceDropDown != 0) + outputDeviceDropDown->setSelectedId (-1, true); + + if (inputDeviceDropDown != 0) + inputDeviceDropDown->setSelectedId (-1, true); + } + + resized(); + setSize (getWidth(), getLowestY() + 4); + } + +private: + AudioIODeviceType* const type; + const AudioIODeviceType::DeviceSetupDetails setup; + + ComboBox* outputDeviceDropDown; + ComboBox* inputDeviceDropDown; + ComboBox* sampleRateDropDown; + ComboBox* bufferSizeDropDown; + Label* outputDeviceLabel; + Label* inputDeviceLabel; + Label* sampleRateLabel; + Label* bufferSizeLabel; + Label* inputChanLabel; + Label* outputChanLabel; + TextButton* testButton; + Component* inputLevelMeter; + TextButton* showUIButton; + + void showCorrectDeviceName (ComboBox* const box, const bool isInput) + { + if (box != 0) + { + AudioIODevice* const currentDevice = dynamic_cast (setup.manager->getCurrentAudioDevice()); + + const int index = type->getIndexOfDevice (currentDevice, isInput); + + box->setSelectedId (index + 1, true); + + if (! isInput) + testButton->setEnabled (index >= 0); + } + } + + void addNamesToDeviceBox (ComboBox& combo, bool isInputs) + { + const StringArray devs (type->getDeviceNames (isInputs)); + + combo.clear(); + + for (int i = 0; i < devs.size(); ++i) + combo.addItem (devs[i], i + 1); + + combo.addItem (TRANS("<< none >>"), -1); + combo.setSelectedId (-1, true); + } + + int getLowestY() const + { + int y = 0; + + for (int i = getNumChildComponents(); --i >= 0;) + y = jmax (y, getChildComponent (i)->getBottom()); + + return y; + } + + //============================================================================== + class ChannelSelectorListBox : public ListBox, + public ListBoxModel + { + public: + enum BoxType + { + audioInputType, + audioOutputType + }; + + //============================================================================== + ChannelSelectorListBox (const AudioIODeviceType::DeviceSetupDetails& setup_, + const BoxType type_, + const String& noItemsMessage_) + : ListBox (String::empty, 0), + setup (setup_), + type (type_), + noItemsMessage (noItemsMessage_) + { + refresh(); + setModel (this); + setOutlineThickness (1); + } + + ~ChannelSelectorListBox() + { + } + + void refresh() + { + items.clear(); + + AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice(); + + if (currentDevice != 0) + { + if (type == audioInputType) + items = currentDevice->getInputChannelNames(); + else if (type == audioOutputType) + items = currentDevice->getOutputChannelNames(); + + if (setup.useStereoPairs) + { + StringArray pairs; + + for (int i = 0; i < items.size(); i += 2) + { + String name (items[i]); + String name2 (items[i + 1]); + + String commonBit; + + for (int j = 0; j < name.length(); ++j) + if (name.substring (0, j).equalsIgnoreCase (name2.substring (0, j))) + commonBit = name.substring (0, j); + + pairs.add (name.trim() + + " + " + + name2.substring (commonBit.length()).trim()); + } + + items = pairs; + } + } + + updateContent(); + repaint(); + } + + int getNumRows() + { + return items.size(); + } + + void paintListBoxItem (int row, + Graphics& g, + int width, int height, + bool rowIsSelected) + { + if (((unsigned int) row) < (unsigned int) items.size()) + { + if (rowIsSelected) + g.fillAll (findColour (TextEditor::highlightColourId) + .withMultipliedAlpha (0.3f)); + + const String item (items [row]); + bool enabled = false; + + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + + if (setup.useStereoPairs) + { + if (type == audioInputType) + enabled = config.inputChannels [row * 2] || config.inputChannels [row * 2 + 1]; + else if (type == audioOutputType) + enabled = config.outputChannels [row * 2] || config.outputChannels [row * 2 + 1]; + } + else + { + if (type == audioInputType) + enabled = config.inputChannels [row]; + else if (type == audioOutputType) + enabled = config.outputChannels [row]; + } + + const int x = getTickX(); + const int tickW = height - height / 4; + + getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW, + enabled, true, true, false); + + g.setFont (height * 0.6f); + g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f)); + g.drawText (item, x, 0, width - x - 2, height, Justification::centredLeft, true); + } + } + + void listBoxItemClicked (int row, const MouseEvent& e) + { + selectRow (row); + + if (e.x < getTickX()) + flipEnablement (row); + } + + void listBoxItemDoubleClicked (int row, const MouseEvent&) + { + flipEnablement (row); + } + + void returnKeyPressed (int row) + { + flipEnablement (row); + } + + void paint (Graphics& g) + { + ListBox::paint (g); + + if (items.size() == 0) + { + g.setColour (Colours::grey); + g.setFont (13.0f); + g.drawText (noItemsMessage, + 0, 0, getWidth(), getHeight() / 2, + Justification::centred, true); + } + } + + int getBestHeight (int maxHeight) + { + return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()), + getNumRows()) + + getOutlineThickness() * 2; + } + + //============================================================================== + juce_UseDebuggingNewOperator + + private: + const AudioIODeviceType::DeviceSetupDetails setup; + const BoxType type; + const String noItemsMessage; + StringArray items; + + void flipEnablement (const int row) + { + jassert (type == audioInputType || type == audioOutputType); + + if (((unsigned int) row) < (unsigned int) items.size()) + { + AudioDeviceManager::AudioDeviceSetup config; + setup.manager->getAudioDeviceSetup (config); + + if (setup.useStereoPairs) + { + BitArray bits; + BitArray& original = (type == audioInputType ? config.inputChannels + : config.outputChannels); + + int i; + for (i = 0; i < 256; i += 2) + bits.setBit (i / 2, original [i] || original [i + 1]); + + if (type == audioInputType) + { + config.useDefaultInputChannels = false; + flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2); + } + else + { + config.useDefaultOutputChannels = false; + flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2); + } + + for (i = 0; i < 256; ++i) + original.setBit (i, bits [i / 2]); + } + else + { + if (type == audioInputType) + { + config.useDefaultInputChannels = false; + flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels); + } + else + { + config.useDefaultOutputChannels = false; + flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels); + } + } + + String error (setup.manager->setAudioDeviceSetup (config, true)); + + if (! error.isEmpty()) + { + //xxx + } + } + } + + static void flipBit (BitArray& chans, int index, int minNumber, int maxNumber) + { + const int numActive = chans.countNumberOfSetBits(); + + if (chans [index]) + { + if (numActive > minNumber) + chans.setBit (index, false); + } + else + { + if (numActive >= maxNumber) + { + const int firstActiveChan = chans.findNextSetBit(); + + chans.setBit (index > firstActiveChan + ? firstActiveChan : chans.getHighestBit(), + false); + } + + chans.setBit (index, true); + } + } + + int getTickX() const throw() + { + return getRowHeight() + 5; + } + + ChannelSelectorListBox (const ChannelSelectorListBox&); + const ChannelSelectorListBox& operator= (const ChannelSelectorListBox&); + }; + + ChannelSelectorListBox* inputChanList; + ChannelSelectorListBox* outputChanList; + + AudioDeviceSettingsPanel (const AudioDeviceSettingsPanel&); + const AudioDeviceSettingsPanel& operator= (const AudioDeviceSettingsPanel&); +}; + + //============================================================================== AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& deviceManager_, const int minInputChannels_, @@ -264,41 +884,40 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& maxOutputChannels (maxOutputChannels_), minInputChannels (minInputChannels_), maxInputChannels (maxInputChannels_), - sampleRateDropDown (0), - inputChansBox (0), - inputsLabel (0), - outputChansBox (0), - outputsLabel (0), - sampleRateLabel (0), - bufferSizeDropDown (0), - bufferSizeLabel (0), - launchUIButton (0) + deviceTypeDropDown (0), + deviceTypeDropDownLabel (0), + audioDeviceSettingsComp (0) { jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels); jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels); - audioDeviceDropDown = new ComboBox ("device"); - deviceManager_.addDeviceNamesToComboBox (*audioDeviceDropDown); - audioDeviceDropDown->setSelectedId (-1, true); + if (deviceManager_.getAvailableDeviceTypes().size() > 1) + { + deviceTypeDropDown = new ComboBox (String::empty); - if (deviceManager_.getCurrentAudioDeviceName().isNotEmpty()) - audioDeviceDropDown->setText (deviceManager_.getCurrentAudioDeviceName(), true); + for (int i = 0; i < deviceManager_.getAvailableDeviceTypes().size(); ++i) + { + deviceTypeDropDown + ->addItem (deviceManager_.getAvailableDeviceTypes().getUnchecked(i)->getTypeName(), + i + 1); + } - audioDeviceDropDown->addListener (this); - addAndMakeVisible (audioDeviceDropDown); + addAndMakeVisible (deviceTypeDropDown); + deviceTypeDropDown->addListener (this); - Label* label = new Label ("l1", TRANS ("audio device:")); - label->attachToComponent (audioDeviceDropDown, true); + deviceTypeDropDownLabel = new Label (String::empty, TRANS ("audio device type:")); + deviceTypeDropDownLabel->setJustificationType (Justification::centredRight); + deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown, true); + } if (showMidiInputOptions) { addAndMakeVisible (midiInputsList - = new AudioDeviceSelectorComponentListBox (deviceManager, - AudioDeviceSelectorComponentListBox::midiInputType, - TRANS("(no midi inputs available)"), - 0, 0)); + = new MidiInputSelectorComponentListBox (deviceManager, + TRANS("(no midi inputs available)"), + 0, 0)); - midiInputsLabel = new Label ("lm", TRANS ("active midi inputs:")); + midiInputsLabel = new Label (String::empty, TRANS ("active midi inputs:")); midiInputsLabel->setJustificationType (Justification::topRight); midiInputsLabel->attachToComponent (midiInputsList, true); } @@ -335,42 +954,26 @@ AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent() void AudioDeviceSelectorComponent::resized() { const int lx = proportionOfWidth (0.35f); - const int w = proportionOfWidth (0.55f); + const int w = proportionOfWidth (0.4f); const int h = 24; const int space = 6; const int dh = h + space; int y = 15; - audioDeviceDropDown->setBounds (lx, y, w, h); - y += dh; - - if (sampleRateDropDown != 0) + if (deviceTypeDropDown != 0) { - sampleRateDropDown->setBounds (lx, y, w, h); - y += dh; + deviceTypeDropDown->setBounds (lx, y, proportionOfWidth (0.3f), h); + y += dh + space * 2; } - if (bufferSizeDropDown != 0) + if (audioDeviceSettingsComp != 0) { - bufferSizeDropDown->setBounds (lx, y, w, h); - y += dh; - } - - if (launchUIButton != 0) - { - launchUIButton->setBounds (lx, y, 150, h); - ((TextButton*) launchUIButton)->changeWidthToFitText(); - y += dh; + audioDeviceSettingsComp->setBounds (0, y, getWidth(), audioDeviceSettingsComp->getHeight()); + y += audioDeviceSettingsComp->getHeight() + space; } VoidArray boxes; - if (outputChansBox != 0) - boxes.add (outputChansBox); - - if (inputChansBox != 0) - boxes.add (inputChansBox); - if (midiInputsList != 0) boxes.add (midiInputsList); @@ -378,7 +981,7 @@ void AudioDeviceSelectorComponent::resized() for (int i = 0; i < boxes.size(); ++i) { - AudioDeviceSelectorComponentListBox* const box = (AudioDeviceSelectorComponentListBox*) boxes.getUnchecked (i); + MidiInputSelectorComponentListBox* const box = (MidiInputSelectorComponentListBox*) boxes.getUnchecked (i); const int bh = box->getBestHeight (jmin (h * 8, boxSpace / boxes.size()) - space); box->setBounds (lx, y, w, bh); @@ -395,13 +998,8 @@ void AudioDeviceSelectorComponent::buttonClicked (Button*) if (device != 0 && device->hasControlPanel()) { - const String lastDevice (device->getName()); - if (device->showControlPanel()) - { - deviceManager.setAudioDevice (String::empty, 0, 0, 0, 0, false); - deviceManager.setAudioDevice (lastDevice, 0, 0, 0, 0, false); - } + deviceManager.restartLastAudioDevice(); getTopLevelComponent()->toFront (true); } @@ -409,18 +1007,28 @@ void AudioDeviceSelectorComponent::buttonClicked (Button*) void AudioDeviceSelectorComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged) { - AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice(); +// AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice(); - if (comboBoxThatHasChanged == audioDeviceDropDown) + if (comboBoxThatHasChanged == deviceTypeDropDown) { - if (audioDeviceDropDown->getSelectedId() < 0) + AudioIODeviceType* const type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1]; + + if (type != 0) { - deviceManager.setAudioDevice (String::empty, 0, 0, 0, 0, true); + deleteAndZero (audioDeviceSettingsComp); + + deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true); + + changeListenerCallback (0); // needed in case the type hasn't actally changed + } + +/* if (outputDeviceDropDown->getSelectedId() < 0) + { + deviceManager.setAudioDevices (String::empty, deviceManager.getCurrentAudioInputDeviceName(), 0, 0, 0, 0, true); } else { - String error (deviceManager.setAudioDevice (audioDeviceDropDown->getText(), - 0, 0, 0, 0, true)); + String error (deviceManager.setAudioDevices (deviceName, deviceManager.getCurrentAudioInputDeviceName(), 0, 0, 0, 0, true)); if (error.isNotEmpty()) { @@ -432,52 +1040,96 @@ void AudioDeviceSelectorComponent::comboBoxChanged (ComboBox* comboBoxThatHasCha // is removed, and this also buggers up our attempt at opening an output // device, so this is a workaround that doesn't fail in that case. BitArray noInputs; - error = deviceManager.setAudioDevice (audioDeviceDropDown->getText(), - 0, 0, &noInputs, 0, false); + error = deviceManager.setAudioDevices (deviceName, deviceManager.getCurrentAudioInputDeviceName(), 0, 0, &noInputs, 0, false); } #endif if (error.isNotEmpty()) AlertWindow::showMessageBox (AlertWindow::WarningIcon, T("Error while opening \"") - + audioDeviceDropDown->getText() + + deviceName + T("\""), error); } } - if (deviceManager.getCurrentAudioDeviceName().isNotEmpty()) - audioDeviceDropDown->setText (deviceManager.getCurrentAudioDeviceName(), true); + deviceName = deviceManager.getCurrentAudioOutputDeviceName(); + + if (deviceName.isNotEmpty()) + outputDeviceDropDown->setText (deviceName, true); else - audioDeviceDropDown->setSelectedId (-1, true); + outputDeviceDropDown->setSelectedId (-1, true); + } + else if (comboBoxThatHasChanged == inputDeviceDropDown) + { + String deviceName (inputDeviceDropDown->getText()); + + if (inputDeviceDropDown->getSelectedId() < 0) + { + deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), String::empty, 0, 0, 0, 0, true); + } + else + { + String error (deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), deviceName, 0, 0, 0, 0, true)); + + if (error.isNotEmpty()) + { +#if JUCE_WIN32 + if (deviceManager.getInputChannels().countNumberOfSetBits() > 0 + && deviceManager.getOutputChannels().countNumberOfSetBits() > 0) + { + // in DSound, some machines lose their primary input device when a mic + // is removed, and this also buggers up our attempt at opening an output + // device, so this is a workaround that doesn't fail in that case. + BitArray noInputs; + error = deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), deviceName, 0, 0, &noInputs, 0, false); + } +#endif + if (error.isNotEmpty()) + AlertWindow::showMessageBox (AlertWindow::WarningIcon, + T("Error while opening \"") + + deviceName + + T("\""), + error); + } + } + + deviceName = deviceManager.getCurrentAudioOutputDeviceName(); + + if (deviceName.isNotEmpty()) + inputDeviceDropDown->setText (deviceName, true); + else + inputDeviceDropDown->setSelectedId (-1, true);*/ } else if (comboBoxThatHasChanged == midiOutputSelector) { deviceManager.setDefaultMidiOutput (midiOutputSelector->getText()); } - else if (audioDevice != 0) + /*else if (audioDevice != 0) { if (bufferSizeDropDown != 0 && comboBoxThatHasChanged == bufferSizeDropDown) { if (bufferSizeDropDown->getSelectedId() > 0) - deviceManager.setAudioDevice (audioDevice->getName(), - bufferSizeDropDown->getSelectedId(), - audioDevice->getCurrentSampleRate(), - 0, 0, true); + deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), + deviceManager.getCurrentAudioInputDeviceName(), + bufferSizeDropDown->getSelectedId(), + audioDevice->getCurrentSampleRate(), + 0, 0, true); } else if (sampleRateDropDown != 0 && comboBoxThatHasChanged == sampleRateDropDown) { if (sampleRateDropDown->getSelectedId() > 0) - deviceManager.setAudioDevice (audioDevice->getName(), - audioDevice->getCurrentBufferSizeSamples(), - sampleRateDropDown->getSelectedId(), - 0, 0, true); + deviceManager.setAudioDevices (deviceManager.getCurrentAudioOutputDeviceName(), + deviceManager.getCurrentAudioInputDeviceName(), + audioDevice->getCurrentBufferSizeSamples(), + sampleRateDropDown->getSelectedId(), + 0, 0, true); } - } + }*/ } void AudioDeviceSelectorComponent::changeListenerCallback (void*) { - deleteAndZero (sampleRateDropDown); + /*deleteAndZero (sampleRateDropDown); deleteAndZero (inputChansBox); deleteAndZero (inputsLabel); deleteAndZero (outputChansBox); @@ -485,13 +1137,57 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) deleteAndZero (sampleRateLabel); deleteAndZero (bufferSizeDropDown); deleteAndZero (bufferSizeLabel); - deleteAndZero (launchUIButton); + deleteAndZero (launchUIButton);*/ - AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice(); + if (deviceTypeDropDown != 0) + { + deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), false); + } + + if (audioDeviceSettingsComp == 0 + || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType()) + { + audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType(); + + deleteAndZero (audioDeviceSettingsComp); + + AudioIODeviceType* const type + = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == 0 + ? 0 : deviceTypeDropDown->getSelectedId() - 1]; + + if (type != 0) + { + AudioIODeviceType::DeviceSetupDetails details; + details.manager = &deviceManager; + details.minNumInputChannels = minInputChannels; + details.maxNumInputChannels = maxInputChannels; + details.minNumOutputChannels = minOutputChannels; + details.maxNumOutputChannels = maxOutputChannels; + details.useStereoPairs = true; + + audioDeviceSettingsComp = new AudioDeviceSettingsPanel (type, details); + + if (audioDeviceSettingsComp != 0) + { + addAndMakeVisible (audioDeviceSettingsComp); + audioDeviceSettingsComp->resized(); + } + } + } + +/* AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice(); if (currentDevice != 0) { - audioDeviceDropDown->setText (currentDevice->getName(), true); + if (deviceManager.getCurrentAudioOutputDevice() == 0) + outputDeviceDropDown->setSelectedId (-1, true); + else + outputDeviceDropDown->setText (deviceManager.getCurrentAudioOutputDeviceName(), true); + + if (deviceManager.getCurrentAudioInputDevice() == 0) + inputDeviceDropDown->setSelectedId (-1, true); + else + inputDeviceDropDown->setText (deviceManager.getCurrentAudioInputDeviceName(), true); // sample rate addAndMakeVisible (sampleRateDropDown = new ComboBox ("samplerate")); @@ -567,8 +1263,9 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) } else { - audioDeviceDropDown->setSelectedId (-1, true); - } + outputDeviceDropDown->setSelectedId (-1, true); + inputDeviceDropDown->setSelectedId (-1, true); + }*/ if (midiInputsList != 0) { @@ -582,7 +1279,7 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) const StringArray midiOuts (MidiOutput::getDevices()); - midiOutputSelector->addItem (TRANS("<< no audio device >>"), -1); + midiOutputSelector->addItem (TRANS("<< none >>"), -1); midiOutputSelector->addSeparator(); for (int i = 0; i < midiOuts.size(); ++i) @@ -599,4 +1296,68 @@ void AudioDeviceSelectorComponent::changeListenerCallback (void*) resized(); } + +//============================================================================== +class SimpleDeviceManagerInputLevelMeter : public Component, + public Timer +{ +public: + SimpleDeviceManagerInputLevelMeter (AudioDeviceManager* const manager_) + : manager (manager_), + totalBlocks (7) + { + numBlocks = 0; + startTimer (50); + manager->enableInputLevelMeasurement (true); + } + + ~SimpleDeviceManagerInputLevelMeter() + { + manager->enableInputLevelMeasurement (false); + } + + void timerCallback() + { + const int newNumBlocks = roundDoubleToInt (manager->getCurrentInputLevel() * totalBlocks); + if (newNumBlocks != numBlocks) + { + numBlocks = newNumBlocks; + repaint(); + } + } + + void paint (Graphics& g) + { + g.setColour (Colours::white.withAlpha (0.8f)); + g.fillRoundedRectangle (0.0f, 0.0f, (float) getWidth(), (float) getHeight(), 3.0f); + g.setColour (Colours::black.withAlpha (0.2f)); + g.drawRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 3.0f, 1.0f); + + const float w = (getWidth() - 6.0f) / (float) totalBlocks; + + for (int i = 0; i < totalBlocks; ++i) + { + if (i >= numBlocks) + g.setColour (Colours::lightblue.withAlpha (0.6f)); + else + g.setColour (i < totalBlocks - 1 ? Colours::blue.withAlpha (0.5f) + : Colours::red); + + g.fillRoundedRectangle (3.0f + i * w + w * 0.1f, 3.0f, w * 0.8f, getHeight() - 6.0f, w * 0.4f); + } + } + +private: + AudioDeviceManager* const manager; + const int totalBlocks; + int numBlocks; +}; + + +Component* AudioDeviceSelectorComponent::createSimpleLevelMeterComponent (AudioDeviceManager* managerToDisplay) +{ + return new SimpleDeviceManagerInputLevelMeter (managerToDisplay); +} + + END_JUCE_NAMESPACE diff --git a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h index aa57f35886..14f72e7b54 100644 --- a/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h +++ b/src/juce_appframework/gui/components/special/juce_AudioDeviceSelectorComponent.h @@ -35,7 +35,7 @@ #include "../controls/juce_ComboBox.h" #include "../controls/juce_ListBox.h" #include "../../../audio/devices/juce_AudioDeviceManager.h" -class AudioDeviceSelectorComponentListBox; +class MidiInputSelectorComponentListBox; //============================================================================== /** @@ -89,24 +89,24 @@ public: /** @internal */ void changeListenerCallback (void*); + //============================================================================== + /** Called by the device-specific displays to create a little level meter that + just displays the current total input levels from the given device manager. + */ + static Component* createSimpleLevelMeterComponent (AudioDeviceManager* managerToDisplay); + //============================================================================== juce_UseDebuggingNewOperator private: AudioDeviceManager& deviceManager; - ComboBox* audioDeviceDropDown; + ComboBox* deviceTypeDropDown; + Label* deviceTypeDropDownLabel; + Component* audioDeviceSettingsComp; + String audioDeviceSettingsCompType; const int minOutputChannels, maxOutputChannels, minInputChannels, maxInputChannels; - ComboBox* sampleRateDropDown; - AudioDeviceSelectorComponentListBox* inputChansBox; - Label* inputsLabel; - AudioDeviceSelectorComponentListBox* outputChansBox; - Label* outputsLabel; - Label* sampleRateLabel; - ComboBox* bufferSizeDropDown; - Label* bufferSizeLabel; - Button* launchUIButton; - AudioDeviceSelectorComponentListBox* midiInputsList; + MidiInputSelectorComponentListBox* midiInputsList; Label* midiInputsLabel; ComboBox* midiOutputSelector; Label* midiOutputLabel;