annotate data/model/WritableWaveFileModel.cpp @ 1780:6d6740b075c3

Support optional max frequency setting, useful when we want to store caches of very constrained frequency ranges (as in melodic-range spectrogram, potentially)
author Chris Cannam
date Thu, 12 Sep 2019 11:52:19 +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