//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midi.cpp,v 1.43.2.4 2004/12/12 19:54:29 spamatica Exp $
//
//  (C) Copyright 1999/2004 Werner Schweer (ws@seh.de)
//=========================================================

#include <cmath>
#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"
#include "audio.h"
#include "mididev.h"
#include "driver/alsamidi.h"
#include "wave.h"
#include "synth.h"
#include "sync.h"
#include "midiseq.h"
#include "gconfig.h"
#include "ticksynth.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;
      }

//---------------------------------------------------------
//   buildMidiEventList
//    TODO:
//      parse data increment/decrement controller
//      NRPN/RPN  fine/course data 7/14 Bit
//          must we set datah/datal to zero after change
//          of NRPN/RPN register?
//      generally: how to handle incomplete messages
//---------------------------------------------------------

void buildMidiEventList(EventList* del, const MPEventList* el, MidiTrack* track,
   int div, bool addSysexMeta)
      {
      int hbank    = 0xff;
      int lbank    = 0xff;
      int rpnh     = -1;
      int rpnl     = -1;
      int datah    = 0;
      int datal    = 0;
      int dataType = 0;   // 0 : disabled, 0x20000 : rpn, 0x30000 : nrpn

      EventList mel;

      for (iMPEvent i = el->begin(); i != el->end(); ++i) {
            MidiPlayEvent ev = *i;
            if (!addSysexMeta && (ev.type() == ME_SYSEX || ev.type() == ME_META))
                  continue;
            if (!(ev.type() == ME_SYSEX || ev.type() == ME_META
               || ((ev.channel() == track->outChannel()) && (ev.port() == track->outPort()))))
                  continue;
            int tick = ev.time();
            Event e;
            switch(ev.type()) {
                  case ME_NOTEON:
                        e.setType(Note);

                        if (track->type() == Track::DRUM) {
                              int instr = drumInmap[ev.dataA()];
                              e.setPitch(instr);
                              }
                        else
                              e.setPitch(ev.dataA());

                        e.setVelo(ev.dataB());
                        e.setLenTick(0);
                        break;
                  case ME_NOTEOFF:
                        e.setType(Note);
                        if (track->type() == Track::DRUM) {
                              int instr = drumInmap[ev.dataA()];
                              e.setPitch(instr);
                              }
                        else
                              e.setPitch(ev.dataA());
                        e.setVelo(0);
                        e.setVeloOff(ev.dataB());
                        e.setLenTick(0);
                        break;
                  case ME_POLYAFTER:
                        e.setType(PAfter);
                        e.setA(ev.dataA());
                        e.setB(ev.dataB());
                        break;
                  case ME_CONTROLLER:
                        {
                        int val = ev.dataB();
                        switch(ev.dataA()) {
                              case CTRL_HBANK:
                                    hbank = val;
                                    break;

                              case CTRL_LBANK:
                                    lbank = val;
                                    break;

                              case CTRL_HDATA:
                                    datah = val;
                                    // check if a CTRL_LDATA follows
                                    // e.g. wie have a 14 bit controller:
                                    {
                                    iMPEvent ii = i;
                                    ++ii;
                                    bool found = false;
                                    for (; ii != el->end(); ++ii) {
                                          MidiPlayEvent ev = *ii;
                                          if (ev.type() == ME_CONTROLLER) {
                                                if (ev.dataA() == CTRL_LDATA) {
                                                      // handle later
                                                      found = true;
                                                      }
                                                break;
                                                }
                                          }
                                    if (!found) {
                                          if (rpnh == -1 || rpnl == -1) {
                                                printf("parameter number not defined, data 0x%x\n", datah);
                                                }
                                          else {
                                                int ctrl = dataType | (rpnh << 8) | rpnl;
                                                e.setType(Controller);
                                                e.setA(ctrl);
                                                e.setB(datah);
                                                }
                                          }
                                    }
                                    break;

                              case CTRL_LDATA:
                                    datal = val;

                                    if (rpnh == -1 || rpnl == -1) {
                                          printf("parameter number not defined, data 0x%x 0x%x, tick %d, channel %d\n",
                                             datah, datal, tick, track->outChannel());
                                          break;
                                          }
                                    // assume that the sequence is always
                                    //    CTRL_HDATA - CTRL_LDATA
                                    // eg. that LDATA is always send last

                                    e.setType(Controller);
                                    // 14 Bit RPN/NRPN
                                    e.setA((dataType+0x30000) | (rpnh << 8) | rpnl);
                                    e.setB((datah << 7) | datal);
                                    break;

                              case CTRL_HNRPN:
                                    rpnh = val;
                                    dataType = 0x30000;
                                    break;

                              case CTRL_LNRPN:
                                    rpnl = val;
                                    dataType = 0x30000;
                                    break;

                              case CTRL_HRPN:
                                    rpnh     = val;
                                    dataType = 0x20000;
                                    break;

                              case CTRL_LRPN:
                                    rpnl     = val;
                                    dataType = 0x20000;
                                    break;

                              default:
                                    e.setType(Controller);
                                    e.setA(ev.dataA());
                                    e.setB(val);
                                    break;
                              }
                        }
                        break;

                  case ME_PROGRAM:
                        e.setType(Controller);
                        e.setA(CTRL_PROGRAM);
                        e.setB((hbank << 16) | (lbank << 8) | ev.dataA());
                        break;

                  case ME_AFTERTOUCH:
                        e.setType(CAfter);
                        e.setA(ev.dataA());
                        break;

                  case ME_PITCHBEND:
                        e.setType(Controller);
                        e.setA(CTRL_PITCH);
                        e.setB(ev.dataA());
                        break;

                  case ME_SYSEX:
                        e.setType(Sysex);
                        e.setData(ev.data(), ev.len());
                        break;

                  case ME_META:
                        {
                        const unsigned char* data = ev.data();
                        switch (ev.dataA()) {
                              case 0x01: // Text
                                    if (track->comment().isEmpty())
                                          track->setComment(QString((const char*)data));
                                    else
                                          track->setComment(track->comment() + "\n" + QString((const char*)data));
                                    break;
                              case 0x03: // Sequence-/TrackName
                                    track->setName(QString((char*)data));
                                    break;
                              case 0x6:   // Marker
                                    {
                                    int ltick  = (tick * config.division + div/2) / div;
                                    song->addMarker(QString((const char*)(data)), ltick, false);
                                    }
                                    break;
                              case 0x5:   // Lyrics
                              case 0x8:   // text
                              case 0x9:
                              case 0xa:
                                    break;

                              case 0x0f:        // Track Comment
                                    track->setComment(QString((char*)data));
                                    break;
                              case 0x51:        // Tempo
                                    {
                                    int tempo = data[2] + (data[1] << 8) + (data[0] <<16);
                                    int ltick  = (tick * config.division + div/2) / div;
                                    tempomap.addTempo(ltick, tempo);
                                    }
                                    break;
                              case 0x58:        // Time Signature
                                    {
                                    int timesig_z = data[0];
                                    int n = data[1];
                                    int timesig_n = 1;
                                    for (int i = 0; i < n; i++)
                                          timesig_n *= 2;
                                    int ltick  = (tick * config.division + div/2) / div;
                                    sigmap.add(ltick, timesig_z, timesig_n);
                                    }
                                    break;
                              case 0x59:  // Key Signature
                                    // track->scale.set(data[0]);
                                    // track->scale.setMajorMinor(data[1]);
                                    break;
                              default:
                                    printf("unknown Meta 0x%x %d\n", ev.dataA(), ev.dataA());
                              }
                        }
                        break;
                  }   // switch(ev.type()
            if (!e.empty()) {
                  e.setTick(tick);
                  mel.add(e);
                  }
            }  // i != el->end()

      //---------------------------------------------------
      //    resolve NoteOff events
      //---------------------------------------------------

//      for (iEvent i = mel.begin(); i != mel.end(); ++i) {
//            Event event = i->second;
//            if (event.isNote())
//                  event.setLenTick(0);
//            }

      for (iEvent i = mel.begin(); i != mel.end(); ++i) {
            Event ev  = i->second;
            if (ev.isNote()) {
                  if (ev.isNoteOff()) {
                        iEvent k;
                        bool found = false;
                        for (k = i; k != mel.end(); ++k) {
                              Event event = k->second;
                              if (event.tick() > ev.tick())
                                    break;
                              if (event.isNoteOff(ev)) {
                                    ev.setLenTick(1);
                                    ev.setVelo(event.velo());
                                    ev.setVeloOff(0);
                                    found = true;
                                    break;
                                    }
                              }
                        if (!found) {
                              printf("NOTE OFF without Note ON tick %d type %d  %d %d\n",
                                    ev.tick(), ev.type(), ev.pitch(), ev.velo());
                              }
                        else {
                              mel.erase(k);
                              continue;
                              }
                        }
                  iEvent k;
                  for (k = mel.lower_bound(ev.tick()); k != mel.end(); ++k) {
                        Event event = 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 == mel.end()) {
                        printf("-no note-off! %d pitch %d velo %d\n",
                           ev.tick(), ev.pitch(), ev.velo());
                        //
                        // switch off at end of measure
                        //
                        int endTick = song->roundUpBar(ev.tick()+1);
                        ev.setLenTick(endTick-ev.tick());
                        }
                  else {
                        mel.erase(k);
                        }
                  }
            }

// DEBUG: any note offs left?

      for (iEvent i = mel.begin(); i != mel.end(); ++i) {
            Event ev  = i->second;
            if (ev.isNoteOff()) {
                  printf("+extra note-off! %d pitch %d velo %d\n",
                           i->first, ev.pitch(), ev.velo());
//                  ev.dump();
                  }
            }
      for (iEvent i = mel.begin(); i != mel.end(); ++i) {
            Event ev  = i->second;
            if (ev.isNoteOff()) {
                  printf("+extra note-off! %d pitch %d velo %d\n",
                           i->first, ev.pitch(), ev.velo());
//                  ev.dump();
                  continue;
                  }
            int tick  = (ev.tick() * config.division + div/2) / div;
            if (ev.isNote()) {
                  int lenTick = (ev.lenTick() * config.division + div/2) / div;
                  ev.setLenTick(lenTick);
                  }
            ev.setTick(tick);
            del->add(ev);
            }
      }

//---------------------------------------------------------
//   midiPortsChanged
//---------------------------------------------------------

void Audio::midiPortsChanged()
      {
      write(sigFd, "P", 1);
      }

//---------------------------------------------------------
//   sendLocalOff
//---------------------------------------------------------

void Audio::sendLocalOff()
      {
      for (int k = 0; k < MIDI_PORTS; ++k) {
            for (int i = 0; i < MIDI_CHANNELS; ++i)
                  midiPorts[k].sendEvent(MidiPlayEvent(0, k, i, ME_CONTROLLER, CTRL_LOCAL_OFF, 0));
            }
      }

//---------------------------------------------------------
//   panic
//---------------------------------------------------------

void Audio::panic()
      {
      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* port = &midiPorts[i];
            if (port == 0)   // ??
                  continue;
            for (int chan = 0; chan < MIDI_CHANNELS; ++chan) {
                  port->sendEvent(MidiPlayEvent(0, i, chan, ME_CONTROLLER, CTRL_ALL_SOUNDS_OFF, 0));
                  port->sendEvent(MidiPlayEvent(0, i, chan, ME_CONTROLLER, CTRL_RESET_ALL_CTRL, 0));
                  }
            }
      }

//---------------------------------------------------------
//   initDevices
//    - called on seek to position 0
//    - called from arranger pulldown menu
//---------------------------------------------------------

void Audio::initDevices()
      {
      //
      // mark all used ports
      //
      bool activePorts[MIDI_PORTS];
      for (int i = 0; i < MIDI_PORTS; ++i)
            activePorts[i] = false;

      MidiTrackList* tracks = song->midis();
      for (iMidiTrack it = tracks->begin(); it != tracks->end(); ++it) {
            MidiTrack* track = *it;
            activePorts[track->outPort()] = true;
            }
      if (song->click())
            activePorts[clickPort] = true;

      //
      // test for explicit instrument initialization
      //

      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activePorts[i])
                  continue;

            MidiPort* port        = &midiPorts[i];
            MidiInstrument* instr = port->instrument();
            MidiDevice* md        = port->device();

            if (instr && md) {
                  EventList* events = instr->midiInit();
                  if (events->empty())
                        continue;
                  for (iEvent ie = events->begin(); ie != events->end(); ++ie) {
                        MidiPlayEvent ev(0, i, 0, ie->second);
                        md->putEvent(ev);
                        }
                  activePorts[i] = false;  // no standard initialization
                  }
            }
      //
      // damit Midi-Devices, die mehrere Ports besitzen, wie z.B.
      // das Korg NS5R, nicht mehrmals zwischen GM und XG/GS hin und
      // hergeschaltet werden, wird zunächst auf allen Ports GM
      // initialisiert, und dann erst XG/GS
      //
      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activePorts[i])
                  continue;
            MidiPort* port = &midiPorts[i];
            switch(song->mtype()) {
                  case MT_GS:
                  case MT_UNKNOWN:
                        break;
                  case MT_GM:
                  case MT_XG:
                        port->sendGmOn();
                        break;
                  }
            }
      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (!activePorts[i])
                  continue;
            MidiPort* port = &midiPorts[i];
            switch(song->mtype()) {
                  case MT_UNKNOWN:
                        break;
                  case MT_GM:
                        break;
                  case MT_GS:
                        port->sendGsOn();
                        break;
                  case MT_XG:
                        port->sendXgOn();
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   collectEvents
//    collect events for next audio segment
//---------------------------------------------------------

void Audio::collectEvents(MidiTrack* track, int cts, int nts)
      {
      int port    = track->outPort();
      int channel = track->outChannel();
      int defaultPort = port;

      MidiDevice* md          = midiPorts[port].device();
      MPEventList* playEvents = md->playEvents();
      MPEventList* stuckNotes = md->stuckNotes();

      PartList* pl = track->parts();
      for (iPart p = pl->begin(); p != pl->end(); ++p) {
            MidiPart* part = (MidiPart*)(p->second);
            // dont play muted parts
            if (part->mute())
                  continue;
            EventList* events = part->events();
            unsigned partTick = part->tick();
            int delay         = track->delay;

            if (cts > nts) {
                  printf("processMidi: FATAL: cur > next %d > %d\n",
                     cts, nts);
                  return;
                  }
            unsigned offset = delay + partTick;
            if (offset > nts)
                  continue;
            unsigned stick = (offset > cts) ? 0 : cts - offset;
            unsigned etick = nts - offset;
            iEvent ie   = events->lower_bound(stick);
            iEvent iend = events->lower_bound(etick);

            for (; ie != iend; ++ie) {
                  Event ev = ie->second;
                  port = defaultPort; //Reset each loop
                  //
                  //  don´t play any meta events
                  //
                  if (ev.type() == Meta)
                        continue;
                  if (track->type() == Track::DRUM) {
                        int instr = ev.pitch();
                        // ignore muted drums
                        if (ev.isNote() && drumMap[instr].mute)
                              continue;
                        }
                  unsigned tick  = ev.tick() + offset;
                  unsigned frame = tempomap.tick2frame(tick) + frameOffset;
                  switch (ev.type()) {
                        case Note:
                              {
                              int len   = ev.lenTick();
                              int pitch = ev.pitch();
                              if (track->type() == Track::DRUM)  {
                                    //
                                    // Map drum-notes to the drum-map values
                                    //
                                   int instr = ev.pitch();
                                   pitch     = drumMap[instr].anote;
                                   port      = drumMap[instr].port; //This changes to non-default port
                                   channel   = drumMap[instr].channel;
                                   }
                              else {
                                    //
                                    // transpose non drum notes
                                    //
                                    pitch += (track->transposition + song->globalPitchShift());
                                    }

                              if (pitch > 127)
                                    pitch = 127;
                              if (pitch < 0)
                                    pitch = 0;
                              int velo  = ev.velo();
                              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;
                              if (len <= 0)     // don´t allow zero length
                                    len = 1;
                              int veloOff = ev.veloOff();

                              if (port == defaultPort) {
                                    //printf("Adding event normally: frame=%d port=%d channel=%d pitch=%d velo=%d\n",frame, port, channel, pitch, velo);
                                    playEvents->add(MidiPlayEvent(frame, port, channel, 0x90, pitch, velo));
                                    stuckNotes->add(MidiPlayEvent(tick + len, port, channel,
                                       veloOff ? 0x80 : 0x90, pitch, veloOff));
                                          track->addActivity(velo);
                                    }
                              else { //Handle events to different port than standard.
                                    //printf("** Adding to spec device!: frame=%d port=%d channel=%d pitch=%d velo=%d\n",frame, port, channel, pitch, velo);
                                    MidiDevice* mdAlt = midiPorts[port].device();
                                    mdAlt->playEvents()->add(MidiPlayEvent(frame, port, channel, 0x90, pitch, velo));
                                    mdAlt->stuckNotes()->add(MidiPlayEvent(tick + len, port, channel,
                                       veloOff ? 0x80 : 0x90, pitch, veloOff));
                                          track->addActivity(velo);
                                    }
                              }
                              break;

                        default:
                              playEvents->add(MidiPlayEvent(frame, port, channel, ev));
                              break;
                        }
                  }
            }
      }

//---------------------------------------------------------
//   processMidi
//    - collects midi events for current audio segment and
//       sends them to midi thread
//    - current audio segment position is (curTickPos, nextTickPos)
//    - called from midiseq thread,
//      executed in audio thread
//---------------------------------------------------------

void Audio::processMidi()
      {
      //
      // TODO: syntis should directly write into recordEventList
      //
      for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) {
            MidiDevice* md = *id;

            MPEventList* playEvents = md->playEvents();
            //
            // erase already played events:
            //
            iMPEvent nextPlayEvent  = md->nextPlayEvent();
            playEvents->erase(playEvents->begin(), nextPlayEvent);

            // klumsy hack for synti devices:
            if (!md->isSynti())
                  continue;
            SynthI* s = (SynthI*)md;
            while (s->eventsPending()) {
                  MidiRecordEvent ev = s->receiveEvent();
                  md->recordEvent(ev);
                  }
            }

      MPEventList* playEvents = metronome->playEvents();
      iMPEvent nextPlayEvent  = metronome->nextPlayEvent();
      playEvents->erase(playEvents->begin(), nextPlayEvent);

      for (iMidiTrack t = song->midis()->begin(); t != song->midis()->end(); ++t) {
            MidiTrack* track = *t;
            int port = track->outPort();
            MidiDevice* md = midiPorts[port].device();
            if (track->isMute() || md == 0)
                  continue;

            MPEventList* playEvents = md->playEvents();

            if (isPlaying())
                  collectEvents(track, curTickPos, nextTickPos);

            //
            //----------midi recording
            //
            if (track->recordFlag()) {
                  int portMask    = track->inPortMask();
                  int channelMask = track->inChannelMask();
                  MPEventList* rl = track->mpevents();

                  for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) {
                        MidiDevice* dev = *id;

                        int port = dev->midiPort();
                        // record only from ports marked in portMask:
                        if (port == -1 || !(portMask & (1 << port)))
                              continue;
                        MREventList* el = dev->recordEvents();
                        for (iMREvent ie = el->begin(); ie != el->end(); ++ie) {
                              int channel = ie->channel();
                              int defaultPort = port;
                              if (!(channelMask & (1 << channel)))
                                    continue;

                              MidiPlayEvent event(*ie);
                              int drumRecPitch; //Hmmm, hehhh... TODO: Clean up a bit around here when it comes to separate events for rec & for playback. But not before 0.7 (ml)
                              int prePitch = 0, preVelo = 0;

                              event.setChannel(track->outChannel());
                              if (event.isNote() || event.isNoteOff()) {
                                    //
                                    // apply track values
                                    //

                                    //Apply drum inkey:
                                    if (track->type() == Track::DRUM) {
                                          int pitch = event.dataA();
                                          //Map note that is played according to drumInmap
                                          drumRecPitch = drumMap[drumInmap[pitch]].enote;
                                          port = drumMap[drumInmap[pitch]].port;
                                          event.setPort(port);
                                          channel = drumMap[drumInmap[pitch]].channel;
                                          event.setA(drumMap[drumInmap[pitch]].anote);
                                          event.setChannel(channel);
                                          }
                                    else { //Track transpose if non-drum
                                          prePitch = event.dataA();
                                          int pitch = prePitch + track->transposition;
                                          if (pitch > 127)
                                                pitch = 127;
                                          if (pitch < 0)
                                                pitch = 0;
                                          event.setA(pitch);
                                          }

                                    if (!event.isNoteOff()) {
                                          preVelo = event.dataB();
                                          int velo = preVelo + track->velocity;
                                          velo = (velo * track->compression) / 100;
                                          if (velo > 127)
                                                velo = 127;
                                          if (velo < 1)
                                                velo = 1;
                                          event.setB(velo);
                                          }
                                    }
                              unsigned time = event.time() + segmentSize*(segmentCount-1);
                              event.setTime(time);

                              // dont't echo controller changes back to software
                              // synthesizer:

                              if (!dev->isSynti()) {
                                    //Check if we're outputting to another port than default:
                                    if (port == defaultPort) {
                                          event.setPort(track->outPort());
                                          playEvents->add(event);
                                          }
                                    else {
                                          // Hmm, this appears to work, but... Will this induce trouble with md->setNextPlayEvent??
                                          MidiDevice* mdAlt = midiPorts[port].device();
                                          mdAlt->playEvents()->add(event);
                                          }
                                    }

                              time = tempomap.frame2tick(event.time());
                              event.setTime(time);  // set tick time

                              // Special handling of events stored in rec-lists. a bit hACKish. TODO: Clean up (after 0.7)! :-/ (ml)
                              if (recording) {
                                    if (track->type() == Track::DRUM) {
                                          MidiPlayEvent drumRecEvent = event;
                                          drumRecEvent.setA(drumRecPitch);
                                          drumRecEvent.setB(preVelo);
                                          drumRecEvent.setPort(port);
                                          drumRecEvent.setChannel(track->outChannel()); //rec-event to current channel
                                          rl->add(drumRecEvent);
                                          }
                                    else {
                                          // Restore record-pitch to non-transposed value since we don't want the note transposed twice next
                                          MidiPlayEvent recEvent = event;
                                          if (prePitch)
                                                recEvent.setA(prePitch);
                                          if (preVelo)
                                                recEvent.setB(preVelo);
                                          recEvent.setPort(track->outPort());
                                          recEvent.setChannel(track->outChannel());
                                                
                                          rl->add(recEvent);
                                          }
                                    }
                              }
                        }
                  }
            md->setNextPlayEvent(playEvents->begin());
            }

      //
      // clear all recorded events in midiDevices
      // process stuck notes
      //
      for (iMidiDevice id = midiDevices.begin(); id != midiDevices.end(); ++id) {
            MidiDevice* md = *id;
            md->recordEvents()->clear();

            MPEventList* stuckNotes = md->stuckNotes();
            MPEventList* playEvents = md->playEvents();

            iMPEvent k;
            for (k = stuckNotes->begin(); k != stuckNotes->end(); ++k) {
                  if (k->time() >= nextTickPos)
                        break;
                  MidiPlayEvent ev(*k);
                  int frame = tempomap.tick2frame(k->time()) + frameOffset;
                  ev.setTime(frame);
                  playEvents->add(ev);
                  }
            stuckNotes->erase(stuckNotes->begin(), k);
            md->setNextPlayEvent(playEvents->begin());
            }

      //---------------------------------------------------
      //    insert metronome clicks
      //---------------------------------------------------

      MidiDevice* md = 0;
      if (midiClickFlag)
            md = midiPorts[clickPort].device();
      if (song->click() && (isPlaying() || state == PRECOUNT)) {
            MPEventList* playEvents;
            MPEventList* stuckNotes;

            if (md) {
                  playEvents = md->playEvents();
                  stuckNotes = md->stuckNotes();
                  }

            int bar, beat;
            unsigned tick;
            bool isMeasure = false;
            while (midiClick < nextTickPos) {
                  if (isPlaying()) {
                        sigmap.tickValues(midiClick, &bar, &beat, &tick);
                        isMeasure = beat == 0;
                        }
                  else if (state == PRECOUNT) {
                        isMeasure = (clickno % clicksMeasure) == 0;
                        }
                  int frame = tempomap.tick2frame(midiClick) + frameOffset;
                  MidiPlayEvent ev(frame, clickPort, clickChan, ME_NOTEON,
                     beatClickNote, beatClickVelo);
                  if (md) {
                        MidiPlayEvent ev(frame, clickPort, clickChan, ME_NOTEON,
                           beatClickNote, beatClickVelo);
                        if (isMeasure) {
                              ev.setA(measureClickNote);
                              ev.setB(measureClickVelo);
                              }
                        playEvents->add(ev);
                        }
                  if (audioClickFlag) {
                        MidiPlayEvent ev1(frame, 0, 0, ME_NOTEON, 0, 0);
                        ev1.setA(isMeasure ? 0 : 1);
                        metronome->playEvents()->add(ev1);
                        }
                  if (md) {
                        ev.setB(0);
                        frame = tempomap.tick2frame(midiClick+20) + frameOffset;
                        ev.setTime(midiClick+10);
                        if (md)
                              stuckNotes->add(ev);
                        }

                  if (isPlaying())
                        midiClick = sigmap.bar2tick(bar, beat+1, 0);
                  else if (state == PRECOUNT) {
                        midiClick += ticksBeat;
                        if (clickno)
                              --clickno;
                        else
                              state = START_PLAY;
                        }
                  }
            if (md)
                  md->setNextPlayEvent(playEvents->begin());
            if (audioClickFlag)
                  metronome->setNextPlayEvent(metronome->playEvents()->begin());
            }

      if (state == STOP) {
            //---------------------------------------------------
            //    end all notes
            //---------------------------------------------------

            for (iMidiDevice imd = midiDevices.begin(); imd != midiDevices.end(); ++imd) {
                  MidiDevice* md = *imd;
                  MPEventList* playEvents = md->playEvents();
                  MPEventList* stuckNotes = md->stuckNotes();
                  for (iMPEvent k = stuckNotes->begin(); k != stuckNotes->end(); ++k) {
                        MidiPlayEvent ev(*k);
                        ev.setTime(0);    // play now
                        playEvents->add(ev);
                        }
                  stuckNotes->clear();
                  }
            }
      }

