/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { struct SimpleDeviceManagerInputLevelMeter : public Component, public Timer { SimpleDeviceManagerInputLevelMeter (AudioDeviceManager& m) : manager (m) { startTimerHz (20); inputLevelGetter = manager.getInputLevelGetter(); } ~SimpleDeviceManagerInputLevelMeter() override { } void timerCallback() override { if (isShowing()) { auto newLevel = (float) inputLevelGetter->getCurrentLevel(); if (std::abs (level - newLevel) > 0.005f) { level = newLevel; repaint(); } } else { level = 0; } } void paint (Graphics& g) override { // (add a bit of a skew to make the level more obvious) getLookAndFeel().drawLevelMeter (g, getWidth(), getHeight(), (float) std::exp (std::log (level) / 3.0)); } AudioDeviceManager& manager; AudioDeviceManager::LevelMeter::Ptr inputLevelGetter; float level = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleDeviceManagerInputLevelMeter) }; //============================================================================== class AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox : public ListBox, private ListBoxModel { public: MidiInputSelectorComponentListBox (AudioDeviceManager& dm, const String& noItems) : ListBox ({}, nullptr), deviceManager (dm), noItemsMessage (noItems) { updateDevices(); setModel (this); setOutlineThickness (1); } void updateDevices() { items = MidiInput::getDevices(); } int getNumRows() override { return items.size(); } void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override { if (isPositiveAndBelow (row, items.size())) { if (rowIsSelected) g.fillAll (findColour (TextEditor::highlightColourId) .withMultipliedAlpha (0.3f)); auto item = items[row]; bool enabled = deviceManager.isMidiInputEnabled (item); auto x = getTickX(); auto tickW = height * 0.75f; 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 + 5, 0, width - x - 5, height, Justification::centredLeft, true); } } void listBoxItemClicked (int row, const MouseEvent& e) override { selectRow (row); if (e.x < getTickX()) flipEnablement (row); } void listBoxItemDoubleClicked (int row, const MouseEvent&) override { flipEnablement (row); } void returnKeyPressed (int row) override { flipEnablement (row); } void paint (Graphics& g) override { ListBox::paint (g); if (items.isEmpty()) { g.setColour (Colours::grey); g.setFont (0.5f * getRowHeight()); g.drawText (noItemsMessage, 0, 0, getWidth(), getHeight() / 2, Justification::centred, true); } } int getBestHeight (int preferredHeight) { auto extra = getOutlineThickness() * 2; return jmax (getRowHeight() * 2 + extra, jmin (getRowHeight() * getNumRows() + extra, preferredHeight)); } private: //============================================================================== AudioDeviceManager& deviceManager; const String noItemsMessage; StringArray items; void flipEnablement (const int row) { if (isPositiveAndBelow (row, items.size())) { auto item = items[row]; deviceManager.setMidiInputEnabled (item, ! deviceManager.isMidiInputEnabled (item)); } } int getTickX() const { return getRowHeight(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputSelectorComponentListBox) }; //============================================================================== struct AudioDeviceSetupDetails { AudioDeviceManager* manager; int minNumInputChannels, maxNumInputChannels; int minNumOutputChannels, maxNumOutputChannels; bool useStereoPairs; }; static String getNoDeviceString() { return "<< " + TRANS("none") + " >>"; } //============================================================================== class AudioDeviceSettingsPanel : public Component, private ChangeListener { public: AudioDeviceSettingsPanel (AudioIODeviceType& t, AudioDeviceSetupDetails& setupDetails, const bool hideAdvancedOptionsWithButton) : type (t), setup (setupDetails) { if (hideAdvancedOptionsWithButton) { showAdvancedSettingsButton.reset (new TextButton (TRANS("Show advanced settings..."))); addAndMakeVisible (showAdvancedSettingsButton.get()); showAdvancedSettingsButton->onClick = [this] { showAdvanced(); }; } type.scanForDevices(); setup.manager->addChangeListener (this); } ~AudioDeviceSettingsPanel() override { setup.manager->removeChangeListener (this); } void resized() override { if (auto* parent = findParentComponentOfClass()) { Rectangle r (proportionOfWidth (0.35f), 0, proportionOfWidth (0.6f), 3000); const int maxListBoxHeight = 100; const int h = parent->getItemHeight(); const int space = h / 4; if (outputDeviceDropDown != nullptr) { auto row = r.removeFromTop (h); if (testButton != nullptr) { testButton->changeWidthToFitText (h); testButton->setBounds (row.removeFromRight (testButton->getWidth())); row.removeFromRight (space); } outputDeviceDropDown->setBounds (row); r.removeFromTop (space); } if (inputDeviceDropDown != nullptr) { auto row = r.removeFromTop (h); inputLevelMeter->setBounds (row.removeFromRight (testButton != nullptr ? testButton->getWidth() : row.getWidth() / 6)); row.removeFromRight (space); inputDeviceDropDown->setBounds (row); r.removeFromTop (space); } if (outputChanList != nullptr) { outputChanList->setRowHeight (jmin (22, h)); outputChanList->setBounds (r.removeFromTop (outputChanList->getBestHeight (maxListBoxHeight))); outputChanLabel->setBounds (0, outputChanList->getBounds().getCentreY() - h / 2, r.getX(), h); r.removeFromTop (space); } if (inputChanList != nullptr) { inputChanList->setRowHeight (jmin (22, h)); inputChanList->setBounds (r.removeFromTop (inputChanList->getBestHeight (maxListBoxHeight))); inputChanLabel->setBounds (0, inputChanList->getBounds().getCentreY() - h / 2, r.getX(), h); r.removeFromTop (space); } r.removeFromTop (space * 2); if (showAdvancedSettingsButton != nullptr) { showAdvancedSettingsButton->setBounds (r.withHeight (h)); showAdvancedSettingsButton->changeWidthToFitText(); } const bool advancedSettingsVisible = showAdvancedSettingsButton == nullptr || ! showAdvancedSettingsButton->isVisible(); if (sampleRateDropDown != nullptr) { sampleRateDropDown->setVisible (advancedSettingsVisible); sampleRateDropDown->setBounds (r.removeFromTop (h)); r.removeFromTop (space); } if (bufferSizeDropDown != nullptr) { bufferSizeDropDown->setVisible (advancedSettingsVisible); bufferSizeDropDown->setBounds (r.removeFromTop (h)); r.removeFromTop (space); } r.removeFromTop (space); if (showUIButton != nullptr || resetDeviceButton != nullptr) { auto buttons = r.removeFromTop (h); if (showUIButton != nullptr) { showUIButton->setVisible (advancedSettingsVisible); showUIButton->changeWidthToFitText (h); showUIButton->setBounds (buttons.removeFromLeft (showUIButton->getWidth())); buttons.removeFromLeft (space); } if (resetDeviceButton != nullptr) { resetDeviceButton->setVisible (advancedSettingsVisible); resetDeviceButton->changeWidthToFitText (h); resetDeviceButton->setBounds (buttons.removeFromLeft (resetDeviceButton->getWidth())); } r.removeFromTop (space); } setSize (getWidth(), r.getY()); } else { jassertfalse; } } void updateConfig (bool updateOutputDevice, bool updateInputDevice, bool updateSampleRate, bool updateBufferSize) { auto config = setup.manager->getAudioDeviceSetup(); String error; if (updateOutputDevice || updateInputDevice) { if (outputDeviceDropDown != nullptr) config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String() : outputDeviceDropDown->getText(); if (inputDeviceDropDown != nullptr) config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String() : inputDeviceDropDown->getText(); if (! type.hasSeparateInputsAndOutputs()) config.inputDeviceName = config.outputDeviceName; if (updateInputDevice) config.useDefaultInputChannels = true; else config.useDefaultOutputChannels = true; error = setup.manager->setAudioDeviceSetup (config, true); showCorrectDeviceName (inputDeviceDropDown.get(), true); showCorrectDeviceName (outputDeviceDropDown.get(), false); updateControlPanelButton(); resized(); } else if (updateSampleRate) { if (sampleRateDropDown->getSelectedId() > 0) { config.sampleRate = sampleRateDropDown->getSelectedId(); error = setup.manager->setAudioDeviceSetup (config, true); } } else if (updateBufferSize) { if (bufferSizeDropDown->getSelectedId() > 0) { config.bufferSize = bufferSizeDropDown->getSelectedId(); error = setup.manager->setAudioDeviceSetup (config, true); } } if (error.isNotEmpty()) AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, TRANS("Error when trying to open audio device!"), error); } bool showDeviceControlPanel() { if (auto* device = setup.manager->getCurrentAudioDevice()) { Component modalWindow; modalWindow.setOpaque (true); modalWindow.addToDesktop (0); modalWindow.enterModalState(); return device->showControlPanel(); } return false; } void showAdvanced() { showAdvancedSettingsButton->setVisible (false); resized(); } void showDeviceUIPanel() { if (showDeviceControlPanel()) { setup.manager->closeAudioDevice(); setup.manager->restartLastAudioDevice(); getTopLevelComponent()->toFront (true); } } void playTestSound() { setup.manager->playTestSound(); } void updateAllControls() { updateOutputsComboBox(); updateInputsComboBox(); updateControlPanelButton(); updateResetButton(); if (auto* currentDevice = setup.manager->getCurrentAudioDevice()) { if (setup.maxNumOutputChannels > 0 && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size()) { if (outputChanList == nullptr) { outputChanList.reset (new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType, TRANS ("(no audio output channels found)"))); addAndMakeVisible (outputChanList.get()); outputChanLabel.reset (new Label ({}, TRANS("Active output channels:"))); outputChanLabel->setJustificationType (Justification::centredRight); outputChanLabel->attachToComponent (outputChanList.get(), true); } outputChanList->refresh(); } else { outputChanLabel.reset(); outputChanList.reset(); } if (setup.maxNumInputChannels > 0 && setup.minNumInputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size()) { if (inputChanList == nullptr) { inputChanList.reset (new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType, TRANS("(no audio input channels found)"))); addAndMakeVisible (inputChanList.get()); inputChanLabel.reset (new Label ({}, TRANS("Active input channels:"))); inputChanLabel->setJustificationType (Justification::centredRight); inputChanLabel->attachToComponent (inputChanList.get(), true); } inputChanList->refresh(); } else { inputChanLabel.reset(); inputChanList.reset(); } updateSampleRateComboBox (currentDevice); updateBufferSizeComboBox (currentDevice); } else { jassert (setup.manager->getCurrentAudioDevice() == nullptr); // not the correct device type! inputChanLabel.reset(); outputChanLabel.reset(); sampleRateLabel.reset(); bufferSizeLabel.reset(); inputChanList.reset(); outputChanList.reset(); sampleRateDropDown.reset(); bufferSizeDropDown.reset(); if (outputDeviceDropDown != nullptr) outputDeviceDropDown->setSelectedId (-1, dontSendNotification); if (inputDeviceDropDown != nullptr) inputDeviceDropDown->setSelectedId (-1, dontSendNotification); } sendLookAndFeelChange(); resized(); setSize (getWidth(), getLowestY() + 4); } void changeListenerCallback (ChangeBroadcaster*) override { updateAllControls(); } void resetDevice() { setup.manager->closeAudioDevice(); setup.manager->restartLastAudioDevice(); } private: AudioIODeviceType& type; const AudioDeviceSetupDetails setup; std::unique_ptr outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown; std::unique_ptr