1 //=============================================================================
2 //  MusE Score
3 //  Linux Music Score Editor
4 //
5 //  Copyright (C) 2009 Werner Schweer and others
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 #include "config.h"
21 
22 #ifdef Q_OS_WIN
23 #define WIN32_LEAN_AND_MEAN
24 #include <windows.h>
25 #define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
26 #endif
27 
28 #ifdef HAS_AUDIOFILE
29 #include <sndfile.h>
30 #endif
31 
32 #include "libmscore/score.h"
33 #include "libmscore/note.h"
34 #include "libmscore/part.h"
35 #include "libmscore/mscore.h"
36 #include "audio/midi/msynthesizer.h"
37 #include "musescore.h"
38 #include "preferences.h"
39 
40 namespace Ms {
41 
42 ///
43 /// \brief Function to synthesize audio and output it into a generic QIODevice
44 /// \param score The score to output
45 /// \param device The output device
46 /// \param updateProgress An optional callback function that will be notified with the progress in range [0, 1]
47 /// \return True on success, false otherwise.
48 ///
49 /// If the callback function is non zero an returns false the export will be canceled.
50 ///
saveAudio(Score * score,QIODevice * device,std::function<bool (float)> updateProgress)51 bool MuseScore::saveAudio(Score* score, QIODevice *device, std::function<bool(float)> updateProgress)
52       {
53       if (!device) {
54             qDebug() << "Invalid device";
55             return false;
56             }
57 
58       if (!device->open(QIODevice::WriteOnly)) {
59             qDebug() << "Could not write to device";
60             return false;
61             }
62 
63       EventMap events;
64       // In non-GUI mode current synthesizer settings won't
65       // allow single note dynamics. See issue #289947.
66       const bool useCurrentSynthesizerState = !MScore::noGui;
67 
68       if (useCurrentSynthesizerState) {
69             score->renderMidi(&events, synthesizerState());
70             if (events.empty()) {
71                   device->close();
72                   return false;
73                   }
74             }
75 
76       MasterSynthesizer* synth = synthesizerFactory();
77       synth->init();
78       int sampleRate = preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE);
79       synth->setSampleRate(sampleRate);
80 
81       const SynthesizerState state = useCurrentSynthesizerState ? mscore->synthesizerState() : score->synthesizerState();
82       const bool setStateOk = synth->setState(state);
83 
84       if (!setStateOk || !synth->hasSoundFontsLoaded())
85             synth->init(); // re-initialize master synthesizer with default settings
86 
87       if (!useCurrentSynthesizerState) {
88             score->masterScore()->rebuildAndUpdateExpressive(synth->synthesizer("Fluid"));
89             score->renderMidi(&events, score->synthesizerState());
90             if (synti)
91                   score->masterScore()->rebuildAndUpdateExpressive(synti->synthesizer("Fluid"));
92 
93             if (events.empty())
94                   return false;
95             }
96 
97       int oldSampleRate  = MScore::sampleRate;
98       MScore::sampleRate = sampleRate;
99 
100       float peak  = 0.0;
101       double gain = 1.0;
102       EventMap::const_iterator endPos = events.cend();
103       --endPos;
104       const int et = (score->utick2utime(endPos->first) + 1) * MScore::sampleRate;
105       const int maxEndTime = (score->utick2utime(endPos->first) + 3) * MScore::sampleRate;
106 
107       bool cancelled = false;
108       int passes = preferences.getBool(PREF_EXPORT_AUDIO_NORMALIZE) ? 2 : 1;
109       for (int pass = 0; pass < passes; ++pass) {
110             EventMap::const_iterator playPos;
111             playPos = events.cbegin();
112             synth->allSoundsOff(-1);
113 
114             //
115             // init instruments
116             //
117             for (Part* part : score->parts()) {
118                   const InstrumentList* il = part->instruments();
119                   for (auto i = il->begin(); i!= il->end(); i++) {
120                         for (const Channel* instrChan : i->second->channel()) {
121                               const Channel* a = score->masterScore()->playbackChannel(instrChan);
122                               for (MidiCoreEvent e : a->initList()) {
123                                     if (e.type() == ME_INVALID)
124                                           continue;
125                                     e.setChannel(a->channel());
126                                     int syntiIdx = synth->index(score->masterScore()->midiMapping(a->channel())->articulation()->synti());
127                                     synth->play(e, syntiIdx);
128                                     }
129                               }
130                         }
131                   }
132 
133             static const unsigned FRAMES = 512;
134             float buffer[FRAMES * 2];
135             int playTime = 0;
136 
137             for (;;) {
138                   unsigned frames = FRAMES;
139                   //
140                   // collect events for one segment
141                   //
142                   float max = 0.0;
143                   memset(buffer, 0, sizeof(float) * FRAMES * 2);
144                   int endTime = playTime + frames;
145                   float* p = buffer;
146                   for (; playPos != events.cend(); ++playPos) {
147                         int f = score->utick2utime(playPos->first) * MScore::sampleRate;
148                         if (f >= endTime)
149                               break;
150                         int n = f - playTime;
151                         if (n) {
152                               synth->process(n, p);
153                               p += 2 * n;
154                               }
155 
156                         playTime  += n;
157                         frames    -= n;
158                         const NPlayEvent& e = playPos->second;
159                         if (!(!e.velo() && e.discard()) && e.isChannelEvent()) {
160                               int channelIdx = e.channel();
161                               const Channel* c = score->masterScore()->midiMapping(channelIdx)->articulation();
162                               if (!c->mute()) {
163                                     synth->play(e, synth->index(c->synti()));
164                                     }
165                               }
166                         }
167                   if (frames) {
168                         synth->process(frames, p);
169                         playTime += frames;
170                         }
171                   if (pass == 1) {
172                         for (unsigned i = 0; i < FRAMES * 2; ++i) {
173                               max = qMax(max, qAbs(buffer[i]));
174                               buffer[i] *= gain;
175                               }
176                         }
177                   else {
178                         for (unsigned i = 0; i < FRAMES * 2; ++i) {
179                               max = qMax(max, qAbs(buffer[i]));
180                               peak = qMax(peak, qAbs(buffer[i]));
181                               }
182                         }
183                   if (pass == (passes - 1))
184                         device->write(reinterpret_cast<const char*>(buffer), 2 * FRAMES * sizeof(float));
185                   playTime = endTime;
186                   if (updateProgress) {
187                         // normalize to [0, 1] range
188                         if (!updateProgress(float(pass * et + playTime) / passes / et)) {
189                               cancelled = true;
190                               break;
191                               }
192                         }
193                   if (playTime >= et)
194                         synth->allNotesOff(-1);
195                   // create sound until the sound decays
196                   if (playTime >= et && max*peak < 0.000001)
197                         break;
198                   // hard limit
199                   if (playTime > maxEndTime)
200                         break;
201                   }
202             if (cancelled)
203                   break;
204             if (pass == 0 && peak == 0.0) {
205                   qDebug("song is empty");
206                   break;
207                   }
208             gain = 0.99 / peak;
209             }
210 
211       MScore::sampleRate = oldSampleRate;
212       delete synth;
213 
214       device->close();
215 
216       return !cancelled;
217       }
218 
219 #ifdef HAS_AUDIOFILE
220 
221 
222 //---------------------------------------------------------
223 //   saveAudio
224 //---------------------------------------------------------
225 
saveAudio(Score * score,const QString & name)226 bool MuseScore::saveAudio(Score* score, const QString& name)
227       {
228       // QIODevice - SoundFile wrapper class
229       class SoundFileDevice : public QIODevice {
230       private:
231             SF_INFO info;
232             SNDFILE *sf = nullptr;
233             const QString filename;
234       public:
235             SoundFileDevice(int sampleRate, int format, const QString& name)
236                   : filename(name) {
237                   memset(&info, 0, sizeof(info));
238                   info.channels   = 2;
239                   info.samplerate = sampleRate;
240                   info.format     = format;
241                   }
242             ~SoundFileDevice() {
243                   if (sf) {
244                         sf_close(sf);
245                         sf = nullptr;
246                         }
247                   }
248 
249             virtual qint64 readData(char *dta, qint64 maxlen) override final {
250                   Q_UNUSED(dta);
251                   qDebug() << "Error: No write supported!";
252                   return maxlen;
253                   }
254 
255             virtual qint64 writeData(const char *dta, qint64 len) override final {
256                   size_t trueFrames = len / sizeof(float) / 2;
257                   sf_writef_float(sf, reinterpret_cast<const float*>(dta), trueFrames);
258                   return trueFrames * 2 * sizeof(float);
259                   }
260 
261             bool open(QIODevice::OpenMode mode) {
262                   if ((mode & QIODevice::WriteOnly) == 0) {
263                         return false;
264                         }
265 
266 #ifdef Q_OS_WIN
267                   #define SF_FILENAME_LEN	1024
268                   QByteArray path = filename.toUtf8();
269                   wchar_t wpath[SF_FILENAME_LEN];
270                   int dwRet = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path.constData(), -1, wpath, SF_FILENAME_LEN);
271                   if (dwRet == 0) {
272                         qCritical() << Q_FUNC_INFO << "filed get path: " << GetLastError() << "\n";
273                         return false;
274                         }
275                   sf = sf_wchar_open(wpath, SFM_WRITE, &info);
276 #else  // Q_OS_WIN
277                   sf = sf_open(qPrintable(filename), SFM_WRITE, &info);
278 #endif // Q_OS_WIN
279 
280                   if (sf == nullptr) {
281                         qDebug("open soundfile failed: %s", sf_strerror(sf));
282                         return false;
283                         }
284 
285                   return QIODevice::open(mode);
286                   }
287             void close() {
288                   if (sf && sf_close(sf)) {
289                         qDebug("close soundfile failed");
290                         }
291 
292                   sf = nullptr;
293                   QIODevice::close();
294                   }
295             };
296       int format;
297       int PCMRate;
298       switch (preferences.getInt(PREF_EXPORT_AUDIO_PCMRATE)) {
299             case 32: PCMRate = SF_FORMAT_PCM_32; break;
300             case 24: PCMRate = SF_FORMAT_PCM_24; break;
301             case 16: PCMRate = SF_FORMAT_PCM_16; break;
302             case 8:  PCMRate = SF_FORMAT_PCM_S8; break;
303             default: PCMRate = SF_FORMAT_PCM_16; break;
304             }
305 
306       if (name.endsWith(".wav"))
307             format = SF_FORMAT_WAV | PCMRate;
308       else if (name.endsWith(".ogg"))
309             format = SF_FORMAT_OGG | SF_FORMAT_VORBIS;
310       else if (name.endsWith(".flac"))
311             format = SF_FORMAT_FLAC | PCMRate;
312       else {
313             qDebug("unknown audio file type <%s>", qPrintable(name));
314             return false;
315             }
316 
317       EventMap events;
318       score->renderMidi(&events, synthesizerState());
319       if(events.size() == 0)
320             return false;
321 
322       MasterSynthesizer* synth = synthesizerFactory();
323       synth->init();
324       int sampleRate = preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE);
325       synth->setSampleRate(sampleRate);
326       bool r = synth->setState(score->synthesizerState());
327       if (!r)
328             synth->init();
329 
330       int oldSampleRate  = MScore::sampleRate;
331       MScore::sampleRate = sampleRate;
332 
333 
334       SoundFileDevice device(sampleRate, format, name);
335 
336       // dummy callback function that will be used if there is no gui
337       std::function<bool(float)> progressCallback = [](float) {return true;};
338 
339       QProgressDialog progress(this);
340       progress.setWindowFlags(Qt::WindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint));
341       progress.setWindowModality(Qt::ApplicationModal);
342       //progress.setCancelButton(0);
343       progress.setCancelButtonText(tr("Cancel"));
344       progress.setLabelText(tr("Exporting…"));
345       if (!MScore::noGui) {
346             // callback function that will update the progress bar
347             // it will return false and thus cancel the export if the user
348             // cancels the progress dialog.
349             progressCallback = [&progress](float v) -> bool {
350                   if (progress.wasCanceled())
351                         return false;
352                   progress.setValue(v * 1000);
353                   qApp->processEvents();
354                   return true;
355                   };
356 
357             progress.show();
358             }
359 
360       // The range is set arbitrarily to 1000 as steps.
361       // The callback will return float numbers between 0 and 1
362       // which will be scaled into integer 0 to 1000 numbers
363       // which allows a smooth transition.
364       progress.setRange(0, 1000);
365 
366       // Save the audio to the SoundFile device
367       bool result = saveAudio(score, &device, progressCallback);
368 
369       bool wasCanceled = progress.wasCanceled();
370       progress.close();
371 
372       MScore::sampleRate = oldSampleRate;
373       delete synth;
374 
375       if (wasCanceled)
376             QFile::remove(name);
377 
378       return result;
379       }
380 
381 #endif // HAS_AUDIOFILE
382 }
383 
384