| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE examples.
 -    Copyright (c) 2022 - 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, vs2022, 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
 - {
 -     explicit MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {}
 - 
 -     MidiDeviceInfo deviceInfo;
 -     std::unique_ptr<MidiInput> inDevice;
 -     std::unique_ptr<MidiOutput> outDevice;
 - 
 -     using Ptr = ReferenceCountedObjectPtr<MidiDeviceListEntry>;
 - 
 -     void stopAndReset()
 -     {
 -         if (inDevice != nullptr)
 -             inDevice->stop();
 - 
 -         inDevice .reset();
 -         outDevice.reset();
 -     }
 - };
 - 
 - 
 - //==============================================================================
 - class MidiDemo  : public Component,
 -                   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);
 - 
 -         updateDeviceLists();
 -     }
 - 
 -     ~MidiDemo() override
 -     {
 -         midiInputs .clear();
 -         midiOutputs.clear();
 -         keyboardState.removeListener (this);
 - 
 -         midiInputSelector .reset();
 -         midiOutputSelector.reset();
 -     }
 - 
 -     //==============================================================================
 -     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)
 -     {
 -         auto& list = isInput ? midiInputs : midiOutputs;
 -         list[index]->stopAndReset();
 -     }
 - 
 -     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 : private ListBoxModel,
 -                                public ListBox
 -     {
 -         MidiDeviceListBox (const String& name,
 -                            MidiDemo& contentComponent,
 -                            bool isInputDeviceList)
 -             : ListBox (name),
 -               parent (contentComponent),
 -               isInput (isInputDeviceList)
 -         {
 -             setModel (this);
 -             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);
 -     }
 - 
 -     void updateDeviceLists()
 -     {
 -         for (const auto isInput : { true, false })
 -             updateDeviceList (isInput);
 -     }
 - 
 -     //==============================================================================
 -     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..." };
 - 
 -     ReferenceCountedArray<MidiDeviceListEntry> midiInputs, midiOutputs;
 -     std::unique_ptr<MidiDeviceListBox> midiInputSelector, midiOutputSelector;
 - 
 -     CriticalSection midiMonitorLock;
 -     Array<MidiMessage> incomingMessages;
 - 
 -     MidiDeviceListConnection connection = MidiDeviceListConnection::make ([this]
 -     {
 -         updateDeviceLists();
 -     });
 - 
 -     //==============================================================================
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo)
 - };
 
 
  |