WavFileReader.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 "WavFileReader.h"
17 
18 #include "base/HitCount.h"
19 #include "base/Profiler.h"
20 
21 #include <iostream>
22 
23 #include <QMutexLocker>
24 #include <QFileInfo>
25 
26 using namespace std;
27 
29  bool fileUpdating,
30  Normalisation normalisation) :
31  m_file(nullptr),
32  m_source(source),
33  m_path(source.getLocalFilename()),
34  m_seekable(false),
35  m_lastStart(0),
36  m_lastCount(0),
37  m_normalisation(normalisation),
38  m_max(0.f),
39  m_updating(fileUpdating)
40 {
41  m_frameCount = 0;
42  m_channelCount = 0;
43  m_sampleRate = 0;
44 
45  m_fileInfo.format = 0;
46  m_fileInfo.frames = 0;
47 
48 #ifdef Q_OS_WIN
49  m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
50 #else
51  m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
52 #endif
53 
54  if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) {
55  SVDEBUG << "WavFileReader::initialize: Failed to open file at \""
56  << m_path << "\" ("
57  << sf_strerror(m_file) << ")" << endl;
58 
59  if (m_file) {
60  m_error = QString("Couldn't load audio file '%1':\n%2")
61  .arg(m_path).arg(sf_strerror(m_file));
62  } else {
63  m_error = QString("Failed to open audio file '%1'")
64  .arg(m_path);
65  }
66  return;
67  }
68 
69  if (m_fileInfo.channels > 0) {
70 
71  m_frameCount = m_fileInfo.frames;
72  m_channelCount = m_fileInfo.channels;
73  m_sampleRate = m_fileInfo.samplerate;
74 
75  m_seekable = (m_fileInfo.seekable != 0);
76 
77  int type = m_fileInfo.format & SF_FORMAT_TYPEMASK;
78  int subtype = m_fileInfo.format & SF_FORMAT_SUBMASK;
79 
80  if (type >= SF_FORMAT_FLAC || type >= SF_FORMAT_OGG) {
81  // Our m_seekable reports whether a file is rapidly
82  // seekable, so things like Ogg don't qualify. We
83  // cautiously report every file type of "at least" the
84  // historical period of Ogg or FLAC as non-seekable.
85  m_seekable = false;
86  } else if (type == SF_FORMAT_WAV && subtype <= SF_FORMAT_DOUBLE) {
87  // libsndfile 1.0.26 has a bug (subsequently fixed in the
88  // repo) that causes all files to be reported as
89  // non-seekable. We know that certain common file types
90  // are definitely seekable so, again cautiously, identify
91  // and mark those (basically only non-adaptive WAVs).
92  m_seekable = true;
93  }
94 
96  m_max = getMax();
97  }
98 
99  const char *str = sf_get_string(m_file, SF_STR_TITLE);
100  if (str) {
101  m_title = str;
102  }
103  str = sf_get_string(m_file, SF_STR_ARTIST);
104  if (str) {
105  m_maker = str;
106  }
107  }
108 
109  SVDEBUG << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << ", normalisation " << int(m_normalisation) << endl;
110 }
111 
113 {
114  Profiler profiler("WavFileReader::~WavFileReader");
115 
116  if (m_file) sf_close(m_file);
117 }
118 
119 void
121 {
122  QMutexLocker locker(&m_mutex);
123 
124  sv_frame_t prevCount = m_fileInfo.frames;
125 
126  if (m_file) {
127  sf_close(m_file);
128 #ifdef Q_OS_WIN
129  m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
130 #else
131  m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
132 #endif
133  if (!m_file || m_fileInfo.channels <= 0) {
134  SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
135  << sf_strerror(m_file) << ")" << endl;
136  }
137  }
138 
139 // SVDEBUG << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << endl;
140 
141  m_frameCount = m_fileInfo.frames;
142 
143  if (m_channelCount == 0) {
144  m_channelCount = m_fileInfo.channels;
145  m_sampleRate = m_fileInfo.samplerate;
146  }
147 
148  if (m_frameCount != prevCount) {
149  emit frameCountChanged();
150  }
151 }
152 
153 void
155 {
157  m_updating = false;
159  m_max = getMax();
160  }
161 }
162 
165 {
166  floatvec_t frames = getInterleavedFramesUnnormalised(start, count);
167 
168  if (m_normalisation == Normalisation::None || m_max == 0.f) {
169  return frames;
170  }
171 
172  for (int i = 0; in_range_for(frames, i); ++i) {
173  frames[i] /= m_max;
174  }
175 
176  return frames;
177 }
178 
181  sv_frame_t count) const
182 {
183  static HitCount lastRead("WavFileReader: last read");
184 
185  if (count == 0) return {};
186 
187  QMutexLocker locker(&m_mutex);
188 
189  Profiler profiler("WavFileReader::getInterleavedFrames");
190 
191  if (!m_file || !m_channelCount) {
192  return {};
193  }
194 
195  if (start >= m_fileInfo.frames) {
196 // SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
197 // << " > " << m_fileInfo.frames << endl;
198  return {};
199  }
200 
201  if (start + count > m_fileInfo.frames) {
202  count = m_fileInfo.frames - start;
203  }
204 
205  // Because WaveFileModel::getSummaries() is called separately for
206  // individual channels, it's quite common for us to be called
207  // repeatedly for the same data. So this is worth cacheing.
208  if (start == m_lastStart && count == m_lastCount) {
209  lastRead.hit();
210  return m_buffer;
211  }
212 
213  // We don't actually support partial cache reads, but let's use
214  // the term partial to refer to any forward seek and consider a
215  // backward seek to be a miss
216  if (start >= m_lastStart) {
217  lastRead.partial();
218  } else {
219  lastRead.miss();
220  }
221 
222  if (sf_seek(m_file, start, SEEK_SET) < 0) {
223  return {};
224  }
225 
226  floatvec_t data;
227  sv_frame_t n = count * m_fileInfo.channels;
228  data.resize(n);
229 
230  m_lastStart = start;
231  m_lastCount = count;
232 
233  sf_count_t readCount = 0;
234  if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
235  return {};
236  }
237 
238  m_buffer = data;
239  return data;
240 }
241 
242 float
244 {
245  if (!m_file || !m_channelCount) {
246  return 0.f;
247  }
248 
249  // First try for a PEAK chunk
250 
251  double sfpeak = 0.0;
252  if (sf_command(m_file, SFC_GET_SIGNAL_MAX, &sfpeak, sizeof(sfpeak))
253  == SF_TRUE) {
254  SVDEBUG << "File has a PEAK chunk reporting max level " << sfpeak
255  << endl;
256  return float(fabs(sfpeak));
257  }
258 
259  // Failing that, read all the samples
260 
261  float peak = 0.f;
262  sv_frame_t ix = 0, chunk = 65536;
263 
264  while (ix < m_frameCount) {
265  auto frames = getInterleavedFrames(ix, chunk);
266  for (float x: frames) {
267  float level = fabsf(x);
268  if (level > peak) {
269  peak = level;
270  }
271  }
272  ix += chunk;
273  }
274 
275  SVDEBUG << "Measured file peak max level as " << peak << endl;
276  return peak;
277 }
278 
279 void
280 WavFileReader::getSupportedExtensions(set<QString> &extensions)
281 {
282  int count;
283 
284  if (sf_command(nullptr, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) {
285  extensions.insert("wav");
286  extensions.insert("aiff");
287  extensions.insert("aifc");
288  extensions.insert("aif");
289  return;
290  }
291 
292  SF_FORMAT_INFO info;
293  for (int i = 0; i < count; ++i) {
294  info.format = i;
295  if (!sf_command(nullptr, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) {
296  QString ext = QString(info.extension).toLower();
297  extensions.insert(ext);
298  if (ext == "oga") {
299  // libsndfile is awfully proper, it says it only
300  // supports .oga but lots of Ogg audio files in the
301  // wild are .ogg and it will accept that
302  extensions.insert("ogg");
303  }
304  }
305  }
306 }
307 
308 bool
310 {
311  set<QString> extensions;
312  getSupportedExtensions(extensions);
313  return (extensions.find(extension.toLower()) != extensions.end());
314 }
315 
316 bool
318 {
319  return (type == "audio/x-wav" ||
320  type == "audio/x-aiff" ||
321  type == "audio/basic");
322 }
323 
324 bool
326 {
327  return (supportsExtension(source.getExtension()) ||
329 }
330 
331 
SF_INFO m_fileInfo
Definition: WavFileReader.h:80
Normalisation m_normalisation
Definition: WavFileReader.h:96
int64_t sv_frame_t
Frame index, the unit of our time axis.
Definition: BaseTypes.h:31
static bool supportsExtension(QString ext)
sv_frame_t m_lastCount
Definition: WavFileReader.h:94
sv_frame_t m_lastStart
Definition: WavFileReader.h:93
floatvec_t getInterleavedFramesUnnormalised(sv_frame_t start, sv_frame_t count) const
static bool supportsContentType(QString type)
WavFileReader(FileSource source, bool fileUpdating=false, Normalisation normalise=Normalisation::None)
static bool supports(FileSource &source)
std::vector< float, breakfastquay::StlAllocator< float > > floatvec_t
Definition: BaseTypes.h:53
void partial()
Definition: HitCount.h:60
Profile class for counting cache hits and the like.
Definition: HitCount.h:26
QString m_title
Definition: WavFileReader.h:86
void frameCountChanged()
FileSource is a class used to refer to the contents of a file that may be either local or at a remote...
Definition: FileSource.h:59
static void getSupportedExtensions(std::set< QString > &extensions)
QString m_path
Definition: WavFileReader.h:84
QString getContentType() const
Return the MIME content type of this file, if known.
Definition: FileSource.cpp:634
floatvec_t m_buffer
Definition: WavFileReader.h:92
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
SNDFILE * m_file
Definition: WavFileReader.h:81
void miss()
Definition: HitCount.h:61
void updateFrameCount()
QString m_maker
Definition: WavFileReader.h:87
sv_frame_t m_frameCount
QString getExtension() const
Return the file extension for this file, if any.
Definition: FileSource.cpp:640
virtual ~WavFileReader()
QString m_error
Definition: WavFileReader.h:85
floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count) const override
Must be safe to call from multiple threads with different arguments on the same object at the same ti...
float getMax() const
sv_samplerate_t m_sampleRate
void hit()
Definition: HitCount.h:59
Profile point instance class.
Definition: Profiler.h:93