Skip to content
Snippets Groups Projects
RtMidi.cpp 91.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • SeeLook's avatar
    SeeLook committed
      }
    
      //  unsigned int packetBytes, bytesLeft = nBytes;
      //  unsigned int messageIndex = 0;
      MIDITimeStamp timeStamp = AudioGetCurrentHostTime();
      CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);
      OSStatus result;
    
    
      /*
        // I don't think this code is necessary.  We can send sysex
        // messages through the normal mechanism.  In addition, this avoids
        // the problem of virtual ports not receiving sysex messages.
    
    SeeLook's avatar
    SeeLook committed
    
    
    SeeLook's avatar
    SeeLook committed
    
    
        // Apple's fantastic API requires us to free the allocated data in
        // the completion callback but trashes the pointer and size before
        // we get a chance to free it!!  This is a somewhat ugly hack
        // submitted by ptarabbia that puts the sysex buffer data right at
        // the end of the MIDISysexSendRequest structure.  This solution
        // does not require that we wait for a previous sysex buffer to be
        // sent before sending a new one, which was the old way we did it.
        MIDISysexSendRequest *newRequest = (MIDISysexSendRequest *) malloc(sizeof(struct MIDISysexSendRequest) + nBytes);
        char * sysexBuffer = ((char *) newRequest) + sizeof(struct MIDISysexSendRequest);
    
    SeeLook's avatar
    SeeLook committed
    
    
        // Copy data to buffer.
        for ( unsigned int i=0; i<nBytes; ++i ) sysexBuffer[i] = message->at(i);
    
    SeeLook's avatar
    SeeLook committed
    
    
        newRequest->destination = data->destinationId;
        newRequest->data = (Byte *)sysexBuffer;
        newRequest->bytesToSend = nBytes;
        newRequest->complete = 0;
        newRequest->completionProc = sysexCompletionProc;
        newRequest->completionRefCon = newRequest;
    
    SeeLook's avatar
    SeeLook committed
    
    
        result = MIDISendSysex(newRequest);
        if ( result != noErr ) {
          free( newRequest );
          errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";
          error( RtMidiError::WARNING, errorString_ );
          return;
        }
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
      else if ( nBytes > 3 ) {
    
        errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";
        error( RtMidiError::WARNING, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
    SeeLook's avatar
    SeeLook committed
    
      MIDIPacketList packetList;
      MIDIPacket *packet = MIDIPacketListInit( &packetList );
      packet = MIDIPacketListAdd( &packetList, sizeof(packetList), packet, timeStamp, nBytes, (const Byte *) &message->at( 0 ) );
      if ( !packet ) {
        errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";      
    
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
      // Send to any destinations that may have connected to us.
      if ( data->endpoint ) {
        result = MIDIReceived( data->endpoint, &packetList );
        if ( result != noErr ) {
          errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";
    
          error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    
      // And send to an explicit destination port if we're connected.
      if ( connected_ ) {
        result = MIDISend( data->port, data->destinationId, &packetList );
        if ( result != noErr ) {
          errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";
    
          error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    }
    
    #endif  // __MACOSX_CORE__
    
    
    //*********************************************************************//
    //  API: LINUX ALSA SEQUENCER
    //*********************************************************************//
    
    // API information found at:
    //   - http://www.alsa-project.org/documentation.php#Library
    
    #if defined(__LINUX_ALSA__)
    
    // The ALSA Sequencer API is based on the use of a callback function for
    // MIDI input.
    //
    // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer
    // time stamps and other assorted fixes!!!
    
    // If you don't need timestamping for incoming MIDI events, define the
    // preprocessor definition AVOID_TIMESTAMPING to save resources
    // associated with the ALSA sequencer queues.
    
    #include <pthread.h>
    #include <sys/time.h>
    
    // ALSA header file.
    #include <alsa/asoundlib.h>
    
    // A structure to hold variables related to the ALSA API
    // implementation.
    struct AlsaMidiData {
      snd_seq_t *seq;
      unsigned int portNum;
      int vport;
      snd_seq_port_subscribe_t *subscription;
      snd_midi_event_t *coder;
      unsigned int bufferSize;
      unsigned char *buffer;
      pthread_t thread;
      pthread_t dummy_thread_id;
      unsigned long long lastTime;
      int queue_id; // an input queue is needed to get timestamped events
      int trigger_fds[2];
    };
    
    #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))
    
    //*********************************************************************//
    //  API: LINUX ALSA
    //  Class Definitions: MidiInAlsa
    //*********************************************************************//
    
    
    static void *alsaMidiHandler( void *ptr )
    
    SeeLook's avatar
    SeeLook committed
    {
      MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr);
      AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);
    
      long nBytes;
      unsigned long long time, lastTime;
      bool continueSysex = false;
      bool doDecode = false;
      MidiInApi::MidiMessage message;
      int poll_fd_count;
      struct pollfd *poll_fds;
    
      snd_seq_event_t *ev;
      int result;
      apiData->bufferSize = 32;
      result = snd_midi_event_new( 0, &apiData->coder );
      if ( result < 0 ) {
        data->doInput = false;
        std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";
        return 0;
      }
      unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );
      if ( buffer == NULL ) {
        data->doInput = false;
        snd_midi_event_free( apiData->coder );
        apiData->coder = 0;
        std::cerr << "\nMidiInAlsa::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
    
      poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;
      poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));
      snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );
      poll_fds[0].fd = apiData->trigger_fds[0];
      poll_fds[0].events = POLLIN;
    
      while ( data->doInput ) {
    
        if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {
          // No data pending
          if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {
            if ( poll_fds[0].revents & POLLIN ) {
              bool dummy;
              int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );
              (void) res;
            }
          }
          continue;
        }
    
        // If here, there should be data.
        result = snd_seq_event_input( apiData->seq, &ev );
        if ( result == -ENOSPC ) {
          std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";
          continue;
        }
        else if ( result <= 0 ) {
    
          std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";
          perror("System reports");
    
    SeeLook's avatar
    SeeLook committed
          continue;
        }
    
        // 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.
        if ( !continueSysex ) message.bytes.clear();
    
        doDecode = false;
        switch ( ev->type ) {
    
    
        case SND_SEQ_EVENT_PORT_SUBSCRIBED:
    
    SeeLook's avatar
    SeeLook committed
    #if defined(__RTMIDI_DEBUG__)
          std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";
    #endif
          break;
    
    
        case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
    
    SeeLook's avatar
    SeeLook committed
    #if defined(__RTMIDI_DEBUG__)
          std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";
          std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"
                    << (int) ev->data.connect.sender.port
                    << ", dest = " << (int) ev->data.connect.dest.client << ":"
                    << (int) ev->data.connect.dest.port
                    << std::endl;
    #endif
          break;
    
        case SND_SEQ_EVENT_QFRAME: // MIDI time code
          if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
          break;
    
    
        case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick
          if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
          break;
    
        case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick
    
    SeeLook's avatar
    SeeLook committed
          if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;
          break;
    
        case SND_SEQ_EVENT_SENSING: // Active sensing
          if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;
          break;
    
    		case SND_SEQ_EVENT_SYSEX:
          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 );
            if ( buffer == NULL ) {
              data->doInput = false;
              std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";
              break;
            }
          }
    
        default:
          doDecode = true;
        }
    
        if ( doDecode ) {
    
          nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );
          if ( nBytes > 0 ) {
            // 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] );
    
            continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );
            if ( !continueSysex ) {
    
              // Calculate the time stamp:
              message.timeStamp = 0.0;
    
              // 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
                message.timeStamp = time * 0.000001;
            }
            else {
    #if defined(__RTMIDI_DEBUG__)
              std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";
    #endif
            }
          }
        }
    
        snd_seq_free_event( ev );
        if ( message.bytes.size() == 0 || continueSysex ) continue;
    
        if ( data->usingCallback ) {
          RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;
          callback( message.timeStamp, &message.bytes, data->userData );
        }
        else {
          // As long as we haven't reached our queue size limit, push the message.
          if ( data->queue.size < data->queue.ringSize ) {
            data->queue.ring[data->queue.back++] = message;
            if ( data->queue.back == data->queue.ringSize )
              data->queue.back = 0;
            data->queue.size++;
          }
          else
            std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";
        }
      }
    
      if ( buffer ) free( buffer );
      snd_midi_event_free( apiData->coder );
      apiData->coder = 0;
      apiData->thread = apiData->dummy_thread_id;
      return 0;
    }
    
    MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )
    {
      initialize( clientName );
    }
    
    MidiInAlsa :: ~MidiInAlsa()
    {
      // Close a connection if it exists.
      closePort();
    
      // Shutdown the input thread.
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( inputData_.doInput ) {
        inputData_.doInput = false;
        int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
        (void) res;
        if ( !pthread_equal(data->thread, data->dummy_thread_id) )
          pthread_join( data->thread, NULL );
      }
    
      // Cleanup.
      close ( data->trigger_fds[0] );
      close ( data->trigger_fds[1] );
      if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
    #ifndef AVOID_TIMESTAMPING
      snd_seq_free_queue( data->seq, data->queue_id );
    #endif
    
    SeeLook's avatar
    SeeLook committed
      delete data;
    }
    
    void MidiInAlsa :: initialize( const std::string& clientName )
    {
    
      // Set up the ALSA sequencer client.
      snd_seq_t *seq;
      int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
      if ( result < 0 ) {
    
    SeeLook's avatar
    SeeLook committed
        errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";
    
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
    
      // Set client name.
      snd_seq_set_client_name( seq, clientName.c_str() );
    
    
    SeeLook's avatar
    SeeLook committed
      // Save our api-specific connection information.
      AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
      data->seq = seq;
      data->portNum = -1;
      data->vport = -1;
      data->subscription = 0;
      data->dummy_thread_id = pthread_self();
      data->thread = data->dummy_thread_id;
      data->trigger_fds[0] = -1;
      data->trigger_fds[1] = -1;
      apiData_ = (void *) data;
      inputData_.apiData = (void *) data;
    
       if ( pipe(data->trigger_fds) == -1 ) {
        errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";
    
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
      // Create the input queue
    #ifndef AVOID_TIMESTAMPING
    
      data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");
    
    SeeLook's avatar
    SeeLook committed
      // 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);
    #endif
    }
    
    // This function is used to count or get the pinfo structure for a given port number.
    unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )
    {
      snd_seq_client_info_t *cinfo;
      int client;
      int count = 0;
      snd_seq_client_info_alloca( &cinfo );
    
      snd_seq_client_info_set_client( cinfo, -1 );
      while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {
        client = snd_seq_client_info_get_client( cinfo );
        if ( client == 0 ) continue;
        // Reset query info
        snd_seq_port_info_set_client( pinfo, client );
        snd_seq_port_info_set_port( pinfo, -1 );
        while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {
          unsigned int atyp = snd_seq_port_info_get_type( pinfo );
          if ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) continue;
          unsigned int caps = snd_seq_port_info_get_capability( pinfo );
          if ( ( caps & type ) != type ) continue;
          if ( count == portNumber ) return 1;
          ++count;
        }
      }
    
      // If a negative portNumber was used, return the port count.
      if ( portNumber < 0 ) return count;
      return 0;
    }
    
    unsigned int MidiInAlsa :: getPortCount()
    {
      snd_seq_port_info_t *pinfo;
      snd_seq_port_info_alloca( &pinfo );
    
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );
    }
    
    std::string MidiInAlsa :: getPortName( unsigned int portNumber )
    {
      snd_seq_client_info_t *cinfo;
      snd_seq_port_info_t *pinfo;
      snd_seq_client_info_alloca( &cinfo );
      snd_seq_port_info_alloca( &pinfo );
    
      std::string stringName;
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {
        int cnum = snd_seq_port_info_get_client( pinfo );
        snd_seq_get_any_client_info( data->seq, cnum, cinfo );
        std::ostringstream os;
        os << snd_seq_client_info_get_name( cinfo );
    
        os << " ";                                    // These lines added to make sure devices are listed
        os << snd_seq_port_info_get_client( pinfo );  // with full portnames added to ensure individual device names
    
    SeeLook's avatar
    SeeLook committed
        os << ":";
        os << snd_seq_port_info_get_port( pinfo );
        stringName = os.str();
        return stringName;
      }
    
      // If we get here, we didn't find a match.
      errorString_ = "MidiInAlsa::getPortName: error looking for port name!";
    
      error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
      return stringName;
    }
    
    void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName )
    {
      if ( connected_ ) {
        errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";
    
        error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
        return;
      }
    
      unsigned int nSrc = this->getPortCount();
    
    SeeLook's avatar
    SeeLook committed
        errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";
    
        error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
    
      snd_seq_port_info_t *src_pinfo;
      snd_seq_port_info_alloca( &src_pinfo );
    
    SeeLook's avatar
    SeeLook committed
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
    
      if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {
        std::ostringstream ost;
    
    SeeLook's avatar
    SeeLook committed
        ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
        errorString_ = ost.str();
    
        error( RtMidiError::INVALID_PARAMETER, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
      snd_seq_addr_t sender, receiver;
    
      sender.client = snd_seq_port_info_get_client( src_pinfo );
      sender.port = snd_seq_port_info_get_port( src_pinfo );
    
      snd_seq_port_info_t *pinfo;
      snd_seq_port_info_alloca( &pinfo );
    
    SeeLook's avatar
    SeeLook committed
      if ( data->vport < 0 ) {
        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);
    #ifndef AVOID_TIMESTAMPING
        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);
    #endif
        snd_seq_port_info_set_name(pinfo,  portName.c_str() );
        data->vport = snd_seq_create_port(data->seq, pinfo);
      
        if ( data->vport < 0 ) {
          errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
    
        data->vport = snd_seq_port_info_get_port(pinfo);
    
    SeeLook's avatar
    SeeLook committed
      }
    
    
      receiver.client = snd_seq_port_info_get_client( pinfo );
    
    SeeLook's avatar
    SeeLook committed
      receiver.port = data->vport;
    
      if ( !data->subscription ) {
        // Make subscription
        if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
          errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
        snd_seq_port_subscribe_set_sender(data->subscription, &sender);
        snd_seq_port_subscribe_set_dest(data->subscription, &receiver);
        if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {
          snd_seq_port_subscribe_free( data->subscription );
          data->subscription = 0;
          errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    
      if ( inputData_.doInput == false ) {
        // Start the input queue
    #ifndef AVOID_TIMESTAMPING
        snd_seq_start_queue( data->seq, data->queue_id, NULL );
        snd_seq_drain_output( data->seq );
    #endif
        // 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_OTHER);
    
        inputData_.doInput = true;
        int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
        pthread_attr_destroy(&attr);
        if ( err ) {
          snd_seq_unsubscribe_port( data->seq, data->subscription );
          snd_seq_port_subscribe_free( data->subscription );
          data->subscription = 0;
          inputData_.doInput = false;
          errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
    
          error( RtMidiError::THREAD_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    
      connected_ = true;
    }
    
    void MidiInAlsa :: openVirtualPort( std::string portName )
    {
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( data->vport < 0 ) {
        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);
    #ifndef AVOID_TIMESTAMPING
        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);
    #endif
        snd_seq_port_info_set_name(pinfo, portName.c_str());
        data->vport = snd_seq_create_port(data->seq, pinfo);
    
        if ( data->vport < 0 ) {
          errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
    
        data->vport = snd_seq_port_info_get_port(pinfo);
    
    SeeLook's avatar
    SeeLook committed
      }
    
      if ( inputData_.doInput == false ) {
        // Wait for old thread to stop, if still running
        if ( !pthread_equal(data->thread, data->dummy_thread_id) )
          pthread_join( data->thread, NULL );
    
        // Start the input queue
    #ifndef AVOID_TIMESTAMPING
        snd_seq_start_queue( data->seq, data->queue_id, NULL );
        snd_seq_drain_output( data->seq );
    #endif
        // 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_OTHER);
    
        inputData_.doInput = true;
        int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);
        pthread_attr_destroy(&attr);
        if ( err ) {
          if ( data->subscription ) {
            snd_seq_unsubscribe_port( data->seq, data->subscription );
            snd_seq_port_subscribe_free( data->subscription );
            data->subscription = 0;
          }
          inputData_.doInput = false;
          errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";
    
          error( RtMidiError::THREAD_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    }
    
    void MidiInAlsa :: closePort( void )
    {
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
    
      if ( connected_ ) {
        if ( data->subscription ) {
          snd_seq_unsubscribe_port( data->seq, data->subscription );
          snd_seq_port_subscribe_free( data->subscription );
          data->subscription = 0;
        }
        // Stop the input queue
    #ifndef AVOID_TIMESTAMPING
        snd_seq_stop_queue( data->seq, data->queue_id, NULL );
        snd_seq_drain_output( data->seq );
    #endif
        connected_ = false;
      }
    
      // Stop thread to avoid triggering the callback, while the port is intended to be closed
      if ( inputData_.doInput ) {
        inputData_.doInput = false;
        int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );
        (void) res;
        if ( !pthread_equal(data->thread, data->dummy_thread_id) )
          pthread_join( data->thread, NULL );
      }
    }
    
    //*********************************************************************//
    //  API: LINUX ALSA
    //  Class Definitions: MidiOutAlsa
    //*********************************************************************//
    
    MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi()
    {
      initialize( clientName );
    }
    
    MidiOutAlsa :: ~MidiOutAlsa()
    {
      // Close a connection if it exists.
      closePort();
    
      // Cleanup.
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );
      if ( data->coder ) snd_midi_event_free( data->coder );
      if ( data->buffer ) free( data->buffer );
    
    SeeLook's avatar
    SeeLook committed
      delete data;
    }
    
    void MidiOutAlsa :: initialize( const std::string& clientName )
    {
    
      // Set up the ALSA sequencer client.
      snd_seq_t *seq;
      int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );
      if ( result1 < 0 ) {
    
    SeeLook's avatar
    SeeLook committed
        errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";
    
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
    	}
    
    
      // Set client name.
      snd_seq_set_client_name( seq, clientName.c_str() );
    
    
    SeeLook's avatar
    SeeLook committed
      // Save our api-specific connection information.
      AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;
      data->seq = seq;
      data->portNum = -1;
      data->vport = -1;
      data->bufferSize = 32;
      data->coder = 0;
      data->buffer = 0;
      int result = snd_midi_event_new( data->bufferSize, &data->coder );
      if ( result < 0 ) {
        delete data;
        errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";
    
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
      data->buffer = (unsigned char *) malloc( data->bufferSize );
      if ( data->buffer == NULL ) {
        delete data;
        errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
    
        error( RtMidiError::MEMORY_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
      snd_midi_event_init( data->coder );
      apiData_ = (void *) data;
    }
    
    unsigned int MidiOutAlsa :: getPortCount()
    {
    	snd_seq_port_info_t *pinfo;
    	snd_seq_port_info_alloca( &pinfo );
    
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );
    }
    
    std::string MidiOutAlsa :: getPortName( unsigned int portNumber )
    {
      snd_seq_client_info_t *cinfo;
      snd_seq_port_info_t *pinfo;
      snd_seq_client_info_alloca( &cinfo );
      snd_seq_port_info_alloca( &pinfo );
    
      std::string stringName;
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {
        int cnum = snd_seq_port_info_get_client(pinfo);
        snd_seq_get_any_client_info( data->seq, cnum, cinfo );
        std::ostringstream os;
        os << snd_seq_client_info_get_name(cinfo);
    
        os << " ";                                    // These lines added to make sure devices are listed
        os << snd_seq_port_info_get_client( pinfo );  // with full portnames added to ensure individual device names
    
    SeeLook's avatar
    SeeLook committed
        os << ":";
        os << snd_seq_port_info_get_port(pinfo);
        stringName = os.str();
        return stringName;
      }
    
      // If we get here, we didn't find a match.
      errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";
    
      error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
      return stringName;
    }
    
    void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName )
    {
      if ( connected_ ) {
        errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";
    
        error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
        return;
      }
    
      unsigned int nSrc = this->getPortCount();
      if (nSrc < 1) {
        errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";
    
        error( RtMidiError::NO_DEVICES_FOUND, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
    	snd_seq_port_info_t *pinfo;
    	snd_seq_port_info_alloca( &pinfo );
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {
    
    SeeLook's avatar
    SeeLook committed
        ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";
        errorString_ = ost.str();
    
        error( RtMidiError::INVALID_PARAMETER, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
      snd_seq_addr_t sender, receiver;
      receiver.client = snd_seq_port_info_get_client( pinfo );
      receiver.port = snd_seq_port_info_get_port( pinfo );
      sender.client = snd_seq_client_id( data->seq );
    
      if ( data->vport < 0 ) {
        data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
                                                  SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
                                                  SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
        if ( data->vport < 0 ) {
          errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    
      sender.port = data->vport;
    
      // Make subscription
      if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {
        snd_seq_port_subscribe_free( data->subscription );
    
        errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
      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) ) {
        snd_seq_port_subscribe_free( data->subscription );
        errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";
    
        error( RtMidiError::DRIVER_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
    
      connected_ = true;
    }
    
    void MidiOutAlsa :: closePort( void )
    {
      if ( connected_ ) {
        AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
        snd_seq_unsubscribe_port( data->seq, data->subscription );
        snd_seq_port_subscribe_free( data->subscription );
        connected_ = false;
      }
    }
    
    void MidiOutAlsa :: openVirtualPort( std::string portName )
    {
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      if ( data->vport < 0 ) {
        data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),
                                                  SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
                                                  SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );
    
        if ( data->vport < 0 ) {
          errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    }
    
    void MidiOutAlsa :: sendMessage( std::vector<unsigned char> *message )
    {
      int result;
      AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);
      unsigned int nBytes = message->size();
      if ( nBytes > data->bufferSize ) {
        data->bufferSize = nBytes;
        result = snd_midi_event_resize_buffer ( data->coder, nBytes);
        if ( result != 0 ) {
          errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";
    
          error( RtMidiError::DRIVER_ERROR, errorString_ );
          return;
    
    SeeLook's avatar
    SeeLook committed
        }
        free (data->buffer);
        data->buffer = (unsigned char *) malloc( data->bufferSize );
        if ( data->buffer == NULL ) {
        errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";
    
        error( RtMidiError::MEMORY_ERROR, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
        }
      }
    
      snd_seq_event_t ev;
      snd_seq_ev_clear(&ev);
      snd_seq_ev_set_source(&ev, data->vport);
      snd_seq_ev_set_subs(&ev);
      snd_seq_ev_set_direct(&ev);
      for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message->at(i);
      result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );
      if ( result < (int)nBytes ) {
        errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";
    
        error( RtMidiError::WARNING, errorString_ );
    
    SeeLook's avatar
    SeeLook committed
        return;
      }
    
      // Send the event.
      result = snd_seq_event_output(data->seq, &ev);
      if ( result < 0 ) {
        errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";
    
        error( RtMidiError::WARNING, errorString_ );
        return;
    
    SeeLook's avatar
    SeeLook committed
      }
      snd_seq_drain_output(data->seq);
    }
    
    #endif // __LINUX_ALSA__
    
    
    //*********************************************************************//
    //  API: Windows Multimedia Library (MM)
    //*********************************************************************//
    
    // API information deciphered from:
    //  - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp
    
    // Thanks to Jean-Baptiste Berruchon for the sysex code.
    
    #if defined(__WINDOWS_MM__)
    
    // The Windows MM API is based on the use of a callback function for
    // MIDI input.  We convert the system specific time stamps to delta
    // time values.
    
    // Windows MM MIDI header files.
    #include <windows.h>
    #include <mmsystem.h>
    
    #define  RT_SYSEX_BUFFER_SIZE 1024
    #define  RT_SYSEX_BUFFER_COUNT 4
    
    // A structure to hold variables related to the CoreMIDI API
    // implementation.
    struct WinMidiData {
      HMIDIIN inHandle;    // Handle to Midi Input Device
      HMIDIOUT outHandle;  // Handle to Midi Output Device
      DWORD lastTime;
      MidiInApi::MidiMessage message;
      LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];
    
      CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo
    
    SeeLook's avatar
    SeeLook committed
    };
    
    //*********************************************************************//
    //  API: Windows MM
    //  Class Definitions: MidiInWinMM
    //*********************************************************************//
    
    
    static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,
    
    SeeLook's avatar
    SeeLook committed
                                            UINT inputStatus, 
                                            DWORD_PTR instancePtr,
                                            DWORD_PTR midiMessage,
                                            DWORD timestamp )
    {
      if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;
    
      //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr);
      MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;
      WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);
    
      // Calculate time stamp.
      if ( data->firstMessage == true ) {
        apiData->message.timeStamp = 0.0;
        data->firstMessage = false;
      }
      else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;
      apiData->lastTime = timestamp;
    
      if ( inputStatus == MIM_DATA ) { // Channel or system message
    
        // Make sure the first byte is a status byte.
        unsigned char status = (unsigned char) (midiMessage & 0x000000FF);
        if ( !(status & 0x80) ) return;
    
        // Determine the number of bytes in the MIDI message.
        unsigned short nBytes = 1;
        if ( status < 0xC0 ) nBytes = 3;
        else if ( status < 0xE0 ) nBytes = 2;
        else if ( status < 0xF0 ) nBytes = 3;
        else if ( status == 0xF1 ) {
          if ( data->ignoreFlags & 0x02 ) return;
          else nBytes = 2;
        }
        else if ( status == 0xF2 ) 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;
        }
    
        // Copy bytes to our MIDI message.
        unsigned char *ptr = (unsigned char *) &midiMessage;
        for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ );
      }
      else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )
        MIDIHDR *sysex = ( MIDIHDR *) midiMessage; 
        if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) {  
          // Sysex message and we're not ignoring it
          for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )
            apiData->message.bytes.push_back( sysex->lpData[i] );
        }