WavFileWriter.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006 Chris Cannam and QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "WavFileWriter.h"
17 
19 #include "base/Selection.h"
20 #include "base/TempWriteFile.h"
21 #include "base/Exceptions.h"
22 #include "base/Debug.h"
23 
24 #include <bqvec/Allocators.h>
25 #include <bqvec/VectorOps.h>
26 
27 #include <QFileInfo>
28 
29 #include <iostream>
30 #include <cmath>
31 #include <string>
32 
33 using namespace std;
34 
36  sv_samplerate_t sampleRate,
37  int channels,
38  FileWriteMode mode) :
39  m_path(path),
40  m_sampleRate(sampleRate),
41  m_channels(channels),
42  m_temp(nullptr),
43  m_file(nullptr)
44 {
45  SF_INFO fileInfo;
46 
47  int fileRate = int(round(m_sampleRate));
48  if (m_sampleRate != sv_samplerate_t(fileRate)) {
49  SVCERR << "WavFileWriter: WARNING: Non-integer sample rate "
50  << m_sampleRate << " presented, rounding to " << fileRate
51  << endl;
52  }
53  fileInfo.samplerate = fileRate;
54  fileInfo.channels = m_channels;
55  fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
56 
57  try {
58  QString writePath = m_path;
59  if (mode == WriteToTemporary) {
61  writePath = m_temp->getTemporaryFilename();
62  }
63 #ifdef Q_OS_WIN
64  m_file = sf_wchar_open((LPCWSTR)writePath.utf16(), SFM_WRITE, &fileInfo);
65 #else
66  m_file = sf_open(writePath.toLocal8Bit(), SFM_WRITE, &fileInfo);
67 #endif
68  if (!m_file) {
69  SVCERR << "WavFileWriter: Failed to create float-WAV file of "
70  << m_channels << " channels at rate " << fileRate << " ("
71  << sf_strerror(m_file) << ")" << endl;
72  m_error = QString("Failed to open audio file '%1' for writing")
73  .arg(writePath);
74  if (m_temp) {
75  delete m_temp;
76  m_temp = nullptr;
77  }
78  }
79  } catch (FileOperationFailed &f) {
80  m_error = f.what();
81  m_temp = nullptr;
82  m_file = nullptr;
83  }
84 }
85 
87 {
88  if (m_file) close();
89 }
90 
91 bool
93 {
94  return (m_error.isEmpty());
95 }
96 
97 QString
99 {
100  return m_error;
101 }
102 
103 QString
105 {
106  if (m_temp) {
107  return m_temp->getTemporaryFilename();
108  } else {
109  return m_path;
110  }
111 }
112 
113 bool
115  MultiSelection *selection)
116 {
117  if (source->getChannelCount() != m_channels) {
118  SVDEBUG << "WavFileWriter::writeModel: Wrong number of channels ("
119  << source->getChannelCount() << " != " << m_channels << ")"
120  << endl;
121  m_error = QString("Failed to write model to audio file '%1'")
122  .arg(getWriteFilename());
123  return false;
124  }
125 
126  if (!m_file) {
127  m_error = QString("Failed to write model to audio file '%1': File not open")
128  .arg(getWriteFilename());
129  return false;
130  }
131 
132  bool ownSelection = false;
133  if (!selection) {
134  selection = new MultiSelection;
135  selection->setSelection(Selection(source->getStartFrame(),
136  source->getEndFrame()));
137  ownSelection = true;
138  }
139 
140  sv_frame_t bs = 2048;
141 
142  for (MultiSelection::SelectionList::iterator i =
143  selection->getSelections().begin();
144  i != selection->getSelections().end(); ++i) {
145 
146  sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame());
147 
148  for (sv_frame_t f = f0; f < f1; f += bs) {
149 
150  sv_frame_t n = min(bs, f1 - f);
151  floatvec_t interleaved(n * m_channels, 0.f);
152 
153  for (int c = 0; c < int(m_channels); ++c) {
154  auto chanbuf = source->getData(c, f, n);
155  for (int i = 0; in_range_for(chanbuf, i); ++i) {
156  interleaved[i * m_channels + c] = chanbuf[i];
157  }
158  }
159 
160  sf_count_t written = sf_writef_float(m_file, interleaved.data(), n);
161 
162  if (written < n) {
163  m_error = QString("Only wrote %1 of %2 frames at file frame %3")
164  .arg(written).arg(n).arg(f);
165  break;
166  }
167  }
168  }
169 
170  if (ownSelection) delete selection;
171 
172  return isOK();
173 }
174 
175 bool
176 WavFileWriter::writeSamples(const float *const *samples, sv_frame_t count)
177 {
178  if (!m_file) {
179  m_error = QString("Failed to write model to audio file '%1': File not open")
180  .arg(getWriteFilename());
181  return false;
182  }
183 
184  float *b = new float[count * m_channels];
185  for (sv_frame_t i = 0; i < count; ++i) {
186  for (int c = 0; c < int(m_channels); ++c) {
187  b[i * m_channels + c] = samples[c][i];
188  }
189  }
190 
191  sv_frame_t written = sf_writef_float(m_file, b, count);
192 
193  delete[] b;
194 
195  if (written < count) {
196  m_error = QString("Only wrote %1 of %2 frames")
197  .arg(written).arg(count);
198  }
199 
200  return isOK();
201 }
202 
203 bool
205 {
206  sv_frame_t count = frames.size() / m_channels;
207  float **samples =
208  breakfastquay::allocate_channels<float>(m_channels, count);
209  breakfastquay::v_deinterleave
210  (samples, frames.data(), m_channels, int(count));
211  bool result = writeSamples(samples, count);
212  breakfastquay::deallocate_channels(samples, m_channels);
213  return result;
214 }
215 
216 bool
218 {
219  if (m_file) {
220  sf_close(m_file);
221  m_file = nullptr;
222  }
223  if (m_temp) {
224  m_temp->moveToTarget();
225  delete m_temp;
226  m_temp = nullptr;
227  }
228  return true;
229 }
230 
bool isOK() const
double sv_samplerate_t
Sample rate.
Definition: BaseTypes.h:51
virtual int getChannelCount() const =0
Return the number of distinct channels for this model.
sv_samplerate_t m_sampleRate
Definition: WavFileWriter.h:77
const char * what() const override
Definition: Exceptions.cpp:89
const SelectionList & getSelections() const
Definition: Selection.cpp:111
QString m_error
Definition: WavFileWriter.h:81
void setSelection(const Selection &selection)
Definition: Selection.cpp:117
int64_t sv_frame_t
Frame index, the unit of our time axis.
Definition: BaseTypes.h:31
virtual ~WavFileWriter()
virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) const =0
Get the specified set of samples from the given channel of the model in single-precision floating-poi...
A selection object simply represents a range in time, via start and end frame.
Definition: Selection.h:40
virtual QString getError() const
QString m_path
Definition: WavFileWriter.h:76
sv_frame_t getEndFrame() const
Return the audio frame at the end of the model, i.e.
Definition: Model.h:87
std::vector< float, breakfastquay::StlAllocator< float > > floatvec_t
Definition: BaseTypes.h:53
WavFileWriter(QString path, sv_samplerate_t sampleRate, int channels, FileWriteMode mode)
bool writeModel(DenseTimeValueModel *source, MultiSelection *selection=0)
A class that manages the creation of a temporary file with a given prefix and the renaming of that fi...
Definition: TempWriteFile.h:26
FileWriteMode
Specify the method used to open the destination file.
Definition: WavFileWriter.h:49
void moveToTarget()
Rename the temporary file to the target filename.
TempWriteFile * m_temp
Definition: WavFileWriter.h:79
virtual sv_frame_t getStartFrame() const =0
Return the first audio frame spanned by the model.
bool in_range_for(const C &container, T i)
Check whether an integer index is in range for a container, avoiding overflows and signed/unsigned co...
Definition: BaseTypes.h:37
#define SVDEBUG
Definition: Debug.h:106
Base class for models containing dense two-dimensional data (value against time). ...
QString getWriteFilename() const
SNDFILE * m_file
Definition: WavFileWriter.h:80
#define SVCERR
Definition: Debug.h:109
QString getTemporaryFilename()
Return the name of the temporary file.
bool putInterleavedFrames(const floatvec_t &frames)
As writeSamples, but compatible with WavFileReader api. More expensive.
bool writeSamples(const float *const *samples, sv_frame_t count)
Write samples from raw arrays; count is per-channel.