Chris@148: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@148: 
Chris@148: /*
Chris@148:     Sonic Visualiser
Chris@148:     An audio file viewer and annotation editor.
Chris@148:     Centre for Digital Music, Queen Mary, University of London.
Chris@202:     This file copyright 2006 Chris Cannam and QMUL.
Chris@148:     
Chris@148:     This program is free software; you can redistribute it and/or
Chris@148:     modify it under the terms of the GNU General Public License as
Chris@148:     published by the Free Software Foundation; either version 2 of the
Chris@148:     License, or (at your option) any later version.  See the file
Chris@148:     COPYING included with this distribution for more information.
Chris@148: */
Chris@148: 
Chris@148: #include "WavFileReader.h"
Chris@148: 
Chris@1256: #include "base/HitCount.h"
Chris@1258: #include "base/Profiler.h"
Chris@1256: 
Chris@148: #include <iostream>
Chris@148: 
Chris@175: #include <QMutexLocker>
Chris@316: #include <QFileInfo>
Chris@175: 
Chris@1096: using namespace std;
Chris@1096: 
Chris@317: WavFileReader::WavFileReader(FileSource source, bool fileUpdating) :
Chris@148:     m_file(0),
Chris@316:     m_source(source),
Chris@316:     m_path(source.getLocalFilename()),
Chris@823:     m_seekable(false),
Chris@148:     m_lastStart(0),
Chris@176:     m_lastCount(0),
Chris@176:     m_updating(fileUpdating)
Chris@148: {
Chris@148:     m_frameCount = 0;
Chris@148:     m_channelCount = 0;
Chris@148:     m_sampleRate = 0;
Chris@148: 
Chris@148:     m_fileInfo.format = 0;
Chris@148:     m_fileInfo.frames = 0;
Chris@290:     m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@148: 
Chris@187:     if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) {
Chris@1279: 	SVDEBUG << "WavFileReader::initialize: Failed to open file at \""
Chris@1279:                 << m_path << "\" ("
Chris@1279:                 << sf_strerror(m_file) << ")" << endl;
Chris@148: 
Chris@148: 	if (m_file) {
Chris@290: 	    m_error = QString("Couldn't load audio file '%1':\n%2")
Chris@290: 		.arg(m_path).arg(sf_strerror(m_file));
Chris@148: 	} else {
Chris@290: 	    m_error = QString("Failed to open audio file '%1'")
Chris@290: 		.arg(m_path);
Chris@148: 	}
Chris@148: 	return;
Chris@148:     }
Chris@148: 
Chris@187:     if (m_fileInfo.channels > 0) {
Chris@823: 
Chris@187:         m_frameCount = m_fileInfo.frames;
Chris@187:         m_channelCount = m_fileInfo.channels;
Chris@187:         m_sampleRate = m_fileInfo.samplerate;
Chris@823: 
Chris@823:         m_seekable = (m_fileInfo.seekable != 0);
Chris@823: 
Chris@823:         int type = m_fileInfo.format & SF_FORMAT_TYPEMASK;
Chris@1162:         int subtype = m_fileInfo.format & SF_FORMAT_SUBMASK;
Chris@1162: 
Chris@823:         if (type >= SF_FORMAT_FLAC || type >= SF_FORMAT_OGG) {
Chris@1162:             // Our m_seekable reports whether a file is rapidly
Chris@1162:             // seekable, so things like Ogg don't qualify. We
Chris@1162:             // cautiously report every file type of "at least" the
Chris@1162:             // historical period of Ogg or FLAC as non-seekable.
Chris@823:             m_seekable = false;
Chris@1162:         } else if (type == SF_FORMAT_WAV && subtype <= SF_FORMAT_DOUBLE) {
Chris@1162:             // libsndfile 1.0.26 has a bug (subsequently fixed in the
Chris@1162:             // repo) that causes all files to be reported as
Chris@1162:             // non-seekable. We know that certain common file types
Chris@1162:             // are definitely seekable so, again cautiously, identify
Chris@1162:             // and mark those (basically only non-adaptive WAVs).
Chris@1162:             m_seekable = true;
Chris@823:         }
Chris@187:     }
Chris@175: 
Chris@1279:     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 << endl;
Chris@148: }
Chris@148: 
Chris@148: WavFileReader::~WavFileReader()
Chris@148: {
Chris@148:     if (m_file) sf_close(m_file);
Chris@148: }
Chris@148: 
Chris@148: void
Chris@175: WavFileReader::updateFrameCount()
Chris@175: {
Chris@175:     QMutexLocker locker(&m_mutex);
Chris@175: 
Chris@1038:     sv_frame_t prevCount = m_fileInfo.frames;
Chris@175: 
Chris@175:     if (m_file) {
Chris@175:         sf_close(m_file);
Chris@290:         m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo);
Chris@175:         if (!m_file || m_fileInfo.channels <= 0) {
Chris@1279:             SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" ("
Chris@1279:                     << sf_strerror(m_file) << ")" << endl;
Chris@175:         }
Chris@175:     }
Chris@175: 
Chris@690: //    SVDEBUG << "WavFileReader::updateFrameCount: now " << m_fileInfo.frames << endl;
Chris@175: 
Chris@176:     m_frameCount = m_fileInfo.frames;
Chris@176: 
Chris@187:     if (m_channelCount == 0) {
Chris@187:         m_channelCount = m_fileInfo.channels;
Chris@187:         m_sampleRate = m_fileInfo.samplerate;
Chris@187:     }
Chris@187: 
Chris@258:     if (m_frameCount != prevCount) {
Chris@258:         emit frameCountChanged();
Chris@258:     }
Chris@176: }
Chris@176: 
Chris@176: void
Chris@176: WavFileReader::updateDone()
Chris@176: {
Chris@176:     updateFrameCount();
Chris@176:     m_updating = false;
Chris@175: }
Chris@175: 
Chris@1096: vector<float>
Chris@1041: WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const
Chris@148: {
Chris@1256:     static HitCount lastRead("WavFileReader: last read");
Chris@1256: 
Chris@1096:     if (count == 0) return {};
Chris@175: 
Chris@175:     QMutexLocker locker(&m_mutex);
Chris@175: 
Chris@1258:     Profiler profiler("WavFileReader::getInterleavedFrames");
Chris@1258:     
Chris@175:     if (!m_file || !m_channelCount) {
Chris@1096:         return {};
Chris@175:     }
Chris@148: 
Chris@1040:     if (start >= m_fileInfo.frames) {
Chris@690: //        SVDEBUG << "WavFileReader::getInterleavedFrames: " << start
Chris@687: //                  << " > " << m_fileInfo.frames << endl;
Chris@1096: 	return {};
Chris@148:     }
Chris@148: 
Chris@1040:     if (start + count > m_fileInfo.frames) {
Chris@148: 	count = m_fileInfo.frames - start;
Chris@148:     }
Chris@148: 
Chris@1096:     // Because WaveFileModel::getSummaries() is called separately for
Chris@1096:     // individual channels, it's quite common for us to be called
Chris@1096:     // repeatedly for the same data. So this is worth cacheing.
Chris@1096:     if (start == m_lastStart && count == m_lastCount) {
Chris@1256:         lastRead.hit();
Chris@1096:         return m_buffer;
Chris@1096:     }
Chris@1256: 
Chris@1256:     // We don't actually support partial cache reads, but let's use
Chris@1256:     // the term partial to refer to any forward seek and consider a
Chris@1256:     // backward seek to be a miss
Chris@1256:     if (start >= m_lastStart) {
Chris@1256:         lastRead.partial();
Chris@1256:     } else {
Chris@1256:         lastRead.miss();
Chris@1256:     }
Chris@1096:     
Chris@1096:     if (sf_seek(m_file, start, SEEK_SET) < 0) {
Chris@1096:         return {};
Chris@148:     }
Chris@148: 
Chris@1096:     vector<float> data;
Chris@1096:     sv_frame_t n = count * m_fileInfo.channels;
Chris@1096:     data.resize(n);
Chris@1096: 
Chris@1096:     m_lastStart = start;
Chris@1096:     m_lastCount = count;
Chris@1096:     
Chris@1096:     sf_count_t readCount = 0;
Chris@1096:     if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) {
Chris@1096:         return {};
Chris@1096:     }
Chris@1096: 
Chris@1103:     m_buffer = data;
Chris@1096:     return data;
Chris@148: }
Chris@148: 
Chris@157: void
Chris@1096: WavFileReader::getSupportedExtensions(set<QString> &extensions)
Chris@157: {
Chris@157:     int count;
Chris@157: 
Chris@157:     if (sf_command(0, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) {
Chris@157:         extensions.insert("wav");
Chris@157:         extensions.insert("aiff");
Chris@316:         extensions.insert("aifc");
Chris@157:         extensions.insert("aif");
Chris@157:         return;
Chris@157:     }
Chris@157: 
Chris@157:     SF_FORMAT_INFO info;
Chris@157:     for (int i = 0; i < count; ++i) {
Chris@157:         info.format = i;
Chris@157:         if (!sf_command(0, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) {
Chris@783:             QString ext = QString(info.extension).toLower();
Chris@783:             extensions.insert(ext);
Chris@783:             if (ext == "oga") {
Chris@783:                 // libsndfile is awfully proper, it says it only
Chris@783:                 // supports .oga but lots of Ogg audio files in the
Chris@783:                 // wild are .ogg and it will accept that
Chris@783:                 extensions.insert("ogg");
Chris@783:             }
Chris@157:         }
Chris@157:     }
Chris@157: }
Chris@316: 
Chris@316: bool
Chris@316: WavFileReader::supportsExtension(QString extension)
Chris@316: {
Chris@1096:     set<QString> extensions;
Chris@316:     getSupportedExtensions(extensions);
Chris@316:     return (extensions.find(extension.toLower()) != extensions.end());
Chris@316: }
Chris@316: 
Chris@316: bool
Chris@316: WavFileReader::supportsContentType(QString type)
Chris@316: {
Chris@316:     return (type == "audio/x-wav" ||
Chris@316:             type == "audio/x-aiff" ||
Chris@316:             type == "audio/basic");
Chris@316: }
Chris@316: 
Chris@316: bool
Chris@317: WavFileReader::supports(FileSource &source)
Chris@316: {
Chris@316:     return (supportsExtension(source.getExtension()) ||
Chris@316:             supportsContentType(source.getContentType()));
Chris@316: }
Chris@316: 
Chris@316: