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.

1162 lines
41KB

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