|
@@ -8,7 +8,7 @@
|
|
|
RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/
|
|
|
|
|
|
RtMidi: realtime MIDI i/o C++ classes
|
|
|
- Copyright (c) 2003-2004 Gary P. Scavone
|
|
|
+ Copyright (c) 2003-2005 Gary P. Scavone
|
|
|
|
|
|
Permission is hereby granted, free of charge, to any person
|
|
|
obtaining a copy of this software and associated documentation files
|
|
@@ -35,7 +35,7 @@
|
|
|
*/
|
|
|
/**********************************************************************/
|
|
|
|
|
|
-// RtMidi: Version 1.0.3, 22 November 2004
|
|
|
+// RtMidi: Version 1.0.4, 14 October 2005
|
|
|
|
|
|
#include "RtMidi.h"
|
|
|
#include <sstream>
|
|
@@ -270,6 +270,14 @@ void midiInputCallback( const MIDIPacketList *list, void *procRef, void *srcRef
|
|
|
else size = 3;
|
|
|
}
|
|
|
else if ( status == 0xF3 ) size = 2;
|
|
|
+ else if ( status == 0xF8 ) {
|
|
|
+ size = 1;
|
|
|
+ if ( data->ignoreFlags & 0x02 ) {
|
|
|
+ // A MIDI timing tick message and we're ignoring it.
|
|
|
+ size = 0;
|
|
|
+ iByte += 3;
|
|
|
+ }
|
|
|
+ }
|
|
|
else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
|
|
|
// A MIDI active sensing message and we're ignoring it.
|
|
|
size = 0;
|
|
@@ -620,8 +628,10 @@ void RtMidiOut :: sendMessage( std::vector<unsigned char> *message )
|
|
|
#if defined(__LINUX_ALSASEQ__)
|
|
|
|
|
|
// The ALSA Sequencer API is based on the use of a callback function for
|
|
|
-// MIDI input. We convert the system specific time stamps to delta
|
|
|
-// time values.
|
|
|
+// MIDI input.
|
|
|
+//
|
|
|
+// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
|
|
|
+// time stamps and other assorted fixes!!!
|
|
|
|
|
|
#include <pthread.h>
|
|
|
#include <sys/time.h>
|
|
@@ -640,6 +650,7 @@ struct AlsaMidiData {
|
|
|
unsigned char *buffer;
|
|
|
pthread_t thread;
|
|
|
unsigned long long lastTime;
|
|
|
+ int queue_id; // an input queue is needed to get timestamped events
|
|
|
};
|
|
|
|
|
|
#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
|
|
@@ -656,11 +667,10 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
|
|
|
long nBytes;
|
|
|
unsigned long long time, lastTime;
|
|
|
- unsigned char lastStatus = 0;
|
|
|
+ bool continueSysex = false;
|
|
|
RtMidiIn::MidiMessage message;
|
|
|
|
|
|
snd_seq_event_t *ev;
|
|
|
- struct timeval tv;
|
|
|
int result;
|
|
|
apiData->bufferSize = 32;
|
|
|
result = snd_midi_event_new( 0, &apiData->coder );
|
|
@@ -669,13 +679,14 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
std::cerr << "\nRtMidiIn::alsaMidiHandler: error initializing MIDI event parser!\n\n";
|
|
|
return 0;
|
|
|
}
|
|
|
- unsigned char *buffer = (unsigned char *) malloc(apiData->bufferSize);
|
|
|
+ unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
|
|
|
if ( buffer == NULL ) {
|
|
|
data->doInput = false;
|
|
|
std::cerr << "\nRtMidiIn::alsaMidiHandler: error initializing buffer memory!\n\n";
|
|
|
return 0;
|
|
|
}
|
|
|
snd_midi_event_init( apiData->coder );
|
|
|
+ snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages
|
|
|
|
|
|
while ( data->doInput ) {
|
|
|
|
|
@@ -699,7 +710,7 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
// This is a bit weird, but we now have to decode an ALSA MIDI
|
|
|
// event (back) into MIDI bytes. We'll ignore non-MIDI types.
|
|
|
message.bytes.clear();
|
|
|
- switch (ev->type) {
|
|
|
+ switch ( ev->type ) {
|
|
|
|
|
|
case SND_SEQ_EVENT_PORT_SUBSCRIBED:
|
|
|
#if defined(__RTMIDI_DEBUG__)
|
|
@@ -715,6 +726,9 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
case SND_SEQ_EVENT_QFRAME: // MIDI time code
|
|
|
if ( data->ignoreFlags & 0x02 ) break;
|
|
|
|
|
|
+ case SND_SEQ_EVENT_TICK: // MIDI timing tick
|
|
|
+ if ( data->ignoreFlags & 0x02 ) break;
|
|
|
+
|
|
|
case SND_SEQ_EVENT_SENSING: // Active sensing
|
|
|
if ( data->ignoreFlags & 0x04 ) break;
|
|
|
|
|
@@ -722,8 +736,8 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
if ( (data->ignoreFlags & 0x01) ) break;
|
|
|
if ( ev->data.ext.len > apiData->bufferSize ) {
|
|
|
apiData->bufferSize = ev->data.ext.len;
|
|
|
- free(buffer);
|
|
|
- buffer = (unsigned char *) malloc(apiData->bufferSize);
|
|
|
+ free( buffer );
|
|
|
+ buffer = (unsigned char *) malloc( apiData->bufferSize );
|
|
|
if ( buffer == NULL ) {
|
|
|
data->doInput = false;
|
|
|
std::cerr << "\nRtMidiIn::alsaMidiHandler: error resizing buffer memory!\n\n";
|
|
@@ -739,25 +753,41 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
#endif
|
|
|
break;
|
|
|
}
|
|
|
- message.bytes.assign( buffer, &buffer[nBytes] );
|
|
|
-
|
|
|
- // Save last status byte in case of running status.
|
|
|
- if ( message.bytes[0] & 0x80 ) lastStatus = message.bytes[0];
|
|
|
- else if ( lastStatus ) message.bytes.insert( message.bytes.begin(), lastStatus );
|
|
|
- // I found the ALSA sequencer documentation to be very inadequate,
|
|
|
- // especially regarding timestamps. So, I ignore the event
|
|
|
- // timestamp and use system time to determine ours.
|
|
|
+
|
|
|
+ // The ALSA sequencer has a maximum buffer size for MIDI sysex
|
|
|
+ // events of 256 bytes. If a device sends sysex messages larger
|
|
|
+ // than this, they are segmented into 256 byte chunks. So,
|
|
|
+ // we'll watch for this and concatenate sysex chunks into a
|
|
|
+ // single sysex message if necessary.
|
|
|
+ if ( !continueSysex )
|
|
|
+ message.bytes.assign( buffer, &buffer[nBytes] );
|
|
|
+ else
|
|
|
+ message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );
|
|
|
+
|
|
|
+ if ( ev->type == SND_SEQ_EVENT_SYSEX && message.bytes.back() == 0xF7 )
|
|
|
+ continueSysex = false;
|
|
|
+ else {
|
|
|
+ continueSysex = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate the time stamp:
|
|
|
message.timeStamp = 0.0;
|
|
|
- (void)gettimeofday(&tv, (struct timezone *)NULL);
|
|
|
- time = (tv.tv_sec * 1000000) + tv.tv_usec;
|
|
|
+
|
|
|
+ // Method 1: Use the system time.
|
|
|
+ //(void)gettimeofday(&tv, (struct timezone *)NULL);
|
|
|
+ //time = (tv.tv_sec * 1000000) + tv.tv_usec;
|
|
|
+
|
|
|
+ // Method 2: Use the ALSA sequencer event time data.
|
|
|
+ // (thanks to Pedro Lopez-Cabanillas!).
|
|
|
+ time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );
|
|
|
lastTime = time;
|
|
|
time -= apiData->lastTime;
|
|
|
apiData->lastTime = lastTime;
|
|
|
if ( data->firstMessage == true )
|
|
|
data->firstMessage = false;
|
|
|
- else {
|
|
|
+ else
|
|
|
message.timeStamp = time * 0.000001;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
snd_seq_free_event(ev);
|
|
@@ -776,6 +806,7 @@ extern "C" void *alsaMidiHandler( void *ptr )
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if ( buffer ) free( buffer );
|
|
|
snd_midi_event_free( apiData->coder );
|
|
|
apiData->coder = 0;
|
|
|
return 0;
|
|
@@ -785,7 +816,7 @@ void RtMidiIn :: initialize( void )
|
|
|
{
|
|
|
// Set up the ALSA sequencer client.
|
|
|
snd_seq_t *seq;
|
|
|
- int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_INPUT, 0);
|
|
|
+ int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
|
|
|
if ( result < 0 ) {
|
|
|
errorString_ = "RtMidiIn::initialize: error creating ALSA sequencer input client object.";
|
|
|
error( RtError::DRIVER_ERROR );
|
|
@@ -800,6 +831,16 @@ void RtMidiIn :: initialize( void )
|
|
|
data->vport = -1;
|
|
|
apiData_ = (void *) data;
|
|
|
inputData_.apiData = (void *) data;
|
|
|
+
|
|
|
+ // Create the input queue
|
|
|
+ data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");
|
|
|
+ // Set arbitrary tempo (mm=100) and resolution (240)
|
|
|
+ snd_seq_queue_tempo_t *qtempo;
|
|
|
+ snd_seq_queue_tempo_alloca(&qtempo);
|
|
|
+ snd_seq_queue_tempo_set_tempo(qtempo, 600000);
|
|
|
+ snd_seq_queue_tempo_set_ppq(qtempo, 240);
|
|
|
+ snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo);
|
|
|
+ snd_seq_drain_output(data->seq);
|
|
|
}
|
|
|
|
|
|
// This function is used to count or get the pinfo structure for a given port number.
|
|
@@ -859,9 +900,21 @@ void RtMidiIn :: openPort( unsigned int portNumber )
|
|
|
sender.port = snd_seq_port_info_get_port( pinfo );
|
|
|
receiver.client = snd_seq_client_id( data->seq );
|
|
|
if ( data->vport < 0 ) {
|
|
|
- data->vport = snd_seq_create_simple_port( data->seq, "RtMidi Input",
|
|
|
- SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
|
|
|
- SND_SEQ_PORT_TYPE_MIDI_GENERIC );
|
|
|
+ snd_seq_port_info_set_client( pinfo, 0 );
|
|
|
+ snd_seq_port_info_set_port( pinfo, 0 );
|
|
|
+ snd_seq_port_info_set_capability( pinfo,
|
|
|
+ SND_SEQ_PORT_CAP_WRITE |
|
|
|
+ SND_SEQ_PORT_CAP_SUBS_WRITE );
|
|
|
+ snd_seq_port_info_set_type( pinfo,
|
|
|
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
|
|
+ SND_SEQ_PORT_TYPE_APPLICATION );
|
|
|
+ snd_seq_port_info_set_midi_channels(pinfo, 16);
|
|
|
+ snd_seq_port_info_set_timestamping(pinfo, 1);
|
|
|
+ snd_seq_port_info_set_timestamp_real(pinfo, 1);
|
|
|
+ snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
|
|
|
+ snd_seq_port_info_set_name(pinfo, "RtMidi Input");
|
|
|
+ data->vport = snd_seq_create_port(data->seq, pinfo);
|
|
|
+
|
|
|
if ( data->vport < 0 ) {
|
|
|
errorString_ = "RtMidiIn::openPort: ALSA error creating input port.";
|
|
|
error( RtError::DRIVER_ERROR );
|
|
@@ -874,19 +927,20 @@ void RtMidiIn :: openPort( unsigned int portNumber )
|
|
|
snd_seq_port_subscribe_malloc( &data->subscription );
|
|
|
snd_seq_port_subscribe_set_sender(data->subscription, &sender);
|
|
|
snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
|
|
|
- snd_seq_port_subscribe_set_time_update(data->subscription, 1);
|
|
|
- snd_seq_port_subscribe_set_time_real(data->subscription, 1);
|
|
|
if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
|
|
|
errorString_ = "RtMidiIn::openPort: ALSA error making port connection.";
|
|
|
error( RtError::DRIVER_ERROR );
|
|
|
}
|
|
|
|
|
|
if ( inputData_.doInput == false ) {
|
|
|
+ // Start the input queue
|
|
|
+ snd_seq_start_queue( data->seq, data->queue_id, NULL );
|
|
|
+ snd_seq_drain_output( data->seq );
|
|
|
// Start our MIDI input thread.
|
|
|
pthread_attr_t attr;
|
|
|
pthread_attr_init(&attr);
|
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
|
|
- pthread_attr_setschedpolicy(&attr, SCHED_RR);
|
|
|
+ pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
|
|
|
|
|
|
inputData_.doInput = true;
|
|
|
int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
|
|
@@ -907,9 +961,20 @@ void RtMidiIn :: openVirtualPort()
|
|
|
{
|
|
|
AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
|
|
if ( data->vport < 0 ) {
|
|
|
- data->vport = snd_seq_create_simple_port( data->seq, "RtMidi Input",
|
|
|
- SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE,
|
|
|
- SND_SEQ_PORT_TYPE_MIDI_GENERIC );
|
|
|
+ snd_seq_port_info_t *pinfo;
|
|
|
+ snd_seq_port_info_alloca( &pinfo );
|
|
|
+ snd_seq_port_info_set_capability( pinfo,
|
|
|
+ SND_SEQ_PORT_CAP_WRITE |
|
|
|
+ SND_SEQ_PORT_CAP_SUBS_WRITE );
|
|
|
+ snd_seq_port_info_set_type( pinfo,
|
|
|
+ SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
|
|
+ SND_SEQ_PORT_TYPE_APPLICATION );
|
|
|
+ snd_seq_port_info_set_midi_channels(pinfo, 16);
|
|
|
+ snd_seq_port_info_set_timestamping(pinfo, 1);
|
|
|
+ snd_seq_port_info_set_timestamp_real(pinfo, 1);
|
|
|
+ snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);
|
|
|
+ snd_seq_port_info_set_name(pinfo, "RtMidi Input");
|
|
|
+ data->vport = snd_seq_create_port(data->seq, pinfo);
|
|
|
|
|
|
if ( data->vport < 0 ) {
|
|
|
errorString_ = "RtMidiIn::openVirtualPort: ALSA error creating virtual port.";
|
|
@@ -918,11 +983,14 @@ void RtMidiIn :: openVirtualPort()
|
|
|
}
|
|
|
|
|
|
if ( inputData_.doInput == false ) {
|
|
|
+ // Start the input queue
|
|
|
+ snd_seq_start_queue( data->seq, data->queue_id, NULL );
|
|
|
+ snd_seq_drain_output( data->seq );
|
|
|
// Start our MIDI input thread.
|
|
|
pthread_attr_t attr;
|
|
|
pthread_attr_init(&attr);
|
|
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
|
|
- pthread_attr_setschedpolicy(&attr, SCHED_RR);
|
|
|
+ pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
|
|
|
|
|
|
inputData_.doInput = true;
|
|
|
int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
|
|
@@ -943,6 +1011,9 @@ void RtMidiIn :: closePort( void )
|
|
|
AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
|
|
|
snd_seq_unsubscribe_port( data->seq, data->subscription );
|
|
|
snd_seq_port_subscribe_free( data->subscription );
|
|
|
+ // Stop the input queue
|
|
|
+ snd_seq_stop_queue( data->seq, data->queue_id, NULL );
|
|
|
+ snd_seq_drain_output( data->seq );
|
|
|
connected_ = false;
|
|
|
}
|
|
|
}
|
|
@@ -961,6 +1032,7 @@ RtMidiIn :: ~RtMidiIn()
|
|
|
|
|
|
// Cleanup.
|
|
|
if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
|
|
|
+ snd_seq_free_queue( data->seq, data->queue_id );
|
|
|
snd_seq_close( data->seq );
|
|
|
delete data;
|
|
|
}
|
|
@@ -1316,6 +1388,12 @@ extern "C" void *irixMidiHandler( void *ptr )
|
|
|
}
|
|
|
}
|
|
|
else if ( status == 0xF3 ) size = 2;
|
|
|
+ else if ( status == 0xF8 ) {
|
|
|
+ if ( !(data->ignoreFlags & 0x02) ) {
|
|
|
+ // A MIDI timing tick message and we're not ignoring it.
|
|
|
+ size = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
else if ( status == 0xFE ) { // MIDI active sensing
|
|
|
if ( !(data->ignoreFlags & 0x04) )
|
|
|
size = 1;
|
|
@@ -1657,6 +1735,10 @@ static void CALLBACK midiInputCallback( HMIDIOUT hmin,
|
|
|
nBytes = 3;
|
|
|
}
|
|
|
else if ( status == 0xF3 ) nBytes = 2;
|
|
|
+ else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) {
|
|
|
+ // A MIDI timing tick message and we're ignoring it.
|
|
|
+ return;
|
|
|
+ }
|
|
|
else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {
|
|
|
// A MIDI active sensing message and we're ignoring it.
|
|
|
return;
|