Problem creating devices from MidiOutput Class


#1

Hey,

I’m just starting out with JUCE and I’m stuck at a problem with the MidiOutput class that I can’t seem to figure out. I’m very new to C++ and JUCE so it could well be I’m missing something fairly obvious.

I’m going from the “Midi Test” example that comes with Projucer. I want to add 20 Midi Output Ports, that can be picked up by a DAW. I’m using MidiOutput::createNewMidiDevice(); for this.

This is the function where I do this:

void MainContentComponent::addMidiOutputs()
{


    for (int i = 0; i < 20; ++i)
    {
        const String devName = "MidiOut Port " + String(i+1);
       //For Debugging      
       midiMonitor.insertTextAtCaret ("\nMIDIOutput Created: " + devName);
        
  
        
        MidiOutput *newMidiOut = MidiOutput::createNewDevice(devName);
        
        MidiDeviceListEntry *newEntry = new MidiDeviceListEntry(newMidiOut->getName());
        
        midiOutputs.add(newEntry);
        
    
    }
}

When I run the program and check in my DAW the MidiPorts created show up as Inputs. But in the program itself they show up in the Midi Input section and MidiOutput::getDevices();doesn’t find those devices either. Also I can’t open the devices (MidiOutput::openDevice();) and also can’t send Midi Messages to them.

This is the whole code including everything from the example:

/*
  ==============================================================================

   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);
    
     addMidiOutputs();
    
    

}

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


void MainContentComponent::addMidiOutputs()
{


    for (int i = 0; i < 20; ++i)
    {
        const String devName = "MidiOut Port " + String(i+1);
        midiMonitor.insertTextAtCaret ("\nMIDIOutput Created: " + devName);
        
  
        MidiOutput *newMidiOut = MidiOutput::createNewDevice(devName);
        
        MidiDeviceListEntry *newEntry = new MidiDeviceListEntry(devName);
        
   
        midiOutputs.add(newEntry);
        
    
    }
    


   
    
}


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));
    
    
    //BENE FORWARD TO MAX 2
    const int firstDivisiDevIndex = 1;
    
    
    MidiOutput *midiOut = MidiOutput::openDevice(firstDivisiDevIndex);
    midiOut->sendMessageNow(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];
}

What am I doing wrong?


#2

I don’t think, you are doing something wrong about outputs shown as inputs in the DAW, but a MidiOutput of your app is a MidiInput to the host maybe?
So the host reads the midi generated from your app, hence it shows inputs…


#3

Thanks daniel, that actually makes sense and now I feel stupid :slight_smile: Thinking about it like this and reading over my code again I was able to solve the problem and now I am able to pass a midi input through to my virtual ports! very nice, thank you!


#4

@jules @benediktsailer could you tell me how did you fix that, Should I call MidiOutput::createNewDevice or MidiInput::createNewDevice? How to send messages to the new device?


#5

Can you describe your problem in your own words?
The OP missed the fact, that a separate app would have to show an Output as it’s input, so there can be data sent from the output to the input.
What is your problem?


#6

Problem solved, I call the addMessageNow on the midiout device I just created and other apps could receive it properly. Thanks for the reply!


#7

Great, glad you figured it out :slight_smile: