//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: alsamidi.cpp,v 1.8.2.7 2009/11/19 04:20:33 terminator356 Exp $
//  (C) Copyright 2000-2001 Werner Schweer (ws@seh.de)
//  (C) Copyright 2011 Tim E. Real (terminator356 on sourceforge)
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; version 2 of
//  the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//
//=========================================================

#include <stdio.h>

#include "alsamidi.h"
#include "globals.h"
#include "midi.h"
//#include "mididev.h"
#include "../midiport.h"
#include "../midiseq.h"
#include "../midictrl.h"
#include "../audio.h"
//#include "mpevent.h"
//#include "sync.h"
#include "utils.h"
#include "audiodev.h"
#include "xml.h"
#include "part.h"
#include "gconfig.h"
#include "track.h"

#include <QApplication>

// Enable debugging:
//#define ALSA_DEBUG 1  


namespace MusECore {

static int alsaSeqFdi = -1;
static int alsaSeqFdo = -1;

snd_seq_t* alsaSeq = 0;
static snd_seq_addr_t musePort;
static snd_seq_addr_t announce_adr;

//---------------------------------------------------------
//   MidiAlsaDevice
//---------------------------------------------------------

MidiAlsaDevice::MidiAlsaDevice(const snd_seq_addr_t& a, const QString& n)
   : MidiDevice(n)
      {
      adr = a;
      init();
      }

//---------------------------------------------------------
//   selectWfd
//---------------------------------------------------------

int MidiAlsaDevice::selectWfd()
      {
      return alsaSeqFdo;
      }

//---------------------------------------------------------
//   open
//---------------------------------------------------------

QString MidiAlsaDevice::open()
{
      _openFlags &= _rwFlags; // restrict to available bits

      snd_seq_port_info_t *pinfo;
      snd_seq_port_info_alloca(&pinfo);
      int rv = snd_seq_get_any_port_info(alsaSeq, adr.client, adr.port, pinfo);
      if(rv < 0)
      {  
        printf("MidiAlsaDevice::open Error getting port info: adr: %d:%d: %s\n", adr.client, adr.port, snd_strerror(rv));
        return QString(snd_strerror(rv));
      }
      
      snd_seq_port_subscribe_t* subs;
      // Allocated on stack, no need to call snd_seq_port_subscribe_free() later.
      snd_seq_port_subscribe_alloca(&subs);

      QString estr;
      int wer = 0;
      int rer = 0;

      int cap = snd_seq_port_info_get_capability(pinfo);

#ifdef ALSA_DEBUG
      printf("MidiAlsaDevice::open cap:%d\n", cap);  
#endif
      
      // subscribe for writing
      if (_openFlags & 1) 
      {
            if(cap & SND_SEQ_PORT_CAP_SUBS_WRITE)
            {  
              snd_seq_port_subscribe_set_sender(subs, &musePort);
              snd_seq_port_subscribe_set_dest(subs, &adr);
              // Not already subscribed (or error)? Then try subscribing.
              if(snd_seq_get_port_subscription(alsaSeq, subs) < 0)
              {
                //int error = snd_seq_subscribe_port(alsaSeq, subs);
                wer = snd_seq_subscribe_port(alsaSeq, subs);
                //if (error < 0)
                if(wer < 0)
                      //return QString("Play: ")+QString(snd_strerror(error));
                      estr += (QString("Play: ") + QString(snd_strerror(wer)) + QString(" "));
              }        
            }  
            if(!wer && (cap & SND_SEQ_PORT_CAP_WRITE))
              _writeEnable = true;      
      }

      // subscribe for reading
      if (_openFlags & 2) 
      {
            if(cap & SND_SEQ_PORT_CAP_SUBS_READ)
            {  
              snd_seq_port_subscribe_set_dest(subs, &musePort);
                    snd_seq_port_subscribe_set_sender(subs, &adr);
              // Not already subscribed (or error)? Then try subscribing.
              if(snd_seq_get_port_subscription(alsaSeq, subs) < 0)
              {
                //int error = snd_seq_subscribe_port(alsaSeq, subs);
                rer = snd_seq_subscribe_port(alsaSeq, subs);
                //if (error < 0)
                if(rer < 0)
                      //return QString("Rec: ") + QString(snd_strerror(error));
                      estr += (QString("Rec: ") + QString(snd_strerror(rer)));
              }
            }  
            if(!rer && (cap & SND_SEQ_PORT_CAP_READ))
              _readEnable = true;      
      }
      

      if(wer < 0 || rer < 0)
        return estr;
        
      return QString("OK");
}

//---------------------------------------------------------
//   close
//---------------------------------------------------------

void MidiAlsaDevice::close()
{
      snd_seq_port_info_t *pinfo;
      snd_seq_port_info_alloca(&pinfo);
      int rv = snd_seq_get_any_port_info(alsaSeq, adr.client, adr.port, pinfo);
      if(rv < 0)
      {  
        printf("MidiAlsaDevice::close Error getting port info: adr: %d:%d: %s\n", adr.client, adr.port, snd_strerror(rv));
        return;
      }
      
      snd_seq_port_subscribe_t* subs;
      // Allocated on stack, no need to call snd_seq_port_subscribe_free() later.
      snd_seq_port_subscribe_alloca(&subs);
      
      int wer = 0;
      int rer = 0;

      int cap = snd_seq_port_info_get_capability(pinfo);

#ifdef ALSA_DEBUG
      printf("MidiAlsaDevice::close cap:%d\n", cap);  
#endif

      // This function appears to be called only by MidiPort::setMidiDevice(), 
      //  which closes then opens the device.
      // Because the open flags are set BEFORE setMidiDevice() is called, we must ignore the flags.
      //
      // NOTE: Tested: The read unsubscribe works ok but not the write.
      //               As viewed in say, qjackctl, the connection is clearly lost, 
      //                but strangely the events are still accepted, ie, playback notes 
      //                are still heard etc. Tried an alsa midi device AND external fluidsynth inst.
      //
      //               Also, jack running and with jack midi disabled, we get messages like
      //                MidiAlsaDevice::0x84512c0 putEvent(): midi write error: No such device
      //                 dst 16:0
      //                only sometimes (not when playing notes), but with jack midi turned on, 
      //                we don't get the messages. With jack stopped we get the messages
      //                no matter if jack midi is turned on or not.   Tim.

      //if (_openFlags & 1) {
      //if (!(_openFlags & 1)) 
      {
            if(cap & SND_SEQ_PORT_CAP_SUBS_WRITE)
            {  
              snd_seq_port_subscribe_set_sender(subs, &musePort);
              snd_seq_port_subscribe_set_dest(subs, &adr);
              
              // Already subscribed? Then unsubscribe.
              if(!snd_seq_get_port_subscription(alsaSeq, subs))
              {
                wer = snd_seq_unsubscribe_port(alsaSeq, subs);
                //if(!wer)
                //  _writeEnable = false;      
                //else
                if(wer < 0)
                  printf("MidiAlsaDevice::close Error unsubscribing alsa midi port %d:%d for writing: %s\n", adr.client, adr.port, snd_strerror(wer));
              }   
              //else
                //_writeEnable = false;      
            }   
            _writeEnable = false;      
      }

      //if (_openFlags & 2) {
      //if (!(_openFlags & 2)) 
      {
            if(cap & SND_SEQ_PORT_CAP_SUBS_READ)
            {  
              snd_seq_port_subscribe_set_dest(subs, &musePort);
              snd_seq_port_subscribe_set_sender(subs, &adr);
              
              // Already subscribed? Then unsubscribe.
              if(!snd_seq_get_port_subscription(alsaSeq, subs))
              {
                rer = snd_seq_unsubscribe_port(alsaSeq, subs);
                //if(!rer)
                //  _readEnable = false;      
                //else  
                if(rer < 0)
                  printf("MidiAlsaDevice::close Error unsubscribing alsa midi port %d:%d for reading: %s\n", adr.client, adr.port, snd_strerror(rer));
              }  
              //else
              //  _readEnable = false;      
            }   
            _readEnable = false;      
      }
}

//---------------------------------------------------------
//   writeRouting
//---------------------------------------------------------

void MidiAlsaDevice::writeRouting(int level, Xml& xml) const
{
      // If this device is not actually in use by the song, do not write any routes.
      // This prevents bogus routes from being saved and propagated in the med file.  Tim.
      if(midiPort() == -1)
        return;
     
      QString s;
      for (ciRoute r = _outRoutes.begin(); r != _outRoutes.end(); ++r) 
      {
        if(!r->name().isEmpty())
        {
          s = "Route";
          if(r->channel != -1)
            s += QString(" channel=\"%1\"").arg(r->channel);
          xml.tag(level++, s.toLatin1().constData());
          xml.tag(level, "source devtype=\"%d\" name=\"%s\"/", MidiDevice::ALSA_MIDI, Xml::xmlString(name()).toLatin1().constData());
          s = "dest";
          if(r->type == Route::MIDI_DEVICE_ROUTE)
            s += QString(" devtype=\"%1\"").arg(r->device->deviceType());
          else
          if(r->type != Route::TRACK_ROUTE)
            s += QString(" type=\"%1\"").arg(r->type);
          s += QString(" name=\"%1\"/").arg(Xml::xmlString(r->name()));
          xml.tag(level, s.toLatin1().constData());
          
          xml.etag(level--, "Route");
        }
      }
}
    
//---------------------------------------------------------
//   putEvent
//---------------------------------------------------------

bool MidiAlsaDevice::putMidiEvent(const MidiPlayEvent& e)
      {
      if (MusEGlobal::midiOutputTrace) {
            printf("MidiOut: Alsa: <%s>: ", name().toLatin1().constData());
            e.dump();
            }
      int chn = e.channel();
      int a   = e.dataA();
      int b   = e.dataB();

      snd_seq_event_t event;
      memset(&event, 0, sizeof(event));
      event.queue   = SND_SEQ_QUEUE_DIRECT;
      event.source  = musePort;
      event.dest    = adr;

      switch(e.type()) {
            case ME_NOTEON:
                  snd_seq_ev_set_noteon(&event, chn, a, b);
                  break;
            case ME_NOTEOFF:
                  snd_seq_ev_set_noteoff(&event, chn, a, 0);
                  break;
            case ME_PROGRAM:
                  snd_seq_ev_set_pgmchange(&event, chn, a);
                  break;
            case ME_CONTROLLER:
                  {
                    if(a == CTRL_PROGRAM)
                    {
                      snd_seq_ev_set_pgmchange(&event, chn, b);
                      break;
                    }
                    else if(a == CTRL_PITCH)
                    {
                      snd_seq_ev_set_pitchbend(&event, chn, b);
                      break;
                    }
                    else if((a | 0xff) == CTRL_POLYAFTER)
                    {
                      snd_seq_ev_set_keypress(&event, chn, a & 0x7f, b & 0x7f);
                      break;
                    }
                    else if(a == CTRL_AFTERTOUCH)
                    {
                      snd_seq_ev_set_chanpress(&event, chn, b);
                      break;
                    }
                  }
                  
#if 1
                  snd_seq_ev_set_controller(&event, chn, a, b);
#else
                  {
                  int a   = e.dataA();
                  int b   = e.dataB();
                  int chn = e.channel();
                  if (a < CTRL_14_OFFSET) {          // 7 Bit Controller
                        snd_seq_ev_set_controller(&event, chn, a, b);
                        }
                  else if (a < CTRL_RPN_OFFSET) {     // 14 bit high resolution controller
                        int ctrlH = (a >> 8) & 0x7f;
                        int ctrlL = a & 0x7f;
                        a = (ctrlH << 7) + ctrlL;
                        snd_seq_ev_set_controller(&event, chn, a, b);
                        event.type = SND_SEQ_EVENT_CONTROL14;
                        }
                  else if (a < CTRL_NRPN_OFFSET) {     // RPN 7-Bit Controller
                        int ctrlH = (a >> 8) & 0x7f;
                        int ctrlL = a & 0x7f;
                        a = (ctrlH << 7) + ctrlL;
                        b <<= 7;
                        snd_seq_ev_set_controller(&event, chn, a, b);
                        event.type = SND_SEQ_EVENT_REGPARAM;
                        }
                  else if (a < CTRL_INTERNAL_OFFSET) {     // NRPN 7-Bit Controller
                        int ctrlH = (a >> 8) & 0x7f;
                        int ctrlL = a & 0x7f;
                        a = (ctrlH << 7) + ctrlL;
                        b <<= 7;
                        snd_seq_ev_set_controller(&event, chn, a, b);
                        event.type = SND_SEQ_EVENT_NONREGPARAM;
                        }
                  else if (a < CTRL_NRPN14_OFFSET) {     // RPN14 Controller
                        int ctrlH = (a >> 8) & 0x7f;
                        int ctrlL = a & 0x7f;
                        a = (ctrlH << 7) + ctrlL;
                        snd_seq_ev_set_controller(&event, chn, a, b);
                        event.type = SND_SEQ_EVENT_REGPARAM;
                        }
                  else if (a < CTRL_NONE_OFFSET) {     // NRPN14 Controller
                        int ctrlH = (a >> 8) & 0x7f;
                        int ctrlL = a & 0x7f;
                        a = (ctrlH << 7) + ctrlL;
                        snd_seq_ev_set_controller(&event, chn, a, b);
                        event.type = SND_SEQ_EVENT_NONREGPARAM;
                        }
                  else {
                        printf("putEvent: unknown controller type 0x%x\n", a);
                        }
                  }
#endif
                  break;
            case ME_PITCHBEND:
                  snd_seq_ev_set_pitchbend(&event, chn, a);
                  break;
            case ME_POLYAFTER:
                  snd_seq_ev_set_keypress(&event, chn, a, b);
                  break;
            case ME_AFTERTOUCH:
                  snd_seq_ev_set_chanpress(&event, chn, a);
                  break;
            case ME_SYSEX:
                  {
                  const unsigned char* p = e.data();
                  int n                  = e.len();
                  int len                = n + sizeof(event) + 2;
                  char buf[len];
                  event.type             = SND_SEQ_EVENT_SYSEX;
                  event.flags            = SND_SEQ_EVENT_LENGTH_VARIABLE;
                  event.data.ext.len     = n + 2;
                  event.data.ext.ptr  = (void*)(buf + sizeof(event));
                  memcpy(buf, &event, sizeof(event));
                  char* pp = buf + sizeof(event);
                  *pp++ = 0xf0;
                  memcpy(pp, p, n);
                  pp += n;
                  *pp = 0xf7;
                  return putEvent(&event);
                  }
            case ME_SONGPOS:
                  event.data.control.value = a;
                  event.type = SND_SEQ_EVENT_SONGPOS;
                  break;
            case ME_CLOCK:
                  event.type = SND_SEQ_EVENT_CLOCK;
                  break;
            case ME_START:
                  event.type = SND_SEQ_EVENT_START;
                  break;
            case ME_CONTINUE:
                  event.type = SND_SEQ_EVENT_CONTINUE;
                  break;
            case ME_STOP:
                  event.type = SND_SEQ_EVENT_STOP;
                  break;
            default:
                  if(MusEGlobal::debugMsg)
                    printf("MidiAlsaDevice::putEvent(): event type %d not implemented\n", e.type());
                  return true;
            }
      return putEvent(&event);
      }

//---------------------------------------------------------
//   putEvent
//    return false if event is delivered
//---------------------------------------------------------

bool MidiAlsaDevice::putEvent(snd_seq_event_t* event)
      {
      int error;

#ifdef ALSA_DEBUG
      printf("MidiAlsaDevice::putEvent\n");  
#endif

      do {
            error   = snd_seq_event_output_direct(alsaSeq, event);
            int len = snd_seq_event_length(event);
            if (error == len) {
//                  printf(".");fflush(stdout);
                  return false;
                  }
            if (error < 0) {
                  if (error == -12) {
//                        printf("?");fflush(stdout);
                        return true;
                        }
                  else {
                        fprintf(stderr, "MidiAlsaDevice::%p putEvent(): midi write error: %s\n",
                           this, snd_strerror(error));
                        fprintf(stderr, "  dst %d:%d\n", adr.client, adr.port);
                        //exit(-1);
                        }
                  }
            else
                  fprintf(stderr, "MidiAlsaDevice::putEvent(): midi write returns %d, expected %d: %s\n",
                     error, len, snd_strerror(error));
            } while (error == -12);
      return true;
      }

//---------------------------------------------------------
//   processMidi
//   Called from ALSA midi sequencer thread only.
//---------------------------------------------------------

void MidiAlsaDevice::processMidi()
{
  //bool stop = stopPending;  // Snapshots
  //bool seek = seekPending;  //
  //seekPending = stopPending = false;
  // Transfer the stuck notes FIFO to the play events list.
  // FIXME It would be faster to have MidiAlsaDevice automatically add the stuck note so that only
  //  one FIFO would be needed. But that requires passing an extra 'tick' and 'off velocity' in
  //  addScheduledEvent, which felt too weird.
  //while(!stuckNotesFifo.isEmpty())
  //  _stuckNotes.add(stuckNotesFifo.get());
    
  //int frameOffset = getFrameOffset();
  //int nextTick = MusEGlobal::audio->nextTick();
  
  //bool is_playing = MusEGlobal::audio->isPlaying();  
  // We're in the ALSA midi thread. audio->isPlaying() might not be true during seek right now. Include START_PLAY state...
  //bool is_playing = MusEGlobal::audio->isPlaying() || MusEGlobal::audio->isStarting(); // TODO Check this. It includes LOOP1 and LOOP2 besides PLAY.
  int pos = MusEGlobal::audio->tickPos();
  int port = midiPort();
  MidiPort* mp = port == -1 ? 0 : &MusEGlobal::midiPorts[port];
  bool ext_sync = MusEGlobal::extSyncFlag.value();

  /*
  if(mp)
  {
    MidiSyncInfo& si = mp->syncInfo();
    if(stop)
    {
      // Don't send if external sync is on. The master, and our sync routing system will take care of that.   
      if(!ext_sync)
      {
        // Shall we check open flags?
        //if(!(dev->rwFlags() & 0x1) || !(dev->openFlags() & 1))
        //if(!(dev->openFlags() & 1))
        //  return;
              
        // Send MMC stop...
        if(si.MMCOut())
        {
          unsigned char msg[mmcStopMsgLen];
          memcpy(msg, mmcStopMsg, mmcStopMsgLen);
          msg[1] = si.idOut();
          putMidiEvent(MidiPlayEvent(0, 0, ME_SYSEX, msg, mmcStopMsgLen));
        }
        
        // Send midi stop...
        if(si.MRTOut()) 
        {
          putMidiEvent(MidiPlayEvent(0, 0, 0, ME_STOP, 0, 0));
          // Added check of option send continue not start.    p3.3.31
          // Hmm, is this required? Seems to make other devices unhappy.
          // (Could try now that this is in MidiDevice. p4.0.22 )
          //if(!si.sendContNotStart())
          //  mp->sendSongpos(MusEGlobal::audio->tickPos() * 4 / config.division);
        }
      }  
    }
    
    if(seek)
    {
      // Don't send if external sync is on. The master, and our sync routing system will take care of that.  
      if(!ext_sync)
      {
        // Send midi stop and song position pointer...
        if(si.MRTOut())
        {
          // Shall we check for device write open flag to see if it's ok to send?...
          //if(!(rwFlags() & 0x1) || !(openFlags() & 1))
          //if(!(openFlags() & 1))
          //  continue;
          putMidiEvent(MidiPlayEvent(0, 0, 0, ME_STOP, 0, 0));
          // Hm, try sending these after stuck notes below...
          //putMidiEvent(MidiPlayEvent(0, 0, 0, ME_SONGPOS, beat, 0));
          //if(is_playing)
          //  putMidiEvent(MidiPlayEvent(0, 0, 0, ME_CONTINUE, 0, 0));
        }    
      }
    }    
  }
  */
  
  /*
  if(stop || (seek && is_playing))  
  {
    // Clear all notes and handle stuck notes...
    //playEventFifo.clear();
    _playEvents.clear();
    for(iMPEvent i = _stuckNotes.begin(); i != _stuckNotes.end(); ++i) 
    {
      MidiPlayEvent ev = *i;
      ev.setTime(0);
      //_playEvents.add(ev);
      putMidiEvent(ev);  // Play immediately.
    }
    _stuckNotes.clear();
  }
  */
  
  /*
  if(mp)
  {
    MidiSyncInfo& si = mp->syncInfo();
    // Try sending these now after stuck notes above...
    if(stop || seek)
    {
      // Reset sustain.
      for(int ch = 0; ch < MIDI_CHANNELS; ++ch) 
        if(mp->hwCtrlState(ch, CTRL_SUSTAIN) == 127) 
          putMidiEvent(MidiPlayEvent(0, _port, ch, ME_CONTROLLER, CTRL_SUSTAIN, 0));
    }
    if(seek)
    {
      // Send new song position.
      if(!ext_sync && si.MRTOut())
      {
        int beat = (pos * 4) / MusEGlobal::config.division;
        putMidiEvent(MidiPlayEvent(0, 0, 0, ME_SONGPOS, beat, 0));
      }
      // Send new controller values.
      MidiCtrlValListList* cll = mp->controller();
      for(iMidiCtrlValList ivl = cll->begin(); ivl != cll->end(); ++ivl) 
      {
        MidiCtrlValList* vl = ivl->second;
        iMidiCtrlVal imcv = vl->iValue(pos);
        if(imcv != vl->end()) {
          Part* p = imcv->second.part;
          // Don't send if part or track is muted or off.
          if(!p || p->mute())
            continue;
          Track* track = p->track();
          if(track && (track->isMute() || track->off()))   
            continue;
          unsigned t = (unsigned)imcv->first;
          // Do not add values that are outside of the part.
          if(t >= p->tick() && t < (p->tick() + p->lenTick()))
            // Use sendEvent to get the optimizations and limiting. But force if there's a value at this exact position.
            mp->sendEvent(MidiPlayEvent(0, _port, ivl->first >> 24, ME_CONTROLLER, vl->num(), imcv->second.val), imcv->first == pos);
        }
      }
      
      // Send continue.
      // REMOVE Tim. This is redundant and too early - Audio::startRolling already properly sends it when sync ready.
      //if(is_playing && !ext_sync && si.MRTOut())
      //  putMidiEvent(MidiPlayEvent(0, 0, 0, ME_CONTINUE, 0, 0));
    }
  }
  */
  
  //if(!(stop || (seek && is_playing)))
  {
    // Transfer the play events FIFO to the play events list.
    //while(!playEventFifo.isEmpty())
    //  _playEvents.add(playEventFifo.get());
      
    /*  TODO Handle these more directly than putting them into play events list.
    //if(MusEGlobal::audio->isPlaying())  
    {
      iMPEvent k;
      for (k = _stuckNotes.begin(); k != _stuckNotes.end(); ++k) {
            if (k->time() >= nextTick)  
                  break;
            MidiPlayEvent ev(*k);
            if(extsync)              // p3.3.25
              ev.setTime(k->time());
            else 
              ev.setTime(tempomap.tick2frame(k->time()) + frameOffset);
            _playEvents.add(ev);
            }
      _stuckNotes.erase(_stuckNotes.begin(), k);
    }
    */
    processStuckNotes();  
  }
  
  if(_playEvents.empty())
    return;
  
  unsigned curFrame = MusEGlobal::audio->curFrame();
  
  // Play all events up to current frame.
  iMPEvent i = _playEvents.begin();            
  for (; i != _playEvents.end(); ++i) {
        if (i->time() > (ext_sync ? pos : curFrame))  // p3.3.25  Check: Should be nextTickPos? p4.0.34
          break; 
        if(mp){
          if (mp->sendEvent(*i, true))  // Force the event to be sent.
            break;
              }
        else 
          if(putMidiEvent(*i))
            break;
        }
  _playEvents.erase(_playEvents.begin(), i);
}

/*
//---------------------------------------------------------
//   handleStop
//---------------------------------------------------------

void MidiAlsaDevice::handleStop()
{
  // If the device is not in use by a port, don't bother it.
  if(_port == -1)
    return;
    
  stopPending = true;  // Trigger stop handling in processMidi.
  
  //---------------------------------------------------
  //    send midi stop
  //---------------------------------------------------
  
  // Don't send if external sync is on. The master, and our sync routing system will take care of that.   
  if(!MusEGlobal::extSyncFlag.value())
  {
    // Shall we check open flags?
    //if(!(dev->rwFlags() & 0x1) || !(dev->openFlags() & 1))
    //if(!(dev->openFlags() & 1))
    //  return;
          
    MidiSyncInfo& si = mp->syncInfo();
    if(si.MMCOut())
      mp->sendMMCStop();
    
    if(si.MRTOut()) 
    {
      mp->sendStop();
      // Added check of option send continue not start. Hmm, is this required? Seems to make other devices unhappy.
      // (Could try now that this is in MidiDevice.)
      //if(!si.sendContNotStart())
      //  mp->sendSongpos(MusEGlobal::audio->tickPos() * 4 / config.division);
    }
  }  

  //---------------------------------------------------
  //    reset sustain
  //---------------------------------------------------
  
  MidiPort* mp = &MusEGlobal::midiPorts[_port];
  for(int ch = 0; ch < MIDI_CHANNELS; ++ch) 
  {
    if(mp->hwCtrlState(ch, CTRL_SUSTAIN) == 127) 
    {
      MidiPlayEvent ev(0, _port, ch, ME_CONTROLLER, CTRL_SUSTAIN, 0);
      //putMidiEvent(ev);
      putEvent(ev);
      // Do sendEvent to get the optimizations - send only on a change of value.
      //mp->sendEvent(ev);
    }
  }
  
  //---------------------------------------------------
  //    send midi stop
  //---------------------------------------------------
  
//   // Don't send if external sync is on. The master, and our sync routing system will take care of that.   
//   if(!MusEGlobal::extSyncFlag.value())
//   {
//     // Shall we check open flags?
//     //if(!(dev->rwFlags() & 0x1) || !(dev->openFlags() & 1))
//     //if(!(dev->openFlags() & 1))
//     //  return;
//           
//     MidiSyncInfo& si = mp->syncInfo();
//     if(si.MMCOut())
//       mp->sendMMCStop();
//     
//     if(si.MRTOut()) 
//     {
//       // Send STOP 
//       mp->sendStop();
//       // Added check of option send continue not start. Hmm, is this required? Seems to make other devices unhappy.
//       // (Could try now that this is in MidiDevice.)
//       //if(!si.sendContNotStart())
//       //  mp->sendSongpos(MusEGlobal::audio->tickPos() * 4 / config.division);
//     }
//   }  
}
*/

/*
//---------------------------------------------------------
//   handleSeek
//---------------------------------------------------------

void MidiAlsaDevice::handleSeek()
{
  // If the device is not in use by a port, don't bother it.
  if(_port == -1)
    return;
  
  seekPending = true;  // Trigger seek handling in processMidi.
  
  MidiPort* mp = &MusEGlobal::midiPorts[_port];
  MidiCtrlValListList* cll = mp->controller();
  int pos = MusEGlobal::audio->tickPos();
  
  //---------------------------------------------------
  //    Send STOP 
  //---------------------------------------------------
    
  // Don't send if external sync is on. The master, and our sync routing system will take care of that.  
  if(!MusEGlobal::extSyncFlag.value())
  {
    if(mp->syncInfo().MRTOut())
    {
      // Shall we check for device write open flag to see if it's ok to send?...
      //if(!(rwFlags() & 0x1) || !(openFlags() & 1))
      //if(!(openFlags() & 1))
      //  continue;
      mp->sendStop();
    }    
  }
  
  //---------------------------------------------------
  //    reset sustain
  //---------------------------------------------------
  
  MidiPort* mp = &MusEGlobal::midiPorts[_port];
  for(int ch = 0; ch < MIDI_CHANNELS; ++ch) 
  {
    if(mp->hwCtrlState(ch, CTRL_SUSTAIN) == 127) 
    {
      MidiPlayEvent ev(0, _port, ch, ME_CONTROLLER, CTRL_SUSTAIN, 0);
      putEvent(ev);
      //putMidiEvent(ev);
      // Do sendEvent to get the optimizations - send only on a change of value.
      //mp->sendEvent(ev);
    }
  }
  
  //---------------------------------------------------
  //    Send new controller values
  //---------------------------------------------------
    
  for(iMidiCtrlValList ivl = cll->begin(); ivl != cll->end(); ++ivl) 
  {
    MidiCtrlValList* vl = ivl->second;
    iMidiCtrlVal imcv = vl->iValue(pos);
    if(imcv != vl->end()) 
    {
      Part* p = imcv->second.part;
      // Don't send if part or track is muted or off.
      if(!p || p->mute())
        continue;
      Track* track = p->track();
      if(track && (track->isMute() || track->off()))   
        continue;
      unsigned t = (unsigned)imcv->first;
      // Do not add values that are outside of the part.
      if(p && t >= p->tick() && t < (p->tick() + p->lenTick()) )
        //_playEvents.add(MidiPlayEvent(0, _port, ivl->first >> 24, ME_CONTROLLER, vl->num(), imcv->second.val));
        // Use sendEvent to get the optimizations and limiting. But force if there's a value at this exact position.
        mp->sendEvent(MidiPlayEvent(0, _port, ivl->first >> 24, ME_CONTROLLER, vl->num(), imcv->second.val), imcv->first == pos);
    }
  }
  
  //---------------------------------------------------
  //    Send STOP and "set song position pointer"
  //---------------------------------------------------
    
  // Don't send if external sync is on. The master, and our sync routing system will take care of that.  
  if(!MusEGlobal::extSyncFlag.value())
  {
    if(mp->syncInfo().MRTOut())
    {
      // Shall we check for device write open flag to see if it's ok to send?...
      //if(!(rwFlags() & 0x1) || !(openFlags() & 1))
      //if(!(openFlags() & 1))
      //  continue;
      
      //mp->sendStop(); // Moved above.
      int beat = (pos * 4) / MusEGlobal::config.division;
      mp->sendSongpos(beat);
    }    
  }
}
*/

//---------------------------------------------------------
//   initMidiAlsa
//    return true on error
//---------------------------------------------------------

bool initMidiAlsa()
      {
      if (MusEGlobal::debugMsg)
            printf("initMidiAlsa\n");
      int error = snd_seq_open(&alsaSeq, "hw", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
      if (error < 0) {
            fprintf(stderr, "Could not open ALSA sequencer: %s\n",
               snd_strerror(error));
            return true;
            }
            
      const int inCap  = SND_SEQ_PORT_CAP_SUBS_READ;
      const int outCap = SND_SEQ_PORT_CAP_SUBS_WRITE;
      
      snd_seq_client_info_t *cinfo;
      snd_seq_client_info_alloca(&cinfo);
      snd_seq_client_info_set_client(cinfo, -1);

      while (snd_seq_query_next_client(alsaSeq, cinfo) >= 0) {
            const char* cname = snd_seq_client_info_get_name(cinfo);
            //printf( "ALSA client name: %s\n", cname);  
            
            // Put Midi Through and user clients after others. Insert other unwanted clients here:          // p4.0.41
            if(snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT || strcmp("Midi Through", cname) == 0)                   
              continue;
            
            snd_seq_port_info_t *pinfo;
            snd_seq_port_info_alloca(&pinfo);
            snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
            snd_seq_port_info_set_port(pinfo, -1);

            while (snd_seq_query_next_port(alsaSeq, pinfo) >= 0) {
                  unsigned int capability = snd_seq_port_info_get_capability(pinfo);
                  if (capability & SND_SEQ_PORT_CAP_NO_EXPORT)  // Ignore ports like "qjackctl" or "port".    p4.0.41
                    continue;
                  if ((capability & outCap) == 0) {
                          const char *name = snd_seq_port_info_get_name(pinfo);
                          if (strcmp("Timer", name) == 0 || 
                              strcmp("Announce", name) == 0 || 
                              strcmp("Receiver", name) == 0)
                                continue;
                          }
                  snd_seq_addr_t adr = *snd_seq_port_info_get_addr(pinfo);
                  MidiAlsaDevice* dev = new MidiAlsaDevice(adr, QString(snd_seq_port_info_get_name(pinfo)));
                  int flags = 0;
                  if (capability & outCap)
                        flags |= 1;
                  if (capability & inCap)
                        flags |= 2;
                  dev->setrwFlags(flags);
                  if (MusEGlobal::debugMsg)
                        printf("ALSA port add: <%s>, %d:%d flags %d 0x%0x\n",
                           snd_seq_port_info_get_name(pinfo),
                           adr.client, adr.port,
                           flags, capability);
                  MusEGlobal::midiDevices.add(dev);
                  }
            }

      snd_seq_client_info_set_client(cinfo, -1);   // Reset
      while (snd_seq_query_next_client(alsaSeq, cinfo) >= 0) {
            const char* cname = snd_seq_client_info_get_name(cinfo);
            //printf( "ALSA client name: %s\n", cname);  
            
            bool is_thru = (strcmp("Midi Through", cname) == 0);
            // Put Midi Through and user clients after others. Insert other unwanted clients here: // p4.0.41
            if( !(snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT || is_thru) )
              continue;
            
            snd_seq_port_info_t *pinfo;
            snd_seq_port_info_alloca(&pinfo);
            snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
            snd_seq_port_info_set_port(pinfo, -1);

            while (snd_seq_query_next_port(alsaSeq, pinfo) >= 0) {
                  unsigned int capability = snd_seq_port_info_get_capability(pinfo);
                  if (capability & SND_SEQ_PORT_CAP_NO_EXPORT)  // Ignore ports like "qjackctl" or "port".    p4.0.41
                    continue;
                  if ((capability & outCap) == 0) {
                          const char *name = snd_seq_port_info_get_name(pinfo);
                          if (strcmp("Timer", name) == 0 || 
                              strcmp("Announce", name) == 0 || 
                              strcmp("Receiver", name) == 0)
                                continue;
                          }
                  snd_seq_addr_t adr = *snd_seq_port_info_get_addr(pinfo);
                  MidiAlsaDevice* dev = new MidiAlsaDevice(adr, QString(snd_seq_port_info_get_name(pinfo)));
                  int flags = 0;
                  if (capability & outCap)
                        flags |= 1;
                  if (capability & inCap)
                        flags |= 2;
                  dev->setrwFlags(flags);
                  if(is_thru)             // Don't auto-open Midi Through.
                    dev->setOpenFlags(0); 
                  if (MusEGlobal::debugMsg)
                        printf("ALSA port add: <%s>, %d:%d flags %d 0x%0x\n",
                           snd_seq_port_info_get_name(pinfo),
                           adr.client, adr.port,
                           flags, capability);
                  MusEGlobal::midiDevices.add(dev);
                  }
            }
            
            
      //snd_seq_set_client_name(alsaSeq, "MusE Sequencer");
      error = snd_seq_set_client_name(alsaSeq, MusEGlobal::audioDevice->clientName());
      if (error < 0) {
            printf("Alsa: Set client name failed: %s", snd_strerror(error));
            return true;
            }
      
      int ci = snd_seq_poll_descriptors_count(alsaSeq, POLLIN);
      int co = snd_seq_poll_descriptors_count(alsaSeq, POLLOUT);

      if (ci > 1 || co > 1) {
            printf("ALSA midi: cannot handle more than one poll fd\n");
            abort();
            }

      struct pollfd pfdi[ci];
      struct pollfd pfdo[co];
      snd_seq_poll_descriptors(alsaSeq, pfdi, ci, POLLIN);
      snd_seq_poll_descriptors(alsaSeq, pfdo, co, POLLOUT);
      alsaSeqFdo = pfdo[0].fd;
      alsaSeqFdi = pfdi[0].fd;

      int port  = snd_seq_create_simple_port(alsaSeq, "MusE Port 0",
         inCap | outCap | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE,
         SND_SEQ_PORT_TYPE_APPLICATION);
      if (port < 0) {
            perror("create port");
            exit(1);
            }
      musePort.port   = port;
      musePort.client = snd_seq_client_id(alsaSeq);

      //-----------------------------------------
      //    subscribe to "Announce"
      //    this enables callbacks for any
      //    alsa port changes
      //-----------------------------------------

      //snd_seq_addr_t aadr;
      announce_adr.client = SND_SEQ_CLIENT_SYSTEM;
      announce_adr.port   = SND_SEQ_PORT_SYSTEM_ANNOUNCE;

      snd_seq_port_subscribe_t* subs;
      snd_seq_port_subscribe_alloca(&subs);
      snd_seq_port_subscribe_set_dest(subs, &musePort);
      snd_seq_port_subscribe_set_sender(subs, &announce_adr);
      error = snd_seq_subscribe_port(alsaSeq, subs);
      if (error < 0) {
            printf("Alsa: Subscribe System failed: %s", snd_strerror(error));
            return true;
            }
      return false;
      }


//---------------------------------------------------------
//   exitMidiAlsa
//---------------------------------------------------------

void exitMidiAlsa()
{
  if(alsaSeq)
  { 
    int error = 0;
    snd_seq_port_subscribe_t* subs;
    // Allocated on stack, no need to call snd_seq_port_subscribe_free() later.
    snd_seq_port_subscribe_alloca(&subs);
    
    //snd_seq_port_info_t *pinfo;
    //snd_seq_port_info_alloca(&pinfo);
    //snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
    //snd_seq_port_info_set_addr(pinfo, &announce_adr);

    snd_seq_port_subscribe_set_dest(subs, &musePort);
    snd_seq_port_subscribe_set_sender(subs, &announce_adr);
    
    // Already subscribed? Then unsubscribe.
    if(!snd_seq_get_port_subscription(alsaSeq, subs))
    {
      error = snd_seq_unsubscribe_port(alsaSeq, subs);
      if(error < 0)
        printf("MusE: exitMidiAlsa: Error unsubscribing alsa midi Announce port %d:%d for reading: %s\n", announce_adr.client, announce_adr.port, snd_strerror(error));
    }   
    
    error = snd_seq_delete_simple_port(alsaSeq, musePort.port);
    if(error < 0) 
      fprintf(stderr, "MusE: Could not delete ALSA simple port: %s\n", snd_strerror(error));
    
    error = snd_seq_close(alsaSeq);  
    if(error < 0) 
      fprintf(stderr, "MusE: Could not close ALSA sequencer: %s\n", snd_strerror(error));
  }  
}


//---------------------------------------------------------
//   setAlsaClientName
//---------------------------------------------------------

void setAlsaClientName(const char* name)
{
#ifdef ALSA_DEBUG
  printf("setAlsaClientName: %s  seq:%p\n", name, alsaSeq);
#endif            
  
  if(!alsaSeq)
    return; 
    
  int error = snd_seq_set_client_name(alsaSeq, name);
  if (error < 0) 
    printf("setAlsaClientName: failed: %s", snd_strerror(error));
}

struct AlsaPort {
      snd_seq_addr_t adr;
      char* name;
      int flags;
      AlsaPort(snd_seq_addr_t a, const char* s, int f) {
            adr = a;
            name = strdup(s);
            flags = f;
            }
      };

static std::list<AlsaPort> portList;

//---------------------------------------------------------
//   alsaScanMidiPorts
//---------------------------------------------------------

void alsaScanMidiPorts()
      {
#ifdef ALSA_DEBUG
    printf("alsa scan midi ports\n");
#endif
      const int inCap  = SND_SEQ_PORT_CAP_SUBS_READ;
      const int outCap = SND_SEQ_PORT_CAP_SUBS_WRITE;

      portList.clear();

      snd_seq_client_info_t* cinfo;
      snd_seq_client_info_alloca(&cinfo);
      snd_seq_client_info_set_client(cinfo, 0);

      while (snd_seq_query_next_client(alsaSeq, cinfo) >= 0) {
            snd_seq_port_info_t *pinfo;
            snd_seq_port_info_alloca(&pinfo);
            snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
            snd_seq_port_info_set_port(pinfo, -1);
            while (snd_seq_query_next_port(alsaSeq, pinfo) >= 0) {
                  unsigned int capability = snd_seq_port_info_get_capability(pinfo);
                  if (capability & SND_SEQ_PORT_CAP_NO_EXPORT)  // Ignore ports like "qjackctl" or "port".    p4.0.41
                    continue;
                  if (((capability & outCap) == 0)
                     && ((capability & inCap) == 0))
                        continue;
                  snd_seq_addr_t adr;
                  const char* name;
                  adr  = *snd_seq_port_info_get_addr(pinfo);
                  name = snd_seq_port_info_get_name(pinfo);
                  if (adr.client == musePort.client && adr.port == musePort.port)
                        continue;
                  int flags = 0;
                  if (capability & outCap)
                        flags |= 1;
                  if (capability & inCap)
                        flags |= 2;
// printf("ALSA port add: <%s>, flags %d\n", name, flags);
                  portList.push_back(AlsaPort(adr, name, flags));
                  }
            }
      //
      //  check for devices to delete
      //
      for (iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end();) {
            MidiAlsaDevice* d = dynamic_cast<MidiAlsaDevice*>(*i);
            if (d == 0) {
                  ++i;
                  continue;
                  }
            std::list<AlsaPort>::iterator k = portList.begin();
            for (; k != portList.end(); ++k) {
                  if (k->adr.client == d->adr.client
                     && k->adr.port == d->adr.port) {
                        break;
                        }
                  }
            if (k == portList.end()) {
                  if (d->midiPort() != -1)
                        MusEGlobal::midiPorts[d->midiPort()].setMidiDevice(0);
                  iMidiDevice k = i;
// printf("erase device\n");
                  ++i;
                  MusEGlobal::midiDevices.erase(k);
                  }
            else {
                  ++i;
                  }
            }
      //
      //  check for devices to add
      //
      // TODO: Possibly auto-add them to available midi ports.    p4.0.41            
      //
      for (std::list<AlsaPort>::iterator k = portList.begin(); k != portList.end(); ++k) {
            iMidiDevice i = MusEGlobal::midiDevices.begin();
// printf("ALSA port: <%s>\n", k->name);
            for (;i != MusEGlobal::midiDevices.end(); ++i) {
                  MidiAlsaDevice* d = dynamic_cast<MidiAlsaDevice*>(*i);
                  if (d == 0)
                        continue;
                  if ((k->adr.client == d->adr.client) && (k->adr.port == d->adr.port)) {
                        break;
                        }
                  }
            if (i == MusEGlobal::midiDevices.end()) {
                  // add device
                  MidiAlsaDevice* dev = new MidiAlsaDevice(k->adr,
                     QString(k->name));
                  dev->setrwFlags(k->flags);
                  MusEGlobal::midiDevices.add(dev);
// printf("add device\n");
                  }
            }
            
            
      }

//---------------------------------------------------------
//   alsaSelectRfd
//---------------------------------------------------------

int alsaSelectRfd()
      {
      return alsaSeqFdi;
      }

//---------------------------------------------------------
//   alsaSelectWfd
//---------------------------------------------------------

int alsaSelectWfd()
      {
      return alsaSeqFdo;
      }

//---------------------------------------------------------
//   processInput
//---------------------------------------------------------

void alsaProcessMidiInput()
{
      MidiRecordEvent event;
      snd_seq_event_t* ev;
      
      for (;;) 
      {
            int rv = snd_seq_event_input(alsaSeq, &ev);
// printf("AlsaInput %d\n", rv);
            if (rv < 0) {
//                  printf("AlsaMidi: read error %s\n", snd_strerror(rv));
                  return;
                  }
            switch(ev->type) {
                  case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                  case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
                        return;
                  case SND_SEQ_EVENT_CLIENT_START:
                  case SND_SEQ_EVENT_CLIENT_EXIT:
                        // return;
                        // on first start of a software synthesizer we only
                        // get CLIENT_START event and no PORT_START, why?

                  case SND_SEQ_EVENT_PORT_START:
                  case SND_SEQ_EVENT_PORT_EXIT:
                        alsaScanMidiPorts();
                        MusEGlobal::audio->midiPortsChanged();  // signal gui
                        snd_seq_free_event(ev);
                        return;
                  }

            int curPort = -1;
            MidiAlsaDevice* mdev = 0;
            //
            // find real source device
            //
            for (iMidiDevice i = MusEGlobal::midiDevices.begin(); i != MusEGlobal::midiDevices.end(); ++i) {
                  MidiAlsaDevice* d = dynamic_cast<MidiAlsaDevice*>(*i);
                  if (d  && d->adr.client == ev->source.client
                     && d->adr.port == ev->source.port) {
                        curPort = d->midiPort();
                        mdev = d;
                        }
                  }
            
            if (mdev == 0 || curPort == -1) {
                  if (MusEGlobal::debugMsg) {
                        fprintf(stderr, "no port %d:%d found for received alsa event\n",
                           ev->source.client, ev->source.port);
                        }
                  snd_seq_free_event(ev);
                  return;
                  }
            
            event.setType(0);      // mark as unused
            event.setPort(curPort);
            event.setB(0);

            switch(ev->type) 
            {
                  case SND_SEQ_EVENT_NOTEON:
                        event.setChannel(ev->data.note.channel);
                        event.setType(ME_NOTEON);
                        event.setA(ev->data.note.note);
                        event.setB(ev->data.note.velocity);
                        break;

                  case SND_SEQ_EVENT_NOTEOFF:
                        event.setChannel(ev->data.note.channel);
                        event.setType(ME_NOTEOFF);
                        event.setA(ev->data.note.note);
                        event.setB(ev->data.note.velocity);
                        break;

                  case SND_SEQ_EVENT_KEYPRESS:
                        event.setChannel(ev->data.note.channel);
                        event.setType(ME_POLYAFTER);
                        event.setA(ev->data.note.note);
                        event.setB(ev->data.note.velocity);
                        break;

                  case SND_SEQ_EVENT_CHANPRESS:
                        event.setChannel(ev->data.control.channel);
                        event.setType(ME_AFTERTOUCH);
                        event.setA(ev->data.control.value);
                        break;

                  case SND_SEQ_EVENT_PGMCHANGE:
                        event.setChannel(ev->data.control.channel);
                        event.setType(ME_PROGRAM);
                        event.setA(ev->data.control.value);
                        break;

                  case SND_SEQ_EVENT_PITCHBEND:
                        event.setChannel(ev->data.control.channel);
                        event.setType(ME_PITCHBEND);
                        event.setA(ev->data.control.value);
                        break;

                  case SND_SEQ_EVENT_CONTROLLER:
                        event.setChannel(ev->data.control.channel);
                        event.setType(ME_CONTROLLER);
                        event.setA(ev->data.control.param);
                        event.setB(ev->data.control.value);
                        break;

                  case SND_SEQ_EVENT_CLOCK:
                        MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_CLOCK, curTime());
                        //mdev->syncInfo().trigMCSyncDetect();
                        break;

                  case SND_SEQ_EVENT_START:
                        MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_START, curTime());
                        break;

                  case SND_SEQ_EVENT_CONTINUE:
                        MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_CONTINUE, curTime());
                        break;

                  case SND_SEQ_EVENT_STOP:
                        MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_STOP, curTime());
                        break;

                  case SND_SEQ_EVENT_TICK:
                        MusEGlobal::midiSeq->realtimeSystemInput(curPort, ME_TICK, curTime());
                        //mdev->syncInfo().trigTickDetect();
                        break;

                  case SND_SEQ_EVENT_SYSEX:
                        // TODO: Deal with large sysex, which are broken up into chunks!
                        // For now, do not accept if the first byte is not SYSEX or the last byte is not EOX, 
                        //  meaning it's a chunk, possibly with more chunks to follow.
                        if((*((unsigned char*)ev->data.ext.ptr) != ME_SYSEX) ||
                           (*(((unsigned char*)ev->data.ext.ptr) + ev->data.ext.len - 1) != ME_SYSEX_END))
                        {
                          printf("MusE: alsaProcessMidiInput sysex chunks not supported!\n");
                          break;
                        }
                        
                        event.setTime(0);      // mark as used
                        event.setType(ME_SYSEX);
                        event.setData((unsigned char*)(ev->data.ext.ptr)+1,
                           ev->data.ext.len-2);
                        break;
                  case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                  case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:  // write port is released
                        break;
                  case SND_SEQ_EVENT_SONGPOS:
                        MusEGlobal::midiSeq->setSongPosition(curPort, ev->data.control.value);
                        break;
                  case SND_SEQ_EVENT_SENSING:
                        break;
                  case SND_SEQ_EVENT_QFRAME:
                        MusEGlobal::midiSeq->mtcInputQuarter(curPort, ev->data.control.value);
                        break;
                  // case SND_SEQ_EVENT_CLIENT_START:
                  // case SND_SEQ_EVENT_CLIENT_EXIT:
                  // case SND_SEQ_EVENT_CLIENT_CHANGE:
                  // case SND_SEQ_EVENT_PORT_CHANGE:
                  // case SND_SEQ_EVENT_SONGSEL:
                  // case SND_SEQ_EVENT_TIMESIGN:
                  // case SND_SEQ_EVENT_KEYSIGN:
                  // case SND_SEQ_EVENT_SETPOS_TICK:
                  // case SND_SEQ_EVENT_SETPOS_TIME:
                  // case SND_SEQ_EVENT_TEMPO:
                  // case SND_SEQ_EVENT_TUNE_REQUEST:
                  // case SND_SEQ_EVENT_RESET:

                  // case SND_SEQ_EVENT_NOTE:
                  // case SND_SEQ_EVENT_CONTROL14:
                  // case SND_SEQ_EVENT_NONREGPARAM:
                  // case SND_SEQ_EVENT_REGPARAM:
                  default:
                        printf("ALSA Midi input: type %d not handled\n", ev->type);
                        break;
            }
            if(event.type())
              mdev->recordEvent(event);
                  
            snd_seq_free_event(ev);
            if (rv == 0)
                  break;
      }
}

} // namespace MusECore
