|
- /*
- ==============================================================================
-
- 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];
- }
|