/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-12 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../JuceDemoHeader.h" static String getMidiMessageDescription (const MidiMessage& m) { if (m.isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3); if (m.isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3); if (m.isProgramChange()) return "Program change " + String (m.getProgramChangeNumber()); if (m.isPitchWheel()) return "Pitch wheel " + String (m.getPitchWheelValue()); if (m.isAftertouch()) return "After touch " + MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + ": " + String (m.getAfterTouchValue()); if (m.isChannelPressure()) return "Channel pressure " + String (m.getChannelPressureValue()); if (m.isAllNotesOff()) return "All notes off"; if (m.isAllSoundOff()) return "All sound off"; if (m.isMetaEvent()) return "Meta event"; if (m.isController()) { String name (MidiMessage::getControllerName (m.getControllerNumber())); if (name.isEmpty()) name = "[" + String (m.getControllerNumber()) + "]"; return "Controler " + name + ": " + String (m.getControllerValue()); } return String::toHexString (m.getRawData(), m.getRawDataSize()); } //============================================================================== /** Simple list box that just displays a StringArray. */ class MidiLogListBoxModel : public ListBoxModel { public: MidiLogListBoxModel (const Array& list) : midiMessageList (list) { } int getNumRows() override { return midiMessageList.size(); } void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override { if (rowIsSelected) g.fillAll (Colours::blue.withAlpha (0.2f)); if (isPositiveAndBelow (row, midiMessageList.size())) { g.setColour (Colours::black); const MidiMessage& message = midiMessageList.getReference (row); double time = message.getTimeStamp(); g.drawText (String::formatted ("%02d:%02d:%02d", ((int) (time / 3600.0)) % 24, ((int) (time / 60.0)) % 60, ((int) time) % 60) + " - " + getMidiMessageDescription (message), Rectangle (width, height).reduced (4, 0), Justification::centredLeft, true); } } private: const Array& midiMessageList; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLogListBoxModel) }; //============================================================================== class MidiDemo : public Component, private ComboBox::Listener, private MidiInputCallback, private MidiKeyboardStateListener, private AsyncUpdater { public: MidiDemo() : deviceManager (MainAppWindow::getSharedAudioDeviceManager()), lastInputIndex (0), isAddingFromMidiInput (false), keyboardComponent (keyboardState, MidiKeyboardComponent::horizontalKeyboard), midiLogListBoxModel (midiMessageList) { setOpaque (true); addAndMakeVisible (midiInputListLabel); midiInputListLabel.setText ("MIDI Input:", dontSendNotification); midiInputListLabel.attachToComponent (&midiInputList, true); addAndMakeVisible (midiInputList); midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled"); const StringArray midiInputs (MidiInput::getDevices()); midiInputList.addItemList (midiInputs, 1); midiInputList.addListener (this); // find the first enabled device and use that bu default for (int i = 0; i < midiInputs.size(); ++i) { if (deviceManager.isMidiInputEnabled (midiInputs[i])) { setMidiInput (i); break; } } // if no enabled devices were found just use the first one in the list if (midiInputList.getSelectedId() == 0) setMidiInput (0); addAndMakeVisible (keyboardComponent); keyboardState.addListener (this); addAndMakeVisible (messageListBox); messageListBox.setModel (&midiLogListBoxModel); messageListBox.setColour (ListBox::backgroundColourId, Colour (0x32ffffff)); messageListBox.setColour (ListBox::outlineColourId, Colours::black); } ~MidiDemo() { keyboardState.removeListener (this); deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this); midiInputList.removeListener (this); } void paint (Graphics& g) override { fillTiledBackground (g); } void resized() override { Rectangle area (getLocalBounds()); midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8)); keyboardComponent.setBounds (area.removeFromTop (80).reduced(8)); messageListBox.setBounds (area.reduced (8)); } private: AudioDeviceManager& deviceManager; ComboBox midiInputList; Label midiInputListLabel; int lastInputIndex; bool isAddingFromMidiInput; MidiKeyboardState keyboardState; MidiKeyboardComponent keyboardComponent; ListBox messageListBox; Array midiMessageList; MidiLogListBoxModel midiLogListBoxModel; //============================================================================== /** Starts listening to a MIDI input device, enabling it if necessary. */ void setMidiInput (int index) { const StringArray list (MidiInput::getDevices()); deviceManager.removeMidiInputCallback (list[lastInputIndex], this); const String newInput (list[index]); if (! deviceManager.isMidiInputEnabled (newInput)) deviceManager.setMidiInputEnabled (newInput, true); deviceManager.addMidiInputCallback (newInput, this); midiInputList.setSelectedId (index + 1, dontSendNotification); lastInputIndex = index; } void comboBoxChanged (ComboBox* box) override { if (box == &midiInputList) setMidiInput (midiInputList.getSelectedItemIndex()); } // These methods handle callbacks from the midi device + on-screen keyboard.. void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override { const ScopedValueSetter scopedInputFlag (isAddingFromMidiInput, true); keyboardState.processNextMidiEvent (message); postMessageToList (message); } void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override { if (! isAddingFromMidiInput) { MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); postMessageToList (m); } } void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) override { if (! isAddingFromMidiInput) { MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber)); m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); postMessageToList (m); } } // This is used to dispach an incoming message to the message thread struct IncomingMessageCallback : public CallbackMessage { IncomingMessageCallback (MidiDemo* d, const MidiMessage& m) : demo (d), message (m) {} void messageCallback() override { if (demo != nullptr) demo->addMessageToList (message); } Component::SafePointer demo; MidiMessage message; }; void postMessageToList (const MidiMessage& message) { (new IncomingMessageCallback (this, message))->post(); } void addMessageToList (const MidiMessage& message) { midiMessageList.add (message); triggerAsyncUpdate(); } void handleAsyncUpdate() override { messageListBox.updateContent(); messageListBox.scrollToEnsureRowIsOnscreen (midiMessageList.size() - 1); messageListBox.repaint(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo); }; // This static object will register this demo type in a global list of demos.. static JuceDemoType demo ("32 Audio: MIDI i/o");