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
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. 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 int numRates = currentDevice->getNumSampleRates();
  492. for (int i = 0; i < numRates; ++i)
  493. {
  494. const int rate = roundToInt (currentDevice->getSampleRate (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 int numBufferSizes = currentDevice->getNumBufferSizesAvailable();
  514. double currentRate = currentDevice->getCurrentSampleRate();
  515. if (currentRate == 0)
  516. currentRate = 48000.0;
  517. for (int i = 0; i < numBufferSizes; ++i)
  518. {
  519. const int bs = currentDevice->getBufferSizeSamples (i);
  520. bufferSizeDropDown->addItem (String (bs)
  521. + " samples ("
  522. + String (bs * 1000.0 / currentRate, 1)
  523. + " ms)",
  524. bs);
  525. }
  526. bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples(), dontSendNotification);
  527. bufferSizeDropDown->addListener (this);
  528. }
  529. public:
  530. //==============================================================================
  531. class ChannelSelectorListBox : public ListBox,
  532. private ListBoxModel
  533. {
  534. public:
  535. enum BoxType
  536. {
  537. audioInputType,
  538. audioOutputType
  539. };
  540. //==============================================================================
  541. ChannelSelectorListBox (const AudioDeviceSetupDetails& setupDetails,
  542. const BoxType boxType, const String& noItemsText)
  543. : ListBox (String::empty, nullptr),
  544. setup (setupDetails), type (boxType), noItemsMessage (noItemsText)
  545. {
  546. refresh();
  547. setModel (this);
  548. setOutlineThickness (1);
  549. }
  550. void refresh()
  551. {
  552. items.clear();
  553. if (AudioIODevice* const currentDevice = setup.manager->getCurrentAudioDevice())
  554. {
  555. if (type == audioInputType)
  556. items = currentDevice->getInputChannelNames();
  557. else if (type == audioOutputType)
  558. items = currentDevice->getOutputChannelNames();
  559. if (setup.useStereoPairs)
  560. {
  561. StringArray pairs;
  562. for (int i = 0; i < items.size(); i += 2)
  563. {
  564. const String& name = items[i];
  565. if (i + 1 >= items.size())
  566. pairs.add (name.trim());
  567. else
  568. pairs.add (getNameForChannelPair (name, items[i + 1]));
  569. }
  570. items = pairs;
  571. }
  572. }
  573. updateContent();
  574. repaint();
  575. }
  576. int getNumRows() override
  577. {
  578. return items.size();
  579. }
  580. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  581. {
  582. if (isPositiveAndBelow (row, items.size()))
  583. {
  584. if (rowIsSelected)
  585. g.fillAll (findColour (TextEditor::highlightColourId)
  586. .withMultipliedAlpha (0.3f));
  587. const String item (items [row]);
  588. bool enabled = false;
  589. AudioDeviceManager::AudioDeviceSetup config;
  590. setup.manager->getAudioDeviceSetup (config);
  591. if (setup.useStereoPairs)
  592. {
  593. if (type == audioInputType)
  594. enabled = config.inputChannels [row * 2] || config.inputChannels [row * 2 + 1];
  595. else if (type == audioOutputType)
  596. enabled = config.outputChannels [row * 2] || config.outputChannels [row * 2 + 1];
  597. }
  598. else
  599. {
  600. if (type == audioInputType)
  601. enabled = config.inputChannels [row];
  602. else if (type == audioOutputType)
  603. enabled = config.outputChannels [row];
  604. }
  605. const int x = getTickX();
  606. const float tickW = height * 0.75f;
  607. getLookAndFeel().drawTickBox (g, *this, x - tickW, (height - tickW) / 2, tickW, tickW,
  608. enabled, true, true, false);
  609. g.setFont (height * 0.6f);
  610. g.setColour (findColour (ListBox::textColourId, true).withMultipliedAlpha (enabled ? 1.0f : 0.6f));
  611. g.drawText (item, x, 0, width - x - 2, height, Justification::centredLeft, true);
  612. }
  613. }
  614. void listBoxItemClicked (int row, const MouseEvent& e) override
  615. {
  616. selectRow (row);
  617. if (e.x < getTickX())
  618. flipEnablement (row);
  619. }
  620. void listBoxItemDoubleClicked (int row, const MouseEvent&) override
  621. {
  622. flipEnablement (row);
  623. }
  624. void returnKeyPressed (int row) override
  625. {
  626. flipEnablement (row);
  627. }
  628. void paint (Graphics& g) override
  629. {
  630. ListBox::paint (g);
  631. if (items.size() == 0)
  632. {
  633. g.setColour (Colours::grey);
  634. g.setFont (13.0f);
  635. g.drawText (noItemsMessage,
  636. 0, 0, getWidth(), getHeight() / 2,
  637. Justification::centred, true);
  638. }
  639. }
  640. int getBestHeight (int maxHeight)
  641. {
  642. return getRowHeight() * jlimit (2, jmax (2, maxHeight / getRowHeight()),
  643. getNumRows())
  644. + getOutlineThickness() * 2;
  645. }
  646. private:
  647. //==============================================================================
  648. const AudioDeviceSetupDetails setup;
  649. const BoxType type;
  650. const String noItemsMessage;
  651. StringArray items;
  652. static String getNameForChannelPair (const String& name1, const String& name2)
  653. {
  654. String commonBit;
  655. for (int j = 0; j < name1.length(); ++j)
  656. if (name1.substring (0, j).equalsIgnoreCase (name2.substring (0, j)))
  657. commonBit = name1.substring (0, j);
  658. // Make sure we only split the name at a space, because otherwise, things
  659. // like "input 11" + "input 12" would become "input 11 + 2"
  660. while (commonBit.isNotEmpty() && ! CharacterFunctions::isWhitespace (commonBit.getLastCharacter()))
  661. commonBit = commonBit.dropLastCharacters (1);
  662. return name1.trim() + " + " + name2.substring (commonBit.length()).trim();
  663. }
  664. void flipEnablement (const int row)
  665. {
  666. jassert (type == audioInputType || type == audioOutputType);
  667. if (isPositiveAndBelow (row, items.size()))
  668. {
  669. AudioDeviceManager::AudioDeviceSetup config;
  670. setup.manager->getAudioDeviceSetup (config);
  671. if (setup.useStereoPairs)
  672. {
  673. BigInteger bits;
  674. BigInteger& original = (type == audioInputType ? config.inputChannels
  675. : config.outputChannels);
  676. for (int i = 0; i < 256; i += 2)
  677. bits.setBit (i / 2, original [i] || original [i + 1]);
  678. if (type == audioInputType)
  679. {
  680. config.useDefaultInputChannels = false;
  681. flipBit (bits, row, setup.minNumInputChannels / 2, setup.maxNumInputChannels / 2);
  682. }
  683. else
  684. {
  685. config.useDefaultOutputChannels = false;
  686. flipBit (bits, row, setup.minNumOutputChannels / 2, setup.maxNumOutputChannels / 2);
  687. }
  688. for (int i = 0; i < 256; ++i)
  689. original.setBit (i, bits [i / 2]);
  690. }
  691. else
  692. {
  693. if (type == audioInputType)
  694. {
  695. config.useDefaultInputChannels = false;
  696. flipBit (config.inputChannels, row, setup.minNumInputChannels, setup.maxNumInputChannels);
  697. }
  698. else
  699. {
  700. config.useDefaultOutputChannels = false;
  701. flipBit (config.outputChannels, row, setup.minNumOutputChannels, setup.maxNumOutputChannels);
  702. }
  703. }
  704. String error (setup.manager->setAudioDeviceSetup (config, true));
  705. if (error.isNotEmpty())
  706. {
  707. //xxx
  708. }
  709. }
  710. }
  711. static void flipBit (BigInteger& chans, int index, int minNumber, int maxNumber)
  712. {
  713. const int numActive = chans.countNumberOfSetBits();
  714. if (chans [index])
  715. {
  716. if (numActive > minNumber)
  717. chans.setBit (index, false);
  718. }
  719. else
  720. {
  721. if (numActive >= maxNumber)
  722. {
  723. const int firstActiveChan = chans.findNextSetBit (0);
  724. chans.setBit (index > firstActiveChan
  725. ? firstActiveChan : chans.getHighestBit(),
  726. false);
  727. }
  728. chans.setBit (index, true);
  729. }
  730. }
  731. int getTickX() const
  732. {
  733. return getRowHeight() + 5;
  734. }
  735. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelSelectorListBox)
  736. };
  737. private:
  738. ScopedPointer<ChannelSelectorListBox> inputChanList, outputChanList;
  739. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSettingsPanel)
  740. };
  741. //==============================================================================
  742. AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& dm,
  743. const int minInputChannels_,
  744. const int maxInputChannels_,
  745. const int minOutputChannels_,
  746. const int maxOutputChannels_,
  747. const bool showMidiInputOptions,
  748. const bool showMidiOutputSelector,
  749. const bool showChannelsAsStereoPairs_,
  750. const bool hideAdvancedOptionsWithButton_)
  751. : deviceManager (dm),
  752. minOutputChannels (minOutputChannels_),
  753. maxOutputChannels (maxOutputChannels_),
  754. minInputChannels (minInputChannels_),
  755. maxInputChannels (maxInputChannels_),
  756. showChannelsAsStereoPairs (showChannelsAsStereoPairs_),
  757. hideAdvancedOptionsWithButton (hideAdvancedOptionsWithButton_)
  758. {
  759. jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
  760. jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);
  761. const OwnedArray<AudioIODeviceType>& types = deviceManager.getAvailableDeviceTypes();
  762. if (types.size() > 1)
  763. {
  764. deviceTypeDropDown = new ComboBox (String::empty);
  765. for (int i = 0; i < types.size(); ++i)
  766. deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1);
  767. addAndMakeVisible (deviceTypeDropDown);
  768. deviceTypeDropDown->addListener (this);
  769. deviceTypeDropDownLabel = new Label (String::empty, TRANS ("Audio device type:"));
  770. deviceTypeDropDownLabel->setJustificationType (Justification::centredRight);
  771. deviceTypeDropDownLabel->attachToComponent (deviceTypeDropDown, true);
  772. }
  773. if (showMidiInputOptions)
  774. {
  775. addAndMakeVisible (midiInputsList
  776. = new MidiInputSelectorComponentListBox (deviceManager,
  777. "(" + TRANS("No MIDI inputs available") + ")"));
  778. midiInputsLabel = new Label (String::empty, TRANS ("Active MIDI inputs:"));
  779. midiInputsLabel->setJustificationType (Justification::topRight);
  780. midiInputsLabel->attachToComponent (midiInputsList, true);
  781. }
  782. else
  783. {
  784. midiInputsList = nullptr;
  785. midiInputsLabel = nullptr;
  786. }
  787. if (showMidiOutputSelector)
  788. {
  789. addAndMakeVisible (midiOutputSelector = new ComboBox (String::empty));
  790. midiOutputSelector->addListener (this);
  791. midiOutputLabel = new Label ("lm", TRANS("MIDI Output:"));
  792. midiOutputLabel->attachToComponent (midiOutputSelector, true);
  793. }
  794. else
  795. {
  796. midiOutputSelector = nullptr;
  797. midiOutputLabel = nullptr;
  798. }
  799. deviceManager.addChangeListener (this);
  800. updateAllControls();
  801. }
  802. AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent()
  803. {
  804. deviceManager.removeChangeListener (this);
  805. }
  806. void AudioDeviceSelectorComponent::resized()
  807. {
  808. const int lx = proportionOfWidth (0.35f);
  809. const int w = proportionOfWidth (0.4f);
  810. const int h = 24;
  811. const int space = 6;
  812. const int dh = h + space;
  813. int y = 15;
  814. if (deviceTypeDropDown != nullptr)
  815. {
  816. deviceTypeDropDown->setBounds (lx, y, proportionOfWidth (0.3f), h);
  817. y += dh + space * 2;
  818. }
  819. if (audioDeviceSettingsComp != nullptr)
  820. {
  821. audioDeviceSettingsComp->setBounds (0, y, getWidth(), audioDeviceSettingsComp->getHeight());
  822. y += audioDeviceSettingsComp->getHeight() + space;
  823. }
  824. if (midiInputsList != nullptr)
  825. {
  826. const int bh = midiInputsList->getBestHeight (jmin (h * 8, getHeight() - y - space - h));
  827. midiInputsList->setBounds (lx, y, w, bh);
  828. y += bh + space;
  829. }
  830. if (midiOutputSelector != nullptr)
  831. midiOutputSelector->setBounds (lx, y, w, h);
  832. }
  833. void AudioDeviceSelectorComponent::childBoundsChanged (Component* child)
  834. {
  835. if (child == audioDeviceSettingsComp)
  836. resized();
  837. }
  838. void AudioDeviceSelectorComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged)
  839. {
  840. if (comboBoxThatHasChanged == deviceTypeDropDown)
  841. {
  842. if (AudioIODeviceType* const type = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown->getSelectedId() - 1])
  843. {
  844. audioDeviceSettingsComp = nullptr;
  845. deviceManager.setCurrentAudioDeviceType (type->getTypeName(), true);
  846. updateAllControls(); // needed in case the type hasn't actally changed
  847. }
  848. }
  849. else if (comboBoxThatHasChanged == midiOutputSelector)
  850. {
  851. deviceManager.setDefaultMidiOutput (midiOutputSelector->getText());
  852. }
  853. }
  854. void AudioDeviceSelectorComponent::changeListenerCallback (ChangeBroadcaster*)
  855. {
  856. updateAllControls();
  857. }
  858. void AudioDeviceSelectorComponent::updateAllControls()
  859. {
  860. if (deviceTypeDropDown != nullptr)
  861. deviceTypeDropDown->setText (deviceManager.getCurrentAudioDeviceType(), dontSendNotification);
  862. if (audioDeviceSettingsComp == nullptr
  863. || audioDeviceSettingsCompType != deviceManager.getCurrentAudioDeviceType())
  864. {
  865. audioDeviceSettingsCompType = deviceManager.getCurrentAudioDeviceType();
  866. audioDeviceSettingsComp = nullptr;
  867. if (AudioIODeviceType* const type
  868. = deviceManager.getAvailableDeviceTypes() [deviceTypeDropDown == nullptr
  869. ? 0 : deviceTypeDropDown->getSelectedId() - 1])
  870. {
  871. AudioDeviceSetupDetails details;
  872. details.manager = &deviceManager;
  873. details.minNumInputChannels = minInputChannels;
  874. details.maxNumInputChannels = maxInputChannels;
  875. details.minNumOutputChannels = minOutputChannels;
  876. details.maxNumOutputChannels = maxOutputChannels;
  877. details.useStereoPairs = showChannelsAsStereoPairs;
  878. audioDeviceSettingsComp = new AudioDeviceSettingsPanel (*type, details, hideAdvancedOptionsWithButton);
  879. if (audioDeviceSettingsComp != nullptr)
  880. {
  881. addAndMakeVisible (audioDeviceSettingsComp);
  882. audioDeviceSettingsComp->resized();
  883. }
  884. }
  885. }
  886. if (midiInputsList != nullptr)
  887. {
  888. midiInputsList->updateContent();
  889. midiInputsList->repaint();
  890. }
  891. if (midiOutputSelector != nullptr)
  892. {
  893. midiOutputSelector->clear();
  894. const StringArray midiOuts (MidiOutput::getDevices());
  895. midiOutputSelector->addItem (getNoDeviceString(), -1);
  896. midiOutputSelector->addSeparator();
  897. for (int i = 0; i < midiOuts.size(); ++i)
  898. midiOutputSelector->addItem (midiOuts[i], i + 1);
  899. int current = -1;
  900. if (deviceManager.getDefaultMidiOutput() != nullptr)
  901. current = 1 + midiOuts.indexOf (deviceManager.getDefaultMidiOutputName());
  902. midiOutputSelector->setSelectedId (current, dontSendNotification);
  903. }
  904. resized();
  905. }