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.

264 lines
9.2KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceDemoHeader.h"
  20. /** Simple list box that just displays a StringArray. */
  21. class MidiLogListBoxModel : public ListBoxModel
  22. {
  23. public:
  24. MidiLogListBoxModel (const Array<MidiMessage>& list)
  25. : midiMessageList (list)
  26. {
  27. }
  28. int getNumRows() override { return midiMessageList.size(); }
  29. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  30. {
  31. if (rowIsSelected)
  32. g.fillAll (Colours::blue.withAlpha (0.2f));
  33. if (isPositiveAndBelow (row, midiMessageList.size()))
  34. {
  35. g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
  36. Colours::black));
  37. const MidiMessage& message = midiMessageList.getReference (row);
  38. double time = message.getTimeStamp();
  39. g.drawText (String::formatted ("%02d:%02d:%02d",
  40. ((int) (time / 3600.0)) % 24,
  41. ((int) (time / 60.0)) % 60,
  42. ((int) time) % 60)
  43. + " - " + message.getDescription(),
  44. Rectangle<int> (width, height).reduced (4, 0),
  45. Justification::centredLeft, true);
  46. }
  47. }
  48. private:
  49. const Array<MidiMessage>& midiMessageList;
  50. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLogListBoxModel)
  51. };
  52. //==============================================================================
  53. class MidiDemo : public Component,
  54. private MidiInputCallback,
  55. private MidiKeyboardStateListener,
  56. private AsyncUpdater
  57. {
  58. public:
  59. MidiDemo()
  60. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  61. keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
  62. midiLogListBoxModel (midiMessageList)
  63. {
  64. setOpaque (true);
  65. // MIDI Inputs
  66. addAndMakeVisible (midiInputListLabel);
  67. midiInputListLabel.setText ("MIDI Input:", dontSendNotification);
  68. midiInputListLabel.attachToComponent (&midiInputList, true);
  69. addAndMakeVisible (midiInputList);
  70. midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
  71. auto midiInputs = MidiInput::getDevices();
  72. midiInputList.addItemList (midiInputs, 1);
  73. midiInputList.onChange = [this] { setMidiInput (midiInputList.getSelectedItemIndex()); };
  74. // find the first enabled device and use that by default
  75. for (int i = 0; i < midiInputs.size(); ++i)
  76. {
  77. if (deviceManager.isMidiInputEnabled (midiInputs[i]))
  78. {
  79. setMidiInput (i);
  80. break;
  81. }
  82. }
  83. // if no enabled devices were found just use the first one in the list
  84. if (midiInputList.getSelectedId() == 0)
  85. setMidiInput (0);
  86. // MIDI Outputs
  87. addAndMakeVisible (midiOutputListLabel);
  88. midiOutputListLabel.setText ("MIDI Output:", dontSendNotification);
  89. midiOutputListLabel.attachToComponent (&midiOutputList, true);
  90. addAndMakeVisible (midiOutputList);
  91. midiOutputList.setTextWhenNoChoicesAvailable ("No MIDI Output Enabled");
  92. midiOutputList.addItemList (MidiOutput::getDevices(), 1);
  93. midiOutputList.onChange = [this] { setMidiOutput (midiOutputList.getSelectedItemIndex()); };
  94. addAndMakeVisible (keyboardComponent);
  95. keyboardState.addListener (this);
  96. addAndMakeVisible (messageListBox);
  97. messageListBox.setModel (&midiLogListBoxModel);
  98. }
  99. ~MidiDemo()
  100. {
  101. keyboardState.removeListener (this);
  102. deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this);
  103. }
  104. void paint (Graphics& g) override
  105. {
  106. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  107. }
  108. void resized() override
  109. {
  110. auto area = getLocalBounds();
  111. midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  112. midiOutputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  113. keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
  114. messageListBox.setBounds (area.reduced (8));
  115. }
  116. private:
  117. AudioDeviceManager& deviceManager;
  118. ComboBox midiInputList, midiOutputList;
  119. Label midiInputListLabel, midiOutputListLabel;
  120. int lastInputIndex = 0;
  121. bool isAddingFromMidiInput = false;
  122. MidiKeyboardState keyboardState;
  123. MidiKeyboardComponent keyboardComponent;
  124. ListBox messageListBox;
  125. Array<MidiMessage> midiMessageList;
  126. MidiLogListBoxModel midiLogListBoxModel;
  127. ScopedPointer<MidiOutput> currentMidiOutput;
  128. //==============================================================================
  129. /** Starts listening to a MIDI input device, enabling it if necessary. */
  130. void setMidiInput (int index)
  131. {
  132. auto list = MidiInput::getDevices();
  133. deviceManager.removeMidiInputCallback (list[lastInputIndex], this);
  134. auto newInput = list[index];
  135. if (! deviceManager.isMidiInputEnabled (newInput))
  136. deviceManager.setMidiInputEnabled (newInput, true);
  137. deviceManager.addMidiInputCallback (newInput, this);
  138. midiInputList.setSelectedId (index + 1, dontSendNotification);
  139. lastInputIndex = index;
  140. }
  141. //==============================================================================
  142. void setMidiOutput (int index)
  143. {
  144. currentMidiOutput.reset();
  145. if (MidiOutput::getDevices() [index].isNotEmpty())
  146. {
  147. currentMidiOutput = MidiOutput::openDevice (index);
  148. jassert (currentMidiOutput);
  149. }
  150. }
  151. // These methods handle callbacks from the midi device + on-screen keyboard..
  152. void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override
  153. {
  154. const ScopedValueSetter<bool> scopedInputFlag (isAddingFromMidiInput, true);
  155. keyboardState.processNextMidiEvent (message);
  156. postMessageToList (message);
  157. }
  158. void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  159. {
  160. if (! isAddingFromMidiInput)
  161. {
  162. auto m = MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity);
  163. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  164. postMessageToList (m);
  165. }
  166. }
  167. void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  168. {
  169. if (! isAddingFromMidiInput)
  170. {
  171. auto m = MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity);
  172. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  173. postMessageToList (m);
  174. }
  175. }
  176. // This is used to dispach an incoming message to the message thread
  177. struct IncomingMessageCallback : public CallbackMessage
  178. {
  179. IncomingMessageCallback (MidiDemo* d, const MidiMessage& m)
  180. : demo (d), message (m) {}
  181. void messageCallback() override
  182. {
  183. if (demo != nullptr)
  184. demo->addMessageToList (message);
  185. }
  186. Component::SafePointer<MidiDemo> demo;
  187. MidiMessage message;
  188. };
  189. void postMessageToList (const MidiMessage& message)
  190. {
  191. if (currentMidiOutput != nullptr)
  192. currentMidiOutput->sendMessageNow (message);
  193. (new IncomingMessageCallback (this, message))->post();
  194. }
  195. void addMessageToList (const MidiMessage& message)
  196. {
  197. midiMessageList.add (message);
  198. triggerAsyncUpdate();
  199. }
  200. void handleAsyncUpdate() override
  201. {
  202. messageListBox.updateContent();
  203. messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1);
  204. messageListBox.repaint();
  205. }
  206. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo)
  207. };
  208. // This static object will register this demo type in a global list of demos..
  209. static JuceDemoType<MidiDemo> demo ("32 Audio: MIDI i/o");