annotate data/model/WritableWaveFileModel.cpp @ 1751:77543124651b by-id

Overhaul PlayParameters bits
author Chris Cannam
date Thu, 04 Jul 2019 18:04:21 +0100
parents 52705a328b34
children 6d09d68165a4
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@1133 259 emit modelChanged();
Chris@1723 260 emit writeCompleted();
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