annotate data/model/WritableWaveFileModel.cpp @ 1881:b504df98c3be

Ensure completion on output model is started at zero, so if it's checked before the input model has become ready and the transform has begun, it is not accidentally reported as complete (affected re-aligning models in Sonic Lineup when replacing the session)
author Chris Cannam
date Fri, 26 Jun 2020 11:45:39 +0100
parents dffc70996f54
children
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@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