//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: audio.cpp,v 1.59.2.2 2004/12/28 23:17:17 lunar_shuttle Exp $
//
//  (C) Copyright 2001-2004 Werner Schweer (ws@seh.de)
//=========================================================

#include <cmath>
#include <errno.h>

#include <qsocketnotifier.h>

#include "app.h"
#include "song.h"
#include "node.h"
#include "audiodev.h"
#include "mididev.h"
#include "alsamidi.h"
#include "synth.h"
#include "audioprefetch.h"
#include "plugin.h"
#include "audio.h"
#include "wave.h"
#include "midictrl.h"
#include "midiseq.h"
#include "sync.h"
#include "midi.h"
#include "event.h"
#include "gconfig.h"

extern double curTime();
Audio* audio;
AudioDevice* audioDevice;   // current audio device in use

static const unsigned char mmcDeferredPlayMsg[] = { 0x7f, 0x7f, 0x06, 0x03 };
static const unsigned char mmcStopMsg[] =         { 0x7f, 0x7f, 0x06, 0x01 };

const char* seqMsgList[] = {
      "SEQM_ADD_TRACK", "SEQM_REMOVE_TRACK", "SEQM_CHANGE_TRACK", "SEQM_MOVE_TRACK",
      "SEQM_ADD_PART", "SEQM_REMOVE_PART", "SEQM_CHANGE_PART",
      "SEQM_ADD_EVENT", "SEQM_REMOVE_EVENT", "SEQM_CHANGE_EVENT",
      "SEQM_ADD_TEMPO", "SEQM_SET_TEMPO", "SEQM_REMOVE_TEMPO", "SEQM_ADD_SIG", "SEQM_REMOVE_SIG",
      "SEQM_SET_GLOBAL_TEMPO",
      "SEQM_UNDO", "SEQM_REDO",
      "SEQM_RESET_DEVICES", "SEQM_INIT_DEVICES", "SEQM_PANIC",
      "SEQM_MIDI_LOCAL_OFF",
      "SEQM_SET_MIDI_DEVICE",
      "SEQM_PLAY_MIDI_EVENT",
      "SEQM_SCAN_ALSA_MIDI_PORTS",
      "SEQM_SET_AUX",
      "MIDI_SHOW_INSTR_GUI",
      "AUDIO_RECORD",
      "AUDIO_ROUTEADD", "AUDIO_ROUTEREMOVE",
      "AUDIO_VOL", "AUDIO_PAN",
      "AUDIO_ADDPLUGIN",
      "AUDIO_SET_SEG_SIZE",
      "AUDIO_SET_PREFADER", "AUDIO_SET_CHANNELS",
      "MS_PROCESS", "MS_STOP", "MS_SET_RTC", "MS_UPDATE_POLL_FD",
      "SEQM_IDLE", "SEQM_SEEK"
      };

const char* audioStates[] = {
      "STOP", "START_PLAY", "PLAY", "LOOP1", "LOOP2", "SYNC", "PRECOUNT"
      };


//---------------------------------------------------------
//   Audio
//---------------------------------------------------------

Audio::Audio()
      {
      _running      = false;
      recording     = false;
      idle          = false;
      _freewheel    = false;
      _bounce       = false;
      loopPassed    = false;

      _pos.setType(Pos::FRAMES);
      _pos.setFrame(0);
      curTickPos    = 0;

      midiClick     = 0;
      clickno       = 0;
      clicksMeasure = 0;
      ticksBeat     = 0;

      syncTime      = 0.0;
      syncFrame     = 0;
      frameOffset   = 0;

      state         = STOP;
      msg           = 0;

      startRecordPos.setType(Pos::TICKS);
      endRecordPos.setType(Pos::TICKS);

      _audioMonitor = 0;
      _audioMaster  = 0;

      //---------------------------------------------------
      //  establish pipes/sockets
      //---------------------------------------------------

      int filedes[2];         // 0 - reading   1 - writing
      if (pipe(filedes) == -1) {
            perror("creating pipe0");
            exit(-1);
            }
      fromThreadFdw = filedes[1];
      fromThreadFdr = filedes[0];
      int rv = fcntl(fromThreadFdw, F_SETFL, O_NONBLOCK);
      if (rv == -1)
            perror("set pipe O_NONBLOCK");

      if (pipe(filedes) == -1) {
            perror("creating pipe1");
            exit(-1);
            }
      sigFd = filedes[1];
      QSocketNotifier* ss = new QSocketNotifier(filedes[0], QSocketNotifier::Read);
      song->connect(ss, SIGNAL(activated(int)), song, SLOT(seqSignal(int)));
      }

//---------------------------------------------------------
//   start
//    start audio processing
//---------------------------------------------------------

void Audio::start()
      {
      process(segmentSize);   // warm up caches
      state = STOP;
      muse->setHeartBeat();
      audioDevice->start();
      _running = true;

      // shall we really stop JACK transport and locate to
      // saved position?

      audioDevice->stopTransport();
      audioDevice->seekTransport(song->cPos().frame());
      }

//---------------------------------------------------------
//   stop
//    stop audio processing
//---------------------------------------------------------

void Audio::stop(bool)
      {
      audioDevice->stop();
      _running = false;
      }

//---------------------------------------------------------
//   sync
//    return true if sync is completed
//---------------------------------------------------------

bool Audio::sync(int jackState, unsigned frame)
      {
// printf("sync state %s jackState %s frame %d\n", audioStates[state], audioStates[jackState], frame);
      bool done = true;
      if (state == LOOP1)
            state = LOOP2;
      else {
            if (_pos.frame() != frame) {
                  Pos p(frame, false);
                  seek(p);
                  }
            state = State(jackState);
            if (!_freewheel)
                  done = audioPrefetch->seekDone;
            }
      return done;
      }

//---------------------------------------------------------
//   setFreewheel
//---------------------------------------------------------

void Audio::setFreewheel(bool val)
      {
// printf("JACK: freewheel callback %d\n", val);
      _freewheel = val;
      }

//---------------------------------------------------------
//   shutdown
//---------------------------------------------------------

void Audio::shutdown()
      {
      _running = false;
      printf("JACK shutdown callback\n");
      write(sigFd, "S", 1);
      }

//---------------------------------------------------------
//   process
//    process one audio buffer at position "_pos "
//    of size "frames"
//---------------------------------------------------------

void Audio::process(unsigned frames)
      {
      extern int watchAudio;
      ++watchAudio;           // make a simple watchdog happy
      if (msg) {
            processMsg(msg);
            int sn = msg->serialNo;
            msg    = 0;    // dont process again
            int rv = write(fromThreadFdw, &sn, sizeof(int));
            if (rv != sizeof(int)) {
                  fprintf(stderr, "audio: write(%d) pipe failed: %s\n",
                     fromThreadFdw, strerror(errno));
                  }
            }

      OutputList* ol = song->outputs();
      if (idle) {
            // deliver no audio
            for (iAudioOutput i = ol->begin(); i != ol->end(); ++i)
                  (*i)->silence(frames);
            return;
            }

      int jackState = audioDevice->getState();

      if (state == START_PLAY && jackState == PLAY) {
            startRolling();
            if (_bounce)
                  write(sigFd, "f", 1);
            }
      else if (state == LOOP2 && jackState == PLAY) {
            Pos newPos(loopFrame, false);
            seek(newPos);
            startRolling();
            }
      else if (isPlaying() && jackState == STOP) {
            stopRolling();
            }
      else if (state == START_PLAY && jackState == STOP) {
            state = STOP;
            if (_bounce) {
                  audioDevice->startTransport();
                  }
            else
                  write(sigFd, "0", 1);   // STOP
            }
      else if (state == STOP && jackState == PLAY) {
            startRolling();
            }
      else if (state == LOOP1 && jackState == PLAY)
            ;     // treat as play
      else if (state == LOOP2 && jackState == START_PLAY)
            ;     // sync cycle, treat as play
      else if (state != jackState)
            printf("JACK: state transition %s -> %s ?\n",
               audioStates[state], audioStates[jackState]);

// printf("p %s %s %d\n", audioStates[jackState], audioStates[state], _pos.frame());

      //
      // clear aux send buffers
      //
      AuxList* al = song->auxs();
      for (unsigned i = 0; i < al->size(); ++i) {
            AudioAux* a = (AudioAux*)((*al)[i]);
            float** dst = a->sendBuffer();
            for (int ch = 0; ch < a->channels(); ++ch)
                  memset(dst[ch], 0, sizeof(float) * segmentSize);
            }

      for (iAudioOutput i = ol->begin(); i != ol->end(); ++i)
            (*i)->processInit(frames);
      int samplePos = _pos.frame();
      int offset    = 0;      // buffer offset in audio buffers

      if (isPlaying()) {
            if (!freewheel())
                  audioPrefetch->msgTick();

            if (_bounce && _pos >= song->rPos()) {
                  _bounce = false;
                  write(sigFd, "F", 1);
                  return;
                  }

            //
            //  check for end of song
            //
            if ((curTickPos >= song->len())
               && !(song->record()
                || _bounce
                || song->loop())) {
                  audioDevice->stopTransport();
                  return;
                  }

            //
            //  check for loop end
            //
            if (state == PLAY && song->loop() && !_bounce && !extSyncFlag.value()) {
                  const Pos& loop = song->rPos();
                  unsigned n = loop.frame() - samplePos - (3 * frames);
                  if (n < frames) {
                        // loop end in current cycle
                        unsigned lpos = song->lPos().frame();
                        // adjust loop start so we get exact loop len
                        if (n > lpos)
                              n = 0;
                        state = LOOP1;
                        loopFrame = lpos - n;
                        audioDevice->seekTransport(loopFrame);
// printf("  process: seek to %d, end %d\n", loopFrame, loop.frame());
                        }
                  }
            Pos ppp(_pos);
            ppp += frames;
            nextTickPos = ppp.tick();
            }
      //
      // resync with audio interface
      //
      syncFrame   = audioDevice->framePos();
      syncTime    = curTime();
      frameOffset = syncFrame - samplePos;

      process1(samplePos, offset, frames);
      for (iAudioOutput i = ol->begin(); i != ol->end(); ++i)
            (*i)->processWrite();
      if (isPlaying()) {
            _pos += frames;
            curTickPos = nextTickPos;
            }
      }

//---------------------------------------------------------
//   process1
//---------------------------------------------------------

void Audio::process1(unsigned samplePos, unsigned offset, unsigned frames)
      {
      midiSeq->msgProcess();
      OutputList* ol = song->outputs();
      for (iAudioOutput i = ol->begin(); i != ol->end(); ++i) {
            (*i)->process(samplePos, offset, frames);
            }

      //
      // process not connected tracks
      // to animate meter display
      //
      TrackList* tl = song->tracks();
      for (iTrack it = tl->begin(); it != tl->end(); ++it) {
            if ((*it)->isMidiTrack())
                  continue;
            AudioTrack* track = (AudioTrack*)(*it);
            if (track->noOutRoute() && !track->noInRoute() && track->type() != Track::AUDIO_OUTPUT) {
                  int channels = track->channels();
                  float* buffer[channels];
                  float data[frames * channels];
                  for (int i = 0; i < channels; ++i)
                        buffer[i] = data + i * frames;
                  track->copyData(samplePos, channels, frames, buffer);
                  }
            }
      }

//---------------------------------------------------------
//   processMsg
//---------------------------------------------------------

void Audio::processMsg(AudioMsg* msg)
      {
      switch(msg->id) {
            case AUDIO_RECORD:
                  msg->snode->setRecordFlag2(msg->ival);
                  break;
            case AUDIO_ROUTEADD:
                  addRoute(msg->sroute, msg->droute);
                  break;
            case AUDIO_ROUTEREMOVE:
                  removeRoute(msg->sroute, msg->droute);
                  break;
            case AUDIO_VOL:
                  msg->snode->setVolume(msg->dval);
                  break;
            case AUDIO_PAN:
                  msg->snode->setPan(msg->dval);
                  break;
            case SEQM_SET_AUX:
                  msg->snode->setAuxSend(msg->ival, msg->dval);
                  break;
            case AUDIO_SET_PREFADER:
                  msg->snode->setPrefader(msg->ival);
                  break;
            case AUDIO_SET_CHANNELS:
                  msg->snode->setChannels(msg->ival);
                  break;
            case AUDIO_ADDPLUGIN:
                  msg->snode->addPlugin(msg->plugin, msg->ival);
                  break;

            case AUDIO_SET_SEG_SIZE:
                  segmentSize = msg->ival;
                  sampleRate  = msg->iival;
#if 0 //TODO
                  audioOutput.segmentSizeChanged();
                  for (int i = 0; i < mixerGroups; ++i)
                        audioGroups[i].segmentSizeChanged();
                  for (iSynthI ii = synthiInstances.begin(); ii != synthiInstances.end();++ii)
                        (*ii)->segmentSizeChanged();
#endif
                  break;

            case SEQM_RESET_DEVICES:
                  for (int i = 0; i < MIDI_PORTS; ++i)
                        midiPorts[i].instrument()->reset(i, song->mtype());
                  break;
            case SEQM_INIT_DEVICES:
                  initDevices();
                  break;
            case SEQM_MIDI_LOCAL_OFF:
                  sendLocalOff();
                  break;
            case SEQM_PANIC:
                  panic();
                  break;
            case SEQM_PLAY_MIDI_EVENT:
                  {
                  MidiPlayEvent* ev = (MidiPlayEvent*)(msg->p1);
                  midiPorts[ev->port()].sendEvent(*ev);
                  // Record??
                  }
                  break;
            case SEQM_SCAN_ALSA_MIDI_PORTS:
                  alsaScanMidiPorts();
                  break;
            case MIDI_SHOW_INSTR_GUI:
                  midiSeq->msgUpdatePollFd();
                  break;
            case SEQM_ADD_TEMPO:
            case SEQM_REMOVE_TEMPO:
            case SEQM_SET_GLOBAL_TEMPO:
            case SEQM_SET_TEMPO:
                  song->processMsg(msg);
                  if (isPlaying()) {
                        _pos.setTick(curTickPos);
                        int samplePos = _pos.frame();
                        syncFrame     = audioDevice->framePos();
                        syncTime      = curTime();
                        frameOffset   = syncFrame - samplePos;
                        }
                  break;

            case SEQM_ADD_TRACK:
            case SEQM_REMOVE_TRACK:
            case SEQM_CHANGE_TRACK:
            case SEQM_ADD_PART:
            case SEQM_REMOVE_PART:
            case SEQM_CHANGE_PART:
                  midiSeq->sendMsg(msg);
                  break;

            case SEQM_IDLE:
                  idle = msg->a;
                  midiSeq->sendMsg(msg);
                  break;

            default:
                  song->processMsg(msg);
                  break;
            }
      }

//---------------------------------------------------------
//   seek
//    - called before start play
//    - initiated from gui
//---------------------------------------------------------

void Audio::seek(const Pos& p)
      {
      if (_pos == p) {
            printf("seek: already there\n");
            return;
            }
      _pos        = p;
      syncFrame   = audioDevice->framePos();
      frameOffset = syncFrame - _pos.frame();
      curTickPos  = _pos.tick();

      midiSeq->msgSeek();     // handle stuck notes and set
                              // controller for new position
      if (genMCSync) {
            MidiPort* syncPort = &midiPorts[txSyncPort];
            int beat = (curTickPos * 4) / config.division;
            syncPort->sendStop();
            syncPort->sendSongpos(beat);
            syncPort->sendContinue();
            }
      loopPassed = true;   // for record loop mode
      if (state != LOOP2 && !freewheel())
            audioPrefetch->msgSeek(_pos.frame());
      write(sigFd, "G", 1);   // signal seek to gui
      }

//---------------------------------------------------------
//   writeTick
//    called from audio prefetch thread context
//    write another buffer to soundfile
//---------------------------------------------------------

void Audio::writeTick()
      {
      OutputList* ol = song->outputs();
      if (!ol->empty()) {
            AudioOutput* ao = ol->front();
            if (ao->recordFlag())
                  ao->record();
            }
      WaveTrackList* tl = song->waves();
      for (iWaveTrack t = tl->begin(); t != tl->end(); ++t) {
            WaveTrack* track = *t;
            if (track->recordFlag())
                  track->record();
            }
      }

//---------------------------------------------------------
//   startRolling
//---------------------------------------------------------

void Audio::startRolling()
      {
      startRecordPos = _pos;
      if (song->record()) {
            startRecordPos = _pos;
            recording      = true;
            TrackList* tracks = song->tracks();
            for (iTrack i = tracks->begin(); i != tracks->end(); ++i) {
                  if ((*i)->isMidiTrack())
                        continue;
                  if ((*i)->type() == Track::WAVE)
                        ((WaveTrack*)(*i))->resetMeter();
                  ((AudioTrack*)(*i))->recEvents()->clear();
                  }
            }
      state = PLAY;
      write(sigFd, "1", 1);   // Play

      if (genMMC)
            midiPorts[txSyncPort].sendSysex(mmcDeferredPlayMsg, sizeof(mmcDeferredPlayMsg));
      if (genMCSync) {
            if (curTickPos)
                  midiPorts[txSyncPort].sendContinue();
            else
                  midiPorts[txSyncPort].sendStart();
            }

      if (precountEnableFlag
         && song->click()
         && !extSyncFlag.value()
         && song->record()) {
#if 0
            state = PRECOUNT;
            int z, n;
            if (precountFromMastertrackFlag)
                  sigmap.timesig(playTickPos, z, n);
            else {
                  z = precountSigZ;
                  n = precountSigN;
                  }
            clickno       = z * preMeasures;
            clicksMeasure = z;
            ticksBeat     = (division * 4)/n;
#endif
            }
      else {
            //
            // compute next midi metronome click position
            //
            int bar, beat;
            unsigned tick;
            sigmap.tickValues(curTickPos, &bar, &beat, &tick);
            if (tick)
                  beat += 1;
            midiClick = sigmap.bar2tick(bar, beat, 0);
            }
      }

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

void Audio::stopRolling()
      {
      state = STOP;
      midiSeq->msgStop();

#if 0 //TODO
      //---------------------------------------------------
      //    reset sustain
      //---------------------------------------------------

      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* mp = &midiPorts[i];
            for (int ch = 0; ch < MIDI_CHANNELS; ++ch) {
                  if (mp->hwCtrlState(ch, CTRL_SUSTAIN) != CTRL_VAL_UNKNOWN)
                        mp->sendCtrl(ch, CTRL_SUSTAIN, 0);
                        }
            }

#endif
      MidiPort* syncPort = &midiPorts[txSyncPort];
      if (genMMC) {
            unsigned char mmcPos[] = {
                  0x7f, 0x7f, 0x06, 0x44, 0x06, 0x01,
                  0, 0, 0, 0, 0
                  };
            int frame = tempomap.tick2frame(curTickPos);
            MTC mtc(double(frame) / double(sampleRate));
            mmcPos[6] = mtc.h() | (mtcType << 5);
            mmcPos[7] = mtc.m();
            mmcPos[8] = mtc.s();
            mmcPos[9] = mtc.f();
            mmcPos[10] = mtc.sf();
            syncPort->sendSysex(mmcStopMsg, sizeof(mmcStopMsg));
            syncPort->sendSysex(mmcPos, sizeof(mmcPos));
            }

      if (genMCSync) {         // Midi Clock
            // send STOP and
            // "set song position pointer"
            syncPort->sendStop();
            syncPort->sendSongpos(curTickPos * 4 / config.division);
            }
      WaveTrackList* tracks = song->waves();
      for (iWaveTrack i = tracks->begin(); i != tracks->end(); ++i) {
            WaveTrack* track = *i;
            track->resetMeter();
            }
      recording    = false;
      endRecordPos = _pos;
      write(sigFd, "0", 1);   // STOP
      }

//---------------------------------------------------------
//   recordStop
//    execution environment: gui thread
//---------------------------------------------------------

void Audio::recordStop()
      {
      audio->msgIdle(true); // gain access to all data structures

      song->startUndo();
      WaveTrackList* wl = song->waves();

      for (iWaveTrack it = wl->begin(); it != wl->end(); ++it) {
            WaveTrack* track = *it;
            if (track->recordFlag() || song->bounceTrack == track) {
                  song->cmdAddRecordedWave(track, startRecordPos, endRecordPos);
                  track->setRecFile(0);
                  song->setRecordFlag(track, false);
                  }
            }
      MidiTrackList* ml = song->midis();
      for (iMidiTrack it = ml->begin(); it != ml->end(); ++it) {
            MidiTrack* mt     = *it;
            MPEventList* mpel = mt->mpevents();
            EventList* el     = mt->events();

            //---------------------------------------------------
            //    resolve NoteOff events, Controller etc.
            //---------------------------------------------------

            buildMidiEventList(el, mpel, mt, config.division, true);
            song->cmdAddRecordedEvents(mt, el, startRecordPos.tick());
            el->clear();
            mpel->clear();
            }
      //
      // bounce to file operates on the first
      // output port
      //
      OutputList* ol = song->outputs();
      if (!ol->empty()) {
            AudioOutput* ao = ol->front();
            if (ao->recordFlag()) {
                  SndFile* sf = ao->recFile();
                  if (sf)
                        delete sf;              // close
                  ao->setRecFile(0);
                  ao->setRecordFlag1(false);
                  msgSetRecord(ao, false);
                  }
            }
      audio->msgIdle(false);
      song->endUndo(0);
      song->setRecord(false);
      }

//---------------------------------------------------------
//   curFrame
//    extrapolates current play frame on syncTime/syncFrame
//---------------------------------------------------------

unsigned int Audio::curFrame() const
      {
      return lrint((curTime() - syncTime) * sampleRate) + syncFrame;
      }

//---------------------------------------------------------
//   timestamp
//---------------------------------------------------------

int Audio::timestamp() const
      {
      int t = curFrame() - frameOffset;
      return t;
      }

//---------------------------------------------------------
//   sendMsgToGui
//---------------------------------------------------------

void Audio::sendMsgToGui(char c)
      {
      write(sigFd, &c, 1);
      }

