annotate data/fileio/WavFileWriter.cpp @ 1881:b504df98c3be

Ensure completion on output model is started at zero, so if it's checked before the input model has become ready and the transform has begun, it is not accidentally reported as complete (affected re-aligning models in Sonic Lineup when replacing the session)
author Chris Cannam
date Fri, 26 Jun 2020 11:45:39 +0100
parents 70e172e6cc59
children
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@202 7 This file copyright 2006 Chris Cannam and QMUL.
Chris@148 8
Chris@148 9 This program is free software; you can redistribute it and/or
Chris@148 10 modify it under the terms of the GNU General Public License as
Chris@148 11 published by the Free Software Foundation; either version 2 of the
Chris@148 12 License, or (at your option) any later version. See the file
Chris@148 13 COPYING included with this distribution for more information.
Chris@148 14 */
Chris@148 15
Chris@148 16 #include "WavFileWriter.h"
Chris@148 17
Chris@148 18 #include "model/DenseTimeValueModel.h"
Chris@148 19 #include "base/Selection.h"
Chris@674 20 #include "base/TempWriteFile.h"
Chris@674 21 #include "base/Exceptions.h"
Chris@1428 22 #include "base/Debug.h"
Chris@148 23
Chris@1520 24 #include <bqvec/Allocators.h>
Chris@1520 25 #include <bqvec/VectorOps.h>
Chris@1520 26
Chris@148 27 #include <QFileInfo>
Chris@148 28
Chris@148 29 #include <iostream>
Chris@1055 30 #include <cmath>
Chris@1428 31 #include <string>
Chris@148 32
Chris@1096 33 using namespace std;
Chris@1096 34
Chris@148 35 WavFileWriter::WavFileWriter(QString path,
Chris@1350 36 sv_samplerate_t sampleRate,
Chris@929 37 int channels,
Chris@684 38 FileWriteMode mode) :
Chris@148 39 m_path(path),
Chris@148 40 m_sampleRate(sampleRate),
Chris@174 41 m_channels(channels),
Chris@1582 42 m_temp(nullptr),
Chris@1582 43 m_file(nullptr)
Chris@148 44 {
Chris@174 45 SF_INFO fileInfo;
Chris@1040 46
Chris@1040 47 int fileRate = int(round(m_sampleRate));
Chris@1040 48 if (m_sampleRate != sv_samplerate_t(fileRate)) {
Chris@1428 49 SVCERR << "WavFileWriter: WARNING: Non-integer sample rate "
Chris@1350 50 << m_sampleRate << " presented, rounding to " << fileRate
Chris@1350 51 << endl;
Chris@1040 52 }
Chris@1040 53 fileInfo.samplerate = fileRate;
Chris@174 54 fileInfo.channels = m_channels;
Chris@174 55 fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
Chris@674 56
Chris@674 57 try {
Chris@1350 58 QString writePath = m_path;
Chris@684 59 if (mode == WriteToTemporary) {
Chris@684 60 m_temp = new TempWriteFile(m_path);
Chris@1350 61 writePath = m_temp->getTemporaryFilename();
Chris@1348 62 }
Chris@1350 63 #ifdef Q_OS_WIN
Chris@1350 64 m_file = sf_wchar_open((LPCWSTR)writePath.utf16(), SFM_WRITE, &fileInfo);
Chris@1350 65 #else
Chris@1350 66 m_file = sf_open(writePath.toLocal8Bit(), SFM_WRITE, &fileInfo);
Chris@1350 67 #endif
Chris@1350 68 if (!m_file) {
Chris@1508 69 SVCERR << "WavFileWriter: Failed to create float-WAV file of "
Chris@1508 70 << m_channels << " channels at rate " << fileRate << " ("
Chris@1508 71 << sf_strerror(m_file) << ")" << endl;
Chris@1348 72 m_error = QString("Failed to open audio file '%1' for writing")
Chris@1350 73 .arg(writePath);
Chris@1508 74 if (m_temp) {
Chris@1508 75 delete m_temp;
Chris@1582 76 m_temp = nullptr;
Chris@1508 77 }
Chris@1348 78 }
Chris@674 79 } catch (FileOperationFailed &f) {
Chris@674 80 m_error = f.what();
Chris@1582 81 m_temp = nullptr;
Chris@1582 82 m_file = nullptr;
Chris@174 83 }
Chris@148 84 }
Chris@148 85
Chris@148 86 WavFileWriter::~WavFileWriter()
Chris@148 87 {
Chris@1350 88 if (m_file) close();
Chris@148 89 }
Chris@148 90
Chris@148 91 bool
Chris@148 92 WavFileWriter::isOK() const
Chris@148 93 {
Chris@148 94 return (m_error.isEmpty());
Chris@148 95 }
Chris@148 96
Chris@148 97 QString
Chris@148 98 WavFileWriter::getError() const
Chris@148 99 {
Chris@148 100 return m_error;
Chris@148 101 }
Chris@148 102
Chris@684 103 QString
Chris@684 104 WavFileWriter::getWriteFilename() const
Chris@684 105 {
Chris@684 106 if (m_temp) {
Chris@684 107 return m_temp->getTemporaryFilename();
Chris@684 108 } else {
Chris@684 109 return m_path;
Chris@684 110 }
Chris@684 111 }
Chris@684 112
Chris@174 113 bool
Chris@174 114 WavFileWriter::writeModel(DenseTimeValueModel *source,
Chris@174 115 MultiSelection *selection)
Chris@148 116 {
Chris@174 117 if (source->getChannelCount() != m_channels) {
Chris@690 118 SVDEBUG << "WavFileWriter::writeModel: Wrong number of channels ("
Chris@174 119 << source->getChannelCount() << " != " << m_channels << ")"
Chris@687 120 << endl;
Chris@174 121 m_error = QString("Failed to write model to audio file '%1'")
Chris@684 122 .arg(getWriteFilename());
Chris@174 123 return false;
Chris@148 124 }
Chris@148 125
Chris@1350 126 if (!m_file) {
Chris@174 127 m_error = QString("Failed to write model to audio file '%1': File not open")
Chris@684 128 .arg(getWriteFilename());
Chris@1350 129 return false;
Chris@174 130 }
Chris@148 131
Chris@174 132 bool ownSelection = false;
Chris@174 133 if (!selection) {
Chris@1350 134 selection = new MultiSelection;
Chris@1350 135 selection->setSelection(Selection(source->getStartFrame(),
Chris@1350 136 source->getEndFrame()));
Chris@174 137 ownSelection = true;
Chris@148 138 }
Chris@148 139
Chris@1038 140 sv_frame_t bs = 2048;
Chris@148 141
Chris@148 142 for (MultiSelection::SelectionList::iterator i =
Chris@1350 143 selection->getSelections().begin();
Chris@1350 144 i != selection->getSelections().end(); ++i) {
Chris@1429 145
Chris@1350 146 sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
Chris@148 147
Chris@1350 148 for (sv_frame_t f = f0; f < f1; f += bs) {
Chris@1429 149
Chris@1350 150 sv_frame_t n = min(bs, f1 - f);
Chris@1326 151 floatvec_t interleaved(n * m_channels, 0.f);
Chris@148 152
Chris@1350 153 for (int c = 0; c < int(m_channels); ++c) {
Chris@1326 154 auto chanbuf = source->getData(c, f, n);
Chris@1350 155 for (int i = 0; in_range_for(chanbuf, i); ++i) {
Chris@1350 156 interleaved[i * m_channels + c] = chanbuf[i];
Chris@1350 157 }
Chris@1350 158 }
Chris@148 159
Chris@1350 160 sf_count_t written = sf_writef_float(m_file, interleaved.data(), n);
Chris@148 161
Chris@1350 162 if (written < n) {
Chris@1350 163 m_error = QString("Only wrote %1 of %2 frames at file frame %3")
Chris@1350 164 .arg(written).arg(n).arg(f);
Chris@1350 165 break;
Chris@1350 166 }
Chris@1350 167 }
Chris@148 168 }
Chris@148 169
Chris@174 170 if (ownSelection) delete selection;
Chris@174 171
Chris@174 172 return isOK();
Chris@174 173 }
Chris@1429 174
Chris@174 175 bool
Chris@1325 176 WavFileWriter::writeSamples(const float *const *samples, sv_frame_t count)
Chris@174 177 {
Chris@1350 178 if (!m_file) {
Chris@174 179 m_error = QString("Failed to write model to audio file '%1': File not open")
Chris@684 180 .arg(getWriteFilename());
Chris@1350 181 return false;
Chris@174 182 }
Chris@174 183
Chris@174 184 float *b = new float[count * m_channels];
Chris@1038 185 for (sv_frame_t i = 0; i < count; ++i) {
Chris@929 186 for (int c = 0; c < int(m_channels); ++c) {
Chris@174 187 b[i * m_channels + c] = samples[c][i];
Chris@174 188 }
Chris@174 189 }
Chris@174 190
Chris@1350 191 sv_frame_t written = sf_writef_float(m_file, b, count);
Chris@174 192
Chris@174 193 delete[] b;
Chris@174 194
Chris@1038 195 if (written < count) {
Chris@174 196 m_error = QString("Only wrote %1 of %2 frames")
Chris@174 197 .arg(written).arg(count);
Chris@174 198 }
Chris@174 199
Chris@174 200 return isOK();
Chris@174 201 }
Chris@1520 202
Chris@1520 203 bool
Chris@1520 204 WavFileWriter::putInterleavedFrames(const floatvec_t &frames)
Chris@1520 205 {
Chris@1520 206 sv_frame_t count = frames.size() / m_channels;
Chris@1526 207 float **samples =
Chris@1526 208 breakfastquay::allocate_channels<float>(m_channels, count);
Chris@1526 209 breakfastquay::v_deinterleave
Chris@1526 210 (samples, frames.data(), m_channels, int(count));
Chris@1520 211 bool result = writeSamples(samples, count);
Chris@1520 212 breakfastquay::deallocate_channels(samples, m_channels);
Chris@1520 213 return result;
Chris@1520 214 }
Chris@1520 215
Chris@174 216 bool
Chris@174 217 WavFileWriter::close()
Chris@174 218 {
Chris@1350 219 if (m_file) {
Chris@1350 220 sf_close(m_file);
Chris@1582 221 m_file = nullptr;
Chris@174 222 }
Chris@684 223 if (m_temp) {
Chris@684 224 m_temp->moveToTarget();
Chris@684 225 delete m_temp;
Chris@1582 226 m_temp = nullptr;
Chris@684 227 }
Chris@174 228 return true;
Chris@148 229 }
Chris@148 230