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.

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