| 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 |