changeset 1359:1c9bbbb6116a 3.0-integration

Use W64 instead of WAV for decoded files; use Ogg reader in preference to WAV one for Ogg files (WAV reader works, via libsndfile, but doesn't load metadata); fix Ogg reader to use QFile open instead of non-Win32-compatible API; add more encoder tests, audio writer test, midi reader test
author Chris Cannam
date Tue, 10 Jan 2017 10:58:25 +0000
parents b7be05d57f0a
children 8eddb528ef0c
files base/TempWriteFile.cpp base/TempWriteFile.h data/fileio/AudioFileReaderFactory.cpp data/fileio/CodedAudioFileReader.cpp data/fileio/CodedAudioFileReader.h data/fileio/MIDIFileReader.h data/fileio/OggVorbisFileReader.cpp data/fileio/OggVorbisFileReader.h data/fileio/test/AudioFileReaderTest.h data/fileio/test/AudioFileWriterTest.h data/fileio/test/EncodingTest.h data/fileio/test/MIDIFileReaderTest.h data/fileio/test/audio/aac/32000-1.m4a data/fileio/test/audio/aac/44100-2.m4a data/fileio/test/audio/aiff/12000-6-16.aiff data/fileio/test/audio/aiff/48000-1-24.aiff data/fileio/test/audio/apple_lossless/32000-1.m4a data/fileio/test/audio/apple_lossless/44100-2.m4a data/fileio/test/audio/flac/44100-2.flac data/fileio/test/audio/mp3/32000-1.mp3 data/fileio/test/audio/mp3/44100-2.mp3 data/fileio/test/audio/ogg/32000-1.ogg data/fileio/test/audio/ogg/44100-2.ogg data/fileio/test/audio/wav/32000-1-16.wav data/fileio/test/audio/wav/44100-1-32.wav data/fileio/test/audio/wav/44100-2-16.wav data/fileio/test/audio/wav/44100-2-8.wav data/fileio/test/audio/wav/48000-1-16.wav data/fileio/test/audio/wav/8000-1-8.wav data/fileio/test/audio/wav/8000-2-16.wav data/fileio/test/audio/wav/8000-6-16.wav data/fileio/test/files.pri data/fileio/test/midi/scale.mid data/fileio/test/midi/아브라카다브라.mid data/fileio/test/svcore-data-fileio-test.cpp data/fileio/test/testfiles/aac/32000-1.m4a data/fileio/test/testfiles/aac/44100-2.m4a data/fileio/test/testfiles/aiff/12000-6-16.aiff data/fileio/test/testfiles/aiff/48000-1-24.aiff data/fileio/test/testfiles/apple_lossless/32000-1.m4a data/fileio/test/testfiles/apple_lossless/44100-2.m4a data/fileio/test/testfiles/flac/44100-2.flac data/fileio/test/testfiles/mp3/32000-1.mp3 data/fileio/test/testfiles/mp3/44100-2.mp3 data/fileio/test/testfiles/ogg/32000-1.ogg data/fileio/test/testfiles/ogg/44100-2.ogg data/fileio/test/testfiles/wav/32000-1-16.wav data/fileio/test/testfiles/wav/44100-1-32.wav data/fileio/test/testfiles/wav/44100-2-16.wav data/fileio/test/testfiles/wav/44100-2-8.wav data/fileio/test/testfiles/wav/48000-1-16.wav data/fileio/test/testfiles/wav/8000-1-8.wav data/fileio/test/testfiles/wav/8000-2-16.wav data/fileio/test/testfiles/wav/8000-6-16.wav plugin/plugins/SamplePlayer.cpp
diffstat 55 files changed, 473 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/base/TempWriteFile.cpp	Mon Jan 09 18:51:42 2017 +0000
+++ b/base/TempWriteFile.cpp	Tue Jan 10 10:58:25 2017 +0000
@@ -27,7 +27,7 @@
     temp.setAutoRemove(false);
     temp.open(); // creates the file and opens it atomically
     if (temp.error()) {
-	cerr << "TempWriteFile: Failed to create temporary file in directory of " << m_target << ": " << temp.errorString() << endl;
+	SVCERR << "TempWriteFile: Failed to create temporary file in directory of " << m_target << ": " << temp.errorString() << endl;
 	throw FileOperationFailed(temp.fileName(), "creation");
     }
     
@@ -54,13 +54,17 @@
 {
     if (m_temp == "") return;
 
-    QDir dir(QFileInfo(m_temp).dir());
-    // According to  http://doc.trolltech.com/4.4/qdir.html#rename
-    // some systems fail, if renaming over an existing file.
-    // Therefore, delete first the existing file.
-    if (dir.exists(m_target)) dir.remove(m_target);
-    if (!dir.rename(m_temp, m_target)) {
-	cerr << "TempWriteFile: Failed to rename temporary file " << m_temp << " to target " << m_target << endl;
+    QFile tempFile(m_temp);
+    QFile targetFile(m_target);
+    
+    if (targetFile.exists()) {
+        if (!targetFile.remove()) {
+            SVCERR << "TempWriteFile: WARNING: Failed to remove existing target file " << m_target << " prior to moving temporary file " << m_temp << " to it" << endl;
+        }
+    }
+    
+    if (!tempFile.rename(m_target)) {
+        SVCERR << "TempWriteFile: Failed to rename temporary file " << m_temp << " to target " << m_target << endl;
 	throw FileOperationFailed(m_temp, "rename");
     }
 
--- a/base/TempWriteFile.h	Mon Jan 09 18:51:42 2017 +0000
+++ b/base/TempWriteFile.h	Tue Jan 10 10:58:25 2017 +0000
@@ -12,8 +12,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _TEMP_WRITE_FILE_H_
-#define _TEMP_WRITE_FILE_H_
+#ifndef SV_TEMP_WRITE_FILE_H
+#define SV_TEMP_WRITE_FILE_H
 
 #include <QTemporaryFile>
 
@@ -23,7 +23,6 @@
  * use when saving a file over an existing one, to avoid clobbering
  * the original before the save is complete.
  */
-
 class TempWriteFile
 {
 public:
--- a/data/fileio/AudioFileReaderFactory.cpp	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/AudioFileReaderFactory.cpp	Tue Jan 10 10:58:25 2017 +0000
@@ -126,6 +126,27 @@
             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);
+
+            if (reader->isOK()) {
+                SVDEBUG << "AudioFileReaderFactory: Ogg file reader is OK, returning it" << endl;
+                return reader;
+            } else {
+                delete reader;
+            }
+        }
+#endif
+#endif
 
         if (anyReader || WavFileReader::supports(source)) {
 
@@ -157,23 +178,6 @@
                 delete reader;
             }
         }
-    
-#ifdef HAVE_OGGZ
-#ifdef HAVE_FISHSOUND
-        if (anyReader || OggVorbisFileReader::supports(source)) {
-
-            reader = new OggVorbisFileReader
-                (source, decodeMode, cacheMode, targetRate, normalised, reporter);
-
-            if (reader->isOK()) {
-                SVDEBUG << "AudioFileReaderFactory: Ogg file reader is OK, returning it" << endl;
-                return reader;
-            } else {
-                delete reader;
-            }
-        }
-#endif
-#endif
 
 #ifdef HAVE_MAD
         if (anyReader || MP3FileReader::supports(source)) {
--- a/data/fileio/CodedAudioFileReader.cpp	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/CodedAudioFileReader.cpp	Tue Jan 10 10:58:25 2017 +0000
@@ -164,7 +164,7 @@
 
         try {
             QDir dir(TempDirectory::getInstance()->getPath());
-            m_cacheFileName = dir.filePath(QString("decoded_%1.wav")
+            m_cacheFileName = dir.filePath(QString("decoded_%1.w64")
                                            .arg((intptr_t)this));
 
             SF_INFO fileInfo;
@@ -196,10 +196,15 @@
             // tests.)
             //
             // So: now we write floats.
-            fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
-    
-            m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
-                                          SFM_WRITE, &fileInfo);
+            fileInfo.format = SF_FORMAT_W64 | SF_FORMAT_FLOAT;
+
+#ifdef Q_OS_WIN
+            m_cacheFileWritePtr = sf_wchar_open
+                ((LPCWSTR)m_cacheFileName.utf16(), SFM_WRITE, &m_fileInfo);
+#else
+            m_cacheFileWritePtr = sf_open
+                (m_cacheFileName.toLocal8Bit(), SFM_WRITE, &fileInfo);
+#endif
 
             if (m_cacheFileWritePtr) {
 
--- a/data/fileio/CodedAudioFileReader.h	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/CodedAudioFileReader.h	Tue Jan 10 10:58:25 2017 +0000
@@ -13,15 +13,21 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _CODED_AUDIO_FILE_READER_H_
-#define _CODED_AUDIO_FILE_READER_H_
+#ifndef SV_CODED_AUDIO_FILE_READER_H
+#define SV_CODED_AUDIO_FILE_READER_H
 
 #include "AudioFileReader.h"
 
-#include <sndfile.h>
 #include <QMutex>
 #include <QReadWriteLock>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
+#endif
+
+#include <sndfile.h>
+
 class WavFileReader;
 class Serialiser;
 
--- a/data/fileio/MIDIFileReader.h	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/MIDIFileReader.h	Tue Jan 10 10:58:25 2017 +0000
@@ -12,15 +12,14 @@
     COPYING included with this distribution for more information.
 */
 
-
 /*
    This is a modified version of a source file from the 
    Rosegarden MIDI and audio sequencer and notation editor.
    This file copyright 2000-2006 Richard Bown and Chris Cannam.
 */
 
-#ifndef _MIDI_FILE_READER_H_
-#define _MIDI_FILE_READER_H_
+#ifndef SV_MIDI_FILE_READER_H
+#define SV_MIDI_FILE_READER_H
 
 #include "DataFileReader.h"
 #include "base/RealTime.h"
@@ -61,7 +60,7 @@
 
 public:
     MIDIFileReader(QString path,
-                   MIDIFileImportPreferenceAcquirer *pref,
+                   MIDIFileImportPreferenceAcquirer *pref, // may be null
                    sv_samplerate_t mainModelSampleRate);
     virtual ~MIDIFileReader();
 
--- a/data/fileio/OggVorbisFileReader.cpp	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/OggVorbisFileReader.cpp	Tue Jan 10 10:58:25 2017 +0000
@@ -40,6 +40,10 @@
     CodedAudioFileReader(mode, targetRate, normalised),
     m_source(source),
     m_path(source.getLocalFilename()),
+    m_qfile(0),
+    m_ffile(0),
+    m_oggz(0),
+    m_fishSound(0),
     m_reporter(reporter),
     m_fileSize(0),
     m_bytesRead(0),
@@ -60,11 +64,35 @@
 
     Profiler profiler("OggVorbisFileReader::OggVorbisFileReader");
 
-    QFileInfo info(m_path);
-    m_fileSize = info.size();
+    // 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 = 0;
+        return;
+    }
+    
+    m_fileSize = m_qfile->size();
 
-    if (!(m_oggz = oggz_open(m_path.toLocal8Bit().data(), OGGZ_READ))) {
+    m_ffile = fdopen(dup(m_qfile->handle()), "r");
+    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 = 0;
+        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 = 0;
+        delete m_qfile;
+        m_qfile = 0;
         return;
     }
 
@@ -114,6 +142,11 @@
         m_decodeThread->wait();
         delete m_decodeThread;
     }
+    if (m_qfile) {
+        // don't fclose m_ffile; oggz_close did that
+        delete m_qfile;
+        m_qfile = 0;
+    }
 }
 
 void
@@ -134,8 +167,14 @@
         
     fish_sound_delete(m_reader->m_fishSound);
     m_reader->m_fishSound = 0;
+
     oggz_close(m_reader->m_oggz);
     m_reader->m_oggz = 0;
+
+    // don't fclose m_ffile; oggz_close did that
+
+    delete m_reader->m_qfile;
+    m_reader->m_qfile = 0;
     
     if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
     m_reader->m_completion = 100;
--- a/data/fileio/OggVorbisFileReader.h	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/OggVorbisFileReader.h	Tue Jan 10 10:58:25 2017 +0000
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _OGG_VORBIS_FILE_READER_H_
-#define _OGG_VORBIS_FILE_READER_H_
+#ifndef SV_OGG_VORBIS_FILE_READER_H
+#define SV_OGG_VORBIS_FILE_READER_H
 
 #ifdef HAVE_OGGZ
 #ifdef HAVE_FISHSOUND
@@ -25,6 +25,8 @@
 #include <oggz/oggz.h>
 #include <fishsound/fishsound.h>
 
+#include <cstdio>
+
 #include <set>
 
 class ProgressReporter;
@@ -71,6 +73,8 @@
     QString m_maker;
     TagMap m_tags;
 
+    QFile *m_qfile;
+    FILE *m_ffile;
     OGGZ *m_oggz;
     FishSound *m_fishSound;
     ProgressReporter *m_reporter;
--- a/data/fileio/test/AudioFileReaderTest.h	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/test/AudioFileReaderTest.h	Tue Jan 10 10:58:25 2017 +0000
@@ -47,7 +47,7 @@
             base = "svcore/data/fileio/test";
         }
         testDirBase = base;
-        audioDir = base + "/testfiles";
+        audioDir = base + "/audio";
         diffDir = base + "/diffs";
     }
 
@@ -389,7 +389,7 @@
             diffFile += ".wav";
             diffFile = QDir(diffDir).filePath(diffFile);
             WavFileWriter diffWriter(diffFile, readRate, channels,
-                                     WavFileWriter::WriteToTarget); //!!! NB WriteToTemporary not working, why?
+                                     WavFileWriter::WriteToTemporary);
             QVERIFY(diffWriter.isOK());
 
             vector<vector<float>> diffs(channels);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/AudioFileWriterTest.h	Tue Jan 10 10:58:25 2017 +0000
@@ -0,0 +1,135 @@
+/* -*- 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 TEST_AUDIO_FILE_WRITER_H
+#define TEST_AUDIO_FILE_WRITER_H
+
+#include "../AudioFileReaderFactory.h"
+#include "../AudioFileReader.h"
+#include "../WavFileWriter.h"
+
+#include "AudioTestData.h"
+
+#include "bqvec/VectorOps.h"
+#include "bqvec/Allocators.h"
+
+#include <cmath>
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include <iostream>
+
+using namespace std;
+using namespace breakfastquay;
+
+class AudioFileWriterTest : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString testDirBase;
+    QString outDir;
+
+    static const int rate = 44100;
+    
+public:
+    AudioFileWriterTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        testDirBase = base;
+        outDir = base + "/outfiles";
+    }
+
+    const char *strOf(QString s) {
+        return strdup(s.toLocal8Bit().data());
+    }
+    
+    QString testName(bool direct, int channels) {
+        return QString("%1 %2 %3")
+            .arg(channels)
+            .arg(channels > 1 ? "channels" : "channel")
+            .arg(direct ? "direct" : "via temporary");
+    }
+
+private slots:
+    void init()
+    {
+        if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) {
+            cerr << "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");
+        }
+    }
+
+    void write_data()
+    {
+        QTest::addColumn<bool>("direct");
+        QTest::addColumn<int>("channels");
+        for (int direct = 0; direct <= 1; ++direct) {
+            for (int channels = 1; channels < 8; ++channels) {
+                if (channels == 1 || channels == 2 ||
+                    channels == 5 || channels == 8) {
+                    QString desc = testName(direct, channels);
+                    QTest::newRow(strOf(desc)) << (bool)direct << channels;
+                }
+            }
+        }
+    }
+    
+    void write()
+    {
+        QFETCH(bool, direct);
+        QFETCH(int, channels);
+
+        QString outfile = QString("%1/out-%2ch-%3.wav")
+            .arg(outDir).arg(channels).arg(direct ? "direct" : "via-temporary");
+        
+        WavFileWriter writer(outfile,
+                             rate,
+                             channels,
+                             direct ?
+                             WavFileWriter::WriteToTarget :
+                             WavFileWriter::WriteToTemporary);
+        QVERIFY(writer.isOK());
+
+        AudioTestData data(rate, channels);
+        data.generate();
+
+        sv_frame_t frameCount = data.getFrameCount();
+        float *interleaved = data.getInterleavedData();
+        float **nonInterleaved = allocate_channels<float>(channels, frameCount);
+        v_deinterleave(nonInterleaved, interleaved, channels, int(frameCount));
+        bool ok = writer.writeSamples(nonInterleaved, frameCount);
+        deallocate_channels(nonInterleaved, channels);
+        QVERIFY(ok);
+        
+        ok = writer.close();
+        QVERIFY(ok);
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *rereader =
+            AudioFileReaderFactory::createReader(outfile, params);
+        QVERIFY(rereader != nullptr);
+        
+        floatvec_t readFrames = rereader->getInterleavedFrames(0, frameCount);
+        floatvec_t expected(interleaved, interleaved + frameCount * channels);
+        QCOMPARE(readFrames, expected);
+
+        delete rereader;
+    }
+};
+
+#endif
--- a/data/fileio/test/EncodingTest.h	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/test/EncodingTest.h	Tue Jan 10 10:58:25 2017 +0000
@@ -20,6 +20,7 @@
 
 #include "../AudioFileReaderFactory.h"
 #include "../AudioFileReader.h"
+#include "../WavFileWriter.h"
 
 #include <cmath>
 
@@ -36,6 +37,7 @@
 const char utf8_name_tsprk[] = "T\303\253mple of Sp\303\266rks";
 const char utf8_name_sprkt[] = "\343\202\271\343\203\235\343\203\274\343\202\257\343\201\256\345\257\272\351\231\242";
 
+// Mapping between filename and expected title metadata field
 static const char *mapping[][2] = {
     { "id3v2-iso-8859-1", utf8_name_cdp_1 },
     { "id3v2-ucs-2", utf8_name_cdp_2 },
@@ -51,6 +53,7 @@
 private:
     QString testDirBase;
     QString encodingDir;
+    QString outDir;
 
 public:
     EncodingTest(QString base) {
@@ -59,6 +62,7 @@
         }
         testDirBase = base;
         encodingDir = base + "/encodings";
+        outDir = base + "/outfiles";
     }
 
 private:
@@ -66,6 +70,14 @@
         return strdup(s.toLocal8Bit().data());
     }
 
+    void addAudioFiles() {
+        QTest::addColumn<QString>("audiofile");
+        QStringList files = QDir(encodingDir).entryList(QDir::Files);
+        foreach (QString filename, files) {
+            QTest::newRow(strOf(filename)) << filename;
+        }
+    }
+
 private slots:
     void init()
     {
@@ -73,19 +85,40 @@
             cerr << "ERROR: Audio encoding file directory \"" << encodingDir << "\" does not exist" << endl;
             QVERIFY2(QDir(encodingDir).exists(), "Audio encoding file directory not found");
         }
-    }
-
-    void read_data()
-    {
-        QTest::addColumn<QString>("audiofile");
-        QStringList files = QDir(encodingDir).entryList(QDir::Files);
-        foreach (QString filename, files) {
-            QTest::newRow(strOf(filename)) << filename;
+        if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) {
+            cerr << "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");
         }
     }
 
-    void read()
-    {
+    void readAudio_data() {
+        addAudioFiles();
+    }
+
+    void readAudio() {
+
+        // Ensure that we can open all the files
+        
+        QFETCH(QString, audiofile);
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *reader =
+            AudioFileReaderFactory::createReader
+            (encodingDir + "/" + audiofile, params);
+
+        QVERIFY(reader != nullptr);
+    }
+
+    void readMetadata_data() {
+        addAudioFiles();
+    }
+    
+    void readMetadata() {
+        
+        // All files other than WAVs should have title metadata; check
+        // that the title matches whatever is in our mapping structure
+        // defined at the top
+        
         QFETCH(QString, audiofile);
 
         AudioFileReaderFactory::Parameters params;
@@ -99,8 +132,10 @@
         QString file = fileAndExt[0];
         QString extension = fileAndExt[1];
 
-        if (extension == "mp3") {
+        if (extension != "wav") {
 
+            auto blah = reader->getInterleavedFrames(0, 10);
+            
             QString title = reader->getTitle();
             QVERIFY(title != QString());
 
@@ -128,12 +163,83 @@
             }
 
             if (!found) {
+                // Note that this can happen legitimately on Windows,
+                // where (for annoying VCS-related reasons) the test
+                // files may have a different filename encoding from
+                // the expected UTF-16. We check this properly in
+                // readWriteAudio below, by saving out the file to a
+                // name matching the metadata
                 cerr << "Couldn't find filename \""
                      << file << "\" in title mapping array" << endl;
                 QSKIP("Couldn't find filename in title mapping array");
             }
         }
     }
+
+    void readWriteAudio_data() {
+        addAudioFiles();
+    }
+
+    void readWriteAudio()
+    {
+        // For those files that have title metadata (i.e. all of them
+        // except the WAVs), read the title metadata and write a wav
+        // file (of arbitrary content) whose name matches that.  Then
+        // check that we can re-read it. This is intended to exercise
+        // systems on which the original test filename is miscoded (as
+        // can happen on Windows).
+        
+        QFETCH(QString, audiofile);
+
+        QStringList fileAndExt = audiofile.split(".");
+        QString file = fileAndExt[0];
+        QString extension = fileAndExt[1];
+
+        if (extension == "wav") {
+            return;
+        }
+
+        AudioFileReaderFactory::Parameters params;
+        AudioFileReader *reader =
+            AudioFileReaderFactory::createReader
+            (encodingDir + "/" + audiofile, params);
+        QVERIFY(reader != nullptr);
+
+        QString title = reader->getTitle();
+        QVERIFY(title != QString());
+
+        for (int useTemporary = 0; useTemporary <= 1; ++useTemporary) {
+        
+            QString outfile = outDir + "/" + file + ".wav";
+            WavFileWriter writer(outfile,
+                                 reader->getSampleRate(),
+                                 1,
+                                 useTemporary ?
+                                 WavFileWriter::WriteToTemporary :
+                                 WavFileWriter::WriteToTarget);
+
+            QVERIFY(writer.isOK());
+
+            floatvec_t data { 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0 };
+            const float *samples = data.data();
+            bool ok = writer.writeSamples(&samples, 8);
+            QVERIFY(ok);
+
+            ok = writer.close();
+            QVERIFY(ok);
+
+            AudioFileReader *rereader =
+                AudioFileReaderFactory::createReader(outfile, params);
+            QVERIFY(rereader != nullptr);
+
+            floatvec_t readFrames = rereader->getInterleavedFrames(0, 8);
+            QCOMPARE(readFrames, data);
+
+            delete rereader;
+        }
+
+        delete reader;
+    }
 };
 
 #endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/fileio/test/MIDIFileReaderTest.h	Tue Jan 10 10:58:25 2017 +0000
@@ -0,0 +1,88 @@
+/* -*- 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 2013 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 TEST_MIDI_FILE_READER_H
+#define TEST_MIDI_FILE_READER_H
+
+#include "../MIDIFileReader.h"
+
+#include <cmath>
+
+#include <QObject>
+#include <QtTest>
+#include <QDir>
+
+#include "base/Debug.h"
+
+#include <iostream>
+
+using namespace std;
+
+class MIDIFileReaderTest : public QObject
+{
+    Q_OBJECT
+
+private:
+    QString testDirBase;
+    QString midiDir;
+
+    const char *strOf(QString s) {
+        return strdup(s.toLocal8Bit().data());
+    }
+
+public:
+    MIDIFileReaderTest(QString base) {
+        if (base == "") {
+            base = "svcore/data/fileio/test";
+        }
+        testDirBase = base;
+        midiDir = base + "/midi";
+    }
+
+private slots:
+    void init()
+    {
+        if (!QDir(midiDir).exists()) {
+            cerr << "ERROR: MIDI file directory \"" << midiDir << "\" does not exist" << endl;
+            QVERIFY2(QDir(midiDir).exists(), "MIDI file directory not found");
+        }
+    }
+
+    void read_data()
+    {
+        QTest::addColumn<QString>("filename");
+        QStringList files = QDir(midiDir).entryList(QDir::Files);
+        foreach (QString filename, files) {
+            QTest::newRow(strOf(filename)) << filename;
+        }
+    }
+    
+    void read()
+    {
+        QFETCH(QString, filename);
+        QString path = midiDir + "/" + filename;
+        MIDIFileReader reader(path, nullptr, 44100);
+        Model *m = reader.load();
+        if (!m) {
+            cerr << "MIDI load failed for path: \"" << path << "\"" << endl;
+        }
+        QVERIFY(m != nullptr);
+        //!!! Ah, now here we could do something a bit more informative
+    }
+
+};
+
+#endif
+
Binary file data/fileio/test/audio/aac/32000-1.m4a has changed
Binary file data/fileio/test/audio/aac/44100-2.m4a has changed
Binary file data/fileio/test/audio/aiff/12000-6-16.aiff has changed
Binary file data/fileio/test/audio/aiff/48000-1-24.aiff has changed
Binary file data/fileio/test/audio/apple_lossless/32000-1.m4a has changed
Binary file data/fileio/test/audio/apple_lossless/44100-2.m4a has changed
Binary file data/fileio/test/audio/flac/44100-2.flac has changed
Binary file data/fileio/test/audio/mp3/32000-1.mp3 has changed
Binary file data/fileio/test/audio/mp3/44100-2.mp3 has changed
Binary file data/fileio/test/audio/ogg/32000-1.ogg has changed
Binary file data/fileio/test/audio/ogg/44100-2.ogg has changed
Binary file data/fileio/test/audio/wav/32000-1-16.wav has changed
Binary file data/fileio/test/audio/wav/44100-1-32.wav has changed
Binary file data/fileio/test/audio/wav/44100-2-16.wav has changed
Binary file data/fileio/test/audio/wav/44100-2-8.wav has changed
Binary file data/fileio/test/audio/wav/48000-1-16.wav has changed
Binary file data/fileio/test/audio/wav/8000-1-8.wav has changed
Binary file data/fileio/test/audio/wav/8000-2-16.wav has changed
Binary file data/fileio/test/audio/wav/8000-6-16.wav has changed
--- a/data/fileio/test/files.pri	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/test/files.pri	Tue Jan 10 10:58:25 2017 +0000
@@ -1,8 +1,10 @@
 
 TEST_HEADERS += \
 	     AudioFileReaderTest.h \
+	     AudioFileWriterTest.h \
 	     AudioTestData.h \
-             EncodingTest.h
+             EncodingTest.h \
+             MIDIFileReaderTest.h
 	     
 TEST_SOURCES += \
 	     svcore-data-fileio-test.cpp
Binary file data/fileio/test/midi/scale.mid has changed
Binary file data/fileio/test/midi/아브라카다브라.mid has changed
--- a/data/fileio/test/svcore-data-fileio-test.cpp	Mon Jan 09 18:51:42 2017 +0000
+++ b/data/fileio/test/svcore-data-fileio-test.cpp	Tue Jan 10 10:58:25 2017 +0000
@@ -13,7 +13,9 @@
 */
 
 #include "AudioFileReaderTest.h"
+#include "AudioFileWriterTest.h"
 #include "EncodingTest.h"
+#include "MIDIFileReaderTest.h"
 
 #include <QtTest>
 
@@ -51,11 +53,23 @@
     }
 
     {
+        AudioFileWriterTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
+    {
         EncodingTest t(testDir);
         if (QTest::qExec(&t, argc, argv) == 0) ++good;
         else ++bad;
     }
 
+    {
+        MIDIFileReaderTest t(testDir);
+        if (QTest::qExec(&t, argc, argv) == 0) ++good;
+        else ++bad;
+    }
+
     if (bad > 0) {
 	cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl;
 	return 1;
Binary file data/fileio/test/testfiles/aac/32000-1.m4a has changed
Binary file data/fileio/test/testfiles/aac/44100-2.m4a has changed
Binary file data/fileio/test/testfiles/aiff/12000-6-16.aiff has changed
Binary file data/fileio/test/testfiles/aiff/48000-1-24.aiff has changed
Binary file data/fileio/test/testfiles/apple_lossless/32000-1.m4a has changed
Binary file data/fileio/test/testfiles/apple_lossless/44100-2.m4a has changed
Binary file data/fileio/test/testfiles/flac/44100-2.flac has changed
Binary file data/fileio/test/testfiles/mp3/32000-1.mp3 has changed
Binary file data/fileio/test/testfiles/mp3/44100-2.mp3 has changed
Binary file data/fileio/test/testfiles/ogg/32000-1.ogg has changed
Binary file data/fileio/test/testfiles/ogg/44100-2.ogg has changed
Binary file data/fileio/test/testfiles/wav/32000-1-16.wav has changed
Binary file data/fileio/test/testfiles/wav/44100-1-32.wav has changed
Binary file data/fileio/test/testfiles/wav/44100-2-16.wav has changed
Binary file data/fileio/test/testfiles/wav/44100-2-8.wav has changed
Binary file data/fileio/test/testfiles/wav/48000-1-16.wav has changed
Binary file data/fileio/test/testfiles/wav/8000-1-8.wav has changed
Binary file data/fileio/test/testfiles/wav/8000-2-16.wav has changed
Binary file data/fileio/test/testfiles/wav/8000-6-16.wav has changed
--- a/plugin/plugins/SamplePlayer.cpp	Mon Jan 09 18:51:42 2017 +0000
+++ b/plugin/plugins/SamplePlayer.cpp	Tue Jan 10 10:58:25 2017 +0000
@@ -29,6 +29,11 @@
 #include <QDir>
 #include <QFileInfo>
 
+#ifdef Q_OS_WIN
+#include <windows.h>
+#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1
+#endif
+
 #include <sndfile.h>
 #include <samplerate.h>
 #include <iostream>
@@ -395,7 +400,11 @@
     size_t i;
 
     info.format = 0;
+#ifdef Q_OS_WIN
+    file = sf_wchar_open((LPCWSTR)path.utf16(), SFM_READ, &m_fileInfo);
+#else
     file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info);
+#endif
     if (!file) {
 	cerr << "SamplePlayer::loadSampleData: Failed to open file "
 		  << path << ": "