/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. 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::getAvailableDevices(); } 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.isMidiInputDeviceEnabled (item.identifier); 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.name, 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; Array items; void flipEnablement (const int row) { if (isPositiveAndBelow (row, items.size())) { auto identifier = items[row].identifier; deviceManager.setMidiInputDeviceEnabled (identifier, ! deviceManager.isMidiInputDeviceEnabled (identifier)); } } 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->setClickingTogglesState (true); showAdvancedSettingsButton->onClick = [this] { toggleAdvancedSettings(); }; } 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 && sampleRateDropDown != nullptr && bufferSizeDropDown != nullptr) { showAdvancedSettingsButton->setBounds (r.removeFromTop (h)); r.removeFromTop (space); showAdvancedSettingsButton->changeWidthToFitText(); } auto advancedSettingsVisible = showAdvancedSettingsButton == nullptr || showAdvancedSettingsButton->getToggleState(); if (sampleRateDropDown != nullptr) { sampleRateDropDown->setVisible (advancedSettingsVisible); if (advancedSettingsVisible) { sampleRateDropDown->setBounds (r.removeFromTop (h)); r.removeFromTop (space); } } if (bufferSizeDropDown != nullptr) { bufferSizeDropDown->setVisible (advancedSettingsVisible); if (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 toggleAdvancedSettings() { showAdvancedSettingsButton->setButtonText ((showAdvancedSettingsButton->getToggleState() ? "Hide " : "Show ") + String ("advanced settings...")); 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