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.

1220 lines
43KB

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