//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: wave.cpp,v 1.19.2.2 2004/12/13 20:19:09 spamatica Exp $
//
//  (C) Copyright 2000-2004 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 "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);
      sf    = 0;
      csize = 0;
      cache = 0;
      openFlag = false;
      sndFiles.push_back(this);
      }

SndFile::~SndFile()
      {
      if (openFlag)
            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");
      ::remove(cacheName.latin1());
      if (openRead()) {
            printf("SndFile::openRead(%s) failed: %s\n", path().latin1(), strerror().latin1());
            }
      }

//---------------------------------------------------------
//   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 = path();
      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());
      }

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

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

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

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;
      }

//---------------------------------------------------------
//   channels
//---------------------------------------------------------

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
//
//   A hardcoded limiter was added that limits the output at 0.99/-0.99
//   libsndfile handles signal betwee -1.0/1.0 with current setting
//   outside these values there will be heavy distortion
//
//---------------------------------------------------------

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

      if (srcChannels == dstChannels) {
            for (size_t i = 0; i < n; ++i) {
                  for (int ch = 0; ch < dstChannels; ++ch)
                        //*dst++ = *(src[ch]+i); // < limitValue ? *(src[ch]+i) : limitValue;
                        if (*(src[ch]+i) > 0)
                          *dst++ = *(src[ch]+i) < limitValue ? *(src[ch]+i) : limitValue;
                        else
                          *dst++ = *(src[ch]+i) > -limitValue ? *(src[ch]+i) : -limitValue;
                  }
            }
      else if ((srcChannels == 1) && (dstChannels == 2)) {
            // mono to stereo
            for (size_t i = 0; i < n; ++i) {
                  float data =  *(src[0]+i);
                  if (data > 0) {
                        *dst++ = data < limitValue ? data : limitValue; 
                        *dst++ = data < limitValue ? data : limitValue;
                        }
                  else {
                        *dst++ = data > -limitValue ? data : -limitValue; 
                        *dst++ = data > -limitValue ? data : -limitValue; 
                        }
                  }
            }
      else if ((srcChannels == 2) && (dstChannels == 1)) {
            // stereo to mono
            for (size_t i = 0; i < n; ++i)
                  if (*(src[0]+i) + *(src[1]+i) > 0)
                    *dst++ = (*(src[0]+i) + *(src[1]+i)) < limitValue ? (*(src[0]+i) + *(src[1]+i)) : limitValue;
                  else
                    *dst++ = (*(src[0]+i) + *(src[1]+i)) > -limitValue ? (*(src[0]+i) + *(src[1]+i)) : -limitValue;
            }
      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;
                        }
                  }
            }
// printf("=====%s %s\n", inName.latin1(), name.latin1());
      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 wave track"));
            return;
            }
      QString fn = getOpenFileName(lastWavePath, audioFilePattern, this,
         tr("Import Wave File"), 0);
      if (!fn.isEmpty()) {
            lastWavePath = fn;
            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->setChannels(f->channels());

      WavePart* part = new WavePart(track);
      part->setTick(song->cpos());
      part->setLenFrame(samples);

      Event event(Wave);
      SndFileR sf(f);
      event.setSndFile(sf);
      event.setSpos(0);
      event.setLenFrame(samples);
      part->addEvent(event);

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

bool MusE::importWaveToTrack(QString& name, Track* track)
      {
      SndFile* f = getWave(name, false);

      if (f == 0) {
            printf("import audio file failed\n");
            return true;
            }
      int samples = f->samples();
      track->setChannels(f->channels());

      WavePart* part = new WavePart((WaveTrack *)track);
      part->setTick(song->cpos());
      part->setLenFrame(samples);

      Event event(Wave);
      SndFileR sf(f);
      event.setSndFile(sf);
      event.setSpos(0);
      event.setLenFrame(samples);
      part->addEvent(event);

      part->setName(QFileInfo(name).baseName());
      audio->msgAddPart(part);
      unsigned endTick = part->tick() + part->lenTick();
      if (song->len() < endTick)
            song->setLen(endTick);
      return false;
      }
#if 0
//---------------------------------------------------------
//   Clip
//---------------------------------------------------------

ClipBase::ClipBase(const SndFileR& file, int start, int l)
   : f(file)
      {
      refCount = 0;
      for (int i = 1; true; ++i) {
            _name.sprintf("%s.%d", f.basename().latin1(), i);
            ciClip ic = waveClips->begin();
            for (; ic != waveClips->end(); ++ic) {
                  if ((*ic)->name() == _name)
                        break;
                  }
            if (ic == waveClips->end())
                  break;
            // try another name
            }
      _spos   = start;
      len     = l;
      deleted = false;
      lrefs   = 0;
      waveClips->add(this);
      }

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

void ClipBase::read(unsigned srcOffset, float** buffer, int channel, unsigned n)
      {
      if (f.isNull())
            return;
      f.seek(srcOffset + _spos, 0);
      f.read(channel, buffer, n);
      }

ClipBase::~ClipBase()
      {
      waveClips->remove(this);
      }

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

void ClipList::write(int level, Xml& xml) const
      {
      for (ciClip i = begin(); i != end(); ++i) {
            ClipBase* clip = *i;
            // only write visible clips
            if (clip->references())
                  (*i)->write(level, xml);
            }
      }

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

void ClipBase::write(int level, Xml& xml) const
      {
      xml.tag(level++, "clip");
      QString path = f.dirPath();

      //
      // waves in the project dirctory are stored
      // with relative path name, others with absolute path
      //
      if (path == museProject)
            xml.strTag(level, "file", f.name());
      else
            xml.strTag(level, "file", f.path());

      xml.strTag(level, "name", _name);
      xml.intTag(level, "tick", _spos);
      xml.intTag(level, "len", len);
      xml.etag(level, "clip");
      }

//---------------------------------------------------------
//   ClipBase::read
//---------------------------------------------------------

ClipBase* readClip(Xml& xml)
      {
      SndFile* f = 0;
      QString name;
      unsigned spos = 0;
      int len = 0;

      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return 0;
                  case Xml::TagStart:
                        if (tag == "file")
                              f = getWave(xml.parse1(), false);
                        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") {
                              if (!f)
                                    printf("clip: file not found\n");
                              ClipBase* clip = new ClipBase(f, spos, len);
                              clip->setName(name);
                              return clip;
                              }
                  default:
                        break;
                  }
            }
      }

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

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

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

void ClipList::remove(ClipBase* 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(const Clip& clip) const
      {
      int n = 0;
      for (ciClip i = begin(); i != end(); ++i, ++n) {
            if (clip == *i)
                  return n;
            }
      return -1;
      }
#endif

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

void Song::cmdAddRecordedWave(WaveTrack* track, const Pos& s, const Pos& e)
      {
      SndFile* f = track->recFile();
      if (f == 0) {
            printf("cmdAddRecordedWave: no snd file for track <%s>\n",
               track->name().latin1());
            return;
            }

      unsigned startTick = roundDownBar(s.tick());
      unsigned endTick   = roundUpBar(e.tick());

      f->update();

      WavePart* part = new WavePart(track);
      part->setTick(startTick);
      part->setLenTick(endTick - startTick);
      part->setName(track->name());

      // create Event
      Event event(Wave);
      SndFileR sf(f);
      event.setSndFile(sf);
      event.setSpos(0);
      event.setLenFrame(e.frame() - s.frame());
      part->addEvent(event);

      song->cmdAddPart(part);

      if (song->len() < endTick)
            song->setLen(endTick);
      }

//---------------------------------------------------------
//   SndFileR
//---------------------------------------------------------

SndFileR::SndFileR(SndFile* _sf)
      {
      sf = _sf;
      if (sf)
            (sf->refCount)++;
      }

SndFileR::SndFileR(const SndFileR& ed)
      {
      sf = ed.sf;
      if (sf)
            (sf->refCount)++;
      }

//---------------------------------------------------------
//   operator=
//---------------------------------------------------------

SndFileR& SndFileR::operator=(const SndFileR& ed)
      {
      if (sf == ed.sf)
            return *this;
      if (sf && --(sf->refCount) == 0)
            delete sf;
      sf = ed.sf;
      if (sf)
            (sf->refCount)++;
      return *this;
      }

//---------------------------------------------------------
//   ~SndFileR
//---------------------------------------------------------

SndFileR::~SndFileR()
      {
      if (sf && --(sf->refCount) == 0)
            delete sf;
      }
