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.

268 lines
9.7KB

  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. addAndMakeVisible (midiInputListLabel);
  86. midiInputListLabel.setText ("MIDI Input:", dontSendNotification);
  87. midiInputListLabel.attachToComponent (&midiInputList, true);
  88. addAndMakeVisible (midiInputList);
  89. midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
  90. const StringArray midiInputs (MidiInput::getDevices());
  91. midiInputList.addItemList (midiInputs, 1);
  92. midiInputList.addListener (this);
  93. // find the first enabled device and use that bu default
  94. for (int i = 0; i < midiInputs.size(); ++i)
  95. {
  96. if (deviceManager.isMidiInputEnabled (midiInputs[i]))
  97. {
  98. setMidiInput (i);
  99. break;
  100. }
  101. }
  102. // if no enabled devices were found just use the first one in the list
  103. if (midiInputList.getSelectedId() == 0)
  104. setMidiInput (0);
  105. addAndMakeVisible (keyboardComponent);
  106. keyboardState.addListener (this);
  107. addAndMakeVisible (messageListBox);
  108. messageListBox.setModel (&midiLogListBoxModel);
  109. messageListBox.setColour (ListBox::backgroundColourId, Colour (0x32ffffff));
  110. messageListBox.setColour (ListBox::outlineColourId, Colours::black);
  111. }
  112. ~MidiDemo()
  113. {
  114. keyboardState.removeListener (this);
  115. deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this);
  116. midiInputList.removeListener (this);
  117. }
  118. void paint (Graphics& g) override
  119. {
  120. fillTiledBackground (g);
  121. }
  122. void resized() override
  123. {
  124. Rectangle<int> area (getLocalBounds());
  125. midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  126. keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
  127. messageListBox.setBounds (area.reduced (8));
  128. }
  129. private:
  130. AudioDeviceManager& deviceManager;
  131. ComboBox midiInputList;
  132. Label midiInputListLabel;
  133. int lastInputIndex;
  134. bool isAddingFromMidiInput;
  135. MidiKeyboardState keyboardState;
  136. MidiKeyboardComponent keyboardComponent;
  137. ListBox messageListBox;
  138. Array<MidiMessage> midiMessageList;
  139. MidiLogListBoxModel midiLogListBoxModel;
  140. //==============================================================================
  141. /** Starts listening to a MIDI input device, enabling it if necessary. */
  142. void setMidiInput (int index)
  143. {
  144. const StringArray list (MidiInput::getDevices());
  145. deviceManager.removeMidiInputCallback (list[lastInputIndex], this);
  146. const String newInput (list[index]);
  147. if (! deviceManager.isMidiInputEnabled (newInput))
  148. deviceManager.setMidiInputEnabled (newInput, true);
  149. deviceManager.addMidiInputCallback (newInput, this);
  150. midiInputList.setSelectedId (index + 1, dontSendNotification);
  151. lastInputIndex = index;
  152. }
  153. void comboBoxChanged (ComboBox* box) override
  154. {
  155. if (box == &midiInputList)
  156. setMidiInput (midiInputList.getSelectedItemIndex());
  157. }
  158. // These methods handle callbacks from the midi device + on-screen keyboard..
  159. void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override
  160. {
  161. const ScopedValueSetter<bool> scopedInputFlag (isAddingFromMidiInput, true);
  162. keyboardState.processNextMidiEvent (message);
  163. postMessageToList (message);
  164. }
  165. void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  166. {
  167. if (! isAddingFromMidiInput)
  168. {
  169. MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
  170. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  171. postMessageToList (m);
  172. }
  173. }
  174. void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  175. {
  176. if (! isAddingFromMidiInput)
  177. {
  178. MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
  179. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  180. postMessageToList (m);
  181. }
  182. }
  183. // This is used to dispach an incoming message to the message thread
  184. struct IncomingMessageCallback : public CallbackMessage
  185. {
  186. IncomingMessageCallback (MidiDemo* d, const MidiMessage& m)
  187. : demo (d), message (m) {}
  188. void messageCallback() override
  189. {
  190. if (demo != nullptr)
  191. demo->addMessageToList (message);
  192. }
  193. Component::SafePointer<MidiDemo> demo;
  194. MidiMessage message;
  195. };
  196. void postMessageToList (const MidiMessage& message)
  197. {
  198. (new IncomingMessageCallback (this, message))->post();
  199. }
  200. void addMessageToList (const MidiMessage& message)
  201. {
  202. midiMessageList.add (message);
  203. triggerAsyncUpdate();
  204. }
  205. void handleAsyncUpdate() override
  206. {
  207. messageListBox.updateContent();
  208. messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1);
  209. messageListBox.repaint();
  210. }
  211. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo)
  212. };
  213. // This static object will register this demo type in a global list of demos..
  214. static JuceDemoType<MidiDemo> demo ("32 Audio: MIDI i/o");