ALSA Sequencer Interface for Simple Sysexxer

Simple Sysexxer Icon

Simple Sysexxer Icon

The last release of Simple Sysexxer used RtMidi for MIDI input. However, some weird things happend when I tried to receive data from my beloved Korg Z1 synth. Today I have rewritten the MIDI input thread using the ALSA sequencer API.

I hear complaints about the sparse ALSA documentation every now and then. Most people use Matthias Nagorni’s commented example code. I read the code of other ALSA sequencer clients during the last couple of days, and I got the impression that many coders took advantage of the open source principle. Or the other way around: copy and paste is great ;-) . Anyway, I learned some things about C hacking while writing my own code, and I’m getting better reading other people’s code day by day.

Here are some snippets of the code which “works for me”. I inherit QThread for the MIDI in class. From it’s constructor, I invoke the init method to create the client for the ALSA sequencer (sorry for the formatting, WordPress doesn’t do better):



void MidiIn::init()
{
sequencerHandle = 0L;
sequencerEvent = 0L;
if ( snd_seq_open( &sequencerHandle, "default", SND_SEQ_OPEN_INPUT, 0 ) < 0 )
{
emit errorMessage ( tr( "Cannot initialize the MIDI subsystem."), tr( "Please ensure ALSA is accessible." ) );
}

snd_seq_set_client_name(sequencerHandle, APPNAME );

if ( snd_seq_create_simple_port( sequencerHandle, APPNAME, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_APPLICATION) < 0 )
{
emit errorMessage ( tr( "Cannot create MIDI port."), tr( "Please ensure ALSA is set up properly." ) );
}
}

The run method gets invoked as soon as the thread gets started. The polling code has been stolen from Matthias' examples. Thanks a bunch :) :



void MidiIn::run()
{
recordMidi = false;
int npfd = snd_seq_poll_descriptors_count(sequencerHandle, POLLIN);
struct pollfd* pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
snd_seq_poll_descriptors(sequencerHandle, pfd, npfd, POLLIN);
while ( true )
{
// Avoid too much CPU load. According to the MIDI baud rate, 4 bytes need at least 1024 microseconds to pass the chord.
usleep( 512 );
if ( poll( pfd, npfd, 100000) > 0 && recordMidi == true )
{
processInput();
}
}
}

Here's the code that does "the actual work". The ALSA sequencer gets passed the client's handle and sequencerEvent, which is a pointer of type snd_seq_event_t. ALSA will set this pointer to the memory address where it buffers the current event. As I'm only interested in SysEx data, I leave the body of the loop in any other case. If it is SysEx data, I copy the actual data over to a QByteArray.

ALSA splits (variable length) SysEx data into chunks of 256 bytes. That's why I check whether the data starts with an 0xF0 and ends with an 0xF7. If so, the package is complete and gets send to the main thread via a signal. Otherwise the loop continues collecting data, informing the main (GUI) thread that a chunk of data arrived. This is needed to inform the user that data still is coming in:



void MidiIn::processInput()
{
do
{
snd_seq_event_input( sequencerHandle, &sequencerEvent );
if ( sequencerEvent->type != SND_SEQ_EVENT_SYSEX )
{
snd_seq_free_event( sequencerEvent );
continue;
}
// Stolen from aseqmm/alsaevent.cpp by Pedro Lopez-Cabanillas
tempBuffer.append( QByteArray( (char *) sequencerEvent->data.ext.ptr, sequencerEvent->data.ext.len ) );

// ALSA splits sysex messages into chunks, currently of 256 max bytes in size
// Therefore the data needs collection before sending it to the master thread
if ( tempBuffer.startsWith( 0xF0 ) && tempBuffer.endsWith( 0xF7 ) )
{
emit eventArrived( tempBuffer );
tempBuffer.clear();
}
else
{
emit chunkArrived();
}
snd_seq_free_event( sequencerEvent );
}
while ( snd_seq_event_input_pending( sequencerHandle, 0 ) > 0 );
}

There are a lot of things that can be improved in this code. Patches are always welcome :) . OTOH it will hopefully give other hackers a good starting point. The code also can be found in SVN. See the files src/MidiIn.h and src/MidiIn.cpp.