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.

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