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.

1214 lines
43KB

  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 : 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 : 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 AudioDeviceSettingsPanel : public Component,
  170. private ChangeListener
  171. {
  172. public:
  173. AudioDeviceSettingsPanel (AudioIODeviceType& t, AudioDeviceSetupDetails& setupDetails,
  174. const bool hideAdvancedOptionsWithButton,
  175. AudioDeviceSelectorComponent& p)
  176. : type (t), setup (setupDetails), parent (p)
  177. {
  178. if (hideAdvancedOptionsWithButton)
  179. {
  180. showAdvancedSettingsButton = std::make_unique <TextButton> (TRANS ("Show advanced settings..."));
  181. addAndMakeVisible (showAdvancedSettingsButton.get());
  182. showAdvancedSettingsButton->setClickingTogglesState (true);
  183. showAdvancedSettingsButton->onClick = [this] { toggleAdvancedSettings(); };
  184. }
  185. type.scanForDevices();
  186. setup.manager->addChangeListener (this);
  187. updateAllControls();
  188. }
  189. ~AudioDeviceSettingsPanel() override
  190. {
  191. setup.manager->removeChangeListener (this);
  192. }
  193. void resized() override
  194. {
  195. Rectangle<int> r (proportionOfWidth (0.35f), 0, proportionOfWidth (0.6f), 3000);
  196. const int maxListBoxHeight = 100;
  197. const int h = parent.getItemHeight();
  198. const int space = h / 4;
  199. if (outputDeviceDropDown != nullptr)
  200. {
  201. auto row = r.removeFromTop (h);
  202. if (testButton != nullptr)
  203. {
  204. testButton->changeWidthToFitText (h);
  205. testButton->setBounds (row.removeFromRight (testButton->getWidth()));
  206. row.removeFromRight (space);
  207. }
  208. outputDeviceDropDown->setBounds (row);
  209. r.removeFromTop (space);
  210. }
  211. if (inputDeviceDropDown != nullptr)
  212. {
  213. auto row = r.removeFromTop (h);
  214. inputLevelMeter->setBounds (row.removeFromRight (testButton != nullptr ? testButton->getWidth() : row.getWidth() / 6));
  215. row.removeFromRight (space);
  216. inputDeviceDropDown->setBounds (row);
  217. r.removeFromTop (space);
  218. }
  219. if (outputChanList != nullptr)
  220. {
  221. outputChanList->setRowHeight (jmin (22, h));
  222. outputChanList->setBounds (r.removeFromTop (outputChanList->getBestHeight (maxListBoxHeight)));
  223. outputChanLabel->setBounds (0, outputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  224. r.removeFromTop (space);
  225. }
  226. if (inputChanList != nullptr)
  227. {
  228. inputChanList->setRowHeight (jmin (22, h));
  229. inputChanList->setBounds (r.removeFromTop (inputChanList->getBestHeight (maxListBoxHeight)));
  230. inputChanLabel->setBounds (0, inputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  231. r.removeFromTop (space);
  232. }
  233. r.removeFromTop (space * 2);
  234. if (showAdvancedSettingsButton != nullptr
  235. && sampleRateDropDown != nullptr && bufferSizeDropDown != nullptr)
  236. {
  237. showAdvancedSettingsButton->setBounds (r.removeFromTop (h));
  238. r.removeFromTop (space);
  239. showAdvancedSettingsButton->changeWidthToFitText();
  240. }
  241. auto advancedSettingsVisible = showAdvancedSettingsButton == nullptr
  242. || showAdvancedSettingsButton->getToggleState();
  243. if (sampleRateDropDown != nullptr)
  244. {
  245. sampleRateDropDown->setVisible (advancedSettingsVisible);
  246. if (advancedSettingsVisible)
  247. {
  248. sampleRateDropDown->setBounds (r.removeFromTop (h));
  249. r.removeFromTop (space);
  250. }
  251. }
  252. if (bufferSizeDropDown != nullptr)
  253. {
  254. bufferSizeDropDown->setVisible (advancedSettingsVisible);
  255. if (advancedSettingsVisible)
  256. {
  257. bufferSizeDropDown->setBounds (r.removeFromTop (h));
  258. r.removeFromTop (space);
  259. }
  260. }
  261. r.removeFromTop (space);
  262. if (showUIButton != nullptr || resetDeviceButton != nullptr)
  263. {
  264. auto buttons = r.removeFromTop (h);
  265. if (showUIButton != nullptr)
  266. {
  267. showUIButton->setVisible (advancedSettingsVisible);
  268. showUIButton->changeWidthToFitText (h);
  269. showUIButton->setBounds (buttons.removeFromLeft (showUIButton->getWidth()));
  270. buttons.removeFromLeft (space);
  271. }
  272. if (resetDeviceButton != nullptr)
  273. {
  274. resetDeviceButton->setVisible (advancedSettingsVisible);
  275. resetDeviceButton->changeWidthToFitText (h);
  276. resetDeviceButton->setBounds (buttons.removeFromLeft (resetDeviceButton->getWidth()));
  277. }
  278. r.removeFromTop (space);
  279. }
  280. setSize (getWidth(), r.getY());
  281. }
  282. void updateConfig (bool updateOutputDevice, bool updateInputDevice, bool updateSampleRate, bool updateBufferSize)
  283. {
  284. auto config = setup.manager->getAudioDeviceSetup();
  285. String error;
  286. if (updateOutputDevice || updateInputDevice)
  287. {
  288. if (outputDeviceDropDown != nullptr)
  289. config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String()
  290. : outputDeviceDropDown->getText();
  291. if (inputDeviceDropDown != nullptr)
  292. config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String()
  293. : inputDeviceDropDown->getText();
  294. if (! type.hasSeparateInputsAndOutputs())
  295. config.inputDeviceName = config.outputDeviceName;
  296. if (updateInputDevice)
  297. config.useDefaultInputChannels = true;
  298. else
  299. config.useDefaultOutputChannels = true;
  300. error = setup.manager->setAudioDeviceSetup (config, true);
  301. showCorrectDeviceName (inputDeviceDropDown.get(), true);
  302. showCorrectDeviceName (outputDeviceDropDown.get(), false);
  303. updateControlPanelButton();
  304. resized();
  305. }
  306. else if (updateSampleRate)
  307. {
  308. if (sampleRateDropDown->getSelectedId() > 0)
  309. {
  310. config.sampleRate = sampleRateDropDown->getSelectedId();
  311. error = setup.manager->setAudioDeviceSetup (config, true);
  312. }
  313. }
  314. else if (updateBufferSize)
  315. {
  316. if (bufferSizeDropDown->getSelectedId() > 0)
  317. {
  318. config.bufferSize = bufferSizeDropDown->getSelectedId();
  319. error = setup.manager->setAudioDeviceSetup (config, true);
  320. }
  321. }
  322. if (error.isNotEmpty())
  323. messageBox = AlertWindow::showScopedAsync (MessageBoxOptions().withIconType (MessageBoxIconType::WarningIcon)
  324. .withTitle (TRANS ("Error when trying to open audio device!"))
  325. .withMessage (error)
  326. .withButton (TRANS ("OK")),
  327. nullptr);
  328. }
  329. bool showDeviceControlPanel()
  330. {
  331. if (auto* device = setup.manager->getCurrentAudioDevice())
  332. {
  333. Component modalWindow;
  334. modalWindow.setOpaque (true);
  335. modalWindow.addToDesktop (0);
  336. modalWindow.enterModalState();
  337. return device->showControlPanel();
  338. }
  339. return false;
  340. }
  341. void toggleAdvancedSettings()
  342. {
  343. showAdvancedSettingsButton->setButtonText ((showAdvancedSettingsButton->getToggleState() ? "Hide " : "Show ")
  344. + String ("advanced settings..."));
  345. resized();
  346. }
  347. void showDeviceUIPanel()
  348. {
  349. if (showDeviceControlPanel())
  350. {
  351. setup.manager->closeAudioDevice();
  352. setup.manager->restartLastAudioDevice();
  353. getTopLevelComponent()->toFront (true);
  354. }
  355. }
  356. void playTestSound()
  357. {
  358. setup.manager->playTestSound();
  359. }
  360. void updateAllControls()
  361. {
  362. updateOutputsComboBox();
  363. updateInputsComboBox();
  364. updateControlPanelButton();
  365. updateResetButton();
  366. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  367. {
  368. if (setup.maxNumOutputChannels > 0
  369. && setup.minNumOutputChannels < setup.manager->getCurrentAudioDevice()->getOutputChannelNames().size())
  370. {
  371. if (outputChanList == nullptr)
  372. {
  373. outputChanList = std::make_unique<ChannelSelectorListBox> (setup, ChannelSelectorListBox::audioOutputType,
  374. TRANS ("(no audio output channels found)"));
  375. addAndMakeVisible (outputChanList.get());
  376. outputChanLabel = std::make_unique<Label> (String{}, TRANS ("Active output channels:"));
  377. outputChanLabel->setJustificationType (Justification::centredRight);
  378. outputChanLabel->attachToComponent (outputChanList.get(), true);
  379. }
  380. outputChanList->refresh();
  381. }
  382. else
  383. {
  384. outputChanLabel.reset();
  385. outputChanList.reset();
  386. }
  387. if (setup.maxNumInputChannels > 0
  388. && setup.minNumInputChannels < setup.manager->getCurrentAudioDevice()->getInputChannelNames().size())
  389. {
  390. if (inputChanList == nullptr)
  391. {
  392. inputChanList = std::make_unique<ChannelSelectorListBox> (setup, ChannelSelectorListBox::audioInputType,
  393. TRANS ("(no audio input channels found)"));
  394. addAndMakeVisible (inputChanList.get());
  395. inputChanLabel = std::make_unique<Label> (String{}, TRANS ("Active input channels:"));
  396. inputChanLabel->setJustificationType (Justification::centredRight);
  397. inputChanLabel->attachToComponent (inputChanList.get(), true);
  398. }
  399. inputChanList->refresh();
  400. }
  401. else
  402. {
  403. inputChanLabel.reset();
  404. inputChanList.reset();
  405. }
  406. updateSampleRateComboBox (currentDevice);
  407. updateBufferSizeComboBox (currentDevice);
  408. }
  409. else
  410. {
  411. jassert (setup.manager->getCurrentAudioDevice() == nullptr); // not the correct device type!
  412. inputChanLabel.reset();
  413. outputChanLabel.reset();
  414. sampleRateLabel.reset();
  415. bufferSizeLabel.reset();
  416. inputChanList.reset();
  417. outputChanList.reset();
  418. sampleRateDropDown.reset();
  419. bufferSizeDropDown.reset();
  420. if (outputDeviceDropDown != nullptr)
  421. outputDeviceDropDown->setSelectedId (-1, dontSendNotification);
  422. if (inputDeviceDropDown != nullptr)
  423. inputDeviceDropDown->setSelectedId (-1, dontSendNotification);
  424. }
  425. sendLookAndFeelChange();
  426. resized();
  427. setSize (getWidth(), getLowestY() + 4);
  428. }
  429. void changeListenerCallback (ChangeBroadcaster*) override
  430. {
  431. updateAllControls();
  432. }
  433. void resetDevice()
  434. {
  435. setup.manager->closeAudioDevice();
  436. setup.manager->restartLastAudioDevice();
  437. }
  438. private:
  439. AudioIODeviceType& type;
  440. const AudioDeviceSetupDetails setup;
  441. AudioDeviceSelectorComponent& parent;
  442. std::unique_ptr<ComboBox> outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown;
  443. std::unique_ptr<Label> outputDeviceLabel, inputDeviceLabel, sampleRateLabel, bufferSizeLabel, inputChanLabel, outputChanLabel;
  444. std::unique_ptr<TextButton> testButton;
  445. std::unique_ptr<Component> inputLevelMeter;
  446. std::unique_ptr<TextButton> showUIButton, showAdvancedSettingsButton, resetDeviceButton;
  447. void showCorrectDeviceName (ComboBox* box, bool isInput)
  448. {
  449. if (box != nullptr)
  450. {
  451. auto* currentDevice = setup.manager->getCurrentAudioDevice();
  452. auto index = type.getIndexOfDevice (currentDevice, isInput);
  453. box->setSelectedId (index < 0 ? index : index + 1, dontSendNotification);
  454. if (testButton != nullptr && ! isInput)
  455. testButton->setEnabled (index >= 0);
  456. }
  457. }
  458. void addNamesToDeviceBox (ComboBox& combo, bool isInputs)
  459. {
  460. const StringArray devs (type.getDeviceNames (isInputs));
  461. combo.clear (dontSendNotification);
  462. for (int i = 0; i < devs.size(); ++i)
  463. combo.addItem (devs[i], i + 1);
  464. combo.addItem (getNoDeviceString(), -1);
  465. combo.setSelectedId (-1, dontSendNotification);
  466. }
  467. int getLowestY() const
  468. {
  469. int y = 0;
  470. for (auto* c : getChildren())
  471. y = jmax (y, c->getBottom());
  472. return y;
  473. }
  474. void updateControlPanelButton()
  475. {
  476. auto* currentDevice = setup.manager->getCurrentAudioDevice();
  477. showUIButton.reset();
  478. if (currentDevice != nullptr && currentDevice->hasControlPanel())
  479. {
  480. showUIButton = std::make_unique<TextButton> (TRANS ("Control Panel"),
  481. TRANS ("Opens the device's own control panel"));
  482. addAndMakeVisible (showUIButton.get());
  483. showUIButton->onClick = [this] { showDeviceUIPanel(); };
  484. }
  485. resized();
  486. }
  487. void updateResetButton()
  488. {
  489. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  490. {
  491. if (currentDevice->hasControlPanel())
  492. {
  493. if (resetDeviceButton == nullptr)
  494. {
  495. resetDeviceButton = std::make_unique<TextButton> (TRANS ("Reset Device"),
  496. TRANS ("Resets the audio interface - sometimes needed after changing a device's properties in its custom control panel"));
  497. addAndMakeVisible (resetDeviceButton.get());
  498. resetDeviceButton->onClick = [this] { resetDevice(); };
  499. resized();
  500. }
  501. return;
  502. }
  503. }
  504. resetDeviceButton.reset();
  505. }
  506. void updateOutputsComboBox()
  507. {
  508. if (setup.maxNumOutputChannels > 0 || ! type.hasSeparateInputsAndOutputs())
  509. {
  510. if (outputDeviceDropDown == nullptr)
  511. {
  512. outputDeviceDropDown = std::make_unique<ComboBox>();
  513. outputDeviceDropDown->onChange = [this] { updateConfig (true, false, false, false); };
  514. addAndMakeVisible (outputDeviceDropDown.get());
  515. outputDeviceLabel = std::make_unique<Label> (String{}, type.hasSeparateInputsAndOutputs() ? TRANS ("Output:")
  516. : TRANS ("Device:"));
  517. outputDeviceLabel->attachToComponent (outputDeviceDropDown.get(), true);
  518. if (setup.maxNumOutputChannels > 0)
  519. {
  520. testButton = std::make_unique<TextButton> (TRANS ("Test"), TRANS ("Plays a test tone"));
  521. addAndMakeVisible (testButton.get());
  522. testButton->onClick = [this] { playTestSound(); };
  523. }
  524. }
  525. addNamesToDeviceBox (*outputDeviceDropDown, false);
  526. }
  527. showCorrectDeviceName (outputDeviceDropDown.get(), false);
  528. }
  529. void updateInputsComboBox()
  530. {
  531. if (setup.maxNumInputChannels > 0 && type.hasSeparateInputsAndOutputs())
  532. {
  533. if (inputDeviceDropDown == nullptr)
  534. {
  535. inputDeviceDropDown = std::make_unique<ComboBox>();
  536. inputDeviceDropDown->onChange = [this] { updateConfig (false, true, false, false); };
  537. addAndMakeVisible (inputDeviceDropDown.get());
  538. inputDeviceLabel = std::make_unique<Label> (String{}, TRANS ("Input:"));
  539. inputDeviceLabel->attachToComponent (inputDeviceDropDown.get(), true);
  540. inputLevelMeter = std::make_unique<SimpleDeviceManagerInputLevelMeter> (*setup.manager);
  541. addAndMakeVisible (inputLevelMeter.get());
  542. }
  543. addNamesToDeviceBox (*inputDeviceDropDown, true);
  544. }
  545. showCorrectDeviceName (inputDeviceDropDown.get(), true);
  546. }
  547. void updateSampleRateComboBox (AudioIODevice* currentDevice)
  548. {
  549. if (sampleRateDropDown == nullptr)
  550. {
  551. sampleRateDropDown = std::make_unique<ComboBox>();
  552. addAndMakeVisible (sampleRateDropDown.get());
  553. sampleRateLabel = std::make_unique<Label> (String{}, TRANS ("Sample rate:"));
  554. sampleRateLabel->attachToComponent (sampleRateDropDown.get(), true);
  555. }
  556. else
  557. {
  558. sampleRateDropDown->clear();
  559. sampleRateDropDown->onChange = nullptr;
  560. }
  561. const auto getFrequencyString = [] (int rate) { return String (rate) + " Hz"; };
  562. for (auto rate : currentDevice->getAvailableSampleRates())
  563. {
  564. const auto intRate = roundToInt (rate);
  565. sampleRateDropDown->addItem (getFrequencyString (intRate), intRate);
  566. }
  567. const auto intRate = roundToInt (currentDevice->getCurrentSampleRate());
  568. sampleRateDropDown->setText (getFrequencyString (intRate), dontSendNotification);
  569. sampleRateDropDown->onChange = [this] { updateConfig (false, false, true, false); };
  570. }
  571. void updateBufferSizeComboBox (AudioIODevice* currentDevice)
  572. {
  573. if (bufferSizeDropDown == nullptr)
  574. {
  575. bufferSizeDropDown = std::make_unique<ComboBox>();
  576. addAndMakeVisible (bufferSizeDropDown.get());
  577. bufferSizeLabel = std::make_unique<Label> (String{}, TRANS ("Audio buffer size:"));
  578. bufferSizeLabel->attachToComponent (bufferSizeDropDown.get(), true);
  579. }
  580. else
  581. {
  582. bufferSizeDropDown->clear();
  583. bufferSizeDropDown->onChange = nullptr;
  584. }
  585. auto currentRate = currentDevice->getCurrentSampleRate();
  586. if (exactlyEqual (currentRate, 0.0))
  587. currentRate = 48000.0;
  588. for (auto bs : currentDevice->getAvailableBufferSizes())
  589. bufferSizeDropDown->addItem (String (bs) + " samples (" + String (bs * 1000.0 / currentRate, 1) + " ms)", bs);
  590. bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), dontSendNotification);
  591. bufferSizeDropDown->onChange = [this] { updateConfig (false, false, false, true); };
  592. }
  593. public:
  594. //==============================================================================
  595. class ChannelSelectorListBox : public ListBox,
  596. private ListBoxModel
  597. {
  598. public:
  599. enum BoxType
  600. {
  601. audioInputType,
  602. audioOutputType
  603. };
  604. //==============================================================================
  605. ChannelSelectorListBox (const AudioDeviceSetupDetails& setupDetails, BoxType boxType, const String& noItemsText)
  606. : ListBox ({}, nullptr), setup (setupDetails), type (boxType), noItemsMessage (noItemsText)
  607. {
  608. refresh();
  609. setModel (this);
  610. setOutlineThickness (1);
  611. }
  612. void refresh()
  613. {
  614. items.clear();
  615. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  616. {
  617. if (type == audioInputType)
  618. items = currentDevice->getInputChannelNames();
  619. else if (type == audioOutputType)
  620. items = currentDevice->getOutputChannelNames();
  621. if (setup.useStereoPairs)
  622. {
  623. StringArray pairs;
  624. for (int i = 0; i < items.size(); i += 2)
  625. {
  626. auto& name = items[i];
  627. if (i + 1 >= items.size())
  628. pairs.add (name.trim());
  629. else
  630. pairs.add (getNameForChannelPair (name, items[i + 1]));
  631. }
  632. items = pairs;
  633. }
  634. }
  635. updateContent();
  636. repaint();
  637. }
  638. int getNumRows() override
  639. {
  640. return items.size();
  641. }
  642. void paintListBoxItem (int row, Graphics& g, int width, int height, bool) override
  643. {
  644. if (isPositiveAndBelow (row, items.size()))
  645. {
  646. g.fillAll (findColour (ListBox::backgroundColourId));
  647. auto item = items[row];
  648. bool enabled = false;
  649. auto config = setup.manager->getAudioDeviceSetup();
  650. if (setup.useStereoPairs)
  651. {
  652. if (type == audioInputType)
  653. enabled = config.inputChannels[row * 2] || config.inputChannels[row * 2 + 1];
  654. else if (type == audioOutputType)
  655. enabled = config.outputChannels[row * 2] || config.outputChannels[row * 2 + 1];
  656. }
  657. else
  658. {
  659. if (type == audioInputType)
  660. enabled = config.inputChannels[row];
  661. else if (type == audioOutputType)
  662. enabled = config.outputChannels[row];
  663. }
  664. auto x = getTickX();
  665. auto tickW = (float) height * 0.75f;
  666. getLookAndFeel().drawTickBox (g, *this, (float) x - tickW, ((float) height - tickW) * 0.5f, tickW, tickW,
  667. enabled, true, true, false);
  668. drawTextLayout (g, *this, item, { x + 5, 0, width - x - 5, height }, enabled);
  669. }
  670. }
  671. void listBoxItemClicked (int row, const MouseEvent& e) override
  672. {
  673. selectRow (row);
  674. if (e.x < getTickX())
  675. flipEnablement (row);
  676. }
  677. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  678. {
  679. flipEnablement (row);
  680. }
  681. void returnKeyPressed (int row) override
  682. {
  683. flipEnablement (row);
  684. }
  685. void paint (Graphics& g) override
  686. {
  687. ListBox::paint (g);
  688. if (items.isEmpty())
  689. {
  690. g.setColour (Colours::grey);
  691. g.setFont (0.5f * (float) getRowHeight());
  692. g.drawText (noItemsMessage,
  693. 0, 0, getWidth(), getHeight() / 2,
  694. Justification::centred, true);
  695. }
  696. }
  697. int getBestHeight (int maxHeight)
  698. {
  699. return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()),
  700. getNumRows())
  701. + getOutlineThickness() * 2;
  702. }
  703. private:
  704. //==============================================================================
  705. const AudioDeviceSetupDetails setup;
  706. const BoxType type;
  707. const String noItemsMessage;
  708. StringArray items;
  709. static String getNameForChannelPair (const String& name1, const String& name2)
  710. {
  711. String commonBit;
  712. for (int j = 0; j < name1.length(); ++j)
  713. if (name1.substring (0, j).equalsIgnoreCase (name2.substring (0, j)))
  714. commonBit = name1.substring (0, j);
  715. // Make sure we only split the name at a space, because otherwise, things
  716. // like "input 11" + "input 12" would become "input 11 + 2"
  717. while (commonBit.isNotEmpty() && ! CharacterFunctions::isWhitespace (commonBit.getLastCharacter()))
  718. commonBit = commonBit.dropLastCharacters (1);
  719. return name1.trim() + " + " + name2.substring (commonBit.length()).trim();
  720. }
  721. void flipEnablement (int row)
  722. {
  723. jassert (type == audioInputType || type == audioOutputType);
  724. if (isPositiveAndBelow (row, items.size()))
  725. {
  726. auto config = setup.manager->getAudioDeviceSetup();
  727. if (setup.useStereoPairs)
  728. {
  729. BigInteger bits;
  730. auto& original = (type == audioInputType ? config.inputChannels
  731. : config.outputChannels);
  732. for (int i = 0; i < 256; i += 2)
  733. bits.setBit (i / 2, original[i] || original[i + 1]);
  734. if (type == audioInputType)
  735. {
  736. config.useDefaultInputChannels = false;
  737. flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2);
  738. }
  739. else
  740. {
  741. config.useDefaultOutputChannels = false;
  742. flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2);
  743. }
  744. for (int i = 0; i < 256; ++i)
  745. original.setBit (i, bits[i / 2]);
  746. }
  747. else
  748. {
  749. if (type == audioInputType)
  750. {
  751. config.useDefaultInputChannels = false;
  752. flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels);
  753. }
  754. else
  755. {
  756. config.useDefaultOutputChannels = false;
  757. flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels);
  758. }
  759. }
  760. setup.manager->setAudioDeviceSetup (config, true);
  761. }
  762. }
  763. static void flipBit (BigInteger& chans, int index, int minNumber, int maxNumber)
  764. {
  765. auto numActive = chans.countNumberOfSetBits();
  766. if (chans[index])
  767. {
  768. if (numActive > minNumber)
  769. chans.setBit (index, false);
  770. }
  771. else
  772. {
  773. if (numActive >= maxNumber)
  774. {
  775. auto firstActiveChan = chans.findNextSetBit (0);
  776. chans.clearBit (index > firstActiveChan ? firstActiveChan : chans.getHighestBit());
  777. }
  778. chans.setBit (index, true);
  779. }
  780. }
  781. int getTickX() const
  782. {
  783. return getRowHeight();
  784. }
  785. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelSelectorListBox)
  786. };
  787. private:
  788. std::unique_ptr<ChannelSelectorListBox> inputChanList, outputChanList;
  789. ScopedMessageBox messageBox;
  790. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSettingsPanel)
  791. };
  792. //==============================================================================
  793. AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& dm,
  794. int minInputChannelsToUse,
  795. int maxInputChannelsToUse,
  796. int minOutputChannelsToUse,
  797. int maxOutputChannelsToUse,
  798. bool showMidiInputOptions,
  799. bool showMidiOutputSelector,
  800. bool showChannelsAsStereoPairsToUse,
  801. bool hideAdvancedOptionsWithButtonToUse)
  802. : deviceManager (dm),
  803. itemHeight (24),
  804. minOutputChannels (minOutputChannelsToUse),
  805. maxOutputChannels (maxOutputChannelsToUse),
  806. minInputChannels (minInputChannelsToUse),
  807. maxInputChannels (maxInputChannelsToUse),
  808. showChannelsAsStereoPairs (showChannelsAsStereoPairsToUse),
  809. hideAdvancedOptionsWithButton (hideAdvancedOptionsWithButtonToUse)
  810. {
  811. jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
  812. jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);
  813. const OwnedArray<AudioIODeviceType>& types = deviceManager.getAvailableDeviceTypes();
  814. if (types.size() > 1)
  815. {
  816. deviceTypeDropDown = std::make_unique<ComboBox>();
  817. for (int i = 0; i < types.size(); ++i)
  818. deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1);
  819. addAndMakeVisible (deviceTypeDropDown.get());
  820. deviceTypeDropDown->onChange = [this] { updateDeviceType(); };
  821. deviceTypeDropDownLabel = std::make_unique<Label> (String{}, TRANS ("Audio device type:"));
  822. deviceTypeDropDownLabel->setJustificationType (Justification::centredRight);
  823. deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown.get(), true);
  824. }
  825. if (showMidiInputOptions)
  826. {
  827. midiInputsList = std::make_unique <MidiInputSelectorComponentListBox> (deviceManager,
  828. "(" + TRANS ("No MIDI inputs available") + ")");
  829. addAndMakeVisible (midiInputsList.get());
  830. midiInputsLabel = std::make_unique<Label> (String{}, TRANS ("Active MIDI inputs:"));
  831. midiInputsLabel->setJustificationType (Justification::topRight);
  832. midiInputsLabel->attachToComponent (midiInputsList.get(), true);
  833. if (BluetoothMidiDevicePairingDialogue::isAvailable())
  834. {
  835. bluetoothButton = std::make_unique<TextButton> (TRANS ("Bluetooth MIDI"), TRANS ("Scan for bluetooth MIDI devices"));
  836. addAndMakeVisible (bluetoothButton.get());
  837. bluetoothButton->onClick = [this] { handleBluetoothButton(); };
  838. }
  839. }
  840. else
  841. {
  842. midiInputsList.reset();
  843. midiInputsLabel.reset();
  844. bluetoothButton.reset();
  845. }
  846. if (showMidiOutputSelector)
  847. {
  848. midiOutputSelector = std::make_unique<ComboBox>();
  849. addAndMakeVisible (midiOutputSelector.get());
  850. midiOutputSelector->onChange = [this] { updateMidiOutput(); };
  851. midiOutputLabel = std::make_unique<Label> ("lm", TRANS ("MIDI Output:"));
  852. midiOutputLabel->attachToComponent (midiOutputSelector.get(), true);
  853. }
  854. else
  855. {
  856. midiOutputSelector.reset();
  857. midiOutputLabel.reset();
  858. }
  859. deviceManager.addChangeListener (this);
  860. updateAllControls();
  861. }
  862. AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent()
  863. {
  864. deviceManager.removeChangeListener (this);
  865. }
  866. void AudioDeviceSelectorComponent::setItemHeight (int newItemHeight)
  867. {
  868. itemHeight = newItemHeight;
  869. resized();
  870. }
  871. void AudioDeviceSelectorComponent::resized()
  872. {
  873. Rectangle<int> r (proportionOfWidth (0.35f), 15, proportionOfWidth (0.6f), 3000);
  874. auto space = itemHeight / 4;
  875. if (deviceTypeDropDown != nullptr)
  876. {
  877. deviceTypeDropDown->setBounds (r.removeFromTop (itemHeight));
  878. r.removeFromTop (space * 3);
  879. }
  880. if (audioDeviceSettingsComp != nullptr)
  881. {
  882. audioDeviceSettingsComp->resized();
  883. audioDeviceSettingsComp->setBounds (r.removeFromTop (audioDeviceSettingsComp->getHeight())
  884. .withX (0).withWidth (getWidth()));
  885. r.removeFromTop (space);
  886. }
  887. if (midiInputsList != nullptr)
  888. {
  889. midiInputsList->setRowHeight (jmin (22, itemHeight));
  890. midiInputsList->setBounds (r.removeFromTop (midiInputsList->getBestHeight (jmin (itemHeight * 8,
  891. getHeight() - r.getY() - space - itemHeight))));
  892. r.removeFromTop (space);
  893. }
  894. if (bluetoothButton != nullptr)
  895. {
  896. bluetoothButton->setBounds (r.removeFromTop (24));
  897. r.removeFromTop (space);
  898. }
  899. if (midiOutputSelector != nullptr)
  900. midiOutputSelector->setBounds (r.removeFromTop (itemHeight));
  901. r.removeFromTop (itemHeight);
  902. setSize (getWidth(), r.getY());
  903. }
  904. void AudioDeviceSelectorComponent::updateDeviceType()
  905. {
  906. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1])
  907. {
  908. audioDeviceSettingsComp.reset();
  909. deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true);
  910. updateAllControls(); // needed in case the type hasn't actually changed
  911. }
  912. }
  913. void AudioDeviceSelectorComponent::updateMidiOutput()
  914. {
  915. auto selectedId = midiOutputSelector->getSelectedId();
  916. if (selectedId == -1)
  917. deviceManager.setDefaultMidiOutputDevice ({});
  918. else
  919. deviceManager.setDefaultMidiOutputDevice (currentMidiOutputs[selectedId - 1].identifier);
  920. }
  921. void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*)
  922. {
  923. updateAllControls();
  924. }
  925. void AudioDeviceSelectorComponent::updateAllControls()
  926. {
  927. if (deviceTypeDropDown != nullptr)
  928. deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), dontSendNotification);
  929. if (audioDeviceSettingsComp == nullptr
  930. || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType())
  931. {
  932. audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType();
  933. audioDeviceSettingsComp.reset();
  934. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == nullptr
  935. ? 0 : deviceTypeDropDown->getSelectedId() - 1])
  936. {
  937. AudioDeviceSetupDetails details;
  938. details.manager = &deviceManager;
  939. details.minNumInputChannels = minInputChannels;
  940. details.maxNumInputChannels = maxInputChannels;
  941. details.minNumOutputChannels = minOutputChannels;
  942. details.maxNumOutputChannels = maxOutputChannels;
  943. details.useStereoPairs = showChannelsAsStereoPairs;
  944. audioDeviceSettingsComp = std::make_unique<AudioDeviceSettingsPanel> (*type, details, hideAdvancedOptionsWithButton, *this);
  945. addAndMakeVisible (audioDeviceSettingsComp.get());
  946. }
  947. }
  948. if (midiInputsList != nullptr)
  949. {
  950. midiInputsList->updateDevices();
  951. midiInputsList->updateContent();
  952. midiInputsList->repaint();
  953. }
  954. if (midiOutputSelector != nullptr)
  955. {
  956. midiOutputSelector->clear();
  957. currentMidiOutputs = MidiOutput::getAvailableDevices();
  958. midiOutputSelector->addItem (getNoDeviceString(), -1);
  959. midiOutputSelector->addSeparator();
  960. auto defaultOutputIdentifier = deviceManager.getDefaultMidiOutputIdentifier();
  961. int i = 0;
  962. for (auto& out : currentMidiOutputs)
  963. {
  964. midiOutputSelector->addItem (out.name, i + 1);
  965. if (defaultOutputIdentifier.isNotEmpty() && out.identifier == defaultOutputIdentifier)
  966. midiOutputSelector->setSelectedId (i + 1);
  967. ++i;
  968. }
  969. }
  970. resized();
  971. }
  972. void AudioDeviceSelectorComponent::handleBluetoothButton()
  973. {
  974. if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  975. {
  976. BluetoothMidiDevicePairingDialogue::open();
  977. }
  978. else
  979. {
  980. RuntimePermissions::request (RuntimePermissions::bluetoothMidi, [] (auto)
  981. {
  982. if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  983. BluetoothMidiDevicePairingDialogue::open();
  984. });
  985. }
  986. }
  987. ListBox* AudioDeviceSelectorComponent::getMidiInputSelectorListBox() const noexcept
  988. {
  989. return midiInputsList.get();
  990. }
  991. } // namespace juce