annotate data/model/WritableWaveFileModel.cpp @ 1671:82d03c9661f9 single-point

Rework isReady()/getCompletion() on models. Previously the new overhauled models were implementing getCompletion() but inheriting a version of isReady() (from the Model base) that didn't call it, referring only to isOK(). So they were reporting completion as soon as they had begun. Instead hoist getCompletion() to abstract base and call it from Model::isReady().
author Chris Cannam
date Wed, 27 Mar 2019 13:15:16 +0000
parents 70e172e6cc59
children 26da177d7266
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@188 224 void
Chris@1133 225 WritableWaveFileModel::setWriteProportion(int proportion)
Chris@188 226 {
Chris@1133 227 m_proportion = proportion;
Chris@1133 228 }
Chris@1133 229
Chris@1133 230 int
Chris@1133 231 WritableWaveFileModel::getWriteProportion() const
Chris@1133 232 {
Chris@1133 233 return m_proportion;
Chris@1133 234 }
Chris@1133 235
Chris@1133 236 void
Chris@1133 237 WritableWaveFileModel::writeComplete()
Chris@1133 238 {
Chris@1520 239 if (!m_model) return;
Chris@1520 240
Chris@1520 241 if (m_normalisation == Normalisation::None) {
Chris@1520 242 m_targetWriter->close();
Chris@1520 243 } else {
Chris@1520 244 m_temporaryWriter->close();
Chris@1520 245 normaliseToTarget();
Chris@1520 246 }
Chris@1520 247
Chris@1520 248 m_reader->updateDone();
Chris@1133 249 m_proportion = 100;
Chris@1133 250 emit modelChanged();
Chris@175 251 }
Chris@175 252
Chris@1520 253 void
Chris@1520 254 WritableWaveFileModel::normaliseToTarget()
Chris@1520 255 {
Chris@1520 256 if (m_temporaryPath == "") {
Chris@1520 257 SVCERR << "WritableWaveFileModel::normaliseToTarget: No temporary path available" << endl;
Chris@1520 258 return;
Chris@1520 259 }
Chris@1520 260
Chris@1520 261 WavFileReader normalisingReader(m_temporaryPath, false,
Chris@1520 262 WavFileReader::Normalisation::Peak);
Chris@1520 263
Chris@1520 264 if (!normalisingReader.getError().isEmpty()) {
Chris@1520 265 SVCERR << "WritableWaveFileModel: Error in creating normalising reader: " << normalisingReader.getError() << endl;
Chris@1520 266 return;
Chris@1520 267 }
Chris@1520 268
Chris@1520 269 sv_frame_t frame = 0;
Chris@1520 270 sv_frame_t block = 65536;
Chris@1520 271 sv_frame_t count = normalisingReader.getFrameCount();
Chris@1520 272
Chris@1520 273 while (frame < count) {
Chris@1520 274 auto frames = normalisingReader.getInterleavedFrames(frame, block);
Chris@1520 275 if (!m_targetWriter->putInterleavedFrames(frames)) {
Chris@1520 276 SVCERR << "ERROR: WritableWaveFileModel::normaliseToTarget: writer failed: " << m_targetWriter->getError() << endl;
Chris@1520 277 return;
Chris@1520 278 }
Chris@1520 279 frame += block;
Chris@1520 280 }
Chris@1520 281
Chris@1520 282 m_targetWriter->close();
Chris@1520 283
Chris@1520 284 delete m_temporaryWriter;
Chris@1582 285 m_temporaryWriter = nullptr;
Chris@1520 286 QFile::remove(m_temporaryPath);
Chris@1520 287 }
Chris@1520 288
Chris@1038 289 sv_frame_t
Chris@175 290 WritableWaveFileModel::getFrameCount() const
Chris@175 291 {
Chris@690 292 // SVDEBUG << "WritableWaveFileModel::getFrameCount: count = " << m_frameCount << endl;
Chris@175 293 return m_frameCount;
Chris@175 294 }
Chris@175 295
Chris@1326 296 floatvec_t
Chris@1096 297 WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const
Chris@175 298 {
Chris@1096 299 if (!m_model || m_model->getChannelCount() == 0) return {};
Chris@1096 300 return m_model->getData(channel, start, count);
Chris@175 301 }
Chris@175 302
Chris@1326 303 vector<floatvec_t>
Chris@1086 304 WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel,
Chris@1096 305 sv_frame_t start, sv_frame_t count) const
Chris@175 306 {
Chris@1096 307 if (!m_model || m_model->getChannelCount() == 0) return {};
Chris@1096 308 return m_model->getMultiChannelData(fromchannel, tochannel, start, count);
Chris@363 309 }
Chris@363 310
Chris@929 311 int
Chris@929 312 WritableWaveFileModel::getSummaryBlockSize(int desired) const
Chris@377 313 {
Chris@377 314 if (!m_model) return desired;
Chris@377 315 return m_model->getSummaryBlockSize(desired);
Chris@377 316 }
Chris@377 317
Chris@225 318 void
Chris@1038 319 WritableWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
Chris@300 320 RangeBlock &ranges,
Chris@929 321 int &blockSize) const
Chris@175 322 {
Chris@225 323 ranges.clear();
Chris@225 324 if (!m_model || m_model->getChannelCount() == 0) return;
Chris@300 325 m_model->getSummaries(channel, start, count, ranges, blockSize);
Chris@175 326 }
Chris@175 327
Chris@175 328 WritableWaveFileModel::Range
Chris@1038 329 WritableWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const
Chris@175 330 {
Chris@187 331 if (!m_model || m_model->getChannelCount() == 0) return Range();
Chris@300 332 return m_model->getSummary(channel, start, count);
Chris@175 333 }
Chris@175 334
Chris@175 335 void
Chris@175 336 WritableWaveFileModel::toXml(QTextStream &out,
Chris@175 337 QString indent,
Chris@175 338 QString extraAttributes) const
Chris@175 339 {
Chris@1123 340 // The assumption here is that the underlying wave file has
Chris@1123 341 // already been saved somewhere (its location is available through
Chris@1123 342 // getLocation()) and that the code that uses this class is
Chris@1123 343 // dealing with the problem of making sure it remains available.
Chris@1123 344 // We just write this out as if it were a normal wave file.
Chris@187 345
Chris@188 346 Model::toXml
Chris@188 347 (out, indent,
Chris@1123 348 QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2")
Chris@1520 349 .arg(encodeEntities(m_targetPath))
Chris@1123 350 .arg(extraAttributes));
Chris@175 351 }
Chris@175 352