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
41KB

  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. manager.enableInputLevelMeasurement (true);
  28. }
  29. ~SimpleDeviceManagerInputLevelMeter()
  30. {
  31. manager.enableInputLevelMeasurement (false);
  32. }
  33. void timerCallback() override
  34. {
  35. if (isShowing())
  36. {
  37. auto newLevel = (float) manager.getCurrentInputLevel();
  38. if (std::abs (level - newLevel) > 0.005f)
  39. {
  40. level = newLevel;
  41. repaint();
  42. }
  43. }
  44. else
  45. {
  46. level = 0;
  47. }
  48. }
  49. void paint (Graphics& g) override
  50. {
  51. getLookAndFeel().drawLevelMeter (g, getWidth(), getHeight(),
  52. (float) exp (log (level) / 3.0)); // (add a bit of a skew to make the level more obvious)
  53. }
  54. AudioDeviceManager& manager;
  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, 0, width - x - 2, 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() + 5;
  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. addAndMakeVisible (showAdvancedSettingsButton = new TextButton (TRANS("Show advanced settings...")));
  171. showAdvancedSettingsButton->onClick = [this] { showAdvanced(); };
  172. }
  173. type.scanForDevices();
  174. setup.manager->addChangeListener (this);
  175. }
  176. ~AudioDeviceSettingsPanel()
  177. {
  178. setup.manager->removeChangeListener (this);
  179. }
  180. void resized() override
  181. {
  182. if (auto* parent = findParentComponentOfClass<AudioDeviceSelectorComponent>())
  183. {
  184. Rectangle<int> r (proportionOfWidth (0.35f), 0, proportionOfWidth (0.6f), 3000);
  185. const int maxListBoxHeight = 100;
  186. const int h = parent->getItemHeight();
  187. const int space = h / 4;
  188. if (outputDeviceDropDown != nullptr)
  189. {
  190. auto row = r.removeFromTop (h);
  191. if (testButton != nullptr)
  192. {
  193. testButton->changeWidthToFitText (h);
  194. testButton->setBounds (row.removeFromRight (testButton->getWidth()));
  195. row.removeFromRight (space);
  196. }
  197. outputDeviceDropDown->setBounds (row);
  198. r.removeFromTop (space);
  199. }
  200. if (inputDeviceDropDown != nullptr)
  201. {
  202. auto row = r.removeFromTop (h);
  203. inputLevelMeter->setBounds (row.removeFromRight (testButton != nullptr ? testButton->getWidth() : row.getWidth() / 6));
  204. row.removeFromRight (space);
  205. inputDeviceDropDown->setBounds (row);
  206. r.removeFromTop (space);
  207. }
  208. if (outputChanList != nullptr)
  209. {
  210. outputChanList->setBounds (r.removeFromTop (outputChanList->getBestHeight (maxListBoxHeight)));
  211. outputChanLabel->setBounds (0, outputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  212. r.removeFromTop (space);
  213. }
  214. if (inputChanList != nullptr)
  215. {
  216. inputChanList->setBounds (r.removeFromTop (inputChanList->getBestHeight (maxListBoxHeight)));
  217. inputChanLabel->setBounds (0, inputChanList->getBounds().getCentreY() - h / 2, r.getX(), h);
  218. r.removeFromTop (space);
  219. }
  220. r.removeFromTop (space * 2);
  221. if (showAdvancedSettingsButton != nullptr)
  222. {
  223. showAdvancedSettingsButton->setBounds (r.withHeight (h));
  224. showAdvancedSettingsButton->changeWidthToFitText();
  225. }
  226. const bool advancedSettingsVisible = showAdvancedSettingsButton == nullptr
  227. || ! showAdvancedSettingsButton->isVisible();
  228. if (sampleRateDropDown != nullptr)
  229. {
  230. sampleRateDropDown->setVisible (advancedSettingsVisible);
  231. sampleRateDropDown->setBounds (r.removeFromTop (h));
  232. r.removeFromTop (space);
  233. }
  234. if (bufferSizeDropDown != nullptr)
  235. {
  236. bufferSizeDropDown->setVisible (advancedSettingsVisible);
  237. bufferSizeDropDown->setBounds (r.removeFromTop (h));
  238. r.removeFromTop (space);
  239. }
  240. r.removeFromTop (space);
  241. if (showUIButton != nullptr || resetDeviceButton != nullptr)
  242. {
  243. auto buttons = r.removeFromTop (h);
  244. if (showUIButton != nullptr)
  245. {
  246. showUIButton->setVisible (advancedSettingsVisible);
  247. showUIButton->changeWidthToFitText (h);
  248. showUIButton->setBounds (buttons.removeFromLeft (showUIButton->getWidth()));
  249. buttons.removeFromLeft (space);
  250. }
  251. if (resetDeviceButton != nullptr)
  252. {
  253. resetDeviceButton->setVisible (advancedSettingsVisible);
  254. resetDeviceButton->changeWidthToFitText (h);
  255. resetDeviceButton->setBounds (buttons.removeFromLeft (resetDeviceButton->getWidth()));
  256. }
  257. r.removeFromTop (space);
  258. }
  259. setSize (getWidth(), r.getY());
  260. }
  261. else
  262. {
  263. jassertfalse;
  264. }
  265. }
  266. void updateConfig (bool updateOutputDevice, bool updateInputDevice, bool updateSampleRate, bool updateBufferSize)
  267. {
  268. AudioDeviceManager::AudioDeviceSetup config;
  269. setup.manager->getAudioDeviceSetup (config);
  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, true);
  287. showCorrectDeviceName (outputDeviceDropDown, 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. addAndMakeVisible (outputChanList
  356. = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioOutputType,
  357. TRANS ("(no audio output channels found)")));
  358. outputChanLabel = new Label ({}, TRANS("Active output channels:"));
  359. outputChanLabel->setJustificationType (Justification::centredRight);
  360. outputChanLabel->attachToComponent (outputChanList, 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. addAndMakeVisible (inputChanList
  375. = new ChannelSelectorListBox (setup, ChannelSelectorListBox::audioInputType,
  376. TRANS("(no audio input channels found)")));
  377. inputChanLabel = new Label ({}, TRANS("Active input channels:"));
  378. inputChanLabel->setJustificationType (Justification::centredRight);
  379. inputChanLabel->attachToComponent (inputChanList, 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. ScopedPointer<ComboBox> outputDeviceDropDown, inputDeviceDropDown, sampleRateDropDown, bufferSizeDropDown;
  424. ScopedPointer<Label> outputDeviceLabel, inputDeviceLabel, sampleRateLabel, bufferSizeLabel, inputChanLabel, outputChanLabel;
  425. ScopedPointer<TextButton> testButton;
  426. ScopedPointer<Component> inputLevelMeter;
  427. ScopedPointer<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. addAndMakeVisible (showUIButton = new TextButton (TRANS ("Control Panel"),
  462. TRANS ("Opens the device's own control panel")));
  463. showUIButton->onClick = [this] { showDeviceUIPanel(); };
  464. }
  465. resized();
  466. }
  467. void updateResetButton()
  468. {
  469. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  470. {
  471. if (currentDevice->hasControlPanel())
  472. {
  473. if (resetDeviceButton == nullptr)
  474. {
  475. addAndMakeVisible (resetDeviceButton = new TextButton (TRANS ("Reset Device"),
  476. TRANS ("Resets the audio interface - sometimes needed after changing a device's properties in its custom control panel")));
  477. resetDeviceButton->onClick = [this] { resetDevice(); };
  478. resized();
  479. }
  480. return;
  481. }
  482. }
  483. resetDeviceButton.reset();
  484. }
  485. void updateOutputsComboBox()
  486. {
  487. if (setup.maxNumOutputChannels > 0 || ! type.hasSeparateInputsAndOutputs())
  488. {
  489. if (outputDeviceDropDown == nullptr)
  490. {
  491. outputDeviceDropDown = new ComboBox();
  492. outputDeviceDropDown->onChange = [this] { updateConfig (true, false, false, false); };
  493. addAndMakeVisible (outputDeviceDropDown);
  494. outputDeviceLabel = new Label ({}, type.hasSeparateInputsAndOutputs() ? TRANS("Output:")
  495. : TRANS("Device:"));
  496. outputDeviceLabel->attachToComponent (outputDeviceDropDown, true);
  497. if (setup.maxNumOutputChannels > 0)
  498. {
  499. addAndMakeVisible (testButton = new TextButton (TRANS("Test"),
  500. TRANS("Plays a test tone")));
  501. testButton->onClick = [this] { playTestSound(); };
  502. }
  503. }
  504. addNamesToDeviceBox (*outputDeviceDropDown, false);
  505. }
  506. showCorrectDeviceName (outputDeviceDropDown, false);
  507. }
  508. void updateInputsComboBox()
  509. {
  510. if (setup.maxNumInputChannels > 0 && type.hasSeparateInputsAndOutputs())
  511. {
  512. if (inputDeviceDropDown == nullptr)
  513. {
  514. inputDeviceDropDown = new ComboBox();
  515. inputDeviceDropDown->onChange = [this] { updateConfig (false, true, false, false); };
  516. addAndMakeVisible (inputDeviceDropDown);
  517. inputDeviceLabel = new Label ({}, TRANS("Input:"));
  518. inputDeviceLabel->attachToComponent (inputDeviceDropDown, true);
  519. addAndMakeVisible (inputLevelMeter
  520. = new SimpleDeviceManagerInputLevelMeter (*setup.manager));
  521. }
  522. addNamesToDeviceBox (*inputDeviceDropDown, true);
  523. }
  524. showCorrectDeviceName (inputDeviceDropDown, true);
  525. }
  526. void updateSampleRateComboBox (AudioIODevice* currentDevice)
  527. {
  528. if (sampleRateDropDown == nullptr)
  529. {
  530. addAndMakeVisible (sampleRateDropDown = new ComboBox());
  531. sampleRateLabel = new Label ({}, TRANS("Sample rate:"));
  532. sampleRateLabel->attachToComponent (sampleRateDropDown, true);
  533. }
  534. else
  535. {
  536. sampleRateDropDown->clear();
  537. sampleRateDropDown->onChange = {};
  538. }
  539. for (auto rate : currentDevice->getAvailableSampleRates())
  540. {
  541. auto intRate = roundToInt (rate);
  542. sampleRateDropDown->addItem (String (intRate) + " Hz", intRate);
  543. }
  544. sampleRateDropDown->setSelectedId (roundToInt (currentDevice->getCurrentSampleRate()), dontSendNotification);
  545. sampleRateDropDown->onChange = [this] { updateConfig (false, false, true, false); };
  546. }
  547. void updateBufferSizeComboBox (AudioIODevice* currentDevice)
  548. {
  549. if (bufferSizeDropDown == nullptr)
  550. {
  551. addAndMakeVisible (bufferSizeDropDown = new ComboBox());
  552. bufferSizeLabel = new Label ({}, TRANS("Audio buffer size:"));
  553. bufferSizeLabel->attachToComponent (bufferSizeDropDown, true);
  554. }
  555. else
  556. {
  557. bufferSizeDropDown->clear();
  558. bufferSizeDropDown->onChange = {};
  559. }
  560. auto currentRate = currentDevice->getCurrentSampleRate();
  561. if (currentRate == 0)
  562. currentRate = 48000.0;
  563. for (auto bs : currentDevice->getAvailableBufferSizes())
  564. bufferSizeDropDown->addItem (String (bs) + " samples (" + String (bs * 1000.0 / currentRate, 1) + " ms)", bs);
  565. bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), dontSendNotification);
  566. bufferSizeDropDown->onChange = [this] { updateConfig (false, false, false, true); };
  567. }
  568. public:
  569. //==============================================================================
  570. class ChannelSelectorListBox : public ListBox,
  571. private ListBoxModel
  572. {
  573. public:
  574. enum BoxType
  575. {
  576. audioInputType,
  577. audioOutputType
  578. };
  579. //==============================================================================
  580. ChannelSelectorListBox (const AudioDeviceSetupDetails& setupDetails, BoxType boxType, const String& noItemsText)
  581. : ListBox ({}, nullptr), setup (setupDetails), type (boxType), noItemsMessage (noItemsText)
  582. {
  583. refresh();
  584. setModel (this);
  585. setOutlineThickness (1);
  586. }
  587. void refresh()
  588. {
  589. items.clear();
  590. if (auto* currentDevice = setup.manager->getCurrentAudioDevice())
  591. {
  592. if (type == audioInputType)
  593. items = currentDevice->getInputChannelNames();
  594. else if (type == audioOutputType)
  595. items = currentDevice->getOutputChannelNames();
  596. if (setup.useStereoPairs)
  597. {
  598. StringArray pairs;
  599. for (int i = 0; i < items.size(); i += 2)
  600. {
  601. auto& name = items[i];
  602. if (i + 1 >= items.size())
  603. pairs.add (name.trim());
  604. else
  605. pairs.add (getNameForChannelPair (name, items[i + 1]));
  606. }
  607. items = pairs;
  608. }
  609. }
  610. updateContent();
  611. repaint();
  612. }
  613. int getNumRows() override
  614. {
  615. return items.size();
  616. }
  617. void paintListBoxItem (int row, Graphics& g, int width, int height, bool) override
  618. {
  619. if (isPositiveAndBelow (row, items.size()))
  620. {
  621. g.fillAll (findColour (ListBox::backgroundColourId));
  622. auto item = items[row];
  623. bool enabled = false;
  624. AudioDeviceManager::AudioDeviceSetup config;
  625. setup.manager->getAudioDeviceSetup (config);
  626. if (setup.useStereoPairs)
  627. {
  628. if (type == audioInputType)
  629. enabled = config.inputChannels[row * 2] || config.inputChannels[row * 2 + 1];
  630. else if (type == audioOutputType)
  631. enabled = config.outputChannels[row * 2] || config.outputChannels[row * 2 + 1];
  632. }
  633. else
  634. {
  635. if (type == audioInputType)
  636. enabled = config.inputChannels[row];
  637. else if (type == audioOutputType)
  638. enabled = config.outputChannels[row];
  639. }
  640. auto x = getTickX();
  641. auto tickW = height * 0.75f;
  642. getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW,
  643. enabled, true, true, false);
  644. g.setFont (height * 0.6f);
  645. g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f));
  646. g.drawText (item, x + 5, 0, width - x - 5, height, Justification::centredLeft, true);
  647. }
  648. }
  649. void listBoxItemClicked (int row, const MouseEvent& e) override
  650. {
  651. selectRow (row);
  652. if (e.x < getTickX())
  653. flipEnablement (row);
  654. }
  655. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  656. {
  657. flipEnablement (row);
  658. }
  659. void returnKeyPressed (int row) override
  660. {
  661. flipEnablement (row);
  662. }
  663. void paint (Graphics& g) override
  664. {
  665. ListBox::paint (g);
  666. if (items.isEmpty())
  667. {
  668. g.setColour (Colours::grey);
  669. g.setFont (13.0f);
  670. g.drawText (noItemsMessage,
  671. 0, 0, getWidth(), getHeight() / 2,
  672. Justification::centred, true);
  673. }
  674. }
  675. int getBestHeight (int maxHeight)
  676. {
  677. return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()),
  678. getNumRows())
  679. + getOutlineThickness() * 2;
  680. }
  681. private:
  682. //==============================================================================
  683. const AudioDeviceSetupDetails setup;
  684. const BoxType type;
  685. const String noItemsMessage;
  686. StringArray items;
  687. static String getNameForChannelPair (const String& name1, const String& name2)
  688. {
  689. String commonBit;
  690. for (int j = 0; j < name1.length(); ++j)
  691. if (name1.substring (0, j).equalsIgnoreCase (name2.substring (0, j)))
  692. commonBit = name1.substring (0, j);
  693. // Make sure we only split the name at a space, because otherwise, things
  694. // like "input 11" + "input 12" would become "input 11 + 2"
  695. while (commonBit.isNotEmpty() && ! CharacterFunctions::isWhitespace (commonBit.getLastCharacter()))
  696. commonBit = commonBit.dropLastCharacters (1);
  697. return name1.trim() + " + " + name2.substring (commonBit.length()).trim();
  698. }
  699. void flipEnablement (int row)
  700. {
  701. jassert (type == audioInputType || type == audioOutputType);
  702. if (isPositiveAndBelow (row, items.size()))
  703. {
  704. AudioDeviceManager::AudioDeviceSetup config;
  705. setup.manager->getAudioDeviceSetup (config);
  706. if (setup.useStereoPairs)
  707. {
  708. BigInteger bits;
  709. auto& original = (type == audioInputType ? config.inputChannels
  710. : config.outputChannels);
  711. for (int i = 0; i < 256; i += 2)
  712. bits.setBit (i / 2, original[i] || original[i + 1]);
  713. if (type == audioInputType)
  714. {
  715. config.useDefaultInputChannels = false;
  716. flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2);
  717. }
  718. else
  719. {
  720. config.useDefaultOutputChannels = false;
  721. flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2);
  722. }
  723. for (int i = 0; i < 256; ++i)
  724. original.setBit (i, bits[i / 2]);
  725. }
  726. else
  727. {
  728. if (type == audioInputType)
  729. {
  730. config.useDefaultInputChannels = false;
  731. flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels);
  732. }
  733. else
  734. {
  735. config.useDefaultOutputChannels = false;
  736. flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels);
  737. }
  738. }
  739. auto error = setup.manager->setAudioDeviceSetup (config, true);
  740. if (error.isNotEmpty())
  741. {
  742. //xxx
  743. }
  744. }
  745. }
  746. static void flipBit (BigInteger& chans, int index, int minNumber, int maxNumber)
  747. {
  748. auto numActive = chans.countNumberOfSetBits();
  749. if (chans[index])
  750. {
  751. if (numActive > minNumber)
  752. chans.setBit (index, false);
  753. }
  754. else
  755. {
  756. if (numActive >= maxNumber)
  757. {
  758. auto firstActiveChan = chans.findNextSetBit (0);
  759. chans.clearBit (index > firstActiveChan ? firstActiveChan : chans.getHighestBit());
  760. }
  761. chans.setBit (index, true);
  762. }
  763. }
  764. int getTickX() const
  765. {
  766. return getRowHeight();
  767. }
  768. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelSelectorListBox)
  769. };
  770. private:
  771. ScopedPointer<ChannelSelectorListBox> inputChanList, outputChanList;
  772. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSettingsPanel)
  773. };
  774. //==============================================================================
  775. AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& dm,
  776. int minInputChannelsToUse,
  777. int maxInputChannelsToUse,
  778. int minOutputChannelsToUse,
  779. int maxOutputChannelsToUse,
  780. bool showMidiInputOptions,
  781. bool showMidiOutputSelector,
  782. bool showChannelsAsStereoPairsToUse,
  783. bool hideAdvancedOptionsWithButtonToUse)
  784. : deviceManager (dm),
  785. itemHeight (24),
  786. minOutputChannels (minOutputChannelsToUse),
  787. maxOutputChannels (maxOutputChannelsToUse),
  788. minInputChannels (minInputChannelsToUse),
  789. maxInputChannels (maxInputChannelsToUse),
  790. showChannelsAsStereoPairs (showChannelsAsStereoPairsToUse),
  791. hideAdvancedOptionsWithButton (hideAdvancedOptionsWithButtonToUse)
  792. {
  793. jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
  794. jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);
  795. const OwnedArray<AudioIODeviceType>& types = deviceManager.getAvailableDeviceTypes();
  796. if (types.size() > 1)
  797. {
  798. deviceTypeDropDown = new ComboBox();
  799. for (int i = 0; i < types.size(); ++i)
  800. deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1);
  801. addAndMakeVisible (deviceTypeDropDown);
  802. deviceTypeDropDown->onChange = [this] { updateDeviceType(); };
  803. deviceTypeDropDownLabel = new Label ({}, TRANS("Audio device type:"));
  804. deviceTypeDropDownLabel->setJustificationType (Justification::centredRight);
  805. deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown, true);
  806. }
  807. if (showMidiInputOptions)
  808. {
  809. addAndMakeVisible (midiInputsList
  810. = new MidiInputSelectorComponentListBox (deviceManager,
  811. "(" + TRANS("No MIDI inputs available") + ")"));
  812. midiInputsLabel = new Label ({}, TRANS ("Active MIDI inputs:"));
  813. midiInputsLabel->setJustificationType (Justification::topRight);
  814. midiInputsLabel->attachToComponent (midiInputsList, true);
  815. if (BluetoothMidiDevicePairingDialogue::isAvailable())
  816. {
  817. addAndMakeVisible (bluetoothButton = new TextButton (TRANS("Bluetooth MIDI"),
  818. TRANS("Scan for bluetooth MIDI devices")));
  819. bluetoothButton->onClick = [this] { handleBluetoothButton(); };
  820. }
  821. }
  822. else
  823. {
  824. midiInputsList.reset();
  825. midiInputsLabel.reset();
  826. bluetoothButton.reset();
  827. }
  828. if (showMidiOutputSelector)
  829. {
  830. addAndMakeVisible (midiOutputSelector = new ComboBox());
  831. midiOutputSelector->onChange = [this] { updateMidiOutput(); };
  832. midiOutputLabel = new Label ("lm", TRANS("MIDI Output:"));
  833. midiOutputLabel->attachToComponent (midiOutputSelector, true);
  834. }
  835. else
  836. {
  837. midiOutputSelector.reset();
  838. midiOutputLabel.reset();
  839. }
  840. deviceManager.addChangeListener (this);
  841. updateAllControls();
  842. startTimer (1000);
  843. }
  844. AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent()
  845. {
  846. deviceManager.removeChangeListener (this);
  847. }
  848. void AudioDeviceSelectorComponent::setItemHeight (int newItemHeight)
  849. {
  850. itemHeight = newItemHeight;
  851. resized();
  852. }
  853. void AudioDeviceSelectorComponent::resized()
  854. {
  855. Rectangle<int> r (proportionOfWidth (0.35f), 15, proportionOfWidth (0.6f), 3000);
  856. auto space = itemHeight / 4;
  857. if (deviceTypeDropDown != nullptr)
  858. {
  859. deviceTypeDropDown->setBounds (r.removeFromTop (itemHeight));
  860. r.removeFromTop (space * 3);
  861. }
  862. if (audioDeviceSettingsComp != nullptr)
  863. {
  864. audioDeviceSettingsComp->resized();
  865. audioDeviceSettingsComp->setBounds (r.removeFromTop (audioDeviceSettingsComp->getHeight())
  866. .withX (0).withWidth (getWidth()));
  867. r.removeFromTop (space);
  868. }
  869. if (midiInputsList != nullptr)
  870. {
  871. midiInputsList->setBounds (r.removeFromTop (midiInputsList->getBestHeight (jmin (itemHeight * 8,
  872. getHeight() - r.getY() - space - itemHeight))));
  873. r.removeFromTop (space);
  874. }
  875. if (bluetoothButton != nullptr)
  876. {
  877. bluetoothButton->setBounds (r.removeFromTop (24));
  878. r.removeFromTop (space);
  879. }
  880. if (midiOutputSelector != nullptr)
  881. midiOutputSelector->setBounds (r.removeFromTop (itemHeight));
  882. r.removeFromTop (itemHeight);
  883. setSize (getWidth(), r.getY());
  884. }
  885. void AudioDeviceSelectorComponent::timerCallback()
  886. {
  887. // TODO
  888. // unfortunately, the AudioDeviceManager only gives us changeListenerCallbacks
  889. // if an audio device has changed, but not if a MIDI device has changed.
  890. // This needs to be implemented properly. Until then, we use a workaround
  891. // where we update the whole component once per second on a timer callback.
  892. updateAllControls();
  893. }
  894. void AudioDeviceSelectorComponent::updateDeviceType()
  895. {
  896. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1])
  897. {
  898. audioDeviceSettingsComp.reset();
  899. deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true);
  900. updateAllControls(); // needed in case the type hasn't actually changed
  901. }
  902. }
  903. void AudioDeviceSelectorComponent::updateMidiOutput()
  904. {
  905. auto midiDeviceName = midiOutputSelector->getText();
  906. if (midiDeviceName == getNoDeviceString())
  907. midiDeviceName = {};
  908. deviceManager.setDefaultMidiOutput (midiDeviceName);
  909. }
  910. void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*)
  911. {
  912. updateAllControls();
  913. }
  914. void AudioDeviceSelectorComponent::updateAllControls()
  915. {
  916. if (deviceTypeDropDown != nullptr)
  917. deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), dontSendNotification);
  918. if (audioDeviceSettingsComp == nullptr
  919. || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType())
  920. {
  921. audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType();
  922. audioDeviceSettingsComp.reset();
  923. if (auto* type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == nullptr
  924. ? 0 : deviceTypeDropDown->getSelectedId() - 1])
  925. {
  926. AudioDeviceSetupDetails details;
  927. details.manager = &deviceManager;
  928. details.minNumInputChannels = minInputChannels;
  929. details.maxNumInputChannels = maxInputChannels;
  930. details.minNumOutputChannels = minOutputChannels;
  931. details.maxNumOutputChannels = maxOutputChannels;
  932. details.useStereoPairs = showChannelsAsStereoPairs;
  933. auto* sp = new AudioDeviceSettingsPanel (*type, details, hideAdvancedOptionsWithButton);
  934. audioDeviceSettingsComp = sp;
  935. addAndMakeVisible (sp);
  936. sp->updateAllControls();
  937. }
  938. }
  939. if (midiInputsList != nullptr)
  940. {
  941. midiInputsList->updateDevices();
  942. midiInputsList->updateContent();
  943. midiInputsList->repaint();
  944. }
  945. if (midiOutputSelector != nullptr)
  946. {
  947. midiOutputSelector->clear();
  948. auto midiOuts = MidiOutput::getDevices();
  949. midiOutputSelector->addItem (getNoDeviceString(), -1);
  950. midiOutputSelector->addSeparator();
  951. for (int i = 0; i < midiOuts.size(); ++i)
  952. midiOutputSelector->addItem (midiOuts[i], i + 1);
  953. int current = -1;
  954. if (deviceManager.getDefaultMidiOutput() != nullptr)
  955. current = 1 + midiOuts.indexOf (deviceManager.getDefaultMidiOutputName());
  956. midiOutputSelector->setSelectedId (current, dontSendNotification);
  957. }
  958. resized();
  959. }
  960. void AudioDeviceSelectorComponent::handleBluetoothButton()
  961. {
  962. if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  963. RuntimePermissions::request (RuntimePermissions::bluetoothMidi, nullptr);
  964. if (RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
  965. BluetoothMidiDevicePairingDialogue::open();
  966. }
  967. ListBox* AudioDeviceSelectorComponent::getMidiInputSelectorListBox() const noexcept
  968. {
  969. return midiInputsList;
  970. }
  971. } // namespace juce