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.

269 lines
9.9KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-12 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #include "../JuceDemoHeader.h"
  19. static String getMidiMessageDescription (const MidiMessage& m)
  20. {
  21. if (m.isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3);
  22. if (m.isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3);
  23. if (m.isProgramChange()) return "Program change " + String (m.getProgramChangeNumber());
  24. if (m.isPitchWheel()) return "Pitch wheel " + String (m.getPitchWheelValue());
  25. if (m.isAftertouch()) return "After touch " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + ": " + String (m.getAfterTouchValue());
  26. if (m.isChannelPressure()) return "Channel pressure " + String (m.getChannelPressureValue());
  27. if (m.isAllNotesOff()) return "All notes off";
  28. if (m.isAllSoundOff()) return "All sound off";
  29. if (m.isMetaEvent()) return "Meta event";
  30. if (m.isController())
  31. {
  32. String name (MidiMessage::getControllerName (m.getControllerNumber()));
  33. if (name.isEmpty())
  34. name = "[" + String (m.getControllerNumber()) + "]";
  35. return "Controler " + name + ": " + String (m.getControllerValue());
  36. }
  37. return String::toHexString (m.getRawData(), m.getRawDataSize());
  38. }
  39. //==============================================================================
  40. /** Simple list box that just displays a StringArray. */
  41. class MidiLogListBoxModel : public ListBoxModel
  42. {
  43. public:
  44. MidiLogListBoxModel (const Array<MidiMessage>& list)
  45. : midiMessageList (list)
  46. {
  47. }
  48. int getNumRows() override { return midiMessageList.size(); }
  49. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  50. {
  51. if (rowIsSelected)
  52. g.fillAll (Colours::blue.withAlpha (0.2f));
  53. if (isPositiveAndBelow (row, midiMessageList.size()))
  54. {
  55. g.setColour (Colours::black);
  56. const MidiMessage& message = midiMessageList.getReference (row);
  57. double time = message.getTimeStamp();
  58. g.drawText (String::formatted ("%02d:%02d:%02d",
  59. ((int) (time / 3600.0)) % 24,
  60. ((int) (time / 60.0)) % 60,
  61. ((int) time) % 60)
  62. + " - " + getMidiMessageDescription (message),
  63. Rectangle<int> (width, height).reduced (4, 0),
  64. Justification::centredLeft, true);
  65. }
  66. }
  67. private:
  68. const Array<MidiMessage>& midiMessageList;
  69. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLogListBoxModel)
  70. };
  71. //==============================================================================
  72. class MidiDemo : public Component,
  73. private ComboBox::Listener,
  74. private MidiInputCallback,
  75. private MidiKeyboardStateListener,
  76. private AsyncUpdater
  77. {
  78. public:
  79. MidiDemo()
  80. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  81. lastInputIndex (0), isAddingFromMidiInput (false),
  82. keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
  83. midiLogListBoxModel (midiMessageList)
  84. {
  85. setOpaque (true);
  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 bu 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. addAndMakeVisible (keyboardComponent);
  107. keyboardState.addListener (this);
  108. addAndMakeVisible (messageListBox);
  109. messageListBox.setModel (&midiLogListBoxModel);
  110. messageListBox.setColour (ListBox::backgroundColourId, Colour (0x32ffffff));
  111. messageListBox.setColour (ListBox::outlineColourId, Colours::black);
  112. }
  113. ~MidiDemo()
  114. {
  115. keyboardState.removeListener (this);
  116. deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this);
  117. midiInputList.removeListener (this);
  118. }
  119. void paint (Graphics& g) override
  120. {
  121. fillTiledBackground (g);
  122. }
  123. void resized() override
  124. {
  125. Rectangle<int> area (getLocalBounds());
  126. midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  127. keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
  128. messageListBox.setBounds (area.reduced (8));
  129. }
  130. private:
  131. AudioDeviceManager& deviceManager;
  132. ComboBox midiInputList;
  133. Label midiInputListLabel;
  134. int lastInputIndex;
  135. bool isAddingFromMidiInput;
  136. MidiKeyboardState keyboardState;
  137. MidiKeyboardComponent keyboardComponent;
  138. ListBox messageListBox;
  139. Array<MidiMessage> midiMessageList;
  140. MidiLogListBoxModel midiLogListBoxModel;
  141. //==============================================================================
  142. /** Starts listening to a MIDI input device, enabling it if necessary. */
  143. void setMidiInput (int index)
  144. {
  145. const StringArray list (MidiInput::getDevices());
  146. deviceManager.removeMidiInputCallback (list[lastInputIndex], this);
  147. const String newInput (list[index]);
  148. if (! deviceManager.isMidiInputEnabled (newInput))
  149. deviceManager.setMidiInputEnabled (newInput, true);
  150. deviceManager.addMidiInputCallback (newInput, this);
  151. midiInputList.setSelectedId (index + 1, dontSendNotification);
  152. lastInputIndex = index;
  153. }
  154. void comboBoxChanged (ComboBox* box) override
  155. {
  156. if (box == &midiInputList)
  157. setMidiInput (midiInputList.getSelectedItemIndex());
  158. }
  159. // These methods handle callbacks from the midi device + on-screen keyboard..
  160. void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override
  161. {
  162. const ScopedValueSetter<bool> scopedInputFlag (isAddingFromMidiInput, true);
  163. keyboardState.processNextMidiEvent (message);
  164. postMessageToList (message);
  165. }
  166. void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  167. {
  168. if (! isAddingFromMidiInput)
  169. {
  170. MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
  171. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  172. postMessageToList (m);
  173. }
  174. }
  175. void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) override
  176. {
  177. if (! isAddingFromMidiInput)
  178. {
  179. MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber));
  180. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  181. postMessageToList (m);
  182. }
  183. }
  184. // This is used to dispach an incoming message to the message thread
  185. struct IncomingMessageCallback : public CallbackMessage
  186. {
  187. IncomingMessageCallback (MidiDemo* d, const MidiMessage& m)
  188. : demo (d), message (m) {}
  189. void messageCallback() override
  190. {
  191. if (demo != nullptr)
  192. demo->addMessageToList (message);
  193. }
  194. Component::SafePointer<MidiDemo> demo;
  195. MidiMessage message;
  196. };
  197. void postMessageToList (const MidiMessage& message)
  198. {
  199. (new IncomingMessageCallback (this, message))->post();
  200. }
  201. void addMessageToList (const MidiMessage& message)
  202. {
  203. midiMessageList.add (message);
  204. triggerAsyncUpdate();
  205. }
  206. void handleAsyncUpdate() override
  207. {
  208. messageListBox.updateContent();
  209. messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1);
  210. messageListBox.repaint();
  211. }
  212. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo);
  213. };
  214. // This static object will register this demo type in a global list of demos..
  215. static JuceDemoType<MidiDemo> demo ("32 Audio: MIDI i/o");