1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 #define RG_MODULE_STRING "[AudioFileTimeStretcher]"
19 
20 #include "AudioFileTimeStretcher.h"
21 
22 #include "AudioTimeStretcher.h"
23 #include "AudioFileManager.h"
24 #include "WAVAudioFile.h"
25 #include "base/RealTime.h"
26 #include "misc/Debug.h"
27 
28 #include <QApplication>
29 #include <QProgressDialog>
30 
31 #include <fstream>
32 
33 #ifdef __FreeBSD__
34 #include <stdlib.h>
35 #else
36 #include <alloca.h>
37 #endif
38 
39 namespace Rosegarden {
40 
41 
AudioFileTimeStretcher(AudioFileManager * afm)42 AudioFileTimeStretcher::AudioFileTimeStretcher(AudioFileManager *afm) :
43         m_audioFileManager(afm)
44 {
45 }
46 
~AudioFileTimeStretcher()47 AudioFileTimeStretcher::~AudioFileTimeStretcher()
48 {
49 }
50 
51 AudioFileId
getStretchedAudioFile(AudioFileId source,float ratio)52 AudioFileTimeStretcher::getStretchedAudioFile(AudioFileId source,
53                                               float ratio)
54 {
55     AudioFile *sourceFile = m_audioFileManager->getAudioFile(source);
56     if (!sourceFile) {
57         RG_WARNING << "getStretchedAudioFile(): WARNING: Source file not found for ID" << source;
58         return -1;
59     }
60 
61     RG_DEBUG << "getStretchedAudioFile(): got source file id " << source << ", name " << sourceFile->getFilename();
62 
63     AudioFile *file = m_audioFileManager->createDerivedAudioFile(source, "stretch");
64     if (!file) {
65         RG_WARNING << "getStretchedAudioFile(): WARNING: createDerivedAudioFile() failed for ID" << source << ", using path: " << m_audioFileManager->getAudioPath();
66         return -1;
67     }
68 
69     RG_DEBUG << "getStretchedAudioFile(): got derived file id " << file->getId() << ", name " << file->getFilename();
70 
71     std::ifstream streamIn(sourceFile->getFilename().toLocal8Bit(),
72                            std::ios::in | std::ios::binary);
73     if (!streamIn) {
74         RG_WARNING << "getStretchedAudioFile(): WARNING: Creation of ifstream failed for file " << sourceFile->getFilename();
75         return -1;
76     }
77 
78     if (m_progressDialog) {
79         m_progressDialog->setLabelText(tr("Rescaling audio file..."));
80         m_progressDialog->setRange(0, 100);
81     }
82 
83     //!!!
84     //...
85     // Need to make SoundDriver::getAudioRecFileFormat available?
86     // -- the sound file classes should just have a float interface
87     // (like libsndfile, or hey!, we could use libsndfile...)
88 
89     WAVAudioFile writeFile
90         (file->getFilename(),
91          sourceFile->getChannels(),
92          sourceFile->getSampleRate(),
93          sourceFile->getSampleRate() * 4 * sourceFile->getChannels(),
94          4 * sourceFile->getChannels(),
95          32);
96 
97     if (!writeFile.write()) {
98         RG_WARNING << "getStretchedAudioFile(): WARNING: write() failed for file " << file->getFilename();
99         return -1;
100     }
101 
102     int obs = 1024;
103     int ibs = obs / ratio;
104     int ch = sourceFile->getChannels();
105     int sr = sourceFile->getSampleRate();
106 
107     AudioTimeStretcher stretcher(sr, ch, ratio, true, obs);
108 
109     // We'll first prime the timestretcher with half its window size
110     // of silence, an amount which we then discard at the start of the
111     // output (as well as its own processing latency).  Really the
112     // timestretcher should handle this itself and report it in its
113     // own latency calculation
114 
115     size_t padding = stretcher.getWindowSize()/2;
116 
117     char *ebf = (char *)alloca
118         (ch * ibs * sourceFile->getBytesPerFrame());
119 
120     std::vector<float *> dbfs;
121     for (int c = 0; c < ch; ++c) {
122         dbfs.push_back((float *)alloca((ibs > int(padding) ? size_t(ibs) : padding)
123                                        * sizeof(float)));
124     }
125 
126     float **ibfs = (float **)alloca(ch * sizeof(float *));
127     float **obfs = (float **)alloca(ch * sizeof(float *));
128 
129     for (int c = 0; c < ch; ++c) {
130         ibfs[c] = dbfs[c];
131     }
132 
133     for (int c = 0; c < ch; ++c) {
134         obfs[c] = (float *)alloca(obs * sizeof(float));
135     }
136 
137     char *oebf = (char *)alloca(ch * obs * sizeof(float));
138 
139     int totalIn = 0, totalOut = 0;
140 
141     for (int c = 0; c < ch; ++c) {
142         for (size_t i = 0; i < padding; ++i) {
143             ibfs[c][i] = 0.f;
144         }
145     }
146     stretcher.putInput(ibfs, padding);
147 
148     RealTime totalTime = sourceFile->getLength();
149     long fileTotalIn = RealTime::realTime2Frame
150         (totalTime, sourceFile->getSampleRate());
151     int progressCount = 0;
152 
153     long expectedOut = ceil(fileTotalIn * ratio);
154 
155     bool inputExhausted = false;
156 
157     sourceFile->scanTo(&streamIn, RealTime::zeroTime);
158 
159     while (1) {
160 
161         if (m_progressDialog  &&  m_progressDialog->wasCanceled()) {
162             RG_DEBUG << "getStretchedAudioFile(): cancelled";
163             return -1;
164         }
165 
166         unsigned int thisRead = 0;
167 
168         if (!inputExhausted) {
169             thisRead = sourceFile->getSampleFrames(&streamIn, ebf, ibs);
170             if (int(thisRead) < ibs) inputExhausted = true;
171         }
172 
173         if (thisRead == 0) {
174             if (totalOut >= expectedOut) break;
175             else {
176                 // run out of input data, continue feeding zeroes until
177                 // we have enough output data
178                 for (int c = 0; c < ch; ++c) {
179                     for (int i = 0; i < ibs; ++i) {
180                         ibfs[c][i] = 0.f;
181                     }
182                 }
183                 thisRead = ibs;
184             }
185         }
186 
187         if (!sourceFile->decode((unsigned char *)ebf,
188                                 thisRead * sourceFile->getBytesPerFrame(),
189                                 sr, ch,
190                                 thisRead, dbfs, false)) {
191             RG_WARNING << "getStretchedAudioFile(): ERROR: AudioFile failed to decode its own output";
192             break;
193         }
194 
195         stretcher.putInput(ibfs, thisRead);
196         totalIn += thisRead;
197 
198         unsigned int available = stretcher.getAvailableOutputSamples();
199 
200         while (available > 0) {
201 
202             unsigned int count = available;
203             if (count > (unsigned int)obs) count = (unsigned int)obs;
204 
205             if (padding > 0) {
206                 if (count <= padding) {
207                     stretcher.getOutput(obfs, count);
208                     padding -= count;
209                     available -= count;
210                     continue;
211                 } else {
212                     stretcher.getOutput(obfs, padding);
213                     count -= padding;
214                     available -= padding;
215                     padding = 0;
216                 }
217             }
218 
219             stretcher.getOutput(obfs, count);
220 
221             char *encodePointer = oebf;
222             for (unsigned int i = 0; i < count; ++i) {
223                 for (int c = 0; c < ch; ++c) {
224                     float sample = obfs[c][i];
225                     *(float *)encodePointer = sample;
226                     encodePointer += sizeof(float);
227                 }
228             }
229 
230             if (totalOut < expectedOut &&
231                 totalOut + int(count) > expectedOut) {
232                 count = expectedOut - totalOut;
233             }
234 
235             writeFile.appendSamples(oebf, count);
236             totalOut += count;
237             available -= count;
238 
239             if (totalOut >= expectedOut) break;
240         }
241 
242         if (++progressCount == 100) {
243             int progress = static_cast<int>(100.0 * totalIn / fileTotalIn);
244             if (m_progressDialog)
245                 m_progressDialog->setValue(progress);
246 
247             progressCount = 0;
248         }
249 
250         qApp->processEvents();
251     }
252 
253     if (m_progressDialog)
254         m_progressDialog->setValue(100);
255 
256     qApp->processEvents();
257 
258     writeFile.close();
259 
260     RG_DEBUG << "getStretchedAudioFile(): success, id is " << file->getId();
261 
262     return file->getId();
263 }
264 
265 
266 }
267 
268 
269