annotate data/model/WritableWaveFileModel.cpp @ 1520:954d0cf29ca7 import-audio-data

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