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.

270 lines
9.6KB

  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. /** Simple list box that just displays a StringArray. */
  19. class MidiLogListBoxModel : public ListBoxModel
  20. {
  21. public:
  22. MidiLogListBoxModel (const Array<MidiMessage>& list)
  23. : midiMessageList (list)
  24. {
  25. }
  26. int getNumRows() override { return midiMessageList.size(); }
  27. void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
  28. {
  29. if (rowIsSelected)
  30. g.fillAll (Colours::blue.withAlpha (0.2f));
  31. if (isPositiveAndBelow (row, midiMessageList.size()))
  32. {
  33. g.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
  34. Colours::black));
  35. const MidiMessage& message = midiMessageList.getReference (row);
  36. double time = message.getTimeStamp();
  37. g.drawText (String::formatted ("%02d:%02d:%02d",
  38. ((int) (time / 3600.0)) % 24,
  39. ((int) (time / 60.0)) % 60,
  40. ((int) time) % 60)
  41. + " - " + message.getDescription(),
  42. Rectangle<int> (width, height).reduced (4, 0),
  43. Justification::centredLeft, true);
  44. }
  45. }
  46. private:
  47. const Array<MidiMessage>& midiMessageList;
  48. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLogListBoxModel)
  49. };
  50. //==============================================================================
  51. class MidiDemo : public Component,
  52. private ComboBox::Listener,
  53. private MidiInputCallback,
  54. private MidiKeyboardStateListener,
  55. private AsyncUpdater
  56. {
  57. public:
  58. MidiDemo()
  59. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  60. lastInputIndex (0), isAddingFromMidiInput (false),
  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. const StringArray midiInputs (MidiInput::getDevices());
  72. midiInputList.addItemList (midiInputs, 1);
  73. midiInputList.addListener (this);
  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.addListener (this);
  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. midiInputList.removeListener (this);
  104. }
  105. void paint (Graphics& g) override
  106. {
  107. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  108. }
  109. void resized() override
  110. {
  111. Rectangle<int> area (getLocalBounds());
  112. midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  113. midiOutputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
  114. keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
  115. messageListBox.setBounds (area.reduced (8));
  116. }
  117. private:
  118. AudioDeviceManager& deviceManager;
  119. ComboBox midiInputList, midiOutputList;
  120. Label midiInputListLabel, midiOutputListLabel;
  121. int lastInputIndex;
  122. bool isAddingFromMidiInput;
  123. MidiKeyboardState keyboardState;
  124. MidiKeyboardComponent keyboardComponent;
  125. ListBox messageListBox;
  126. Array<MidiMessage> midiMessageList;
  127. MidiLogListBoxModel midiLogListBoxModel;
  128. ScopedPointer<MidiOutput> currentMidiOutput;
  129. //==============================================================================
  130. /** Starts listening to a MIDI input device, enabling it if necessary. */
  131. void setMidiInput (int index)
  132. {
  133. const StringArray list (MidiInput::getDevices());
  134. deviceManager.removeMidiInputCallback (list[lastInputIndex], this);
  135. const String newInput (list[index]);
  136. if (! deviceManager.isMidiInputEnabled (newInput))
  137. deviceManager.setMidiInputEnabled (newInput, true);
  138. deviceManager.addMidiInputCallback (newInput, this);
  139. midiInputList.setSelectedId (index + 1, dontSendNotification);
  140. lastInputIndex = index;
  141. }
  142. //==============================================================================
  143. void setMidiOutput (int index)
  144. {
  145. currentMidiOutput = nullptr;
  146. if (MidiOutput::getDevices() [index].isNotEmpty())
  147. {
  148. currentMidiOutput = MidiOutput::openDevice (index);
  149. jassert (currentMidiOutput);
  150. }
  151. }
  152. void comboBoxChanged (ComboBox* box) override
  153. {
  154. if (box == &midiInputList) setMidiInput (midiInputList.getSelectedItemIndex());
  155. if (box == &midiOutputList) setMidiOutput (midiOutputList.getSelectedItemIndex());
  156. }
  157. // These methods handle callbacks from the midi device + on-screen keyboard..
  158. void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override
  159. {
  160. const ScopedValueSetter<bool> scopedInputFlag (isAddingFromMidiInput, true);
  161. keyboardState.processNextMidiEvent (message);
  162. postMessageToList (message);
  163. }
  164. void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  165. {
  166. if (! isAddingFromMidiInput)
  167. {
  168. MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
  169. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  170. postMessageToList (m);
  171. }
  172. }
  173. void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
  174. {
  175. if (! isAddingFromMidiInput)
  176. {
  177. MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
  178. m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
  179. postMessageToList (m);
  180. }
  181. }
  182. // This is used to dispach an incoming message to the message thread
  183. struct IncomingMessageCallback : public CallbackMessage
  184. {
  185. IncomingMessageCallback (MidiDemo* d, const MidiMessage& m)
  186. : demo (d), message (m) {}
  187. void messageCallback() override
  188. {
  189. if (demo != nullptr)
  190. demo->addMessageToList (message);
  191. }
  192. Component::SafePointer<MidiDemo> demo;
  193. MidiMessage message;
  194. };
  195. void postMessageToList (const MidiMessage& message)
  196. {
  197. if (currentMidiOutput != nullptr)
  198. currentMidiOutput->sendMessageNow (message);
  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");