//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: wave.cpp,v 1.3 2003/10/30 16:42:19 lunar_shuttle Exp $
//
//  (C) Copyright 2000 Werner Schweer (ws@seh.de)
//=========================================================

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <qfileinfo.h>
#include <cmath>

#include <qmessagebox.h>
#include <qprogressdialog.h>

#include "xml.h"
#include "song.h"
#include "wave.h"
#include "app.h"
#include "filedialog.h"
#include "arranger/arranger.h"
#include "globals.h"
#include "event.h"
#include "midithread.h"
#include "audio.h"

const char* audioFilePattern[] = {
      "Wave/Binary (*.wav *.bin)",
      "Wave (*.wav)",
      "Binary (*.bin)",
      "All Files (*)",
      0
      };
const int cacheMag = 128;

ClipList* waveClips;

SndFileList SndFile::sndFiles;

//---------------------------------------------------------
//   SndFile
//---------------------------------------------------------

SndFile::SndFile(const QString& name)
      {
      finfo = new QFileInfo(name);
      refs  = 0;
      sf    = 0;
      csize = 0;
      cache = 0;
      openFlag = false;
      sndFiles.push_back(this);
      }

SndFile::~SndFile()
      {
      if (openFlag) {
//            printf("~SndFile::close soundfile\n");
            close();
            }
      for (iSndFile i = sndFiles.begin(); i != sndFiles.end(); ++i) {
            if (*i == this) {
                  sndFiles.erase(i);
                  break;
                  }
            }
      delete finfo;
      if (cache) {
            for (unsigned i = 0; i < channels(); ++i)
                  delete cache[i];
            delete[] cache;
            cache = 0;
            }
      }

//---------------------------------------------------------
//   openRead
//---------------------------------------------------------

bool SndFile::openRead()
      {
      if (openFlag) {
            printf("SndFile:: alread open\n");
            return false;
            }
	QString p = path();
      sf = sf_open(p.latin1(), SFM_READ, &sfinfo);
      if (sf == 0)
            return true;
      writeFlag = false;
      openFlag  = true;
      QString cacheName = finfo->dirPath(true) + QString("/") + finfo->baseName() + QString(".wca");
      readCache(cacheName, true);
      return false;
      }

//---------------------------------------------------------
//   update
//    called after recording to file
//---------------------------------------------------------

void SndFile::update()
      {
      close();

      // force recreation of wca data
      QString cacheName = finfo->dirPath(true) +
         QString("/") + finfo->baseName() + QString(".wca");
      printf("SndFile::update()-- remove %s\n", cacheName.latin1());
      ::remove(cacheName.latin1());
      openRead();
      }

//---------------------------------------------------------
//   readCache
//---------------------------------------------------------

void SndFile::readCache(const QString& path, bool showProgress)
      {
//      printf("readCache %s for %d samples channel %d\n",
//         path.latin1(), samples(), channels());

      if (cache) {
            for (unsigned i = 0; i < channels(); ++i)
                  delete cache[i];
            delete[] cache;
            }
      if (samples() == 0) {
//            printf("SndFile::readCache: file empty\n");
            return;
            }
      csize = (samples() + cacheMag - 1)/cacheMag;
      cache = new SampleV*[channels()];
      for (unsigned ch = 0; ch < channels(); ++ch)
            cache[ch] = new SampleV[csize];

      FILE* cfile = fopen(path.latin1(), "r");
      if (cfile) {
            for (unsigned ch = 0; ch < channels(); ++ch)
                  fread(cache[ch], csize * sizeof(SampleV), 1, cfile);
            fclose(cfile);
            return;
            }

      //---------------------------------------------------
      //  create cache
      //---------------------------------------------------

      QProgressDialog* progress = 0;
      if (showProgress) {
            QString label(QWidget::tr("create peakfile for "));
            label += basename();
            progress = new QProgressDialog(label,
               QString::null, csize, 0, 0, true);
            progress->setMinimumDuration(0);
            progress->show();
            }
      float data[channels()][cacheMag];
      float* fp[channels()];
      for (unsigned k = 0; k < channels(); ++k)
            fp[k] = &data[k][0];
      int interval = csize / 10;
      for (int i = 0; i < csize; i++) {
            if (showProgress && ((i % interval) == 0))
                  progress->setProgress(i);
            seek(i * cacheMag, 0);
            read(channels(), fp, cacheMag);
            for (unsigned ch = 0; ch < channels(); ++ch) {
                  float rms = 0.0;
                  cache[ch][i].peak = 0;
                  for (int n = 0; n < cacheMag; n++) {
                        float fd = data[ch][n];
                        rms += fd * fd;
                        int idata = int(fd * 255.0);
                        if (idata < 0)
                              idata = -idata;
                        if (cache[ch][i].peak < idata)
                              cache[ch][i].peak = idata;
                        }
                  // amplify rms value +12dB
                  int rmsValue = int((sqrt(rms/cacheMag) * 255.0));
                  if (rmsValue > 255)
                        rmsValue = 255;
                  cache[ch][i].rms = rmsValue;
                  }
            }
      if (showProgress)
            progress->setProgress(csize);
      writeCache(path);
      if (showProgress)
            delete progress;
      }

//---------------------------------------------------------
//   writeCache
//---------------------------------------------------------

void SndFile::writeCache(const QString& path)
      {
      FILE* cfile = fopen(path.latin1(), "w");
      if (cfile == 0)
            return;
      for (unsigned ch = 0; ch < channels(); ++ch)
            fwrite(cache[ch], csize * sizeof(SampleV), 1, cfile);
      fclose(cfile);
      }

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

void SndFile::read(SampleV* s, int mag, unsigned pos)
      {
      for (unsigned ch = 0; ch < channels(); ++ch) {
            s[ch].peak = 0;
            s[ch].rms = 0;
            }
      if (pos > samples()) {
//            printf("%p pos %d > samples %d\n", this, pos, samples());
            return;
            }

      if (mag < cacheMag) {
            float data[channels()][mag];
            float* fp[channels()];
            for (unsigned i = 0; i < channels(); ++i)
                  fp[i] = &data[i][0];
            seek(pos, 0);
            read(channels(), fp, mag);

            for (unsigned ch = 0; ch < channels(); ++ch) {
                  s[ch].peak = 0;
                  float rms = 0.0;
                  for (int i = 0; i < mag; i++) {
                        float fd = data[ch][i];
                        rms += fd;
                        int idata = int(fd * 255.0);
                        if (idata < 0)
                              idata = -idata;
                        if (s[ch].peak < idata)
                              s[ch].peak = idata;
                        }
                  s[ch].rms = 0;    // TODO rms / mag;
                  }
            }
      else {
            mag /= cacheMag;
            int rest = csize - (pos/cacheMag);
            int end  = mag;
            if (rest < mag)
                              end = rest;

            for (unsigned ch = 0; ch < channels(); ++ch) {
                  int rms = 0;
                  int off = pos/cacheMag;
                  for (int offset = off; offset < off+end; offset++) {
                        rms += cache[ch][offset].rms;
                        if (s[ch].peak < cache[ch][offset].peak)
                              s[ch].peak = cache[ch][offset].peak;
                              }
                  s[ch].rms = rms / mag;
                  }
            }
      }

//---------------------------------------------------------
//   openWrite
//---------------------------------------------------------

bool SndFile::openWrite()
      {
      if (openFlag) {
            printf("SndFile:: alread open\n");
            return false;
            }
	QString p = finfo->filePath();
      sf = sf_open(p.latin1(), SFM_RDWR, &sfinfo);

      if (sf) {
            openFlag  = true;
            writeFlag = true;
            QString cacheName = finfo->dirPath(true) +
               QString("/") + finfo->baseName() + QString(".wca");
            readCache(cacheName, true);
            }
      return sf == 0;
      }

//---------------------------------------------------------
//   close
//---------------------------------------------------------

void SndFile::close()
      {
      if (!openFlag) {
            printf("SndFile:: alread closed\n");
            return;
            }
      sf_close(sf);
      openFlag = false;
      }

//---------------------------------------------------------
//   remove
//---------------------------------------------------------

void SndFile::remove()
      {
      if (openFlag)
            close();
      QFile::remove(finfo->filePath());
      }

void SndFile::incRef()
      {
      ++refs;
      }

void SndFile::decRef()
      {
      --refs;
      }

QString SndFile::basename() const
      {
      return finfo->baseName(true);
      }

QString SndFile::path() const
      {
      return finfo->filePath();
      }

QString SndFile::name() const
      {
      return finfo->fileName();
      }

//---------------------------------------------------------
//   samples
//---------------------------------------------------------

unsigned SndFile::samples() const
      {
      sf_count_t curPos = sf_seek(sf, 0, SEEK_CUR);
      int frames = sf_seek(sf, 0, SEEK_END);
      sf_seek(sf, curPos, SEEK_SET);
      return frames;
      }

unsigned SndFile::channels() const
      {
      return sfinfo.channels;
      }

unsigned SndFile::samplerate() const
      {
      return sfinfo.samplerate;
      }

unsigned SndFile::format() const
      {
      return sfinfo.format;
      }

void SndFile::setFormat(int fmt, int ch, int rate)
      {
      sfinfo.samplerate = rate;
      sfinfo.channels   = ch;
      sfinfo.format     = fmt;
      sfinfo.seekable   = true;
      sfinfo.frames     = 0;
      }

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

size_t SndFile::read(int srcChannels, float** dst, size_t n)
      {
      float buffer[n * sfinfo.channels];
      size_t rn       = sf_readf_float(sf, buffer, n);
      float* src      = buffer;
      int dstChannels = sfinfo.channels;

      if (srcChannels == dstChannels) {
            for (size_t i = 0; i < rn; ++i) {
                  for (int ch = 0; ch < srcChannels; ++ch)
                        *(dst[ch]+i) = *src++;
                  }
            }
      else if ((srcChannels == 1) && (dstChannels == 2)) {
            // stereo to mono
            for (size_t i = 0; i < rn; ++i)
                  *(dst[0] + i) = src[i + i] + src[i + i + 1];
            }
      else if ((srcChannels == 2) && (dstChannels == 1)) {
            // mono to stereo
            for (size_t i = 0; i < rn; ++i) {
                  float data = *src++;
                  *(dst[0]+i) = data;
                  *(dst[1]+i) = data;
                  }
            }
      else {
            printf("SndFile:read channel mismatch %d -> %d\n",
               srcChannels, dstChannels);
            }
      return rn;
      }

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

size_t SndFile::write(int srcChannels, float** src, size_t n)
      {
      int dstChannels = sfinfo.channels;
      float buffer[n * dstChannels];
      float* dst      = buffer;

      if (srcChannels == dstChannels) {
            for (size_t i = 0; i < n; ++i) {
                  for (int ch = 0; ch < dstChannels; ++ch)
                        *dst++ = *(src[ch]+i);
                  }
            }
      else if ((srcChannels == 1) && (dstChannels == 2)) {
            // mono to stereo
            for (size_t i = 0; i < n; ++i) {
                  float data =  *(src[0]+i);
                  *dst++ = data;
                  *dst++ = data;
                  }
            }
      else if ((srcChannels == 2) && (dstChannels == 1)) {
            // stereo to mono
            for (size_t i = 0; i < n; ++i)
                  *dst++ = *(src[0]+i) + *(src[1]+i);
            }
      else {
            printf("SndFile:write channel mismatch %d -> %d\n",
               srcChannels, dstChannels);
            return 0;
            }
      return sf_writef_float(sf, buffer, n) ;
      }

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

off_t SndFile::seek(off_t frames, int whence)
      {
      return sf_seek(sf, frames, whence);
      }

//---------------------------------------------------------
//   strerror
//---------------------------------------------------------

QString SndFile::strerror() const
      {
      char buffer[128];
      buffer[0] = 0;
      sf_error_str(sf, buffer, 128);
      return QString(buffer);
      }

//---------------------------------------------------------
//   search
//---------------------------------------------------------

SndFile* SndFileList::search(const QString& name)
      {
      for (iSndFile i = begin(); i != end(); ++i) {
            if ((*i)->path() == name)
                  return *i;
            }
      return 0;
      }

//---------------------------------------------------------
//   getSnd
//---------------------------------------------------------

SndFile* getWave(const QString& inName, bool writeFlag)
      {
         QString name = inName;

         if (QFileInfo(name).isRelative()) {
               name = museProject + QString("/") + name;
         }
         else {
               if (!QFile::exists(name)) {
                     if(QFile::exists(museProject + QString("/") + name)) {
                           name = museProject + QString("/") + name;
                     }
               }
         }

      SndFile* f = SndFile::sndFiles.search(name);
      if (f == 0) {
            if (!QFile::exists(name)) {
                  fprintf(stderr, "wave file <%s> not found\n",
                     name.latin1());
                  return 0;
                  }
            f = new SndFile(name);
            bool error;
            if (writeFlag)
                  error = f->openRead();
            else
                  error = f->openWrite();
            if (error) {
                  fprintf(stderr, "open wave file(%s) for %s failed: %s\n",
                     name.latin1(),
                     writeFlag ? "writing" : "reading",
                     f->strerror().latin1());
                  delete f;
                  f = 0;
                  }
            }
      else {
            if (writeFlag && ! f->isWritable()) {
                  if (f->isOpen())
                        f->close();
                  f->openWrite();
                  }
            }
      return f;
      }

//---------------------------------------------------------
//   importAudio
//---------------------------------------------------------

void MusE::importWave()
      {
      Track* track = arranger->curTrack();
      if (track == 0 || track->type() != Track::WAVE) {
            QMessageBox::critical(this, QString("MusE"),
              tr("to import a audio file you have first to select"
              "a audio track"));
            return;
            }
      QString fn = getOpenFileName(QString("waves"), audioFilePattern, this,
         tr("Import Wave File"));
      if (!fn.isEmpty()) {
            importWave(fn);
            }
      }

//---------------------------------------------------------
//   importWave
//---------------------------------------------------------

bool MusE::importWave(const QString& name)
      {
      WaveTrack* track = (WaveTrack*)(arranger->curTrack());
      SndFile* f = getWave(name, false);

      if (f == 0) {
            printf("import audio file failed\n");
            return true;
            }
      int samples = f->samples();
      track->setPorts(f->channels());
      Clip* clip = new Clip(f, 0, samples);

      WavePart* part = new WavePart(track);
      part->setPosTick(song->cpos());
      part->setLenSample(samples);

      WaveEvent* event = new WaveEvent(0, clip);

      part->addEvent(event);

      part->setName(QFileInfo(name).baseName());
      midiThread->msgAddPart(part);
      int endTick = part->posTick() + part->lenTick();
      if (song->len() < endTick)
            song->setLen(endTick);
      return false;
      }

//---------------------------------------------------------
//   Clip
//---------------------------------------------------------

Clip::Clip()
      {
      f       = 0;
      lrefs   = 0;
      prefs   = 0;
      deleted = false;
      waveClips->add(this);
      }

Clip::Clip(SndFile* file, int start, int l)
      {
      f = file;
      f->incRef();
      int irefs = f->references();
      _name.sprintf("%s.%d", file->basename().latin1(), irefs);
      _spos   = start;
      len     = l;
      lrefs   = 0;
      prefs   = 0;
      deleted = false;
      waveClips->add(this);
      }

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

void Clip::read(unsigned srcOffset, float** buffer, int channel, unsigned n)
      {
      if (f == 0)
            return;
      f->seek(srcOffset + _spos, 0);
      f->read(channel, buffer, n);
      }

Clip::~Clip()
      {
      waveClips->remove(this);
      if (f) {
            f->decRef();
            if (f->references() <= 0)
                  delete f;
            }
      }

//---------------------------------------------------------
//   incRef
//---------------------------------------------------------

void Clip::incRef()
      {
      ++lrefs;
      ++prefs;
      }

//---------------------------------------------------------
//   decRef
//---------------------------------------------------------

int Clip::decRef()
      {
      --lrefs;
      --prefs;
      return lrefs;
      }

//---------------------------------------------------------
//   ClipList::write(level, xml)
//---------------------------------------------------------

void ClipList::write(int level, Xml& xml) const
      {
      for (ciClip i = begin(); i != end(); ++i)
            (*i)->write(level, xml);
      }

//---------------------------------------------------------
//   Clip::write(level, xml)
//---------------------------------------------------------

void Clip::write(int level, Xml& xml) const
      {
      xml.tag(level++, "clip");
      xml.strTag(level, "file", f->name());
      xml.strTag(level, "name", _name);
      xml.intTag(level, "tick", _spos);
      xml.intTag(level, "len", len);
      xml.etag(level, "clip");
      }

//---------------------------------------------------------
//   Clip::read
//---------------------------------------------------------

void Clip::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 == "file") {
                              f = getWave(xml.parse1(), false);
                              if (f)
                                    f->incRef();
                              else
                                    printf("clip: file not found\n");
                              }
                        else if (tag == "name")
                              _name = xml.parse1();
                        else if (tag == "tick")
                              _spos = xml.parseInt();
                        else if (tag == "len")
                              len = xml.parseInt();
                        else
                              xml.unknown("Clip");
                        break;
                  case Xml::TagEnd:
                        if (tag == "clip")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   search
//---------------------------------------------------------

Clip* ClipList::search(const QString& name) const
      {
      for (ciClip i = begin(); i != end(); ++i)
            if ((*i)->name() == name)
                  return *i;
      fprintf(stderr, "ClipList: clip <%s> not found\n",
         name.latin1());
      return 0;
      }

//---------------------------------------------------------
//   remove
//---------------------------------------------------------

void ClipList::remove(Clip* clip)
      {
      for (iClip i = begin(); i != end(); ++i) {
            if (*i == clip) {
                  erase(i);
                  return;
                  }
            }
      printf("ClipList:remove: clip not found\n");
      }

//---------------------------------------------------------
//   idx
//---------------------------------------------------------

int ClipList::idx(Clip* clip) const
      {
      int n = 0;
      for (ciClip i = begin(); i != end(); ++i, ++n) {
            if (*i == clip)
                  return n;
            }
      printf("ClipList: idx -1\n");
      return -1;
      }

//---------------------------------------------------------
//   cmdAddRecordedWave
//---------------------------------------------------------

void Song::cmdAddRecordedWave(WaveTrack* track, int start, int end)
      {
// printf("add recorded wave %d %d\n", start, end);

      SndFile* sf = track->recFile();
      if (sf == 0) {
            printf("cmdAddRecordedWave: no snd file\n");
            return;
            }

      sf->update();

      WavePart* part = new WavePart(track);
      part->setPosSample(start);
      part->setLenSample(end - start);
      part->setName(track->name());

      // create Event
      Clip* clip = new Clip(sf, 0, end-start);
      WaveEvent* event = new WaveEvent(0, clip);
      part->addEvent(event);

      startUndo();
      audio->msgAddPart(part);
      endUndo(SC_PART_INSERTED);

      int endTick = part->posTick() + part->lenTick();
      if (song->len() < endTick)
            song->setLen(endTick);
      }

