//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: songfile.cpp,v 1.2 2003/10/29 22:16:38 spamatica Exp $
//
//  (C) Copyright 1999/2000 Werner Schweer (ws@seh.de)
//=========================================================

#include <assert.h>
#include <qmessagebox.h>

#include "app.h"
#include "song.h"
#include "seq.h"
#include "arranger.h"
#include "transport.h"
#include "cobject.h"
#include "drumedit.h"
#include "pianoroll.h"
#include "globals.h"
#include "xml.h"
#include "drummap.h"
#include "event.h"
#include "marker/marker.h"
#include "midiport.h"
#include "audio.h"
#include "mitplugin.h"
#include "wave.h"
#include "midictrl.h"
#include "amixer.h"

struct ClonePart {
      const EventList* el;
      int id;
      ClonePart(const EventList* e, int i) : el(e), id(i) {}
      };

typedef std::list<ClonePart> CloneList;
typedef CloneList::iterator iClone;

static CloneList cloneList;

//---------------------------------------------------------
//   NKey::write
//---------------------------------------------------------

void NKey::write(int level, Xml& xml) const
      {
      xml.intTag(level, "key", val);
      }

//---------------------------------------------------------
//   NKey::read
//---------------------------------------------------------

void NKey::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::Text:
                        val = xml.s1().toInt();
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "key")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   Scale::write
//---------------------------------------------------------

void Scale::write(int level, Xml& xml) const
      {
      xml.intTag(level, "scale", val);
      }

//---------------------------------------------------------
//   Scale::read
//---------------------------------------------------------

void Scale::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::Text:
                        val = xml.s1().toInt();
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "scale")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   Part::write
//---------------------------------------------------------

void Part::write(int level, Xml& xml) const
      {
      const EventList* el = cevents();
      int id              = -1;
      bool dumpEvents     = true;

      if (el->arefCount() > 1) {
            for (iClone i = cloneList.begin(); i != cloneList.end(); ++i) {
                  if (i->el == el) {
                        id = i->id;
                        dumpEvents = false;
                        break;
                        }
                  }
            if (id == -1) {
                  id = cloneList.size();
                  ClonePart cp(el, id);
                  cloneList.push_back(cp);
                  }
            }

      if (id != -1)
            xml.tag(level++, "part cloneId=\"%d\"", id);
      else
            xml.tag(level++, "part");
      xml.strTag(level, "name", _name);

      PosLen poslen(*this);
      int tickpos = posTick();
      poslen.setPosTick(tickpos);
      poslen.write(level, xml, "poslen");
      xml.intTag(level, "selected", _selected);
      xml.intTag(level, "color", _colorIndex);
      if (_mute)
            xml.intTag(level, "mute", _mute);
      if (dumpEvents) {
            for (ciEvent e = el->begin(); e != el->end(); ++e)
                  e->second->write(level, xml, tickpos);
            }
      xml.etag(level, "part");
      }

//---------------------------------------------------------
//   Part::read
//---------------------------------------------------------

void Part::read(Xml& xml)
      {
      int id = -1;
      bool containsEvents = false;

      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "name")
                              _name = xml.parse1();
                        else if (tag == "poslen") {
                              PosLen::read(xml, "poslen");
                              }
                        else if (tag == "pos") {
                              Pos pos;
                              pos.read(xml, "pos");  // obsolete
                              setPosTick(pos.posTick());
                              }
                        else if (tag == "len") {
                              Pos len;
                              len.read(xml, "len");  // obsolete
                              setLenTick(len.posTick());
                              }
                        else if (tag == "selected")
                              _selected = xml.parseInt();
                        else if (tag == "color")
                              _colorIndex = xml.parseInt();
                        else if (tag == "mute")
                              _mute = xml.parseInt();
                        else if (tag == "event") {
                              containsEvents = true;
                              Event* e = newEvent();
                              e->read(xml);
                              // tickpos is relative to start of part
                              e->move(-posTick());
                              int tick = e->posTick();
                              if ((tick < 0) || (tick >= lenTick())) {
                                    printf("ReadEvent: warning: event not in part: %d - %d -%d, discarded\n",
                                       0, tick, lenTick());
                                    }
                              else
                                    _events->add(e);
                              }
                        else
                              xml.unknown("Part");
                        break;
                  case Xml::Attribut:
                        if (tag == "cloneId")
                              id = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (tag == "part") {
                              if (id != -1) {
                                    // clone part
                                    if (containsEvents) {
                                          // add to cloneList:
                                          ClonePart cp(_events, id);
                                          cloneList.push_back(cp);
                                          }
                                    else {
                                          // replace event list with clone event
                                          // list
                                          for (iClone i = cloneList.begin();
                                             i != cloneList.end(); ++i) {
                                                if (i->id == id) {
                                                      delete _events;
                                                      _events = (EventList*)(i->el);
                                                      _events->incRef(1);
                                                      _events->incARef(1);
                                                      break;
                                                      }
                                                }
                                          }
                                    }
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   Track::write
//---------------------------------------------------------

void Track::write(int level, Xml& xml) const
      {
      xml.tag(level++, "track type=\"%d\"", _type);
      xml.intTag(level, "height", height());
      xml.intTag(level, "channel", outChannel());
      xml.intTag(level, "device", outPort());
      xml.intTag(level, "inportMap", inPortMask());
      xml.intTag(level, "inchannelMap", inChannelMask());
      xml.strTag(level, "name", _name);
      if (!_comment.isEmpty())
            xml.strTag(level, "comment", _comment);
      xml.intTag(level, "locked", _locked);
      xml.intTag(level, "selected", _selected);
      const PartList* pl = cparts();
      for (ciPart p = pl->begin(); p != pl->end(); ++p)
            p->second->write(level, xml);
      xml.etag(level, "track");
      }

//---------------------------------------------------------
//   Track::read
//---------------------------------------------------------

void Track::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "name")
                              _name = xml.parse1();
                        else if (tag == "comment")
                              _comment = xml.parse1();
                        else if (tag == "selected")
                              _selected = xml.parseInt();
                        else if (tag == "height")
                              _height = xml.parseInt();
                        else if (tag == "locked")
                              _locked = xml.parseInt();
                        else if (tag == "part") {
                              Part* p = newPart();
                              p->read(xml);
                              parts()->add(p);
                              }
                        else if (tag == "wavepart") {
                              WavePart* p = new WavePart((WaveTrack*)this);
                              p->read(xml);
                              parts()->add(p);
                              }
                        else if (tag == "channel")
                              setOutChannel(xml.parseInt());
                        else if (tag == "device")
                              setOutPort(xml.parseInt());
                        else if (tag == "inport")   // old
                              setInPortMask(1 << xml.parseInt());
                        else if (tag == "inchannel") // old
                              setInChannelMask(1 << xml.parseInt());
                        else if (tag == "inportMap")
                              setInPortMask(xml.parseInt());
                        else if (tag == "inchannelMap")
                              setInChannelMask(xml.parseInt());
                        else
                              xml.unknown("Track");
                        break;
                  case Xml::Attribut:
                        if (tag == "type")
                              _type = TrackType(xml.s2().toInt());
                        break;
                  case Xml::TagEnd:
                        if (tag == "track")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   MidiTrack::write
//---------------------------------------------------------

void MidiTrack::write(int level, Xml& xml) const
      {
      xml.tag(level++, "miditrack");
      Track::write(level, xml);

      xml.tag(level++, "staff sys=0");
      key.write(level, xml);
      scale.write(level, xml);
      xml.tag(level--, "/staff");

      xml.tag(level++, "staff sys=1");
      keyL.write(level, xml);
      scaleL.write(level, xml);
      xml.tag(level--, "/staff");

      xml.intTag(level, "noteQuant", noteQuant);
      xml.intTag(level, "restQuant", restQuant);
      xml.intTag(level, "staffmode", int(mode));
      xml.intTag(level, "splitpoint", splitpoint);
      xml.intTag(level, "transposition", transposition);
      xml.intTag(level, "velocity", velocity);
      xml.intTag(level, "delay", delay);
      xml.intTag(level, "len", len);
      xml.intTag(level, "compression", compression);
      xml.intTag(level, "mute", mute());
      xml.intTag(level, "solo", solo());
      xml.intTag(level, "midiThru", midiThruFlag());
      xml.intTag(level, "record", recordFlag());

      xml.tag(level, "/miditrack");
      }

//---------------------------------------------------------
//   readStaff
//---------------------------------------------------------

void MidiTrack::readStaff(Xml& xml)
      {
      int staff = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "key") {
                              if (staff == 0)
                                    key.read(xml);
                              else
                                    keyL.read(xml);
                              }
                        else if (tag == "scale") {
                              if (staff == 0)
                                    scale.read(xml);
                              else
                                    scaleL.read(xml);
                              }
                        else
                              xml.unknown("Staff");
                        break;
                  case Xml::Attribut:
                        if (tag == "sys")
                              staff = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (tag == "staff") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   MidiTrack::read
//---------------------------------------------------------

void MidiTrack::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "track")
                              Track::read(xml);
                        else if (tag == "noteQuant")
                              noteQuant = xml.parseInt();
                        else if (tag == "restQuant")
                              restQuant = xml.parseInt();
                        else if (tag == "splitpoint")
                              splitpoint = xml.parseInt();
                        else if (tag == "staffmode")
                              mode = StaffMode(xml.parseInt());
                        else if (tag == "transposition")
                              transposition = xml.parseInt();
                        else if (tag == "velocity")
                              velocity = xml.parseInt();
                        else if (tag == "delay")
                              delay = xml.parseInt();
                        else if (tag == "len")
                              len = xml.parseInt();
                        else if (tag == "compression")
                              compression = xml.parseInt();
                        else if (tag == "staff")
                              readStaff(xml);
                        else if (SNode::readProperty(xml, tag))
                              ;
                        else if (tag == "midiThru")
                              _midiThruFlag = xml.parseInt();
                        else if (tag == "record")
                              _recordFlag = xml.parseInt();
                        else
                              xml.unknown("MidiTrack");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "miditrack") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

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

void WaveTrack::write(int level, Xml& xml) const
      {
      xml.tag(level++, "wavetrack");
      Track::write(level, xml);
      AudioNode::writeConfiguration(level, xml);
      xml.etag(level, "wavetrack");
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void WaveTrack::read(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "track") {
                              WaveTrack::firstWaveTrack = false;
                              Track::read(xml);
                              }
                        else if (tag == "audionode") {
                              AudioNode::readConfiguration(xml);
                              }
                        else
                              xml.unknown("WaveTrack");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "wavetrack") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   writeFont
//---------------------------------------------------------

void Song::writeFont(int level, Xml& xml, const char* name,
   const Font& font) const
      {
      xml.nput(level, "<%s family=\"%s\" size=\"%d\"",
         name, font.family().latin1(), font.pointSize());
      if (font.weight() != QFont::Normal)
            xml.nput(" weight=\"%d\"", font.weight());
      if (font.italic())
            xml.nput(" italic=\"1\"");
      xml.nput(" />\n");
      }

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

void Song::write(int level, Xml& xml) const
      {
      xml.tag(level++, "song");
      if (!_name.isEmpty())
            xml.strTag(level, "name", _name);
      if (!_komponist1.isEmpty())
            xml.strTag(level, "komponist1", _komponist1);
      if (!_komponist2.isEmpty())
            xml.strTag(level, "komponist2", _komponist2);

      writeFont(level, xml, "name_font", _nameFont);
      writeFont(level, xml, "author_font", _komponistFont);
      writeFont(level, xml, "pageno_font", _pageNoFont);
      writeFont(level, xml, "measureno_font", _measureNoFont);
      writeFont(level, xml, "trackname_font", _tracknameFont);
      writeFont(level, xml, "lyrics_font", _lyricsFont);

      xml.intTag(level, "show_page_no", _showPageNo);
      xml.intTag(level, "show_measure_no", _showMeasureNo);
      xml.doubleTag(level, "paper_width", paperWidth);
      xml.doubleTag(level, "paper_height", paperHeight);
      xml.doubleTag(level, "top_margin", topMargin);
      xml.doubleTag(level, "bottom_margin", bottomMargin);
      xml.doubleTag(level, "left_margin", leftMargin);
      xml.doubleTag(level, "right_margin", rightMargin);
      xml.intTag(level, "bars_page", barsPage);
      xml.doubleTag(level, "print_scale", printScale);

      xml.intTag(level, "show_track_name", _showTrackname);
      xml.intTag(level, "master", _masterFlag);
      xml.intTag(level, "loop", loopFlag);
      xml.intTag(level, "punchin", punchinFlag);
      xml.intTag(level, "punchout", punchoutFlag);
      xml.intTag(level, "record", recordFlag);
      xml.intTag(level, "solo", soloFlag);
      xml.intTag(level, "type", _mtype);
      xml.intTag(level, "recmode", _recMode);
      xml.intTag(level, "cycle", _cycleMode);
      xml.intTag(level, "click", _click);
      xml.intTag(level, "quantize", _quantize);
      xml.intTag(level, "len", _len);
      xml.intTag(level, "follow", _follow);
      if (_globalPitchShift)
            xml.intTag(level, "globalPitchShift", _globalPitchShift);

      if (!_copyright.isEmpty())
            xml.strTag(level, "copyright", _copyright);

      cloneList.clear();
      for (ciTrack i = _tracks.begin(); i != _tracks.end(); ++i)
            (*i)->write(level, xml);
      tempomap.write(level, xml);
      sigmap.write(level, xml);
      _markerList->write(level, xml);

      xml.strTag(level, "AudioInRoute", node2Name(audioInput.route()));
      xml.tag(level, "/song");
      }

//---------------------------------------------------------
//   readFont
//---------------------------------------------------------

Font Song::readFont(Xml& xml, const char* name)
      {
      Font f;
      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return f;
                  case Xml::TagStart:
                        xml.unknown("readFont");
                        break;
                  case Xml::Attribut:
                        if (xml.s1() == "family")
                              f.setFamily(xml.s2());
                        else if (xml.s1() == "size")
                              f.setPointSize(xml.s2().toInt());
                        else if (xml.s1() == "weight")
                              f.setWeight(xml.s2().toInt());
                        else if (xml.s1() == "italic")
                              f.setItalic(xml.s2().toInt());
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == name)
                              return f;
                  default:
                        break;
                  }
            }
      return f;
      }

//---------------------------------------------------------
//   read
//---------------------------------------------------------

void Song::read(Xml& xml)
      {
      cloneList.clear();
      for (;;) {
            Xml::Token token;
            token = xml.parse();
            const QString& tag = xml.s1();
// printf("song <%s>\n", tag.latin1());
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "name")
                              _name = xml.parse1();
                        else if (tag == "name_font")
                              _nameFont = readFont(xml, "name_font");
                        else if (tag == "author_font")
                              _komponistFont = readFont(xml, "author_font");
                        else if (tag == "pageno_font")
                              _pageNoFont = readFont(xml, "pageno_font");
                        else if (tag == "measureno_font")
                              _measureNoFont = readFont(xml, "measureno_font");
                        else if (tag == "trackname_font")
                              _tracknameFont = readFont(xml, "trackname_font");
                        else if (tag == "lyrics_font")
                              _lyricsFont = readFont(xml, "lyrics_font");
                        else if (tag == "komponist1")
                              _komponist1 = xml.parse1();
                        else if (tag == "komponist2")
                              _komponist2 = xml.parse1();
                        else if (tag == "show_page_no")
                              _showPageNo = xml.parseInt();
                        else if (tag == "show_measure_no")
                              _showMeasureNo  = xml.parseInt();
                        else if (tag == "show_track_name")
                              _showTrackname  = xml.parseInt();
                        else if (tag == "paper_width")
                              paperWidth  = xml.parseDouble();
                        else if (tag == "paper_height")
                              paperHeight  = xml.parseDouble();
                        else if (tag == "top_margin")
                              topMargin  = xml.parseDouble();
                        else if (tag == "bottom_margin")
                              bottomMargin  = xml.parseDouble();
                        else if (tag == "left_margin")
                              leftMargin  = xml.parseDouble();
                        else if (tag == "right_margin")
                              rightMargin  = xml.parseDouble();
                        else if (tag == "bars_page")
                              barsPage  = xml.parseInt();
                        else if (tag == "print_scale")
                              printScale  = xml.parseDouble();
                        else if (tag == "master")
                              setMasterFlag(xml.parseInt());
                        else if (tag == "loop")
                              setLoop(xml.parseInt());
                        else if (tag == "punchin")
                              setPunchin(xml.parseInt());
                        else if (tag == "punchout")
                              setPunchout(xml.parseInt());
                        else if (tag == "record")
                              setRecord(xml.parseInt());
                        else if (tag == "solo")
                              soloFlag = xml.parseInt();
                        else if (tag == "type")
                              _mtype  = MType(xml.parseInt());
                        else if (tag == "recmode")
                              _recMode  = xml.parseInt();
                        else if (tag == "cycle")
                              _cycleMode  = xml.parseInt();
                        else if (tag == "click")
                              setClick(xml.parseInt());
                        else if (tag == "quantize")
                              _quantize  = xml.parseInt();
                        else if (tag == "len")
                              _len  = xml.parseInt();
                        else if (tag == "follow")
                              _follow  = FollowMode(xml.parseInt());
                        else if (tag == "copyright")
                              _copyright  = xml.parse(QString("copyright"));
                        else if (tag == "tempolist") {
                              tempomap.read(xml);
                              }
                        else if (tag == "siglist")
                              sigmap.read(xml);
                        else if (tag == "miditrack") {
                              MidiTrack* track = new MidiTrack();
                              track->read(xml);
                              _tracks.add(track);
                              }
                        else if (tag == "wavetrack") {
                              WaveTrack* track = new WaveTrack();
                              track->read(xml);
                              _tracks.add(track);
                              }
                        else if (tag == "marker")
                              readMarker(xml);
                        else if (tag == "AudioInRoute") {
                              QString s = xml.parse1();
                              AudioNode* n = name2Node(s);
                              if (n)
                                    audioInput.connectOut(n);
                              }
                        else if (tag == "globalPitchShift")
                              _globalPitchShift = xml.parseInt();
                        else
                              xml.unknown("Song");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "song") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      dirty = false;
      }

//---------------------------------------------------------
//   readPart
//---------------------------------------------------------

Part* MusE::readPart(Xml& xml)
      {
      Part* part = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return part;
                  case Xml::Text:
                        {
                        int trackIdx, partIdx;
                        sscanf(tag.latin1(), "%d:%d", &trackIdx, &partIdx);
                        Track* track = song->tracks()->findIdx(trackIdx);
                        if (track)
                              part = track->parts()->find(partIdx);
                        }
                        break;
                  case Xml::TagStart:
                        xml.unknown("readPart");
                        break;
                  case Xml::TagEnd:
                        if (tag == "part")
                              return part;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readToplevels
//---------------------------------------------------------

void MusE::readToplevels(Xml& xml)
      {
      PartList* pl = new PartList;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "part") {
                              Part* part = readPart(xml);
                              if (part)
                                    pl->add(part);
                              }
                        else if (tag == "pianoroll") {
                              startPianoroll(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "drumedit") {
                              startDrumEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "midimixer") {
                              startMidiMixer();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "listeditor") {
                              startListEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "scoreeditor") {
                              startScoreEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "master") {
                              startMasterEditor();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "lmaster") {
                              startLMasterEditor();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "audiomixer") {
                              startAudioMixer();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "marker") {
                              startMarkerView();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else if (tag == "waveedit") {
                              startWaveEditor(pl);
                              toplevels.back().cobject()->readStatus(xml);
                              pl = new PartList;
                              }
                        else if (tag == "cliplist") {
                              startClipList();
                              toplevels.back().cobject()->readStatus(xml);
                              }
                        else
                              xml.unknown("MusE");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "toplevels") {
                              delete pl;
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readCtrl
//---------------------------------------------------------

void MusE::readCtrl(Xml& xml, int prt, int channel)
      {
      ChannelState* iState = midiPorts[prt].iState(channel);

      int idx = 0;
      int val = -1;

      for (;;) {
            Xml::Token token = xml.parse();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        xml.unknown("readCtrl");
                        break;
                  case Xml::Attribut:
                        if (xml.s1() == "idx")
                              idx = xml.s2().toInt();
                        else if (xml.s1() == "val")
                              val = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (xml.s1() == "ctrl") {
                              iState->controller[idx] = val;
// printf("%d %d ctrl %d val %d\n", prt, channel, idx, val);
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMidichannel
//---------------------------------------------------------

void MusE::readMidichannel(Xml& xml, int prt)
      {
      int channel = 0;
      MidiPort* port = &midiPorts[prt];
      ChannelState* iState = 0;

      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "pitch")
                              iState->pitch = xml.parseInt();
                        else if (tag == "program") {
                              iState->program = xml.parseInt();
                              }
                        else if (tag == "ctrl")
                              readCtrl(xml, prt, channel);
                        else {
                              xml.unknown("readMidichannel");
                              }
                        break;
                  case Xml::Attribut:
                        if (tag == "ch") {
                              channel = xml.s2().toInt();
                              iState = port->iState(channel);
                              }
                        break;
                  case Xml::TagEnd:
                        if (tag == "midichannel")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMidiport
//---------------------------------------------------------

void MusE::readMidiport(Xml& xml)
      {
      int port = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "midichannel")
                              readMidichannel(xml, port);
                        else {
                              xml.unknown("readMidiport");
                              }
                        break;
                  case Xml::Attribut:
                        if (tag == "port") {
                              port = xml.s2().toInt();
                              }
                        break;
                  case Xml::TagEnd:
                        if (tag == "midiport") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   write
//    write song
//---------------------------------------------------------

void MusE::write(Xml& xml) const
      {
      xml.header();

      int level = 0;
      xml.tag(level++, "muse version=\"1.0\"");
      writeConfiguration(level, xml);

      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* port = &midiPorts[i];
            bool active = false;
            for (int k = 0; k < MIDI_CHANNELS; ++k) {
                  ChannelState* iState = port->iState(k);
                  if ((iState->pitch != -1) || (iState->program != -1))
                        active = true;
                  for (int l = 0; l < 128; ++l) {
                        if (iState->controller[l] != -1)
                              active = true;
                        }
                  }
            if (!active)
                  continue;

            xml.tag(level++, "midiport port=\"%d\"", i);

            for (int k = 0; k < MIDI_CHANNELS; ++k) {
                  bool active = false;
                  ChannelState* iState = port->iState(k);
                  if ((iState->pitch != -1) || (iState->program != -1))
                        active = true;
                  for (int l = 0; l < 128; ++l) {
                        if (iState->controller[l] != -1)
                              active = true;
                        }
                  if (!active)
                        continue;

                  xml.tag(level++, "midichannel ch=\"%d\"", k);
                  if (iState->pitch != -1)
                        xml.intTag(level, "pitch", iState->pitch);
                  if (iState->program != -1)
                        xml.intTag(level, "program", iState->program);
                  for (int l = 0; l < 128; ++l) {
                        if (iState->controller[l] != -1)
                              xml.tag(level, "ctrl idx=\"%d\" val=\"%d\" /", l,
                                 iState->controller[l]);
                        }
                  xml.etag(level--, "midichannel");
                  }
            xml.etag(level--, "midiport");
            }

      waveClips->write(level, xml);
      writeMidiController(level, xml);
      writeStatusMidiInputTransformPlugins(level, xml);

      song->write(level, xml);

      xml.intTag(level, "cpos", song->cpos());
      xml.intTag(level, "rpos", song->rpos());
      xml.intTag(level, "lpos", song->lpos());

      if (!toplevels.empty()) {
            xml.tag(level++, "toplevels");
            for (ciToplevel i = toplevels.begin(); i != toplevels.end(); ++i) {
                  if (i->cobject()->isVisible())
                        i->cobject()->writeStatus(level, xml);
                  }
            xml.tag(level--, "/toplevels");
            }
      writeDrumMap(level, xml, false);
      xml.tag(level, "/muse");
      }

//---------------------------------------------------------
//   readMarker
//---------------------------------------------------------

void Song::readMarker(Xml& xml)
      {
      Marker m;
      m.read(xml);
      _markerList->add(m);
      }

//---------------------------------------------------------
//   read
//    read song
//---------------------------------------------------------

void MusE::read(Xml& xml)
      {
      bool skipmode = true;
      int pos;
      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (skipmode && tag == "muse")
                              skipmode = false;
                        else if (skipmode)
                              break;
                        else if (tag == "configuration")
                              readConfiguration(xml);
                        else if (tag == "song")
                              song->read(xml);
                        else if (tag == "cpos") {
                              pos = xml.parseInt();
                              song->setPos(Song::CPOS, pos, true, true, true);
                              }
                        else if (tag == "lpos") {
                              pos = xml.parseInt();
                              song->setPos(Song::LPOS, pos, true, true, true);
                              }
                        else if (tag == "rpos") {
                              pos = xml.parseInt();
                              song->setPos(Song::RPOS, pos, true, true, true);
                              }
                        else if (tag == "midiport")
                              readMidiport(xml);
                        else if (tag == "toplevels")
                              readToplevels(xml);
                        else if (tag == "drummap")
                              readDrumMap(xml, false);
                        else if (tag == "clip") {
                              Clip* clip = new Clip();
                              clip->read(xml);
                              // if file not found, remove clip
//                              if (clip->file() == 0)
//DEBUG                                    delete clip;
                              }
                        else if (tag == "mctrl") {
                              MidiController* ctrl = readMidiController(xml);
                              midiControllerList.push_back(ctrl);
                              }
                        else if (tag == "mplugin")
                              readStatusMidiInputTransformPlugin(xml);
                        else
                              xml.unknown("muse");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (!skipmode && tag == "muse") {
                              initDrumMap();
                              return;
                              }
                  default:
                        break;
                  }
            }
      }
