Chris@175: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@175: Chris@175: /* Chris@175: Sonic Visualiser Chris@175: An audio file viewer and annotation editor. Chris@175: Centre for Digital Music, Queen Mary, University of London. Chris@202: This file copyright 2006 QMUL. Chris@175: Chris@175: This program is free software; you can redistribute it and/or Chris@175: modify it under the terms of the GNU General Public License as Chris@175: published by the Free Software Foundation; either version 2 of the Chris@175: License, or (at your option) any later version. See the file Chris@175: COPYING included with this distribution for more information. Chris@175: */ Chris@175: Chris@175: #include "WritableWaveFileModel.h" Chris@175: Chris@1122: #include "ReadOnlyWaveFileModel.h" Chris@1122: Chris@175: #include "base/TempDirectory.h" Chris@175: #include "base/Exceptions.h" Chris@175: Chris@175: #include "fileio/WavFileWriter.h" Chris@175: #include "fileio/WavFileReader.h" Chris@175: Chris@175: #include Chris@314: #include Chris@175: Chris@175: #include Chris@175: #include Chris@723: #include Chris@175: Chris@1096: using namespace std; Chris@1096: Chris@1133: const int WritableWaveFileModel::PROPORTION_UNKNOWN = -1; Chris@1133: Chris@258: //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1 Chris@187: Chris@1520: WritableWaveFileModel::WritableWaveFileModel(QString path, Chris@1520: sv_samplerate_t sampleRate, Chris@1429: int channels, Chris@1520: Normalisation norm) : Chris@1582: m_model(nullptr), Chris@1582: m_temporaryWriter(nullptr), Chris@1582: m_targetWriter(nullptr), Chris@1582: m_reader(nullptr), Chris@1520: m_normalisation(norm), Chris@175: m_sampleRate(sampleRate), Chris@175: m_channels(channels), Chris@188: m_frameCount(0), Chris@300: m_startFrame(0), Chris@1133: m_proportion(PROPORTION_UNKNOWN) Chris@175: { Chris@1520: init(path); Chris@1520: } Chris@1520: Chris@1520: WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate, Chris@1520: int channels, Chris@1520: Normalisation norm) : Chris@1582: m_model(nullptr), Chris@1582: m_temporaryWriter(nullptr), Chris@1582: m_targetWriter(nullptr), Chris@1582: m_reader(nullptr), Chris@1520: m_normalisation(norm), Chris@1520: m_sampleRate(sampleRate), Chris@1520: m_channels(channels), Chris@1520: m_frameCount(0), Chris@1520: m_startFrame(0), Chris@1520: m_proportion(PROPORTION_UNKNOWN) Chris@1520: { Chris@1520: init(); Chris@1520: } Chris@1520: Chris@1520: WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate, Chris@1520: int channels) : Chris@1582: m_model(nullptr), Chris@1582: m_temporaryWriter(nullptr), Chris@1582: m_targetWriter(nullptr), Chris@1582: m_reader(nullptr), Chris@1520: m_normalisation(Normalisation::None), Chris@1520: m_sampleRate(sampleRate), Chris@1520: m_channels(channels), Chris@1520: m_frameCount(0), Chris@1520: m_startFrame(0), Chris@1520: m_proportion(PROPORTION_UNKNOWN) Chris@1520: { Chris@1520: init(); Chris@1520: } Chris@1520: Chris@1520: void Chris@1520: WritableWaveFileModel::init(QString path) Chris@1520: { Chris@175: if (path.isEmpty()) { Chris@175: try { Chris@1520: // Temp dir is exclusive to this run of the application, Chris@1520: // so the filename only needs to be unique within that - Chris@1520: // model ID should be ok Chris@175: QDir dir(TempDirectory::getInstance()->getPath()); Chris@1520: path = dir.filePath(QString("written_%1.wav").arg(getId())); Chris@1465: } catch (const DirectoryCreationFailed &f) { Chris@1428: SVCERR << "WritableWaveFileModel: Failed to create temporary directory" << endl; Chris@175: return; Chris@175: } Chris@175: } Chris@175: Chris@1520: m_targetPath = path; Chris@1520: m_temporaryPath = ""; Chris@1520: Chris@1520: // We don't delete or null-out writer/reader members after Chris@1520: // failures here - they are all deleted in the dtor, and the Chris@1520: // presence/existence of the model is what's used to determine Chris@1520: // whether to go ahead, not the writer/readers. If the model is Chris@1520: // non-null, then the necessary writer/readers must be OK, as the Chris@1520: // model is the last thing initialised Chris@1520: Chris@1520: m_targetWriter = new WavFileWriter(m_targetPath, m_sampleRate, m_channels, Chris@1520: WavFileWriter::WriteToTarget); Chris@1520: Chris@1520: if (!m_targetWriter->isOK()) { Chris@1520: SVCERR << "WritableWaveFileModel: Error in creating WAV file writer: " << m_targetWriter->getError() << endl; Chris@175: return; Chris@175: } Chris@1520: Chris@1520: if (m_normalisation != Normalisation::None) { Chris@187: Chris@1520: // Temp dir is exclusive to this run of the application, so Chris@1520: // the filename only needs to be unique within that Chris@1520: QDir dir(TempDirectory::getInstance()->getPath()); Chris@1520: m_temporaryPath = dir.filePath(QString("prenorm_%1.wav").arg(getId())); Chris@316: Chris@1520: m_temporaryWriter = new WavFileWriter Chris@1520: (m_temporaryPath, m_sampleRate, m_channels, Chris@1520: WavFileWriter::WriteToTarget); Chris@1520: Chris@1520: if (!m_temporaryWriter->isOK()) { Chris@1520: SVCERR << "WritableWaveFileModel: Error in creating temporary WAV file writer: " << m_temporaryWriter->getError() << endl; Chris@1520: return; Chris@1520: } Chris@1520: } Chris@1520: Chris@1520: FileSource source(m_targetPath); Chris@1520: Chris@1520: m_reader = new WavFileReader(source, true); Chris@290: if (!m_reader->getError().isEmpty()) { Chris@1520: SVCERR << "WritableWaveFileModel: Error in creating wave file reader: " << m_reader->getError() << endl; Chris@187: return; Chris@187: } Chris@187: Chris@1122: m_model = new ReadOnlyWaveFileModel(source, m_reader); Chris@187: if (!m_model->isOK()) { Chris@1428: SVCERR << "WritableWaveFileModel: Error in creating wave file model" << endl; Chris@187: delete m_model; Chris@1582: m_model = nullptr; Chris@187: return; Chris@187: } Chris@300: m_model->setStartFrame(m_startFrame); Chris@187: Chris@258: connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); Chris@1038: connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), Chris@1038: this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t))); Chris@175: } Chris@175: Chris@175: WritableWaveFileModel::~WritableWaveFileModel() Chris@175: { Chris@175: delete m_model; Chris@1520: delete m_targetWriter; Chris@1520: delete m_temporaryWriter; Chris@175: delete m_reader; Chris@175: } Chris@175: Chris@300: void Chris@1038: WritableWaveFileModel::setStartFrame(sv_frame_t startFrame) Chris@300: { Chris@300: m_startFrame = startFrame; Chris@1520: if (m_model) { Chris@1520: m_model->setStartFrame(startFrame); Chris@1520: } Chris@300: } Chris@300: Chris@175: bool Chris@1325: WritableWaveFileModel::addSamples(const float *const *samples, sv_frame_t count) Chris@175: { Chris@1520: if (!m_model) return false; Chris@175: Chris@258: #ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL Chris@690: // SVDEBUG << "WritableWaveFileModel::addSamples(" << count << ")" << endl; Chris@258: #endif Chris@258: Chris@1520: WavFileWriter *writer = m_targetWriter; Chris@1520: if (m_normalisation != Normalisation::None) { Chris@1520: writer = m_temporaryWriter; Chris@1520: } Chris@1520: Chris@1520: if (!writer->writeSamples(samples, count)) { Chris@1520: SVCERR << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << writer->getError() << endl; Chris@175: return false; Chris@175: } Chris@175: Chris@175: m_frameCount += count; Chris@175: Chris@1520: if (m_normalisation == Normalisation::None) { Chris@1520: if (m_reader->getChannelCount() == 0) { Chris@1520: m_reader->updateFrameCount(); Chris@1520: } Chris@175: } Chris@175: Chris@175: return true; Chris@175: } Chris@175: Chris@1337: void Chris@1337: WritableWaveFileModel::updateModel() Chris@1337: { Chris@1520: if (!m_model) return; Chris@1520: Chris@1520: m_reader->updateFrameCount(); Chris@1337: } Chris@1337: Chris@175: bool Chris@175: WritableWaveFileModel::isOK() const Chris@175: { Chris@1520: return (m_model && m_model->isOK()); Chris@175: } Chris@175: Chris@175: bool Chris@175: WritableWaveFileModel::isReady(int *completion) const Chris@175: { Chris@1133: int c = getCompletion(); Chris@1133: if (completion) *completion = c; Chris@1133: if (!isOK()) return false; Chris@1133: return (c == 100); Chris@188: } Chris@188: Chris@188: void Chris@1133: WritableWaveFileModel::setWriteProportion(int proportion) Chris@188: { Chris@1133: m_proportion = proportion; Chris@1133: } Chris@1133: Chris@1133: int Chris@1133: WritableWaveFileModel::getWriteProportion() const Chris@1133: { Chris@1133: return m_proportion; Chris@1133: } Chris@1133: Chris@1133: void Chris@1133: WritableWaveFileModel::writeComplete() Chris@1133: { Chris@1520: if (!m_model) return; Chris@1520: Chris@1520: if (m_normalisation == Normalisation::None) { Chris@1520: m_targetWriter->close(); Chris@1520: } else { Chris@1520: m_temporaryWriter->close(); Chris@1520: normaliseToTarget(); Chris@1520: } Chris@1520: Chris@1520: m_reader->updateDone(); Chris@1133: m_proportion = 100; Chris@1133: emit modelChanged(); Chris@175: } Chris@175: Chris@1520: void Chris@1520: WritableWaveFileModel::normaliseToTarget() Chris@1520: { Chris@1520: if (m_temporaryPath == "") { Chris@1520: SVCERR << "WritableWaveFileModel::normaliseToTarget: No temporary path available" << endl; Chris@1520: return; Chris@1520: } Chris@1520: Chris@1520: WavFileReader normalisingReader(m_temporaryPath, false, Chris@1520: WavFileReader::Normalisation::Peak); Chris@1520: Chris@1520: if (!normalisingReader.getError().isEmpty()) { Chris@1520: SVCERR << "WritableWaveFileModel: Error in creating normalising reader: " << normalisingReader.getError() << endl; Chris@1520: return; Chris@1520: } Chris@1520: Chris@1520: sv_frame_t frame = 0; Chris@1520: sv_frame_t block = 65536; Chris@1520: sv_frame_t count = normalisingReader.getFrameCount(); Chris@1520: Chris@1520: while (frame < count) { Chris@1520: auto frames = normalisingReader.getInterleavedFrames(frame, block); Chris@1520: if (!m_targetWriter->putInterleavedFrames(frames)) { Chris@1520: SVCERR << "ERROR: WritableWaveFileModel::normaliseToTarget: writer failed: " << m_targetWriter->getError() << endl; Chris@1520: return; Chris@1520: } Chris@1520: frame += block; Chris@1520: } Chris@1520: Chris@1520: m_targetWriter->close(); Chris@1520: Chris@1520: delete m_temporaryWriter; Chris@1582: m_temporaryWriter = nullptr; Chris@1520: QFile::remove(m_temporaryPath); Chris@1520: } Chris@1520: Chris@1038: sv_frame_t Chris@175: WritableWaveFileModel::getFrameCount() const Chris@175: { Chris@690: // SVDEBUG << "WritableWaveFileModel::getFrameCount: count = " << m_frameCount << endl; Chris@175: return m_frameCount; Chris@175: } Chris@175: Chris@1326: floatvec_t Chris@1096: WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const Chris@175: { Chris@1096: if (!m_model || m_model->getChannelCount() == 0) return {}; Chris@1096: return m_model->getData(channel, start, count); Chris@175: } Chris@175: Chris@1326: vector Chris@1086: WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel, Chris@1096: sv_frame_t start, sv_frame_t count) const Chris@175: { Chris@1096: if (!m_model || m_model->getChannelCount() == 0) return {}; Chris@1096: return m_model->getMultiChannelData(fromchannel, tochannel, start, count); Chris@363: } Chris@363: Chris@929: int Chris@929: WritableWaveFileModel::getSummaryBlockSize(int desired) const Chris@377: { Chris@377: if (!m_model) return desired; Chris@377: return m_model->getSummaryBlockSize(desired); Chris@377: } Chris@377: Chris@225: void Chris@1038: WritableWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count, Chris@300: RangeBlock &ranges, Chris@929: int &blockSize) const Chris@175: { Chris@225: ranges.clear(); Chris@225: if (!m_model || m_model->getChannelCount() == 0) return; Chris@300: m_model->getSummaries(channel, start, count, ranges, blockSize); Chris@175: } Chris@175: Chris@175: WritableWaveFileModel::Range Chris@1038: WritableWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const Chris@175: { Chris@187: if (!m_model || m_model->getChannelCount() == 0) return Range(); Chris@300: return m_model->getSummary(channel, start, count); Chris@175: } Chris@175: Chris@175: void Chris@175: WritableWaveFileModel::toXml(QTextStream &out, Chris@175: QString indent, Chris@175: QString extraAttributes) const Chris@175: { Chris@1123: // The assumption here is that the underlying wave file has Chris@1123: // already been saved somewhere (its location is available through Chris@1123: // getLocation()) and that the code that uses this class is Chris@1123: // dealing with the problem of making sure it remains available. Chris@1123: // We just write this out as if it were a normal wave file. Chris@187: Chris@188: Model::toXml Chris@188: (out, indent, Chris@1123: QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2") Chris@1520: .arg(encodeEntities(m_targetPath)) Chris@1123: .arg(extraAttributes)); Chris@175: } Chris@175: