annotate data/fileio/WavFileWriter.cpp @ 1520:954d0cf29ca7 import-audio-data

Switch the normalisation option in WritableWaveFileModel from normalising on read to normalising on write, so that the saved file is already normalised and therefore can be read again without having to remember to normalise it
author Chris Cannam
date Wed, 12 Sep 2018 13:56:56 +0100
parents b837ccdd4946
children 8988b27ebf38
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@674 42 m_temp(0),
Chris@1350 43 m_file(0)
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@1508 76 m_temp = 0;
Chris@1508 77 }
Chris@1348 78 }
Chris@674 79 } catch (FileOperationFailed &f) {
Chris@674 80 m_error = f.what();
Chris@674 81 m_temp = 0;
Chris@1350 82 m_file = 0;
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@1520 207 float **samples = breakfastquay::allocate_channels<float>(m_channels, count);
Chris@1520 208 breakfastquay::v_deinterleave(samples, frames.data(), m_channels, count);
Chris@1520 209 bool result = writeSamples(samples, count);
Chris@1520 210 breakfastquay::deallocate_channels(samples, m_channels);
Chris@1520 211 return result;
Chris@1520 212 }
Chris@1520 213
Chris@174 214 bool
Chris@174 215 WavFileWriter::close()
Chris@174 216 {
Chris@1350 217 if (m_file) {
Chris@1350 218 sf_close(m_file);
Chris@1350 219 m_file = 0;
Chris@174 220 }
Chris@684 221 if (m_temp) {
Chris@684 222 m_temp->moveToTarget();
Chris@684 223 delete m_temp;
Chris@684 224 m_temp = 0;
Chris@684 225 }
Chris@174 226 return true;
Chris@148 227 }
Chris@148 228