//=============================================================================
//  MusE Score
//  Linux Music Score Editor
//
//  Copyright (C) 2009 Werner Schweer and others
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================

#include "config.h"
#ifdef HAS_AUDIOFILE
#include <sndfile.h>
#endif
#include "libmscore/score.h"
#include "libmscore/note.h"
#include "libmscore/part.h"
#include "libmscore/mscore.h"
#include "synthesizer/msynthesizer.h"
#include "musescore.h"
#include "preferences.h"

namespace Ms {

///
/// \brief Function to synthesize audio and output it into a generic QIODevice
/// \param The score to output
/// \param The output device
/// \param An optional callback function that will be notified with the progress in range [0, 1]
/// \return True on success, false otherwise.
///
/// If the callback function is non zero an returns false the export will be canceled.
///
bool MuseScore::saveAudio(Score* score, QIODevice *device, std::function<bool(float)> updateProgress)
    {
    if (!device) {
        qDebug() << "Invalid device";
        return false;
    }

    if (!device->open(QIODevice::WriteOnly)) {
        qDebug() << "Could not write to device";
        return false;
    }

    EventMap events;
    score->renderMidi(&events, synthesizerState());
    if(events.size() == 0)
          return false;

    MasterSynthesizer* synth = synthesizerFactory();
    synth->init();
    int sampleRate = preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE);
    synth->setSampleRate(sampleRate);
    if (MScore::noGui) { // use score settings if possible
          bool r = synth->setState(score->synthesizerState());
          if (!r)
                synth->init();
          }
    else { // use current synth settings
          bool r = synth->setState(mscore->synthesizerState());
          if (!r)
                synth->init();
          }

    int oldSampleRate  = MScore::sampleRate;
    MScore::sampleRate = sampleRate;

    float peak  = 0.0;
    double gain = 1.0;
    EventMap::const_iterator endPos = events.cend();
    --endPos;
    const int et = (score->utick2utime(endPos->first) + 1) * MScore::sampleRate;
    const int maxEndTime = (score->utick2utime(endPos->first) + 3) * MScore::sampleRate;

    bool cancelled = false;
    int passes = preferences.getBool(PREF_EXPORT_AUDIO_NORMALIZE) ? 2 : 1;
    for (int pass = 0; pass < passes; ++pass) {
          EventMap::const_iterator playPos;
          playPos = events.cbegin();
          synth->allSoundsOff(-1);

          //
          // init instruments
          //
          for (Part* part : score->parts()) {
                const InstrumentList* il = part->instruments();
                for (auto i = il->begin(); i!= il->end(); i++) {
                      for (const Channel* instrChan : i->second->channel()) {
                            const Channel* a = score->masterScore()->playbackChannel(instrChan);
                            for (MidiCoreEvent e : a->initList()) {
                                  if (e.type() == ME_INVALID)
                                        continue;
                                  e.setChannel(a->channel());
                                  int syntiIdx = synth->index(score->masterScore()->midiMapping(a->channel())->articulation()->synti());
								  synth->play(e, syntiIdx);
                                  }
                            }
                      }
                }

          static const unsigned FRAMES = 512;
          float buffer[FRAMES * 2];
          int playTime = 0;

          for (;;) {
                unsigned frames = FRAMES;
                //
                // collect events for one segment
                //
                float max = 0.0;
                memset(buffer, 0, sizeof(float) * FRAMES * 2);
                int endTime = playTime + frames;
                float* p = buffer;
                for (; playPos != events.cend(); ++playPos) {
                      int f = score->utick2utime(playPos->first) * MScore::sampleRate;
                      if (f >= endTime)
                            break;
                      int n = f - playTime;
                      if (n) {
                            synth->process(n, p);
                            p += 2 * n;
                            }

                      playTime  += n;
                      frames    -= n;
                      const NPlayEvent& e = playPos->second;
                      if (e.isChannelEvent()) {
                            int channelIdx = e.channel();
                            const Channel* c = score->masterScore()->midiMapping(channelIdx)->articulation();
                            if (!c->mute()) {
                                  synth->play(e, synth->index(c->synti()));
                                  }
                            }
                      }
                if (frames) {
                      synth->process(frames, p);
                      playTime += frames;
                      }
                if (pass == 1) {
                      for (unsigned i = 0; i < FRAMES * 2; ++i) {
                            max = qMax(max, qAbs(buffer[i]));
                            buffer[i] *= gain;
                            }
                      }
                else {
                      for (unsigned i = 0; i < FRAMES * 2; ++i) {
                            max = qMax(max, qAbs(buffer[i]));
                            peak = qMax(peak, qAbs(buffer[i]));
                            }
                      }
                if (pass == (passes - 1))
                      device->write(reinterpret_cast<const char*>(buffer), 2 * FRAMES * sizeof(float));
                playTime = endTime;
                if (updateProgress) {
                    // normalize to [0, 1] range
                    if (!updateProgress(float(pass * et + playTime) / passes / et)) {
                        cancelled = true;
                        break;
                    }
                      }
                if (playTime >= et)
                      synth->allNotesOff(-1);
                // create sound until the sound decays
                if (playTime >= et && max*peak < 0.000001)
                      break;
                // hard limit
                if (playTime > maxEndTime)
                      break;
                }
          if (cancelled)
                break;
          if (pass == 0 && peak == 0.0) {
                qDebug("song is empty");
                break;
                }
          gain = 0.99 / peak;
          }

    MScore::sampleRate = oldSampleRate;
    delete synth;

    device->close();

    return !cancelled;
}

#ifdef HAS_AUDIOFILE


//---------------------------------------------------------
//   saveAudio
//---------------------------------------------------------

bool MuseScore::saveAudio(Score* score, const QString& name)
      {
    // QIODevice - SoundFile wrapper class
    class SoundFileDevice : public QIODevice {
    private:
        SF_INFO info;
        SNDFILE *sf = nullptr;
        const QString filename;
    public:
        SoundFileDevice(int sampleRate, int format, const QString& name)
            : filename(name) {
            memset(&info, 0, sizeof(info));
            info.channels   = 2;
            info.samplerate = sampleRate;
            info.format     = format;
        }
        ~SoundFileDevice() {
            if (sf) {
                sf_close(sf);
                sf = nullptr;
            }
        }

        virtual qint64 readData(char *dta, qint64 maxlen) override final {
            Q_UNUSED(dta);
            qDebug() << "Error: No write supported!";
            return maxlen;
        }

        virtual qint64 writeData(const char *dta, qint64 len) override final {
            size_t trueFrames = len / sizeof(float) / 2;
            sf_writef_float(sf, reinterpret_cast<const float*>(dta), trueFrames);
            return trueFrames * 2 * sizeof(float);
        }

        bool open(QIODevice::OpenMode mode) {
            if ((mode & QIODevice::WriteOnly) == 0) {
                return false;
            }
            sf     = sf_open(qPrintable(filename), SFM_WRITE, &info);
            if (sf == nullptr) {
                  qDebug("open soundfile failed: %s", sf_strerror(sf));
                  return false;
            }
            return QIODevice::open(mode);
        }
        void close() {
            if (sf && sf_close(sf)) {
                  qDebug("close soundfile failed");
            }
            sf = nullptr;
            QIODevice::close();
        }
    };
      int format;
      if (name.endsWith(".wav"))
            format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
      else if (name.endsWith(".ogg"))
            format = SF_FORMAT_OGG | SF_FORMAT_VORBIS;
      else if (name.endsWith("flac"))
            format = SF_FORMAT_FLAC | SF_FORMAT_PCM_16;
      else {
            qDebug("unknown audio file type <%s>", qPrintable(name));
            return false;
            }

      EventMap events;
      score->renderMidi(&events, synthesizerState());
      if(events.size() == 0)
            return false;

      MasterSynthesizer* synth = synthesizerFactory();
	  synth->init();
      int sampleRate = preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE);
	  synth->setSampleRate(sampleRate);
      bool r = synth->setState(score->synthesizerState());
      if (!r)
          synth->init();

      int oldSampleRate  = MScore::sampleRate;
      MScore::sampleRate = sampleRate;


      SoundFileDevice device(sampleRate, format, name);

      // dummy callback function that will be used if there is no gui
      std::function<bool(float)> progressCallback = [](float) {return true;};

      QProgressDialog progress(this);
      progress.setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint));
      progress.setWindowModality(Qt::ApplicationModal);
      //progress.setCancelButton(0);
      progress.setCancelButtonText(tr("Cancel"));
      progress.setLabelText(tr("Exporting…"));
      if (!MScore::noGui) {
          // callback function that will update the progress bar
          // it will return false and thus cancel the export if the user
          // cancels the progress dialog.
          progressCallback = [&progress](float v) -> bool {
              if (progress.wasCanceled())
                    return false;
              progress.setValue(v * 1000);
              qApp->processEvents();
              return true;
          };

            progress.show();
      }

      // The range is set arbitrarily to 1000 as steps.
      // The callback will return float numbers between 0 and 1
      // which will be scaled into integer 0 to 1000 numbers
      // which allows a smooth transition.
      progress.setRange(0, 1000);

      // Save the audio to the SoundFile device
      bool result = saveAudio(score, &device, progressCallback);

      bool wasCanceled = progress.wasCanceled();
      progress.close();

      MScore::sampleRate = oldSampleRate;
      delete synth;

      if (wasCanceled)
            QFile::remove(name);

      return result;
      }

#endif // HAS_AUDIOFILE
}

