annotate data/fileio/WavFileReader.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 14747f24ad04
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 "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@1582 31 m_file(nullptr),
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@1592 98
Chris@1592 99 const char *str = sf_get_string(m_file, SF_STR_TITLE);
Chris@1592 100 if (str) {
Chris@1592 101 m_title = str;
Chris@1592 102 }
Chris@1592 103 str = sf_get_string(m_file, SF_STR_ARTIST);
Chris@1592 104 if (str) {
Chris@1592 105 m_maker = str;
Chris@1592 106 }
Chris@187 107 }
Chris@175 108
Chris@1520 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;
Chris@148 110 }
Chris@148 111
Chris@148 112 WavFileReader::~WavFileReader()
Chris@148 113 {
Chris@1858 114 Profiler profiler("WavFileReader::~WavFileReader");
Chris@1858 115
Chris@1349 116 if (m_file) sf_close(m_file);
Chris@148 117 }
Chris@148 118
Chris@148 119 void
Chris@175 120 WavFileReader::updateFrameCount()
Chris@175 121 {
Chris@175 122 QMutexLocker locker(&m_mutex);
Chris@175 123
Chris@1038 124 sv_frame_t prevCount = m_fileInfo.frames;
Chris@175 125
Chris@1349 126 if (m_file) {
Chris@1349 127 sf_close(m_file);
Chris@1349 128 #ifdef Q_OS_WIN
Chris@1349 129 m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo);
Chris@1349 130 #else
Chris@1349 131 m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@1349 132 #endif
Chris@1349 133 if (!m_file || m_fileInfo.channels <= 0) {
Chris@1349 134 SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
Chris@1349 135 << sf_strerror(m_file) << ")" << endl;
Chris@175 136 }
Chris@175 137 }
Chris@175 138
Chris@690 139 // SVDEBUG << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << endl;
Chris@175 140
Chris@176 141 m_frameCount = m_fileInfo.frames;
Chris@176 142
Chris@187 143 if (m_channelCount == 0) {
Chris@187 144 m_channelCount = m_fileInfo.channels;
Chris@187 145 m_sampleRate = m_fileInfo.samplerate;
Chris@187 146 }
Chris@187 147
Chris@258 148 if (m_frameCount != prevCount) {
Chris@258 149 emit frameCountChanged();
Chris@258 150 }
Chris@176 151 }
Chris@176 152
Chris@176 153 void
Chris@176 154 WavFileReader::updateDone()
Chris@176 155 {
Chris@176 156 updateFrameCount();
Chris@176 157 m_updating = false;
Chris@1520 158 if (m_normalisation != Normalisation::None) {
Chris@1513 159 m_max = getMax();
Chris@1513 160 }
Chris@175 161 }
Chris@175 162
Chris@1326 163 floatvec_t
Chris@1041 164 WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148 165 {
Chris@1513 166 floatvec_t frames = getInterleavedFramesUnnormalised(start, count);
Chris@1513 167
Chris@1520 168 if (m_normalisation == Normalisation::None || m_max == 0.f) {
Chris@1513 169 return frames;
Chris@1513 170 }
Chris@1513 171
Chris@1513 172 for (int i = 0; in_range_for(frames, i); ++i) {
Chris@1513 173 frames[i] /= m_max;
Chris@1513 174 }
Chris@1513 175
Chris@1513 176 return frames;
Chris@1513 177 }
Chris@1513 178
Chris@1513 179 floatvec_t
Chris@1513 180 WavFileReader::getInterleavedFramesUnnormalised(sv_frame_t start,
Chris@1513 181 sv_frame_t count) const
Chris@1513 182 {
Chris@1256 183 static HitCount lastRead("WavFileReader: last read");
Chris@1256 184
Chris@1096 185 if (count == 0) return {};
Chris@175 186
Chris@175 187 QMutexLocker locker(&m_mutex);
Chris@175 188
Chris@1258 189 Profiler profiler("WavFileReader::getInterleavedFrames");
Chris@1258 190
Chris@1349 191 if (!m_file || !m_channelCount) {
Chris@1096 192 return {};
Chris@175 193 }
Chris@148 194
Chris@1040 195 if (start >= m_fileInfo.frames) {
Chris@690 196 // SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
Chris@687 197 // << " > " << m_fileInfo.frames << endl;
Chris@1343 198 return {};
Chris@148 199 }
Chris@148 200
Chris@1040 201 if (start + count > m_fileInfo.frames) {
Chris@1343 202 count = m_fileInfo.frames - start;
Chris@148 203 }
Chris@148 204
Chris@1096 205 // Because WaveFileModel::getSummaries() is called separately for
Chris@1096 206 // individual channels, it's quite common for us to be called
Chris@1096 207 // repeatedly for the same data. So this is worth cacheing.
Chris@1096 208 if (start == m_lastStart && count == m_lastCount) {
Chris@1256 209 lastRead.hit();
Chris@1096 210 return m_buffer;
Chris@1096 211 }
Chris@1256 212
Chris@1256 213 // We don't actually support partial cache reads, but let's use
Chris@1256 214 // the term partial to refer to any forward seek and consider a
Chris@1256 215 // backward seek to be a miss
Chris@1256 216 if (start >= m_lastStart) {
Chris@1256 217 lastRead.partial();
Chris@1256 218 } else {
Chris@1256 219 lastRead.miss();
Chris@1256 220 }
Chris@1096 221
Chris@1349 222 if (sf_seek(m_file, start, SEEK_SET) < 0) {
Chris@1096 223 return {};
Chris@148 224 }
Chris@148 225
Chris@1326 226 floatvec_t data;
Chris@1096 227 sv_frame_t n = count * m_fileInfo.channels;
Chris@1096 228 data.resize(n);
Chris@1096 229
Chris@1096 230 m_lastStart = start;
Chris@1096 231 m_lastCount = count;
Chris@1096 232
Chris@1096 233 sf_count_t readCount = 0;
Chris@1349 234 if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
Chris@1096 235 return {};
Chris@1096 236 }
Chris@1096 237
Chris@1103 238 m_buffer = data;
Chris@1096 239 return data;
Chris@148 240 }
Chris@148 241
Chris@1513 242 float
Chris@1513 243 WavFileReader::getMax() const
Chris@1513 244 {
Chris@1513 245 if (!m_file || !m_channelCount) {
Chris@1513 246 return 0.f;
Chris@1513 247 }
Chris@1513 248
Chris@1513 249 // First try for a PEAK chunk
Chris@1513 250
Chris@1513 251 double sfpeak = 0.0;
Chris@1513 252 if (sf_command(m_file, SFC_GET_SIGNAL_MAX, &sfpeak, sizeof(sfpeak))
Chris@1513 253 == SF_TRUE) {
Chris@1513 254 SVDEBUG << "File has a PEAK chunk reporting max level " << sfpeak
Chris@1513 255 << endl;
Chris@1513 256 return float(fabs(sfpeak));
Chris@1513 257 }
Chris@1513 258
Chris@1513 259 // Failing that, read all the samples
Chris@1513 260
Chris@1513 261 float peak = 0.f;
Chris@1513 262 sv_frame_t ix = 0, chunk = 65536;
Chris@1513 263
Chris@1513 264 while (ix < m_frameCount) {
Chris@1513 265 auto frames = getInterleavedFrames(ix, chunk);
Chris@1513 266 for (float x: frames) {
Chris@1513 267 float level = fabsf(x);
Chris@1513 268 if (level > peak) {
Chris@1513 269 peak = level;
Chris@1513 270 }
Chris@1513 271 }
Chris@1513 272 ix += chunk;
Chris@1513 273 }
Chris@1513 274
Chris@1513 275 SVDEBUG << "Measured file peak max level as " << peak << endl;
Chris@1513 276 return peak;
Chris@1513 277 }
Chris@1513 278
Chris@157 279 void
Chris@1096 280 WavFileReader::getSupportedExtensions(set<QString> &extensions)
Chris@157 281 {
Chris@157 282 int count;
Chris@157 283
Chris@1582 284 if (sf_command(nullptr, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) {
Chris@157 285 extensions.insert("wav");
Chris@157 286 extensions.insert("aiff");
Chris@316 287 extensions.insert("aifc");
Chris@157 288 extensions.insert("aif");
Chris@157 289 return;
Chris@157 290 }
Chris@157 291
Chris@157 292 SF_FORMAT_INFO info;
Chris@157 293 for (int i = 0; i < count; ++i) {
Chris@157 294 info.format = i;
Chris@1582 295 if (!sf_command(nullptr, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) {
Chris@783 296 QString ext = QString(info.extension).toLower();
Chris@783 297 extensions.insert(ext);
Chris@783 298 if (ext == "oga") {
Chris@783 299 // libsndfile is awfully proper, it says it only
Chris@783 300 // supports .oga but lots of Ogg audio files in the
Chris@783 301 // wild are .ogg and it will accept that
Chris@783 302 extensions.insert("ogg");
Chris@783 303 }
Chris@157 304 }
Chris@157 305 }
Chris@157 306 }
Chris@316 307
Chris@316 308 bool
Chris@316 309 WavFileReader::supportsExtension(QString extension)
Chris@316 310 {
Chris@1096 311 set<QString> extensions;
Chris@316 312 getSupportedExtensions(extensions);
Chris@316 313 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 314 }
Chris@316 315
Chris@316 316 bool
Chris@316 317 WavFileReader::supportsContentType(QString type)
Chris@316 318 {
Chris@316 319 return (type == "audio/x-wav" ||
Chris@316 320 type == "audio/x-aiff" ||
Chris@316 321 type == "audio/basic");
Chris@316 322 }
Chris@316 323
Chris@316 324 bool
Chris@317 325 WavFileReader::supports(FileSource &source)
Chris@316 326 {
Chris@316 327 return (supportsExtension(source.getExtension()) ||
Chris@316 328 supportsContentType(source.getContentType()));
Chris@316 329 }
Chris@316 330
Chris@316 331