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

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

#include "song.h"
#include "node.h"
#include "driver/audiodev.h"
#include "driver/mididev.h"
#include "seq.h"
#include "synth.h"
#include "audioprefetch.h"
#include "plugins/plugin.h"
#include "audio.h"
#include "wave.h"
#include "midithread.h"

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

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

Audio::Audio()
      {
      msg = 0;

#if 0  // done in clearSong()
      // set master defaults
      audioOutput.setPorts(2);
      audioOutput.setVolume(1.0);
      for (int i = 0; i < mixerGroups; ++i) {
            audioGroups[i].setPorts(2);
            audioGroups[i].connectOut(&audioOutput);
            }
      audioInput.setPorts(2);
      audioInput.connectOut(&audioOutput);
#endif

      // create message channel
      int filedes[2];         // 0 - reading   1 - writing
      if (pipe(filedes) == -1) {
            perror("Audio: creating pipe");
            exit(-1);
            }
      fromThreadFdr = filedes[0];
      fromThreadFdw = filedes[1];

      _running = false;
      pthread_mutex_init(&mutex, 0);
      pthread_mutex_init(&msgMutex, 0);
      }

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

void Audio::start()
      {
      playState    = false;
      curSamplePos = 0;

      for (int i = 0; i < mixerGroups; ++i) {
            audioGroups[i].connect();
            }
      audioInput.connect();

      TrackList* tl = song->tracks();
      for (iTrack t = tl->begin(); t != tl->end(); ++t) {
            if ((*t)->type() == Track::WAVE) {
                  WaveTrack* track = (WaveTrack*)(*t);
                  track->connect();
                  }
            }
      if (audioDevice) {
            audioDevice->start();
            _running = true;
            }
      }

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

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

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

Audio::~Audio()
      {
      }

//---------------------------------------------------------
//   processAudio1
//    ALSA callback
//---------------------------------------------------------

void processAudio1(void*, void*)
      {
      audio->process(segmentSize);
      }

//---------------------------------------------------------
//   processAudio
//    JACK callback
//---------------------------------------------------------

int processAudio(jack_nframes_t frames, void*)
      {
      segmentSize = (unsigned long)frames;
      audio->process(segmentSize);
      return 0;
      }

//---------------------------------------------------------
//   process
//---------------------------------------------------------

void Audio::process(unsigned long frames)
      {
      extern int watchAudio;
      ++watchAudio;                 // make watchdog happy

      // get reference time position
      if (pthread_mutex_trylock(&mutex) == 0) {
            samplePos  = audioDevice->curPlayPos();
            sampleTime = curTime();
            pthread_mutex_unlock(&mutex);
            }
      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));
                  }
            }
      audioOutput.process(frames);

      // process not connected mixer groups
      for (int i = 0; i < mixerGroups; ++i) {
            AudioNode* node = &audioGroups[i];
            if (node->_outRoute.empty() && ! node->_inRoute.empty()) {
                  int ports = node->ports();
                  float* buffer[ports];
                  float data[frames * ports];
                  for (int i = 0; i < ports; ++i)
                        buffer[i] = data + i * frames;
                  node->copyData(ports, frames, buffer);
                  }
            }
      if (playState) {
            curSamplePos += frames;
            audioPrefetch->msgTick();
            }
      }

//---------------------------------------------------------
//   curPlayPos
//    called from midi thread
//---------------------------------------------------------

int Audio::curPlayPos()
      {
      static int pos;
      static double time;
      if (pthread_mutex_trylock(&mutex) == 0) {
            pos  = samplePos;
            time = sampleTime;
            pthread_mutex_unlock(&mutex);
            }
//      else {
//            perror("mutex try lock failed");
//            }
      int elapsed = lrint((curTime() - time) * sampleRate);
      return pos + elapsed;
      }

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

void Audio::processMsg(const volatile AudioMsg* msg)
      {
      switch(msg->id) {
            case AUDIO_PLAY:
                  if (msg->ival)
                        startPlay();
                  else
                        stopPlay();
                  break;

            case AUDIO_SEEK:
                  seek(msg->iival);
                  break;

            case AUDIO_RECORD:
                  msg->snode->setRecordFlag2(msg->ival);
                  break;
            case AUDIO_ROUTEADD:
                  connectNodes(msg->snode, msg->dnode);
                  break;
            case AUDIO_ROUTESET:
                  msg->snode->disconnectClear();
                  if (msg->dnode)
                        connectNodes(msg->snode, msg->dnode);
                  break;
            case AUDIO_ROUTEREMOVE:
                  disconnectNodes(msg->snode, msg->dnode);
                  break;
            case AUDIO_VOL:
                  msg->snode->setVolume(msg->dval);
                  break;
            case AUDIO_PAN:
                  msg->snode->setPan(msg->dval);
                  break;
            case AUDIO_SET_PREFADER:
                  msg->snode->setPrefader(msg->ival);
                  break;
            case AUDIO_SET_CHANNELS:
                  msg->snode->setPorts(msg->ival);
                  break;
            case AUDIO_ADDPLUGIN:
                  msg->snode->efxPipe()->insert(msg->plugin, msg->ival);
                  break;
            case AUDIO_REMOVE_SYNTHI:
                  msg->synth->disconnectClear();     // remove from audio chain
                  removeMidiInstrument(msg->synth->iname());
                  break;
            case AUDIO_ADD_SYNTHI:
                  synthiInstances.push_back((SynthI*)(msg->synth));
                  msg->synth->connect();
                  break;

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

            default:
                  song->processAudioMsg((const AudioMsg*)msg);
                  break;
            }
      }

//---------------------------------------------------------
//   msgRemoveRoute
//---------------------------------------------------------

void Audio::msgRemoveRoute(AudioNode* src, AudioNode* dst)
      {
      AudioMsg msg;
      msg.id = AUDIO_ROUTEREMOVE;
      msg.snode = src;
      msg.dnode = dst;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgAddRoute
//---------------------------------------------------------

void Audio::msgAddRoute(AudioNode* src, AudioNode* dst)
      {
      AudioMsg msg;
      msg.id = AUDIO_ROUTEADD;
      msg.snode = src;
      msg.dnode = dst;
      sendMsg(&msg);
      song->update(SC_ROUTE);
      }

//---------------------------------------------------------
//   msgSetRoute
//---------------------------------------------------------

void Audio::msgSetRoute(AudioNode* src, AudioNode* dst)
      {
      AudioMsg msg;
      msg.id = AUDIO_ROUTESET;
      msg.snode = src;
      msg.dnode = dst;
      sendMsg(&msg);
      song->update(SC_ROUTE);
      }

//---------------------------------------------------------
//   msgAddPlugin
//---------------------------------------------------------

void Audio::msgAddPlugin(AudioNode* node, int idx, PluginI* plugin)
      {
      AudioMsg msg;
      msg.id     = AUDIO_ADDPLUGIN;
      msg.snode  = node;
      msg.ival   = idx;
      msg.plugin = plugin;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSetRecord
//---------------------------------------------------------

void Audio::msgSetRecord(AudioNode* node, bool val)
      {
      AudioMsg msg;
      msg.id     = AUDIO_RECORD;
      msg.snode  = node;
      msg.ival   = int(val);
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSetVolume
//---------------------------------------------------------

void Audio::msgSetVolume(AudioNode* src, double val)
      {
      AudioMsg msg;
      msg.id    = AUDIO_VOL;
      msg.snode    = src;
      msg.dval  = val;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgRemoveSynthi
//---------------------------------------------------------

void Audio::msgRemoveSynthI(SynthI* synthi)
      {
      AudioMsg msg;
      msg.id = AUDIO_REMOVE_SYNTHI;
      msg.synth = synthi;
      sendMsg(&msg);
      for (iSynthI i = synthiInstances.begin();
            i != synthiInstances.end(); ++i) {
            if (*i == synthi) {
                  midiThread->msgShowInstrumentGui(*i, false);
                  synthiInstances.erase(i);
                  break;
                  }
            }
      song->updateAudioMixer();
      delete synthi;
      }

//---------------------------------------------------------
//   msgAddSynthi
//---------------------------------------------------------

void Audio::msgAddSynthI(SynthI* synth)
      {
      AudioMsg msg;
      msg.id = AUDIO_ADD_SYNTHI;
      msg.synth = synth;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSetPan
//---------------------------------------------------------

void Audio::msgSetPan(AudioNode* node, double val)
      {
      AudioMsg msg;
      msg.id    = AUDIO_PAN;
      msg.snode = node;
      msg.dval  = val;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSetPrefader
//---------------------------------------------------------

void Audio::msgSetPrefader(AudioNode* node, int val)
      {
      AudioMsg msg;
      msg.id    = AUDIO_SET_PREFADER;
      msg.snode = node;
      msg.ival  = val;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSetChannels
//---------------------------------------------------------

void Audio::msgSetChannels(AudioNode* node, int val)
      {
      AudioMsg msg;
      msg.id    = AUDIO_SET_CHANNELS;
      msg.snode = node;
      msg.ival  = val;
      sendMsg(&msg);
      song->update(SC_CHANNELS);
      }

//---------------------------------------------------------
//   msgSetSegSize
//---------------------------------------------------------

void Audio::msgSetSegSize(int bs, int sr)
      {
      AudioMsg msg;
      msg.id = AUDIO_SET_SEG_SIZE;
      msg.ival = bs;
      msg.iival = sr;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgPlay
//---------------------------------------------------------

void Audio::msgPlay(bool flag)
      {
      AudioMsg msg;
      msg.id = AUDIO_PLAY;
      msg.ival = flag;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgSeek
//---------------------------------------------------------

void Audio::msgSeek(int pos)
      {
      AudioMsg msg;
      msg.id = AUDIO_SEEK;
      msg.iival = pos;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgAddPart
//---------------------------------------------------------

void Audio::msgAddPart(WavePart* part)
      {
      AudioMsg msg;
      msg.id = AUDIO_ADD_PART;
      msg.spart = part;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgRemovePart
//---------------------------------------------------------

void Audio::msgRemovePart(WavePart* part)
      {
      AudioMsg msg;
      msg.id = AUDIO_REMOVE_PART;
      msg.spart = part;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgChangePart
//---------------------------------------------------------

void Audio::msgChangePart(WavePart* spart, WavePart* dpart)
      {
      AudioMsg msg;
      msg.id = AUDIO_CHANGE_PART;
      msg.spart = spart;
      msg.dpart = dpart;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgRemoveTrack
//---------------------------------------------------------

void Audio::msgRemoveTrack(WaveTrack* track)
      {
      AudioMsg msg;
      msg.id = AUDIO_REMOVE_TRACK;
      msg.track = track;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgUndo
//---------------------------------------------------------

void Audio::msgUndo()
      {
      AudioMsg msg;
      msg.id = AUDIO_UNDO;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   msgRedo
//---------------------------------------------------------

void Audio::msgRedo()
      {
      AudioMsg msg;
      msg.id = AUDIO_REDO;
      sendMsg(&msg);
      }

//---------------------------------------------------------
//   seek
//---------------------------------------------------------

void Audio::seek(int tickpos)
      {
      if (noAudio || recordState)
            return;
      curSamplePos = lrint(tempomap.tick2time(tickpos) * sampleRate);
      audioPrefetch->msgSeek(curSamplePos);
      }

//---------------------------------------------------------
//   sendMsg
//---------------------------------------------------------

void Audio::sendMsg(AudioMsg* m)
      {
      static int sno = 0;

      if (_running) {
            m->serialNo = sno++;
            //DEBUG:
//            pthread_mutex_lock(&msgMutex);  // block until msg is free
            msg = m;
//            pthread_mutex_unlock(&msgMutex);
            // wait for next audio "process" call to finish operation
            int no = -1;
            int rv = read(fromThreadFdr, &no, sizeof(int));
            if (rv != sizeof(int))
                  perror("Audio: read pipe failed");
            else if (no != (sno-1)) {
                  fprintf(stderr, "audio: bad serial number, read %d expected %d\n",
                     no, sno-1);
                  }
            }
      else {
            // if audio is not running (during initialization)
            // process commands directly:
            processMsg(m);
            }
      }

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

void Audio::writeTick()
      {
      if (audioOutput.recordFlag())
            audioOutput.record();

      TrackList* tl = song->tracks();
      for (iTrack t = tl->begin(); t != tl->end(); ++t) {
            if ((*t)->type() == Track::WAVE) {
                  WaveTrack* track = (WaveTrack*)(*t);
                  if (track->recordFlag())
                        track->record();
                  }
            }
      }

//---------------------------------------------------------
//   startPlay
//---------------------------------------------------------

void Audio::startPlay()
      {
      playState = true;
      if (song->record())
            startRecord();
      }

//---------------------------------------------------------
//   stopPlay
//---------------------------------------------------------

void Audio::stopPlay()
      {
      playState = false;
      TrackList* tracks = song->tracks();
      for (iTrack i = tracks->begin(); i != tracks->end(); ++i) {
            if ((*i)->type() != Track::WAVE)
                  continue;
            WaveTrack* track = (WaveTrack*)(*i);
            track->resetMeter();
            }
      if (song->record())
            stopRecord();
      }

//---------------------------------------------------------
//   startRecord
//---------------------------------------------------------

void Audio::startRecord()
      {
      recordState = true;
      TrackList* tracks = song->tracks();
      for (iTrack i = tracks->begin(); i != tracks->end(); ++i) {
            if ((*i)->type() != Track::WAVE)
                  continue;
            WaveTrack* track = (WaveTrack*)(*i);
            track->resetMeter();
//            if (track->recordFlag()) {
//                  track->recFile()->openWrite();
//                  }
            }
      }

//---------------------------------------------------------
//   stopRecord
//---------------------------------------------------------

void Audio::stopRecord()
      {
      recordState = false;
      }

