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