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.

1210 lines
43KB

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