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.

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