The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1243 lines
44KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. struct SimpleDeviceManagerInputLevelMeter final : public Component,
  21. public Timer
  22. {
  23. SimpleDeviceManagerInputLevelMeter (AudioDeviceManager& m) : manager (m)
  24. {
  25. startTimerHz (20);
  26. inputLevelGetter = manager.getInputLevelGetter();
  27. }
  28. void timerCallback() override
  29. {
  30. if (isShowing())
  31. {
  32. auto newLevel = (float) inputLevelGetter->getCurrentLevel();
  33. if (std::abs (level - newLevel) > 0.005f)
  34. {
  35. level = newLevel;
  36. repaint();
  37. }
  38. }
  39. else
  40. {
  41. level = 0;
  42. }
  43. }
  44. void paint (Graphics& g) override
  45. {
  46. // (add a bit of a skew to make the level more obvious)
  47. getLookAndFeel().drawLevelMeter (g, getWidth(), getHeight(),
  48. (float) std::exp (std::log (level) / 3.0));
  49. }
  50. AudioDeviceManager& manager;
  51. AudioDeviceManager::LevelMeter::Ptr inputLevelGetter;
  52. float level = 0;
  53. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleDeviceManagerInputLevelMeter)
  54. };
  55. static void drawTextLayout (Graphics& g, Component& owner, StringRef text, const Rectangle<int>& textBounds, bool enabled)
  56. {
  57. const auto textColour = owner.findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f);
  58. AttributedString attributedString { text };
  59. attributedString.setColour (textColour);
  60. attributedString.setFont ((float) textBounds.getHeight() * 0.6f);
  61. attributedString.setJustification (Justification::centredLeft);
  62. attributedString.setWordWrap (AttributedString::WordWrap::none);
  63. TextLayout textLayout;
  64. textLayout.createLayout (attributedString,
  65. (float) textBounds.getWidth(),
  66. (float) textBounds.getHeight());
  67. textLayout.draw (g, textBounds.toFloat());
  68. }
  69. //==============================================================================
  70. class AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox final : public ListBox,
  71. private ListBoxModel
  72. {
  73. public:
  74. MidiInputSelectorComponentListBox (AudioDeviceManager& dm, const String& noItems)
  75. : ListBox ({}, nullptr),
  76. deviceManager (dm),
  77. noItemsMessage (noItems)
  78. {
  79. updateDevices();
  80. setModel (this);
  81. setOutlineThickness (1);
  82. }
  83. void updateDevices()
  84. {
  85. items = MidiInput::getAvailableDevices();
  86. }
  87. int getNumRows() override
  88. {
  89. return items.size();
  90. }
  91. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  92. {
  93. if (isPositiveAndBelow (row, items.size()))
  94. {
  95. if (rowIsSelected)
  96. g.fillAll (findColour (TextEditor::highlightColourId)
  97. .withMultipliedAlpha (0.3f));
  98. auto item = items[row];
  99. bool enabled = deviceManager.isMidiInputDeviceEnabled (item.identifier);
  100. auto x = getTickX();
  101. auto tickW = (float) height * 0.75f;
  102. getLookAndFeel().drawTickBox (g, *this, (float) x - tickW, ((float) height - tickW) * 0.5f, tickW, tickW,
  103. enabled, true, true, false);
  104. drawTextLayout (g, *this, item.name, { x + 5, 0, width - x - 5, height }, enabled);
  105. }
  106. }
  107. void listBoxItemClicked (int row, const MouseEvent& e) override
  108. {
  109. selectRow (row);
  110. if (e.x < getTickX())
  111. flipEnablement (row);
  112. }
  113. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  114. {
  115. flipEnablement (row);
  116. }
  117. void returnKeyPressed (int row) override
  118. {
  119. flipEnablement (row);
  120. }
  121. void paint (Graphics& g) override
  122. {
  123. ListBox::paint (g);
  124. if (items.isEmpty())
  125. {
  126. g.setColour (Colours::grey);
  127. g.setFont (0.5f * (float) getRowHeight());
  128. g.drawText (noItemsMessage,
  129. 0, 0, getWidth(), getHeight() / 2,
  130. Justification::centred, true);
  131. }
  132. }
  133. int getBestHeight (int preferredHeight)
  134. {
  135. auto extra = getOutlineThickness() * 2;
  136. return jmax (getRowHeight() * 2 + extra,
  137. jmin (getRowHeight() * getNumRows() + extra,
  138. preferredHeight));
  139. }
  140. private:
  141. //==============================================================================
  142. AudioDeviceManager& deviceManager;
  143. const String noItemsMessage;
  144. Array<MidiDeviceInfo> items;
  145. void flipEnablement (const int row)
  146. {
  147. if (isPositiveAndBelow (row, items.size()))
  148. {
  149. auto identifier = items[row].identifier;
  150. deviceManager.setMidiInputDeviceEnabled (identifier, ! deviceManager.isMidiInputDeviceEnabled (identifier));
  151. }
  152. }
  153. int getTickX() const
  154. {
  155. return getRowHeight();
  156. }
  157. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputSelectorComponentListBox)
  158. };
  159. //==============================================================================
  160. struct AudioDeviceSetupDetails
  161. {
  162. AudioDeviceManager* manager;
  163. int minNumInputChannels, maxNumInputChannels;
  164. int minNumOutputChannels, maxNumOutputChannels;
  165. bool useStereoPairs;
  166. };
  167. static String getNoDeviceString() { return "<< " + TRANS ("none") + " >>"; }
  168. //==============================================================================
  169. class AudioDeviceSelectorComponent::MidiOutputSelector final : public Component,
  170. private ChangeListener
  171. {
  172. public:
  173. explicit MidiOutputSelector (AudioDeviceManager& dm)
  174. : deviceManager (dm)
  175. {
  176. deviceManager.addChangeListener (this);
  177. selector.onChange = [&]
  178. {
  179. const auto selectedId = selector.getSelectedId();
  180. jassert (selectedId != 0);
  181. const auto deviceId = selectedId == -1
  182. ? String{}
  183. : MidiOutput::getAvailableDevices()[selectedId - 1].identifier;
  184. deviceManager.setDefaultMidiOutputDevice (deviceId);
  185. };
  186. updateListOfDevices();
  187. addAndMakeVisible (selector);
  188. }
  189. ~MidiOutputSelector() final
  190. {
  191. deviceManager.removeChangeListener (this);
  192. }
  193. void resized() final { selector.setBounds (getLocalBounds()); }
  194. private:
  195. void updateListOfDevices()
  196. {
  197. selector.clear();
  198. const auto midiOutputs = MidiOutput::getAvailableDevices();
  199. selector.addItem (getNoDeviceString(), -1);
  200. selector.setSelectedId (-1, dontSendNotification);
  201. selector.addSeparator();
  202. for (auto [id, midiOutput] : enumerate (midiOutputs, 1))
  203. {
  204. selector.addItem (midiOutput.name, id);
  205. if (midiOutput.identifier == deviceManager.getDefaultMidiOutputIdentifier())
  206. selector.setSelectedId (id, dontSendNotification);
  207. }
  208. }
  209. void changeListenerCallback (ChangeBroadcaster*) final { updateListOfDevices(); }
  210. ComboBox selector;
  211. AudioDeviceManager& deviceManager;
  212. };
  213. //==============================================================================
  214. class AudioDeviceSettingsPanel : public Component,
  215. private ChangeListener
  216. {
  217. public:
  218. AudioDeviceSettingsPanel (AudioIODeviceType& t, AudioDeviceSetupDetails& setupDetails,
  219. const bool hideAdvancedOptionsWithButton,
  220. AudioDeviceSelectorComponent& p)
  221. : type (t), setup (setupDetails), parent (p)
  222. {
  223. if (hideAdvancedOptionsWithButton)
  224. {
  225. showAdvancedSettingsButton = std::make_unique <TextButton> (TRANS ("Show advanced settings..."));
  226. addAndMakeVisible (showAdvancedSettingsButton.get());
  227. showAdvancedSettingsButton->setClickingTogglesState (true);
  228. showAdvancedSettingsButton->onClick = [this] { toggleAdvancedSettings(); };
  229. }
  230. type.scanForDevices();
  231. setup.manager->addChangeListener (this);
  232. updateAllControls();
  233. }
  234. ~AudioDeviceSettingsPanel() override
  235. {
  236. setup.manager->removeChangeListener (this);
  237. }
  238. void resized() override
  239. {
  240. Rectangle<int> r (proportionOfWidth (0.35f), 0, proportionOfWidth (0.6f), 3000);
  241. const int maxListBoxHeight = 100;
  242. const int h = parent.getItemHeight();
  243. const int space = h / 4;
  244. if (outputDeviceDropDown != nullptr)
  245. {
  246. auto row = r.removeFromTop (h);
  247. if (testButton != nullptr)
  248. {
  249. testButton->changeWidthToFitText (h);
  250. testButton->setBounds (row.removeFromRight (testButton->getWidth()));
  251. row.removeFromRight (space);
  252. }
  253. outputDeviceDropDown->setBounds (row);
  254. r.removeFromTop (space);
  255. }
  256. if (inputDeviceDropDown != nullptr)
  257. {
  258. auto row = r.removeFromTop (h);
  259. inputLevelMeter->setBounds (row.removeFromRight (testButton != nullptr ? testButton->getWidth() : row.getWidth() / 6));
  260. row.removeFromRight (space);
  261. inputDeviceDropDown->setBounds (row);
  262. r.removeFromTop (space);
  263. }
  264. if (outputChanList != nullptr)
  265. {
  266. outputChanList->setRowHeight (jmin (22, h));
  267. outputChanList->setBounds (r.removeFromTop (outputChanList->getBestHeight (maxListBoxHeight)));
  268. outputChanLabel->setBounds (0, outputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  269. r.removeFromTop (space);
  270. }
  271. if (inputChanList != nullptr)
  272. {
  273. inputChanList->setRowHeight (jmin (22, h));
  274. inputChanList->setBounds (r.removeFromTop (inputChanList->getBestHeight (maxListBoxHeight)));
  275. inputChanLabel->setBounds (0, inputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  276. r.removeFromTop (space);
  277. }
  278. r.removeFromTop (space * 2);
  279. if (showAdvancedSettingsButton != nullptr
  280. && sampleRateDropDown != nullptr && bufferSizeDropDown != nullptr)
  281. {
  282. showAdvancedSettingsButton->setBounds (r.removeFromTop (h));
  283. r.removeFromTop (space);
  284. showAdvancedSettingsButton->changeWidthToFitText();
  285. }
  286. auto advancedSettingsVisible = showAdvancedSettingsButton == nullptr
  287. || showAdvancedSettingsButton->getToggleState();
  288. if (sampleRateDropDown != nullptr)
  289. {
  290. sampleRateDropDown->setVisible (advancedSettingsVisible);
  291. if (advancedSettingsVisible)
  292. {
  293. sampleRateDropDown->setBounds (r.removeFromTop (h));
  294. r.removeFromTop (space);
  295. }
  296. }
  297. if (bufferSizeDropDown != nullptr)
  298. {
  299. bufferSizeDropDown->setVisible (advancedSettingsVisible);
  300. if (advancedSettingsVisible)
  301. {
  302. bufferSizeDropDown->setBounds (r.removeFromTop (h));
  303. r.removeFromTop (space);
  304. }
  305. }
  306. r.removeFromTop (space);
  307. if (showUIButton != nullptr || resetDeviceButton != nullptr)
  308. {
  309. auto buttons = r.removeFromTop (h);
  310. if (showUIButton != nullptr)
  311. {
  312. showUIButton->setVisible (advancedSettingsVisible);
  313. showUIButton->changeWidthToFitText (h);
  314. showUIButton->setBounds (buttons.removeFromLeft (showUIButton->getWidth()));
  315. buttons.removeFromLeft (space);
  316. }
  317. if (resetDeviceButton != nullptr)
  318. {
  319. resetDeviceButton->setVisible (advancedSettingsVisible);
  320. resetDeviceButton->changeWidthToFitText (h);
  321. resetDeviceButton->setBounds (buttons.removeFromLeft (resetDeviceButton->getWidth()));
  322. }
  323. r.removeFromTop (space);
  324. }
  325. setSize (getWidth(), r.getY());
  326. }
  327. void updateConfig (bool updateOutputDevice, bool updateInputDevice, bool updateSampleRate, bool updateBufferSize)
  328. {
  329. auto config = setup.manager->getAudioDeviceSetup();
  330. String error;
  331. if (updateOutputDevice || updateInputDevice)
  332. {
  333. if (outputDeviceDropDown != nullptr)
  334. config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String()
  335. : outputDeviceDropDown->getText();
  336. if (inputDeviceDropDown != nullptr)
  337. config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String()
  338. : inputDeviceDropDown->getText();
  339. if (! type.hasSeparateInputsAndOutputs())
  340. config.inputDeviceName = config.outputDeviceName;
  341. if (updateInputDevice)
  342. config.useDefaultInputChannels = true;
  343. else
  344. config.useDefaultOutputChannels = true;
  345. error = setup.manager->setAudioDeviceSetup (config, true);
  346. showCorrectDeviceName (inputDeviceDropDown.get(), true);
  347. showCorrectDeviceName (outputDeviceDropDown.get(), false);
  348. updateControlPanelButton();
  349. resized();
  350. }
  351. else if (updateSampleRate)
  352. {
  353. if (sampleRateDropDown->getSelectedId() > 0)
  354. {
  355. config.sampleRate = sampleRateDropDown->getSelectedId();
  356. error = setup.manager->setAudioDeviceSetup (config, true);
  357. }
  358. }
  359. else if (updateBufferSize)
  360. {
  361. if (bufferSizeDropDown->getSelectedId() > 0)
  362. {
  363. config.bufferSize = bufferSizeDropDown->getSelectedId();
  364. error = setup.manager->setAudioDeviceSetup (config, true);
  365. }
  366. }
  367. if (error.isNotEmpty())
  368. messageBox = AlertWindow::showScopedAsync (MessageBoxOptions().withIconType (MessageBoxIconType::WarningIcon)
  369. .withTitle (TRANS ("Error when trying to open audio device!"))
  370. .withMessage (error)
  371. .withButton (TRANS ("OK")),
  372. nullptr);
  373. }
  374. bool showDeviceControlPanel()
  375. {
  376. if (auto* device = setup.manager->getCurrentAudioDevice())
  377. {
  378. Component modalWindow;
  379. modalWindow.setOpaque (true);
  380. modalWindow.addToDesktop (0);
  381. modalWindow.enterModalState();
  382. return device->showControlPanel();
  383. }
  384. return false;
  385. }
  386. void toggleAdvancedSettings()
  387. {
  388. showAdvancedSettingsButton->setButtonText ((showAdvancedSettingsButton->getToggleState() ? "Hide " : "Show ")
  389. + String ("advanced settings..."));
  390. resized();
  391. }
  392. void showDeviceUIPanel()
  393. {
  394. if (showDeviceControlPanel())
  395. {
  396. setup.manager->closeAudioDevice();
  397. setup.manager->restartLastAudioDevice();
  398. getTopLevelComponent()->toFront (true);
  399. }
  400. }
  401. void playTestSound()
  402. {
  403. setup.manager->playTestSound();
  404. }
  405. void updateAllControls()
  406. {
  407. updateOutputsComboBox();
  408. updateInputsComboBox();
  409. updateControlPanelButton();
  410. updateResetButton();
  411. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  412. {
  413. if (setup.maxNumOutputChannels > 0
  414. && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size())
  415. {
  416. if (outputChanList == nullptr)
  417. {
  418. outputChanList = std::make_unique<ChannelSelectorListBox> (setup, ChannelSelectorListBox::audioOutputType,
  419. TRANS ("(no audio output channels found)"));
  420. addAndMakeVisible (outputChanList.get());
  421. outputChanLabel = std::make_unique<Label> (String{}, TRANS ("Active output channels:"));
  422. outputChanLabel->setJustificationType (Justification::centredRight);
  423. outputChanLabel->attachToComponent (outputChanList.get(), true);
  424. }
  425. outputChanList->refresh();
  426. }
  427. else
  428. {
  429. outputChanLabel.reset();
  430. outputChanList.reset();
  431. }
  432. if (setup.maxNumInputChannels > 0
  433. && setup.minNumInputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size())
  434. {
  435. if (inputChanList == nullptr)
  436. {
  437. inputChanList = std::make_unique<ChannelSelectorListBox> (setup, ChannelSelectorListBox::audioInputType,
  438. TRANS ("(no audio input channels found)"));
  439. addAndMakeVisible (inputChanList.get());
  440. inputChanLabel = std::make_unique<Label> (String{}, TRANS ("Active input channels:"));
  441. inputChanLabel->setJustificationType (Justification::centredRight);
  442. inputChanLabel->attachToComponent (inputChanList.get(), true);
  443. }
  444. inputChanList->refresh();
  445. }
  446. else
  447. {
  448. inputChanLabel.reset();
  449. inputChanList.reset();
  450. }
  451. updateSampleRateComboBox (currentDevice);
  452. updateBufferSizeComboBox (currentDevice);
  453. }
  454. else
  455. {
  456. jassert (setup.manager->getCurrentAudioDevice() == nullptr); // not the correct device type!
  457. inputChanLabel.reset();
  458. outputChanLabel.reset();
  459. sampleRateLabel.reset();
  460. bufferSizeLabel.reset();
  461. inputChanList.reset();
  462. outputChanList.reset();
  463. sampleRateDropDown.reset();
  464. bufferSizeDropDown.reset();
  465. if (outputDeviceDropDown != nullptr)
  466. outputDeviceDropDown->setSelectedId (-1, dontSendNotification);
  467. if (inputDeviceDropDown != nullptr)
  468. inputDeviceDropDown->setSelectedId (-1, dontSendNotification);
  469. }
  470. sendLookAndFeelChange();
  471. resized();
  472. setSize (getWidth(), getLowestY() + 4);
  473. }
  474. void changeListenerCallback (ChangeBroadcaster*) override
  475. {
  476. updateAllControls();
  477. }
  478. void resetDevice()
  479. {
  480. setup.manager->closeAudioDevice();
  481. setup.manager->restartLastAudioDevice();
  482. }
  483. private:
  484. AudioIODeviceType& type;
  485. const AudioDeviceSetupDetails setup;
  486. AudioDeviceSelectorComponent& parent;
  487. std::unique_ptr<ComboBox> outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown;
  488. std::unique_ptr<Label> outputDeviceLabel, inputDeviceLabel, sampleRateLabel, bufferSizeLabel, inputChanLabel, outputChanLabel;
  489. std::unique_ptr<TextButton> testButton;
  490. std::unique_ptr<Component> inputLevelMeter;
  491. std::unique_ptr<TextButton> showUIButton, showAdvancedSettingsButton, resetDeviceButton;
  492. void showCorrectDeviceName (ComboBox* box, bool isInput)
  493. {
  494. if (box != nullptr)
  495. {
  496. auto* currentDevice = setup.manager->getCurrentAudioDevice();
  497. auto index = type.getIndexOfDevice (currentDevice, isInput);
  498. box->setSelectedId (index < 0 ? index : index + 1, dontSendNotification);
  499. if (testButton != nullptr && ! isInput)
  500. testButton->setEnabled (index >= 0);
  501. }
  502. }
  503. void addNamesToDeviceBox (ComboBox& combo, bool isInputs)
  504. {
  505. const StringArray devs (type.getDeviceNames (isInputs));
  506. combo.clear (dontSendNotification);
  507. for (int i = 0; i < devs.size(); ++i)
  508. combo.addItem (devs[i], i + 1);
  509. combo.addItem (getNoDeviceString(), -1);
  510. combo.setSelectedId (-1, dontSendNotification);
  511. }
  512. int getLowestY() const
  513. {
  514. int y = 0;
  515. for (auto* c : getChildren())
  516. y = jmax (y, c->getBottom());
  517. return y;
  518. }
  519. void updateControlPanelButton()
  520. {
  521. auto* currentDevice = setup.manager->getCurrentAudioDevice();
  522. showUIButton.reset();
  523. if (currentDevice != nullptr && currentDevice->hasControlPanel())
  524. {
  525. showUIButton = std::make_unique<TextButton> (TRANS ("Control Panel"),
  526. TRANS ("Opens the device's own control panel"));
  527. addAndMakeVisible (showUIButton.get());
  528. showUIButton->onClick = [this] { showDeviceUIPanel(); };
  529. }
  530. resized();
  531. }
  532. void updateResetButton()
  533. {
  534. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  535. {
  536. if (currentDevice->hasControlPanel())
  537. {
  538. if (resetDeviceButton == nullptr)
  539. {
  540. resetDeviceButton = std::make_unique<TextButton> (TRANS ("Reset Device"),
  541. TRANS ("Resets the audio interface - sometimes needed after changing a device's properties in its custom control panel"));
  542. addAndMakeVisible (resetDeviceButton.get());
  543. resetDeviceButton->onClick = [this] { resetDevice(); };
  544. resized();
  545. }
  546. return;
  547. }
  548. }
  549. resetDeviceButton.reset();
  550. }
  551. void updateOutputsComboBox()
  552. {
  553. if (setup.maxNumOutputChannels > 0 || ! type.hasSeparateInputsAndOutputs())
  554. {
  555. if (outputDeviceDropDown == nullptr)
  556. {
  557. outputDeviceDropDown = std::make_unique<ComboBox>();
  558. outputDeviceDropDown->onChange = [this] { updateConfig (true, false, false, false); };
  559. addAndMakeVisible (outputDeviceDropDown.get());
  560. outputDeviceLabel = std::make_unique<Label> (String{}, type.hasSeparateInputsAndOutputs() ? TRANS ("Output:")
  561. : TRANS ("Device:"));
  562. outputDeviceLabel->attachToComponent (outputDeviceDropDown.get(), true);
  563. if (setup.maxNumOutputChannels > 0)
  564. {
  565. testButton = std::make_unique<TextButton> (TRANS ("Test"), TRANS ("Plays a test tone"));
  566. addAndMakeVisible (testButton.get());
  567. testButton->onClick = [this] { playTestSound(); };
  568. }
  569. }
  570. addNamesToDeviceBox (*outputDeviceDropDown, false);
  571. }
  572. showCorrectDeviceName (outputDeviceDropDown.get(), false);
  573. }
  574. void updateInputsComboBox()
  575. {
  576. if (setup.maxNumInputChannels > 0 && type.hasSeparateInputsAndOutputs())
  577. {
  578. if (inputDeviceDropDown == nullptr)
  579. {
  580. inputDeviceDropDown = std::make_unique<ComboBox>();
  581. inputDeviceDropDown->onChange = [this] { updateConfig (false, true, false, false); };
  582. addAndMakeVisible (inputDeviceDropDown.get());
  583. inputDeviceLabel = std::make_unique<Label> (String{}, TRANS ("Input:"));
  584. inputDeviceLabel->attachToComponent (inputDeviceDropDown.get(), true);
  585. inputLevelMeter = std::make_unique<SimpleDeviceManagerInputLevelMeter> (*setup.manager);
  586. addAndMakeVisible (inputLevelMeter.get());
  587. }
  588. addNamesToDeviceBox (*inputDeviceDropDown, true);
  589. }
  590. showCorrectDeviceName (inputDeviceDropDown.get(), true);
  591. }
  592. void updateSampleRateComboBox (AudioIODevice* currentDevice)
  593. {
  594. if (sampleRateDropDown == nullptr)
  595. {
  596. sampleRateDropDown = std::make_unique<ComboBox>();
  597. addAndMakeVisible (sampleRateDropDown.get());
  598. sampleRateLabel = std::make_unique<Label> (String{}, TRANS ("Sample rate:"));
  599. sampleRateLabel->attachToComponent (sampleRateDropDown.get(), true);
  600. }
  601. else
  602. {
  603. sampleRateDropDown->clear();
  604. sampleRateDropDown->onChange = nullptr;
  605. }
  606. const auto getFrequencyString = [] (int rate) { return String (rate) + " Hz"; };
  607. for (auto rate : currentDevice->getAvailableSampleRates())
  608. {
  609. const auto intRate = roundToInt (rate);
  610. sampleRateDropDown->addItem (getFrequencyString (intRate), intRate);
  611. }
  612. const auto intRate = roundToInt (currentDevice->getCurrentSampleRate());
  613. sampleRateDropDown->setText (getFrequencyString (intRate), dontSendNotification);
  614. sampleRateDropDown->onChange = [this] { updateConfig (false, false, true, false); };
  615. }
  616. void updateBufferSizeComboBox (AudioIODevice* currentDevice)
  617. {
  618. if (bufferSizeDropDown == nullptr)
  619. {
  620. bufferSizeDropDown = std::make_unique<ComboBox>();
  621. addAndMakeVisible (bufferSizeDropDown.get());
  622. bufferSizeLabel = std::make_unique<Label> (String{}, TRANS ("Audio buffer size:"));
  623. bufferSizeLabel->attachToComponent (bufferSizeDropDown.get(), true);
  624. }
  625. else
  626. {
  627. bufferSizeDropDown->clear();
  628. bufferSizeDropDown->onChange = nullptr;
  629. }
  630. auto currentRate = currentDevice->getCurrentSampleRate();
  631. if (exactlyEqual (currentRate, 0.0))
  632. currentRate = 48000.0;
  633. for (auto bs : currentDevice->getAvailableBufferSizes())
  634. bufferSizeDropDown->addItem (String (bs) + " samples (" + String (bs * 1000.0 / currentRate, 1) + " ms)", bs);
  635. bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), dontSendNotification);
  636. bufferSizeDropDown->onChange = [this] { updateConfig (false, false, false, true); };
  637. }
  638. public:
  639. //==============================================================================
  640. class ChannelSelectorListBox final : public ListBox,
  641. private ListBoxModel
  642. {
  643. public:
  644. enum BoxType
  645. {
  646. audioInputType,
  647. audioOutputType
  648. };
  649. //==============================================================================
  650. ChannelSelectorListBox (const AudioDeviceSetupDetails& setupDetails, BoxType boxType, const String& noItemsText)
  651. : ListBox ({}, nullptr), setup (setupDetails), type (boxType), noItemsMessage (noItemsText)
  652. {
  653. refresh();
  654. setModel (this);
  655. setOutlineThickness (1);
  656. }
  657. void refresh()
  658. {
  659. items.clear();
  660. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  661. {
  662. if (type == audioInputType)
  663. items = currentDevice->getInputChannelNames();
  664. else if (type == audioOutputType)
  665. items = currentDevice->getOutputChannelNames();
  666. if (setup.useStereoPairs)
  667. {
  668. StringArray pairs;
  669. for (int i = 0; i < items.size(); i += 2)
  670. {
  671. auto& name = items[i];
  672. if (i + 1 >= items.size())
  673. pairs.add (name.trim());
  674. else
  675. pairs.add (getNameForChannelPair (name, items[i + 1]));
  676. }
  677. items = pairs;
  678. }
  679. }
  680. updateContent();
  681. repaint();
  682. }
  683. int getNumRows() override
  684. {
  685. return items.size();
  686. }
  687. void paintListBoxItem (int row, Graphics& g, int width, int height, bool) override
  688. {
  689. if (isPositiveAndBelow (row, items.size()))
  690. {
  691. g.fillAll (findColour (ListBox::backgroundColourId));
  692. auto item = items[row];
  693. bool enabled = false;
  694. auto config = setup.manager->getAudioDeviceSetup();
  695. if (setup.useStereoPairs)
  696. {
  697. if (type == audioInputType)
  698. enabled = config.inputChannels[row * 2] || config.inputChannels[row * 2 + 1];
  699. else if (type == audioOutputType)
  700. enabled = config.outputChannels[row * 2] || config.outputChannels[row * 2 + 1];
  701. }
  702. else
  703. {
  704. if (type == audioInputType)
  705. enabled = config.inputChannels[row];
  706. else if (type == audioOutputType)
  707. enabled = config.outputChannels[row];
  708. }
  709. auto x = getTickX();
  710. auto tickW = (float) height * 0.75f;
  711. getLookAndFeel().drawTickBox (g, *this, (float) x - tickW, ((float) height - tickW) * 0.5f, tickW, tickW,
  712. enabled, true, true, false);
  713. drawTextLayout (g, *this, item, { x + 5, 0, width - x - 5, height }, enabled);
  714. }
  715. }
  716. void listBoxItemClicked (int row, const MouseEvent& e) override
  717. {
  718. selectRow (row);
  719. if (e.x < getTickX())
  720. flipEnablement (row);
  721. }
  722. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  723. {
  724. flipEnablement (row);
  725. }
  726. void returnKeyPressed (int row) override
  727. {
  728. flipEnablement (row);
  729. }
  730. void paint (Graphics& g) override
  731. {
  732. ListBox::paint (g);
  733. if (items.isEmpty())
  734. {
  735. g.setColour (Colours::grey);
  736. g.setFont (0.5f * (float) getRowHeight());
  737. g.drawText (noItemsMessage,
  738. 0, 0, getWidth(), getHeight() / 2,
  739. Justification::centred, true);
  740. }
  741. }
  742. int getBestHeight (int maxHeight)
  743. {
  744. return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()),
  745. getNumRows())
  746. + getOutlineThickness() * 2;
  747. }
  748. private:
  749. //==============================================================================
  750. const AudioDeviceSetupDetails setup;
  751. const BoxType type;
  752. const String noItemsMessage;
  753. StringArray items;
  754. static String getNameForChannelPair (const String& name1, const String& name2)
  755. {
  756. String commonBit;
  757. for (int j = 0; j < name1.length(); ++j)
  758. if (name1.substring (0, j).equalsIgnoreCase (name2.substring (0, j)))
  759. commonBit = name1.substring (0, j);
  760. // Make sure we only split the name at a space, because otherwise, things
  761. // like "input 11" + "input 12" would become "input 11 + 2"
  762. while (commonBit.isNotEmpty() && ! CharacterFunctions::isWhitespace (commonBit.getLastCharacter()))
  763. commonBit = commonBit.dropLastCharacters (1);
  764. return name1.trim() + " + " + name2.substring (commonBit.length()).trim();
  765. }
  766. void flipEnablement (int row)
  767. {
  768. jassert (type == audioInputType || type == audioOutputType);
  769. if (isPositiveAndBelow (row, items.size()))
  770. {
  771. auto config = setup.manager->getAudioDeviceSetup();
  772. if (setup.useStereoPairs)
  773. {
  774. BigInteger bits;
  775. auto& original = (type == audioInputType ? config.inputChannels
  776. : config.outputChannels);
  777. for (int i = 0; i < 256; i += 2)
  778. bits.setBit (i / 2, original[i] || original[i + 1]);
  779. if (type == audioInputType)
  780. {
  781. config.useDefaultInputChannels = false;
  782. flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2);
  783. }
  784. else
  785. {
  786. config.useDefaultOutputChannels = false;
  787. flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2);
  788. }
  789. for (int i = 0; i < 256; ++i)
  790. original.setBit (i, bits[i / 2]);
  791. }
  792. else
  793. {
  794. if (type == audioInputType)
  795. {
  796. config.useDefaultInputChannels = false;
  797. flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels);
  798. }
  799. else
  800. {
  801. config.useDefaultOutputChannels = false;
  802. flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels);
  803. }
  804. }
  805. setup.manager->setAudioDeviceSetup (config, true);
  806. }
  807. }
  808. static void flipBit (BigInteger& chans, int index, int minNumber, int maxNumber)
  809. {
  810. auto numActive = chans.countNumberOfSetBits();
  811. if (chans[index])
  812. {
  813. if (numActive > minNumber)
  814. chans.setBit (index, false);
  815. }
  816. else
  817. {
  818. if (numActive >= maxNumber)
  819. {
  820. auto firstActiveChan = chans.findNextSetBit (0);
  821. chans.clearBit (index > firstActiveChan ? firstActiveChan : chans.getHighestBit());
  822. }
  823. chans.setBit (index, true);
  824. }
  825. }
  826. int getTickX() const
  827. {
  828. return getRowHeight();
  829. }
  830. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelSelectorListBox)
  831. };
  832. private:
  833. std::unique_ptr<ChannelSelectorListBox> inputChanList, outputChanList;
  834. ScopedMessageBox messageBox;
  835. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSettingsPanel)
  836. };
  837. //==============================================================================
  838. AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& dm,
  839. int minInputChannelsToUse,
  840. int maxInputChannelsToUse,
  841. int minOutputChannelsToUse,
  842. int maxOutputChannelsToUse,
  843. bool showMidiInputOptions,
  844. bool showMidiOutputSelector,
  845. bool showChannelsAsStereoPairsToUse,
  846. bool hideAdvancedOptionsWithButtonToUse)
  847. : deviceManager (dm),
  848. itemHeight (24),
  849. minOutputChannels (minOutputChannelsToUse),
  850. maxOutputChannels (maxOutputChannelsToUse),
  851. minInputChannels (minInputChannelsToUse),
  852. maxInputChannels (maxInputChannelsToUse),
  853. showChannelsAsStereoPairs (showChannelsAsStereoPairsToUse),
  854. hideAdvancedOptionsWithButton (hideAdvancedOptionsWithButtonToUse)
  855. {
  856. jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
  857. jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);
  858. const OwnedArray<AudioIODeviceType>& types = deviceManager.getAvailableDeviceTypes();
  859. if (types.size() > 1)
  860. {
  861. deviceTypeDropDown = std::make_unique<ComboBox>();
  862. for (int i = 0; i < types.size(); ++i)
  863. deviceTypeDropDown->addItem (types.getUnchecked (i)->getTypeName(), i + 1);
  864. addAndMakeVisible (deviceTypeDropDown.get());
  865. deviceTypeDropDown->onChange = [this] { updateDeviceType(); };
  866. deviceTypeDropDownLabel = std::make_unique<Label> (String{}, TRANS ("Audio device type:"));
  867. deviceTypeDropDownLabel->setJustificationType (Justification::centredRight);
  868. deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown.get(), true);
  869. }
  870. if (showMidiInputOptions)
  871. {
  872. midiInputsList = std::make_unique <MidiInputSelectorComponentListBox> (deviceManager,
  873. "(" + TRANS ("No MIDI inputs available") + ")");
  874. addAndMakeVisible (midiInputsList.get());
  875. midiInputsLabel = std::make_unique<Label> (String{}, TRANS ("Active MIDI inputs:"));
  876. midiInputsLabel->setJustificationType (Justification::topRight);
  877. midiInputsLabel->attachToComponent (midiInputsList.get(), true);
  878. if (BluetoothMidiDevicePairingDialogue::isAvailable())
  879. {
  880. bluetoothButton = std::make_unique<TextButton> (TRANS ("Bluetooth MIDI"), TRANS ("Scan for bluetooth MIDI devices"));
  881. addAndMakeVisible (bluetoothButton.get());
  882. bluetoothButton->onClick = [this] { handleBluetoothButton(); };
  883. }
  884. }
  885. else
  886. {
  887. midiInputsList.reset();
  888. midiInputsLabel.reset();
  889. bluetoothButton.reset();
  890. }
  891. if (showMidiOutputSelector)
  892. {
  893. midiOutputSelector = std::make_unique<MidiOutputSelector> (deviceManager);
  894. addAndMakeVisible (midiOutputSelector.get());
  895. midiOutputLabel = std::make_unique<Label> ("lm", TRANS ("MIDI Output:"));
  896. midiOutputLabel->attachToComponent (midiOutputSelector.get(), true);
  897. }
  898. else
  899. {
  900. midiOutputSelector.reset();
  901. midiOutputLabel.reset();
  902. }
  903. deviceManager.addChangeListener (this);
  904. updateAllControls();
  905. }
  906. AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent()
  907. {
  908. deviceManager.removeChangeListener (this);
  909. }
  910. void AudioDeviceSelectorComponent::setItemHeight (int newItemHeight)
  911. {
  912. itemHeight = newItemHeight;
  913. resized();
  914. }
  915. void AudioDeviceSelectorComponent::resized()
  916. {
  917. Rectangle<int> r (proportionOfWidth (0.35f), 15, proportionOfWidth (0.6f), 3000);
  918. auto space = itemHeight / 4;
  919. if (deviceTypeDropDown != nullptr)
  920. {
  921. deviceTypeDropDown->setBounds (r.removeFromTop (itemHeight));
  922. r.removeFromTop (space * 3);
  923. }
  924. if (audioDeviceSettingsComp != nullptr)
  925. {
  926. audioDeviceSettingsComp->resized();
  927. audioDeviceSettingsComp->setBounds (r.removeFromTop (audioDeviceSettingsComp->getHeight())
  928. .withX (0).withWidth (getWidth()));
  929. r.removeFromTop (space);
  930. }
  931. if (midiInputsList != nullptr)
  932. {
  933. midiInputsList->setRowHeight (jmin (22, itemHeight));
  934. midiInputsList->setBounds (r.removeFromTop (midiInputsList->getBestHeight (jmin (itemHeight * 8,
  935. getHeight() - r.getY() - space - itemHeight))));
  936. r.removeFromTop (space);
  937. }
  938. if (bluetoothButton != nullptr)
  939. {
  940. bluetoothButton->setBounds (r.removeFromTop (24));
  941. r.removeFromTop (space);
  942. }
  943. if (midiOutputSelector != nullptr)
  944. midiOutputSelector->setBounds (r.removeFromTop (itemHeight));
  945. r.removeFromTop (itemHeight);
  946. setSize (getWidth(), r.getY());
  947. }
  948. void AudioDeviceSelectorComponent::childBoundsChanged (Component* child)
  949. {
  950. if (child == audioDeviceSettingsComp.get())
  951. resized();
  952. }
  953. void AudioDeviceSelectorComponent::updateDeviceType()
  954. {
  955. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1])
  956. {
  957. audioDeviceSettingsComp.reset();
  958. deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true);
  959. updateAllControls(); // needed in case the type hasn't actually changed
  960. }
  961. }
  962. void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*)
  963. {
  964. updateAllControls();
  965. }
  966. void AudioDeviceSelectorComponent::updateAllControls()
  967. {
  968. if (deviceTypeDropDown != nullptr)
  969. deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), dontSendNotification);
  970. if (audioDeviceSettingsComp == nullptr
  971. || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType())
  972. {
  973. audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType();
  974. audioDeviceSettingsComp.reset();
  975. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == nullptr
  976. ? 0 : deviceTypeDropDown->getSelectedId() - 1])
  977. {
  978. AudioDeviceSetupDetails details;
  979. details.manager = &deviceManager;
  980. details.minNumInputChannels = minInputChannels;
  981. details.maxNumInputChannels = maxInputChannels;
  982. details.minNumOutputChannels = minOutputChannels;
  983. details.maxNumOutputChannels = maxOutputChannels;
  984. details.useStereoPairs = showChannelsAsStereoPairs;
  985. audioDeviceSettingsComp = std::make_unique<AudioDeviceSettingsPanel> (*type, details, hideAdvancedOptionsWithButton, *this);
  986. addAndMakeVisible (audioDeviceSettingsComp.get());
  987. }
  988. }
  989. if (midiInputsList != nullptr)
  990. {
  991. midiInputsList->updateDevices();
  992. midiInputsList->updateContent();
  993. midiInputsList->repaint();
  994. }
  995. resized();
  996. }
  997. void AudioDeviceSelectorComponent::handleBluetoothButton()
  998. {
  999. if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  1000. {
  1001. BluetoothMidiDevicePairingDialogue::open();
  1002. }
  1003. else
  1004. {
  1005. RuntimePermissions::request (RuntimePermissions::bluetoothMidi, [] (auto)
  1006. {
  1007. if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  1008. BluetoothMidiDevicePairingDialogue::open();
  1009. });
  1010. }
  1011. }
  1012. ListBox* AudioDeviceSelectorComponent::getMidiInputSelectorListBox() const noexcept
  1013. {
  1014. return midiInputsList.get();
  1015. }
  1016. } // namespace juce