1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/examples/Demo/Source/Demos/MidiDemo.cpp

271 lines
9.6 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../JuceDemoHeader.h"
/** Simple list box that just displays a StringArray. */
class MidiLogListBoxModel : public ListBoxModel
{
public:
MidiLogListBoxModel (const Array<MidiMessage>& 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 (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText,
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)
+ " - " + message.getDescription(),
Rectangle<int> (width, height).reduced (4, 0),
Justification::centredLeft, true);
}
}
private:
const Array<MidiMessage>& 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);
// MIDI Inputs
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 by 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);
// MIDI Outputs
addAndMakeVisible (midiOutputListLabel);
midiOutputListLabel.setText ("MIDI Output:", dontSendNotification);
midiOutputListLabel.attachToComponent (&midiOutputList, true);
addAndMakeVisible (midiOutputList);
midiOutputList.setTextWhenNoChoicesAvailable ("No MIDI Output Enabled");
midiOutputList.addItemList (MidiOutput::getDevices(), 1);
midiOutputList.addListener (this);
addAndMakeVisible (keyboardComponent);
keyboardState.addListener (this);
addAndMakeVisible (messageListBox);
messageListBox.setModel (&midiLogListBoxModel);
}
~MidiDemo()
{
keyboardState.removeListener (this);
deviceManager.removeMidiInputCallback (MidiInput::getDevices()[midiInputList.getSelectedItemIndex()], this);
midiInputList.removeListener (this);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
Rectangle<int> area (getLocalBounds());
midiInputList.setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
midiOutputList.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, midiOutputList;
Label midiInputListLabel, midiOutputListLabel;
int lastInputIndex;
bool isAddingFromMidiInput;
MidiKeyboardState keyboardState;
MidiKeyboardComponent keyboardComponent;
ListBox messageListBox;
Array<MidiMessage> midiMessageList;
MidiLogListBoxModel midiLogListBoxModel;
ScopedPointer<MidiOutput> currentMidiOutput;
//==============================================================================
/** 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 setMidiOutput (int index)
{
currentMidiOutput = nullptr;
if (MidiOutput::getDevices() [index].isNotEmpty())
{
currentMidiOutput = MidiOutput::openDevice (index);
jassert (currentMidiOutput);
}
}
void comboBoxChanged (ComboBox* box) override
{
if (box == &midiInputList) setMidiInput (midiInputList.getSelectedItemIndex());
if (box == &midiOutputList) setMidiOutput (midiOutputList.getSelectedItemIndex());
}
// These methods handle callbacks from the midi device + on-screen keyboard..
void handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) override
{
const ScopedValueSetter<bool> 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, float velocity) override
{
if (! isAddingFromMidiInput)
{
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
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<MidiDemo> demo;
MidiMessage message;
};
void postMessageToList (const MidiMessage& message)
{
if (currentMidiOutput != nullptr)
currentMidiOutput->sendMessageNow (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<MidiDemo> demo ("32 Audio: MIDI i/o");