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.

457 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI 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. #include "MainComponent.h"
  18. //==============================================================================
  19. struct MidiDeviceListEntry : ReferenceCountedObject
  20. {
  21. MidiDeviceListEntry (const String& deviceName) : name (deviceName) {}
  22. String name;
  23. ScopedPointer<MidiInput> inDevice;
  24. ScopedPointer<MidiOutput> outDevice;
  25. typedef ReferenceCountedObjectPtr<MidiDeviceListEntry> Ptr;
  26. };
  27. //==============================================================================
  28. struct MidiCallbackMessage : public Message
  29. {
  30. MidiCallbackMessage (const MidiMessage& msg) : message (msg) {}
  31. MidiMessage message;
  32. };
  33. //==============================================================================
  34. class MidiDeviceListBox : public ListBox,
  35. private ListBoxModel
  36. {
  37. public:
  38. //==============================================================================
  39. MidiDeviceListBox (const String& name,
  40. MainContentComponent& contentComponent,
  41. bool isInputDeviceList)
  42. : ListBox (name, this),
  43. parent (contentComponent),
  44. isInput (isInputDeviceList)
  45. {
  46. setOutlineThickness (1);
  47. setMultipleSelectionEnabled (true);
  48. setClickingTogglesRowSelection (true);
  49. }
  50. //==============================================================================
  51. int getNumRows() override
  52. {
  53. return isInput ? parent.getNumMidiInputs()
  54. : parent.getNumMidiOutputs();
  55. }
  56. //==============================================================================
  57. void paintListBoxItem (int rowNumber, Graphics &g,
  58. int width, int height, bool rowIsSelected) override
  59. {
  60. const auto textColour = getLookAndFeel().findColour (ListBox::textColourId);
  61. if (rowIsSelected)
  62. g.fillAll (textColour.interpolatedWith (getLookAndFeel().findColour (ListBox::backgroundColourId), 0.5));
  63. g.setColour (textColour);
  64. g.setFont (height * 0.7f);
  65. if (isInput)
  66. {
  67. if (rowNumber < parent.getNumMidiInputs())
  68. g.drawText (parent.getMidiDevice (rowNumber, true)->name,
  69. 5, 0, width, height,
  70. Justification::centredLeft, true);
  71. }
  72. else
  73. {
  74. if (rowNumber < parent.getNumMidiOutputs())
  75. g.drawText (parent.getMidiDevice (rowNumber, false)->name,
  76. 5, 0, width, height,
  77. Justification::centredLeft, true);
  78. }
  79. }
  80. //==============================================================================
  81. void selectedRowsChanged (int) override
  82. {
  83. SparseSet<int> newSelectedItems = getSelectedRows();
  84. if (newSelectedItems != lastSelectedItems)
  85. {
  86. for (int i = 0; i < lastSelectedItems.size(); ++i)
  87. {
  88. if (! newSelectedItems.contains (lastSelectedItems[i]))
  89. parent.closeDevice (isInput, lastSelectedItems[i]);
  90. }
  91. for (int i = 0; i < newSelectedItems.size(); ++i)
  92. {
  93. if (! lastSelectedItems.contains (newSelectedItems[i]))
  94. parent.openDevice (isInput, newSelectedItems[i]);
  95. }
  96. lastSelectedItems = newSelectedItems;
  97. }
  98. }
  99. //==============================================================================
  100. void syncSelectedItemsWithDeviceList (const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices)
  101. {
  102. SparseSet<int> selectedRows;
  103. for (int i = 0; i < midiDevices.size(); ++i)
  104. if (midiDevices[i]->inDevice != nullptr || midiDevices[i]->outDevice != nullptr)
  105. selectedRows.addRange (Range<int> (i, i+1));
  106. lastSelectedItems = selectedRows;
  107. updateContent();
  108. setSelectedRows (selectedRows, dontSendNotification);
  109. }
  110. private:
  111. //==============================================================================
  112. MainContentComponent& parent;
  113. bool isInput;
  114. SparseSet<int> lastSelectedItems;
  115. };
  116. //==============================================================================
  117. MainContentComponent::MainContentComponent ()
  118. : midiInputLabel ("Midi Input Label", "MIDI Input:"),
  119. midiOutputLabel ("Midi Output Label", "MIDI Output:"),
  120. incomingMidiLabel ("Incoming Midi Label", "Received MIDI messages:"),
  121. outgoingMidiLabel ("Outgoing Midi Label", "Play the keyboard to send MIDI messages..."),
  122. midiKeyboard (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
  123. midiMonitor ("MIDI Monitor"),
  124. pairButton ("MIDI Bluetooth devices..."),
  125. midiInputSelector (new MidiDeviceListBox ("Midi Input Selector", *this, true)),
  126. midiOutputSelector (new MidiDeviceListBox ("Midi Input Selector", *this, false))
  127. {
  128. setSize (732, 520);
  129. addLabelAndSetStyle (midiInputLabel);
  130. addLabelAndSetStyle (midiOutputLabel);
  131. addLabelAndSetStyle (incomingMidiLabel);
  132. addLabelAndSetStyle (outgoingMidiLabel);
  133. midiKeyboard.setName ("MIDI Keyboard");
  134. addAndMakeVisible (midiKeyboard);
  135. midiMonitor.setMultiLine (true);
  136. midiMonitor.setReturnKeyStartsNewLine (false);
  137. midiMonitor.setReadOnly (true);
  138. midiMonitor.setScrollbarsShown (true);
  139. midiMonitor.setCaretVisible (false);
  140. midiMonitor.setPopupMenuEnabled (false);
  141. midiMonitor.setText (String());
  142. addAndMakeVisible (midiMonitor);
  143. if (! BluetoothMidiDevicePairingDialogue::isAvailable())
  144. pairButton.setEnabled (false);
  145. addAndMakeVisible (pairButton);
  146. pairButton.addListener (this);
  147. keyboardState.addListener (this);
  148. addAndMakeVisible (midiInputSelector);
  149. addAndMakeVisible (midiOutputSelector);
  150. startTimer (500);
  151. }
  152. //==============================================================================
  153. void MainContentComponent::addLabelAndSetStyle (Label& label)
  154. {
  155. label.setFont (Font (15.00f, Font::plain));
  156. label.setJustificationType (Justification::centredLeft);
  157. label.setEditable (false, false, false);
  158. label.setColour (TextEditor::textColourId, Colours::black);
  159. label.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  160. addAndMakeVisible (label);
  161. }
  162. //==============================================================================
  163. MainContentComponent::~MainContentComponent()
  164. {
  165. stopTimer();
  166. midiInputs.clear();
  167. midiOutputs.clear();
  168. keyboardState.removeListener (this);
  169. midiInputSelector = nullptr;
  170. midiOutputSelector = nullptr;
  171. midiOutputSelector = nullptr;
  172. }
  173. //==============================================================================
  174. void MainContentComponent::paint (Graphics&)
  175. {
  176. }
  177. //==============================================================================
  178. void MainContentComponent::resized()
  179. {
  180. const int margin = 10;
  181. midiInputLabel.setBounds (margin, margin,
  182. (getWidth() / 2) - (2 * margin), 24);
  183. midiOutputLabel.setBounds ((getWidth() / 2) + margin, margin,
  184. (getWidth() / 2) - (2 * margin), 24);
  185. midiInputSelector->setBounds (margin, (2 * margin) + 24,
  186. (getWidth() / 2) - (2 * margin),
  187. (getHeight() / 2) - ((4 * margin) + 24 + 24));
  188. midiOutputSelector->setBounds ((getWidth() / 2) + margin, (2 * margin) + 24,
  189. (getWidth() / 2) - (2 * margin),
  190. (getHeight() / 2) - ((4 * margin) + 24 + 24));
  191. pairButton.setBounds (margin, (getHeight() / 2) - (margin + 24),
  192. getWidth() - (2 * margin), 24);
  193. outgoingMidiLabel.setBounds (margin, getHeight() / 2, getWidth() - (2*margin), 24);
  194. midiKeyboard.setBounds (margin, (getHeight() / 2) + (24 + margin), getWidth() - (2*margin), 64);
  195. incomingMidiLabel.setBounds (margin, (getHeight() / 2) + (24 + (2 * margin) + 64),
  196. getWidth() - (2*margin), 24);
  197. int y = (getHeight() / 2) + ((2 * 24) + (3 * margin) + 64);
  198. midiMonitor.setBounds (margin, y,
  199. getWidth() - (2*margin), getHeight() - y - margin);
  200. }
  201. //==============================================================================
  202. void MainContentComponent::buttonClicked (Button* buttonThatWasClicked)
  203. {
  204. if (buttonThatWasClicked == &pairButton)
  205. RuntimePermissions::request (
  206. RuntimePermissions::bluetoothMidi,
  207. [] (bool wasGranted) { if (wasGranted) BluetoothMidiDevicePairingDialogue::open(); } );
  208. }
  209. //==============================================================================
  210. bool MainContentComponent::hasDeviceListChanged (const StringArray& deviceNames, bool isInputDevice)
  211. {
  212. ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
  213. : midiOutputs;
  214. if (deviceNames.size() != midiDevices.size())
  215. return true;
  216. for (int i = 0; i < deviceNames.size(); ++i)
  217. if (deviceNames[i] != midiDevices[i]->name)
  218. return true;
  219. return false;
  220. }
  221. MidiDeviceListEntry::Ptr MainContentComponent::findDeviceWithName (const String& name, bool isInputDevice) const
  222. {
  223. const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
  224. : midiOutputs;
  225. for (int i = 0; i < midiDevices.size(); ++i)
  226. if (midiDevices[i]->name == name)
  227. return midiDevices[i];
  228. return nullptr;
  229. }
  230. void MainContentComponent::closeUnpluggedDevices (StringArray& currentlyPluggedInDevices, bool isInputDevice)
  231. {
  232. ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
  233. : midiOutputs;
  234. for (int i = midiDevices.size(); --i >= 0;)
  235. {
  236. MidiDeviceListEntry& d = *midiDevices[i];
  237. if (! currentlyPluggedInDevices.contains (d.name))
  238. {
  239. if (isInputDevice ? d.inDevice != nullptr
  240. : d.outDevice != nullptr)
  241. closeDevice (isInputDevice, i);
  242. midiDevices.remove (i);
  243. }
  244. }
  245. }
  246. void MainContentComponent::updateDeviceList (bool isInputDeviceList)
  247. {
  248. StringArray newDeviceNames = isInputDeviceList ? MidiInput::getDevices()
  249. : MidiOutput::getDevices();
  250. if (hasDeviceListChanged (newDeviceNames, isInputDeviceList))
  251. {
  252. ReferenceCountedArray<MidiDeviceListEntry>& midiDevices
  253. = isInputDeviceList ? midiInputs : midiOutputs;
  254. closeUnpluggedDevices (newDeviceNames, isInputDeviceList);
  255. ReferenceCountedArray<MidiDeviceListEntry> newDeviceList;
  256. // add all currently plugged-in devices to the device list
  257. for (int i = 0; i < newDeviceNames.size(); ++i)
  258. {
  259. MidiDeviceListEntry::Ptr entry = findDeviceWithName (newDeviceNames[i], isInputDeviceList);
  260. if (entry == nullptr)
  261. entry = new MidiDeviceListEntry (newDeviceNames[i]);
  262. newDeviceList.add (entry);
  263. }
  264. // actually update the device list
  265. midiDevices = newDeviceList;
  266. // update the selection status of the combo-box
  267. if (MidiDeviceListBox* midiSelector = isInputDeviceList ? midiInputSelector : midiOutputSelector)
  268. midiSelector->syncSelectedItemsWithDeviceList (midiDevices);
  269. }
  270. }
  271. //==============================================================================
  272. void MainContentComponent::timerCallback ()
  273. {
  274. updateDeviceList (true);
  275. updateDeviceList (false);
  276. }
  277. //==============================================================================
  278. void MainContentComponent::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
  279. {
  280. MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
  281. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  282. sendToOutputs (m);
  283. }
  284. //==============================================================================
  285. void MainContentComponent::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
  286. {
  287. MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
  288. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  289. sendToOutputs (m);
  290. }
  291. //==============================================================================
  292. void MainContentComponent::sendToOutputs(const MidiMessage& msg)
  293. {
  294. for (int i = 0; i < midiOutputs.size(); ++i)
  295. if (midiOutputs[i]->outDevice != nullptr)
  296. midiOutputs[i]->outDevice->sendMessageNow (msg);
  297. }
  298. //==============================================================================
  299. void MainContentComponent::handleIncomingMidiMessage (MidiInput* /*source*/, const MidiMessage &message)
  300. {
  301. // This is called on the MIDI thread
  302. if (message.isNoteOnOrOff())
  303. postMessage (new MidiCallbackMessage (message));
  304. }
  305. //==============================================================================
  306. void MainContentComponent::handleMessage (const Message& msg)
  307. {
  308. // This is called on the message loop
  309. const MidiMessage& mm = dynamic_cast<const MidiCallbackMessage&> (msg).message;
  310. String midiString;
  311. midiString << (mm.isNoteOn() ? String ("Note on: ") : String ("Note off: "));
  312. midiString << (MidiMessage::getMidiNoteName (mm.getNoteNumber(), true, true, true));
  313. midiString << (String (" vel = "));
  314. midiString << static_cast<int>(mm.getVelocity());
  315. midiString << "\n";
  316. midiMonitor.insertTextAtCaret (midiString);
  317. }
  318. //==============================================================================
  319. void MainContentComponent::openDevice (bool isInput, int index)
  320. {
  321. if (isInput)
  322. {
  323. jassert (midiInputs[index]->inDevice == nullptr);
  324. midiInputs[index]->inDevice = MidiInput::openDevice (index, this);
  325. if (midiInputs[index]->inDevice == nullptr)
  326. {
  327. DBG ("MainContentComponent::openDevice: open input device for index = " << index << " failed!" );
  328. return;
  329. }
  330. midiInputs[index]->inDevice->start();
  331. }
  332. else
  333. {
  334. jassert (midiOutputs[index]->outDevice == nullptr);
  335. midiOutputs[index]->outDevice = MidiOutput::openDevice (index);
  336. if (midiOutputs[index]->outDevice == nullptr)
  337. DBG ("MainContentComponent::openDevice: open output device for index = " << index << " failed!" );
  338. }
  339. }
  340. //==============================================================================
  341. void MainContentComponent::closeDevice (bool isInput, int index)
  342. {
  343. if (isInput)
  344. {
  345. jassert (midiInputs[index]->inDevice != nullptr);
  346. midiInputs[index]->inDevice->stop();
  347. midiInputs[index]->inDevice = nullptr;
  348. }
  349. else
  350. {
  351. jassert (midiOutputs[index]->outDevice != nullptr);
  352. midiOutputs[index]->outDevice = nullptr;
  353. }
  354. }
  355. //==============================================================================
  356. int MainContentComponent::getNumMidiInputs() const noexcept
  357. {
  358. return midiInputs.size();
  359. }
  360. //==============================================================================
  361. int MainContentComponent::getNumMidiOutputs() const noexcept
  362. {
  363. return midiOutputs.size();
  364. }
  365. //==============================================================================
  366. ReferenceCountedObjectPtr<MidiDeviceListEntry>
  367. MainContentComponent::getMidiDevice (int index, bool isInput) const noexcept
  368. {
  369. return isInput ? midiInputs[index] : midiOutputs[index];
  370. }