changeset 1605:d83ab62cdc28

Merge from branch bqaudiostream
author Chris Cannam
date Wed, 30 Jan 2019 14:57:12 +0000
parents 841b2a3e606d (current diff) 8aa1447fe27e (diff)
children 4a7ebaaf9f53
files data/fileio/CoreAudioFileReader.cpp data/fileio/CoreAudioFileReader.h data/fileio/OggVorbisFileReader.cpp data/fileio/OggVorbisFileReader.h
diffstat 28 files changed, 531 insertions(+), 817 deletions(-) [+]
line wrap: on
line diff
--- a/base/test/svcore-base-test.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/base/test/svcore-base-test.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -21,6 +21,8 @@
 #include "TestColumnOp.h"
 #include "TestMovingMedian.h"
 
+#include "system/Init.h"
+
 #include <QtTest>
 
 #include <iostream>
@@ -29,6 +31,10 @@
 {
     int good = 0, bad = 0;
 
+    // This is necessary to ensure correct behaviour of snprintf with
+    // older MinGW implementations
+    svSystemSpecificInitialisation();
+
     QCoreApplication app(argc, argv);
     app.setOrganizationName("sonic-visualiser");
     app.setApplicationName("test-svcore-base");
--- a/data/fileio/AudioFileReader.h	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/AudioFileReader.h	Wed Jan 30 14:57:12 2019 +0000
@@ -79,14 +79,14 @@
      * may be implemented by subclasses that support file tagging.
      * This is not the same thing as the file name.
      */
-    virtual QString getTitle() const { return ""; }
+    virtual QString getTitle() const = 0;
 
     /**
      * Return the "maker" of the work in the audio file, if known.
      * This could represent almost anything (band, composer,
      * conductor, artist etc).
      */
-    virtual QString getMaker() const { return ""; }
+    virtual QString getMaker() const = 0;
 
     /**
      * Return the local file path of the audio data. This is the
--- a/data/fileio/AudioFileReaderFactory.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/AudioFileReaderFactory.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -17,9 +17,8 @@
 
 #include "WavFileReader.h"
 #include "DecodingWavFileReader.h"
-#include "OggVorbisFileReader.h"
 #include "MP3FileReader.h"
-#include "CoreAudioFileReader.h"
+#include "BQAFileReader.h"
 #include "AudioFileSizeEstimator.h"
 
 #include "base/StorageAdviser.h"
@@ -28,26 +27,21 @@
 #include <QFileInfo>
 #include <iostream>
 
+using namespace std;
+
 QString
 AudioFileReaderFactory::getKnownExtensions()
 {
-    std::set<QString> extensions;
+    set<QString> extensions;
 
     WavFileReader::getSupportedExtensions(extensions);
 #ifdef HAVE_MAD
     MP3FileReader::getSupportedExtensions(extensions);
 #endif
-#ifdef HAVE_OGGZ
-#ifdef HAVE_FISHSOUND
-    OggVorbisFileReader::getSupportedExtensions(extensions);
-#endif
-#endif
-#ifdef HAVE_COREAUDIO
-    CoreAudioFileReader::getSupportedExtensions(extensions);
-#endif
+    BQAFileReader::getSupportedExtensions(extensions);
 
     QString rv;
-    for (std::set<QString>::const_iterator i = extensions.begin();
+    for (set<QString>::const_iterator i = extensions.begin();
          i != extensions.end(); ++i) {
         if (i != extensions.begin()) rv += " ";
         rv += "*." + *i;
@@ -56,6 +50,23 @@
     return rv;
 }
 
+bool
+AudioFileReaderFactory::isSupported(FileSource source)
+{
+#ifdef HAVE_MAD
+    if (MP3FileReader::supports(source)) {
+        return true;
+    }
+#endif
+    if (WavFileReader::supports(source)) {
+        return true;
+    }
+    if (BQAFileReader::supports(source)) {
+        return true;
+    }
+    return false;
+}
+
 AudioFileReader *
 AudioFileReaderFactory::createReader(FileSource source,
                                      Parameters params,
@@ -126,27 +137,35 @@
             SVDEBUG << "AudioFileReaderFactory: Source not officially handled by any reader, trying again with each reader in turn"
                     << endl;
         }
-    
-#ifdef HAVE_OGGZ
-#ifdef HAVE_FISHSOUND
-        // If we have the "real" Ogg reader, use that first. Otherwise
-        // the WavFileReader will likely accept Ogg files (as
-        // libsndfile supports them) but it has no ability to return
-        // file metadata, so we get a slightly less useful result.
-        if (anyReader || OggVorbisFileReader::supports(source)) {
 
-            reader = new OggVorbisFileReader
-                (source, decodeMode, cacheMode, targetRate, normalised, reporter);
+#ifdef HAVE_MAD
+        // Having said we'll try any reader on the second pass, we
+        // actually don't want to try the mp3 reader for anything not
+        // identified as an mp3 - it can't identify files by header,
+        // it'll try to read any data and then fail with
+        // synchronisation errors - causing misleading and potentially
+        // alarming warning messages at the least
+        if (!anyReader) {
+            if (MP3FileReader::supports(source)) {
 
-            if (reader->isOK()) {
-                SVDEBUG << "AudioFileReaderFactory: Ogg file reader is OK, returning it" << endl;
-                return reader;
-            } else {
-                delete reader;
+                MP3FileReader::GaplessMode gapless =
+                    params.gaplessMode == GaplessMode::Gapless ?
+                    MP3FileReader::GaplessMode::Gapless :
+                    MP3FileReader::GaplessMode::Gappy;
+            
+                reader = new MP3FileReader
+                    (source, decodeMode, cacheMode, gapless,
+                     targetRate, normalised, reporter);
+
+                if (reader->isOK()) {
+                    SVDEBUG << "AudioFileReaderFactory: MP3 file reader is OK, returning it" << endl;
+                    return reader;
+                } else {
+                    delete reader;
+                }
             }
         }
 #endif
-#endif
 
         if (anyReader || WavFileReader::supports(source)) {
 
@@ -178,43 +197,20 @@
                 delete reader;
             }
         }
+    
+        if (anyReader || BQAFileReader::supports(source)) {
 
-#ifdef HAVE_MAD
-        if (anyReader || MP3FileReader::supports(source)) {
-
-            MP3FileReader::GaplessMode gapless =
-                params.gaplessMode == GaplessMode::Gapless ?
-                MP3FileReader::GaplessMode::Gapless :
-                MP3FileReader::GaplessMode::Gappy;
-            
-            reader = new MP3FileReader
-                (source, decodeMode, cacheMode, gapless,
+            reader = new BQAFileReader
+                (source, decodeMode, cacheMode, 
                  targetRate, normalised, reporter);
 
             if (reader->isOK()) {
-                SVDEBUG << "AudioFileReaderFactory: MP3 file reader is OK, returning it" << endl;
+                SVDEBUG << "AudioFileReaderFactory: BQA reader is OK, returning it" << endl;
                 return reader;
             } else {
                 delete reader;
             }
         }
-#endif
-
-#ifdef HAVE_COREAUDIO
-        if (anyReader || CoreAudioFileReader::supports(source)) {
-
-            reader = new CoreAudioFileReader
-                (source, decodeMode, cacheMode, targetRate, normalised, reporter);
-
-            if (reader->isOK()) {
-                SVDEBUG << "AudioFileReaderFactory: CoreAudio reader is OK, returning it" << endl;
-                return reader;
-            } else {
-                delete reader;
-            }
-        }
-#endif
-
     }
     
     SVCERR << "AudioFileReaderFactory::Failed to create a reader for "
--- a/data/fileio/AudioFileReaderFactory.h	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/AudioFileReaderFactory.h	Wed Jan 30 14:57:12 2019 +0000
@@ -141,6 +141,15 @@
     static AudioFileReader *createReader(FileSource source,
                                          Parameters parameters,
                                          ProgressReporter *reporter = 0);
+
+    /**
+     * Return true if the given source has a file extension that
+     * indicates a supported file type. This does not necessarily mean
+     * that it can be opened; conversely it may theoretically be
+     * possible to open some files without supported extensions,
+     * depending on the readers available.
+     */
+    static bool isSupported(FileSource source);
 };
 
 #endif
--- a/data/fileio/AudioFileSizeEstimator.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/AudioFileSizeEstimator.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -83,7 +83,7 @@
 
         if (extension == "ogg" || extension == "oga" ||
             extension == "m4a" || extension == "mp3" ||
-            extension == "wma") {
+            extension == "wma" || extension == "opus") {
 
             // Usually a lossy file. Compression ratios can vary
             // dramatically, but don't usually exceed about 20x compared
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/BQAFileReader.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -0,0 +1,204 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "BQAFileReader.h"
+
+#include <bqaudiostream/AudioReadStreamFactory.h>
+#include <bqaudiostream/AudioReadStream.h>
+#include <bqaudiostream/Exceptions.h>
+
+#include "base/Profiler.h"
+#include "base/ProgressReporter.h"
+
+#include <QFileInfo>
+
+using namespace std;
+
+BQAFileReader::BQAFileReader(FileSource source,
+			     DecodeMode decodeMode,
+			     CacheMode mode,
+			     sv_samplerate_t targetRate,
+			     bool normalised,
+			     ProgressReporter *reporter) :
+    CodedAudioFileReader(mode, targetRate, normalised),
+    m_source(source),
+    m_path(source.getLocalFilename()),
+    m_cancelled(false),
+    m_completion(0),
+    m_reporter(reporter),
+    m_decodeThread(0)
+{
+    SVDEBUG << "BQAFileReader: local path: \"" << m_path
+            << "\", decode mode: " << decodeMode << " ("
+            << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded")
+            << ")" << endl;
+
+    m_channelCount = 0;
+    m_fileRate = 0;
+
+    Profiler profiler("BQAFileReader::BQAFileReader");
+
+    try {
+	m_stream = breakfastquay::AudioReadStreamFactory::createReadStream
+	    (m_path.toUtf8().data());
+    } catch (const std::exception &e) {
+	m_error = e.what();
+        SVDEBUG << "BQAFileReader: createReadStream failed: " << m_error << endl;
+	m_stream = 0;
+	return;
+    }
+
+    m_channelCount = int(m_stream->getChannelCount());
+    m_fileRate = sv_samplerate_t(m_stream->getSampleRate());
+    m_title = QString::fromUtf8(m_stream->getTrackName().c_str());
+    m_maker = QString::fromUtf8(m_stream->getArtistName().c_str());
+
+    initialiseDecodeCache();
+
+    if (decodeMode == DecodeAtOnce) {
+
+        if (m_reporter) {
+            connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
+            m_reporter->setMessage
+                (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
+        }
+
+        sv_frame_t blockSize = 65536;
+        floatvec_t block(blockSize * m_channelCount, 0.f);
+
+	while (true) {
+	    try {
+		sv_frame_t retrieved = 
+		    m_stream->getInterleavedFrames(blockSize, block.data());
+
+		addSamplesToDecodeCache(block.data(), retrieved);
+
+		if (retrieved < blockSize) {
+		    break;
+		}
+	    } catch (const breakfastquay::InvalidFileFormat &f) {
+		m_error = f.what();
+                SVDEBUG << "BQAFileReader: init failed: " << m_error << endl;
+		break;
+	    }
+
+	    if (m_cancelled) break;
+        }
+
+        if (isDecodeCacheInitialised()) finishDecodeCache();
+        endSerialised();
+
+        if (m_reporter) m_reporter->setProgress(100);
+
+        delete m_stream;
+        m_stream = 0;
+
+    } else {
+
+        if (m_reporter) m_reporter->setProgress(100);
+
+        m_decodeThread = new DecodeThread(this);
+        m_decodeThread->start();
+    }
+}
+
+BQAFileReader::~BQAFileReader()
+{
+    if (m_decodeThread) {
+        m_cancelled = true;
+        m_decodeThread->wait();
+        delete m_decodeThread;
+    }
+    
+    delete m_stream;
+}
+
+void
+BQAFileReader::cancelled()
+{
+    m_cancelled = true;
+}
+
+void
+BQAFileReader::DecodeThread::run()
+{
+    if (m_reader->m_cacheMode == CacheInTemporaryFile) {
+        m_reader->startSerialised("BQAFileReader::Decode");
+    }
+
+    sv_frame_t blockSize = 65536;
+    floatvec_t block(blockSize * m_reader->getChannelCount(), 0.f);
+    
+    while (true) {
+	try {
+	    sv_frame_t retrieved = 
+		m_reader->m_stream->getInterleavedFrames
+		(blockSize, block.data());
+
+	    m_reader->addSamplesToDecodeCache(block.data(), retrieved);
+
+	    if (retrieved < blockSize) {
+		break;
+	    }
+	} catch (const breakfastquay::InvalidFileFormat &f) {
+	    m_reader->m_error = f.what();
+            SVDEBUG << "BQAFileReader: decode failed: " << m_reader->m_error << endl;
+	    break;
+	}
+
+	if (m_reader->m_cancelled) break;
+    }
+    
+    if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
+    m_reader->m_completion = 100;
+
+    m_reader->endSerialised();
+
+    delete m_reader->m_stream;
+    m_reader->m_stream = 0;
+} 
+
+void
+BQAFileReader::getSupportedExtensions(set<QString> &extensions)
+{
+    vector<string> exts = 
+        breakfastquay::AudioReadStreamFactory::getSupportedFileExtensions();
+    for (auto e: exts) {
+        extensions.insert(QString::fromUtf8(e.c_str()));
+    }
+}
+
+bool
+BQAFileReader::supportsExtension(QString extension)
+{
+    set<QString> extensions;
+    getSupportedExtensions(extensions);
+    return (extensions.find(extension.toLower()) != extensions.end());
+}
+
+bool
+BQAFileReader::supportsContentType(QString type)
+{
+    // extremely optimistic, but it's better than rejecting everything
+    //!!! todo: be more sensible
+    return (type.startsWith("audio/"));
+}
+
+bool
+BQAFileReader::supports(FileSource &source)
+{
+    return (supportsExtension(source.getExtension()) ||
+            supportsContentType(source.getContentType()));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/BQAFileReader.h	Wed Jan 30 14:57:12 2019 +0000
@@ -0,0 +1,87 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SV_BQA_FILE_READER_H
+#define SV_BQA_FILE_READER_H
+
+#include <bqaudiostream/AudioReadStreamFactory.h>
+
+#include "CodedAudioFileReader.h"
+#include "base/Thread.h"
+
+#include <set>
+
+class ProgressReporter;
+
+/**
+ * Audio file reader using bqaudiostream library AudioReadStream
+ * classes.
+ */
+class BQAFileReader : public CodedAudioFileReader
+{
+    Q_OBJECT
+
+public:
+    BQAFileReader(FileSource source,
+                  DecodeMode decodeMode,
+                  CacheMode cacheMode,
+                  sv_samplerate_t targetRate = 0,
+                  bool normalised = false,
+                  ProgressReporter *reporter = 0);
+    virtual ~BQAFileReader();
+
+    QString getError() const override { return m_error; }
+    QString getLocation() const override { return m_source.getLocation(); }
+    QString getTitle() const override { return m_title; }
+    QString getMaker() const override { return m_maker; }
+    
+    static void getSupportedExtensions(std::set<QString> &extensions);
+    static bool supportsExtension(QString ext);
+    static bool supportsContentType(QString type);
+    static bool supports(FileSource &source);
+
+    int getDecodeCompletion() const override { return m_completion; }
+
+    bool isUpdating() const override {
+        return m_decodeThread && m_decodeThread->isRunning();
+    }
+
+public slots:
+    void cancelled();
+
+protected:
+    FileSource m_source;
+    QString m_path;
+    QString m_error;
+    QString m_title;
+    QString m_maker;
+
+    breakfastquay::AudioReadStream *m_stream;
+
+    bool m_cancelled;
+    int m_completion;
+    ProgressReporter *m_reporter;
+    
+    class DecodeThread : public Thread {
+    public:
+        DecodeThread(BQAFileReader *reader) : m_reader(reader) { }
+        virtual void run();
+    protected:
+	BQAFileReader *m_reader;
+    };
+    DecodeThread *m_decodeThread;
+};
+
+#endif
+
--- a/data/fileio/CoreAudioFileReader.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,251 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006-2012 Chris Cannam and QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
- */
-
-#ifdef HAVE_COREAUDIO
-
-#include "CoreAudioFileReader.h"
-#include "base/Profiler.h"
-#include "base/ProgressReporter.h"
-#include "system/System.h"
-
-#include <QFileInfo>
-
-#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__)
-#include <AudioToolbox/AudioToolbox.h>
-#include <AudioToolbox/ExtendedAudioFile.h>
-#else
-#include "AudioToolbox.h"
-#include "ExtendedAudioFile.h"
-#endif
-
-class CoreAudioFileReader::D
-{
-public:
-    D() : blockSize(1024), valid(false) { }
-
-    ExtAudioFileRef file;
-    AudioBufferList buffer;
-    OSStatus err;
-    AudioStreamBasicDescription asbd;
-    int blockSize;
-    bool valid;
-};
-
-static QString
-codestr(OSStatus err)
-{
-    char text[5];
-    UInt32 uerr = err;
-    text[0] = (uerr >> 24) & 0xff;
-    text[1] = (uerr >> 16) & 0xff;
-    text[2] = (uerr >> 8) & 0xff;
-    text[3] = (uerr) & 0xff;
-    text[4] = '\0';
-    return QString("%1 (%2)").arg(err).arg(QString::fromLocal8Bit(text));
-}
-
-CoreAudioFileReader::CoreAudioFileReader(FileSource source,
-                                         DecodeMode /* decodeMode */,
-                                         CacheMode mode,
-                                         sv_samplerate_t targetRate,
-                                         bool normalised,
-                                         ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate, normalised),
-    m_source(source),
-    m_path(source.getLocalFilename()),
-    m_d(new D),
-    m_reporter(reporter),
-    m_cancelled(false),
-    m_completion(0),
-    m_decodeThread(0)
-{
-    SVDEBUG << "CoreAudioFileReader: local path: \"" << m_path << "\"" << endl;
-
-    m_channelCount = 0;
-    m_fileRate = 0;
-
-    m_d->buffer.mBuffers[0].mData = 0;
-
-    Profiler profiler("CoreAudioFileReader::CoreAudioFileReader", true);
-
-    QByteArray ba = m_path.toLocal8Bit();
-
-    CFURLRef url = CFURLCreateFromFileSystemRepresentation
-        (kCFAllocatorDefault,
-         (const UInt8 *)ba.data(),
-         (CFIndex)ba.length(),
-         false);
-
-    //!!! how do we find out if the file open fails because of DRM protection?
-
-//#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040)
-//    FSRef fsref;
-//    if (!CFURLGetFSRef(url, &fsref)) { // returns Boolean, not error code
-//        m_error = "CoreAudioReadStream: Error looking up FS ref (file not found?)";
-//        return;
-//    }
-//    m_d->err = ExtAudioFileOpen(&fsref, &m_d->file);
-//#else
-    m_d->err = ExtAudioFileOpenURL(url, &m_d->file);
-//#endif
-
-    CFRelease(url);
-
-    if (m_d->err) { 
-        m_error = "CoreAudioReadStream: Error opening file: code " + codestr(m_d->err);
-        return;
-    }
-    if (!m_d->file) { 
-        m_error = "CoreAudioReadStream: Failed to open file, but no error reported!";
-        return;
-    }
-    
-    UInt32 propsize = sizeof(AudioStreamBasicDescription);
-    m_d->err = ExtAudioFileGetProperty
-        (m_d->file, kExtAudioFileProperty_FileDataFormat, &propsize, &m_d->asbd);
-    
-    if (m_d->err) {
-        m_error = "CoreAudioReadStream: Error in getting basic description: code " + codestr(m_d->err);
-        ExtAudioFileDispose(m_d->file);
-        return;
-    }
-        
-    m_channelCount = m_d->asbd.mChannelsPerFrame;
-    m_fileRate = m_d->asbd.mSampleRate;
-
-    SVDEBUG << "CoreAudioFileReader: " << m_channelCount << " channels, " << m_fileRate << " Hz" << endl;
-
-    m_d->asbd.mFormatID = kAudioFormatLinearPCM;
-    m_d->asbd.mFormatFlags =
-        kAudioFormatFlagIsFloat |
-        kAudioFormatFlagIsPacked |
-        kAudioFormatFlagsNativeEndian;
-    m_d->asbd.mBitsPerChannel = sizeof(float) * 8;
-    m_d->asbd.mBytesPerFrame = sizeof(float) * m_channelCount;
-    m_d->asbd.mBytesPerPacket = sizeof(float) * m_channelCount;
-    m_d->asbd.mFramesPerPacket = 1;
-    m_d->asbd.mReserved = 0;
-        
-    m_d->err = ExtAudioFileSetProperty
-        (m_d->file, kExtAudioFileProperty_ClientDataFormat, propsize, &m_d->asbd);
-    
-    if (m_d->err) {
-        m_error = "CoreAudioReadStream: Error in setting client format: code " + codestr(m_d->err);
-        ExtAudioFileDispose(m_d->file);
-        return;
-    }
-
-    m_d->buffer.mNumberBuffers = 1;
-    m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount;
-    m_d->buffer.mBuffers[0].mDataByteSize = sizeof(float) * m_channelCount * m_d->blockSize;
-    m_d->buffer.mBuffers[0].mData = new float[m_channelCount * m_d->blockSize];
-
-    m_d->valid = true;
-
-    initialiseDecodeCache();
-
-    if (m_reporter) {
-        connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
-        m_reporter->setMessage
-            (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
-    }
-
-    while (1) {
-
-        UInt32 framesRead = m_d->blockSize;
-        m_d->err = ExtAudioFileRead(m_d->file, &framesRead, &m_d->buffer);
-
-        if (m_d->err) {
-            m_error = QString("Error in CoreAudio decoding: code %1")
-                .arg(m_d->err);
-            break;
-        }
-
-        //!!! progress?
-
-        //    cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << endl;
-
-        // buffers are interleaved unless specified otherwise
-        addSamplesToDecodeCache((float *)m_d->buffer.mBuffers[0].mData, framesRead);
-
-        if ((int)framesRead < m_d->blockSize) break;
-    }
-
-    finishDecodeCache();
-    endSerialised();
-
-    m_completion = 100;
-}
-
-
-CoreAudioFileReader::~CoreAudioFileReader()
-{
-    SVDEBUG << "CoreAudioFileReader::~CoreAudioFileReader" << endl;
-
-    if (m_d->valid) {
-        ExtAudioFileDispose(m_d->file);
-        delete[] (float *)(m_d->buffer.mBuffers[0].mData);
-    }
-
-    delete m_d;
-}
-
-void
-CoreAudioFileReader::cancelled()
-{
-  m_cancelled = true;
-}
-
-void
-CoreAudioFileReader::getSupportedExtensions(std::set<QString> &extensions)
-{
-    extensions.insert("aiff");
-    extensions.insert("aif");
-    extensions.insert("au");
-    extensions.insert("m4a");
-    extensions.insert("m4b");
-    extensions.insert("m4p");
-    extensions.insert("mp3");
-    extensions.insert("mp4");
-    extensions.insert("wav");
-}
-
-bool
-CoreAudioFileReader::supportsExtension(QString extension)
-{
-    std::set<QString> extensions;
-    getSupportedExtensions(extensions);
-    return (extensions.find(extension.toLower()) != extensions.end());
-}
-
-bool
-CoreAudioFileReader::supportsContentType(QString type)
-{
-    return (type == "audio/x-aiff" ||
-            type == "audio/x-wav" ||
-            type == "audio/mpeg" ||
-            type == "audio/basic" ||
-            type == "audio/x-aac");
-}
-
-bool
-CoreAudioFileReader::supports(FileSource &source)
-{
-    return (supportsExtension(source.getExtension()) ||
-            supportsContentType(source.getContentType()));
-}
-
-#endif
-
--- a/data/fileio/CoreAudioFileReader.h	Wed Jan 09 15:24:38 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006-2012 Chris Cannam and QMUL.
-
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef SV_COREAUDIO_FILE_READER_H
-#define SV_COREAUDIO_FILE_READER_H
-
-#ifdef HAVE_COREAUDIO
-
-#include "CodedAudioFileReader.h"
-
-#include "base/Thread.h"
-
-#include <set>
-
-class ProgressReporter;
-
-class CoreAudioFileReader : public CodedAudioFileReader
-{
-    Q_OBJECT
-
-public:
-    CoreAudioFileReader(FileSource source,
-                        DecodeMode decodeMode,
-                        CacheMode cacheMode,
-                        sv_samplerate_t targetRate = 0,
-                        bool normalised = false,
-                        ProgressReporter *reporter = nullptr);
-    virtual ~CoreAudioFileReader();
-
-    virtual QString getError() const { return m_error; }
-    virtual QString getLocation() const { return m_source.getLocation(); }
-    virtual QString getTitle() const { return m_title; }
-    
-    static void getSupportedExtensions(std::set<QString> &extensions);
-    static bool supportsExtension(QString ext);
-    static bool supportsContentType(QString type);
-    static bool supports(FileSource &source);
-
-    virtual int getDecodeCompletion() const { return m_completion; }
-
-    virtual bool isUpdating() const {
-        return m_decodeThread && m_decodeThread->isRunning();
-    }
-
-public slots:
-    void cancelled();
-
-protected:
-    FileSource m_source;
-    QString m_path;
-    QString m_error;
-    QString m_title;
-
-    class D;
-    D *m_d;
-
-    ProgressReporter *m_reporter;
-    bool m_cancelled;
-    int m_completion;
-
-    class DecodeThread : public Thread
-    {
-    public:
-       // DecodeThread(QuickTimeFileReader *reader) : m_reader(reader) { }
-        virtual void run();
-
-    protected:
-       // QuickTimeFileReader *m_reader;
-    };
-
-    DecodeThread *m_decodeThread;
-};
-
-#endif
-
-#endif
--- a/data/fileio/DecodingWavFileReader.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/DecodingWavFileReader.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -58,6 +58,9 @@
     m_channelCount = m_original->getChannelCount();
     m_fileRate = m_original->getSampleRate();
 
+    m_title = m_original->getTitle();
+    m_maker = m_original->getMaker();
+
     initialiseDecodeCache();
 
     if (decodeMode == DecodeAtOnce) {
--- a/data/fileio/DecodingWavFileReader.h	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/DecodingWavFileReader.h	Wed Jan 30 14:57:12 2019 +0000
@@ -37,8 +37,12 @@
                           ProgressReporter *reporter = 0);
     virtual ~DecodingWavFileReader();
 
+    QString getTitle() const override { return m_title; }
+    QString getMaker() const override { return m_maker; }
+    
     QString getError() const override { return m_error; }
     QString getLocation() const override { return m_source.getLocation(); }
+
     static void getSupportedExtensions(std::set<QString> &extensions);
     static bool supportsExtension(QString ext);
     static bool supportsContentType(QString type);
@@ -55,6 +59,8 @@
 
 protected:
     FileSource m_source;
+    QString m_title;
+    QString m_maker;
     QString m_path;
     QString m_error;
     bool m_cancelled;
--- a/data/fileio/OggVorbisFileReader.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifdef HAVE_OGGZ
-#ifdef HAVE_FISHSOUND
-
-#include "OggVorbisFileReader.h"
-
-#include "base/ProgressReporter.h"
-#include "base/Profiler.h"
-#include "system/System.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <cmath>
-
-#include <QFileInfo>
-
-//static int instances = 0;
-
-OggVorbisFileReader::OggVorbisFileReader(FileSource source,
-                                         DecodeMode decodeMode,
-                                         CacheMode mode,
-                                         sv_samplerate_t targetRate,
-                                         bool normalised,
-                                         ProgressReporter *reporter) :
-    CodedAudioFileReader(mode, targetRate, normalised),
-    m_source(source),
-    m_path(source.getLocalFilename()),
-    m_qfile(nullptr),
-    m_ffile(nullptr),
-    m_oggz(nullptr),
-    m_fishSound(nullptr),
-    m_reporter(reporter),
-    m_fileSize(0),
-    m_bytesRead(0),
-    m_commentsRead(false),
-    m_cancelled(false),
-    m_completion(0),
-    m_decodeThread(nullptr)
-{
-    SVDEBUG << "OggVorbisFileReader: local path: \"" << m_path
-            << "\", decode mode: " << decodeMode << " ("
-            << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded")
-            << ")" << endl;
-
-    m_channelCount = 0;
-    m_fileRate = 0;
-
-//    SVDEBUG << "OggVorbisFileReader::OggVorbisFileReader(" << m_path << "): now have " << (++instances) << " instances" << endl;
-
-    Profiler profiler("OggVorbisFileReader::OggVorbisFileReader");
-
-    // These shenanigans are to avoid using oggz_open(..) with a local
-    // codepage on Windows (make sure proper filename encoding is used)
-    
-    m_qfile = new QFile(m_path);
-    if (!m_qfile->open(QIODevice::ReadOnly)) {
-        m_error = QString("Failed to open file %1 for reading.").arg(m_path);
-        SVDEBUG << "OggVorbisFileReader: " << m_error << endl;
-        delete m_qfile;
-        m_qfile = nullptr;
-        return;
-    }
-    
-    m_fileSize = m_qfile->size();
-
-    m_ffile = fdopen(dup(m_qfile->handle()), "rb");
-    if (!m_ffile) {
-        m_error = QString("Failed to open file pointer for file %1").arg(m_path);
-        SVDEBUG << "OggVorbisFileReader: " << m_error << endl;
-        delete m_qfile;
-        m_qfile = nullptr;
-        return;
-    }
-    
-    if (!(m_oggz = oggz_open_stdio(m_ffile, OGGZ_READ))) {
-        m_error = QString("File %1 is not an OGG file.").arg(m_path);
-        fclose(m_ffile);
-        m_ffile = nullptr;
-        delete m_qfile;
-        m_qfile = nullptr;
-        return;
-    }
-
-    FishSoundInfo fsinfo;
-    m_fishSound = fish_sound_new(FISH_SOUND_DECODE, &fsinfo);
-
-    fish_sound_set_decoded_callback(m_fishSound, acceptFrames, this);
-    oggz_set_read_callback(m_oggz, -1, (OggzReadPacket)readPacket, this);
-
-    if (decodeMode == DecodeAtOnce) {
-
-        if (m_reporter) {
-            connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
-            m_reporter->setMessage
-                (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
-        }
-
-        while (oggz_read(m_oggz, 1024) > 0);
-        
-        fish_sound_delete(m_fishSound);
-        m_fishSound = nullptr;
-        oggz_close(m_oggz);
-        m_oggz = nullptr;
-
-        if (isDecodeCacheInitialised()) finishDecodeCache();
-        endSerialised();
-
-    } else {
-
-        if (m_reporter) m_reporter->setProgress(100);
-
-        while (oggz_read(m_oggz, 1024) > 0 &&
-               (m_channelCount == 0 || m_fileRate == 0 || m_sampleRate == 0));
-
-        if (m_channelCount > 0) {
-            m_decodeThread = new DecodeThread(this);
-            m_decodeThread->start();
-        }
-    }
-}
-
-OggVorbisFileReader::~OggVorbisFileReader()
-{
-//    SVDEBUG << "OggVorbisFileReader::~OggVorbisFileReader(" << m_path << "): now have " << (--instances) << " instances" << endl;
-    if (m_decodeThread) {
-        m_cancelled = true;
-        m_decodeThread->wait();
-        delete m_decodeThread;
-    }
-    if (m_qfile) {
-        // don't fclose m_ffile; oggz_close did that
-        delete m_qfile;
-        m_qfile = nullptr;
-    }
-}
-
-void
-OggVorbisFileReader::cancelled()
-{
-    m_cancelled = true; 
-}
-
-void
-OggVorbisFileReader::DecodeThread::run()
-{
-    if (m_reader->m_cacheMode == CacheInTemporaryFile) {
-        m_reader->m_completion = 1;
-        m_reader->startSerialised("OggVorbisFileReader::Decode");
-    }
-
-    while (oggz_read(m_reader->m_oggz, 1024) > 0);
-        
-    fish_sound_delete(m_reader->m_fishSound);
-    m_reader->m_fishSound = nullptr;
-
-    oggz_close(m_reader->m_oggz);
-    m_reader->m_oggz = nullptr;
-
-    // don't fclose m_ffile; oggz_close did that
-
-    delete m_reader->m_qfile;
-    m_reader->m_qfile = nullptr;
-    
-    if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
-    m_reader->m_completion = 100;
-
-    m_reader->endSerialised();
-} 
-
-int
-OggVorbisFileReader::readPacket(OGGZ *, ogg_packet *packet, long, void *data)
-{
-    OggVorbisFileReader *reader = (OggVorbisFileReader *)data;
-    FishSound *fs = reader->m_fishSound;
-
-    fish_sound_prepare_truncation(fs, packet->granulepos, int(packet->e_o_s));
-    fish_sound_decode(fs, packet->packet, packet->bytes);
-
-    reader->m_bytesRead += packet->bytes;
-
-    // The number of bytes read by this function is smaller than
-    // the file size because of the packet headers
-    int p = int(lrint(double(reader->m_bytesRead) * 114 /
-                      double(reader->m_fileSize)));
-    if (p > 99) p = 99;
-    reader->m_completion = p;
-    reader->progress(p);
-
-    if (reader->m_fileSize > 0 && reader->m_reporter) {
-        reader->m_reporter->setProgress(p);
-    }
-
-    if (reader->m_cancelled) return 1;
-    return 0;
-}
-
-int
-OggVorbisFileReader::acceptFrames(FishSound *fs, float **frames, long nframes,
-                                  void *data)
-{
-    OggVorbisFileReader *reader = (OggVorbisFileReader *)data;
-
-    if (!reader->m_commentsRead) {
-        const FishSoundComment *comment;
-        comment = fish_sound_comment_first_byname(fs, (char *)"TITLE");
-        if (comment && comment->value) {
-            reader->m_title = QString::fromUtf8(comment->value);
-        }
-        comment = fish_sound_comment_first_byname(fs, (char *)"ARTIST");
-        if (comment && comment->value) {
-            reader->m_maker = QString::fromUtf8(comment->value);
-        }
-        comment = fish_sound_comment_first(fs);
-        while (comment) {
-            reader->m_tags[QString::fromUtf8(comment->name).toUpper()] =
-                QString::fromUtf8(comment->value);
-            comment = fish_sound_comment_next(fs, comment);
-        }
-        reader->m_commentsRead = true;
-    }
-
-    if (reader->m_channelCount == 0) {
-        FishSoundInfo fsinfo;
-        fish_sound_command(fs, FISH_SOUND_GET_INFO,
-                           &fsinfo, sizeof(FishSoundInfo));
-        reader->m_fileRate = fsinfo.samplerate;
-        reader->m_channelCount = fsinfo.channels;
-        reader->initialiseDecodeCache();
-    }
-
-    if (nframes > 0) {
-        reader->addSamplesToDecodeCache(frames, nframes);
-    }
-
-    if (reader->m_cancelled) return 1;
-    return 0;
-}
-
-void
-OggVorbisFileReader::getSupportedExtensions(std::set<QString> &extensions)
-{
-    extensions.insert("ogg");
-    extensions.insert("oga");
-}
-
-bool
-OggVorbisFileReader::supportsExtension(QString extension)
-{
-    std::set<QString> extensions;
-    getSupportedExtensions(extensions);
-    return (extensions.find(extension.toLower()) != extensions.end());
-}
-
-bool
-OggVorbisFileReader::supportsContentType(QString type)
-{
-    return (type == "application/ogg");
-}
-
-bool
-OggVorbisFileReader::supports(FileSource &source)
-{
-    return (supportsExtension(source.getExtension()) ||
-            supportsContentType(source.getContentType()));
-}
-
-#endif
-#endif
--- a/data/fileio/OggVorbisFileReader.h	Wed Jan 09 15:24:38 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-    
-    This program is free software; you can redistribute it and/or
-    modify it under the terms of the GNU General Public License as
-    published by the Free Software Foundation; either version 2 of the
-    License, or (at your option) any later version.  See the file
-    COPYING included with this distribution for more information.
-*/
-
-#ifndef SV_OGG_VORBIS_FILE_READER_H
-#define SV_OGG_VORBIS_FILE_READER_H
-
-#ifdef HAVE_OGGZ
-#ifdef HAVE_FISHSOUND
-
-#include "CodedAudioFileReader.h"
-
-#include "base/Thread.h"
-#include <oggz/oggz.h>
-#include <fishsound/fishsound.h>
-
-#include <cstdio>
-
-#include <set>
-
-class ProgressReporter;
-
-class OggVorbisFileReader : public CodedAudioFileReader
-{
-    Q_OBJECT
-
-public:
-    OggVorbisFileReader(FileSource source,
-                        DecodeMode decodeMode,
-                        CacheMode cacheMode,
-                        sv_samplerate_t targetRate = 0,
-                        bool normalised = false,
-                        ProgressReporter *reporter = nullptr);
-    virtual ~OggVorbisFileReader();
-
-    QString getError() const override { return m_error; }
-
-    QString getLocation() const override { return m_source.getLocation(); }
-    QString getTitle() const override { return m_title; }
-    QString getMaker() const override { return m_maker; }
-    TagMap getTags() const override { return m_tags; }
-    
-    static void getSupportedExtensions(std::set<QString> &extensions);
-    static bool supportsExtension(QString ext);
-    static bool supportsContentType(QString type);
-    static bool supports(FileSource &source);
-
-    int getDecodeCompletion() const override { return m_completion; }
-
-    bool isUpdating() const override {
-        return m_decodeThread && m_decodeThread->isRunning();
-    }
-
-public slots:
-    void cancelled();
-
-protected:
-    FileSource m_source;
-    QString m_path;
-    QString m_error;
-    QString m_title;
-    QString m_maker;
-    TagMap m_tags;
-
-    QFile *m_qfile;
-    FILE *m_ffile;
-    OGGZ *m_oggz;
-    FishSound *m_fishSound;
-    ProgressReporter *m_reporter;
-    sv_frame_t m_fileSize;
-    sv_frame_t m_bytesRead;
-    bool m_commentsRead;
-    bool m_cancelled;
-    int m_completion;
- 
-    static int readPacket(OGGZ *, ogg_packet *, long, void *);
-    static int acceptFrames(FishSound *, float **, long, void *);
-
-    class DecodeThread : public Thread
-    {
-    public:
-        DecodeThread(OggVorbisFileReader *reader) : m_reader(reader) { }
-        void run() override;
-
-    protected:
-        OggVorbisFileReader *m_reader; 
-    };
-
-    DecodeThread *m_decodeThread;
-};
-
-#endif
-#endif
-
-#endif
--- a/data/fileio/WavFileReader.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/WavFileReader.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -95,6 +95,15 @@
         if (m_normalisation != Normalisation::None && !m_updating) {
             m_max = getMax();
         }
+        
+        const char *str = sf_get_string(m_file, SF_STR_TITLE);
+        if (str) {
+            m_title = str;
+        }
+        str = sf_get_string(m_file, SF_STR_ARTIST);
+        if (str) {
+            m_maker = str;
+        }
     }
 
     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;
--- a/data/fileio/WavFileReader.h	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/WavFileReader.h	Wed Jan 30 14:57:12 2019 +0000
@@ -51,6 +51,9 @@
     QString getLocation() const override { return m_source.getLocation(); }
     QString getError() const override { return m_error; }
 
+    QString getTitle() const override { return m_title; }
+    QString getMaker() const override { return m_maker; }
+    
     QString getLocalFilename() const override { return m_path; }
     
     bool isQuicklySeekable() const override { return m_seekable; }
@@ -80,6 +83,8 @@
     FileSource m_source;
     QString m_path;
     QString m_error;
+    QString m_title;
+    QString m_maker;
 
     bool m_seekable;
 
--- a/data/fileio/test/AudioFileReaderTest.h	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/test/AudioFileReaderTest.h	Wed Jan 30 14:57:12 2019 +0000
@@ -98,6 +98,11 @@
                 maxLimit = 0.1;
                 rmsLimit = 0.03;
 
+            } else if (format == "opus") {
+
+                maxLimit = 0.06;
+                rmsLimit = 0.015;
+
             } else if (format == "aac") {
 
                 // Terrible performance for this test, load of spill
@@ -109,6 +114,11 @@
                 maxLimit = 0.2;
                 rmsLimit = 0.1;
 
+            } else if (format == "wma") {
+
+                maxLimit = 0.05;
+                rmsLimit = 0.01;
+
             } else if (format == "mp3") {
 
                 if (resampled && !gapless) {
@@ -148,11 +158,21 @@
                 maxLimit = 0.06;
                 rmsLimit = 0.03;
 
+            } else if (format == "opus") {
+
+                maxLimit = 0.06;
+                rmsLimit = 0.015;
+
             } else if (format == "aac") {
 
-                maxLimit = 0.1;
+                maxLimit = 0.2;
                 rmsLimit = 0.1;
 
+            } else if (format == "wma") {
+
+                maxLimit = 0.05;
+                rmsLimit = 0.01;
+
             } else if (format == "mp3") {
 
                 // all mp3 figures are worse when not normalising
@@ -217,9 +237,35 @@
                     for (bool norm: norms) {
                         for (bool gapless: gaplesses) {
 
-                            if (format != "mp3" && !gapless) {
-                                continue;
+#ifdef Q_OS_WIN
+                            if (format == "aac") {
+                                if (gapless) {
+                                    // Apparently no support for AAC
+                                    // encoder delay compensation in
+                                    // MediaFoundation, so these tests
+                                    // are only available non-gapless
+                                    continue;
+                                }
+                            } else if (format != "mp3") {
+                                if (!gapless) {
+                                    // All other formats but mp3 are
+                                    // intrinsically gapless, so we
+                                    // can skip the non-gapless option
+                                    continue;
+                                }
                             }
+#else
+                            if (format != "mp3") {
+                                if (!gapless) {
+                                    // All other formats but mp3 are
+                                    // intrinsically gapless
+                                    // everywhere except for Windows
+                                    // (see above), so we can skip the
+                                    // non-gapless option
+                                    continue;
+                                }                                    
+                            }
+#endif
                         
                             QString desc = testName
                                 (format, filename, rate, norm, gapless);
@@ -295,7 +341,9 @@
 
         bool perceptual = (extension == "mp3" ||
                            extension == "aac" ||
-                           extension == "m4a");
+                           extension == "m4a" ||
+                           extension == "wma" ||
+                           extension == "opus");
         
         if (perceptual && !gapless) {
             // allow silence at start and end
@@ -372,7 +420,11 @@
             // "something else" otherwise.
             
             if (gapless) {
-                if (format == "aac") {
+                if (format == "aac"
+#ifdef Q_OS_WIN
+                    || (format == "mp3" && (readRate != fileRate))
+#endif
+                    ) {
                     // ouch!
                     if (offset == -1) offset = 0;
                 }
--- a/data/fileio/test/EncodingTest.h	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/test/EncodingTest.h	Wed Jan 30 14:57:12 2019 +0000
@@ -46,6 +46,22 @@
 };
 static const int mappingCount = 4;
 
+#ifdef Q_OS_MAC
+// see note in addAudioFiles below
+static const char *testFiles[][2] = {
+    { "id3v2-iso-8859-1", "mp3" },
+    { "id3v2-ucs-2", "mp3" },
+    { utf8_name_tsprk, "flac" },
+    { utf8_name_tsprk, "m4a" },
+    { utf8_name_tsprk, "mp3" },
+    { utf8_name_tsprk, "ogg" },
+    { utf8_name_tsprk, "opus" },
+    { utf8_name_sprkt, "mp3" },
+    { utf8_name_sprkt, "ogg" },
+};
+static const int testFileCount = 8;
+#endif
+
 class EncodingTest : public QObject
 {
     Q_OBJECT
@@ -71,11 +87,26 @@
     }
 
     void addAudioFiles() {
-        QTest::addColumn<QString>("audiofile");
-        QStringList files = QDir(encodingDir).entryList(QDir::Files);
-        foreach (QString filename, files) {
-            QTest::newRow(strOf(filename)) << filename;
-        }
+         QTest::addColumn<QString>("audiofile");
+#ifndef Q_OS_MAC
+         // The normal case - populate the file list from the files
+         // actually present in the encodings directory
+         QStringList files = QDir(encodingDir).entryList(QDir::Files);
+         foreach (QString filename, files) {
+             QTest::newRow(strOf(filename)) << filename;
+         }
+#else
+         // Deviant case for Mac - populate the file list from the
+         // hard-coded list of expected files in testFiles. This is
+         // because QDir::entryList is currently broken on APFS (as of
+         // Qt 5.12) because of variant Unicode normalisations.
+         for (int i = 0; i < testFileCount; ++i) {
+             std::string s = testFiles[i][0];
+             s += ".";
+             s += testFiles[i][1];
+             QTest::newRow(strdup(s.c_str())) << QString::fromStdString(s);
+         }
+#endif
     }
 
 private slots:
@@ -84,7 +115,7 @@
         if (!QDir(encodingDir).exists()) {
             SVCERR << "ERROR: Audio encoding file directory \"" << encodingDir << "\" does not exist" << endl;
             QVERIFY2(QDir(encodingDir).exists(), "Audio encoding file directory not found");
-        }
+         }
         if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) {
             SVCERR << "ERROR: Audio out directory \"" << outDir << "\" does not exist and could not be created" << endl;
             QVERIFY2(QDir(outDir).exists(), "Audio out directory not found and could not be created");
@@ -101,6 +132,15 @@
         
         QFETCH(QString, audiofile);
 
+        if (!AudioFileReaderFactory::isSupported(encodingDir + "/" +
+                                                 audiofile)) {
+#if ( QT_VERSION >= 0x050000 )
+            QSKIP("Known unsupported file, skipping");
+#else
+            QSKIP("Known unsupported file, skipping", SkipSingle);
+#endif
+        }            
+        
         AudioFileReaderFactory::Parameters params;
         AudioFileReader *reader =
             AudioFileReaderFactory::createReader
@@ -128,7 +168,13 @@
             AudioFileReaderFactory::createReader
             (encodingDir + "/" + audiofile, params);
 
-        QVERIFY(reader != nullptr);
+        if (!reader) {
+#if ( QT_VERSION >= 0x050000 )
+            QSKIP("Unsupported file, skipping");
+#else
+            QSKIP("Unsupported file, skipping", SkipSingle);
+#endif
+        }
 
         QStringList fileAndExt = audiofile.split(".");
         QString file = fileAndExt[0];
@@ -142,11 +188,11 @@
 
         } else {
 
-#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND))
-            if (extension == "ogg") {
-                QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata");
-            }
-#endif
+//#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND))
+//            if (extension == "ogg") {
+//                QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata");
+//            }
+//#endif
             
             auto blah = reader->getInterleavedFrames(0, 10);
             
@@ -215,17 +261,24 @@
             return;
         }
 
-#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND))
-        if (extension == "ogg") {
-            QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata");
-        }
-#endif
+//#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND))
+//        if (extension == "ogg") {
+//            QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata");
+//        }
+//#endif
 
         AudioFileReaderFactory::Parameters params;
         AudioFileReader *reader =
             AudioFileReaderFactory::createReader
             (encodingDir + "/" + audiofile, params);
-        QVERIFY(reader != nullptr);
+        
+        if (!reader) {
+#if ( QT_VERSION >= 0x050000 )
+            QSKIP("Unsupported file, skipping");
+#else
+            QSKIP("Unsupported file, skipping", SkipSingle);
+#endif
+        }
 
         QString title = reader->getTitle();
         QVERIFY(title != QString());
Binary file data/fileio/test/audio/opus/48000-2.opus has changed
Binary file data/fileio/test/audio/wma/44100-2.wma has changed
Binary file data/fileio/test/encodings/Tëmple of Spörks.flac has changed
Binary file data/fileio/test/encodings/Tëmple of Spörks.m4a has changed
Binary file data/fileio/test/encodings/Tëmple of Spörks.opus has changed
Binary file data/fileio/test/encodings/Tëmple of Spörks.wma has changed
--- a/data/fileio/test/svcore-data-fileio-test.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/fileio/test/svcore-data-fileio-test.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -19,6 +19,8 @@
 #include "CSVFormatTest.h"
 #include "CSVStreamWriterTest.h"
 
+#include "system/Init.h"
+
 #include <QtTest>
 
 #include <iostream>
@@ -27,6 +29,8 @@
 {
     int good = 0, bad = 0;
 
+    svSystemSpecificInitialisation();
+
     QString testDir;
 
 #ifdef Q_OS_WIN
--- a/data/model/test/svcore-data-model-test.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/data/model/test/svcore-data-model-test.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -15,6 +15,8 @@
 #include "TestZoomConstraints.h"
 #include "TestWaveformOversampler.h"
 
+#include "system/Init.h"
+
 #include <QtTest>
 
 #include <iostream>
@@ -25,6 +27,8 @@
 {
     int good = 0, bad = 0;
 
+    svSystemSpecificInitialisation();
+
     QCoreApplication app(argc, argv);
     app.setOrganizationName("sonic-visualiser");
     app.setApplicationName("test-model");
--- a/files.pri	Wed Jan 09 15:24:38 2019 +0000
+++ b/files.pri	Wed Jan 30 14:57:12 2019 +0000
@@ -47,6 +47,7 @@
            data/fileio/AudioFileReader.h \
            data/fileio/AudioFileReaderFactory.h \
            data/fileio/AudioFileSizeEstimator.h \
+           data/fileio/BQAFileReader.h \
            data/fileio/BZipFileDevice.h \
            data/fileio/CachedFile.h \
            data/fileio/CodedAudioFileReader.h \
@@ -62,9 +63,7 @@
            data/fileio/MIDIFileReader.h \
            data/fileio/MIDIFileWriter.h \
            data/fileio/MP3FileReader.h \
-           data/fileio/OggVorbisFileReader.h \
            data/fileio/PlaylistFileReader.h \
-           data/fileio/CoreAudioFileReader.h \
            data/fileio/DecodingWavFileReader.h \
            data/fileio/WavFileReader.h \
            data/fileio/WavFileWriter.h \
@@ -182,6 +181,7 @@
            data/fileio/AudioFileReader.cpp \
            data/fileio/AudioFileReaderFactory.cpp \
            data/fileio/AudioFileSizeEstimator.cpp \
+           data/fileio/BQAFileReader.cpp \
            data/fileio/BZipFileDevice.cpp \
            data/fileio/CachedFile.cpp \
            data/fileio/CodedAudioFileReader.cpp \
@@ -194,9 +194,7 @@
            data/fileio/MIDIFileReader.cpp \
            data/fileio/MIDIFileWriter.cpp \
            data/fileio/MP3FileReader.cpp \
-           data/fileio/OggVorbisFileReader.cpp \
            data/fileio/PlaylistFileReader.cpp \
-           data/fileio/CoreAudioFileReader.cpp \
            data/fileio/DecodingWavFileReader.cpp \
            data/fileio/WavFileReader.cpp \
            data/fileio/WavFileWriter.cpp \
--- a/plugin/PluginScan.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/plugin/PluginScan.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -31,7 +31,11 @@
 #endif
 {
 protected:
-    void log(std::string message) override {
+    void log(std::string message) 
+#ifdef HAVE_PLUGIN_CHECKER_HELPER
+        override
+#endif
+    {
         SVDEBUG << "PluginScan: " << message << endl;
     }
 };
--- a/system/Init.cpp	Wed Jan 09 15:24:38 2019 +0000
+++ b/system/Init.cpp	Wed Jan 30 14:57:12 2019 +0000
@@ -63,9 +63,11 @@
     // Remove the CWD from the DLL search path, just in case
     SetDllDirectory(L"");
     putenv("PATH=");
+
+    // Some older versions of MinGW require this in order to get
+    // C99/POSIX-standard behaviour for (s)printf formatting
+    putenv("PRINTF_EXPONENT_DIGITS=2");
 #else
 #endif
 }
 
-
-