MidiInput and MidiOutput as devices


#1

would be a better approach to redefine MidiInput and MidiOutput to be part of a MidiDevice that can be subclassed and plugged at runtime with a MidiDeviceManager so more midi devices can be incorporated with different implementation ? just thorwing some ideas, i want to plug in jack midi support i need to differentiate it from the standard alsa sequencer handling… would be possible ?


#2

i’ve initially made MidiOutput, but i can’t see anything changes in the midi flow:

class MidiOutputDevice
{
public:
    MidiOutputDevice (MidiOutput* const midiOutput_,
                      snd_seq_t* const seqHandle_,
                      int const outputPort_)
        :
          midiOutput (midiOutput_),
          seqHandle (seqHandle_),
          outputPort (outputPort_)
    {
        jassert (seqHandle != 0 && midiOutput != 0);
    }

    ~MidiOutputDevice()
    {
        snd_seq_close (seqHandle);
    }

    void sendMessageNow (const MidiMessage& message)
    {
        const int currentEventSize = message.getRawDataSize ();
        snd_midi_event_t* midiParser;

        if (snd_midi_event_new (currentEventSize, &midiParser) >= 0)
        {
            snd_seq_event_t event;
            snd_seq_ev_clear (&event);

            snd_midi_event_encode (midiParser, message.getRawData(), message.getRawDataSize (), &event);

            snd_midi_event_reset_encode (midiParser);

            snd_seq_ev_set_source (&event, outputPort);
            snd_seq_ev_set_subs (&event);
            snd_seq_ev_set_direct (&event);

            snd_seq_event_output_direct (seqHandle, &event);

            snd_midi_event_free (midiParser);
        }
    }

    juce_UseDebuggingNewOperator

private:
    MidiOutput* const midiOutput;
    snd_seq_t* const seqHandle;
    int outputPort;
};


static snd_seq_t* iterateOutputDevices (StringArray& deviceNamesFound, const int deviceIndexToOpen, int& portIndexOpened)
{
    snd_seq_t* returnedHandle = 0;

    snd_seq_t* seqHandle;
    if (snd_seq_open (&seqHandle, "default", SND_SEQ_OPEN_OUTPUT, 0) == 0)
    {
        snd_seq_system_info_t* systemInfo;
        snd_seq_client_info_t* clientInfo;

        if (snd_seq_system_info_malloc (&systemInfo) == 0)
        {
            if (snd_seq_system_info (seqHandle, systemInfo) == 0
                 && snd_seq_client_info_malloc (&clientInfo) == 0)
            {
                int numClients = snd_seq_system_info_get_cur_clients (systemInfo);

                while (--numClients >= 0 && returnedHandle == 0)
                {
                    if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
                    {
                        snd_seq_port_info_t* portInfo;
                        if (snd_seq_port_info_malloc (&portInfo) == 0)
                        {
                            int numPorts = snd_seq_client_info_get_num_ports (clientInfo);
                            const int client = snd_seq_client_info_get_client (clientInfo);

                            snd_seq_port_info_set_client (portInfo, client);
                            snd_seq_port_info_set_port (portInfo, -1);

                            while (--numPorts >= 0)
                            {
                                if (snd_seq_query_next_port (seqHandle, portInfo) == 0
                                     && (snd_seq_port_info_get_capability (portInfo) & SND_SEQ_PORT_CAP_WRITE) != 0)
                                {
                                    deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo));

                                    if (deviceNamesFound.size() == deviceIndexToOpen + 1)
                                    {
                                        const int sourcePort = snd_seq_port_info_get_port (portInfo);
                                        const int sourceClient = snd_seq_client_info_get_client (clientInfo);

                                        if (sourcePort != -1)
                                        {
                                            snd_seq_set_client_name (seqHandle, "Juce Midi Output");

                                            const int portId
                                                = snd_seq_create_simple_port (seqHandle, "Juce Midi Out Port",
                                                                              SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
                                                                              // SND_SEQ_PORT_TYPE_APPLICATION);
                                                                              SND_SEQ_PORT_TYPE_MIDI_GENERIC);

                                            snd_seq_connect_from (seqHandle, portId, sourceClient, sourcePort);

                                            returnedHandle = seqHandle;
                                            portIndexOpened = portId;

                                            printf ("setup output port %s : %d -> %d \n", snd_seq_client_info_get_name (clientInfo), portId, sourcePort);

                                            break;
                                        }
                                    }
                                }
                            }

                            snd_seq_port_info_free (portInfo);
                        }
                    }
                }

                snd_seq_client_info_free (clientInfo);
            }

            snd_seq_system_info_free (systemInfo);
        }

        if (returnedHandle == 0)
            snd_seq_close (seqHandle);
    }

    return returnedHandle;
}

//==============================================================================
const StringArray MidiOutput::getDevices()
{
    int portIndex = 0;
    StringArray devices;
    iterateOutputDevices (devices, -1, portIndex);
    return devices;
}

int MidiOutput::getDefaultDeviceIndex()
{
    return 0;
}

MidiOutput* MidiOutput::openDevice (int deviceIndex)
{
    MidiOutput* newDevice = 0;

    int portIndex = 0;
    StringArray devices;
    snd_seq_t* const handle = iterateOutputDevices (devices, deviceIndex, portIndex);

    if (handle != 0)
    {
        newDevice = new MidiOutput ();
        newDevice->internal = new MidiOutputDevice (newDevice, handle, portIndex);
    }

    return newDevice;
}

MidiOutput::MidiOutput() :
    internal (0)
{
}

MidiOutput::~MidiOutput()
{
    MidiOutputDevice* const device = (MidiOutputDevice*) internal;
    delete device;
}

void MidiOutput::reset()
{
    // @XXX : what i need to do here ?
}

bool MidiOutput::getVolume (float& leftVol, float& rightVol)
{
    // @XXX : mmmh...
    return false;
}

void MidiOutput::setVolume (float leftVol, float rightVol)
{
    // @XXX : doh !
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
    ((MidiOutputDevice*) internal)->sendMessageNow (message);
}


it enumerates correctly the possible writable devices as outputs, the connections are made without errors, but i still can’t receive any midi messages.

other thing. i’ve made the event to be sent in direct mode (no timestamp)
with snd_seq_event_output_direct. should i care of event timestamps ? but how could my application timestamp be understood by another application ?


#3

just for trying if there are errors in encoding the bytes of the midi message, i’ve replaced the sendMessageNow function to be like:

    void sendMessageNow (const MidiMessage& message)
    {
        snd_seq_event_t ev;

        snd_seq_ev_clear(&ev);
        snd_seq_ev_set_source(&ev, outputPort);
        snd_seq_ev_set_subs(&ev);
        snd_seq_ev_set_direct(&ev);

        if (message.isNoteOnOrOff())
        {
            if (message.isNoteOn ())
                ev.type = SND_SEQ_EVENT_NOTEON;
            else if (message.isNoteOff ())
                ev.type = SND_SEQ_EVENT_NOTEOFF;

            ev.data.note.channel = message.getChannel ();
            ev.data.note.note = message.getNoteNumber ();
            ev.data.note.velocity = message.getVelocity ();

            printf("Sending note on/off to port %d, ch=%d, note=%d, vel=%d, pid=%d\n",
                   outputPort, ev.data.note.channel, ev.data.note.note, ev.data.note.velocity, getpid());
        }
        else if (message.isAllNotesOff())
        {
            // @XXX how to handle this ?
        }
        else if (message.isAllSoundOff())
        {
            // @XXX how to handle this ?
        }
        else if (message.isPitchWheel ())
        {
            ev.type = SND_SEQ_EVENT_PITCHBEND;
            ev.data.control.channel = message.getChannel ();
            ev.data.control.value = message.getPitchWheelValue ();

            printf("Sending pitchbend to port %d, ch=%d, value=%d\n",
                   outputPort, ev.data.control.channel, ev.data.control.value);
        }
        else if (message.isChannelPressure())
        {
            ev.type = SND_SEQ_EVENT_CHANPRESS;
            ev.data.control.channel = message.getChannel ();
            ev.data.control.param = 0;
            ev.data.control.value = message.getChannelPressureValue ();

            printf("Sending channel pressure to port %d, ch=%d, value=%d\n",
                   outputPort, ev.data.control.channel, ev.data.control.value);
        }
        else if (message.isAftertouch())
        {
            ev.type = SND_SEQ_EVENT_KEYPRESS
            ev.data.note.channel = message.getChannel ();
            ev.data.note.note = message.getNoteNumber ();
            ev.data.note.velocity = message.getAfterTouchValue ();
            ev.data.note.off_velocity = 0;
            ev.data.note.duration = 0;

            printf("Sending aftertouch to port %d, ch=%d, value=%d\n",
                   outputPort, ev.data.note.channel, ev.data.note.value);
        }
        else if (message.isController ())
        {
            ev.type = SND_SEQ_EVENT_CONTROLLER;
            ev.data.control.channel = message.getChannel ();
            ev.data.control.param = message.getControllerNumber ();
            ev.data.control.value = message.getControllerValue ();

            printf("Sending cc to port %d, ch=%d, cc=%d, value=%d\n",
                   outputPort, ev.data.control.channel, ev.data.control.param, ev.data.control.value);
        }
        else if (message.isProgramChange())
        {
            ev.type = SND_SEQ_EVENT_PGMCHANGE;
            ev.data.control.channel = message.getChannel ();
            ev.data.control.value = message.getProgramChangeNumber ();

            printf("Sending program change to port %d, ch=%d, value=%d\n",
                   outputPort, ev.data.note.channel, ev.data.note.value);
        }

        snd_seq_event_output_direct(seqHandle, &ev);

but still i can’t receive any midi message. what could i’ve mistaken ?


#4

whau ! i thought not but is working. looking with a midi monitor the correct messages are generated, even for sysex cc and aftertouch !
ok. now the juce application is shown in the jack audio midi connections (only for output port, the midi in not).
now the problem is another one: since i’m sending midi from juce demo, i’ve hacked the Synth to a hold a MidiOutput that sends midi messages on every audiocallback after the midibuffer get filled. but jucedemo is holding on the alsa device, so i can’t start jack daemon… aaargh. i need to finish JackAudioDevice as soon as we speak.

jules: will be possible to tweak the AudioDeviceManager (and its component selector) to let you select midi output also ?


#5

groovy stuff, I’ll take a proper look at all this asap, just need to finish a couple of other things first…

Not sure about your MidiDeviceManager idea, I think it’s probably more useful just to stick it all into the AudioDeviceManager. I’ll have a think about that.


#6