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.

1207 lines
43KB

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