code: //=============================================================================
  //  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 score The score to output
  /// \param device The output device
  /// \param updateProgress 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;
        // In non-GUI mode current synthesizer settings won't
        // allow single note dynamics. See issue #289947.
        const bool useCurrentSynthesizerState = !MScore::noGui;
  
        if (useCurrentSynthesizerState) {
              score->renderMidi(&events, synthesizerState());
              if (events.empty())
                    return false;
              }
  
        MasterSynthesizer* synth = synthesizerFactory();
        synth->init();
        int sampleRate = preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE);
        synth->setSampleRate(sampleRate);
  
        const SynthesizerState state = useCurrentSynthesizerState ? mscore->synthesizerState() : score->synthesizerState();
        const bool setStateOk = synth->setState(state);
  
        if (!setStateOk || !synth->hasSoundFontsLoaded())
              synth->init(); // re-initialize master synthesizer with default settings
  
        if (!useCurrentSynthesizerState) {
              score->masterScore()->rebuildAndUpdateExpressive(synth->synthesizer("Fluid"));
              score->renderMidi(&events, score->synthesizerState());
              if (synti)
                    score->masterScore()->rebuildAndUpdateExpressive(synti->synthesizer("Fluid"));
  
              if (events.empty())
                    return false;
              }
  
        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;
        int PCMRate;
        switch (preferences.getInt(PREF_EXPORT_AUDIO_PCMRATE)) {
              case 32: PCMRate = SF_FORMAT_PCM_32; break;
              case 24: PCMRate = SF_FORMAT_PCM_24; break;
              case 16: PCMRate = SF_FORMAT_PCM_16; break;
              case 8:  PCMRate = SF_FORMAT_PCM_S8; break;
              default: PCMRate = SF_FORMAT_PCM_16; break;
              }
  
        if (name.endsWith(".wav"))
              format = SF_FORMAT_WAV | PCMRate;
        else if (name.endsWith(".ogg"))
              format = SF_FORMAT_OGG | SF_FORMAT_VORBIS;
        else if (name.endsWith(".flac"))
              format = SF_FORMAT_FLAC | PCMRate;
        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
  }
  
  