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.

271 lines
9.5KB

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