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.

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