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