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.

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