git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
483
deps/juce/examples/Audio/MidiDemo.h
vendored
Normal file
483
deps/juce/examples/Audio/MidiDemo.h
vendored
Normal file
@ -0,0 +1,483 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE examples.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
|
||||
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
|
||||
PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
The block below describes the properties of this PIP. A PIP is a short snippet
|
||||
of code that can be read by the Projucer and used to generate a JUCE project.
|
||||
|
||||
BEGIN_JUCE_PIP_METADATA
|
||||
|
||||
name: MidiDemo
|
||||
version: 1.0.0
|
||||
vendor: JUCE
|
||||
website: http://juce.com
|
||||
description: Handles incoming and outcoming midi messages.
|
||||
|
||||
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
|
||||
juce_audio_processors, juce_audio_utils, juce_core,
|
||||
juce_data_structures, juce_events, juce_graphics,
|
||||
juce_gui_basics, juce_gui_extra
|
||||
exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
|
||||
|
||||
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
|
||||
|
||||
type: Component
|
||||
mainClass: MidiDemo
|
||||
|
||||
useLocalCopy: 1
|
||||
|
||||
END_JUCE_PIP_METADATA
|
||||
|
||||
*******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
//==============================================================================
|
||||
struct MidiDeviceListEntry : ReferenceCountedObject
|
||||
{
|
||||
MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {}
|
||||
|
||||
MidiDeviceInfo deviceInfo;
|
||||
std::unique_ptr<MidiInput> inDevice;
|
||||
std::unique_ptr<MidiOutput> outDevice;
|
||||
|
||||
using Ptr = ReferenceCountedObjectPtr<MidiDeviceListEntry>;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class MidiDemo : public Component,
|
||||
private Timer,
|
||||
private MidiKeyboardState::Listener,
|
||||
private MidiInputCallback,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
MidiDemo()
|
||||
: midiKeyboard (keyboardState, MidiKeyboardComponent::horizontalKeyboard),
|
||||
midiInputSelector (new MidiDeviceListBox ("Midi Input Selector", *this, true)),
|
||||
midiOutputSelector (new MidiDeviceListBox ("Midi Output Selector", *this, false))
|
||||
{
|
||||
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 ({});
|
||||
addAndMakeVisible (midiMonitor);
|
||||
|
||||
if (! BluetoothMidiDevicePairingDialogue::isAvailable())
|
||||
pairButton.setEnabled (false);
|
||||
|
||||
addAndMakeVisible (pairButton);
|
||||
pairButton.onClick = []
|
||||
{
|
||||
RuntimePermissions::request (RuntimePermissions::bluetoothMidi,
|
||||
[] (bool wasGranted)
|
||||
{
|
||||
if (wasGranted)
|
||||
BluetoothMidiDevicePairingDialogue::open();
|
||||
});
|
||||
};
|
||||
keyboardState.addListener (this);
|
||||
|
||||
addAndMakeVisible (midiInputSelector .get());
|
||||
addAndMakeVisible (midiOutputSelector.get());
|
||||
|
||||
setSize (732, 520);
|
||||
|
||||
startTimer (500);
|
||||
}
|
||||
|
||||
~MidiDemo() override
|
||||
{
|
||||
stopTimer();
|
||||
midiInputs .clear();
|
||||
midiOutputs.clear();
|
||||
keyboardState.removeListener (this);
|
||||
|
||||
midiInputSelector .reset();
|
||||
midiOutputSelector.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void timerCallback() override
|
||||
{
|
||||
updateDeviceList (true);
|
||||
updateDeviceList (false);
|
||||
}
|
||||
|
||||
void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
|
||||
{
|
||||
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
|
||||
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
|
||||
sendToOutputs (m);
|
||||
}
|
||||
|
||||
void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override
|
||||
{
|
||||
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
|
||||
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
|
||||
sendToOutputs (m);
|
||||
}
|
||||
|
||||
void paint (Graphics&) override {}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto 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);
|
||||
|
||||
auto y = (getHeight() / 2) + ((2 * 24) + (3 * margin) + 64);
|
||||
midiMonitor.setBounds (margin, y,
|
||||
getWidth() - (2 * margin), getHeight() - y - margin);
|
||||
}
|
||||
|
||||
void openDevice (bool isInput, int index)
|
||||
{
|
||||
if (isInput)
|
||||
{
|
||||
jassert (midiInputs[index]->inDevice.get() == nullptr);
|
||||
midiInputs[index]->inDevice = MidiInput::openDevice (midiInputs[index]->deviceInfo.identifier, this);
|
||||
|
||||
if (midiInputs[index]->inDevice.get() == nullptr)
|
||||
{
|
||||
DBG ("MidiDemo::openDevice: open input device for index = " << index << " failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
midiInputs[index]->inDevice->start();
|
||||
}
|
||||
else
|
||||
{
|
||||
jassert (midiOutputs[index]->outDevice.get() == nullptr);
|
||||
midiOutputs[index]->outDevice = MidiOutput::openDevice (midiOutputs[index]->deviceInfo.identifier);
|
||||
|
||||
if (midiOutputs[index]->outDevice.get() == nullptr)
|
||||
{
|
||||
DBG ("MidiDemo::openDevice: open output device for index = " << index << " failed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void closeDevice (bool isInput, int index)
|
||||
{
|
||||
if (isInput)
|
||||
{
|
||||
jassert (midiInputs[index]->inDevice.get() != nullptr);
|
||||
midiInputs[index]->inDevice->stop();
|
||||
midiInputs[index]->inDevice.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
jassert (midiOutputs[index]->outDevice.get() != nullptr);
|
||||
midiOutputs[index]->outDevice.reset();
|
||||
}
|
||||
}
|
||||
|
||||
int getNumMidiInputs() const noexcept
|
||||
{
|
||||
return midiInputs.size();
|
||||
}
|
||||
|
||||
int getNumMidiOutputs() const noexcept
|
||||
{
|
||||
return midiOutputs.size();
|
||||
}
|
||||
|
||||
ReferenceCountedObjectPtr<MidiDeviceListEntry> getMidiDevice (int index, bool isInput) const noexcept
|
||||
{
|
||||
return isInput ? midiInputs[index] : midiOutputs[index];
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct MidiDeviceListBox : public ListBox,
|
||||
private ListBoxModel
|
||||
{
|
||||
MidiDeviceListBox (const String& name,
|
||||
MidiDemo& 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
|
||||
{
|
||||
auto textColour = getLookAndFeel().findColour (ListBox::textColourId);
|
||||
|
||||
if (rowIsSelected)
|
||||
g.fillAll (textColour.interpolatedWith (getLookAndFeel().findColour (ListBox::backgroundColourId), 0.5));
|
||||
|
||||
|
||||
g.setColour (textColour);
|
||||
g.setFont ((float) height * 0.7f);
|
||||
|
||||
if (isInput)
|
||||
{
|
||||
if (rowNumber < parent.getNumMidiInputs())
|
||||
g.drawText (parent.getMidiDevice (rowNumber, true)->deviceInfo.name,
|
||||
5, 0, width, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rowNumber < parent.getNumMidiOutputs())
|
||||
g.drawText (parent.getMidiDevice (rowNumber, false)->deviceInfo.name,
|
||||
5, 0, width, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void selectedRowsChanged (int) override
|
||||
{
|
||||
auto newSelectedItems = getSelectedRows();
|
||||
if (newSelectedItems != lastSelectedItems)
|
||||
{
|
||||
for (auto i = 0; i < lastSelectedItems.size(); ++i)
|
||||
{
|
||||
if (! newSelectedItems.contains (lastSelectedItems[i]))
|
||||
parent.closeDevice (isInput, lastSelectedItems[i]);
|
||||
}
|
||||
|
||||
for (auto 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 (auto i = 0; i < midiDevices.size(); ++i)
|
||||
if (midiDevices[i]->inDevice.get() != nullptr || midiDevices[i]->outDevice.get() != nullptr)
|
||||
selectedRows.addRange (Range<int> (i, i + 1));
|
||||
|
||||
lastSelectedItems = selectedRows;
|
||||
updateContent();
|
||||
setSelectedRows (selectedRows, dontSendNotification);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
MidiDemo& parent;
|
||||
bool isInput;
|
||||
SparseSet<int> lastSelectedItems;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void handleIncomingMidiMessage (MidiInput* /*source*/, const MidiMessage& message) override
|
||||
{
|
||||
// This is called on the MIDI thread
|
||||
const ScopedLock sl (midiMonitorLock);
|
||||
incomingMessages.add (message);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
// This is called on the message loop
|
||||
Array<MidiMessage> messages;
|
||||
|
||||
{
|
||||
const ScopedLock sl (midiMonitorLock);
|
||||
messages.swapWith (incomingMessages);
|
||||
}
|
||||
|
||||
String messageText;
|
||||
|
||||
for (auto& m : messages)
|
||||
messageText << m.getDescription() << "\n";
|
||||
|
||||
midiMonitor.insertTextAtCaret (messageText);
|
||||
}
|
||||
|
||||
void sendToOutputs (const MidiMessage& msg)
|
||||
{
|
||||
for (auto midiOutput : midiOutputs)
|
||||
if (midiOutput->outDevice.get() != nullptr)
|
||||
midiOutput->outDevice->sendMessageNow (msg);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool hasDeviceListChanged (const Array<MidiDeviceInfo>& availableDevices, bool isInputDevice)
|
||||
{
|
||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
|
||||
: midiOutputs;
|
||||
|
||||
if (availableDevices.size() != midiDevices.size())
|
||||
return true;
|
||||
|
||||
for (auto i = 0; i < availableDevices.size(); ++i)
|
||||
if (availableDevices[i] != midiDevices[i]->deviceInfo)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ReferenceCountedObjectPtr<MidiDeviceListEntry> findDevice (MidiDeviceInfo device, bool isInputDevice) const
|
||||
{
|
||||
const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
|
||||
: midiOutputs;
|
||||
|
||||
for (auto& d : midiDevices)
|
||||
if (d->deviceInfo == device)
|
||||
return d;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void closeUnpluggedDevices (const Array<MidiDeviceInfo>& currentlyPluggedInDevices, bool isInputDevice)
|
||||
{
|
||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
|
||||
: midiOutputs;
|
||||
|
||||
for (auto i = midiDevices.size(); --i >= 0;)
|
||||
{
|
||||
auto& d = *midiDevices[i];
|
||||
|
||||
if (! currentlyPluggedInDevices.contains (d.deviceInfo))
|
||||
{
|
||||
if (isInputDevice ? d.inDevice .get() != nullptr
|
||||
: d.outDevice.get() != nullptr)
|
||||
closeDevice (isInputDevice, i);
|
||||
|
||||
midiDevices.remove (i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateDeviceList (bool isInputDeviceList)
|
||||
{
|
||||
auto availableDevices = isInputDeviceList ? MidiInput::getAvailableDevices()
|
||||
: MidiOutput::getAvailableDevices();
|
||||
|
||||
if (hasDeviceListChanged (availableDevices, isInputDeviceList))
|
||||
{
|
||||
|
||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices
|
||||
= isInputDeviceList ? midiInputs : midiOutputs;
|
||||
|
||||
closeUnpluggedDevices (availableDevices, isInputDeviceList);
|
||||
|
||||
ReferenceCountedArray<MidiDeviceListEntry> newDeviceList;
|
||||
|
||||
// add all currently plugged-in devices to the device list
|
||||
for (auto& newDevice : availableDevices)
|
||||
{
|
||||
MidiDeviceListEntry::Ptr entry = findDevice (newDevice, isInputDeviceList);
|
||||
|
||||
if (entry == nullptr)
|
||||
entry = new MidiDeviceListEntry (newDevice);
|
||||
|
||||
newDeviceList.add (entry);
|
||||
}
|
||||
|
||||
// actually update the device list
|
||||
midiDevices = newDeviceList;
|
||||
|
||||
// update the selection status of the combo-box
|
||||
if (auto* midiSelector = isInputDeviceList ? midiInputSelector.get() : midiOutputSelector.get())
|
||||
midiSelector->syncSelectedItemsWithDeviceList (midiDevices);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void 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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Label midiInputLabel { "Midi Input Label", "MIDI Input:" };
|
||||
Label midiOutputLabel { "Midi Output Label", "MIDI Output:" };
|
||||
Label incomingMidiLabel { "Incoming Midi Label", "Received MIDI messages:" };
|
||||
Label outgoingMidiLabel { "Outgoing Midi Label", "Play the keyboard to send MIDI messages..." };
|
||||
MidiKeyboardState keyboardState;
|
||||
MidiKeyboardComponent midiKeyboard;
|
||||
TextEditor midiMonitor { "MIDI Monitor" };
|
||||
TextButton pairButton { "MIDI Bluetooth devices..." };
|
||||
|
||||
std::unique_ptr<MidiDeviceListBox> midiInputSelector, midiOutputSelector;
|
||||
ReferenceCountedArray<MidiDeviceListEntry> midiInputs, midiOutputs;
|
||||
|
||||
CriticalSection midiMonitorLock;
|
||||
Array<MidiMessage> incomingMessages;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo)
|
||||
};
|
Reference in New Issue
Block a user