//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midi.cpp,v 1.2 2003/11/01 18:12:52 wschweer Exp $
//
//  (C) Copyright 1999/2000 Werner Schweer (ws@seh.de)
//=========================================================

#include <errno.h>
#include <values.h>
#include <assert.h>

#include "song.h"
#include "midi.h"
#include "drummap.h"
#include "event.h"
#include "globals.h"
#include "midictrl.h"
#include "marker/marker.h"
#include "midiport.h"
#include "midictrl.h"

extern void dump(const unsigned char* p, int n);

unsigned const char gmOnMsg[] = { 0x7e, 0x7f, 0x09, 0x01 };
unsigned const char gsOnMsg[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41 };
unsigned const char xgOnMsg[] = { 0x43, 0x10, 0x4c, 0x00, 0x00, 0x7e, 0x00 };
unsigned const int  gmOnMsgLen = sizeof(gmOnMsg);
unsigned const int  gsOnMsgLen = sizeof(gsOnMsg);
unsigned const int  xgOnMsgLen = sizeof(xgOnMsg);

/*---------------------------------------------------------
 *    midi_meta_name
 *---------------------------------------------------------*/

QString midiMetaName(int meta)
      {
      const char* s = "";
      switch (meta) {
            case 0:     s = "Sequence Number"; break;
            case 1:     s = "Text Event"; break;
            case 2:     s = "Copyright"; break;
            case 3:     s = "Sequence/Track Name"; break;
            case 4:     s = "Instrument Name"; break;
            case 5:     s = "Lyric"; break;
            case 6:     s = "Marker"; break;
            case 7:     s = "Cue Point"; break;
            case 8:
            case 9:
            case 0x0a:
            case 0x0b:
            case 0x0c:
            case 0x0d:
            case 0x0e:
            case 0x0f:  s = "Text"; break;
            case 0x20:  s = "Channel Prefix"; break;
            case 0x21:  s = "Port Change"; break;
            case 0x2f:  s = "End of Track"; break;
            case 0x51:  s = "Set Tempo"; break;
            case 0x54:  s = "SMPTE Offset"; break;
            case 0x58:  s = "Time Signature"; break;
            case 0x59:  s = "Key Signature"; break;
            case 0x74:  s = "Sequencer-Specific1"; break;
            case 0x7f:  s = "Sequencer-Specific2"; break;
            default:
                  break;
            }
      return QString(s);
      }

//---------------------------------------------------------
//   QString nameSysex
//---------------------------------------------------------

QString nameSysex(unsigned int len, const unsigned char* buf)
      {
      QString s;
      switch(buf[0]) {
            case 0x00:
                  if (buf[1] == 0 && buf[2] == 0x41)
                        s = "Microsoft";
                  break;
            case 0x01:  s = "Sequential Circuits: "; break;
            case 0x02:  s = "Big Briar: "; break;
            case 0x03:  s = "Octave / Plateau: "; break;
            case 0x04:  s = "Moog: "; break;
            case 0x05:  s = "Passport Designs: "; break;
            case 0x06:  s = "Lexicon: "; break;
            case 0x07:  s = "Kurzweil"; break;
            case 0x08:  s = "Fender"; break;
            case 0x09:  s = "Gulbransen"; break;
            case 0x0a:  s = "Delta Labas"; break;
            case 0x0b:  s = "Sound Comp."; break;
            case 0x0c:  s = "General Electro"; break;
            case 0x0d:  s = "Techmar"; break;
            case 0x0e:  s = "Matthews Research"; break;
            case 0x10:  s = "Oberheim"; break;
            case 0x11:  s = "PAIA: "; break;
            case 0x12:  s = "Simmons: "; break;
            case 0x13:  s = "DigiDesign"; break;
            case 0x14:  s = "Fairlight: "; break;
            case 0x15:  s = "JL Cooper"; break;
            case 0x16:  s = "Lowery"; break;
            case 0x17:  s = "Lin"; break;
            case 0x18:  s = "Emu"; break;
            case 0x1b:  s = "Peavy"; break;
            case 0x20:  s = "Bon Tempi: "; break;
            case 0x21:  s = "S.I.E.L: "; break;
            case 0x23:  s = "SyntheAxe: "; break;
            case 0x24:  s = "Hohner"; break;
            case 0x25:  s = "Crumar"; break;
            case 0x26:  s = "Solton"; break;
            case 0x27:  s = "Jellinghaus Ms"; break;
            case 0x28:  s = "CTS"; break;
            case 0x29:  s = "PPG"; break;
            case 0x2f:  s = "Elka"; break;
            case 0x36:  s = "Cheetah"; break;
            case 0x3e:  s = "Waldorf"; break;
            case 0x40:  s = "Kawai: "; break;
            case 0x41:  s = "Roland: "; break;
            case 0x42:  s = "Korg: "; break;
            case 0x43:  s = "Yamaha: "; break;
            case 0x44:  s = "Casio"; break;
            case 0x45:  s = "Akai"; break;
            case 0x7c:  s = "MusE Soft Synth"; break;
            case 0x7d:  s = "Educational Use"; break;
            case 0x7e:  s = "Universal: Non Real Time"; break;
            case 0x7f:  s = "Universal: Real Time"; break;
            default:    s = "??: "; break;
            }
      //
      // following messages should not show up in event list
      // they are filtered while importing midi files
      //
      if ((len == gmOnMsgLen) && memcmp(buf, gmOnMsg, gmOnMsgLen) == 0)
            s += "GM-ON";
      else if ((len == gsOnMsgLen) && memcmp(buf, gsOnMsg, gsOnMsgLen) == 0)
            s += "GS-ON";
      else if ((len == xgOnMsgLen) && memcmp(buf, xgOnMsg, xgOnMsgLen) == 0)
            s += "XG-ON";
      return s;
      }

//---------------------------------------------------------
//   MidiFile
//---------------------------------------------------------

MidiFile::MidiFile(FILE* f)
      {
      fp        = f;
      error     = 0;
      timesig_z = 4;
      timesig_n = 4;
      curPos    = 0;
      }

/*---------------------------------------------------------
 *    read
 *    write
 *---------------------------------------------------------*/

bool MidiFile::read(void* p, size_t len)
      {
      for (;;) {
            curPos += len;
            size_t rv = fread(p, 1, len, fp);
            if (rv == len)
                  return false;
            if (feof(fp)) {
                  error = EIO;
                  return true;
                  }
            error = errno;
            return true;
            }
      return false;
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

bool MidiFile::write(const void* p, size_t len)
      {
      size_t rv = fwrite(p, 1, len, fp);
      if (rv == len)
            return false;
      printf("write midifile failed: %s\n", strerror(errno));
      error = errno;
      return true;
      }

//---------------------------------------------------------
//   readShort
//---------------------------------------------------------

int MidiFile::readShort()
      {
      short format;
      read(&format, 2);
      return BE_SHORT(format);
      }

//---------------------------------------------------------
//   writeShort
//---------------------------------------------------------

void MidiFile::writeShort(int i)
      {
      int format = BE_SHORT(i);
      write(&format, 2);
      }

//---------------------------------------------------------
//   readLong
//   writeLong
//---------------------------------------------------------

int MidiFile::readLong()
      {
      int format;
      read(&format, 4);
      return BE_LONG(format);
      }

//---------------------------------------------------------
//   writeLong
//---------------------------------------------------------

void MidiFile::writeLong(int i)
      {
      int format = BE_LONG(i);
      write(&format, 4);
      }

/*---------------------------------------------------------
 *    skip
 *    This is meant for skipping a few bytes in a
 *    file or fifo.
 *---------------------------------------------------------*/

bool MidiFile::skip(size_t len)
      {
      char tmp[len];
      return read(tmp, len);
      }

/*---------------------------------------------------------
 *    getvl
 *    Read variable-length number (7 bits per byte, MSB first)
 *---------------------------------------------------------*/

int MidiFile::getvl()
      {
      int l = 0;
      for (int i = 0;i < 16; i++) {
            uchar c;
            if (read(&c, 1))
                  return -1;
            l += (c & 0x7f);
            if (!(c & 0x80))
                  return l;
            l <<= 7;
            }
      return -1;
      }

/*---------------------------------------------------------
 *    putvl
 *    Write variable-length number (7 bits per byte, MSB first)
 *---------------------------------------------------------*/

void MidiFile::putvl(unsigned val)
      {
      unsigned long buf = val & 0x7f;
      while ((val >>= 7) > 0) {
            buf <<= 8;
            buf |= 0x80;
            buf += (val & 0x7f);
            }
      for (;;) {
            put(buf);
            if (buf & 0x80)
                  buf >>= 8;
            else
                  break;
            }
      }

//---------------------------------------------------------
//   writeEvent
//---------------------------------------------------------

void MidiFile::writeEvent(int channel, MidiEvent* event)
      {
      int c = channel;
      int nstat = 0;

      switch (event->type()) {
            case MidiEvent::NoteOff: nstat = 0x80; break;
            case MidiEvent::Note:    nstat = 0x90; break;
            case MidiEvent::PAfter:  nstat = 0xa0; break;
            case MidiEvent::Ctrl7:   nstat = 0xb0; break;
            case MidiEvent::Program: nstat = 0xc0; break;
            case MidiEvent::CAfter:  nstat = 0xd0; break;
            case MidiEvent::Pitch:   nstat = 0xe0; break;
            case MidiEvent::Sysex:   nstat = 0xf0; break;
            case MidiEvent::Meta:    nstat = 0xff; break;

            case MidiEvent::NRPN:
            case MidiEvent::RPN:
            case MidiEvent::Ctrl14:
                  nstat = 0xb0;
                  break;

            case MidiEvent::NoEvent:
            case MidiEvent::Symbol:
            case MidiEvent::Clock:
            case MidiEvent::Quantize:
                  return;
            };
      nstat |= c;
      //
      //  running status; except for Sysex- and Meta Events
      //
      if (((nstat & 0xf0) != 0xf0) && (nstat != status)) {
            status = nstat;
            put(nstat);
            }
      switch (event->type()) {
            case MidiEvent::NoteOff:
                  put(event->pitch());
                  put(event->veloOff());
                  break;
            case MidiEvent::Note:
            case MidiEvent::PAfter:
            case MidiEvent::Ctrl7:
                  put(event->dataA());
                  put(event->dataB());
                  break;
            case MidiEvent::Pitch:
                  {
                  int lval = (event->dataA()-8192) & 0x7f;
                  int hval = ((event->dataA()-8192)>>7) & 0x7f;
                  put(lval);
                  put(hval);
                  }
                  break;
            case MidiEvent::Program:
            case MidiEvent::CAfter:
                  put(event->dataA());
                  break;
            case MidiEvent::Sysex:
                  put(0xf0);
                  putvl(event->dataLen()+1);  // 0xf7 berücksichtigen
                  write(event->data(), event->dataLen());
                  put(0xf7);
                  status = -1;      // invalidate running status
                  break;
            case MidiEvent::Meta:
                  put(0xff);
                  put(event->dataA());
                  putvl(event->dataLen());
                  write(event->data(), event->dataLen());
                  status = -1;
                  break;

            case MidiEvent::Ctrl14:
                  put(event->dataC());          // send least significant bits
                  put(event->dataB());
                  put(event->dataA());          // send msb
                  put(event->dataB() >> 7);
                  break;

            case MidiEvent::NRPN:               // TODO: support running status
                  put(CTRL_LDATA);
                  put(event->dataB());

                  put(CTRL_HDATA);
                  put(event->dataB() >> 7);

                  put(CTRL_LNRPN);
                  put(event->dataC());

                  put(CTRL_HNRPN);
                  put(event->dataA());
                  break;

            case MidiEvent::RPN:
                  put(CTRL_LDATA);
                  put(event->dataB());

                  put(CTRL_HDATA);
                  put(event->dataB() >> 7);

                  put(CTRL_LRPN);
                  put(event->dataC());

                  put(CTRL_HRPN);
                  put(event->dataA());
                  break;

            case MidiEvent::NoEvent:
            case MidiEvent::Symbol:
            case MidiEvent::Clock:
            case MidiEvent::Quantize:
                  break;
            };
      }

//---------------------------------------------------------
//   checkSysex
//    return false, if Sysex is detected and can be removed
//    from event list
//---------------------------------------------------------

bool MidiFile::checkSysex(MidiTrack* track, unsigned int len, unsigned char* buf)
      {
      if ((len == gmOnMsgLen) && memcmp(buf, gmOnMsg, gmOnMsgLen) == 0) {
            song->setMType(MT_GM);
            return false;
            }
      if ((len == gsOnMsgLen) && memcmp(buf, gsOnMsg, gsOnMsgLen) == 0) {
            song->setMType(MT_GS);
            return false;
            }
      if (buf[0] == 0x41) {   // Roland
            if (song->mtype() != MT_XG)
                  song->setMType(MT_GS);
            }

      else if (buf[0] == 0x43) {   // Yamaha
            song->setMType(MT_XG);
            int type   = buf[1] & 0xf0;
            switch (type) {
                  case 0x00:  // bulk dump
                        buf[1] = 0;
                        break;
                  case 0x10:
                        if (buf[1] != 0x10) {
// printf("SYSEX Device changed from %d(0x%x) to 1\n", (buf[1] & 0xf) + 1, buf[1]);
                              buf[1] = 0x10;    // fix to Device 1
                              }
                        if (len == 7 && buf[2] == 0x4c && buf[3] == 0x08 && buf[5] == 7) {
                              // part mode
                              // 0 - normal
                              // 1 - DRUM
                              // 2 - DRUM 1
                              // 3 - DRUM 2
                              // 4 - DRUM 3
                              // 5 - DRUM 4
                              printf("xg set part mode channel %d to %d\n", buf[4]+1, buf[6]);
                              if (buf[6] != 0)
                                    track->setType(Track::DRUM);
                              }
                        break;
                  case 0x20:
                        printf("YAMAHA DUMP REQUEST\n");
                        return true;
                  case 0x30:
                        printf("YAMAHA PARAMETER REQUEST\n");
                        return true;
                  default:
                        printf("YAMAHA unknown SYSEX: data[2]=%02x\n", buf[1]);
//                        dump(buf, len);
                        return true;
                  }
            }
      else if (buf[0] == 0x42) {   // Korg
//            int device = buf[1] & 0xf;
            if ((buf[1] & 0xf0) != 0x30) {
                  printf("KORG SYSEX??\n");
                  }
//            buf[1] &= 0xf;    // fest auf Device 1 klemmen
            fflush(stdout);
            }
      else if (buf[0] == 0x41) {   // Korg
//            int device = buf[1] & 0xf;
            if ((buf[1] & 0xf0) != 0x10) {
                  printf("GM SYSEX??\n");
                  }
//            buf[1] &= 0xf;    // fest auf Device 1 klemmen
            fflush(stdout);
            }

      if ((len == xgOnMsgLen) && memcmp(buf, xgOnMsg, xgOnMsgLen) == 0) {
            song->setMType(MT_XG);
            return false;
            }
      return true;
      }

//---------------------------------------------------------
//   readTrack
//    return true on error
//---------------------------------------------------------

bool MidiFile::readTrack(bool mergeChannels)
      {
      char tmp[4];
      read(tmp, 4);
      if (memcmp(tmp, "MTrk", 4)) {
            printf("BAD FILE, MTrk expected at track %d, error %d <%s>\n",
              -1, error, strerror(error));
            return true;
            }
      int len       = readLong();       // len
      int endPos    = curPos + len;
      status        = -1;
      sstatus       = -1;        // running status, der nicht bei meta oder sysex zurückgesetzt wird
      lastchan      = -1;
      lastport      = -1;
      channelprefix = -1;
      click         = 0;
      int channel   = -1;
      int port      = -1;
      MidiTrack* track  = new MidiTrack();
      song->tracks()->add(track);
      EventList* el = track->events();

      for (;;) {
            MidiEvent* event = readEvent(track);
            if (event == 0)
                  break;
            if (int(event) == -1)
                  continue;
            if (int(event) == -2)         // error
                  return true;
            if (lastchan == -1) {
                  el->add(event);         // kann kein channel event sein
                  continue;
                  }
            if (port == -1) {
                  if (lastport == -1) {
                        port = 0;
                        }
                  else {
                        port = lastport;
                        if (channel != -1) {
                              track->setOutPort(port);
                              }
                        }
                  }
            else {
                  if ((lastport != -1) && (lastport != port)) {
                        printf("PORT CHANGE in Midi File\n");
                        if (channel != -1) {
                              track->setOutPort(port);
                              }
                        }
                  }
            if (port >= MIDI_PORTS) {
                  printf("port %d >= %d, reset to 1\n", port, MIDI_PORTS);
                  port = 1;
                  }
            if (channel == -1) {
                  channel = lastchan;
                  track->setOutChannel(channel);
                  if (port != track->outPort())
                        printf("2 HALOOOO %d %d\n", port, track->outPort());
                  el->add(event);
                  continue;
                  }
            if (lastchan != channel) {
                  mergeChannels = true;   // DEBUG
                  if (!mergeChannels) {
                        //
                        // try to insert in approp track
                        //
                        TrackList* tl = song->tracks();
                        MidiTrack* mtrack;
                        iTrack t;
                        for (t = tl->begin(); t != tl->end(); ++t) {
                              mtrack = dynamic_cast<MidiTrack*>(*t);
                              if (mtrack == 0)
                                    continue;
                              if (mtrack->outChannel() == lastchan) {
                                    mtrack->events()->add(event);
                                    break;
                                    }
                              }
                        if (t == tl->end())
                              printf("no approp. track found\n");
                        }
                  else {
                        //
                        // event with new channel in track
                        //    try to find channel with appropr. track
                        //    or create new track
                        //
                        TrackList* tl = song->tracks();
                        iTrack t;
                        MidiTrack* mtrack = 0;
                        for (t = tl->begin(); t != tl->end(); ++t) {
                              mtrack = dynamic_cast<MidiTrack*>(*t);
                              if (mtrack == 0)
                                    continue;
                              if (mtrack->outChannel() == lastchan)
                                    break;
                              }
                        if (t == tl->end()) {
                              mtrack = new MidiTrack();
                              mtrack->setOutChannel(lastchan);
                              mtrack->setOutPort(port);
                              song->tracks()->add(mtrack);
                              }
                        track   = mtrack;
                        channel = lastchan;
                        el = track->events();
                        }
                  }
            el->add(event);
            }
      int end = curPos;
      if (end != endPos) {
            printf("TRACKLAENGE pass nicht %d+%d != %d, %d zu viel\n",
               endPos-len, len, end, endPos-end);
            if (end < endPos)
                  skip(endPos - end);
            }
      return false;
      }

//---------------------------------------------------------
//   printKaraoke
//---------------------------------------------------------

#if 0
static void printKaraoke(char* p)
      {
      if (*p == '\\') {
            printf("\n\n");
            ++p;
            }
      else if (*p == '/') {
            printf("\n");
            ++p;
            }
      else if (*p == '@')
            return;
      printf("%s", p);
      fflush(stdout);
      }
#endif

//---------------------------------------------------------
//   readEvent
//    returns:
//          0     End of track
//          -1    Event filtered
//          -2    Error
//---------------------------------------------------------

MidiEvent* MidiFile::readEvent(MidiTrack* track)
      {
      uchar me, type, a, b;

      int nclick = getvl();
      if (nclick == -1) {
            printf("readEvent: error 1\n");
            return 0;
            }
      click += nclick;
      for (;;) {
            if (read(&me, 1)) {
                  printf("readEvent: error 2\n");
                  return 0;
                  }
            //    0xf8  timing clock
            //    0xf9  undefined
            //    0xfa  start
            //    0xfb  continue
            //    0xfc  stop
            //    0xfd  undefined
            //    0xfe  active sensing

            if (me >= 0xf8 && me <= 0xfe) {
                  printf("Midi: Real Time Message 0x%02x\n", me & 0xff);
                  }
            else
                  break;
            }

      int t = (click * ::division + division/2) / division;
      MidiEvent* event = new MidiEvent;
      event->setPosTick(t);

      int len;
      uchar* buffer;

      switch (me) {
            case 0xf1:        // undefined
            case 0xf2:        // song position, 2 - 14 bit value, least significant byte first
            case 0xf3:        // song select          1 - song number
            case 0xf4:        // undefined
            case 0xf5:        // Device Select + Parameter 0, 1, 2, 3
            case 0xf6:        // tune request         0
                  printf("Midi: unknown Message 0x%02x\n", me & 0xff);
                  break;
            case ME_SYSEX:    // 0xf0
            case 0xf7:        // EOX
                  status = -1;                  // no running status
                  len = getvl();
                  if (len == -1) {
                        printf("readEvent: error 3\n");
                        delete event;
                        return (MidiEvent*)-2;
                        }
                  buffer = new uchar[len];
                  if (read(buffer, len)) {
                        printf("readEvent: error 4\n");
                        delete event;
                        delete[] buffer;
                        return (MidiEvent*)-2;
                        }
                  if (buffer[len-1] != 0xf7) {
                        printf("SYSEX endet nicht mit 0xf7!\n");
                        // Forstsetzung folgt?
                        }
                  else
                        --len;      // don't count 0xf7
                  if (checkSysex(track, len, buffer)) {
                        event->setType(MidiEvent::Sysex);
                        EvData* ed = new EvData(buffer, len);
                        ed->refCount = 1;
                        event->setEvData(ed);
                        }
                  else {
                        delete[] buffer;
                        delete event;
                        return (MidiEvent*)-1;
                        }
                  break;
            case ME_META:     // 0xff
                  status = -1;                  // no running status
                  if (read(&type, 1)) {            // read type
                        printf("readEvent: error 5\n");
                        delete event;
                        return (MidiEvent*)-2;
                        }
                  len = getvl();                // read len
                  if (len == -1) {
                        printf("readEvent: error 6\n");
                        delete event;
                        return (MidiEvent*)-2;
                        }
                  buffer = new uchar[len+1];
                  if (len) {
                        if (read(buffer, len)) {
                              printf("readEvent: error 7\n");
                              delete event;
                              delete[] buffer;
                              return (MidiEvent*)-2;
                              }
                        }
                  buffer[len] = 0;
                  if (type == 0x2f) {           // End of Track
                        delete event;
                        delete[] buffer;
                        return 0;
                        }
                  switch (type) {
                        case 0x03:        // Sequence-/TrackName
                              {
                              QString s((char*)buffer);
                              track->setName(s);
                              delete[] buffer;
                              delete event;
                              }
                              return (MidiEvent*)-1;  // DEBUG: eigentlich nächsten Event lesen
                        case 0x0f:        // Track Comment
                              {
                              QString s((char*)buffer);
                              track->setComment(s);
                              delete[] buffer;
                              delete event;
                              }
                              return (MidiEvent*)-1;  // DEBUG: eigentlich nächsten Event lesen
                        case 0x22:        // Channel Prefix
                              if (len == 1) {
                                    channelprefix = buffer[0];
                                    delete[] buffer;
                                    delete event;
                                    return (MidiEvent*)-1;
                                    }
                              goto rest;
                        case 0x21:        // Port Change
                              if (len == 1) {
                                    lastport = buffer[0];
                                    if (lastport == 10)
                                          lastport = 1;
                                    delete[] buffer;
                                    delete event;
                                    return (MidiEvent*)-1;
                                    }
                              goto rest;
                        case 0x51:        // Tempo
                              {
                              int tempo = buffer[2] + (buffer[1] << 8) + (buffer[0] <<16);
                              tempomap.addTempo(t, tempo);
                              delete[] buffer;
                              delete event;
                              }
                              return (MidiEvent*)-1;  // DEBUG: eigentlich nächsten Event lesen
                        case 0x58:        // Time Signature
                              {
                              timesig_z = buffer[0];
                              int n = buffer[1];
                              timesig_n = 1;
                              for (int i = 0; i < n; i++)
                                    timesig_n *= 2;
                              sigmap.add(t, timesig_z, timesig_n);
                              delete event;
                              delete[] buffer;
                              }
                              return (MidiEvent*)-1;  // DEBUG: eigentlich nächsten Event lesen
                        case 0x59:  // Key Signature
                              {
                              track->scale.set(buffer[0]);
                              track->scale.setMajorMinor(buffer[1]);
                              delete event;
                              delete[] buffer;
                              }
                              return (MidiEvent*)-1;  // DEBUG: eigentlich nächsten Event lesen
                        case 0x6:   // Marker
                              song->addMarker(QString((const char*)(buffer)), event->posTick(), false);
                              delete event;
                              delete[] buffer;
                              return (MidiEvent*)-1;  // DEBUG: eigentlich nächsten Event lesen

                        case 0:     // Sequence Number
                        case 1:     // Text Event
                        case 2:     // Copyright
                        case 4:     // Instrument Name
                        case 5:     // Lyric
                        case 7:     // Cue Point
                        case 0x20:  // Channel Prefix
                        case 0x54:  // SMPTE Offset
                        case 0x74:  // Seq. Spezific 1
                        case 0x7f:  // Seq. Spezific 1
rest:
                        default:
                              event->setType(MidiEvent::Meta);
                              {
                              EvData* ed = new EvData(buffer, len+1);
                              ed->refCount = 1;
                              event->setEvData(ed);
                              }
                              event->setA(type);
                              break;
                        }
                  break;
            default:
                  if (me & 0x80) {                     // status byte
                        lastchan = me & 0x0f;
                        status   = me & 0xf0;
                        sstatus  = status;
                        if (read(&a, 1)) {
                              printf("readEvent: error 9\n");
                              delete event;
                              return (MidiEvent*)-2;
                              }
                        a &= 0x7F;
                        }
                  else {
                        if (status == -1) {
                              printf("readEvent: no running status, read 0x%02x\n", me);
                              printf("sstatus ist 0x%02x\n", sstatus);
                              if (sstatus == -1) {
                                    delete event;
                                    return (MidiEvent*)-1;
                                    }
                              status = sstatus;
                              }
                        a = me;
                        }
                  b = 0;
                  event->setA(a);
                  switch (status & 0xf0) {
                        case 0x80:        // Taste loslassen
                        case 0x90:        // Taste anschlagen
                        case 0xa0:        // Polyphoner Aftertouch
                        case 0xb0:        // Controller
                        case 0xe0:        // Pitch Bender
                              if (read(&b, 1)) {
                                    printf("readEvent: error 15\n");
                                    delete event;
                                    return (MidiEvent*)-2;
                                    }
                              if ((status & 0xf0) == 0xe0)
                                    event->setA(((((b & 0x80) ? 0 : b) << 7) + a) - 8192);
                              else
                                    event->setB(b & 0x80 ? 0 : b);
                              break;
                        case 0xc0:        // Program Change
                        case 0xd0:        // Channel Aftertouch
                              break;
                        default:          // f1 f2 f3 f4 f5 f6 f7 f8 f9
                              printf("BAD STATUS 0x%02x, me 0x%02x\n", status, me);
                              delete event;
                              return (MidiEvent*)-2;
                        }
                  switch (status & 0xf0) {
                        case 0x80:        // Taste loslassen
                              event->setType(MidiEvent::NoteOff);
                              break;
                        case 0x90:        // Taste anschlagen
                              event->setType(event->velo() == 0 ? MidiEvent::NoteOff : MidiEvent::Note);
                              break;
                        case 0xa0:        // Polyphoner Aftertouch
                              event->setType(MidiEvent::PAfter);
                              break;
                        case 0xb0:        // Controller
                              event->setType(MidiEvent::Ctrl7);
                              break;
                        case 0xc0:        // Program Change
                              event->setType(MidiEvent::Program);
                              break;
                        case 0xd0:        // Channel Aftertouch
                              event->setType(MidiEvent::CAfter);
                              break;
                        case 0xe0:        // Pitch Bender
                              event->setType(MidiEvent::Pitch);
                              break;
                        default:
                              assert(false);
                              break;
                        }
                  if ((a & 0x80) || (b & 0x80)) {
                        printf("8'tes Bit in Daten(%02x %02x): tick %d read 0x%02x  status:0x%02x channel:0x%02x\n",
                          a & 0xff, b & 0xff, click, me, status, lastchan);
                        printf("readEvent: error 16\n");
                        if (b & 0x80) {
                              // Try to fix: interpret as channel byte
                              lastchan = b & 0x0f;
                              status   = b & 0xf0;
                              sstatus  = status;
                              return event;
                              }
                        delete event;
                        return (MidiEvent*)-1;
                        }
                  break;
            }
      return event;
      }

//---------------------------------------------------------
//   writeTrack
//---------------------------------------------------------

bool MidiFile::writeTrack(MidiTrack* track)
      {
      write("MTrk", 4);
      int lenpos = ftell(fp);
      writeLong(0);                 // dummy len

      int port    = track->outPort();
      int channel = track->outChannel();

      EventList* events = new EventList;

      //-------------------------------------------------------------
      //    Setup Measure:
      //    Trackname, Comment, Instrument, Volume, Pan
      //
      if (!track->name().isEmpty()) {
            const char* name = track->name().latin1();
            int len = strlen(name);
            MidiEvent* ev = new MidiEvent(0, MidiEvent::Meta, len, (unsigned char*)name);
            ev->setA(0x3);    // Meta Sequence/Track Name
            events->add(ev);
            }
      if (!track->comment().isEmpty()) {
            const char* comment = track->comment().latin1();
            int len = strlen(comment);
            MidiEvent* ev = new MidiEvent(0, MidiEvent::Meta, len, (unsigned char*)comment);
            ev->setA(0x0f);   // Meta Text
            events->add(ev);
            }

      ChannelState* state = midiPorts[port].iState(track->outChannel());    // Initial State

      int tick = 300;
      if (state->controller[CTRL_HBANK] != -1) {
            events->add(new MidiEvent(tick, MidiEvent::Ctrl7, CTRL_HBANK,
               state->controller[CTRL_HBANK], 0, 0));
            tick += 10;
            }
      if (state->controller[CTRL_LBANK] != -1) {
            events->add(new MidiEvent(tick, MidiEvent::Ctrl7, CTRL_LBANK,
               state->controller[CTRL_LBANK], 0, 0));
            tick += 10;
            }
      if (state->program != -1) {
            events->add(new MidiEvent(tick, MidiEvent::Program, state->program,
               0, 0, 0));
            tick += 10;
            }
      if (state->controller[CTRL_VOLUME] != -1) {
            events->add(new MidiEvent(tick, MidiEvent::Ctrl7, CTRL_VOLUME,
               state->controller[CTRL_VOLUME], 0, 0));
            tick += 10;
            }
      if (state->controller[CTRL_PANPOT] != -1) {
            events->add(new MidiEvent(tick, MidiEvent::Ctrl7, CTRL_PANPOT,
               state->controller[CTRL_PANPOT], 0, 0));
            tick += 10;
            }

      //---------------------------------------------------
      //    generate Note Off Events
      //

      PartList* parts = track->parts();
      for (iPart p = parts->begin(); p != parts->end(); ++p) {
            MidiPart* part    = (MidiPart*) (p->second);
            EventList* evlist = part->events();
            for (iEvent i = evlist->begin(); i != evlist->end(); ++i) {
                  MidiEvent* ev    = (MidiEvent*)i->second;
                  int tick         = ev->posTick() + part->posTick();
                  MidiEvent* event = new MidiEvent(*ev);
                  event->setPosTick(tick);

                  if (event->isNote()) {
                        if (event->velo() == 0) {
                              printf("Warning: midi note has velocity 0, (ignored)\n");
                              continue;
                              }
                        int len = event->lenTick();

                        //---------------------------------------
                        //   apply trackinfo values
                        //---------------------------------------

                        if (track->transposition
                           || track->velocity
                           || track->compression != 100
                           || track->len != 100) {
                              int pitch = event->pitch();
                              int velo  = event->velo();
                              pitch += track->transposition;
                              if (pitch > 127)
                                    pitch = 127;
                              if (pitch < 0)
                                    pitch = 0;

                              velo += track->velocity;
                              velo = (velo * track->compression) / 100;
                              if (velo > 127)
                                    velo = 127;
                              if (velo < 1)           // no off event
                                    velo = 1;

                              len = (len *  track->len) / 100;
                              event->setPitch(pitch);
                              event->setVelo(velo);
                              }
                        events->add(event);
                        MidiEvent* off = new MidiEvent(*event);
                        if (len <= 0)
                              len = 1;
                        off->setPosTick(tick + len);
                        off->setVelo(0);
                        if (off->veloOff()) {
                              // this is a "real" note off event
                              off->setType(MidiEvent::NoteOff);
                              off->setVelo(off->veloOff());
                              }
                        events->add(off);
                        }
                  else
                        events->add(event);
                  }
            }

      //---------------------------------------------------
      //    only first midi track contains
      //          - Track Marker
      //          - copyright
      //          - time signature
      //          - tempo map
      //          - GM/GS/XG Initialization

      for (iTrack i = song->tracks()->begin(); i != song->tracks()->end(); ++i) {
            MidiTrack* t = dynamic_cast<MidiTrack*>(*i);
            if (t == 0)
                  continue;
            if (track != t)
                  break;

            //---------------------------------------------------
            //    Write Track Marker
            //
            MarkerList* ml = song->marker();
            for (ciMarker m = ml->begin(); m != ml->end(); ++m) {
                  const char* name = m->second.name().latin1();
                  int len = strlen(name);
                  MidiEvent* ev = new MidiEvent(
                     m->first, MidiEvent::Meta,
                     len, (unsigned char*)name);
                  ev->setA(0x6);
                  events->add(ev);
                  }

            //---------------------------------------------------
            //    Write Copyright
            //
            const char* copyright = song->copyright().latin1();
            if (copyright && *copyright) {
                  int len = strlen(copyright);
                  MidiEvent* ev = new MidiEvent(0, MidiEvent::Meta,
                     len, (unsigned char*)copyright);
                  ev->setA(0x2);
                  events->add(ev);
                  }
            //---------------------------------------------------
            //    Write Songtype SYSEX: GM/GS/XG
            //
            switch(song->mtype()) {
                  case MT_GM:
                        events->add(new MidiEvent(0, MidiEvent::Sysex, gmOnMsgLen, gmOnMsg));
                        break;
                  case MT_GS:
                        events->add(new MidiEvent(0, MidiEvent::Sysex, gmOnMsgLen, gmOnMsg));
                        events->add(new MidiEvent(250, MidiEvent::Sysex, gsOnMsgLen, gsOnMsg));
                        break;
                  case MT_XG:
                        events->add(new MidiEvent(0, MidiEvent::Sysex, gmOnMsgLen, gmOnMsg));
                        events->add(new MidiEvent(250, MidiEvent::Sysex, xgOnMsgLen, xgOnMsg));
                        break;
                  case MT_UNKNOWN:
                        break;
                  }
            //---------------------------------------------------
            //    Write Tempomap
            //
            TempoList* tl = &tempomap;
            for (ciTEvent e = tl->begin(); e != tl->end(); ++e) {
                  TEvent* event = e->second;
                  unsigned char data[3];
                  int tempo = event->tempo;
                  data[2] = tempo & 0xff;
                  data[1] = (tempo >> 8) & 0xff;
                  data[0] = (tempo >> 16) & 0xff;
                  MidiEvent* ev = new MidiEvent(
                     event->tick, MidiEvent::Meta, 3, data);
                  ev->setA(0x51);
                  events->add(ev);
                  }
            //---------------------------------------------------
            //    Write Signatures
            //
            const SigList* sl = &sigmap;
            for (ciSigEvent e = sl->begin(); e != sl->end(); ++e) {
                  SigEvent* event = e->second;
                  unsigned char data[2];
                  data[0] = event->z;
                  switch(event->n) {
                        case 1:  data[1] = 0; break;
                        case 2:  data[1] = 1; break;
                        case 4:  data[1] = 2; break;
                        case 8:  data[1] = 3; break;
                        case 16: data[1] = 4; break;
                        case 32: data[1] = 5; break;
                        case 64: data[1] = 6; break;
                        default:
                              fprintf(stderr, "falsche Signatur; nenner %d\n", event->n);
                              break;
                        }
                  MidiEvent* ev = new MidiEvent(
                     event->tick, MidiEvent::Meta, 2, data);
                  ev->setA(0x58);
                  events->add(ev);
                  printf("write meta %d/%d\n", data[0], data[1]);
                  }
            }

      //---------------------------------------------------
      //    write Events to file
      //

      status = -1;
      tick = 0;
      for (iEvent i = events->begin(); i != events->end(); ++i) {
            putvl(((i->first-tick) * midiDivision + ::division/2)/::division);
            tick = i->first;
            writeEvent(channel, (MidiEvent*)i->second);
            }
      delete events;

      //---------------------------------------------------
      //    write "End Of Track" Meta
      //    write Track Len
      //

      putvl(0);
      put(0xff);        // Meta
      put(0x2f);        // EOT
      putvl(0);         // len 0
      int endpos = ftell(fp);
      fseek(fp, lenpos, SEEK_SET);
      writeLong(endpos-lenpos-4);   // tracklen
      fseek(fp, endpos, SEEK_SET);
      return false;
      }

//---------------------------------------------------------
//   save
//---------------------------------------------------------

bool MidiFile::save()
      {
      division = midiDivision;
      write("MThd", 4);
      writeLong(6);                 // header len
      writeShort(1);                // format
      TrackList* tracks = song->tracks();
      writeShort(tracks->size());
      writeShort(division);

      for (iTrack i = tracks->begin(); i != tracks->end(); ++i) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*i);
            if (track == 0)
                  continue;
            writeTrack(track);
            }
      return false;
      }

//---------------------------------------------------------
//   processTrack
//---------------------------------------------------------

void MidiFile::processTrack1(MidiTrack* track)
      {
      if (track->outChannel() == 9 && song->mtype() != MT_UNKNOWN)
            track->setType(Track::DRUM);

      MidiPort* port = &midiPorts[track->outPort()];
      int channel    = track->outChannel();
      int trackno    = song->tracks()->index(track);
      bool karaoke   = song->karaokeFlag();

      //---------------------------------------------------
      //    get initial controller state
      //---------------------------------------------------

      int karaokeState = 0;
      EventList* tevents = track->events();

      for (iEvent i = tevents->begin(); i != tevents->end();) {
            iEvent ie = i;
            ++ie;
            MidiEvent* ev  = (MidiEvent*)i->second;
            switch(ev->type()) {
                  case MidiEvent::Note:
                        break;
                  case MidiEvent::Program:
                        if (port->iState(channel)->program == -1) {
                              port->iState(channel)->program = ev->program();
                              tevents->erase(i);
                              }
                        break;
                  case MidiEvent::Ctrl7:
                        switch(ev->cntrl()) {
                              case CTRL_LDATA:
                              case CTRL_HDATA:
                              case CTRL_LNRPN:
                              case CTRL_HNRPN:
                              case CTRL_LRPN:
                              case CTRL_HRPN:
                                    break;
                              default:
                                    if (port->iState(channel)->controller[ev->cntrl()] == -1) {
                                          port->iState(channel)->controller[ev->cntrl()] = ev->cntrlVal();
                                          // add to controller list
                                          addMidiController(MidiController::Controller7, ev->cntrl(), 0);
                                          tevents->erase(i);
                                          }
                                    break;
                              }
                        break;
                  case MidiEvent::Meta:
                        switch(ev->dataA()) {
                              case 0x1:        // text event
                                    if (trackno == 1 && memcmp(ev->data(), "@KMID", 5) == 0) {
                                          karaoke = true;
                                          song->setKaraokeFlag(true);
                                          song->setKaraokeChannel(channel);
                                          tevents->erase(i);
                                          }
                                    if ((trackno == 2) && karaoke) {
                                          char* p = (char*)(ev->data());
//                                          printKaraoke(p);
                                          if (*p == '@') {
                                                switch(karaokeState) {
                                                      case 0:     // expect language
                                                            ++karaokeState;
                                                            break;
                                                      case 1:     // expect titel
                                                            ++karaokeState;  // language
                                                            song->setName(QString(p+2));
                                                            break;
                                                      case 2:     // expect interpret
                                                            ++karaokeState;
                                                            song->setKomponist1(QString(p+2));
                                                            break;
                                                      default:
                                                            printf("unexpected @ in karaoke\n");
                                                            break;
                                                      }
                                                }
                                          else if (karaokeState == 3) {
                                                // text sammeln
                                                song->karaoke()->add(ev);
                                                }
                                          tevents->erase(i);
                                          }
                                    break;
                              case 0x2:         // Copyright
                                    song->setCopyright(QString((const char*)(ev->data())));
                                    tevents->erase(i);
                                    break;

                              default:
                                    break;
                              }
                        break;
                  default:
                        break;
                  }
            i = ie;
            }

      //---------------------------------------------------
      //    NoteOff Events auflösen
      //---------------------------------------------------

      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            MidiEvent* event = (MidiEvent*)i->second;
            if (event->isNote())
                  event->setLenTick(-1);
            }

      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            MidiEvent* ev  = (MidiEvent*)i->second;
            if (ev->isNote()) {
                  if (ev->isNoteOff()) {
                        printf("NOTE OFF without Note ON\n");
                        }
                  iEvent k;
                  for (k = tevents->lower_bound(ev->posTick()); k != tevents->end(); ++k) {
                        MidiEvent* event = (MidiEvent*)k->second;
                        if (ev->isNoteOff(event)) {
                              int t = k->first - i->first;
                              if (t <= 0) {
                                    if (debugMsg) {
                                          printf("Note len is (%d-%d)=%d, set to 1\n",
                                            k->first, i->first, k->first - i->first);
                                          ev->dump();
                                          event->dump();
                                          }
                                    t = 1;
                                    }
                              ev->setLenTick(t);
                              ev->setVeloOff(event->veloOff());
                              break;
                              }
                        }
                  if (k == tevents->end()) {
                        printf("-kein note-off! %d pitch %d velo %d\n",
                           i->first, ev->pitch(), ev->velo());
                        //
                        // ton am Taktende abschalten
                        //
                        int endTick = song->roundUpBar(ev->posTick()+1);
                        ev->setLenTick(endTick-ev->posTick());
                        }
                  else {
                        tevents->erase(k);
                        }
                  }
            }

// DEBUG: any note offs left?

      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            MidiEvent* ev  = (MidiEvent*)i->second;
            if (ev->isNoteOff()) {
                  printf("+extra note-off! %d pitch %d velo %d\n",
                           i->first, ev->pitch(), ev->velo());
//                  ev->dump();
                  }
            }
      }

//---------------------------------------------------------
//   processTrack
//    divide events into parts
//---------------------------------------------------------

void MidiFile::processTrack2(MidiTrack* track)
      {
      EventList* tevents = track->events();
      if (tevents->empty())
            return;

      //---------------------------------------------------
      //    Parts ermitteln
      //    die Midi-Spuren werden in Parts aufgebrochen;
      //    ein neuer Part wird bei einer Lücke von einem
      //    Takt gebildet; die Länge wird jeweils auf
      //    Takte aufgerundet und aligned
      //---------------------------------------------------

      PartList* pl = track->parts();

      int lastTick = 0;
      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            Event* event = i->second;
            int epos = event->posTick() + event->lenTick();
            if (epos > lastTick)
                  lastTick = epos;
            }

      int len = song->roundUpBar(lastTick+1);
      int bar2, beat, tick;
      sigmap.tickValues(len, &bar2, &beat, &tick);

      QString partname = track->name();

      int lastOff = 0;
      int st = -1;      // start tick current part
      int x1 = 0;       // start tick current measure
      int x2 = 0;       // end tick current measure

      for (int bar = 0; bar < bar2; ++bar, x1 = x2) {
            x2 = sigmap.bar2tick(bar+1, 0, 0);
            if (lastOff > x2) {
                  // this measure is busy!
                  continue;
                  }
            iEvent i1 = tevents->lower_bound(x1);
            iEvent i2 = tevents->lower_bound(x2);

            if (i1 == i2) {   // empty?
                  if (st != -1) {
                        MidiPart* part = new MidiPart(track);
                        part->setPosTick(st);
                        part->setLenTick(x1-st);
                        part->setName(partname);
                        pl->add(part);
                        st = -1;
                        }
                  }
            else {
                  if (st == -1)
                        st = x1;    // begin new  part
                  //HACK:
                  //lastOff:
                  for (iEvent i = i1; i != i2; ++i) {
                        MidiEvent* event = (MidiEvent*)i->second;
                        if (event->type() == MidiEvent::Note) {
                              int off = event->posTick() + event->lenTick();
                              if (off > lastOff)
                                    lastOff = off;
                              }
                        }
                  }
            }
      if (st != -1) {
            MidiPart* part = new MidiPart(track);
            part->setPosTick(st);
            part->setLenTick(x2-st);
            part->setName(partname);
            pl->add(part);
            }

      //-------------------------------------------------------------
      //    assign events to parts
      //-------------------------------------------------------------

      for (iPart p = pl->begin(); p != pl->end(); ++p) {
            MidiPart* part = (MidiPart*)(p->second);
            int stick = part->posTick();
            int etick = part->posTick() + part->lenTick();
            iEvent r1 = tevents->lower_bound(stick);
            iEvent r2 = tevents->lower_bound(etick);
            int startTick = part->posTick();

            EventList* el = part->events();
            for (iEvent i = r1; i != r2; ++i) {
                  MidiEvent* ev = (MidiEvent*)(i->second);
                  int ntick = ev->posTick() - startTick;
                  ev->setPosTick(ntick);
                  el->add(ev, ntick);
                  }
            tevents->erase(r1, r2);
            }

      if (tevents->size())
            printf("-----------events left: %d\n", tevents->size());
      for (iEvent i = tevents->begin(); i != tevents->end(); ++i) {
            printf("%d===\n", i->first);
            i->second->dump();
            }
      // all events should be processed:
      assert(tevents->empty());
      }

//---------------------------------------------------------
//   readMidi
//    returns true on error
//---------------------------------------------------------

bool MidiFile::read()
      {
      int i;
      char tmp[4];

      read(tmp, 4);
      int len = readLong();
      if (memcmp(tmp, "MThd", 4) || len < 6) {
            fprintf(stderr, "bad Midifile: MThd expected\n");
            return true;
            }

      int format = readShort();
      ntracks    = readShort();
      division   = readShort();

      if (division < 0)
            division = (-(division/256)) * (division & 0xff);
      if (len > 6)
            skip(len-6); // skip the excess

      // reset midi ports to "unknown state"
      for (int i = 0; i < MIDI_PORTS; ++i) {
            for (int k = 0; k < MIDI_CHANNELS; ++k) {
                  midiPorts[i].resetIstate(k, false);
                  }
            }
      switch (format) {
            case 0:
                  if (readTrack(true)) {
                        printf("read midifile failed at pos %d: %s\n", curPos, strerror(error));
                        return true;
                        }
                  break;
            case 1:
                  for (i = 0; i < ntracks; i++) {
                        if (readTrack(false)) {
                              printf("read midifile failed at pos %d: %s\n", curPos, strerror(error));
                              return true;
                              }
                        }
                  break;
            default:
            case 2:
                  printf("midi fileformat %d not implemented!\n", format);
                  return true;
            }

      for (int i = 0; i < 16; ++i)
            midiPorts[0].resetIstate(i, false);

      TrackList* tl = song->tracks();
      for (iTrack t = tl->begin(); t != tl->end(); ++t) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
            if (track == 0)
                  continue;
            processTrack1(track);
            }

      //-------------------------------------------------------------
      // calculate tick offset
      //
      int tickOffset = MAXINT;
      for (iTrack t = tl->begin(); t != tl->end(); ++t) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
            if (track == 0)
                  continue;
            EventList* events = track->events();
            if (!events->empty()) {
                  MidiEvent* event = (MidiEvent*)events->begin()->second;
                  int tick = event->posTick();
                  if (tick < tickOffset)
                        tickOffset = tick;
                  }
            }
//      printf("OFFSET %d %d\n", tickOffset, tickOffset/::division);

      for (iTrack t = tl->begin(); t != tl->end(); ++t) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
            if (track == 0)
                  continue;
            processTrack2(track);
            }

      if (song->karaokeFlag()) {
            for (iTrack t = tl->begin(); t != tl->end(); ++t) {
                  MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
                  if (track == 0)
                        continue;
                  if (track->outChannel() == song->karaokeChannel()) {
                        addKaraoke(track);
                        }
                  }
            }
      return false;
      }

//---------------------------------------------------------
//   addKaraoke
//---------------------------------------------------------

void MidiFile::addKaraoke(MidiTrack*)
      {
#if 0
      EventList* el = song->karaoke();
      for (iEvent i = el->begin(); i != el->end(); ++i) {
            MidiEvent* event = (MidiEvent*)i->second;
            char* p = (char*)(event->data());
            if (*p == '\\' || *p ==  '/')
                  ++p;
            while (*p == ' ')
                  ++p;
            PartList* pl = track->parts();
            for (iPart ip = pl->begin(); ip != pl->end(); ++ip) {
                  EventList* nel = ((MidiPart*)*ip)->events();
                  }
            }
#endif
      }

