annotate data/model/WritableWaveFileModel.cpp @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +0100
parents 77543124651b
children dffc70996f54
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@258 161 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
Chris@1038 162 connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
Chris@1038 163 this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)));
Chris@1751 164
Chris@1751 165 PlayParameterRepository::getInstance()->addPlayable
Chris@1751 166 (getId().untyped, this);
Chris@175 167 }
Chris@175 168
Chris@175 169 WritableWaveFileModel::~WritableWaveFileModel()
Chris@175 170 {
Chris@1751 171 PlayParameterRepository::getInstance()->removePlayable
Chris@1751 172 (getId().untyped);
Chris@1751 173
Chris@175 174 delete m_model;
Chris@1520 175 delete m_targetWriter;
Chris@1520 176 delete m_temporaryWriter;
Chris@175 177 delete m_reader;
Chris@175 178 }
Chris@175 179
Chris@300 180 void
Chris@1038 181 WritableWaveFileModel::setStartFrame(sv_frame_t startFrame)
Chris@300 182 {
Chris@300 183 m_startFrame = startFrame;
Chris@1520 184 if (m_model) {
Chris@1520 185 m_model->setStartFrame(startFrame);
Chris@1520 186 }
Chris@300 187 }
Chris@300 188
Chris@175 189 bool
Chris@1325 190 WritableWaveFileModel::addSamples(const float *const *samples, sv_frame_t count)
Chris@175 191 {
Chris@1520 192 if (!m_model) return false;
Chris@175 193
Chris@258 194 #ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL
Chris@690 195 // SVDEBUG << "WritableWaveFileModel::addSamples(" << count << ")" << endl;
Chris@258 196 #endif
Chris@258 197
Chris@1520 198 WavFileWriter *writer = m_targetWriter;
Chris@1520 199 if (m_normalisation != Normalisation::None) {
Chris@1520 200 writer = m_temporaryWriter;
Chris@1520 201 }
Chris@1520 202
Chris@1520 203 if (!writer->writeSamples(samples, count)) {
Chris@1520 204 SVCERR << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << writer->getError() << endl;
Chris@175 205 return false;
Chris@175 206 }
Chris@175 207
Chris@175 208 m_frameCount += count;
Chris@175 209
Chris@1520 210 if (m_normalisation == Normalisation::None) {
Chris@1520 211 if (m_reader->getChannelCount() == 0) {
Chris@1520 212 m_reader->updateFrameCount();
Chris@1520 213 }
Chris@175 214 }
Chris@175 215
Chris@175 216 return true;
Chris@175 217 }
Chris@175 218
Chris@1337 219 void
Chris@1337 220 WritableWaveFileModel::updateModel()
Chris@1337 221 {
Chris@1520 222 if (!m_model) return;
Chris@1520 223
Chris@1520 224 m_reader->updateFrameCount();
Chris@1337 225 }
Chris@1337 226
Chris@175 227 bool
Chris@175 228 WritableWaveFileModel::isOK() const
Chris@175 229 {
Chris@1520 230 return (m_model && m_model->isOK());
Chris@175 231 }
Chris@175 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@1752 259 emit modelChanged(getId());
Chris@1752 260 emit writeCompleted(getId());
Chris@175 261 }
Chris@175 262
Chris@1520 263 void
Chris@1520 264 WritableWaveFileModel::normaliseToTarget()
Chris@1520 265 {
Chris@1520 266 if (m_temporaryPath == "") {
Chris@1520 267 SVCERR << "WritableWaveFileModel::normaliseToTarget: No temporary path available" << endl;
Chris@1520 268 return;
Chris@1520 269 }
Chris@1520 270
Chris@1520 271 WavFileReader normalisingReader(m_temporaryPath, false,
Chris@1520 272 WavFileReader::Normalisation::Peak);
Chris@1520 273
Chris@1520 274 if (!normalisingReader.getError().isEmpty()) {
Chris@1520 275 SVCERR << "WritableWaveFileModel: Error in creating normalising reader: " << normalisingReader.getError() << endl;
Chris@1520 276 return;
Chris@1520 277 }
Chris@1520 278
Chris@1520 279 sv_frame_t frame = 0;
Chris@1520 280 sv_frame_t block = 65536;
Chris@1520 281 sv_frame_t count = normalisingReader.getFrameCount();
Chris@1520 282
Chris@1520 283 while (frame < count) {
Chris@1520 284 auto frames = normalisingReader.getInterleavedFrames(frame, block);
Chris@1520 285 if (!m_targetWriter->putInterleavedFrames(frames)) {
Chris@1520 286 SVCERR << "ERROR: WritableWaveFileModel::normaliseToTarget: writer failed: " << m_targetWriter->getError() << endl;
Chris@1520 287 return;
Chris@1520 288 }
Chris@1520 289 frame += block;
Chris@1520 290 }
Chris@1520 291
Chris@1520 292 m_targetWriter->close();
Chris@1520 293
Chris@1520 294 delete m_temporaryWriter;
Chris@1582 295 m_temporaryWriter = nullptr;
Chris@1520 296 QFile::remove(m_temporaryPath);
Chris@1520 297 }
Chris@1520 298
Chris@1038 299 sv_frame_t
Chris@175 300 WritableWaveFileModel::getFrameCount() const
Chris@175 301 {
Chris@690 302 // SVDEBUG << "WritableWaveFileModel::getFrameCount: count = " << m_frameCount << endl;
Chris@175 303 return m_frameCount;
Chris@175 304 }
Chris@175 305
Chris@1326 306 floatvec_t
Chris@1096 307 WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
Chris@175 308 {
Chris@1096 309 if (!m_model || m_model->getChannelCount() == 0) return {};
Chris@1096 310 return m_model->getData(channel, start, count);
Chris@175 311 }
Chris@175 312
Chris@1326 313 vector<floatvec_t>
Chris@1086 314 WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
Chris@1096 315 sv_frame_t start, sv_frame_t count) const
Chris@175 316 {
Chris@1096 317 if (!m_model || m_model->getChannelCount() == 0) return {};
Chris@1096 318 return m_model->getMultiChannelData(fromchannel, tochannel, start, count);
Chris@363 319 }
Chris@363 320
Chris@929 321 int
Chris@929 322 WritableWaveFileModel::getSummaryBlockSize(int desired) const
Chris@377 323 {
Chris@377 324 if (!m_model) return desired;
Chris@377 325 return m_model->getSummaryBlockSize(desired);
Chris@377 326 }
Chris@377 327
Chris@225 328 void
Chris@1038 329 WritableWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
Chris@300 330 RangeBlock &ranges,
Chris@929 331 int &blockSize) const
Chris@175 332 {
Chris@225 333 ranges.clear();
Chris@225 334 if (!m_model || m_model->getChannelCount() == 0) return;
Chris@300 335 m_model->getSummaries(channel, start, count, ranges, blockSize);
Chris@175 336 }
Chris@175 337
Chris@175 338 WritableWaveFileModel::Range
Chris@1038 339 WritableWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const
Chris@175 340 {
Chris@187 341 if (!m_model || m_model->getChannelCount() == 0) return Range();
Chris@300 342 return m_model->getSummary(channel, start, count);
Chris@175 343 }
Chris@175 344
Chris@175 345 void
Chris@175 346 WritableWaveFileModel::toXml(QTextStream &out,
Chris@175 347 QString indent,
Chris@175 348 QString extraAttributes) const
Chris@175 349 {
Chris@1123 350 // The assumption here is that the underlying wave file has
Chris@1123 351 // already been saved somewhere (its location is available through
Chris@1123 352 // getLocation()) and that the code that uses this class is
Chris@1123 353 // dealing with the problem of making sure it remains available.
Chris@1123 354 // We just write this out as if it were a normal wave file.
Chris@187 355
Chris@188 356 Model::toXml
Chris@188 357 (out, indent,
Chris@1123 358 QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2")
Chris@1520 359 .arg(encodeEntities(m_targetPath))
Chris@1123 360 .arg(extraAttributes));
Chris@175 361 }
Chris@175 362