//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: song.cpp,v 1.59.2.6 2005/01/10 23:30:02 spamatica Exp $
//
//  (C) Copyright 2000-2004 Werner Schweer (ws@seh.de)
//=========================================================

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <qapplication.h>
#include <qmessagebox.h>

#include "app.h"
#include "song.h"
#include "track.h"
#include "undo.h"
#include "key.h"
#include "globals.h"
#include "event.h"
#include "drummap.h"
#include "marker/marker.h"
#include "synth.h"
#include "audio.h"
#include "mididev.h"
#include "amixer.h"
#include "midiseq.h"
#include "audiodev.h"
#include "gconfig.h"

Song* song;

//---------------------------------------------------------
//   Song
//---------------------------------------------------------

Song::Song(const char* name)
   :QObject(0, name)
      {
      noteFifoSize   = 0;
      noteFifoWindex = 0;
      noteFifoRindex = 0;
      undoList     = new UndoList;
      redoList     = new UndoList;
      _markerList  = new MarkerList;
      _globalPitchShift = 0;
      clear(false);
      }

//---------------------------------------------------------
//   Song
//---------------------------------------------------------

Song::~Song()
      {
      delete undoList;
      delete redoList;
      delete _markerList;
      }

//---------------------------------------------------------
//   putEvent
//---------------------------------------------------------

void Song::putEvent(int pv)
      {
      if (noteFifoSize < REC_NOTE_FIFO_SIZE) {
            recNoteFifo[noteFifoWindex] = pv;
            noteFifoWindex = (noteFifoWindex + 1) % REC_NOTE_FIFO_SIZE;
            ++noteFifoSize;
            }
      }

//---------------------------------------------------------
//   setTempo
//    public slot
//---------------------------------------------------------

void Song::setTempo(int newTempo)
      {
      audio->msgSetTempo(pos[0].tick(), newTempo, true);
      }

//---------------------------------------------------------
//   setSig
//    called from transport window
//---------------------------------------------------------

void Song::setSig(int z, int n)
      {
      if (_masterFlag) {
            audio->msgAddSig(pos[0].tick(), z, n);
            }
      }

//---------------------------------------------------------
//    addTrack
//    called from GUI context
//---------------------------------------------------------

Track* Song::addTrack(int t)
      {
      Track::TrackType type = (Track::TrackType) t;
      Track* track = 0;
      int lastAuxIdx = _auxs.size();
      switch(type) {
            case Track::MIDI:
                  track = new MidiTrack();
                  track->setType(Track::MIDI);
                  break;
            case Track::DRUM:
                  track = new MidiTrack();
                  track->setType(Track::DRUM);
                  break;
            case Track::WAVE:
                  track = new WaveTrack();
                  ((AudioTrack*)track)->addAuxSend(lastAuxIdx);
                  break;
            case Track::AUDIO_OUTPUT:
                  track = new AudioOutput();
                  break;
            case Track::AUDIO_GROUP:
                  track = new AudioGroup();
                  ((AudioTrack*)track)->addAuxSend(lastAuxIdx);
                  break;
            case Track::AUDIO_AUX:
                  track = new AudioAux();
                  break;
            case Track::AUDIO_INPUT:
                  track = new AudioInput();
                  ((AudioTrack*)track)->addAuxSend(lastAuxIdx);
                  break;
            case Track::AUDIO_SOFTSYNTH:
                  printf("not implemented: Song::addTrack(SOFTSYNTH)\n");
                  // ((AudioTrack*)track)->addAuxSend(lastAuxIdx);
                  break;
            default:
                  printf("Song::addTrack() illegal type %d\n", type);
                  abort();
            }
      track->setDefaultName();
      insertTrack1(track, -1);
      msgInsertTrack(track, -1, true);
      insertTrack3(track, -1);

      //
      //  add default route to master
      //
      OutputList* ol = song->outputs();
      if (!ol->empty()) {
            AudioOutput* ao = ol->front();
            switch(type) {
                  case Track::MIDI:
                  case Track::DRUM:
                  case Track::AUDIO_OUTPUT:
                        break;
                  case Track::WAVE:
                  case Track::AUDIO_GROUP:
                  case Track::AUDIO_AUX:
                  case Track::AUDIO_INPUT:
                  case Track::AUDIO_SOFTSYNTH:
                        audio->msgAddRoute(Route((AudioTrack*)track, -1), Route(ao, -1));
                        updateFlags |= SC_ROUTE;
                        break;
                  }
            }
      return track;
      }

//---------------------------------------------------------
//   cmdRemoveTrack
//---------------------------------------------------------

void Song::cmdRemoveTrack(Track* track)
      {
      int idx = _tracks.index(track);
      undoOp(UndoOp::DeleteTrack, idx, track);
      removeTrack2(track);
      updateFlags |= SC_TRACK_REMOVED;
      }

//---------------------------------------------------------
//   removeMarkedTracks
//---------------------------------------------------------

void Song::removeMarkedTracks()
      {
      bool loop;
      do {
            loop = false;
            for (iTrack t = _tracks.begin(); t != _tracks.end(); ++t) {
                  if ((*t)->selected()) {
                        removeTrack2(*t);
                        loop = true;
                        break;
                        }
                  }
            } while (loop);
      }

//---------------------------------------------------------
//   deselectTracks
//---------------------------------------------------------

void Song::deselectTracks()
      {
      for (iTrack t = _tracks.begin(); t != _tracks.end(); ++t)
            (*t)->setSelected(false);
      }

//---------------------------------------------------------
//   changeTrack
//    oldTrack - copy of the original track befor modification
//    newTrack - modified original track
//---------------------------------------------------------

void Song::changeTrack(Track* oldTrack, Track* newTrack)
      {
      oldTrack->setSelected(false);  //??
      undoOp(UndoOp::ModifyTrack, oldTrack, newTrack);
      updateFlags |= SC_TRACK_MODIFIED;
      }

//---------------------------------------------------------
//  addEvent
//    return true if event was added
//---------------------------------------------------------

bool Song::addEvent(Event& event, Part* part)
      {
      if (event.type() == Controller) {
            MidiTrack* track = (MidiTrack*)part->track();
            int ch    = track->outChannel();
            int tick  = event.tick();
            int cntrl = event.dataA();
            int val   = event.dataB();
            MidiPort* mp = &midiPorts[track->outPort()];
            if (!mp->setCtrl(ch, tick, cntrl, val)) {
                  mp->addManagedController(ch, cntrl);
                  if (!mp->setCtrl(ch, tick, cntrl, val))
                        return false;
                  }
            updateFlags |= SC_MIDI_CONTROLLER;
            }
      part->events()->add(event);
      return true;
      }

//---------------------------------------------------------
//   changeEvent
//---------------------------------------------------------

void Song::changeEvent(Event& oldEvent, Event& newEvent, Part* part)
      {
      iEvent i = part->events()->find(oldEvent);
      if (i == part->events()->end()) {
            printf("Song::changeEvent(): EVENT not found !! %d\n", part->events()->size());
            // abort();
            return;
            }
      part->events()->erase(i);
      part->events()->add(newEvent);
      if (newEvent.type() == Controller) {
            MidiTrack* track = (MidiTrack*)part->track();
            int ch    = track->outChannel();
            int tick  = newEvent.tick();
            int cntrl = newEvent.dataA();
            int val   = newEvent.dataB();
            midiPorts[track->outPort()].setCtrl(ch, tick, cntrl, val);
            }
      }

//---------------------------------------------------------
//   deleteEvent
//---------------------------------------------------------

void Song::deleteEvent(Event& event, Part* part)
      {
      if (event.type() == Controller) {
            MidiTrack* track = (MidiTrack*)part->track();
            int ch    = track->outChannel();
            int tick  = event.tick();
            int cntrl = event.dataA();
            midiPorts[track->outPort()].deleteController(ch, tick, cntrl);
            }
      iEvent ev = part->events()->find(event);
      if (ev == part->events()->end()) {
            printf("event not found in part\n");
            return;
            }
      part->events()->erase(ev);
      }

//---------------------------------------------------------
//   cmdAddRecordedEvents
//    add recorded Events into part
//---------------------------------------------------------

void Song::cmdAddRecordedEvents(MidiTrack* mt, EventList* events, unsigned startTick)
      {
      if (events->empty()) {
            if (debugMsg)
                  printf("no events recorded\n");
            return;
            }
      iEvent s;
      iEvent e;
      unsigned endTick;

      if (punchin()) {
            s = events->lower_bound(lpos());
            startTick = lpos();
            }
      else {
            s = events->begin();
            startTick = s->first;
            }
      if (punchout()) {
            e = events->lower_bound(rpos());
            endTick = rpos();
            }
      else {
            // search for last noteOff:
            endTick = 0;
            for (iEvent i = events->begin(); i != events->end(); ++i) {
                  Event ev = i->second;
                  unsigned l     = ev.endTick();
                  if (l > endTick)
                        endTick = l;
                  }
            e = events->end();
            }

      if (startTick > endTick) {
            if (debugMsg)
                  printf("no events in record area\n");
            return;
            }

      //---------------------------------------------------
      //    if startTick points into a part,
      //          record to that part
      //    else
      //          create new part
      //---------------------------------------------------

      PartList* pl = mt->parts();
      MidiPart* part = 0;
      iPart ip;
      for (ip = pl->begin(); ip != pl->end(); ++ip) {
            part = (MidiPart*)(ip->second);
            unsigned partStart = part->tick();
            unsigned partEnd   = partStart + part->lenTick();
            if (startTick >= partStart && startTick < partEnd)
                  break;
            }
      if (ip == pl->end()) {
            if (debugMsg)
                  printf("create new part for recorded events\n");
            // create new part
            part      = new MidiPart(mt);
            startTick = roundDownBar(startTick);
            endTick   = roundUpBar(endTick);
            part->setTick(startTick);
            part->setLenTick(endTick - startTick);
            part->setName(mt->name());
            // copy events
            EventList* de = part->events();
            for (iEvent i = s; i != e; ++i) {
                  Event old = i->second;
                  Event event = old.clone();
                  event.setTick(old.tick() - startTick);
                  de->add(event);
                  }
            audio->msgAddPart(part);
            updateFlags |= SC_PART_INSERTED;
            return;
            }

      updateFlags |= SC_EVENT_INSERTED;
      unsigned partTick = part->tick();
      if (endTick > partTick + part->lenTick()) {
            Part* newPart = part->clone();
            if (_recMode == REC_REPLACE) {
                  // TODO: delete Events
                  }
            endTick = 0;
            for (iEvent i = s; i != e; ++i) {
                  Event event = i->second;
                  unsigned tick = event.tick() - partTick;
                  event.setTick(tick);
                  Event e;
                  undoOp(UndoOp::AddEvent, e, event, newPart);
                  addEvent(event, (MidiPart*)newPart);
                  if (endTick < event.tick() + event.lenTick())
                        endTick = event.tick() + event.lenTick();
                  }
            newPart->setLenTick(endTick - part->tick());

            changePart(part, newPart);
            part->events()->incARef(-1);
            newPart->events()->incARef(1);

            undoOp(UndoOp::ModifyPart, part, newPart);
            part->events()->incARef(-1);
            updateFlags |= SC_PART_MODIFIED;
            }
      else {
            if (_recMode == REC_REPLACE) {
                  // TODO: delete Events
                  }
            for (iEvent i = s; i != e; ++i) {
                  Event event = i->second;
                  int tick = event.tick() - partTick;
                  event.setTick(tick);
                  Event e;
                  undoOp(UndoOp::AddEvent, e, event, part);
                  addEvent(event, part);
                  }
            }
      }

//---------------------------------------------------------
//   findTrack
//---------------------------------------------------------

MidiTrack* Song::findTrack(const Part* part) const
      {
      for (ciTrack t = _tracks.begin(); t != _tracks.end(); ++t) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
            if (track == 0)
                  continue;
            PartList* pl = track->parts();
            for (iPart p = pl->begin(); p != pl->end(); ++p) {
                  if (part == p->second)
                        return track;
                  }
            }
      return 0;
      }

//---------------------------------------------------------
//   findTrack
//    find track by name
//---------------------------------------------------------

Track* Song::findTrack(const QString& name) const
      {
      for (ciTrack i = _tracks.begin(); i != _tracks.end(); ++i) {
            if ((*i)->name() == name)
                  return *i;
            }
      return 0;
      }

//---------------------------------------------------------
//   setLoop
//    set transport loop flag
//---------------------------------------------------------

void Song::setLoop(bool f)
      {
      if (loopFlag != f) {
            loopFlag = f;
            loopAction->setOn(loopFlag);
            emit loopChanged(loopFlag);
            }
      }

//---------------------------------------------------------
//   setRecord
//    set transport loop flag
//---------------------------------------------------------

void Song::setRecord(bool f, bool autoRecEnable)
      {
      if (recordFlag != f) {
            if (f && autoRecEnable) {
                bool alreadyRecEnabled = false;
                Track *selectedTrack = 0;
                // loop through list and check if any track is rec enabled
                // if not then rec enable the selected track
                WaveTrackList* wtl = waves();
                for (iWaveTrack i = wtl->begin(); i != wtl->end(); ++i) {
                      if((*i)->recordFlag())
                          {
                          alreadyRecEnabled = true;
                          break;
                          }
                      if((*i)->selected())
                          selectedTrack = (*i);
                      }
                if (!alreadyRecEnabled) {
                      MidiTrackList* mtl = midis();
                      for (iMidiTrack it = mtl->begin(); it != mtl->end(); ++it) {
                            if((*it)->recordFlag())
                                {
                                alreadyRecEnabled = true;
                                break;
                                }
                            if((*it)->selected())
                                selectedTrack = (*it);
                            }
                      }
                if (!alreadyRecEnabled && selectedTrack) {
                      setRecordFlag(selectedTrack, true);
                      }
                else if (alreadyRecEnabled)  {
                      // do nothing
                      }
                else  {
                      // if there are no tracks, do not enable record
                      if (!waves()->size() && !midis()->size()) {
                            printf("No track to select, won't enable record\n");
                            f = false;
                            }
                      }
#if 0
                  // check for midi devices suitable for recording
                  bool portFound = false;
                  for (int i = 0; i < MIDI_PORTS; ++i) {
                        MidiDevice* dev = midiPorts[i].device();
                        if (dev && (dev->rwFlags() & 0x2))
                              portFound = true;
                        }
                  if (!portFound) {
                        QMessageBox::critical(qApp->mainWidget(), "MusE: Record",
                           "There are no midi devices configured for recording");
                        f = false;
                        }
#endif
                  }
            else {
                  bounceTrack = 0;
                  }
            if (audio->isPlaying() && f)
                  f = false;
            recordFlag = f;
            recordAction->setOn(recordFlag);
            emit recordChanged(recordFlag);
            }
      }

//---------------------------------------------------------
//   setPunchin
//    set punchin flag
//---------------------------------------------------------

void Song::setPunchin(bool f)
      {
      if (punchinFlag != f) {
            punchinFlag = f;
            punchinAction->setOn(punchinFlag);
            emit punchinChanged(punchinFlag);
            }
      }

//---------------------------------------------------------
//   setPunchout
//    set punchout flag
//---------------------------------------------------------

void Song::setPunchout(bool f)
      {
      if (punchoutFlag != f) {
            punchoutFlag = f;
            punchoutAction->setOn(punchoutFlag);
            emit punchoutChanged(punchoutFlag);
            }
      }

//---------------------------------------------------------
//   setClick
//---------------------------------------------------------

void Song::setClick(bool val)
      {
      if (_click != val) {
            _click = val;
            emit clickChanged(_click);
            }
      }

//---------------------------------------------------------
//   setQuantize
//---------------------------------------------------------

void Song::setQuantize(bool val)
      {
      if (_quantize != val) {
            _quantize = val;
            emit quantizeChanged(_quantize);
            }
      }

//---------------------------------------------------------
//   setMasterFlag
//---------------------------------------------------------

void Song::setMasterFlag(bool val)
      {
      _masterFlag = val;
      if (tempomap.setMasterFlag(cpos(), val))
            emit songChanged(SC_MASTER);
      }

//---------------------------------------------------------
//   setPlay
//    set transport play flag
//---------------------------------------------------------

void Song::setPlay(bool f)
      {
      // only allow the user to set the button "on"
      if (!f)
            playAction->setOn(true);
      else
            audio->msgPlay(true);
      }

void Song::setStop(bool f)
      {
      // only allow the user to set the button "on"
      if (!f)
            stopAction->setOn(true);
      else
            audio->msgPlay(false);
      }

void Song::setStopPlay(bool f)
      {
      playAction->blockSignals(true);
      stopAction->blockSignals(true);

      emit playChanged(f);   // signal transport window

      playAction->setOn(f);
      stopAction->setOn(!f);

      stopAction->blockSignals(false);
      playAction->blockSignals(false);
      }

//---------------------------------------------------------
//   swapTracks
//---------------------------------------------------------

void Song::swapTracks(int i1, int i2)
      {
      undoOp(UndoOp::SwapTrack, i1, i2);
      Track* track = _tracks[i1];
      _tracks[i1]  = _tracks[i2];
      _tracks[i2]  = track;
      }

//---------------------------------------------------------
//   setPos
//   song->setPos(Song::CPOS, pos, true, true, true);
//---------------------------------------------------------

void Song::setPos(int idx, const Pos& val, bool sig,
   bool isSeek, bool adjustScrollbar)
      {
//      printf("setPos %d sig=%d,seek=%d,scroll=%d  ",
//         idx, sig, isSeek, adjustScrollbar);
//      val.dump(0);
//      printf("\n");

      if (pos[idx] == val)
            return;
      if (idx == CPOS) {
            _vcpos = val;
            if (isSeek) {
                  audio->msgSeek(val);
                  return;
                  }
            }
      pos[idx] = val;
      bool swap = pos[LPOS] > pos[RPOS];
      if (swap) {        // swap lpos/rpos if lpos > rpos
            Pos tmp   = pos[LPOS];
            pos[LPOS] = pos[RPOS];
            pos[RPOS] = tmp;
            }
      if (sig) {
            if (swap) {
                  emit posChanged(LPOS, pos[LPOS].tick(), adjustScrollbar);
                  emit posChanged(RPOS, pos[RPOS].tick(), adjustScrollbar);
                  if (idx != LPOS && idx != RPOS)
                        emit posChanged(idx, pos[idx].tick(), adjustScrollbar);
                  }
            else
                  emit posChanged(idx, pos[idx].tick(), adjustScrollbar);
            }

      if (idx == CPOS) {
            iMarker i1 = _markerList->begin();
            iMarker i2 = i1;
            bool currentChanged = false;
            for (; i1 != _markerList->end(); ++i1) {
                  ++i2;
                  if (val.tick() >= i1->first && (i2==_markerList->end() || val.tick() < i2->first)) {
                        if (i1->second.current())
                              return;
                        i1->second.setCurrent(true);
                        if (currentChanged) {
                              emit markerChanged(MARKER_CUR);
                              return;
                              }
                        ++i1;
                        for (; i1 != _markerList->end(); ++i1) {
                              if (i1->second.current())
                                    i1->second.setCurrent(false);
                              }
                        emit markerChanged(MARKER_CUR);
                        return;
                        }
                  else {
                        if (i1->second.current()) {
                              currentChanged = true;
                              i1->second.setCurrent(false);
                              }
                        }
                  }
            if (currentChanged)
                  emit markerChanged(MARKER_CUR);
            }
      }

//---------------------------------------------------------
//   forward
//---------------------------------------------------------

void Song::forward()
      {
      unsigned newPos = pos[0].tick() + config.division;
      audio->msgSeek(Pos(newPos, true));
      }

//---------------------------------------------------------
//   rewind
//---------------------------------------------------------

void Song::rewind()
      {
      unsigned newPos;
      if (unsigned(config.division) > pos[0].tick())
            newPos = 0;
      else
            newPos = pos[0].tick() - config.division;
      audio->msgSeek(Pos(newPos, true));
      }

//---------------------------------------------------------
//   rewindStart
//---------------------------------------------------------

void Song::rewindStart()
      {
      audio->msgSeek(Pos(0, true));
      }

//---------------------------------------------------------
//   update
//---------------------------------------------------------

void Song::update(int flags)
      {
      static int level = 0;         // DEBUG
      if (level) {
            printf("Song::update %08x, level %d\n", flags, level);
            return;
            }
      ++level;
      emit songChanged(flags);
      --level;
      }

//---------------------------------------------------------
//   updatePos
//---------------------------------------------------------

void Song::updatePos()
      {
      emit posChanged(0, pos[0].tick(), false);
      emit posChanged(1, pos[1].tick(), false);
      emit posChanged(2, pos[2].tick(), false);
      }

//---------------------------------------------------------
//   setChannelMute
//    mute all midi tracks associated with channel
//---------------------------------------------------------

void Song::setChannelMute(int channel, bool val)
      {
      for (iTrack i = _tracks.begin(); i != _tracks.end(); ++i) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*i);
            if (track == 0)
                  continue;
            if (track->outChannel() == channel)
                  track->setMute(val);
            }
      emit songChanged(SC_MUTE);
      }

//---------------------------------------------------------
//   len
//---------------------------------------------------------

void Song::initLen()
      {
      _len = sigmap.bar2tick(40, 0, 0);    // default song len
      for (iTrack t = _tracks.begin(); t != _tracks.end(); ++t) {
            MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
            if (track == 0)
                  continue;
            PartList* parts = track->parts();
            for (iPart p = parts->begin(); p != parts->end(); ++p) {
                  unsigned last = p->second->tick() + p->second->lenTick();
                  if (last > _len)
                        _len = last;
                  }
            }
      _len = roundUpBar(_len);
      }

//---------------------------------------------------------
//   roundUpBar
//---------------------------------------------------------

int Song::roundUpBar(int t) const
      {
      int bar, beat;
      unsigned tick;
      sigmap.tickValues(t, &bar, &beat, &tick);
      if (beat || tick)
            return sigmap.bar2tick(bar+1, 0, 0);
      return t;
      }

//---------------------------------------------------------
//   roundUpBeat
//---------------------------------------------------------

int Song::roundUpBeat(int t) const
      {
      int bar, beat;
      unsigned tick;
      sigmap.tickValues(t, &bar, &beat, &tick);
      if (tick)
            return sigmap.bar2tick(bar, beat+1, 0);
      return t;
      }

//---------------------------------------------------------
//   roundDownBar
//---------------------------------------------------------

int Song::roundDownBar(int t) const
      {
      int bar, beat;
      unsigned tick;
      sigmap.tickValues(t, &bar, &beat, &tick);
      return sigmap.bar2tick(bar, 0, 0);
      }

//---------------------------------------------------------
//   dumpMaster
//---------------------------------------------------------

void Song::dumpMaster()
      {
      tempomap.dump();
      sigmap.dump();
      }

//---------------------------------------------------------
//   getSelectedParts
//---------------------------------------------------------

PartList* Song::getSelectedMidiParts() const
      {
      PartList* parts = new PartList();

      //------------------------------------------------------
      //    wenn ein Part selektiert ist, diesen editieren
      //    wenn ein Track selektiert ist, den Ersten
      //       Part des Tracks editieren, die restlichen sind
      //       'ghostparts'
      //    wenn mehrere Parts selektiert sind, dann Ersten
      //       editieren, die restlichen sind 'ghostparts'
      //

      // collect marked parts
      for (ciMidiTrack t = _midis.begin(); t != _midis.end(); ++t) {
            MidiTrack* track = *t;
            PartList* pl = track->parts();
            for (iPart p = pl->begin(); p != pl->end(); ++p) {
                  if (p->second->selected()) {
                        parts->add(p->second);
                        }
                  }
            }
      // if no part is selected, then search for selected track
      // and collect all parts of this track

      if (parts->empty()) {
            for (ciTrack t = _tracks.begin(); t != _tracks.end(); ++t) {
                  if ((*t)->selected()) {
                        MidiTrack* track = dynamic_cast<MidiTrack*>(*t);
                        if (track == 0)
                              continue;
                        PartList* pl = track->parts();
                        for (iPart p = pl->begin(); p != pl->end(); ++p)
                              parts->add(p->second);
                        break;
                        }
                  }
            }
      return parts;
      }

PartList* Song::getSelectedWaveParts() const
      {
      PartList* parts = new PartList();

      //------------------------------------------------------
      //    wenn ein Part selektiert ist, diesen editieren
      //    wenn ein Track selektiert ist, den Ersten
      //       Part des Tracks editieren, die restlichen sind
      //       'ghostparts'
      //    wenn mehrere Parts selektiert sind, dann Ersten
      //       editieren, die restlichen sind 'ghostparts'
      //

      // markierte Parts sammeln
      for (ciTrack t = _tracks.begin(); t != _tracks.end(); ++t) {
            WaveTrack* track = dynamic_cast<WaveTrack*>(*t);
            if (track == 0)
                  continue;
            PartList* pl = track->parts();
            for (iPart p = pl->begin(); p != pl->end(); ++p) {
                  if (p->second->selected()) {
                        parts->add(p->second);
                        }
                  }
            }
      // wenn keine Parts selektiert, dann markierten Track suchen
      // und alle Parts dieses Tracks zusammensuchen

      if (parts->empty()) {
            for (ciTrack t = _tracks.begin(); t != _tracks.end(); ++t) {
                  if ((*t)->selected()) {
                        WaveTrack* track =  dynamic_cast<WaveTrack*>(*t);
                        if (track == 0)
                              continue;
                        PartList* pl = track->parts();
                        for (iPart p = pl->begin(); p != pl->end(); ++p)
                              parts->add(p->second);
                        break;
                        }
                  }
            }
      return parts;
      }

void Song::setMType(MType t)
      {
//   printf("set MType %d\n", t);
      _mtype = t;
      }

//---------------------------------------------------------
//   beat
//---------------------------------------------------------

void Song::beat()
      {
      int tick = audio->tickPos();
      if (audio->isPlaying())
            setPos(0, tick, true, false, true);
      while (noteFifoSize) {
            int pv = recNoteFifo[noteFifoRindex];
            noteFifoRindex = (noteFifoRindex + 1) % REC_NOTE_FIFO_SIZE;
            int pitch = (pv >> 8) & 0xff;
            int velo = pv & 0xff;

            //---------------------------------------------------
            // filter midi remote control events
            //---------------------------------------------------

            if (rcEnable && velo != 0) {
                  if (pitch == rcStopNote)
                        setStop(true);
                  else if (pitch == rcRecordNote)
                        setRecord(true);
                  else if (pitch == rcGotoLeftMarkNote)
                        setPos(0, pos[LPOS].tick(), true, true, true);
                  else if (pitch == rcPlayNote)
                        setPlay(true);
                  }
            emit song->midiNote(pitch, velo);
            --noteFifoSize;
            }
      }

//---------------------------------------------------------
//   setLen
//---------------------------------------------------------

void Song::setLen(unsigned l)
      {
      _len = l;
      update();
      }

//---------------------------------------------------------
//   addMarker
//---------------------------------------------------------

Marker* Song::addMarker(const QString& s, int t, bool lck)
      {
      Marker* marker = _markerList->add(s, t, lck);
      emit markerChanged(MARKER_ADD);
      return marker;
      }

//---------------------------------------------------------
//   addMarker
//---------------------------------------------------------

Marker* Song::getMarkerAt(int t)
      {
      iMarker markerI;
      for (markerI=_markerList->begin(); markerI != _markerList->end(); ++markerI) {
//                        if (i1->second.current())
          if (t == markerI->second.tick())
            return &markerI->second;
          }
      //Marker* marker = _markerList->add(s, t, lck);
      return NULL;
      }

//---------------------------------------------------------
//   removeMarker
//---------------------------------------------------------

void Song::removeMarker(Marker* marker)
      {
      _markerList->remove(marker);
      emit markerChanged(MARKER_REMOVE);
      }

Marker* Song::setMarkerName(Marker* m, const QString& s)
      {
      m->setName(s);
      emit markerChanged(MARKER_NAME);
      return m;
      }

Marker* Song::setMarkerTick(Marker* m, int t)
      {
      Marker mm(*m);
      _markerList->remove(m);
      mm.setTick(t);
      m = _markerList->add(mm);
      emit markerChanged(MARKER_TICK);
      return m;
      }

Marker* Song::setMarkerLock(Marker* m, bool f)
      {
      m->setType(f ? Pos::FRAMES : Pos::TICKS);
      emit markerChanged(MARKER_LOCK);
      return m;
      }

//---------------------------------------------------------
//   setRecordFlag
//---------------------------------------------------------

void Song::setRecordFlag(Track* track, bool val)
      {
      if (track->type() == Track::WAVE) {
            WaveTrack* audioTrack = (WaveTrack*)track;
            audioTrack->setRecordFlag1(val);
            audio->msgSetRecord(audioTrack, val);
            }
      else {
            track->setRecordFlag1(val);
            track->setRecordFlag2(val);
            }
//      updateFlags |= SC_RECFLAG;
      update(SC_RECFLAG);
      }

//---------------------------------------------------------
//   rescanAlsaPorts
//---------------------------------------------------------

void Song::rescanAlsaPorts()
      {
      emit midiPortsChanged();
      }

//---------------------------------------------------------
//   endMsgCmd
//---------------------------------------------------------

void Song::endMsgCmd()
      {
      if (updateFlags) {
            redoList->clear();            // TODO: delete elements in list
            undoAction->setEnabled(true);
            redoAction->setEnabled(false);
            emit songChanged(updateFlags);
            }
      }

//---------------------------------------------------------
//   undo
//---------------------------------------------------------

void Song::undo()
      {
      updateFlags = 0;
      if (doUndo1())
            return;
      audio->msgUndo();
      doUndo3();
      redoAction->setEnabled(true);
      undoAction->setEnabled(!undoList->empty());
      emit songChanged(updateFlags);
      }

//---------------------------------------------------------
//   redo
//---------------------------------------------------------

void Song::redo()
      {
      updateFlags = 0;
      if (doRedo1())
            return;
      audio->msgRedo();
      doRedo3();
      undoAction->setEnabled(true);
      redoAction->setEnabled(!redoList->empty());
      emit songChanged(updateFlags);
      }

//---------------------------------------------------------
//   processMsg
//    executed in realtime thread context
//---------------------------------------------------------

void Song::processMsg(AudioMsg* msg)
      {
      switch(msg->id) {
            case SEQM_UNDO:
                  doUndo2();
                  break;
            case SEQM_REDO:
                  doRedo2();
                  break;
            case SEQM_MOVE_TRACK:
                  if (msg->a > msg->b) {
                        for (int i = msg->a; i > msg->b; --i) {
                              swapTracks(i, i-1);
                              }
                        }
                  else {
                        for (int i = msg->a; i < msg->b; ++i) {
                              swapTracks(i, i+1);
                              }
                        }
                  updateFlags = SC_TRACK_MODIFIED;
                  break;
            case SEQM_ADD_EVENT:
                  updateFlags = SC_EVENT_INSERTED;
                  if (addEvent(msg->ev1, (MidiPart*)msg->p2)) {
                        Event ev;
                        undoOp(UndoOp::AddEvent, ev, msg->ev1, (Part*)msg->p2);
                        }
                  else
                        updateFlags = 0;
                  break;
            case SEQM_REMOVE_EVENT:
                  {
                  Event event = msg->ev1;
                  MidiPart* part = (MidiPart*)msg->p2;
                  Event e;
                  undoOp(UndoOp::DeleteEvent, e, event, (Part*)part);
                  deleteEvent(event, part);
                  updateFlags = SC_EVENT_REMOVED;
                  }
                  break;
            case SEQM_CHANGE_EVENT:
                  changeEvent(msg->ev1, msg->ev2, (MidiPart*)msg->p3);
                  undoOp(UndoOp::ModifyEvent, msg->ev2, msg->ev1, (Part*)msg->p3);
                  updateFlags = SC_EVENT_MODIFIED;
                  break;

            case SEQM_ADD_TEMPO:
                  //printf("processMsg (SEQM_ADD_TEMPO) UndoOp::AddTempo. adding tempo at: %d with tempo=%d\n", msg->a, msg->b);
                  undoOp(UndoOp::AddTempo, msg->a, msg->b);
                  tempomap.addTempo(msg->a, msg->b);
                  updateFlags = SC_TEMPO;
                  break;

            case SEQM_SET_TEMPO:
                  //printf("processMsg (SEQM_SET_TEMPO) UndoOp::AddTempo. adding tempo at: %d with tempo=%d\n", msg->a, msg->b);
                  undoOp(UndoOp::AddTempo, msg->a, msg->b);
                  tempomap.setTempo(msg->a, msg->b);
                  updateFlags = SC_TEMPO;
                  break;

            case SEQM_SET_GLOBAL_TEMPO:
                  tempomap.setGlobalTempo(msg->a);
                  break;

            case SEQM_REMOVE_TEMPO:
                  //printf("processMsg (SEQM_REMOVE_TEMPO) UndoOp::DeleteTempo. adding tempo at: %d with tempo=%d\n", msg->a, msg->b);
                  undoOp(UndoOp::DeleteTempo, msg->a, msg->b);
                  tempomap.delTempo(msg->a);
                  updateFlags = SC_TEMPO;
                  break;

            case SEQM_ADD_SIG:
                  undoOp(UndoOp::AddSig, msg->a, msg->b, msg->c);
                  sigmap.add(msg->a, msg->b, msg->c);
                  updateFlags = SC_SIG;
                  break;

            case SEQM_REMOVE_SIG:
                  undoOp(UndoOp::DeleteSig, msg->a, msg->b, msg->c);
                  sigmap.del(msg->a);
                  updateFlags = SC_SIG;
                  break;

            default:
                  printf("unknown seq message %d\n", msg->id);
                  break;
            }
      }

//---------------------------------------------------------
//   cmdAddPart
//---------------------------------------------------------

void Song::cmdAddPart(Part* part)
      {
      addPart(part);
      undoOp(UndoOp::AddPart, part);
      updateFlags = SC_PART_INSERTED;
      }

//---------------------------------------------------------
//   cmdRemovePart
//---------------------------------------------------------

void Song::cmdRemovePart(Part* part)
      {
      removePart(part);
      undoOp(UndoOp::DeletePart, part);
      part->events()->incARef(-1);
      updateFlags = SC_PART_REMOVED;
      }

//---------------------------------------------------------
//   cmdChangePart
//---------------------------------------------------------

void Song::cmdChangePart(Part* oldPart, Part* newPart)
      {
      changePart(oldPart, newPart);
      undoOp(UndoOp::ModifyPart, oldPart, newPart);
      oldPart->events()->incARef(-1);
      updateFlags = SC_PART_MODIFIED;
      }

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

void Song::panic()
      {
      audio->msgPanic();
      }

//---------------------------------------------------------
//   clear
//    signal - emit signals for changes if true
//    called from constructor as clear(false) and
//    from MusE::clearSong() as clear(false)
//---------------------------------------------------------

void Song::clear(bool signal)
      {
      bounceTrack    = 0;

      _tracks.clear();
      _midis.clearDelete();
      _waves.clearDelete();
      _inputs.clearDelete();     // audio input ports
      _outputs.clearDelete();    // audio output ports
      _groups.clearDelete();     // mixer groups
      _auxs.clearDelete();       // aux sends
      _synthIs.clearDelete();

      tempomap.clear();
      sigmap.clear();
      undoList->clear();
      redoList->clear();
      _markerList->clear();
      pos[0].setTick(0);
      pos[1].setTick(0);
      pos[2].setTick(0);
      _vcpos.setTick(0);

      _masterFlag    = true;
      loopFlag       = false;
      loopFlag       = false;
      punchinFlag    = false;
      punchoutFlag   = false;
      recordFlag     = false;
      soloFlag       = false;
      // seq
      _mtype         = MT_UNKNOWN;
      _recMode       = REC_OVERDUP;
      _cycleMode     = CYCLE_NORMAL;
      _click         = false;
      _quantize      = false;
      _len           = 0;           // song len in ticks
      _follow        = JUMP;
      // _tempo      = 500000;      // default tempo 120
      dirty          = false;
      initDrumMap();
      if (signal) {
            emit loopChanged(false);
            recordChanged(false);
            }
      }

//---------------------------------------------------------
//   seqSignal
//    sequencer message to GUI
//    execution environment: gui thread
//---------------------------------------------------------

void Song::seqSignal(int fd)
      {
      char buffer[16];

      int n = ::read(fd, buffer, 16);
      if (n < 0) {
            printf("Song: seqSignal(): READ PIPE failed: %s\n",
               strerror(errno));
            return;
            }
      for (int i = 0; i < n; ++i) {
// printf("seqSignal to gui:<%c>\n", buffer[i]);
            switch(buffer[i]) {
                  case '0':         // STOP
                        stopRolling();
                        break;
                  case '1':         // PLAY
                        setStopPlay(true);
                        break;
                  case '2':   // record
                        setRecord(true);
                  case 'P':   // alsa ports changed
                        rescanAlsaPorts();
                        break;
                  case 'G':
                        setPos(0, audio->tickPos(), true, false, true);
                        break;
                  case 'S':   // shutdown audio
                        muse->seqStop();

                        // give the user a sensible explanation
                        QMessageBox::critical( muse, tr(QString("Jack shutdown!")),
                            tr(QString("Jack has detected a performance problem which has lead to\n"
                            "MusE being disconnected.\n"
                            "This could happen due to a number of reasons:\n"
                            "- a performance issue with your particular setup.\n"
                            "- a bug in MusE (or possibly in another connected software).\n"
                            "- a random hiccup which might never occur again.\n"
                            "If the problem persists you are much welcome to discuss your\n"
                            "problem on the MusE mailinglist.\n"
                            "(there is information about joining the mailinglist on the MusE\n"
                            " homepage, which is available through the help menu)\n"
                            "\n"
                            "To proceed from here you need to save your work and restart MusE,\n"
                            "after which the problem will hopefully have vanished.")));

                        break;
                  case 'f':   // start freewheel
                        //audioDevice->setFreewheel(true);
                        break;

                  case 'F':   // stop freewheel
                        //audioDevice->setFreewheel(false);
                        audio->msgPlay(false);
#if 0
                        if (record())
                              audio->recordStop();
                        setStopPlay(false);
#endif
                        break;

                  case 'C':
                        audioDevice->graphChanged();
                        break;

                  default:
                        printf("unknown Seq Signal <%c>\n", buffer[i]);
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   recordEvent
//---------------------------------------------------------

void Song::recordEvent(MidiTrack* mt, Event& event)
      {
      //---------------------------------------------------
      //    if tick points into a part,
      //          record to that part
      //    else
      //          create new part
      //---------------------------------------------------

      unsigned tick  = event.tick();
      PartList* pl   = mt->parts();
      MidiPart* part = 0;
      iPart ip;
      for (ip = pl->begin(); ip != pl->end(); ++ip) {
            part = (MidiPart*)(ip->second);
            unsigned partStart = part->tick();
            unsigned partEnd   = partStart + part->lenTick();
            if (tick >= partStart && tick < partEnd)
                  break;
            }
      updateFlags |= SC_EVENT_INSERTED;
      if (ip == pl->end()) {
            // create new part
            part          = new MidiPart(mt);
            int startTick = roundDownBar(tick);
            int endTick   = roundUpBar(tick);
            part->setTick(startTick);
            part->setLenTick(endTick - startTick);
            part->setName(mt->name());
            event.move(-startTick);
            part->events()->add(event);
            audio->msgAddPart(part);
            return;
            }
      part = (MidiPart*)(ip->second);
      tick -= part->tick();
      event.setTick(tick);
      audio->msgAddEvent(event, part);
      }

//---------------------------------------------------------
//   stopRolling
//---------------------------------------------------------

void Song::stopRolling()
      {
      if (record())
            audio->recordStop();
      setStopPlay(false);
      if (!automation)
            return;
      //
      // process recorded automation events
      //
      for (iTrack it = tracks()->begin(); it != tracks()->end(); ++it) {
            if ((*it)->isMidiTrack())
                  continue;
            AudioTrack* track = (AudioTrack*)(*it);
            if (track->automationType() != AUTO_TOUCH && track->automationType() != AUTO_WRITE)
                  continue;
            CtrlListList* cl = track->controller();
            for (iCtrlList icl = cl->begin(); icl != cl->end(); ++icl) {
                  CtrlList* cl = icl->second;
                  int id = cl->id();
                  CtrlRecList* crl = track->recEvents();
                  //
                  //  remove old events from record region
                  //
                  if (track->automationType() == AUTO_WRITE) {
                        int start = audio->getStartRecordPos().frame();
                        int end   = audio->getEndRecordPos().frame();
                        iCtrl   s = cl->lower_bound(start);
                        iCtrl   e = cl->lower_bound(end);
                        cl->erase(s, e);
                        }
                  else {  // type AUTO_TOUCH
                        for (iCtrlRec icr = crl->begin(); icr != crl->end(); ++icr) {
                              if (icr->id == id && icr->type == 1) {
                                    int start = icr->frame;
                                    ++icr;
                                    for (; icr != crl->end(); ++icr) {
                                          if (icr->id == id && icr->type == 2) {
                                                int end = icr->frame;
                                                iCtrl s = cl->lower_bound(start);
                                                iCtrl e = cl->lower_bound(end);
                                                cl->erase(s, e);
                                                break;
                                                }
                                          }
                                    if (icr == crl->end())
                                          break;
                                    }
                              }
                        }
                  //
                  //  extract all recorded events for controller "id"
                  //  from CtrlRecList and put into cl
                  //
                  for (iCtrlRec icr = crl->begin(); icr != crl->end(); ++icr) {
                        if (icr->id == id && icr->type == 0)
                              cl->add(icr->frame, icr->val);
                        }
                  }
            if (track->automationType() == AUTO_WRITE)
                  track->setAutomationType(AUTO_READ);
            }
      }

//---------------------------------------------------------
//   insertTrack0
//---------------------------------------------------------

void Song::insertTrack0(Track* track, int idx)
      {
      insertTrack1(track, idx);
      insertTrack2(track, idx);  // audio->msgInsertTrack(track, idx, false);
      insertTrack3(track, idx);
      }

//---------------------------------------------------------
//   insertTrack1
//    non realtime part of insertTrack
//---------------------------------------------------------

void Song::insertTrack1(Track* track, int /*idx*/)
      {
      switch(track->type()) {
            case Track::AUDIO_SOFTSYNTH:
                  {
                  SynthI* s = (SynthI*)track;
                  Synth* sy = s->synth();
                  if (!s->isActivated()) {
                        s->initInstance(sy, s->name());
                        }
                  }
                  break;
            default:
                  break;
            }
      }

//---------------------------------------------------------
//   insertTrack2
//    realtime part
//---------------------------------------------------------

void Song::insertTrack2(Track* track, int idx)
      {
      int n;
      switch(track->type()) {
            case Track::MIDI:
            case Track::DRUM:
                  _midis.push_back((MidiTrack*)track);
                  break;
            case Track::WAVE:
                  _waves.push_back((WaveTrack*)track);
                  break;
            case Track::AUDIO_OUTPUT:
                  _outputs.push_back((AudioOutput*)track);
                  // set default master & monitor if not defined
                  if (audio->audioMaster() == 0)
                        audio->setMaster((AudioOutput*)track);
                  if (audio->audioMonitor() == 0)
                        audio->setMonitor((AudioOutput*)track);
                  break;
            case Track::AUDIO_GROUP:
                  _groups.push_back((AudioGroup*)track);
                  break;
            case Track::AUDIO_AUX:
                  _auxs.push_back((AudioAux*)track);
                  break;
            case Track::AUDIO_INPUT:
                  _inputs.push_back((AudioInput*)track);
                  break;
            case Track::AUDIO_SOFTSYNTH:
                  {
                  SynthI* s = (SynthI*)track;
                  midiDevices.add(s);
                  midiInstruments.push_back(s);
                  _synthIs.push_back(s);
                  }
                  break;
            default:
                  fprintf(stderr, "unknown track type %d\n", track->type());
                  // abort();
                  return;
            }

      //
      // initialize missing aux send
      //
      iTrack i = _tracks.index2iterator(idx);
      _tracks.insert(i, track);

      n = _auxs.size();
      for (iTrack i = _tracks.begin(); i != _tracks.end(); ++i) {
            if ((*i)->isMidiTrack())
                  continue;
            WaveTrack* wt = (WaveTrack*)*i;
            if (wt->hasAuxSend()) {
                  wt->addAuxSend(n);
                  }
            }

      //
      //  add routes
      //

      if (track->isMidiTrack())
            return;
      AudioTrack* at = (AudioTrack*)track;
      Route src(at, -1);
      if (at->type() == Track::AUDIO_OUTPUT) {
            const RouteList* rl = at->inRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->outRoutes()->push_back(src);
            }
      else if (at->type() == Track::AUDIO_INPUT) {
            const RouteList* rl = at->outRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->inRoutes()->push_back(src);
            }
      else {
            const RouteList* rl = at->inRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->outRoutes()->push_back(src);
            rl = at->outRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->inRoutes()->push_back(src);
            }
      }

//---------------------------------------------------------
//   insertTrack3
//    non realtime part of insertTrack
//---------------------------------------------------------

void Song::insertTrack3(Track* track, int idx)
      {
      switch(track->type()) {
            case Track::AUDIO_SOFTSYNTH:
                  break;
            default:
                  break;
            }
      }

//---------------------------------------------------------
//   removeTrack0
//---------------------------------------------------------

void Song::removeTrack0(Track* track)
      {
      removeTrack1(track);
      audio->msgRemoveTrack(track);
      removeTrack3(track);
      update(SC_TRACK_REMOVED);
      }

//---------------------------------------------------------
//   removeTrack1
//    non realtime part of removeTrack
//---------------------------------------------------------

void Song::removeTrack1(Track* track)
      {
      switch(track->type()) {
            case Track::AUDIO_SOFTSYNTH:
                  break;
            default:
                  break;
            }
      }

//---------------------------------------------------------
//   removeTrack
//    called from RT context
//---------------------------------------------------------

void Song::removeTrack2(Track* track)
      {
      switch(track->type()) {
            case Track::MIDI:
            case Track::DRUM:
                  _midis.erase(track);
                  break;
            case Track::WAVE:
                  _waves.erase(track);
                  break;
            case Track::AUDIO_OUTPUT:
                  _outputs.erase(track);
                  break;
            case Track::AUDIO_INPUT:
                  _inputs.erase(track);
                  break;
            case Track::AUDIO_GROUP:
                  _groups.erase(track);
                  break;
            case Track::AUDIO_AUX:
                  _auxs.erase(track);
                  break;
            case Track::AUDIO_SOFTSYNTH:
                  {
                  SynthI* s = (SynthI*) track;
                  s->deactivate2();
                  _synthIs.erase(track);
                  }
                  break;
            }
      _tracks.erase(track);
      if (track->isMidiTrack())
            return;
      //
      //  remove routes
      //

      AudioTrack* at = (AudioTrack*)track;
      Route src(at, -1);
      if (at->type() == Track::AUDIO_OUTPUT) {
            const RouteList* rl = at->inRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->outRoutes()->removeRoute(src);
            }
      else if (at->type() == Track::AUDIO_INPUT) {
            const RouteList* rl = at->outRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->inRoutes()->removeRoute(src);
            }
      else {
            const RouteList* rl = at->inRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->outRoutes()->removeRoute(src);
            rl = at->outRoutes();
            for (ciRoute r = rl->begin(); r != rl->end(); ++r)
                  r->track->inRoutes()->removeRoute(src);
            }
      }

//---------------------------------------------------------
//   removeTrack3
//    non realtime part of removeTrack
//---------------------------------------------------------

void Song::removeTrack3(Track* track)
      {
      switch(track->type()) {
            case Track::AUDIO_SOFTSYNTH:
                  {
                  SynthI* s = (SynthI*) track;
                  s->deactivate3();
                  }
                  break;
            default:
                  break;
            }
      }
