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.

1110 lines
39KB

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