annotate data/model/WritableWaveFileModel.cpp @ 1651:7a56bb85030f single-point

Introduce deferred notifier, + start converting sparse time-value model (perhaps we should rename it too)
author Chris Cannam
date Mon, 18 Mar 2019 14:17:20 +0000
parents 70e172e6cc59
children 82d03c9661f9
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@1582 43 m_model(nullptr),
Chris@1582 44 m_temporaryWriter(nullptr),
Chris@1582 45 m_targetWriter(nullptr),
Chris@1582 46 m_reader(nullptr),
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@1582 60 m_model(nullptr),
Chris@1582 61 m_temporaryWriter(nullptr),
Chris@1582 62 m_targetWriter(nullptr),
Chris@1582 63 m_reader(nullptr),
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@1582 76 m_model(nullptr),
Chris@1582 77 m_temporaryWriter(nullptr),
Chris@1582 78 m_targetWriter(nullptr),
Chris@1582 79 m_reader(nullptr),
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@1582 153 m_model = nullptr;
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@1582 294 m_temporaryWriter = nullptr;
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