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.

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