Mercurial > hg > svcore
changeset 1365:3382d914e110
Merge from branch 3.0-integration
author | Chris Cannam |
---|---|
date | Fri, 13 Jan 2017 10:29:44 +0000 |
parents | 6a7ea3bd0e10 (current diff) b812df0351d9 (diff) |
children | bbc4e4ee15d5 |
files | base/RealTime.cpp base/Resampler.cpp base/Resampler.h base/ResizeableBitset.h base/test/TestRealTime.h base/test/main.cpp base/test/test.pro data/fft/FFTCacheReader.h data/fft/FFTCacheStorageType.h data/fft/FFTCacheWriter.h data/fft/FFTDataServer.cpp data/fft/FFTDataServer.h data/fft/FFTFileCacheReader.cpp data/fft/FFTFileCacheReader.h data/fft/FFTFileCacheWriter.cpp data/fft/FFTFileCacheWriter.h data/fft/FFTMemoryCache.cpp data/fft/FFTMemoryCache.h data/fft/FFTapi.cpp data/fft/FFTapi.h data/fileio/MatrixFile.cpp data/fileio/MatrixFile.h data/fileio/QuickTimeFileReader.cpp data/fileio/QuickTimeFileReader.h data/fileio/test/main.cpp data/fileio/test/test.pro data/fileio/test/testfiles/12000-6-16.aiff data/fileio/test/testfiles/32000-1-16.wav data/fileio/test/testfiles/32000-1.m4a data/fileio/test/testfiles/32000-1.mp3 data/fileio/test/testfiles/32000-1.ogg data/fileio/test/testfiles/44100-1-32.wav data/fileio/test/testfiles/44100-2-16.wav data/fileio/test/testfiles/44100-2-8.wav data/fileio/test/testfiles/44100-2.flac data/fileio/test/testfiles/44100-2.m4a data/fileio/test/testfiles/44100-2.mp3 data/fileio/test/testfiles/44100-2.ogg data/fileio/test/testfiles/48000-1-16.wav data/fileio/test/testfiles/48000-1-24.aiff data/fileio/test/testfiles/8000-1-8.wav data/fileio/test/testfiles/8000-2-16.wav data/fileio/test/testfiles/8000-6-16.wav data/model/test/main.cpp data/model/test/test.pro |
diffstat | 209 files changed, 8881 insertions(+), 9617 deletions(-) [+] |
line wrap: on
line diff
--- a/base/AudioPlaySource.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/AudioPlaySource.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _AUDIO_PLAY_SOURCE_H_ -#define _AUDIO_PLAY_SOURCE_H_ +#ifndef SV_AUDIO_PLAY_SOURCE_H +#define SV_AUDIO_PLAY_SOURCE_H #include "BaseTypes.h" @@ -59,7 +59,9 @@ /** * Return the current (or thereabouts) output levels in the range - * 0.0 -> 1.0, for metering purposes. + * 0.0 -> 1.0, for metering purposes. The values returned are + * peak values since the last call to this function was made + * (i.e. calling this function also resets them). */ virtual bool getOutputLevels(float &left, float &right) = 0; @@ -70,11 +72,16 @@ virtual sv_samplerate_t getSourceSampleRate() const = 0; /** - * Return the sample rate set by the target audio device (or the - * source sample rate if the target hasn't set one). If the - * source and target sample rates differ, resampling will occur. + * Return the sample rate set by the target audio device (or 0 if + * the target hasn't told us yet). If the source and target + * sample rates differ, resampling will occur. + * + * Note that we don't actually do any processing at the device + * sample rate. All processing happens at the source sample rate, + * and then a resampler is applied if necessary at the interface + * between application and driver layer. */ - virtual sv_samplerate_t getTargetSampleRate() const = 0; + virtual sv_samplerate_t getDeviceSampleRate() const = 0; /** * Get the block size of the target audio device. This may be an
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/AudioRecordTarget.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,50 @@ +/* -*- 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_AUDIO_RECORD_TARGET_H +#define SV_AUDIO_RECORD_TARGET_H + +#include "BaseTypes.h" + +/** + * The record target API used by the view manager. See also AudioPlaySource. + */ +class AudioRecordTarget +{ +public: + virtual ~AudioRecordTarget() { } + + /** + * Return whether recording is currently happening. + */ + virtual bool isRecording() const = 0; + + /** + * Return the approximate duration of the audio recording so far. + */ + virtual sv_frame_t getRecordDuration() const = 0; + + /** + * Return the current (or thereabouts) input levels in the range + * 0.0 -> 1.0, for metering purposes. Only valid while recording. + * The values returned are peak values since the last call to this + * function was made (i.e. calling this function also resets them). + */ + virtual bool getInputLevels(float &left, float &right) = 0; +}; + +#endif + + +
--- a/base/BaseTypes.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/BaseTypes.h Fri Jan 13 10:29:44 2017 +0000 @@ -16,6 +16,10 @@ #define BASE_TYPES_H #include <cstdint> +#include <complex> +#include <vector> + +#include <bqvec/Allocators.h> /** Frame index, the unit of our time axis. This is signed because the axis conceptually extends below zero: zero represents the start of @@ -46,5 +50,10 @@ */ typedef double sv_samplerate_t; +typedef std::vector<float, breakfastquay::StlAllocator<float>> floatvec_t; + +typedef std::vector<std::complex<float>, + breakfastquay::StlAllocator<std::complex<float>>> complexvec_t; + #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ColumnOp.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,185 @@ +/* -*- 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-2016 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. +*/ + +#include "ColumnOp.h" + +#include <cmath> +#include <algorithm> +#include <iostream> + +#include "base/Debug.h" + +using namespace std; + +ColumnOp::Column +ColumnOp::fftScale(const Column &in, int fftSize) +{ + return applyGain(in, 2.0 / fftSize); +} + +ColumnOp::Column +ColumnOp::peakPick(const Column &in) +{ + vector<float> out(in.size(), 0.f); + + for (int i = 0; in_range_for(in, i); ++i) { + if (isPeak(in, i)) { + out[i] = in[i]; + } + } + + return out; +} + +ColumnOp::Column +ColumnOp::normalize(const Column &in, ColumnNormalization n) { + + if (n == ColumnNormalization::None || in.empty()) { + return in; + } + + float scale = 1.f; + + if (n == ColumnNormalization::Sum1) { + + float sum = 0.f; + + for (auto v: in) { + sum += fabsf(v); + } + + if (sum != 0.f) { + scale = 1.f / sum; + } + + } else { + + float max = 0.f; + + for (auto v: in) { + v = fabsf(v); + if (v > max) { + max = v; + } + } + + if (n == ColumnNormalization::Max1) { + if (max != 0.f) { + scale = 1.f / max; + } + } else if (n == ColumnNormalization::Hybrid) { + if (max > 0.f) { + scale = log10f(max + 1.f) / max; + } + } + } + + return applyGain(in, scale); +} + +ColumnOp::Column +ColumnOp::distribute(const Column &in, + int h, + const vector<double> &binfory, + int minbin, + bool interpolate) +{ + vector<float> out(h, 0.f); + int bins = int(in.size()); + + if (interpolate) { + // If the bins are all closer together than the target y + // coordinate increments, then we don't want to interpolate + // after all. But because the binfory mapping isn't + // necessarily linear, just checking e.g. whether bins > h is + // not enough -- the bins could still be spaced more widely at + // either end of the scale. We are prepared to assume however + // that if the bins are closer at both ends of the scale, they + // aren't going to diverge mysteriously in the middle. + if (h > 1 && + fabs(binfory[1] - binfory[0]) >= 1.0 && + fabs(binfory[h-1] - binfory[h-2]) >= 1.0) { + interpolate = false; + } + } + + for (int y = 0; y < h; ++y) { + + if (interpolate) { + + double sy = binfory[y] - minbin - 0.5; + double syf = floor(sy); + + int mainbin = int(syf); + int other = mainbin; + if (sy > syf) { + other = mainbin + 1; + } else if (sy < syf) { + other = mainbin - 1; + } + + if (mainbin < 0) { + mainbin = 0; + } + if (mainbin >= bins) { + mainbin = bins - 1; + } + + if (other < 0) { + other = 0; + } + if (other >= bins) { + other = bins - 1; + } + + double prop = 1.0 - fabs(sy - syf); + + double v0 = in[mainbin]; + double v1 = in[other]; + + out[y] = float(prop * v0 + (1.0 - prop) * v1); + + } else { + + double sy0 = binfory[y] - minbin; + + double sy1; + if (y+1 < h) { + sy1 = binfory[y+1] - minbin; + } else { + sy1 = bins; + } + + int by0 = int(sy0 + 0.0001); + int by1 = int(sy1 + 0.0001); + + if (by0 < 0 || by0 >= bins || by1 > bins) { + SVCERR << "ERROR: bin index out of range in ColumnOp::distribute: by0 = " << by0 << ", by1 = " << by1 << ", sy0 = " << sy0 << ", sy1 = " << sy1 << ", y = " << y << ", binfory[y] = " << binfory[y] << ", minbin = " << minbin << ", bins = " << bins << endl; + continue; + } + + for (int bin = by0; bin == by0 || bin < by1; ++bin) { + + float value = in[bin]; + + if (bin == by0 || value > out[y]) { + out[y] = value; + } + } + } + } + + return out; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ColumnOp.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,126 @@ +/* -*- 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-2016 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 COLUMN_OP_H +#define COLUMN_OP_H + +#include "BaseTypes.h" + +#include <vector> + +/** + * Display normalization types for columns in e.g. grid plots. + * + * Max1 means to normalize to max value = 1.0. + * Sum1 means to normalize to sum of values = 1.0. + * + * Hybrid means normalize to max = 1.0 and then multiply by + * log10 of the max value, to retain some difference between + * levels of neighbouring columns. + * + * Area normalization is handled separately. + */ +enum class ColumnNormalization { + None, + Max1, + Sum1, + Hybrid +}; + +/** + * Class containing static functions for simple operations on data + * columns, for use by display layers. + */ +class ColumnOp +{ +public: + /** + * Column type. + */ + typedef std::vector<float> Column; + + /** + * Scale the given column using the given gain multiplier. + */ + static Column applyGain(const Column &in, double gain) { + if (gain == 1.0) return in; + Column out; + out.reserve(in.size()); + for (auto v: in) out.push_back(float(v * gain)); + return out; + } + + /** + * Scale an FFT output downward by half the FFT size. + */ + static Column fftScale(const Column &in, int fftSize); + + /** + * Determine whether an index points to a local peak. + */ + static bool isPeak(const Column &in, int ix) { + if (!in_range_for(in, ix)) { + return false; + } + if (ix == 0) { + return in[0] >= in[1]; + } + if (!in_range_for(in, ix+1)) { + return in[ix] > in[ix-1]; + } + if (in[ix] < in[ix+1]) { + return false; + } + if (in[ix] <= in[ix-1]) { + return false; + } + return true; + } + + /** + * Return a column containing only the local peak values (all + * others zero). + */ + static Column peakPick(const Column &in); + + /** + * Return a column normalized from the input column according to + * the given normalization scheme. + * + * Note that the sum or max (as appropriate) used for + * normalisation will be calculated from the absolute values of + * the column elements, should any of them be negative. + */ + static Column normalize(const Column &in, ColumnNormalization n); + + /** + * Distribute the given column into a target vector of a different + * size, optionally using linear interpolation. The binfory vector + * contains a mapping from y coordinate (i.e. index into the + * target vector) to bin (i.e. index into the source column). The + * source column ("in") may be a partial column; it's assumed to + * contain enough bins to span the destination range, starting + * with the bin of index minbin. + */ + static Column distribute(const Column &in, + int h, + const std::vector<double> &binfory, + int minbin, + bool interpolate); + +}; + +#endif +
--- a/base/Debug.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Debug.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -21,25 +21,48 @@ #include <QUrl> #include <QCoreApplication> -#ifndef NDEBUG +#include <stdexcept> -static SVDebug *debug = 0; +static SVDebug *svdebug = 0; +static SVCerr *svcerr = 0; static QMutex mutex; SVDebug &getSVDebug() { mutex.lock(); - if (!debug) { - debug = new SVDebug(); + if (!svdebug) { + svdebug = new SVDebug(); } mutex.unlock(); - return *debug; + return *svdebug; } +SVCerr &getSVCerr() { + mutex.lock(); + if (!svcerr) { + if (!svdebug) { + svdebug = new SVDebug(); + } + svcerr = new SVCerr(*svdebug); + } + mutex.unlock(); + return *svcerr; +} + +bool SVDebug::m_silenced = false; +bool SVCerr::m_silenced = false; + SVDebug::SVDebug() : m_prefix(0), m_ok(false), - m_eol(false) + m_eol(true) { + if (m_silenced) return; + + if (qApp->applicationName() == "") { + cerr << "ERROR: Can't use SVDEBUG before setting application name" << endl; + throw std::logic_error("Can't use SVDEBUG before setting application name"); + } + QString pfx = ResourceFinder().getUserResourcePrefix(); QDir logdir(QString("%1/%2").arg(pfx).arg("log")); @@ -59,14 +82,14 @@ << "Failed to open debug log file " << fileName << " for writing"; } else { - cerr << m_prefix << ": Log file is " << fileName << endl; +// cerr << m_prefix << ": Log file is " << fileName << endl; m_ok = true; } } SVDebug::~SVDebug() { - m_stream.close(); + if (m_stream) m_stream.close(); } QDebug & @@ -76,8 +99,6 @@ return dbg; } -#endif - std::ostream & operator<<(std::ostream &target, const QString &str) {
--- a/base/Debug.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Debug.h Fri Jan 13 10:29:44 2017 +0000 @@ -36,8 +36,6 @@ using std::cerr; using std::endl; -#ifndef NDEBUG - class SVDebug { public: SVDebug(); @@ -45,6 +43,7 @@ template <typename T> inline SVDebug &operator<<(const T &t) { + if (m_silenced) return *this; if (m_ok) { if (m_eol) { m_stream << m_prefix << " "; @@ -56,39 +55,56 @@ } inline SVDebug &operator<<(QTextStreamFunction) { + if (m_silenced) return *this; m_stream << std::endl; m_eol = true; return *this; } + static void silence() { m_silenced = true; } + private: std::fstream m_stream; char *m_prefix; bool m_ok; bool m_eol; + static bool m_silenced; +}; + +class SVCerr { +public: + SVCerr(SVDebug &d) : m_d(d) { } + + template <typename T> + inline SVCerr &operator<<(const T &t) { + if (m_silenced) return *this; + m_d << t; + cerr << t; + return *this; + } + + inline SVCerr &operator<<(QTextStreamFunction f) { + if (m_silenced) return *this; + m_d << f; + cerr << std::endl; + return *this; + } + + static void silence() { m_silenced = true; } + +private: + SVDebug &m_d; + static bool m_silenced; }; extern SVDebug &getSVDebug(); +extern SVCerr &getSVCerr(); +// Writes to debug log only #define SVDEBUG getSVDebug() -#else - -class NoDebug -{ -public: - inline NoDebug() {} - inline ~NoDebug(){} - - template <typename T> - inline NoDebug &operator<<(const T &) { return *this; } - - inline NoDebug &operator<<(QTextStreamFunction) { return *this; } -}; - -#define SVDEBUG NoDebug() - -#endif /* !NDEBUG */ +// Writes to both SVDEBUG and cerr +#define SVCERR getSVCerr() #endif /* !_DEBUG_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/HelperExecPath.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,105 @@ +/* -*- 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-2016 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. +*/ + +#include "HelperExecPath.h" + +#include <QCoreApplication> +#include <QFile> +#include <QDir> +#include <QFileInfo> + +QStringList +HelperExecPath::getTags() +{ + if (sizeof(void *) == 8) { + if (m_type == NativeArchitectureOnly) { + return { "64", "" }; + } else { + return { "64", "", "32" }; + } + } else { + return { "", "32" }; + } +} + +static bool +isGood(QString path) +{ + return QFile(path).exists() && QFileInfo(path).isExecutable(); +} + +QList<HelperExecPath::HelperExec> +HelperExecPath::getHelperExecutables(QString basename) +{ + QStringList dummy; + return search(basename, dummy); +} + +QString +HelperExecPath::getHelperExecutable(QString basename) +{ + auto execs = getHelperExecutables(basename); + if (execs.empty()) return ""; + else return execs[0].executable; +} + +QStringList +HelperExecPath::getHelperDirPaths() +{ + QStringList dirs; + QString myDir = QCoreApplication::applicationDirPath(); + dirs.push_back(myDir + "/helpers"); + dirs.push_back(myDir); + return dirs; +} + +QStringList +HelperExecPath::getHelperCandidatePaths(QString basename) +{ + QStringList candidates; + (void)search(basename, candidates); + return candidates; +} + +QList<HelperExecPath::HelperExec> +HelperExecPath::search(QString basename, QStringList &candidates) +{ + // Helpers are expected to exist either in the same directory as + // this executable was found, or in a subdirectory called helpers. + + QString extension = ""; +#ifdef _WIN32 + extension = ".exe"; +#endif + + QList<HelperExec> executables; + QStringList dirs = getHelperDirPaths(); + + for (QString t: getTags()) { + for (QString d: dirs) { + QString path = d + QDir::separator() + basename; + if (t != QString()) path += "-" + t; + path += extension; + candidates.push_back(path); + if (isGood(path)) { + executables.push_back({ path, t }); + break; + } + } + } + + return executables; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/HelperExecPath.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,84 @@ +/* -*- 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-2016 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_HELPER_EXEC_PATH_H +#define SV_HELPER_EXEC_PATH_H + +#include <QStringList> + +/** + * Class to find helper executables that have been installed alongside + * the application. There may be more than one executable available + * with a given base name, because it's possible to have more than one + * implementation of a given service. For example, a plugin helper or + * scanner may exist in both 32-bit and 64-bit variants. + * + * This class encodes both the expected locations of helper + * executables, and the expected priority between different + * implementations (e.g. preferring the architecture that matches that + * of the host). + */ +class HelperExecPath +{ +public: + enum SearchType { + NativeArchitectureOnly, + AllInstalled + }; + + HelperExecPath(SearchType type) : m_type(type) { } + + /** + * Find a helper executable with the given base name in the bundle + * directory or installation location, if one exists, and return + * its full path. Equivalent to calling getHelperExecutables() and + * taking the first result from the returned list (or "" if empty). + */ + QString getHelperExecutable(QString basename); + + struct HelperExec { + QString executable; + QString tag; + }; + + /** + * Find all helper executables with the given base name in the + * bundle directory or installation location, and return their + * full paths in order of priority. The "tag" string contains an + * identifier for the location or architecture of the helper, for + * example "32", "64", "js" etc. An empty tag signifies a default + * helper that matches the application's architecture. + */ + QList<HelperExec> getHelperExecutables(QString basename); + + /** + * Return the list of directories searched for helper + * executables. + */ + QStringList getHelperDirPaths(); + + /** + * Return the list of executable paths examined in the search for + * the helper executable with the given basename. + */ + QStringList getHelperCandidatePaths(QString basename); + +private: + SearchType m_type; + QList<HelperExec> search(QString, QStringList &); + QStringList getTags(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/HitCount.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,70 @@ +/* -*- 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 HIT_COUNT_H +#define HIT_COUNT_H + +#include <string> +#include <iostream> + +/** + * Profile class for counting cache hits and the like. + */ +class HitCount +{ +public: + HitCount(std::string name) : + m_name(name), + m_hit(0), + m_partial(0), + m_miss(0) + { } + + ~HitCount() { +#ifndef NO_HIT_COUNTS + using namespace std; + int total = m_hit + m_partial + m_miss; + cerr << "Hit count: " << m_name << ": "; + if (m_partial > 0) { + cerr << m_hit << " hits, " << m_partial << " partial, " + << m_miss << " misses"; + } else { + cerr << m_hit << " hits, " << m_miss << " misses"; + } + if (total > 0) { + if (m_partial > 0) { + cerr << " (" << ((m_hit * 100.0) / total) << "%, " + << ((m_partial * 100.0) / total) << "%, " + << ((m_miss * 100.0) / total) << "%)"; + } else { + cerr << " (" << ((m_hit * 100.0) / total) << "%, " + << ((m_miss * 100.0) / total) << "%)"; + } + } + cerr << endl; +#endif + } + + void hit() { ++m_hit; } + void partial() { ++m_partial; } + void miss() { ++m_miss; } + +private: + std::string m_name; + int m_hit; + int m_partial; + int m_miss; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/MagnitudeRange.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,83 @@ +/* -*- 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 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 MAGNITUDE_RANGE_H +#define MAGNITUDE_RANGE_H + +#include <vector> + +/** + * Maintain a min and max value, and update them when supplied a new + * data point. + */ +class MagnitudeRange +{ +public: + MagnitudeRange() : m_min(0), m_max(0) { } + MagnitudeRange(float min, float max) : m_min(min), m_max(max) { } + + bool operator==(const MagnitudeRange &r) { + return r.m_min == m_min && r.m_max == m_max; + } + bool operator!=(const MagnitudeRange &r) { + return !(*this == r); + } + + bool isSet() const { return (m_min != 0.f || m_max != 0.f); } + void set(float min, float max) { + m_min = min; + m_max = max; + if (m_max < m_min) m_max = m_min; + } + bool sample(float f) { + bool changed = false; + if (isSet()) { + if (f < m_min) { m_min = f; changed = true; } + if (f > m_max) { m_max = f; changed = true; } + } else { + m_max = m_min = f; + changed = true; + } + return changed; + } + bool sample(const std::vector<float> &ff) { + bool changed = false; + for (auto f: ff) { + if (sample(f)) { + changed = true; + } + } + return changed; + } + bool sample(const MagnitudeRange &r) { + bool changed = false; + if (isSet()) { + if (r.m_min < m_min) { m_min = r.m_min; changed = true; } + if (r.m_max > m_max) { m_max = r.m_max; changed = true; } + } else { + m_min = r.m_min; + m_max = r.m_max; + changed = true; + } + return changed; + } + float getMin() const { return m_min; } + float getMax() const { return m_max; } +private: + float m_min; + float m_max; +}; + +#endif
--- a/base/PlayParameterRepository.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/PlayParameterRepository.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -35,14 +35,14 @@ void PlayParameterRepository::addPlayable(const Playable *playable) { - cerr << "PlayParameterRepository:addPlayable playable = " << playable << endl; +// cerr << "PlayParameterRepository:addPlayable playable = " << playable << endl; if (!getPlayParameters(playable)) { // Give all playables the same type of play parameters for the // moment - cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl; +// cerr << "PlayParameterRepository:addPlayable: Adding play parameters for " << playable << endl; PlayParameters *params = new PlayParameters; m_playParameters[playable] = params; @@ -59,8 +59,8 @@ connect(params, SIGNAL(playClipIdChanged(QString)), this, SLOT(playClipIdChanged(QString))); - cerr << "Connected play parameters " << params << " for playable " - << playable << " to this " << this << endl; +// cerr << "Connected play parameters " << params << " for playable " +// << playable << " to this " << this << endl; } }
--- a/base/Preferences.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Preferences.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -40,11 +40,12 @@ m_tuningFrequency(440), m_propertyBoxLayout(VerticallyStacked), m_windowType(HanningWindow), - m_resampleQuality(1), + m_runPluginsInProcess(true), m_omitRecentTemps(true), m_tempDirRoot(""), m_fixedSampleRate(0), m_resampleOnLoad(false), + m_gapless(true), m_normaliseAudio(false), m_viewFontSize(10), m_backgroundMode(BackgroundFromTheme), @@ -64,9 +65,10 @@ (settings.value("property-box-layout", int(VerticallyStacked)).toInt()); m_windowType = WindowType (settings.value("window-type", int(HanningWindow)).toInt()); - m_resampleQuality = settings.value("resample-quality", 1).toInt(); + m_runPluginsInProcess = settings.value("run-vamp-plugins-in-process", true).toBool(); m_fixedSampleRate = settings.value("fixed-sample-rate", 0).toDouble(); m_resampleOnLoad = settings.value("resample-on-load", false).toBool(); + m_gapless = settings.value("gapless", true).toBool(); m_normaliseAudio = settings.value("normalise-audio", false).toBool(); m_backgroundMode = BackgroundMode (settings.value("background-mode", int(BackgroundFromTheme)).toInt()); @@ -99,6 +101,7 @@ props.push_back("Resample Quality"); props.push_back("Omit Temporaries from Recent Files"); props.push_back("Resample On Load"); + props.push_back("Use Gapless Mode"); props.push_back("Normalise Audio"); props.push_back("Fixed Sample Rate"); props.push_back("Temporary Directory Root"); @@ -141,6 +144,9 @@ if (name == "Resample On Load") { return tr("Resample mismatching files on import"); } + if (name == "Use Gapless Mode") { + return tr("Load mp3 files in gapless mode"); + } if (name == "Fixed Sample Rate") { return tr("Single fixed sample rate to resample all files to"); } @@ -198,6 +204,9 @@ if (name == "Resample On Load") { return ToggleProperty; } + if (name == "Use Gapless Mode") { + return ToggleProperty; + } if (name == "Fixed Sample Rate") { return ValueProperty; } @@ -259,13 +268,10 @@ return int(m_windowType); } - if (name == "Resample Quality") { - if (min) *min = 0; - if (max) *max = 2; - if (deflt) *deflt = 1; - return m_resampleQuality; + if (name == "Run Vamp Plugins In Process") { + return m_runPluginsInProcess; } - + if (name == "Omit Temporaries from Recent Files") { if (deflt) *deflt = 1; return m_omitRecentTemps ? 1 : 0; @@ -412,8 +418,8 @@ setPropertyBoxLayout(value == 0 ? VerticallyStacked : Layered); } else if (name == "Window Type") { setWindowType(WindowType(value)); - } else if (name == "Resample Quality") { - setResampleQuality(value); + } else if (name == "Run Vamp Plugins In Process") { + setRunPluginsInProcess(value ? true : false); } else if (name == "Omit Temporaries from Recent Files") { setOmitTempsFromRecentFiles(value ? true : false); } else if (name == "Background Mode") { @@ -506,15 +512,15 @@ } void -Preferences::setResampleQuality(int q) +Preferences::setRunPluginsInProcess(bool run) { - if (m_resampleQuality != q) { - m_resampleQuality = q; + if (m_runPluginsInProcess != run) { + m_runPluginsInProcess = run; QSettings settings; settings.beginGroup("Preferences"); - settings.setValue("resample-quality", q); + settings.setValue("run-vamp-plugins-in-process", run); settings.endGroup(); - emit propertyChanged("Resample Quality"); + emit propertyChanged("Run Vamp Plugins In Process"); } } @@ -561,6 +567,19 @@ } void +Preferences::setUseGaplessMode(bool gapless) +{ + if (m_gapless != gapless) { + m_gapless = gapless; + QSettings settings; + settings.beginGroup("Preferences"); + settings.setValue("gapless", gapless); + settings.endGroup(); + emit propertyChanged("Use Gapless Mode"); + } +} + +void Preferences::setFixedSampleRate(sv_samplerate_t rate) { if (m_fixedSampleRate != rate) {
--- a/base/Preferences.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Preferences.h Fri Jan 13 10:29:44 2017 +0000 @@ -51,8 +51,9 @@ SpectrogramXSmoothing getSpectrogramXSmoothing() const { return m_spectrogramXSmoothing; } double getTuningFrequency() const { return m_tuningFrequency; } WindowType getWindowType() const { return m_windowType; } - int getResampleQuality() const { return m_resampleQuality; } + bool getRunPluginsInProcess() const { return m_runPluginsInProcess; } + //!!! harmonise with PaneStack enum PropertyBoxLayout { VerticallyStacked, @@ -70,7 +71,10 @@ sv_samplerate_t getFixedSampleRate() const { return m_fixedSampleRate; } /// True if we should resample second or subsequent audio file to match first audio file's rate - bool getResampleOnLoad() const { return m_resampleOnLoad; } + bool getResampleOnLoad() const { return m_resampleOnLoad; } + + /// True if mp3 files should be loaded "gaplessly", i.e. compensating for encoder/decoder delay and padding + bool getUseGaplessMode() const { return m_gapless; } /// True if audio files should be loaded with normalisation (max == 1) bool getNormaliseAudio() const { return m_normaliseAudio; } @@ -113,11 +117,12 @@ void setTuningFrequency(double freq); void setPropertyBoxLayout(PropertyBoxLayout layout); void setWindowType(WindowType type); - void setResampleQuality(int quality); + void setRunPluginsInProcess(bool r); void setOmitTempsFromRecentFiles(bool omit); void setTemporaryDirectoryRoot(QString tempDirRoot); void setFixedSampleRate(sv_samplerate_t); void setResampleOnLoad(bool); + void setUseGaplessMode(bool); void setNormaliseAudio(bool); void setBackgroundMode(BackgroundMode mode); void setTimeToTextMode(TimeToTextMode mode); @@ -150,11 +155,12 @@ double m_tuningFrequency; PropertyBoxLayout m_propertyBoxLayout; WindowType m_windowType; - int m_resampleQuality; + bool m_runPluginsInProcess; bool m_omitRecentTemps; QString m_tempDirRoot; sv_samplerate_t m_fixedSampleRate; bool m_resampleOnLoad; + bool m_gapless; bool m_normaliseAudio; int m_viewFontSize; BackgroundMode m_backgroundMode;
--- a/base/Profiler.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Profiler.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -47,15 +47,11 @@ dump(); } +#ifndef NO_TIMING void Profiles::accumulate( -#ifndef NO_TIMING const char* id, clock_t time, RealTime rt -#else - const char*, clock_t, RealTime -#endif ) { -#ifndef NO_TIMING ProfilePair &pair(m_profiles[id]); ++pair.first; pair.second.first += time; @@ -72,8 +68,8 @@ if (rt > worstPair.second) { worstPair.second = rt; } +} #endif -} void Profiles::dump() const {
--- a/base/Profiler.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Profiler.h Fri Jan 13 10:29:44 2017 +0000 @@ -25,8 +25,6 @@ #include "system/System.h" -#include <ctime> -#include <sys/time.h> #include <map> #include "RealTime.h" @@ -41,6 +39,11 @@ #endif #endif +#ifndef NO_TIMING +#include <ctime> +#include <sys/time.h> +#endif + /** * Profiling classes */ @@ -56,12 +59,15 @@ static Profiles* getInstance(); ~Profiles(); +#ifndef NO_TIMING void accumulate(const char* id, clock_t time, RealTime rt); +#endif void dump() const; protected: Profiles(); +#ifndef NO_TIMING typedef std::pair<clock_t, RealTime> TimePair; typedef std::pair<int, TimePair> ProfilePair; typedef std::map<const char *, ProfilePair> ProfileMap; @@ -70,6 +76,7 @@ ProfileMap m_profiles; LastCallMap m_lastCalls; WorstCallMap m_worstCalls; +#endif static Profiles* m_instance; };
--- a/base/PropertyContainer.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/PropertyContainer.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -59,6 +59,12 @@ return QString(); } +QString +PropertyContainer::getPropertyValueIconName(const PropertyName &, int) const +{ + return QString(); +} + RangeMapper * PropertyContainer::getNewPropertyRangeMapper(const PropertyName &) const { @@ -172,6 +178,7 @@ case ValueProperty: case ColourProperty: + case ColourMapProperty: { int min, max; getPropertyRangeAndValue(name, &min, &max, 0);
--- a/base/PropertyContainer.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/PropertyContainer.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _PROPERTY_CONTAINER_H_ -#define _PROPERTY_CONTAINER_H_ +#ifndef SV_PROPERTY_CONTAINER_H +#define SV_PROPERTY_CONTAINER_H #include "Command.h" @@ -40,6 +40,7 @@ RangeProperty, // range of integers ValueProperty, // range of integers given string labels ColourProperty, // colours, get/set as ColourDatabase indices + ColourMapProperty, // colour maps, get/set as ColourMapper::StandardMap enum UnitsProperty, // unit from UnitDatabase, get/set unit id InvalidProperty, // property not found! }; @@ -91,6 +92,13 @@ int value) const; /** + * If the given property is a ValueProperty, return the icon to be + * used for the given value for that property, if any. + */ + virtual QString getPropertyValueIconName(const PropertyName &, + int value) const; + + /** * If the given property is a RangeProperty, return a new * RangeMapper object mapping its integer range onto an underlying * floating point value range for human-intelligible display, if
--- a/base/RangeMapper.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/RangeMapper.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -23,13 +23,15 @@ LinearRangeMapper::LinearRangeMapper(int minpos, int maxpos, double minval, double maxval, - QString unit, bool inverted) : + QString unit, bool inverted, + std::map<int, QString> labels) : m_minpos(minpos), m_maxpos(maxpos), m_minval(minval), m_maxval(maxval), m_unit(unit), - m_inverted(inverted) + m_inverted(inverted), + m_labels(labels) { assert(m_maxval != m_minval); assert(m_maxpos != m_minpos); @@ -70,9 +72,20 @@ double value = m_minval + ((double(position - m_minpos) / double(m_maxpos - m_minpos)) * (m_maxval - m_minval)); +// cerr << "getValueForPositionUnclamped(" << position << "): minval " << m_minval << ", maxval " << m_maxval << ", value " << value << endl; return value; } +QString +LinearRangeMapper::getLabel(int position) const +{ + if (m_labels.find(position) != m_labels.end()) { + return m_labels.at(position); + } else { + return ""; + } +} + LogRangeMapper::LogRangeMapper(int minpos, int maxpos, double minval, double maxval, QString unit, bool inverted) :
--- a/base/RangeMapper.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/RangeMapper.h Fri Jan 13 10:29:44 2017 +0000 @@ -50,7 +50,7 @@ virtual double getValueForPosition(int position) const = 0; /** - * Return the value mapped from the given positionq, without + * Return the value mapped from the given position, without * clamping. That is, whatever mapping function is in use will be * projected even outside the minimum and maximum extents of the * mapper's value range. (The mapping outside that range is not @@ -62,6 +62,16 @@ * Get the unit of the mapper's value range. */ virtual QString getUnit() const { return ""; } + + /** + * The mapper may optionally provide special labels for one or + * more individual positions (such as the minimum position, the + * default, or indeed all positions). These should be used in any + * display context in preference to just showing the numerical + * value for the position. If a position has such a label, return + * it here. + */ + virtual QString getLabel(int /* position */) const { return ""; } }; @@ -76,7 +86,8 @@ */ LinearRangeMapper(int minpos, int maxpos, double minval, double maxval, - QString unit = "", bool inverted = false); + QString unit = "", bool inverted = false, + std::map<int, QString> labels = {}); virtual int getPositionForValue(double value) const; virtual int getPositionForValueUnclamped(double value) const; @@ -85,6 +96,7 @@ virtual double getValueForPositionUnclamped(int position) const; virtual QString getUnit() const { return m_unit; } + virtual QString getLabel(int position) const; protected: int m_minpos; @@ -93,6 +105,7 @@ double m_maxval; QString m_unit; bool m_inverted; + std::map<int, QString> m_labels; }; class LogRangeMapper : public RangeMapper
--- a/base/RealTime.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,481 +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 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. -*/ - -/* - This is a modified version of a source file from the - Rosegarden MIDI and audio sequencer and notation editor. - This file copyright 2000-2006 Chris Cannam. -*/ - -#include <iostream> - -#include <cstdlib> -#include <sstream> - -#include "RealTime.h" -#include "sys/time.h" - -#include "Debug.h" - -#include "Preferences.h" - -// A RealTime consists of two ints that must be at least 32 bits each. -// A signed 32-bit int can store values exceeding +/- 2 billion. This -// means we can safely use our lower int for nanoseconds, as there are -// 1 billion nanoseconds in a second and we need to handle double that -// because of the implementations of addition etc that we use. -// -// The maximum valid RealTime on a 32-bit system is somewhere around -// 68 years: 999999999 nanoseconds longer than the classic Unix epoch. - -#define ONE_BILLION 1000000000 - -RealTime::RealTime(int s, int n) : - sec(s), nsec(n) -{ - if (sec == 0) { - while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } - while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } - } else if (sec < 0) { - while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } - while (nsec > 0) { nsec -= ONE_BILLION; ++sec; } - } else { - while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } - while (nsec < 0) { nsec += ONE_BILLION; --sec; } - } -} - -RealTime -RealTime::fromSeconds(double sec) -{ - if (sec >= 0) { - return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); - } else { - return -fromSeconds(-sec); - } -} - -RealTime -RealTime::fromMilliseconds(int msec) -{ - return RealTime(msec / 1000, (msec % 1000) * 1000000); -} - -RealTime -RealTime::fromTimeval(const struct timeval &tv) -{ - return RealTime(int(tv.tv_sec), int(tv.tv_usec * 1000)); -} - -RealTime -RealTime::fromXsdDuration(std::string xsdd) -{ - RealTime t; - - int year = 0, month = 0, day = 0, hour = 0, minute = 0; - double second = 0.0; - - int i = 0; - - const char *s = xsdd.c_str(); - int len = int(xsdd.length()); - - bool negative = false, afterT = false; - - while (i < len) { - - if (s[i] == '-') { - if (i == 0) negative = true; - ++i; - continue; - } - - double value = 0.0; - char *eptr = 0; - - if (isdigit(s[i]) || s[i] == '.') { - value = strtod(&s[i], &eptr); - i = int(eptr - s); - } - - if (i == len) break; - - switch (s[i]) { - case 'Y': year = int(value + 0.1); break; - case 'D': day = int(value + 0.1); break; - case 'H': hour = int(value + 0.1); break; - case 'M': - if (afterT) minute = int(value + 0.1); - else month = int(value + 0.1); - break; - case 'S': - second = value; - break; - case 'T': afterT = true; break; - }; - - ++i; - } - - if (year > 0) { - cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero year.\nWith no origin and a limited data size, I will treat a year as exactly 31556952\nseconds and you should expect overflow and/or poor results." << endl; - t = t + RealTime(year * 31556952, 0); - } - - if (month > 0) { - cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero month.\nWith no origin and a limited data size, I will treat a month as exactly 2629746\nseconds and you should expect overflow and/or poor results." << endl; - t = t + RealTime(month * 2629746, 0); - } - - if (day > 0) { - t = t + RealTime(day * 86400, 0); - } - - if (hour > 0) { - t = t + RealTime(hour * 3600, 0); - } - - if (minute > 0) { - t = t + RealTime(minute * 60, 0); - } - - t = t + fromSeconds(second); - - if (negative) { - return -t; - } else { - return t; - } -} - -double -RealTime::toDouble() const -{ - double d = sec; - d += double(nsec) / double(ONE_BILLION); - return d; -} - -std::ostream &operator<<(std::ostream &out, const RealTime &rt) -{ - if (rt < RealTime::zeroTime) { - out << "-"; - } else { - out << " "; - } - - int s = (rt.sec < 0 ? -rt.sec : rt.sec); - int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec); - - out << s << "."; - - int nn(n); - if (nn == 0) out << "00000000"; - else while (nn < (ONE_BILLION / 10)) { - out << "0"; - nn *= 10; - } - - out << n << "R"; - return out; -} - -std::string -RealTime::toString(bool align) const -{ - std::stringstream out; - out << *this; - - std::string s = out.str(); - - if (!align && *this >= RealTime::zeroTime) { - // remove leading " " - s = s.substr(1, s.length() - 1); - } - - // remove trailing R - return s.substr(0, s.length() - 1); -} - -RealTime -RealTime::fromString(std::string s) -{ - bool negative = false; - int section = 0; - std::string ssec, snsec; - - for (size_t i = 0; i < s.length(); ++i) { - - char c = s[i]; - if (isspace(c)) continue; - - if (section == 0) { - - if (c == '-') negative = true; - else if (isdigit(c)) { section = 1; ssec += c; } - else if (c == '.') section = 2; - else break; - - } else if (section == 1) { - - if (c == '.') section = 2; - else if (isdigit(c)) ssec += c; - else break; - - } else if (section == 2) { - - if (isdigit(c)) snsec += c; - else break; - } - } - - while (snsec.length() < 8) snsec += '0'; - - int sec = atoi(ssec.c_str()); - int nsec = atoi(snsec.c_str()); - if (negative) sec = -sec; - -// SVDEBUG << "RealTime::fromString: string " << s << " -> " -// << sec << " sec, " << nsec << " nsec" << endl; - - return RealTime(sec, nsec); -} - -std::string -RealTime::toText(bool fixedDp) const -{ - if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp); - - Preferences *p = Preferences::getInstance(); - bool hms = true; - - if (p) { - hms = p->getShowHMS(); - int fps = 0; - switch (p->getTimeToTextMode()) { - case Preferences::TimeToTextMs: break; - case Preferences::TimeToTextUs: fps = 1000000; break; - case Preferences::TimeToText24Frame: fps = 24; break; - case Preferences::TimeToText25Frame: fps = 25; break; - case Preferences::TimeToText30Frame: fps = 30; break; - case Preferences::TimeToText50Frame: fps = 50; break; - case Preferences::TimeToText60Frame: fps = 60; break; - } - if (fps != 0) return toFrameText(fps, hms); - } - - return toMSText(fixedDp, hms); -} - -static void -writeSecPart(std::stringstream &out, bool hms, int sec) -{ - if (hms) { - if (sec >= 3600) { - out << (sec / 3600) << ":"; - } - - if (sec >= 60) { - int minutes = (sec % 3600) / 60; - if (sec >= 3600 && minutes < 10) out << "0"; - out << minutes << ":"; - } - - if (sec >= 10) { - out << ((sec % 60) / 10); - } - - out << (sec % 10); - - } else { - out << sec; - } -} - -std::string -RealTime::toMSText(bool fixedDp, bool hms) const -{ - if (*this < RealTime::zeroTime) return "-" + (-*this).toMSText(fixedDp, hms); - - std::stringstream out; - - writeSecPart(out, hms, sec); - - int ms = msec(); - - if (ms != 0) { - out << "."; - out << (ms / 100); - ms = ms % 100; - if (ms != 0) { - out << (ms / 10); - ms = ms % 10; - } else if (fixedDp) { - out << "0"; - } - if (ms != 0) { - out << ms; - } else if (fixedDp) { - out << "0"; - } - } else if (fixedDp) { - out << ".000"; - } - - std::string s = out.str(); - - return s; -} - -std::string -RealTime::toFrameText(int fps, bool hms) const -{ - if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps, hms); - - std::stringstream out; - - writeSecPart(out, hms, sec); - - // avoid rounding error if fps does not divide into ONE_BILLION - int64_t fbig = nsec; - fbig *= fps; - int f = int(fbig / ONE_BILLION); - - int div = 1; - int n = fps - 1; - while ((n = n / 10)) { - div *= 10; - } - - out << ":"; - -// cerr << "div = " << div << ", f = "<< f << endl; - - while (div) { - int d = (f / div) % 10; - out << d; - div /= 10; - } - - std::string s = out.str(); - -// cerr << "converted " << toString() << " to " << s << endl; - - return s; -} - -std::string -RealTime::toSecText() const -{ - if (*this < RealTime::zeroTime) return "-" + (-*this).toSecText(); - - std::stringstream out; - - writeSecPart(out, true, sec); - - if (sec < 60) { - out << "s"; - } - - std::string s = out.str(); - - return s; -} - -std::string -RealTime::toXsdDuration() const -{ - std::string s = "PT" + toString(false) + "S"; - return s; -} - -RealTime -RealTime::operator*(int m) const -{ - double t = (double(nsec) / ONE_BILLION) * m; - t += sec * m; - return fromSeconds(t); -} - -RealTime -RealTime::operator/(int d) const -{ - int secdiv = sec / d; - int secrem = sec % d; - - double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d; - - return RealTime(secdiv, int(nsecdiv + 0.5)); -} - -RealTime -RealTime::operator*(double m) const -{ - double t = (double(nsec) / ONE_BILLION) * m; - t += sec * m; - return fromSeconds(t); -} - -RealTime -RealTime::operator/(double d) const -{ - double t = (double(nsec) / ONE_BILLION) / d; - t += sec / d; - return fromSeconds(t); -} - -double -RealTime::operator/(const RealTime &r) const -{ - double lTotal = double(sec) * ONE_BILLION + double(nsec); - double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec); - - if (rTotal == 0) return 0.0; - else return lTotal/rTotal; -} - -static RealTime -frame2RealTime_i(sv_frame_t frame, sv_frame_t iSampleRate) -{ - if (frame < 0) return -frame2RealTime_i(-frame, iSampleRate); - - RealTime rt; - sv_frame_t sec = frame / iSampleRate; - rt.sec = int(sec); - frame -= sec * iSampleRate; - rt.nsec = (int)(((double(frame) * 1000000.0) / double(iSampleRate)) * 1000.0); - return rt; -} - -sv_frame_t -RealTime::realTime2Frame(const RealTime &time, sv_samplerate_t sampleRate) -{ - if (time < zeroTime) return -realTime2Frame(-time, sampleRate); - double s = time.sec + double(time.nsec + 1) / 1000000000.0; - return sv_frame_t(s * sampleRate); -} - -RealTime -RealTime::frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate) -{ - if (sampleRate == double(int(sampleRate))) { - return frame2RealTime_i(frame, int(sampleRate)); - } - - double sec = double(frame) / sampleRate; - return fromSeconds(sec); -} - -const RealTime RealTime::zeroTime(0,0); -
--- a/base/RealTime.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/RealTime.h Fri Jan 13 10:29:44 2017 +0000 @@ -28,7 +28,11 @@ #include <vamp-hostsdk/RealTime.h> -struct timeval; +#ifdef _MSC_VER +#include "winsock.h" // struct timeval is in here +#else +#include "sys/time.h" +#endif /** * RealTime represents time values to nanosecond precision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/RealTimeSV.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,486 @@ +/* -*- 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. +*/ + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Chris Cannam. +*/ + +#include <iostream> + +#include <cstdlib> +#include <sstream> + +#include "RealTime.h" + +#include "Debug.h" + +#include "Preferences.h" + +// A RealTime consists of two ints that must be at least 32 bits each. +// A signed 32-bit int can store values exceeding +/- 2 billion. This +// means we can safely use our lower int for nanoseconds, as there are +// 1 billion nanoseconds in a second and we need to handle double that +// because of the implementations of addition etc that we use. +// +// The maximum valid RealTime on a 32-bit system is somewhere around +// 68 years: 999999999 nanoseconds longer than the classic Unix epoch. + +#define ONE_BILLION 1000000000 + +RealTime::RealTime(int s, int n) : + sec(s), nsec(n) +{ + if (sec == 0) { + while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } + while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } + } else if (sec < 0) { + while (nsec <= -ONE_BILLION) { nsec += ONE_BILLION; --sec; } + while (nsec > 0 && sec < 0) { nsec -= ONE_BILLION; ++sec; } + } else { + while (nsec >= ONE_BILLION) { nsec -= ONE_BILLION; ++sec; } + while (nsec < 0 && sec > 0) { nsec += ONE_BILLION; --sec; } + } +} + +RealTime +RealTime::fromSeconds(double sec) +{ + if (sec >= 0) { + return RealTime(int(sec), int((sec - int(sec)) * ONE_BILLION + 0.5)); + } else { + return -fromSeconds(-sec); + } +} + +RealTime +RealTime::fromMilliseconds(int msec) +{ + return RealTime(msec / 1000, (msec % 1000) * 1000000); +} + +RealTime +RealTime::fromTimeval(const struct timeval &tv) +{ + return RealTime(int(tv.tv_sec), int(tv.tv_usec * 1000)); +} + +RealTime +RealTime::fromXsdDuration(std::string xsdd) +{ + RealTime t; + + int year = 0, month = 0, day = 0, hour = 0, minute = 0; + double second = 0.0; + + char *loc = setlocale(LC_NUMERIC, 0); + (void)setlocale(LC_NUMERIC, "C"); // avoid strtod expecting ,-separator in DE + + int i = 0; + + const char *s = xsdd.c_str(); + int len = int(xsdd.length()); + + bool negative = false, afterT = false; + + while (i < len) { + + if (s[i] == '-') { + if (i == 0) negative = true; + ++i; + continue; + } + + double value = 0.0; + char *eptr = 0; + + if (isdigit(s[i]) || s[i] == '.') { + value = strtod(&s[i], &eptr); + i = int(eptr - s); + } + + if (i == len) break; + + switch (s[i]) { + case 'Y': year = int(value + 0.1); break; + case 'D': day = int(value + 0.1); break; + case 'H': hour = int(value + 0.1); break; + case 'M': + if (afterT) minute = int(value + 0.1); + else month = int(value + 0.1); + break; + case 'S': + second = value; + break; + case 'T': afterT = true; break; + }; + + ++i; + } + + if (year > 0) { + cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero year.\nWith no origin and a limited data size, I will treat a year as exactly 31556952\nseconds and you should expect overflow and/or poor results." << endl; + t = t + RealTime(year * 31556952, 0); + } + + if (month > 0) { + cerr << "WARNING: This xsd:duration (\"" << xsdd << "\") contains a non-zero month.\nWith no origin and a limited data size, I will treat a month as exactly 2629746\nseconds and you should expect overflow and/or poor results." << endl; + t = t + RealTime(month * 2629746, 0); + } + + if (day > 0) { + t = t + RealTime(day * 86400, 0); + } + + if (hour > 0) { + t = t + RealTime(hour * 3600, 0); + } + + if (minute > 0) { + t = t + RealTime(minute * 60, 0); + } + + t = t + fromSeconds(second); + + setlocale(LC_NUMERIC, loc); + + if (negative) { + return -t; + } else { + return t; + } +} + +double +RealTime::toDouble() const +{ + double d = sec; + d += double(nsec) / double(ONE_BILLION); + return d; +} + +std::ostream &operator<<(std::ostream &out, const RealTime &rt) +{ + if (rt < RealTime::zeroTime) { + out << "-"; + } else { + out << " "; + } + + int s = (rt.sec < 0 ? -rt.sec : rt.sec); + int n = (rt.nsec < 0 ? -rt.nsec : rt.nsec); + + out << s << "."; + + int nn(n); + if (nn == 0) out << "00000000"; + else while (nn < (ONE_BILLION / 10)) { + out << "0"; + nn *= 10; + } + + out << n << "R"; + return out; +} + +std::string +RealTime::toString(bool align) const +{ + std::stringstream out; + out << *this; + + std::string s = out.str(); + + if (!align && *this >= RealTime::zeroTime) { + // remove leading " " + s = s.substr(1, s.length() - 1); + } + + // remove trailing R + return s.substr(0, s.length() - 1); +} + +RealTime +RealTime::fromString(std::string s) +{ + bool negative = false; + int section = 0; + std::string ssec, snsec; + + for (size_t i = 0; i < s.length(); ++i) { + + char c = s[i]; + if (isspace(c)) continue; + + if (section == 0) { + + if (c == '-') negative = true; + else if (isdigit(c)) { section = 1; ssec += c; } + else if (c == '.') section = 2; + else break; + + } else if (section == 1) { + + if (c == '.') section = 2; + else if (isdigit(c)) ssec += c; + else break; + + } else if (section == 2) { + + if (isdigit(c)) snsec += c; + else break; + } + } + + while (snsec.length() < 8) snsec += '0'; + + int sec = atoi(ssec.c_str()); + int nsec = atoi(snsec.c_str()); + if (negative) sec = -sec; + +// SVDEBUG << "RealTime::fromString: string " << s << " -> " +// << sec << " sec, " << nsec << " nsec" << endl; + + return RealTime(sec, nsec); +} + +std::string +RealTime::toText(bool fixedDp) const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toText(fixedDp); + + Preferences *p = Preferences::getInstance(); + bool hms = true; + + if (p) { + hms = p->getShowHMS(); + int fps = 0; + switch (p->getTimeToTextMode()) { + case Preferences::TimeToTextMs: break; + case Preferences::TimeToTextUs: fps = 1000000; break; + case Preferences::TimeToText24Frame: fps = 24; break; + case Preferences::TimeToText25Frame: fps = 25; break; + case Preferences::TimeToText30Frame: fps = 30; break; + case Preferences::TimeToText50Frame: fps = 50; break; + case Preferences::TimeToText60Frame: fps = 60; break; + } + if (fps != 0) return toFrameText(fps, hms); + } + + return toMSText(fixedDp, hms); +} + +static void +writeSecPart(std::stringstream &out, bool hms, int sec) +{ + if (hms) { + if (sec >= 3600) { + out << (sec / 3600) << ":"; + } + + if (sec >= 60) { + int minutes = (sec % 3600) / 60; + if (sec >= 3600 && minutes < 10) out << "0"; + out << minutes << ":"; + } + + if (sec >= 10) { + out << ((sec % 60) / 10); + } + + out << (sec % 10); + + } else { + out << sec; + } +} + +std::string +RealTime::toMSText(bool fixedDp, bool hms) const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toMSText(fixedDp, hms); + + std::stringstream out; + + writeSecPart(out, hms, sec); + + int ms = msec(); + + if (ms != 0) { + out << "."; + out << (ms / 100); + ms = ms % 100; + if (ms != 0) { + out << (ms / 10); + ms = ms % 10; + } else if (fixedDp) { + out << "0"; + } + if (ms != 0) { + out << ms; + } else if (fixedDp) { + out << "0"; + } + } else if (fixedDp) { + out << ".000"; + } + + std::string s = out.str(); + + return s; +} + +std::string +RealTime::toFrameText(int fps, bool hms) const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps, hms); + + std::stringstream out; + + writeSecPart(out, hms, sec); + + // avoid rounding error if fps does not divide into ONE_BILLION + int64_t fbig = nsec; + fbig *= fps; + int f = int(fbig / ONE_BILLION); + + int div = 1; + int n = fps - 1; + while ((n = n / 10)) { + div *= 10; + } + + out << ":"; + +// cerr << "div = " << div << ", f = "<< f << endl; + + while (div) { + int d = (f / div) % 10; + out << d; + div /= 10; + } + + std::string s = out.str(); + +// cerr << "converted " << toString() << " to " << s << endl; + + return s; +} + +std::string +RealTime::toSecText() const +{ + if (*this < RealTime::zeroTime) return "-" + (-*this).toSecText(); + + std::stringstream out; + + writeSecPart(out, true, sec); + + if (sec < 60) { + out << "s"; + } + + std::string s = out.str(); + + return s; +} + +std::string +RealTime::toXsdDuration() const +{ + std::string s = "PT" + toString(false) + "S"; + return s; +} + +RealTime +RealTime::operator*(int m) const +{ + double t = (double(nsec) / ONE_BILLION) * m; + t += sec * m; + return fromSeconds(t); +} + +RealTime +RealTime::operator/(int d) const +{ + int secdiv = sec / d; + int secrem = sec % d; + + double nsecdiv = (double(nsec) + ONE_BILLION * double(secrem)) / d; + + return RealTime(secdiv, int(nsecdiv + 0.5)); +} + +RealTime +RealTime::operator*(double m) const +{ + double t = (double(nsec) / ONE_BILLION) * m; + t += sec * m; + return fromSeconds(t); +} + +RealTime +RealTime::operator/(double d) const +{ + double t = (double(nsec) / ONE_BILLION) / d; + t += sec / d; + return fromSeconds(t); +} + +double +RealTime::operator/(const RealTime &r) const +{ + double lTotal = double(sec) * ONE_BILLION + double(nsec); + double rTotal = double(r.sec) * ONE_BILLION + double(r.nsec); + + if (rTotal == 0) return 0.0; + else return lTotal/rTotal; +} + +static RealTime +frame2RealTime_i(sv_frame_t frame, sv_frame_t iSampleRate) +{ + if (frame < 0) return -frame2RealTime_i(-frame, iSampleRate); + + int sec = int(frame / iSampleRate); + frame -= sec * iSampleRate; + int nsec = int((double(frame) / double(iSampleRate)) * ONE_BILLION + 0.5); + // Use ctor here instead of setting data members directly to + // ensure nsec > ONE_BILLION is handled properly. It's extremely + // unlikely, but not impossible. + return RealTime(sec, nsec); +} + +sv_frame_t +RealTime::realTime2Frame(const RealTime &time, sv_samplerate_t sampleRate) +{ + if (time < zeroTime) return -realTime2Frame(-time, sampleRate); + double s = time.sec + double(time.nsec) / 1000000000.0; + return sv_frame_t(s * sampleRate + 0.5); +} + +RealTime +RealTime::frame2RealTime(sv_frame_t frame, sv_samplerate_t sampleRate) +{ + if (sampleRate == double(int(sampleRate))) { + return frame2RealTime_i(frame, int(sampleRate)); + } + + double sec = double(frame) / sampleRate; + return fromSeconds(sec); +} + +const RealTime RealTime::zeroTime(0,0); +
--- a/base/Resampler.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,196 +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 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. -*/ - -/* - This is a modified version of a source file from the - Rubber Band audio timestretcher library. - This file copyright 2007 Chris Cannam. -*/ - -#include "Resampler.h" - -#include <cstdlib> -#include <cmath> - -#include <iostream> - -#include <samplerate.h> - -#include "Debug.h" - -class Resampler::D -{ -public: - D(Quality quality, int channels, sv_frame_t chunkSize); - ~D(); - - sv_frame_t resample(float **in, float **out, - sv_frame_t incount, double ratio, - bool final); - - sv_frame_t resampleInterleaved(float *in, float *out, - sv_frame_t incount, double ratio, - bool final); - - void reset(); - -protected: - SRC_STATE *m_src; - float *m_iin; - float *m_iout; - int m_channels; - sv_frame_t m_iinsize; - sv_frame_t m_ioutsize; -}; - -Resampler::D::D(Quality quality, int channels, sv_frame_t chunkSize) : - m_src(0), - m_iin(0), - m_iout(0), - m_channels(channels), - m_iinsize(0), - m_ioutsize(0) -{ - int err = 0; - m_src = src_new(quality == Best ? SRC_SINC_BEST_QUALITY : - quality == Fastest ? SRC_LINEAR : - SRC_SINC_FASTEST, - channels, &err); - - //!!! check err, throw - - if (chunkSize > 0 && m_channels > 1) { - //!!! alignment? - m_iinsize = chunkSize * m_channels; - m_ioutsize = chunkSize * m_channels * 2; - m_iin = (float *)malloc(m_iinsize * sizeof(float)); - m_iout = (float *)malloc(m_ioutsize * sizeof(float)); - } -} - -Resampler::D::~D() -{ - src_delete(m_src); - if (m_iinsize > 0) { - free(m_iin); - } - if (m_ioutsize > 0) { - free(m_iout); - } -} - -sv_frame_t -Resampler::D::resample(float **in, float **out, - sv_frame_t incount, double ratio, - bool final) -{ - if (m_channels == 1) { - return resampleInterleaved(*in, *out, incount, ratio, final); - } - - sv_frame_t outcount = lrint(ceil(double(incount) * ratio)); - - if (incount * m_channels > m_iinsize) { - m_iinsize = incount * m_channels; - m_iin = (float *)realloc(m_iin, m_iinsize * sizeof(float)); - } - if (outcount * m_channels > m_ioutsize) { - m_ioutsize = outcount * m_channels; - m_iout = (float *)realloc(m_iout, m_ioutsize * sizeof(float)); - } - for (sv_frame_t i = 0; i < incount; ++i) { - for (int c = 0; c < m_channels; ++c) { - m_iin[i * m_channels + c] = in[c][i]; - } - } - - sv_frame_t gen = resampleInterleaved(m_iin, m_iout, incount, ratio, final); - - for (sv_frame_t i = 0; i < gen; ++i) { - for (int c = 0; c < m_channels; ++c) { - out[c][i] = m_iout[i * m_channels + c]; - } - } - - return gen; -} - -sv_frame_t -Resampler::D::resampleInterleaved(float *in, float *out, - sv_frame_t incount, double ratio, - bool final) -{ - SRC_DATA data; - - sv_frame_t outcount = lrint(ceil(double(incount) * ratio)); - - data.data_in = in; - data.data_out = out; - data.input_frames = incount; - data.output_frames = outcount; - data.src_ratio = ratio; - data.end_of_input = (final ? 1 : 0); - - int err = src_process(m_src, &data); - - if (err) { - cerr << "Resampler: ERROR: src_process returned error: " << - src_strerror(err) << endl; - return 0; - } - - if (data.input_frames_used != incount) { - cerr << "Resampler: NOTE: input_frames_used == " << data.input_frames_used << " (while incount = " << incount << ")" << endl; - } - - return data.output_frames_gen; -} - -void -Resampler::D::reset() -{ - src_reset(m_src); -} - -Resampler::Resampler(Quality quality, int channels, sv_frame_t chunkSize) -{ - m_d = new D(quality, channels, chunkSize); -} - -Resampler::~Resampler() -{ - delete m_d; -} - -sv_frame_t -Resampler::resample(float **in, float **out, - sv_frame_t incount, double ratio, - bool final) -{ - return m_d->resample(in, out, incount, ratio, final); -} - -sv_frame_t -Resampler::resampleInterleaved(float *in, float *out, - sv_frame_t incount, double ratio, - bool final) -{ - return m_d->resampleInterleaved(in, out, incount, ratio, final); -} - -void -Resampler::reset() -{ - m_d->reset(); -} -
--- a/base/Resampler.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +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 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. -*/ - -/* - This is a modified version of a source file from the - Rubber Band audio timestretcher library. - This file copyright 2007 Chris Cannam. -*/ - -#ifndef _RESAMPLER_H_ -#define _RESAMPLER_H_ - -#include "BaseTypes.h" - -#include <sys/types.h> - -class Resampler -{ -public: - enum Quality { Best, FastestTolerable, Fastest }; - - Resampler(Quality quality, int channels, sv_frame_t chunkSize = 0); - ~Resampler(); - - sv_frame_t resample(float **in, float **out, - sv_frame_t incount, double ratio, - bool final = false); - - sv_frame_t resampleInterleaved(float *in, float *out, - sv_frame_t incount, double ratio, - bool final = false); - - void reset(); - -protected: - class D; - D *m_d; -}; - -#endif
--- a/base/ResizeableBitset.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +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 _RESIZEABLE_BITMAP_H_ -#define _RESIZEABLE_BITMAP_H_ - -#include <vector> -#include <stdint.h> -#include <stddef.h> -#include <stdlib.h> - -class ResizeableBitset { - -public: - ResizeableBitset() : m_bits(0), m_size(0) { - } - ResizeableBitset(size_t size) : m_bits(new std::vector<uint8_t>), m_size(size) { - m_bits->assign((size >> 3) + 1, 0); - } - ResizeableBitset(const ResizeableBitset &b) { - m_bits = new std::vector<uint8_t>(*b.m_bits); - } - ResizeableBitset &operator=(const ResizeableBitset &b) { - if (&b != this) return *this; - delete m_bits; - m_bits = new std::vector<uint8_t>(*b.m_bits); - return *this; - } - ~ResizeableBitset() { - delete m_bits; - } - - void resize(size_t size) { // retaining existing data; not thread safe - size_t bytes = (size >> 3) + 1; - if (m_bits && bytes == m_bits->size()) return; - std::vector<uint8_t> *newbits = new std::vector<uint8_t>(bytes); - newbits->assign(bytes, 0); - if (m_bits) { - for (size_t i = 0; i < bytes && i < m_bits->size(); ++i) { - (*newbits)[i] = (*m_bits)[i]; - } - delete m_bits; - } - m_bits = newbits; - m_size = size; - } - - bool get(size_t column) const { - return ((*m_bits)[column >> 3]) & (1u << (column & 0x07)); - } - - void set(size_t column) { - size_t ix = (column >> 3); - uint8_t prior = (*m_bits)[ix]; - uint8_t extra = ((1u << (column & 0x07)) & 0xff); - (*m_bits)[ix] = uint8_t(prior | extra); - } - - void reset(size_t column) { - ((*m_bits)[column >> 3]) &= uint8_t((~(1u << (column & 0x07))) & 0xff); - } - - void copy(size_t source, size_t dest) { - get(source) ? set(dest) : reset(dest); - } - - bool isAllOff() const { - for (size_t i = 0; i < m_bits->size(); ++i) { - if ((*m_bits)[i]) return false; - } - return true; - } - - bool isAllOn() const { - for (size_t i = 0; i + 1 < m_bits->size(); ++i) { - if ((*m_bits)[i] != 0xff) return false; - } - for (size_t i = (m_size / 8) * 8; i < m_size; ++i) { - if (!get(i)) return false; - } - return true; - } - - size_t size() const { - return m_size; - } - -private: - std::vector<uint8_t> *m_bits; - size_t m_size; -}; - - -#endif -
--- a/base/ResourceFinder.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/ResourceFinder.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -33,6 +33,7 @@ #include <cstdlib> #include <iostream> +#include <stdexcept> /** Resource files may be found in three places: @@ -126,6 +127,11 @@ static QString getNewStyleUserResourcePrefix() { + if (qApp->applicationName() == "") { + cerr << "ERROR: Can't use ResourceFinder before setting application name" << endl; + throw std::logic_error("Can't use ResourceFinder before setting application name"); + } + #if QT_VERSION >= 0x050000 // This is expected to be much more reliable than // getOldStyleUserResourcePrefix(), but it returns a different @@ -133,7 +139,7 @@ // fair enough). Hence migrateOldStyleResources() which moves // across any resources found in the old-style path the first time // we look for the new-style one - return QStandardPaths::writableLocation(QStandardPaths::DataLocation); + return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); #else return getOldStyleUserResourcePrefix(); #endif
--- a/base/Scavenger.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Scavenger.h Fri Jan 13 10:29:44 2017 +0000 @@ -26,7 +26,6 @@ #include <vector> #include <list> -#include <sys/time.h> #include <QMutex> #include <iostream>
--- a/base/StorageAdviser.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/StorageAdviser.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -22,7 +22,40 @@ #include <iostream> -//#define DEBUG_STORAGE_ADVISER 1 +QString +StorageAdviser::criteriaToString(int criteria) +{ + QStringList labels; + if (criteria & SpeedCritical) labels.push_back("SpeedCritical"); + if (criteria & PrecisionCritical) labels.push_back("PrecisionCritical"); + if (criteria & LongRetentionLikely) labels.push_back("LongRetentionLikely"); + if (criteria & FrequentLookupLikely) labels.push_back("FrequentLookupLikely"); + if (labels.empty()) return "None"; + else return labels.join("+"); +} + +QString +StorageAdviser::recommendationToString(int recommendation) +{ + QStringList labels; + if (recommendation & UseMemory) labels.push_back("UseMemory"); + if (recommendation & PreferMemory) labels.push_back("PreferMemory"); + if (recommendation & PreferDisc) labels.push_back("PreferDisc"); + if (recommendation & UseDisc) labels.push_back("UseDisc"); + if (recommendation & ConserveSpace) labels.push_back("ConserveSpace"); + if (recommendation & UseAsMuchAsYouLike) labels.push_back("UseAsMuchAsYouLike"); + if (labels.empty()) return "None"; + else return labels.join("+"); +} + +QString +StorageAdviser::storageStatusToString(StorageStatus status) +{ + if (status == Insufficient) return "Insufficient"; + if (status == Marginal) return "Marginal"; + if (status == Sufficient) return "Sufficient"; + return "Unknown"; +} size_t StorageAdviser::m_discPlanned = 0; size_t StorageAdviser::m_memoryPlanned = 0; @@ -35,13 +68,15 @@ size_t minimumSize, size_t maximumSize) { -#ifdef DEBUG_STORAGE_ADVISER - SVDEBUG << "StorageAdviser::recommend: Criteria " << criteria - << ", minimumSize " << minimumSize - << ", maximumSize " << maximumSize << endl; -#endif + SVDEBUG << "StorageAdviser::recommend: criteria " << criteria + << " (" + criteriaToString(criteria) + ")" + << ", minimumSize " << minimumSize + << ", maximumSize " << maximumSize << endl; if (m_baseRecommendation != NoRecommendation) { + SVDEBUG << "StorageAdviser::recommend: Returning fixed recommendation " + << m_baseRecommendation << " (" + << recommendationToString(m_baseRecommendation) << ")" << endl; return m_baseRecommendation; // for now } @@ -49,13 +84,24 @@ try { path = TempDirectory::getInstance()->getPath(); } catch (std::exception e) { - cerr << "StorageAdviser::recommend: ERROR: Failed to get temporary directory path: " << e.what() << endl; - return Recommendation(UseMemory | ConserveSpace); + SVDEBUG << "StorageAdviser::recommend: ERROR: Failed to get temporary directory path: " << e.what() << endl; + int r = UseMemory | ConserveSpace; + SVDEBUG << "StorageAdviser: returning fallback " << r + << " (" << recommendationToString(r) << ")" << endl; + return Recommendation(r); } ssize_t discFree = GetDiscSpaceMBAvailable(path.toLocal8Bit()); ssize_t memoryFree, memoryTotal; GetRealMemoryMBAvailable(memoryFree, memoryTotal); + SVDEBUG << "StorageAdviser: disc space: " << discFree + << "M, memory free: " << memoryFree + << "M, memory total: " << memoryTotal << "M" << endl; + SVDEBUG << "StorageAdviser: disc planned: " << (m_discPlanned / 1024) + << "K, memory planned: " << (m_memoryPlanned / 1024) << "K" << endl; + SVDEBUG << "StorageAdviser: min requested: " << minimumSize + << "K, max requested: " << maximumSize << "K" << endl; + if (discFree > ssize_t(m_discPlanned / 1024 + 1)) { discFree -= m_discPlanned / 1024 + 1; } else if (discFree > 0) { // can also be -1 for unknown @@ -68,22 +114,11 @@ memoryFree = 0; } -#ifdef DEBUG_STORAGE_ADVISER - cerr << "Disc space: " << discFree << ", memory free: " << memoryFree << ", memory total: " << memoryTotal << ", min " << minimumSize << ", max " << maximumSize << endl; -#endif - //!!! We have a potentially serious problem here if multiple //recommendations are made in advance of any of the resulting //allocations, as the allocations that have been recommended for //won't be taken into account in subsequent recommendations. - enum StorageStatus { - Unknown, - Insufficient, - Marginal, - Sufficient - }; - StorageStatus memoryStatus = Unknown; StorageStatus discStatus = Unknown; @@ -91,7 +126,7 @@ ssize_t maxmb = ssize_t(maximumSize / 1024 + 1); if (memoryFree == -1) memoryStatus = Unknown; - else if (memoryFree < memoryTotal / 3) memoryStatus = Insufficient; + else if (memoryFree < memoryTotal / 3 && memoryFree < 512) memoryStatus = Insufficient; else if (minmb > (memoryFree * 3) / 4) memoryStatus = Insufficient; else if (maxmb > (memoryFree * 3) / 4) memoryStatus = Marginal; else if (minmb > (memoryFree / 3)) memoryStatus = Marginal; @@ -105,10 +140,10 @@ else if (minmb > (discFree / 10)) discStatus = Marginal; else discStatus = Sufficient; -#ifdef DEBUG_STORAGE_ADVISER - cerr << "Memory status: " << memoryStatus << ", disc status " - << discStatus << endl; -#endif + SVDEBUG << "StorageAdviser: memory status: " << memoryStatus + << " (" << storageStatusToString(memoryStatus) << ")" + << ", disc status " << discStatus + << " (" << storageStatusToString(discStatus) << ")" << endl; int recommendation = NoRecommendation; @@ -181,6 +216,9 @@ } } + SVDEBUG << "StorageAdviser: returning recommendation " << recommendation + << " (" << recommendationToString(recommendation) << ")" << endl; + return Recommendation(recommendation); } @@ -189,8 +227,8 @@ { if (area == MemoryAllocation) m_memoryPlanned += size; else if (area == DiscAllocation) m_discPlanned += size; -// cerr << "storage planned up: memory: " << m_memoryPlanned << ", disc " -// << m_discPlanned << endl; + SVDEBUG << "StorageAdviser: storage planned up: now memory: " << m_memoryPlanned << ", disc " + << m_discPlanned << endl; } void @@ -203,8 +241,8 @@ if (m_discPlanned > size) m_discPlanned -= size; else m_discPlanned = 0; } -// cerr << "storage planned down: memory: " << m_memoryPlanned << ", disc " -// << m_discPlanned << endl; + SVDEBUG << "StorageAdviser: storage planned down: now memory: " << m_memoryPlanned << ", disc " + << m_discPlanned << endl; } void
--- a/base/StorageAdviser.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/StorageAdviser.h Fri Jan 13 10:29:44 2017 +0000 @@ -14,11 +14,13 @@ COPYING included with this distribution for more information. */ -#ifndef _STORAGE_ADVISER_H_ -#define _STORAGE_ADVISER_H_ +#ifndef SV_STORAGE_ADVISER_H +#define SV_STORAGE_ADVISER_H #include <cstdlib> +#include <QString> + /** * A utility class designed to help decide whether to store cache data * (for example FFT outputs) in memory or on disk in the TempDirectory. @@ -91,6 +93,17 @@ static size_t m_discPlanned; static size_t m_memoryPlanned; static Recommendation m_baseRecommendation; + + enum StorageStatus { + Unknown, + Insufficient, + Marginal, + Sufficient + }; + + static QString criteriaToString(int); + static QString recommendationToString(int); + static QString storageStatusToString(StorageStatus); }; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Strings.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,28 @@ +/* -*- 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 "Strings.h" + +QString +Strings::pi = QChar(0x3c0); + +QString +Strings::minus_pi = "-" + pi; + +QString +Strings::infinity = QChar(0x221e); + +QString +Strings::minus_infinity = "-" + infinity; +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/Strings.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,30 @@ +/* -*- 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_STRINGS_H +#define SV_STRINGS_H + +#include <QString> + +class Strings +{ +public: + static QString pi; + static QString minus_pi; + + static QString infinity; + static QString minus_infinity; +}; + +#endif
--- a/base/TempDirectory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/TempDirectory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -25,7 +25,7 @@ #include <iostream> #include <cassert> -#include <unistd.h> + #include <time.h> TempDirectory *
--- a/base/TempWriteFile.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/base/TempWriteFile.cpp Fri Jan 13 10:29:44 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 Nov 21 16:32:58 2016 +0000 +++ b/base/TempWriteFile.h Fri Jan 13 10:29:44 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/base/ViewManagerBase.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/ViewManagerBase.h Fri Jan 13 10:29:44 2017 +0000 @@ -21,6 +21,7 @@ #include "Selection.h" class AudioPlaySource; +class AudioRecordTarget; /** * Base class for ViewManager, with no GUI content. This should @@ -36,6 +37,7 @@ virtual ~ViewManagerBase(); virtual void setAudioPlaySource(AudioPlaySource *source) = 0; + virtual void setAudioRecordTarget(AudioRecordTarget *target) = 0; virtual sv_frame_t alignPlaybackFrameToReference(sv_frame_t) const = 0; virtual sv_frame_t alignReferenceToPlaybackFrame(sv_frame_t) const = 0;
--- a/base/Window.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/Window.h Fri Jan 13 10:29:44 2017 +0000 @@ -22,6 +22,9 @@ #include <map> #include <cstdlib> +#include <bqvec/VectorOps.h> +#include <bqvec/Allocators.h> + #include "system/System.h" enum WindowType { @@ -47,8 +50,12 @@ * than symmetrical. (A window of size N is equivalent to a * symmetrical window of size N+1 with the final element missing.) */ - Window(WindowType type, int size) : m_type(type), m_size(size) { encache(); } - Window(const Window &w) : m_type(w.m_type), m_size(w.m_size) { encache(); } + Window(WindowType type, int size) : m_type(type), m_size(size), m_cache(0) { + encache(); + } + Window(const Window &w) : m_type(w.m_type), m_size(w.m_size), m_cache(0) { + encache(); + } Window &operator=(const Window &w) { if (&w == this) return *this; m_type = w.m_type; @@ -56,11 +63,16 @@ encache(); return *this; } - virtual ~Window() { delete[] m_cache; } + virtual ~Window() { + breakfastquay::deallocate(m_cache); + } - void cut(T *src) const { cut(src, src); } - void cut(T *src, T *dst) const { - for (int i = 0; i < m_size; ++i) dst[i] = src[i] * m_cache[i]; + inline void cut(T *const BQ_R__ block) const { + breakfastquay::v_multiply(block, m_cache, m_size); + } + + inline void cut(const T *const BQ_R__ src, T *const BQ_R__ dst) const { + breakfastquay::v_multiply(dst, src, m_cache, m_size); } T getArea() { return m_area; } @@ -78,7 +90,7 @@ protected: WindowType m_type; int m_size; - T *m_cache; + T *BQ_R__ m_cache; T m_area; void encache(); @@ -88,41 +100,42 @@ template <typename T> void Window<T>::encache() { + if (!m_cache) m_cache = breakfastquay::allocate<T>(m_size); + const int n = m_size; - T *mult = new T[n]; + breakfastquay::v_set(m_cache, T(1.0), n); int i; - for (i = 0; i < n; ++i) mult[i] = 1.0; switch (m_type) { case RectangularWindow: for (i = 0; i < n; ++i) { - mult[i] *= T(0.5); + m_cache[i] *= T(0.5); } break; case BartlettWindow: for (i = 0; i < n/2; ++i) { - mult[i] *= T(i) / T(n/2); - mult[i + n/2] *= T(1.0) - T(i) / T(n/2); + m_cache[i] *= T(i) / T(n/2); + m_cache[i + n/2] *= T(1.0) - T(i) / T(n/2); } break; case HammingWindow: - cosinewin(mult, 0.54, 0.46, 0.0, 0.0); + cosinewin(m_cache, 0.54, 0.46, 0.0, 0.0); break; case HanningWindow: - cosinewin(mult, 0.50, 0.50, 0.0, 0.0); + cosinewin(m_cache, 0.50, 0.50, 0.0, 0.0); break; case BlackmanWindow: - cosinewin(mult, 0.42, 0.50, 0.08, 0.0); + cosinewin(m_cache, 0.42, 0.50, 0.08, 0.0); break; case GaussianWindow: for (i = 0; i < n; ++i) { - mult[i] *= T(pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2))); + m_cache[i] *= T(pow(2, - pow((i - (n-1)/2.0) / ((n-1)/2.0 / 3), 2))); } break; @@ -131,29 +144,27 @@ int N = n-1; for (i = 0; i < N/4; ++i) { T m = T(2 * pow(1.0 - (T(N)/2 - T(i)) / (T(N)/2), 3)); - mult[i] *= m; - mult[N-i] *= m; + m_cache[i] *= m; + m_cache[N-i] *= m; } for (i = N/4; i <= N/2; ++i) { int wn = i - N/2; T m = T(1.0 - 6 * pow(T(wn) / (T(N)/2), 2) * (1.0 - T(abs(wn)) / (T(N)/2))); - mult[i] *= m; - mult[N-i] *= m; + m_cache[i] *= m; + m_cache[N-i] *= m; } break; } case NuttallWindow: - cosinewin(mult, 0.3635819, 0.4891775, 0.1365995, 0.0106411); + cosinewin(m_cache, 0.3635819, 0.4891775, 0.1365995, 0.0106411); break; case BlackmanHarrisWindow: - cosinewin(mult, 0.35875, 0.48829, 0.14128, 0.01168); + cosinewin(m_cache, 0.35875, 0.48829, 0.14128, 0.01168); break; } - m_cache = mult; - m_area = 0; for (int i = 0; i < n; ++i) { m_area += m_cache[i];
--- a/base/ZoomConstraint.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/ZoomConstraint.h Fri Jan 13 10:29:44 2017 +0000 @@ -58,8 +58,11 @@ /** * Return the maximum zoom level within range for this constraint. + * This is quite large -- individual views will probably want to + * limit how far a user might reasonably zoom out based on other + * factors such as the duration of the file. */ - virtual int getMaxZoomLevel() const { return 262144; } + virtual int getMaxZoomLevel() const { return 4194304; } // 2^22, arbitrarily }; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestColumnOp.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,276 @@ +/* -*- 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_COLUMN_OP_H +#define TEST_COLUMN_OP_H + +#include "../ColumnOp.h" + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +//#define REPORT 1 + +using namespace std; + +class TestColumnOp : public QObject +{ + Q_OBJECT + + typedef ColumnOp C; + typedef ColumnOp::Column Column; + typedef vector<double> BinMapping; + +#ifdef REPORT + template <typename T> + void report(vector<T> v) { + cerr << "Vector is: [ "; + for (int i = 0; i < int(v.size()); ++i) { + if (i > 0) cerr << ", "; + cerr << v[i]; + } + cerr << " ]\n"; + } +#else + template <typename T> + void report(vector<T> ) { } +#endif + +private slots: + void applyGain() { + QCOMPARE(C::applyGain({}, 1.0), Column()); + Column c { 1, 2, 3, -4, 5, 6 }; + Column actual(C::applyGain(c, 1.5)); + Column expected { 1.5, 3, 4.5, -6, 7.5, 9 }; + QCOMPARE(actual, expected); + actual = C::applyGain(c, 1.0); + QCOMPARE(actual, c); + actual = C::applyGain(c, 0.0); + expected = { 0, 0, 0, 0, 0, 0 }; + QCOMPARE(actual, expected); + } + + void fftScale() { + QCOMPARE(C::fftScale({}, 2.0), Column()); + Column c { 1, 2, 3, -4, 5 }; + Column actual(C::fftScale(c, 8)); + Column expected { 0.25, 0.5, 0.75, -1, 1.25 }; + QCOMPARE(actual, expected); + } + + void isPeak_null() { + QVERIFY(!C::isPeak({}, 0)); + QVERIFY(!C::isPeak({}, 1)); + QVERIFY(!C::isPeak({}, -1)); + } + + void isPeak_obvious() { + Column c { 0.4, 0.5, 0.3 }; + QVERIFY(!C::isPeak(c, 0)); + QVERIFY(C::isPeak(c, 1)); + QVERIFY(!C::isPeak(c, 2)); + } + + void isPeak_edges() { + Column c { 0.5, 0.4, 0.3 }; + QVERIFY(C::isPeak(c, 0)); + QVERIFY(!C::isPeak(c, 1)); + QVERIFY(!C::isPeak(c, 2)); + QVERIFY(!C::isPeak(c, 3)); + QVERIFY(!C::isPeak(c, -1)); + c = { 1.4, 1.5 }; + QVERIFY(!C::isPeak(c, 0)); + QVERIFY(C::isPeak(c, 1)); + } + + void isPeak_flat() { + Column c { 0.0, 0.0, 0.0 }; + QVERIFY(C::isPeak(c, 0)); + QVERIFY(!C::isPeak(c, 1)); + QVERIFY(!C::isPeak(c, 2)); + } + + void isPeak_mixedSign() { + Column c { 0.4, -0.5, -0.3, -0.6, 0.1, -0.3 }; + QVERIFY(C::isPeak(c, 0)); + QVERIFY(!C::isPeak(c, 1)); + QVERIFY(C::isPeak(c, 2)); + QVERIFY(!C::isPeak(c, 3)); + QVERIFY(C::isPeak(c, 4)); + QVERIFY(!C::isPeak(c, 5)); + } + + void isPeak_duplicate() { + Column c({ 0.5, 0.5, 0.4, 0.4 }); + QVERIFY(C::isPeak(c, 0)); + QVERIFY(!C::isPeak(c, 1)); + QVERIFY(!C::isPeak(c, 2)); + QVERIFY(!C::isPeak(c, 3)); + c = { 0.4, 0.4, 0.5, 0.5 }; + QVERIFY(C::isPeak(c, 0)); // counterintuitive but necessary + QVERIFY(!C::isPeak(c, 1)); + QVERIFY(C::isPeak(c, 2)); + QVERIFY(!C::isPeak(c, 3)); + } + + void peakPick() { + QCOMPARE(C::peakPick({}), Column()); + Column c({ 0.5, 0.5, 0.4, 0.4 }); + QCOMPARE(C::peakPick(c), Column({ 0.5, 0.0, 0.0, 0.0 })); + c = Column({ 0.4, -0.5, -0.3, -0.6, 0.1, -0.3 }); + QCOMPARE(C::peakPick(c), Column({ 0.4, 0.0, -0.3, 0.0, 0.1, 0.0 })); + } + + void normalize_null() { + QCOMPARE(C::normalize({}, ColumnNormalization::None), Column()); + QCOMPARE(C::normalize({}, ColumnNormalization::Sum1), Column()); + QCOMPARE(C::normalize({}, ColumnNormalization::Max1), Column()); + QCOMPARE(C::normalize({}, ColumnNormalization::Hybrid), Column()); + } + + void normalize_none() { + Column c { 1, 2, 3, 4 }; + QCOMPARE(C::normalize(c, ColumnNormalization::None), c); + } + + void normalize_none_mixedSign() { + Column c { 1, 2, -3, -4 }; + QCOMPARE(C::normalize(c, ColumnNormalization::None), c); + } + + void normalize_sum1() { + Column c { 1, 2, 4, 3 }; + QCOMPARE(C::normalize(c, ColumnNormalization::Sum1), + Column({ 0.1, 0.2, 0.4, 0.3 })); + } + + void normalize_sum1_mixedSign() { + Column c { 1, 2, -4, -3 }; + QCOMPARE(C::normalize(c, ColumnNormalization::Sum1), + Column({ 0.1, 0.2, -0.4, -0.3 })); + } + + void normalize_max1() { + Column c { 4, 3, 2, 1 }; + QCOMPARE(C::normalize(c, ColumnNormalization::Max1), + Column({ 1.0, 0.75, 0.5, 0.25 })); + } + + void normalize_max1_mixedSign() { + Column c { -4, -3, 2, 1 }; + QCOMPARE(C::normalize(c, ColumnNormalization::Max1), + Column({ -1.0, -0.75, 0.5, 0.25 })); + } + + void normalize_hybrid() { + // with max == 99, log10(max+1) == 2 so scale factor will be 2/99 + Column c { 22, 44, 99, 66 }; + QCOMPARE(C::normalize(c, ColumnNormalization::Hybrid), + Column({ 44.0/99.0, 88.0/99.0, 2.0, 132.0/99.0 })); + } + + void normalize_hybrid_mixedSign() { + // with max == 99, log10(max+1) == 2 so scale factor will be 2/99 + Column c { 22, 44, -99, -66 }; + QCOMPARE(C::normalize(c, ColumnNormalization::Hybrid), + Column({ 44.0/99.0, 88.0/99.0, -2.0, -132.0/99.0 })); + } + + void distribute_simple() { + Column in { 1, 2, 3 }; + BinMapping binfory { 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 }; + Column expected { 1, 1, 2, 2, 3, 3 }; + Column actual(C::distribute(in, 6, binfory, 0, false)); + report(actual); + QCOMPARE(actual, expected); + } + + void distribute_simple_interpolated() { + Column in { 1, 2, 3 }; + BinMapping binfory { 0.0, 0.5, 1.0, 1.5, 2.0, 2.5 }; + // There is a 0.5-bin offset from the distribution you might + // expect, because this corresponds visually to the way that + // bin values are duplicated upwards in simple_distribution. + // It means that switching between interpolated and + // non-interpolated views retains the visual position of each + // bin peak as somewhere in the middle of the scale area for + // that bin. + Column expected { 1, 1, 1.5, 2, 2.5, 3 }; + Column actual(C::distribute(in, 6, binfory, 0, true)); + report(actual); + QCOMPARE(actual, expected); + } + + void distribute_nonlinear() { + Column in { 1, 2, 3 }; + BinMapping binfory { 0.0, 0.2, 0.5, 1.0, 2.0, 2.5 }; + Column expected { 1, 1, 1, 2, 3, 3 }; + Column actual(C::distribute(in, 6, binfory, 0, false)); + report(actual); + QCOMPARE(actual, expected); + } + + void distribute_nonlinear_interpolated() { + // See distribute_simple_interpolated + Column in { 1, 2, 3 }; + BinMapping binfory { 0.0, 0.2, 0.5, 1.0, 2.0, 2.5 }; + Column expected { 1, 1, 1, 1.5, 2.5, 3 }; + Column actual(C::distribute(in, 6, binfory, 0, true)); + report(actual); + QCOMPARE(actual, expected); + } + + void distribute_shrinking() { + Column in { 4, 1, 2, 3, 5, 6 }; + BinMapping binfory { 0.0, 2.0, 4.0 }; + Column expected { 4, 3, 6 }; + Column actual(C::distribute(in, 3, binfory, 0, false)); + report(actual); + QCOMPARE(actual, expected); + } + + void distribute_shrinking_interpolated() { + // should be same as distribute_shrinking, we don't + // interpolate when resizing down + Column in { 4, 1, 2, 3, 5, 6 }; + BinMapping binfory { 0.0, 2.0, 4.0 }; + Column expected { 4, 3, 6 }; + Column actual(C::distribute(in, 3, binfory, 0, true)); + report(actual); + QCOMPARE(actual, expected); + } + + void distribute_nonlinear_someshrinking_interpolated() { + // But we *should* interpolate if the mapping involves + // shrinking some bins but expanding others. See + // distribute_simple_interpolated for note on 0.5 offset + Column in { 4, 1, 2, 3, 5, 6 }; + BinMapping binfory { 0.0, 3.0, 4.0, 4.5 }; + Column expected { 4.0, 2.5, 4.0, 5.0 }; + Column actual(C::distribute(in, 4, binfory, 0, true)); + report(actual); + QCOMPARE(actual, expected); + binfory = BinMapping { 0.5, 1.0, 2.0, 5.0 }; + expected = { 4.0, 2.5, 1.5, 5.5 }; + actual = (C::distribute(in, 4, binfory, 0, true)); + report(actual); + QCOMPARE(actual, expected); + } +}; + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestOurRealTime.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,469 @@ +/* -*- 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_OUR_REALTIME_H +#define TEST_OUR_REALTIME_H + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +#include "../RealTime.h" + +using namespace std; + +#define ONE_MILLION 1000000 +#define ONE_BILLION 1000000000 + +class TestOurRealTime : public QObject +{ + Q_OBJECT + + void compareTexts(string s, const char *e) { + QCOMPARE(QString(s.c_str()), QString(e)); + } + + typedef sv_frame_t frame_type; + +private slots: + + void zero() + { + QCOMPARE(RealTime(0, 0), RealTime::zeroTime); + QCOMPARE(RealTime(0, 0).sec, 0); + QCOMPARE(RealTime(0, 0).nsec, 0); + QCOMPARE(RealTime(0, 0).msec(), 0); + QCOMPARE(RealTime(0, 0).usec(), 0); + } + + void ctor() + { + QCOMPARE(RealTime(0, 0), RealTime(0, 0)); + + // wraparounds + QCOMPARE(RealTime(0, ONE_BILLION/2), RealTime(1, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2), RealTime(-1, ONE_BILLION/2)); + + QCOMPARE(RealTime(1, ONE_BILLION), RealTime(2, 0)); + QCOMPARE(RealTime(1, -ONE_BILLION), RealTime(0, 0)); + QCOMPARE(RealTime(-1, ONE_BILLION), RealTime(0, 0)); + QCOMPARE(RealTime(-1, -ONE_BILLION), RealTime(-2, 0)); + + QCOMPARE(RealTime(1, -ONE_BILLION-ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(-1, ONE_BILLION+ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + + QCOMPARE(RealTime(2, -ONE_BILLION*2), RealTime(0, 0)); + QCOMPARE(RealTime(2, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime(-2, ONE_BILLION*2), RealTime(0, 0)); + QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 1).sec, 0); + QCOMPARE(RealTime(0, 1).nsec, 1); + QCOMPARE(RealTime(0, -1).sec, 0); + QCOMPARE(RealTime(0, -1).nsec, -1); + QCOMPARE(RealTime(1, -1).sec, 0); + QCOMPARE(RealTime(1, -1).nsec, ONE_BILLION-1); + QCOMPARE(RealTime(-1, 1).sec, 0); + QCOMPARE(RealTime(-1, 1).nsec, -ONE_BILLION+1); + QCOMPARE(RealTime(-1, -1).sec, -1); + QCOMPARE(RealTime(-1, -1).nsec, -1); + + QCOMPARE(RealTime(2, -ONE_BILLION*2).sec, 0); + QCOMPARE(RealTime(2, -ONE_BILLION*2).nsec, 0); + QCOMPARE(RealTime(2, -ONE_BILLION/2).sec, 1); + QCOMPARE(RealTime(2, -ONE_BILLION/2).nsec, ONE_BILLION/2); + + QCOMPARE(RealTime(-2, ONE_BILLION*2).sec, 0); + QCOMPARE(RealTime(-2, ONE_BILLION*2).nsec, 0); + QCOMPARE(RealTime(-2, ONE_BILLION/2).sec, -1); + QCOMPARE(RealTime(-2, ONE_BILLION/2).nsec, -ONE_BILLION/2); + } + + void fromSeconds() + { + QCOMPARE(RealTime::fromSeconds(0), RealTime(0, 0)); + + QCOMPARE(RealTime::fromSeconds(0.5).sec, 0); + QCOMPARE(RealTime::fromSeconds(0.5).nsec, ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(0.5).usec(), ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(0.5).msec(), 500); + + QCOMPARE(RealTime::fromSeconds(0.5), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromSeconds(1), RealTime(1, 0)); + QCOMPARE(RealTime::fromSeconds(1.5), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromSeconds(-0.5).sec, 0); + QCOMPARE(RealTime::fromSeconds(-0.5).nsec, -ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(-0.5).usec(), -ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(-0.5).msec(), -500); + + QCOMPARE(RealTime::fromSeconds(-1.5).sec, -1); + QCOMPARE(RealTime::fromSeconds(-1.5).nsec, -ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(-1.5).usec(), -ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(-1.5).msec(), -500); + + QCOMPARE(RealTime::fromSeconds(-0.5), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromSeconds(-1), RealTime(-1, 0)); + QCOMPARE(RealTime::fromSeconds(-1.5), RealTime(-1, -ONE_BILLION/2)); + } + + void fromMilliseconds() + { + QCOMPARE(RealTime::fromMilliseconds(0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMilliseconds(500), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromMilliseconds(1000), RealTime(1, 0)); + QCOMPARE(RealTime::fromMilliseconds(1500), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromMilliseconds(-0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMilliseconds(-500), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0)); + QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2)); + } + + void fromTimeval() + { + struct timeval tv; + + tv.tv_sec = 0; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, 0)); + tv.tv_sec = 0; tv.tv_usec = ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, ONE_BILLION/2)); + tv.tv_sec = 1; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, 0)); + tv.tv_sec = 1; tv.tv_usec = ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, ONE_BILLION/2)); + + tv.tv_sec = 0; tv.tv_usec = -ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, -ONE_BILLION/2)); + tv.tv_sec = -1; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, 0)); + tv.tv_sec = -1; tv.tv_usec = -ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, -ONE_BILLION/2)); + } + + void fromXsdDuration() + { + QCOMPARE(RealTime::fromXsdDuration("PT0"), RealTime::zeroTime); + QCOMPARE(RealTime::fromXsdDuration("PT0S"), RealTime::zeroTime); + QCOMPARE(RealTime::fromXsdDuration("PT10S"), RealTime(10, 0)); + QCOMPARE(RealTime::fromXsdDuration("PT10.5S"), RealTime(10, ONE_BILLION/2)); + QCOMPARE(RealTime::fromXsdDuration("PT1.5S").sec, 1); + QCOMPARE(RealTime::fromXsdDuration("PT1.5S").msec(), 500); + QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").sec, -1); + QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").msec(), -500); + QCOMPARE(RealTime::fromXsdDuration("PT1M30.5S"), RealTime(90, ONE_BILLION/2)); + QCOMPARE(RealTime::fromXsdDuration("PT1H2M30.5S"), RealTime(3750, ONE_BILLION/2)); + } + + void toDouble() + { + QCOMPARE(RealTime(0, 0).toDouble(), 0.0); + QCOMPARE(RealTime(0, ONE_BILLION/2).toDouble(), 0.5); + QCOMPARE(RealTime(1, 0).toDouble(), 1.0); + QCOMPARE(RealTime(1, ONE_BILLION/2).toDouble(), 1.5); + + QCOMPARE(RealTime(0, -ONE_BILLION/2).toDouble(), -0.5); + QCOMPARE(RealTime(-1, 0).toDouble(), -1.0); + QCOMPARE(RealTime(-1, -ONE_BILLION/2).toDouble(), -1.5); + } + + void assign() + { + RealTime r; + r = RealTime(0, 0); + QCOMPARE(r, RealTime::zeroTime); + r = RealTime(0, ONE_BILLION/2); + QCOMPARE(r.sec, 0); + QCOMPARE(r.nsec, ONE_BILLION/2); + r = RealTime(-1, -ONE_BILLION/2); + QCOMPARE(r.sec, -1); + QCOMPARE(r.nsec, -ONE_BILLION/2); + } + + void plus() + { + QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0)); + + QCOMPARE(RealTime(0, 0) + RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(0, ONE_BILLION/2), RealTime(1, 0)); + QCOMPARE(RealTime(1, 0) + RealTime(0, ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(-1, 0)); + QCOMPARE(RealTime(-1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + + QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(-1, 0), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(1, 0), RealTime(0, ONE_BILLION/2)); + } + + void minus() + { + QCOMPARE(RealTime(0, 0) - RealTime(0, 0), RealTime(0, 0)); + + QCOMPARE(RealTime(0, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(0, ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(1, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(-1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, 0)); + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(-1, 0), RealTime(1, ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(1, 0), RealTime(-1, -ONE_BILLION/2)); + } + + void negate() + { + QCOMPARE(-RealTime(0, 0), RealTime(0, 0)); + QCOMPARE(-RealTime(1, 0), RealTime(-1, 0)); + QCOMPARE(-RealTime(1, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + QCOMPARE(-RealTime(-1, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + } + + void compare() + { + int sec, nsec; + for (sec = -2; sec <= 2; sec += 2) { + for (nsec = -1; nsec <= 1; nsec += 1) { + QCOMPARE(RealTime(sec, nsec) < RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) > RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) == RealTime(sec, nsec), true); + QCOMPARE(RealTime(sec, nsec) != RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) <= RealTime(sec, nsec), true); + QCOMPARE(RealTime(sec, nsec) >= RealTime(sec, nsec), true); + } + } + RealTime prev(-3, 0); + for (sec = -2; sec <= 2; sec += 2) { + for (nsec = -1; nsec <= 1; nsec += 1) { + + RealTime curr(sec, nsec); + + QCOMPARE(prev < curr, true); + QCOMPARE(prev > curr, false); + QCOMPARE(prev == curr, false); + QCOMPARE(prev != curr, true); + QCOMPARE(prev <= curr, true); + QCOMPARE(prev >= curr, false); + + QCOMPARE(curr < prev, false); + QCOMPARE(curr > prev, true); + QCOMPARE(curr == prev, false); + QCOMPARE(curr != prev, true); + QCOMPARE(curr <= prev, false); + QCOMPARE(curr >= prev, true); + + prev = curr; + } + } + } + + void frame() + { + int frames[] = { + 0, 1, 2047, 2048, 6656, + 32767, 32768, 44100, 44101, + 999999999, 2000000000 + }; + int n = sizeof(frames)/sizeof(frames[0]); + + int rates[] = { + 1, 2, 8000, 22050, + 44100, 44101, 192000, 2000000001 + }; + int m = sizeof(rates)/sizeof(rates[0]); + + vector<vector<RealTime>> realTimes = { + { { 0, 0 }, { 1, 0 }, { 2047, 0 }, { 2048, 0 }, + { 6656, 0 }, { 32767, 0 }, { 32768, 0 }, { 44100, 0 }, + { 44101, 0 }, { 999999999, 0 }, { 2000000000, 0 } }, + { { 0, 0 }, { 0, 500000000 }, { 1023, 500000000 }, { 1024, 0 }, + { 3328, 0 }, { 16383, 500000000 }, { 16384, 0 }, { 22050, 0 }, + { 22050, 500000000 }, { 499999999, 500000000 }, { 1000000000, 0 } }, + { { 0, 0 }, { 0, 125000 }, { 0, 255875000 }, { 0, 256000000 }, + { 0, 832000000 }, { 4, 95875000 }, { 4, 96000000 }, { 5, 512500000 }, + { 5, 512625000 }, { 124999, 999875000 }, { 250000, 0 } }, + { { 0, 0 }, { 0, 45351 }, { 0, 92834467 }, { 0, 92879819 }, + { 0, 301859410 }, { 1, 486031746 }, { 1, 486077098 }, { 2, 0 }, + { 2, 45351 }, { 45351, 473877551 }, { 90702, 947845805 } }, + { { 0, 0 }, { 0, 22676 }, { 0, 46417234 }, { 0, 46439909 }, + { 0, 150929705 }, { 0, 743015873 }, { 0, 743038549 }, { 1, 0 }, + { 1, 22676 }, { 22675, 736938776 }, { 45351, 473922902 } }, + { { 0, 0 }, { 0, 22675 }, { 0, 46416181 }, { 0, 46438856 }, + { 0, 150926283 }, { 0, 742999025 }, { 0, 743021700 }, { 0, 999977325 }, + { 1, 0 }, { 22675, 222761389 }, { 45350, 445568128 } }, + { { 0, 0 }, { 0, 5208 }, { 0, 10661458 }, { 0, 10666667 }, + { 0, 34666667 }, { 0, 170661458 }, { 0, 170666667 }, { 0, 229687500 }, + { 0, 229692708 }, { 5208, 333328125 }, { 10416, 666666667 } }, + { { 0, 0 }, { 0, 0 }, { 0, 1023 }, { 0, 1024 }, + { 0, 3328 }, { 0, 16383 }, { 0, 16384 }, { 0, 22050 }, + { 0, 22050 }, { 0, 499999999 }, { 1, 0 } } + }; + + for (int i = 0; i < n; ++i) { + frame_type frame = frames[i]; + for (int j = 0; j < m; ++j) { + int rate = rates[j]; + + RealTime rt = RealTime::frame2RealTime(frame, rate); + QCOMPARE(rt.sec, realTimes[j][i].sec); + QCOMPARE(rt.nsec, realTimes[j][i].nsec); + + frame_type conv = RealTime::realTime2Frame(rt, rate); + + rt = RealTime::frame2RealTime(-frame, rate); + frame_type negconv = RealTime::realTime2Frame(rt, rate); + + if (rate > ONE_BILLION) { + // We don't have enough precision in RealTime + // for this absurd sample rate, so a round trip + // conversion may round + QVERIFY(abs(frame - conv) < 2); + QVERIFY(abs(-frame - negconv) < 2); + } else { + QCOMPARE(conv, frame); + QCOMPARE(negconv, -frame); + } + } + } + } + + // Our own RealTime has toMSText, toFrameText, toSecText + + void toText() + { + // we want to use QStrings, because then the Qt test library + // will print out any conflicts. The compareTexts function + // does this for us + + int halfSec = ONE_BILLION/2; // nsec + + RealTime rt = RealTime(0, 0); + compareTexts(rt.toMSText(false, false), "0"); + compareTexts(rt.toMSText(true, false), "0.000"); + compareTexts(rt.toMSText(false, true), "0"); + compareTexts(rt.toMSText(true, true), "0.000"); + compareTexts(rt.toFrameText(24, false), "0:00"); + compareTexts(rt.toFrameText(24, true), "0:00"); + compareTexts(rt.toSecText(), "0s"); + + rt = RealTime(1, halfSec); + compareTexts(rt.toMSText(false, false), "1.5"); + compareTexts(rt.toMSText(true, false), "1.500"); + compareTexts(rt.toMSText(false, true), "1.5"); + compareTexts(rt.toMSText(true, true), "1.500"); + compareTexts(rt.toFrameText(24, false), "1:12"); + compareTexts(rt.toFrameText(24, true), "1:12"); + compareTexts(rt.toFrameText(25, false), "1:12"); + compareTexts(rt.toFrameText(25, true), "1:12"); + compareTexts(rt.toSecText(), "1s"); + + rt = RealTime::fromSeconds(-1.5); + compareTexts(rt.toMSText(false, false), "-1.5"); + compareTexts(rt.toMSText(true, false), "-1.500"); + compareTexts(rt.toMSText(false, true), "-1.5"); + compareTexts(rt.toMSText(true, true), "-1.500"); + compareTexts(rt.toFrameText(24, false), "-1:12"); + compareTexts(rt.toFrameText(24, true), "-1:12"); + compareTexts(rt.toSecText(), "-1s"); + + rt = RealTime(1, 1000); + compareTexts(rt.toMSText(false, false), "1"); + compareTexts(rt.toFrameText(24, false), "1:00"); + compareTexts(rt.toFrameText(ONE_MILLION, false), "1:000001"); + compareTexts(rt.toSecText(), "1s"); + + rt = RealTime(1, 100000); + compareTexts(rt.toFrameText(ONE_MILLION, false), "1:000100"); + compareTexts(rt.toSecText(), "1s"); + + rt = RealTime::fromSeconds(60); + compareTexts(rt.toMSText(false, false), "60"); + compareTexts(rt.toMSText(true, false), "60.000"); + compareTexts(rt.toMSText(false, true), "1:00"); + compareTexts(rt.toMSText(true, true), "1:00.000"); + compareTexts(rt.toFrameText(24, false), "60:00"); + compareTexts(rt.toFrameText(24, true), "1:00:00"); + compareTexts(rt.toSecText(), "1:00"); + + rt = RealTime::fromSeconds(61.05); + compareTexts(rt.toMSText(false, false), "61.05"); + compareTexts(rt.toMSText(true, false), "61.050"); + compareTexts(rt.toMSText(false, true), "1:01.05"); + compareTexts(rt.toMSText(true, true), "1:01.050"); + compareTexts(rt.toFrameText(24, false), "61:01"); + compareTexts(rt.toFrameText(24, true), "1:01:01"); + compareTexts(rt.toSecText(), "1:01"); + + rt = RealTime::fromSeconds(601.05); + compareTexts(rt.toMSText(false, false), "601.05"); + compareTexts(rt.toMSText(true, false), "601.050"); + compareTexts(rt.toMSText(false, true), "10:01.05"); + compareTexts(rt.toMSText(true, true), "10:01.050"); + compareTexts(rt.toFrameText(24, false), "601:01"); + compareTexts(rt.toFrameText(24, true), "10:01:01"); + compareTexts(rt.toSecText(), "10:01"); + + rt = RealTime::fromSeconds(3600); + compareTexts(rt.toMSText(false, false), "3600"); + compareTexts(rt.toMSText(true, false), "3600.000"); + compareTexts(rt.toMSText(false, true), "1:00:00"); + compareTexts(rt.toMSText(true, true), "1:00:00.000"); + compareTexts(rt.toFrameText(24, false), "3600:00"); + compareTexts(rt.toFrameText(24, true), "1:00:00:00"); + compareTexts(rt.toSecText(), "1:00:00"); + + // For practical reasons our time display always rounds down + rt = RealTime(3599, ONE_BILLION-1); + compareTexts(rt.toMSText(false, false), "3599.999"); + compareTexts(rt.toMSText(true, false), "3599.999"); + compareTexts(rt.toMSText(false, true), "59:59.999"); + compareTexts(rt.toMSText(true, true), "59:59.999"); + compareTexts(rt.toFrameText(24, false), "3599:23"); + compareTexts(rt.toFrameText(24, true), "59:59:23"); + compareTexts(rt.toSecText(), "59:59"); + + rt = RealTime::fromSeconds(3600 * 4 + 60 * 5 + 3 + 0.01); + compareTexts(rt.toMSText(false, false), "14703.01"); + compareTexts(rt.toMSText(true, false), "14703.010"); + compareTexts(rt.toMSText(false, true), "4:05:03.01"); + compareTexts(rt.toMSText(true, true), "4:05:03.010"); + compareTexts(rt.toFrameText(24, false), "14703:00"); + compareTexts(rt.toFrameText(24, true), "4:05:03:00"); + compareTexts(rt.toSecText(), "4:05:03"); + + rt = RealTime::fromSeconds(-(3600 * 4 + 60 * 5 + 3 + 0.01)); + compareTexts(rt.toMSText(false, false), "-14703.01"); + compareTexts(rt.toMSText(true, false), "-14703.010"); + compareTexts(rt.toMSText(false, true), "-4:05:03.01"); + compareTexts(rt.toMSText(true, true), "-4:05:03.010"); + compareTexts(rt.toFrameText(24, false), "-14703:00"); + compareTexts(rt.toFrameText(24, true), "-4:05:03:00"); + compareTexts(rt.toSecText(), "-4:05:03"); + } +}; + +#endif +
--- a/base/test/TestRangeMapper.h Mon Nov 21 16:32:58 2016 +0000 +++ b/base/test/TestRangeMapper.h Fri Jan 13 10:29:44 2017 +0000 @@ -77,7 +77,7 @@ QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1)); QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8)); QCOMPARE(rm.getValueForPositionUnclamped(6), 3.0); - QCOMPARE(rm.getValueForPositionUnclamped(0), 0.0); + QCOMPARE(rm.getValueForPositionUnclamped(0) + 1.0, 0.0 + 1.0); QCOMPARE(rm.getValueForPositionUnclamped(-24), -12.0); QCOMPARE(rm.getValueForPositionUnclamped(12), 6.0); } @@ -93,7 +93,7 @@ QCOMPARE(rm.getValueForPosition(0), rm.getValueForPosition(1)); QCOMPARE(rm.getValueForPosition(9), rm.getValueForPosition(8)); QCOMPARE(rm.getValueForPositionUnclamped(3), 3.0); - QCOMPARE(rm.getValueForPositionUnclamped(9), 0.0); + QCOMPARE(rm.getValueForPositionUnclamped(9) + 1.0, 0.0 + 1.0); QCOMPARE(rm.getValueForPositionUnclamped(33), -12.0); QCOMPARE(rm.getValueForPositionUnclamped(-3), 6.0); }
--- a/base/test/TestRealTime.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,417 +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 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_REALTIME_H -#define TEST_REALTIME_H - -#include "../RealTime.h" - -#include <QObject> -#include <QtTest> -#include <QDir> - -#include <iostream> - -using namespace std; - -class TestRealTime : public QObject -{ - Q_OBJECT - - void compareTexts(string s, const char *e) { - QCOMPARE(QString(s.c_str()), QString(e)); - } - -private slots: - -#define ONE_MILLION 1000000 -#define ONE_BILLION 1000000000 - - void zero() - { - QCOMPARE(RealTime(0, 0), RealTime::zeroTime); - QCOMPARE(RealTime(0, 0).sec, 0); - QCOMPARE(RealTime(0, 0).nsec, 0); - QCOMPARE(RealTime(0, 0).msec(), 0); - QCOMPARE(RealTime(0, 0).usec(), 0); - } - - void ctor() - { - QCOMPARE(RealTime(0, 0), RealTime(0, 0)); - - // wraparounds - QCOMPARE(RealTime(0, ONE_BILLION/2), RealTime(1, -ONE_BILLION/2)); - QCOMPARE(RealTime(0, -ONE_BILLION/2), RealTime(-1, ONE_BILLION/2)); - - QCOMPARE(RealTime(1, ONE_BILLION), RealTime(2, 0)); - QCOMPARE(RealTime(1, -ONE_BILLION), RealTime(0, 0)); - QCOMPARE(RealTime(-1, ONE_BILLION), RealTime(0, 0)); - QCOMPARE(RealTime(-1, -ONE_BILLION), RealTime(-2, 0)); - - QCOMPARE(RealTime(2, -ONE_BILLION*2), RealTime(0, 0)); - QCOMPARE(RealTime(2, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); - - QCOMPARE(RealTime(-2, ONE_BILLION*2), RealTime(0, 0)); - QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); - - QCOMPARE(RealTime(0, 1).sec, 0); - QCOMPARE(RealTime(0, 1).nsec, 1); - QCOMPARE(RealTime(0, -1).sec, 0); - QCOMPARE(RealTime(0, -1).nsec, -1); - QCOMPARE(RealTime(1, -1).sec, 0); - QCOMPARE(RealTime(1, -1).nsec, ONE_BILLION-1); - QCOMPARE(RealTime(-1, 1).sec, 0); - QCOMPARE(RealTime(-1, 1).nsec, -ONE_BILLION+1); - QCOMPARE(RealTime(-1, -1).sec, -1); - QCOMPARE(RealTime(-1, -1).nsec, -1); - - QCOMPARE(RealTime(2, -ONE_BILLION*2).sec, 0); - QCOMPARE(RealTime(2, -ONE_BILLION*2).nsec, 0); - QCOMPARE(RealTime(2, -ONE_BILLION/2).sec, 1); - QCOMPARE(RealTime(2, -ONE_BILLION/2).nsec, ONE_BILLION/2); - - QCOMPARE(RealTime(-2, ONE_BILLION*2).sec, 0); - QCOMPARE(RealTime(-2, ONE_BILLION*2).nsec, 0); - QCOMPARE(RealTime(-2, ONE_BILLION/2).sec, -1); - QCOMPARE(RealTime(-2, ONE_BILLION/2).nsec, -ONE_BILLION/2); - } - - void fromSeconds() - { - QCOMPARE(RealTime::fromSeconds(0), RealTime(0, 0)); - - QCOMPARE(RealTime::fromSeconds(0.5).sec, 0); - QCOMPARE(RealTime::fromSeconds(0.5).nsec, ONE_BILLION/2); - QCOMPARE(RealTime::fromSeconds(0.5).usec(), ONE_MILLION/2); - QCOMPARE(RealTime::fromSeconds(0.5).msec(), 500); - - QCOMPARE(RealTime::fromSeconds(0.5), RealTime(0, ONE_BILLION/2)); - QCOMPARE(RealTime::fromSeconds(1), RealTime(1, 0)); - QCOMPARE(RealTime::fromSeconds(1.5), RealTime(1, ONE_BILLION/2)); - - QCOMPARE(RealTime::fromSeconds(-0.5).sec, 0); - QCOMPARE(RealTime::fromSeconds(-0.5).nsec, -ONE_BILLION/2); - QCOMPARE(RealTime::fromSeconds(-0.5).usec(), -ONE_MILLION/2); - QCOMPARE(RealTime::fromSeconds(-0.5).msec(), -500); - - QCOMPARE(RealTime::fromSeconds(-1.5).sec, -1); - QCOMPARE(RealTime::fromSeconds(-1.5).nsec, -ONE_BILLION/2); - QCOMPARE(RealTime::fromSeconds(-1.5).usec(), -ONE_MILLION/2); - QCOMPARE(RealTime::fromSeconds(-1.5).msec(), -500); - - QCOMPARE(RealTime::fromSeconds(-0.5), RealTime(0, -ONE_BILLION/2)); - QCOMPARE(RealTime::fromSeconds(-1), RealTime(-1, 0)); - QCOMPARE(RealTime::fromSeconds(-1.5), RealTime(-1, -ONE_BILLION/2)); - } - - void fromMilliseconds() - { - QCOMPARE(RealTime::fromMilliseconds(0), RealTime(0, 0)); - QCOMPARE(RealTime::fromMilliseconds(500), RealTime(0, ONE_BILLION/2)); - QCOMPARE(RealTime::fromMilliseconds(1000), RealTime(1, 0)); - QCOMPARE(RealTime::fromMilliseconds(1500), RealTime(1, ONE_BILLION/2)); - - QCOMPARE(RealTime::fromMilliseconds(-0), RealTime(0, 0)); - QCOMPARE(RealTime::fromMilliseconds(-500), RealTime(0, -ONE_BILLION/2)); - QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0)); - QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2)); - } - - void fromTimeval() - { - struct timeval tv; - - tv.tv_sec = 0; tv.tv_usec = 0; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, 0)); - tv.tv_sec = 0; tv.tv_usec = ONE_MILLION/2; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, ONE_BILLION/2)); - tv.tv_sec = 1; tv.tv_usec = 0; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, 0)); - tv.tv_sec = 1; tv.tv_usec = ONE_MILLION/2; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, ONE_BILLION/2)); - - tv.tv_sec = 0; tv.tv_usec = -ONE_MILLION/2; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, -ONE_BILLION/2)); - tv.tv_sec = -1; tv.tv_usec = 0; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, 0)); - tv.tv_sec = -1; tv.tv_usec = -ONE_MILLION/2; - QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, -ONE_BILLION/2)); - } - - void fromXsdDuration() - { - QCOMPARE(RealTime::fromXsdDuration("PT0"), RealTime::zeroTime); - QCOMPARE(RealTime::fromXsdDuration("PT0S"), RealTime::zeroTime); - QCOMPARE(RealTime::fromXsdDuration("PT10S"), RealTime(10, 0)); - QCOMPARE(RealTime::fromXsdDuration("PT10.5S"), RealTime(10, ONE_BILLION/2)); - QCOMPARE(RealTime::fromXsdDuration("PT1.5S").sec, 1); - QCOMPARE(RealTime::fromXsdDuration("PT1.5S").msec(), 500); - QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").sec, -1); - QCOMPARE(RealTime::fromXsdDuration("-PT1.5S").msec(), -500); - QCOMPARE(RealTime::fromXsdDuration("PT1M30.5S"), RealTime(90, ONE_BILLION/2)); - QCOMPARE(RealTime::fromXsdDuration("PT1H2M30.5S"), RealTime(3750, ONE_BILLION/2)); - } - - void toDouble() - { - QCOMPARE(RealTime(0, 0).toDouble(), 0.0); - QCOMPARE(RealTime(0, ONE_BILLION/2).toDouble(), 0.5); - QCOMPARE(RealTime(1, 0).toDouble(), 1.0); - QCOMPARE(RealTime(1, ONE_BILLION/2).toDouble(), 1.5); - - QCOMPARE(RealTime(0, -ONE_BILLION/2).toDouble(), -0.5); - QCOMPARE(RealTime(-1, 0).toDouble(), -1.0); - QCOMPARE(RealTime(-1, -ONE_BILLION/2).toDouble(), -1.5); - } - - void assign() - { - RealTime r; - r = RealTime(0, 0); - QCOMPARE(r, RealTime::zeroTime); - r = RealTime(0, ONE_BILLION/2); - QCOMPARE(r.toDouble(), 0.5); - r = RealTime(-1, -ONE_BILLION/2); - QCOMPARE(r.toDouble(), -1.5); - } - - void plus() - { - QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0)); - - QCOMPARE(RealTime(0, 0) + RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); - QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(0, ONE_BILLION/2), RealTime(1, 0)); - QCOMPARE(RealTime(1, 0) + RealTime(0, ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); - - QCOMPARE(RealTime(0, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); - QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(-1, 0)); - QCOMPARE(RealTime(-1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); - - QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); - QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); - QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); - - QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(-1, 0), RealTime(0, -ONE_BILLION/2)); - QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(1, 0), RealTime(0, ONE_BILLION/2)); - } - - void minus() - { - QCOMPARE(RealTime(0, 0) - RealTime(0, 0), RealTime(0, 0)); - - QCOMPARE(RealTime(0, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); - QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(0, ONE_BILLION/2), RealTime(0, 0)); - QCOMPARE(RealTime(1, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); - - QCOMPARE(RealTime(0, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); - QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); - QCOMPARE(RealTime(-1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); - - QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); - QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, 0)); - QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, ONE_BILLION/2)); - - QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(-1, 0), RealTime(1, ONE_BILLION/2)); - QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(1, 0), RealTime(-1, -ONE_BILLION/2)); - } - - void negate() - { - QCOMPARE(-RealTime(0, 0), RealTime(0, 0)); - QCOMPARE(-RealTime(1, 0), RealTime(-1, 0)); - QCOMPARE(-RealTime(1, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); - QCOMPARE(-RealTime(-1, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); - } - - void compare() - { - int sec, nsec; - for (sec = -2; sec <= 2; sec += 2) { - for (nsec = -1; nsec <= 1; nsec += 1) { - QCOMPARE(RealTime(sec, nsec) < RealTime(sec, nsec), false); - QCOMPARE(RealTime(sec, nsec) > RealTime(sec, nsec), false); - QCOMPARE(RealTime(sec, nsec) == RealTime(sec, nsec), true); - QCOMPARE(RealTime(sec, nsec) != RealTime(sec, nsec), false); - QCOMPARE(RealTime(sec, nsec) <= RealTime(sec, nsec), true); - QCOMPARE(RealTime(sec, nsec) >= RealTime(sec, nsec), true); - } - } - RealTime prev(-3, 0); - for (sec = -2; sec <= 2; sec += 2) { - for (nsec = -1; nsec <= 1; nsec += 1) { - - RealTime curr(sec, nsec); - - QCOMPARE(prev < curr, true); - QCOMPARE(prev > curr, false); - QCOMPARE(prev == curr, false); - QCOMPARE(prev != curr, true); - QCOMPARE(prev <= curr, true); - QCOMPARE(prev >= curr, false); - - QCOMPARE(curr < prev, false); - QCOMPARE(curr > prev, true); - QCOMPARE(curr == prev, false); - QCOMPARE(curr != prev, true); - QCOMPARE(curr <= prev, false); - QCOMPARE(curr >= prev, true); - - prev = curr; - } - } - } - - void frame() - { - int frames[] = { - 0, 1, 2047, 2048, 6656, 32767, 32768, 44100, 44101, 999999999 - }; - int n = sizeof(frames)/sizeof(frames[0]); - - int rates[] = { - 1, 2, 8000, 22050, 44100, 44101, 192000 - }; - int m = sizeof(rates)/sizeof(rates[0]); - - for (int i = 0; i < n; ++i) { - sv_frame_t frame = frames[i]; - for (int j = 0; j < m; ++j) { - int rate = rates[j]; - - RealTime rt = RealTime::frame2RealTime(frame, rate); - sv_frame_t conv = RealTime::realTime2Frame(rt, rate); - QCOMPARE(frame, conv); - - rt = RealTime::frame2RealTime(-frame, rate); - conv = RealTime::realTime2Frame(rt, rate); - QCOMPARE(-frame, conv); - } - } - } - - void toText() - { - // we want to use QStrings, because then the Qt test library - // will print out any conflicts. The compareTexts function - // does this for us - - int halfSec = ONE_BILLION/2; // nsec - - RealTime rt = RealTime(0, 0); - compareTexts(rt.toMSText(false, false), "0"); - compareTexts(rt.toMSText(true, false), "0.000"); - compareTexts(rt.toMSText(false, true), "0"); - compareTexts(rt.toMSText(true, true), "0.000"); - compareTexts(rt.toFrameText(24, false), "0:00"); - compareTexts(rt.toFrameText(24, true), "0:00"); - compareTexts(rt.toSecText(), "0s"); - - rt = RealTime(1, halfSec); - compareTexts(rt.toMSText(false, false), "1.5"); - compareTexts(rt.toMSText(true, false), "1.500"); - compareTexts(rt.toMSText(false, true), "1.5"); - compareTexts(rt.toMSText(true, true), "1.500"); - compareTexts(rt.toFrameText(24, false), "1:12"); - compareTexts(rt.toFrameText(24, true), "1:12"); - compareTexts(rt.toFrameText(25, false), "1:12"); - compareTexts(rt.toFrameText(25, true), "1:12"); - compareTexts(rt.toSecText(), "1s"); - - rt = RealTime::fromSeconds(-1.5); - compareTexts(rt.toMSText(false, false), "-1.5"); - compareTexts(rt.toMSText(true, false), "-1.500"); - compareTexts(rt.toMSText(false, true), "-1.5"); - compareTexts(rt.toMSText(true, true), "-1.500"); - compareTexts(rt.toFrameText(24, false), "-1:12"); - compareTexts(rt.toFrameText(24, true), "-1:12"); - compareTexts(rt.toSecText(), "-1s"); - - rt = RealTime(1, 1000); - compareTexts(rt.toMSText(false, false), "1"); - compareTexts(rt.toFrameText(24, false), "1:00"); - compareTexts(rt.toFrameText(ONE_MILLION, false), "1:000001"); - compareTexts(rt.toSecText(), "1s"); - - rt = RealTime(1, 100000); - compareTexts(rt.toFrameText(ONE_MILLION, false), "1:000100"); - compareTexts(rt.toSecText(), "1s"); - - rt = RealTime::fromSeconds(60); - compareTexts(rt.toMSText(false, false), "60"); - compareTexts(rt.toMSText(true, false), "60.000"); - compareTexts(rt.toMSText(false, true), "1:00"); - compareTexts(rt.toMSText(true, true), "1:00.000"); - compareTexts(rt.toFrameText(24, false), "60:00"); - compareTexts(rt.toFrameText(24, true), "1:00:00"); - compareTexts(rt.toSecText(), "1:00"); - - rt = RealTime::fromSeconds(61.05); - compareTexts(rt.toMSText(false, false), "61.05"); - compareTexts(rt.toMSText(true, false), "61.050"); - compareTexts(rt.toMSText(false, true), "1:01.05"); - compareTexts(rt.toMSText(true, true), "1:01.050"); - compareTexts(rt.toFrameText(24, false), "61:01"); - compareTexts(rt.toFrameText(24, true), "1:01:01"); - compareTexts(rt.toSecText(), "1:01"); - - rt = RealTime::fromSeconds(601.05); - compareTexts(rt.toMSText(false, false), "601.05"); - compareTexts(rt.toMSText(true, false), "601.050"); - compareTexts(rt.toMSText(false, true), "10:01.05"); - compareTexts(rt.toMSText(true, true), "10:01.050"); - compareTexts(rt.toFrameText(24, false), "601:01"); - compareTexts(rt.toFrameText(24, true), "10:01:01"); - compareTexts(rt.toSecText(), "10:01"); - - rt = RealTime::fromSeconds(3600); - compareTexts(rt.toMSText(false, false), "3600"); - compareTexts(rt.toMSText(true, false), "3600.000"); - compareTexts(rt.toMSText(false, true), "1:00:00"); - compareTexts(rt.toMSText(true, true), "1:00:00.000"); - compareTexts(rt.toFrameText(24, false), "3600:00"); - compareTexts(rt.toFrameText(24, true), "1:00:00:00"); - compareTexts(rt.toSecText(), "1:00:00"); - - // For practical reasons our time display always rounds down - rt = RealTime(3599, ONE_BILLION-1); - compareTexts(rt.toMSText(false, false), "3599.999"); - compareTexts(rt.toMSText(true, false), "3599.999"); - compareTexts(rt.toMSText(false, true), "59:59.999"); - compareTexts(rt.toMSText(true, true), "59:59.999"); - compareTexts(rt.toFrameText(24, false), "3599:23"); - compareTexts(rt.toFrameText(24, true), "59:59:23"); - compareTexts(rt.toSecText(), "59:59"); - - rt = RealTime::fromSeconds(3600 * 4 + 60 * 5 + 3 + 0.01); - compareTexts(rt.toMSText(false, false), "14703.01"); - compareTexts(rt.toMSText(true, false), "14703.010"); - compareTexts(rt.toMSText(false, true), "4:05:03.01"); - compareTexts(rt.toMSText(true, true), "4:05:03.010"); - compareTexts(rt.toFrameText(24, false), "14703:00"); - compareTexts(rt.toFrameText(24, true), "4:05:03:00"); - compareTexts(rt.toSecText(), "4:05:03"); - - rt = RealTime::fromSeconds(-(3600 * 4 + 60 * 5 + 3 + 0.01)); - compareTexts(rt.toMSText(false, false), "-14703.01"); - compareTexts(rt.toMSText(true, false), "-14703.010"); - compareTexts(rt.toMSText(false, true), "-4:05:03.01"); - compareTexts(rt.toMSText(true, true), "-4:05:03.010"); - compareTexts(rt.toFrameText(24, false), "-14703:00"); - compareTexts(rt.toFrameText(24, true), "-4:05:03:00"); - compareTexts(rt.toSecText(), "-4:05:03"); - } -}; - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/TestVampRealTime.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,383 @@ +/* -*- 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_VAMP_REALTIME_H + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +#include <vamp-hostsdk/RealTime.h> + +using namespace std; + +#define ONE_MILLION 1000000 +#define ONE_BILLION 1000000000 + +class TestVampRealTime : public QObject +{ + Q_OBJECT + + void compareTexts(string s, const char *e) { + QString actual(s.c_str()); + QString expected(e); + QCOMPARE(actual, expected); + } + + typedef Vamp::RealTime RealTime; + typedef long frame_type; + +private slots: + + void zero() + { + QCOMPARE(RealTime(0, 0), RealTime::zeroTime); + QCOMPARE(RealTime(0, 0).sec, 0); + QCOMPARE(RealTime(0, 0).nsec, 0); + QCOMPARE(RealTime(0, 0).msec(), 0); + QCOMPARE(RealTime(0, 0).usec(), 0); + } + + void ctor() + { + QCOMPARE(RealTime(0, 0), RealTime(0, 0)); + + // wraparounds + QCOMPARE(RealTime(0, ONE_BILLION/2), RealTime(1, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2), RealTime(-1, ONE_BILLION/2)); + + QCOMPARE(RealTime(1, ONE_BILLION), RealTime(2, 0)); + QCOMPARE(RealTime(1, -ONE_BILLION), RealTime(0, 0)); + QCOMPARE(RealTime(-1, ONE_BILLION), RealTime(0, 0)); + QCOMPARE(RealTime(-1, -ONE_BILLION), RealTime(-2, 0)); + + QCOMPARE(RealTime(2, -ONE_BILLION*2), RealTime(0, 0)); + QCOMPARE(RealTime(2, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime(-2, ONE_BILLION*2), RealTime(0, 0)); + QCOMPARE(RealTime(-2, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 1).sec, 0); + QCOMPARE(RealTime(0, 1).nsec, 1); + QCOMPARE(RealTime(0, -1).sec, 0); + QCOMPARE(RealTime(0, -1).nsec, -1); + QCOMPARE(RealTime(1, -1).sec, 0); + QCOMPARE(RealTime(1, -1).nsec, ONE_BILLION-1); + QCOMPARE(RealTime(-1, 1).sec, 0); + QCOMPARE(RealTime(-1, 1).nsec, -ONE_BILLION+1); + QCOMPARE(RealTime(-1, -1).sec, -1); + QCOMPARE(RealTime(-1, -1).nsec, -1); + + QCOMPARE(RealTime(2, -ONE_BILLION*2).sec, 0); + QCOMPARE(RealTime(2, -ONE_BILLION*2).nsec, 0); + QCOMPARE(RealTime(2, -ONE_BILLION/2).sec, 1); + QCOMPARE(RealTime(2, -ONE_BILLION/2).nsec, ONE_BILLION/2); + + QCOMPARE(RealTime(-2, ONE_BILLION*2).sec, 0); + QCOMPARE(RealTime(-2, ONE_BILLION*2).nsec, 0); + QCOMPARE(RealTime(-2, ONE_BILLION/2).sec, -1); + QCOMPARE(RealTime(-2, ONE_BILLION/2).nsec, -ONE_BILLION/2); + } + + void fromSeconds() + { + QCOMPARE(RealTime::fromSeconds(0), RealTime(0, 0)); + + QCOMPARE(RealTime::fromSeconds(0.5).sec, 0); + QCOMPARE(RealTime::fromSeconds(0.5).nsec, ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(0.5).usec(), ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(0.5).msec(), 500); + + QCOMPARE(RealTime::fromSeconds(0.5), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromSeconds(1), RealTime(1, 0)); + QCOMPARE(RealTime::fromSeconds(1.5), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromSeconds(-0.5).sec, 0); + QCOMPARE(RealTime::fromSeconds(-0.5).nsec, -ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(-0.5).usec(), -ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(-0.5).msec(), -500); + + QCOMPARE(RealTime::fromSeconds(-1.5).sec, -1); + QCOMPARE(RealTime::fromSeconds(-1.5).nsec, -ONE_BILLION/2); + QCOMPARE(RealTime::fromSeconds(-1.5).usec(), -ONE_MILLION/2); + QCOMPARE(RealTime::fromSeconds(-1.5).msec(), -500); + + QCOMPARE(RealTime::fromSeconds(-0.5), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromSeconds(-1), RealTime(-1, 0)); + QCOMPARE(RealTime::fromSeconds(-1.5), RealTime(-1, -ONE_BILLION/2)); + } + + void fromMilliseconds() + { + QCOMPARE(RealTime::fromMilliseconds(0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMilliseconds(500), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromMilliseconds(1000), RealTime(1, 0)); + QCOMPARE(RealTime::fromMilliseconds(1500), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromMilliseconds(-0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMilliseconds(-500), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0)); + QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2)); + } + +#ifndef Q_OS_WIN + void fromTimeval() + { + struct timeval tv; + + tv.tv_sec = 0; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, 0)); + tv.tv_sec = 0; tv.tv_usec = ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, ONE_BILLION/2)); + tv.tv_sec = 1; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, 0)); + tv.tv_sec = 1; tv.tv_usec = ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(1, ONE_BILLION/2)); + + tv.tv_sec = 0; tv.tv_usec = -ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(0, -ONE_BILLION/2)); + tv.tv_sec = -1; tv.tv_usec = 0; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, 0)); + tv.tv_sec = -1; tv.tv_usec = -ONE_MILLION/2; + QCOMPARE(RealTime::fromTimeval(tv), RealTime(-1, -ONE_BILLION/2)); + } +#endif + + void assign() + { + RealTime r; + r = RealTime(0, 0); + QCOMPARE(r, RealTime::zeroTime); + r = RealTime(0, ONE_BILLION/2); + QCOMPARE(r.sec, 0); + QCOMPARE(r.nsec, ONE_BILLION/2); + r = RealTime(-1, -ONE_BILLION/2); + QCOMPARE(r.sec, -1); + QCOMPARE(r.nsec, -ONE_BILLION/2); + } + + void plus() + { + QCOMPARE(RealTime(0, 0) + RealTime(0, 0), RealTime(0, 0)); + + QCOMPARE(RealTime(0, 0) + RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(0, ONE_BILLION/2), RealTime(1, 0)); + QCOMPARE(RealTime(1, 0) + RealTime(0, ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(-1, 0)); + QCOMPARE(RealTime(-1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(1, 0) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2) + RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + + QCOMPARE(RealTime(0, ONE_BILLION/2) + RealTime(-1, 0), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) + RealTime(1, 0), RealTime(0, ONE_BILLION/2)); + } + + void minus() + { + QCOMPARE(RealTime(0, 0) - RealTime(0, 0), RealTime(0, 0)); + + QCOMPARE(RealTime(0, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(0, ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(1, 0) - RealTime(0, ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(0, 0)); + QCOMPARE(RealTime(-1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(0, -ONE_BILLION/2)); + + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, 0)); + QCOMPARE(RealTime(1, 0) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2) - RealTime(0, -ONE_BILLION/2), RealTime(2, ONE_BILLION/2)); + + QCOMPARE(RealTime(0, ONE_BILLION/2) - RealTime(-1, 0), RealTime(1, ONE_BILLION/2)); + QCOMPARE(RealTime(0, -ONE_BILLION/2) - RealTime(1, 0), RealTime(-1, -ONE_BILLION/2)); + } + + void negate() + { + QCOMPARE(-RealTime(0, 0), RealTime(0, 0)); + QCOMPARE(-RealTime(1, 0), RealTime(-1, 0)); + QCOMPARE(-RealTime(1, ONE_BILLION/2), RealTime(-1, -ONE_BILLION/2)); + QCOMPARE(-RealTime(-1, -ONE_BILLION/2), RealTime(1, ONE_BILLION/2)); + } + + void compare() + { + int sec, nsec; + for (sec = -2; sec <= 2; sec += 2) { + for (nsec = -1; nsec <= 1; nsec += 1) { + QCOMPARE(RealTime(sec, nsec) < RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) > RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) == RealTime(sec, nsec), true); + QCOMPARE(RealTime(sec, nsec) != RealTime(sec, nsec), false); + QCOMPARE(RealTime(sec, nsec) <= RealTime(sec, nsec), true); + QCOMPARE(RealTime(sec, nsec) >= RealTime(sec, nsec), true); + } + } + RealTime prev(-3, 0); + for (sec = -2; sec <= 2; sec += 2) { + for (nsec = -1; nsec <= 1; nsec += 1) { + + RealTime curr(sec, nsec); + + QCOMPARE(prev < curr, true); + QCOMPARE(prev > curr, false); + QCOMPARE(prev == curr, false); + QCOMPARE(prev != curr, true); + QCOMPARE(prev <= curr, true); + QCOMPARE(prev >= curr, false); + + QCOMPARE(curr < prev, false); + QCOMPARE(curr > prev, true); + QCOMPARE(curr == prev, false); + QCOMPARE(curr != prev, true); + QCOMPARE(curr <= prev, false); + QCOMPARE(curr >= prev, true); + + prev = curr; + } + } + } + + void frame() + { + int frames[] = { + 0, 1, 2047, 2048, 6656, + 32767, 32768, 44100, 44101, + 999999999, 2000000000 + }; + int n = sizeof(frames)/sizeof(frames[0]); + + int rates[] = { + 1, 2, 8000, 22050, + 44100, 44101, 192000, 2000000001 + }; + int m = sizeof(rates)/sizeof(rates[0]); + + vector<vector<RealTime>> realTimes = { + { { 0, 0 }, { 1, 0 }, { 2047, 0 }, { 2048, 0 }, + { 6656, 0 }, { 32767, 0 }, { 32768, 0 }, { 44100, 0 }, + { 44101, 0 }, { 999999999, 0 }, { 2000000000, 0 } }, + { { 0, 0 }, { 0, 500000000 }, { 1023, 500000000 }, { 1024, 0 }, + { 3328, 0 }, { 16383, 500000000 }, { 16384, 0 }, { 22050, 0 }, + { 22050, 500000000 }, { 499999999, 500000000 }, { 1000000000, 0 } }, + { { 0, 0 }, { 0, 125000 }, { 0, 255875000 }, { 0, 256000000 }, + { 0, 832000000 }, { 4, 95875000 }, { 4, 96000000 }, { 5, 512500000 }, + { 5, 512625000 }, { 124999, 999875000 }, { 250000, 0 } }, + { { 0, 0 }, { 0, 45351 }, { 0, 92834467 }, { 0, 92879819 }, + { 0, 301859410 }, { 1, 486031746 }, { 1, 486077098 }, { 2, 0 }, + { 2, 45351 }, { 45351, 473877551 }, { 90702, 947845805 } }, + { { 0, 0 }, { 0, 22676 }, { 0, 46417234 }, { 0, 46439909 }, + { 0, 150929705 }, { 0, 743015873 }, { 0, 743038549 }, { 1, 0 }, + { 1, 22676 }, { 22675, 736938776 }, { 45351, 473922902 } }, + { { 0, 0 }, { 0, 22675 }, { 0, 46416181 }, { 0, 46438856 }, + { 0, 150926283 }, { 0, 742999025 }, { 0, 743021700 }, { 0, 999977325 }, + { 1, 0 }, { 22675, 222761389 }, { 45350, 445568128 } }, + { { 0, 0 }, { 0, 5208 }, { 0, 10661458 }, { 0, 10666667 }, + { 0, 34666667 }, { 0, 170661458 }, { 0, 170666667 }, { 0, 229687500 }, + { 0, 229692708 }, { 5208, 333328125 }, { 10416, 666666667 } }, + { { 0, 0 }, { 0, 0 }, { 0, 1023 }, { 0, 1024 }, + { 0, 3328 }, { 0, 16383 }, { 0, 16384 }, { 0, 22050 }, + { 0, 22050 }, { 0, 499999999 }, { 1, 0 } } + }; + + for (int i = 0; i < n; ++i) { + frame_type frame = frames[i]; + for (int j = 0; j < m; ++j) { + int rate = rates[j]; + + RealTime rt = RealTime::frame2RealTime(frame, rate); + QCOMPARE(rt.sec, realTimes[j][i].sec); + QCOMPARE(rt.nsec, realTimes[j][i].nsec); + + frame_type conv = RealTime::realTime2Frame(rt, rate); + + rt = RealTime::frame2RealTime(-frame, rate); + frame_type negconv = RealTime::realTime2Frame(rt, rate); + + if (rate > ONE_BILLION) { + // We don't have enough precision in RealTime + // for this absurd sample rate, so a round trip + // conversion may round + QVERIFY(abs(frame - conv) < 2); + QVERIFY(abs(-frame - negconv) < 2); + } else { + QCOMPARE(conv, frame); + QCOMPARE(negconv, -frame); + } + } + } + } + + // Vamp SDK version just has toText, which is like our own + // toMSText with true for its second arg + + void toText() + { + // we want to use QStrings, because then the Qt test library + // will print out any conflicts. The compareTexts function + // does this for us + + int halfSec = ONE_BILLION/2; // nsec + + RealTime rt = RealTime(0, 0); + compareTexts(rt.toText(false), "0"); + compareTexts(rt.toText(true), "0.000"); + + rt = RealTime(1, halfSec); + compareTexts(rt.toText(false), "1.5"); + compareTexts(rt.toText(true), "1.500"); + + rt = RealTime::fromSeconds(-1.5); + compareTexts(rt.toText(false), "-1.5"); + compareTexts(rt.toText(true), "-1.500"); + + rt = RealTime::fromSeconds(60); + compareTexts(rt.toText(false), "1:00"); + compareTexts(rt.toText(true), "1:00.000"); + + rt = RealTime::fromSeconds(61.05); + compareTexts(rt.toText(false), "1:01.05"); + compareTexts(rt.toText(true), "1:01.050"); + + rt = RealTime::fromSeconds(601.05); + compareTexts(rt.toText(false), "10:01.05"); + compareTexts(rt.toText(true), "10:01.050"); + + rt = RealTime::fromSeconds(3600); + compareTexts(rt.toText(false), "1:00:00"); + compareTexts(rt.toText(true), "1:00:00.000"); + + // For practical reasons our time display always rounds down + rt = RealTime(3599, ONE_BILLION-1); + compareTexts(rt.toText(false), "59:59.999"); + compareTexts(rt.toText(true), "59:59.999"); + + rt = RealTime::fromSeconds(3600 * 4 + 60 * 5 + 3 + 0.01); + compareTexts(rt.toText(false), "4:05:03.01"); + compareTexts(rt.toText(true), "4:05:03.010"); + + rt = RealTime::fromSeconds(-(3600 * 4 + 60 * 5 + 3 + 0.01)); + compareTexts(rt.toText(false), "-4:05:03.01"); + compareTexts(rt.toText(true), "-4:05:03.010"); + } +}; + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/files.pri Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,10 @@ +TEST_HEADERS = \ + TestRangeMapper.h \ + TestPitch.h \ + TestOurRealTime.h \ + TestVampRealTime.h \ + TestStringBits.h \ + TestColumnOp.h + +TEST_SOURCES += \ + svcore-base-test.cpp
--- a/base/test/main.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +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 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 "TestRangeMapper.h" -#include "TestPitch.h" -#include "TestRealTime.h" -#include "TestStringBits.h" - -#include <QtTest> - -#include <iostream> - -int main(int argc, char *argv[]) -{ - int good = 0, bad = 0; - - QCoreApplication app(argc, argv); - app.setOrganizationName("Sonic Visualiser"); - app.setApplicationName("test-svcore-base"); - - { - TestRangeMapper t; - if (QTest::qExec(&t, argc, argv) == 0) ++good; - else ++bad; - } - { - TestPitch t; - if (QTest::qExec(&t, argc, argv) == 0) ++good; - else ++bad; - } - { - TestRealTime t; - if (QTest::qExec(&t, argc, argv) == 0) ++good; - else ++bad; - } - { - TestStringBits t; - if (QTest::qExec(&t, argc, argv) == 0) ++good; - else ++bad; - } - - if (bad > 0) { - cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; - return 1; - } else { - cerr << "All tests passed" << endl; - return 0; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/test/svcore-base-test.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,71 @@ +/* -*- 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 "TestRangeMapper.h" +#include "TestPitch.h" +#include "TestStringBits.h" +#include "TestOurRealTime.h" +#include "TestVampRealTime.h" +#include "TestColumnOp.h" + +#include <QtTest> + +#include <iostream> + +int main(int argc, char *argv[]) +{ + int good = 0, bad = 0; + + QCoreApplication app(argc, argv); + app.setOrganizationName("Sonic Visualiser"); + app.setApplicationName("test-svcore-base"); + + { + TestRangeMapper t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestPitch t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestOurRealTime t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestVampRealTime t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestStringBits t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + { + TestColumnOp t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + if (bad > 0) { + cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; + return 1; + } else { + cerr << "All tests passed" << endl; + return 0; + } +}
--- a/base/test/test.pro Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ - -TEMPLATE = app - -LIBS += -L../.. -L../../../dataquay -L../../release -L../../../dataquay/release -lsvcore -ldataquay - -win32-g++ { - INCLUDEPATH += ../../../sv-dependency-builds/win32-mingw/include - LIBS += -L../../../sv-dependency-builds/win32-mingw/lib -} -win32-msvc* { - INCLUDEPATH += ../../../sv-dependency-builds/win32-msvc/include - LIBS += -L../../../sv-dependency-builds/win32-msvc/lib -} -mac* { - INCLUDEPATH += ../../../sv-dependency-builds/osx/include - LIBS += -L../../../sv-dependency-builds/osx/lib -} - -exists(../../config.pri) { - include(../../config.pri) -} - -!exists(../../config.pri) { - - CONFIG += release - DEFINES += NDEBUG BUILD_RELEASE NO_TIMING - - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO_2_0 - - LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0 - - win* { - LIBS += -llo -lwinmm -lws2_32 - } - macx* { - DEFINES += HAVE_COREAUDIO - LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate - } -} - -CONFIG += qt thread warn_on stl rtti exceptions console c++11 -QT += network xml testlib -QT -= gui - -TARGET = svcore-base-test - -DEPENDPATH += ../.. -INCLUDEPATH += ../.. -OBJECTS_DIR = o -MOC_DIR = o - -HEADERS += TestRangeMapper.h TestPitch.h TestRealTime.h TestStringBits.h -SOURCES += main.cpp - -win* { -//PRE_TARGETDEPS += ../../svcore.lib -} -!win* { -PRE_TARGETDEPS += ../../libsvcore.a -} - -!win32 { - !macx* { - QMAKE_POST_LINK=./$${TARGET} - } - macx* { - QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET} - } -} - -win32:QMAKE_POST_LINK=./release/$${TARGET}.exe -
--- a/configure.ac Mon Nov 21 16:32:58 2016 +0000 +++ b/configure.ac Fri Jan 13 10:29:44 2017 +0000 @@ -85,7 +85,7 @@ SV_MODULE_REQUIRED([samplerate],[samplerate >= 0.1.2],[samplerate.h],[samplerate],[src_new]) SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new]) -SV_MODULE_OPTIONAL([portaudio_2_0],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported]) +SV_MODULE_OPTIONAL([portaudio],[portaudio-2.0 >= 19],[portaudio.h],[portaudio],[Pa_IsFormatSupported]) SV_MODULE_OPTIONAL([JACK],[jack >= 0.100],[jack/jack.h],[jack],[jack_client_open]) SV_MODULE_OPTIONAL([libpulse],[libpulse >= 0.9],[pulse/pulseaudio.h],[pulse],[pa_stream_new]) SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init])
--- a/data/fft/FFTCacheReader.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +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-2009 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 _FFT_CACHE_READER_H_ -#define _FFT_CACHE_READER_H_ - -#include "FFTCacheStorageType.h" -#include <stddef.h> - -class FFTCacheReader -{ -public: - virtual ~FFTCacheReader() { } - - virtual int getWidth() const = 0; - virtual int getHeight() const = 0; - - virtual float getMagnitudeAt(int x, int y) const = 0; - virtual float getNormalizedMagnitudeAt(int x, int y) const = 0; - virtual float getMaximumMagnitudeAt(int x) const = 0; - virtual float getPhaseAt(int x, int y) const = 0; - - virtual void getValuesAt(int x, int y, float &real, float &imag) const = 0; - virtual void getMagnitudesAt(int x, float *values, int minbin, int count, int step) const = 0; - - virtual bool haveSetColumnAt(int x) const = 0; - - virtual FFTCache::StorageType getStorageType() const = 0; -}; - -#endif
--- a/data/fft/FFTCacheStorageType.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +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-2009 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 _FFT_CACHE_STORAGE_TYPE_H_ -#define _FFT_CACHE_STORAGE_TYPE_H_ - -namespace FFTCache { -enum StorageType { //!!! dup - Compact, // 16 bits normalized polar - Rectangular, // floating point real+imag - Polar // floating point mag+phase -}; -} - -#endif
--- a/data/fft/FFTCacheWriter.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +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-2009 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 _FFT_CACHE_WRITER_H_ -#define _FFT_CACHE_WRITER_H_ - -#include <stddef.h> - -class FFTCacheWriter -{ -public: - virtual ~FFTCacheWriter() { } - - virtual int getWidth() const = 0; - virtual int getHeight() const = 0; - - virtual void setColumnAt(int x, float *mags, float *phases, float factor) = 0; - virtual void setColumnAt(int x, float *reals, float *imags) = 0; - - virtual bool haveSetColumnAt(int x) const = 0; - - virtual void allColumnsWritten() = 0; // notify cache to close - - virtual FFTCache::StorageType getStorageType() const = 0; -}; - -#endif -
--- a/data/fft/FFTDataServer.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1601 +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 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. -*/ - -#include "FFTDataServer.h" - -#include "FFTFileCacheReader.h" -#include "FFTFileCacheWriter.h" -#include "FFTMemoryCache.h" - -#include "model/DenseTimeValueModel.h" - -#include "system/System.h" - -#include "base/StorageAdviser.h" -#include "base/Exceptions.h" -#include "base/Profiler.h" -#include "base/Thread.h" // for debug mutex locker - -#include <QWriteLocker> - -#include <stdexcept> - -//#define DEBUG_FFT_SERVER 1 -//#define DEBUG_FFT_SERVER_FILL 1 - -#ifdef DEBUG_FFT_SERVER_FILL -#ifndef DEBUG_FFT_SERVER -#define DEBUG_FFT_SERVER 1 -#endif -#endif - - -FFTDataServer::ServerMap FFTDataServer::m_servers; -FFTDataServer::ServerQueue FFTDataServer::m_releasedServers; -QMutex FFTDataServer::m_serverMapMutex; - -FFTDataServer * -FFTDataServer::getInstance(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria, - sv_frame_t fillFromFrame) -{ - QString n = generateFileBasename(model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar); - - FFTDataServer *server = 0; - - MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getInstance::m_serverMapMutex"); - - if ((server = findServer(n))) { - return server; - } - - QString npn = generateFileBasename(model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - !polar); - - if ((server = findServer(npn))) { - return server; - } - - try { - server = new FFTDataServer(n, - model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar, - criteria, - fillFromFrame); - } catch (InsufficientDiscSpace) { - delete server; - server = 0; - } - - if (server) { - m_servers[n] = ServerCountPair(server, 1); - } - - return server; -} - -FFTDataServer * -FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria, - sv_frame_t fillFromFrame) -{ - // Fuzzy matching: - // - // -- if we're asked for polar and have non-polar, use it (and - // vice versa). This one is vital, and we do it for non-fuzzy as - // well (above). - // - // -- if we're asked for an instance with a given fft size and we - // have one already with a multiple of that fft size but the same - // window size and type (and model), we can draw the results from - // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the - // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the - // same window plus zero padding). - // - // -- if we're asked for an instance with a given window type and - // size and fft size and we have one already the same but with a - // smaller increment, we can draw the results from it (provided - // our increment is a multiple of its) - // - // The FFTModel knows how to interpret these things. In - // both cases we require that the larger one is a power-of-two - // multiple of the smaller (e.g. even though in principle you can - // draw the results at increment 256 from those at increment 768 - // or 1536, the model doesn't support this). - - { - MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getFuzzyInstance::m_serverMapMutex"); - - ServerMap::iterator best = m_servers.end(); - int bestdist = -1; - - for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { - - FFTDataServer *server = i->second.first; - - if (server->getModel() == model && - (server->getChannel() == channel || model->getChannelCount() == 1) && - server->getWindowType() == windowType && - server->getWindowSize() == windowSize && - server->getWindowIncrement() <= windowIncrement && - server->getFFTSize() >= fftSize) { - - if ((windowIncrement % server->getWindowIncrement()) != 0) continue; - int ratio = windowIncrement / server->getWindowIncrement(); - bool poweroftwo = true; - while (ratio > 1) { - if (ratio & 0x1) { - poweroftwo = false; - break; - } - ratio >>= 1; - } - if (!poweroftwo) continue; - - if ((server->getFFTSize() % fftSize) != 0) continue; - ratio = server->getFFTSize() / fftSize; - while (ratio > 1) { - if (ratio & 0x1) { - poweroftwo = false; - break; - } - ratio >>= 1; - } - if (!poweroftwo) continue; - - int distance = 0; - - if (server->getPolar() != polar) distance += 1; - - distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15; - distance += ((server->getFFTSize() / fftSize) - 1) * 10; - - if (server->getFillCompletion() < 50) distance += 100; - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << std::endl; -#endif - - if (bestdist == -1 || distance < bestdist) { - bestdist = distance; - best = i; - } - } - } - - if (bestdist >= 0) { - FFTDataServer *server = best->second.first; -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << std::endl; -#endif - claimInstance(server, false); - return server; - } - } - - // Nothing found, make a new one - - return getInstance(model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar, - criteria, - fillFromFrame); -} - -FFTDataServer * -FFTDataServer::findServer(QString n) -{ -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::findServer(\"" << n << "\")" << std::endl; -#endif - - if (m_servers.find(n) != m_servers.end()) { - - FFTDataServer *server = m_servers[n].first; - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::findServer(\"" << n << "\"): found " << server << std::endl; -#endif - - claimInstance(server, false); - - return server; - } - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::findServer(\"" << n << "\"): not found" << std::endl; -#endif - - return 0; -} - -void -FFTDataServer::claimInstance(FFTDataServer *server) -{ - claimInstance(server, true); -} - -void -FFTDataServer::claimInstance(FFTDataServer *server, bool needLock) -{ - MutexLocker locker(needLock ? &m_serverMapMutex : 0, - "FFTDataServer::claimInstance::m_serverMapMutex"); - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::claimInstance(" << server << ")" << std::endl; -#endif - - for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { - if (i->second.first == server) { - - for (ServerQueue::iterator j = m_releasedServers.begin(); - j != m_releasedServers.end(); ++j) { - - if (*j == server) { -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::claimInstance: found in released server list, removing from it" << std::endl; -#endif - m_releasedServers.erase(j); - break; - } - } - - ++i->second.second; - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::claimInstance: new refcount is " << i->second.second << std::endl; -#endif - - return; - } - } - - cerr << "ERROR: FFTDataServer::claimInstance: instance " - << server << " unknown!" << endl; -} - -void -FFTDataServer::releaseInstance(FFTDataServer *server) -{ - releaseInstance(server, true); -} - -void -FFTDataServer::releaseInstance(FFTDataServer *server, bool needLock) -{ - MutexLocker locker(needLock ? &m_serverMapMutex : 0, - "FFTDataServer::releaseInstance::m_serverMapMutex"); - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl; -#endif - - // -- if ref count > 0, decrement and return - // -- if the instance hasn't been used at all, delete it immediately - // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts, - // leave them hanging around - // -- if N instances with zero refcounts remain, delete the one that - // was last released first - // -- if we run out of disk space when allocating an instance, go back - // and delete the spare N instances before trying again - // -- have an additional method to indicate that a model has been - // destroyed, so that we can delete all of its fft server instances - - for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { - if (i->second.first == server) { - if (i->second.second == 0) { - cerr << "ERROR: FFTDataServer::releaseInstance(" - << server << "): instance not allocated" << endl; - } else if (--i->second.second == 0) { -/*!!! - if (server->m_lastUsedCache == -1) { // never used -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::releaseInstance: instance " - << server << " has never been used, erasing" - << std::endl; -#endif - delete server; - m_servers.erase(i); - } else { -*/ -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::releaseInstance: instance " - << server << " no longer in use, marking for possible collection" - << std::endl; -#endif - bool found = false; - for (ServerQueue::iterator j = m_releasedServers.begin(); - j != m_releasedServers.end(); ++j) { - if (*j == server) { - cerr << "ERROR: FFTDataServer::releaseInstance(" - << server << "): server is already in " - << "released servers list" << endl; - found = true; - } - } - if (!found) m_releasedServers.push_back(server); - server->suspend(); - purgeLimbo(); -//!!! } - } else { -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::releaseInstance: instance " - << server << " now has refcount " << i->second.second - << std::endl; -#endif - } - return; - } - } - - cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): " - << "instance not found" << endl; -} - -void -FFTDataServer::purgeLimbo(int maxSize) -{ -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): " - << m_releasedServers.size() << " candidates" << std::endl; -#endif - - while (int(m_releasedServers.size()) > maxSize) { - - FFTDataServer *server = *m_releasedServers.begin(); - - bool found = false; - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::purgeLimbo: considering candidate " - << server << std::endl; -#endif - - for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { - - if (i->second.first == server) { - found = true; - if (i->second.second > 0) { - cerr << "ERROR: FFTDataServer::purgeLimbo: Server " - << server << " is in released queue, but still has non-zero refcount " - << i->second.second << endl; - // ... so don't delete it - break; - } -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::purgeLimbo: looks OK, erasing it" - << std::endl; -#endif - - m_servers.erase(i); - delete server; - break; - } - } - - if (!found) { - cerr << "ERROR: FFTDataServer::purgeLimbo: Server " - << server << " is in released queue, but not in server map!" - << endl; - delete server; - } - - m_releasedServers.pop_front(); - } - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): " - << m_releasedServers.size() << " remain" << std::endl; -#endif - -} - -void -FFTDataServer::modelAboutToBeDeleted(Model *model) -{ - MutexLocker locker(&m_serverMapMutex, - "FFTDataServer::modelAboutToBeDeleted::m_serverMapMutex"); - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::modelAboutToBeDeleted(" << model << ")" - << std::endl; -#endif - - for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { - - FFTDataServer *server = i->second.first; - - if (server->getModel() == model) { - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::modelAboutToBeDeleted: server is " - << server << std::endl; -#endif - - if (i->second.second > 0) { - cerr << "WARNING: FFTDataServer::modelAboutToBeDeleted: Model " << model << " (\"" << model->objectName() << "\") is about to be deleted, but is still being referred to by FFT server " << server << " with non-zero refcount " << i->second.second << endl; - server->suspendWrites(); - return; - } - for (ServerQueue::iterator j = m_releasedServers.begin(); - j != m_releasedServers.end(); ++j) { - if (*j == server) { -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << std::endl; -#endif - m_releasedServers.erase(j); - break; - } - } -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing server" << std::endl; -#endif - m_servers.erase(i); - delete server; - return; - } - } -} - -FFTDataServer::FFTDataServer(QString fileBaseName, - const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria, - sv_frame_t fillFromFrame) : - m_fileBaseName(fileBaseName), - m_model(model), - m_channel(channel), - m_windower(windowType, windowSize), - m_windowSize(windowSize), - m_windowIncrement(windowIncrement), - m_fftSize(fftSize), - m_polar(polar), - m_width(0), - m_height(0), - m_cacheWidth(0), - m_cacheWidthPower(0), - m_cacheWidthMask(0), - m_criteria(criteria), - m_fftInput(0), - m_exiting(false), - m_suspended(true), //!!! or false? - m_fillThread(0) -{ -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << endl; -#endif - - //!!! end is not correct until model finished reading -- what to do??? - - sv_frame_t start = m_model->getStartFrame(); - sv_frame_t end = m_model->getEndFrame(); - - m_width = int((end - start) / m_windowIncrement) + 1; - m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2 - -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << "): dimensions are " - << m_width << "x" << m_height << endl; -#endif - - int maxCacheSize = 20 * 1024 * 1024; - int columnSize = int(m_height * sizeof(fftsample) * 2 + sizeof(fftsample)); - if (m_width < ((maxCacheSize * 2) / columnSize)) m_cacheWidth = m_width; - else m_cacheWidth = maxCacheSize / columnSize; - -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << "): cache width nominal " - << m_cacheWidth << ", actual "; -#endif - - int bits = 0; - while (m_cacheWidth > 1) { m_cacheWidth >>= 1; ++bits; } - m_cacheWidthPower = bits + 1; - m_cacheWidth = 2; - while (bits) { m_cacheWidth <<= 1; --bits; } - m_cacheWidthMask = m_cacheWidth - 1; - -#ifdef DEBUG_FFT_SERVER - cerr << m_cacheWidth << " (power " << m_cacheWidthPower << ", mask " - << m_cacheWidthMask << ")" << endl; -#endif - - if (m_criteria == StorageAdviser::NoCriteria) { - - // assume "spectrogram" criteria for polar ffts, and "feature - // extraction" criteria for rectangular ones. - - if (m_polar) { - m_criteria = StorageAdviser::Criteria - (StorageAdviser::SpeedCritical | - StorageAdviser::LongRetentionLikely); - } else { - m_criteria = StorageAdviser::Criteria - (StorageAdviser::PrecisionCritical); - } - } - - for (int i = 0; i <= m_width / m_cacheWidth; ++i) { - m_caches.push_back(0); - } - - m_fftInput = (fftsample *) - fftf_malloc(fftSize * sizeof(fftsample)); - - m_fftOutput = (fftf_complex *) - fftf_malloc((fftSize/2 + 1) * sizeof(fftf_complex)); - - m_workbuffer = (float *) - fftf_malloc((fftSize+2) * sizeof(float)); - - m_fftPlan = fftf_plan_dft_r2c_1d(m_fftSize, - m_fftInput, - m_fftOutput, - FFTW_MEASURE); - - if (!m_fftPlan) { - cerr << "ERROR: fftf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << endl; - throw(0); - } - - m_fillThread = new FillThread(*this, fillFromFrame); -} - -FFTDataServer::~FFTDataServer() -{ -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << endl; -#endif - - m_suspended = false; - m_exiting = true; - m_condition.wakeAll(); - if (m_fillThread) { - m_fillThread->wait(); - delete m_fillThread; - } - -// MutexLocker locker(&m_writeMutex, -// "FFTDataServer::~FFTDataServer::m_writeMutex"); - - QMutexLocker mlocker(&m_fftBuffersLock); - QWriteLocker wlocker(&m_cacheVectorLock); - - for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { - if (*i) { - delete *i; - } - } - - deleteProcessingData(); -} - -void -FFTDataServer::deleteProcessingData() -{ -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << endl; -#endif - if (m_fftInput) { - fftf_destroy_plan(m_fftPlan); - fftf_free(m_fftInput); - fftf_free(m_fftOutput); - fftf_free(m_workbuffer); - } - m_fftInput = 0; -} - -void -FFTDataServer::suspend() -{ -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << endl; -#endif - Profiler profiler("FFTDataServer::suspend", false); - - QMutexLocker locker(&m_fftBuffersLock); - m_suspended = true; -} - -void -FFTDataServer::suspendWrites() -{ -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << endl; -#endif - Profiler profiler("FFTDataServer::suspendWrites", false); - - m_suspended = true; -} - -void -FFTDataServer::resume() -{ -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << endl; -#endif - Profiler profiler("FFTDataServer::resume", false); - - m_suspended = false; - if (m_fillThread) { - if (m_fillThread->isFinished()) { - delete m_fillThread; - m_fillThread = 0; - deleteProcessingData(); - } else if (!m_fillThread->isRunning()) { - m_fillThread->start(); - } else { - m_condition.wakeAll(); - } - } -} - -void -FFTDataServer::getStorageAdvice(int w, int h, - bool &memoryCache, bool &compactCache) -{ - if (w < 0 || h < 0) throw std::domain_error("width & height must be non-negative"); - size_t cells = size_t(w) * h; - size_t minimumSize = (cells / 1024) * sizeof(uint16_t); // kb - size_t maximumSize = (cells / 1024) * sizeof(float); // kb - - // We don't have a compact rectangular representation, and compact - // of course is never precision-critical - - bool canCompact = true; - if ((m_criteria & StorageAdviser::PrecisionCritical) || !m_polar) { - canCompact = false; - minimumSize = maximumSize; // don't use compact - } - - StorageAdviser::Recommendation recommendation; - - try { - - recommendation = - StorageAdviser::recommend(m_criteria, minimumSize, maximumSize); - - } catch (InsufficientDiscSpace s) { - - // Delete any unused servers we may have been leaving around - // in case we wanted them again - - purgeLimbo(0); - - // This time we don't catch InsufficientDiscSpace -- we - // haven't allocated anything yet and can safely let the - // exception out to indicate to the caller that we can't - // handle it. - - recommendation = - StorageAdviser::recommend(m_criteria, minimumSize, maximumSize); - } - -// cerr << "Recommendation was: " << recommendation << endl; - - memoryCache = false; - - if ((recommendation & StorageAdviser::UseMemory) || - (recommendation & StorageAdviser::PreferMemory)) { - memoryCache = true; - } - - compactCache = canCompact && - (recommendation & StorageAdviser::ConserveSpace); - -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer: memory cache = " << memoryCache << ", compact cache = " << compactCache << endl; - - cerr << "Width " << w << " of " << m_width << ", height " << h << ", size " << w * h << endl; -#endif -} - -bool -FFTDataServer::makeCache(int c) -{ - // Creating the cache could take a significant amount of time. We - // don't want to block readers on m_cacheVectorLock while this is - // happening, but we do want to block any further calls to - // makeCache. So we use this lock solely to serialise this - // particular function -- it isn't used anywhere else. - - QMutexLocker locker(&m_cacheCreationMutex); - - m_cacheVectorLock.lockForRead(); - if (m_caches[c]) { - // someone else must have created the cache between our - // testing for it and taking the mutex - m_cacheVectorLock.unlock(); - return true; - } - m_cacheVectorLock.unlock(); - - // Now m_cacheCreationMutex is held, but m_cacheVectorLock is not - // -- readers can proceed, but callers to this function will block - - CacheBlock *cb = new CacheBlock; - - QString name = QString("%1-%2").arg(m_fileBaseName).arg(c); - - int width = m_cacheWidth; - if (c * m_cacheWidth + width > m_width) { - width = m_width - c * m_cacheWidth; - } - - bool memoryCache = false; - bool compactCache = false; - - getStorageAdvice(width, m_height, memoryCache, compactCache); - - bool success = false; - - if (memoryCache) { - - try { - - cb->memoryCache = new FFTMemoryCache - (compactCache ? FFTCache::Compact : - m_polar ? FFTCache::Polar : - FFTCache::Rectangular, - width, m_height); - - success = true; - - } catch (std::bad_alloc) { - - delete cb->memoryCache; - cb->memoryCache = 0; - - cerr << "WARNING: Memory allocation failed when creating" - << " FFT memory cache no. " << c << " of " << width - << "x" << m_height << " (of total width " << m_width - << "): falling back to disc cache" << endl; - - memoryCache = false; - } - } - - if (!memoryCache) { - - try { - - cb->fileCacheWriter = new FFTFileCacheWriter - (name, - compactCache ? FFTCache::Compact : - m_polar ? FFTCache::Polar : - FFTCache::Rectangular, - width, m_height); - - success = true; - - } catch (std::exception &e) { - - delete cb->fileCacheWriter; - cb->fileCacheWriter = 0; - - cerr << "ERROR: Failed to construct disc cache for FFT data: " - << e.what() << endl; - - throw; - } - } - - m_cacheVectorLock.lockForWrite(); - - m_caches[c] = cb; - - m_cacheVectorLock.unlock(); - - return success; -} - -bool -FFTDataServer::makeCacheReader(int c) -{ - // preconditions: m_caches[c] exists and contains a file writer; - // m_cacheVectorLock is not locked by this thread -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::makeCacheReader(" << c << ")" << std::endl; -#endif - - QThread *me = QThread::currentThread(); - QWriteLocker locker(&m_cacheVectorLock); - CacheBlock *cb(m_caches.at(c)); - if (!cb || !cb->fileCacheWriter) return false; - - try { - - cb->fileCacheReader[me] = new FFTFileCacheReader(cb->fileCacheWriter); - - } catch (std::exception &e) { - - delete cb->fileCacheReader[me]; - cb->fileCacheReader.erase(me); - - cerr << "ERROR: Failed to construct disc cache reader for FFT data: " - << e.what() << endl; - return false; - } - - // erase a reader that looks like it may no longer going to be - // used by this thread for a while (leaving alone the current - // and previous cache readers) - int deleteCandidate = c - 2; - if (deleteCandidate < 0) deleteCandidate = c + 2; - if (deleteCandidate >= (int)m_caches.size()) { - return true; - } - - cb = m_caches.at(deleteCandidate); - if (cb && cb->fileCacheReader.find(me) != cb->fileCacheReader.end()) { -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << std::endl; -#endif - delete cb->fileCacheReader[me]; - cb->fileCacheReader.erase(me); - } - - return true; -} - -float -FFTDataServer::getMagnitudeAt(int x, int y) -{ - Profiler profiler("FFTDataServer::getMagnitudeAt", false); - - if (x >= m_width || y >= m_height) return 0; - - float val = 0; - - try { - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return 0; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getMagnitudeAt: filling"); -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn(" - << x << ")" << std::endl; -#endif - fillColumn(x); - } - - val = cache->getMagnitudeAt(col, y); - - } catch (std::exception &e) { - m_error = e.what(); - } - - return val; -} - -bool -FFTDataServer::getMagnitudesAt(int x, float *values, int minbin, int count, int step) -{ - Profiler profiler("FFTDataServer::getMagnitudesAt", false); - - if (x >= m_width) return false; - - if (minbin >= m_height) minbin = m_height - 1; - if (count == 0) count = (m_height - minbin) / step; - else if (minbin + count * step > m_height) { - count = (m_height - minbin) / step; - } - - try { - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return false; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getMagnitudesAt: filling"); - fillColumn(x); - } - - cache->getMagnitudesAt(col, values, minbin, count, step); - - } catch (std::exception &e) { - m_error = e.what(); - return false; - } - - return true; -} - -float -FFTDataServer::getNormalizedMagnitudeAt(int x, int y) -{ - Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false); - - if (x >= m_width || y >= m_height) return 0; - - float val = 0; - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return 0; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt: filling"); - fillColumn(x); - } - val = cache->getNormalizedMagnitudeAt(col, y); - - } catch (std::exception &e) { - m_error = e.what(); - } - - return val; -} - -bool -FFTDataServer::getNormalizedMagnitudesAt(int x, float *values, int minbin, int count, int step) -{ - Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt", false); - - if (x >= m_width) return false; - - if (minbin >= m_height) minbin = m_height - 1; - if (count == 0) count = (m_height - minbin) / step; - else if (minbin + count * step > m_height) { - count = (m_height - minbin) / step; - } - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return false; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt: filling"); - fillColumn(x); - } - - for (int i = 0; i < count; ++i) { - values[i] = cache->getNormalizedMagnitudeAt(col, i * step + minbin); - } - - } catch (std::exception &e) { - m_error = e.what(); - return false; - } - - return true; -} - -float -FFTDataServer::getMaximumMagnitudeAt(int x) -{ - Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false); - - if (x >= m_width) return 0; - - float val = 0; - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return 0; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getMaximumMagnitudeAt: filling"); - fillColumn(x); - } - val = cache->getMaximumMagnitudeAt(col); - - } catch (std::exception &e) { - m_error = e.what(); - } - - return val; -} - -float -FFTDataServer::getPhaseAt(int x, int y) -{ - Profiler profiler("FFTDataServer::getPhaseAt", false); - - if (x >= m_width || y >= m_height) return 0; - - float val = 0; - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return 0; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getPhaseAt: filling"); - fillColumn(x); - } - val = cache->getPhaseAt(col, y); - - } catch (std::exception &e) { - m_error = e.what(); - } - - return val; -} - -bool -FFTDataServer::getPhasesAt(int x, float *values, int minbin, int count, int step) -{ - Profiler profiler("FFTDataServer::getPhasesAt", false); - - if (x >= m_width) return false; - - if (minbin >= m_height) minbin = m_height - 1; - if (count == 0) count = (m_height - minbin) / step; - else if (minbin + count * step > m_height) { - count = (m_height - minbin) / step; - } - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return false; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getPhasesAt: filling"); - fillColumn(x); - } - - for (int i = 0; i < count; ++i) { - values[i] = cache->getPhaseAt(col, i * step + minbin); - } - - } catch (std::exception &e) { - m_error = e.what(); - return false; - } - - return true; -} - -void -FFTDataServer::getValuesAt(int x, int y, float &real, float &imaginary) -{ - Profiler profiler("FFTDataServer::getValuesAt", false); - - if (x >= m_width || y >= m_height) { - real = 0; - imaginary = 0; - return; - } - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - - if (!cache) { - real = 0; - imaginary = 0; - return; - } - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") { - real = 0; - imaginary = 0; - return; - } - Profiler profiler("FFTDataServer::getValuesAt: filling"); -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl; -#endif - fillColumn(x); - } - - cache->getValuesAt(col, y, real, imaginary); - - } catch (std::exception &e) { - m_error = e.what(); - } -} - -bool -FFTDataServer::getValuesAt(int x, float *reals, float *imaginaries, int minbin, int count, int step) -{ - Profiler profiler("FFTDataServer::getValuesAt", false); - - if (x >= m_width) return false; - - if (minbin >= m_height) minbin = m_height - 1; - if (count == 0) count = (m_height - minbin) / step; - else if (minbin + count * step > m_height) { - count = (m_height - minbin) / step; - } - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return false; - - if (!cache->haveSetColumnAt(col)) { - if (getError() != "") return false; - Profiler profiler("FFTDataServer::getValuesAt: filling"); - fillColumn(x); - } - - for (int i = 0; i < count; ++i) { - cache->getValuesAt(col, i * step + minbin, reals[i], imaginaries[i]); - } - - } catch (std::exception &e) { - m_error = e.what(); - return false; - } - - return true; -} - -bool -FFTDataServer::isColumnReady(int x) -{ - Profiler profiler("FFTDataServer::isColumnReady", false); - - if (x >= m_width) return true; - - if (!haveCache(x)) { -/*!!! - if (m_lastUsedCache == -1) { - if (m_suspended) { - std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl; - resume(); - } - m_fillThread->start(); - } -*/ - return false; - } - - try { - - int col; - FFTCacheReader *cache = getCacheReader(x, col); - if (!cache) return true; - - return cache->haveSetColumnAt(col); - - } catch (std::exception &e) { - m_error = e.what(); - return false; - } -} - -void -FFTDataServer::fillColumn(int x) -{ - Profiler profiler("FFTDataServer::fillColumn", false); - - if (!m_model->isReady()) { - cerr << "WARNING: FFTDataServer::fillColumn(" - << x << "): model not yet ready" << endl; - return; - } -/* - if (!m_fftInput) { - cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " - << "input has already been completed and discarded?" - << endl; - return; - } -*/ - if (x >= m_width) { - cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " - << "x > width (" << x << " > " << m_width << ")" - << endl; - return; - } - - int col; -#ifdef DEBUG_FFT_SERVER_FILL - cout << "FFTDataServer::fillColumn(" << x << ")" << endl; -#endif - FFTCacheWriter *cache = getCacheWriter(x, col); - if (!cache) return; - - int winsize = m_windowSize; - int fftsize = m_fftSize; - int hs = fftsize/2; - - sv_frame_t pfx = 0; - int off = (fftsize - winsize) / 2; - - sv_frame_t startFrame = m_windowIncrement * sv_frame_t(x); - sv_frame_t endFrame = startFrame + m_windowSize; - - // FFT windows are centred at the respective audio sample frame, - // so the first one is centred at 0 - startFrame -= winsize / 2; - endFrame -= winsize / 2; - -#ifdef DEBUG_FFT_SERVER_FILL - std::cerr << "FFTDataServer::fillColumn: requesting frames " - << startFrame + pfx << " -> " << endFrame << " ( = " - << endFrame - (startFrame + pfx) << ") at index " - << off + pfx << " in buffer of size " << m_fftSize - << " with window size " << m_windowSize - << " from channel " << m_channel << std::endl; -#endif - - QMutexLocker locker(&m_fftBuffersLock); - - // We may have been called from a function that wanted to obtain a - // column using an FFTCacheReader. Before calling us, it checked - // whether the column was available already, and the reader - // reported that it wasn't. Now we test again, with the mutex - // held, to avoid a race condition in case another thread has - // called fillColumn at the same time. - if (cache->haveSetColumnAt(x & m_cacheWidthMask)) { - return; - } - - if (!m_fftInput) { - cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " - << "input has already been completed and discarded?" - << endl; - return; - } - - for (int i = 0; i < off; ++i) { - m_fftInput[i] = 0.0; - } - - for (int i = 0; i < off; ++i) { - m_fftInput[fftsize - i - 1] = 0.0; - } - - if (startFrame < 0) { - pfx = -startFrame; - for (int i = 0; i < pfx; ++i) { - m_fftInput[off + i] = 0.0; - } - } - - sv_frame_t count = 0; - if (endFrame > startFrame + pfx) count = endFrame - (startFrame + pfx); - - sv_frame_t got = m_model->getData(m_channel, startFrame + pfx, - count, m_fftInput + off + pfx); - - while (got + pfx < winsize) { - m_fftInput[off + got + pfx] = 0.0; - ++got; - } - - if (m_channel == -1) { - int channels = m_model->getChannelCount(); - if (channels > 1) { - for (int i = 0; i < winsize; ++i) { - m_fftInput[off + i] /= float(channels); - } - } - } - - m_windower.cut(m_fftInput + off); - - for (int i = 0; i < hs; ++i) { - fftsample temp = m_fftInput[i]; - m_fftInput[i] = m_fftInput[i + hs]; - m_fftInput[i + hs] = temp; - } - - fftf_execute(m_fftPlan); - - float factor = 0.f; - - if (cache->getStorageType() == FFTCache::Compact || - cache->getStorageType() == FFTCache::Polar) { - - for (int i = 0; i <= hs; ++i) { - fftsample real = m_fftOutput[i][0]; - fftsample imag = m_fftOutput[i][1]; - float mag = sqrtf(real * real + imag * imag); - m_workbuffer[i] = mag; - m_workbuffer[i + hs + 1] = atan2f(imag, real); - if (mag > factor) factor = mag; - } - - } else { - - for (int i = 0; i <= hs; ++i) { - m_workbuffer[i] = m_fftOutput[i][0]; - m_workbuffer[i + hs + 1] = m_fftOutput[i][1]; - } - } - - Profiler subprof("FFTDataServer::fillColumn: set to cache"); - - if (cache->getStorageType() == FFTCache::Compact || - cache->getStorageType() == FFTCache::Polar) { - - cache->setColumnAt(col, - m_workbuffer, - m_workbuffer + hs + 1, - factor); - - } else { - - cache->setColumnAt(col, - m_workbuffer, - m_workbuffer + hs + 1); - } - - if (m_suspended) { -// std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::endl; -// resume(); - } -} - -void -FFTDataServer::fillComplete() -{ - for (int i = 0; i < int(m_caches.size()); ++i) { - if (!m_caches[i]) continue; - if (m_caches[i]->memoryCache) { - m_caches[i]->memoryCache->allColumnsWritten(); - } - if (m_caches[i]->fileCacheWriter) { - m_caches[i]->fileCacheWriter->allColumnsWritten(); - } - } -} - -QString -FFTDataServer::getError() const -{ - QString err; - if (m_error != "") { - err = m_error; -// cerr << "FFTDataServer::getError: err (server " << this << ") = " << err << endl; - } else { - MutexLocker locker(&m_fftBuffersLock, "FFTDataServer::getError"); - if (m_fillThread) { - err = m_fillThread->getError(); -// cerr << "FFTDataServer::getError: err (server " << this << ", from thread " << m_fillThread -// << ") = " << err << endl; - } - } - return err; -} - -int -FFTDataServer::getFillCompletion() const -{ - if (m_fillThread) return m_fillThread->getCompletion(); - else return 100; -} - -sv_frame_t -FFTDataServer::getFillExtent() const -{ - if (m_fillThread) return m_fillThread->getExtent(); - else return m_model->getEndFrame(); -} - -QString -FFTDataServer::generateFileBasename() const -{ - return generateFileBasename(m_model, m_channel, m_windower.getType(), - m_windowSize, m_windowIncrement, m_fftSize, - m_polar); -} - -QString -FFTDataServer::generateFileBasename(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar) -{ - return QString("%1-%2-%3-%4-%5-%6%7") - .arg(XmlExportable::getObjectExportId(model)) - .arg(channel + 1) - .arg((int)windowType) - .arg(windowSize) - .arg(windowIncrement) - .arg(fftSize) - .arg(polar ? "-p" : "-r"); -} - -void -FFTDataServer::FillThread::run() -{ -#ifdef DEBUG_FFT_SERVER_FILL - std::cerr << "FFTDataServer::FillThread::run()" << std::endl; -#endif - - m_extent = 0; - m_completion = 0; - - while (!m_server.m_model->isReady() && !m_server.m_exiting) { -#ifdef DEBUG_FFT_SERVER_FILL - std::cerr << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << std::endl; -#endif - sleep(1); - } - if (m_server.m_exiting) return; - - sv_frame_t start = m_server.m_model->getStartFrame(); - sv_frame_t end = m_server.m_model->getEndFrame(); - sv_frame_t remainingEnd = end; - - int counter = 0; - int updateAt = 1; - int maxUpdateAt = int(end / m_server.m_windowIncrement) / 20; - if (maxUpdateAt < 100) maxUpdateAt = 100; - - if (m_fillFrom > start) { - - for (sv_frame_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) { - - try { - m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); - } catch (std::exception &e) { - MutexLocker locker(&m_server.m_fftBuffersLock, - "FFTDataServer::run::m_fftBuffersLock [err]"); - m_threadError = e.what(); - std::cerr << "FFTDataServer::FillThread::run: exception: " << m_threadError << " (thread = " << this << " from server " << &m_server << ")" << std::endl; - m_server.fillComplete(); - m_completion = 100; - m_extent = end; - return; - } - - if (m_server.m_exiting) return; - - while (m_server.m_suspended) { -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << endl; -#endif - MutexLocker locker(&m_server.m_fftBuffersLock, - "FFTDataServer::run::m_fftBuffersLock [1]"); - if (m_server.m_suspended && !m_server.m_exiting) { - m_server.m_condition.wait(&m_server.m_fftBuffersLock, 10000); - } -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << endl; -#endif - if (m_server.m_exiting) return; - } - - if (++counter == updateAt) { - m_extent = f; - m_completion = int(100 * fabsf(float(f - m_fillFrom) / - float(end - start))); - counter = 0; - if (updateAt < maxUpdateAt) { - updateAt *= 2; - if (updateAt > maxUpdateAt) updateAt = maxUpdateAt; - } - } - } - - remainingEnd = m_fillFrom; - if (remainingEnd > start) --remainingEnd; - else remainingEnd = start; - } - - int baseCompletion = m_completion; - - for (sv_frame_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) { - - try { - m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); - } catch (std::exception &e) { - MutexLocker locker(&m_server.m_fftBuffersLock, - "FFTDataServer::run::m_fftBuffersLock [err]"); - m_threadError = e.what(); - std::cerr << "FFTDataServer::FillThread::run: exception: " << m_threadError << " (thread = " << this << " from server " << &m_server << ")" << std::endl; - m_server.fillComplete(); - m_completion = 100; - m_extent = end; - return; - } - - if (m_server.m_exiting) return; - - while (m_server.m_suspended) { -#ifdef DEBUG_FFT_SERVER - cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << endl; -#endif - { - MutexLocker locker(&m_server.m_fftBuffersLock, - "FFTDataServer::run::m_fftBuffersLock [2]"); - if (m_server.m_suspended && !m_server.m_exiting) { - m_server.m_condition.wait(&m_server.m_fftBuffersLock, 10000); - } - } - if (m_server.m_exiting) return; - } - - if (++counter == updateAt) { - m_extent = f; - m_completion = baseCompletion + - int(100 * fabsf(float(f - start) / - float(end - start))); - counter = 0; - if (updateAt < maxUpdateAt) { - updateAt *= 2; - if (updateAt > maxUpdateAt) updateAt = maxUpdateAt; - } - } - } - - m_server.fillComplete(); - m_completion = 100; - m_extent = end; - -#ifdef DEBUG_FFT_SERVER - std::cerr << "FFTDataServer::FillThread::run exiting" << std::endl; -#endif -} -
--- a/data/fft/FFTDataServer.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,295 +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 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 _FFT_DATA_SERVER_H_ -#define _FFT_DATA_SERVER_H_ - -#include "base/Window.h" -#include "base/Thread.h" -#include "base/StorageAdviser.h" - -#include "FFTapi.h" -#include "FFTFileCacheReader.h" -#include "FFTFileCacheWriter.h" -#include "FFTMemoryCache.h" - -#include <QMutex> -#include <QReadWriteLock> -#include <QReadLocker> -#include <QWaitCondition> -#include <QString> - -#include <vector> -#include <deque> - -class DenseTimeValueModel; -class Model; - -class FFTDataServer -{ -public: - static FFTDataServer *getInstance(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria = - StorageAdviser::NoCriteria, - sv_frame_t fillFromFrame = 0); - - static FFTDataServer *getFuzzyInstance(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria = - StorageAdviser::NoCriteria, - sv_frame_t fillFromFrame = 0); - - static void claimInstance(FFTDataServer *); - static void releaseInstance(FFTDataServer *); - - static void modelAboutToBeDeleted(Model *); - - const DenseTimeValueModel *getModel() const { return m_model; } - int getChannel() const { return m_channel; } - WindowType getWindowType() const { return m_windower.getType(); } - int getWindowSize() const { return m_windowSize; } - int getWindowIncrement() const { return m_windowIncrement; } - int getFFTSize() const { return m_fftSize; } - bool getPolar() const { return m_polar; } - - int getWidth() const { return m_width; } - int getHeight() const { return m_height; } - - float getMagnitudeAt(int x, int y); - float getNormalizedMagnitudeAt(int x, int y); - float getMaximumMagnitudeAt(int x); - float getPhaseAt(int x, int y); - void getValuesAt(int x, int y, float &real, float &imaginary); - bool isColumnReady(int x); - - bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0, int step = 1); - bool getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0, int step = 1); - bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0, int step = 1); - bool getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0, int step = 1); - - void suspend(); - void suspendWrites(); - void resume(); // also happens automatically if new data needed - - // Convenience functions: - - bool isLocalPeak(int x, int y) { - float mag = getMagnitudeAt(x, y); - if (y > 0 && mag < getMagnitudeAt(x, y - 1)) return false; - if (y < getHeight()-1 && mag < getMagnitudeAt(x, y + 1)) return false; - return true; - } - bool isOverThreshold(int x, int y, float threshold) { - return getMagnitudeAt(x, y) > threshold; - } - - QString getError() const; - int getFillCompletion() const; - sv_frame_t getFillExtent() const; - -private: - FFTDataServer(QString fileBaseName, - const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria, - sv_frame_t fillFromFrame = 0); - - virtual ~FFTDataServer(); - - FFTDataServer(const FFTDataServer &); // not implemented - FFTDataServer &operator=(const FFTDataServer &); // not implemented - - typedef float fftsample; - - QString m_fileBaseName; - const DenseTimeValueModel *m_model; - int m_channel; - - Window<fftsample> m_windower; - - int m_windowSize; - int m_windowIncrement; - int m_fftSize; - bool m_polar; - - int m_width; - int m_height; - int m_cacheWidth; - int m_cacheWidthPower; - int m_cacheWidthMask; - - struct CacheBlock { - FFTMemoryCache *memoryCache; - typedef std::map<QThread *, FFTFileCacheReader *> ThreadReaderMap; - ThreadReaderMap fileCacheReader; - FFTFileCacheWriter *fileCacheWriter; - CacheBlock() : memoryCache(0), fileCacheWriter(0) { } - ~CacheBlock() { - delete memoryCache; - while (!fileCacheReader.empty()) { - delete fileCacheReader.begin()->second; - fileCacheReader.erase(fileCacheReader.begin()); - } - delete fileCacheWriter; - } - }; - - typedef std::vector<CacheBlock *> CacheVector; - CacheVector m_caches; - QReadWriteLock m_cacheVectorLock; // locks cache lookup, not use - QMutex m_cacheCreationMutex; // solely to serialise makeCache() calls - - FFTCacheReader *getCacheReader(int x, int &col) { - Profiler profiler("FFTDataServer::getCacheReader"); - col = x & m_cacheWidthMask; - int c = x >> m_cacheWidthPower; - m_cacheVectorLock.lockForRead(); - CacheBlock *cb(m_caches.at(c)); - if (cb) { - if (cb->memoryCache) { - m_cacheVectorLock.unlock(); - return cb->memoryCache; - } - if (cb->fileCacheWriter) { - QThread *me = QThread::currentThread(); - CacheBlock::ThreadReaderMap &map = cb->fileCacheReader; - if (map.find(me) == map.end()) { - m_cacheVectorLock.unlock(); - if (!makeCacheReader(c)) return 0; - return getCacheReader(x, col); - } - FFTCacheReader *reader = cb->fileCacheReader[me]; - m_cacheVectorLock.unlock(); - return reader; - } - // if cb exists but cb->fileCacheWriter doesn't, creation - // must have failed: don't try again - m_cacheVectorLock.unlock(); - return 0; - } - m_cacheVectorLock.unlock(); - if (getError() != "") return 0; - if (!makeCache(c)) return 0; - return getCacheReader(x, col); - } - - FFTCacheWriter *getCacheWriter(int x, int &col) { - Profiler profiler("FFTDataServer::getCacheWriter"); - col = x & m_cacheWidthMask; - int c = x >> m_cacheWidthPower; - { - QReadLocker locker(&m_cacheVectorLock); - CacheBlock *cb(m_caches.at(c)); - if (cb) { - if (cb->memoryCache) return cb->memoryCache; - if (cb->fileCacheWriter) return cb->fileCacheWriter; - // if cb exists, creation must have failed: don't try again - return 0; - } - } - if (!makeCache(c)) return 0; - return getCacheWriter(x, col); - } - - bool haveCache(int x) { - int c = x >> m_cacheWidthPower; - return (m_caches.at(c) != 0); - } - - bool makeCache(int c); - bool makeCacheReader(int c); - - StorageAdviser::Criteria m_criteria; - - void getStorageAdvice(int w, int h, bool &memory, bool &compact); - - mutable QMutex m_fftBuffersLock; - QWaitCondition m_condition; - - fftsample *m_fftInput; - fftf_complex *m_fftOutput; - float *m_workbuffer; - fftf_plan m_fftPlan; - - class FillThread : public Thread - { - public: - FillThread(FFTDataServer &server, sv_frame_t fillFromFrame) : - m_server(server), m_extent(0), m_completion(0), - m_fillFrom(fillFromFrame) { } - - sv_frame_t getExtent() const { return m_extent; } - int getCompletion() const { return m_completion ? m_completion : 1; } - QString getError() const { return m_threadError; } - virtual void run(); - - protected: - FFTDataServer &m_server; - sv_frame_t m_extent; - int m_completion; - sv_frame_t m_fillFrom; - QString m_threadError; - }; - - bool m_exiting; - bool m_suspended; - FillThread *m_fillThread; - QString m_error; - - void deleteProcessingData(); - void fillColumn(int x); - void fillComplete(); - - QString generateFileBasename() const; - static QString generateFileBasename(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar); - - typedef std::pair<FFTDataServer *, int> ServerCountPair; - typedef std::map<QString, ServerCountPair> ServerMap; - typedef std::deque<FFTDataServer *> ServerQueue; - - static ServerMap m_servers; - static ServerQueue m_releasedServers; // these are still in m_servers as well, with zero refcount - static QMutex m_serverMapMutex; - static FFTDataServer *findServer(QString); // call with serverMapMutex held - static void purgeLimbo(int maxSize = 3); // call with serverMapMutex held - - static void claimInstance(FFTDataServer *, bool needLock); - static void releaseInstance(FFTDataServer *, bool needLock); - -}; - -#endif
--- a/data/fft/FFTFileCacheReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +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-2009 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. -*/ - -#include "FFTFileCacheReader.h" -#include "FFTFileCacheWriter.h" - -#include "fileio/MatrixFile.h" - -#include "base/Profiler.h" -#include "base/Thread.h" -#include "base/Exceptions.h" - -#include <iostream> - -//#define DEBUG_FFT_FILE_CACHE_READER 1 - -// The underlying matrix has height (m_height * 2 + 1). In each -// column we store magnitude at [0], [2] etc and phase at [1], [3] -// etc, and then store the normalization factor (maximum magnitude) at -// [m_height * 2]. In compact mode, the factor takes two cells. - -FFTFileCacheReader::FFTFileCacheReader(FFTFileCacheWriter *writer) : - m_readbuf(0), - m_readbufCol(0), - m_readbufWidth(0), - m_readbufGood(false), - m_storageType(writer->getStorageType()), - m_factorSize(m_storageType == FFTCache::Compact ? 2 : 1), - m_mfc(new MatrixFile - (writer->getFileBase(), - MatrixFile::ReadOnly, - int((m_storageType == FFTCache::Compact) ? sizeof(uint16_t) : sizeof(float)), - writer->getWidth(), - writer->getHeight() * 2 + m_factorSize)) -{ -#ifdef DEBUG_FFT_FILE_CACHE_READER - cerr << "FFTFileCacheReader: storage type is " << (m_storageType == FFTCache::Compact ? "Compact" : m_storageType == FFTCache::Polar ? "Polar" : "Rectangular") << endl; -#endif -} - -FFTFileCacheReader::~FFTFileCacheReader() -{ - if (m_readbuf) delete[] m_readbuf; - delete m_mfc; -} - -int -FFTFileCacheReader::getWidth() const -{ - return m_mfc->getWidth(); -} - -int -FFTFileCacheReader::getHeight() const -{ - int mh = m_mfc->getHeight(); - if (mh > m_factorSize) return (mh - m_factorSize) / 2; - else return 0; -} - -float -FFTFileCacheReader::getMagnitudeAt(int x, int y) const -{ - Profiler profiler("FFTFileCacheReader::getMagnitudeAt", false); - - float value = 0.f; - - switch (m_storageType) { - - case FFTCache::Compact: - value = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.f) - * getNormalizationFactor(x); - break; - - case FFTCache::Rectangular: - { - float real, imag; - getValuesAt(x, y, real, imag); - value = sqrtf(real * real + imag * imag); - break; - } - - case FFTCache::Polar: - value = getFromReadBufStandard(x, y * 2); - break; - } - - return value; -} - -float -FFTFileCacheReader::getNormalizedMagnitudeAt(int x, int y) const -{ - float value = 0.f; - - switch (m_storageType) { - - case FFTCache::Compact: - value = getFromReadBufCompactUnsigned(x, y * 2) / 65535.f; - break; - - case FFTCache::Rectangular: - case FFTCache::Polar: - { - float mag = getMagnitudeAt(x, y); - float factor = getNormalizationFactor(x); - if (factor != 0) value = mag / factor; - else value = 0.f; - break; - } - } - - return value; -} - -float -FFTFileCacheReader::getMaximumMagnitudeAt(int x) const -{ - return getNormalizationFactor(x); -} - -float -FFTFileCacheReader::getPhaseAt(int x, int y) const -{ - float value = 0.f; - - switch (m_storageType) { - - case FFTCache::Compact: - value = (getFromReadBufCompactSigned(x, y * 2 + 1) / 32767.f) * float(M_PI); - break; - - case FFTCache::Rectangular: - { - float real, imag; - getValuesAt(x, y, real, imag); - value = atan2f(imag, real); - break; - } - - case FFTCache::Polar: - value = getFromReadBufStandard(x, y * 2 + 1); - break; - } - - return value; -} - -void -FFTFileCacheReader::getValuesAt(int x, int y, float &real, float &imag) const -{ -// SVDEBUG << "FFTFileCacheReader::getValuesAt(" << x << "," << y << ")" << endl; - - switch (m_storageType) { - - case FFTCache::Rectangular: - real = getFromReadBufStandard(x, y * 2); - imag = getFromReadBufStandard(x, y * 2 + 1); - return; - - case FFTCache::Compact: - case FFTCache::Polar: - float mag = getMagnitudeAt(x, y); - float phase = getPhaseAt(x, y); - real = mag * cosf(phase); - imag = mag * sinf(phase); - return; - } -} - -void -FFTFileCacheReader::getMagnitudesAt(int x, float *values, int minbin, int count, int step) const -{ - Profiler profiler("FFTFileCacheReader::getMagnitudesAt"); - - switch (m_storageType) { - - case FFTCache::Compact: - for (int i = 0; i < count; ++i) { - int y = minbin + i * step; - values[i] = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.f) - * getNormalizationFactor(x); - } - break; - - case FFTCache::Rectangular: - { - float real, imag; - for (int i = 0; i < count; ++i) { - int y = minbin + i * step; - real = getFromReadBufStandard(x, y * 2); - imag = getFromReadBufStandard(x, y * 2 + 1); - values[i] = sqrtf(real * real + imag * imag); - } - break; - } - - case FFTCache::Polar: - for (int i = 0; i < count; ++i) { - int y = minbin + i * step; - values[i] = getFromReadBufStandard(x, y * 2); - } - break; - } -} - -bool -FFTFileCacheReader::haveSetColumnAt(int x) const -{ - if (m_readbuf && m_readbufGood && - (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { -// SVDEBUG << "FFTFileCacheReader::haveSetColumnAt: short-circuiting; we know about this one" << endl; - return true; - } - return m_mfc->haveSetColumnAt(x); -} - -size_t -FFTFileCacheReader::getCacheSize(int width, int height, - FFTCache::StorageType type) -{ - return (height * 2 + (type == FFTCache::Compact ? 2 : 1)) * width * - (type == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float)) + - 2 * sizeof(int); // matrix file header size -} - -void -FFTFileCacheReader::populateReadBuf(int x) const -{ - Profiler profiler("FFTFileCacheReader::populateReadBuf", false); - -// SVDEBUG << "FFTFileCacheReader::populateReadBuf(" << x << ")" << endl; - - if (!m_readbuf) { - m_readbuf = new char[m_mfc->getHeight() * 2 * m_mfc->getCellSize()]; - } - - m_readbufGood = false; - - try { - bool good = false; - if (m_mfc->haveSetColumnAt(x)) { - // If the column is not available, we have no obligation - // to do anything with the readbuf -- we can cheerfully - // return garbage. It's the responsibility of the caller - // to check haveSetColumnAt before trusting any retrieved - // data. However, we do record whether the data in the - // readbuf is good or not, because we can use that to - // return an immediate result for haveSetColumnAt if the - // column is right. - good = true; - m_mfc->getColumnAt(x, m_readbuf); - } - if (m_mfc->haveSetColumnAt(x + 1)) { - m_mfc->getColumnAt - (x + 1, m_readbuf + m_mfc->getCellSize() * m_mfc->getHeight()); - m_readbufWidth = 2; - } else { - m_readbufWidth = 1; - } - m_readbufGood = good; - } catch (FileReadFailed f) { - cerr << "ERROR: FFTFileCacheReader::populateReadBuf: File read failed: " - << f.what() << endl; - memset(m_readbuf, 0, m_mfc->getHeight() * 2 * m_mfc->getCellSize()); - } - m_readbufCol = x; -} -
--- a/data/fft/FFTFileCacheReader.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +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-2009 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 _FFT_FILE_CACHE_READER_H_ -#define _FFT_FILE_CACHE_READER_H_ - -#include "data/fileio/MatrixFile.h" -#include "FFTCacheReader.h" -#include "FFTCacheStorageType.h" - -class FFTFileCacheWriter; - -class FFTFileCacheReader : public FFTCacheReader -{ -public: - FFTFileCacheReader(FFTFileCacheWriter *); - ~FFTFileCacheReader(); - - int getWidth() const; - int getHeight() const; - - float getMagnitudeAt(int x, int y) const; - float getNormalizedMagnitudeAt(int x, int y) const; - float getMaximumMagnitudeAt(int x) const; - float getPhaseAt(int x, int y) const; - - void getValuesAt(int x, int y, float &real, float &imag) const; - void getMagnitudesAt(int x, float *values, int minbin, int count, int step) const; - - bool haveSetColumnAt(int x) const; - - static size_t getCacheSize(int width, int height, - FFTCache::StorageType type); - - FFTCache::StorageType getStorageType() const { return m_storageType; } - -protected: - mutable char *m_readbuf; - mutable int m_readbufCol; - mutable int m_readbufWidth; - mutable bool m_readbufGood; - - float getFromReadBufStandard(int x, int y) const { - float v; - if (m_readbuf && - (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { - v = ((float *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; - return v; - } else { - populateReadBuf(x); - v = getFromReadBufStandard(x, y); - return v; - } - } - - float getFromReadBufCompactUnsigned(int x, int y) const { - float v; - if (m_readbuf && - (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { - v = ((uint16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; - return v; - } else { - populateReadBuf(x); - v = getFromReadBufCompactUnsigned(x, y); - return v; - } - } - - float getFromReadBufCompactSigned(int x, int y) const { - float v; - if (m_readbuf && - (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { - v = ((int16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; - return v; - } else { - populateReadBuf(x); - v = getFromReadBufCompactSigned(x, y); - return v; - } - } - - void populateReadBuf(int x) const; - - float getNormalizationFactor(int col) const { - int h = m_mfc->getHeight(); - if (h < m_factorSize) return 0; - if (m_storageType != FFTCache::Compact) { - return getFromReadBufStandard(col, h - 1); - } else { - union { - float f; - uint16_t u[2]; - } factor; - if (!m_readbuf || - !(m_readbufCol == col || - (m_readbufWidth > 1 && m_readbufCol+1 == col))) { - populateReadBuf(col); - } - int ix = (col - m_readbufCol) * m_mfc->getHeight() + h; - factor.u[0] = ((uint16_t *)m_readbuf)[ix - 2]; - factor.u[1] = ((uint16_t *)m_readbuf)[ix - 1]; - return factor.f; - } - } - - FFTCache::StorageType m_storageType; - int m_factorSize; - MatrixFile *m_mfc; -}; - -#endif
--- a/data/fft/FFTFileCacheWriter.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +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-2009 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. -*/ - -#include "FFTFileCacheWriter.h" - -#include "fileio/MatrixFile.h" - -#include "base/Profiler.h" -#include "base/Thread.h" -#include "base/Exceptions.h" - -#include <iostream> - -//#define DEBUG_FFT_FILE_CACHE_WRITER 1 - - -// The underlying matrix has height (m_height * 2 + 1). In each -// column we store magnitude at [0], [2] etc and phase at [1], [3] -// etc, and then store the normalization factor (maximum magnitude) at -// [m_height * 2]. In compact mode, the factor takes two cells. - -FFTFileCacheWriter::FFTFileCacheWriter(QString fileBase, - FFTCache::StorageType storageType, - int width, int height) : - m_writebuf(0), - m_fileBase(fileBase), - m_storageType(storageType), - m_factorSize(storageType == FFTCache::Compact ? 2 : 1), - m_mfc(new MatrixFile - (fileBase, MatrixFile::WriteOnly, - int((storageType == FFTCache::Compact) ? sizeof(uint16_t) : sizeof(float)), - width, height * 2 + m_factorSize)) -{ -#ifdef DEBUG_FFT_FILE_CACHE_WRITER - cerr << "FFTFileCacheWriter: storage type is " << (storageType == FFTCache::Compact ? "Compact" : storageType == FFTCache::Polar ? "Polar" : "Rectangular") << ", size " << width << "x" << height << endl; -#endif - m_mfc->setAutoClose(true); - m_writebuf = new char[(height * 2 + m_factorSize) * m_mfc->getCellSize()]; -} - -FFTFileCacheWriter::~FFTFileCacheWriter() -{ - if (m_writebuf) delete[] m_writebuf; - delete m_mfc; -} - -QString -FFTFileCacheWriter::getFileBase() const -{ - return m_fileBase; -} - -int -FFTFileCacheWriter::getWidth() const -{ - return m_mfc->getWidth(); -} - -int -FFTFileCacheWriter::getHeight() const -{ - int mh = m_mfc->getHeight(); - if (mh > m_factorSize) return (mh - m_factorSize) / 2; - else return 0; -} - -bool -FFTFileCacheWriter::haveSetColumnAt(int x) const -{ - return m_mfc->haveSetColumnAt(x); -} - -void -FFTFileCacheWriter::setColumnAt(int x, float *mags, float *phases, float factor) -{ - int h = getHeight(); - - switch (m_storageType) { - - case FFTCache::Compact: - for (int y = 0; y < h; ++y) { - ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mags[y] / factor) * 65535.0); - ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phases[y] * 32767) / M_PI)); - } - break; - - case FFTCache::Rectangular: - for (int y = 0; y < h; ++y) { - ((float *)m_writebuf)[y * 2] = mags[y] * cosf(phases[y]); - ((float *)m_writebuf)[y * 2 + 1] = mags[y] * sinf(phases[y]); - } - break; - - case FFTCache::Polar: - for (int y = 0; y < h; ++y) { - ((float *)m_writebuf)[y * 2] = mags[y]; - ((float *)m_writebuf)[y * 2 + 1] = phases[y]; - } - break; - } - - static float maxFactor = 0; - if (factor > maxFactor) maxFactor = factor; -#ifdef DEBUG_FFT_FILE_CACHE_WRITER - cerr << "Column " << x << ": normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << endl; -#endif - - setNormalizationFactorToWritebuf(factor); - - m_mfc->setColumnAt(x, m_writebuf); -} - -void -FFTFileCacheWriter::setColumnAt(int x, float *real, float *imag) -{ - int h = getHeight(); - - float factor = 0.0f; - - switch (m_storageType) { - - case FFTCache::Compact: - for (int y = 0; y < h; ++y) { - float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); - if (mag > factor) factor = mag; - } - for (int y = 0; y < h; ++y) { - float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); - float phase = atan2f(imag[y], real[y]); - ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mag / factor) * 65535.0); - ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phase * 32767) / M_PI)); - } - break; - - case FFTCache::Rectangular: - for (int y = 0; y < h; ++y) { - ((float *)m_writebuf)[y * 2] = real[y]; - ((float *)m_writebuf)[y * 2 + 1] = imag[y]; - float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); - if (mag > factor) factor = mag; - } - break; - - case FFTCache::Polar: - for (int y = 0; y < h; ++y) { - float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); - if (mag > factor) factor = mag; - ((float *)m_writebuf)[y * 2] = mag; - float phase = atan2f(imag[y], real[y]); - ((float *)m_writebuf)[y * 2 + 1] = phase; - } - break; - } - - static float maxFactor = 0; - if (factor > maxFactor) maxFactor = factor; -#ifdef DEBUG_FFT_FILE_CACHE_WRITER - cerr << "[RI] Column " << x << ": normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << endl; -#endif - - setNormalizationFactorToWritebuf(factor); - - m_mfc->setColumnAt(x, m_writebuf); -} - -size_t -FFTFileCacheWriter::getCacheSize(int width, int height, - FFTCache::StorageType type) -{ - return (height * 2 + (type == FFTCache::Compact ? 2 : 1)) * width * - (type == FFTCache::Compact ? sizeof(uint16_t) : sizeof(float)) + - 2 * sizeof(int); // matrix file header size -} - -void -FFTFileCacheWriter::allColumnsWritten() -{ -#ifdef DEBUG_FFT_FILE_CACHE_WRITER - SVDEBUG << "FFTFileCacheWriter::allColumnsWritten" << endl; -#endif - m_mfc->close(); -} -
--- a/data/fft/FFTFileCacheWriter.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +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-2009 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 _FFT_FILE_CACHE_WRITER_H_ -#define _FFT_FILE_CACHE_WRITER_H_ - -#include "FFTCacheStorageType.h" -#include "FFTCacheWriter.h" -#include "data/fileio/MatrixFile.h" - -class FFTFileCacheWriter : public FFTCacheWriter -{ -public: - FFTFileCacheWriter(QString fileBase, - FFTCache::StorageType storageType, - int width, int height); - ~FFTFileCacheWriter(); - - int getWidth() const; - int getHeight() const; - - void setColumnAt(int x, float *mags, float *phases, float factor); - void setColumnAt(int x, float *reals, float *imags); - - static size_t getCacheSize(int width, int height, - FFTCache::StorageType type); - - bool haveSetColumnAt(int x) const; - - void allColumnsWritten(); - - QString getFileBase() const; - FFTCache::StorageType getStorageType() const { return m_storageType; } - -protected: - char *m_writebuf; - - void setNormalizationFactorToWritebuf(float newfactor) { - int h = m_mfc->getHeight(); - if (h < m_factorSize) return; - if (m_storageType != FFTCache::Compact) { - ((float *)m_writebuf)[h - 1] = newfactor; - } else { - union { - float f; - uint16_t u[2]; - } factor; - factor.f = newfactor; - ((uint16_t *)m_writebuf)[h - 2] = factor.u[0]; - ((uint16_t *)m_writebuf)[h - 1] = factor.u[1]; - } - } - - QString m_fileBase; - FFTCache::StorageType m_storageType; - int m_factorSize; - MatrixFile *m_mfc; -}; - -#endif
--- a/data/fft/FFTMemoryCache.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,218 +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. -*/ - -#include "FFTMemoryCache.h" -#include "system/System.h" - -#include <iostream> -#include <cstdlib> - -//#define DEBUG_FFT_MEMORY_CACHE 1 - -FFTMemoryCache::FFTMemoryCache(FFTCache::StorageType storageType, - int width, int height) : - m_width(width), - m_height(height), - m_magnitude(0), - m_phase(0), - m_fmagnitude(0), - m_fphase(0), - m_freal(0), - m_fimag(0), - m_factor(0), - m_storageType(storageType) -{ -#ifdef DEBUG_FFT_MEMORY_CACHE - cerr << "FFTMemoryCache[" << this << "]::FFTMemoryCache (type " - << m_storageType << "), size " << m_width << "x" << m_height << endl; -#endif - - initialise(); -} - -FFTMemoryCache::~FFTMemoryCache() -{ -#ifdef DEBUG_FFT_MEMORY_CACHE - cerr << "FFTMemoryCache[" << this << "]::~FFTMemoryCache" << endl; -#endif - - for (int i = 0; i < m_width; ++i) { - if (m_magnitude && m_magnitude[i]) free(m_magnitude[i]); - if (m_phase && m_phase[i]) free(m_phase[i]); - if (m_fmagnitude && m_fmagnitude[i]) free(m_fmagnitude[i]); - if (m_fphase && m_fphase[i]) free(m_fphase[i]); - if (m_freal && m_freal[i]) free(m_freal[i]); - if (m_fimag && m_fimag[i]) free(m_fimag[i]); - } - - if (m_magnitude) free(m_magnitude); - if (m_phase) free(m_phase); - if (m_fmagnitude) free(m_fmagnitude); - if (m_fphase) free(m_fphase); - if (m_freal) free(m_freal); - if (m_fimag) free(m_fimag); - if (m_factor) free(m_factor); -} - -void -FFTMemoryCache::initialise() -{ - Profiler profiler("FFTMemoryCache::initialise"); - - int width = m_width, height = m_height; - -#ifdef DEBUG_FFT_MEMORY_CACHE - cerr << "FFTMemoryCache[" << this << "]::initialise(" << width << "x" << height << " = " << width*height << ")" << endl; -#endif - - if (m_storageType == FFTCache::Compact) { - initialise(m_magnitude); - initialise(m_phase); - } else if (m_storageType == FFTCache::Polar) { - initialise(m_fmagnitude); - initialise(m_fphase); - } else { - initialise(m_freal); - initialise(m_fimag); - } - - m_colset.resize(width); - - m_factor = (float *)realloc(m_factor, width * sizeof(float)); - - m_width = width; - m_height = height; - -#ifdef DEBUG_FFT_MEMORY_CACHE - cerr << "done, width = " << m_width << " height = " << m_height << endl; -#endif -} - -void -FFTMemoryCache::initialise(uint16_t **&array) -{ - array = (uint16_t **)malloc(m_width * sizeof(uint16_t *)); - if (!array) throw std::bad_alloc(); - MUNLOCK(array, m_width * sizeof(uint16_t *)); - - for (int i = 0; i < m_width; ++i) { - array[i] = (uint16_t *)malloc(m_height * sizeof(uint16_t)); - if (!array[i]) throw std::bad_alloc(); - MUNLOCK(array[i], m_height * sizeof(uint16_t)); - } -} - -void -FFTMemoryCache::initialise(float **&array) -{ - array = (float **)malloc(m_width * sizeof(float *)); - if (!array) throw std::bad_alloc(); - MUNLOCK(array, m_width * sizeof(float *)); - - for (int i = 0; i < m_width; ++i) { - array[i] = (float *)malloc(m_height * sizeof(float)); - if (!array[i]) throw std::bad_alloc(); - MUNLOCK(array[i], m_height * sizeof(float)); - } -} - -void -FFTMemoryCache::setColumnAt(int x, float *mags, float *phases, float factor) -{ - Profiler profiler("FFTMemoryCache::setColumnAt: from polar"); - - setNormalizationFactor(x, factor); - - if (m_storageType == FFTCache::Rectangular) { - Profiler subprof("FFTMemoryCache::setColumnAt: polar to cart"); - for (int y = 0; y < m_height; ++y) { - m_freal[x][y] = mags[y] * cosf(phases[y]); - m_fimag[x][y] = mags[y] * sinf(phases[y]); - } - } else { - for (int y = 0; y < m_height; ++y) { - setMagnitudeAt(x, y, mags[y]); - setPhaseAt(x, y, phases[y]); - } - } - - m_colsetLock.lockForWrite(); - m_colset.set(x); - m_colsetLock.unlock(); -} - -void -FFTMemoryCache::setColumnAt(int x, float *reals, float *imags) -{ - Profiler profiler("FFTMemoryCache::setColumnAt: from cart"); - - float max = 0.0; - - switch (m_storageType) { - - case FFTCache::Rectangular: - for (int y = 0; y < m_height; ++y) { - m_freal[x][y] = reals[y]; - m_fimag[x][y] = imags[y]; - float mag = sqrtf(reals[y] * reals[y] + imags[y] * imags[y]); - if (mag > max) max = mag; - } - break; - - case FFTCache::Compact: - case FFTCache::Polar: - { - Profiler subprof("FFTMemoryCache::setColumnAt: cart to polar"); - for (int y = 0; y < m_height; ++y) { - float mag = sqrtf(reals[y] * reals[y] + imags[y] * imags[y]); - float phase = atan2f(imags[y], reals[y]); - reals[y] = mag; - imags[y] = phase; - if (mag > max) max = mag; - } - break; - } - }; - - if (m_storageType == FFTCache::Rectangular) { - m_factor[x] = max; - m_colsetLock.lockForWrite(); - m_colset.set(x); - m_colsetLock.unlock(); - } else { - setColumnAt(x, reals, imags, max); - } -} - -size_t -FFTMemoryCache::getCacheSize(int width, int height, FFTCache::StorageType type) -{ - size_t sz = 0; - - switch (type) { - - case FFTCache::Compact: - sz = (height * 2 + 1) * width * sizeof(uint16_t); - break; - - case FFTCache::Polar: - case FFTCache::Rectangular: - sz = (height * 2 + 1) * width * sizeof(float); - break; - } - - return sz; -} -
--- a/data/fft/FFTMemoryCache.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +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 _FFT_MEMORY_CACHE_H_ -#define _FFT_MEMORY_CACHE_H_ - -#include "FFTCacheReader.h" -#include "FFTCacheWriter.h" -#include "FFTCacheStorageType.h" -#include "base/ResizeableBitset.h" -#include "base/Profiler.h" - -#include <QReadWriteLock> - -/** - * In-memory FFT cache. For this we want to cache magnitude with - * enough resolution to have gain applied afterwards and determine - * whether something is a peak or not, and also cache phase rather - * than only phase-adjusted frequency so that we don't have to - * recalculate if switching between phase and magnitude displays. At - * the same time, we don't want to take up too much memory. It's not - * expected to be accurate enough to be used as input for DSP or - * resynthesis code. - * - * This implies probably 16 bits for a normalized magnitude and at - * most 16 bits for phase. - * - * Each column's magnitudes are expected to be stored normalized - * to [0,1] with respect to the column, so the normalization - * factor should be calculated before all values in a column, and - * set appropriately. - */ - -class FFTMemoryCache : public FFTCacheReader, public FFTCacheWriter -{ -public: - FFTMemoryCache(FFTCache::StorageType storageType, - int width, int height); - ~FFTMemoryCache(); - - int getWidth() const { return m_width; } - int getHeight() const { return m_height; } - - float getMagnitudeAt(int x, int y) const { - if (m_storageType == FFTCache::Rectangular) { - Profiler profiler("FFTMemoryCache::getMagnitudeAt: cart to polar"); - return sqrtf(m_freal[x][y] * m_freal[x][y] + - m_fimag[x][y] * m_fimag[x][y]); - } else { - return getNormalizedMagnitudeAt(x, y) * m_factor[x]; - } - } - - float getNormalizedMagnitudeAt(int x, int y) const { - if (m_storageType == FFTCache::Rectangular) return getMagnitudeAt(x, y) / m_factor[x]; - else if (m_storageType == FFTCache::Polar) return m_fmagnitude[x][y]; - else return float(m_magnitude[x][y]) / 65535.f; - } - - float getMaximumMagnitudeAt(int x) const { - return m_factor[x]; - } - - float getPhaseAt(int x, int y) const { - if (m_storageType == FFTCache::Rectangular) { - Profiler profiler("FFTMemoryCache::getValuesAt: cart to polar"); - return atan2f(m_fimag[x][y], m_freal[x][y]); - } else if (m_storageType == FFTCache::Polar) { - return m_fphase[x][y]; - } else { - int16_t i = (int16_t)m_phase[x][y]; - return float(i / 32767.0 * M_PI); - } - } - - void getValuesAt(int x, int y, float &real, float &imag) const { - if (m_storageType == FFTCache::Rectangular) { - real = m_freal[x][y]; - imag = m_fimag[x][y]; - } else { - Profiler profiler("FFTMemoryCache::getValuesAt: polar to cart"); - float mag = getMagnitudeAt(x, y); - float phase = getPhaseAt(x, y); - real = mag * cosf(phase); - imag = mag * sinf(phase); - } - } - - void getMagnitudesAt(int x, float *values, int minbin, int count, int step) const - { - if (m_storageType == FFTCache::Rectangular) { - for (int i = 0; i < count; ++i) { - int y = i * step + minbin; - values[i] = sqrtf(m_freal[x][y] * m_freal[x][y] + - m_fimag[x][y] * m_fimag[x][y]); - } - } else if (m_storageType == FFTCache::Polar) { - for (int i = 0; i < count; ++i) { - int y = i * step + minbin; - values[i] = m_fmagnitude[x][y] * m_factor[x]; - } - } else { - for (int i = 0; i < count; ++i) { - int y = i * step + minbin; - values[i] = float(double(m_magnitude[x][y]) * m_factor[x] / 65535.0); - } - } - } - - bool haveSetColumnAt(int x) const { - m_colsetLock.lockForRead(); - bool have = m_colset.get(x); - m_colsetLock.unlock(); - return have; - } - - void setColumnAt(int x, float *mags, float *phases, float factor); - - void setColumnAt(int x, float *reals, float *imags); - - void allColumnsWritten() { } - - static size_t getCacheSize(int width, int height, - FFTCache::StorageType type); - - FFTCache::StorageType getStorageType() const { return m_storageType; } - -private: - int m_width; - int m_height; - uint16_t **m_magnitude; - uint16_t **m_phase; - float **m_fmagnitude; - float **m_fphase; - float **m_freal; - float **m_fimag; - float *m_factor; - FFTCache::StorageType m_storageType; - ResizeableBitset m_colset; - mutable QReadWriteLock m_colsetLock; - - void initialise(); - - void setNormalizationFactor(int x, float factor) { - if (x < m_width) m_factor[x] = factor; - } - - void setMagnitudeAt(int x, int y, float mag) { - // norm factor must already be set - setNormalizedMagnitudeAt(x, y, mag / m_factor[x]); - } - - void setNormalizedMagnitudeAt(int x, int y, float norm) { - if (x < m_width && y < m_height) { - if (m_storageType == FFTCache::Polar) m_fmagnitude[x][y] = norm; - else m_magnitude[x][y] = uint16_t(norm * 65535.0); - } - } - - void setPhaseAt(int x, int y, float phase) { - // phase in range -pi -> pi - if (x < m_width && y < m_height) { - if (m_storageType == FFTCache::Polar) m_fphase[x][y] = phase; - else m_phase[x][y] = uint16_t(int16_t((phase * 32767) / M_PI)); - } - } - - void initialise(uint16_t **&); - void initialise(float **&); -}; - - -#endif -
--- a/data/fft/FFTapi.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,224 +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 and QMUL. - FFT code from Don Cross's public domain FFT implementation. - - 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 "FFTapi.h" - -#ifndef HAVE_FFTW3F - -#include <cmath> -#include <iostream> - -void -fft(unsigned int n, bool inverse, double *ri, double *ii, double *ro, double *io) -{ - if (!ri || !ro || !io) return; - - unsigned int bits; - unsigned int i, j, k, m; - unsigned int blockSize, blockEnd; - - double tr, ti; - - if (n < 2) return; - if (n & (n-1)) return; - - double angle = 2.0 * M_PI; - if (inverse) angle = -angle; - - for (i = 0; ; ++i) { - if (n & (1 << i)) { - bits = i; - break; - } - } - - int *table = new int[n]; - - for (i = 0; i < n; ++i) { - - m = i; - - for (j = k = 0; j < bits; ++j) { - k = (k << 1) | (m & 1); - m >>= 1; - } - - table[i] = k; - } - - if (ii) { - for (i = 0; i < n; ++i) { - ro[table[i]] = ri[i]; - io[table[i]] = ii[i]; - } - } else { - for (i = 0; i < n; ++i) { - ro[table[i]] = ri[i]; - io[table[i]] = 0.0; - } - } - - blockEnd = 1; - - for (blockSize = 2; blockSize <= n; blockSize <<= 1) { - - double delta = angle / (double)blockSize; - double sm2 = -sin(-2 * delta); - double sm1 = -sin(-delta); - double cm2 = cos(-2 * delta); - double cm1 = cos(-delta); - double w = 2 * cm1; - double ar[3], ai[3]; - - for (i = 0; i < n; i += blockSize) { - - ar[2] = cm2; - ar[1] = cm1; - - ai[2] = sm2; - ai[1] = sm1; - - for (j = i, m = 0; m < blockEnd; j++, m++) { - - ar[0] = w * ar[1] - ar[2]; - ar[2] = ar[1]; - ar[1] = ar[0]; - - ai[0] = w * ai[1] - ai[2]; - ai[2] = ai[1]; - ai[1] = ai[0]; - - k = j + blockEnd; - tr = ar[0] * ro[k] - ai[0] * io[k]; - ti = ar[0] * io[k] + ai[0] * ro[k]; - - ro[k] = ro[j] - tr; - io[k] = io[j] - ti; - - ro[j] += tr; - io[j] += ti; - } - } - - blockEnd = blockSize; - } - -/* fftw doesn't normalise, so nor will we - - if (inverse) { - - double denom = (double)n; - - for (i = 0; i < n; i++) { - ro[i] /= denom; - io[i] /= denom; - } - } -*/ - delete[] table; -} - -struct fftf_plan_ { - int size; - int inverse; - float *real; - fftf_complex *cplx; -}; - -fftf_plan -fftf_plan_dft_r2c_1d(int n, float *in, fftf_complex *out, unsigned) -{ - if (n < 2) return 0; - if (n & (n-1)) return 0; - - fftf_plan_ *plan = new fftf_plan_; - plan->size = n; - plan->inverse = 0; - plan->real = in; - plan->cplx = out; - return plan; -} - -fftf_plan -fftf_plan_dft_c2r_1d(int n, fftf_complex *in, float *out, unsigned) -{ - if (n < 2) return 0; - if (n & (n-1)) return 0; - - fftf_plan_ *plan = new fftf_plan_; - plan->size = n; - plan->inverse = 1; - plan->real = out; - plan->cplx = in; - return plan; -} - -void -fftf_destroy_plan(fftf_plan p) -{ - delete p; -} - -void -fftf_execute(const fftf_plan p) -{ - float *real = p->real; - fftf_complex *cplx = p->cplx; - int n = p->size; - int forward = !p->inverse; - - double *ri = new double[n]; - double *ro = new double[n]; - double *io = new double[n]; - - double *ii = 0; - if (!forward) ii = new double[n]; - - if (forward) { - for (int i = 0; i < n; ++i) { - ri[i] = real[i]; - } - } else { - for (int i = 0; i < n/2+1; ++i) { - ri[i] = cplx[i][0]; - ii[i] = cplx[i][1]; - if (i > 0) { - ri[n-i] = ri[i]; - ii[n-i] = -ii[i]; - } - } - } - - fft(n, !forward, ri, ii, ro, io); - - if (forward) { - for (int i = 0; i < n/2+1; ++i) { - cplx[i][0] = ro[i]; - cplx[i][1] = io[i]; - } - } else { - for (int i = 0; i < n; ++i) { - real[i] = ro[i]; - } - } - - delete[] ri; - delete[] ro; - delete[] io; - if (ii) delete[] ii; -} - -#endif
--- a/data/fft/FFTapi.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +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 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 _FFT_API_H_ -#define _FFT_API_H_ - -#ifdef HAVE_FFTW3F - -#include <fftw3.h> - -#define fftf_complex fftwf_complex -#define fftf_malloc fftwf_malloc -#define fftf_free fftwf_free -#define fftf_plan fftwf_plan -#define fftf_plan_dft_r2c_1d fftwf_plan_dft_r2c_1d -#define fftf_plan_dft_c2r_1d fftwf_plan_dft_c2r_1d -#define fftf_execute fftwf_execute -#define fftf_destroy_plan fftwf_destroy_plan - -#else - -// Provide a fallback FFT implementation if FFTW3f is not available. - -typedef float fftf_complex[2]; -#define fftf_malloc malloc -#define fftf_free free - -struct fftf_plan_; -typedef fftf_plan_ *fftf_plan; - -fftf_plan fftf_plan_dft_r2c_1d(int n, float *in, fftf_complex *out, unsigned); -fftf_plan fftf_plan_dft_c2r_1d(int n, fftf_complex *in, float *out, unsigned); -void fftf_execute(const fftf_plan p); -void fftf_destroy_plan(fftf_plan p); - -#define FFTW_ESTIMATE 0 -#define FFTW_MEASURE 0 - -#endif - -#endif -
--- a/data/fileio/AudioFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/AudioFileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -17,15 +17,17 @@ using std::vector; -vector<SampleBlock> +vector<floatvec_t> AudioFileReader::getDeInterleavedFrames(sv_frame_t start, sv_frame_t count) const { - SampleBlock interleaved = getInterleavedFrames(start, count); + floatvec_t interleaved = getInterleavedFrames(start, count); int channels = getChannelCount(); + if (channels == 1) return { interleaved }; + sv_frame_t rc = interleaved.size() / channels; - vector<SampleBlock> frames(channels, SampleBlock(rc, 0.f)); + vector<floatvec_t> frames(channels, floatvec_t(rc, 0.f)); for (int c = 0; c < channels; ++c) { for (sv_frame_t i = 0; i < rc; ++i) {
--- a/data/fileio/AudioFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/AudioFileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -24,8 +24,6 @@ #include <vector> #include <map> -typedef std::vector<float> SampleBlock; - class AudioFileReader : public QObject { Q_OBJECT @@ -33,15 +31,42 @@ public: virtual ~AudioFileReader() { } + /** + * Return true if the file was opened successfully and no error + * has subsequently occurred. + */ bool isOK() const { return (m_channelCount > 0); } + /** + * If isOK() is false, return an error string. + */ virtual QString getError() const { return ""; } + /** + * Return the number of audio sample frames (i.e. samples per + * channel) in the file. + */ sv_frame_t getFrameCount() const { return m_frameCount; } + + /** + * Return the number of channels in the file. + */ int getChannelCount() const { return m_channelCount; } + + /** + * Return the samplerate at which the file is being read. This is + * the rate requested when the file was opened, which may differ + * from the native rate of the file (in which case the file will + * be resampled as it is read). + */ sv_samplerate_t getSampleRate() const { return m_sampleRate; } - virtual sv_samplerate_t getNativeRate() const { return m_sampleRate; } // if resampled + /** + * Return the native samplerate of the file. This will differ from + * getSampleRate() if the file is being resampled because it was + * requested to open at a different rate from native. + */ + virtual sv_samplerate_t getNativeRate() const { return m_sampleRate; } /** * Return the location of the audio data in the reader (as passed @@ -85,16 +110,15 @@ /** * Return interleaved samples for count frames from index start. - * The resulting sample block will contain count * - * getChannelCount() samples (or fewer if end of file is - * reached). The caller does not need to allocate space and any - * existing content in the SampleBlock will be erased. + * The resulting vector will contain count * getChannelCount() + * samples (or fewer if end of file is reached). * * The subclass implementations of this function must be * thread-safe -- that is, safe to call from multiple threads with * different arguments on the same object at the same time. */ - virtual SampleBlock getInterleavedFrames(sv_frame_t start, sv_frame_t count) const = 0; + virtual floatvec_t getInterleavedFrames(sv_frame_t start, + sv_frame_t count) const = 0; /** * Return de-interleaved samples for count frames from index @@ -103,7 +127,8 @@ * will contain getChannelCount() sample blocks of count samples * each (or fewer if end of file is reached). */ - virtual std::vector<SampleBlock> getDeInterleavedFrames(sv_frame_t start, sv_frame_t count) const; + virtual std::vector<floatvec_t> getDeInterleavedFrames(sv_frame_t start, + sv_frame_t count) const; // only subclasses that do not know exactly how long the audio // file is until it's been completely decoded should implement this
--- a/data/fileio/AudioFileReaderFactory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/AudioFileReaderFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -19,15 +19,15 @@ #include "DecodingWavFileReader.h" #include "OggVorbisFileReader.h" #include "MP3FileReader.h" -#include "QuickTimeFileReader.h" #include "CoreAudioFileReader.h" +#include "AudioFileSizeEstimator.h" + +#include "base/StorageAdviser.h" #include <QString> #include <QFileInfo> #include <iostream> -//#define DEBUG_AUDIO_FILE_READER_FACTORY 1 - QString AudioFileReaderFactory::getKnownExtensions() { @@ -42,9 +42,6 @@ OggVorbisFileReader::getSupportedExtensions(extensions); #endif #endif -#ifdef HAVE_QUICKTIME - QuickTimeFileReader::getSupportedExtensions(extensions); -#endif #ifdef HAVE_COREAUDIO CoreAudioFileReader::getSupportedExtensions(extensions); #endif @@ -60,300 +57,171 @@ } AudioFileReader * -AudioFileReaderFactory::createReader(FileSource source, - sv_samplerate_t targetRate, - bool normalised, +AudioFileReaderFactory::createReader(FileSource source, + Parameters params, ProgressReporter *reporter) { - return create(source, targetRate, normalised, false, reporter); -} - -AudioFileReader * -AudioFileReaderFactory::createThreadingReader(FileSource source, - sv_samplerate_t targetRate, - bool normalised, - ProgressReporter *reporter) -{ - return create(source, targetRate, normalised, true, reporter); -} - -AudioFileReader * -AudioFileReaderFactory::create(FileSource source, - sv_samplerate_t targetRate, - bool normalised, - bool threading, - ProgressReporter *reporter) -{ QString err; -#ifdef DEBUG_AUDIO_FILE_READER_FACTORY - cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\"): Requested rate: " << targetRate << endl; -#endif + SVDEBUG << "AudioFileReaderFactory: url \"" << source.getLocation() << "\": requested rate: " << params.targetRate << (params.targetRate == 0 ? " (use source rate)" : "") << endl; + SVDEBUG << "AudioFileReaderFactory: local filename \"" << source.getLocalFilename() << "\", content type \"" << source.getContentType() << "\"" << endl; if (!source.isOK()) { - cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Failed to retrieve source (transmission error?): " << source.getErrorString() << endl; + SVCERR << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Failed to retrieve source (transmission error?): " << source.getErrorString() << endl; return 0; } if (!source.isAvailable()) { - cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl; + SVCERR << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl; return 0; } AudioFileReader *reader = 0; - // Try to construct a preferred reader based on the extension or - // MIME type. + sv_samplerate_t targetRate = params.targetRate; + bool normalised = (params.normalisation == Normalisation::Peak); + + sv_frame_t estimatedSamples = + AudioFileSizeEstimator::estimate(source, targetRate); + + CodedAudioFileReader::CacheMode cacheMode = + CodedAudioFileReader::CacheInTemporaryFile; - if (WavFileReader::supports(source)) { - - reader = new WavFileReader(source); - - sv_samplerate_t fileRate = reader->getSampleRate(); - - if (reader->isOK() && - (!reader->isQuicklySeekable() || - normalised || - (targetRate != 0 && fileRate != targetRate))) { - -#ifdef DEBUG_AUDIO_FILE_READER_FACTORY - cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl; -#endif - - delete reader; - reader = new DecodingWavFileReader - (source, - threading ? - DecodingWavFileReader::ResampleThreaded : - DecodingWavFileReader::ResampleAtOnce, - DecodingWavFileReader::CacheInTemporaryFile, - targetRate ? targetRate : fileRate, - normalised, - reporter); - if (!reader->isOK()) { - delete reader; - reader = 0; - } + if (estimatedSamples > 0) { + size_t kb = (estimatedSamples * sizeof(float)) / 1024; + SVDEBUG << "AudioFileReaderFactory: checking where to potentially cache " + << kb << "K of sample data" << endl; + StorageAdviser::Recommendation rec = + StorageAdviser::recommend(StorageAdviser::SpeedCritical, kb, kb); + if ((rec & StorageAdviser::UseMemory) || + (rec & StorageAdviser::PreferMemory)) { + SVDEBUG << "AudioFileReaderFactory: cacheing (if at all) in memory" << endl; + cacheMode = CodedAudioFileReader::CacheInMemory; + } else { + SVDEBUG << "AudioFileReaderFactory: cacheing (if at all) on disc" << endl; } } + CodedAudioFileReader::DecodeMode decodeMode = + (params.threadingMode == ThreadingMode::Threaded ? + CodedAudioFileReader::DecodeThreaded : + CodedAudioFileReader::DecodeAtOnce); + + // We go through the set of supported readers at most twice: once + // picking out only the readers that claim to support the given + // file's extension or MIME type, and (if that fails) again + // providing the file to every reader in turn regardless of + // extension or type. (If none of the readers claim to support a + // file, that may just mean its extension is missing or + // misleading. We have to be confident that the reader won't open + // just any old text file or whatever and pretend it's succeeded.) + + for (int any = 0; any <= 1; ++any) { + + bool anyReader = (any > 0); + + if (!anyReader) { + SVDEBUG << "AudioFileReaderFactory: Checking whether any reader officially handles this source" << endl; + } else { + SVDEBUG << "AudioFileReaderFactory: Source not officially handled by any reader, trying again with each reader in turn" + << endl; + } + #ifdef HAVE_OGGZ #ifdef HAVE_FISHSOUND - if (!reader) { - if (OggVorbisFileReader::supports(source)) { + // 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, - threading ? - OggVorbisFileReader::DecodeThreaded : - OggVorbisFileReader::DecodeAtOnce, - OggVorbisFileReader::CacheInTemporaryFile, - targetRate, - normalised, - reporter); - if (!reader->isOK()) { + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + + if (reader->isOK()) { + SVDEBUG << "AudioFileReaderFactory: Ogg file reader is OK, returning it" << endl; + return reader; + } else { delete reader; - reader = 0; } } - } #endif #endif -#ifdef HAVE_MAD - if (!reader) { - if (MP3FileReader::supports(source)) { - reader = new MP3FileReader - (source, - threading ? - MP3FileReader::DecodeThreaded : - MP3FileReader::DecodeAtOnce, - MP3FileReader::CacheInTemporaryFile, - targetRate, - normalised, - reporter); - if (!reader->isOK()) { + if (anyReader || WavFileReader::supports(source)) { + + reader = new WavFileReader(source); + + sv_samplerate_t fileRate = reader->getSampleRate(); + + if (reader->isOK() && + (!reader->isQuicklySeekable() || + normalised || + (cacheMode == CodedAudioFileReader::CacheInMemory) || + (targetRate != 0 && fileRate != targetRate))) { + + SVDEBUG << "AudioFileReaderFactory: WAV file reader rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl; + delete reader; - reader = 0; + reader = new DecodingWavFileReader + (source, + decodeMode, cacheMode, + targetRate ? targetRate : fileRate, + normalised, + reporter); + } + + if (reader->isOK()) { + SVDEBUG << "AudioFileReaderFactory: WAV file reader is OK, returning it" << endl; + return reader; + } else { + delete reader; } } - } -#endif -#ifdef HAVE_QUICKTIME - if (!reader) { - if (QuickTimeFileReader::supports(source)) { - reader = new QuickTimeFileReader - (source, - threading ? - QuickTimeFileReader::DecodeThreaded : - QuickTimeFileReader::DecodeAtOnce, - QuickTimeFileReader::CacheInTemporaryFile, - targetRate, - normalised, - reporter); - if (!reader->isOK()) { +#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, + targetRate, normalised, reporter); + + if (reader->isOK()) { + SVDEBUG << "AudioFileReaderFactory: MP3 file reader is OK, returning it" << endl; + return reader; + } else { delete reader; - reader = 0; } } - } #endif #ifdef HAVE_COREAUDIO - if (!reader) { - if (CoreAudioFileReader::supports(source)) { + if (anyReader || CoreAudioFileReader::supports(source)) { + reader = new CoreAudioFileReader - (source, - threading ? - CoreAudioFileReader::DecodeThreaded : - CoreAudioFileReader::DecodeAtOnce, - CoreAudioFileReader::CacheInTemporaryFile, - targetRate, - normalised, - reporter); - if (!reader->isOK()) { + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + + if (reader->isOK()) { + SVDEBUG << "AudioFileReaderFactory: CoreAudio reader is OK, returning it" << endl; + return reader; + } else { delete reader; - reader = 0; } } - } #endif - - // If none of the readers claimed to support this file extension, - // perhaps the extension is missing or misleading. Try again, - // ignoring it. We have to be confident that the reader won't - // open just any old text file or whatever and pretend it's - // succeeded - - if (!reader) { - - reader = new WavFileReader(source); - - sv_samplerate_t fileRate = reader->getSampleRate(); - - if (reader->isOK() && - (!reader->isQuicklySeekable() || - normalised || - (targetRate != 0 && fileRate != targetRate))) { - -#ifdef DEBUG_AUDIO_FILE_READER_FACTORY - cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl; -#endif - - delete reader; - reader = new DecodingWavFileReader - (source, - threading ? - DecodingWavFileReader::ResampleThreaded : - DecodingWavFileReader::ResampleAtOnce, - DecodingWavFileReader::CacheInTemporaryFile, - targetRate ? targetRate : fileRate, - normalised, - reporter); - } - - if (!reader->isOK()) { - delete reader; - reader = 0; - } } -#ifdef HAVE_OGGZ -#ifdef HAVE_FISHSOUND - if (!reader) { - reader = new OggVorbisFileReader - (source, - threading ? - OggVorbisFileReader::DecodeThreaded : - OggVorbisFileReader::DecodeAtOnce, - OggVorbisFileReader::CacheInTemporaryFile, - targetRate, - reporter); - - if (!reader->isOK()) { - delete reader; - reader = 0; - } - } -#endif -#endif - -#ifdef HAVE_MAD - if (!reader) { - reader = new MP3FileReader - (source, - threading ? - MP3FileReader::DecodeThreaded : - MP3FileReader::DecodeAtOnce, - MP3FileReader::CacheInTemporaryFile, - targetRate, - reporter); - - if (!reader->isOK()) { - delete reader; - reader = 0; - } - } -#endif - -#ifdef HAVE_QUICKTIME - if (!reader) { - reader = new QuickTimeFileReader - (source, - threading ? - QuickTimeFileReader::DecodeThreaded : - QuickTimeFileReader::DecodeAtOnce, - QuickTimeFileReader::CacheInTemporaryFile, - targetRate, - reporter); - - if (!reader->isOK()) { - delete reader; - reader = 0; - } - } -#endif - -#ifdef HAVE_COREAUDIO - if (!reader) { - reader = new CoreAudioFileReader - (source, - threading ? - CoreAudioFileReader::DecodeThreaded : - CoreAudioFileReader::DecodeAtOnce, - CoreAudioFileReader::CacheInTemporaryFile, - targetRate, - reporter); - - if (!reader->isOK()) { - delete reader; - reader = 0; - } - } -#endif - - if (reader) { - if (reader->isOK()) { -#ifdef DEBUG_AUDIO_FILE_READER_FACTORY - cerr << "AudioFileReaderFactory: Reader is OK" << endl; -#endif - return reader; - } - cerr << "AudioFileReaderFactory: Preferred reader for " - << "url \"" << source.getLocation() - << "\" (content type \"" - << source.getContentType() << "\") failed"; - - if (reader->getError() != "") { - cerr << ": \"" << reader->getError() << "\""; - } - cerr << endl; - delete reader; - reader = 0; - } - - cerr << "AudioFileReaderFactory: No reader" << endl; - return reader; + SVCERR << "AudioFileReaderFactory::Failed to create a reader for " + << "url \"" << source.getLocation() + << "\" (local filename \"" << source.getLocalFilename() + << "\", content type \"" + << source.getContentType() << "\")" << endl; + return nullptr; }
--- a/data/fileio/AudioFileReaderFactory.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/AudioFileReaderFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _AUDIO_FILE_READER_FACTORY_H_ -#define _AUDIO_FILE_READER_FACTORY_H_ +#ifndef AUDIO_FILE_READER_FACTORY_H +#define AUDIO_FILE_READER_FACTORY_H #include <QString> @@ -34,65 +34,113 @@ */ static QString getKnownExtensions(); + enum class Normalisation { + + /** + * Do not normalise file data. + */ + None, + + /** + * Normalise file data to abs(max) == 1.0. + */ + Peak + }; + + enum class GaplessMode { + + /** + * Any encoder delay and padding found in file metadata will + * be compensated for, giving gapless decoding (assuming the + * metadata are correct). This is currently only applicable to + * mp3 files: all other supported files are always gapless + * where the file metadata provides for it. See documentation + * for MP3FileReader::GaplessMode for details of the specific + * implementation. + */ + Gapless, + + /** + * No delay compensation will happen and the results will be + * equivalent to the behaviour of audio readers before the + * compensation logic was implemented. This is currently only + * applicable to mp3 files: all other supported files are + * always gapless where the file metadata provides for it. See + * documentation for MP3FileReader::GaplessMode for details of + * the specific implementation. + */ + Gappy + }; + + enum class ThreadingMode { + + /** + * Any necessary decoding will happen synchronously when the + * reader is created. + */ + NotThreaded, + + /** + * If the reader supports threaded decoding, it will be used + * and the file will be decoded in a background thread. If the + * reader does not support threaded decoding, behaviour will + * be as for NotThreaded. + */ + Threaded + }; + + struct Parameters { + + /** + * Sample rate to open the file at. If zero (the default), the + * file's native rate will be used. If non-zero, the file will + * be automatically resampled to that rate. You can query + * reader->getNativeRate() if you want to find out whether the + * file needed to be resampled. + */ + sv_samplerate_t targetRate; + + /** + * Normalisation to use. The default is Normalisation::None. + */ + Normalisation normalisation; + + /** + * Gapless mode to use. The default is GaplessMode::Gapless. + */ + GaplessMode gaplessMode; + + /** + * Threading mode. The default is ThreadingMode::NotThreaded. + */ + ThreadingMode threadingMode; + + Parameters() : + targetRate(0), + normalisation(Normalisation::None), + gaplessMode(GaplessMode::Gapless), + threadingMode(ThreadingMode::NotThreaded) + { } + }; + /** * Return an audio file reader initialised to the file at the * given path, or NULL if no suitable reader for this path is * available or the file cannot be opened. * - * If targetRate is non-zero, the file will be resampled to that - * rate (transparently). You can query reader->getNativeRate() - * if you want to find out whether the file is being resampled - * or not. - * - * If normalised is true, the file data will be normalised to - * abs(max) == 1.0. Otherwise the file will not be normalised. - * * If a ProgressReporter is provided, it will be updated with - * progress status. Caller retains ownership of the reporter - * object. + * progress status. This will only be meaningful if decoding is + * being carried out in non-threaded mode (either because the + * threaded parameter was not supplied or because the specific + * file reader used does not support it); in threaded mode, + * reported progress will jump straight to 100% before threading + * takes over. Caller retains ownership of the reporter object. * * Caller owns the returned object and must delete it after use. */ static AudioFileReader *createReader(FileSource source, - sv_samplerate_t targetRate = 0, - bool normalised = false, + Parameters parameters, ProgressReporter *reporter = 0); - - /** - * Return an audio file reader initialised to the file at the - * given path, or NULL if no suitable reader for this path is - * available or the file cannot be opened. If the reader supports - * threaded decoding, it will be used and the file decoded in a - * background thread. - * - * If targetRate is non-zero, the file will be resampled to that - * rate (transparently). You can query reader->getNativeRate() - * if you want to find out whether the file is being resampled - * or not. - * - * If normalised is true, the file data will be normalised to - * abs(max) == 1.0. Otherwise the file will not be normalised. - * - * If a ProgressReporter is provided, it will be updated with - * progress status. This will only be meaningful if threading - * mode is not used because the file reader in use does not - * support it; otherwise progress as reported will jump straight - * to 100% before threading mode takes over. Caller retains - * ownership of the reporter object. - * - * Caller owns the returned object and must delete it after use. - */ - static AudioFileReader *createThreadingReader(FileSource source, - sv_samplerate_t targetRate = 0, - bool normalised = false, - ProgressReporter *reporter = 0); - -protected: - static AudioFileReader *create(FileSource source, - sv_samplerate_t targetRate, - bool normalised, - bool threading, - ProgressReporter *reporter); }; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileSizeEstimator.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,111 @@ +/* -*- 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 "AudioFileSizeEstimator.h" + +#include "WavFileReader.h" + +#include <QFile> + +#include "base/Debug.h" + +sv_frame_t +AudioFileSizeEstimator::estimate(FileSource source, + sv_samplerate_t targetRate) +{ + sv_frame_t estimate = 0; + + SVDEBUG << "AudioFileSizeEstimator: Sample count estimate requested for file \"" + << source.getLocalFilename() << "\"" << endl; + + // Most of our file readers don't know the sample count until + // after they've finished decoding. This is an exception: + + WavFileReader *reader = new WavFileReader(source); + if (reader->isOK() && + reader->getChannelCount() > 0 && + reader->getFrameCount() > 0) { + sv_frame_t samples = + reader->getFrameCount() * reader->getChannelCount(); + sv_samplerate_t rate = reader->getSampleRate(); + if (targetRate != 0.0 && targetRate != rate) { + samples = sv_frame_t(double(samples) * targetRate / rate); + } + delete reader; + SVDEBUG << "AudioFileSizeEstimator: WAV file reader accepts this file, reports " + << samples << " samples" << endl; + estimate = samples; + } else { + SVDEBUG << "AudioFileSizeEstimator: WAV file reader doesn't like this file, " + << "estimating from file size and extension instead" << endl; + } + + if (estimate == 0) { + + // The remainder just makes an estimate based on the file size + // and extension. We don't even know its sample rate at this + // point, so the following is a wild guess. + + double rateRatio = 1.0; + if (targetRate != 0.0) { + rateRatio = targetRate / 44100.0; + } + + QString extension = source.getExtension(); + + source.waitForData(); + if (!source.isOK()) return 0; + + sv_frame_t sz = 0; + + { + QFile f(source.getLocalFilename()); + if (f.open(QFile::ReadOnly)) { + SVDEBUG << "AudioFileSizeEstimator: opened file, size is " + << f.size() << endl; + sz = f.size(); + f.close(); + } + } + + if (extension == "ogg" || extension == "oga" || + extension == "m4a" || extension == "mp3" || + extension == "wma") { + + // Usually a lossy file. Compression ratios can vary + // dramatically, but don't usually exceed about 20x compared + // to 16-bit PCM (e.g. a 128kbps mp3 has 11x ratio over WAV at + // 44.1kHz). We can estimate the number of samples to be file + // size x 20, divided by 2 as we're comparing with 16-bit PCM. + + estimate = sv_frame_t(double(sz) * 10 * rateRatio); + } + + if (extension == "flac") { + + // FLAC usually takes up a bit more than half the space of + // 16-bit PCM. So the number of 16-bit samples is roughly the + // same as the file size in bytes. As above, let's be + // conservative. + + estimate = sv_frame_t(double(sz) * 1.2 * rateRatio); + } + + SVDEBUG << "AudioFileSizeEstimator: for extension \"" + << extension << "\", estimate = " << estimate << " samples" << endl; + } + + return estimate; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileSizeEstimator.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,49 @@ +/* -*- 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 AUDIO_FILE_SIZE_ESTIMATOR_H +#define AUDIO_FILE_SIZE_ESTIMATOR_H + +#include "base/BaseTypes.h" +#include "data/fileio/FileSource.h" + +/** + * Estimate the number of samples in an audio file. For many + * compressed files this returns only a very approximate estimate, + * based on a rough estimate of compression ratio. Initially we're + * only aiming for a conservative estimate for purposes like "will + * this file fit in memory?" (and if unsure, say no). + */ +class AudioFileSizeEstimator +{ +public: + /** + * Return an estimate of the number of samples (across all + * channels) in the given audio file, once it has been decoded and + * (if applicable) resampled to the given rate. + * + * This function is intended to be reasonably fast -- it may open + * the file, but it should not do any decoding. (However, if the + * file source is remote, it will probably be downloaded in its + * entirety before anything can be estimated.) + * + * The returned value is an estimate, and is deliberately usually + * on the high side. If the estimator has no idea at all, this + * will return 0. + */ + static sv_frame_t estimate(FileSource source, + sv_samplerate_t targetRate = 0); +}; + +#endif
--- a/data/fileio/BZipFileDevice.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/BZipFileDevice.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -23,6 +23,7 @@ BZipFileDevice::BZipFileDevice(QString fileName) : m_fileName(fileName), + m_qfile(fileName), m_file(0), m_bzFile(0), m_atEnd(true), @@ -72,9 +73,16 @@ if (mode & WriteOnly) { - m_file = fopen(m_fileName.toLocal8Bit().data(), "wb"); + if (!m_qfile.open(QIODevice::WriteOnly)) { + setErrorString(tr("Failed to open file for writing")); + m_ok = false; + return false; + } + + m_file = fdopen(m_qfile.handle(), "wb"); if (!m_file) { - setErrorString(tr("Failed to open file for writing")); + setErrorString(tr("Failed to open file handle for writing")); + m_qfile.close(); m_ok = false; return false; } @@ -85,6 +93,7 @@ if (!m_bzFile) { fclose(m_file); m_file = 0; + m_qfile.close(); setErrorString(tr("Failed to open bzip2 stream for writing")); m_ok = false; return false; @@ -99,9 +108,15 @@ if (mode & ReadOnly) { - m_file = fopen(m_fileName.toLocal8Bit().data(), "rb"); + if (!m_qfile.open(QIODevice::ReadOnly)) { + setErrorString(tr("Failed to open file for reading")); + m_ok = false; + return false; + } + + m_file = fdopen(m_qfile.handle(), "rb"); if (!m_file) { - setErrorString(tr("Failed to open file for reading")); + setErrorString(tr("Failed to open file handle for reading")); m_ok = false; return false; } @@ -112,6 +127,7 @@ if (!m_bzFile) { fclose(m_file); m_file = 0; + m_qfile.close(); setErrorString(tr("Failed to open bzip2 stream for reading")); m_ok = false; return false; @@ -150,6 +166,7 @@ setErrorString(tr("bzip2 stream write close error")); } fclose(m_file); + m_qfile.close(); m_bzFile = 0; m_file = 0; m_ok = false; @@ -162,6 +179,7 @@ setErrorString(tr("bzip2 stream read close error")); } fclose(m_file); + m_qfile.close(); m_bzFile = 0; m_file = 0; m_ok = false;
--- a/data/fileio/BZipFileDevice.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/BZipFileDevice.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,10 +13,11 @@ COPYING included with this distribution for more information. */ -#ifndef _BZIP_FILE_DEVICE_H_ -#define _BZIP_FILE_DEVICE_H_ +#ifndef SV_BZIP_FILE_DEVICE_H +#define SV_BZIP_FILE_DEVICE_H #include <QIODevice> +#include <QFile> #include <bzlib.h> @@ -41,6 +42,7 @@ QString m_fileName; + QFile m_qfile; FILE *m_file; BZFILE *m_bzFile; bool m_atEnd;
--- a/data/fileio/CSVFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CSVFileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _CSV_FILE_READER_H_ -#define _CSV_FILE_READER_H_ +#ifndef SV_CSV_FILE_READER_H +#define SV_CSV_FILE_READER_H #include "DataFileReader.h"
--- a/data/fileio/CSVFormat.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CSVFormat.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -25,6 +25,8 @@ #include <iostream> +#include "base/Debug.h" + CSVFormat::CSVFormat(QString path) : m_separator(""), m_sampleRate(44100), @@ -92,7 +94,6 @@ return; } } - m_separator = " "; } void @@ -100,7 +101,7 @@ { if (m_separator == "") guessSeparator(line); - QStringList list = StringBits::split(line, m_separator[0], m_allowQuoting); + QStringList list = StringBits::split(line, getSeparator(), m_allowQuoting); int cols = list.size(); if (lineno == 0 || (cols > m_columnCount)) m_columnCount = cols; @@ -182,11 +183,13 @@ } } -// cerr << "Estimated column qualities: "; -// for (int i = 0; i < m_columnCount; ++i) { -// cerr << int(m_columnQualities[i]) << " "; -// } -// cerr << endl; + if (lineno < 10) { + SVDEBUG << "Estimated column qualities for line " << lineno << " (reporting up to first 10): "; + for (int i = 0; i < m_columnCount; ++i) { + SVDEBUG << int(m_columnQualities[i]) << " "; + } + SVDEBUG << endl; + } } void @@ -314,15 +317,15 @@ } } -// cerr << "Estimated column purposes: "; -// for (int i = 0; i < m_columnCount; ++i) { -// cerr << int(m_columnPurposes[i]) << " "; -// } -// cerr << endl; + SVDEBUG << "Estimated column purposes: "; + for (int i = 0; i < m_columnCount; ++i) { + SVDEBUG << int(m_columnPurposes[i]) << " "; + } + SVDEBUG << endl; -// cerr << "Estimated model type: " << m_modelType << endl; -// cerr << "Estimated timing type: " << m_timingType << endl; -// cerr << "Estimated units: " << m_timeUnits << endl; + SVDEBUG << "Estimated model type: " << m_modelType << endl; + SVDEBUG << "Estimated timing type: " << m_timingType << endl; + SVDEBUG << "Estimated units: " << m_timeUnits << endl; } CSVFormat::ColumnPurpose
--- a/data/fileio/CSVFormat.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CSVFormat.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _CSV_FORMAT_H_ -#define _CSV_FORMAT_H_ +#ifndef SV_CSV_FORMAT_H +#define SV_CSV_FORMAT_H #include <QString> #include <QStringList>
--- a/data/fileio/CodedAudioFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CodedAudioFileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -20,13 +20,17 @@ #include "base/Exceptions.h" #include "base/Profiler.h" #include "base/Serialiser.h" -#include "base/Resampler.h" +#include "base/StorageAdviser.h" + +#include <bqresample/Resampler.h> #include <stdint.h> #include <iostream> #include <QDir> #include <QMutexLocker> +using namespace std; + CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode, sv_samplerate_t targetRate, bool normalised) : @@ -38,15 +42,26 @@ m_cacheFileReader(0), m_cacheWriteBuffer(0), m_cacheWriteBufferIndex(0), - m_cacheWriteBufferSize(16384), + m_cacheWriteBufferFrames(65536), m_resampler(0), m_resampleBuffer(0), + m_resampleBufferFrames(0), m_fileFrameCount(0), m_normalised(normalised), m_max(0.f), - m_gain(1.f) + m_gain(1.f), + m_trimFromStart(0), + m_trimFromEnd(0), + m_clippedCount(0), + m_firstNonzero(0), + m_lastNonzero(0) { - SVDEBUG << "CodedAudioFileReader::CodedAudioFileReader: rate " << targetRate << ", normalised = " << normalised << endl; + SVDEBUG << "CodedAudioFileReader:: cache mode: " << cacheMode + << " (" << (cacheMode == CacheInTemporaryFile + ? "CacheInTemporaryFile" : "CacheInMemory") << ")" + << ", rate: " << targetRate + << (targetRate == 0 ? " (use source rate)" : "") + << ", normalised: " << normalised << endl; m_frameCount = 0; m_sampleRate = targetRate; @@ -56,29 +71,43 @@ { QMutexLocker locker(&m_cacheMutex); - endSerialised(); - + if (m_serialiser) endSerialised(); + if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr); SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl; delete m_cacheFileReader; delete[] m_cacheWriteBuffer; - + if (m_cacheFileName != "") { + SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file " << m_cacheFileName << endl; if (!QFile(m_cacheFileName).remove()) { - cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl; + SVDEBUG << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName << "\"" << endl; } } delete m_resampler; delete[] m_resampleBuffer; + + if (!m_data.empty()) { + StorageAdviser::notifyDoneAllocation + (StorageAdviser::MemoryAllocation, + (m_data.size() * sizeof(float)) / 1024); + } +} + +void +CodedAudioFileReader::setFramesToTrim(sv_frame_t fromStart, sv_frame_t fromEnd) +{ + m_trimFromStart = fromStart; + m_trimFromEnd = fromEnd; } void CodedAudioFileReader::startSerialised(QString id) { - SVDEBUG << "CodedAudioFileReader::startSerialised(" << id << ")" << endl; + SVDEBUG << "CodedAudioFileReader(" << this << ")::startSerialised: id = " << id << endl; delete m_serialiser; m_serialiser = new Serialiser(id); @@ -100,9 +129,14 @@ SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: file rate = " << m_fileRate << endl; + if (m_channelCount == 0) { + SVCERR << "CodedAudioFileReader::initialiseDecodeCache: No channel count set!" << endl; + throw std::logic_error("No channel count set"); + } + if (m_fileRate == 0) { - cerr << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl; - throw FileOperationFailed("(coded file)", "File sample rate unknown (bug in subclass implementation?)"); + SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: ERROR: File sample rate unknown (bug in subclass implementation?)" << endl; + throw FileOperationFailed("(coded file)", "sample rate unknown (bug in subclass implementation?)"); } if (m_sampleRate == 0) { m_sampleRate = m_fileRate; @@ -110,28 +144,33 @@ } if (m_fileRate != m_sampleRate) { SVDEBUG << "CodedAudioFileReader: resampling " << m_fileRate << " -> " << m_sampleRate << endl; - m_resampler = new Resampler(Resampler::FastestTolerable, - m_channelCount, - m_cacheWriteBufferSize); + + breakfastquay::Resampler::Parameters params; + params.quality = breakfastquay::Resampler::FastestTolerable; + params.maxBufferSize = int(m_cacheWriteBufferFrames); + params.initialSampleRate = m_fileRate; + m_resampler = new breakfastquay::Resampler(params, m_channelCount); + double ratio = m_sampleRate / m_fileRate; - m_resampleBuffer = new float - [lrint(ceil(double(m_cacheWriteBufferSize) * m_channelCount * ratio + 1))]; + m_resampleBufferFrames = int(ceil(double(m_cacheWriteBufferFrames) * + ratio + 1)); + m_resampleBuffer = new float[m_resampleBufferFrames * m_channelCount]; } - m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount]; + m_cacheWriteBuffer = new float[m_cacheWriteBufferFrames * m_channelCount]; m_cacheWriteBufferIndex = 0; if (m_cacheMode == CacheInTemporaryFile) { 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; int fileRate = int(round(m_sampleRate)); if (m_sampleRate != sv_samplerate_t(fileRate)) { - cerr << "CodedAudioFileReader: WARNING: Non-integer sample rate " + SVDEBUG << "CodedAudioFileReader: WARNING: Non-integer sample rate " << m_sampleRate << " presented for writing, rounding to " << fileRate << endl; } @@ -157,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, &fileInfo); +#else + m_cacheFileWritePtr = sf_open + (m_cacheFileName.toLocal8Bit(), SFM_WRITE, &fileInfo); +#endif if (m_cacheFileWritePtr) { @@ -172,7 +216,7 @@ m_cacheFileReader = new WavFileReader(m_cacheFileName); if (!m_cacheFileReader->isOK()) { - cerr << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl; + SVDEBUG << "ERROR: CodedAudioFileReader::initialiseDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError() << endl; delete m_cacheFileReader; m_cacheFileReader = 0; m_cacheMode = CacheInMemory; @@ -180,12 +224,12 @@ } } else { - cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << endl; + SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << endl; m_cacheMode = CacheInMemory; } } catch (DirectoryCreationFailed f) { - cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl; + SVDEBUG << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << endl; m_cacheMode = CacheInMemory; } } @@ -194,6 +238,11 @@ m_data.clear(); } + if (m_trimFromEnd >= (m_cacheWriteBufferFrames * m_channelCount)) { + SVCERR << "WARNING: CodedAudioFileReader::setSamplesToTrim: Can't handle trimming more frames from end (" << m_trimFromEnd << ") than can be stored in cache-write buffer (" << (m_cacheWriteBufferFrames * m_channelCount) << "), won't trim anything from the end after all"; + m_trimFromEnd = 0; + } + m_initialised = true; } @@ -205,25 +254,20 @@ if (!m_initialised) return; for (sv_frame_t i = 0; i < nframes; ++i) { + + if (m_trimFromStart > 0) { + --m_trimFromStart; + continue; + } for (int c = 0; c < m_channelCount; ++c) { float sample = samples[c][i]; - m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; - if (m_cacheWriteBufferIndex == - m_cacheWriteBufferSize * m_channelCount) { + } - pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false); - m_cacheWriteBufferIndex = 0; - } - - if (m_cacheWriteBufferIndex % 10240 == 0 && - m_cacheFileReader) { - m_cacheFileReader->updateFrameCount(); - } - } + pushCacheWriteBufferMaybe(false); } } @@ -235,50 +279,40 @@ if (!m_initialised) return; for (sv_frame_t i = 0; i < nframes; ++i) { + + if (m_trimFromStart > 0) { + --m_trimFromStart; + continue; + } for (int c = 0; c < m_channelCount; ++c) { float sample = samples[i * m_channelCount + c]; m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; + } - if (m_cacheWriteBufferIndex == - m_cacheWriteBufferSize * m_channelCount) { - - pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false); - m_cacheWriteBufferIndex = 0; - } - - if (m_cacheWriteBufferIndex % 10240 == 0 && - m_cacheFileReader) { - m_cacheFileReader->updateFrameCount(); - } - } + pushCacheWriteBufferMaybe(false); } } void -CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples) +CodedAudioFileReader::addSamplesToDecodeCache(const floatvec_t &samples) { QMutexLocker locker(&m_cacheMutex); if (!m_initialised) return; for (float sample: samples) { + + if (m_trimFromStart > 0) { + --m_trimFromStart; + continue; + } m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; - if (m_cacheWriteBufferIndex == - m_cacheWriteBufferSize * m_channelCount) { - - pushBuffer(m_cacheWriteBuffer, m_cacheWriteBufferSize, false); - m_cacheWriteBufferIndex = 0; - } - - if (m_cacheWriteBufferIndex % 10240 == 0 && - m_cacheFileReader) { - m_cacheFileReader->updateFrameCount(); - } + pushCacheWriteBufferMaybe(false); } } @@ -287,16 +321,14 @@ { QMutexLocker locker(&m_cacheMutex); - Profiler profiler("CodedAudioFileReader::finishDecodeCache", true); + Profiler profiler("CodedAudioFileReader::finishDecodeCache"); if (!m_initialised) { - cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl; + SVDEBUG << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << endl; return; } - pushBuffer(m_cacheWriteBuffer, - m_cacheWriteBufferIndex / m_channelCount, - true); + pushCacheWriteBufferMaybe(true); delete[] m_cacheWriteBuffer; m_cacheWriteBuffer = 0; @@ -308,13 +340,76 @@ m_resampler = 0; if (m_cacheMode == CacheInTemporaryFile) { + sf_close(m_cacheFileWritePtr); m_cacheFileWritePtr = 0; if (m_cacheFileReader) m_cacheFileReader->updateFrameCount(); + + } else { + // I know, I know, we already allocated it... + StorageAdviser::notifyPlannedAllocation + (StorageAdviser::MemoryAllocation, + (m_data.size() * sizeof(float)) / 1024); + } + + SVDEBUG << "CodedAudioFileReader: File decodes to " << m_fileFrameCount + << " frames" << endl; + if (m_fileFrameCount != m_frameCount) { + SVDEBUG << "CodedAudioFileReader: Resampled to " << m_frameCount + << " frames" << endl; + } + SVDEBUG << "CodedAudioFileReader: Signal abs max is " << m_max + << ", " << m_clippedCount + << " samples clipped, first non-zero frame is at " + << m_firstNonzero << ", last at " << m_lastNonzero << endl; + if (m_normalised) { + SVDEBUG << "CodedAudioFileReader: Normalising, gain is " << m_gain << endl; } } void +CodedAudioFileReader::pushCacheWriteBufferMaybe(bool final) +{ + if (final || + (m_cacheWriteBufferIndex == + m_cacheWriteBufferFrames * m_channelCount)) { + + if (m_trimFromEnd > 0) { + + sv_frame_t framesToPush = + (m_cacheWriteBufferIndex / m_channelCount) - m_trimFromEnd; + + if (framesToPush <= 0 && !final) { + // This won't do, the buffer is full so we have to push + // something. Should have checked for this earlier + throw std::logic_error("Buffer full but nothing to push"); + } + + pushBuffer(m_cacheWriteBuffer, framesToPush, final); + + m_cacheWriteBufferIndex -= framesToPush * m_channelCount; + + for (sv_frame_t i = 0; i < m_cacheWriteBufferIndex; ++i) { + m_cacheWriteBuffer[i] = + m_cacheWriteBuffer[framesToPush * m_channelCount + i]; + } + + } else { + + pushBuffer(m_cacheWriteBuffer, + m_cacheWriteBufferIndex / m_channelCount, + final); + + m_cacheWriteBufferIndex = 0; + } + + if (m_cacheFileReader) { + m_cacheFileReader->updateFrameCount(); + } + } +} + +sv_frame_t CodedAudioFileReader::pushBuffer(float *buffer, sv_frame_t sz, bool final) { m_fileFrameCount += sz; @@ -329,6 +424,8 @@ } else { pushBufferNonResampling(buffer, sz); } + + return sz; } void @@ -337,24 +434,37 @@ float clip = 1.0; sv_frame_t count = sz * m_channelCount; - if (m_normalised) { - for (sv_frame_t i = 0; i < count; ++i) { - float v = fabsf(buffer[i]); - if (v > m_max) { - m_max = v; - m_gain = 1.f / m_max; + // statistics + for (sv_frame_t j = 0; j < sz; ++j) { + for (int c = 0; c < m_channelCount; ++c) { + sv_frame_t i = j * m_channelCount + c; + float v = buffer[i]; + if (!m_normalised) { + if (v > clip) { + buffer[i] = clip; + ++m_clippedCount; + } else if (v < -clip) { + buffer[i] = -clip; + ++m_clippedCount; + } + } + v = fabsf(v); + if (v != 0.f) { + if (m_firstNonzero == 0) { + m_firstNonzero = m_frameCount; + } + m_lastNonzero = m_frameCount; + if (v > m_max) { + m_max = v; + } } } - } else { - for (sv_frame_t i = 0; i < count; ++i) { - if (buffer[i] > clip) buffer[i] = clip; - } - for (sv_frame_t i = 0; i < count; ++i) { - if (buffer[i] < -clip) buffer[i] = -clip; - } + ++m_frameCount; } - m_frameCount += sz; + if (m_max > 0.f) { + m_gain = 1.f / m_max; // used when normalising only + } switch (m_cacheMode) { @@ -367,10 +477,8 @@ break; case CacheInMemory: - m_dataLock.lockForWrite(); - for (sv_frame_t s = 0; s < count; ++s) { - m_data.push_back(buffer[s]); - } + m_dataLock.lock(); + m_data.insert(m_data.end(), buffer, buffer + count); m_dataLock.unlock(); break; } @@ -380,14 +488,15 @@ CodedAudioFileReader::pushBufferResampling(float *buffer, sv_frame_t sz, double ratio, bool final) { - SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl; +// SVDEBUG << "pushBufferResampling: ratio = " << ratio << ", sz = " << sz << ", final = " << final << endl; if (sz > 0) { sv_frame_t out = m_resampler->resampleInterleaved - (buffer, - m_resampleBuffer, - sz, + (m_resampleBuffer, + m_resampleBufferFrames, + buffer, + int(sz), ratio, false); @@ -403,15 +512,16 @@ sv_frame_t padSamples = padFrames * m_channelCount; - SVDEBUG << "frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames= " << padFrames << ", padSamples = " << padSamples << endl; + SVDEBUG << "CodedAudioFileReader::pushBufferResampling: frameCount = " << m_frameCount << ", equivFileFrames = " << double(m_frameCount) / ratio << ", m_fileFrameCount = " << m_fileFrameCount << ", padFrames = " << padFrames << ", padSamples = " << padSamples << endl; float *padding = new float[padSamples]; for (sv_frame_t i = 0; i < padSamples; ++i) padding[i] = 0.f; sv_frame_t out = m_resampler->resampleInterleaved - (padding, - m_resampleBuffer, - padFrames, + (m_resampleBuffer, + m_resampleBufferFrames, + padding, + int(padFrames), ratio, true); @@ -424,7 +534,7 @@ } } -SampleBlock +floatvec_t CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const { // Lock is only required in CacheInMemory mode (the cache file @@ -433,10 +543,10 @@ if (!m_initialised) { SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl; - return SampleBlock(); + return {}; } - SampleBlock frames; + floatvec_t frames; switch (m_cacheMode) { @@ -448,22 +558,23 @@ case CacheInMemory: { - if (!isOK()) return SampleBlock(); - if (count == 0) return SampleBlock(); + if (!isOK()) return {}; + if (count == 0) return {}; - sv_frame_t idx = start * m_channelCount; - sv_frame_t i = 0; - sv_frame_t n = count * m_channelCount; + sv_frame_t ix0 = start * m_channelCount; + sv_frame_t ix1 = ix0 + (count * m_channelCount); - frames.resize(size_t(n)); - - m_dataLock.lockForRead(); - while (i < n && in_range_for(m_data, idx)) { - frames[size_t(i++)] = m_data[size_t(idx++)]; - } + // This lock used to be a QReadWriteLock, but it appears that + // its lock mechanism is significantly slower than QMutex so + // it's not a good idea in cases like this where we don't + // really have threads taking a long time to read concurrently + m_dataLock.lock(); + sv_frame_t n = sv_frame_t(m_data.size()); + if (ix0 > n) ix0 = n; + if (ix1 > n) ix1 = n; + frames = floatvec_t(m_data.begin() + ix0, m_data.begin() + ix1); m_dataLock.unlock(); - - frames.resize(size_t(i)); + break; } }
--- a/data/fileio/CodedAudioFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CodedAudioFileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,18 +13,27 @@ 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; -class Resampler; + +namespace breakfastquay { + class Resampler; +} class CodedAudioFileReader : public AudioFileReader { @@ -38,7 +47,12 @@ CacheInMemory }; - virtual SampleBlock getInterleavedFrames(sv_frame_t start, sv_frame_t count) const; + enum DecodeMode { + DecodeAtOnce, // decode the file on construction, with progress + DecodeThreaded // decode in a background thread after construction + }; + + virtual floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count) const; virtual sv_samplerate_t getNativeRate() const { return m_fileRate; } @@ -57,10 +71,13 @@ void initialiseDecodeCache(); // samplerate, channels must have been set + // compensation for encoder delays: + void setFramesToTrim(sv_frame_t fromStart, sv_frame_t fromEnd); + // may throw InsufficientDiscSpace: void addSamplesToDecodeCache(float **samples, sv_frame_t nframes); void addSamplesToDecodeCache(float *samplesInterleaved, sv_frame_t nframes); - void addSamplesToDecodeCache(const SampleBlock &interleaved); + void addSamplesToDecodeCache(const floatvec_t &interleaved); // may throw InsufficientDiscSpace: void finishDecodeCache(); @@ -71,15 +88,21 @@ void endSerialised(); private: - void pushBuffer(float *interleaved, sv_frame_t sz, bool final); + void pushCacheWriteBufferMaybe(bool final); + + sv_frame_t pushBuffer(float *interleaved, sv_frame_t sz, bool final); + + // to be called only by pushBuffer void pushBufferResampling(float *interleaved, sv_frame_t sz, double ratio, bool final); + + // to be called only by pushBuffer and pushBufferResampling void pushBufferNonResampling(float *interleaved, sv_frame_t sz); protected: QMutex m_cacheMutex; CacheMode m_cacheMode; - SampleBlock m_data; - mutable QReadWriteLock m_dataLock; + floatvec_t m_data; + mutable QMutex m_dataLock; bool m_initialised; Serialiser *m_serialiser; sv_samplerate_t m_fileRate; @@ -88,16 +111,24 @@ SNDFILE *m_cacheFileWritePtr; WavFileReader *m_cacheFileReader; float *m_cacheWriteBuffer; - sv_frame_t m_cacheWriteBufferIndex; - sv_frame_t m_cacheWriteBufferSize; // frames + sv_frame_t m_cacheWriteBufferIndex; // buffer write pointer in samples + sv_frame_t m_cacheWriteBufferFrames; // buffer size in frames - Resampler *m_resampler; + breakfastquay::Resampler *m_resampler; float *m_resampleBuffer; + int m_resampleBufferFrames; sv_frame_t m_fileFrameCount; bool m_normalised; float m_max; float m_gain; + + sv_frame_t m_trimFromStart; + sv_frame_t m_trimFromEnd; + + sv_frame_t m_clippedCount; + sv_frame_t m_firstNonzero; + sv_frame_t m_lastNonzero; }; #endif
--- a/data/fileio/CoreAudioFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CoreAudioFileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -71,6 +71,8 @@ m_completion(0), m_decodeThread(0) { + SVDEBUG << "CoreAudioFileReader: local path: \"" << m_path << "\"" << endl; + m_channelCount = 0; m_fileRate = 0; @@ -78,8 +80,6 @@ Profiler profiler("CoreAudioFileReader::CoreAudioFileReader", true); - SVDEBUG << "CoreAudioFileReader: path is \"" << m_path << "\"" << endl; - QByteArray ba = m_path.toLocal8Bit(); CFURLRef url = CFURLCreateFromFileSystemRepresentation @@ -114,18 +114,18 @@ UInt32 propsize = sizeof(AudioStreamBasicDescription); m_d->err = ExtAudioFileGetProperty - (m_d->file, kExtAudioFileProperty_FileDataFormat, &propsize, &m_d->asbd); + (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; - cerr << "CoreAudioReadStream: " << m_channelCount << " channels, " << m_fileRate << " Hz" << endl; + SVDEBUG << "CoreAudioFileReader: " << m_channelCount << " channels, " << m_fileRate << " Hz" << endl; m_d->asbd.mFormatID = kAudioFormatLinearPCM; m_d->asbd.mFormatFlags = @@ -137,9 +137,9 @@ 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); + (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); @@ -192,7 +192,7 @@ CoreAudioFileReader::~CoreAudioFileReader() { - cerr << "CoreAudioFileReader::~CoreAudioFileReader" << endl; + SVDEBUG << "CoreAudioFileReader::~CoreAudioFileReader" << endl; if (m_d->valid) { ExtAudioFileDispose(m_d->file);
--- a/data/fileio/CoreAudioFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/CoreAudioFileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -31,17 +31,12 @@ Q_OBJECT public: - enum DecodeMode { - DecodeAtOnce, // decode the file on construction, with progress - DecodeThreaded // decode in a background thread after construction - }; - CoreAudioFileReader(FileSource source, DecodeMode decodeMode, CacheMode cacheMode, sv_samplerate_t targetRate = 0, bool normalised = false, - ProgressReporter *reporter = 0); + ProgressReporter *reporter = nullptr); virtual ~CoreAudioFileReader(); virtual QString getError() const { return m_error; }
--- a/data/fileio/DecodingWavFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/DecodingWavFileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -21,8 +21,10 @@ #include <QFileInfo> +using namespace std; + DecodingWavFileReader::DecodingWavFileReader(FileSource source, - ResampleMode resampleMode, + DecodeMode decodeMode, CacheMode mode, sv_samplerate_t targetRate, bool normalised, @@ -37,13 +39,15 @@ m_reporter(reporter), m_decodeThread(0) { + SVDEBUG << "DecodingWavFileReader: local path: \"" << m_path + << "\", decode mode: " << decodeMode << " (" + << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded") + << ")" << endl; + m_channelCount = 0; m_fileRate = 0; - SVDEBUG << "DecodingWavFileReader::DecodingWavFileReader(\"" - << m_path << "\"): rate " << targetRate << endl; - - Profiler profiler("DecodingWavFileReader::DecodingWavFileReader", true); + Profiler profiler("DecodingWavFileReader::DecodingWavFileReader"); m_original = new WavFileReader(m_path); if (!m_original->isOK()) { @@ -56,7 +60,7 @@ initialiseDecodeCache(); - if (resampleMode == ResampleAtOnce) { + if (decodeMode == DecodeAtOnce) { if (m_reporter) { connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled())); @@ -67,7 +71,7 @@ sv_frame_t blockSize = 16384; sv_frame_t total = m_original->getFrameCount(); - SampleBlock block; + floatvec_t block; for (sv_frame_t i = 0; i < total; i += blockSize) { @@ -124,7 +128,7 @@ sv_frame_t blockSize = 16384; sv_frame_t total = m_reader->m_original->getFrameCount(); - SampleBlock block; + floatvec_t block; for (sv_frame_t i = 0; i < total; i += blockSize) { @@ -147,7 +151,7 @@ } void -DecodingWavFileReader::addBlock(const SampleBlock &frames) +DecodingWavFileReader::addBlock(const floatvec_t &frames) { addSamplesToDecodeCache(frames); @@ -167,7 +171,7 @@ } void -DecodingWavFileReader::getSupportedExtensions(std::set<QString> &extensions) +DecodingWavFileReader::getSupportedExtensions(set<QString> &extensions) { WavFileReader::getSupportedExtensions(extensions); }
--- a/data/fileio/DecodingWavFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/DecodingWavFileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -29,13 +29,8 @@ { Q_OBJECT public: - enum ResampleMode { - ResampleAtOnce, // resample the file on construction, with progress dialog - ResampleThreaded // resample in a background thread after construction - }; - DecodingWavFileReader(FileSource source, - ResampleMode resampleMode, + DecodeMode decodeMode, // determines when to resample CacheMode cacheMode, sv_samplerate_t targetRate = 0, bool normalised = false, @@ -69,7 +64,7 @@ WavFileReader *m_original; ProgressReporter *m_reporter; - void addBlock(const SampleBlock &frames); + void addBlock(const floatvec_t &frames); class DecodeThread : public Thread {
--- a/data/fileio/FileFinder.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/FileFinder.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _FILE_FINDER_H_ -#define _FILE_FINDER_H_ +#ifndef SV_FILE_FINDER_H +#define SV_FILE_FINDER_H #include <QString> @@ -28,6 +28,7 @@ LayerFileNoMidi, SessionOrAudioFile, ImageFile, + SVGFile, AnyFile, CSVFile, LayerFileNonSV,
--- a/data/fileio/FileReadThread.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/FileReadThread.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -19,7 +19,14 @@ #include "base/Thread.h" #include <iostream> + +#ifdef _MSC_VER +#include <io.h> +#define _lseek lseek +#else #include <unistd.h> +#endif + #include <cstdio> //#define DEBUG_FILE_READ_THREAD 1
--- a/data/fileio/FileSource.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/FileSource.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -30,8 +30,6 @@ #include <iostream> #include <cstdlib> -#include <unistd.h> - //#define DEBUG_FILE_SOURCE 1 int
--- a/data/fileio/FileSource.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/FileSource.h Fri Jan 13 10:29:44 2017 +0000 @@ -167,7 +167,8 @@ QString getContentType() const; /** - * Return the file extension for this file, if any. + * Return the file extension for this file, if any. The returned + * extension is always lower-case. */ QString getExtension() const;
--- a/data/fileio/MIDIFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/MIDIFileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -53,7 +53,7 @@ using namespace MIDIConstants; -//#define MIDI_SVDEBUG 1 +//#define MIDI_DEBUG 1 MIDIFileReader::MIDIFileReader(QString path, @@ -323,7 +323,7 @@ if (!skipToNextTrack()) { #ifdef MIDI_DEBUG - cerr << "Couldn't find Track " << j << endl; + SVDEBUG << "Couldn't find Track " << j << endl; #endif m_error = "File corrupted or in non-standard format?"; m_format = MIDI_FILE_BAD_FORMAT; @@ -331,14 +331,14 @@ } #ifdef MIDI_DEBUG - cerr << "Track has " << m_trackByteCount << " bytes" << endl; + SVDEBUG << "Track has " << m_trackByteCount << " bytes" << endl; #endif // Run through the events taking them into our internal // representation. if (!parseTrack(i)) { #ifdef MIDI_DEBUG - cerr << "Track " << j << " parsing failed" << endl; + SVDEBUG << "Track " << j << " parsing failed" << endl; #endif m_error = "File corrupted or in non-standard format?"; m_format = MIDI_FILE_BAD_FORMAT; @@ -476,7 +476,7 @@ if (eventCode < 0x80) { #ifdef MIDI_DEBUG - cerr << "WARNING: Invalid event code " << eventCode + SVDEBUG << "WARNING: Invalid event code " << eventCode << " in MIDI file" << endl; #endif throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode))); @@ -485,7 +485,7 @@ deltaTime = getNumberFromMIDIBytes(); #ifdef MIDI_DEBUG - cerr << "read delta time " << deltaTime << endl; + SVDEBUG << "read delta time " << deltaTime << endl; #endif // Get a single byte @@ -505,7 +505,7 @@ #endif } else { #ifdef MIDI_DEBUG - cerr << "have new event code " << int(midiByte) << endl; + SVDEBUG << "have new event code " << int(midiByte) << endl; #endif eventCode = midiByte; data1 = getMIDIByte(); @@ -517,7 +517,7 @@ messageLength = getNumberFromMIDIBytes(); //#ifdef MIDI_DEBUG - cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; + SVDEBUG << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; //#endif metaMessage = getMIDIBytes(messageLength); @@ -572,7 +572,7 @@ midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); /* - cerr << "MIDI event for channel " << channel << " (track " + SVDEBUG << "MIDI event for channel " << channel << " (track " << trackNum << ")" << endl; midiEvent->print(); */ @@ -605,7 +605,7 @@ messageLength = getNumberFromMIDIBytes(data1); #ifdef MIDI_DEBUG - cerr << "SysEx of " << messageLength << " bytes found" << endl; + SVDEBUG << "SysEx of " << messageLength << " bytes found" << endl; #endif metaMessage= getMIDIBytes(messageLength); @@ -709,7 +709,7 @@ void MIDIFileReader::updateTempoMap(unsigned int track) { - cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << endl; + SVDEBUG << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << endl; for (MIDITrack::iterator i = m_midiComposition[track].begin(); i != m_midiComposition[track].end(); ++i) { @@ -723,7 +723,7 @@ long tempo = (((m0 << 8) + m1) << 8) + m2; - cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << endl; + SVDEBUG << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << endl; if (tempo != 0) { double qpm = 60000000.0 / double(tempo); @@ -786,11 +786,11 @@ SVDEBUG << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")" << endl; SVDEBUG << "timing division = " << td << endl; - cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" + SVDEBUG << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" << tempoRealTime << ")" << endl; - cerr << "quarters since then = " << quarters << endl; - cerr << "tempo = " << tempo << " quarters per minute" << endl; - cerr << "seconds since then = " << seconds << endl; + SVDEBUG << "quarters since then = " << quarters << endl; + SVDEBUG << "tempo = " << tempo << " quarters per minute" << endl; + SVDEBUG << "seconds since then = " << seconds << endl; SVDEBUG << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << endl; */ @@ -928,7 +928,7 @@ if (existingModel) { model = dynamic_cast<NoteModel *>(existingModel); if (!model) { - cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << endl; + SVDEBUG << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << endl; } }
--- a/data/fileio/MIDIFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/MIDIFileReader.h Fri Jan 13 10:29:44 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/MP3FileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/MP3FileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -27,87 +27,102 @@ #include <iostream> #include <cstdlib> -#include <unistd.h> #ifdef HAVE_ID3TAG #include <id3tag.h> #endif -//#define DEBUG_ID3TAG 1 +#ifdef _WIN32 +#include <io.h> +#include <fcntl.h> +#else +#include <fcntl.h> +#include <unistd.h> +#endif #include <QFileInfo> +#include <QTextCodec> + +using std::string; + +static sv_frame_t DEFAULT_DECODER_DELAY = 529; + MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode, - CacheMode mode, sv_samplerate_t targetRate, + CacheMode mode, GaplessMode gaplessMode, + sv_samplerate_t targetRate, bool normalised, ProgressReporter *reporter) : CodedAudioFileReader(mode, targetRate, normalised), m_source(source), m_path(source.getLocalFilename()), + m_gaplessMode(gaplessMode), + m_decodeErrorShown(false), m_decodeThread(0) { + SVDEBUG << "MP3FileReader: local path: \"" << m_path + << "\", decode mode: " << decodeMode << " (" + << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded") + << ")" << endl; + m_channelCount = 0; m_fileRate = 0; m_fileSize = 0; m_bitrateNum = 0; m_bitrateDenom = 0; m_cancelled = false; + m_mp3FrameCount = 0; m_completion = 0; m_done = false; m_reporter = reporter; - struct stat stat; - if (::stat(m_path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) { - m_error = QString("File %1 does not exist.").arg(m_path); - return; + if (m_gaplessMode == GaplessMode::Gapless) { + CodedAudioFileReader::setFramesToTrim(DEFAULT_DECODER_DELAY, 0); + } + + m_fileSize = 0; + + m_fileBuffer = 0; + m_fileBufferSize = 0; + + m_sampleBuffer = 0; + m_sampleBufferSize = 0; + + QFile qfile(m_path); + if (!qfile.open(QIODevice::ReadOnly)) { + m_error = QString("Failed to open file %1 for reading.").arg(m_path); + SVDEBUG << "MP3FileReader: " << m_error << endl; + return; + } + + m_fileSize = qfile.size(); + + try { + // We need a mysterious MAD_BUFFER_GUARD (== 8) zero bytes at + // end of input, to ensure libmad decodes the last frame + // correctly. Otherwise the decoded audio is truncated. + m_fileBufferSize = m_fileSize + MAD_BUFFER_GUARD; + m_fileBuffer = new unsigned char[m_fileBufferSize]; + memset(m_fileBuffer + m_fileSize, 0, MAD_BUFFER_GUARD); + } catch (...) { + m_error = QString("Out of memory"); + SVDEBUG << "MP3FileReader: " << m_error << endl; + return; } - m_fileSize = stat.st_size; + auto amountRead = qfile.read(reinterpret_cast<char *>(m_fileBuffer), + m_fileSize); - m_filebuffer = 0; - m_samplebuffer = 0; - m_samplebuffersize = 0; + if (amountRead < m_fileSize) { + SVCERR << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes") + .arg(amountRead).arg(m_fileSize) << endl; + memset(m_fileBuffer + amountRead, 0, m_fileSize - amountRead); + m_fileSize = amountRead; + } + + loadTags(qfile.handle()); - int fd = -1; - if ((fd = ::open(m_path.toLocal8Bit().data(), O_RDONLY -#ifdef _WIN32 - | O_BINARY -#endif - , 0)) < 0) { - m_error = QString("Failed to open file %1 for reading.").arg(m_path); - return; - } - - try { - m_filebuffer = new unsigned char[m_fileSize]; - } catch (...) { - m_error = QString("Out of memory"); - ::close(fd); - return; - } - - ssize_t sz = 0; - ssize_t offset = 0; - while (offset < m_fileSize) { - sz = ::read(fd, m_filebuffer + offset, m_fileSize - offset); - if (sz < 0) { - m_error = QString("Read error for file %1 (after %2 bytes)") - .arg(m_path).arg(offset); - delete[] m_filebuffer; - ::close(fd); - return; - } else if (sz == 0) { - cerr << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes") - .arg(offset).arg(m_fileSize) << endl; - m_fileSize = offset; - break; - } - offset += sz; - } - - ::close(fd); - - loadTags(); + qfile.close(); if (decodeMode == DecodeAtOnce) { @@ -117,12 +132,12 @@ (tr("Decoding %1...").arg(QFileInfo(m_path).fileName())); } - if (!decode(m_filebuffer, m_fileSize)) { + if (!decode(m_fileBuffer, m_fileBufferSize)) { m_error = QString("Failed to decode file %1.").arg(m_path); } - delete[] m_filebuffer; - m_filebuffer = 0; + delete[] m_fileBuffer; + m_fileBuffer = 0; if (isDecodeCacheInitialised()) finishDecodeCache(); endSerialised(); @@ -139,11 +154,11 @@ usleep(10); } - cerr << "MP3FileReader ctor: exiting with file rate = " << m_fileRate << endl; + SVDEBUG << "MP3FileReader: decoding startup complete, file rate = " << m_fileRate << endl; } if (m_error != "") { - cerr << "MP3FileReader::MP3FileReader(\"" << m_path << "\"): ERROR: " << m_error << endl; + SVDEBUG << "MP3FileReader::MP3FileReader(\"" << m_path << "\"): ERROR: " << m_error << endl; } } @@ -163,14 +178,19 @@ } void -MP3FileReader::loadTags() +MP3FileReader::loadTags(int fd) { m_title = ""; #ifdef HAVE_ID3TAG - id3_file *file = id3_file_open(m_path.toLocal8Bit().data(), - ID3_FILE_MODE_READONLY); +#ifdef _WIN32 + int id3fd = _dup(fd); +#else + int id3fd = dup(fd); +#endif + + id3_file *file = id3_file_fdopen(id3fd, ID3_FILE_MODE_READONLY); if (!file) return; // We can do this a lot more elegantly, but we'll leave that for @@ -178,33 +198,32 @@ id3_tag *tag = id3_file_tag(file); if (!tag) { -#ifdef DEBUG_ID3TAG - cerr << "MP3FileReader::loadTags: No ID3 tag found" << endl; -#endif - id3_file_close(file); + SVDEBUG << "MP3FileReader::loadTags: No ID3 tag found" << endl; + id3_file_close(file); // also closes our dup'd fd return; } m_title = loadTag(tag, "TIT2"); // work title if (m_title == "") m_title = loadTag(tag, "TIT1"); + if (m_title == "") SVDEBUG << "MP3FileReader::loadTags: No title found" << endl; m_maker = loadTag(tag, "TPE1"); // "lead artist" if (m_maker == "") m_maker = loadTag(tag, "TPE2"); + if (m_maker == "") SVDEBUG << "MP3FileReader::loadTags: No artist/maker found" << endl; for (unsigned int i = 0; i < tag->nframes; ++i) { if (tag->frames[i]) { QString value = loadTag(tag, tag->frames[i]->id); - if (value != "") m_tags[tag->frames[i]->id] = value; + if (value != "") { + m_tags[tag->frames[i]->id] = value; + } } } - id3_file_close(file); + id3_file_close(file); // also closes our dup'd fd #else -#ifdef DEBUG_ID3TAG - cerr << "MP3FileReader::loadTags: ID3 tag support not compiled in" - << endl; -#endif + SVDEBUG << "MP3FileReader::loadTags: ID3 tag support not compiled in" << endl; #endif } @@ -216,47 +235,38 @@ id3_frame *frame = id3_tag_findframe(tag, name, 0); if (!frame) { -#ifdef DEBUG_ID3TAG - cerr << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << endl; -#endif + SVDEBUG << "MP3FileReader::loadTag: No \"" << name << "\" frame found in ID3 tag" << endl; return ""; } if (frame->nfields < 2) { - cerr << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl; + cerr << "MP3FileReader::loadTag: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl; return ""; } unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]); if (nstrings == 0) { -#ifdef DEBUG_ID3TAG - cerr << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << endl; -#endif + SVDEBUG << "MP3FileReader::loadTag: No strings for \"" << name << "\" in ID3 tag" << endl; return ""; } id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0); if (!ustr) { -#ifdef DEBUG_ID3TAG - cerr << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl; -#endif + SVDEBUG << "MP3FileReader::loadTag: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl; return ""; } id3_utf8_t *u8str = id3_ucs4_utf8duplicate(ustr); if (!u8str) { - cerr << "MP3FileReader::loadTags: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 title" << endl; + SVDEBUG << "MP3FileReader::loadTag: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 tag" << endl; return ""; } QString rv = QString::fromUtf8((const char *)u8str); free(u8str); -#ifdef DEBUG_ID3TAG - cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \"" - << rv << "\"" << endl; -#endif - + SVDEBUG << "MP3FileReader::loadTag: Tag \"" << name << "\" -> \"" + << rv << "\"" << endl; return rv; @@ -268,19 +278,19 @@ void MP3FileReader::DecodeThread::run() { - if (!m_reader->decode(m_reader->m_filebuffer, m_reader->m_fileSize)) { + if (!m_reader->decode(m_reader->m_fileBuffer, m_reader->m_fileBufferSize)) { m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path); } - delete[] m_reader->m_filebuffer; - m_reader->m_filebuffer = 0; + delete[] m_reader->m_fileBuffer; + m_reader->m_fileBuffer = 0; - if (m_reader->m_samplebuffer) { + if (m_reader->m_sampleBuffer) { for (int c = 0; c < m_reader->m_channelCount; ++c) { - delete[] m_reader->m_samplebuffer[c]; + delete[] m_reader->m_sampleBuffer[c]; } - delete[] m_reader->m_samplebuffer; - m_reader->m_samplebuffer = 0; + delete[] m_reader->m_sampleBuffer; + m_reader->m_sampleBuffer = 0; } if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); @@ -298,35 +308,51 @@ struct mad_decoder decoder; data.start = (unsigned char const *)mm; - data.length = (unsigned long)sz; + data.length = sz; + data.finished = false; data.reader = this; - mad_decoder_init(&decoder, &data, input, 0, 0, output, error, 0); + mad_decoder_init(&decoder, // decoder to initialise + &data, // our own data block for callbacks + input_callback, // provides (entire) input to mad + 0, // checks header + filter_callback, // filters frame before decoding + output_callback, // receives decoded output + error_callback, // handles decode errors + 0); // "message_func" + mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC); mad_decoder_finish(&decoder); + SVDEBUG << "MP3FileReader: Decoding complete, decoded " << m_mp3FrameCount + << " mp3 frames" << endl; + m_done = true; return true; } enum mad_flow -MP3FileReader::input(void *dp, struct mad_stream *stream) +MP3FileReader::input_callback(void *dp, struct mad_stream *stream) { DecoderData *data = (DecoderData *)dp; - if (!data->length) return MAD_FLOW_STOP; + if (!data->length) { + data->finished = true; + return MAD_FLOW_STOP; + } unsigned char const *start = data->start; - unsigned long length = data->length; + sv_frame_t length = data->length; #ifdef HAVE_ID3TAG - if (length > ID3_TAG_QUERYSIZE) { + while (length > ID3_TAG_QUERYSIZE) { ssize_t taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE); - if (taglen > 0) { -// cerr << "ID3 tag length to skip: " << taglen << endl; - start += taglen; - length -= taglen; + if (taglen <= 0) { + break; } + SVDEBUG << "MP3FileReader: ID3 tag length to skip: " << taglen << endl; + start += taglen; + length -= taglen; } #endif @@ -337,9 +363,107 @@ } enum mad_flow -MP3FileReader::output(void *dp, - struct mad_header const *header, - struct mad_pcm *pcm) +MP3FileReader::filter_callback(void *dp, + struct mad_stream const *stream, + struct mad_frame *frame) +{ + DecoderData *data = (DecoderData *)dp; + return data->reader->filter(stream, frame); +} + +static string toMagic(unsigned long fourcc) +{ + string magic("...."); + for (int i = 0; i < 4; ++i) { + magic[3-i] = char((fourcc >> (8*i)) & 0xff); + } + return magic; +} + +enum mad_flow +MP3FileReader::filter(struct mad_stream const *stream, + struct mad_frame *) +{ + if (m_mp3FrameCount > 0) { + // only handle info frame if it appears as first mp3 frame + return MAD_FLOW_CONTINUE; + } + + if (m_gaplessMode == GaplessMode::Gappy) { + // Our non-gapless mode does not even filter out the Xing/LAME + // frame. That's because the main reason non-gapless mode + // exists is for backward compatibility with MP3FileReader + // behaviour before the gapless support was added, so we even + // need to keep the spurious 1152 samples resulting from + // feeding Xing/LAME frame to the decoder as otherwise we'd + // have different output from before. + SVDEBUG << "MP3FileReader: Not gapless mode, not checking Xing/LAME frame" + << endl; + return MAD_FLOW_CONTINUE; + } + + struct mad_bitptr ptr = stream->anc_ptr; + string magic = toMagic(mad_bit_read(&ptr, 32)); + + if (magic == "Xing" || magic == "Info") { + + SVDEBUG << "MP3FileReader: Found Xing/LAME metadata frame (magic = \"" + << magic << "\")" << endl; + + // All we want at this point is the LAME encoder delay and + // padding values. We expect to see the Xing/Info magic (which + // we've already read), then 116 bytes of Xing data, then LAME + // magic, 5 byte version string, 12 bytes of LAME data that we + // aren't currently interested in, then the delays encoded as + // two 12-bit numbers into three bytes. + // + // (See gabriel.mp3-tech.org/mp3infotag.html) + + for (int skip = 0; skip < 116; ++skip) { + (void)mad_bit_read(&ptr, 8); + } + + magic = toMagic(mad_bit_read(&ptr, 32)); + + if (magic == "LAME") { + + SVDEBUG << "MP3FileReader: Found LAME-specific metadata" << endl; + + for (int skip = 0; skip < 5 + 12; ++skip) { + (void)mad_bit_read(&ptr, 8); + } + + auto delay = mad_bit_read(&ptr, 12); + auto padding = mad_bit_read(&ptr, 12); + + sv_frame_t delayToDrop = DEFAULT_DECODER_DELAY + delay; + sv_frame_t paddingToDrop = padding - DEFAULT_DECODER_DELAY; + if (paddingToDrop < 0) paddingToDrop = 0; + + SVDEBUG << "MP3FileReader: LAME encoder delay = " << delay + << ", padding = " << padding << endl; + + SVDEBUG << "MP3FileReader: Will be trimming " << delayToDrop + << " samples from start and " << paddingToDrop + << " from end" << endl; + + CodedAudioFileReader::setFramesToTrim(delayToDrop, paddingToDrop); + + } else { + SVDEBUG << "MP3FileReader: Xing frame has no LAME metadata" << endl; + } + + return MAD_FLOW_IGNORE; + + } else { + return MAD_FLOW_CONTINUE; + } +} + +enum mad_flow +MP3FileReader::output_callback(void *dp, + struct mad_header const *header, + struct mad_pcm *pcm) { DecoderData *data = (DecoderData *)dp; return data->reader->accept(header, pcm); @@ -347,11 +471,11 @@ enum mad_flow MP3FileReader::accept(struct mad_header const *header, - struct mad_pcm *pcm) + struct mad_pcm *pcm) { int channels = pcm->channels; int frames = pcm->length; - + if (header) { m_bitrateNum = m_bitrateNum + double(header->bitrate); m_bitrateDenom ++; @@ -364,6 +488,10 @@ m_fileRate = pcm->samplerate; m_channelCount = channels; + SVDEBUG << "MP3FileReader::accept: file rate = " << pcm->samplerate + << ", channel count = " << channels << ", about to init " + << "decode cache" << endl; + initialiseDecodeCache(); if (m_cacheMode == CacheInTemporaryFile) { @@ -387,24 +515,30 @@ } } - if (m_cancelled) return MAD_FLOW_STOP; + if (m_cancelled) { + SVDEBUG << "MP3FileReader: Decoding cancelled" << endl; + return MAD_FLOW_STOP; + } if (!isDecodeCacheInitialised()) { + SVDEBUG << "MP3FileReader::accept: fallback case: file rate = " << pcm->samplerate + << ", channel count = " << channels << ", about to init " + << "decode cache" << endl; initialiseDecodeCache(); } - if (int(m_samplebuffersize) < frames) { - if (!m_samplebuffer) { - m_samplebuffer = new float *[channels]; + if (m_sampleBufferSize < size_t(frames)) { + if (!m_sampleBuffer) { + m_sampleBuffer = new float *[channels]; for (int c = 0; c < channels; ++c) { - m_samplebuffer[c] = 0; + m_sampleBuffer[c] = 0; } } for (int c = 0; c < channels; ++c) { - delete[] m_samplebuffer[c]; - m_samplebuffer[c] = new float[frames]; + delete[] m_sampleBuffer[c]; + m_sampleBuffer[c] = new float[frames]; } - m_samplebuffersize = frames; + m_sampleBufferSize = frames; } int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0])); @@ -413,31 +547,48 @@ for (int i = 0; i < frames; ++i) { - mad_fixed_t sample = 0; - if (ch < activeChannels) { - sample = pcm->samples[ch][i]; - } - float fsample = float(sample) / float(MAD_F_ONE); + mad_fixed_t sample = 0; + if (ch < activeChannels) { + sample = pcm->samples[ch][i]; + } + float fsample = float(sample) / float(MAD_F_ONE); - m_samplebuffer[ch][i] = fsample; - } + m_sampleBuffer[ch][i] = fsample; + } } - addSamplesToDecodeCache(m_samplebuffer, frames); + addSamplesToDecodeCache(m_sampleBuffer, frames); + + ++m_mp3FrameCount; return MAD_FLOW_CONTINUE; } enum mad_flow -MP3FileReader::error(void * /* dp */, - struct mad_stream * /* stream */, - struct mad_frame *) +MP3FileReader::error_callback(void *dp, + struct mad_stream *stream, + struct mad_frame *) { -// DecoderData *data = (DecoderData *)dp; + DecoderData *data = (DecoderData *)dp; -// fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %lu\n", -// stream->error, mad_stream_errorstr(stream), -// (unsigned long)(stream->this_frame - data->start)); + sv_frame_t ix = stream->this_frame - data->start; + + if (stream->error == MAD_ERROR_LOSTSYNC && + (data->finished || ix >= data->length)) { + // We are at end of file, losing sync is expected behaviour, + // don't report it + return MAD_FLOW_CONTINUE; + } + + if (!data->reader->m_decodeErrorShown) { + char buffer[256]; + snprintf(buffer, 255, + "MP3 decoding error 0x%04x (%s) at byte offset %lld", + stream->error, mad_stream_errorstr(stream), (long long int)ix); + SVCERR << "Warning: in file \"" << data->reader->m_path << "\": " + << buffer << " (continuing; will not report any further decode errors for this file)" << endl; + data->reader->m_decodeErrorShown = true; + } return MAD_FLOW_CONTINUE; }
--- a/data/fileio/MP3FileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/MP3FileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -32,14 +32,43 @@ Q_OBJECT public: - enum DecodeMode { - DecodeAtOnce, // decode the file on construction, with progress - DecodeThreaded // decode in a background thread after construction + /** + * How the MP3FileReader should handle leading and trailing gaps. + * See http://lame.sourceforge.net/tech-FAQ.txt for a technical + * explanation of the numbers here. + */ + enum class GaplessMode { + /** + * Trim unwanted samples from the start and end of the decoded + * audio. From the start, trim a number of samples equal to + * the decoder delay (a fixed 529 samples) plus any encoder + * delay that may be specified in Xing/LAME metadata. From the + * end, trim any padding specified in Xing/LAME metadata, less + * the fixed decoder delay. This usually results in "gapless" + * audio, i.e. with no spurious zero padding at either end. + */ + Gapless, + + /** + * Do not trim any samples. Also do not suppress any frames + * from being passed to the mp3 decoder, even Xing/LAME + * metadata frames. This will result in the audio being padded + * with zeros at either end: at the start, typically + * 529+576+1152 = 2257 samples for LAME-encoded mp3s; at the + * end an unknown number depending on the fill ratio of the + * final coded frame, but typically less than 1152-529 = 623. + * + * This mode produces the same output as produced by older + * versions of this code before the gapless option was added, + * and is present mostly for backward compatibility. + */ + Gappy }; - + MP3FileReader(FileSource source, DecodeMode decodeMode, CacheMode cacheMode, + GaplessMode gaplessMode, sv_samplerate_t targetRate = 0, bool normalised = false, ProgressReporter *reporter = 0); @@ -73,32 +102,43 @@ QString m_title; QString m_maker; TagMap m_tags; + GaplessMode m_gaplessMode; sv_frame_t m_fileSize; double m_bitrateNum; int m_bitrateDenom; + int m_mp3FrameCount; int m_completion; bool m_done; - unsigned char *m_filebuffer; - float **m_samplebuffer; - int m_samplebuffersize; + unsigned char *m_fileBuffer; + size_t m_fileBufferSize; + + float **m_sampleBuffer; + size_t m_sampleBufferSize; ProgressReporter *m_reporter; bool m_cancelled; - struct DecoderData - { + bool m_decodeErrorShown; + + struct DecoderData { unsigned char const *start; - unsigned long length; + sv_frame_t length; + bool finished; MP3FileReader *reader; }; bool decode(void *mm, sv_frame_t sz); + enum mad_flow filter(struct mad_stream const *, struct mad_frame *); enum mad_flow accept(struct mad_header const *, struct mad_pcm *); - static enum mad_flow input(void *, struct mad_stream *); - static enum mad_flow output(void *, struct mad_header const *, struct mad_pcm *); - static enum mad_flow error(void *, struct mad_stream *, struct mad_frame *); + static enum mad_flow input_callback(void *, struct mad_stream *); + static enum mad_flow output_callback(void *, struct mad_header const *, + struct mad_pcm *); + static enum mad_flow filter_callback(void *, struct mad_stream const *, + struct mad_frame *); + static enum mad_flow error_callback(void *, struct mad_stream *, + struct mad_frame *); class DecodeThread : public Thread { @@ -112,7 +152,7 @@ DecodeThread *m_decodeThread; - void loadTags(); + void loadTags(int fd); QString loadTag(void *vtag, const char *name); };
--- a/data/fileio/MatrixFile.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,451 +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-2009 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. -*/ - -#include "MatrixFile.h" -#include "base/TempDirectory.h" -#include "system/System.h" -#include "base/Profiler.h" -#include "base/Exceptions.h" -#include "base/Thread.h" - -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h> - -#include <iostream> - -#include <cstdio> -#include <cassert> - -#include <cstdlib> - -#include <QFileInfo> -#include <QDir> - -//#define DEBUG_MATRIX_FILE 1 -//#define DEBUG_MATRIX_FILE_READ_SET 1 - -#ifdef DEBUG_MATRIX_FILE_READ_SET -#ifndef DEBUG_MATRIX_FILE -#define DEBUG_MATRIX_FILE 1 -#endif -#endif - -std::map<QString, int> MatrixFile::m_refcount; -QMutex MatrixFile::m_createMutex; - -static size_t totalStorage = 0; -static size_t totalCount = 0; -static size_t openCount = 0; - -MatrixFile::MatrixFile(QString fileBase, Mode mode, - int cellSize, int width, int height) : - m_fd(-1), - m_mode(mode), - m_flags(0), - m_fmode(0), - m_cellSize(cellSize), - m_width(width), - m_height(height), - m_headerSize(2 * sizeof(int)), - m_setColumns(0), - m_autoClose(false), - m_readyToReadColumn(-1) -{ - Profiler profiler("MatrixFile::MatrixFile", true); - -#ifdef DEBUG_MATRIX_FILE - SVDEBUG << "MatrixFile::MatrixFile(" << fileBase << ", " << int(mode) << ", " << cellSize << ", " << width << ", " << height << ")" << endl; -#endif - - m_createMutex.lock(); - - QDir tempDir(TempDirectory::getInstance()->getPath()); - QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); - bool newFile = !QFileInfo(fileName).exists(); - - if (newFile && m_mode == ReadOnly) { - cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode " - << "specified, but cache file does not exist" << endl; - throw FileNotFound(fileName); - } - - if (!newFile && m_mode == WriteOnly) { - cerr << "ERROR: MatrixFile::MatrixFile: Write-only mode " - << "specified, but file already exists" << endl; - throw FileOperationFailed(fileName, "create"); - } - - // Use floating-point here to avoid integer overflow. We can be - // approximate so long as we are on the cautious side - if ((double(m_width) * m_height) * m_cellSize + m_headerSize + m_width >= - pow(2, 31) - 10.0) { // bit of slack there - cerr << "ERROR: MatrixFile::MatrixFile: width " << m_width - << " is too large for height " << m_height << " and cell size " - << m_cellSize << " (should be using multiple files)" << endl; - throw FileOperationFailed(fileName, "size"); - } - - m_flags = 0; - m_fmode = S_IRUSR | S_IWUSR; - - if (m_mode == WriteOnly) { - m_flags = O_WRONLY | O_CREAT; - } else { - m_flags = O_RDONLY; - } - -#ifdef _WIN32 - m_flags |= O_BINARY; -#endif - -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile(" << this << ")::MatrixFile: opening " << fileName << "..." << endl; -#endif - - if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) { - ::perror("Open failed"); - cerr << "ERROR: MatrixFile::MatrixFile: " - << "Failed to open cache file \"" - << fileName << "\""; - if (m_mode == WriteOnly) cerr << " for writing"; - cerr << endl; - throw FailedToOpenFile(fileName); - } - - m_createMutex.unlock(); - -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile(" << this << ")::MatrixFile: fd is " << m_fd << endl; -#endif - - if (newFile) { - initialise(); // write header and "unwritten" column tags - } else { - int header[2]; - if (::read(m_fd, header, 2 * sizeof(int)) < 0) { - ::perror("MatrixFile::MatrixFile: read failed"); - cerr << "ERROR: MatrixFile::MatrixFile: " - << "Failed to read header (fd " << m_fd << ", file \"" - << fileName << "\")" << endl; - throw FileReadFailed(fileName); - } - if (header[0] != m_width || header[1] != m_height) { - cerr << "ERROR: MatrixFile::MatrixFile: " - << "Dimensions in file header (" << header[0] << "x" - << header[1] << ") differ from expected dimensions " - << m_width << "x" << m_height << endl; - throw FailedToOpenFile(fileName); - } - } - - m_fileName = fileName; - ++m_refcount[fileName]; - -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile[" << m_fd << "]::MatrixFile: File " << fileName << ", ref " << m_refcount[fileName] << endl; - - cerr << "MatrixFile[" << m_fd << "]::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << endl; -#endif - - ++totalCount; - ++openCount; -} - -MatrixFile::~MatrixFile() -{ - if (m_fd >= 0) { - if (::close(m_fd) < 0) { - ::perror("MatrixFile::~MatrixFile: close failed"); - } - openCount --; - } - - QMutexLocker locker(&m_createMutex); - - delete m_setColumns; - - if (m_fileName != "") { - - if (--m_refcount[m_fileName] == 0) { - - if (::unlink(m_fileName.toLocal8Bit())) { - cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName << "\"" << endl; - } else { - cerr << "deleted " << m_fileName << endl; - } - } - } - - if (m_mode == WriteOnly) { - totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize) + m_width); - } - totalCount --; - -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile[" << m_fd << "]::~MatrixFile: " << endl; - cerr << "MatrixFile: Total storage now " << totalStorage/1024 << "K in " << totalCount << " instances (" << openCount << " open)" << endl; -#endif -} - -void -MatrixFile::initialise() -{ - Profiler profiler("MatrixFile::initialise", true); - - assert(m_mode == WriteOnly); - - m_setColumns = new ResizeableBitset(m_width); - - off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width; - -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): cell size " << m_cellSize << ", header size " << m_headerSize << ", resizing fd " << m_fd << " to " << off << endl; -#endif - - if (::lseek(m_fd, off - 1, SEEK_SET) < 0) { - ::perror("ERROR: MatrixFile::initialise: seek to end failed"); - throw FileOperationFailed(m_fileName, "lseek"); - } - - unsigned char byte = 0; - if (::write(m_fd, &byte, 1) != 1) { - ::perror("ERROR: MatrixFile::initialise: write at end failed"); - throw FileOperationFailed(m_fileName, "write"); - } - - if (::lseek(m_fd, 0, SEEK_SET) < 0) { - ::perror("ERROR: MatrixFile::initialise: Seek to write header failed"); - throw FileOperationFailed(m_fileName, "lseek"); - } - - int header[2]; - header[0] = m_width; - header[1] = m_height; - if (::write(m_fd, header, 2 * sizeof(int)) != 2 * sizeof(int)) { - ::perror("ERROR: MatrixFile::initialise: Failed to write header"); - throw FileOperationFailed(m_fileName, "write"); - } - - if (m_mode == WriteOnly) { - totalStorage += (m_headerSize + (m_width * m_height * m_cellSize) + m_width); - } - -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile[" << m_fd << "]::initialise(" << m_width << ", " << m_height << "): storage " - << (m_headerSize + m_width * m_height * m_cellSize + m_width) << endl; - - cerr << "MatrixFile: Total storage " << totalStorage/1024 << "K" << endl; -#endif - - seekTo(0); -} - -void -MatrixFile::close() -{ -#ifdef DEBUG_MATRIX_FILE - SVDEBUG << "MatrixFile::close()" << endl; -#endif - if (m_fd >= 0) { - if (::close(m_fd) < 0) { - ::perror("MatrixFile::close: close failed"); - } - m_fd = -1; - -- openCount; -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile: Now " << openCount << " open instances" << endl; -#endif - } -} - -void -MatrixFile::getColumnAt(int x, void *data) -{ - assert(m_mode == ReadOnly); - -#ifdef DEBUG_MATRIX_FILE_READ_SET - cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << ")" << endl; -#endif - - Profiler profiler("MatrixFile::getColumnAt"); - - ssize_t r = -1; - - if (m_readyToReadColumn < 0 || - m_readyToReadColumn != x) { - - unsigned char set = 0; - if (!seekTo(x)) { - cerr << "ERROR: MatrixFile::getColumnAt(" << x << "): Seek failed" << endl; - throw FileOperationFailed(m_fileName, "seek"); - } - - r = ::read(m_fd, &set, 1); - if (r < 0) { - ::perror("MatrixFile::getColumnAt: read failed"); - throw FileReadFailed(m_fileName); - } - if (!set) { - cerr << "MatrixFile[" << m_fd << "]::getColumnAt(" << x << "): Column has not been set" << endl; - return; - } - } - - r = ::read(m_fd, data, m_height * m_cellSize); - if (r < 0) { - ::perror("MatrixFile::getColumnAt: read failed"); - throw FileReadFailed(m_fileName); - } -} - -bool -MatrixFile::haveSetColumnAt(int x) const -{ - if (m_mode == WriteOnly) { - return m_setColumns->get(x); - } - - if (m_readyToReadColumn >= 0 && - int(m_readyToReadColumn) == x) return true; - - Profiler profiler("MatrixFile::haveSetColumnAt"); - -#ifdef DEBUG_MATRIX_FILE_READ_SET - cerr << "MatrixFile[" << m_fd << "]::haveSetColumnAt(" << x << ")" << endl; -// cerr << "."; -#endif - - unsigned char set = 0; - if (!seekTo(x)) { - cerr << "ERROR: MatrixFile::haveSetColumnAt(" << x << "): Seek failed" << endl; - throw FileOperationFailed(m_fileName, "seek"); - } - - ssize_t r = -1; - r = ::read(m_fd, &set, 1); - if (r < 0) { - ::perror("MatrixFile::haveSetColumnAt: read failed"); - throw FileReadFailed(m_fileName); - } - - if (set) m_readyToReadColumn = int(x); - - return set; -} - -void -MatrixFile::setColumnAt(int x, const void *data) -{ - assert(m_mode == WriteOnly); - if (m_fd < 0) return; // closed - -#ifdef DEBUG_MATRIX_FILE_READ_SET - cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << ")" << endl; -// cerr << "."; -#endif - - ssize_t w = 0; - - if (!seekTo(x)) { - cerr << "ERROR: MatrixFile::setColumnAt(" << x << "): Seek failed" << endl; - throw FileOperationFailed(m_fileName, "seek"); - } - - unsigned char set = 0; - w = ::write(m_fd, &set, 1); - if (w != 1) { - ::perror("WARNING: MatrixFile::setColumnAt: write failed (1)"); - throw FileOperationFailed(m_fileName, "write"); - } - - w = ::write(m_fd, data, m_height * m_cellSize); - if (w != ssize_t(m_height * m_cellSize)) { - ::perror("WARNING: MatrixFile::setColumnAt: write failed (2)"); - throw FileOperationFailed(m_fileName, "write"); - } -/* - if (x == 0) { - cerr << "Wrote " << m_height * m_cellSize << " bytes, as follows:" << endl; - for (int i = 0; i < m_height * m_cellSize; ++i) { - cerr << (int)(((char *)data)[i]) << " "; - } - cerr << endl; - } -*/ - if (!seekTo(x)) { - cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Seek failed" << endl; - throw FileOperationFailed(m_fileName, "seek"); - } - - set = 1; - w = ::write(m_fd, &set, 1); - if (w != 1) { - ::perror("WARNING: MatrixFile::setColumnAt: write failed (3)"); - throw FileOperationFailed(m_fileName, "write"); - } - - m_setColumns->set(x); - if (m_autoClose) { - if (m_setColumns->isAllOn()) { -#ifdef DEBUG_MATRIX_FILE - cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): All columns set: auto-closing" << endl; -#endif - close(); -/* - } else { - int set = 0; - for (int i = 0; i < m_width; ++i) { - if (m_setColumns->get(i)) ++set; - } - cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): Auto-close on, but not all columns set yet (" << set << " of " << m_width << ")" << endl; -*/ - } - } -} - -bool -MatrixFile::seekTo(int x) const -{ - if (m_fd < 0) { - cerr << "ERROR: MatrixFile::seekTo: File not open" << endl; - return false; - } - - m_readyToReadColumn = -1; // not ready, unless this is subsequently re-set - - off_t off = m_headerSize + x * m_height * m_cellSize + x; - -#ifdef DEBUG_MATRIX_FILE_READ_SET - if (m_mode == ReadOnly) { - cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << endl; - } -#endif - -#ifdef DEBUG_MATRIX_FILE_READ_SET - cerr << "MatrixFile[" << m_fd << "]::seekTo(" << x << "): off = " << off << endl; -#endif - - if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { - ::perror("Seek failed"); - cerr << "ERROR: MatrixFile::seekTo(" << x - << ") = " << off << " failed" << endl; - return false; - } - - return true; -} -
--- a/data/fileio/MatrixFile.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +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-2009 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 _MATRIX_FILE_CACHE_H_ -#define _MATRIX_FILE_CACHE_H_ - -#include "base/ResizeableBitset.h" - -#include "FileReadThread.h" - -#include <sys/types.h> -#include <QString> -#include <QMutex> -#include <map> - -class MatrixFile : public QObject -{ - Q_OBJECT - -public: - enum Mode { ReadOnly, WriteOnly }; - - /** - * Construct a MatrixFile object reading from and/or writing to - * the matrix file with the given base name in the application's - * temporary directory. - * - * If mode is ReadOnly, the file must exist and be readable. - * - * If mode is WriteOnly, the file must not exist. - * - * cellSize specifies the size in bytes of the object type stored - * in the matrix. For example, use cellSize = sizeof(float) for a - * matrix of floats. The MatrixFile object doesn't care about the - * objects themselves, it just deals with raw data of a given size. - * - * width and height specify the dimensions of the file. These - * cannot be changed after construction. - * - * MatrixFiles are reference counted by name. When the last - * MatrixFile with a given name is destroyed, the file is removed. - * These are temporary files; the normal usage is to have one - * MatrixFile of WriteOnly type creating the file and then - * persisting until all readers are complete. - * - * MatrixFile has no built-in cache and is not thread-safe. Use a - * separate MatrixFile in each thread. - */ - MatrixFile(QString fileBase, Mode mode, int cellSize, - int width, int height); - virtual ~MatrixFile(); - - Mode getMode() const { return m_mode; } - - int getWidth() const { return m_width; } - int getHeight() const { return m_height; } - int getCellSize() const { return m_cellSize; } - - /** - * If this is set true on a write-mode MatrixFile, then the file - * will close() itself when all columns have been written. - */ - void setAutoClose(bool a) { m_autoClose = a; } - - void close(); // does not decrement ref count; that happens in dtor - - bool haveSetColumnAt(int x) const; - void getColumnAt(int x, void *data); // may throw FileReadFailed - void setColumnAt(int x, const void *data); - -protected: - int m_fd; - Mode m_mode; - int m_flags; - mode_t m_fmode; - int m_cellSize; - int m_width; - int m_height; - int m_headerSize; - QString m_fileName; - - ResizeableBitset *m_setColumns; // only in writer - bool m_autoClose; - - // In reader: if this is >= 0, we can read that column directly - // without seeking (and we know that the column exists) - mutable int m_readyToReadColumn; - - static std::map<QString, int> m_refcount; - static QMutex m_createMutex; - - void initialise(); - bool seekTo(int col) const; -}; - -#endif -
--- a/data/fileio/OggVorbisFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/OggVorbisFileReader.cpp Fri Jan 13 10:29:44 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), @@ -48,19 +52,48 @@ m_completion(0), m_decodeThread(0) { + 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", true); + 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_error = QString("File %1 is not an OGG file.").arg(m_path); - return; + 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; } FishSoundInfo fsinfo; @@ -109,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 @@ -129,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; @@ -167,7 +211,7 @@ int OggVorbisFileReader::acceptFrames(FishSound *fs, float **frames, long nframes, - void *data) + void *data) { OggVorbisFileReader *reader = (OggVorbisFileReader *)data; @@ -191,11 +235,11 @@ } 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; + 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(); }
--- a/data/fileio/OggVorbisFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/OggVorbisFileReader.h Fri Jan 13 10:29:44 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; @@ -34,17 +36,12 @@ Q_OBJECT public: - enum DecodeMode { - DecodeAtOnce, // decode the file on construction, with progress - DecodeThreaded // decode in a background thread after construction - }; - OggVorbisFileReader(FileSource source, DecodeMode decodeMode, CacheMode cacheMode, sv_samplerate_t targetRate = 0, bool normalised = false, - ProgressReporter *reporter = 0); + ProgressReporter *reporter = nullptr); virtual ~OggVorbisFileReader(); virtual QString getError() const { return m_error; } @@ -76,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/QuickTimeFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,380 +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-2007 Chris Cannam and QMUL. - - Based on QTAudioFile.cpp from SoundBite, copyright 2006 - Chris Sutton and Mark Levy. - - 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_QUICKTIME - -#include "QuickTimeFileReader.h" -#include "base/Profiler.h" -#include "base/ProgressReporter.h" -#include "system/System.h" - -#include <QFileInfo> - -#ifdef _WIN32 -#include <QTML.h> -#include <Movies.h> -#else -#include <QuickTime/QuickTime.h> -#endif - -class QuickTimeFileReader::D -{ -public: - D() : data(0), blockSize(1024) { } - - MovieAudioExtractionRef extractionSessionRef; - AudioBufferList buffer; - float *data; - OSErr err; - AudioStreamBasicDescription asbd; - Movie movie; - int blockSize; -}; - - -QuickTimeFileReader::QuickTimeFileReader(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) -{ - m_channelCount = 0; - m_fileRate = 0; - - Profiler profiler("QuickTimeFileReader::QuickTimeFileReader", true); - -SVDEBUG << "QuickTimeFileReader: path is \"" << m_path << "\"" << endl; - - long QTversion; - -#ifdef WIN32 - InitializeQTML(0); // FIXME should check QT version -#else - m_d->err = Gestalt(gestaltQuickTime,&QTversion); - if ((m_d->err != noErr) || (QTversion < 0x07000000)) { - m_error = QString("Failed to find compatible version of QuickTime (version 7 or above required)"); - return; - } -#endif - - EnterMovies(); - - Handle dataRef; - OSType dataRefType; - -// CFStringRef URLString = CFStringCreateWithCString - // (0, m_path.toLocal8Bit().data(), 0); - - - QByteArray ba = m_path.toLocal8Bit(); - - CFURLRef url = CFURLCreateFromFileSystemRepresentation - (kCFAllocatorDefault, - (const UInt8 *)ba.data(), - (CFIndex)ba.length(), - false); - - -// m_d->err = QTNewDataReferenceFromURLCFString - m_d->err = QTNewDataReferenceFromCFURL - (url, 0, &dataRef, &dataRefType); - - if (m_d->err) { - m_error = QString("Error creating data reference for QuickTime decoder: code %1").arg(m_d->err); - return; - } - - short fileID = movieInDataForkResID; - short flags = 0; - m_d->err = NewMovieFromDataRef - (&m_d->movie, flags, &fileID, dataRef, dataRefType); - - DisposeHandle(dataRef); - if (m_d->err) { - m_error = QString("Error creating new movie for QuickTime decoder: code %1").arg(m_d->err); - return; - } - - Boolean isProtected = 0; - Track aTrack = GetMovieIndTrackType - (m_d->movie, 1, SoundMediaType, - movieTrackMediaType | movieTrackEnabledOnly); - - if (aTrack) { - Media aMedia = GetTrackMedia(aTrack); // get the track media - if (aMedia) { - MediaHandler mh = GetMediaHandler(aMedia); // get the media handler we can query - if (mh) { - m_d->err = QTGetComponentProperty(mh, - kQTPropertyClass_DRM, - kQTDRMPropertyID_IsProtected, - sizeof(Boolean), &isProtected,nil); - } else { - m_d->err = 1; - } - } else { - m_d->err = 1; - } - } else { - m_d->err = 1; - } - - if (m_d->err && m_d->err != kQTPropertyNotSupportedErr) { - m_error = QString("Error checking for DRM in QuickTime decoder: code %1").arg(m_d->err); - return; - } else if (!m_d->err && isProtected) { - m_error = QString("File is protected with DRM"); - return; - } else if (m_d->err == kQTPropertyNotSupportedErr && !isProtected) { - cerr << "QuickTime: File is not protected with DRM" << endl; - } - - if (m_d->movie) { - SetMovieActive(m_d->movie, TRUE); - m_d->err = GetMoviesError(); - if (m_d->err) { - m_error = QString("Error in QuickTime decoder activation: code %1").arg(m_d->err); - return; - } - } else { - m_error = QString("Error in QuickTime decoder: Movie object not valid"); - return; - } - - m_d->err = MovieAudioExtractionBegin - (m_d->movie, 0, &m_d->extractionSessionRef); - if (m_d->err) { - m_error = QString("Error in QuickTime decoder extraction init: code %1").arg(m_d->err); - return; - } - - m_d->err = MovieAudioExtractionGetProperty - (m_d->extractionSessionRef, - kQTPropertyClass_MovieAudioExtraction_Audio, kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, - sizeof(m_d->asbd), - &m_d->asbd, - nil); - - if (m_d->err) { - m_error = QString("Error in QuickTime decoder property get: code %1").arg(m_d->err); - return; - } - - m_channelCount = m_d->asbd.mChannelsPerFrame; - m_fileRate = m_d->asbd.mSampleRate; - - cerr << "QuickTime: " << m_channelCount << " channels, " << m_fileRate << " kHz" << endl; - - m_d->asbd.mFormatFlags = - kAudioFormatFlagIsFloat | - kAudioFormatFlagIsPacked | - kAudioFormatFlagsNativeEndian; - m_d->asbd.mBitsPerChannel = sizeof(float) * 8; - m_d->asbd.mBytesPerFrame = sizeof(float) * m_d->asbd.mChannelsPerFrame; - m_d->asbd.mBytesPerPacket = m_d->asbd.mBytesPerFrame; - - m_d->err = MovieAudioExtractionSetProperty - (m_d->extractionSessionRef, - kQTPropertyClass_MovieAudioExtraction_Audio, - kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, - sizeof(m_d->asbd), - &m_d->asbd); - - if (m_d->err) { - m_error = QString("Error in QuickTime decoder property set: code %1").arg(m_d->err); - m_channelCount = 0; - 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->data = new float[m_channelCount * m_d->blockSize]; - m_d->buffer.mBuffers[0].mData = m_d->data; - - 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())); - } - - while (1) { - - UInt32 framesRead = m_d->blockSize; - UInt32 extractionFlags = 0; - m_d->err = MovieAudioExtractionFillBuffer - (m_d->extractionSessionRef, &framesRead, &m_d->buffer, - &extractionFlags); - if (m_d->err) { - m_error = QString("Error in QuickTime decoding: code %1") - .arg(m_d->err); - break; - } - - //!!! progress? - -// cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << endl; - - // QuickTime buffers are interleaved unless specified otherwise - addSamplesToDecodeCache(m_d->data, framesRead); - - if (framesRead < m_d->blockSize) break; - } - - finishDecodeCache(); - endSerialised(); - - m_d->err = MovieAudioExtractionEnd(m_d->extractionSessionRef); - if (m_d->err) { - m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_d->err); - } - - m_completion = 100; - - } else { - if (m_reporter) m_reporter->setProgress(100); - - if (m_channelCount > 0) { - m_decodeThread = new DecodeThread(this); - m_decodeThread->start(); - } - } - - cerr << "QuickTimeFileReader::QuickTimeFileReader: frame count is now " << getFrameCount() << ", error is \"\"" << m_error << "\"" << endl; -} - -QuickTimeFileReader::~QuickTimeFileReader() -{ - SVDEBUG << "QuickTimeFileReader::~QuickTimeFileReader" << endl; - - if (m_decodeThread) { - m_cancelled = true; - m_decodeThread->wait(); - delete m_decodeThread; - } - - SetMovieActive(m_d->movie, FALSE); - DisposeMovie(m_d->movie); - - delete[] m_d->data; - delete m_d; -} - -void -QuickTimeFileReader::cancelled() -{ - m_cancelled = true; -} - -void -QuickTimeFileReader::DecodeThread::run() -{ - if (m_reader->m_cacheMode == CacheInTemporaryFile) { - m_reader->m_completion = 1; - m_reader->startSerialised("QuickTimeFileReader::Decode"); - } - - while (1) { - - UInt32 framesRead = m_reader->m_d->blockSize; - UInt32 extractionFlags = 0; - m_reader->m_d->err = MovieAudioExtractionFillBuffer - (m_reader->m_d->extractionSessionRef, &framesRead, - &m_reader->m_d->buffer, &extractionFlags); - if (m_reader->m_d->err) { - m_reader->m_error = QString("Error in QuickTime decoding: code %1") - .arg(m_reader->m_d->err); - break; - } - - // QuickTime buffers are interleaved unless specified otherwise - m_reader->addSamplesToDecodeCache(m_reader->m_d->data, framesRead); - - if (framesRead < m_reader->m_d->blockSize) break; - } - - m_reader->finishDecodeCache(); - - m_reader->m_d->err = MovieAudioExtractionEnd(m_reader->m_d->extractionSessionRef); - if (m_reader->m_d->err) { - m_reader->m_error = QString("Error ending QuickTime extraction session: code %1").arg(m_reader->m_d->err); - } - - m_reader->m_completion = 100; - m_reader->endSerialised(); -} - -void -QuickTimeFileReader::getSupportedExtensions(std::set<QString> &extensions) -{ - extensions.insert("aiff"); - extensions.insert("aif"); - extensions.insert("au"); - extensions.insert("avi"); - extensions.insert("m4a"); - extensions.insert("m4b"); - extensions.insert("m4p"); - extensions.insert("m4v"); - extensions.insert("mov"); - extensions.insert("mp3"); - extensions.insert("mp4"); - extensions.insert("wav"); -} - -bool -QuickTimeFileReader::supportsExtension(QString extension) -{ - std::set<QString> extensions; - getSupportedExtensions(extensions); - return (extensions.find(extension.toLower()) != extensions.end()); -} - -bool -QuickTimeFileReader::supportsContentType(QString type) -{ - return (type == "audio/x-aiff" || - type == "audio/x-wav" || - type == "audio/mpeg" || - type == "audio/basic" || - type == "audio/x-aac" || - type == "video/mp4" || - type == "video/quicktime"); -} - -bool -QuickTimeFileReader::supports(FileSource &source) -{ - return (supportsExtension(source.getExtension()) || - supportsContentType(source.getContentType())); -} - -#endif -
--- a/data/fileio/QuickTimeFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +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-2007 Chris Cannam and QMUL. - - Based in part on QTAudioFile.h from SoundBite, copyright 2006 - Chris Sutton and Mark Levy. - - 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 _QUICKTIME_FILE_READER_H_ -#define _QUICKTIME_FILE_READER_H_ - -#ifdef HAVE_QUICKTIME - -#include "CodedAudioFileReader.h" - -#include "base/Thread.h" - -#include <set> - -class ProgressReporter; - -class QuickTimeFileReader : public CodedAudioFileReader -{ - Q_OBJECT - -public: - enum DecodeMode { - DecodeAtOnce, // decode the file on construction, with progress - DecodeThreaded // decode in a background thread after construction - }; - - QuickTimeFileReader(FileSource source, - DecodeMode decodeMode, - CacheMode cacheMode, - sv_samplerate_t targetRate = 0, - bool normalised = false, - ProgressReporter *reporter = 0); - virtual ~QuickTimeFileReader(); - - 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/WavFileReader.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/WavFileReader.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -15,11 +15,16 @@ #include "WavFileReader.h" +#include "base/HitCount.h" +#include "base/Profiler.h" + #include <iostream> #include <QMutexLocker> #include <QFileInfo> +using namespace std; + WavFileReader::WavFileReader(FileSource source, bool fileUpdating) : m_file(0), m_source(source), @@ -35,21 +40,26 @@ m_fileInfo.format = 0; m_fileInfo.frames = 0; + +#ifdef Q_OS_WIN + m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo); +#else m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo); +#endif if (!m_file || (!fileUpdating && m_fileInfo.channels <= 0)) { - cerr << "WavFileReader::initialize: Failed to open file at \"" - << m_path << "\" (" - << sf_strerror(m_file) << ")" << endl; + SVDEBUG << "WavFileReader::initialize: Failed to open file at \"" + << m_path << "\" (" + << sf_strerror(m_file) << ")" << endl; - if (m_file) { - m_error = QString("Couldn't load audio file '%1':\n%2") - .arg(m_path).arg(sf_strerror(m_file)); - } else { - m_error = QString("Failed to open audio file '%1'") - .arg(m_path); - } - return; + if (m_file) { + m_error = QString("Couldn't load audio file '%1':\n%2") + .arg(m_path).arg(sf_strerror(m_file)); + } else { + m_error = QString("Failed to open audio file '%1'") + .arg(m_path); + } + return; } if (m_fileInfo.channels > 0) { @@ -79,7 +89,7 @@ } } -// cerr << "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; + 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; } WavFileReader::~WavFileReader() @@ -96,10 +106,14 @@ if (m_file) { sf_close(m_file); +#ifdef Q_OS_WIN + m_file = sf_wchar_open((LPCWSTR)m_path.utf16(), SFM_READ, &m_fileInfo); +#else m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo); +#endif if (!m_file || m_fileInfo.channels <= 0) { - cerr << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" (" - << sf_strerror(m_file) << ")" << endl; + SVDEBUG << "WavFileReader::updateFrameCount: Failed to open file at \"" << m_path << "\" (" + << sf_strerror(m_file) << ")" << endl; } } @@ -113,7 +127,6 @@ } if (m_frameCount != prevCount) { -// cerr << "frameCountChanged" << endl; emit frameCountChanged(); } } @@ -125,53 +138,70 @@ m_updating = false; } -SampleBlock +floatvec_t WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const { - if (count == 0) return SampleBlock(); + static HitCount lastRead("WavFileReader: last read"); + + if (count == 0) return {}; QMutexLocker locker(&m_mutex); + Profiler profiler("WavFileReader::getInterleavedFrames"); + if (!m_file || !m_channelCount) { - return SampleBlock(); + return {}; } if (start >= m_fileInfo.frames) { // SVDEBUG << "WavFileReader::getInterleavedFrames: " << start // << " > " << m_fileInfo.frames << endl; - return SampleBlock(); + return {}; } if (start + count > m_fileInfo.frames) { - count = m_fileInfo.frames - start; + count = m_fileInfo.frames - start; } - if (start != m_lastStart || count != m_lastCount) { - - if (sf_seek(m_file, start, SEEK_SET) < 0) { - return SampleBlock(); - } - - sv_frame_t n = count * m_fileInfo.channels; - m_buffer.resize(size_t(n)); - - sf_count_t readCount = 0; - - if ((readCount = sf_readf_float(m_file, m_buffer.data(), count)) < 0) { - return SampleBlock(); - } - - m_buffer.resize(size_t(readCount * m_fileInfo.channels)); - - m_lastStart = start; - m_lastCount = readCount; + // Because WaveFileModel::getSummaries() is called separately for + // individual channels, it's quite common for us to be called + // repeatedly for the same data. So this is worth cacheing. + if (start == m_lastStart && count == m_lastCount) { + lastRead.hit(); + return m_buffer; } - return m_buffer; + // We don't actually support partial cache reads, but let's use + // the term partial to refer to any forward seek and consider a + // backward seek to be a miss + if (start >= m_lastStart) { + lastRead.partial(); + } else { + lastRead.miss(); + } + + if (sf_seek(m_file, start, SEEK_SET) < 0) { + return {}; + } + + floatvec_t data; + sv_frame_t n = count * m_fileInfo.channels; + data.resize(n); + + m_lastStart = start; + m_lastCount = count; + + sf_count_t readCount = 0; + if ((readCount = sf_readf_float(m_file, data.data(), count)) < 0) { + return {}; + } + + m_buffer = data; + return data; } void -WavFileReader::getSupportedExtensions(std::set<QString> &extensions) +WavFileReader::getSupportedExtensions(set<QString> &extensions) { int count; @@ -202,7 +232,7 @@ bool WavFileReader::supportsExtension(QString extension) { - std::set<QString> extensions; + set<QString> extensions; getSupportedExtensions(extensions); return (extensions.find(extension.toLower()) != extensions.end()); }
--- a/data/fileio/WavFileReader.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/WavFileReader.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,11 +13,16 @@ COPYING included with this distribution for more information. */ -#ifndef _WAV_FILE_READER_H_ -#define _WAV_FILE_READER_H_ +#ifndef SV_WAV_FILE_READER_H +#define SV_WAV_FILE_READER_H #include "AudioFileReader.h" +#ifdef Q_OS_WIN +#include <windows.h> +#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1 +#endif + #include <sndfile.h> #include <QMutex> @@ -50,7 +55,7 @@ * Must be safe to call from multiple threads with different * arguments on the same object at the same time. */ - virtual SampleBlock getInterleavedFrames(sv_frame_t start, sv_frame_t count) const; + virtual floatvec_t getInterleavedFrames(sv_frame_t start, sv_frame_t count) const; static void getSupportedExtensions(std::set<QString> &extensions); static bool supportsExtension(QString ext); @@ -75,8 +80,7 @@ bool m_seekable; mutable QMutex m_mutex; - mutable SampleBlock m_buffer; - mutable sv_frame_t m_bufsiz; + mutable floatvec_t m_buffer; mutable sv_frame_t m_lastStart; mutable sv_frame_t m_lastCount;
--- a/data/fileio/WavFileWriter.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/WavFileWriter.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -25,8 +25,10 @@ #include <iostream> #include <cmath> +using namespace std; + WavFileWriter::WavFileWriter(QString path, - sv_samplerate_t sampleRate, + sv_samplerate_t sampleRate, int channels, FileWriteMode mode) : m_path(path), @@ -48,25 +50,22 @@ fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; try { + QString writePath = m_path; if (mode == WriteToTemporary) { m_temp = new TempWriteFile(m_path); - m_file = sf_open(m_temp->getTemporaryFilename().toLocal8Bit(), - SFM_WRITE, &fileInfo); - if (!m_file) { - cerr << "WavFileWriter: Failed to open file (" - << sf_strerror(m_file) << ")" << endl; - m_error = QString("Failed to open audio file '%1' for writing") - .arg(m_temp->getTemporaryFilename()); - } - } else { - m_file = sf_open(m_path.toLocal8Bit(), SFM_WRITE, &fileInfo); - if (!m_file) { - cerr << "WavFileWriter: Failed to open file (" - << sf_strerror(m_file) << ")" << endl; - m_error = QString("Failed to open audio file '%1' for writing") - .arg(m_path); - } - } + writePath = m_temp->getTemporaryFilename(); + } +#ifdef Q_OS_WIN + m_file = sf_wchar_open((LPCWSTR)writePath.utf16(), SFM_WRITE, &fileInfo); +#else + m_file = sf_open(writePath.toLocal8Bit(), SFM_WRITE, &fileInfo); +#endif + if (!m_file) { + cerr << "WavFileWriter: Failed to open file (" + << sf_strerror(m_file) << ")" << endl; + m_error = QString("Failed to open audio file '%1' for writing") + .arg(writePath); + } } catch (FileOperationFailed &f) { m_error = f.what(); m_temp = 0; @@ -117,62 +116,59 @@ if (!m_file) { m_error = QString("Failed to write model to audio file '%1': File not open") .arg(getWriteFilename()); - return false; + return false; } bool ownSelection = false; if (!selection) { - selection = new MultiSelection; - selection->setSelection(Selection(source->getStartFrame(), - source->getEndFrame())); + selection = new MultiSelection; + selection->setSelection(Selection(source->getStartFrame(), + source->getEndFrame())); ownSelection = true; } sv_frame_t bs = 2048; - float *ub = new float[bs]; // uninterleaved buffer (one channel) - float *ib = new float[bs * m_channels]; // interleaved buffer for (MultiSelection::SelectionList::iterator i = - selection->getSelections().begin(); - i != selection->getSelections().end(); ++i) { + selection->getSelections().begin(); + i != selection->getSelections().end(); ++i) { - sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame()); + sv_frame_t f0(i->getStartFrame()), f1(i->getEndFrame()); - for (sv_frame_t f = f0; f < f1; f += bs) { + for (sv_frame_t f = f0; f < f1; f += bs) { - sv_frame_t n = std::min(bs, f1 - f); + sv_frame_t n = min(bs, f1 - f); + floatvec_t interleaved(n * m_channels, 0.f); - for (int c = 0; c < int(m_channels); ++c) { - source->getData(c, f, n, ub); - for (int i = 0; i < n; ++i) { - ib[i * m_channels + c] = ub[i]; - } - } + for (int c = 0; c < int(m_channels); ++c) { + auto chanbuf = source->getData(c, f, n); + for (int i = 0; in_range_for(chanbuf, i); ++i) { + interleaved[i * m_channels + c] = chanbuf[i]; + } + } - sf_count_t written = sf_writef_float(m_file, ib, n); + sf_count_t written = sf_writef_float(m_file, interleaved.data(), n); - if (written < n) { - m_error = QString("Only wrote %1 of %2 frames at file frame %3") - .arg(written).arg(n).arg(f); - break; - } - } + if (written < n) { + m_error = QString("Only wrote %1 of %2 frames at file frame %3") + .arg(written).arg(n).arg(f); + break; + } + } } - delete[] ub; - delete[] ib; if (ownSelection) delete selection; return isOK(); } bool -WavFileWriter::writeSamples(float **samples, sv_frame_t count) +WavFileWriter::writeSamples(const float *const *samples, sv_frame_t count) { if (!m_file) { m_error = QString("Failed to write model to audio file '%1': File not open") .arg(getWriteFilename()); - return false; + return false; } float *b = new float[count * m_channels];
--- a/data/fileio/WavFileWriter.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/WavFileWriter.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,11 +13,16 @@ COPYING included with this distribution for more information. */ -#ifndef _WAV_FILE_WRITER_H_ -#define _WAV_FILE_WRITER_H_ +#ifndef SV_WAV_FILE_WRITER_H +#define SV_WAV_FILE_WRITER_H #include <QString> +#ifdef Q_OS_WIN +#include <windows.h> +#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1 +#endif + #include <sndfile.h> #include "base/BaseTypes.h" @@ -59,7 +64,7 @@ bool writeModel(DenseTimeValueModel *source, MultiSelection *selection = 0); - bool writeSamples(float **samples, sv_frame_t count); // count per channel + bool writeSamples(const float *const *samples, sv_frame_t count); // count per channel bool close();
--- a/data/fileio/test/AudioFileReaderTest.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/fileio/test/AudioFileReaderTest.h Fri Jan 13 10:29:44 2017 +0000 @@ -18,6 +18,7 @@ #include "../AudioFileReaderFactory.h" #include "../AudioFileReader.h" +#include "../WavFileWriter.h" #include "AudioTestData.h" @@ -31,52 +32,232 @@ using namespace std; -static QString audioDir = "testfiles"; - class AudioFileReaderTest : public QObject { Q_OBJECT +private: + QString testDirBase; + QString audioDir; + QString diffDir; + +public: + AudioFileReaderTest(QString base) { + if (base == "") { + base = "svcore/data/fileio/test"; + } + testDirBase = base; + audioDir = base + "/audio"; + diffDir = base + "/diffs"; + } + +private: const char *strOf(QString s) { return strdup(s.toLocal8Bit().data()); } + void getFileMetadata(QString filename, + QString &extension, + sv_samplerate_t &rate, + int &channels, + int &bitdepth) { + + QStringList fileAndExt = filename.split("."); + QStringList bits = fileAndExt[0].split("-"); + + extension = fileAndExt[1]; + rate = bits[0].toInt(); + channels = bits[1].toInt(); + bitdepth = 16; + if (bits.length() > 2) { + bitdepth = bits[2].toInt(); + } + } + + void getExpectedThresholds(QString format, + QString filename, + bool resampled, + bool gapless, + bool normalised, + double &maxLimit, + double &rmsLimit) { + + QString extension; + sv_samplerate_t fileRate; + int channels; + int bitdepth; + getFileMetadata(filename, extension, fileRate, channels, bitdepth); + + if (normalised) { + + if (format == "ogg") { + + // Our ogg is not especially high quality and is + // actually further from the original if normalised + + maxLimit = 0.1; + rmsLimit = 0.03; + + } else if (format == "aac") { + + // Terrible performance for this test, load of spill + // from one channel to the other. I guess they know + // what they're doing, it's perceptual after all, but + // it does make this check a bit superfluous, you + // could probably pass it with a signal that sounds + // nothing like the original + maxLimit = 0.2; + rmsLimit = 0.1; + + } else if (format == "mp3") { + + if (resampled && !gapless) { + + // We expect worse figures here, because the + // combination of uncompensated encoder delay + + // resampling results in a fractional delay which + // means the decoded signal is slightly out of + // phase compared to the test signal + + maxLimit = 0.1; + rmsLimit = 0.05; + + } else { + + maxLimit = 0.05; + rmsLimit = 0.01; + } + + } else { + + // lossless formats (wav, aiff, flac, apple_lossless) + + if (bitdepth >= 16 && !resampled) { + maxLimit = 1e-3; + rmsLimit = 3e-4; + } else { + maxLimit = 0.01; + rmsLimit = 5e-3; + } + } + + } else { // !normalised + + if (format == "ogg") { + + maxLimit = 0.06; + rmsLimit = 0.03; + + } else if (format == "aac") { + + maxLimit = 0.1; + rmsLimit = 0.1; + + } else if (format == "mp3") { + + // all mp3 figures are worse when not normalising + maxLimit = 0.1; + rmsLimit = 0.05; + + } else { + + // lossless formats (wav, aiff, flac, apple_lossless) + + if (bitdepth >= 16 && !resampled) { + maxLimit = 1e-3; + rmsLimit = 3e-4; + } else { + maxLimit = 0.02; + rmsLimit = 0.01; + } + } + } + } + + QString testName(QString format, QString filename, int rate, bool norm, bool gapless) { + return QString("%1/%2 at %3%4%5") + .arg(format) + .arg(filename) + .arg(rate) + .arg(norm ? " normalised": "") + .arg(gapless ? "" : " non-gapless"); + } + private slots: void init() { if (!QDir(audioDir).exists()) { - cerr << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist" << endl; + QString cwd = QDir::currentPath(); + cerr << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist (cwd = " << cwd << ")" << endl; QVERIFY2(QDir(audioDir).exists(), "Audio test file directory not found"); } + if (!QDir(diffDir).exists() && !QDir().mkpath(diffDir)) { + cerr << "ERROR: Audio diff directory \"" << diffDir << "\" does not exist and could not be created" << endl; + QVERIFY2(QDir(diffDir).exists(), "Audio diff directory not found and could not be created"); + } } void read_data() { + QTest::addColumn<QString>("format"); QTest::addColumn<QString>("audiofile"); - QStringList files = QDir(audioDir).entryList(QDir::Files); - foreach (QString filename, files) { - QTest::newRow(strOf(filename)) << filename; + QTest::addColumn<int>("rate"); + QTest::addColumn<bool>("normalised"); + QTest::addColumn<bool>("gapless"); + QStringList dirs = QDir(audioDir).entryList(QDir::Dirs | + QDir::NoDotAndDotDot); + for (QString format: dirs) { + QStringList files = QDir(QDir(audioDir).filePath(format)) + .entryList(QDir::Files); + int readRates[] = { 44100, 48000 }; + bool norms[] = { false, true }; + bool gaplesses[] = { true, false }; + foreach (QString filename, files) { + for (int rate: readRates) { + for (bool norm: norms) { + for (bool gapless: gaplesses) { + + if (format != "mp3" && !gapless) { + continue; + } + + QString desc = testName + (format, filename, rate, norm, gapless); + + QTest::newRow(strOf(desc)) + << format << filename << rate << norm << gapless; + } + } + } + } } } void read() { + QFETCH(QString, format); QFETCH(QString, audiofile); + QFETCH(int, rate); + QFETCH(bool, normalised); + QFETCH(bool, gapless); - sv_samplerate_t readRate = 48000; + sv_samplerate_t readRate(rate); + +// cerr << "\naudiofile = " << audiofile << endl; + + AudioFileReaderFactory::Parameters params; + params.targetRate = readRate; + params.normalisation = (normalised ? + AudioFileReaderFactory::Normalisation::Peak : + AudioFileReaderFactory::Normalisation::None); + params.gaplessMode = (gapless ? + AudioFileReaderFactory::GaplessMode::Gapless : + AudioFileReaderFactory::GaplessMode::Gappy); AudioFileReader *reader = AudioFileReaderFactory::createReader - (audioDir + "/" + audiofile, readRate); - - QStringList fileAndExt = audiofile.split("."); - QStringList bits = fileAndExt[0].split("-"); - QString extension = fileAndExt[1]; - sv_samplerate_t nominalRate = bits[0].toInt(); - int nominalChannels = bits[1].toInt(); - int nominalDepth = 16; - if (bits.length() > 2) nominalDepth = bits[2].toInt(); - + (audioDir + "/" + format + "/" + audiofile, params); + if (!reader) { #if ( QT_VERSION >= 0x050000 ) QSKIP("Unsupported file, skipping"); @@ -85,11 +266,16 @@ #endif } - QCOMPARE((int)reader->getChannelCount(), nominalChannels); - QCOMPARE(reader->getNativeRate(), nominalRate); + QString extension; + sv_samplerate_t fileRate; + int channels; + int fileBitdepth; + getFileMetadata(audiofile, extension, fileRate, channels, fileBitdepth); + + QCOMPARE((int)reader->getChannelCount(), channels); + QCOMPARE(reader->getNativeRate(), fileRate); QCOMPARE(reader->getSampleRate(), readRate); - int channels = reader->getChannelCount(); AudioTestData tdata(readRate, channels); float *reference = tdata.getInterleavedData(); @@ -100,95 +286,200 @@ // more, though, so we can (a) check that we only get the // expected number back (if this is not mp3/aac) or (b) take // into account silence at beginning and end (if it is). - vector<float> test = reader->getInterleavedFrames(0, refFrames + 5000); + floatvec_t test = reader->getInterleavedFrames(0, refFrames + 5000); sv_frame_t read = test.size() / channels; - if (extension == "mp3" || extension == "aac" || extension == "m4a") { - // mp3s and aacs can have silence at start and end + bool perceptual = (extension == "mp3" || + extension == "aac" || + extension == "m4a"); + + if (perceptual && !gapless) { + // allow silence at start and end QVERIFY(read >= refFrames); } else { QCOMPARE(read, refFrames); } - // Our limits are pretty relaxed -- we're not testing decoder - // or resampler quality here, just whether the results are - // plainly wrong (e.g. at wrong samplerate or with an offset) - - double limit = 0.01; - double edgeLimit = limit * 10; // in first or final edgeSize frames + bool resampled = readRate != fileRate; + double maxLimit, rmsLimit; + getExpectedThresholds(format, + audiofile, + resampled, + gapless, + normalised, + maxLimit, rmsLimit); + + double edgeLimit = maxLimit * 3; // in first or final edgeSize frames + if (resampled && edgeLimit < 0.1) edgeLimit = 0.1; int edgeSize = 100; - if (nominalDepth < 16) { - limit = 0.02; - } - if (extension == "ogg" || extension == "mp3" || - extension == "aac" || extension == "m4a") { - limit = 0.2; - edgeLimit = limit * 3; - } - // And we ignore completely the last few frames when upsampling - int discard = 1 + int(round(readRate / nominalRate)); + int discard = 1 + int(round(readRate / fileRate)); int offset = 0; - if (extension == "aac" || extension == "m4a") { - // our m4a file appears to have a fixed offset of 1024 (at - // file sample rate) - offset = int(round((1024 / nominalRate) * readRate)); - } + if (perceptual) { - if (extension == "mp3") { - // while mp3s appear to vary - for (int i = 0; i < read; ++i) { - bool any = false; - double thresh = 0.01; - for (int c = 0; c < channels; ++c) { - if (fabs(test[i * channels + c]) > thresh) { - any = true; + // Look for an initial offset. + // + // We know the first channel has a sinusoid in it. It + // should have a peak at 0.4ms (see AudioTestData.h) but + // that might have been clipped, which would make it + // imprecise. We can tell if it's clipped, though, as + // there will be samples having exactly identical + // values. So what we look for is the peak if it's not + // clipped and, if it is, the first zero crossing after + // the peak, which should be at 0.8ms. + + int expectedPeak = int(0.0004 * readRate); + int expectedZC = int(0.0008 * readRate); + bool foundPeak = false; + for (int i = 1; i+1 < read; ++i) { + float prevSample = test[(i-1) * channels]; + float thisSample = test[i * channels]; + float nextSample = test[(i+1) * channels]; + if (thisSample > 0.8 && nextSample < thisSample) { + foundPeak = true; + if (thisSample > prevSample) { + // not clipped + offset = i - expectedPeak - 1; break; } } - if (any) { - offset = i; + if (foundPeak && (thisSample >= 0.0 && nextSample < 0.0)) { +// cerr << "thisSample = " << thisSample << ", nextSample = " +// << nextSample << endl; + offset = i - expectedZC - 1; break; } } + +// int fileRateEquivalent = int((offset / readRate) * fileRate); // std::cerr << "offset = " << offset << std::endl; +// std::cerr << "at file rate would be " << fileRateEquivalent << std::endl; + + // Previously our m4a test file had a fixed offset of 1024 + // at the file sample rate -- this may be because it was + // produced by FAAC which did not write in the delay as + // metadata? We now have an m4a produced by Core Audio + // which gives a 0 offset. What to do... + + // Anyway, mp3s should have 0 offset in gapless mode and + // "something else" otherwise. + + if (gapless) { + if (format == "aac") { + // ouch! + if (offset == -1) offset = 0; + } + QCOMPARE(offset, 0); + } } - for (int c = 0; c < channels; ++c) { - float maxdiff = 0.f; - int maxAt = 0; - float totdiff = 0.f; - for (int i = 0; i < read - offset - discard && i < refFrames; ++i) { - float diff = fabsf(test[(i + offset) * channels + c] - - reference[i * channels + c]); - totdiff += diff; + { + // Write the diff file now, so that it's already been written + // even if the comparison fails. We aren't checking anything + // here except as necessary to avoid buffer overruns etc + + QString diffFile = + testName(format, audiofile, rate, normalised, gapless); + diffFile.replace("/", "_"); + diffFile.replace(".", "_"); + diffFile.replace(" ", "_"); + diffFile += ".wav"; + diffFile = QDir(diffDir).filePath(diffFile); + WavFileWriter diffWriter(diffFile, readRate, channels, + WavFileWriter::WriteToTemporary); + QVERIFY(diffWriter.isOK()); + + vector<vector<float>> diffs(channels); + for (int c = 0; c < channels; ++c) { + for (int i = 0; i < refFrames; ++i) { + int ix = i + offset; + if (ix < read) { + float signeddiff = + test[ix * channels + c] - + reference[i * channels + c]; + diffs[c].push_back(signeddiff); + } + } + } + float **ptrs = new float*[channels]; + for (int c = 0; c < channels; ++c) { + ptrs[c] = diffs[c].data(); + } + diffWriter.writeSamples(ptrs, refFrames); + delete[] ptrs; + } + + for (int c = 0; c < channels; ++c) { + + double maxDiff = 0.0; + double totalDiff = 0.0; + double totalSqrDiff = 0.0; + int maxIndex = 0; + + for (int i = 0; i < refFrames; ++i) { + int ix = i + offset; + if (ix >= read) { + cerr << "ERROR: audiofile " << audiofile << " reads truncated (read-rate reference frames " << i << " onward, of " << refFrames << ", are lost)" << endl; + QVERIFY(ix < read); + } + + if (ix + discard >= read) { + // we forgive the very edge samples when + // resampling (discard > 0) + continue; + } + + double diff = fabs(test[ix * channels + c] - + reference[i * channels + c]); + + totalDiff += diff; + totalSqrDiff += diff * diff; + // in edge areas, record this only if it exceeds edgeLimit - if (i < edgeSize || i + edgeSize >= read - offset) { - if (diff > edgeLimit && diff > maxdiff) { - maxdiff = diff; - maxAt = i; + if (i < edgeSize || i + edgeSize >= refFrames) { + if (diff > edgeLimit && diff > maxDiff) { + maxDiff = diff; + maxIndex = i; } } else { - if (diff > maxdiff) { - maxdiff = diff; - maxAt = i; + if (diff > maxDiff) { + maxDiff = diff; + maxIndex = i; } - } - } - float meandiff = totdiff / float(read); -// cerr << "meandiff on channel " << c << ": " << meandiff << endl; -// cerr << "maxdiff on channel " << c << ": " << maxdiff << " at " << maxAt << endl; - if (meandiff >= limit) { - cerr << "ERROR: for audiofile " << audiofile << ": mean diff = " << meandiff << " for channel " << c << endl; - QVERIFY(meandiff < limit); + } } - if (maxdiff >= limit) { - cerr << "ERROR: for audiofile " << audiofile << ": max diff = " << maxdiff << " at frame " << maxAt << " of " << read << " on channel " << c << " (mean diff = " << meandiff << ")" << endl; - QVERIFY(maxdiff < limit); - } + + double meanDiff = totalDiff / double(refFrames); + double rmsDiff = sqrt(totalSqrDiff / double(refFrames)); + + /* + cerr << "channel " << c << ": mean diff " << meanDiff << endl; + cerr << "channel " << c << ": rms diff " << rmsDiff << endl; + cerr << "channel " << c << ": max diff " << maxDiff << " at " << maxIndex << endl; + */ + if (rmsDiff >= rmsLimit) { + cerr << "ERROR: for audiofile " << audiofile << ": RMS diff = " << rmsDiff << " for channel " << c << " (limit = " << rmsLimit << ")" << endl; + QVERIFY(rmsDiff < rmsLimit); + } + if (maxDiff >= maxLimit) { + cerr << "ERROR: for audiofile " << audiofile << ": max diff = " << maxDiff << " at frame " << maxIndex << " of " << read << " on channel " << c << " (limit = " << maxLimit << ", edge limit = " << edgeLimit << ", mean diff = " << meanDiff << ", rms = " << rmsDiff << ")" << endl; + QVERIFY(maxDiff < maxLimit); + } + + // and check for spurious material at end + + for (sv_frame_t i = refFrames; i + offset < read; ++i) { + sv_frame_t ix = i + offset; + float quiet = 0.1f; //!!! allow some ringing - but let's come back to this, it should tail off + float mag = fabsf(test[ix * channels + c]); + if (mag > quiet) { + cerr << "ERROR: audiofile " << audiofile << " contains spurious data after end of reference (found sample " << test[ix * channels + c] << " at index " << ix << " of channel " << c << " after reference+offset ended at " << refFrames+offset << ")" << endl; + QVERIFY(mag < quiet); + } + } } } };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/test/AudioFileWriterTest.h Fri Jan 13 10:29:44 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/test/EncodingTest.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,257 @@ +/* -*- 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_ENCODINGS_H +#define TEST_AUDIO_ENCODINGS_H + +// Quick tests for filename encodings and encoding of ID3 data. Not a +// test of audio codecs. + +#include "../AudioFileReaderFactory.h" +#include "../AudioFileReader.h" +#include "../WavFileWriter.h" + +#include <cmath> + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +using namespace std; + +const char utf8_name_cdp_1[] = "Caf\303\251 de Paris"; +const char utf8_name_cdp_2[] = "Caf\303\251 de \351\207\215\345\272\206"; +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 }, + { utf8_name_tsprk, utf8_name_tsprk }, + { utf8_name_sprkt, utf8_name_sprkt }, +}; +static const int mappingCount = 4; + +class EncodingTest : public QObject +{ + Q_OBJECT + +private: + QString testDirBase; + QString encodingDir; + QString outDir; + +public: + EncodingTest(QString base) { + if (base == "") { + base = "svcore/data/fileio/test"; + } + testDirBase = base; + encodingDir = base + "/encodings"; + outDir = base + "/outfiles"; + } + +private: + const char *strOf(QString s) { + 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() + { + if (!QDir(encodingDir).exists()) { + cerr << "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)) { + 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 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; + AudioFileReader *reader = + AudioFileReaderFactory::createReader + (encodingDir + "/" + audiofile, params); + + QVERIFY(reader != nullptr); + + QStringList fileAndExt = audiofile.split("."); + QString file = fileAndExt[0]; + QString extension = fileAndExt[1]; + + if (extension != "wav") { + +#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); + + QString title = reader->getTitle(); + QVERIFY(title != QString()); + + bool found = false; + for (int m = 0; m < mappingCount; ++m) { + if (file == QString::fromUtf8(mapping[m][0])) { + found = true; + QString expected = QString::fromUtf8(mapping[m][1]); + if (title != expected) { + cerr << "Title does not match expected: codepoints are" << endl; + cerr << "Title (" << title.length() << "ch): "; + for (int i = 0; i < title.length(); ++i) { + cerr << title[i].unicode() << " "; + } + cerr << endl; + cerr << "Expected (" << expected.length() << "ch): "; + for (int i = 0; i < expected.length(); ++i) { + cerr << expected[i].unicode() << " "; + } + cerr << endl; + } + QCOMPARE(title, expected); + break; + } + } + + 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; + } + +#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); + + 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 Fri Jan 13 10:29:44 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 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/test/files.pri Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,10 @@ + +TEST_HEADERS += \ + AudioFileReaderTest.h \ + AudioFileWriterTest.h \ + AudioTestData.h \ + EncodingTest.h \ + MIDIFileReaderTest.h + +TEST_SOURCES += \ + svcore-data-fileio-test.cpp
--- a/data/fileio/test/main.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +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 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. -*/ - -#include "AudioFileReaderTest.h" - -#include <QtTest> - -#include <iostream> - -int main(int argc, char *argv[]) -{ - int good = 0, bad = 0; - - QCoreApplication app(argc, argv); - app.setOrganizationName("Sonic Visualiser"); - app.setApplicationName("test-fileio"); - - { - AudioFileReaderTest t; - if (QTest::qExec(&t, argc, argv) == 0) ++good; - else ++bad; - } - - if (bad > 0) { - cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; - return 1; - } else { - cerr << "All tests passed" << endl; - return 0; - } -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/test/svcore-data-fileio-test.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,81 @@ +/* -*- 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. +*/ + +#include "AudioFileReaderTest.h" +#include "AudioFileWriterTest.h" +#include "EncodingTest.h" +#include "MIDIFileReaderTest.h" + +#include <QtTest> + +#include <iostream> + +int main(int argc, char *argv[]) +{ + int good = 0, bad = 0; + + QString testDir; + +#ifdef Q_OS_WIN + // incredible to have to hardcode this, but I just can't figure out how to + // get QMAKE_POST_LINK to add an arg to its command successfully on Windows + testDir = "../sonic-visualiser/svcore/data/fileio/test"; +#endif + + if (argc > 1) { + cerr << "argc = " << argc << endl; + testDir = argv[1]; + } + + if (testDir != "") { + cerr << "Setting test directory base path to \"" << testDir << "\"" << endl; + } + + QCoreApplication app(argc, argv); + app.setOrganizationName("Sonic Visualiser"); + app.setApplicationName("test-fileio"); + + { + AudioFileReaderTest t(testDir); + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + { + 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; + } else { + cerr << "All tests passed" << endl; + return 0; + } +} +
--- a/data/fileio/test/test.pro Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ - -TEMPLATE = app - -LIBS += -L../../.. -L../../../../dataquay -L../../../release -L../../../../dataquay/release -lsvcore -ldataquay - -win32-g++ { - INCLUDEPATH += ../../../../sv-dependency-builds/win32-mingw/include - LIBS += -L../../../../sv-dependency-builds/win32-mingw/lib -} -win32-msvc* { - INCLUDEPATH += ../../../../sv-dependency-builds/win32-msvc/include - LIBS += -L../../../../sv-dependency-builds/win32-msvc/lib -} -mac* { - INCLUDEPATH += ../../../../sv-dependency-builds/osx/include - LIBS += -L../../../../sv-dependency-builds/osx/lib -} - -exists(../../../config.pri) { - include(../../../config.pri) -} - -!exists(../../../config.pri) { - - CONFIG += release - DEFINES += NDEBUG BUILD_RELEASE NO_TIMING - - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO_2_0 - - LIBS += -lbz2 -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0 - - win* { - LIBS += -llo -lwinmm -lws2_32 - } - macx* { - DEFINES += HAVE_COREAUDIO - LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate - } - linux* { - LIBS += -ldl - } -} - -CONFIG += qt thread warn_on stl rtti exceptions console c++11 -QT += network xml testlib -QT -= gui - -TARGET = svcore-data-fileio-test - -DEPENDPATH += ../../.. -INCLUDEPATH += ../../.. -OBJECTS_DIR = o -MOC_DIR = o - -HEADERS += AudioFileReaderTest.h \ - AudioTestData.h -SOURCES += main.cpp - -win* { -//PRE_TARGETDEPS += ../../../svcore.lib -} -!win* { -PRE_TARGETDEPS += ../../../libsvcore.a -} - -!win32 { - !macx* { - QMAKE_POST_LINK=./$${TARGET} - } - macx* { - QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET} - } -} - -win32:QMAKE_POST_LINK=./release/$${TARGET}.exe -
--- a/data/midi/MIDIInput.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/midi/MIDIInput.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -17,7 +17,7 @@ #include "rtmidi/RtMidi.h" -#include <unistd.h> +#include "system/System.h" MIDIInput::MIDIInput(QString name, FrameTimer *timer) : m_rtmidi(),
--- a/data/midi/rtmidi/RtMidi.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/midi/rtmidi/RtMidi.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -63,7 +63,7 @@ #endif } else { - cerr << '\n' << errorString_ << "\n\n"; + cerr << "\nRtMidi error: " << errorString_ << "\n\n"; throw RtError( errorString_, type ); } }
--- a/data/model/AggregateWaveModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/AggregateWaveModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -19,6 +19,8 @@ #include <QTextStream> +using namespace std; + PowerOfSqrtTwoZoomConstraint AggregateWaveModel::m_zoomConstraint; @@ -92,65 +94,55 @@ return m_components.begin()->model->getSampleRate(); } -sv_frame_t -AggregateWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const +floatvec_t +AggregateWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count) const { int ch0 = channel, ch1 = channel; - bool mixing = false; if (channel == -1) { ch0 = 0; ch1 = getChannelCount()-1; - mixing = true; } - float *readbuf = buffer; - if (mixing) { - readbuf = new float[count]; - for (sv_frame_t i = 0; i < count; ++i) { - buffer[i] = 0.f; + floatvec_t result(count, 0.f); + sv_frame_t longest = 0; + + for (int c = ch0; c <= ch1; ++c) { + + auto here = m_components[c].model->getData(m_components[c].channel, + start, count); + if (sv_frame_t(here.size()) > longest) { + longest = sv_frame_t(here.size()); + } + for (sv_frame_t i = 0; in_range_for(here, i); ++i) { + result[i] += here[i]; } } - sv_frame_t longest = 0; - - for (int c = ch0; c <= ch1; ++c) { - sv_frame_t here = - m_components[c].model->getData(m_components[c].channel, - start, count, - readbuf); - if (here > longest) { - longest = here; - } - if (here < count) { - for (sv_frame_t i = here; i < count; ++i) { - readbuf[i] = 0.f; - } - } - if (mixing) { - for (sv_frame_t i = 0; i < count; ++i) { - buffer[i] += readbuf[i]; - } - } - } - - if (mixing) delete[] readbuf; - return longest; + result.resize(longest); + return result; } -sv_frame_t +vector<floatvec_t> AggregateWaveModel::getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffer) const + sv_frame_t start, sv_frame_t count) const { sv_frame_t min = count; + vector<floatvec_t> result; + for (int c = fromchannel; c <= tochannel; ++c) { - sv_frame_t here = getData(c, start, count, buffer[c - fromchannel]); - if (here < min) min = here; + auto here = getData(c, start, count); + if (sv_frame_t(here.size()) < min) { + min = sv_frame_t(here.size()); + } + result.push_back(here); + } + + if (min < count) { + for (auto &v : result) v.resize(min); } - return min; + return result; } int
--- a/data/model/AggregateWaveModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/AggregateWaveModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -59,12 +59,9 @@ virtual sv_frame_t getStartFrame() const { return 0; } virtual sv_frame_t getEndFrame() const { return getFrameCount(); } - virtual sv_frame_t getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const; + virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) const; - virtual sv_frame_t getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffer) const; + virtual std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; virtual int getSummaryBlockSize(int desired) const;
--- a/data/model/Dense3DModelPeakCache.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/Dense3DModelPeakCache.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -17,13 +17,13 @@ #include "base/Profiler.h" -Dense3DModelPeakCache::Dense3DModelPeakCache(DenseThreeDimensionalModel *source, +#include "base/HitCount.h" + +Dense3DModelPeakCache::Dense3DModelPeakCache(const DenseThreeDimensionalModel *source, int columnsPerPeak) : m_source(source), - m_resolution(columnsPerPeak) + m_columnsPerPeak(columnsPerPeak) { - m_coverage.resize(1); // otherwise it is simply invalid - m_cache = new EditableDenseThreeDimensionalModel (source->getSampleRate(), getResolution(), @@ -43,24 +43,9 @@ delete m_cache; } -bool -Dense3DModelPeakCache::isColumnAvailable(int column) const -{ - if (!m_source) return false; - if (haveColumn(column)) return true; - for (int i = m_resolution; i > 0; ) { - --i; - if (!m_source->isColumnAvailable(column * m_resolution + i)) { - return false; - } - } - return true; -} - Dense3DModelPeakCache::Column Dense3DModelPeakCache::getColumn(int column) const { - Profiler profiler("Dense3DModelPeakCache::getColumn"); if (!m_source) return Column(); if (!haveColumn(column)) fillColumn(column); return m_cache->getColumn(column); @@ -81,9 +66,9 @@ if (m_coverage.size() > 0) { // The last peak may have come from an incomplete read, which // may since have been filled, so reset it - m_coverage.reset(m_coverage.size()-1); + m_coverage[m_coverage.size()-1] = false; } - m_coverage.resize(getWidth()); // retaining data + m_coverage.resize(getWidth(), false); // retaining data } void @@ -95,7 +80,14 @@ bool Dense3DModelPeakCache::haveColumn(int column) const { - return column < (int)m_coverage.size() && m_coverage.get(column); + static HitCount count("Dense3DModelPeakCache"); + if (in_range_for(m_coverage, column) && m_coverage[column]) { + count.hit(); + return true; + } else { + count.miss(); + return false; + } } void @@ -103,26 +95,43 @@ { Profiler profiler("Dense3DModelPeakCache::fillColumn"); - if (column >= (int)m_coverage.size()) { - // see note in sourceModelChanged - if (m_coverage.size() > 0) m_coverage.reset(m_coverage.size()-1); - m_coverage.resize(column + 1); + if (!in_range_for(m_coverage, column)) { + if (m_coverage.size() > 0) { + // The last peak may have come from an incomplete read, which + // may since have been filled, so reset it + m_coverage[m_coverage.size()-1] = false; + } + m_coverage.resize(column + 1, false); } + int sourceWidth = m_source->getWidth(); + Column peak; - for (int i = 0; i < int(m_resolution); ++i) { - Column here = m_source->getColumn(column * m_resolution + i); + int n = 0; + for (int i = 0; i < m_columnsPerPeak; ++i) { + + int sourceColumn = column * m_columnsPerPeak + i; + if (sourceColumn >= sourceWidth) break; + + Column here = m_source->getColumn(sourceColumn); + +// cerr << "Dense3DModelPeakCache::fillColumn(" << column << "): source col " +// << sourceColumn << " of " << sourceWidth +// << " returned " << here.size() << " elts" << endl; + if (i == 0) { peak = here; + n = int(peak.size()); } else { - for (int j = 0; j < (int)peak.size() && j < (int)here.size(); ++j) { - if (here[j] > peak[j]) peak[j] = here[j]; + int m = std::min(n, int(here.size())); + for (int j = 0; j < m; ++j) { + peak[j] = std::max(here[j], peak[j]); } } } m_cache->setColumn(column, peak); - m_coverage.set(column); + m_coverage[column] = true; }
--- a/data/model/Dense3DModelPeakCache.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/Dense3DModelPeakCache.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,19 +13,18 @@ COPYING included with this distribution for more information. */ -#ifndef _DENSE_3D_MODEL_PEAK_CACHE_H_ -#define _DENSE_3D_MODEL_PEAK_CACHE_H_ +#ifndef DENSE_3D_MODEL_PEAK_CACHE_H +#define DENSE_3D_MODEL_PEAK_CACHE_H #include "DenseThreeDimensionalModel.h" #include "EditableDenseThreeDimensionalModel.h" -#include "base/ResizeableBitset.h" class Dense3DModelPeakCache : public DenseThreeDimensionalModel { Q_OBJECT public: - Dense3DModelPeakCache(DenseThreeDimensionalModel *source, + Dense3DModelPeakCache(const DenseThreeDimensionalModel *source, int columnsPerPeak); ~Dense3DModelPeakCache(); @@ -46,11 +45,20 @@ } virtual int getResolution() const { - return m_source->getResolution() * m_resolution; + return m_source->getResolution() * m_columnsPerPeak; } + virtual int getColumnsPerPeak() const { + return m_columnsPerPeak; + } + virtual int getWidth() const { - return m_source->getWidth() / m_resolution + 1; + int sourceWidth = m_source->getWidth(); + if ((sourceWidth % m_columnsPerPeak) == 0) { + return sourceWidth / m_columnsPerPeak; + } else { + return sourceWidth / m_columnsPerPeak + 1; + } } virtual int getHeight() const { @@ -65,11 +73,15 @@ return m_source->getMaximumLevel(); } - virtual bool isColumnAvailable(int column) const; + /** + * Retrieve the peaks column at peak-cache column number col. This + * will consist of the peak values in the underlying model from + * columns (col * getColumnsPerPeak()) to ((col+1) * + * getColumnsPerPeak() - 1) inclusive. + */ + virtual Column getColumn(int col) const; - virtual Column getColumn(int column) const; - - virtual float getValueAt(int column, int n) const; + virtual float getValueAt(int col, int n) const; virtual QString getBinName(int n) const { return m_source->getBinName(n); @@ -90,10 +102,11 @@ void sourceModelAboutToBeDeleted(); private: - DenseThreeDimensionalModel *m_source; + const DenseThreeDimensionalModel *m_source; mutable EditableDenseThreeDimensionalModel *m_cache; - mutable ResizeableBitset m_coverage; - int m_resolution; + mutable std::vector<bool> m_coverage; // must be bool, for space efficiency + // (vector of bool uses 1-bit elements) + int m_columnsPerPeak; bool haveColumn(int column) const; void fillColumn(int column) const;
--- a/data/model/DenseThreeDimensionalModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/DenseThreeDimensionalModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -18,6 +18,7 @@ #include "Model.h" #include "TabularModel.h" +#include "base/ColumnOp.h" #include "base/ZoomConstraint.h" #include "base/RealTime.h" @@ -55,16 +56,7 @@ */ virtual float getMaximumLevel() const = 0; - /** - * Return true if there are data available for the given column. - * This should return true only if getColumn(column) would not - * have to do any substantial work to calculate its return values. - * If this function returns false, it may still be possible to - * retrieve the column, but its values may have to be calculated. - */ - virtual bool isColumnAvailable(int column) const = 0; - - typedef QVector<float> Column; + typedef ColumnOp::Column Column; /** * Get data from the given column of bin values.
--- a/data/model/DenseTimeValueModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/DenseTimeValueModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -37,27 +37,19 @@ if (f1 <= f0) return ""; - float **all = new float *[ch]; - for (int c = 0; c < ch; ++c) { - all[c] = new float[f1 - f0]; - } + auto data = getMultiChannelData(0, ch - 1, f0, f1 - f0); - sv_frame_t n = getMultiChannelData(0, ch - 1, f0, f1 - f0, all); - + if (data.empty() || data[0].empty()) return ""; + QStringList list; - for (sv_frame_t i = 0; i < n; ++i) { + for (sv_frame_t i = 0; in_range_for(data[0], i); ++i) { QStringList parts; parts << QString("%1").arg(f0 + i); - for (int c = 0; c < ch; ++c) { - parts << QString("%1").arg(all[c][i]); + for (int c = 0; in_range_for(data, c); ++c) { + parts << QString("%1").arg(data[c][i]); } list << parts.join(delimiter); } - for (int c = 0; c < ch; ++c) { - delete[] all[c]; - } - delete[] all; - return list.join("\n"); }
--- a/data/model/DenseTimeValueModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/DenseTimeValueModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _DENSE_TIME_VALUE_MODEL_H_ -#define _DENSE_TIME_VALUE_MODEL_H_ +#ifndef SV_DENSE_TIME_VALUE_MODEL_H +#define SV_DENSE_TIME_VALUE_MODEL_H #include <QObject> @@ -57,27 +57,34 @@ /** * Get the specified set of samples from the given channel of the - * model in single-precision floating-point format. Return the - * number of samples actually retrieved. + * model in single-precision floating-point format. Returned + * vector may have fewer samples than requested, if the end of + * file was reached. + * * If the channel is given as -1, mix all available channels and * return the result. */ - virtual sv_frame_t getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const = 0; + virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) + const = 0; /** - * Get the specified set of samples from given contiguous range - * of channels of the model in single-precision floating-point - * format. Return the number of sample frames actually retrieved. + * Get the specified set of samples from given contiguous range of + * channels of the model in single-precision floating-point + * format. Returned vector may have fewer samples than requested, + * if the end of file was reached. */ - virtual sv_frame_t getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffers) const = 0; + virtual std::vector<floatvec_t> getMultiChannelData(int fromchannel, + int tochannel, + sv_frame_t start, + sv_frame_t count) + const = 0; virtual bool canPlay() const { return true; } virtual QString getDefaultPlayClipId() const { return ""; } - virtual QString toDelimitedDataStringSubset(QString delimiter, sv_frame_t f0, sv_frame_t f1) const; + virtual QString toDelimitedDataStringSubset(QString delimiter, + sv_frame_t f0, sv_frame_t f1) + const; QString getTypeName() const { return tr("Dense Time-Value"); } };
--- a/data/model/EditableDenseThreeDimensionalModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/EditableDenseThreeDimensionalModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -96,7 +96,7 @@ int EditableDenseThreeDimensionalModel::getWidth() const { - return m_data.size(); + return int(m_data.size()); } int @@ -139,15 +139,15 @@ EditableDenseThreeDimensionalModel::getColumn(int index) const { QReadLocker locker(&m_lock); - if (index < 0 || index >= m_data.size()) return Column(); - return expandAndRetrieve(index); + if (in_range_for(m_data, index)) return expandAndRetrieve(index); + else return Column(); } float EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const { Column c = getColumn(index); - if (int(n) < c.size()) return c.at(n); + if (in_range_for(c, n)) return c.at(n); return m_minimum; } @@ -157,7 +157,7 @@ EditableDenseThreeDimensionalModel::truncateAndStore(int index, const Column &values) { - assert(int(index) < m_data.size()); + assert(in_range_for(m_data, index)); //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl; @@ -169,7 +169,7 @@ m_trunc[index] = 0; if (index == 0 || m_compression == NoCompression || - values.size() != int(m_yBinCount)) { + int(values.size()) != m_yBinCount) { // given += values.size(); // stored += values.size(); m_data[index] = values; @@ -206,7 +206,7 @@ Column p = expandAndRetrieve(index - tdist); int h = m_yBinCount; - if (p.size() == h && tdist <= maxdist) { + if (int(p.size()) == h && tdist <= maxdist) { int bcount = 0, tcount = 0; if (!known || !top) { @@ -265,6 +265,17 @@ } EditableDenseThreeDimensionalModel::Column +EditableDenseThreeDimensionalModel::rightHeight(const Column &c) const +{ + if (int(c.size()) == m_yBinCount) return c; + else { + Column cc(c); + cc.resize(m_yBinCount, 0.0); + return cc; + } +} + +EditableDenseThreeDimensionalModel::Column EditableDenseThreeDimensionalModel::expandAndRetrieve(int index) const { // See comment above m_trunc declaration in header @@ -272,17 +283,17 @@ assert(index >= 0 && index < int(m_data.size())); Column c = m_data.at(index); if (index == 0) { - return c; + return rightHeight(c); } int trunc = (int)m_trunc[index]; if (trunc == 0) { - return c; + return rightHeight(c); } bool top = true; int tdist = trunc; if (trunc < 0) { top = false; tdist = -trunc; } Column p = expandAndRetrieve(index - tdist); - int psize = p.size(), csize = c.size(); + int psize = int(p.size()), csize = int(c.size()); if (psize != m_yBinCount) { cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl; } @@ -291,10 +302,6 @@ c.push_back(p.at(i)); } } else { - // push_front is very slow on QVector -- but not enough to - // make it desirable to choose a different container, since - // QVector has all the other advantages for us. easier to - // write the whole array out to a new vector Column cc(psize); for (int i = 0; i < psize - csize; ++i) { cc[i] = p.at(i); @@ -313,16 +320,14 @@ { QWriteLocker locker(&m_lock); - while (int(index) >= m_data.size()) { + while (index >= int(m_data.size())) { m_data.push_back(Column()); m_trunc.push_back(0); } bool allChange = false; -// if (values.size() > m_yBinCount) m_yBinCount = values.size(); - - for (int i = 0; i < values.size(); ++i) { + for (int i = 0; in_range_for(values, i); ++i) { float value = values[i]; if (ISNAN(value) || ISINF(value)) { continue; @@ -432,13 +437,13 @@ for (int i = 0; i < 10; ++i) { int index = i * 10; - if (index < m_data.size()) { + if (in_range_for(m_data, index)) { const Column &c = m_data.at(index); - while (c.size() > int(sample.size())) { + while (c.size() > sample.size()) { sample.push_back(0.0); n.push_back(0); } - for (int j = 0; j < c.size(); ++j) { + for (int j = 0; in_range_for(c, j); ++j) { sample[j] += c.at(j); ++n[j]; } @@ -486,9 +491,9 @@ { QReadLocker locker(&m_lock); QString s; - for (int i = 0; i < m_data.size(); ++i) { + for (int i = 0; in_range_for(m_data, i); ++i) { QStringList list; - for (int j = 0; j < m_data.at(i).size(); ++j) { + for (int j = 0; in_range_for(m_data.at(i), j); ++j) { list << QString("%1").arg(m_data.at(i).at(j)); } s += list.join(delimiter) + "\n"; @@ -501,11 +506,11 @@ { QReadLocker locker(&m_lock); QString s; - for (int i = 0; i < m_data.size(); ++i) { + for (int i = 0; in_range_for(m_data, i); ++i) { sv_frame_t fr = m_startFrame + i * m_resolution; if (fr >= f0 && fr < f1) { QStringList list; - for (int j = 0; j < m_data.at(i).size(); ++j) { + for (int j = 0; in_range_for(m_data.at(i), j); ++j) { list << QString("%1").arg(m_data.at(i).at(j)); } s += list.join(delimiter) + "\n";
--- a/data/model/EditableDenseThreeDimensionalModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/EditableDenseThreeDimensionalModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -44,7 +44,7 @@ EditableDenseThreeDimensionalModel(sv_samplerate_t sampleRate, int resolution, - int yBinCount, + int height, CompressionType compression, bool notifyOnAdd = true); @@ -75,12 +75,19 @@ virtual int getWidth() const; /** - * Return the number of bins in each set of bins. + * Return the number of bins in each column. */ virtual int getHeight() const; /** - * Set the number of bins in each set of bins. + * Set the number of bins in each column. + * + * You can set (via setColumn) a vector of any length as a column, + * but any column being retrieved will be resized to this height + * (or the height that was supplied to the constructor, if this is + * never called) on retrieval. That is, the model owner determines + * the height of the model at a single stroke; the columns + * themselves don't have any effect on the height of the model. */ virtual void setHeight(int sz); @@ -105,11 +112,6 @@ virtual void setMaximumLevel(float sz); /** - * Return true if there are data available for the given column. - */ - virtual bool isColumnAvailable(int x) const { return x < getWidth(); } - - /** * Get the set of bin values at the given column. */ virtual Column getColumn(int x) const; @@ -194,7 +196,7 @@ QString extraAttributes = "") const; protected: - typedef QVector<Column> ValueMatrix; + typedef std::vector<Column> ValueMatrix; ValueMatrix m_data; // m_trunc is used for simple compression. If at least the top N @@ -208,6 +210,7 @@ std::vector<signed char> m_trunc; void truncateAndStore(int index, const Column & values); Column expandAndRetrieve(int index) const; + Column rightHeight(const Column &c) const; std::vector<QString> m_binNames; std::vector<float> m_binValues;
--- a/data/model/FFTModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/FFTModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -15,184 +15,75 @@ #include "FFTModel.h" #include "DenseTimeValueModel.h" -#include "AggregateWaveModel.h" #include "base/Profiler.h" #include "base/Pitch.h" +#include "base/HitCount.h" #include <algorithm> #include <cassert> +#include <deque> -#ifndef __GNUC__ -#include <alloca.h> -#endif +using namespace std; + +static HitCount inSmallCache("FFTModel: Small FFT cache"); +static HitCount inSourceCache("FFTModel: Source data cache"); FFTModel::FFTModel(const DenseTimeValueModel *model, int channel, WindowType windowType, int windowSize, int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria, - sv_frame_t fillFromFrame) : - //!!! ZoomConstraint! - m_server(0), - m_xshift(0), - m_yshift(0) + int fftSize) : + m_model(model), + m_channel(channel), + m_windowType(windowType), + m_windowSize(windowSize), + m_windowIncrement(windowIncrement), + m_fftSize(fftSize), + m_windower(windowType, windowSize), + m_fft(fftSize), + m_cacheSize(3) { - setSourceModel(const_cast<DenseTimeValueModel *>(model)); //!!! hmm. - - m_server = getServer(model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar, - criteria, - fillFromFrame); - - if (!m_server) return; // caller should check isOK() - - int xratio = windowIncrement / m_server->getWindowIncrement(); - int yratio = m_server->getFFTSize() / fftSize; - - while (xratio > 1) { - if (xratio & 0x1) { - cerr << "ERROR: FFTModel: Window increment ratio " - << windowIncrement << " / " - << m_server->getWindowIncrement() - << " must be a power of two" << endl; - assert(!(xratio & 0x1)); - } - ++m_xshift; - xratio >>= 1; + if (m_windowSize > m_fftSize) { + cerr << "ERROR: FFTModel::FFTModel: window size (" << m_windowSize + << ") must be at least FFT size (" << m_fftSize << ")" << endl; + throw invalid_argument("FFTModel window size must be at least FFT size"); } - while (yratio > 1) { - if (yratio & 0x1) { - cerr << "ERROR: FFTModel: FFT size ratio " - << m_server->getFFTSize() << " / " << fftSize - << " must be a power of two" << endl; - assert(!(yratio & 0x1)); - } - ++m_yshift; - yratio >>= 1; - } + m_fft.initFloat(); + + connect(model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); + connect(model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), + this, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t))); } FFTModel::~FFTModel() { - if (m_server) FFTDataServer::releaseInstance(m_server); } void FFTModel::sourceModelAboutToBeDeleted() { - if (m_sourceModel) { - cerr << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_sourceModel << ")" << endl; - if (m_server) { - FFTDataServer::releaseInstance(m_server); - m_server = 0; - } - FFTDataServer::modelAboutToBeDeleted(m_sourceModel); + if (m_model) { + cerr << "FFTModel[" << this << "]::sourceModelAboutToBeDeleted(" << m_model << ")" << endl; + m_model = 0; } } -FFTDataServer * -FFTModel::getServer(const DenseTimeValueModel *model, - int channel, - WindowType windowType, - int windowSize, - int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria, - sv_frame_t fillFromFrame) +int +FFTModel::getWidth() const { - // Obviously, an FFT model of channel C (where C != -1) of an - // aggregate model is the same as the FFT model of the appropriate - // channel of whichever model that aggregate channel is drawn - // from. We should use that model here, in case we already have - // the data for it or will be wanting the same data again later. - - // If the channel is -1 (i.e. mixture of all channels), then we - // can't do this shortcut unless the aggregate model only has one - // channel or contains exactly all of the channels of a single - // other model. That isn't very likely -- if it were the case, - // why would we be using an aggregate model? - - if (channel >= 0) { - - const AggregateWaveModel *aggregate = - dynamic_cast<const AggregateWaveModel *>(model); - - if (aggregate && channel < aggregate->getComponentCount()) { - - AggregateWaveModel::ModelChannelSpec spec = - aggregate->getComponent(channel); - - return getServer(spec.model, - spec.channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar, - criteria, - fillFromFrame); - } - } - - // The normal case - - return FFTDataServer::getFuzzyInstance(model, - channel, - windowType, - windowSize, - windowIncrement, - fftSize, - polar, - criteria, - fillFromFrame); + if (!m_model) return 0; + return int((m_model->getEndFrame() - m_model->getStartFrame()) + / m_windowIncrement) + 1; } -sv_samplerate_t -FFTModel::getSampleRate() const +int +FFTModel::getHeight() const { - return isOK() ? m_server->getModel()->getSampleRate() : 0; -} - -FFTModel::Column -FFTModel::getColumn(int x) const -{ - Profiler profiler("FFTModel::getColumn", false); - - Column result; - - result.clear(); - int h = getHeight(); - result.reserve(h); - -#ifdef __GNUC__ - float magnitudes[h]; -#else - float *magnitudes = (float *)alloca(h * sizeof(float)); -#endif - - if (m_server->getMagnitudesAt(x << m_xshift, magnitudes)) { - - for (int y = 0; y < h; ++y) { - result.push_back(magnitudes[y]); - } - - } else { - for (int i = 0; i < h; ++i) result.push_back(0.f); - } - - return result; + return m_fftSize / 2 + 1; } QString @@ -204,15 +95,250 @@ return name; } +FFTModel::Column +FFTModel::getColumn(int x) const +{ + auto cplx = getFFTColumn(x); + Column col; + col.reserve(cplx.size()); + for (auto c: cplx) col.push_back(abs(c)); + return col; +} + +FFTModel::Column +FFTModel::getPhases(int x) const +{ + auto cplx = getFFTColumn(x); + Column col; + col.reserve(cplx.size()); + for (auto c: cplx) { + col.push_back(arg(c)); + } + return col; +} + +float +FFTModel::getMagnitudeAt(int x, int y) const +{ + if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f; + auto col = getFFTColumn(x); + return abs(col[y]); +} + +float +FFTModel::getMaximumMagnitudeAt(int x) const +{ + Column col(getColumn(x)); + float max = 0.f; + int n = int(col.size()); + for (int i = 0; i < n; ++i) { + if (col[i] > max) max = col[i]; + } + return max; +} + +float +FFTModel::getPhaseAt(int x, int y) const +{ + if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return 0.f; + return arg(getFFTColumn(x)[y]); +} + +void +FFTModel::getValuesAt(int x, int y, float &re, float &im) const +{ + auto col = getFFTColumn(x); + re = col[y].real(); + im = col[y].imag(); +} + +bool +FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const +{ + if (count == 0) count = getHeight(); + auto col = getFFTColumn(x); + for (int i = 0; i < count; ++i) { + values[i] = abs(col[minbin + i]); + } + return true; +} + +bool +FFTModel::getPhasesAt(int x, float *values, int minbin, int count) const +{ + if (count == 0) count = getHeight(); + auto col = getFFTColumn(x); + for (int i = 0; i < count; ++i) { + values[i] = arg(col[minbin + i]); + } + return true; +} + +bool +FFTModel::getValuesAt(int x, float *reals, float *imags, int minbin, int count) const +{ + if (count == 0) count = getHeight(); + auto col = getFFTColumn(x); + for (int i = 0; i < count; ++i) { + reals[i] = col[minbin + i].real(); + } + for (int i = 0; i < count; ++i) { + imags[i] = col[minbin + i].imag(); + } + return true; +} + +FFTModel::fvec +FFTModel::getSourceSamples(int column) const +{ + // m_fftSize may be greater than m_windowSize, but not the reverse + +// cerr << "getSourceSamples(" << column << ")" << endl; + + auto range = getSourceSampleRange(column); + auto data = getSourceData(range); + + int off = (m_fftSize - m_windowSize) / 2; + + if (off == 0) { + return data; + } else { + vector<float> pad(off, 0.f); + fvec padded; + padded.reserve(m_fftSize); + padded.insert(padded.end(), pad.begin(), pad.end()); + padded.insert(padded.end(), data.begin(), data.end()); + padded.insert(padded.end(), pad.begin(), pad.end()); + return padded; + } +} + +FFTModel::fvec +FFTModel::getSourceData(pair<sv_frame_t, sv_frame_t> range) const +{ +// cerr << "getSourceData(" << range.first << "," << range.second +// << "): saved range is (" << m_savedData.range.first +// << "," << m_savedData.range.second << ")" << endl; + + if (m_savedData.range == range) { + inSourceCache.hit(); + return m_savedData.data; + } + + Profiler profiler("FFTModel::getSourceData (cache miss)"); + + if (range.first < m_savedData.range.second && + range.first >= m_savedData.range.first && + range.second > m_savedData.range.second) { + + inSourceCache.partial(); + + sv_frame_t discard = range.first - m_savedData.range.first; + + fvec acc(m_savedData.data.begin() + discard, m_savedData.data.end()); + + fvec rest = getSourceDataUncached({ m_savedData.range.second, range.second }); + + acc.insert(acc.end(), rest.begin(), rest.end()); + + m_savedData = { range, acc }; + return acc; + + } else { + + inSourceCache.miss(); + + auto data = getSourceDataUncached(range); + m_savedData = { range, data }; + return data; + } +} + +FFTModel::fvec +FFTModel::getSourceDataUncached(pair<sv_frame_t, sv_frame_t> range) const +{ + decltype(range.first) pfx = 0; + if (range.first < 0) { + pfx = -range.first; + range = { 0, range.second }; + } + + auto data = m_model->getData(m_channel, + range.first, + range.second - range.first); + + if (data.empty()) { + SVDEBUG << "NOTE: empty source data for range (" << range.first << "," + << range.second << ") (model end frame " + << m_model->getEndFrame() << ")" << endl; + } + + // don't return a partial frame + data.resize(range.second - range.first, 0.f); + + if (pfx > 0) { + vector<float> pad(pfx, 0.f); + data.insert(data.begin(), pad.begin(), pad.end()); + } + + if (m_channel == -1) { + int channels = m_model->getChannelCount(); + if (channels > 1) { + int n = int(data.size()); + float factor = 1.f / float(channels); + // use mean instead of sum for fft model input + for (int i = 0; i < n; ++i) { + data[i] *= factor; + } + } + } + + return data; +} + +FFTModel::cvec +FFTModel::getFFTColumn(int n) const +{ + // The small cache (i.e. the m_cached deque) is for cases where + // values are looked up individually, and for e.g. peak-frequency + // spectrograms where values from two consecutive columns are + // needed at once. This cache gets essentially no hits when + // scrolling through a magnitude spectrogram, but 95%+ hits with a + // peak-frequency spectrogram. + for (const auto &incache : m_cached) { + if (incache.n == n) { + inSmallCache.hit(); + return incache.col; + } + } + inSmallCache.miss(); + + Profiler profiler("FFTModel::getFFTColumn (cache miss)"); + + auto samples = getSourceSamples(n); + m_windower.cut(samples.data()); + breakfastquay::v_fftshift(samples.data(), m_fftSize); + + cvec col(m_fftSize/2 + 1); + + m_fft.forwardInterleaved(samples.data(), + reinterpret_cast<float *>(col.data())); + + SavedColumn sc { n, col }; + if (m_cached.size() >= m_cacheSize) { + m_cached.pop_front(); + } + m_cached.push_back(sc); + + return col; +} + bool FFTModel::estimateStableFrequency(int x, int y, double &frequency) { if (!isOK()) return false; - sv_samplerate_t sampleRate = m_server->getModel()->getSampleRate(); - - int fftSize = m_server->getFFTSize() >> m_yshift; - frequency = double(y * sampleRate) / fftSize; + frequency = double(y * getSampleRate()) / m_fftSize; if (x+1 >= getWidth()) return false; @@ -230,24 +356,22 @@ int incr = getResolution(); - double expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / fftSize; + double expectedPhase = oldPhase + (2.0 * M_PI * y * incr) / m_fftSize; double phaseError = princarg(newPhase - expectedPhase); -// bool stable = (fabsf(phaseError) < (1.1f * (m_windowIncrement * M_PI) / m_fftSize)); - // The new frequency estimate based on the phase error resulting // from assuming the "native" frequency of this bin frequency = - (sampleRate * (expectedPhase + phaseError - oldPhase)) / + (getSampleRate() * (expectedPhase + phaseError - oldPhase)) / (2.0 * M_PI * incr); return true; } FFTModel::PeakLocationSet -FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) +FFTModel::getPeaks(PeakPickType type, int x, int ymin, int ymax) const { Profiler profiler("FFTModel::getPeaks"); @@ -264,11 +388,7 @@ int maxbin = ymax; if (maxbin < getHeight() - 1) maxbin = maxbin + 1; const int n = maxbin - minbin + 1; -#ifdef __GNUC__ - float values[n]; -#else - float *values = (float *)alloca(n * sizeof(float)); -#endif + float *values = new float[n]; getMagnitudesAt(x, values, minbin, maxbin - minbin + 1); for (int bin = ymin; bin <= ymax; ++bin) { if (bin == minbin || bin == maxbin) continue; @@ -277,14 +397,16 @@ peaks.insert(bin); } } + delete[] values; return peaks; } Column values = getColumn(x); + int nv = int(values.size()); float mean = 0.f; - for (int i = 0; i < values.size(); ++i) mean += values[i]; - if (values.size() > 0) mean = mean / float(values.size()); + for (int i = 0; i < nv; ++i) mean += values[i]; + if (nv > 0) mean = mean / float(values.size()); // For peak picking we use a moving median window, picking the // highest value within each continuous region of values that @@ -293,8 +415,8 @@ sv_samplerate_t sampleRate = getSampleRate(); - std::deque<float> window; - std::vector<int> inrange; + deque<float> window; + vector<int> inrange; float dist = 0.5; int medianWinSize = getPeakPickWindowSize(type, sampleRate, ymin, dist); @@ -305,8 +427,8 @@ else binmin = 0; int binmax; - if (ymax + halfWin < values.size()) binmax = ymax + halfWin; - else binmax = values.size()-1; + if (ymax + halfWin < nv) binmax = ymax + halfWin; + else binmax = nv - 1; int prevcentre = 0; @@ -327,12 +449,12 @@ int actualSize = int(window.size()); if (type == MajorPitchAdaptivePeaks) { - if (ymax + halfWin < values.size()) binmax = ymax + halfWin; - else binmax = values.size()-1; + if (ymax + halfWin < nv) binmax = ymax + halfWin; + else binmax = nv - 1; } - std::deque<float> sorted(window); - std::sort(sorted.begin(), sorted.end()); + deque<float> sorted(window); + sort(sorted.begin(), sorted.end()); float median = sorted[int(float(sorted.size()) * dist)]; int centrebin = 0; @@ -348,7 +470,7 @@ inrange.push_back(centrebin); } - if (centre <= median || centrebin+1 == values.size()) { + if (centre <= median || centrebin+1 == nv) { if (!inrange.empty()) { int peakbin = 0; float peakval = 0.f; @@ -380,11 +502,10 @@ if (type == MajorPeaks) return 10; if (bin == 0) return 3; - int fftSize = m_server->getFFTSize() >> m_yshift; - double binfreq = (sampleRate * bin) / fftSize; + double binfreq = (sampleRate * bin) / m_fftSize; double hifreq = Pitch::getFrequencyForPitch(73, 0, binfreq); - int hibin = int(lrint((hifreq * fftSize) / sampleRate)); + int hibin = int(lrint((hifreq * m_fftSize) / sampleRate)); int medianWinSize = hibin - bin; if (medianWinSize < 3) medianWinSize = 3; @@ -395,7 +516,7 @@ FFTModel::PeakSet FFTModel::getPeakFrequencies(PeakPickType type, int x, - int ymin, int ymax) + int ymin, int ymax) const { Profiler profiler("FFTModel::getPeakFrequencies"); @@ -404,7 +525,6 @@ PeakLocationSet locations = getPeaks(type, x, ymin, ymax); sv_samplerate_t sampleRate = getSampleRate(); - int fftSize = m_server->getFFTSize() >> m_yshift; int incr = getResolution(); // This duplicates some of the work of estimateStableFrequency to @@ -412,7 +532,7 @@ // columns, instead of jumping back and forth between columns x and // x+1, which may be significantly slower if re-seeking is needed - std::vector<float> phases; + vector<float> phases; for (PeakLocationSet::iterator i = locations.begin(); i != locations.end(); ++i) { phases.push_back(getPhaseAt(x, *i)); @@ -423,13 +543,11 @@ i != locations.end(); ++i) { double oldPhase = phases[phaseIndex]; double newPhase = getPhaseAt(x+1, *i); - double expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / fftSize; + double expectedPhase = oldPhase + (2.0 * M_PI * *i * incr) / m_fftSize; double phaseError = princarg(newPhase - expectedPhase); double frequency = (sampleRate * (expectedPhase + phaseError - oldPhase)) / (2 * M_PI * incr); -// bool stable = (fabsf(phaseError) < (1.1f * (incr * M_PI) / fftSize)); -// if (stable) peaks[*i] = frequency; ++phaseIndex; } @@ -437,12 +555,3 @@ return peaks; } -FFTModel::FFTModel(const FFTModel &model) : - DenseThreeDimensionalModel(), - m_server(model.m_server), - m_xshift(model.m_xshift), - m_yshift(model.m_yshift) -{ - FFTDataServer::claimInstance(m_server); -} -
--- a/data/model/FFTModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/FFTModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -16,24 +16,31 @@ #ifndef FFT_MODEL_H #define FFT_MODEL_H -#include "data/fft/FFTDataServer.h" #include "DenseThreeDimensionalModel.h" +#include "DenseTimeValueModel.h" + +#include "base/Window.h" + +#include <bqfft/FFT.h> +#include <bqvec/Allocators.h> #include <set> -#include <map> +#include <vector> +#include <complex> +#include <deque> /** * An implementation of DenseThreeDimensionalModel that makes FFT data * derived from a DenseTimeValueModel available as a generic data - * grid. The FFT data is acquired using FFTDataServer. Note that any - * of the accessor functions may throw AllocationFailed if a cache - * resize fails. + * grid. */ - class FFTModel : public DenseThreeDimensionalModel { Q_OBJECT + //!!! threading requirements? + //!!! doubles? since we're not caching much + public: /** * Construct an FFT model derived from the given @@ -43,111 +50,63 @@ * If the model has multiple channels use only the given channel, * unless the channel is -1 in which case merge all available * channels. - * - * If polar is true, the data will normally be retrieved from the - * FFT model in magnitude/phase form; otherwise it will normally - * be retrieved in "cartesian" real/imaginary form. The results - * should be the same either way, but a "polar" model addressed in - * "cartesian" form or vice versa may suffer a performance - * penalty. - * - * The fillFromColumn argument gives a hint that the FFT data - * server should aim to start calculating FFT data at that column - * number if possible, as that is likely to be requested first. */ FFTModel(const DenseTimeValueModel *model, int channel, WindowType windowType, int windowSize, int windowIncrement, - int fftSize, - bool polar, - StorageAdviser::Criteria criteria = StorageAdviser::NoCriteria, - sv_frame_t fillFromFrame = 0); + int fftSize); ~FFTModel(); - inline float getMagnitudeAt(int x, int y) { - return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift); - } - inline float getNormalizedMagnitudeAt(int x, int y) { - return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift); - } - inline float getMaximumMagnitudeAt(int x) { - return m_server->getMaximumMagnitudeAt(x << m_xshift); - } - inline float getPhaseAt(int x, int y) { - return m_server->getPhaseAt(x << m_xshift, y << m_yshift); - } - inline void getValuesAt(int x, int y, float &real, float &imaginary) { - m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary); - } - inline bool isColumnAvailable(int x) const { - return m_server->isColumnReady(x << m_xshift); - } - - inline bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) { - return m_server->getMagnitudesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio()); - } - inline bool getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) { - return m_server->getNormalizedMagnitudesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio()); - } - inline bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0) { - return m_server->getPhasesAt(x << m_xshift, values, minbin << m_yshift, count, getYRatio()); - } - inline bool getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0) { - return m_server->getValuesAt(x << m_xshift, reals, imaginaries, minbin << m_yshift, count, getYRatio()); - } - - inline sv_frame_t getFillExtent() const { return m_server->getFillExtent(); } - // DenseThreeDimensionalModel and Model methods: // - inline virtual int getWidth() const { - return m_server->getWidth() >> m_xshift; - } - inline virtual int getHeight() const { - // If there is no y-shift, the server's height (based on its - // fftsize/2 + 1) is correct. If there is a shift, then the - // server is using a larger fft size than we want, so we shift - // it right as many times as necessary, but then we need to - // re-add the "+1" part (because ((fftsize*2)/2 + 1) / 2 != - // fftsize/2 + 1). - return (m_server->getHeight() >> m_yshift) + (m_yshift > 0 ? 1 : 0); - } - virtual float getValueAt(int x, int y) const { - return const_cast<FFTModel *>(this)->getMagnitudeAt(x, y); - } - virtual bool isOK() const { - // Return true if the model was constructed successfully (not - // necessarily whether an error has occurred since - // construction, use getError for that) - return m_server && m_server->getModel(); - } - virtual sv_frame_t getStartFrame() const { - return 0; - } + virtual int getWidth() const; + virtual int getHeight() const; + virtual float getValueAt(int x, int y) const { return getMagnitudeAt(x, y); } + virtual bool isOK() const { return m_model && m_model->isOK(); } + virtual sv_frame_t getStartFrame() const { return 0; } virtual sv_frame_t getEndFrame() const { return sv_frame_t(getWidth()) * getResolution() + getResolution(); } - virtual sv_samplerate_t getSampleRate() const; - virtual int getResolution() const { - return m_server->getWindowIncrement() << m_xshift; + virtual sv_samplerate_t getSampleRate() const { + return isOK() ? m_model->getSampleRate() : 0; } - virtual int getYBinCount() const { - return getHeight(); + virtual int getResolution() const { return m_windowIncrement; } + virtual int getYBinCount() const { return getHeight(); } + virtual float getMinimumLevel() const { return 0.f; } // Can't provide + virtual float getMaximumLevel() const { return 1.f; } // Can't provide + virtual Column getColumn(int x) const; // magnitudes + virtual Column getPhases(int x) const; + virtual QString getBinName(int n) const; + virtual bool shouldUseLogValueScale() const { return true; } + virtual int getCompletion() const { + int c = 100; + if (m_model) { + if (m_model->isReady(&c)) return 100; + } + return c; } - virtual float getMinimumLevel() const { - return 0.f; // Can't provide - } - virtual float getMaximumLevel() const { - return 1.f; // Can't provide - } - virtual Column getColumn(int x) const; - virtual QString getBinName(int n) const; + virtual QString getError() const { return ""; } //!!!??? + virtual sv_frame_t getFillExtent() const { return getEndFrame(); } - virtual bool shouldUseLogValueScale() const { - return true; // Although obviously it's up to the user... - } + // FFTModel methods: + // + int getChannel() const { return m_channel; } + WindowType getWindowType() const { return m_windowType; } + int getWindowSize() const { return m_windowSize; } + int getWindowIncrement() const { return m_windowIncrement; } + int getFFTSize() const { return m_fftSize; } + +//!!! review which of these are ever actually called + + float getMagnitudeAt(int x, int y) const; + float getMaximumMagnitudeAt(int x) const; + float getPhaseAt(int x, int y) const; + void getValuesAt(int x, int y, float &real, float &imaginary) const; + bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const; + bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0) const; + bool getValuesAt(int x, float *reals, float *imaginaries, int minbin = 0, int count = 0) const; /** * Calculate an estimated frequency for a stable signal in this @@ -171,20 +130,13 @@ * ymax is zero, getHeight()-1 will be used. */ virtual PeakLocationSet getPeaks(PeakPickType type, int x, - int ymin = 0, int ymax = 0); + int ymin = 0, int ymax = 0) const; /** * Return locations and estimated stable frequencies of peak bins. */ virtual PeakSet getPeakFrequencies(PeakPickType type, int x, - int ymin = 0, int ymax = 0); - - virtual int getCompletion() const { return m_server->getFillCompletion(); } - virtual QString getError() const { return m_server->getError(); } - - virtual void suspend() { m_server->suspend(); } - virtual void suspendWrites() { m_server->suspendWrites(); } - virtual void resume() { m_server->resume(); } + int ymin = 0, int ymax = 0) const; QString getTypeName() const { return tr("FFT"); } @@ -195,23 +147,48 @@ FFTModel(const FFTModel &); // not implemented FFTModel &operator=(const FFTModel &); // not implemented - FFTDataServer *m_server; - int m_xshift; - int m_yshift; - - FFTDataServer *getServer(const DenseTimeValueModel *, - int, WindowType, int, int, int, - bool, StorageAdviser::Criteria, sv_frame_t); - + const DenseTimeValueModel *m_model; + int m_channel; + WindowType m_windowType; + int m_windowSize; + int m_windowIncrement; + int m_fftSize; + Window<float> m_windower; + mutable breakfastquay::FFT m_fft; + int getPeakPickWindowSize(PeakPickType type, sv_samplerate_t sampleRate, int bin, float &percentile) const; - int getYRatio() { - int ys = m_yshift; - int r = 1; - while (ys) { --ys; r <<= 1; } - return r; + std::pair<sv_frame_t, sv_frame_t> getSourceSampleRange(int column) const { + sv_frame_t startFrame = m_windowIncrement * sv_frame_t(column); + sv_frame_t endFrame = startFrame + m_windowSize; + // Cols are centred on the audio sample (e.g. col 0 is centred at sample 0) + startFrame -= m_windowSize / 2; + endFrame -= m_windowSize / 2; + return { startFrame, endFrame }; } + + typedef std::vector<float, breakfastquay::StlAllocator<float>> fvec; + typedef std::vector<std::complex<float>, + breakfastquay::StlAllocator<std::complex<float>>> cvec; + + cvec getFFTColumn(int column) const; + fvec getSourceSamples(int column) const; + fvec getSourceData(std::pair<sv_frame_t, sv_frame_t>) const; + fvec getSourceDataUncached(std::pair<sv_frame_t, sv_frame_t>) const; + + struct SavedSourceData { + std::pair<sv_frame_t, sv_frame_t> range; + fvec data; + }; + mutable SavedSourceData m_savedData; + + struct SavedColumn { + int n; + cvec col; + }; + mutable std::deque<SavedColumn> m_cached; + size_t m_cacheSize; }; #endif
--- a/data/model/Labeller.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/Labeller.h Fri Jan 13 10:29:44 2017 +0000 @@ -171,37 +171,35 @@ } } + /** + * Relabel all points in the given model that lie within the given + * multi-selection, according to the labelling properties of this + * labeller. Return a command that has been executed but not yet + * added to the history. + */ template <typename PointType> - void labelAll(SparseModel<PointType> &model, MultiSelection *ms) { + Command *labelAll(SparseModel<PointType> &model, MultiSelection *ms) { - typename SparseModel<PointType>::PointList::iterator i; - typename SparseModel<PointType>::PointList pl(model.getPoints()); - - typename SparseModel<PointType>::EditCommand *command = - new typename SparseModel<PointType>::EditCommand + auto points(model.getPoints()); + auto command = new typename SparseModel<PointType>::EditCommand (&model, tr("Label Points")); PointType prevPoint(0); + bool havePrevPoint(false); - for (i = pl.begin(); i != pl.end(); ++i) { + for (auto p: points) { - bool inRange = true; if (ms) { - Selection s(ms->getContainingSelection(i->frame, false)); - if (s.isEmpty() || !s.contains(i->frame)) { - inRange = false; + Selection s(ms->getContainingSelection(p.frame, false)); + if (!s.contains(p.frame)) { + prevPoint = p; + havePrevPoint = true; + continue; } } - PointType p(*i); - - if (!inRange) { - prevPoint = p; - continue; - } - if (actingOnPrevPoint()) { - if (i != pl.begin()) { + if (havePrevPoint) { command->deletePoint(prevPoint); label<PointType>(p, &prevPoint); command->addPoint(prevPoint); @@ -213,9 +211,94 @@ } prevPoint = p; + havePrevPoint = true; } - command->finish(); + return command->finish(); + } + + /** + * For each point in the given model (except the last), if that + * point lies within the given multi-selection, add n-1 new points + * at equally spaced intervals between it and the following point. + * Return a command that has been executed but not yet added to + * the history. + */ + template <typename PointType> + Command *subdivide(SparseModel<PointType> &model, MultiSelection *ms, int n) { + + auto points(model.getPoints()); + auto command = new typename SparseModel<PointType>::EditCommand + (&model, tr("Subdivide Points")); + + for (auto i = points.begin(); i != points.end(); ++i) { + + auto j = i; + // require a "next point" even if it's not in selection + if (++j == points.end()) { + break; + } + + if (ms) { + Selection s(ms->getContainingSelection(i->frame, false)); + if (!s.contains(i->frame)) { + continue; + } + } + + PointType p(*i); + PointType nextP(*j); + + // n is the number of subdivisions, so we add n-1 new + // points equally spaced between p and nextP + + for (int m = 1; m < n; ++m) { + sv_frame_t f = p.frame + (m * (nextP.frame - p.frame)) / n; + PointType newPoint(p); + newPoint.frame = f; + newPoint.label = tr("%1.%2").arg(p.label).arg(m+1); + command->addPoint(newPoint); + } + } + + return command->finish(); + } + + /** + * Return a command that has been executed but not yet added to + * the history. + */ + template <typename PointType> + Command *winnow(SparseModel<PointType> &model, MultiSelection *ms, int n) { + + auto points(model.getPoints()); + auto command = new typename SparseModel<PointType>::EditCommand + (&model, tr("Winnow Points")); + + int counter = 0; + + for (auto p: points) { + + if (ms) { + Selection s(ms->getContainingSelection(p.frame, false)); + if (!s.contains(p.frame)) { + counter = 0; + continue; + } + } + + ++counter; + + if (counter == n+1) counter = 1; + if (counter == 1) { + // this is an Nth instant, don't remove it + continue; + } + + command->deletePoint(p); + } + + return command->finish(); } template <typename PointType>
--- a/data/model/Model.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/Model.h Fri Jan 13 10:29:44 2017 +0000 @@ -24,8 +24,6 @@ #include "base/BaseTypes.h" #include "base/DataExportOptions.h" -typedef std::vector<float> SampleBlock; - class ZoomConstraint; class AlignmentModel; @@ -123,7 +121,8 @@ * through it an estimated percentage value showing how far * through the background operation it thinks it is (for progress * reporting). If it has no way to calculate progress, it may - * return the special value COMPLETION_UNKNOWN. + * return the special value COMPLETION_UNKNOWN. See also + * getCompletion(). */ virtual bool isReady(int *completion = 0) const { bool ok = isOK();
--- a/data/model/ModelDataTableModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/ModelDataTableModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -47,7 +47,8 @@ if (!m_model) return QVariant(); if (role != Qt::EditRole && role != Qt::DisplayRole) return QVariant(); if (!index.isValid()) return QVariant(); - return m_model->getData(getUnsorted(index.row()), index.column(), role); + QVariant d = m_model->getData(getUnsorted(index.row()), index.column(), role); + return d; } bool
--- a/data/model/RangeSummarisableTimeValueModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/RangeSummarisableTimeValueModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_ -#define _RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H_ +#ifndef SV_RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H +#define SV_RANGE_SUMMARISABLE_TIME_VALUE_MODEL_H #include <QObject>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/ReadOnlyWaveFileModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,738 @@ +/* -*- 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 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. +*/ + +#include "ReadOnlyWaveFileModel.h" + +#include "fileio/AudioFileReader.h" +#include "fileio/AudioFileReaderFactory.h" + +#include "system/System.h" + +#include "base/Preferences.h" + +#include <QFileInfo> +#include <QTextStream> + +#include <iostream> +//#include <unistd.h> +#include <cmath> +#include <sndfile.h> + +#include <cassert> + +using namespace std; + +//#define DEBUG_WAVE_FILE_MODEL 1 + +PowerOfSqrtTwoZoomConstraint +ReadOnlyWaveFileModel::m_zoomConstraint; + +ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate) : + m_source(source), + m_path(source.getLocation()), + m_reader(0), + m_myReader(true), + m_startFrame(0), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false), + m_lastDirectReadStart(0), + m_lastDirectReadCount(0) +{ + m_source.waitForData(); + + if (m_source.isOK()) { + + Preferences *prefs = Preferences::getInstance(); + + AudioFileReaderFactory::Parameters params; + params.targetRate = targetRate; + + params.normalisation = prefs->getNormaliseAudio() ? + AudioFileReaderFactory::Normalisation::Peak : + AudioFileReaderFactory::Normalisation::None; + + params.gaplessMode = prefs->getUseGaplessMode() ? + AudioFileReaderFactory::GaplessMode::Gapless : + AudioFileReaderFactory::GaplessMode::Gappy; + + params.threadingMode = AudioFileReaderFactory::ThreadingMode::Threaded; + + m_reader = AudioFileReaderFactory::createReader(m_source, params); + if (m_reader) { + SVDEBUG << "ReadOnlyWaveFileModel::ReadOnlyWaveFileModel: reader rate: " + << m_reader->getSampleRate() << endl; + } + } + + if (m_reader) setObjectName(m_reader->getTitle()); + if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); + if (isOK()) fillCache(); +} + +ReadOnlyWaveFileModel::ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader) : + m_source(source), + m_path(source.getLocation()), + m_reader(0), + m_myReader(false), + m_startFrame(0), + m_fillThread(0), + m_updateTimer(0), + m_lastFillExtent(0), + m_exiting(false) +{ + m_reader = reader; + if (m_reader) setObjectName(m_reader->getTitle()); + if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); + fillCache(); +} + +ReadOnlyWaveFileModel::~ReadOnlyWaveFileModel() +{ + m_exiting = true; + if (m_fillThread) m_fillThread->wait(); + if (m_myReader) delete m_reader; + m_reader = 0; +} + +bool +ReadOnlyWaveFileModel::isOK() const +{ + return m_reader && m_reader->isOK(); +} + +bool +ReadOnlyWaveFileModel::isReady(int *completion) const +{ + bool ready = (isOK() && (m_fillThread == 0)); + double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); + static int prevCompletion = 0; + if (completion) { + *completion = int(c * 100.0 + 0.01); + if (m_reader) { + int decodeCompletion = m_reader->getDecodeCompletion(); + if (decodeCompletion < 90) *completion = decodeCompletion; + else *completion = min(*completion, decodeCompletion); + } + if (*completion != 0 && + *completion != 100 && + prevCompletion != 0 && + prevCompletion > *completion) { + // just to avoid completion going backwards + *completion = prevCompletion; + } + prevCompletion = *completion; + } +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl; +#endif + return ready; +} + +sv_frame_t +ReadOnlyWaveFileModel::getFrameCount() const +{ + if (!m_reader) return 0; + return m_reader->getFrameCount(); +} + +int +ReadOnlyWaveFileModel::getChannelCount() const +{ + if (!m_reader) return 0; + return m_reader->getChannelCount(); +} + +sv_samplerate_t +ReadOnlyWaveFileModel::getSampleRate() const +{ + if (!m_reader) return 0; + return m_reader->getSampleRate(); +} + +sv_samplerate_t +ReadOnlyWaveFileModel::getNativeRate() const +{ + if (!m_reader) return 0; + sv_samplerate_t rate = m_reader->getNativeRate(); + if (rate == 0) rate = getSampleRate(); + return rate; +} + +QString +ReadOnlyWaveFileModel::getTitle() const +{ + QString title; + if (m_reader) title = m_reader->getTitle(); + if (title == "") title = objectName(); + return title; +} + +QString +ReadOnlyWaveFileModel::getMaker() const +{ + if (m_reader) return m_reader->getMaker(); + return ""; +} + +QString +ReadOnlyWaveFileModel::getLocation() const +{ + if (m_reader) return m_reader->getLocation(); + return ""; +} + +QString +ReadOnlyWaveFileModel::getLocalFilename() const +{ + if (m_reader) return m_reader->getLocalFilename(); + return ""; +} + +floatvec_t +ReadOnlyWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const +{ + // Read directly from the file. This is used for e.g. audio + // playback or input to transforms. + +#ifdef DEBUG_WAVE_FILE_MODEL + cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << endl; +#endif + + int channels = getChannelCount(); + + if (channel >= channels) { + cerr << "ERROR: WaveFileModel::getData: channel (" + << channel << ") >= channel count (" << channels << ")" + << endl; + return {}; + } + + if (!m_reader || !m_reader->isOK() || count == 0) { + return {}; + } + + if (start >= m_startFrame) { + start -= m_startFrame; + } else { + if (count <= m_startFrame - start) { + return {}; + } else { + count -= (m_startFrame - start); + start = 0; + } + } + + floatvec_t interleaved = m_reader->getInterleavedFrames(start, count); + if (channels == 1) return interleaved; + + sv_frame_t obtained = interleaved.size() / channels; + + floatvec_t result(obtained, 0.f); + + if (channel != -1) { + // get a single channel + for (int i = 0; i < obtained; ++i) { + result[i] = interleaved[i * channels + channel]; + } + } else { + // channel == -1, mix down all channels + for (int i = 0; i < obtained; ++i) { + for (int c = 0; c < channels; ++c) { + result[i] += interleaved[i * channels + c]; + } + } + } + + return result; +} + +vector<floatvec_t> +ReadOnlyWaveFileModel::getMultiChannelData(int fromchannel, int tochannel, + sv_frame_t start, sv_frame_t count) const +{ + // Read directly from the file. This is used for e.g. audio + // playback or input to transforms. + +#ifdef DEBUG_WAVE_FILE_MODEL + cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << endl; +#endif + + int channels = getChannelCount(); + + if (fromchannel > tochannel) { + cerr << "ERROR: ReadOnlyWaveFileModel::getData: fromchannel (" + << fromchannel << ") > tochannel (" << tochannel << ")" + << endl; + return {}; + } + + if (tochannel >= channels) { + cerr << "ERROR: ReadOnlyWaveFileModel::getData: tochannel (" + << tochannel << ") >= channel count (" << channels << ")" + << endl; + return {}; + } + + if (!m_reader || !m_reader->isOK() || count == 0) { + return {}; + } + + int reqchannels = (tochannel - fromchannel) + 1; + + if (start >= m_startFrame) { + start -= m_startFrame; + } else { + if (count <= m_startFrame - start) { + return {}; + } else { + count -= (m_startFrame - start); + start = 0; + } + } + + floatvec_t interleaved = m_reader->getInterleavedFrames(start, count); + if (channels == 1) return { interleaved }; + + sv_frame_t obtained = interleaved.size() / channels; + vector<floatvec_t> result(reqchannels, floatvec_t(obtained, 0.f)); + + for (int c = fromchannel; c <= tochannel; ++c) { + int destc = c - fromchannel; + for (int i = 0; i < obtained; ++i) { + result[destc][i] = interleaved[i * channels + c]; + } + } + + return result; +} + +int +ReadOnlyWaveFileModel::getSummaryBlockSize(int desired) const +{ + int cacheType = 0; + int power = m_zoomConstraint.getMinCachePower(); + int roundedBlockSize = m_zoomConstraint.getNearestBlockSize + (desired, cacheType, power, ZoomConstraint::RoundDown); + if (cacheType != 0 && cacheType != 1) { + // We will be reading directly from file, so can satisfy any + // blocksize requirement + return desired; + } else { + return roundedBlockSize; + } +} + +void +ReadOnlyWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count, + RangeBlock &ranges, int &blockSize) const +{ + ranges.clear(); + if (!isOK()) return; + ranges.reserve((count / blockSize) + 1); + + if (start > m_startFrame) start -= m_startFrame; + else if (count <= m_startFrame - start) return; + else { + count -= (m_startFrame - start); + start = 0; + } + + int cacheType = 0; + int power = m_zoomConstraint.getMinCachePower(); + int roundedBlockSize = m_zoomConstraint.getNearestBlockSize + (blockSize, cacheType, power, ZoomConstraint::RoundDown); + + int channels = getChannelCount(); + + if (cacheType != 0 && cacheType != 1) { + + // We need to read directly from the file. We haven't got + // this cached. Hope the requested area is small. This is + // not optimal -- we'll end up reading the same frames twice + // for stereo files, in two separate calls to this method. + // We could fairly trivially handle this for most cases that + // matter by putting a single cache in getInterleavedFrames + // for short queries. + + m_directReadMutex.lock(); + + if (m_lastDirectReadStart != start || + m_lastDirectReadCount != count || + m_directRead.empty()) { + + m_directRead = m_reader->getInterleavedFrames(start, count); + m_lastDirectReadStart = start; + m_lastDirectReadCount = count; + } + + float max = 0.0, min = 0.0, total = 0.0; + sv_frame_t i = 0, got = 0; + + while (i < count) { + + sv_frame_t index = i * channels + channel; + if (index >= (sv_frame_t)m_directRead.size()) break; + + float sample = m_directRead[index]; + if (sample > max || got == 0) max = sample; + if (sample < min || got == 0) min = sample; + total += fabsf(sample); + + ++i; + ++got; + + if (got == blockSize) { + ranges.push_back(Range(min, max, total / float(got))); + min = max = total = 0.0f; + got = 0; + } + } + + m_directReadMutex.unlock(); + + if (got > 0) { + ranges.push_back(Range(min, max, total / float(got))); + } + + return; + + } else { + + QMutexLocker locker(&m_mutex); + + const RangeBlock &cache = m_cache[cacheType]; + + blockSize = roundedBlockSize; + + sv_frame_t cacheBlock, div; + + cacheBlock = (sv_frame_t(1) << m_zoomConstraint.getMinCachePower()); + if (cacheType == 1) { + cacheBlock = sv_frame_t(double(cacheBlock) * sqrt(2.) + 0.01); + } + div = blockSize / cacheBlock; + + sv_frame_t startIndex = start / cacheBlock; + sv_frame_t endIndex = (start + count) / cacheBlock; + + float max = 0.0, min = 0.0, total = 0.0; + sv_frame_t i = 0, got = 0; + +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; +#endif + + for (i = 0; i <= endIndex - startIndex; ) { + + sv_frame_t index = (i + startIndex) * channels + channel; + if (!in_range_for(cache, index)) break; + + const Range &range = cache[index]; + if (range.max() > max || got == 0) max = range.max(); + if (range.min() < min || got == 0) min = range.min(); + total += range.absmean(); + + ++i; + ++got; + + if (got == div) { + ranges.push_back(Range(min, max, total / float(got))); + min = max = total = 0.0f; + got = 0; + } + } + + if (got > 0) { + ranges.push_back(Range(min, max, total / float(got))); + } + } + +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "returning " << ranges.size() << " ranges" << endl; +#endif + return; +} + +ReadOnlyWaveFileModel::Range +ReadOnlyWaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const +{ + Range range; + if (!isOK()) return range; + + if (start > m_startFrame) start -= m_startFrame; + else if (count <= m_startFrame - start) return range; + else { + count -= (m_startFrame - start); + start = 0; + } + + int blockSize; + for (blockSize = 1; blockSize <= count; blockSize *= 2); + if (blockSize > 1) blockSize /= 2; + + bool first = false; + + sv_frame_t blockStart = (start / blockSize) * blockSize; + sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize; + + if (blockStart < start) blockStart += blockSize; + + if (blockEnd > blockStart) { + RangeBlock ranges; + getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize); + for (int i = 0; i < (int)ranges.size(); ++i) { + if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min()); + if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max()); + if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean()); + first = false; + } + } + + if (blockStart > start) { + Range startRange = getSummary(channel, start, blockStart - start); + range.setMin(min(range.min(), startRange.min())); + range.setMax(max(range.max(), startRange.max())); + range.setAbsmean(min(range.absmean(), startRange.absmean())); + } + + if (blockEnd < start + count) { + Range endRange = getSummary(channel, blockEnd, start + count - blockEnd); + range.setMin(min(range.min(), endRange.min())); + range.setMax(max(range.max(), endRange.max())); + range.setAbsmean(min(range.absmean(), endRange.absmean())); + } + + return range; +} + +void +ReadOnlyWaveFileModel::fillCache() +{ + m_mutex.lock(); + + m_updateTimer = new QTimer(this); + connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); + m_updateTimer->start(100); + + m_fillThread = new RangeCacheFillThread(*this); + connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); + + m_mutex.unlock(); + m_fillThread->start(); + +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::fillCache: started fill thread" << endl; +#endif +} + +void +ReadOnlyWaveFileModel::fillTimerTimedOut() +{ + if (m_fillThread) { + sv_frame_t fillExtent = m_fillThread->getFillExtent(); +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl; +#endif + if (fillExtent > m_lastFillExtent) { + emit modelChangedWithin(m_lastFillExtent, fillExtent); + m_lastFillExtent = fillExtent; + } + } else { +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: no thread" << endl; +#endif + emit modelChanged(); + } +} + +void +ReadOnlyWaveFileModel::cacheFilled() +{ + m_mutex.lock(); + delete m_fillThread; + m_fillThread = 0; + delete m_updateTimer; + m_updateTimer = 0; + m_mutex.unlock(); + if (getEndFrame() > m_lastFillExtent) { + emit modelChangedWithin(m_lastFillExtent, getEndFrame()); + } + emit modelChanged(); + emit ready(); +#ifdef DEBUG_WAVE_FILE_MODEL + SVDEBUG << "ReadOnlyWaveFileModel::cacheFilled" << endl; +#endif +} + +void +ReadOnlyWaveFileModel::RangeCacheFillThread::run() +{ + int cacheBlockSize[2]; + cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower()); + cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) * + sqrt(2.) + 0.01)); + + sv_frame_t frame = 0; + const sv_frame_t readBlockSize = 32768; + floatvec_t block; + + if (!m_model.isOK()) return; + + int channels = m_model.getChannelCount(); + bool updating = m_model.m_reader->isUpdating(); + + if (updating) { + while (channels == 0 && !m_model.m_exiting) { +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl; +#endif + sleep(1); + channels = m_model.getChannelCount(); + } + } + + Range *range = new Range[2 * channels]; + float *means = new float[2 * channels]; + int count[2]; + count[0] = count[1] = 0; + for (int i = 0; i < 2 * channels; ++i) { + means[i] = 0.f; + } + + bool first = true; + + while (first || updating) { + + updating = m_model.m_reader->isUpdating(); + m_frameCount = m_model.getFrameCount(); + + m_model.m_mutex.lock(); + + while (frame < m_frameCount) { + + m_model.m_mutex.unlock(); + +#ifdef DEBUG_WAVE_FILE_MODEL + cerr << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl; +#endif + + if (updating && (frame + readBlockSize > m_frameCount)) { + m_model.m_mutex.lock(); // must be locked on exiting loop + break; + } + + block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize); + + sv_frame_t gotBlockSize = block.size() / channels; + + m_model.m_mutex.lock(); + + for (sv_frame_t i = 0; i < gotBlockSize; ++i) { + + for (int ch = 0; ch < channels; ++ch) { + + sv_frame_t index = channels * i + ch; + float sample = block[index]; + + for (int cacheType = 0; cacheType < 2; ++cacheType) { + sv_frame_t rangeIndex = ch * 2 + cacheType; + range[rangeIndex].sample(sample); + means[rangeIndex] += fabsf(sample); + } + } + + for (int cacheType = 0; cacheType < 2; ++cacheType) { + + if (++count[cacheType] == cacheBlockSize[cacheType]) { + + for (int ch = 0; ch < int(channels); ++ch) { + int rangeIndex = ch * 2 + cacheType; + means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); + range[rangeIndex].setAbsmean(means[rangeIndex]); + m_model.m_cache[cacheType].push_back(range[rangeIndex]); + range[rangeIndex] = Range(); + means[rangeIndex] = 0.f; + } + + count[cacheType] = 0; + } + } + + ++frame; + } + + if (m_model.m_exiting) break; + m_fillExtent = frame; + } + + m_model.m_mutex.unlock(); + + first = false; + if (m_model.m_exiting) break; + if (updating) { + sleep(1); + } + } + + if (!m_model.m_exiting) { + + QMutexLocker locker(&m_model.m_mutex); + + for (int cacheType = 0; cacheType < 2; ++cacheType) { + + if (count[cacheType] > 0) { + + for (int ch = 0; ch < int(channels); ++ch) { + int rangeIndex = ch * 2 + cacheType; + means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); + range[rangeIndex].setAbsmean(means[rangeIndex]); + m_model.m_cache[cacheType].push_back(range[rangeIndex]); + range[rangeIndex] = Range(); + means[rangeIndex] = 0.f; + } + + count[cacheType] = 0; + } + + const Range &rr = *m_model.m_cache[cacheType].begin(); + MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range)); + } + } + + delete[] means; + delete[] range; + + m_fillExtent = m_frameCount; + +#ifdef DEBUG_WAVE_FILE_MODEL + for (int cacheType = 0; cacheType < 2; ++cacheType) { + cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl; + } +#endif +} + +void +ReadOnlyWaveFileModel::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + Model::toXml(out, indent, + QString("type=\"wavefile\" file=\"%1\" %2") + .arg(encodeEntities(m_path)).arg(extraAttributes)); +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/ReadOnlyWaveFileModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,131 @@ +/* -*- 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 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 READ_ONLY_WAVE_FILE_MODEL_H +#define READ_ONLY_WAVE_FILE_MODEL_H + +#include "WaveFileModel.h" + +#include "base/Thread.h" +#include <QMutex> +#include <QTimer> + +#include "data/fileio/FileSource.h" + +#include "RangeSummarisableTimeValueModel.h" +#include "PowerOfSqrtTwoZoomConstraint.h" + +#include <stdlib.h> + +class AudioFileReader; + +class ReadOnlyWaveFileModel : public WaveFileModel +{ + Q_OBJECT + +public: + ReadOnlyWaveFileModel(FileSource source, sv_samplerate_t targetRate = 0); + ReadOnlyWaveFileModel(FileSource source, AudioFileReader *reader); + ~ReadOnlyWaveFileModel(); + + bool isOK() const; + bool isReady(int *) const; + + const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; } + + sv_frame_t getFrameCount() const; + int getChannelCount() const; + sv_samplerate_t getSampleRate() const; + sv_samplerate_t getNativeRate() const; + + QString getTitle() const; + QString getMaker() const; + QString getLocation() const; + + QString getLocalFilename() const; + + float getValueMinimum() const { return -1.0f; } + float getValueMaximum() const { return 1.0f; } + + virtual sv_frame_t getStartFrame() const { return m_startFrame; } + virtual sv_frame_t getEndFrame() const { return m_startFrame + getFrameCount(); } + + void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; } + + virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) const; + + virtual std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; + + virtual int getSummaryBlockSize(int desired) const; + + virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count, + RangeBlock &ranges, + int &blockSize) const; + + virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const; + + QString getTypeName() const { return tr("Wave File"); } + + virtual void toXml(QTextStream &out, + QString indent = "", + QString extraAttributes = "") const; + +protected slots: + void fillTimerTimedOut(); + void cacheFilled(); + +protected: + void initialize(); + + class RangeCacheFillThread : public Thread + { + public: + RangeCacheFillThread(ReadOnlyWaveFileModel &model) : + m_model(model), m_fillExtent(0), + m_frameCount(model.getFrameCount()) { } + + sv_frame_t getFillExtent() const { return m_fillExtent; } + virtual void run(); + + protected: + ReadOnlyWaveFileModel &m_model; + sv_frame_t m_fillExtent; + sv_frame_t m_frameCount; + }; + + void fillCache(); + + FileSource m_source; + QString m_path; + AudioFileReader *m_reader; + bool m_myReader; + + sv_frame_t m_startFrame; + + RangeBlock m_cache[2]; // interleaved at two base resolutions + mutable QMutex m_mutex; + RangeCacheFillThread *m_fillThread; + QTimer *m_updateTimer; + sv_frame_t m_lastFillExtent; + bool m_exiting; + static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; + + mutable floatvec_t m_directRead; + mutable sv_frame_t m_lastDirectReadStart; + mutable sv_frame_t m_lastDirectReadCount; + mutable QMutex m_directReadMutex; +}; + +#endif
--- a/data/model/SparseModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/SparseModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -20,6 +20,7 @@ #include "TabularModel.h" #include "base/Command.h" #include "base/RealTime.h" +#include "system/System.h" #include <iostream> @@ -331,8 +332,13 @@ virtual QVariant getData(int row, int column, int role) const { PointListConstIterator i = getPointListIteratorForRow(row); - if (i == m_points.end()) return QVariant(); + if (i == m_points.end()) { +// cerr << "no iterator for row " << row << " (have " << getRowCount() << " rows)" << endl; + return QVariant(); + } +// cerr << "returning data for row " << row << " col " << column << endl; + switch (column) { case 0: { if (role == SortRole) return int(i->frame); @@ -410,6 +416,7 @@ // This is only used if the model is called on to act in // TabularModel mode mutable std::vector<sv_frame_t> m_rows; // map from row number to frame + void rebuildRowVector() const { m_rows.clear(); @@ -457,7 +464,7 @@ while (ri > 0 && m_rows[ri-1] == m_rows[row]) { --ri; ++indexAtFrame; } int initialIndexAtFrame = indexAtFrame; -// std::cerr << "getPointListIteratorForRow " << row << ": initialIndexAtFrame = " << initialIndexAtFrame << std::endl; +// std::cerr << "getPointListIteratorForRow " << row << ": initialIndexAtFrame = " << initialIndexAtFrame << " for frame " << frame << std::endl; PointListConstIterator i0, i1; getPointIterators(frame, i0, i1); @@ -470,9 +477,13 @@ if (indexAtFrame > 0) { --indexAtFrame; continue; } return i; } - -// std::cerr << "returning i with i->frame = " << i->frame << std::endl; - +/* + if (i == m_points.end()) { + std::cerr << "returning i at end" << std::endl; + } else { + std::cerr << "returning i with i->frame = " << i->frame << std::endl; + } +*/ if (indexAtFrame > 0) { std::cerr << "WARNING: SparseModel::getPointListIteratorForRow: No iterator available for row " << row << " (frame = " << frame << ", index at frame = " << initialIndexAtFrame << ", leftover index " << indexAtFrame << ")" << std::endl; } @@ -729,8 +740,8 @@ { QMutexLocker locker(&m_mutex); m_resolution = resolution; + m_rows.clear(); } - m_rows.clear(); emit modelChanged(); } @@ -742,8 +753,8 @@ QMutexLocker locker(&m_mutex); m_points.clear(); m_pointCount = 0; + m_rows.clear(); } - m_rows.clear(); emit modelChanged(); } @@ -751,12 +762,11 @@ void SparseModel<PointType>::addPoint(const PointType &point) { - { - QMutexLocker locker(&m_mutex); - m_points.insert(point); - m_pointCount++; - if (point.getLabel() != "") m_hasTextLabels = true; - } + QMutexLocker locker(&m_mutex); + + m_points.insert(point); + m_pointCount++; + if (point.getLabel() != "") m_hasTextLabels = true; // Even though this model is nominally sparse, there may still be // too many signals going on here (especially as they'll probably @@ -783,18 +793,16 @@ bool SparseModel<PointType>::containsPoint(const PointType &point) { - { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - PointListIterator i = m_points.lower_bound(point); - typename PointType::Comparator comparator; - while (i != m_points.end()) { - if (i->frame > point.frame) break; - if (!comparator(*i, point) && !comparator(point, *i)) { - return true; - } - ++i; - } + PointListIterator i = m_points.lower_bound(point); + typename PointType::Comparator comparator; + while (i != m_points.end()) { + if (i->frame > point.frame) break; + if (!comparator(*i, point) && !comparator(point, *i)) { + return true; + } + ++i; } return false; @@ -804,21 +812,20 @@ void SparseModel<PointType>::deletePoint(const PointType &point) { - { - QMutexLocker locker(&m_mutex); + QMutexLocker locker(&m_mutex); - PointListIterator i = m_points.lower_bound(point); - typename PointType::Comparator comparator; - while (i != m_points.end()) { - if (i->frame > point.frame) break; - if (!comparator(*i, point) && !comparator(point, *i)) { - m_points.erase(i); - m_pointCount--; - break; + PointListIterator i = m_points.lower_bound(point); + typename PointType::Comparator comparator; + while (i != m_points.end()) { + if (i->frame > point.frame) break; + if (!comparator(*i, point) && !comparator(point, *i)) { + m_points.erase(i); + m_pointCount--; + break; } - ++i; - } + ++i; } + // std::cout << "SparseOneDimensionalModel: emit modelChanged(" // << point.frame << ")" << std::endl; m_rows.clear(); //!!! inefficient @@ -831,6 +838,8 @@ { // std::cerr << "SparseModel::setCompletion(" << completion << ")" << std::endl; + QMutexLocker locker(&m_mutex); + if (m_completion != completion) { m_completion = completion;
--- a/data/model/WaveFileModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/WaveFileModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -15,738 +15,7 @@ #include "WaveFileModel.h" -#include "fileio/AudioFileReader.h" -#include "fileio/AudioFileReaderFactory.h" - -#include "system/System.h" - -#include "base/Preferences.h" - -#include <QFileInfo> -#include <QTextStream> - -#include <iostream> -#include <unistd.h> -#include <cmath> -#include <sndfile.h> - -#include <cassert> - -//#define DEBUG_WAVE_FILE_MODEL 1 - -PowerOfSqrtTwoZoomConstraint -WaveFileModel::m_zoomConstraint; - -WaveFileModel::WaveFileModel(FileSource source, sv_samplerate_t targetRate) : - m_source(source), - m_path(source.getLocation()), - m_reader(0), - m_myReader(true), - m_startFrame(0), - m_fillThread(0), - m_updateTimer(0), - m_lastFillExtent(0), - m_exiting(false), - m_lastDirectReadStart(0), - m_lastDirectReadCount(0) +WaveFileModel::~WaveFileModel() { - m_source.waitForData(); - if (m_source.isOK()) { - bool normalise = Preferences::getInstance()->getNormaliseAudio(); - m_reader = AudioFileReaderFactory::createThreadingReader - (m_source, targetRate, normalise); - if (m_reader) { - SVDEBUG << "WaveFileModel::WaveFileModel: reader rate: " - << m_reader->getSampleRate() << endl; - } - } - if (m_reader) setObjectName(m_reader->getTitle()); - if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); - if (isOK()) fillCache(); } -WaveFileModel::WaveFileModel(FileSource source, AudioFileReader *reader) : - m_source(source), - m_path(source.getLocation()), - m_reader(0), - m_myReader(false), - m_startFrame(0), - m_fillThread(0), - m_updateTimer(0), - m_lastFillExtent(0), - m_exiting(false) -{ - m_reader = reader; - if (m_reader) setObjectName(m_reader->getTitle()); - if (objectName() == "") setObjectName(QFileInfo(m_path).fileName()); - fillCache(); -} - -WaveFileModel::~WaveFileModel() -{ - m_exiting = true; - if (m_fillThread) m_fillThread->wait(); - if (m_myReader) delete m_reader; - m_reader = 0; -} - -bool -WaveFileModel::isOK() const -{ - return m_reader && m_reader->isOK(); -} - -bool -WaveFileModel::isReady(int *completion) const -{ - bool ready = (isOK() && (m_fillThread == 0)); - double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame()); - static int prevCompletion = 0; - if (completion) { - *completion = int(c * 100.0 + 0.01); - if (m_reader) { - int decodeCompletion = m_reader->getDecodeCompletion(); - if (decodeCompletion < 90) *completion = decodeCompletion; - else *completion = std::min(*completion, decodeCompletion); - } - if (*completion != 0 && - *completion != 100 && - prevCompletion != 0 && - prevCompletion > *completion) { - // just to avoid completion going backwards - *completion = prevCompletion; - } - prevCompletion = *completion; - } -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl; -#endif - return ready; -} - -sv_frame_t -WaveFileModel::getFrameCount() const -{ - if (!m_reader) return 0; - return m_reader->getFrameCount(); -} - -int -WaveFileModel::getChannelCount() const -{ - if (!m_reader) return 0; - return m_reader->getChannelCount(); -} - -sv_samplerate_t -WaveFileModel::getSampleRate() const -{ - if (!m_reader) return 0; - return m_reader->getSampleRate(); -} - -sv_samplerate_t -WaveFileModel::getNativeRate() const -{ - if (!m_reader) return 0; - sv_samplerate_t rate = m_reader->getNativeRate(); - if (rate == 0) rate = getSampleRate(); - return rate; -} - -QString -WaveFileModel::getTitle() const -{ - QString title; - if (m_reader) title = m_reader->getTitle(); - if (title == "") title = objectName(); - return title; -} - -QString -WaveFileModel::getMaker() const -{ - if (m_reader) return m_reader->getMaker(); - return ""; -} - -QString -WaveFileModel::getLocation() const -{ - if (m_reader) return m_reader->getLocation(); - return ""; -} - -QString -WaveFileModel::getLocalFilename() const -{ - if (m_reader) return m_reader->getLocalFilename(); - return ""; -} - -sv_frame_t -WaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const -{ - // Always read these directly from the file. - // This is used for e.g. audio playback. - // Could be much more efficient (although compiler optimisation will help) - -#ifdef DEBUG_WAVE_FILE_MODEL - cout << "WaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl; -#endif - - if (start >= m_startFrame) { - start -= m_startFrame; - } else { - for (sv_frame_t i = 0; i < count; ++i) { - buffer[i] = 0.f; - } - if (count <= m_startFrame - start) { - return 0; - } else { - count -= (m_startFrame - start); - start = 0; - } - } - - if (!m_reader || !m_reader->isOK() || count == 0) { - for (sv_frame_t i = 0; i < count; ++i) buffer[i] = 0.f; - return 0; - } - -#ifdef DEBUG_WAVE_FILE_MODEL -// SVDEBUG << "WaveFileModel::getValues(" << channel << ", " -// << start << ", " << end << "): calling reader" << endl; -#endif - - int channels = getChannelCount(); - - SampleBlock frames = m_reader->getInterleavedFrames(start, count); - - sv_frame_t i = 0; - - int ch0 = channel, ch1 = channel; - if (channel == -1) { - ch0 = 0; - ch1 = channels - 1; - } - - while (i < count) { - - buffer[i] = 0.0; - - for (int ch = ch0; ch <= ch1; ++ch) { - - sv_frame_t index = i * channels + ch; - if (index >= (sv_frame_t)frames.size()) break; - - float sample = frames[index]; - buffer[i] += sample; - } - - ++i; - } - - return i; -} - -sv_frame_t -WaveFileModel::getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffer) const -{ -#ifdef DEBUG_WAVE_FILE_MODEL - cout << "WaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl; -#endif - - int channels = getChannelCount(); - - if (fromchannel > tochannel) { - cerr << "ERROR: WaveFileModel::getData: fromchannel (" - << fromchannel << ") > tochannel (" << tochannel << ")" - << endl; - return 0; - } - - if (tochannel >= channels) { - cerr << "ERROR: WaveFileModel::getData: tochannel (" - << tochannel << ") >= channel count (" << channels << ")" - << endl; - return 0; - } - - if (fromchannel == tochannel) { - return getData(fromchannel, start, count, buffer[0]); - } - - int reqchannels = (tochannel - fromchannel) + 1; - - // Always read these directly from the file. - // This is used for e.g. audio playback. - // Could be much more efficient (although compiler optimisation will help) - - if (start >= m_startFrame) { - start -= m_startFrame; - } else { - for (int c = 0; c < reqchannels; ++c) { - for (sv_frame_t i = 0; i < count; ++i) buffer[c][i] = 0.f; - } - if (count <= m_startFrame - start) { - return 0; - } else { - count -= (m_startFrame - start); - start = 0; - } - } - - if (!m_reader || !m_reader->isOK() || count == 0) { - for (int c = 0; c < reqchannels; ++c) { - for (sv_frame_t i = 0; i < count; ++i) buffer[c][i] = 0.f; - } - return 0; - } - - SampleBlock frames = m_reader->getInterleavedFrames(start, count); - - sv_frame_t i = 0; - - sv_frame_t index = 0, available = frames.size(); - - while (i < count) { - - if (index >= available) break; - - int destc = 0; - - for (int c = 0; c < channels; ++c) { - - if (c >= fromchannel && c <= tochannel) { - buffer[destc][i] = frames[index]; - ++destc; - } - - ++index; - } - - ++i; - } - - return i; -} - -int -WaveFileModel::getSummaryBlockSize(int desired) const -{ - int cacheType = 0; - int power = m_zoomConstraint.getMinCachePower(); - int roundedBlockSize = m_zoomConstraint.getNearestBlockSize - (desired, cacheType, power, ZoomConstraint::RoundDown); - if (cacheType != 0 && cacheType != 1) { - // We will be reading directly from file, so can satisfy any - // blocksize requirement - return desired; - } else { - return roundedBlockSize; - } -} - -void -WaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count, - RangeBlock &ranges, int &blockSize) const -{ - ranges.clear(); - if (!isOK()) return; - ranges.reserve((count / blockSize) + 1); - - if (start > m_startFrame) start -= m_startFrame; - else if (count <= m_startFrame - start) return; - else { - count -= (m_startFrame - start); - start = 0; - } - - int cacheType = 0; - int power = m_zoomConstraint.getMinCachePower(); - int roundedBlockSize = m_zoomConstraint.getNearestBlockSize - (blockSize, cacheType, power, ZoomConstraint::RoundDown); - - int channels = getChannelCount(); - - if (cacheType != 0 && cacheType != 1) { - - // We need to read directly from the file. We haven't got - // this cached. Hope the requested area is small. This is - // not optimal -- we'll end up reading the same frames twice - // for stereo files, in two separate calls to this method. - // We could fairly trivially handle this for most cases that - // matter by putting a single cache in getInterleavedFrames - // for short queries. - - m_directReadMutex.lock(); - - if (m_lastDirectReadStart != start || - m_lastDirectReadCount != count || - m_directRead.empty()) { - - m_directRead = m_reader->getInterleavedFrames(start, count); - m_lastDirectReadStart = start; - m_lastDirectReadCount = count; - } - - float max = 0.0, min = 0.0, total = 0.0; - sv_frame_t i = 0, got = 0; - - while (i < count) { - - sv_frame_t index = i * channels + channel; - if (index >= (sv_frame_t)m_directRead.size()) break; - - float sample = m_directRead[index]; - if (sample > max || got == 0) max = sample; - if (sample < min || got == 0) min = sample; - total += fabsf(sample); - - ++i; - ++got; - - if (got == blockSize) { - ranges.push_back(Range(min, max, total / float(got))); - min = max = total = 0.0f; - got = 0; - } - } - - m_directReadMutex.unlock(); - - if (got > 0) { - ranges.push_back(Range(min, max, total / float(got))); - } - - return; - - } else { - - QMutexLocker locker(&m_mutex); - - const RangeBlock &cache = m_cache[cacheType]; - - blockSize = roundedBlockSize; - - sv_frame_t cacheBlock, div; - - if (cacheType == 0) { - cacheBlock = (1 << m_zoomConstraint.getMinCachePower()); - div = (1 << power) / cacheBlock; - } else { - cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01); - div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock)); - } - - sv_frame_t startIndex = start / cacheBlock; - sv_frame_t endIndex = (start + count) / cacheBlock; - - float max = 0.0, min = 0.0, total = 0.0; - sv_frame_t i = 0, got = 0; - -#ifdef DEBUG_WAVE_FILE_MODEL - cerr << "blockSize is " << blockSize << ", cacheBlock " << cacheBlock << ", start " << start << ", count " << count << " (frame count " << getFrameCount() << "), power is " << power << ", div is " << div << ", startIndex " << startIndex << ", endIndex " << endIndex << endl; -#endif - - for (i = 0; i <= endIndex - startIndex; ) { - - sv_frame_t index = (i + startIndex) * channels + channel; - if (index >= (sv_frame_t)cache.size()) break; - - const Range &range = cache[index]; - if (range.max() > max || got == 0) max = range.max(); - if (range.min() < min || got == 0) min = range.min(); - total += range.absmean(); - - ++i; - ++got; - - if (got == div) { - ranges.push_back(Range(min, max, total / float(got))); - min = max = total = 0.0f; - got = 0; - } - } - - if (got > 0) { - ranges.push_back(Range(min, max, total / float(got))); - } - } - -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "returning " << ranges.size() << " ranges" << endl; -#endif - return; -} - -WaveFileModel::Range -WaveFileModel::getSummary(int channel, sv_frame_t start, sv_frame_t count) const -{ - Range range; - if (!isOK()) return range; - - if (start > m_startFrame) start -= m_startFrame; - else if (count <= m_startFrame - start) return range; - else { - count -= (m_startFrame - start); - start = 0; - } - - int blockSize; - for (blockSize = 1; blockSize <= count; blockSize *= 2); - if (blockSize > 1) blockSize /= 2; - - bool first = false; - - sv_frame_t blockStart = (start / blockSize) * blockSize; - sv_frame_t blockEnd = ((start + count) / blockSize) * blockSize; - - if (blockStart < start) blockStart += blockSize; - - if (blockEnd > blockStart) { - RangeBlock ranges; - getSummaries(channel, blockStart, blockEnd - blockStart, ranges, blockSize); - for (int i = 0; i < (int)ranges.size(); ++i) { - if (first || ranges[i].min() < range.min()) range.setMin(ranges[i].min()); - if (first || ranges[i].max() > range.max()) range.setMax(ranges[i].max()); - if (first || ranges[i].absmean() < range.absmean()) range.setAbsmean(ranges[i].absmean()); - first = false; - } - } - - if (blockStart > start) { - Range startRange = getSummary(channel, start, blockStart - start); - range.setMin(std::min(range.min(), startRange.min())); - range.setMax(std::max(range.max(), startRange.max())); - range.setAbsmean(std::min(range.absmean(), startRange.absmean())); - } - - if (blockEnd < start + count) { - Range endRange = getSummary(channel, blockEnd, start + count - blockEnd); - range.setMin(std::min(range.min(), endRange.min())); - range.setMax(std::max(range.max(), endRange.max())); - range.setAbsmean(std::min(range.absmean(), endRange.absmean())); - } - - return range; -} - -void -WaveFileModel::fillCache() -{ - m_mutex.lock(); - - m_updateTimer = new QTimer(this); - connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut())); - m_updateTimer->start(100); - - m_fillThread = new RangeCacheFillThread(*this); - connect(m_fillThread, SIGNAL(finished()), this, SLOT(cacheFilled())); - - m_mutex.unlock(); - m_fillThread->start(); - -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::fillCache: started fill thread" << endl; -#endif -} - -void -WaveFileModel::fillTimerTimedOut() -{ - if (m_fillThread) { - sv_frame_t fillExtent = m_fillThread->getFillExtent(); -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl; -#endif - if (fillExtent > m_lastFillExtent) { - emit modelChangedWithin(m_lastFillExtent, fillExtent); - m_lastFillExtent = fillExtent; - } - } else { -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::fillTimerTimedOut: no thread" << endl; -#endif - emit modelChanged(); - } -} - -void -WaveFileModel::cacheFilled() -{ - m_mutex.lock(); - delete m_fillThread; - m_fillThread = 0; - delete m_updateTimer; - m_updateTimer = 0; - m_mutex.unlock(); - if (getEndFrame() > m_lastFillExtent) { - emit modelChangedWithin(m_lastFillExtent, getEndFrame()); - } - emit modelChanged(); - emit ready(); -#ifdef DEBUG_WAVE_FILE_MODEL - SVDEBUG << "WaveFileModel::cacheFilled" << endl; -#endif -} - -void -WaveFileModel::RangeCacheFillThread::run() -{ - int cacheBlockSize[2]; - cacheBlockSize[0] = (1 << m_model.m_zoomConstraint.getMinCachePower()); - cacheBlockSize[1] = (int((1 << m_model.m_zoomConstraint.getMinCachePower()) * - sqrt(2.) + 0.01)); - - sv_frame_t frame = 0; - const sv_frame_t readBlockSize = 16384; - SampleBlock block; - - if (!m_model.isOK()) return; - - int channels = m_model.getChannelCount(); - bool updating = m_model.m_reader->isUpdating(); - - if (updating) { - while (channels == 0 && !m_model.m_exiting) { -// SVDEBUG << "WaveFileModel::fill: Waiting for channels..." << endl; - sleep(1); - channels = m_model.getChannelCount(); - } - } - - Range *range = new Range[2 * channels]; - float *means = new float[2 * channels]; - int count[2]; - count[0] = count[1] = 0; - for (int i = 0; i < 2 * channels; ++i) { - means[i] = 0.f; - } - - bool first = true; - - while (first || updating) { - - updating = m_model.m_reader->isUpdating(); - m_frameCount = m_model.getFrameCount(); - -// SVDEBUG << "WaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl; - - while (frame < m_frameCount) { - -// SVDEBUG << "WaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl; - - if (updating && (frame + readBlockSize > m_frameCount)) break; - - block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize); - -// cerr << "block is " << block.size() << endl; - - for (sv_frame_t i = 0; i < readBlockSize; ++i) { - - if (channels * i + channels > (int)block.size()) break; - - for (int ch = 0; ch < channels; ++ch) { - - sv_frame_t index = channels * i + ch; - float sample = block[index]; - - for (int cacheType = 0; cacheType < 2; ++cacheType) { // cache type - - sv_frame_t rangeIndex = ch * 2 + cacheType; - range[rangeIndex].sample(sample); - means[rangeIndex] += fabsf(sample); - } - } - - //!!! this looks like a ludicrous way to do synchronisation - QMutexLocker locker(&m_model.m_mutex); - - for (int cacheType = 0; cacheType < 2; ++cacheType) { - - if (++count[cacheType] == cacheBlockSize[cacheType]) { - - for (int ch = 0; ch < int(channels); ++ch) { - int rangeIndex = ch * 2 + cacheType; - means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); - range[rangeIndex].setAbsmean(means[rangeIndex]); - m_model.m_cache[cacheType].push_back(range[rangeIndex]); - range[rangeIndex] = Range(); - means[rangeIndex] = 0.f; - } - - count[cacheType] = 0; - } - } - - ++frame; - } - - if (m_model.m_exiting) break; - - m_fillExtent = frame; - } - -// cerr << "WaveFileModel: inner loop ended" << endl; - - first = false; - if (m_model.m_exiting) break; - if (updating) { -// cerr << "sleeping..." << endl; - sleep(1); - } - } - - if (!m_model.m_exiting) { - - QMutexLocker locker(&m_model.m_mutex); - - for (int cacheType = 0; cacheType < 2; ++cacheType) { - - if (count[cacheType] > 0) { - - for (int ch = 0; ch < int(channels); ++ch) { - int rangeIndex = ch * 2 + cacheType; - means[rangeIndex] = means[rangeIndex] / float(count[cacheType]); - range[rangeIndex].setAbsmean(means[rangeIndex]); - m_model.m_cache[cacheType].push_back(range[rangeIndex]); - range[rangeIndex] = Range(); - means[rangeIndex] = 0.f; - } - - count[cacheType] = 0; - } - - const Range &rr = *m_model.m_cache[cacheType].begin(); - MUNLOCK(&rr, m_model.m_cache[cacheType].capacity() * sizeof(Range)); - } - } - - delete[] means; - delete[] range; - - m_fillExtent = m_frameCount; - -#ifdef DEBUG_WAVE_FILE_MODEL - for (int cacheType = 0; cacheType < 2; ++cacheType) { - cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl; - } -#endif -} - -void -WaveFileModel::toXml(QTextStream &out, - QString indent, - QString extraAttributes) const -{ - Model::toXml(out, indent, - QString("type=\"wavefile\" file=\"%1\" %2") - .arg(encodeEntities(m_path)).arg(extraAttributes)); -} - -
--- a/data/model/WaveFileModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/WaveFileModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,120 +13,36 @@ COPYING included with this distribution for more information. */ -#ifndef _WAVE_FILE_MODEL_H_ -#define _WAVE_FILE_MODEL_H_ - -#include "base/Thread.h" -#include <QMutex> -#include <QTimer> - -#include "data/fileio/FileSource.h" +#ifndef WAVE_FILE_MODEL_H +#define WAVE_FILE_MODEL_H #include "RangeSummarisableTimeValueModel.h" -#include "PowerOfSqrtTwoZoomConstraint.h" #include <stdlib.h> -class AudioFileReader; - class WaveFileModel : public RangeSummarisableTimeValueModel { Q_OBJECT public: - WaveFileModel(FileSource source, sv_samplerate_t targetRate = 0); - WaveFileModel(FileSource source, AudioFileReader *reader); - ~WaveFileModel(); + virtual ~WaveFileModel(); - bool isOK() const; - bool isReady(int *) const; + virtual sv_frame_t getFrameCount() const = 0; + virtual int getChannelCount() const = 0; + virtual sv_samplerate_t getSampleRate() const = 0; + virtual sv_samplerate_t getNativeRate() const = 0; - const ZoomConstraint *getZoomConstraint() const { return &m_zoomConstraint; } + virtual QString getTitle() const = 0; + virtual QString getMaker() const = 0; + virtual QString getLocation() const = 0; - sv_frame_t getFrameCount() const; - int getChannelCount() const; - sv_samplerate_t getSampleRate() const; - sv_samplerate_t getNativeRate() const; + virtual sv_frame_t getStartFrame() const = 0; + virtual sv_frame_t getEndFrame() const = 0; - QString getTitle() const; - QString getMaker() const; - QString getLocation() const; + virtual void setStartFrame(sv_frame_t startFrame) = 0; - QString getLocalFilename() const; - - float getValueMinimum() const { return -1.0f; } - float getValueMaximum() const { return 1.0f; } - - virtual sv_frame_t getStartFrame() const { return m_startFrame; } - virtual sv_frame_t getEndFrame() const { return m_startFrame + getFrameCount(); } - - void setStartFrame(sv_frame_t startFrame) { m_startFrame = startFrame; } - - virtual sv_frame_t getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const; - - virtual sv_frame_t getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffers) const; - - virtual int getSummaryBlockSize(int desired) const; - - virtual void getSummaries(int channel, sv_frame_t start, sv_frame_t count, - RangeBlock &ranges, - int &blockSize) const; - - virtual Range getSummary(int channel, sv_frame_t start, sv_frame_t count) const; - - QString getTypeName() const { return tr("Wave File"); } - - virtual void toXml(QTextStream &out, - QString indent = "", - QString extraAttributes = "") const; - -protected slots: - void fillTimerTimedOut(); - void cacheFilled(); - protected: - void initialize(); - - class RangeCacheFillThread : public Thread - { - public: - RangeCacheFillThread(WaveFileModel &model) : - m_model(model), m_fillExtent(0), - m_frameCount(model.getFrameCount()) { } - - sv_frame_t getFillExtent() const { return m_fillExtent; } - virtual void run(); - - protected: - WaveFileModel &m_model; - sv_frame_t m_fillExtent; - sv_frame_t m_frameCount; - }; - - void fillCache(); - - FileSource m_source; - QString m_path; - AudioFileReader *m_reader; - bool m_myReader; - - sv_frame_t m_startFrame; - - RangeBlock m_cache[2]; // interleaved at two base resolutions - mutable QMutex m_mutex; - RangeCacheFillThread *m_fillThread; - QTimer *m_updateTimer; - sv_frame_t m_lastFillExtent; - bool m_exiting; - static PowerOfSqrtTwoZoomConstraint m_zoomConstraint; - - mutable SampleBlock m_directRead; - mutable sv_frame_t m_lastDirectReadStart; - mutable sv_frame_t m_lastDirectReadCount; - mutable QMutex m_directReadMutex; + WaveFileModel() { } // only accessible from subclasses }; #endif
--- a/data/model/WritableWaveFileModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/WritableWaveFileModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -15,6 +15,8 @@ #include "WritableWaveFileModel.h" +#include "ReadOnlyWaveFileModel.h" + #include "base/TempDirectory.h" #include "base/Exceptions.h" @@ -28,6 +30,10 @@ #include <iostream> #include <stdint.h> +using namespace std; + +const int WritableWaveFileModel::PROPORTION_UNKNOWN = -1; + //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1 WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate, @@ -40,7 +46,7 @@ m_channels(channels), m_frameCount(0), m_startFrame(0), - m_completion(0) + m_proportion(PROPORTION_UNKNOWN) { if (path.isEmpty()) { try { @@ -74,7 +80,7 @@ return; } - m_model = new WaveFileModel(source, m_reader); + m_model = new ReadOnlyWaveFileModel(source, m_reader); if (!m_model->isOK()) { cerr << "WritableWaveFileModel: Error in creating wave file model" << endl; delete m_model; @@ -105,7 +111,7 @@ } bool -WritableWaveFileModel::addSamples(float **samples, sv_frame_t count) +WritableWaveFileModel::addSamples(const float *const *samples, sv_frame_t count) { if (!m_writer) return false; @@ -114,30 +120,27 @@ #endif if (!m_writer->writeSamples(samples, count)) { - cerr << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << m_writer->getError() << endl; + SVCERR << "ERROR: WritableWaveFileModel::addSamples: writer failed: " << m_writer->getError() << endl; return false; } m_frameCount += count; - static int updateCounter = 0; - if (m_reader && m_reader->getChannelCount() == 0) { -#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL - SVDEBUG << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (initial)" << endl; -#endif m_reader->updateFrameCount(); - } else if (++updateCounter == 100) { -#ifdef DEBUG_WRITABLE_WAVE_FILE_MODEL - SVDEBUG << "WritableWaveFileModel::addSamples(" << count << "): calling updateFrameCount (periodic)" << endl; -#endif - if (m_reader) m_reader->updateFrameCount(); - updateCounter = 0; } return true; } +void +WritableWaveFileModel::updateModel() +{ + if (m_reader) { + m_reader->updateFrameCount(); + } +} + bool WritableWaveFileModel::isOK() const { @@ -149,17 +152,31 @@ bool WritableWaveFileModel::isReady(int *completion) const { - if (completion) *completion = m_completion; - return (m_completion == 100); + int c = getCompletion(); + if (completion) *completion = c; + if (!isOK()) return false; + return (c == 100); } void -WritableWaveFileModel::setCompletion(int completion) +WritableWaveFileModel::setWriteProportion(int proportion) { - m_completion = completion; - if (completion == 100) { - if (m_reader) m_reader->updateDone(); - } + m_proportion = proportion; +} + +int +WritableWaveFileModel::getWriteProportion() const +{ + return m_proportion; +} + +void +WritableWaveFileModel::writeComplete() +{ + m_writer->close(); + if (m_reader) m_reader->updateDone(); + m_proportion = 100; + emit modelChanged(); } sv_frame_t @@ -169,21 +186,19 @@ return m_frameCount; } -sv_frame_t -WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const +floatvec_t +WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count) const { - if (!m_model || m_model->getChannelCount() == 0) return 0; - return m_model->getData(channel, start, count, buffer); + if (!m_model || m_model->getChannelCount() == 0) return {}; + return m_model->getData(channel, start, count); } -sv_frame_t +vector<floatvec_t> WritableWaveFileModel::getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffers) const + sv_frame_t start, sv_frame_t count) const { - if (!m_model || m_model->getChannelCount() == 0) return 0; - return m_model->getMultiChannelData(fromchannel, tochannel, start, count, buffers); + if (!m_model || m_model->getChannelCount() == 0) return {}; + return m_model->getMultiChannelData(fromchannel, tochannel, start, count); } int @@ -215,15 +230,16 @@ QString indent, QString extraAttributes) const { - // We don't actually write the data to XML. We just write a brief - // description of the model. Any code that uses this class is - // going to need to be aware that it will have to make separate - // arrangements for the audio file itself. + // The assumption here is that the underlying wave file has + // already been saved somewhere (its location is available through + // getLocation()) and that the code that uses this class is + // dealing with the problem of making sure it remains available. + // We just write this out as if it were a normal wave file. Model::toXml (out, indent, - QString("type=\"writablewavefile\" file=\"%1\" channels=\"%2\" %3") + QString("type=\"wavefile\" file=\"%1\" subtype=\"writable\" %2") .arg(encodeEntities(m_writer->getPath())) - .arg(m_model->getChannelCount()).arg(extraAttributes)); + .arg(extraAttributes)); }
--- a/data/model/WritableWaveFileModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/WritableWaveFileModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -13,15 +13,17 @@ COPYING included with this distribution for more information. */ -#ifndef _WRITABLE_WAVE_FILE_MODEL_H_ -#define _WRITABLE_WAVE_FILE_MODEL_H_ +#ifndef WRITABLE_WAVE_FILE_MODEL_H +#define WRITABLE_WAVE_FILE_MODEL_H #include "WaveFileModel.h" +#include "ReadOnlyWaveFileModel.h" +#include "PowerOfSqrtTwoZoomConstraint.h" class WavFileWriter; class WavFileReader; -class WritableWaveFileModel : public RangeSummarisableTimeValueModel +class WritableWaveFileModel : public WaveFileModel { Q_OBJECT @@ -31,17 +33,74 @@ /** * Call addSamples to append a block of samples to the end of the - * file. Caller should also call setCompletion to update the - * progress of this file, if it has a known end point, and should - * call setCompletion(100) when the file has been written. + * file. + * + * This function only appends the samples to the file being + * written; it does not update the model's view of the samples in + * that file. That is, it updates the file on disc but the model + * itself does not change its content. This is because re-reading + * the file to update the model may be more expensive than adding + * the samples in the first place. If you are writing small + * numbers of samples repeatedly, you probably only want the model + * to update periodically rather than after every write. + * + * Call updateModel() periodically to tell the model to update its + * own view of the samples in the file being written. + * + * Call setWriteProportion() periodically if the file being + * written has known duration and you want the model to be able to + * report the write progress as a percentage. + * + * Call writeComplete() when the file has been completely written. */ - virtual bool addSamples(float **samples, sv_frame_t count); + virtual bool addSamples(const float *const *samples, sv_frame_t count); + + /** + * Tell the model to update its own (read) view of the (written) + * file. May cause modelChanged() and modelChangedWithin() to be + * emitted. See the comment to addSamples above for rationale. + */ + void updateModel(); + + /** + * Set the proportion of the file which has been written so far, + * as a percentage. This may be used to indicate progress. + * + * Note that this differs from the "completion" percentage + * reported through isReady()/getCompletion(). That percentage is + * updated when "internal processing has advanced... but the model + * has not changed externally", i.e. it reports progress in + * calculating the initial state of a model. In contrast, an + * update to setWriteProportion corresponds to a change in the + * externally visible state of the model (i.e. it contains more + * data than before). + */ + void setWriteProportion(int proportion); + + /** + * Indicate that writing is complete. You should call this even if + * you have never called setWriteProportion() or updateModel(). + */ + void writeComplete(); + + static const int PROPORTION_UNKNOWN; + + /** + * Get the proportion of the file which has been written so far, + * as a percentage. Return PROPORTION_UNKNOWN if unknown. + */ + int getWriteProportion() const; bool isOK() const; bool isReady(int *) const; - - virtual void setCompletion(int completion); // percentage - virtual int getCompletion() const { return m_completion; } + + /** + * Return the generation completion percentage of this model. This + * is always 100, because the model is always in a complete state + * -- it just contains varying amounts of data depending on how + * much has been written. + */ + virtual int getCompletion() const { return 100; } const ZoomConstraint *getZoomConstraint() const { static PowerOfSqrtTwoZoomConstraint zc; @@ -51,6 +110,20 @@ sv_frame_t getFrameCount() const; int getChannelCount() const { return m_channels; } sv_samplerate_t getSampleRate() const { return m_sampleRate; } + sv_samplerate_t getNativeRate() const { return m_sampleRate; } + + QString getTitle() const { + if (m_model) return m_model->getTitle(); + else return ""; + } + QString getMaker() const { + if (m_model) return m_model->getMaker(); + else return ""; + } + QString getLocation() const { + if (m_model) return m_model->getLocation(); + else return ""; + } float getValueMinimum() const { return -1.0f; } float getValueMaximum() const { return 1.0f; } @@ -60,12 +133,9 @@ void setStartFrame(sv_frame_t startFrame); - virtual sv_frame_t getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const; + virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) const; - virtual sv_frame_t getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffer) const; + virtual std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; virtual int getSummaryBlockSize(int desired) const; @@ -81,14 +151,14 @@ QString extraAttributes = "") const; protected: - WaveFileModel *m_model; + ReadOnlyWaveFileModel *m_model; WavFileWriter *m_writer; WavFileReader *m_reader; sv_samplerate_t m_sampleRate; int m_channels; sv_frame_t m_frameCount; sv_frame_t m_startFrame; - int m_completion; + int m_proportion; }; #endif
--- a/data/model/test/MockWaveModel.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/test/MockWaveModel.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -26,40 +26,39 @@ } } -sv_frame_t -MockWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const +floatvec_t +MockWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count) const { sv_frame_t i = 0; - cerr << "MockWaveModel::getData(" << channel << "," << start << "," << count << "): "; +// cerr << "MockWaveModel::getData(" << channel << "," << start << "," << count << "): "; + floatvec_t data; + while (i < count) { sv_frame_t idx = start + i; if (!in_range_for(m_data[channel], idx)) break; - buffer[i] = m_data[channel][idx]; - cerr << buffer[i] << " "; + data.push_back(m_data[channel][idx]); +// cerr << data[i] << " "; ++i; } - cerr << endl; +// cerr << endl; - return i; + return data; } -sv_frame_t +vector<floatvec_t> MockWaveModel::getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffers) const + sv_frame_t start, sv_frame_t count) const { - sv_frame_t min = count; - + vector<floatvec_t> data(tochannel - fromchannel + 1); + for (int c = fromchannel; c <= tochannel; ++c) { - sv_frame_t n = getData(c, start, count, buffers[c]); - if (n < min) min = n; + data.push_back(getData(c, start, count)); } - return min; + return data; } vector<float>
--- a/data/model/test/MockWaveModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/test/MockWaveModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -41,11 +41,8 @@ virtual float getValueMaximum() const { return 1.f; } virtual int getChannelCount() const { return int(m_data.size()); } - virtual sv_frame_t getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const; - virtual sv_frame_t getMultiChannelData(int fromchannel, int tochannel, - sv_frame_t start, sv_frame_t count, - float **buffers) const; + virtual floatvec_t getData(int channel, sv_frame_t start, sv_frame_t count) const; + virtual std::vector<floatvec_t> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; virtual bool canPlay() const { return true; } virtual QString getDefaultPlayClipId() const { return ""; }
--- a/data/model/test/TestFFTModel.h Mon Nov 21 16:32:58 2016 +0000 +++ b/data/model/test/TestFFTModel.h Fri Jan 13 10:29:44 2017 +0000 @@ -40,24 +40,35 @@ int columnNo, vector<vector<complex<float>>> expectedValues, int expectedWidth) { for (int ch = 0; in_range_for(expectedValues, ch); ++ch) { - for (int polar = 0; polar <= 1; ++polar) { - FFTModel fftm(model, ch, window, windowSize, windowIncrement, - fftSize, bool(polar)); - QCOMPARE(fftm.getWidth(), expectedWidth); - int hs1 = fftSize/2 + 1; - QCOMPARE(fftm.getHeight(), hs1); - vector<float> reals(hs1 + 1, 0.f); - vector<float> imags(hs1 + 1, 0.f); - reals[hs1] = 999.f; // overrun guards - imags[hs1] = 999.f; + FFTModel fftm(model, ch, window, windowSize, windowIncrement, fftSize); + QCOMPARE(fftm.getWidth(), expectedWidth); + int hs1 = fftSize/2 + 1; + QCOMPARE(fftm.getHeight(), hs1); + vector<float> reals(hs1 + 1, 0.f); + vector<float> imags(hs1 + 1, 0.f); + reals[hs1] = 999.f; // overrun guards + imags[hs1] = 999.f; + for (int stepThrough = 0; stepThrough <= 1; ++stepThrough) { + if (stepThrough) { + // Read through the columns in order instead of + // randomly accessing the one we want. This is to + // exercise the case where the FFT model saves + // part of each input frame and moves along by + // only the non-overlapping distance + for (int sc = 0; sc < columnNo; ++sc) { + fftm.getValuesAt(sc, &reals[0], &imags[0]); + } + } fftm.getValuesAt(columnNo, &reals[0], &imags[0]); for (int i = 0; i < hs1; ++i) { float eRe = expectedValues[ch][i].real(); float eIm = expectedValues[ch][i].imag(); - if (reals[i] != eRe || imags[i] != eIm) { - cerr << "NOTE: output is not as expected for column " - << i << " in channel " << ch << " (polar store = " - << polar << ")" << endl; + float thresh = 1e-5f; + if (abs(reals[i] - eRe) > thresh || + abs(imags[i] - eIm) > thresh) { + cerr << "ERROR: output is not as expected for column " + << i << " in channel " << ch << " (stepThrough = " + << stepThrough << ")" << endl; cerr << "expected : "; for (int j = 0; j < hs1; ++j) { cerr << expectedValues[ch][j] << " "; @@ -76,7 +87,7 @@ } } } - + private slots: // NB. FFTModel columns are centred on the sample frame, and in @@ -88,7 +99,7 @@ // (rather than something with a step in it that is harder to // reason about the FFT of) and the results for subsequent columns // are those of our expected signal. - + void dc_simple_rect() { MockWaveModel mwm({ DC }, 16, 4); test(&mwm, RectangularWindow, 8, 8, 8, 0, @@ -98,7 +109,7 @@ test(&mwm, RectangularWindow, 8, 8, 8, 2, { { { 4.f, 0.f }, {}, {}, {}, {} } }, 4); test(&mwm, RectangularWindow, 8, 8, 8, 3, - { { { }, {}, {}, {}, {} } }, 4); + { { {}, {}, {}, {}, {} } }, 4); } void dc_simple_hann() { @@ -112,7 +123,131 @@ test(&mwm, HanningWindow, 8, 8, 8, 2, { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 4); test(&mwm, HanningWindow, 8, 8, 8, 3, - { { { }, {}, {}, {}, {} } }, 4); + { { {}, {}, {}, {}, {} } }, 4); + } + + void dc_simple_hann_halfoverlap() { + MockWaveModel mwm({ DC }, 16, 4); + test(&mwm, HanningWindow, 8, 4, 8, 0, + { { {}, {}, {}, {}, {} } }, 7); + test(&mwm, HanningWindow, 8, 4, 8, 2, + { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7); + test(&mwm, HanningWindow, 8, 4, 8, 3, + { { { 4.f, 0.f }, { 2.f, 0.f }, {}, {}, {} } }, 7); + test(&mwm, HanningWindow, 8, 4, 8, 6, + { { {}, {}, {}, {}, {} } }, 7); + } + + void sine_simple_rect() { + MockWaveModel mwm({ Sine }, 16, 4); + // Sine: output is purely imaginary. Note the sign is flipped + // (normally the first half of the output would have negative + // sign for a sine starting at 0) because the model does an + // FFT shift to centre the phase + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { { {}, { 0.f, 2.f }, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { { {}, {}, {}, {}, {} } }, 4); + } + + void cosine_simple_rect() { + MockWaveModel mwm({ Cosine }, 16, 4); + // Cosine: output is purely real. Note the sign is flipped + // because the model does an FFT shift to centre the phase + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { { {}, { -2.f, 0.f }, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { { {}, {}, {}, {}, {} } }, 4); + } + + void twochan_simple_rect() { + MockWaveModel mwm({ Sine, Cosine }, 16, 4); + // Test that the two channels are read and converted separately + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { + { {}, {}, {}, {}, {} }, + { {}, {}, {}, {}, {} } + }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { + { {}, { 0.f, 2.f }, {}, {}, {} }, + { {}, { -2.f, 0.f }, {}, {}, {} } + }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { + { {}, { 0.f, 2.f }, {}, {}, {} }, + { {}, { -2.f, 0.f }, {}, {}, {} } + }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { + { {}, {}, {}, {}, {} }, + { {}, {}, {}, {}, {} } + }, 4); + } + + void nyquist_simple_rect() { + MockWaveModel mwm({ Nyquist }, 16, 4); + // Again, the sign is flipped. This has the same amount of + // energy as the DC example + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { { {}, {}, {}, {}, { -4.f, 0.f } } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { { {}, {}, {}, {}, {} } }, 4); + } + + void dirac_simple_rect() { + MockWaveModel mwm({ Dirac }, 16, 4); + // The window scales by 0.5 and some signs are flipped. Only + // column 1 has any data (the single impulse). + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { { {}, {}, {}, {}, {} } }, 4); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { { {}, {}, {}, {}, {} } }, 4); + } + + void dirac_simple_rect_2() { + MockWaveModel mwm({ Dirac }, 16, 8); + // With 8 samples padding, the FFT shift places the first + // Dirac impulse at the start of column 1, thus giving all + // positive values + test(&mwm, RectangularWindow, 8, 8, 8, 0, + { { {}, {}, {}, {}, {} } }, 5); + test(&mwm, RectangularWindow, 8, 8, 8, 1, + { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 5); + test(&mwm, RectangularWindow, 8, 8, 8, 2, + { { {}, {}, {}, {}, {} } }, 5); + test(&mwm, RectangularWindow, 8, 8, 8, 3, + { { {}, {}, {}, {}, {} } }, 5); + test(&mwm, RectangularWindow, 8, 8, 8, 4, + { { {}, {}, {}, {}, {} } }, 5); + } + + void dirac_simple_rect_halfoverlap() { + MockWaveModel mwm({ Dirac }, 16, 4); + test(&mwm, RectangularWindow, 8, 4, 8, 0, + { { {}, {}, {}, {}, {} } }, 7); + test(&mwm, RectangularWindow, 8, 4, 8, 1, + { { { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f }, { 0.5f, 0.f } } }, 7); + test(&mwm, RectangularWindow, 8, 4, 8, 2, + { { { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f }, { -0.5f, 0.f }, { 0.5f, 0.f } } }, 7); + test(&mwm, RectangularWindow, 8, 4, 8, 3, + { { {}, {}, {}, {}, {} } }, 7); } };
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/test/files.pri Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,8 @@ +TEST_HEADERS += \ + Compares.h \ + MockWaveModel.h \ + TestFFTModel.h + +TEST_SOURCES += \ + MockWaveModel.cpp \ + svcore-data-model-test.cpp
--- a/data/model/test/main.cpp Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +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 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 "TestFFTModel.h" - -#include <QtTest> - -#include <iostream> - -using namespace std; - -int main(int argc, char *argv[]) -{ - int good = 0, bad = 0; - - QCoreApplication app(argc, argv); - app.setOrganizationName("Sonic Visualiser"); - app.setApplicationName("test-model"); - - { - TestFFTModel t; - if (QTest::qExec(&t, argc, argv) == 0) ++good; - else ++bad; - } - - if (bad > 0) { - cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; - return 1; - } else { - cerr << "All tests passed" << endl; - return 0; - } -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/test/svcore-data-model-test.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,44 @@ +/* -*- 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 "TestFFTModel.h" + +#include <QtTest> + +#include <iostream> + +using namespace std; + +int main(int argc, char *argv[]) +{ + int good = 0, bad = 0; + + QCoreApplication app(argc, argv); + app.setOrganizationName("Sonic Visualiser"); + app.setApplicationName("test-model"); + + { + TestFFTModel t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + if (bad > 0) { + cerr << "\n********* " << bad << " test suite(s) failed!\n" << endl; + return 1; + } else { + cerr << "All tests passed" << endl; + return 0; + } +} +
--- a/data/model/test/test.pro Mon Nov 21 16:32:58 2016 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ - -TEMPLATE = app - -LIBS += -L../../.. -L../../../../dataquay -L../../../release -L../../../../dataquay/release -lsvcore -ldataquay - -win32-g++ { - INCLUDEPATH += ../../../../sv-dependency-builds/win32-mingw/include - LIBS += -L../../../../sv-dependency-builds/win32-mingw/lib -} -win32-msvc* { - INCLUDEPATH += ../../../../sv-dependency-builds/win32-msvc/include - LIBS += -L../../../../sv-dependency-builds/win32-msvc/lib -} -mac* { - INCLUDEPATH += ../../../../sv-dependency-builds/osx/include - LIBS += -L../../../../sv-dependency-builds/osx/lib -} - -exists(../../../config.pri) { - include(../../../config.pri) -} - -!exists(../../../config.pri) { - - CONFIG += release - DEFINES += NDEBUG BUILD_RELEASE NO_TIMING - - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_LIBLO HAVE_MAD HAVE_ID3TAG HAVE_PORTAUDIO_2_0 - - LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0 - - win* { - LIBS += -llo -lwinmm -lws2_32 - } - macx* { - DEFINES += HAVE_COREAUDIO - LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate - } -} - -CONFIG += qt thread warn_on stl rtti exceptions console c++11 -QT += network xml testlib -QT -= gui - -TARGET = svcore-data-model-test - -DEPENDPATH += ../../.. -INCLUDEPATH += ../../.. -OBJECTS_DIR = o -MOC_DIR = o - -HEADERS += Compares.h MockWaveModel.h TestFFTModel.h -SOURCES += MockWaveModel.cpp main.cpp - -win* { -//PRE_TARGETDEPS += ../../../svcore.lib -} -!win* { -PRE_TARGETDEPS += ../../../libsvcore.a -} - -!win32 { - !macx* { - QMAKE_POST_LINK=./$${TARGET} - } - macx* { - QMAKE_POST_LINK=./$${TARGET}.app/Contents/MacOS/$${TARGET} - } -} - -win32:QMAKE_POST_LINK=./release/$${TARGET}.exe -
--- a/data/osc/OSCQueue.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/data/osc/OSCQueue.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -23,12 +23,13 @@ #include "base/Profiler.h" #include <iostream> -#include <unistd.h> #define OSC_MESSAGE_QUEUE_SIZE 1023 #ifdef HAVE_LIBLO +#include <unistd.h> + void OSCQueue::oscError(int num, const char *msg, const char *path) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/files.pri Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,246 @@ +SVCORE_HEADERS = \ + base/AudioLevel.h \ + base/AudioPlaySource.h \ + base/AudioRecordTarget.h \ + base/BaseTypes.h \ + base/Clipboard.h \ + base/ColumnOp.h \ + base/Command.h \ + base/Debug.h \ + base/Exceptions.h \ + base/HelperExecPath.h \ + base/HitCount.h \ + base/LogRange.h \ + base/MagnitudeRange.h \ + base/Pitch.h \ + base/Playable.h \ + base/PlayParameterRepository.h \ + base/PlayParameters.h \ + base/Preferences.h \ + base/Profiler.h \ + base/ProgressPrinter.h \ + base/ProgressReporter.h \ + base/PropertyContainer.h \ + base/RangeMapper.h \ + base/RealTime.h \ + base/RecentFiles.h \ + base/ResourceFinder.h \ + base/RingBuffer.h \ + base/Scavenger.h \ + base/Selection.h \ + base/Serialiser.h \ + base/StorageAdviser.h \ + base/StringBits.h \ + base/Strings.h \ + base/TempDirectory.h \ + base/TempWriteFile.h \ + base/TextMatcher.h \ + base/Thread.h \ + base/UnitDatabase.h \ + base/ViewManagerBase.h \ + base/Window.h \ + base/XmlExportable.h \ + base/ZoomConstraint.h \ + data/fileio/AudioFileReader.h \ + data/fileio/AudioFileReaderFactory.h \ + data/fileio/AudioFileSizeEstimator.h \ + data/fileio/BZipFileDevice.h \ + data/fileio/CachedFile.h \ + data/fileio/CodedAudioFileReader.h \ + data/fileio/CSVFileReader.h \ + data/fileio/CSVFileWriter.h \ + data/fileio/CSVFormat.h \ + data/fileio/DataFileReader.h \ + data/fileio/DataFileReaderFactory.h \ + data/fileio/FileFinder.h \ + data/fileio/FileReadThread.h \ + data/fileio/FileSource.h \ + 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 \ + data/midi/MIDIEvent.h \ + data/midi/MIDIInput.h \ + data/midi/rtmidi/RtError.h \ + data/midi/rtmidi/RtMidi.h \ + data/model/AggregateWaveModel.h \ + data/model/AlignmentModel.h \ + data/model/Dense3DModelPeakCache.h \ + data/model/DenseThreeDimensionalModel.h \ + data/model/DenseTimeValueModel.h \ + data/model/EditableDenseThreeDimensionalModel.h \ + data/model/FFTModel.h \ + data/model/ImageModel.h \ + data/model/IntervalModel.h \ + data/model/Labeller.h \ + data/model/Model.h \ + data/model/ModelDataTableModel.h \ + data/model/NoteModel.h \ + data/model/FlexiNoteModel.h \ + data/model/PathModel.h \ + data/model/PowerOfSqrtTwoZoomConstraint.h \ + data/model/PowerOfTwoZoomConstraint.h \ + data/model/RangeSummarisableTimeValueModel.h \ + data/model/RegionModel.h \ + data/model/SparseModel.h \ + data/model/SparseOneDimensionalModel.h \ + data/model/SparseTimeValueModel.h \ + data/model/SparseValueModel.h \ + data/model/TabularModel.h \ + data/model/TextModel.h \ + data/model/WaveFileModel.h \ + data/model/ReadOnlyWaveFileModel.h \ + data/model/WritableWaveFileModel.h \ + data/osc/OSCMessage.h \ + data/osc/OSCQueue.h \ + plugin/PluginScan.h \ + plugin/DSSIPluginFactory.h \ + plugin/DSSIPluginInstance.h \ + plugin/FeatureExtractionPluginFactory.h \ + plugin/LADSPAPluginFactory.h \ + plugin/LADSPAPluginInstance.h \ + plugin/NativeVampPluginFactory.h \ + plugin/PiperVampPluginFactory.h \ + plugin/PluginIdentifier.h \ + plugin/PluginXml.h \ + plugin/RealTimePluginFactory.h \ + plugin/RealTimePluginInstance.h \ + plugin/api/dssi.h \ + plugin/api/ladspa.h \ + plugin/plugins/SamplePlayer.h \ + plugin/api/alsa/asoundef.h \ + plugin/api/alsa/asoundlib.h \ + plugin/api/alsa/seq.h \ + plugin/api/alsa/seq_event.h \ + plugin/api/alsa/seq_midi_event.h \ + plugin/api/alsa/sound/asequencer.h \ + rdf/PluginRDFIndexer.h \ + rdf/PluginRDFDescription.h \ + rdf/RDFExporter.h \ + rdf/RDFFeatureWriter.h \ + rdf/RDFImporter.h \ + rdf/RDFTransformFactory.h \ + system/Init.h \ + system/System.h \ + transform/CSVFeatureWriter.h \ + transform/FeatureExtractionModelTransformer.h \ + transform/FeatureWriter.h \ + transform/FileFeatureWriter.h \ + transform/RealTimeEffectModelTransformer.h \ + transform/Transform.h \ + transform/TransformDescription.h \ + transform/TransformFactory.h \ + transform/ModelTransformer.h \ + transform/ModelTransformerFactory.h + +SVCORE_SOURCES = \ + base/AudioLevel.cpp \ + base/Clipboard.cpp \ + base/ColumnOp.cpp \ + base/Command.cpp \ + base/Debug.cpp \ + base/Exceptions.cpp \ + base/HelperExecPath.cpp \ + base/LogRange.cpp \ + base/Pitch.cpp \ + base/PlayParameterRepository.cpp \ + base/PlayParameters.cpp \ + base/Preferences.cpp \ + base/Profiler.cpp \ + base/ProgressPrinter.cpp \ + base/ProgressReporter.cpp \ + base/PropertyContainer.cpp \ + base/RangeMapper.cpp \ + base/RealTimeSV.cpp \ + base/RecentFiles.cpp \ + base/ResourceFinder.cpp \ + base/Selection.cpp \ + base/Serialiser.cpp \ + base/StorageAdviser.cpp \ + base/StringBits.cpp \ + base/Strings.cpp \ + base/TempDirectory.cpp \ + base/TempWriteFile.cpp \ + base/TextMatcher.cpp \ + base/Thread.cpp \ + base/UnitDatabase.cpp \ + base/ViewManagerBase.cpp \ + base/XmlExportable.cpp \ + data/fileio/AudioFileReader.cpp \ + data/fileio/AudioFileReaderFactory.cpp \ + data/fileio/AudioFileSizeEstimator.cpp \ + data/fileio/BZipFileDevice.cpp \ + data/fileio/CachedFile.cpp \ + data/fileio/CodedAudioFileReader.cpp \ + data/fileio/CSVFileReader.cpp \ + data/fileio/CSVFileWriter.cpp \ + data/fileio/CSVFormat.cpp \ + data/fileio/DataFileReaderFactory.cpp \ + data/fileio/FileReadThread.cpp \ + data/fileio/FileSource.cpp \ + 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 \ + data/midi/MIDIInput.cpp \ + data/midi/rtmidi/RtMidi.cpp \ + data/model/AggregateWaveModel.cpp \ + data/model/AlignmentModel.cpp \ + data/model/Dense3DModelPeakCache.cpp \ + data/model/DenseTimeValueModel.cpp \ + data/model/EditableDenseThreeDimensionalModel.cpp \ + data/model/FFTModel.cpp \ + data/model/Model.cpp \ + data/model/ModelDataTableModel.cpp \ + data/model/PowerOfSqrtTwoZoomConstraint.cpp \ + data/model/PowerOfTwoZoomConstraint.cpp \ + data/model/RangeSummarisableTimeValueModel.cpp \ + data/model/WaveFileModel.cpp \ + data/model/ReadOnlyWaveFileModel.cpp \ + data/model/WritableWaveFileModel.cpp \ + data/osc/OSCMessage.cpp \ + data/osc/OSCQueue.cpp \ + plugin/PluginScan.cpp \ + plugin/DSSIPluginFactory.cpp \ + plugin/DSSIPluginInstance.cpp \ + plugin/FeatureExtractionPluginFactory.cpp \ + plugin/LADSPAPluginFactory.cpp \ + plugin/LADSPAPluginInstance.cpp \ + plugin/NativeVampPluginFactory.cpp \ + plugin/PiperVampPluginFactory.cpp \ + plugin/PluginIdentifier.cpp \ + plugin/PluginXml.cpp \ + plugin/RealTimePluginFactory.cpp \ + plugin/RealTimePluginInstance.cpp \ + plugin/plugins/SamplePlayer.cpp \ + rdf/PluginRDFIndexer.cpp \ + rdf/PluginRDFDescription.cpp \ + rdf/RDFExporter.cpp \ + rdf/RDFFeatureWriter.cpp \ + rdf/RDFImporter.cpp \ + rdf/RDFTransformFactory.cpp \ + system/Init.cpp \ + system/System.cpp \ + transform/CSVFeatureWriter.cpp \ + transform/FeatureExtractionModelTransformer.cpp \ + transform/FileFeatureWriter.cpp \ + transform/RealTimeEffectModelTransformer.cpp \ + transform/Transform.cpp \ + transform/TransformFactory.cpp \ + transform/ModelTransformer.cpp \ + transform/ModelTransformerFactory.cpp + +!linux* { + SVCORE_SOURCES += plugin/api/dssi_alsa_compat.c +} +
--- a/plugin/DSSIPluginFactory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/DSSIPluginFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -225,7 +225,7 @@ } #ifdef _WIN32 - char *pfiles = getenv("ProgramFiles"); + const char *pfiles = getenv("ProgramFiles"); if (!pfiles) pfiles = "C:\\Program Files"; { std::string::size_type f;
--- a/plugin/DSSIPluginFactory.h Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/DSSIPluginFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -48,6 +48,10 @@ DSSIPluginFactory(); friend class RealTimePluginFactory; + virtual PluginScan::PluginType getPluginType() const { + return PluginScan::DSSIPlugin; + } + virtual std::vector<QString> getPluginPath(); virtual std::vector<QString> getLRDFPath(QString &baseUri);
--- a/plugin/FeatureExtractionPluginFactory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/FeatureExtractionPluginFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -4,7 +4,7 @@ Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam and QMUL. + This file copyright 2006-2016 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 @@ -13,502 +13,38 @@ COPYING included with this distribution for more information. */ -#include "FeatureExtractionPluginFactory.h" -#include "PluginIdentifier.h" +#include "PiperVampPluginFactory.h" +#include "NativeVampPluginFactory.h" -#include <vamp-hostsdk/PluginHostAdapter.h> -#include <vamp-hostsdk/PluginWrapper.h> +#include <QMutex> +#include <QMutexLocker> -#include "system/System.h" - -#include <QDir> -#include <QFile> -#include <QFileInfo> -#include <QTextStream> - -#include <iostream> - -#include "base/Profiler.h" - -using namespace std; - -//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1 - -class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper { -public: - PluginDeletionNotifyAdapter(Vamp::Plugin *plugin, - FeatureExtractionPluginFactory *factory) : - PluginWrapper(plugin), m_factory(factory) { } - virtual ~PluginDeletionNotifyAdapter(); -protected: - FeatureExtractionPluginFactory *m_factory; -}; - -PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() -{ - // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn - Vamp::Plugin *p = m_plugin; - delete m_plugin; - m_plugin = 0; - // acceptable use after free here, as pluginDeleted uses p only as - // pointer key and does not deref it - if (m_factory) m_factory->pluginDeleted(p); -} - -static FeatureExtractionPluginFactory *_nativeInstance = 0; +#include "base/Preferences.h" +#include "base/Debug.h" FeatureExtractionPluginFactory * -FeatureExtractionPluginFactory::instance(QString pluginType) +FeatureExtractionPluginFactory::instance() { - if (pluginType == "vamp") { - if (!_nativeInstance) { -// SVDEBUG << "FeatureExtractionPluginFactory::instance(" << pluginType// << "): creating new FeatureExtractionPluginFactory" << endl; - _nativeInstance = new FeatureExtractionPluginFactory(); - } - return _nativeInstance; + static QMutex mutex; + static FeatureExtractionPluginFactory *instance = 0; + + QMutexLocker locker(&mutex); + + if (!instance) { + +#ifdef HAVE_PIPER + if (Preferences::getInstance()->getRunPluginsInProcess()) { + SVDEBUG << "FeatureExtractionPluginFactory: in-process preference set, using native factory" << endl; + instance = new NativeVampPluginFactory(); + } else { + SVDEBUG << "FeatureExtractionPluginFactory: in-process preference not set, using Piper factory" << endl; + instance = new PiperVampPluginFactory(); + } +#else + SVDEBUG << "FeatureExtractionPluginFactory: no Piper support compiled in, using native factory" << endl; + instance = new NativeVampPluginFactory(); +#endif } - else return 0; + return instance; } - -FeatureExtractionPluginFactory * -FeatureExtractionPluginFactory::instanceFor(QString identifier) -{ - QString type, soName, label; - PluginIdentifier::parseIdentifier(identifier, type, soName, label); - return instance(type); -} - -vector<QString> -FeatureExtractionPluginFactory::getPluginPath() -{ - if (!m_pluginPath.empty()) return m_pluginPath; - - vector<string> p = Vamp::PluginHostAdapter::getPluginPath(); - for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str()); - return m_pluginPath; -} - -vector<QString> -FeatureExtractionPluginFactory::getAllPluginIdentifiers() -{ - FeatureExtractionPluginFactory *factory; - vector<QString> rv; - - factory = instance("vamp"); - if (factory) { - vector<QString> tmp = factory->getPluginIdentifiers(); - for (size_t i = 0; i < tmp.size(); ++i) { -// cerr << "identifier: " << tmp[i] << endl; - rv.push_back(tmp[i]); - } - } - - // Plugins can change the locale, revert it to default. - RestoreStartupLocale(); - - return rv; -} - -vector<QString> -FeatureExtractionPluginFactory::getPluginCandidateFiles() -{ - vector<QString> path = getPluginPath(); - vector<QString> candidates; - - for (QString dirname : path) { - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: scanning directory " << dirname << endl; -#endif - - QDir pluginDir(dirname, PLUGIN_GLOB, - QDir::Name | QDir::IgnoreCase, - QDir::Files | QDir::Readable); - - for (unsigned int j = 0; j < pluginDir.count(); ++j) { - QString soname = pluginDir.filePath(pluginDir[j]); - candidates.push_back(soname); - } - } - - return candidates; -} - -vector<QString> -FeatureExtractionPluginFactory::winnowPluginCandidates(vector<QString> candidates, - QString &warningMessage) -{ - vector<QString> good, bad; - vector<PluginLoadStatus> badStatuses; - - for (QString c: candidates) { - - PluginLoadStatus status = - TestPluginLoadability(c, "vampGetPluginDescriptor"); - - if (status == PluginLoadOK) { - good.push_back(c); - } else if (status == UnknownPluginLoadStatus) { - cerr << "WARNING: Unknown load status for plugin candidate \"" - << c << "\", continuing" << endl; - good.push_back(c); - } else { - bad.push_back(c); - badStatuses.push_back(status); - } - } - - if (!bad.empty()) { - warningMessage = - QObject::tr("<b>Failed to load plugins</b>" - "<p>Failed to load one or more plugin libraries:</p>\n"); - warningMessage += "<ul>"; - for (int i = 0; in_range_for(bad, i); ++i) { - QString m; - if (badStatuses[i] == PluginLoadFailedToLoadLibrary) { - m = QObject::tr("Failed to load library"); - } else if (badStatuses[i] == PluginLoadFailedToFindDescriptor) { - m = QObject::tr("Failed to query plugins from library after loading"); - } else if (badStatuses[i] == PluginLoadFailedElsewhere) { - m = QObject::tr("Unknown failure"); - } else { - m = QObject::tr("Success: internal error?"); - } - warningMessage += QString("<li>%1 (%2)</li>\n") - .arg(bad[i]) - .arg(m); - } - warningMessage += "</ul>"; - } - return good; -} - -vector<QString> -FeatureExtractionPluginFactory::getPluginIdentifiers() -{ - Profiler profiler("FeatureExtractionPluginFactory::getPluginIdentifiers"); - - vector<QString> rv; - vector<QString> candidates = winnowPluginCandidates(getPluginCandidateFiles(), - m_pluginScanError); - - for (QString soname : candidates) { - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: trying potential library " << soname << endl; -#endif - - void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); - - if (!libraryHandle) { - cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl; - continue; - } - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: It's a library all right, checking for descriptor" << endl; -#endif - - VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) - DLSYM(libraryHandle, "vampGetPluginDescriptor"); - - if (!fn) { - cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl; - if (DLCLOSE(libraryHandle) != 0) { - cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl; - } - continue; - } - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl; -#endif - - const VampPluginDescriptor *descriptor = 0; - int index = 0; - - map<string, int> known; - bool ok = true; - - while ((descriptor = fn(VAMP_API_VERSION, index))) { - - if (known.find(descriptor->identifier) != known.end()) { - cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library " - << soname - << " returns the same plugin identifier \"" - << descriptor->identifier << "\" at indices " - << known[descriptor->identifier] << " and " - << index << endl; - cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl; - ok = false; - break; - } else { - known[descriptor->identifier] = index; - } - - ++index; - } - - if (ok) { - - index = 0; - - while ((descriptor = fn(VAMP_API_VERSION, index))) { - - QString id = PluginIdentifier::createIdentifier - ("vamp", soname, descriptor->identifier); - rv.push_back(id); -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl; -#endif - ++index; - } - } - - if (DLCLOSE(libraryHandle) != 0) { - cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl; - } - } - - generateTaxonomy(); - - return rv; -} - -QString -FeatureExtractionPluginFactory::findPluginFile(QString soname, QString inDir) -{ - QString file = ""; - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::findPluginFile(\"" - << soname << "\", \"" << inDir << "\")" - << endl; -#endif - - if (inDir != "") { - - QDir dir(inDir, PLUGIN_GLOB, - QDir::Name | QDir::IgnoreCase, - QDir::Files | QDir::Readable); - if (!dir.exists()) return ""; - - file = dir.filePath(QFileInfo(soname).fileName()); - - if (QFileInfo(file).exists() && QFileInfo(file).isFile()) { - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::findPluginFile: " - << "found trivially at " << file << endl; -#endif - - return file; - } - - for (unsigned int j = 0; j < dir.count(); ++j) { - file = dir.filePath(dir[j]); - if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) { - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::findPluginFile: " - << "found \"" << soname << "\" at " << file << endl; -#endif - - return file; - } - } - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::findPluginFile (with dir): " - << "not found" << endl; -#endif - - return ""; - - } else { - - QFileInfo fi(soname); - - if (fi.isAbsolute() && fi.exists() && fi.isFile()) { -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::findPluginFile: " - << "found trivially at " << soname << endl; -#endif - return soname; - } - - if (fi.isAbsolute() && fi.absolutePath() != "") { - file = findPluginFile(soname, fi.absolutePath()); - if (file != "") return file; - } - - vector<QString> path = getPluginPath(); - for (vector<QString>::iterator i = path.begin(); - i != path.end(); ++i) { - if (*i != "") { - file = findPluginFile(soname, *i); - if (file != "") return file; - } - } - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::findPluginFile: " - << "not found" << endl; -#endif - - return ""; - } -} - -Vamp::Plugin * -FeatureExtractionPluginFactory::instantiatePlugin(QString identifier, - sv_samplerate_t inputSampleRate) -{ - Profiler profiler("FeatureExtractionPluginFactory::instantiatePlugin"); - - Vamp::Plugin *rv = 0; - Vamp::PluginHostAdapter *plugin = 0; - - const VampPluginDescriptor *descriptor = 0; - int index = 0; - - QString type, soname, label; - PluginIdentifier::parseIdentifier(identifier, type, soname, label); - if (type != "vamp") { -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl; -#endif - return 0; - } - - QString found = findPluginFile(soname); - - if (found == "") { - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl; - return 0; - } else if (found != soname) { - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl; - cerr << soname << " -> " << found << endl; -#endif - - } - - soname = found; - - void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); - - if (!libraryHandle) { - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl; - return 0; - } - - VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) - DLSYM(libraryHandle, "vampGetPluginDescriptor"); - - if (!fn) { - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl; - goto done; - } - - while ((descriptor = fn(VAMP_API_VERSION, index))) { - if (label == descriptor->identifier) break; - ++index; - } - - if (!descriptor) { - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl; - goto done; - } - - plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate)); - - if (plugin) { - m_handleMap[plugin] = libraryHandle; - rv = new PluginDeletionNotifyAdapter(plugin, this); - } - -// SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << endl; - - //!!! need to dlclose() when plugins from a given library are unloaded - -done: - if (!rv) { - if (DLCLOSE(libraryHandle) != 0) { - cerr << "WARNING: FeatureExtractionPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl; - } - } - -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl; -#endif - - return rv; -} - -void -FeatureExtractionPluginFactory::pluginDeleted(Vamp::Plugin *plugin) -{ - void *handle = m_handleMap[plugin]; - if (handle) { -#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE - cerr << "unloading library " << handle << " for plugin " << plugin << endl; -#endif - DLCLOSE(handle); - } - m_handleMap.erase(plugin); -} - -QString -FeatureExtractionPluginFactory::getPluginCategory(QString identifier) -{ - return m_taxonomy[identifier]; -} - -void -FeatureExtractionPluginFactory::generateTaxonomy() -{ - vector<QString> pluginPath = getPluginPath(); - vector<QString> path; - - for (size_t i = 0; i < pluginPath.size(); ++i) { - if (pluginPath[i].contains("/lib/")) { - QString p(pluginPath[i]); - path.push_back(p); - p.replace("/lib/", "/share/"); - path.push_back(p); - } - path.push_back(pluginPath[i]); - } - - for (size_t i = 0; i < path.size(); ++i) { - - QDir dir(path[i], "*.cat"); - -// SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl; - for (unsigned int j = 0; j < dir.count(); ++j) { - - QFile file(path[i] + "/" + dir[j]); - -// SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl; - - if (file.open(QIODevice::ReadOnly)) { -// cerr << "...opened" << endl; - QTextStream stream(&file); - QString line; - - while (!stream.atEnd()) { - line = stream.readLine(); -// cerr << "line is: \"" << line << "\"" << endl; - QString id = PluginIdentifier::canonicalise - (line.section("::", 0, 0)); - QString cat = line.section("::", 1, 1); - m_taxonomy[id] = cat; -// cerr << "FeatureExtractionPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl; - } - } - } - } -}
--- a/plugin/FeatureExtractionPluginFactory.h Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/FeatureExtractionPluginFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -4,7 +4,7 @@ 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 file copyright 2006-2016 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 @@ -13,66 +13,55 @@ COPYING included with this distribution for more information. */ -#ifndef _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_ -#define _FEATURE_EXTRACTION_PLUGIN_FACTORY_H_ - -#include <QString> -#include <vector> -#include <map> +#ifndef SV_FEATURE_EXTRACTION_PLUGIN_FACTORY_H +#define SV_FEATURE_EXTRACTION_PLUGIN_FACTORY_H #include <vamp-hostsdk/Plugin.h> -#include "base/Debug.h" +#include "vamp-support/PluginStaticData.h" + #include "base/BaseTypes.h" +#include <QString> + class FeatureExtractionPluginFactory { public: + static FeatureExtractionPluginFactory *instance(); + virtual ~FeatureExtractionPluginFactory() { } - static FeatureExtractionPluginFactory *instance(QString pluginType); - static FeatureExtractionPluginFactory *instanceFor(QString identifier); - static std::vector<QString> getAllPluginIdentifiers(); - - virtual std::vector<QString> getPluginPath(); - - virtual std::vector<QString> getPluginIdentifiers(); + /** + * Return all installed plugin identifiers. + */ + virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage) { + return instance()->getPluginIdentifiers(errorMessage); + } /** - * Return any error message arising from the initial plugin - * scan. The return value will either be an empty string (nothing - * to report) or an HTML string suitable for dropping into a - * dialog and showing the user. + * Return static data for the given plugin. */ - virtual QString getPluginPopulationWarning() { return m_pluginScanError; } - - virtual QString findPluginFile(QString soname, QString inDir = ""); + virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier) { + return instance()->getPluginStaticData(identifier); + } - // We don't set blockSize or channels on this -- they're - // negotiated and handled via initialize() on the plugin + /** + * Instantiate (load) and return pointer to the plugin with the + * given identifier, at the given sample rate. We don't set + * blockSize or channels on this -- they're negotiated and handled + * via initialize() on the plugin itself after loading. + */ virtual Vamp::Plugin *instantiatePlugin(QString identifier, - sv_samplerate_t inputSampleRate); + sv_samplerate_t inputSampleRate) { + return instance()->instantiatePlugin(identifier, inputSampleRate); + } /** * Get category metadata about a plugin (without instantiating it). */ - virtual QString getPluginCategory(QString identifier); - -protected: - std::vector<QString> m_pluginPath; - std::map<QString, QString> m_taxonomy; - - friend class PluginDeletionNotifyAdapter; - void pluginDeleted(Vamp::Plugin *); - std::map<Vamp::Plugin *, void *> m_handleMap; - - std::vector<QString> getPluginCandidateFiles(); - std::vector<QString> winnowPluginCandidates(std::vector<QString> candidates, - QString &warningMessage); - - void generateTaxonomy(); - - QString m_pluginScanError; + virtual QString getPluginCategory(QString identifier) { + return instance()->getPluginCategory(identifier); + } }; #endif
--- a/plugin/LADSPAPluginFactory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/LADSPAPluginFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -579,7 +579,7 @@ } #ifdef _WIN32 - char *pfiles = getenv("ProgramFiles"); + const char *pfiles = getenv("ProgramFiles"); if (!pfiles) pfiles = "C:\\Program Files"; { std::string::size_type f; @@ -668,14 +668,11 @@ generateFallbackCategories(); - for (std::vector<QString>::iterator i = pathList.begin(); - i != pathList.end(); ++i) { + auto candidates = + PluginScan::getInstance()->getCandidateLibrariesFor(getPluginType()); - QDir pluginDir(*i, PLUGIN_GLOB); - - for (unsigned int j = 0; j < pluginDir.count(); ++j) { - discoverPluginsFrom(QString("%1/%2").arg(*i).arg(pluginDir[j])); - } + for (auto c: candidates) { + discoverPluginsFrom(c.libraryPath); } }
--- a/plugin/LADSPAPluginFactory.h Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/LADSPAPluginFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -24,6 +24,8 @@ #include "RealTimePluginFactory.h" #include "api/ladspa.h" +#include "PluginScan.h" + #include <vector> #include <map> #include <set> @@ -63,6 +65,10 @@ LADSPAPluginFactory(); friend class RealTimePluginFactory; + virtual PluginScan::PluginType getPluginType() const { + return PluginScan::LADSPAPlugin; + } + virtual std::vector<QString> getPluginPath(); virtual std::vector<QString> getLRDFPath(QString &baseUri);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/NativeVampPluginFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,471 @@ +/* -*- 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-2016 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. +*/ + +#include "NativeVampPluginFactory.h" +#include "PluginIdentifier.h" + +#include <vamp-hostsdk/PluginHostAdapter.h> +#include <vamp-hostsdk/PluginWrapper.h> + +#include "system/System.h" + +#include "PluginScan.h" + +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QTextStream> + +#include <iostream> + +#include "base/Profiler.h" + +#include <QMutex> +#include <QMutexLocker> + +using namespace std; + +//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1 + +class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper { +public: + PluginDeletionNotifyAdapter(Vamp::Plugin *plugin, + NativeVampPluginFactory *factory) : + PluginWrapper(plugin), m_factory(factory) { } + virtual ~PluginDeletionNotifyAdapter(); +protected: + NativeVampPluginFactory *m_factory; +}; + +PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() +{ + // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn + Vamp::Plugin *p = m_plugin; + delete m_plugin; + m_plugin = 0; + // acceptable use after free here, as pluginDeleted uses p only as + // pointer key and does not deref it + if (m_factory) m_factory->pluginDeleted(p); +} + +vector<QString> +NativeVampPluginFactory::getPluginPath() +{ + if (!m_pluginPath.empty()) return m_pluginPath; + + vector<string> p = Vamp::PluginHostAdapter::getPluginPath(); + for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str()); + return m_pluginPath; +} + +static +QList<PluginScan::Candidate> +getCandidateLibraries() +{ +#ifdef HAVE_PLUGIN_CHECKER_HELPER + return PluginScan::getInstance()->getCandidateLibrariesFor + (PluginScan::VampPlugin); +#else + auto path = Vamp::PluginHostAdapter::getPluginPath(); + QList<PluginScan::Candidate> candidates; + for (string dirname: path) { + SVDEBUG << "NativeVampPluginFactory: scanning directory myself: " + << dirname << endl; +#if defined(_WIN32) +#define PLUGIN_GLOB "*.dll" +#elif defined(__APPLE__) +#define PLUGIN_GLOB "*.dylib *.so" +#else +#define PLUGIN_GLOB "*.so" +#endif + QDir dir(dirname.c_str(), PLUGIN_GLOB, + QDir::Name | QDir::IgnoreCase, + QDir::Files | QDir::Readable); + + for (unsigned int i = 0; i < dir.count(); ++i) { + QString soname = dir.filePath(dir[i]); + candidates.push_back({ soname, "" }); + } + } + + return candidates; +#endif +} + +vector<QString> +NativeVampPluginFactory::getPluginIdentifiers(QString &) +{ + Profiler profiler("NativeVampPluginFactory::getPluginIdentifiers"); + + QMutexLocker locker(&m_mutex); + + if (!m_identifiers.empty()) { + return m_identifiers; + } + + auto candidates = getCandidateLibraries(); + + SVDEBUG << "INFO: Have " << candidates.size() << " candidate Vamp plugin libraries" << endl; + + for (auto candidate : candidates) { + + QString soname = candidate.libraryPath; + + SVDEBUG << "INFO: Considering candidate Vamp plugin library " << soname << endl; + + void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); + + if (!libraryHandle) { + SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl; + continue; + } + + VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) + DLSYM(libraryHandle, "vampGetPluginDescriptor"); + + if (!fn) { + SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl; + if (DLCLOSE(libraryHandle) != 0) { + SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl; + } + continue; + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl; +#endif + + const VampPluginDescriptor *descriptor = 0; + int index = 0; + + map<string, int> known; + bool ok = true; + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + + if (known.find(descriptor->identifier) != known.end()) { + SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library " + << soname + << " returns the same plugin identifier \"" + << descriptor->identifier << "\" at indices " + << known[descriptor->identifier] << " and " + << index << endl; + SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl; + ok = false; + break; + } else { + known[descriptor->identifier] = index; + } + + ++index; + } + + if (ok) { + + index = 0; + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + + QString id = PluginIdentifier::createIdentifier + ("vamp", soname, descriptor->identifier); + m_identifiers.push_back(id); +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl; +#endif + ++index; + } + } + + if (DLCLOSE(libraryHandle) != 0) { + SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl; + } + } + + generateTaxonomy(); + + // Plugins can change the locale, revert it to default. + RestoreStartupLocale(); + + return m_identifiers; +} + +QString +NativeVampPluginFactory::findPluginFile(QString soname, QString inDir) +{ + QString file = ""; + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::findPluginFile(\"" + << soname << "\", \"" << inDir << "\")" + << endl; +#endif + + if (inDir != "") { + + QDir dir(inDir, PLUGIN_GLOB, + QDir::Name | QDir::IgnoreCase, + QDir::Files | QDir::Readable); + if (!dir.exists()) return ""; + + file = dir.filePath(QFileInfo(soname).fileName()); + + if (QFileInfo(file).exists() && QFileInfo(file).isFile()) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::findPluginFile: " + << "found trivially at " << file << endl; +#endif + + return file; + } + + for (unsigned int j = 0; j < dir.count(); ++j) { + file = dir.filePath(dir[j]); + if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::findPluginFile: " + << "found \"" << soname << "\" at " << file << endl; +#endif + + return file; + } + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::findPluginFile (with dir): " + << "not found" << endl; +#endif + + return ""; + + } else { + + QFileInfo fi(soname); + + if (fi.isAbsolute() && fi.exists() && fi.isFile()) { +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::findPluginFile: " + << "found trivially at " << soname << endl; +#endif + return soname; + } + + if (fi.isAbsolute() && fi.absolutePath() != "") { + file = findPluginFile(soname, fi.absolutePath()); + if (file != "") return file; + } + + vector<QString> path = getPluginPath(); + for (vector<QString>::iterator i = path.begin(); + i != path.end(); ++i) { + if (*i != "") { + file = findPluginFile(soname, *i); + if (file != "") return file; + } + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::findPluginFile: " + << "not found" << endl; +#endif + + return ""; + } +} + +Vamp::Plugin * +NativeVampPluginFactory::instantiatePlugin(QString identifier, + sv_samplerate_t inputSampleRate) +{ + Profiler profiler("NativeVampPluginFactory::instantiatePlugin"); + + Vamp::Plugin *rv = 0; + Vamp::PluginHostAdapter *plugin = 0; + + const VampPluginDescriptor *descriptor = 0; + int index = 0; + + QString type, soname, label; + PluginIdentifier::parseIdentifier(identifier, type, soname, label); + if (type != "vamp") { +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl; +#endif + return 0; + } + + QString found = findPluginFile(soname); + + if (found == "") { + SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl; + return 0; + } else if (found != soname) { + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl; + cerr << soname << " -> " << found << endl; +#endif + + } + + soname = found; + + void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); + + if (!libraryHandle) { + SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl; + return 0; + } + + VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) + DLSYM(libraryHandle, "vampGetPluginDescriptor"); + + if (!fn) { + SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl; + goto done; + } + + while ((descriptor = fn(VAMP_API_VERSION, index))) { + if (label == descriptor->identifier) break; + ++index; + } + + if (!descriptor) { + SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl; + goto done; + } + + plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate)); + + if (plugin) { + m_handleMap[plugin] = libraryHandle; + rv = new PluginDeletionNotifyAdapter(plugin, this); + } + +// SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << endl; + + //!!! need to dlclose() when plugins from a given library are unloaded + +done: + if (!rv) { + if (DLCLOSE(libraryHandle) != 0) { + SVDEBUG << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl; + } + } + +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "NativeVampPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl; +#endif + + return rv; +} + +void +NativeVampPluginFactory::pluginDeleted(Vamp::Plugin *plugin) +{ + void *handle = m_handleMap[plugin]; + if (handle) { +#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE + cerr << "unloading library " << handle << " for plugin " << plugin << endl; +#endif + DLCLOSE(handle); + } + m_handleMap.erase(plugin); +} + +QString +NativeVampPluginFactory::getPluginCategory(QString identifier) +{ + return m_taxonomy[identifier]; +} + +void +NativeVampPluginFactory::generateTaxonomy() +{ + vector<QString> pluginPath = getPluginPath(); + vector<QString> path; + + for (size_t i = 0; i < pluginPath.size(); ++i) { + if (pluginPath[i].contains("/lib/")) { + QString p(pluginPath[i]); + path.push_back(p); + p.replace("/lib/", "/share/"); + path.push_back(p); + } + path.push_back(pluginPath[i]); + } + + for (size_t i = 0; i < path.size(); ++i) { + + QDir dir(path[i], "*.cat"); + +// SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl; + for (unsigned int j = 0; j < dir.count(); ++j) { + + QFile file(path[i] + "/" + dir[j]); + +// SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl; + + if (file.open(QIODevice::ReadOnly)) { +// cerr << "...opened" << endl; + QTextStream stream(&file); + QString line; + + while (!stream.atEnd()) { + line = stream.readLine(); +// cerr << "line is: \"" << line << "\"" << endl; + QString id = PluginIdentifier::canonicalise + (line.section("::", 0, 0)); + QString cat = line.section("::", 1, 1); + m_taxonomy[id] = cat; +// cerr << "NativeVampPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl; + } + } + } + } +} + +piper_vamp::PluginStaticData +NativeVampPluginFactory::getPluginStaticData(QString identifier) +{ + QMutexLocker locker(&m_mutex); + + if (m_pluginData.find(identifier) != m_pluginData.end()) { + return m_pluginData[identifier]; + } + + QString type, soname, label; + PluginIdentifier::parseIdentifier(identifier, type, soname, label); + std::string pluginKey = (soname + ":" + label).toStdString(); + + std::vector<std::string> catlist; + for (auto s: getPluginCategory(identifier).split(" > ")) { + catlist.push_back(s.toStdString()); + } + + Vamp::Plugin *p = instantiatePlugin(identifier, 44100); + if (!p) return {}; + + auto psd = piper_vamp::PluginStaticData::fromPlugin(pluginKey, + catlist, + p); + + delete p; + + m_pluginData[identifier] = psd; + return psd; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/NativeVampPluginFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,68 @@ +/* -*- 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-2016 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_NATIVE_VAMP_PLUGIN_FACTORY_H +#define SV_NATIVE_VAMP_PLUGIN_FACTORY_H + +#include "FeatureExtractionPluginFactory.h" + +#include <vector> +#include <map> + +#include "base/Debug.h" + +#include <QMutex> + +/** + * FeatureExtractionPluginFactory type for Vamp plugins hosted + * in-process. + */ +class NativeVampPluginFactory : public FeatureExtractionPluginFactory +{ +public: + virtual ~NativeVampPluginFactory() { } + + virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage) + override; + + virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier) + override; + + virtual Vamp::Plugin *instantiatePlugin(QString identifier, + sv_samplerate_t inputSampleRate) + override; + + /** + * Get category metadata about a plugin (without instantiating it). + */ + virtual QString getPluginCategory(QString identifier) override; + +protected: + QMutex m_mutex; + std::vector<QString> m_pluginPath; + std::vector<QString> m_identifiers; + std::map<QString, QString> m_taxonomy; // identifier -> category string + std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data (created opportunistically) + + friend class PluginDeletionNotifyAdapter; + void pluginDeleted(Vamp::Plugin *); + std::map<Vamp::Plugin *, void *> m_handleMap; + + QString findPluginFile(QString soname, QString inDir = ""); + std::vector<QString> getPluginPath(); + void generateTaxonomy(); +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PiperVampPluginFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,273 @@ +/* -*- 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-2016 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_PIPER + +#include "PiperVampPluginFactory.h" +#include "PluginIdentifier.h" + +#include "system/System.h" + +#include "PluginScan.h" + +#ifdef _WIN32 +#undef VOID +#undef ERROR +#define CAPNP_LITE 1 +#endif + +#include "vamp-client/AutoPlugin.h" + +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QTextStream> +#include <QCoreApplication> + +#include <iostream> + +#include "base/Profiler.h" +#include "base/HelperExecPath.h" + +#include "vamp-client/ProcessQtTransport.h" +#include "vamp-client/CapnpRRClient.h" + +using namespace std; + +//#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1 + +class PiperVampPluginFactory::Logger : public piper_vamp::client::LogCallback { +protected: + void log(std::string message) const override { + SVDEBUG << "PiperVampPluginFactory: " << message << endl; + } +}; + +PiperVampPluginFactory::PiperVampPluginFactory() : + m_logger(new Logger) +{ + QString serverName = "piper-vamp-simple-server"; + + HelperExecPath hep(HelperExecPath::AllInstalled); + m_servers = hep.getHelperExecutables(serverName); + + for (auto n: m_servers) { + SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: " + << n.executable << endl; + } + + if (m_servers.empty()) { + SVDEBUG << "NOTE: No Piper Vamp servers found in installation;" + << " found none of the following:" << endl; + for (auto d: hep.getHelperCandidatePaths(serverName)) { + SVDEBUG << "NOTE: " << d << endl; + } + } +} + +PiperVampPluginFactory::~PiperVampPluginFactory() +{ + delete m_logger; +} + +vector<QString> +PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage) +{ + Profiler profiler("PiperVampPluginFactory::getPluginIdentifiers"); + + QMutexLocker locker(&m_mutex); + + if (m_servers.empty()) { + errorMessage = QObject::tr("External plugin host executable does not appear to be installed"); + return {}; + } + + if (m_pluginData.empty()) { + populate(errorMessage); + } + + vector<QString> rv; + + for (const auto &d: m_pluginData) { + rv.push_back(QString("vamp:") + QString::fromStdString(d.second.pluginKey)); + } + + return rv; +} + +Vamp::Plugin * +PiperVampPluginFactory::instantiatePlugin(QString identifier, + sv_samplerate_t inputSampleRate) +{ + Profiler profiler("PiperVampPluginFactory::instantiatePlugin"); + + if (m_origins.find(identifier) == m_origins.end()) { + cerr << "ERROR: No known server for identifier " << identifier << endl; + SVDEBUG << "ERROR: No known server for identifier " << identifier << endl; + return 0; + } + + auto psd = getPluginStaticData(identifier); + if (psd.pluginKey == "") { + return 0; + } + + SVDEBUG << "PiperVampPluginFactory: Creating Piper AutoPlugin for server " + << m_origins[identifier] << ", identifier " << identifier << endl; + + auto ap = new piper_vamp::client::AutoPlugin + (m_origins[identifier].toStdString(), + psd.pluginKey, + float(inputSampleRate), + 0, + m_logger); + + if (!ap->isOK()) { + delete ap; + return 0; + } + + return ap; +} + +piper_vamp::PluginStaticData +PiperVampPluginFactory::getPluginStaticData(QString identifier) +{ + if (m_pluginData.find(identifier) != m_pluginData.end()) { + return m_pluginData[identifier]; + } else { + return {}; + } +} + +QString +PiperVampPluginFactory::getPluginCategory(QString identifier) +{ + if (m_taxonomy.find(identifier) != m_taxonomy.end()) { + return m_taxonomy[identifier]; + } else { + return {}; + } +} + +void +PiperVampPluginFactory::populate(QString &errorMessage) +{ + QString someError; + + for (auto s: m_servers) { + + populateFrom(s, someError); + + if (someError != "" && errorMessage == "") { + errorMessage = someError; + } + } +} + +void +PiperVampPluginFactory::populateFrom(const HelperExecPath::HelperExec &server, + QString &errorMessage) +{ + QString tag = server.tag; + string executable = server.executable.toStdString(); + + PluginScan *scan = PluginScan::getInstance(); + auto candidateLibraries = + scan->getCandidateLibrariesFor(PluginScan::VampPlugin); + + SVDEBUG << "PiperVampPluginFactory: Populating from " << executable << endl; + SVDEBUG << "INFO: Have " << candidateLibraries.size() + << " candidate Vamp plugin libraries from scanner" << endl; + + vector<string> from; + for (const auto &c: candidateLibraries) { + if (c.helperTag == tag) { + string soname = QFileInfo(c.libraryPath).baseName().toStdString(); + SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl; + from.push_back(soname); + } + } + + if (from.empty()) { + SVDEBUG << "PiperVampPluginFactory: No candidate libraries for tag \"" + << tag << "\""; + if (scan->scanSucceeded()) { + // we have to assume that they all failed to load (i.e. we + // exclude them all) rather than sending an empty list + // (which would mean no exclusions) + SVDEBUG << ", skipping" << endl; + return; + } else { + SVDEBUG << ", but it seems the scan failed, so bumbling on anyway" << endl; + } + } + + piper_vamp::client::ProcessQtTransport transport(executable, "capnp", m_logger); + if (!transport.isOK()) { + SVDEBUG << "PiperVampPluginFactory: Failed to start Piper process transport" << endl; + errorMessage = QObject::tr("Could not start external plugin host"); + return; + } + + piper_vamp::client::CapnpRRClient client(&transport, m_logger); + + piper_vamp::ListRequest req; + req.from = from; + + piper_vamp::ListResponse resp; + + try { + resp = client.listPluginData(req); + } catch (piper_vamp::client::ServerCrashed) { + SVDEBUG << "PiperVampPluginFactory: Piper server crashed" << endl; + errorMessage = QObject::tr + ("External plugin host exited unexpectedly while listing plugins"); + return; + } catch (const std::exception &e) { + SVDEBUG << "PiperVampPluginFactory: Exception caught: " << e.what() << endl; + errorMessage = QObject::tr("External plugin host invocation failed: %1") + .arg(e.what()); + return; + } + + SVDEBUG << "PiperVampPluginFactory: server \"" << executable << "\" lists " + << resp.available.size() << " plugin(s)" << endl; + + for (const auto &pd: resp.available) { + + QString identifier = + QString("vamp:") + QString::fromStdString(pd.pluginKey); + + if (m_origins.find(identifier) != m_origins.end()) { + // have it already, from a higher-priority server + // (e.g. 64-bit instead of 32-bit) + continue; + } + + m_origins[identifier] = server.executable; + + m_pluginData[identifier] = pd; + + QStringList catlist; + for (const auto &cs: pd.category) { + catlist.push_back(QString::fromStdString(cs)); + } + + m_taxonomy[identifier] = catlist.join(" > "); + } +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PiperVampPluginFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,68 @@ +/* -*- 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-2016 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_PIPER_VAMP_PLUGIN_FACTORY_H +#define SV_PIPER_VAMP_PLUGIN_FACTORY_H + +#ifdef HAVE_PIPER + +#include "FeatureExtractionPluginFactory.h" + +#include <QMutex> +#include <vector> +#include <map> + +#include "base/Debug.h" +#include "base/HelperExecPath.h" + +/** + * FeatureExtractionPluginFactory type for Vamp plugins hosted in a + * separate process using Piper protocol. + */ +class PiperVampPluginFactory : public FeatureExtractionPluginFactory +{ +public: + PiperVampPluginFactory(); + virtual ~PiperVampPluginFactory(); + + virtual std::vector<QString> getPluginIdentifiers(QString &errorMessage) + override; + + virtual piper_vamp::PluginStaticData getPluginStaticData(QString identifier) + override; + + virtual Vamp::Plugin *instantiatePlugin(QString identifier, + sv_samplerate_t inputSampleRate) + override; + + virtual QString getPluginCategory(QString identifier) override; + +protected: + QMutex m_mutex; + QList<HelperExecPath::HelperExec> m_servers; // executable file paths + std::map<QString, QString> m_origins; // plugin identifier -> server path + std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data + std::map<QString, QString> m_taxonomy; // identifier -> category string + + void populate(QString &errorMessage); + void populateFrom(const HelperExecPath::HelperExec &, QString &errorMessage); + + class Logger; + Logger *m_logger; +}; + +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PluginScan.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,222 @@ +/* -*- 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 "PluginScan.h" + +#include "base/Debug.h" +#include "base/Preferences.h" +#include "base/HelperExecPath.h" + +#ifdef HAVE_PLUGIN_CHECKER_HELPER +#include "checker/knownplugins.h" +#else +class KnownPlugins {}; +#endif + +#include <QMutex> +#include <QCoreApplication> + +using std::string; + +class PluginScan::Logger +#ifdef HAVE_PLUGIN_CHECKER_HELPER + : public PluginCandidates::LogCallback +#endif +{ +protected: + void log(std::string message) { + SVDEBUG << "PluginScan: " << message << endl; + } +}; + +PluginScan *PluginScan::getInstance() +{ + static QMutex mutex; + static PluginScan *m_instance = 0; + mutex.lock(); + if (!m_instance) m_instance = new PluginScan(); + mutex.unlock(); + return m_instance; +} + +PluginScan::PluginScan() : m_succeeded(false), m_logger(new Logger) { +} + +PluginScan::~PluginScan() { + QMutexLocker locker(&m_mutex); + clear(); + delete m_logger; +} + +void +PluginScan::scan() +{ +#ifdef HAVE_PLUGIN_CHECKER_HELPER + + QMutexLocker locker(&m_mutex); + + bool inProcess = Preferences::getInstance()->getRunPluginsInProcess(); + + HelperExecPath hep(inProcess ? + HelperExecPath::NativeArchitectureOnly : + HelperExecPath::AllInstalled); + + QString helperName("vamp-plugin-load-checker"); + auto helpers = hep.getHelperExecutables(helperName); + + clear(); + + for (auto p: helpers) { + SVDEBUG << "NOTE: PluginScan: Found helper: " << p.executable << endl; + } + + if (helpers.empty()) { + SVDEBUG << "NOTE: No plugin checker helpers found in installation;" + << " found none of the following:" << endl; + for (auto d: hep.getHelperCandidatePaths(helperName)) { + SVDEBUG << "NOTE: " << d << endl; + } + } + + for (auto p: helpers) { + try { + KnownPlugins *kp = new KnownPlugins + (p.executable.toStdString(), m_logger); + if (m_kp.find(p.tag) != m_kp.end()) { + SVDEBUG << "WARNING: PluginScan::scan: Duplicate tag " << p.tag + << " for helpers" << endl; + continue; + } + m_kp[p.tag] = kp; + m_succeeded = true; + } catch (const std::exception &e) { + SVDEBUG << "ERROR: PluginScan::scan: " << e.what() + << " (with helper path = " << p.executable << ")" << endl; + } + } + +#endif +} + +bool +PluginScan::scanSucceeded() const +{ + QMutexLocker locker(&m_mutex); + return m_succeeded; +} + +void +PluginScan::clear() +{ + for (auto &p: m_kp) { + delete p.second; + } + m_kp.clear(); + m_succeeded = false; +} + +QList<PluginScan::Candidate> +PluginScan::getCandidateLibrariesFor(PluginType +#ifdef HAVE_PLUGIN_CHECKER_HELPER + type +#endif + ) const +{ +#ifdef HAVE_PLUGIN_CHECKER_HELPER + + QMutexLocker locker(&m_mutex); + + KnownPlugins::PluginType kpt; + switch (type) { + case VampPlugin: kpt = KnownPlugins::VampPlugin; break; + case LADSPAPlugin: kpt = KnownPlugins::LADSPAPlugin; break; + case DSSIPlugin: kpt = KnownPlugins::DSSIPlugin; break; + default: throw std::logic_error("Inconsistency in plugin type enums"); + } + + QList<Candidate> candidates; + + for (auto rec: m_kp) { + + KnownPlugins *kp = rec.second; + + auto c = kp->getCandidateLibrariesFor(kpt); + + SVDEBUG << "PluginScan: helper \"" << kp->getHelperExecutableName() + << "\" likes " << c.size() << " libraries of type " + << kp->getTagFor(kpt) << endl; + + for (auto s: c) { + candidates.push_back({ s.c_str(), rec.first }); + } + + if (type != VampPlugin) { + // We are only interested in querying multiple helpers + // when dealing with Vamp plugins, for which we can use + // external servers and so in some cases can support + // additional architectures. Other plugin formats are + // loaded directly and so must match the host, which is + // what the first helper is supposed to handle -- so + // break after the first one if not querying Vamp + break; + } + } + + return candidates; + +#else + return {}; +#endif +} + +QString +PluginScan::getStartupFailureReport() const +{ +#ifdef HAVE_PLUGIN_CHECKER_HELPER + + QMutexLocker locker(&m_mutex); + + if (!m_succeeded) { + return QObject::tr("<b>Failed to scan for plugins</b>" + "<p>Failed to scan for plugins at startup. Possibly " + "the plugin checker program was not correctly " + "installed alongside %1?</p>") + .arg(QCoreApplication::applicationName()); + } + if (m_kp.empty()) { + return QObject::tr("<b>Did not scan for plugins</b>" + "<p>Apparently no scan for plugins was attempted " + "(internal error?)</p>"); + } + + QString report; + for (auto kp: m_kp) { + report += QString::fromStdString(kp.second->getFailureReport()); + } + if (report == "") { + return report; + } + + return QObject::tr("<b>Failed to load plugins</b>" + "<p>Failed to load one or more plugin libraries:</p>") + + report + + QObject::tr("<p>These plugins may be incompatible with the system, " + "and will be ignored during this run of %1.</p>") + .arg(QCoreApplication::applicationName()); + +#else + return ""; +#endif +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugin/PluginScan.h Fri Jan 13 10:29:44 2017 +0000 @@ -0,0 +1,85 @@ +/* -*- 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 PLUGIN_SCAN_H +#define PLUGIN_SCAN_H + +#include <QStringList> +#include <QMutex> +#include <vector> +#include <map> + +class KnownPlugins; + +class PluginScan +{ +public: + static PluginScan *getInstance(); + + /** + * Carry out startup scan of available plugins. Do not call + * getCandidateLibrariesFor() unless this has been called and + * scanSucceeded() is returning true. + */ + void scan(); + + /** + * Return true if scan() completed successfully. If the scan + * failed, consider using the normal plugin path to load any + * available plugins (as if they had all been found to be + * loadable) rather than rejecting all of them -- i.e. consider + * falling back on the behaviour of code from before the scan + * logic was added. + */ + bool scanSucceeded() const; + + enum PluginType { + VampPlugin, + LADSPAPlugin, + DSSIPlugin + }; + struct Candidate { + QString libraryPath; // full path, not just soname + QString helperTag; // identifies the helper that found it + // (see HelperExecPath) + }; + + /** + * Return the candidate plugin libraries of the given type that + * were found by helpers during the startup scan. + * + * This could return an empty list for two reasons: the scan + * succeeded but no libraries were found; or the scan failed. Call + * scanSucceeded() to distinguish between them. + */ + QList<Candidate> getCandidateLibrariesFor(PluginType) const; + + QString getStartupFailureReport() const; + +private: + PluginScan(); + ~PluginScan(); + + void clear(); + + mutable QMutex m_mutex; // while scanning; definitely can't multi-thread this + + std::map<QString, KnownPlugins *> m_kp; // tag -> KnownPlugins client + bool m_succeeded; + + class Logger; + Logger *m_logger; +}; + +#endif
--- a/plugin/api/alsa/seq_event.h Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/api/alsa/seq_event.h Fri Jan 13 10:29:44 2017 +0000 @@ -321,7 +321,11 @@ typedef struct snd_seq_ev_ext { unsigned int len; /**< length of data */ void *ptr; /**< pointer to data (note: can be 64-bit) */ -} __attribute__((packed)) snd_seq_ev_ext_t; +} +#ifdef __GNUC__ +__attribute__((packed)) +#endif +snd_seq_ev_ext_t; /** Instrument cluster type */ typedef unsigned int snd_seq_instr_cluster_t;
--- a/plugin/plugins/SamplePlayer.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/plugin/plugins/SamplePlayer.cpp Fri Jan 13 10:29:44 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, &info); +#else file = sf_open(path.toLocal8Bit().data(), SFM_READ, &info); +#endif if (!file) { cerr << "SamplePlayer::loadSampleData: Failed to open file " << path << ": "
--- a/rdf/RDFImporter.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/rdf/RDFImporter.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -30,7 +30,7 @@ #include "data/model/NoteModel.h" #include "data/model/TextModel.h" #include "data/model/RegionModel.h" -#include "data/model/WaveFileModel.h" +#include "data/model/ReadOnlyWaveFileModel.h" #include "data/fileio/FileSource.h" #include "data/fileio/CachedFile.h" @@ -270,7 +270,7 @@ reporter->setMessage(RDFImporter::tr("Importing audio referenced in RDF...")); } fs->waitForData(); - WaveFileModel *newModel = new WaveFileModel(*fs, m_sampleRate); + ReadOnlyWaveFileModel *newModel = new ReadOnlyWaveFileModel(*fs, m_sampleRate); if (newModel->isOK()) { cerr << "Successfully created wave file model from source at \"" << source << "\"" << endl; models.push_back(newModel);
--- a/svcore.pro Mon Nov 21 16:32:58 2016 +0000 +++ b/svcore.pro Fri Jan 13 10:29:44 2017 +0000 @@ -2,35 +2,10 @@ TEMPLATE = lib INCLUDEPATH += ../vamp-plugin-sdk -DEFINES += HAVE_VAMP HAVE_VAMPHOSTSDK exists(config.pri) { include(config.pri) } -!exists(config.pri) { - - CONFIG += release - DEFINES += NDEBUG BUILD_RELEASE NO_TIMING - - win32-g++ { - INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include - LIBS += -L../sv-dependency-builds/win32-mingw/lib - } - win32-msvc* { - INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include - LIBS += -L../sv-dependency-builds/win32-msvc/lib - } - macx* { - INCLUDEPATH += ../sv-dependency-builds/osx/include - LIBS += -L../sv-dependency-builds/osx/lib - } - - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_LIBLO HAVE_MAD HAVE_ID3TAG - - macx* { - DEFINES += HAVE_COREAUDIO - } -} CONFIG += staticlib qt thread warn_on stl rtti exceptions c++11 QT += network xml @@ -38,8 +13,8 @@ TARGET = svcore -DEPENDPATH += . data plugin plugin/api/alsa -INCLUDEPATH += . data plugin plugin/api/alsa ../dataquay +DEPENDPATH += . data plugin plugin/api/alsa ../dataquay ../checker ../piper-cpp +INCLUDEPATH += . data plugin plugin/api/alsa ../dataquay ../checker ../piper-cpp OBJECTS_DIR = o MOC_DIR = o @@ -52,253 +27,8 @@ win*: DEFINES += __WINDOWS_MM__ solaris*: DEFINES += __RTMIDI_DUMMY_ONLY__ -HEADERS += base/AudioLevel.h \ - base/AudioPlaySource.h \ - base/BaseTypes.h \ - base/Clipboard.h \ - base/Command.h \ - base/Debug.h \ - base/Exceptions.h \ - base/LogRange.h \ - base/Pitch.h \ - base/Playable.h \ - base/PlayParameterRepository.h \ - base/PlayParameters.h \ - base/Preferences.h \ - base/Profiler.h \ - base/ProgressPrinter.h \ - base/ProgressReporter.h \ - base/PropertyContainer.h \ - base/RangeMapper.h \ - base/RealTime.h \ - base/RecentFiles.h \ - base/Resampler.h \ - base/ResizeableBitset.h \ - base/ResourceFinder.h \ - base/RingBuffer.h \ - base/Scavenger.h \ - base/Selection.h \ - base/Serialiser.h \ - base/StorageAdviser.h \ - base/StringBits.h \ - base/TempDirectory.h \ - base/TempWriteFile.h \ - base/TextMatcher.h \ - base/Thread.h \ - base/UnitDatabase.h \ - base/ViewManagerBase.h \ - base/Window.h \ - base/XmlExportable.h \ - base/ZoomConstraint.h -SOURCES += base/AudioLevel.cpp \ - base/Clipboard.cpp \ - base/Command.cpp \ - base/Debug.cpp \ - base/Exceptions.cpp \ - base/LogRange.cpp \ - base/Pitch.cpp \ - base/PlayParameterRepository.cpp \ - base/PlayParameters.cpp \ - base/Preferences.cpp \ - base/Profiler.cpp \ - base/ProgressPrinter.cpp \ - base/ProgressReporter.cpp \ - base/PropertyContainer.cpp \ - base/RangeMapper.cpp \ - base/RealTime.cpp \ - base/RecentFiles.cpp \ - base/Resampler.cpp \ - base/ResourceFinder.cpp \ - base/Selection.cpp \ - base/Serialiser.cpp \ - base/StorageAdviser.cpp \ - base/StringBits.cpp \ - base/TempDirectory.cpp \ - base/TempWriteFile.cpp \ - base/TextMatcher.cpp \ - base/Thread.cpp \ - base/UnitDatabase.cpp \ - base/ViewManagerBase.cpp \ - base/XmlExportable.cpp +include(files.pri) -HEADERS += data/fft/FFTapi.h \ - data/fft/FFTCacheReader.h \ - data/fft/FFTCacheStorageType.h \ - data/fft/FFTCacheWriter.h \ - data/fft/FFTDataServer.h \ - data/fft/FFTFileCacheReader.h \ - data/fft/FFTFileCacheWriter.h \ - data/fft/FFTMemoryCache.h \ - data/fileio/AudioFileReader.h \ - data/fileio/AudioFileReaderFactory.h \ - data/fileio/BZipFileDevice.h \ - data/fileio/CachedFile.h \ - data/fileio/CodedAudioFileReader.h \ - data/fileio/CSVFileReader.h \ - data/fileio/CSVFileWriter.h \ - data/fileio/CSVFormat.h \ - data/fileio/DataFileReader.h \ - data/fileio/DataFileReaderFactory.h \ - data/fileio/FileFinder.h \ - data/fileio/FileReadThread.h \ - data/fileio/FileSource.h \ - data/fileio/MatrixFile.h \ - data/fileio/MIDIFileReader.h \ - data/fileio/MIDIFileWriter.h \ - data/fileio/MP3FileReader.h \ - data/fileio/OggVorbisFileReader.h \ - data/fileio/PlaylistFileReader.h \ - data/fileio/QuickTimeFileReader.h \ - data/fileio/CoreAudioFileReader.h \ - data/fileio/DecodingWavFileReader.h \ - data/fileio/WavFileReader.h \ - data/fileio/WavFileWriter.h \ - data/midi/MIDIEvent.h \ - data/midi/MIDIInput.h \ - data/midi/rtmidi/RtError.h \ - data/midi/rtmidi/RtMidi.h \ - data/model/AggregateWaveModel.h \ - data/model/AlignmentModel.h \ - data/model/Dense3DModelPeakCache.h \ - data/model/DenseThreeDimensionalModel.h \ - data/model/DenseTimeValueModel.h \ - data/model/EditableDenseThreeDimensionalModel.h \ - data/model/FFTModel.h \ - data/model/ImageModel.h \ - data/model/IntervalModel.h \ - data/model/Labeller.h \ - data/model/Model.h \ - data/model/ModelDataTableModel.h \ - data/model/NoteModel.h \ - data/model/FlexiNoteModel.h \ - data/model/PathModel.h \ - data/model/PowerOfSqrtTwoZoomConstraint.h \ - data/model/PowerOfTwoZoomConstraint.h \ - data/model/RangeSummarisableTimeValueModel.h \ - data/model/RegionModel.h \ - data/model/SparseModel.h \ - data/model/SparseOneDimensionalModel.h \ - data/model/SparseTimeValueModel.h \ - data/model/SparseValueModel.h \ - data/model/TabularModel.h \ - data/model/TextModel.h \ - data/model/WaveFileModel.h \ - data/model/WritableWaveFileModel.h \ - data/osc/OSCMessage.h \ - data/osc/OSCQueue.h -SOURCES += data/fft/FFTapi.cpp \ - data/fft/FFTDataServer.cpp \ - data/fft/FFTFileCacheReader.cpp \ - data/fft/FFTFileCacheWriter.cpp \ - data/fft/FFTMemoryCache.cpp \ - data/fileio/AudioFileReader.cpp \ - data/fileio/AudioFileReaderFactory.cpp \ - data/fileio/BZipFileDevice.cpp \ - data/fileio/CachedFile.cpp \ - data/fileio/CodedAudioFileReader.cpp \ - data/fileio/CSVFileReader.cpp \ - data/fileio/CSVFileWriter.cpp \ - data/fileio/CSVFormat.cpp \ - data/fileio/DataFileReaderFactory.cpp \ - data/fileio/FileReadThread.cpp \ - data/fileio/FileSource.cpp \ - data/fileio/MatrixFile.cpp \ - data/fileio/MIDIFileReader.cpp \ - data/fileio/MIDIFileWriter.cpp \ - data/fileio/MP3FileReader.cpp \ - data/fileio/OggVorbisFileReader.cpp \ - data/fileio/PlaylistFileReader.cpp \ - data/fileio/QuickTimeFileReader.cpp \ - data/fileio/CoreAudioFileReader.cpp \ - data/fileio/DecodingWavFileReader.cpp \ - data/fileio/WavFileReader.cpp \ - data/fileio/WavFileWriter.cpp \ - data/midi/MIDIInput.cpp \ - data/midi/rtmidi/RtMidi.cpp \ - data/model/AggregateWaveModel.cpp \ - data/model/AlignmentModel.cpp \ - data/model/Dense3DModelPeakCache.cpp \ - data/model/DenseTimeValueModel.cpp \ - data/model/EditableDenseThreeDimensionalModel.cpp \ - data/model/FFTModel.cpp \ - data/model/Model.cpp \ - data/model/ModelDataTableModel.cpp \ - data/model/PowerOfSqrtTwoZoomConstraint.cpp \ - data/model/PowerOfTwoZoomConstraint.cpp \ - data/model/RangeSummarisableTimeValueModel.cpp \ - data/model/WaveFileModel.cpp \ - data/model/WritableWaveFileModel.cpp \ - data/osc/OSCMessage.cpp \ - data/osc/OSCQueue.cpp +HEADERS = $$SVCORE_HEADERS +SOURCES = $$SVCORE_SOURCES -HEADERS += plugin/DSSIPluginFactory.h \ - plugin/DSSIPluginInstance.h \ - plugin/FeatureExtractionPluginFactory.h \ - plugin/LADSPAPluginFactory.h \ - plugin/LADSPAPluginInstance.h \ - plugin/PluginIdentifier.h \ - plugin/PluginXml.h \ - plugin/RealTimePluginFactory.h \ - plugin/RealTimePluginInstance.h \ - plugin/api/dssi.h \ - plugin/api/ladspa.h \ - plugin/plugins/SamplePlayer.h \ - plugin/api/alsa/asoundef.h \ - plugin/api/alsa/asoundlib.h \ - plugin/api/alsa/seq.h \ - plugin/api/alsa/seq_event.h \ - plugin/api/alsa/seq_midi_event.h \ - plugin/api/alsa/sound/asequencer.h - - -SOURCES += plugin/DSSIPluginFactory.cpp \ - plugin/DSSIPluginInstance.cpp \ - plugin/FeatureExtractionPluginFactory.cpp \ - plugin/LADSPAPluginFactory.cpp \ - plugin/LADSPAPluginInstance.cpp \ - plugin/PluginIdentifier.cpp \ - plugin/PluginXml.cpp \ - plugin/RealTimePluginFactory.cpp \ - plugin/RealTimePluginInstance.cpp \ - plugin/plugins/SamplePlayer.cpp - -!linux* { -SOURCES += plugin/api/dssi_alsa_compat.c -} - -HEADERS += rdf/PluginRDFIndexer.h \ - rdf/PluginRDFDescription.h \ - rdf/RDFExporter.h \ - rdf/RDFFeatureWriter.h \ - rdf/RDFImporter.h \ - rdf/RDFTransformFactory.h -SOURCES += rdf/PluginRDFIndexer.cpp \ - rdf/PluginRDFDescription.cpp \ - rdf/RDFExporter.cpp \ - rdf/RDFFeatureWriter.cpp \ - rdf/RDFImporter.cpp \ - rdf/RDFTransformFactory.cpp - -HEADERS += system/Init.h \ - system/System.h -SOURCES += system/Init.cpp \ - system/System.cpp - -HEADERS += transform/CSVFeatureWriter.h \ - transform/FeatureExtractionModelTransformer.h \ - transform/FeatureWriter.h \ - transform/FileFeatureWriter.h \ - transform/RealTimeEffectModelTransformer.h \ - transform/Transform.h \ - transform/TransformDescription.h \ - transform/TransformFactory.h \ - transform/ModelTransformer.h \ - transform/ModelTransformerFactory.h -SOURCES += transform/CSVFeatureWriter.cpp \ - transform/FeatureExtractionModelTransformer.cpp \ - transform/FileFeatureWriter.cpp \ - transform/RealTimeEffectModelTransformer.cpp \ - transform/Transform.cpp \ - transform/TransformFactory.cpp \ - transform/ModelTransformer.cpp \ - transform/ModelTransformerFactory.cpp
--- a/system/System.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/system/System.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -1,16 +1,16 @@ /* -*- 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 and QMUL. + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 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. + 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 "System.h" @@ -39,16 +39,16 @@ #ifdef __APPLE__ extern "C" { -void * -rpl_realloc (void *p, size_t n) -{ - p = realloc(p, n); - if (p == 0 && n == 0) + void * + rpl_realloc (void *p, size_t n) { - p = malloc(0); + p = realloc(p, n); + if (p == 0 && n == 0) + { + p = malloc(0); + } + return p; } - return p; -} } #endif @@ -56,25 +56,25 @@ extern "C" { -/* usleep is now in mingw -void usleep(unsigned long usec) -{ - ::Sleep(usec / 1000); -} -*/ +#ifdef _MSC_VER + void usleep(unsigned long usec) + { + ::Sleep(usec / 1000); + } +#endif -int gettimeofday(struct timeval *tv, void *tz) -{ - union { - long long ns100; - FILETIME ft; - } now; + int gettimeofday(struct timeval *tv, void *tz) + { + union { + long long ns100; + FILETIME ft; + } now; - ::GetSystemTimeAsFileTime(&now.ft); - tv->tv_usec = (long)((now.ns100 / 10LL) % 1000000LL); - tv->tv_sec = (long)((now.ns100 - 116444736000000000LL) / 10000000LL); - return 0; -} + ::GetSystemTimeAsFileTime(&now.ft); + tv->tv_usec = (long)((now.ns100 / 10LL) % 1000000LL); + tv->tv_sec = (long)((now.ns100 - 116444736000000000LL) / 10000000LL); + return 0; + } } @@ -119,7 +119,7 @@ DWORDLONG ullAvailVirtual; DWORDLONG ullAvailExtendedVirtual; } lMEMORYSTATUSEX; -typedef WINBOOL (WINAPI *PFN_MS_EX) (lMEMORYSTATUSEX*); +typedef BOOL (WINAPI *PFN_MS_EX) (lMEMORYSTATUSEX*); #endif void @@ -153,10 +153,10 @@ if (exFound) { lMEMORYSTATUSEX lms; - lms.dwLength = sizeof(lms); - if (!ex(&lms)) { + lms.dwLength = sizeof(lms); + if (!ex(&lms)) { cerr << "WARNING: GlobalMemoryStatusEx failed: error code " - << GetLastError() << endl; + << GetLastError() << endl; return; } wavail = lms.ullAvailPhys; @@ -167,9 +167,9 @@ /* Fall back to GlobalMemoryStatus which is always available. but returns wrong results for physical memory > 4GB */ - MEMORYSTATUS ms; - GlobalMemoryStatus(&ms); - wavail = ms.dwAvailPhys; + MEMORYSTATUS ms; + GlobalMemoryStatus(&ms); + wavail = ms.dwAvailPhys; wtotal = ms.dwTotalPhys; } @@ -211,7 +211,9 @@ char buf[256]; while (!feof(meminfo)) { - fgets(buf, 256, meminfo); + if (!fgets(buf, 256, meminfo)) { + return; + } bool isMemFree = (strncmp(buf, "MemFree:", 8) == 0); bool isMemTotal = (!isMemFree && (strncmp(buf, "MemTotal:", 9) == 0)); if (isMemFree || isMemTotal) { @@ -249,13 +251,13 @@ #ifdef _WIN32 ULARGE_INTEGER available, total, totalFree; if (GetDiskFreeSpaceExA(path, &available, &total, &totalFree)) { - __int64 a = available.QuadPart; + __int64 a = available.QuadPart; a /= 1048576; if (a > INT_MAX) a = INT_MAX; return ssize_t(a); } else { cerr << "WARNING: GetDiskFreeSpaceEx failed: error code " - << GetLastError() << endl; + << GetLastError() << endl; return -1; } #else @@ -277,7 +279,7 @@ #ifdef _WIN32 extern void SystemMemoryBarrier() { -#ifdef __MSVC__ +#ifdef _MSC_VER MemoryBarrier(); #else /* mingw */ LONG Barrier = 0; @@ -286,7 +288,7 @@ #endif } #else /* !_WIN32 */ -#if !defined(__APPLE__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ == 0)) +#if !defined(__APPLE__) && defined(__GNUC__) && ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ == 0)) void SystemMemoryBarrier() { @@ -325,76 +327,4 @@ double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; } float princargf(float a) { return float(princarg(a)); } -#ifdef _WIN32 -PluginLoadStatus -TestPluginLoadability(QString soname, QString descriptorFn) -{ - //!!! Can't do the POSIX logic below, but we have no good - // alternative here yet - return PluginLoadOK; -} - -#else - -#include <unistd.h> -#include <sys/wait.h> - -PluginLoadStatus -TestPluginLoadability(QString soname, QString descriptorFn) -{ - //!!! This is POSIX only, no equivalent on Windows, where we'll - //!!! have to do something completely different - - pid_t pid = fork(); - - if (pid < 0) { - return UnknownPluginLoadStatus; // fork failed - } - - if (pid == 0) { // the child process - - void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL); - if (!handle) { - cerr << "isPluginLibraryLoadable: Failed to open plugin library \"" - << soname << "\": " << dlerror() << "\n"; - cerr << "exiting with status 1" << endl; - exit(1); - } - - void *fn = DLSYM(handle, descriptorFn.toLocal8Bit().data()); - if (!fn) { - cerr << "isPluginLibraryLoadable: Failed to find plugin descriptor function \"" << descriptorFn << "\" in library \"" << soname << "\": " << dlerror() << "\n"; - exit(2); - } - -// cerr << "isPluginLibraryLoadable: Successfully loaded library \"" << soname << "\" and retrieved descriptor function" << endl; - - exit(0); - - } else { // the parent process - - int status = 0; - - do { - waitpid(pid, &status, 0); - } while (WIFSTOPPED(status)); - - if (WIFEXITED(status)) { - switch (WEXITSTATUS(status)) { - case 0: return PluginLoadOK; // success - case 1: return PluginLoadFailedToLoadLibrary; - case 2: return PluginLoadFailedToFindDescriptor; - default: return PluginLoadFailedElsewhere; - } - } - - if (WIFSIGNALED(status)) { - return PluginLoadFailedElsewhere; - } - - return UnknownPluginLoadStatus; - } -} - -#endif
--- a/system/System.h Mon Nov 21 16:32:58 2016 +0000 +++ b/system/System.h Fri Jan 13 10:29:44 2017 +0000 @@ -59,10 +59,20 @@ #define getpid _getpid +#if defined(_MSC_VER) +#include <BaseTsd.h> +typedef SSIZE_T ssize_t; +#endif + +#ifdef _MSC_VER extern "C" { -/* usleep is now in mingw void usleep(unsigned long usec); -*/ +} +#else +#include <unistd.h> +#endif + +extern "C" { int gettimeofday(struct timeval *p, void *tz); } @@ -75,6 +85,7 @@ #include <dlfcn.h> #include <stdio.h> // for perror #include <cmath> +#include <unistd.h> // sleep + usleep primarily #define MLOCK(a,b) ::mlock((a),(b)) #define MUNLOCK(a,b) (::munlock((a),(b)) ? (::perror("munlock failed"), 0) : 0) @@ -154,21 +165,6 @@ extern void StoreStartupLocale(); extern void RestoreStartupLocale(); -enum PluginLoadStatus { - UnknownPluginLoadStatus, - PluginLoadOK, - PluginLoadFailedToLoadLibrary, - PluginLoadFailedToFindDescriptor, - PluginLoadFailedElsewhere -}; - -// Check whether a plugin library is loadable without crashing (may -// need to spawn an external process to do it). Descriptor fn is the -// name of a LADSPA/DSSI/Vamp-style descriptor function to try -// calling; may be an empty string if the plugin doesn't follow that -// convention. -PluginLoadStatus TestPluginLoadability(QString soname, QString descriptorFn); - #include <cmath> #ifndef M_PI
--- a/transform/CSVFeatureWriter.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/CSVFeatureWriter.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -276,7 +276,20 @@ } for (unsigned int j = 0; j < f.values.size(); ++j) { - stream << m_separator << QString("%1").arg(f.values[j], 0, 'g', m_digits); + + QString number = QString("%1").arg(f.values[j], 0, 'g', m_digits); + + // Qt pre-5.6 zero pads single-digit exponents to two digits; + // Qt 5.7+ doesn't by default. But we want both to produce the + // same output. Getting the new behaviour from standard APIs + // in Qt 5.6 isn't possible I think; getting the old behaviour + // from Qt 5.7 is possible but fiddly, involving setting up an + // appropriate locale and using the %L specifier. We could + // doubtless do it with sprintf but Qt is a known quantity at + // this point. Let's just convert the old format to the new. + number.replace("e-0", "e-"); + + stream << m_separator << number; } if (f.label != "") {
--- a/transform/FeatureExtractionModelTransformer.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/FeatureExtractionModelTransformer.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -16,6 +16,7 @@ #include "FeatureExtractionModelTransformer.h" #include "plugin/FeatureExtractionPluginFactory.h" + #include "plugin/PluginXml.h" #include <vamp-hostsdk/Plugin.h> @@ -42,25 +43,23 @@ FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in, const Transform &transform) : ModelTransformer(in, transform), - m_plugin(0) + m_plugin(0), + m_haveOutputs(false) { SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl; - - initialise(); } FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in, const Transforms &transforms) : ModelTransformer(in, transforms), - m_plugin(0) + m_plugin(0), + m_haveOutputs(false) { if (m_transforms.empty()) { SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s)" << endl; } else { SVDEBUG << "FeatureExtractionModelTransformer::FeatureExtractionModelTransformer: " << transforms.size() << " transform(s), first has plugin " << m_transforms.begin()->getPluginIdentifier() << ", outputName " << m_transforms.begin()->getOutput() << endl; } - - initialise(); } static bool @@ -74,6 +73,10 @@ bool FeatureExtractionModelTransformer::initialise() { + // This is (now) called from the run thread. The plugin is + // constructed, initialised, used, and destroyed all from a single + // thread. + // All transforms must use the same plugin, parameters, and // inputs: they can differ only in choice of plugin output. So we // initialise based purely on the first transform in the list (but @@ -91,7 +94,7 @@ QString pluginId = primaryTransform.getPluginIdentifier(); FeatureExtractionPluginFactory *factory = - FeatureExtractionPluginFactory::instanceFor(pluginId); + FeatureExtractionPluginFactory::instance(); if (!factory) { m_message = tr("No factory available for feature extraction plugin id \"%1\" (unknown plugin type, or internal error?)").arg(pluginId); @@ -104,6 +107,9 @@ return false; } + SVDEBUG << "FeatureExtractionModelTransformer: Instantiating plugin for transform in thread " + << QThread::currentThreadId() << endl; + m_plugin = factory->instantiatePlugin(pluginId, input->getSampleRate()); if (!m_plugin) { m_message = tr("Failed to instantiate plugin \"%1\"").arg(pluginId); @@ -130,13 +136,13 @@ } SVDEBUG << "Initialising feature extraction plugin with channels = " - << channelCount << ", step = " << primaryTransform.getStepSize() - << ", block = " << primaryTransform.getBlockSize() << endl; + << channelCount << ", step = " << primaryTransform.getStepSize() + << ", block = " << primaryTransform.getBlockSize() << endl; if (!m_plugin->initialise(channelCount, primaryTransform.getStepSize(), primaryTransform.getBlockSize())) { - + int pstep = primaryTransform.getStepSize(); int pblock = primaryTransform.getBlockSize(); @@ -148,16 +154,24 @@ if (primaryTransform.getStepSize() != pstep || primaryTransform.getBlockSize() != pblock) { + + SVDEBUG << "Initialisation failed, trying again with default step = " + << primaryTransform.getStepSize() + << ", block = " << primaryTransform.getBlockSize() << endl; if (!m_plugin->initialise(channelCount, primaryTransform.getStepSize(), primaryTransform.getBlockSize())) { + SVDEBUG << "Initialisation failed again" << endl; + m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId); return false; } else { - + + SVDEBUG << "Initialisation succeeded this time" << endl; + m_message = tr("Feature extraction plugin \"%1\" rejected the given step and block sizes (%2 and %3); using plugin defaults (%4 and %5) instead") .arg(pluginId) .arg(pstep) @@ -168,9 +182,13 @@ } else { + SVDEBUG << "Initialisation failed" << endl; + m_message = tr("Failed to initialise feature extraction plugin \"%1\"").arg(pluginId); return false; } + } else { + SVDEBUG << "Initialisation succeeded" << endl; } if (primaryTransform.getPluginVersion() != "") { @@ -220,15 +238,30 @@ createOutputModels(j); } + m_outputMutex.lock(); + m_haveOutputs = true; + m_outputsCondition.wakeAll(); + m_outputMutex.unlock(); + return true; } void +FeatureExtractionModelTransformer::deinitialise() +{ + SVDEBUG << "FeatureExtractionModelTransformer: deleting plugin for transform in thread " + << QThread::currentThreadId() << endl; + + delete m_plugin; + for (int j = 0; j < (int)m_descriptors.size(); ++j) { + delete m_descriptors[j]; + } +} + +void FeatureExtractionModelTransformer::createOutputModels(int n) { DenseTimeValueModel *input = getConformingInput(); - -// cerr << "FeatureExtractionModelTransformer::createOutputModel: sample type " << m_descriptor->sampleType << ", rate " << m_descriptor->sampleRate << endl; PluginRDFDescription description(m_transforms[n].getPluginIdentifier()); QString outputId = m_transforms[n].getOutput(); @@ -254,21 +287,33 @@ } sv_samplerate_t modelRate = input->getSampleRate(); + sv_samplerate_t outputRate = modelRate; int modelResolution = 1; if (m_descriptors[n]->sampleType != Vamp::Plugin::OutputDescriptor::OneSamplePerStep) { - if (m_descriptors[n]->sampleRate > input->getSampleRate()) { - cerr << "WARNING: plugin reports output sample rate as " - << m_descriptors[n]->sampleRate << " (can't display features with finer resolution than the input rate of " << input->getSampleRate() << ")" << endl; + + outputRate = m_descriptors[n]->sampleRate; + + //!!! SV doesn't actually support display of models that have + //!!! different underlying rates together -- so we always set + //!!! the model rate to be the input model's rate, and adjust + //!!! the resolution appropriately. We can't properly display + //!!! data with a higher resolution than the base model at all + if (outputRate > input->getSampleRate()) { + SVDEBUG << "WARNING: plugin reports output sample rate as " + << outputRate + << " (can't display features with finer resolution than the input rate of " + << modelRate << ")" << endl; + outputRate = modelRate; } } switch (m_descriptors[n]->sampleType) { case Vamp::Plugin::OutputDescriptor::VariableSampleRate: - if (m_descriptors[n]->sampleRate != 0.0) { - modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate)); + if (outputRate != 0.0) { + modelResolution = int(round(modelRate / outputRate)); } break; @@ -277,18 +322,12 @@ break; case Vamp::Plugin::OutputDescriptor::FixedSampleRate: - //!!! SV doesn't actually support display of models that have - //!!! different underlying rates together -- so we always set - //!!! the model rate to be the input model's rate, and adjust - //!!! the resolution appropriately. We can't properly display - //!!! data with a higher resolution than the base model at all - if (m_descriptors[n]->sampleRate > input->getSampleRate()) { - modelResolution = 1; - } else if (m_descriptors[n]->sampleRate <= 0.0) { - cerr << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl; + if (outputRate <= 0.0) { + SVDEBUG << "WARNING: Fixed sample-rate plugin reports invalid sample rate " << m_descriptors[n]->sampleRate << "; defaulting to input rate of " << input->getSampleRate() << endl; modelResolution = 1; } else { - modelResolution = int(round(modelRate / m_descriptors[n]->sampleRate)); + modelResolution = int(round(modelRate / outputRate)); +// cerr << "modelRate = " << modelRate << ", descriptor rate = " << outputRate << ", modelResolution = " << modelResolution << endl; } break; } @@ -479,13 +518,21 @@ } } +void +FeatureExtractionModelTransformer::awaitOutputModels() +{ + m_outputMutex.lock(); + while (!m_haveOutputs) { + m_outputsCondition.wait(&m_outputMutex); + } + m_outputMutex.unlock(); +} + FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer() { -// SVDEBUG << "FeatureExtractionModelTransformer::~FeatureExtractionModelTransformer()" << endl; - delete m_plugin; - for (int j = 0; j < (int)m_descriptors.size(); ++j) { - delete m_descriptors[j]; - } + // Parent class dtor set the abandoned flag and waited for the run + // thread to exit; the run thread owns the plugin, and should have + // destroyed it before exiting (via a call to deinitialise) } FeatureExtractionModelTransformer::Models @@ -566,6 +613,8 @@ void FeatureExtractionModelTransformer::run() { + initialise(); + DenseTimeValueModel *input = getConformingInput(); if (!input) return; @@ -606,9 +655,7 @@ primaryTransform.getWindowType(), blockSize, stepSize, - blockSize, - false, - StorageAdviser::PrecisionCritical); + blockSize); if (!model->isOK() || model->getError() != "") { QString err = model->getError(); delete model; @@ -618,7 +665,6 @@ //!!! need a better way to handle this -- previously we were using a QMessageBox but that isn't an appropriate thing to do here either throw AllocationFailed("Failed to create the FFT model for this feature extraction model transformer: error is: " + err); } - model->resume(); fftModels.push_back(model); cerr << "created model for channel " << ch << endl; } @@ -700,7 +746,7 @@ } error = fftModels[ch]->getError(); if (error != "") { - cerr << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl; + SVDEBUG << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl; m_abandoned = true; m_message = error; break; @@ -761,6 +807,8 @@ delete[] buffers[ch]; } delete[] buffers; + + deinitialise(); } void @@ -790,31 +838,28 @@ if (channelCount == 1) { - got = input->getData(m_input.getChannel(), startFrame, size, - buffers[0] + offset); + auto data = input->getData(m_input.getChannel(), startFrame, size); + got = data.size(); + + copy(data.begin(), data.end(), buffers[0] + offset); if (m_input.getChannel() == -1 && input->getChannelCount() > 1) { // use mean instead of sum, as plugin input float cc = float(input->getChannelCount()); - for (sv_frame_t i = 0; i < size; ++i) { + for (sv_frame_t i = 0; i < got; ++i) { buffers[0][i + offset] /= cc; } } } else { - float **writebuf = buffers; - if (offset > 0) { - writebuf = new float *[channelCount]; - for (int i = 0; i < channelCount; ++i) { - writebuf[i] = buffers[i] + offset; + auto data = input->getMultiChannelData(0, channelCount-1, startFrame, size); + if (!data.empty()) { + got = data[0].size(); + for (int c = 0; in_range_for(data, c); ++c) { + copy(data[c].begin(), data[c].end(), buffers[c] + offset); } } - - got = input->getMultiChannelData - (0, channelCount-1, startFrame, size, writebuf); - - if (writebuf != buffers) delete[] writebuf; } while (got < size) { @@ -844,7 +889,7 @@ Vamp::Plugin::OutputDescriptor::VariableSampleRate) { if (!feature.hasTimestamp) { - cerr + SVDEBUG << "WARNING: FeatureExtractionModelTransformer::addFeature: " << "Feature has variable sample rate but no timestamp!" << endl; @@ -880,7 +925,7 @@ } if (frame < 0) { - cerr + SVDEBUG << "WARNING: FeatureExtractionModelTransformer::addFeature: " << "Negative frame counts are not supported (frame = " << frame << " from timestamp " << feature.timestamp @@ -1013,8 +1058,7 @@ } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) { - DenseThreeDimensionalModel::Column values = - DenseThreeDimensionalModel::Column::fromStdVector(feature.values); + DenseThreeDimensionalModel::Column values = feature.values; EditableDenseThreeDimensionalModel *model = getConformingOutput<EditableDenseThreeDimensionalModel>(n);
--- a/transform/FeatureExtractionModelTransformer.h Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/FeatureExtractionModelTransformer.h Fri Jan 13 10:29:44 2017 +0000 @@ -19,6 +19,8 @@ #include "ModelTransformer.h" #include <QString> +#include <QMutex> +#include <QWaitCondition> #include <vamp-hostsdk/Plugin.h> @@ -28,7 +30,7 @@ class DenseTimeValueModel; class SparseTimeValueModel; -class FeatureExtractionModelTransformer : public ModelTransformer +class FeatureExtractionModelTransformer : public ModelTransformer // + is a Thread { Q_OBJECT @@ -50,6 +52,7 @@ protected: bool initialise(); + void deinitialise(); virtual void run(); @@ -74,7 +77,12 @@ void getFrames(int channelCount, sv_frame_t startFrame, sv_frame_t size, float **buffer); - // just casts + bool m_haveOutputs; + QMutex m_outputMutex; + QWaitCondition m_outputsCondition; + void awaitOutputModels(); + + // just casts: DenseTimeValueModel *getConformingInput();
--- a/transform/ModelTransformer.h Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/ModelTransformer.h Fri Jan 13 10:29:44 2017 +0000 @@ -89,16 +89,20 @@ * be initialised; an error message may be available via * getMessage() in this situation. */ - Models getOutputModels() { return m_outputs; } + Models getOutputModels() { + awaitOutputModels(); + return m_outputs; + } /** * Return the set of output models, also detaching them from the * transformer so that they will not be deleted when the * transformer is. The caller takes ownership of the models. */ - Models detachOutputModels() { + Models detachOutputModels() { + awaitOutputModels(); m_detached = true; - return getOutputModels(); + return m_outputs; } /** @@ -138,6 +142,8 @@ ModelTransformer(Input input, const Transform &transform); ModelTransformer(Input input, const Transforms &transforms); + virtual void awaitOutputModels() = 0; + Transforms m_transforms; Input m_input; // I don't own the model in this Models m_outputs; // I own this, unless...
--- a/transform/ModelTransformerFactory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/ModelTransformerFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -89,29 +89,21 @@ bool ok = true; QString configurationXml = m_lastConfigurations[transform.getIdentifier()]; - cerr << "last configuration: " << configurationXml << endl; + SVDEBUG << "ModelTransformer: last configuration for identifier " << transform.getIdentifier() << ": " << configurationXml << endl; Vamp::PluginBase *plugin = 0; - if (FeatureExtractionPluginFactory::instanceFor(id)) { + if (RealTimePluginFactory::instanceFor(id)) { - cerr << "getConfigurationForTransform: instantiating Vamp plugin" << endl; - - Vamp::Plugin *vp = - FeatureExtractionPluginFactory::instanceFor(id)->instantiatePlugin - (id, float(inputModel->getSampleRate())); - - plugin = vp; - - } else if (RealTimePluginFactory::instanceFor(id)) { - + SVDEBUG << "ModelTransformerFactory::getConfigurationForTransform: instantiating real-time plugin" << endl; + RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(id); sv_samplerate_t sampleRate = inputModel->getSampleRate(); int blockSize = 1024; int channels = 1; if (source) { - sampleRate = source->getTargetSampleRate(); + sampleRate = source->getSourceSampleRate(); blockSize = source->getTargetBlockSize(); channels = source->getTargetChannelCount(); } @@ -120,6 +112,16 @@ (id, 0, 0, sampleRate, blockSize, channels); plugin = rtp; + + } else { + + SVDEBUG << "ModelTransformerFactory::getConfigurationForTransform: instantiating Vamp plugin" << endl; + + Vamp::Plugin *vp = + FeatureExtractionPluginFactory::instance()->instantiatePlugin + (id, float(inputModel->getSampleRate())); + + plugin = vp; } if (plugin) { @@ -152,6 +154,8 @@ configurationXml = PluginXml(plugin).toXmlString(); + SVDEBUG << "ModelTransformerFactory::getConfigurationForTransform: got configuration, deleting plugin" << endl; + delete plugin; } @@ -171,20 +175,15 @@ QString id = transforms[0].getPluginIdentifier(); - if (FeatureExtractionPluginFactory::instanceFor(id)) { - - transformer = - new FeatureExtractionModelTransformer(input, transforms); - - } else if (RealTimePluginFactory::instanceFor(id)) { + if (RealTimePluginFactory::instanceFor(id)) { transformer = new RealTimeEffectModelTransformer(input, transforms[0]); } else { - SVDEBUG << "ModelTransformerFactory::createTransformer: Unknown transform \"" - << transforms[0].getIdentifier() << "\"" << endl; - return transformer; + + transformer = + new FeatureExtractionModelTransformer(input, transforms); } if (transformer) transformer->setObjectName(transforms[0].getIdentifier());
--- a/transform/RealTimeEffectModelTransformer.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/RealTimeEffectModelTransformer.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -191,10 +191,14 @@ if (channelCount == 1) { if (inbufs && inbufs[0]) { - got = input->getData - (m_input.getChannel(), blockFrame, blockSize, inbufs[0]); + auto data = input->getData + (m_input.getChannel(), blockFrame, blockSize); + got = data.size(); + for (sv_frame_t i = 0; i < got; ++i) { + inbufs[0][i] = data[i]; + } while (got < blockSize) { - inbufs[0][got++] = 0.0; + inbufs[0][got++] = 0.f; } for (int ch = 1; ch < (int)m_plugin->getAudioInputCount(); ++ch) { for (sv_frame_t i = 0; i < blockSize; ++i) { @@ -204,9 +208,14 @@ } } else { if (inbufs && inbufs[0]) { - got = input->getMultiChannelData(0, channelCount - 1, - blockFrame, blockSize, - inbufs); + auto data = input->getMultiChannelData + (0, channelCount - 1, blockFrame, blockSize); + if (!data.empty()) got = data[0].size(); + for (int ch = 0; ch < channelCount; ++ch) { + for (sv_frame_t i = 0; i < got; ++i) { + inbufs[ch][i] = data[ch][i]; + } + } while (got < blockSize) { for (int ch = 0; ch < channelCount; ++ch) { inbufs[ch][got] = 0.0; @@ -273,8 +282,10 @@ } if (blockFrame == contextStart || completion > prevCompletion) { + // This setCompletion is probably misusing the completion + // terminology, just as it was for WritableWaveFileModel if (stvm) stvm->setCompletion(completion); - if (wwfm) wwfm->setCompletion(completion); + if (wwfm) wwfm->setWriteProportion(completion); prevCompletion = completion; } @@ -284,6 +295,6 @@ if (m_abandoned) return; if (stvm) stvm->setCompletion(100); - if (wwfm) wwfm->setCompletion(100); + if (wwfm) wwfm->writeComplete(); }
--- a/transform/RealTimeEffectModelTransformer.h Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/RealTimeEffectModelTransformer.h Fri Jan 13 10:29:44 2017 +0000 @@ -31,6 +31,8 @@ protected: virtual void run(); + virtual void awaitOutputModels() { } // they're created synchronously + QString m_units; RealTimePluginInstance *m_plugin; int m_outputNo;
--- a/transform/Transform.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/Transform.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -202,12 +202,10 @@ Transform::Type Transform::getType() const { - if (FeatureExtractionPluginFactory::instanceFor(getPluginIdentifier())) { - return FeatureExtraction; - } else if (RealTimePluginFactory::instanceFor(getPluginIdentifier())) { + if (RealTimePluginFactory::instanceFor(getPluginIdentifier())) { return RealTimeEffect; } else { - return UnknownType; + return FeatureExtraction; } }
--- a/transform/TransformFactory.cpp Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/TransformFactory.cpp Fri Jan 13 10:29:44 2017 +0000 @@ -16,6 +16,7 @@ #include "TransformFactory.h" #include "plugin/FeatureExtractionPluginFactory.h" + #include "plugin/RealTimePluginFactory.h" #include "plugin/RealTimePluginInstance.h" #include "plugin/PluginXml.h" @@ -399,95 +400,80 @@ m_transformsPopulated = true; } -QString -TransformFactory::getPluginPopulationWarning() -{ - FeatureExtractionPluginFactory *vfactory = - FeatureExtractionPluginFactory::instance("vamp"); - QString warningMessage; - if (vfactory) { - warningMessage = vfactory->getPluginPopulationWarning(); - } - return warningMessage; -} - void TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms) { - std::vector<QString> plugs = - FeatureExtractionPluginFactory::getAllPluginIdentifiers(); + FeatureExtractionPluginFactory *factory = + FeatureExtractionPluginFactory::instance(); + + QString errorMessage; + std::vector<QString> plugs = factory->getPluginIdentifiers(errorMessage); + if (errorMessage != "") { + m_errorString = tr("Failed to list Vamp plugins: %1").arg(errorMessage); + } + if (m_exiting) return; for (int i = 0; i < (int)plugs.size(); ++i) { QString pluginId = plugs[i]; - FeatureExtractionPluginFactory *factory = - FeatureExtractionPluginFactory::instanceFor(pluginId); + piper_vamp::PluginStaticData psd = factory->getPluginStaticData(pluginId); - if (!factory) { - cerr << "WARNING: TransformFactory::populateTransforms: No feature extraction plugin factory for instance " << pluginId << endl; - continue; - } + if (psd.pluginKey == "") { + cerr << "WARNING: TransformFactory::populateTransforms: No plugin static data available for instance " << pluginId << endl; + continue; + } - Vamp::Plugin *plugin = - factory->instantiatePlugin(pluginId, 44100); + QString pluginName = QString::fromStdString(psd.basic.name); + QString category = factory->getPluginCategory(pluginId); + + const auto &basicOutputs = psd.basicOutputInfo; - if (!plugin) { - cerr << "WARNING: TransformFactory::populateTransforms: Failed to instantiate plugin " << pluginId << endl; - continue; - } - - QString pluginName = plugin->getName().c_str(); - QString category = factory->getPluginCategory(pluginId); + for (const auto &o: basicOutputs) { - Vamp::Plugin::OutputList outputs = - plugin->getOutputDescriptors(); - - for (int j = 0; j < (int)outputs.size(); ++j) { + QString outputName = QString::fromStdString(o.name); QString transformId = QString("%1:%2") - .arg(pluginId).arg(outputs[j].identifier.c_str()); + .arg(pluginId).arg(QString::fromStdString(o.identifier)); QString userName; QString friendlyName; - QString units = outputs[j].unit.c_str(); - QString description = plugin->getDescription().c_str(); - QString maker = plugin->getMaker().c_str(); +//!!! return to this QString units = outputs[j].unit.c_str(); + QString description = QString::fromStdString(psd.basic.description); + QString maker = QString::fromStdString(psd.maker); if (maker == "") maker = tr("<unknown maker>"); QString longDescription = description; if (longDescription == "") { - if (outputs.size() == 1) { + if (basicOutputs.size() == 1) { longDescription = tr("Extract features using \"%1\" plugin (from %2)") .arg(pluginName).arg(maker); } else { longDescription = tr("Extract features using \"%1\" output of \"%2\" plugin (from %3)") - .arg(outputs[j].name.c_str()).arg(pluginName).arg(maker); + .arg(outputName).arg(pluginName).arg(maker); } } else { - if (outputs.size() == 1) { + if (basicOutputs.size() == 1) { longDescription = tr("%1 using \"%2\" plugin (from %3)") .arg(longDescription).arg(pluginName).arg(maker); } else { longDescription = tr("%1 using \"%2\" output of \"%3\" plugin (from %4)") - .arg(longDescription).arg(outputs[j].name.c_str()).arg(pluginName).arg(maker); + .arg(longDescription).arg(outputName).arg(pluginName).arg(maker); } } - if (outputs.size() == 1) { + if (basicOutputs.size() == 1) { userName = pluginName; friendlyName = pluginName; } else { - userName = QString("%1: %2") - .arg(pluginName) - .arg(outputs[j].name.c_str()); - friendlyName = outputs[j].name.c_str(); + userName = QString("%1: %2").arg(pluginName).arg(outputName); + friendlyName = outputName; } - bool configurable = (!plugin->getPrograms().empty() || - !plugin->getParameterDescriptors().empty()); + bool configurable = (!psd.programs.empty() || + !psd.parameters.empty()); #ifdef DEBUG_TRANSFORM_FACTORY cerr << "Feature extraction plugin transform: " << transformId << " friendly name: " << friendlyName << endl; @@ -502,11 +488,10 @@ description, longDescription, maker, - units, +//!!! units, + "", configurable); } - - delete plugin; } } @@ -768,6 +753,9 @@ t.setIdentifier(id); if (rate != 0) t.setSampleRate(rate); + SVDEBUG << "TransformFactory::getDefaultTransformFor: identifier \"" + << id << "\"" << endl; + Vamp::PluginBase *plugin = instantiateDefaultPluginFor(id, rate); if (plugin) { @@ -783,6 +771,9 @@ Vamp::PluginBase * TransformFactory::instantiatePluginFor(const Transform &transform) { + SVDEBUG << "TransformFactory::instantiatePluginFor: identifier \"" + << transform.getIdentifier() << "\"" << endl; + Vamp::PluginBase *plugin = instantiateDefaultPluginFor (transform.getIdentifier(), transform.getSampleRate()); @@ -806,11 +797,11 @@ if (t.getType() == Transform::FeatureExtraction) { -// cerr << "TransformFactory::instantiateDefaultPluginFor: identifier \"" -// << identifier << "\" is a feature extraction transform" << endl; + SVDEBUG << "TransformFactory::instantiateDefaultPluginFor: identifier \"" + << identifier << "\" is a feature extraction transform" << endl; - FeatureExtractionPluginFactory *factory = - FeatureExtractionPluginFactory::instanceFor(pluginId); + FeatureExtractionPluginFactory *factory = + FeatureExtractionPluginFactory::instance(); if (factory) { plugin = factory->instantiatePlugin(pluginId, rate); @@ -818,8 +809,8 @@ } else if (t.getType() == Transform::RealTimeEffect) { -// cerr << "TransformFactory::instantiateDefaultPluginFor: identifier \"" -// << identifier << "\" is a real-time transform" << endl; + SVDEBUG << "TransformFactory::instantiateDefaultPluginFor: identifier \"" + << identifier << "\" is a real-time transform" << endl; RealTimePluginFactory *factory = RealTimePluginFactory::instanceFor(pluginId); @@ -829,8 +820,8 @@ } } else { - cerr << "TransformFactory: ERROR: transform id \"" - << identifier << "\" is of unknown type" << endl; + SVDEBUG << "TransformFactory: ERROR: transform id \"" + << identifier << "\" is of unknown type" << endl; } return plugin; @@ -899,6 +890,9 @@ Transform transform; transform.setIdentifier(identifier); + SVDEBUG << "TransformFactory::getTransformInputDomain: identifier \"" + << identifier << "\"" << endl; + if (transform.getType() != Transform::FeatureExtraction) { return Vamp::Plugin::TimeDomain; } @@ -929,22 +923,7 @@ { QString id = identifier.section(':', 0, 2); - if (FeatureExtractionPluginFactory::instanceFor(id)) { - - Vamp::Plugin *plugin = - FeatureExtractionPluginFactory::instanceFor(id)-> - instantiatePlugin(id, 44100); - if (!plugin) return false; - - min = (int)plugin->getMinChannelCount(); - max = (int)plugin->getMaxChannelCount(); - delete plugin; - - return true; - - } else if (RealTimePluginFactory::instanceFor(id)) { - - // don't need to instantiate + if (RealTimePluginFactory::instanceFor(id)) { const RealTimePluginDescriptor *descriptor = RealTimePluginFactory::instanceFor(id)-> @@ -955,6 +934,17 @@ max = descriptor->audioInputPortCount; return true; + + } else { + + auto psd = FeatureExtractionPluginFactory::instance()-> + getPluginStaticData(id); + if (psd.pluginKey == "") return false; + + min = (int)psd.minChannelCount; + max = (int)psd.maxChannelCount; + + return true; } return false; @@ -1088,12 +1078,15 @@ { QString xml; + SVDEBUG << "TransformFactory::getPluginConfigurationXml: identifier \"" + << t.getIdentifier() << "\"" << endl; + Vamp::PluginBase *plugin = instantiateDefaultPluginFor (t.getIdentifier(), 0); if (!plugin) { - cerr << "TransformFactory::getPluginConfigurationXml: " - << "Unable to instantiate plugin for transform \"" - << t.getIdentifier() << "\"" << endl; + SVDEBUG << "TransformFactory::getPluginConfigurationXml: " + << "Unable to instantiate plugin for transform \"" + << t.getIdentifier() << "\"" << endl; return xml; } @@ -1110,12 +1103,15 @@ TransformFactory::setParametersFromPluginConfigurationXml(Transform &t, QString xml) { + SVDEBUG << "TransformFactory::setParametersFromPluginConfigurationXml: identifier \"" + << t.getIdentifier() << "\"" << endl; + Vamp::PluginBase *plugin = instantiateDefaultPluginFor (t.getIdentifier(), 0); if (!plugin) { - cerr << "TransformFactory::setParametersFromPluginConfigurationXml: " - << "Unable to instantiate plugin for transform \"" - << t.getIdentifier() << "\"" << endl; + SVDEBUG << "TransformFactory::setParametersFromPluginConfigurationXml: " + << "Unable to instantiate plugin for transform \"" + << t.getIdentifier() << "\"" << endl; return; } @@ -1169,14 +1165,14 @@ if (!m_uninstalledTransformsMutex.tryLock()) { // uninstalled transforms are being populated; this may take some time, // and they aren't critical, but we will speed them up if necessary - cerr << "TransformFactory::search: Uninstalled transforms mutex is held, skipping" << endl; + SVDEBUG << "TransformFactory::search: Uninstalled transforms mutex is held, skipping" << endl; m_populatingSlowly = false; return results; } if (!m_uninstalledTransformsPopulated) { - cerr << "WARNING: TransformFactory::search: Uninstalled transforms are not populated yet" << endl - << "and are not being populated either -- was the thread not started correctly?" << endl; + SVDEBUG << "WARNING: TransformFactory::search: Uninstalled transforms are not populated yet" << endl + << "and are not being populated either -- was the thread not started correctly?" << endl; m_uninstalledTransformsMutex.unlock(); return results; }
--- a/transform/TransformFactory.h Mon Nov 21 16:32:58 2016 +0000 +++ b/transform/TransformFactory.h Fri Jan 13 10:29:44 2017 +0000 @@ -195,14 +195,10 @@ */ void setParametersFromPluginConfigurationXml(Transform &transform, QString xml); - - /** - * Return any error message arising from the initial plugin - * scan. The return value will either be an empty string (nothing - * to report) or an HTML string suitable for dropping into a - * dialog and showing the user. - */ - QString getPluginPopulationWarning(); + + QString getStartupFailureReport() const { + return m_errorString; + } protected: typedef std::map<TransformId, TransformDescription> TransformDescriptionMap; @@ -213,6 +209,8 @@ TransformDescriptionMap m_uninstalledTransforms; bool m_uninstalledTransformsPopulated; + QString m_errorString; + void populateTransforms(); void populateUninstalledTransforms(); void populateFeatureExtractionPlugins(TransformDescriptionMap &);