annotate data/fileio/WavFileReader.cpp @ 1833:21c792334c2e sensible-delimited-data-strings

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