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.

1103 lines
38KB

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