Chris@175
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@175
|
2
|
Chris@175
|
3 /*
|
Chris@175
|
4 Sonic Visualiser
|
Chris@175
|
5 An audio file viewer and annotation editor.
|
Chris@175
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@202
|
7 This file copyright 2006 QMUL.
|
Chris@175
|
8
|
Chris@175
|
9 This program is free software; you can redistribute it and/or
|
Chris@175
|
10 modify it under the terms of the GNU General Public License as
|
Chris@175
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@175
|
12 License, or (at your option) any later version. See the file
|
Chris@175
|
13 COPYING included with this distribution for more information.
|
Chris@175
|
14 */
|
Chris@175
|
15
|
Chris@175
|
16 #include "WritableWaveFileModel.h"
|
Chris@175
|
17
|
Chris@1122
|
18 #include "ReadOnlyWaveFileModel.h"
|
Chris@1122
|
19
|
Chris@175
|
20 #include "base/TempDirectory.h"
|
Chris@175
|
21 #include "base/Exceptions.h"
|
Chris@1751
|
22 #include "base/PlayParameterRepository.h"
|
Chris@175
|
23
|
Chris@175
|
24 #include "fileio/WavFileWriter.h"
|
Chris@175
|
25 #include "fileio/WavFileReader.h"
|
Chris@175
|
26
|
Chris@175
|
27 #include <QDir>
|
Chris@314
|
28 #include <QTextStream>
|
Chris@175
|
29
|
Chris@175
|
30 #include <cassert>
|
Chris@175
|
31 #include <iostream>
|
Chris@723
|
32 #include <stdint.h>
|
Chris@175
|
33
|
Chris@1096
|
34 using namespace std;
|
Chris@1096
|
35
|
Chris@1133
|
36 const int WritableWaveFileModel::PROPORTION_UNKNOWN = -1;
|
Chris@1133
|
37
|
Chris@258
|
38 //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1
|
Chris@187
|
39
|
Chris@1520
|
40 WritableWaveFileModel::WritableWaveFileModel(QString path,
|
Chris@1520
|
41 sv_samplerate_t sampleRate,
|
Chris@1429
|
42 int channels,
|
Chris@1520
|
43 Normalisation norm) :
|
Chris@1582
|
44 m_model(nullptr),
|
Chris@1582
|
45 m_temporaryWriter(nullptr),
|
Chris@1582
|
46 m_targetWriter(nullptr),
|
Chris@1582
|
47 m_reader(nullptr),
|
Chris@1520
|
48 m_normalisation(norm),
|
Chris@175
|
49 m_sampleRate(sampleRate),
|
Chris@175
|
50 m_channels(channels),
|
Chris@188
|
51 m_frameCount(0),
|
Chris@300
|
52 m_startFrame(0),
|
Chris@1133
|
53 m_proportion(PROPORTION_UNKNOWN)
|
Chris@175
|
54 {
|
Chris@1520
|
55 init(path);
|
Chris@1520
|
56 }
|
Chris@1520
|
57
|
Chris@1520
|
58 WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
|
Chris@1520
|
59 int channels,
|
Chris@1520
|
60 Normalisation norm) :
|
Chris@1582
|
61 m_model(nullptr),
|
Chris@1582
|
62 m_temporaryWriter(nullptr),
|
Chris@1582
|
63 m_targetWriter(nullptr),
|
Chris@1582
|
64 m_reader(nullptr),
|
Chris@1520
|
65 m_normalisation(norm),
|
Chris@1520
|
66 m_sampleRate(sampleRate),
|
Chris@1520
|
67 m_channels(channels),
|
Chris@1520
|
68 m_frameCount(0),
|
Chris@1520
|
69 m_startFrame(0),
|
Chris@1520
|
70 m_proportion(PROPORTION_UNKNOWN)
|
Chris@1520
|
71 {
|
Chris@1520
|
72 init();
|
Chris@1520
|
73 }
|
Chris@1520
|
74
|
Chris@1520
|
75 WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate,
|
Chris@1520
|
76 int channels) :
|
Chris@1582
|
77 m_model(nullptr),
|
Chris@1582
|
78 m_temporaryWriter(nullptr),
|
Chris@1582
|
79 m_targetWriter(nullptr),
|
Chris@1582
|
80 m_reader(nullptr),
|
Chris@1520
|
81 m_normalisation(Normalisation::None),
|
Chris@1520
|
82 m_sampleRate(sampleRate),
|
Chris@1520
|
83 m_channels(channels),
|
Chris@1520
|
84 m_frameCount(0),
|
Chris@1520
|
85 m_startFrame(0),
|
Chris@1520
|
86 m_proportion(PROPORTION_UNKNOWN)
|
Chris@1520
|
87 {
|
Chris@1520
|
88 init();
|
Chris@1520
|
89 }
|
Chris@1520
|
90
|
Chris@1520
|
91 void
|
Chris@1520
|
92 WritableWaveFileModel::init(QString path)
|
Chris@1520
|
93 {
|
Chris@175
|
94 if (path.isEmpty()) {
|
Chris@175
|
95 try {
|
Chris@1520
|
96 // Temp dir is exclusive to this run of the application,
|
Chris@1520
|
97 // so the filename only needs to be unique within that -
|
Chris@1520
|
98 // model ID should be ok
|
Chris@175
|
99 QDir dir(TempDirectory::getInstance()->getPath());
|
Chris@1731
|
100 path = dir.filePath(QString("written_%1.wav")
|
Chris@1742
|
101 .arg(getId().untyped));
|
Chris@1465
|
102 } catch (const DirectoryCreationFailed &f) {
|
Chris@1428
|
103 SVCERR << "WritableWaveFileModel: Failed to create temporary directory" << endl;
|
Chris@175
|
104 return;
|
Chris@175
|
105 }
|
Chris@175
|
106 }
|
Chris@175
|
107
|
Chris@1520
|
108 m_targetPath = path;
|
Chris@1520
|
109 m_temporaryPath = "";
|
Chris@1520
|
110
|
Chris@1520
|
111 // We don't delete or null-out writer/reader members after
|
Chris@1520
|
112 // failures here - they are all deleted in the dtor, and the
|
Chris@1520
|
113 // presence/existence of the model is what's used to determine
|
Chris@1520
|
114 // whether to go ahead, not the writer/readers. If the model is
|
Chris@1520
|
115 // non-null, then the necessary writer/readers must be OK, as the
|
Chris@1520
|
116 // model is the last thing initialised
|
Chris@1520
|
117
|
Chris@1520
|
118 m_targetWriter = new WavFileWriter(m_targetPath, m_sampleRate, m_channels,
|
Chris@1520
|
119 WavFileWriter::WriteToTarget);
|
Chris@1520
|
120
|
Chris@1520
|
121 if (!m_targetWriter->isOK()) {
|
Chris@1520
|
122 SVCERR << "WritableWaveFileModel: Error in creating WAV file writer: " << m_targetWriter->getError() << endl;
|
Chris@175
|
123 return;
|
Chris@175
|
124 }
|
Chris@1520
|
125
|
Chris@1520
|
126 if (m_normalisation != Normalisation::None) {
|
Chris@187
|
127
|
Chris@1520
|
128 // Temp dir is exclusive to this run of the application, so
|
Chris@1520
|
129 // the filename only needs to be unique within that
|
Chris@1520
|
130 QDir dir(TempDirectory::getInstance()->getPath());
|
Chris@1731
|
131 m_temporaryPath = dir.filePath(QString("prenorm_%1.wav")
|
Chris@1742
|
132 .arg(getId().untyped));
|
Chris@316
|
133
|
Chris@1520
|
134 m_temporaryWriter = new WavFileWriter
|
Chris@1520
|
135 (m_temporaryPath, m_sampleRate, m_channels,
|
Chris@1520
|
136 WavFileWriter::WriteToTarget);
|
Chris@1520
|
137
|
Chris@1520
|
138 if (!m_temporaryWriter->isOK()) {
|
Chris@1520
|
139 SVCERR << "WritableWaveFileModel: Error in creating temporary WAV file writer: " << m_temporaryWriter->getError() << endl;
|
Chris@1520
|
140 return;
|
Chris@1520
|
141 }
|
Chris@1520
|
142 }
|
Chris@1520
|
143
|
Chris@1520
|
144 FileSource source(m_targetPath);
|
Chris@1520
|
145
|
Chris@1520
|
146 m_reader = new WavFileReader(source, true);
|
Chris@290
|
147 if (!m_reader->getError().isEmpty()) {
|
Chris@1520
|
148 SVCERR << "WritableWaveFileModel: Error in creating wave file reader: " << m_reader->getError() << endl;
|
Chris@187
|
149 return;
|
Chris@187
|
150 }
|
Chris@187
|
151
|
Chris@1122
|
152 m_model = new ReadOnlyWaveFileModel(source, m_reader);
|
Chris@187
|
153 if (!m_model->isOK()) {
|
Chris@1428
|
154 SVCERR << "WritableWaveFileModel: Error in creating wave file model" << endl;
|
Chris@187
|
155 delete m_model;
|
Chris@1582
|
156 m_model = nullptr;
|
Chris@187
|
157 return;
|
Chris@187
|
158 }
|
Chris@300
|
159 m_model->setStartFrame(m_startFrame);
|
Chris@187
|
160
|
Chris@1770
|
161 connect(m_model, SIGNAL(modelChanged(ModelId)),
|
Chris@1770
|
162 this, SLOT(componentModelChanged(ModelId)));
|
Chris@1770
|
163 connect(m_model, SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
|
Chris@1770
|
164 this, SLOT(componentModelChangedWithin(ModelId, sv_frame_t, sv_frame_t)));
|
Chris@1751
|
165
|
Chris@1751
|
166 PlayParameterRepository::getInstance()->addPlayable
|
Chris@1751
|
167 (getId().untyped, this);
|
Chris@175
|
168 }
|
Chris@175
|
169
|
Chris@175
|
170 WritableWaveFileModel::~WritableWaveFileModel()
|
Chris@175
|
171 {
|
Chris@1751
|
172 PlayParameterRepository::getInstance()->removePlayable
|
Chris@1751
|
173 (getId().untyped);
|
Chris@1751
|
174
|
Chris@175
|
175 delete m_model;
|
Chris@1520
|
176 delete m_targetWriter;
|
Chris@1520
|
177 delete m_temporaryWriter;
|
Chris@175
|
178 delete m_reader;
|
Chris@175
|
179 }
|
Chris@175
|
180
|
Chris@300
|
181 void
|
Chris@1770
|
182 WritableWaveFileModel::componentModelChanged(ModelId)
|
Chris@1770
|
183 {
|
Chris@1770
|
184 emit modelChanged(getId());
|
Chris@1770
|
185 }
|
Chris@1770
|
186
|
Chris@1770
|
187 void
|
Chris@1770
|
188 WritableWaveFileModel::componentModelChangedWithin(ModelId, sv_frame_t f0, sv_frame_t f1)
|
Chris@1770
|
189 {
|
Chris@1770
|
190 emit modelChangedWithin(getId(), f0, f1);
|
Chris@1770
|
191 }
|
Chris@1770
|
192
|
Chris@1770
|
193 void
|
Chris@1038
|
194 WritableWaveFileModel::setStartFrame(sv_frame_t startFrame)
|
Chris@300
|
195 {
|
Chris@300
|
196 m_startFrame = startFrame;
|
Chris@1520
|
197 if (m_model) {
|
Chris@1520
|
198 m_model->setStartFrame(startFrame);
|
Chris@1520
|
199 }
|
Chris@300
|
200 }
|
Chris@300
|
201
|
Chris@175
|
202 bool
|
Chris@1325
|
203 WritableWaveFileModel::addSamples(const float *const *samples, sv_frame_t count)
|
Chris@175
|
204 {
|
Chris@1520
|
205 if (!m_model) return false;
|
Chris@175
|
206
|
Chris@258
|
207 #ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
|
Chris@690
|
208 // SVDEBUG << "WritableWaveFileModel::addSamples(" << count << ")" << endl;
|
Chris@258
|
209 #endif
|
Chris@258
|
210
|
Chris@1520
|
211 WavFileWriter *writer = m_targetWriter;
|
Chris@1520
|
212 if (m_normalisation != Normalisation::None) {
|
Chris@1520
|
213 writer = m_temporaryWriter;
|
Chris@1520
|
214 }
|
Chris@1520
|
215
|
Chris@1520
|
216 if (!writer->writeSamples(samples, count)) {
|
Chris@1520
|
217 SVCERR << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << writer->getError() << endl;
|
Chris@175
|
218 return false;
|
Chris@175
|
219 }
|
Chris@175
|
220
|
Chris@175
|
221 m_frameCount += count;
|
Chris@175
|
222
|
Chris@1520
|
223 if (m_normalisation == Normalisation::None) {
|
Chris@1520
|
224 if (m_reader->getChannelCount() == 0) {
|
Chris@1520
|
225 m_reader->updateFrameCount();
|
Chris@1520
|
226 }
|
Chris@175
|
227 }
|
Chris@175
|
228
|
Chris@175
|
229 return true;
|
Chris@175
|
230 }
|
Chris@175
|
231
|
Chris@1337
|
232 void
|
Chris@1337
|
233 WritableWaveFileModel::updateModel()
|
Chris@1337
|
234 {
|
Chris@1520
|
235 if (!m_model) return;
|
Chris@1520
|
236
|
Chris@1520
|
237 m_reader->updateFrameCount();
|
Chris@1337
|
238 }
|
Chris@1337
|
239
|
Chris@175
|
240 bool
|
Chris@175
|
241 WritableWaveFileModel::isOK() const
|
Chris@175
|
242 {
|
Chris@1520
|
243 return (m_model && m_model->isOK());
|
Chris@175
|
244 }
|
Chris@175
|
245
|
Chris@188
|
246 void
|
Chris@1133
|
247 WritableWaveFileModel::setWriteProportion(int proportion)
|
Chris@188
|
248 {
|
Chris@1133
|
249 m_proportion = proportion;
|
Chris@1133
|
250 }
|
Chris@1133
|
251
|
Chris@1133
|
252 int
|
Chris@1133
|
253 WritableWaveFileModel::getWriteProportion() const
|
Chris@1133
|
254 {
|
Chris@1133
|
255 return m_proportion;
|
Chris@1133
|
256 }
|
Chris@1133
|
257
|
Chris@1133
|
258 void
|
Chris@1133
|
259 WritableWaveFileModel::writeComplete()
|
Chris@1133
|
260 {
|
Chris@1520
|
261 if (!m_model) return;
|
Chris@1520
|
262
|
Chris@1520
|
263 if (m_normalisation == Normalisation::None) {
|
Chris@1520
|
264 m_targetWriter->close();
|
Chris@1520
|
265 } else {
|
Chris@1520
|
266 m_temporaryWriter->close();
|
Chris@1520
|
267 normaliseToTarget();
|
Chris@1520
|
268 }
|
Chris@1520
|
269
|
Chris@1520
|
270 m_reader->updateDone();
|
Chris@1133
|
271 m_proportion = 100;
|
Chris@1752
|
272 emit modelChanged(getId());
|
Chris@1752
|
273 emit writeCompleted(getId());
|
Chris@175
|
274 }
|
Chris@175
|
275
|
Chris@1520
|
276 void
|
Chris@1520
|
277 WritableWaveFileModel::normaliseToTarget()
|
Chris@1520
|
278 {
|
Chris@1520
|
279 if (m_temporaryPath == "") {
|
Chris@1520
|
280 SVCERR << "WritableWaveFileModel::normaliseToTarget: No temporary path available" << endl;
|
Chris@1520
|
281 return;
|
Chris@1520
|
282 }
|
Chris@1520
|
283
|
Chris@1520
|
284 WavFileReader normalisingReader(m_temporaryPath, false,
|
Chris@1520
|
285 WavFileReader::Normalisation::Peak);
|
Chris@1520
|
286
|
Chris@1520
|
287 if (!normalisingReader.getError().isEmpty()) {
|
Chris@1520
|
288 SVCERR << "WritableWaveFileModel: Error in creating normalising reader: " << normalisingReader.getError() << endl;
|
Chris@1520
|
289 return;
|
Chris@1520
|
290 }
|
Chris@1520
|
291
|
Chris@1520
|
292 sv_frame_t frame = 0;
|
Chris@1520
|
293 sv_frame_t block = 65536;
|
Chris@1520
|
294 sv_frame_t count = normalisingReader.getFrameCount();
|
Chris@1520
|
295
|
Chris@1520
|
296 while (frame < count) {
|
Chris@1520
|
297 auto frames = normalisingReader.getInterleavedFrames(frame, block);
|
Chris@1520
|
298 if (!m_targetWriter->putInterleavedFrames(frames)) {
|
Chris@1520
|
299 SVCERR << "ERROR: WritableWaveFileModel::normaliseToTarget: writer failed: " << m_targetWriter->getError() << endl;
|
Chris@1520
|
300 return;
|
Chris@1520
|
301 }
|
Chris@1520
|
302 frame += block;
|
Chris@1520
|
303 }
|
Chris@1520
|
304
|
Chris@1520
|
305 m_targetWriter->close();
|
Chris@1520
|
306
|
Chris@1520
|
307 delete m_temporaryWriter;
|
Chris@1582
|
308 m_temporaryWriter = nullptr;
|
Chris@1520
|
309 QFile::remove(m_temporaryPath);
|
Chris@1520
|
310 }
|
Chris@1520
|
311
|
Chris@1038
|
312 sv_frame_t
|
Chris@175
|
313 WritableWaveFileModel::getFrameCount() const
|
Chris@175
|
314 {
|
Chris@690
|
315 // SVDEBUG << "WritableWaveFileModel::getFrameCount: count = " << m_frameCount << endl;
|
Chris@175
|
316 return m_frameCount;
|
Chris@175
|
317 }
|
Chris@175
|
318
|
Chris@1326
|
319 floatvec_t
|
Chris@1096
|
320 WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
|
Chris@175
|
321 {
|
Chris@1096
|
322 if (!m_model || m_model->getChannelCount() == 0) return {};
|
Chris@1096
|
323 return m_model->getData(channel, start, count);
|
Chris@175
|
324 }
|
Chris@175
|
325
|
Chris@1326
|
326 vector<floatvec_t>
|
Chris@1086
|
327 WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
|
Chris@1096
|
328 sv_frame_t start, sv_frame_t count) const
|
Chris@175
|
329 {
|
Chris@1096
|
330 if (!m_model || m_model->getChannelCount() == 0) return {};
|
Chris@1096
|
331 return m_model->getMultiChannelData(fromchannel, tochannel, start, count);
|
Chris@363
|
332 }
|
Chris@363
|
333
|
Chris@929
|
334 int
|
Chris@929
|
335 WritableWaveFileModel::getSummaryBlockSize(int desired) const
|
Chris@377
|
336 {
|
Chris@377
|
337 if (!m_model) return desired;
|
Chris@377
|
338 return m_model->getSummaryBlockSize(desired);
|
Chris@377
|
339 }
|
Chris@377
|
340
|
Chris@225
|
341 void
|
Chris@1038
|
342 WritableWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
|
Chris@300
|
343 RangeBlock &ranges,
|
Chris@929
|
344 int &blockSize) const
|
Chris@175
|
345 {
|
Chris@225
|
346 ranges.clear();
|
Chris@225
|
347 if (!m_model || m_model->getChannelCount() == 0) return;
|
Chris@300
|
348 m_model->getSummaries(channel, start, count, ranges, blockSize);
|
Chris@175
|
349 }
|
Chris@175
|
350
|
Chris@175
|
351 WritableWaveFileModel::Range
|
Chris@1038
|
352 WritableWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const
|
Chris@175
|
353 {
|
Chris@187
|
354 if (!m_model || m_model->getChannelCount() == 0) return Range();
|
Chris@300
|
355 return m_model->getSummary(channel, start, count);
|
Chris@175
|
356 }
|
Chris@175
|
357
|
Chris@175
|
358 void
|
Chris@175
|
359 WritableWaveFileModel::toXml(QTextStream &out,
|
Chris@175
|
360 QString indent,
|
Chris@175
|
361 QString extraAttributes) const
|
Chris@175
|
362 {
|
Chris@1123
|
363 // The assumption here is that the underlying wave file has
|
Chris@1123
|
364 // already been saved somewhere (its location is available through
|
Chris@1123
|
365 // getLocation()) and that the code that uses this class is
|
Chris@1123
|
366 // dealing with the problem of making sure it remains available.
|
Chris@1123
|
367 // We just write this out as if it were a normal wave file.
|
Chris@187
|
368
|
Chris@188
|
369 Model::toXml
|
Chris@188
|
370 (out, indent,
|
Chris@1123
|
371 QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2")
|
Chris@1520
|
372 .arg(encodeEntities(m_targetPath))
|
Chris@1123
|
373 .arg(extraAttributes));
|
Chris@175
|
374 }
|
Chris@175
|
375
|