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.

1198 lines
42KB

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