1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00
JUCE/examples/MidiTest/Source/MainComponent.cpp

458 lines
17 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 "MainComponent.h"
//==============================================================================
struct MidiDeviceListEntry : ReferenceCountedObject
{
MidiDeviceListEntry (const String& deviceName) : name (deviceName) {}
String name;
ScopedPointer<MidiInput> inDevice;
ScopedPointer<MidiOutput> outDevice;
typedef ReferenceCountedObjectPtr<MidiDeviceListEntry> Ptr;
};
//==============================================================================
struct MidiCallbackMessage : public Message
{
MidiCallbackMessage (const MidiMessage& msg) : message (msg) {}
MidiMessage message;
};
//==============================================================================
class MidiDeviceListBox : public ListBox,
private ListBoxModel
{
public:
//==============================================================================
MidiDeviceListBox (const String& name,
MainContentComponent& contentComponent,
bool isInputDeviceList)
: ListBox (name, this),
parent (contentComponent),
isInput (isInputDeviceList)
{
setOutlineThickness (1);
setMultipleSelectionEnabled (true);
setClickingTogglesRowSelection (true);
}
//==============================================================================
int getNumRows() override
{
return isInput ? parent.getNumMidiInputs()
: parent.getNumMidiOutputs();
}
//==============================================================================
void paintListBoxItem (int rowNumber, Graphics &g,
int width, int height, bool rowIsSelected) override
{
const auto textColour = getLookAndFeel().findColour (ListBox::textColourId);
if (rowIsSelected)
g.fillAll (textColour.interpolatedWith (getLookAndFeel().findColour (ListBox::backgroundColourId), 0.5));
g.setColour (textColour);
g.setFont (height * 0.7f);
if (isInput)
{
if (rowNumber < parent.getNumMidiInputs())
g.drawText (parent.getMidiDevice (rowNumber, true)->name,
5, 0, width, height,
Justification::centredLeft, true);
}
else
{
if (rowNumber < parent.getNumMidiOutputs())
g.drawText (parent.getMidiDevice (rowNumber, false)->name,
5, 0, width, height,
Justification::centredLeft, true);
}
}
//==============================================================================
void selectedRowsChanged (int) override
{
SparseSet<int> newSelectedItems = getSelectedRows();
if (newSelectedItems != lastSelectedItems)
{
for (int i = 0; i < lastSelectedItems.size(); ++i)
{
if (! newSelectedItems.contains (lastSelectedItems[i]))
parent.closeDevice (isInput, lastSelectedItems[i]);
}
for (int i = 0; i < newSelectedItems.size(); ++i)
{
if (! lastSelectedItems.contains (newSelectedItems[i]))
parent.openDevice (isInput, newSelectedItems[i]);
}
lastSelectedItems = newSelectedItems;
}
}
//==============================================================================
void syncSelectedItemsWithDeviceList (const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices)
{
SparseSet<int> selectedRows;
for (int i = 0; i < midiDevices.size(); ++i)
if (midiDevices[i]->inDevice != nullptr || midiDevices[i]->outDevice != nullptr)
selectedRows.addRange (Range<int> (i, i+1));
lastSelectedItems = selectedRows;
updateContent();
setSelectedRows (selectedRows, dontSendNotification);
}
private:
//==============================================================================
MainContentComponent& parent;
bool isInput;
SparseSet<int> lastSelectedItems;
};
//==============================================================================
MainContentComponent::MainContentComponent ()
: midiInputLabel ("Midi Input Label", "MIDI Input:"),
midiOutputLabel ("Midi Output Label", "MIDI Output:"),
incomingMidiLabel ("Incoming Midi Label", "Received MIDI messages:"),
outgoingMidiLabel ("Outgoing Midi Label", "Play the keyboard to send MIDI messages..."),
midiKeyboard (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
midiMonitor ("MIDI Monitor"),
pairButton ("MIDI Bluetooth devices..."),
midiInputSelector (new MidiDeviceListBox ("Midi Input Selector", *this, true)),
midiOutputSelector (new MidiDeviceListBox ("Midi Input Selector", *this, false))
{
setSize (732, 520);
addLabelAndSetStyle (midiInputLabel);
addLabelAndSetStyle (midiOutputLabel);
addLabelAndSetStyle (incomingMidiLabel);
addLabelAndSetStyle (outgoingMidiLabel);
midiKeyboard.setName ("MIDI Keyboard");
addAndMakeVisible (midiKeyboard);
midiMonitor.setMultiLine (true);
midiMonitor.setReturnKeyStartsNewLine (false);
midiMonitor.setReadOnly (true);
midiMonitor.setScrollbarsShown (true);
midiMonitor.setCaretVisible (false);
midiMonitor.setPopupMenuEnabled (false);
midiMonitor.setText (String());
addAndMakeVisible (midiMonitor);
if (! BluetoothMidiDevicePairingDialogue::isAvailable())
pairButton.setEnabled (false);
addAndMakeVisible (pairButton);
pairButton.addListener (this);
keyboardState.addListener (this);
addAndMakeVisible (midiInputSelector);
addAndMakeVisible (midiOutputSelector);
startTimer (500);
}
//==============================================================================
void MainContentComponent::addLabelAndSetStyle (Label& label)
{
label.setFont (Font (15.00f, Font::plain));
label.setJustificationType (Justification::centredLeft);
label.setEditable (false, false, false);
label.setColour (TextEditor::textColourId, Colours::black);
label.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
addAndMakeVisible (label);
}
//==============================================================================
MainContentComponent::~MainContentComponent()
{
stopTimer();
midiInputs.clear();
midiOutputs.clear();
keyboardState.removeListener (this);
midiInputSelector = nullptr;
midiOutputSelector = nullptr;
midiOutputSelector = nullptr;
}
//==============================================================================
void MainContentComponent::paint (Graphics&)
{
}
//==============================================================================
void MainContentComponent::resized()
{
const int margin = 10;
midiInputLabel.setBounds (margin, margin,
(getWidth() / 2) - (2 * margin), 24);
midiOutputLabel.setBounds ((getWidth() / 2) + margin, margin,
(getWidth() / 2) - (2 * margin), 24);
midiInputSelector->setBounds (margin, (2 * margin) + 24,
(getWidth() / 2) - (2 * margin),
(getHeight() / 2) - ((4 * margin) + 24 + 24));
midiOutputSelector->setBounds ((getWidth() / 2) + margin, (2 * margin) + 24,
(getWidth() / 2) - (2 * margin),
(getHeight() / 2) - ((4 * margin) + 24 + 24));
pairButton.setBounds (margin, (getHeight() / 2) - (margin + 24),
getWidth() - (2 * margin), 24);
outgoingMidiLabel.setBounds (margin, getHeight() / 2, getWidth() - (2*margin), 24);
midiKeyboard.setBounds (margin, (getHeight() / 2) + (24 + margin), getWidth() - (2*margin), 64);
incomingMidiLabel.setBounds (margin, (getHeight() / 2) + (24 + (2 * margin) + 64),
getWidth() - (2*margin), 24);
int y = (getHeight() / 2) + ((2 * 24) + (3 * margin) + 64);
midiMonitor.setBounds (margin, y,
getWidth() - (2*margin), getHeight() - y - margin);
}
//==============================================================================
void MainContentComponent::buttonClicked (Button* buttonThatWasClicked)
{
if (buttonThatWasClicked == &pairButton)
RuntimePermissions::request (
RuntimePermissions::bluetoothMidi,
[] (bool wasGranted) { if (wasGranted) BluetoothMidiDevicePairingDialogue::open(); } );
}
//==============================================================================
bool MainContentComponent::hasDeviceListChanged (const StringArray& deviceNames, bool isInputDevice)
{
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
: midiOutputs;
if (deviceNames.size() != midiDevices.size())
return true;
for (int i = 0; i < deviceNames.size(); ++i)
if (deviceNames[i] != midiDevices[i]->name)
return true;
return false;
}
MidiDeviceListEntry::Ptr MainContentComponent::findDeviceWithName (const String& name, bool isInputDevice) const
{
const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
: midiOutputs;
for (int i = 0; i < midiDevices.size(); ++i)
if (midiDevices[i]->name == name)
return midiDevices[i];
return nullptr;
}
void MainContentComponent::closeUnpluggedDevices (StringArray& currentlyPluggedInDevices, bool isInputDevice)
{
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
: midiOutputs;
for (int i = midiDevices.size(); --i >= 0;)
{
MidiDeviceListEntry& d = *midiDevices[i];
if (! currentlyPluggedInDevices.contains (d.name))
{
if (isInputDevice ? d.inDevice != nullptr
: d.outDevice != nullptr)
closeDevice (isInputDevice, i);
midiDevices.remove (i);
}
}
}
void MainContentComponent::updateDeviceList (bool isInputDeviceList)
{
StringArray newDeviceNames = isInputDeviceList ? MidiInput::getDevices()
: MidiOutput::getDevices();
if (hasDeviceListChanged (newDeviceNames, isInputDeviceList))
{
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices
= isInputDeviceList ? midiInputs : midiOutputs;
closeUnpluggedDevices (newDeviceNames, isInputDeviceList);
ReferenceCountedArray<MidiDeviceListEntry> newDeviceList;
// add all currently plugged-in devices to the device list
for (int i = 0; i < newDeviceNames.size(); ++i)
{
MidiDeviceListEntry::Ptr entry = findDeviceWithName (newDeviceNames[i], isInputDeviceList);
if (entry == nullptr)
entry = new MidiDeviceListEntry (newDeviceNames[i]);
newDeviceList.add (entry);
}
// actually update the device list
midiDevices = newDeviceList;
// update the selection status of the combo-box
if (MidiDeviceListBox* midiSelector = isInputDeviceList ? midiInputSelector : midiOutputSelector)
midiSelector->syncSelectedItemsWithDeviceList (midiDevices);
}
}
//==============================================================================
void MainContentComponent::timerCallback ()
{
updateDeviceList (true);
updateDeviceList (false);
}
//==============================================================================
void MainContentComponent::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
sendToOutputs (m);
}
//==============================================================================
void MainContentComponent::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
sendToOutputs (m);
}
//==============================================================================
void MainContentComponent::sendToOutputs(const MidiMessage& msg)
{
for (int i = 0; i < midiOutputs.size(); ++i)
if (midiOutputs[i]->outDevice != nullptr)
midiOutputs[i]->outDevice->sendMessageNow (msg);
}
//==============================================================================
void MainContentComponent::handleIncomingMidiMessage (MidiInput* /*source*/, const MidiMessage &message)
{
// This is called on the MIDI thread
if (message.isNoteOnOrOff())
postMessage (new MidiCallbackMessage (message));
}
//==============================================================================
void MainContentComponent::handleMessage (const Message& msg)
{
// This is called on the message loop
const MidiMessage& mm = dynamic_cast<const MidiCallbackMessage&> (msg).message;
String midiString;
midiString << (mm.isNoteOn() ? String ("Note on: ") : String ("Note off: "));
midiString << (MidiMessage::getMidiNoteName (mm.getNoteNumber(), true, true, true));
midiString << (String (" vel = "));
midiString << static_cast<int>(mm.getVelocity());
midiString << "\n";
midiMonitor.insertTextAtCaret (midiString);
}
//==============================================================================
void MainContentComponent::openDevice (bool isInput, int index)
{
if (isInput)
{
jassert (midiInputs[index]->inDevice == nullptr);
midiInputs[index]->inDevice = MidiInput::openDevice (index, this);
if (midiInputs[index]->inDevice == nullptr)
{
DBG ("MainContentComponent::openDevice: open input device for index = " << index << " failed!" );
return;
}
midiInputs[index]->inDevice->start();
}
else
{
jassert (midiOutputs[index]->outDevice == nullptr);
midiOutputs[index]->outDevice = MidiOutput::openDevice (index);
if (midiOutputs[index]->outDevice == nullptr)
DBG ("MainContentComponent::openDevice: open output device for index = " << index << " failed!" );
}
}
//==============================================================================
void MainContentComponent::closeDevice (bool isInput, int index)
{
if (isInput)
{
jassert (midiInputs[index]->inDevice != nullptr);
midiInputs[index]->inDevice->stop();
midiInputs[index]->inDevice = nullptr;
}
else
{
jassert (midiOutputs[index]->outDevice != nullptr);
midiOutputs[index]->outDevice = nullptr;
}
}
//==============================================================================
int MainContentComponent::getNumMidiInputs() const noexcept
{
return midiInputs.size();
}
//==============================================================================
int MainContentComponent::getNumMidiOutputs() const noexcept
{
return midiOutputs.size();
}
//==============================================================================
ReferenceCountedObjectPtr<MidiDeviceListEntry>
MainContentComponent::getMidiDevice (int index, bool isInput) const noexcept
{
return isInput ? midiInputs[index] : midiOutputs[index];
}