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.

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