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