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.

1220 lines
43KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2020 - 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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-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. ~SimpleDeviceManagerInputLevelMeter() override
  29. {
  30. }
  31. void timerCallback() override
  32. {
  33. if (isShowing())
  34. {
  35. auto newLevel = (float) inputLevelGetter->getCurrentLevel();
  36. if (std::abs (level - newLevel) > 0.005f)
  37. {
  38. level = newLevel;
  39. repaint();
  40. }
  41. }
  42. else
  43. {
  44. level = 0;
  45. }
  46. }
  47. void paint (Graphics& g) override
  48. {
  49. // (add a bit of a skew to make the level more obvious)
  50. getLookAndFeel().drawLevelMeter (g, getWidth(), getHeight(),
  51. (float) std::exp (std::log (level) / 3.0));
  52. }
  53. AudioDeviceManager& manager;
  54. AudioDeviceManager::LevelMeter::Ptr inputLevelGetter;
  55. float level = 0;
  56. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleDeviceManagerInputLevelMeter)
  57. };
  58. static void drawTextLayout (Graphics& g, Component& owner, StringRef text, const Rectangle<int>& textBounds, bool enabled)
  59. {
  60. const auto textColour = owner.findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f);
  61. AttributedString attributedString { text };
  62. attributedString.setColour (textColour);
  63. attributedString.setFont ((float) textBounds.getHeight() * 0.6f);
  64. attributedString.setJustification (Justification::centredLeft);
  65. attributedString.setWordWrap (AttributedString::WordWrap::none);
  66. TextLayout textLayout;
  67. textLayout.createLayout (attributedString,
  68. (float) textBounds.getWidth(),
  69. (float) textBounds.getHeight());
  70. textLayout.draw (g, textBounds.toFloat());
  71. }
  72. //==============================================================================
  73. class AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox : public ListBox,
  74. private ListBoxModel
  75. {
  76. public:
  77. MidiInputSelectorComponentListBox (AudioDeviceManager& dm, const String& noItems)
  78. : ListBox ({}, nullptr),
  79. deviceManager (dm),
  80. noItemsMessage (noItems)
  81. {
  82. updateDevices();
  83. setModel (this);
  84. setOutlineThickness (1);
  85. }
  86. void updateDevices()
  87. {
  88. items = MidiInput::getAvailableDevices();
  89. }
  90. int getNumRows() override
  91. {
  92. return items.size();
  93. }
  94. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  95. {
  96. if (isPositiveAndBelow (row, items.size()))
  97. {
  98. if (rowIsSelected)
  99. g.fillAll (findColour (TextEditor::highlightColourId)
  100. .withMultipliedAlpha (0.3f));
  101. auto item = items[row];
  102. bool enabled = deviceManager.isMidiInputDeviceEnabled (item.identifier);
  103. auto x = getTickX();
  104. auto tickW = (float) height * 0.75f;
  105. getLookAndFeel().drawTickBox (g, *this, (float) x - tickW, ((float) height - tickW) * 0.5f, tickW, tickW,
  106. enabled, true, true, false);
  107. drawTextLayout (g, *this, item.name, { x + 5, 0, width - x - 5, height }, enabled);
  108. }
  109. }
  110. void listBoxItemClicked (int row, const MouseEvent& e) override
  111. {
  112. selectRow (row);
  113. if (e.x < getTickX())
  114. flipEnablement (row);
  115. }
  116. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  117. {
  118. flipEnablement (row);
  119. }
  120. void returnKeyPressed (int row) override
  121. {
  122. flipEnablement (row);
  123. }
  124. void paint (Graphics& g) override
  125. {
  126. ListBox::paint (g);
  127. if (items.isEmpty())
  128. {
  129. g.setColour (Colours::grey);
  130. g.setFont (0.5f * (float) getRowHeight());
  131. g.drawText (noItemsMessage,
  132. 0, 0, getWidth(), getHeight() / 2,
  133. Justification::centred, true);
  134. }
  135. }
  136. int getBestHeight (int preferredHeight)
  137. {
  138. auto extra = getOutlineThickness() * 2;
  139. return jmax (getRowHeight() * 2 + extra,
  140. jmin (getRowHeight() * getNumRows() + extra,
  141. preferredHeight));
  142. }
  143. private:
  144. //==============================================================================
  145. AudioDeviceManager& deviceManager;
  146. const String noItemsMessage;
  147. Array<MidiDeviceInfo> items;
  148. void flipEnablement (const int row)
  149. {
  150. if (isPositiveAndBelow (row, items.size()))
  151. {
  152. auto identifier = items[row].identifier;
  153. deviceManager.setMidiInputDeviceEnabled (identifier, ! deviceManager.isMidiInputDeviceEnabled (identifier));
  154. }
  155. }
  156. int getTickX() const
  157. {
  158. return getRowHeight();
  159. }
  160. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInputSelectorComponentListBox)
  161. };
  162. //==============================================================================
  163. struct AudioDeviceSetupDetails
  164. {
  165. AudioDeviceManager* manager;
  166. int minNumInputChannels, maxNumInputChannels;
  167. int minNumOutputChannels, maxNumOutputChannels;
  168. bool useStereoPairs;
  169. };
  170. static String getNoDeviceString() { return "<< " + TRANS("none") + " >>"; }
  171. //==============================================================================
  172. class AudioDeviceSettingsPanel : public Component,
  173. private ChangeListener
  174. {
  175. public:
  176. AudioDeviceSettingsPanel (AudioIODeviceType& t, AudioDeviceSetupDetails& setupDetails,
  177. const bool hideAdvancedOptionsWithButton)
  178. : type (t), setup (setupDetails)
  179. {
  180. if (hideAdvancedOptionsWithButton)
  181. {
  182. showAdvancedSettingsButton.reset (new TextButton (TRANS("Show advanced settings...")));
  183. addAndMakeVisible (showAdvancedSettingsButton.get());
  184. showAdvancedSettingsButton->setClickingTogglesState (true);
  185. showAdvancedSettingsButton->onClick = [this] { toggleAdvancedSettings(); };
  186. }
  187. type.scanForDevices();
  188. setup.manager->addChangeListener (this);
  189. }
  190. ~AudioDeviceSettingsPanel() override
  191. {
  192. setup.manager->removeChangeListener (this);
  193. }
  194. void resized() override
  195. {
  196. if (auto* parent = findParentComponentOfClass<AudioDeviceSelectorComponent>())
  197. {
  198. Rectangle<int> r (proportionOfWidth (0.35f), 0, proportionOfWidth (0.6f), 3000);
  199. const int maxListBoxHeight = 100;
  200. const int h = parent->getItemHeight();
  201. const int space = h / 4;
  202. if (outputDeviceDropDown != nullptr)
  203. {
  204. auto row = r.removeFromTop (h);
  205. if (testButton != nullptr)
  206. {
  207. testButton->changeWidthToFitText (h);
  208. testButton->setBounds (row.removeFromRight (testButton->getWidth()));
  209. row.removeFromRight (space);
  210. }
  211. outputDeviceDropDown->setBounds (row);
  212. r.removeFromTop (space);
  213. }
  214. if (inputDeviceDropDown != nullptr)
  215. {
  216. auto row = r.removeFromTop (h);
  217. inputLevelMeter->setBounds (row.removeFromRight (testButton != nullptr ? testButton->getWidth() : row.getWidth() / 6));
  218. row.removeFromRight (space);
  219. inputDeviceDropDown->setBounds (row);
  220. r.removeFromTop (space);
  221. }
  222. if (outputChanList != nullptr)
  223. {
  224. outputChanList->setRowHeight (jmin (22, h));
  225. outputChanList->setBounds (r.removeFromTop (outputChanList->getBestHeight (maxListBoxHeight)));
  226. outputChanLabel->setBounds (0, outputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  227. r.removeFromTop (space);
  228. }
  229. if (inputChanList != nullptr)
  230. {
  231. inputChanList->setRowHeight (jmin (22, h));
  232. inputChanList->setBounds (r.removeFromTop (inputChanList->getBestHeight (maxListBoxHeight)));
  233. inputChanLabel->setBounds (0, inputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  234. r.removeFromTop (space);
  235. }
  236. r.removeFromTop (space * 2);
  237. if (showAdvancedSettingsButton != nullptr
  238. && sampleRateDropDown != nullptr && bufferSizeDropDown != nullptr)
  239. {
  240. showAdvancedSettingsButton->setBounds (r.removeFromTop (h));
  241. r.removeFromTop (space);
  242. showAdvancedSettingsButton->changeWidthToFitText();
  243. }
  244. auto advancedSettingsVisible = showAdvancedSettingsButton == nullptr
  245. || showAdvancedSettingsButton->getToggleState();
  246. if (sampleRateDropDown != nullptr)
  247. {
  248. sampleRateDropDown->setVisible (advancedSettingsVisible);
  249. if (advancedSettingsVisible)
  250. {
  251. sampleRateDropDown->setBounds (r.removeFromTop (h));
  252. r.removeFromTop (space);
  253. }
  254. }
  255. if (bufferSizeDropDown != nullptr)
  256. {
  257. bufferSizeDropDown->setVisible (advancedSettingsVisible);
  258. if (advancedSettingsVisible)
  259. {
  260. bufferSizeDropDown->setBounds (r.removeFromTop (h));
  261. r.removeFromTop (space);
  262. }
  263. }
  264. r.removeFromTop (space);
  265. if (showUIButton != nullptr || resetDeviceButton != nullptr)
  266. {
  267. auto buttons = r.removeFromTop (h);
  268. if (showUIButton != nullptr)
  269. {
  270. showUIButton->setVisible (advancedSettingsVisible);
  271. showUIButton->changeWidthToFitText (h);
  272. showUIButton->setBounds (buttons.removeFromLeft (showUIButton->getWidth()));
  273. buttons.removeFromLeft (space);
  274. }
  275. if (resetDeviceButton != nullptr)
  276. {
  277. resetDeviceButton->setVisible (advancedSettingsVisible);
  278. resetDeviceButton->changeWidthToFitText (h);
  279. resetDeviceButton->setBounds (buttons.removeFromLeft (resetDeviceButton->getWidth()));
  280. }
  281. r.removeFromTop (space);
  282. }
  283. setSize (getWidth(), r.getY());
  284. }
  285. else
  286. {
  287. jassertfalse;
  288. }
  289. }
  290. void updateConfig (bool updateOutputDevice, bool updateInputDevice, bool updateSampleRate, bool updateBufferSize)
  291. {
  292. auto config = setup.manager->getAudioDeviceSetup();
  293. String error;
  294. if (updateOutputDevice || updateInputDevice)
  295. {
  296. if (outputDeviceDropDown != nullptr)
  297. config.outputDeviceName = outputDeviceDropDown->getSelectedId() < 0 ? String()
  298. : outputDeviceDropDown->getText();
  299. if (inputDeviceDropDown != nullptr)
  300. config.inputDeviceName = inputDeviceDropDown->getSelectedId() < 0 ? String()
  301. : inputDeviceDropDown->getText();
  302. if (! type.hasSeparateInputsAndOutputs())
  303. config.inputDeviceName = config.outputDeviceName;
  304. if (updateInputDevice)
  305. config.useDefaultInputChannels = true;
  306. else
  307. config.useDefaultOutputChannels = true;
  308. error = setup.manager->setAudioDeviceSetup (config, true);
  309. showCorrectDeviceName (inputDeviceDropDown.get(), true);
  310. showCorrectDeviceName (outputDeviceDropDown.get(), false);
  311. updateControlPanelButton();
  312. resized();
  313. }
  314. else if (updateSampleRate)
  315. {
  316. if (sampleRateDropDown->getSelectedId() > 0)
  317. {
  318. config.sampleRate = sampleRateDropDown->getSelectedId();
  319. error = setup.manager->setAudioDeviceSetup (config, true);
  320. }
  321. }
  322. else if (updateBufferSize)
  323. {
  324. if (bufferSizeDropDown->getSelectedId() > 0)
  325. {
  326. config.bufferSize = bufferSizeDropDown->getSelectedId();
  327. error = setup.manager->setAudioDeviceSetup (config, true);
  328. }
  329. }
  330. if (error.isNotEmpty())
  331. AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon,
  332. TRANS("Error when trying to open audio device!"),
  333. error);
  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.reset (new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType,
  380. TRANS ("(no audio output channels found)")));
  381. addAndMakeVisible (outputChanList.get());
  382. outputChanLabel.reset (new Label ({}, 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.reset (new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType,
  399. TRANS("(no audio input channels found)")));
  400. addAndMakeVisible (inputChanList.get());
  401. inputChanLabel.reset (new Label ({}, 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.reset (new 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.reset (new 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.reset (new ComboBox());
  518. outputDeviceDropDown->onChange = [this] { updateConfig (true, false, false, false); };
  519. addAndMakeVisible (outputDeviceDropDown.get());
  520. outputDeviceLabel.reset (new Label ({}, type.hasSeparateInputsAndOutputs() ? TRANS("Output:")
  521. : TRANS("Device:")));
  522. outputDeviceLabel->attachToComponent (outputDeviceDropDown.get(), true);
  523. if (setup.maxNumOutputChannels > 0)
  524. {
  525. testButton.reset (new 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.reset (new ComboBox());
  541. inputDeviceDropDown->onChange = [this] { updateConfig (false, true, false, false); };
  542. addAndMakeVisible (inputDeviceDropDown.get());
  543. inputDeviceLabel.reset (new Label ({}, TRANS("Input:")));
  544. inputDeviceLabel->attachToComponent (inputDeviceDropDown.get(), true);
  545. inputLevelMeter.reset (new 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.reset (new ComboBox());
  557. addAndMakeVisible (sampleRateDropDown.get());
  558. sampleRateLabel.reset (new Label ({}, TRANS("Sample rate:")));
  559. sampleRateLabel->attachToComponent (sampleRateDropDown.get(), true);
  560. }
  561. else
  562. {
  563. sampleRateDropDown->clear();
  564. sampleRateDropDown->onChange = nullptr;
  565. }
  566. for (auto rate : currentDevice->getAvailableSampleRates())
  567. {
  568. auto intRate = roundToInt (rate);
  569. sampleRateDropDown->addItem (String (intRate) + " Hz", intRate);
  570. }
  571. sampleRateDropDown->setSelectedId (roundToInt (currentDevice->getCurrentSampleRate()), dontSendNotification);
  572. sampleRateDropDown->onChange = [this] { updateConfig (false, false, true, false); };
  573. }
  574. void updateBufferSizeComboBox (AudioIODevice* currentDevice)
  575. {
  576. if (bufferSizeDropDown == nullptr)
  577. {
  578. bufferSizeDropDown.reset (new ComboBox());
  579. addAndMakeVisible (bufferSizeDropDown.get());
  580. bufferSizeLabel.reset (new Label ({}, TRANS("Audio buffer size:")));
  581. bufferSizeLabel->attachToComponent (bufferSizeDropDown.get(), true);
  582. }
  583. else
  584. {
  585. bufferSizeDropDown->clear();
  586. bufferSizeDropDown->onChange = nullptr;
  587. }
  588. auto currentRate = currentDevice->getCurrentSampleRate();
  589. if (currentRate == 0)
  590. currentRate = 48000.0;
  591. for (auto bs : currentDevice->getAvailableBufferSizes())
  592. bufferSizeDropDown->addItem (String (bs) + " samples (" + String (bs * 1000.0 / currentRate, 1) + " ms)", bs);
  593. bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), dontSendNotification);
  594. bufferSizeDropDown->onChange = [this] { updateConfig (false, false, false, true); };
  595. }
  596. public:
  597. //==============================================================================
  598. class ChannelSelectorListBox : public ListBox,
  599. private ListBoxModel
  600. {
  601. public:
  602. enum BoxType
  603. {
  604. audioInputType,
  605. audioOutputType
  606. };
  607. //==============================================================================
  608. ChannelSelectorListBox (const AudioDeviceSetupDetails& setupDetails, BoxType boxType, const String& noItemsText)
  609. : ListBox ({}, nullptr), setup (setupDetails), type (boxType), noItemsMessage (noItemsText)
  610. {
  611. refresh();
  612. setModel (this);
  613. setOutlineThickness (1);
  614. }
  615. void refresh()
  616. {
  617. items.clear();
  618. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  619. {
  620. if (type == audioInputType)
  621. items = currentDevice->getInputChannelNames();
  622. else if (type == audioOutputType)
  623. items = currentDevice->getOutputChannelNames();
  624. if (setup.useStereoPairs)
  625. {
  626. StringArray pairs;
  627. for (int i = 0; i < items.size(); i += 2)
  628. {
  629. auto& name = items[i];
  630. if (i + 1 >= items.size())
  631. pairs.add (name.trim());
  632. else
  633. pairs.add (getNameForChannelPair (name, items[i + 1]));
  634. }
  635. items = pairs;
  636. }
  637. }
  638. updateContent();
  639. repaint();
  640. }
  641. int getNumRows() override
  642. {
  643. return items.size();
  644. }
  645. void paintListBoxItem (int row, Graphics& g, int width, int height, bool) override
  646. {
  647. if (isPositiveAndBelow (row, items.size()))
  648. {
  649. g.fillAll (findColour (ListBox::backgroundColourId));
  650. auto item = items[row];
  651. bool enabled = false;
  652. auto config = setup.manager->getAudioDeviceSetup();
  653. if (setup.useStereoPairs)
  654. {
  655. if (type == audioInputType)
  656. enabled = config.inputChannels[row * 2] || config.inputChannels[row * 2 + 1];
  657. else if (type == audioOutputType)
  658. enabled = config.outputChannels[row * 2] || config.outputChannels[row * 2 + 1];
  659. }
  660. else
  661. {
  662. if (type == audioInputType)
  663. enabled = config.inputChannels[row];
  664. else if (type == audioOutputType)
  665. enabled = config.outputChannels[row];
  666. }
  667. auto x = getTickX();
  668. auto tickW = (float) height * 0.75f;
  669. getLookAndFeel().drawTickBox (g, *this, (float) x - tickW, ((float) height - tickW) * 0.5f, tickW, tickW,
  670. enabled, true, true, false);
  671. drawTextLayout (g, *this, item, { x + 5, 0, width - x - 5, height }, enabled);
  672. }
  673. }
  674. void listBoxItemClicked (int row, const MouseEvent& e) override
  675. {
  676. selectRow (row);
  677. if (e.x < getTickX())
  678. flipEnablement (row);
  679. }
  680. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  681. {
  682. flipEnablement (row);
  683. }
  684. void returnKeyPressed (int row) override
  685. {
  686. flipEnablement (row);
  687. }
  688. void paint (Graphics& g) override
  689. {
  690. ListBox::paint (g);
  691. if (items.isEmpty())
  692. {
  693. g.setColour (Colours::grey);
  694. g.setFont (0.5f * (float) getRowHeight());
  695. g.drawText (noItemsMessage,
  696. 0, 0, getWidth(), getHeight() / 2,
  697. Justification::centred, true);
  698. }
  699. }
  700. int getBestHeight (int maxHeight)
  701. {
  702. return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()),
  703. getNumRows())
  704. + getOutlineThickness() * 2;
  705. }
  706. private:
  707. //==============================================================================
  708. const AudioDeviceSetupDetails setup;
  709. const BoxType type;
  710. const String noItemsMessage;
  711. StringArray items;
  712. static String getNameForChannelPair (const String& name1, const String& name2)
  713. {
  714. String commonBit;
  715. for (int j = 0; j < name1.length(); ++j)
  716. if (name1.substring (0, j).equalsIgnoreCase (name2.substring (0, j)))
  717. commonBit = name1.substring (0, j);
  718. // Make sure we only split the name at a space, because otherwise, things
  719. // like "input 11" + "input 12" would become "input 11 + 2"
  720. while (commonBit.isNotEmpty() && ! CharacterFunctions::isWhitespace (commonBit.getLastCharacter()))
  721. commonBit = commonBit.dropLastCharacters (1);
  722. return name1.trim() + " + " + name2.substring (commonBit.length()).trim();
  723. }
  724. void flipEnablement (int row)
  725. {
  726. jassert (type == audioInputType || type == audioOutputType);
  727. if (isPositiveAndBelow (row, items.size()))
  728. {
  729. auto config = setup.manager->getAudioDeviceSetup();
  730. if (setup.useStereoPairs)
  731. {
  732. BigInteger bits;
  733. auto& original = (type == audioInputType ? config.inputChannels
  734. : config.outputChannels);
  735. for (int i = 0; i < 256; i += 2)
  736. bits.setBit (i / 2, original[i] || original[i + 1]);
  737. if (type == audioInputType)
  738. {
  739. config.useDefaultInputChannels = false;
  740. flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2);
  741. }
  742. else
  743. {
  744. config.useDefaultOutputChannels = false;
  745. flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2);
  746. }
  747. for (int i = 0; i < 256; ++i)
  748. original.setBit (i, bits[i / 2]);
  749. }
  750. else
  751. {
  752. if (type == audioInputType)
  753. {
  754. config.useDefaultInputChannels = false;
  755. flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels);
  756. }
  757. else
  758. {
  759. config.useDefaultOutputChannels = false;
  760. flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels);
  761. }
  762. }
  763. setup.manager->setAudioDeviceSetup (config, true);
  764. }
  765. }
  766. static void flipBit (BigInteger& chans, int index, int minNumber, int maxNumber)
  767. {
  768. auto numActive = chans.countNumberOfSetBits();
  769. if (chans[index])
  770. {
  771. if (numActive > minNumber)
  772. chans.setBit (index, false);
  773. }
  774. else
  775. {
  776. if (numActive >= maxNumber)
  777. {
  778. auto firstActiveChan = chans.findNextSetBit (0);
  779. chans.clearBit (index > firstActiveChan ? firstActiveChan : chans.getHighestBit());
  780. }
  781. chans.setBit (index, true);
  782. }
  783. }
  784. int getTickX() const
  785. {
  786. return getRowHeight();
  787. }
  788. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelSelectorListBox)
  789. };
  790. private:
  791. std::unique_ptr<ChannelSelectorListBox> inputChanList, outputChanList;
  792. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSettingsPanel)
  793. };
  794. //==============================================================================
  795. AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& dm,
  796. int minInputChannelsToUse,
  797. int maxInputChannelsToUse,
  798. int minOutputChannelsToUse,
  799. int maxOutputChannelsToUse,
  800. bool showMidiInputOptions,
  801. bool showMidiOutputSelector,
  802. bool showChannelsAsStereoPairsToUse,
  803. bool hideAdvancedOptionsWithButtonToUse)
  804. : deviceManager (dm),
  805. itemHeight (24),
  806. minOutputChannels (minOutputChannelsToUse),
  807. maxOutputChannels (maxOutputChannelsToUse),
  808. minInputChannels (minInputChannelsToUse),
  809. maxInputChannels (maxInputChannelsToUse),
  810. showChannelsAsStereoPairs (showChannelsAsStereoPairsToUse),
  811. hideAdvancedOptionsWithButton (hideAdvancedOptionsWithButtonToUse)
  812. {
  813. jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
  814. jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);
  815. const OwnedArray<AudioIODeviceType>& types = deviceManager.getAvailableDeviceTypes();
  816. if (types.size() > 1)
  817. {
  818. deviceTypeDropDown.reset (new ComboBox());
  819. for (int i = 0; i < types.size(); ++i)
  820. deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1);
  821. addAndMakeVisible (deviceTypeDropDown.get());
  822. deviceTypeDropDown->onChange = [this] { updateDeviceType(); };
  823. deviceTypeDropDownLabel.reset (new Label ({}, TRANS("Audio device type:")));
  824. deviceTypeDropDownLabel->setJustificationType (Justification::centredRight);
  825. deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown.get(), true);
  826. }
  827. if (showMidiInputOptions)
  828. {
  829. midiInputsList.reset (new MidiInputSelectorComponentListBox (deviceManager,
  830. "(" + TRANS("No MIDI inputs available") + ")"));
  831. addAndMakeVisible (midiInputsList.get());
  832. midiInputsLabel.reset (new Label ({}, TRANS ("Active MIDI inputs:")));
  833. midiInputsLabel->setJustificationType (Justification::topRight);
  834. midiInputsLabel->attachToComponent (midiInputsList.get(), true);
  835. if (BluetoothMidiDevicePairingDialogue::isAvailable())
  836. {
  837. bluetoothButton.reset (new TextButton (TRANS("Bluetooth MIDI"), TRANS("Scan for bluetooth MIDI devices")));
  838. addAndMakeVisible (bluetoothButton.get());
  839. bluetoothButton->onClick = [this] { handleBluetoothButton(); };
  840. }
  841. }
  842. else
  843. {
  844. midiInputsList.reset();
  845. midiInputsLabel.reset();
  846. bluetoothButton.reset();
  847. }
  848. if (showMidiOutputSelector)
  849. {
  850. midiOutputSelector.reset (new ComboBox());
  851. addAndMakeVisible (midiOutputSelector.get());
  852. midiOutputSelector->onChange = [this] { updateMidiOutput(); };
  853. midiOutputLabel.reset (new Label ("lm", TRANS("MIDI Output:")));
  854. midiOutputLabel->attachToComponent (midiOutputSelector.get(), true);
  855. }
  856. else
  857. {
  858. midiOutputSelector.reset();
  859. midiOutputLabel.reset();
  860. }
  861. deviceManager.addChangeListener (this);
  862. updateAllControls();
  863. startTimer (1000);
  864. }
  865. AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent()
  866. {
  867. deviceManager.removeChangeListener (this);
  868. }
  869. void AudioDeviceSelectorComponent::setItemHeight (int newItemHeight)
  870. {
  871. itemHeight = newItemHeight;
  872. resized();
  873. }
  874. void AudioDeviceSelectorComponent::resized()
  875. {
  876. Rectangle<int> r (proportionOfWidth (0.35f), 15, proportionOfWidth (0.6f), 3000);
  877. auto space = itemHeight / 4;
  878. if (deviceTypeDropDown != nullptr)
  879. {
  880. deviceTypeDropDown->setBounds (r.removeFromTop (itemHeight));
  881. r.removeFromTop (space * 3);
  882. }
  883. if (audioDeviceSettingsComp != nullptr)
  884. {
  885. audioDeviceSettingsComp->resized();
  886. audioDeviceSettingsComp->setBounds (r.removeFromTop (audioDeviceSettingsComp->getHeight())
  887. .withX (0).withWidth (getWidth()));
  888. r.removeFromTop (space);
  889. }
  890. if (midiInputsList != nullptr)
  891. {
  892. midiInputsList->setRowHeight (jmin (22, itemHeight));
  893. midiInputsList->setBounds (r.removeFromTop (midiInputsList->getBestHeight (jmin (itemHeight * 8,
  894. getHeight() - r.getY() - space - itemHeight))));
  895. r.removeFromTop (space);
  896. }
  897. if (bluetoothButton != nullptr)
  898. {
  899. bluetoothButton->setBounds (r.removeFromTop (24));
  900. r.removeFromTop (space);
  901. }
  902. if (midiOutputSelector != nullptr)
  903. midiOutputSelector->setBounds (r.removeFromTop (itemHeight));
  904. r.removeFromTop (itemHeight);
  905. setSize (getWidth(), r.getY());
  906. }
  907. void AudioDeviceSelectorComponent::timerCallback()
  908. {
  909. // TODO
  910. // unfortunately, the AudioDeviceManager only gives us changeListenerCallbacks
  911. // if an audio device has changed, but not if a MIDI device has changed.
  912. // This needs to be implemented properly. Until then, we use a workaround
  913. // where we update the whole component once per second on a timer callback.
  914. updateAllControls();
  915. }
  916. void AudioDeviceSelectorComponent::updateDeviceType()
  917. {
  918. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1])
  919. {
  920. audioDeviceSettingsComp.reset();
  921. deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true);
  922. updateAllControls(); // needed in case the type hasn't actually changed
  923. }
  924. }
  925. void AudioDeviceSelectorComponent::updateMidiOutput()
  926. {
  927. auto selectedId = midiOutputSelector->getSelectedId();
  928. if (selectedId == -1)
  929. deviceManager.setDefaultMidiOutputDevice ({});
  930. else
  931. deviceManager.setDefaultMidiOutputDevice (currentMidiOutputs[selectedId - 1].identifier);
  932. }
  933. void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*)
  934. {
  935. updateAllControls();
  936. }
  937. void AudioDeviceSelectorComponent::updateAllControls()
  938. {
  939. if (deviceTypeDropDown != nullptr)
  940. deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), dontSendNotification);
  941. if (audioDeviceSettingsComp == nullptr
  942. || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType())
  943. {
  944. audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType();
  945. audioDeviceSettingsComp.reset();
  946. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == nullptr
  947. ? 0 : deviceTypeDropDown->getSelectedId() - 1])
  948. {
  949. AudioDeviceSetupDetails details;
  950. details.manager = &deviceManager;
  951. details.minNumInputChannels = minInputChannels;
  952. details.maxNumInputChannels = maxInputChannels;
  953. details.minNumOutputChannels = minOutputChannels;
  954. details.maxNumOutputChannels = maxOutputChannels;
  955. details.useStereoPairs = showChannelsAsStereoPairs;
  956. auto* sp = new AudioDeviceSettingsPanel (*type, details, hideAdvancedOptionsWithButton);
  957. audioDeviceSettingsComp.reset (sp);
  958. addAndMakeVisible (sp);
  959. sp->updateAllControls();
  960. }
  961. }
  962. if (midiInputsList != nullptr)
  963. {
  964. midiInputsList->updateDevices();
  965. midiInputsList->updateContent();
  966. midiInputsList->repaint();
  967. }
  968. if (midiOutputSelector != nullptr)
  969. {
  970. midiOutputSelector->clear();
  971. currentMidiOutputs = MidiOutput::getAvailableDevices();
  972. midiOutputSelector->addItem (getNoDeviceString(), -1);
  973. midiOutputSelector->addSeparator();
  974. auto defaultOutputIdentifier = deviceManager.getDefaultMidiOutputIdentifier();
  975. int i = 0;
  976. for (auto& out : currentMidiOutputs)
  977. {
  978. midiOutputSelector->addItem (out.name, i + 1);
  979. if (defaultOutputIdentifier.isNotEmpty() && out.identifier == defaultOutputIdentifier)
  980. midiOutputSelector->setSelectedId (i + 1);
  981. ++i;
  982. }
  983. }
  984. resized();
  985. }
  986. void AudioDeviceSelectorComponent::handleBluetoothButton()
  987. {
  988. if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  989. RuntimePermissions::request (RuntimePermissions::bluetoothMidi, nullptr);
  990. if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  991. BluetoothMidiDevicePairingDialogue::open();
  992. }
  993. ListBox* AudioDeviceSelectorComponent::getMidiInputSelectorListBox() const noexcept
  994. {
  995. return midiInputsList.get();
  996. }
  997. } // namespace juce