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