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.

297 lines
11KB

  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 "../JuceDemoHeader.h"
  18. static String getMidiMessageDescription (const MidiMessage& m)
  19. {
  20. if (m.isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3);
  21. if (m.isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3);
  22. if (m.isProgramChange()) return "Program change " + String (m.getProgramChangeNumber());
  23. if (m.isPitchWheel()) return "Pitch wheel " + String (m.getPitchWheelValue());
  24. if (m.isAftertouch()) return "After touch " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + ": " + String (m.getAfterTouchValue());
  25. if (m.isChannelPressure()) return "Channel pressure " + String (m.getChannelPressureValue());
  26. if (m.isAllNotesOff()) return "All notes off";
  27. if (m.isAllSoundOff()) return "All sound off";
  28. if (m.isMetaEvent()) return "Meta event";
  29. if (m.isController())
  30. {
  31. String name (MidiMessage::getControllerName (m.getControllerNumber()));
  32. if (name.isEmpty())
  33. name = "[" + String (m.getControllerNumber()) + "]";
  34. return "Controler " + name + ": " + String (m.getControllerValue());
  35. }
  36. return String::toHexString (m.getRawData(), m.getRawDataSize());
  37. }
  38. //==============================================================================
  39. /** Simple list box that just displays a StringArray. */
  40. class MidiLogListBoxModel : public ListBoxModel
  41. {
  42. public:
  43. MidiLogListBoxModel (const Array<MidiMessage>& list)
  44. : midiMessageList (list)
  45. {
  46. }
  47. int getNumRows() override { return midiMessageList.size(); }
  48. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  49. {
  50. if (rowIsSelected)
  51. g.fillAll (Colours::blue.withAlpha (0.2f));
  52. if (isPositiveAndBelow (row, midiMessageList.size()))
  53. {
  54. g.setColour (Colours::black);
  55. const MidiMessage& message = midiMessageList.getReference (row);
  56. double time = message.getTimeStamp();
  57. g.drawText (String::formatted ("%02d:%02d:%02d",
  58. ((int) (time / 3600.0)) % 24,
  59. ((int) (time / 60.0)) % 60,
  60. ((int) time) % 60)
  61. + " - " + getMidiMessageDescription (message),
  62. Rectangle<int> (width, height).reduced (4, 0),
  63. Justification::centredLeft, true);
  64. }
  65. }
  66. private:
  67. const Array<MidiMessage>& midiMessageList;
  68. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLogListBoxModel)
  69. };
  70. //==============================================================================
  71. class MidiDemo : public Component,
  72. private ComboBox::Listener,
  73. private MidiInputCallback,
  74. private MidiKeyboardStateListener,
  75. private AsyncUpdater
  76. {
  77. public:
  78. MidiDemo()
  79. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  80. lastInputIndex (0), isAddingFromMidiInput (false),
  81. keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
  82. midiLogListBoxModel (midiMessageList)
  83. {
  84. setOpaque (true);
  85. // MIDI Inputs
  86. addAndMakeVisible (midiInputListLabel);
  87. midiInputListLabel.setText ("MIDI Input:", dontSendNotification);
  88. midiInputListLabel.attachToComponent (&midiInputList, true);
  89. addAndMakeVisible (midiInputList);
  90. midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
  91. const StringArray midiInputs (MidiInput::getDevices());
  92. midiInputList.addItemList (midiInputs, 1);
  93. midiInputList.addListener (this);
  94. // find the first enabled device and use that by default
  95. for (int i = 0; i < midiInputs.size(); ++i)
  96. {
  97. if (deviceManager.isMidiInputEnabled (midiInputs[i]))
  98. {
  99. setMidiInput (i);
  100. break;
  101. }
  102. }
  103. // if no enabled devices were found just use the first one in the list
  104. if (midiInputList.getSelectedId() == 0)
  105. setMidiInput (0);
  106. // MIDI Outputs
  107. addAndMakeVisible (midiOutputListLabel);
  108. midiOutputListLabel.setText ("MIDI Output:", dontSendNotification);
  109. midiOutputListLabel.attachToComponent (&midiOutputList, true);
  110. addAndMakeVisible (midiOutputList);
  111. midiOutputList.setTextWhenNoChoicesAvailable ("No MIDI Output Enabled");
  112. midiOutputList.addItemList (MidiOutput::getDevices(), 1);
  113. midiOutputList.addListener (this);
  114. addAndMakeVisible (keyboardComponent);
  115. keyboardState.addListener (this);
  116. addAndMakeVisible (messageListBox);
  117. messageListBox.setModel (&midiLogListBoxModel);
  118. messageListBox.setColour (ListBox::backgroundColourId, Colour (0x32ffffff));
  119. messageListBox.setColour (ListBox::outlineColourId, Colours::black);
  120. }
  121. ~MidiDemo()
  122. {
  123. keyboardState.removeListener (this);
  124. deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this);
  125. midiInputList.removeListener (this);
  126. }
  127. void paint (Graphics& g) override
  128. {
  129. fillStandardDemoBackground (g);
  130. }
  131. void resized() override
  132. {
  133. Rectangle<int> area (getLocalBounds());
  134. midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  135. midiOutputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  136. keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
  137. messageListBox.setBounds (area.reduced (8));
  138. }
  139. private:
  140. AudioDeviceManager& deviceManager;
  141. ComboBox midiInputList, midiOutputList;
  142. Label midiInputListLabel, midiOutputListLabel;
  143. int lastInputIndex;
  144. bool isAddingFromMidiInput;
  145. MidiKeyboardState keyboardState;
  146. MidiKeyboardComponent keyboardComponent;
  147. ListBox messageListBox;
  148. Array<MidiMessage> midiMessageList;
  149. MidiLogListBoxModel midiLogListBoxModel;
  150. ScopedPointer<MidiOutput> currentMidiOutput;
  151. //==============================================================================
  152. /** Starts listening to a MIDI input device, enabling it if necessary. */
  153. void setMidiInput (int index)
  154. {
  155. const StringArray list (MidiInput::getDevices());
  156. deviceManager.removeMidiInputCallback (list[lastInputIndex], this);
  157. const String newInput (list[index]);
  158. if (! deviceManager.isMidiInputEnabled (newInput))
  159. deviceManager.setMidiInputEnabled (newInput, true);
  160. deviceManager.addMidiInputCallback (newInput, this);
  161. midiInputList.setSelectedId (index + 1, dontSendNotification);
  162. lastInputIndex = index;
  163. }
  164. //==============================================================================
  165. void setMidiOutput (int index)
  166. {
  167. currentMidiOutput = nullptr;
  168. if (MidiOutput::getDevices() [index].isNotEmpty())
  169. {
  170. currentMidiOutput = MidiOutput::openDevice (index);
  171. jassert (currentMidiOutput);
  172. }
  173. }
  174. void comboBoxChanged (ComboBox* box) override
  175. {
  176. if (box == &midiInputList) setMidiInput (midiInputList.getSelectedItemIndex());
  177. if (box == &midiOutputList) setMidiOutput (midiOutputList.getSelectedItemIndex());
  178. }
  179. // These methods handle callbacks from the midi device + on-screen keyboard..
  180. void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override
  181. {
  182. const ScopedValueSetter<bool> scopedInputFlag (isAddingFromMidiInput, true);
  183. keyboardState.processNextMidiEvent (message);
  184. postMessageToList (message);
  185. }
  186. void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  187. {
  188. if (! isAddingFromMidiInput)
  189. {
  190. MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
  191. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  192. postMessageToList (m);
  193. }
  194. }
  195. void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  196. {
  197. if (! isAddingFromMidiInput)
  198. {
  199. MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
  200. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  201. postMessageToList (m);
  202. }
  203. }
  204. // This is used to dispach an incoming message to the message thread
  205. struct IncomingMessageCallback : public CallbackMessage
  206. {
  207. IncomingMessageCallback (MidiDemo* d, const MidiMessage& m)
  208. : demo (d), message (m) {}
  209. void messageCallback() override
  210. {
  211. if (demo != nullptr)
  212. demo->addMessageToList (message);
  213. }
  214. Component::SafePointer<MidiDemo> demo;
  215. MidiMessage message;
  216. };
  217. void postMessageToList (const MidiMessage& message)
  218. {
  219. if (currentMidiOutput != nullptr)
  220. currentMidiOutput->sendMessageNow (message);
  221. (new IncomingMessageCallback (this, message))->post();
  222. }
  223. void addMessageToList (const MidiMessage& message)
  224. {
  225. midiMessageList.add (message);
  226. triggerAsyncUpdate();
  227. }
  228. void handleAsyncUpdate() override
  229. {
  230. messageListBox.updateContent();
  231. messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1);
  232. messageListBox.repaint();
  233. }
  234. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo)
  235. };
  236. // This static object will register this demo type in a global list of demos..
  237. static JuceDemoType<MidiDemo> demo ("32 Audio: MIDI i/o");