Mercurial > hg > svcore
changeset 1130:98898331dc2a tony-2.0-integration
Merge from the default branch
author | Chris Cannam |
---|---|
date | Mon, 12 Oct 2015 13:38:24 +0100 |
parents | 815f82508f96 (diff) 1cc96e03a903 (current diff) |
children | db946591a391 |
files | |
diffstat | 58 files changed, 1934 insertions(+), 4650 deletions(-) [+] |
line wrap: on
line diff
--- a/base/PlayParameterRepository.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/base/PlayParameterRepository.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -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/PropertyContainer.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/base/PropertyContainer.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -59,6 +59,12 @@ return QString(); } +QString +PropertyContainer::getPropertyValueIconName(const PropertyName &, int) const +{ + return QString(); +} + RangeMapper * PropertyContainer::getNewPropertyRangeMapper(const PropertyName &) const {
--- a/base/PropertyContainer.h Tue Sep 01 17:05:32 2015 +0100 +++ b/base/PropertyContainer.h Mon Oct 12 13:38:24 2015 +0100 @@ -91,6 +91,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/StorageAdviser.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/base/StorageAdviser.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -36,9 +36,9 @@ size_t maximumSize) { #ifdef DEBUG_STORAGE_ADVISER - SVDEBUG << "StorageAdviser::recommend: Criteria " << criteria - << ", minimumSize " << minimumSize - << ", maximumSize " << maximumSize << endl; + cerr << "StorageAdviser::recommend: Criteria " << criteria + << ", minimumSize " << minimumSize + << ", maximumSize " << maximumSize << endl; #endif if (m_baseRecommendation != NoRecommendation) { @@ -91,7 +91,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; @@ -181,6 +181,10 @@ } } +#ifdef DEBUG_STORAGE_ADVISER + cerr << "StorageAdviser: returning recommendation " << recommendation << endl; +#endif + return Recommendation(recommendation); }
--- a/base/ZoomConstraint.h Tue Sep 01 17:05:32 2015 +0100 +++ b/base/ZoomConstraint.h Mon Oct 12 13:38:24 2015 +0100 @@ -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
--- a/base/test/test.pro Tue Sep 01 17:05:32 2015 +0100 +++ b/base/test/test.pro Mon Oct 12 13:38:24 2015 +0100 @@ -25,7 +25,7 @@ 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 + 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 LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
--- a/configure.ac Tue Sep 01 17:05:32 2015 +0100 +++ b/configure.ac Mon Oct 12 13:38:24 2015 +0100 @@ -87,7 +87,7 @@ SV_MODULE_REQUIRED([vamphostsdk],[vamp-hostsdk >= 2.5],[vamp-hostsdk/PluginLoader.h],[vamp-hostsdk],[libvamphostsdk_v_2_5_present]) 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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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 Tue Sep 01 17:05:32 2015 +0100 +++ /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.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fft/FFTapi.h Mon Oct 12 13:38:24 2015 +0100 @@ -50,5 +50,48 @@ #endif +#include <vector> +#include <complex> + +class FFTForward // with fft shift but not window +{ +public: + FFTForward(int size) : + m_size(size), + m_input((float *)fftf_malloc(size * sizeof(float))), + m_output((fftf_complex *)fftf_malloc((size/2 + 1) * sizeof(fftf_complex))), + m_plan(fftf_plan_dft_r2c_1d(size, m_input, m_output, FFTW_MEASURE)) + { } + + ~FFTForward() { + fftf_destroy_plan(m_plan); + fftf_free(m_input); + fftf_free(m_output); + } + + std::vector<std::complex<float> > process(std::vector<float> in) const { + const int hs = m_size/2; + for (int i = 0; i < hs; ++i) { + m_input[i] = in[i + hs]; + } + for (int i = 0; i < hs; ++i) { + m_input[i + hs] = in[i]; + } + fftf_execute(m_plan); + std::vector<std::complex<float> > result; + result.reserve(hs + 1); + for (int i = 0; i <= hs; ++i) { + result.push_back({ m_output[i][0], m_output[i][1] }); + } + return result; + } + +private: + int m_size; + float *m_input; + fftf_complex *m_output; + fftf_plan m_plan; +}; + #endif
--- a/data/fileio/AudioFileReader.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/AudioFileReader.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -17,15 +17,17 @@ using std::vector; -vector<SampleBlock> +vector<vector<float>> AudioFileReader::getDeInterleavedFrames(sv_frame_t start, sv_frame_t count) const { - SampleBlock interleaved = getInterleavedFrames(start, count); + vector<float> 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<vector<float>> frames(channels, vector<float>(rc, 0.f)); for (int c = 0; c < channels; ++c) { for (sv_frame_t i = 0; i < rc; ++i) {
--- a/data/fileio/AudioFileReader.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/AudioFileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -24,8 +24,6 @@ #include <vector> #include <map> -typedef std::vector<float> SampleBlock; - class AudioFileReader : public QObject { Q_OBJECT @@ -85,16 +83,14 @@ /** * 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 std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const = 0; /** * Return de-interleaved samples for count frames from index @@ -103,7 +99,7 @@ * 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<std::vector<float> > 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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/AudioFileReaderFactory.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -21,6 +21,9 @@ #include "MP3FileReader.h" #include "QuickTimeFileReader.h" #include "CoreAudioFileReader.h" +#include "AudioFileSizeEstimator.h" + +#include "base/StorageAdviser.h" #include <QString> #include <QFileInfo> @@ -98,9 +101,32 @@ AudioFileReader *reader = 0; + sv_frame_t estimatedSamples = + AudioFileSizeEstimator::estimate(source, targetRate); + + CodedAudioFileReader::CacheMode cacheMode = + CodedAudioFileReader::CacheInTemporaryFile; + + if (estimatedSamples > 0) { + size_t kb = (estimatedSamples * sizeof(float)) / 1024; + StorageAdviser::Recommendation rec = + StorageAdviser::recommend(StorageAdviser::SpeedCritical, kb, kb); + if (rec == StorageAdviser::UseMemory || + rec == StorageAdviser::PreferMemory) { + cacheMode = CodedAudioFileReader::CacheInMemory; + } + } + + CodedAudioFileReader::DecodeMode decodeMode = + (threading ? + CodedAudioFileReader::DecodeThreaded : + CodedAudioFileReader::DecodeAtOnce); + // Try to construct a preferred reader based on the extension or // MIME type. +#define CHECK(reader) if (!reader->isOK()) { delete reader; reader = 0; } + if (WavFileReader::supports(source)) { reader = new WavFileReader(source); @@ -110,163 +136,97 @@ if (reader->isOK() && (!reader->isQuicklySeekable() || normalised || + (cacheMode == CodedAudioFileReader::CacheInMemory) || (targetRate != 0 && fileRate != targetRate))) { - SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl; + SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl; delete reader; reader = new DecodingWavFileReader (source, - threading ? - DecodingWavFileReader::ResampleThreaded : - DecodingWavFileReader::ResampleAtOnce, - DecodingWavFileReader::CacheInTemporaryFile, + decodeMode, cacheMode, targetRate ? targetRate : fileRate, normalised, reporter); - if (!reader->isOK()) { - delete reader; - reader = 0; - } + CHECK(reader); } } #ifdef HAVE_OGGZ #ifdef HAVE_FISHSOUND - if (!reader) { - if (OggVorbisFileReader::supports(source)) { - reader = new OggVorbisFileReader - (source, - threading ? - OggVorbisFileReader::DecodeThreaded : - OggVorbisFileReader::DecodeAtOnce, - OggVorbisFileReader::CacheInTemporaryFile, - targetRate, - normalised, - reporter); - if (!reader->isOK()) { - delete reader; - reader = 0; - } - } + if (!reader && OggVorbisFileReader::supports(source)) { + reader = new OggVorbisFileReader + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #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()) { - delete reader; - reader = 0; - } - } + if (!reader && MP3FileReader::supports(source)) { + reader = new MP3FileReader + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(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()) { - delete reader; - reader = 0; - } - } + if (!reader && QuickTimeFileReader::supports(source)) { + reader = new QuickTimeFileReader + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #endif #ifdef HAVE_COREAUDIO - if (!reader) { - if (CoreAudioFileReader::supports(source)) { - reader = new CoreAudioFileReader - (source, - threading ? - CoreAudioFileReader::DecodeThreaded : - CoreAudioFileReader::DecodeAtOnce, - CoreAudioFileReader::CacheInTemporaryFile, - targetRate, - normalised, - reporter); - if (!reader->isOK()) { - delete reader; - reader = 0; - } - } + if (!reader && CoreAudioFileReader::supports(source)) { + reader = new CoreAudioFileReader + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #endif - + if (reader) { + // The happy case: a reader recognised the file extension & + // succeeded in opening the file + return reader; + } + // 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); - reader = new WavFileReader(source); + sv_samplerate_t fileRate = reader->getSampleRate(); - sv_samplerate_t fileRate = reader->getSampleRate(); + if (reader->isOK() && + (!reader->isQuicklySeekable() || + normalised || + (cacheMode == CodedAudioFileReader::CacheInMemory) || + (targetRate != 0 && fileRate != targetRate))) { - if (reader->isOK() && - (!reader->isQuicklySeekable() || - normalised || - (targetRate != 0 && fileRate != targetRate))) { + SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", in memory " << (cacheMode == CodedAudioFileReader::CacheInMemory) << ", creating decoding reader" << endl; - SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl; + delete reader; + reader = new DecodingWavFileReader + (source, + decodeMode, cacheMode, + targetRate ? targetRate : fileRate, + normalised, + reporter); + } - 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; - } - } + CHECK(reader); #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; - } + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #endif #endif @@ -274,76 +234,35 @@ #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; - } + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #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; - } + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #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; - } + (source, decodeMode, cacheMode, targetRate, normalised, reporter); + CHECK(reader); } #endif - if (reader) { - if (reader->isOK()) { - SVDEBUG << "AudioFileReaderFactory: Reader is OK" << endl; - 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; + if (!reader) { + cerr << "AudioFileReaderFactory::Failed to create a reader for " + << "url \"" << source.getLocation() + << "\" (content type \"" + << source.getContentType() << "\")" << endl; + return nullptr; } - - cerr << "AudioFileReaderFactory: No reader" << endl; + return reader; }
--- a/data/fileio/AudioFileReaderFactory.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/AudioFileReaderFactory.h Mon Oct 12 13:38:24 2015 +0100 @@ -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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileSizeEstimator.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -0,0 +1,108 @@ +/* -*- 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> + +//#define DEBUG_AUDIO_FILE_SIZE_ESTIMATOR 1 + +sv_frame_t +AudioFileSizeEstimator::estimate(FileSource source, + sv_samplerate_t targetRate) +{ + sv_frame_t estimate = 0; + + // 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; + estimate = samples; + } + + 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)) { +#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR + cerr << "opened file, size is " << f.size() << endl; +#endif + 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); + } + +#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR + cerr << "AudioFileSizeEstimator: for extension " << extension << ", estimate = " << estimate << endl; +#endif + } + +#ifdef DEBUG_AUDIO_FILE_SIZE_ESTIMATOR + cerr << "estimate = " << estimate << endl; +#endif + + return estimate; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileSizeEstimator.h Mon Oct 12 13:38:24 2015 +0100 @@ -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/CodedAudioFileReader.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/CodedAudioFileReader.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -21,12 +21,15 @@ #include "base/Profiler.h" #include "base/Serialiser.h" #include "base/Resampler.h" +#include "base/StorageAdviser.h" #include <stdint.h> #include <iostream> #include <QDir> #include <QMutexLocker> +using namespace std; + CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode, sv_samplerate_t targetRate, bool normalised) : @@ -57,7 +60,7 @@ QMutexLocker locker(&m_cacheMutex); endSerialised(); - + if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr); SVDEBUG << "CodedAudioFileReader::~CodedAudioFileReader: deleting cache file reader" << endl; @@ -73,6 +76,12 @@ delete m_resampler; delete[] m_resampleBuffer; + + if (!m_data.empty()) { + StorageAdviser::notifyDoneAllocation + (StorageAdviser::MemoryAllocation, + (m_data.size() * sizeof(float)) / 1024); + } } void @@ -242,7 +251,7 @@ } void -CodedAudioFileReader::addSamplesToDecodeCache(const SampleBlock &samples) +CodedAudioFileReader::addSamplesToDecodeCache(const vector<float> &samples) { QMutexLocker locker(&m_cacheMutex); @@ -292,9 +301,16 @@ 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); } } @@ -351,10 +367,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; } @@ -408,7 +422,7 @@ } } -SampleBlock +vector<float> CodedAudioFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const { // Lock is only required in CacheInMemory mode (the cache file @@ -417,10 +431,10 @@ if (!m_initialised) { SVDEBUG << "CodedAudioFileReader::getInterleavedFrames: not initialised" << endl; - return SampleBlock(); + return {}; } - SampleBlock frames; + vector<float> frames; switch (m_cacheMode) { @@ -432,22 +446,22 @@ 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 (ix1 > n) ix1 = n; + frames = vector<float>(m_data.begin() + ix0, m_data.begin() + ix1); m_dataLock.unlock(); - - frames.resize(size_t(i)); } }
--- a/data/fileio/CodedAudioFileReader.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/CodedAudioFileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -38,7 +38,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 std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const; virtual sv_samplerate_t getNativeRate() const { return m_fileRate; } @@ -60,7 +65,7 @@ // 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 std::vector<float> &interleaved); // may throw InsufficientDiscSpace: void finishDecodeCache(); @@ -78,8 +83,8 @@ protected: QMutex m_cacheMutex; CacheMode m_cacheMode; - SampleBlock m_data; - mutable QReadWriteLock m_dataLock; + std::vector<float> m_data; + mutable QMutex m_dataLock; bool m_initialised; Serialiser *m_serialiser; sv_samplerate_t m_fileRate;
--- a/data/fileio/CoreAudioFileReader.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/CoreAudioFileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/DecodingWavFileReader.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -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, @@ -56,7 +58,7 @@ initialiseDecodeCache(); - if (resampleMode == ResampleAtOnce) { + if (decodeMode == DecodeAtOnce) { if (m_reporter) { connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled())); @@ -67,7 +69,7 @@ sv_frame_t blockSize = 16384; sv_frame_t total = m_original->getFrameCount(); - SampleBlock block; + vector<float> block; for (sv_frame_t i = 0; i < total; i += blockSize) { @@ -124,7 +126,7 @@ sv_frame_t blockSize = 16384; sv_frame_t total = m_reader->m_original->getFrameCount(); - SampleBlock block; + vector<float> block; for (sv_frame_t i = 0; i < total; i += blockSize) { @@ -147,7 +149,7 @@ } void -DecodingWavFileReader::addBlock(const SampleBlock &frames) +DecodingWavFileReader::addBlock(const vector<float> &frames) { addSamplesToDecodeCache(frames); @@ -167,7 +169,7 @@ } void -DecodingWavFileReader::getSupportedExtensions(std::set<QString> &extensions) +DecodingWavFileReader::getSupportedExtensions(set<QString> &extensions) { WavFileReader::getSupportedExtensions(extensions); }
--- a/data/fileio/DecodingWavFileReader.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/DecodingWavFileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -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 std::vector<float> &frames); class DecodeThread : public Thread {
--- a/data/fileio/FileSource.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/FileSource.h Mon Oct 12 13:38:24 2015 +0100 @@ -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/MP3FileReader.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/MP3FileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -32,11 +32,6 @@ Q_OBJECT public: - enum DecodeMode { - DecodeAtOnce, // decode the file on construction, with progress - DecodeThreaded // decode in a background thread after construction - }; - MP3FileReader(FileSource source, DecodeMode decodeMode, CacheMode cacheMode,
--- a/data/fileio/OggVorbisFileReader.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/OggVorbisFileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -34,17 +34,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; }
--- a/data/fileio/WavFileReader.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/WavFileReader.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -20,6 +20,8 @@ #include <QMutexLocker> #include <QFileInfo> +using namespace std; + WavFileReader::WavFileReader(FileSource source, bool fileUpdating) : m_file(0), m_source(source), @@ -119,53 +121,56 @@ m_updating = false; } -SampleBlock +vector<float> WavFileReader::getInterleavedFrames(sv_frame_t start, sv_frame_t count) const { - if (count == 0) return SampleBlock(); + if (count == 0) return {}; QMutexLocker locker(&m_mutex); 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; } - 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) { + return m_buffer; + } + + if (sf_seek(m_file, start, SEEK_SET) < 0) { + return {}; } - return m_buffer; + vector<float> 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; @@ -196,7 +201,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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/WavFileReader.h Mon Oct 12 13:38:24 2015 +0100 @@ -50,7 +50,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 std::vector<float> getInterleavedFrames(sv_frame_t start, sv_frame_t count) const; static void getSupportedExtensions(std::set<QString> &extensions); static bool supportsExtension(QString ext); @@ -75,8 +75,7 @@ bool m_seekable; mutable QMutex m_mutex; - mutable SampleBlock m_buffer; - mutable sv_frame_t m_bufsiz; + mutable std::vector<float> m_buffer; mutable sv_frame_t m_lastStart; mutable sv_frame_t m_lastCount;
--- a/data/fileio/WavFileWriter.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/WavFileWriter.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -25,6 +25,8 @@ #include <iostream> #include <cmath> +using namespace std; + WavFileWriter::WavFileWriter(QString path, sv_samplerate_t sampleRate, int channels, @@ -129,8 +131,6 @@ } 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(); @@ -140,16 +140,17 @@ 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); + vector<float> 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]; + vector<float> 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") @@ -159,8 +160,6 @@ } } - delete[] ub; - delete[] ib; if (ownSelection) delete selection; return isOK();
--- a/data/fileio/test/test.pro Tue Sep 01 17:05:32 2015 +0100 +++ b/data/fileio/test/test.pro Mon Oct 12 13:38:24 2015 +0100 @@ -25,7 +25,7 @@ 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 + 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 LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
--- a/data/model/AggregateWaveModel.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/AggregateWaveModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -19,6 +19,8 @@ #include <QTextStream> +using namespace std; + PowerOfSqrtTwoZoomConstraint AggregateWaveModel::m_zoomConstraint; @@ -92,65 +94,56 @@ return m_components.begin()->model->getSampleRate(); } -sv_frame_t -AggregateWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const +vector<float> +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; - } - } + vector<float> result(count, 0.f); 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; + + 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()); } - 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]; - } + for (sv_frame_t i = 0; in_range_for(here, i); ++i) { + result[i] += here[i]; } } - if (mixing) delete[] readbuf; - return longest; + result.resize(longest); + return result; } -sv_frame_t +vector<vector<float>> 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<vector<float>> 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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/AggregateWaveModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -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 std::vector<float> 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<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; virtual int getSummaryBlockSize(int desired) const;
--- a/data/model/DenseTimeValueModel.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/DenseTimeValueModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/DenseTimeValueModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -57,22 +57,22 @@ /** * 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 std::vector<float> 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<std::vector<float>> 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 ""; }
--- a/data/model/FFTModel.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/FFTModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -15,7 +15,6 @@ #include "FFTModel.h" #include "DenseTimeValueModel.h" -#include "AggregateWaveModel.h" #include "base/Profiler.h" #include "base/Pitch.h" @@ -23,176 +22,62 @@ #include <algorithm> #include <cassert> +#include <deque> #ifndef __GNUC__ #include <alloca.h> #endif +using namespace std; + 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; - } - - 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; + 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"); } } 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 +89,235 @@ return name; } +FFTModel::Column +FFTModel::getColumn(int x) const +{ + auto cplx = getFFTColumn(x); + Column col; + col.reserve(int(cplx.size())); + for (auto c: cplx) col.push_back(abs(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; + for (int i = 0; i < col.size(); ++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::isColumnAvailable(int) const +{ + //!!! + return true; +} + +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::getNormalizedMagnitudesAt(int x, float *values, int minbin, int count) const +{ + if (!getMagnitudesAt(x, values, minbin, count)) return false; + if (count == 0) count = getHeight(); + float max = 0.f; + for (int i = 0; i < count; ++i) { + if (values[i] > max) max = values[i]; + } + if (max > 0.f) { + for (int i = 0; i < count; ++i) { + values[i] /= max; + } + } + 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; +} + +vector<float> +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); + vector<float> 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; + } +} + +vector<float> +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) { + return m_savedData.data; + } + + if (range.first < m_savedData.range.second && + range.first >= m_savedData.range.first && + range.second > m_savedData.range.second) { + + sv_frame_t discard = range.first - m_savedData.range.first; + + vector<float> acc(m_savedData.data.begin() + discard, + m_savedData.data.end()); + + vector<float> rest = + getSourceDataUncached({ m_savedData.range.second, range.second }); + + acc.insert(acc.end(), rest.begin(), rest.end()); + + m_savedData = { range, acc }; + return acc; + + } else { + + auto data = getSourceDataUncached(range); + m_savedData = { range, data }; + return data; + } +} + +vector<float> +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); + + // 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; +} + +vector<complex<float>> +FFTModel::getFFTColumn(int n) const +{ + for (auto &incache : m_cached) { + if (incache.n == n) { + return incache.col; + } + } + + auto samples = getSourceSamples(n); + m_windower.cut(samples.data()); + auto col = m_fft.process(samples); + + 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,17 +335,15 @@ 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; @@ -293,8 +396,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); @@ -331,8 +434,8 @@ else binmax = values.size()-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; @@ -380,11 +483,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; @@ -404,7 +506,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 +513,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 +524,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 +536,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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/FFTModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -16,24 +16,30 @@ #ifndef FFT_MODEL_H #define FFT_MODEL_H -#include "data/fft/FFTDataServer.h" #include "DenseThreeDimensionalModel.h" +#include "DenseTimeValueModel.h" + +#include "base/Window.h" + +#include "data/fft/FFTapi.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 +49,62 @@ * 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 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; } + + 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 isColumnAvailable(int x) const; + bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const; + bool getNormalizedMagnitudesAt(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 @@ -179,13 +136,6 @@ 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(); } - QString getTypeName() const { return tr("FFT"); } public slots: @@ -195,23 +145,44 @@ 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; + FFTForward 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 }; } + + std::vector<std::complex<float> > getFFTColumn(int column) const; + std::vector<float> getSourceSamples(int column) const; + std::vector<float> getSourceData(std::pair<sv_frame_t, sv_frame_t>) const; + std::vector<float> getSourceDataUncached(std::pair<sv_frame_t, sv_frame_t>) const; + + struct SavedSourceData { + std::pair<sv_frame_t, sv_frame_t> range; + std::vector<float> data; + }; + mutable SavedSourceData m_savedData; + + struct SavedColumn { + int n; + std::vector<std::complex<float> > col; + }; + mutable std::deque<SavedColumn> m_cached; + size_t m_cacheSize; }; #endif
--- a/data/model/Model.h Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/Model.h Mon Oct 12 13:38:24 2015 +0100 @@ -24,8 +24,6 @@ #include "base/BaseTypes.h" #include "base/DataExportOptions.h" -typedef std::vector<float> SampleBlock; - class ZoomConstraint; class AlignmentModel;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/ReadOnlyWaveFileModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -0,0 +1,721 @@ +/* -*- 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()) { + bool normalise = Preferences::getInstance()->getNormaliseAudio(); + m_reader = AudioFileReaderFactory::createThreadingReader + (m_source, targetRate, normalise); + 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 ""; +} + +vector<float> +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 << ", " << buffer << 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; + } + } + + vector<float> interleaved = m_reader->getInterleavedFrames(start, count); + if (channels == 1) return interleaved; + + sv_frame_t obtained = interleaved.size() / channels; + + vector<float> 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 c = 0; c < channels; ++c) { + for (int i = 0; i < obtained; ++i) { + result[i] += interleaved[i * channels + c]; + } + } + } + + return result; +} + +vector<vector<float>> +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 << ", " << buffer << 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; + } + } + + vector<float> interleaved = m_reader->getInterleavedFrames(start, count); + if (channels == 1) return { interleaved }; + + sv_frame_t obtained = interleaved.size() / channels; + vector<vector<float>> result(reqchannels, vector<float>(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; + + 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; +} + +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 = 16384; + vector<float> 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 << "ReadOnlyWaveFileModel::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 << "ReadOnlyWaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl; + + while (frame < m_frameCount) { + +// SVDEBUG << "ReadOnlyWaveFileModel::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 << "ReadOnlyWaveFileModel: 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 +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 Mon Oct 12 13:38:24 2015 +0100 @@ -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 std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const; + + virtual std::vector<std::vector<float>> 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 std::vector<float> m_directRead; + mutable sv_frame_t m_lastDirectReadStart; + mutable sv_frame_t m_lastDirectReadCount; + mutable QMutex m_directReadMutex; +}; + +#endif
--- a/data/model/WaveFileModel.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/WaveFileModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/WaveFileModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/WritableWaveFileModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -15,6 +15,8 @@ #include "WritableWaveFileModel.h" +#include "ReadOnlyWaveFileModel.h" + #include "base/TempDirectory.h" #include "base/Exceptions.h" @@ -28,6 +30,8 @@ #include <iostream> #include <stdint.h> +using namespace std; + //#define DEBUG_WRITABLE_WAVE_FILE_MODEL 1 WritableWaveFileModel::WritableWaveFileModel(sv_samplerate_t sampleRate, @@ -74,7 +78,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; @@ -169,21 +173,19 @@ return m_frameCount; } -sv_frame_t -WritableWaveFileModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const +vector<float> +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<vector<float>> 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 +217,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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/WritableWaveFileModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -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 @@ -51,6 +53,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 +76,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 std::vector<float> 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<std::vector<float>> getMultiChannelData(int fromchannel, int tochannel, sv_frame_t start, sv_frame_t count) const; virtual int getSummaryBlockSize(int desired) const; @@ -81,7 +94,7 @@ QString extraAttributes = "") const; protected: - WaveFileModel *m_model; + ReadOnlyWaveFileModel *m_model; WavFileWriter *m_writer; WavFileReader *m_reader; sv_samplerate_t m_sampleRate;
--- a/data/model/test/MockWaveModel.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/test/MockWaveModel.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -15,6 +15,8 @@ #include "MockWaveModel.h" +#include <cmath> + using namespace std; MockWaveModel::MockWaveModel(vector<Sort> sorts, int length, int pad) @@ -24,40 +26,39 @@ } } -sv_frame_t -MockWaveModel::getData(int channel, sv_frame_t start, sv_frame_t count, - float *buffer) const +vector<float> +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 << "): "; + vector<float> 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<vector<float>> 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<vector<float>> 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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/test/MockWaveModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -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 std::vector<float> getData(int channel, sv_frame_t start, sv_frame_t count) const; + virtual std::vector<std::vector<float>> 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 Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/test/TestFFTModel.h Mon Oct 12 13:38:24 2015 +0100 @@ -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); } };
--- a/data/model/test/test.pro Tue Sep 01 17:05:32 2015 +0100 +++ b/data/model/test/test.pro Mon Oct 12 13:38:24 2015 +0100 @@ -25,7 +25,7 @@ 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 + 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 LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lportaudio -lsamplerate -lz -lsord-0 -lserd-0
--- a/rdf/RDFImporter.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/rdf/RDFImporter.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -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 Tue Sep 01 17:05:32 2015 +0100 +++ b/svcore.pro Mon Oct 12 13:38:24 2015 +0100 @@ -119,15 +119,9 @@ base/XmlExportable.cpp 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/AudioFileSizeEstimator.h \ data/fileio/BZipFileDevice.h \ data/fileio/CachedFile.h \ data/fileio/CodedAudioFileReader.h \ @@ -180,16 +174,14 @@ 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 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/AudioFileSizeEstimator.cpp \ data/fileio/BZipFileDevice.cpp \ data/fileio/CachedFile.cpp \ data/fileio/CodedAudioFileReader.cpp \ @@ -224,6 +216,7 @@ 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
--- a/transform/FeatureExtractionModelTransformer.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/transform/FeatureExtractionModelTransformer.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -606,9 +606,7 @@ primaryTransform.getWindowType(), blockSize, stepSize, - blockSize, - false, - StorageAdviser::PrecisionCritical); + blockSize); if (!model->isOK() || model->getError() != "") { QString err = model->getError(); delete model; @@ -618,7 +616,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; } @@ -790,31 +787,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) {
--- a/transform/RealTimeEffectModelTransformer.cpp Tue Sep 01 17:05:32 2015 +0100 +++ b/transform/RealTimeEffectModelTransformer.cpp Mon Oct 12 13:38:24 2015 +0100 @@ -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;