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.

272 lines
9.6KB

  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 ComboBox::Listener,
  55. private MidiInputCallback,
  56. private MidiKeyboardStateListener,
  57. private AsyncUpdater
  58. {
  59. public:
  60. MidiDemo()
  61. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  62. lastInputIndex (0), isAddingFromMidiInput (false),
  63. keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
  64. midiLogListBoxModel (midiMessageList)
  65. {
  66. setOpaque (true);
  67. // MIDI Inputs
  68. addAndMakeVisible (midiInputListLabel);
  69. midiInputListLabel.setText ("MIDI Input:", dontSendNotification);
  70. midiInputListLabel.attachToComponent (&midiInputList, true);
  71. addAndMakeVisible (midiInputList);
  72. midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
  73. const StringArray midiInputs (MidiInput::getDevices());
  74. midiInputList.addItemList (midiInputs, 1);
  75. midiInputList.addListener (this);
  76. // find the first enabled device and use that by default
  77. for (int i = 0; i < midiInputs.size(); ++i)
  78. {
  79. if (deviceManager.isMidiInputEnabled (midiInputs[i]))
  80. {
  81. setMidiInput (i);
  82. break;
  83. }
  84. }
  85. // if no enabled devices were found just use the first one in the list
  86. if (midiInputList.getSelectedId() == 0)
  87. setMidiInput (0);
  88. // MIDI Outputs
  89. addAndMakeVisible (midiOutputListLabel);
  90. midiOutputListLabel.setText ("MIDI Output:", dontSendNotification);
  91. midiOutputListLabel.attachToComponent (&midiOutputList, true);
  92. addAndMakeVisible (midiOutputList);
  93. midiOutputList.setTextWhenNoChoicesAvailable ("No MIDI Output Enabled");
  94. midiOutputList.addItemList (MidiOutput::getDevices(), 1);
  95. midiOutputList.addListener (this);
  96. addAndMakeVisible (keyboardComponent);
  97. keyboardState.addListener (this);
  98. addAndMakeVisible (messageListBox);
  99. messageListBox.setModel (&midiLogListBoxModel);
  100. }
  101. ~MidiDemo()
  102. {
  103. keyboardState.removeListener (this);
  104. deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this);
  105. midiInputList.removeListener (this);
  106. }
  107. void paint (Graphics& g) override
  108. {
  109. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  110. }
  111. void resized() override
  112. {
  113. Rectangle<int> area (getLocalBounds());
  114. midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  115. midiOutputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  116. keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
  117. messageListBox.setBounds (area.reduced (8));
  118. }
  119. private:
  120. AudioDeviceManager& deviceManager;
  121. ComboBox midiInputList, midiOutputList;
  122. Label midiInputListLabel, midiOutputListLabel;
  123. int lastInputIndex;
  124. bool isAddingFromMidiInput;
  125. MidiKeyboardState keyboardState;
  126. MidiKeyboardComponent keyboardComponent;
  127. ListBox messageListBox;
  128. Array<MidiMessage> midiMessageList;
  129. MidiLogListBoxModel midiLogListBoxModel;
  130. ScopedPointer<MidiOutput> currentMidiOutput;
  131. //==============================================================================
  132. /** Starts listening to a MIDI input device, enabling it if necessary. */
  133. void setMidiInput (int index)
  134. {
  135. const StringArray list (MidiInput::getDevices());
  136. deviceManager.removeMidiInputCallback (list[lastInputIndex], this);
  137. const String newInput (list[index]);
  138. if (! deviceManager.isMidiInputEnabled (newInput))
  139. deviceManager.setMidiInputEnabled (newInput, true);
  140. deviceManager.addMidiInputCallback (newInput, this);
  141. midiInputList.setSelectedId (index + 1, dontSendNotification);
  142. lastInputIndex = index;
  143. }
  144. //==============================================================================
  145. void setMidiOutput (int index)
  146. {
  147. currentMidiOutput = nullptr;
  148. if (MidiOutput::getDevices() [index].isNotEmpty())
  149. {
  150. currentMidiOutput = MidiOutput::openDevice (index);
  151. jassert (currentMidiOutput);
  152. }
  153. }
  154. void comboBoxChanged (ComboBox* box) override
  155. {
  156. if (box == &midiInputList) setMidiInput (midiInputList.getSelectedItemIndex());
  157. if (box == &midiOutputList) setMidiOutput (midiOutputList.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, float velocity) override
  176. {
  177. if (! isAddingFromMidiInput)
  178. {
  179. MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
  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. if (currentMidiOutput != nullptr)
  200. currentMidiOutput->sendMessageNow (message);
  201. (new IncomingMessageCallback (this, message))->post();
  202. }
  203. void addMessageToList (const MidiMessage& message)
  204. {
  205. midiMessageList.add (message);
  206. triggerAsyncUpdate();
  207. }
  208. void handleAsyncUpdate() override
  209. {
  210. messageListBox.updateContent();
  211. messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1);
  212. messageListBox.repaint();
  213. }
  214. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo)
  215. };
  216. // This static object will register this demo type in a global list of demos..
  217. static JuceDemoType<MidiDemo> demo ("32 Audio: MIDI i/o");