//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: seq.cpp,v 1.1.1.1 2003/10/29 10:05:04 wschweer Exp $
//
//  (C) Copyright 1999-2002 Werner Schweer (ws@seh.de)
//=========================================================

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <assert.h>
#include <fcntl.h>

#include <qstring.h>
#include <qtimer.h>

#include "seq.h"
#include "xml.h"
#include "song.h"
#include "midi.h"
#include "globals.h"
#include "plugins/plugin.h"
#include "midictrl.h"
#include "midiport.h"
#include "driver/mididev.h"
#include "driver/audiodev.h"
#include "event.h"
#include "minstrument.h"
#include "driver/alsamidi.h"
#include "synth.h"
#include "audio.h"
#include "audioprefetch.h"
#include "midithread.h"
#include "node.h"
#include "driver/jackaudio.h"
#include "wave.h"
#include <sndfile.h>

Sequencer* seq;

//---------------------------------------------------------
//   Sequencer
//---------------------------------------------------------

Sequencer::Sequencer()
      {
      if (realTimePriority < sched_get_priority_min(SCHED_FIFO))
            realTimePriority = sched_get_priority_min(SCHED_FIFO);
      else if (realTimePriority > sched_get_priority_max(SCHED_FIFO))
            realTimePriority = sched_get_priority_max(SCHED_FIFO);

      midiThread = new MidiThread(realTimeScheduling ? realTimePriority : 0, "Midi");
      audio = new Audio();
      audioPrefetch = new AudioPrefetch(realTimeScheduling ? realTimePriority/4 : 0,
         "AudioDisc");
      }

int watchAudio, watchAudioPrefetch, watchMidi, watchALSA;

//---------------------------------------------------------
//   watchdog thread
//---------------------------------------------------------

static void* watchdog(void*)
      {
      int policy;
      if ((policy = sched_getscheduler (0)) < 0) {
            printf("Cannot get current client scheduler: %s\n", strerror(errno));
            }
      if (policy != SCHED_FIFO)
            printf("watchdog _NOT_ running SCHED_FIFO\n");
      else if (debugMsg)
            printf("watchdog set to SCHED_FIFO priority %d\n",
               sched_get_priority_max(SCHED_FIFO));

      int fatal = 0;
      for (;;) {
            watchAudio = 0;
            watchMidi = 0;

            sleep(3);

            bool timeout = false;
            if (watchMidi == 0)
                  timeout = true;
            if (!noAudio) {
                  if (!noAudio) {
                        if (watchAudio == 0)
                              timeout = true;
                        if (watchAudio > 500000)
                              timeout = true;
                        }
                  }
            if (timeout)
                  ++fatal;
            else
                  fatal = 0;
            if (fatal >= 3) {
                  printf("WatchDog: fatal error, realtime task timeout\n");
                  printf("   (%d,%d-%d) - stopping all services\n",
                     watchMidi, watchAudio, fatal);
                  break;
                  }
//            printf("wd %d %d %d\n", watchMidi, watchAudio, fatal);
            }
      midiThread->stop(true);
      if (!noAudio) {
            audioPrefetch->stop(true);
            audio->stop(true);
            }
      exit(-1);
      }

//---------------------------------------------------------
//   start
//---------------------------------------------------------

void Sequencer::start()
      {
      if (realTimeScheduling) {
            doSetuid();
            struct sched_param rt_param;
            memset(&rt_param, 0, sizeof(rt_param));
            rt_param.sched_priority = sched_get_priority_max(SCHED_FIFO);

            pthread_attr_t* attributes = (pthread_attr_t*) malloc(sizeof(pthread_attr_t));
            pthread_attr_init(attributes);

            if (pthread_attr_setschedpolicy(attributes, SCHED_FIFO)) {
                  printf("cannot set FIFO scheduling class for RT thread\n");
                  }
            if (pthread_attr_setschedparam (attributes, &rt_param)) {
                  printf("Cannot set scheduling priority for RT thread (%s)\n", strerror(errno));
                  }
            if (pthread_attr_setscope (attributes, PTHREAD_SCOPE_SYSTEM)) {
                  printf("Cannot set scheduling scope for RT thread\n");
                  }
            if (pthread_create(&watchdogThread, attributes, ::watchdog, this)) {
                  perror("creating watchdog thread failed:");
                  }
            pthread_attr_destroy(attributes);
            undoSetuid();
            }
      if (!noAudio) {
            audio->start();
            audioPrefetch->start();
            }
      midiThread->start();
      }

//---------------------------------------------------------
//   isRunning
//---------------------------------------------------------

bool Sequencer::isRunning()
      {
      return midiThread->isRunning();
      }

//---------------------------------------------------------
//   stop
//---------------------------------------------------------

void Sequencer::stop()
      {
      song->setStopPlay(false);
      midiThread->stop(true);
      if (!noAudio) {
            audioPrefetch->stop(true);
            audio->stop(true);
            }
      if (realTimeScheduling)
            pthread_cancel(watchdogThread);
      }

//---------------------------------------------------------
//   Sequencer
//---------------------------------------------------------

Sequencer::~Sequencer()
      {
      delete audioPrefetch;
      delete audio;
      delete midiThread;
      }

//---------------------------------------------------------
//   writeConfiguration
//---------------------------------------------------------

void Sequencer::writeConfiguration(int level, Xml& xml)
      {
      xml.tag(level++, "sequencer");

      xml.tag(level++, "metronom");
      xml.intTag(level, "premeasures", preMeasures);
      xml.intTag(level, "measurepitch", measureClickNote);
      xml.intTag(level, "measurevelo", measureClickVelo);
      xml.intTag(level, "beatpitch", beatClickNote);
      xml.intTag(level, "beatvelo", beatClickVelo);
      xml.intTag(level, "channel", clickChan);
      xml.intTag(level, "port", clickPort);

      xml.intTag(level, "precountEnable", precountEnableFlag);
      xml.intTag(level, "fromMastertrack", precountFromMastertrackFlag);
      xml.intTag(level, "signatureZ", precountSigZ);
      xml.intTag(level, "signatureN", precountSigN);
      xml.intTag(level, "prerecord", precountPrerecord);
      xml.intTag(level, "preroll", precountPreroll);
      xml.intTag(level, "midiClickEnable", midiClickFlag);
      xml.intTag(level, "audioClickEnable", audioClickFlag);
      xml.tag(level--, "/metronom");

      xml.intTag(level, "rcEnable",   rcEnable);
      xml.intTag(level, "rcStop",     rcStopNote);
      xml.intTag(level, "rcRecord",   rcRecordNote);
      xml.intTag(level, "rcGotoLeft", rcGotoLeftMarkNote);
      xml.intTag(level, "rcPlay",     rcPlayNote);

      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* mport = &midiPorts[i];
            MidiDevice* dev = mport->device();
            if (dev) {
                  xml.tag(level++, "midiport idx=\"%d\"", i);
                  xml.strTag(level, "instrument", mport->instrument()->iname());
                  xml.strTag(level, "name",   dev->name());
                  xml.intTag(level, "record", dev->rwFlags() & 0x2 ? 1 : 0);
                  xml.etag(level--, "midiport");
                  }
            }

      for (int i = 0; i < mixerGroups; ++i) {
            xml.tag(level++, "audiogroup idx=\"%d\"", i);
            audioGroups[i].writeConfiguration(level, xml);
            xml.tag(level--, "/audiogroup");
            }

      xml.tag(level++, "audiomaster");
      audioOutput.writeConfiguration(level, xml);
      xml.tag(level--, "/audiomaster");

      xml.tag(level++, "audioinput");
      audioInput.writeConfiguration(level, xml);
      xml.tag(level--, "/audioinput");

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

//---------------------------------------------------------
//   loadConfigMetronom
//---------------------------------------------------------

void Sequencer::loadConfigMetronom(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            QString tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "premeasures")
                              preMeasures = xml.parseInt();
                        else if (tag == "measurepitch")
                              measureClickNote = xml.parseInt();
                        else if (tag == "measurevelo")
                              measureClickVelo = xml.parseInt();
                        else if (tag == "beatpitch")
                              beatClickNote = xml.parseInt();
                        else if (tag == "beatvelo")
                              beatClickVelo = xml.parseInt();
                        else if (tag == "channel")
                              clickChan = xml.parseInt();
                        else if (tag == "port")
                              clickPort = xml.parseInt();
                        else if (tag == "precountEnable")
                              precountEnableFlag = xml.parseInt();
                        else if (tag == "fromMastertrack")
                              precountFromMastertrackFlag = xml.parseInt();
                        else if (tag == "signatureZ")
                              precountSigZ = xml.parseInt();
                        else if (tag == "signatureN")
                              precountSigN = xml.parseInt();
                        else if (tag == "prerecord")
                              precountPrerecord = xml.parseInt();
                        else if (tag == "preroll")
                              precountPreroll = xml.parseInt();
                        else if (tag == "midiClickEnable")
                              midiClickFlag = xml.parseInt();
                        else if (tag == "audioClickEnable")
                              audioClickFlag = xml.parseInt();
                        else
                              xml.unknown("Metronome");
                        break;
                  case Xml::TagEnd:
                        if (tag == "metronom")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfiguration
//---------------------------------------------------------

void Sequencer::readConfiguration(Xml& xml)
      {
      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "metronom")
                              loadConfigMetronom(xml);
                        else if (tag == "midiport")
                              readConfigMidiPort(xml);
                        else if (tag == "audioport")
                              readConfigAudioPort(xml);
                        else if (tag == "audiogroup")
                              readConfigAudioGroup(xml);
                        else if (tag == "audiomaster")
                              readConfigAudioMaster(xml);
                        else if (tag == "audioinput")
                              readConfigAudioInput(xml);
                        else if (tag == "rcStop")
                              rcStopNote = xml.parseInt();
                        else if (tag == "rcEnable")
                              rcEnable = xml.parseInt();
                        else if (tag == "rcRecord")
                              rcRecordNote = xml.parseInt();
                        else if (tag == "rcGotoLeft")
                              rcGotoLeftMarkNote = xml.parseInt();
                        else if (tag == "rcPlay")
                              rcPlayNote = xml.parseInt();
                        else
                              xml.unknown("Seq");
                        break;
                  case Xml::TagEnd:
                        if (tag == "sequencer") {
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readMixdown
//---------------------------------------------------------

void Sequencer::readMixdown(Xml& xml)
      {
      QString path;
      int channels   = 2;
      int format     = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
      bool record    = false;

      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "path")
                              path = xml.parse1();
                        else if (tag == "channels")
                              channels = xml.parseInt();
                        else if (tag == "format")
                              format = xml.parseInt();
                        else if (tag == "samplebits")
                              ;
                        else if (tag == "record")
                              record = xml.parseInt();
                        else
                              xml.unknown("mixdown");
                        break;
                  case Xml::TagEnd:
                        if (tag == "mixdown") {
                              SndFile* sf = getWave(path, true);
                              sf->setFormat(format, channels, sampleRate);
//TODO                              audio->msgSetRecFile(&audioOutput, sf);
//                              if (record)
//                                    audio->msgSetRecord(&audioOutput, true);
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfigMidiPort
//---------------------------------------------------------

void Sequencer::readConfigMidiPort(Xml& xml)
      {
      int idx = 0;
      QString device;
      QString instrument;
      bool recFlag = false;
      bool thruFlag = false;

      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            QString tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "name")
                              device = xml.parse1();
                        else if (tag == "record")
                              recFlag = xml.parseInt();
                        else if (tag == "instrument")
                              instrument = xml.parse1();
                        else if (tag == "midithru")
                              thruFlag = xml.parseInt(); // obsolete
                        else
                              xml.unknown("MidiDevice");
                        break;
                  case Xml::Attribut:
                        if (tag == "idx") {
                              idx = xml.s2().toInt();
                              }
                        break;
                  case Xml::TagEnd:
                        if (tag == "midiport") {
                              if (idx > MIDI_PORTS) {
                                    fprintf(stderr, "bad midi port %d (>%d)\n",
                                       idx, MIDI_PORTS);
                                    idx = 0;
                                    }
                              MidiDevice* dev = midiDevices.find(device);
                              MidiPort* mp = &midiPorts[idx];
                              if (dev) {
                                    mp->setrwFlags(recFlag ? 3 : 1);
                                    midiThread->setMidiDevice(mp, dev);
                                    }
                              midiPorts[idx].setInstrument(
                                 registerMidiInstrument(instrument)
                                 );
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfigAudioPort
//---------------------------------------------------------

void Sequencer::readConfigAudioPort(Xml& xml)
      {
      QString device;
      AudioDevice* dev = 0;
      AudioNode* node = &audioOutput;
      int rwFlags = 0;

      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            QString tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "name")
                              device = xml.parse1();
                        else if (tag == "record")
                              rwFlags |= xml.parseInt() ? 2 : 0;
                        else if (tag == "play")
                              rwFlags |= xml.parseInt() ? 1 : 0;
                        else if (tag == "audionode")
                              node->readConfiguration(xml);
                        else
                              xml.unknown("AudioDevice");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "audioport") {
/*                              if (useAlsaAudio && !device.isEmpty()) {
                                    dev = audioDevices.find(device);
                                    if (dev) {
                                          port->setrwFlags(rwFlags);
                                          audio->msgSetAudioDevice(port, dev);
                                          }
                                    audio->msgAddRoute(port, port->route());
                                    }
                              else*/ if (useJackAudio) {
#if HAVE_JACK
                                    dev = jackAudioDevice;
                                    if (dev) {
//                                        node->setrwFlags(rwFlags);
//                                        jackAudioDevice->stop();
//                                        audio->msgSetAudioDevice(port, dev);
//                                        jackAudioDevice->start();
                                          }
#endif
                                    }
                        return;
                        }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfigAudioGroup
//---------------------------------------------------------

void Sequencer::readConfigAudioGroup(Xml& xml)
      {
      AudioNode* mixer = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            QString tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "audionode")
                              mixer->readConfiguration(xml);
                        else
                              xml.unknown("AudioGroup");
                        break;
                  case Xml::Attribut:
                        if (tag == "idx") {
                              int idx = xml.s2().toInt();
                              if (idx > mixerGroups) {
                                    fprintf(stderr, "bad audio group %d (>%d)\n",
                                       idx, AUDIO_GROUPS);
                                    idx = 0;
                                    }
                              mixer = &audioGroups[idx];
                              }
                        break;
                  case Xml::TagEnd:
                        if (tag == "audiogroup") {
//TODO                              audio->msgAddRoute(mixer, mixer->route());
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfigAudioMaster
//---------------------------------------------------------

void Sequencer::readConfigAudioMaster(Xml& xml)
      {
      AudioNode* mixer = &audioOutput;
      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            QString tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "audionode")
                              mixer->readConfiguration(xml);
                        else
                              xml.unknown("AudioMaster");
                        break;
                  case Xml::TagEnd:
                        if (tag == "audiomaster")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfigAudioInput
//---------------------------------------------------------

void Sequencer::readConfigAudioInput(Xml& xml)
      {
      AudioNode* mixer = &audioInput;
      for (;;) {
            Xml::Token token = xml.parse();
            if (token == Xml::Error || token == Xml::End)
                  break;
            QString tag = xml.s1();
            switch (token) {
                  case Xml::TagStart:
                        if (tag == "audionode")
                              mixer->readConfiguration(xml);
                        else
                              xml.unknown("AudioInput");
                        break;
                  case Xml::TagEnd:
                        if (tag == "audioinput")
                              return;
                  default:
                        break;
                  }
            }
      }

