# HG changeset patch # User Chris Cannam # Date 1154346598 0 # Node ID 1a42221a1522b96fc5f96c800d9e941cb0170dd0 # Parent 3a13b0d4934e2dcb04beadba28076a9b01f5cfe4 * Reorganising code base. This revision will not compile. diff -r 3a13b0d4934e -r 1a42221a1522 data/fft/FFTDataServer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTDataServer.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,751 @@ +/* -*- 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 "FFTDataServer.h" + +#include "FFTFileCache.h" + +#include "model/DenseTimeValueModel.h" + +#include "base/System.h" + +//#define DEBUG_FFT_SERVER 1 +//#define DEBUG_FFT_SERVER_FILL 1 + +#ifdef DEBUG_FFT_SERVER_FILL +#define DEBUG_FFT_SERVER +#endif + +FFTDataServer::ServerMap FFTDataServer::m_servers; +QMutex FFTDataServer::m_serverMapMutex; + +FFTDataServer * +FFTDataServer::getInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + QString n = generateFileBasename(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar); + + FFTDataServer *server = 0; + + QMutexLocker locker(&m_serverMapMutex); + + if ((server = findServer(n))) { + return server; + } + + QString npn = generateFileBasename(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + !polar); + + if ((server = findServer(npn))) { + return server; + } + + m_servers[n] = ServerCountPair + (new FFTDataServer(n, + model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn), + 1); + + return m_servers[n].first; +} + +FFTDataServer * +FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + // 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 FFTFuzzyAdapter 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 fuzzy adapter doesn't support this). + + { + QMutexLocker locker(&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 << "Distance " << distance << ", best is " << bestdist << std::endl; +#endif + + if (bestdist == -1 || distance < bestdist) { + bestdist = distance; + best = i; + } + } + } + + if (bestdist >= 0) { + ++best->second.second; + return best->second.first; + } + } + + // Nothing found, make a new one + + return getInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); +} + +FFTDataServer * +FFTDataServer::findServer(QString n) +{ + if (m_servers.find(n) != m_servers.end()) { + ++m_servers[n].second; + return m_servers[n].first; + } + + return 0; +} + +void +FFTDataServer::releaseInstance(FFTDataServer *server) +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl; +#endif + + QMutexLocker locker(&m_serverMapMutex); + + //!!! not a good strategy. Want something like: + + // -- 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 + + // also: + // + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + if (i->second.first == server) { + if (i->second.second == 0) { + std::cerr << "ERROR: FFTDataServer::releaseInstance(" + << server << "): instance not allocated" << std::endl; + } else if (--i->second.second == 0) { + if (server->m_lastUsedCache == -1) { // never used + delete server; + m_servers.erase(i); + } else { + server->suspend(); + purgeLimbo(); + } + } + return; + } + } + + std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): " + << "instance not found" << std::endl; +} + +void +FFTDataServer::purgeLimbo(int maxSize) +{ + ServerMap::iterator i = m_servers.end(); + + int count = 0; + + while (i != m_servers.begin()) { + --i; + if (i->second.second == 0) { + if (++count > maxSize) { + delete i->second.first; + m_servers.erase(i); + return; + } + } + } +} + +FFTDataServer::FFTDataServer(QString fileBaseName, + const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + 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_lastUsedCache(-1), + m_fftInput(0), + m_exiting(false), + m_fillThread(0) +{ + size_t start = m_model->getStartFrame(); + size_t end = m_model->getEndFrame(); + + m_width = (end - start) / m_windowIncrement + 1; + m_height = m_fftSize / 2; + + size_t maxCacheSize = 20 * 1024 * 1024; + size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample); + if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width; + else m_cacheWidth = maxCacheSize / columnSize; + + int bits = 0; + while (m_cacheWidth) { m_cacheWidth >>= 1; ++bits; } + m_cacheWidth = 2; + while (bits) { m_cacheWidth <<= 1; --bits; } + +#ifdef DEBUG_FFT_SERVER + std::cerr << "Width " << m_width << ", cache width " << m_cacheWidth << " (size " << m_cacheWidth * columnSize << ")" << std::endl; +#endif + + for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) { + m_caches.push_back(0); + } + + m_fftInput = (fftsample *) + fftwf_malloc(fftSize * sizeof(fftsample)); + + m_fftOutput = (fftwf_complex *) + fftwf_malloc(fftSize * sizeof(fftwf_complex)); + + m_workbuffer = (float *) + fftwf_malloc(fftSize * sizeof(float)); + + m_fftPlan = fftwf_plan_dft_r2c_1d(m_fftSize, + m_fftInput, + m_fftOutput, + FFTW_ESTIMATE); + + if (!m_fftPlan) { + std::cerr << "ERROR: fftwf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl; + throw(0); + } + + m_fillThread = new FillThread(*this, fillFromColumn); + + //!!! respond appropriately when thread exits (deleteProcessingData etc) +} + +FFTDataServer::~FFTDataServer() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << ")::~FFTDataServer()" << std::endl; +#endif + + m_exiting = true; + m_condition.wakeAll(); + if (m_fillThread) { + m_fillThread->wait(); + delete m_fillThread; + } + + QMutexLocker locker(&m_writeMutex); + + for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { + delete *i; + } + + deleteProcessingData(); +} + +void +FFTDataServer::deleteProcessingData() +{ + if (m_fftInput) { + fftwf_destroy_plan(m_fftPlan); + fftwf_free(m_fftInput); + fftwf_free(m_fftOutput); + fftwf_free(m_workbuffer); + } + m_fftInput = 0; +} + +void +FFTDataServer::suspend() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): suspend" << std::endl; +#endif + QMutexLocker locker(&m_writeMutex); + m_suspended = true; + for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { + if (*i) (*i)->suspend(); + } +} + +void +FFTDataServer::resume() +{ + m_suspended = false; + m_condition.wakeAll(); +} + +FFTCache * +FFTDataServer::getCacheAux(size_t c) +{ + QMutexLocker locker(&m_writeMutex); + + if (m_lastUsedCache == -1) { + m_fillThread->start(); + } + + if (int(c) != m_lastUsedCache) { + +// std::cerr << "switch from " << m_lastUsedCache << " to " << c << std::endl; + + for (IntQueue::iterator i = m_dormantCaches.begin(); + i != m_dormantCaches.end(); ++i) { + if (*i == c) { + m_dormantCaches.erase(i); + break; + } + } + + if (m_lastUsedCache >= 0) { + bool inDormant = false; + for (size_t i = 0; i < m_dormantCaches.size(); ++i) { + if (m_dormantCaches[i] == m_lastUsedCache) { + inDormant = true; + break; + } + } + if (!inDormant) { + m_dormantCaches.push_back(m_lastUsedCache); + } + while (m_dormantCaches.size() > 4) { + int dc = m_dormantCaches.front(); + m_dormantCaches.pop_front(); + m_caches[dc]->suspend(); + } + } + } + + if (m_caches[c]) { + m_lastUsedCache = c; + return m_caches[c]; + } + + QString name = QString("%1-%2").arg(m_fileBaseName).arg(c); + + FFTCache *cache = new FFTFileCache(name, MatrixFile::ReadWrite, + m_polar ? FFTFileCache::Polar : + FFTFileCache::Rectangular); + + size_t width = m_cacheWidth; + if (c * m_cacheWidth + width > m_width) { + width = m_width - c * m_cacheWidth; + } + + cache->resize(width, m_height); + cache->reset(); + + m_caches[c] = cache; + m_lastUsedCache = c; + + return cache; +} + +float +FFTDataServer::getMagnitudeAt(size_t x, size_t y) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getMagnitudeAt(col, y); +} + +float +FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getNormalizedMagnitudeAt(col, y); +} + +float +FFTDataServer::getMaximumMagnitudeAt(size_t x) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getMaximumMagnitudeAt(col); +} + +float +FFTDataServer::getPhaseAt(size_t x, size_t y) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getPhaseAt(col, y); +} + +void +FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl; +#endif + fillColumn(x); + } + float magnitude = cache->getMagnitudeAt(col, y); + float phase = cache->getPhaseAt(col, y); + real = magnitude * cosf(phase); + imaginary = magnitude * sinf(phase); +} + +bool +FFTDataServer::isColumnReady(size_t x) +{ + if (!haveCache(x)) { + if (m_lastUsedCache == -1) { + m_fillThread->start(); + } + return false; + } + + size_t col; + FFTCache *cache = getCache(x, col); + + return cache->haveSetColumnAt(col); +} + +void +FFTDataServer::fillColumn(size_t x) +{ + size_t col; +#ifdef DEBUG_FFT_SERVER_FILL + std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl; +#endif + FFTCache *cache = getCache(x, col); + + QMutexLocker locker(&m_writeMutex); + + if (cache->haveSetColumnAt(col)) return; + + int startFrame = m_windowIncrement * x; + int endFrame = startFrame + m_windowSize; + + startFrame -= int(m_windowSize - m_windowIncrement) / 2; + endFrame -= int(m_windowSize - m_windowIncrement) / 2; + size_t pfx = 0; + + size_t off = (m_fftSize - m_windowSize) / 2; + + for (size_t i = 0; i < off; ++i) { + m_fftInput[i] = 0.0; + m_fftInput[m_fftSize - i - 1] = 0.0; + } + + if (startFrame < 0) { + pfx = size_t(-startFrame); + for (size_t i = 0; i < pfx; ++i) { + m_fftInput[off + i] = 0.0; + } + } + + size_t got = m_model->getValues(m_channel, startFrame + pfx, + endFrame, m_fftInput + off + pfx); + + while (got + pfx < m_windowSize) { + m_fftInput[off + got + pfx] = 0.0; + ++got; + } + + if (m_channel == -1) { + int channels = m_model->getChannelCount(); + if (channels > 1) { + for (size_t i = 0; i < m_windowSize; ++i) { + m_fftInput[off + i] /= channels; + } + } + } + + m_windower.cut(m_fftInput + off); + + for (size_t i = 0; i < m_fftSize/2; ++i) { + fftsample temp = m_fftInput[i]; + m_fftInput[i] = m_fftInput[i + m_fftSize/2]; + m_fftInput[i + m_fftSize/2] = temp; + } + + fftwf_execute(m_fftPlan); + + fftsample factor = 0.0; + + for (size_t i = 0; i < m_fftSize/2; ++i) { + + fftsample mag = sqrtf(m_fftOutput[i][0] * m_fftOutput[i][0] + + m_fftOutput[i][1] * m_fftOutput[i][1]); + mag /= m_windowSize / 2; + + if (mag > factor) factor = mag; + + fftsample phase = atan2f(m_fftOutput[i][1], m_fftOutput[i][0]); + phase = princargf(phase); + + m_workbuffer[i] = mag; + m_workbuffer[i + m_fftSize/2] = phase; + } + + cache->setColumnAt(col, + m_workbuffer, + m_workbuffer + m_fftSize/2, + factor); +} + +size_t +FFTDataServer::getFillCompletion() const +{ + if (m_fillThread) return m_fillThread->getCompletion(); + else return 100; +} + +size_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, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar) +{ + char buffer[200]; + + sprintf(buffer, "%u-%u-%u-%u-%u-%u%s", + (unsigned int)XmlExportable::getObjectExportId(model), + (unsigned int)(channel + 1), + (unsigned int)windowType, + (unsigned int)windowSize, + (unsigned int)windowIncrement, + (unsigned int)fftSize, + polar ? "-p" : "-r"); + + return buffer; +} + +void +FFTDataServer::FillThread::run() +{ + m_extent = 0; + m_completion = 0; + + size_t start = m_server.m_model->getStartFrame(); + size_t end = m_server.m_model->getEndFrame(); + size_t remainingEnd = end; + + int counter = 0; + int updateAt = (end / m_server.m_windowIncrement) / 20; + if (updateAt < 100) updateAt = 100; + + if (m_fillFrom > start) { + + for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) { + + m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); + + if (m_server.m_exiting) return; + + while (m_server.m_suspended) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): suspended, waiting..." << std::endl; +#endif + m_server.m_writeMutex.lock(); + m_server.m_condition.wait(&m_server.m_writeMutex, 10000); + m_server.m_writeMutex.unlock(); + if (m_server.m_exiting) return; + } + + if (++counter == updateAt) { + m_extent = f; + m_completion = size_t(100 * fabsf(float(f - m_fillFrom) / + float(end - start))); + counter = 0; + } + } + + remainingEnd = m_fillFrom; + if (remainingEnd > start) --remainingEnd; + else remainingEnd = start; + } + + size_t baseCompletion = m_completion; + + for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) { + + m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); + + if (m_server.m_exiting) return; + + while (m_server.m_suspended) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): suspended, waiting..." << std::endl; +#endif + m_server.m_writeMutex.lock(); + m_server.m_condition.wait(&m_server.m_writeMutex, 10000); + m_server.m_writeMutex.unlock(); + if (m_server.m_exiting) return; + } + + if (++counter == updateAt) { + m_extent = f; + m_completion = baseCompletion + + size_t(100 * fabsf(float(f - start) / + float(end - start))); + counter = 0; + } + } + + m_completion = 100; + m_extent = end; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fft/FFTDataServer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTDataServer.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,201 @@ +/* -*- 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_DATA_SERVER_H_ +#define _FFT_DATA_SERVER_H_ + +#include "base/Window.h" +#include "base/Thread.h" + +#include + +#include +#include +#include + +#include +#include + +class DenseTimeValueModel; +class FFTCache; + +class FFTDataServer +{ +public: + static FFTDataServer *getInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + static FFTDataServer *getFuzzyInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + static void releaseInstance(FFTDataServer *); + + const DenseTimeValueModel *getModel() const { return m_model; } + int getChannel() const { return m_channel; } + WindowType getWindowType() const { return m_windower.getType(); } + size_t getWindowSize() const { return m_windowSize; } + size_t getWindowIncrement() const { return m_windowIncrement; } + size_t getFFTSize() const { return m_fftSize; } + bool getPolar() const { return m_polar; } + + size_t getWidth() const { return m_width; } + size_t getHeight() const { return m_height; } + + float getMagnitudeAt(size_t x, size_t y); + float getNormalizedMagnitudeAt(size_t x, size_t y); + float getMaximumMagnitudeAt(size_t x); + float getPhaseAt(size_t x, size_t y); + void getValuesAt(size_t x, size_t y, float &real, float &imaginary); + bool isColumnReady(size_t x); + + void suspend(); + + // Convenience functions: + + bool isLocalPeak(size_t x, size_t 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(size_t x, size_t y, float threshold) { + return getMagnitudeAt(x, y) > threshold; + } + + size_t getFillCompletion() const; + size_t getFillExtent() const; + +private: + FFTDataServer(QString fileBaseName, + const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 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 m_windower; + + size_t m_windowSize; + size_t m_windowIncrement; + size_t m_fftSize; + bool m_polar; + + size_t m_width; + size_t m_height; + size_t m_cacheWidth; + + typedef std::vector CacheVector; + CacheVector m_caches; + + typedef std::deque IntQueue; + IntQueue m_dormantCaches; + + int m_lastUsedCache; + FFTCache *getCache(size_t x, size_t &col) { + if (m_suspended) resume(); + col = x % m_cacheWidth; + int c = x / m_cacheWidth; + // The only use of m_lastUsedCache without a lock is to + // establish whether a cache has been created at all (they're + // created on demand, but not destroyed until the server is). + if (c == m_lastUsedCache) return m_caches[c]; + else return getCacheAux(c); + } + bool haveCache(size_t x) { + int c = x / m_cacheWidth; + if (c == m_lastUsedCache) return true; + else return (m_caches[c] != 0); + } + + FFTCache *getCacheAux(size_t c); + QMutex m_writeMutex; + QWaitCondition m_condition; + + fftsample *m_fftInput; + fftwf_complex *m_fftOutput; + float *m_workbuffer; + fftwf_plan m_fftPlan; + + class FillThread : public Thread + { + public: + FillThread(FFTDataServer &server, size_t fillFromColumn) : + m_server(server), m_extent(0), m_completion(0), + m_fillFrom(fillFromColumn) { } + + size_t getExtent() const { return m_extent; } + size_t getCompletion() const { return m_completion ? m_completion : 1; } + virtual void run(); + + protected: + FFTDataServer &m_server; + size_t m_extent; + size_t m_completion; + size_t m_fillFrom; + }; + + bool m_exiting; + bool m_suspended; + FillThread *m_fillThread; + + void deleteProcessingData(); + void fillColumn(size_t x); + void resume(); + + QString generateFileBasename() const; + static QString generateFileBasename(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar); + + typedef std::pair ServerCountPair; + typedef std::map ServerMap; + + static ServerMap m_servers; + static QMutex m_serverMapMutex; + static FFTDataServer *findServer(QString); // call with serverMapMutex held + static void purgeLimbo(int maxSize = 3); // call with serverMapMutex held +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fft/FFTFileCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTFileCache.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,288 @@ +/* -*- 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 "FFTFileCache.h" + +#include "MatrixFile.h" + +#include "base/Profiler.h" + +#include + +#include + +// 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]. + +FFTFileCache::FFTFileCache(QString fileBase, MatrixFile::Mode mode, + StorageType storageType) : + m_writebuf(0), + m_readbuf(0), + m_readbufCol(0), + m_readbufWidth(0), + m_mfc(new MatrixFile + (fileBase, mode, + storageType == Compact ? sizeof(uint16_t) : sizeof(float), + mode == MatrixFile::ReadOnly)), + m_storageType(storageType) +{ + std::cerr << "FFTFileCache: storage type is " << (storageType == Compact ? "Compact" : storageType == Polar ? "Polar" : "Rectangular") << std::endl; +} + +FFTFileCache::~FFTFileCache() +{ + if (m_readbuf) delete[] m_readbuf; + if (m_writebuf) delete[] m_writebuf; + delete m_mfc; +} + +size_t +FFTFileCache::getWidth() const +{ + return m_mfc->getWidth(); +} + +size_t +FFTFileCache::getHeight() const +{ + size_t mh = m_mfc->getHeight(); + if (mh > 0) return (mh - 1) / 2; + else return 0; +} + +void +FFTFileCache::resize(size_t width, size_t height) +{ + QMutexLocker locker(&m_writeMutex); + + m_mfc->resize(width, height * 2 + 1); + if (m_readbuf) { + delete[] m_readbuf; + m_readbuf = 0; + } + if (m_writebuf) { + delete[] m_writebuf; + } + m_writebuf = new char[(height * 2 + 1) * m_mfc->getCellSize()]; +} + +void +FFTFileCache::reset() +{ + m_mfc->reset(); +} + +float +FFTFileCache::getMagnitudeAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.0) + * getNormalizationFactor(x); + break; + + case Rectangular: + { + float real, imag; + getValuesAt(x, y, real, imag); + value = sqrtf(real * real + imag * imag); + break; + } + + case Polar: + value = getFromReadBufStandard(x, y * 2); + break; + } + + return value; +} + +float +FFTFileCache::getNormalizedMagnitudeAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = getFromReadBufCompactUnsigned(x, y * 2) / 65535.0; + break; + + default: + { + float mag = getMagnitudeAt(x, y); + float factor = getNormalizationFactor(x); + if (factor != 0) value = mag / factor; + else value = 0.f; + break; + } + } + + return value; +} + +float +FFTFileCache::getMaximumMagnitudeAt(size_t x) const +{ + return getNormalizationFactor(x); +} + +float +FFTFileCache::getPhaseAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = (getFromReadBufCompactSigned(x, y * 2 + 1) / 32767.0) * M_PI; + break; + + case Rectangular: + { + float real, imag; + getValuesAt(x, y, real, imag); + value = princargf(atan2f(imag, real)); + break; + } + + case Polar: + value = getFromReadBufStandard(x, y * 2 + 1); + break; + } + + return value; +} + +void +FFTFileCache::getValuesAt(size_t x, size_t y, float &real, float &imag) const +{ + switch (m_storageType) { + + case Rectangular: + real = getFromReadBufStandard(x, y * 2); + imag = getFromReadBufStandard(x, y * 2 + 1); + return; + + default: + float mag = getMagnitudeAt(x, y); + float phase = getPhaseAt(x, y); + real = mag * cosf(phase); + imag = mag * sinf(phase); + return; + } +} + +bool +FFTFileCache::haveSetColumnAt(size_t x) const +{ + return m_mfc->haveSetColumnAt(x); +} + +void +FFTFileCache::setColumnAt(size_t x, float *mags, float *phases, float factor) +{ + QMutexLocker locker(&m_writeMutex); + + size_t h = getHeight(); + + switch (m_storageType) { + + case Compact: + for (size_t 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 Rectangular: + for (size_t 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 Polar: + for (size_t 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; +// std::cerr << "Normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << std::endl; + + if (m_storageType == Compact) { + ((uint16_t *)m_writebuf)[h * 2] = factor * 65535.0; + } else { + ((float *)m_writebuf)[h * 2] = factor; + } + m_mfc->setColumnAt(x, m_writebuf); +} + +void +FFTFileCache::setColumnAt(size_t x, float *real, float *imag) +{ + QMutexLocker locker(&m_writeMutex); + + size_t h = getHeight(); + + float max = 0.0f; + + switch (m_storageType) { + + case Compact: + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + } + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + float phase = princargf(atan2f(imag[y], real[y])); + ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mag / max) * 65535.0); + ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phase * 32767) / M_PI)); + } + break; + + case Rectangular: + for (size_t 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 > max) max = mag; + } + break; + + case Polar: + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + ((float *)m_writebuf)[y * 2] = mag; + ((float *)m_writebuf)[y * 2 + 1] = princargf(atan2f(imag[y], real[y])); + } + break; + } + + ((float *)m_writebuf)[h * 2] = max; + m_mfc->setColumnAt(x, m_writebuf); +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fft/FFTFileCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTFileCache.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,125 @@ +/* -*- 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_FILE_CACHE_H_ +#define _FFT_FILE_CACHE_H_ + +#include "FFTCache.h" +#include "MatrixFile.h" + +#include + +class FFTFileCache : public FFTCache +{ +public: + enum StorageType { + Compact, // 16 bits normalized polar + Rectangular, // floating point real+imag + Polar, // floating point mag+phase + }; + + FFTFileCache(QString fileBase, MatrixFile::Mode mode, + StorageType storageType); + virtual ~FFTFileCache(); + + MatrixFile::Mode getMode() const { return m_mfc->getMode(); } + + virtual size_t getWidth() const; + virtual size_t getHeight() const; + + virtual void resize(size_t width, size_t height); + virtual void reset(); // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const; + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const; + virtual float getMaximumMagnitudeAt(size_t x) const; + virtual float getPhaseAt(size_t x, size_t y) const; + + virtual void getValuesAt(size_t x, size_t y, float &real, float &imag) const; + + virtual bool haveSetColumnAt(size_t x) const; + + virtual void setColumnAt(size_t x, float *mags, float *phases, float factor); + virtual void setColumnAt(size_t x, float *reals, float *imags); + + virtual void suspend() { m_mfc->suspend(); } + +protected: + char *m_writebuf; + mutable char *m_readbuf; + mutable size_t m_readbufCol; + mutable size_t m_readbufWidth; + + float getFromReadBufStandard(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((float *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufStandard(x, y); + } + } + + float getFromReadBufCompactUnsigned(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((uint16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufCompactUnsigned(x, y); + } + } + + float getFromReadBufCompactSigned(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((int16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufCompactSigned(x, y); + } + } + + void populateReadBuf(size_t x) const { + if (!m_readbuf) { + m_readbuf = new char[m_mfc->getHeight() * 2 * m_mfc->getCellSize()]; + } + 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_readbufCol = x; + } + + float getNormalizationFactor(size_t col) const { + if (m_storageType != Compact) { + return getFromReadBufStandard(col, m_mfc->getHeight() - 1); + } else { + float factor; + factor = getFromReadBufCompactUnsigned(col, m_mfc->getHeight() - 1); + return factor / 65535.0; + } + } + + MatrixFile *m_mfc; + QMutex m_writeMutex; + StorageType m_storageType; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fft/FFTFuzzyAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTFuzzyAdapter.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,72 @@ +/* -*- 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 "FFTFuzzyAdapter.h" + +#include + +FFTFuzzyAdapter::FFTFuzzyAdapter(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + m_server(0), + m_xshift(0), + m_yshift(0) +{ + m_server = FFTDataServer::getFuzzyInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); + + size_t xratio = windowIncrement / m_server->getWindowIncrement(); + size_t yratio = m_server->getFFTSize() / fftSize; + + while (xratio > 1) { + if (xratio & 0x1) { + std::cerr << "ERROR: FFTFuzzyAdapter: Window increment ratio " + << windowIncrement << " / " + << m_server->getWindowIncrement() + << " must be a power of two" << std::endl; + assert(!(xratio & 0x1)); + } + ++m_xshift; + xratio >>= 1; + } + + while (yratio > 1) { + if (yratio & 0x1) { + std::cerr << "ERROR: FFTFuzzyAdapter: FFT size ratio " + << m_server->getFFTSize() << " / " << fftSize + << " must be a power of two" << std::endl; + assert(!(yratio & 0x1)); + } + ++m_yshift; + yratio >>= 1; + } +} + +FFTFuzzyAdapter::~FFTFuzzyAdapter() +{ + FFTDataServer::releaseInstance(m_server); +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fft/FFTFuzzyAdapter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fft/FFTFuzzyAdapter.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,80 @@ +/* -*- 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_FUZZY_ADAPTER_H_ +#define _FFT_FUZZY_ADAPTER_H_ + +#include "FFTDataServer.h" + +class FFTFuzzyAdapter +{ +public: + FFTFuzzyAdapter(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + ~FFTFuzzyAdapter(); + + size_t getWidth() const { + return m_server->getWidth() >> m_xshift; + } + size_t getHeight() const { + return m_server->getHeight() >> m_yshift; + } + float getMagnitudeAt(size_t x, size_t y) { + return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getNormalizedMagnitudeAt(size_t x, size_t y) { + return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getMaximumMagnitudeAt(size_t x) { + return m_server->getMaximumMagnitudeAt(x << m_xshift); + } + float getPhaseAt(size_t x, size_t y) { + return m_server->getPhaseAt(x << m_xshift, y << m_yshift); + } + void getValuesAt(size_t x, size_t y, float &real, float &imaginary) { + m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary); + } + bool isColumnReady(size_t x) { + return m_server->isColumnReady(x << m_xshift); + } + bool isLocalPeak(size_t x, size_t 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(size_t x, size_t y, float threshold) { + return getMagnitudeAt(x, y) > threshold; + } + + size_t getFillCompletion() const { return m_server->getFillCompletion(); } + size_t getFillExtent() const { return m_server->getFillExtent(); } + +private: + FFTFuzzyAdapter(const FFTFuzzyAdapter &); // not implemented + FFTFuzzyAdapter &operator=(const FFTFuzzyAdapter &); // not implemented + + FFTDataServer *m_server; + int m_xshift; + int m_yshift; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/AudioFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,49 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This 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 _AUDIO_FILE_READER_H_ +#define _AUDIO_FILE_READER_H_ + +#include +#include "base/Model.h" // for SampleBlock + +class AudioFileReader +{ +public: + virtual ~AudioFileReader() { } + + bool isOK() const { return (m_channelCount > 0); } + + virtual QString getError() const { return ""; } + + size_t getFrameCount() const { return m_frameCount; } + size_t getChannelCount() const { return m_channelCount; } + size_t getSampleRate() const { return m_sampleRate; } + + /** + * 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 void getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const = 0; + +protected: + size_t m_frameCount; + size_t m_channelCount; + size_t m_sampleRate; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/AudioFileReaderFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReaderFactory.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,72 @@ +/* -*- 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 "AudioFileReaderFactory.h" + +#include "WavFileReader.h" +#include "OggVorbisFileReader.h" +#include "MP3FileReader.h" + +#include + +QString +AudioFileReaderFactory::getKnownExtensions() +{ + return + "*.wav *.aiff *.aif" +#ifdef HAVE_MAD + " *.mp3" +#endif +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + " *.ogg" +#endif +#endif + ; +} + +AudioFileReader * +AudioFileReaderFactory::createReader(QString path) +{ + QString err; + + AudioFileReader *reader = 0; + + reader = new WavFileReader(path); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + reader = new OggVorbisFileReader(path, true, + OggVorbisFileReader::CacheInTemporaryFile); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; +#endif +#endif + +#ifdef HAVE_MAD + reader = new MP3FileReader(path, true, + MP3FileReader::CacheInTemporaryFile); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; +#endif + + return 0; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/AudioFileReaderFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/AudioFileReaderFactory.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,43 @@ +/* -*- 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 _AUDIO_FILE_READER_FACTORY_H_ +#define _AUDIO_FILE_READER_FACTORY_H_ + +#include + +class AudioFileReader; + +class AudioFileReaderFactory +{ +public: + /** + * Return the file extensions that we have audio file readers for, + * in a format suitable for use with QFileDialog. For example, + * "*.wav *.aiff *.ogg". + */ + static QString getKnownExtensions(); + + /** + * Return an audio file reader initialised to the file at the + * given path, or NULL if no suitable reader for this path is + * available or the file cannot be opened. + * Caller owns the returned object and must delete it after use. + */ + static AudioFileReader *createReader(QString path); +}; + +#endif + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/BZipFileDevice.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/BZipFileDevice.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,195 @@ +/* -*- 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 "BZipFileDevice.h" + +#include + +#include + +BZipFileDevice::BZipFileDevice(QString fileName) : + m_fileName(fileName), + m_file(0), + m_bzFile(0), + m_atEnd(true) +{ +} + +BZipFileDevice::~BZipFileDevice() +{ +// std::cerr << "BZipFileDevice::~BZipFileDevice(" << m_fileName.toStdString() << ")" << std::endl; + if (m_bzFile) close(); +} + +bool +BZipFileDevice::open(OpenMode mode) +{ + if (m_bzFile) { + setErrorString(tr("File is already open")); + return false; + } + + if (mode & Append) { + setErrorString(tr("Append mode not supported")); + return false; + } + + if ((mode & (ReadOnly | WriteOnly)) == 0) { + setErrorString(tr("File access mode not specified")); + return false; + } + + if ((mode & ReadOnly) && (mode & WriteOnly)) { + setErrorString(tr("Read and write modes both specified")); + return false; + } + + if (mode & WriteOnly) { + + m_file = fopen(m_fileName.toLocal8Bit().data(), "wb"); + if (!m_file) { + setErrorString(tr("Failed to open file for writing")); + return false; + } + + int bzError = BZ_OK; + m_bzFile = BZ2_bzWriteOpen(&bzError, m_file, 9, 0, 0); + + if (!m_bzFile) { + fclose(m_file); + m_file = 0; + setErrorString(tr("Failed to open bzip2 stream for writing")); + return false; + } + +// std::cerr << "BZipFileDevice: opened \"" << m_fileName.toStdString() << "\" for writing" << std::endl; + + setErrorString(QString()); + setOpenMode(mode); + return true; + } + + if (mode & ReadOnly) { + + m_file = fopen(m_fileName.toLocal8Bit().data(), "rb"); + if (!m_file) { + setErrorString(tr("Failed to open file for reading")); + return false; + } + + int bzError = BZ_OK; + m_bzFile = BZ2_bzReadOpen(&bzError, m_file, 0, 0, NULL, 0); + + if (!m_bzFile) { + fclose(m_file); + m_file = 0; + setErrorString(tr("Failed to open bzip2 stream for reading")); + return false; + } + +// std::cerr << "BZipFileDevice: opened \"" << m_fileName.toStdString() << "\" for reading" << std::endl; + + m_atEnd = false; + + setErrorString(QString()); + setOpenMode(mode); + return true; + } + + setErrorString(tr("Internal error (open for neither read nor write)")); + return false; +} + +void +BZipFileDevice::close() +{ + if (!m_bzFile) { + setErrorString(tr("File not open")); + return; + } + + int bzError = BZ_OK; + + if (openMode() & WriteOnly) { + unsigned int in = 0, out = 0; + BZ2_bzWriteClose(&bzError, m_bzFile, 0, &in, &out); +// std::cerr << "Wrote bzip2 stream (in=" << in << ", out=" << out << ")" << std::endl; + if (bzError != BZ_OK) { + setErrorString(tr("bzip2 stream write close error")); + } + fclose(m_file); + m_bzFile = 0; + m_file = 0; + return; + } + + if (openMode() & ReadOnly) { + BZ2_bzReadClose(&bzError, m_bzFile); + if (bzError != BZ_OK) { + setErrorString(tr("bzip2 stream read close error")); + } + fclose(m_file); + m_bzFile = 0; + m_file = 0; + return; + } + + setErrorString(tr("Internal error (close for neither read nor write)")); + return; +} + +qint64 +BZipFileDevice::readData(char *data, qint64 maxSize) +{ + if (m_atEnd) return 0; + + int bzError = BZ_OK; + int read = BZ2_bzRead(&bzError, m_bzFile, data, maxSize); + +// std::cerr << "BZipFileDevice::readData: requested " << maxSize << ", read " << read << std::endl; + + if (bzError != BZ_OK) { + if (bzError != BZ_STREAM_END) { + std::cerr << "BZipFileDevice::readData: error condition" << std::endl; + setErrorString(tr("bzip2 stream read error")); + return -1; + } else { +// std::cerr << "BZipFileDevice::readData: reached end of file" << std::endl; + m_atEnd = true; + } + } + + return read; +} + +qint64 +BZipFileDevice::writeData(const char *data, qint64 maxSize) +{ + int bzError = BZ_OK; + BZ2_bzWrite(&bzError, m_bzFile, (void *)data, maxSize); + +// std::cerr << "BZipFileDevice::writeData: " << maxSize << " to write" << std::endl; + + if (bzError != BZ_OK) { + std::cerr << "BZipFileDevice::writeData: error condition" << std::endl; + setErrorString("bzip2 stream write error"); + return -1; + } + +// std::cerr << "BZipFileDevice::writeData: wrote " << maxSize << std::endl; + + return maxSize; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/BZipFileDevice.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/BZipFileDevice.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,47 @@ +/* -*- 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 _BZIP_FILE_DEVICE_H_ +#define _BZIP_FILE_DEVICE_H_ + +#include + +#include + +class BZipFileDevice : public QIODevice +{ + Q_OBJECT + +public: + BZipFileDevice(QString fileName); + virtual ~BZipFileDevice(); + + virtual bool open(OpenMode mode); + virtual void close(); + + virtual bool isSequential() const { return true; } + +protected: + virtual qint64 readData(char *data, qint64 maxSize); + virtual qint64 writeData(const char *data, qint64 maxSize); + + QString m_fileName; + + FILE *m_file; + BZFILE *m_bzFile; + bool m_atEnd; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/CSVFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,645 @@ +/* -*- 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 "CSVFileReader.h" + +#include "base/Model.h" +#include "base/RealTime.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/DenseThreeDimensionalModel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +CSVFileReader::CSVFileReader(QString path, size_t mainModelSampleRate) : + m_file(0), + m_mainModelSampleRate(mainModelSampleRate) +{ + m_file = new QFile(path); + bool good = false; + + if (!m_file->exists()) { + m_error = QFile::tr("File \"%1\" does not exist").arg(path); + } else if (!m_file->open(QIODevice::ReadOnly | QIODevice::Text)) { + m_error = QFile::tr("Failed to open file \"%1\"").arg(path); + } else { + good = true; + } + + if (!good) { + delete m_file; + m_file = 0; + } +} + +CSVFileReader::~CSVFileReader() +{ + std::cerr << "CSVFileReader::~CSVFileReader: file is " << m_file << std::endl; + + if (m_file) { + std::cerr << "CSVFileReader::CSVFileReader: Closing file" << std::endl; + m_file->close(); + } + delete m_file; +} + +bool +CSVFileReader::isOK() const +{ + return (m_file != 0); +} + +QString +CSVFileReader::getError() const +{ + return m_error; +} + +Model * +CSVFileReader::load() const +{ + if (!m_file) return 0; + + CSVFormatDialog *dialog = new CSVFormatDialog + (0, m_file, m_mainModelSampleRate); + + if (dialog->exec() == QDialog::Rejected) { + delete dialog; + return 0; + } + + CSVFormatDialog::ModelType modelType = dialog->getModelType(); + CSVFormatDialog::TimingType timingType = dialog->getTimingType(); + CSVFormatDialog::TimeUnits timeUnits = dialog->getTimeUnits(); + QString separator = dialog->getSeparator(); + size_t sampleRate = dialog->getSampleRate(); + size_t windowSize = dialog->getWindowSize(); + + delete dialog; + + if (timingType == CSVFormatDialog::ExplicitTiming) { + windowSize = 1; + if (timeUnits == CSVFormatDialog::TimeSeconds) { + sampleRate = m_mainModelSampleRate; + } + } + + SparseOneDimensionalModel *model1 = 0; + SparseTimeValueModel *model2 = 0; + DenseThreeDimensionalModel *model3 = 0; + Model *model = 0; + + QTextStream in(m_file); + in.seek(0); + + unsigned int warnings = 0, warnLimit = 10; + unsigned int lineno = 0; + + float min = 0.0, max = 0.0; + + size_t frameNo = 0; + + while (!in.atEnd()) { + + QString line = in.readLine().trimmed(); + if (line.startsWith("#")) continue; + + QStringList list = line.split(separator); + + if (!model) { + + switch (modelType) { + + case CSVFormatDialog::OneDimensionalModel: + model1 = new SparseOneDimensionalModel(sampleRate, windowSize); + model = model1; + break; + + case CSVFormatDialog::TwoDimensionalModel: + model2 = new SparseTimeValueModel(sampleRate, windowSize, + 0.0, 0.0, + false); + model = model2; + break; + + case CSVFormatDialog::ThreeDimensionalModel: + model3 = new DenseThreeDimensionalModel(sampleRate, windowSize, + list.size()); + model = model3; + break; + } + } + + QStringList tidyList; + QRegExp nonNumericRx("[^0-9.,+-]"); + + for (int i = 0; i < list.size(); ++i) { + + QString s(list[i].trimmed()); + + if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) { + s = s.mid(1, s.length() - 2); + } else if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) { + s = s.mid(1, s.length() - 2); + } + + if (i == 0 && timingType == CSVFormatDialog::ExplicitTiming) { + + bool ok = false; + QString numeric = s; + numeric.remove(nonNumericRx); + + if (timeUnits == CSVFormatDialog::TimeSeconds) { + + double time = numeric.toDouble(&ok); + frameNo = int(time * sampleRate + 0.00001); + + } else { + + frameNo = numeric.toInt(&ok); + + if (timeUnits == CSVFormatDialog::TimeWindows) { + frameNo *= windowSize; + } + } + + if (!ok) { + if (warnings < warnLimit) { + std::cerr << "WARNING: CSVFileReader::load: " + << "Bad time format (\"" << s.toStdString() + << "\") in data line " + << lineno << ":" << std::endl; + std::cerr << line.toStdString() << std::endl; + } else if (warnings == warnLimit) { + std::cerr << "WARNING: Too many warnings" << std::endl; + } + ++warnings; + } + } else { + tidyList.push_back(s); + } + } + + if (modelType == CSVFormatDialog::OneDimensionalModel) { + + SparseOneDimensionalModel::Point point + (frameNo, + tidyList.size() > 0 ? tidyList[tidyList.size()-1] : + QString("%1").arg(lineno)); + + model1->addPoint(point); + + } else if (modelType == CSVFormatDialog::TwoDimensionalModel) { + + SparseTimeValueModel::Point point + (frameNo, + tidyList.size() > 0 ? tidyList[0].toFloat() : 0.0, + tidyList.size() > 1 ? tidyList[1] : QString("%1").arg(lineno)); + + model2->addPoint(point); + + } else if (modelType == CSVFormatDialog::ThreeDimensionalModel) { + + DenseThreeDimensionalModel::BinValueSet values; + + for (int i = 0; i < tidyList.size(); ++i) { + + bool ok = false; + float value = list[i].toFloat(&ok); + values.push_back(value); + + if ((lineno == 0 && i == 0) || value < min) min = value; + if ((lineno == 0 && i == 0) || value > max) max = value; + + if (!ok) { + if (warnings < warnLimit) { + std::cerr << "WARNING: CSVFileReader::load: " + << "Non-numeric value in data line " << lineno + << ":" << std::endl; + std::cerr << line.toStdString() << std::endl; + ++warnings; + } else if (warnings == warnLimit) { + std::cerr << "WARNING: Too many warnings" << std::endl; + } + } + } + + std::cerr << "Setting bin values for count " << lineno << ", frame " + << frameNo << ", time " << RealTime::frame2RealTime(frameNo, sampleRate) << std::endl; + + model3->setBinValues(frameNo, values); + } + + ++lineno; + if (timingType == CSVFormatDialog::ImplicitTiming || + list.size() == 0) { + frameNo += windowSize; + } + } + + if (modelType == CSVFormatDialog::ThreeDimensionalModel) { + model3->setMinimumLevel(min); + model3->setMaximumLevel(max); + } + + return model; +} + + +CSVFormatDialog::CSVFormatDialog(QWidget *parent, QFile *file, + size_t defaultSampleRate) : + QDialog(parent), + m_modelType(OneDimensionalModel), + m_timingType(ExplicitTiming), + m_timeUnits(TimeAudioFrames), + m_separator("") +{ + setModal(true); + setWindowTitle(tr("Select Data Format")); + + (void)guessFormat(file); + + QGridLayout *layout = new QGridLayout; + + layout->addWidget(new QLabel(tr("\nPlease select the correct data format for this file.\n")), + 0, 0, 1, 4); + + layout->addWidget(new QLabel(tr("Each row specifies:")), 1, 0); + + m_modelTypeCombo = new QComboBox; + m_modelTypeCombo->addItem(tr("A point in time")); + m_modelTypeCombo->addItem(tr("A value at a time")); + m_modelTypeCombo->addItem(tr("A set of values")); + layout->addWidget(m_modelTypeCombo, 1, 1, 1, 2); + connect(m_modelTypeCombo, SIGNAL(activated(int)), + this, SLOT(modelTypeChanged(int))); + m_modelTypeCombo->setCurrentIndex(int(m_modelType)); + + layout->addWidget(new QLabel(tr("The first column contains:")), 2, 0); + + m_timingTypeCombo = new QComboBox; + m_timingTypeCombo->addItem(tr("Time, in seconds")); + m_timingTypeCombo->addItem(tr("Time, in audio sample frames")); + m_timingTypeCombo->addItem(tr("Data (rows are consecutive in time)")); + layout->addWidget(m_timingTypeCombo, 2, 1, 1, 2); + connect(m_timingTypeCombo, SIGNAL(activated(int)), + this, SLOT(timingTypeChanged(int))); + m_timingTypeCombo->setCurrentIndex(m_timingType == ExplicitTiming ? + m_timeUnits == TimeSeconds ? 0 : 1 : 2); + + m_sampleRateLabel = new QLabel(tr("Audio sample rate (Hz):")); + layout->addWidget(m_sampleRateLabel, 3, 0); + + size_t sampleRates[] = { + 8000, 11025, 12000, 22050, 24000, 32000, + 44100, 48000, 88200, 96000, 176400, 192000 + }; + + m_sampleRateCombo = new QComboBox; + m_sampleRate = defaultSampleRate; + for (size_t i = 0; i < sizeof(sampleRates) / sizeof(sampleRates[0]); ++i) { + m_sampleRateCombo->addItem(QString("%1").arg(sampleRates[i])); + if (sampleRates[i] == m_sampleRate) m_sampleRateCombo->setCurrentIndex(i); + } + m_sampleRateCombo->setEditable(true); + + layout->addWidget(m_sampleRateCombo, 3, 1); + connect(m_sampleRateCombo, SIGNAL(activated(QString)), + this, SLOT(sampleRateChanged(QString))); + connect(m_sampleRateCombo, SIGNAL(editTextChanged(QString)), + this, SLOT(sampleRateChanged(QString))); + + m_windowSizeLabel = new QLabel(tr("Frame increment between rows:")); + layout->addWidget(m_windowSizeLabel, 4, 0); + + m_windowSizeCombo = new QComboBox; + m_windowSize = 1024; + for (int i = 0; i <= 16; ++i) { + int value = 1 << i; + m_windowSizeCombo->addItem(QString("%1").arg(value)); + if (value == m_windowSize) m_windowSizeCombo->setCurrentIndex(i); + } + m_windowSizeCombo->setEditable(true); + + layout->addWidget(m_windowSizeCombo, 4, 1); + connect(m_windowSizeCombo, SIGNAL(activated(QString)), + this, SLOT(windowSizeChanged(QString))); + connect(m_windowSizeCombo, SIGNAL(editTextChanged(QString)), + this, SLOT(windowSizeChanged(QString))); + + layout->addWidget(new QLabel(tr("\nExample data from file:")), 5, 0, 1, 4); + + m_exampleWidget = new QTableWidget + (std::min(10, m_example.size()), m_maxExampleCols); + + layout->addWidget(m_exampleWidget, 6, 0, 1, 4); + layout->setColumnStretch(3, 10); + layout->setRowStretch(4, 10); + + QPushButton *ok = new QPushButton(tr("OK")); + connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + ok->setDefault(true); + + QPushButton *cancel = new QPushButton(tr("Cancel")); + connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(1); + buttonLayout->addWidget(ok); + buttonLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(layout); + mainLayout->addLayout(buttonLayout); + + setLayout(mainLayout); + + timingTypeChanged(m_timingTypeCombo->currentIndex()); +} + +CSVFormatDialog::~CSVFormatDialog() +{ +} + +void +CSVFormatDialog::populateExample() +{ + m_exampleWidget->setColumnCount + (m_timingType == ExplicitTiming ? + m_maxExampleCols - 1 : m_maxExampleCols); + + m_exampleWidget->setHorizontalHeaderLabels(QStringList()); + + for (int i = 0; i < m_example.size(); ++i) { + for (int j = 0; j < m_example[i].size(); ++j) { + + QTableWidgetItem *item = new QTableWidgetItem(m_example[i][j]); + + if (j == 0) { + if (m_timingType == ExplicitTiming) { + m_exampleWidget->setVerticalHeaderItem(i, item); + continue; + } else { + QTableWidgetItem *header = + new QTableWidgetItem(QString("%1").arg(i)); + header->setFlags(Qt::ItemIsEnabled); + m_exampleWidget->setVerticalHeaderItem(i, header); + } + } + int index = j; + if (m_timingType == ExplicitTiming) --index; + item->setFlags(Qt::ItemIsEnabled); + m_exampleWidget->setItem(i, index, item); + } + } +} + +void +CSVFormatDialog::modelTypeChanged(int type) +{ + m_modelType = (ModelType)type; + + if (m_modelType == ThreeDimensionalModel) { + // We can't load 3d models with explicit timing, because the 3d + // model is dense so we need a fixed sample increment + m_timingTypeCombo->setCurrentIndex(2); + timingTypeChanged(2); + } +} + +void +CSVFormatDialog::timingTypeChanged(int type) +{ + switch (type) { + + case 0: + m_timingType = ExplicitTiming; + m_timeUnits = TimeSeconds; + m_sampleRateCombo->setEnabled(false); + m_sampleRateLabel->setEnabled(false); + m_windowSizeCombo->setEnabled(false); + m_windowSizeLabel->setEnabled(false); + if (m_modelType == ThreeDimensionalModel) { + m_modelTypeCombo->setCurrentIndex(1); + modelTypeChanged(1); + } + break; + + case 1: + m_timingType = ExplicitTiming; + m_timeUnits = TimeAudioFrames; + m_sampleRateCombo->setEnabled(true); + m_sampleRateLabel->setEnabled(true); + m_windowSizeCombo->setEnabled(false); + m_windowSizeLabel->setEnabled(false); + if (m_modelType == ThreeDimensionalModel) { + m_modelTypeCombo->setCurrentIndex(1); + modelTypeChanged(1); + } + break; + + case 2: + m_timingType = ImplicitTiming; + m_timeUnits = TimeWindows; + m_sampleRateCombo->setEnabled(true); + m_sampleRateLabel->setEnabled(true); + m_windowSizeCombo->setEnabled(true); + m_windowSizeLabel->setEnabled(true); + break; + } + + populateExample(); +} + +void +CSVFormatDialog::sampleRateChanged(QString rateString) +{ + bool ok = false; + int sampleRate = rateString.toInt(&ok); + if (ok) m_sampleRate = sampleRate; +} + +void +CSVFormatDialog::windowSizeChanged(QString sizeString) +{ + bool ok = false; + int size = sizeString.toInt(&ok); + if (ok) m_windowSize = size; +} + +bool +CSVFormatDialog::guessFormat(QFile *file) +{ + QTextStream in(file); + in.seek(0); + + unsigned int lineno = 0; + + bool nonIncreasingPrimaries = false; + bool nonNumericPrimaries = false; + bool floatPrimaries = false; + bool variableItemCount = false; + int itemCount = 1; + int earliestNonNumericItem = -1; + + float prevPrimary = 0.0; + + m_maxExampleCols = 0; + + while (!in.atEnd()) { + + QString line = in.readLine().trimmed(); + if (line.startsWith("#")) continue; + + if (m_separator == "") { + //!!! to do: ask the user + if (line.split(",").size() >= 2) m_separator = ","; + else if (line.split("\t").size() >= 2) m_separator = "\t"; + else if (line.split("|").size() >= 2) m_separator = "|"; + else if (line.split("/").size() >= 2) m_separator = "/"; + else if (line.split(":").size() >= 2) m_separator = ":"; + else m_separator = " "; + } + + QStringList list = line.split(m_separator); + QStringList tidyList; + + for (int i = 0; i < list.size(); ++i) { + + QString s(list[i]); + bool numeric = false; + + if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) { + s = s.mid(1, s.length() - 2); + } else if (s.length() >= 2 && s.startsWith("'") && s.endsWith("'")) { + s = s.mid(1, s.length() - 2); + } else { + (void)s.toFloat(&numeric); + } + + tidyList.push_back(s); + + if (lineno == 0 || (list.size() < itemCount)) { + itemCount = list.size(); + } else { + if (itemCount != list.size()) { + variableItemCount = true; + } + } + + if (i == 0) { // primary + + if (numeric) { + + float primary = s.toFloat(); + + if (lineno > 0 && primary <= prevPrimary) { + nonIncreasingPrimaries = true; + } + + if (s.contains(".") || s.contains(",")) { + floatPrimaries = true; + } + + prevPrimary = primary; + + } else { + nonNumericPrimaries = true; + } + } else { // secondary + + if (!numeric) { + if (earliestNonNumericItem < 0 || + i < earliestNonNumericItem) { + earliestNonNumericItem = i; + } + } + } + } + + if (lineno < 10) { + m_example.push_back(tidyList); + if (lineno == 0 || tidyList.size() > m_maxExampleCols) { + m_maxExampleCols = tidyList.size(); + } + } + + ++lineno; + + if (lineno == 50) break; + } + + if (nonNumericPrimaries || nonIncreasingPrimaries) { + + // Primaries are probably not a series of times + + m_timingType = ImplicitTiming; + m_timeUnits = TimeWindows; + + if (nonNumericPrimaries) { + m_modelType = OneDimensionalModel; + } else if (itemCount == 1 || variableItemCount || + (earliestNonNumericItem != -1)) { + m_modelType = TwoDimensionalModel; + } else { + m_modelType = ThreeDimensionalModel; + } + + } else { + + // Increasing numeric primaries -- likely to be time + + m_timingType = ExplicitTiming; + + if (floatPrimaries) { + m_timeUnits = TimeSeconds; + } else { + m_timeUnits = TimeAudioFrames; + } + + if (itemCount == 1) { + m_modelType = OneDimensionalModel; + } else if (variableItemCount || (earliestNonNumericItem != -1)) { + if (earliestNonNumericItem != -1 && earliestNonNumericItem < 2) { + m_modelType = OneDimensionalModel; + } else { + m_modelType = TwoDimensionalModel; + } + } else { + m_modelType = ThreeDimensionalModel; + } + } + + std::cerr << "Estimated model type: " << m_modelType << std::endl; + std::cerr << "Estimated timing type: " << m_timingType << std::endl; + std::cerr << "Estimated units: " << m_timeUnits << std::endl; + + in.seek(0); + return true; +} diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/CSVFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,111 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This 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 _CSV_FILE_READER_H_ +#define _CSV_FILE_READER_H_ + +#include "DataFileReader.h" + +#include +#include +#include + +class QFile; +class QTableWidget; +class QComboBox; +class QLabel; + + +class CSVFileReader : public DataFileReader +{ +public: + CSVFileReader(QString path, size_t mainModelSampleRate); + virtual ~CSVFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + virtual Model *load() const; + +protected: + QFile *m_file; + QString m_error; + size_t m_mainModelSampleRate; +}; + + +class CSVFormatDialog : public QDialog +{ + Q_OBJECT + +public: + CSVFormatDialog(QWidget *parent, QFile *file, size_t defaultSampleRate); + + ~CSVFormatDialog(); + + enum ModelType { + OneDimensionalModel, + TwoDimensionalModel, + ThreeDimensionalModel + }; + + enum TimingType { + ExplicitTiming, + ImplicitTiming + }; + + enum TimeUnits { + TimeSeconds, + TimeAudioFrames, + TimeWindows + }; + + ModelType getModelType() const { return m_modelType; } + TimingType getTimingType() const { return m_timingType; } + TimeUnits getTimeUnits() const { return m_timeUnits; } + QString getSeparator() const { return m_separator; } + size_t getSampleRate() const { return m_sampleRate; } + size_t getWindowSize() const { return m_windowSize; } + +protected slots: + void modelTypeChanged(int type); + void timingTypeChanged(int type); + void sampleRateChanged(QString); + void windowSizeChanged(QString); + +protected: + ModelType m_modelType; + TimingType m_timingType; + TimeUnits m_timeUnits; + QString m_separator; + size_t m_sampleRate; + size_t m_windowSize; + + QList m_example; + int m_maxExampleCols; + QTableWidget *m_exampleWidget; + + QComboBox *m_modelTypeCombo; + QComboBox *m_timingTypeCombo; + QLabel *m_sampleRateLabel; + QComboBox *m_sampleRateCombo; + QLabel *m_windowSizeLabel; + QComboBox *m_windowSizeCombo; + + bool guessFormat(QFile *file); + void populateExample(); +}; + +#endif + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/CSVFileWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileWriter.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,66 @@ +/* -*- 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 "CSVFileWriter.h" + +#include "base/Model.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/NoteModel.h" +#include "model/TextModel.h" + +#include +#include + +CSVFileWriter::CSVFileWriter(QString path, Model *model, QString delimiter) : + m_path(path), + m_model(model), + m_error(""), + m_delimiter(delimiter) +{ +} + +CSVFileWriter::~CSVFileWriter() +{ +} + +bool +CSVFileWriter::isOK() const +{ + return m_error == ""; +} + +QString +CSVFileWriter::getError() const +{ + return m_error; +} + +void +CSVFileWriter::write() +{ + QFile file(m_path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + m_error = tr("Failed to open file %1 for writing").arg(m_path); + return; + } + + QTextStream out(&file); + out << m_model->toDelimitedDataString(m_delimiter); + + file.close(); +} + + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/CSVFileWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CSVFileWriter.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,46 @@ +/* -*- 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 _CSV_FILE_WRITER_H_ +#define _CSV_FILE_WRITER_H_ + +#include +#include + +class Model; + +class CSVFileWriter : public QObject +{ + Q_OBJECT + +public: + CSVFileWriter(QString path, Model *model, QString delimiter = ","); + virtual ~CSVFileWriter(); + + virtual bool isOK() const; + virtual QString getError() const; + + virtual void write(); + +protected: + QString m_path; + Model *m_model; + QString m_error; + QString m_delimiter; +}; + +#endif + + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/CodedAudioFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CodedAudioFileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,192 @@ +/* -*- 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 "CodedAudioFileReader.h" + +#include "WavFileReader.h" +#include "base/TempDirectory.h" +#include "base/Exceptions.h" + +#include +#include + +CodedAudioFileReader::CodedAudioFileReader(CacheMode cacheMode) : + m_cacheMode(cacheMode), + m_initialised(false), + m_cacheFileWritePtr(0), + m_cacheFileReader(0), + m_cacheWriteBuffer(0), + m_cacheWriteBufferIndex(0), + m_cacheWriteBufferSize(16384) +{ +} + +CodedAudioFileReader::~CodedAudioFileReader() +{ + if (m_cacheFileWritePtr) sf_close(m_cacheFileWritePtr); + if (m_cacheFileReader) delete m_cacheFileReader; + if (m_cacheWriteBuffer) delete[] m_cacheWriteBuffer; + + if (m_cacheFileName != "") { + if (!QFile(m_cacheFileName).remove()) { + std::cerr << "WARNING: CodedAudioFileReader::~CodedAudioFileReader: Failed to delete cache file \"" << m_cacheFileName.toStdString() << "\"" << std::endl; + } + } +} + +void +CodedAudioFileReader::initialiseDecodeCache() +{ + if (m_cacheMode == CacheInTemporaryFile) { + + m_cacheWriteBuffer = new float[m_cacheWriteBufferSize * m_channelCount]; + m_cacheWriteBufferIndex = 0; + + try { + QDir dir(TempDirectory::getInstance()->getPath()); + m_cacheFileName = dir.filePath(QString("decoded_%1.wav") + .arg((intptr_t)this)); + + SF_INFO fileInfo; + fileInfo.samplerate = m_sampleRate; + fileInfo.channels = m_channelCount; + fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + + m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(), + SFM_WRITE, &fileInfo); + + if (!m_cacheFileWritePtr) { + std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to open cache file \"" << m_cacheFileName.toStdString() << "\" (" << m_channelCount << " channels, sample rate " << m_sampleRate << " for writing, falling back to in-memory cache" << std::endl; + m_cacheMode = CacheInMemory; + } + } catch (DirectoryCreationFailed f) { + std::cerr << "CodedAudioFileReader::initialiseDecodeCache: failed to create temporary directory! Falling back to in-memory cache" << std::endl; + m_cacheMode = CacheInMemory; + } + } + + if (m_cacheMode == CacheInMemory) { + m_data.clear(); + } + + m_initialised = true; +} + +void +CodedAudioFileReader::addSampleToDecodeCache(float sample) +{ + if (!m_initialised) return; + + switch (m_cacheMode) { + + case CacheInTemporaryFile: + + m_cacheWriteBuffer[m_cacheWriteBufferIndex++] = sample; + + if (m_cacheWriteBufferIndex == + m_cacheWriteBufferSize * m_channelCount) { + + //!!! check for return value! out of disk space, etc! + sf_writef_float(m_cacheFileWritePtr, + m_cacheWriteBuffer, + m_cacheWriteBufferSize); + + m_cacheWriteBufferIndex = 0; + } + break; + + case CacheInMemory: + m_data.push_back(sample); + break; + } +} + +void +CodedAudioFileReader::finishDecodeCache() +{ + if (!m_initialised) { + std::cerr << "WARNING: CodedAudioFileReader::finishDecodeCache: Cache was never initialised!" << std::endl; + return; + } + + switch (m_cacheMode) { + + case CacheInTemporaryFile: + + if (m_cacheWriteBufferIndex > 0) { + //!!! check for return value! out of disk space, etc! + sf_writef_float(m_cacheFileWritePtr, + m_cacheWriteBuffer, + m_cacheWriteBufferIndex / m_channelCount); + } + + if (m_cacheWriteBuffer) { + delete[] m_cacheWriteBuffer; + m_cacheWriteBuffer = 0; + } + + m_cacheWriteBufferIndex = 0; + + sf_close(m_cacheFileWritePtr); + m_cacheFileWritePtr = 0; + + m_cacheFileReader = new WavFileReader(m_cacheFileName); + + if (!m_cacheFileReader->isOK()) { + std::cerr << "ERROR: CodedAudioFileReader::finishDecodeCache: Failed to construct WAV file reader for temporary file: " << m_cacheFileReader->getError().toStdString() << std::endl; + delete m_cacheFileReader; + m_cacheFileReader = 0; + } + break; + + case CacheInMemory: + // nothing to do + break; + } +} + +void +CodedAudioFileReader::getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const +{ + if (!m_initialised) return; + + switch (m_cacheMode) { + + case CacheInTemporaryFile: + if (m_cacheFileReader) { + m_cacheFileReader->getInterleavedFrames(start, count, frames); + } + break; + + case CacheInMemory: + { + frames.clear(); + if (!isOK()) return; + if (count == 0) return; + + // slownessabounds + + for (size_t i = start; i < start + count; ++i) { + for (size_t ch = 0; ch < m_channelCount; ++ch) { + size_t index = i * m_channelCount + ch; + if (index >= m_data.size()) return; + frames.push_back(m_data[index]); + } + } + } + } +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/CodedAudioFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/CodedAudioFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,58 @@ +/* -*- 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 _CODED_AUDIO_FILE_READER_H_ +#define _CODED_AUDIO_FILE_READER_H_ + +#include "AudioFileReader.h" + +#include + +class WavFileReader; + +class CodedAudioFileReader : public AudioFileReader +{ +public: + virtual ~CodedAudioFileReader(); + + enum CacheMode { + CacheInTemporaryFile, + CacheInMemory + }; + + virtual void getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const; + +protected: + CodedAudioFileReader(CacheMode cacheMode); + + void initialiseDecodeCache(); // samplerate, channels must have been set + void addSampleToDecodeCache(float sample); + void finishDecodeCache(); + bool isDecodeCacheInitialised() const { return m_initialised; } + + CacheMode m_cacheMode; + SampleBlock m_data; + bool m_initialised; + + QString m_cacheFileName; + SNDFILE *m_cacheFileWritePtr; + WavFileReader *m_cacheFileReader; + float *m_cacheWriteBuffer; + size_t m_cacheWriteBufferIndex; + size_t m_cacheWriteBufferSize; // frames +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/ConfigFile.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/ConfigFile.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,181 @@ +/* -*- 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 "ConfigFile.h" + +#include "base/Exceptions.h" + +#include + +#include +#include +#include +#include + +ConfigFile::ConfigFile(QString filename) : + m_filename(filename), + m_loaded(false), + m_modified(false) +{ +} + +ConfigFile::~ConfigFile() +{ + try { + commit(); + } catch (FileOperationFailed f) { + std::cerr << "WARNING: ConfigFile::~ConfigFile: Commit failed for " + << m_filename.toStdString() << std::endl; + } +} + +QString +ConfigFile::get(QString key, QString deft) +{ + if (!m_loaded) load(); + + QMutexLocker locker(&m_mutex); + + if (m_data.find(key) == m_data.end()) return deft; + return m_data[key]; +} + +int +ConfigFile::getInt(QString key, int deft) +{ + return get(key, QString("%1").arg(deft)).toInt(); +} + +bool +ConfigFile::getBool(QString key, bool deft) +{ + QString value = get(key, deft ? "true" : "false").trimmed().toLower(); + return (value == "true" || value == "yes" || value == "on" || value == "1"); +} + +float +ConfigFile::getFloat(QString key, float deft) +{ + return get(key, QString("%1").arg(deft)).toFloat(); +} + +QStringList +ConfigFile::getStringList(QString key) +{ + return get(key).split('|'); +} + +void +ConfigFile::set(QString key, QString value) +{ + if (!m_loaded) load(); + + QMutexLocker locker(&m_mutex); + + m_data[key] = value; + + m_modified = true; +} + +void +ConfigFile::set(QString key, int value) +{ + set(key, QString("%1").arg(value)); +} + +void +ConfigFile::set(QString key, bool value) +{ + set(key, value ? QString("true") : QString("false")); +} + +void +ConfigFile::set(QString key, float value) +{ + set(key, QString("%1").arg(value)); +} + +void +ConfigFile::set(QString key, const QStringList &values) +{ + set(key, values.join("|")); +} + +void +ConfigFile::commit() +{ + QMutexLocker locker(&m_mutex); + + if (!m_modified) return; + + // Really we should write to another file and then move to the + // intended target, but I don't think we're all that particular + // about reliability here at the moment + + QFile file(m_filename); + + if (!file.open(QFile::WriteOnly | QFile::Text)) { + throw FileOperationFailed(m_filename, "open for writing"); + } + + QTextStream out(&file); + + for (DataMap::const_iterator i = m_data.begin(); i != m_data.end(); ++i) { + out << i->first << "=" << i->second << endl; + } + + m_modified = false; +} + +bool +ConfigFile::load() +{ + QMutexLocker locker(&m_mutex); + + if (m_loaded) return true; + + QFile file(m_filename); + + if (!file.open(QFile::ReadOnly | QFile::Text)) { + return false; + } + + QTextStream in(&file); + + m_data.clear(); + + while (!in.atEnd()) { + + QString line = in.readLine(2048); + QString key = line.section('=', 0, 0); + QString value = line.section('=', 1, -1); + if (key == "") continue; + + m_data[key] = value; + } + + m_loaded = true; + m_modified = false; + return true; +} + +void +ConfigFile::reset() +{ + QMutexLocker locker(&m_mutex); + m_loaded = false; + m_modified = false; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/ConfigFile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/ConfigFile.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,89 @@ +/* -*- 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 _CONFIG_FILE_H_ +#define _CONFIG_FILE_H_ + +#include +#include + +#include + +class ConfigFile +{ +public: + ConfigFile(QString filename); + virtual ~ConfigFile(); + + /** + * Get a value, with a default if it hasn't been set. + */ + QString get(QString key, QString deft = ""); + + bool getBool(QString key, bool deft); + + int getInt(QString key, int deft); + + float getFloat(QString key, float deft); + + QStringList getStringList(QString key); + + /** + * Set a value. Values must not contain carriage return or other + * non-printable characters. Keys must contain [a-zA-Z0-9_-] only. + */ + void set(QString key, QString value); + + void set(QString key, bool value); + + void set(QString key, int value); + + void set(QString key, float value); + + void set(QString key, const QStringList &values); // must not contain '|' + + /** + * Write the data to file. May throw FileOperationFailed. + * + * This is called automatically on destruction if any data has + * changed since it was last called. At that time, any exception + * will be ignored. If you want to ensure that exceptions are + * handled, call it yourself before destruction. + */ + void commit(); + + /** + * Return to the stored values. You can also call this before + * destruction if you want to ensure that any values modified so + * far are not written out to file on destruction. + */ + void reset(); + +protected: + bool load(); + + QString m_filename; + + typedef std::map DataMap; + DataMap m_data; + + bool m_loaded; + bool m_modified; + + QMutex m_mutex; +}; + +#endif + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/DataFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/DataFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,58 @@ +/* -*- 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 _DATA_FILE_READER_H_ +#define _DATA_FILE_READER_H_ + +#include + +class Model; + +class DataFileReader +{ +public: + /** + * Return true if the file appears to be of the correct type. + * + * The DataFileReader will be constructed by passing a file path + * to its constructor. If the file can at that time be determined + * to be not of a type that this reader can read, it should return + * false in response to any subsequent call to isOK(). + * + * If the file is apparently of the correct type, isOK() should + * return true; if it turns out that the file cannot after all be + * read (because it's corrupted or the detection misfired), then + * the read() function may return NULL. + */ + virtual bool isOK() const = 0; + + virtual QString getError() const { return ""; } + + /** + * Read the file and return the corresponding data model. This + * function is not expected to be thread-safe or reentrant. This + * function may be interactive (i.e. it's permitted to pop up + * dialogs and windows and ask the user to specify any details + * that can't be automatically extracted from the file). + * + * Return NULL if the file cannot be parsed at all (although it's + * preferable to return a partial model and warn the user). + * + * Caller owns the returned model and must delete it after use. + */ + virtual Model *load() const = 0; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/DataFileReaderFactory.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/DataFileReaderFactory.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,61 @@ +/* -*- 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 "DataFileReaderFactory.h" +#include "MIDIFileReader.h" +#include "CSVFileReader.h" + +#include "base/Model.h" + +#include + +QString +DataFileReaderFactory::getKnownExtensions() +{ + return "*.svl *.csv *.lab *.mid *.txt"; +} + +DataFileReader * +DataFileReaderFactory::createReader(QString path, size_t mainModelSampleRate) +{ + QString err; + + DataFileReader *reader = 0; + + reader = new MIDIFileReader(path, mainModelSampleRate); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; + + reader = new CSVFileReader(path, mainModelSampleRate); + if (reader->isOK()) return reader; + if (reader->getError() != "") err = reader->getError(); + delete reader; + + return 0; +} + +Model * +DataFileReaderFactory::load(QString path, size_t mainModelSampleRate) +{ + DataFileReader *reader = createReader(path, mainModelSampleRate); + if (!reader) return NULL; + + Model *model = reader->load(); + delete reader; + + return model; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/DataFileReaderFactory.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/DataFileReaderFactory.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,51 @@ +/* -*- 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 _DATA_FILE_READER_FACTORY_H_ +#define _DATA_FILE_READER_FACTORY_H_ + +#include + +class DataFileReader; +class Model; + +class DataFileReaderFactory +{ +public: + /** + * Return the file extensions that we have data file readers for, + * in a format suitable for use with QFileDialog. For example, + * "*.csv *.xml". + */ + static QString getKnownExtensions(); + + /** + * Return a data file reader initialised to the file at the + * given path, or NULL if no suitable reader for this path is + * available or the file cannot be opened. + * Caller owns the returned object and must delete it after use. + */ + static DataFileReader *createReader(QString path, + size_t mainModelSampleRate); + + /** + * Read the given path, if a suitable reader is available. + * Return NULL if no reader succeeded in reading this file. + */ + static Model *load(QString path, size_t mainModelSampleRate); +}; + +#endif + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FFTDataServer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTDataServer.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,751 @@ +/* -*- 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 "FFTDataServer.h" + +#include "FFTFileCache.h" + +#include "model/DenseTimeValueModel.h" + +#include "base/System.h" + +//#define DEBUG_FFT_SERVER 1 +//#define DEBUG_FFT_SERVER_FILL 1 + +#ifdef DEBUG_FFT_SERVER_FILL +#define DEBUG_FFT_SERVER +#endif + +FFTDataServer::ServerMap FFTDataServer::m_servers; +QMutex FFTDataServer::m_serverMapMutex; + +FFTDataServer * +FFTDataServer::getInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + QString n = generateFileBasename(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar); + + FFTDataServer *server = 0; + + QMutexLocker locker(&m_serverMapMutex); + + if ((server = findServer(n))) { + return server; + } + + QString npn = generateFileBasename(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + !polar); + + if ((server = findServer(npn))) { + return server; + } + + m_servers[n] = ServerCountPair + (new FFTDataServer(n, + model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn), + 1); + + return m_servers[n].first; +} + +FFTDataServer * +FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) +{ + // 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 FFTFuzzyAdapter 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 fuzzy adapter doesn't support this). + + { + QMutexLocker locker(&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 << "Distance " << distance << ", best is " << bestdist << std::endl; +#endif + + if (bestdist == -1 || distance < bestdist) { + bestdist = distance; + best = i; + } + } + } + + if (bestdist >= 0) { + ++best->second.second; + return best->second.first; + } + } + + // Nothing found, make a new one + + return getInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); +} + +FFTDataServer * +FFTDataServer::findServer(QString n) +{ + if (m_servers.find(n) != m_servers.end()) { + ++m_servers[n].second; + return m_servers[n].first; + } + + return 0; +} + +void +FFTDataServer::releaseInstance(FFTDataServer *server) +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl; +#endif + + QMutexLocker locker(&m_serverMapMutex); + + //!!! not a good strategy. Want something like: + + // -- 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 + + // also: + // + + for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { + if (i->second.first == server) { + if (i->second.second == 0) { + std::cerr << "ERROR: FFTDataServer::releaseInstance(" + << server << "): instance not allocated" << std::endl; + } else if (--i->second.second == 0) { + if (server->m_lastUsedCache == -1) { // never used + delete server; + m_servers.erase(i); + } else { + server->suspend(); + purgeLimbo(); + } + } + return; + } + } + + std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): " + << "instance not found" << std::endl; +} + +void +FFTDataServer::purgeLimbo(int maxSize) +{ + ServerMap::iterator i = m_servers.end(); + + int count = 0; + + while (i != m_servers.begin()) { + --i; + if (i->second.second == 0) { + if (++count > maxSize) { + delete i->second.first; + m_servers.erase(i); + return; + } + } + } +} + +FFTDataServer::FFTDataServer(QString fileBaseName, + const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + 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_lastUsedCache(-1), + m_fftInput(0), + m_exiting(false), + m_fillThread(0) +{ + size_t start = m_model->getStartFrame(); + size_t end = m_model->getEndFrame(); + + m_width = (end - start) / m_windowIncrement + 1; + m_height = m_fftSize / 2; + + size_t maxCacheSize = 20 * 1024 * 1024; + size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample); + if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width; + else m_cacheWidth = maxCacheSize / columnSize; + + int bits = 0; + while (m_cacheWidth) { m_cacheWidth >>= 1; ++bits; } + m_cacheWidth = 2; + while (bits) { m_cacheWidth <<= 1; --bits; } + +#ifdef DEBUG_FFT_SERVER + std::cerr << "Width " << m_width << ", cache width " << m_cacheWidth << " (size " << m_cacheWidth * columnSize << ")" << std::endl; +#endif + + for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) { + m_caches.push_back(0); + } + + m_fftInput = (fftsample *) + fftwf_malloc(fftSize * sizeof(fftsample)); + + m_fftOutput = (fftwf_complex *) + fftwf_malloc(fftSize * sizeof(fftwf_complex)); + + m_workbuffer = (float *) + fftwf_malloc(fftSize * sizeof(float)); + + m_fftPlan = fftwf_plan_dft_r2c_1d(m_fftSize, + m_fftInput, + m_fftOutput, + FFTW_ESTIMATE); + + if (!m_fftPlan) { + std::cerr << "ERROR: fftwf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl; + throw(0); + } + + m_fillThread = new FillThread(*this, fillFromColumn); + + //!!! respond appropriately when thread exits (deleteProcessingData etc) +} + +FFTDataServer::~FFTDataServer() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << ")::~FFTDataServer()" << std::endl; +#endif + + m_exiting = true; + m_condition.wakeAll(); + if (m_fillThread) { + m_fillThread->wait(); + delete m_fillThread; + } + + QMutexLocker locker(&m_writeMutex); + + for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { + delete *i; + } + + deleteProcessingData(); +} + +void +FFTDataServer::deleteProcessingData() +{ + if (m_fftInput) { + fftwf_destroy_plan(m_fftPlan); + fftwf_free(m_fftInput); + fftwf_free(m_fftOutput); + fftwf_free(m_workbuffer); + } + m_fftInput = 0; +} + +void +FFTDataServer::suspend() +{ +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): suspend" << std::endl; +#endif + QMutexLocker locker(&m_writeMutex); + m_suspended = true; + for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { + if (*i) (*i)->suspend(); + } +} + +void +FFTDataServer::resume() +{ + m_suspended = false; + m_condition.wakeAll(); +} + +FFTCache * +FFTDataServer::getCacheAux(size_t c) +{ + QMutexLocker locker(&m_writeMutex); + + if (m_lastUsedCache == -1) { + m_fillThread->start(); + } + + if (int(c) != m_lastUsedCache) { + +// std::cerr << "switch from " << m_lastUsedCache << " to " << c << std::endl; + + for (IntQueue::iterator i = m_dormantCaches.begin(); + i != m_dormantCaches.end(); ++i) { + if (*i == c) { + m_dormantCaches.erase(i); + break; + } + } + + if (m_lastUsedCache >= 0) { + bool inDormant = false; + for (size_t i = 0; i < m_dormantCaches.size(); ++i) { + if (m_dormantCaches[i] == m_lastUsedCache) { + inDormant = true; + break; + } + } + if (!inDormant) { + m_dormantCaches.push_back(m_lastUsedCache); + } + while (m_dormantCaches.size() > 4) { + int dc = m_dormantCaches.front(); + m_dormantCaches.pop_front(); + m_caches[dc]->suspend(); + } + } + } + + if (m_caches[c]) { + m_lastUsedCache = c; + return m_caches[c]; + } + + QString name = QString("%1-%2").arg(m_fileBaseName).arg(c); + + FFTCache *cache = new FFTFileCache(name, MatrixFile::ReadWrite, + m_polar ? FFTFileCache::Polar : + FFTFileCache::Rectangular); + + size_t width = m_cacheWidth; + if (c * m_cacheWidth + width > m_width) { + width = m_width - c * m_cacheWidth; + } + + cache->resize(width, m_height); + cache->reset(); + + m_caches[c] = cache; + m_lastUsedCache = c; + + return cache; +} + +float +FFTDataServer::getMagnitudeAt(size_t x, size_t y) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getMagnitudeAt(col, y); +} + +float +FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getNormalizedMagnitudeAt(col, y); +} + +float +FFTDataServer::getMaximumMagnitudeAt(size_t x) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getMaximumMagnitudeAt(col); +} + +float +FFTDataServer::getPhaseAt(size_t x, size_t y) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { + fillColumn(x); + } + return cache->getPhaseAt(col, y); +} + +void +FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary) +{ + size_t col; + FFTCache *cache = getCache(x, col); + + if (!cache->haveSetColumnAt(col)) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl; +#endif + fillColumn(x); + } + float magnitude = cache->getMagnitudeAt(col, y); + float phase = cache->getPhaseAt(col, y); + real = magnitude * cosf(phase); + imaginary = magnitude * sinf(phase); +} + +bool +FFTDataServer::isColumnReady(size_t x) +{ + if (!haveCache(x)) { + if (m_lastUsedCache == -1) { + m_fillThread->start(); + } + return false; + } + + size_t col; + FFTCache *cache = getCache(x, col); + + return cache->haveSetColumnAt(col); +} + +void +FFTDataServer::fillColumn(size_t x) +{ + size_t col; +#ifdef DEBUG_FFT_SERVER_FILL + std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl; +#endif + FFTCache *cache = getCache(x, col); + + QMutexLocker locker(&m_writeMutex); + + if (cache->haveSetColumnAt(col)) return; + + int startFrame = m_windowIncrement * x; + int endFrame = startFrame + m_windowSize; + + startFrame -= int(m_windowSize - m_windowIncrement) / 2; + endFrame -= int(m_windowSize - m_windowIncrement) / 2; + size_t pfx = 0; + + size_t off = (m_fftSize - m_windowSize) / 2; + + for (size_t i = 0; i < off; ++i) { + m_fftInput[i] = 0.0; + m_fftInput[m_fftSize - i - 1] = 0.0; + } + + if (startFrame < 0) { + pfx = size_t(-startFrame); + for (size_t i = 0; i < pfx; ++i) { + m_fftInput[off + i] = 0.0; + } + } + + size_t got = m_model->getValues(m_channel, startFrame + pfx, + endFrame, m_fftInput + off + pfx); + + while (got + pfx < m_windowSize) { + m_fftInput[off + got + pfx] = 0.0; + ++got; + } + + if (m_channel == -1) { + int channels = m_model->getChannelCount(); + if (channels > 1) { + for (size_t i = 0; i < m_windowSize; ++i) { + m_fftInput[off + i] /= channels; + } + } + } + + m_windower.cut(m_fftInput + off); + + for (size_t i = 0; i < m_fftSize/2; ++i) { + fftsample temp = m_fftInput[i]; + m_fftInput[i] = m_fftInput[i + m_fftSize/2]; + m_fftInput[i + m_fftSize/2] = temp; + } + + fftwf_execute(m_fftPlan); + + fftsample factor = 0.0; + + for (size_t i = 0; i < m_fftSize/2; ++i) { + + fftsample mag = sqrtf(m_fftOutput[i][0] * m_fftOutput[i][0] + + m_fftOutput[i][1] * m_fftOutput[i][1]); + mag /= m_windowSize / 2; + + if (mag > factor) factor = mag; + + fftsample phase = atan2f(m_fftOutput[i][1], m_fftOutput[i][0]); + phase = princargf(phase); + + m_workbuffer[i] = mag; + m_workbuffer[i + m_fftSize/2] = phase; + } + + cache->setColumnAt(col, + m_workbuffer, + m_workbuffer + m_fftSize/2, + factor); +} + +size_t +FFTDataServer::getFillCompletion() const +{ + if (m_fillThread) return m_fillThread->getCompletion(); + else return 100; +} + +size_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, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar) +{ + char buffer[200]; + + sprintf(buffer, "%u-%u-%u-%u-%u-%u%s", + (unsigned int)XmlExportable::getObjectExportId(model), + (unsigned int)(channel + 1), + (unsigned int)windowType, + (unsigned int)windowSize, + (unsigned int)windowIncrement, + (unsigned int)fftSize, + polar ? "-p" : "-r"); + + return buffer; +} + +void +FFTDataServer::FillThread::run() +{ + m_extent = 0; + m_completion = 0; + + size_t start = m_server.m_model->getStartFrame(); + size_t end = m_server.m_model->getEndFrame(); + size_t remainingEnd = end; + + int counter = 0; + int updateAt = (end / m_server.m_windowIncrement) / 20; + if (updateAt < 100) updateAt = 100; + + if (m_fillFrom > start) { + + for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) { + + m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); + + if (m_server.m_exiting) return; + + while (m_server.m_suspended) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): suspended, waiting..." << std::endl; +#endif + m_server.m_writeMutex.lock(); + m_server.m_condition.wait(&m_server.m_writeMutex, 10000); + m_server.m_writeMutex.unlock(); + if (m_server.m_exiting) return; + } + + if (++counter == updateAt) { + m_extent = f; + m_completion = size_t(100 * fabsf(float(f - m_fillFrom) / + float(end - start))); + counter = 0; + } + } + + remainingEnd = m_fillFrom; + if (remainingEnd > start) --remainingEnd; + else remainingEnd = start; + } + + size_t baseCompletion = m_completion; + + for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) { + + m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); + + if (m_server.m_exiting) return; + + while (m_server.m_suspended) { +#ifdef DEBUG_FFT_SERVER + std::cerr << "FFTDataServer(" << this << "): suspended, waiting..." << std::endl; +#endif + m_server.m_writeMutex.lock(); + m_server.m_condition.wait(&m_server.m_writeMutex, 10000); + m_server.m_writeMutex.unlock(); + if (m_server.m_exiting) return; + } + + if (++counter == updateAt) { + m_extent = f; + m_completion = baseCompletion + + size_t(100 * fabsf(float(f - start) / + float(end - start))); + counter = 0; + } + } + + m_completion = 100; + m_extent = end; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FFTDataServer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTDataServer.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,201 @@ +/* -*- 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_DATA_SERVER_H_ +#define _FFT_DATA_SERVER_H_ + +#include "base/Window.h" +#include "base/Thread.h" + +#include + +#include +#include +#include + +#include +#include + +class DenseTimeValueModel; +class FFTCache; + +class FFTDataServer +{ +public: + static FFTDataServer *getInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + static FFTDataServer *getFuzzyInstance(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + + static void releaseInstance(FFTDataServer *); + + const DenseTimeValueModel *getModel() const { return m_model; } + int getChannel() const { return m_channel; } + WindowType getWindowType() const { return m_windower.getType(); } + size_t getWindowSize() const { return m_windowSize; } + size_t getWindowIncrement() const { return m_windowIncrement; } + size_t getFFTSize() const { return m_fftSize; } + bool getPolar() const { return m_polar; } + + size_t getWidth() const { return m_width; } + size_t getHeight() const { return m_height; } + + float getMagnitudeAt(size_t x, size_t y); + float getNormalizedMagnitudeAt(size_t x, size_t y); + float getMaximumMagnitudeAt(size_t x); + float getPhaseAt(size_t x, size_t y); + void getValuesAt(size_t x, size_t y, float &real, float &imaginary); + bool isColumnReady(size_t x); + + void suspend(); + + // Convenience functions: + + bool isLocalPeak(size_t x, size_t 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(size_t x, size_t y, float threshold) { + return getMagnitudeAt(x, y) > threshold; + } + + size_t getFillCompletion() const; + size_t getFillExtent() const; + +private: + FFTDataServer(QString fileBaseName, + const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 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 m_windower; + + size_t m_windowSize; + size_t m_windowIncrement; + size_t m_fftSize; + bool m_polar; + + size_t m_width; + size_t m_height; + size_t m_cacheWidth; + + typedef std::vector CacheVector; + CacheVector m_caches; + + typedef std::deque IntQueue; + IntQueue m_dormantCaches; + + int m_lastUsedCache; + FFTCache *getCache(size_t x, size_t &col) { + if (m_suspended) resume(); + col = x % m_cacheWidth; + int c = x / m_cacheWidth; + // The only use of m_lastUsedCache without a lock is to + // establish whether a cache has been created at all (they're + // created on demand, but not destroyed until the server is). + if (c == m_lastUsedCache) return m_caches[c]; + else return getCacheAux(c); + } + bool haveCache(size_t x) { + int c = x / m_cacheWidth; + if (c == m_lastUsedCache) return true; + else return (m_caches[c] != 0); + } + + FFTCache *getCacheAux(size_t c); + QMutex m_writeMutex; + QWaitCondition m_condition; + + fftsample *m_fftInput; + fftwf_complex *m_fftOutput; + float *m_workbuffer; + fftwf_plan m_fftPlan; + + class FillThread : public Thread + { + public: + FillThread(FFTDataServer &server, size_t fillFromColumn) : + m_server(server), m_extent(0), m_completion(0), + m_fillFrom(fillFromColumn) { } + + size_t getExtent() const { return m_extent; } + size_t getCompletion() const { return m_completion ? m_completion : 1; } + virtual void run(); + + protected: + FFTDataServer &m_server; + size_t m_extent; + size_t m_completion; + size_t m_fillFrom; + }; + + bool m_exiting; + bool m_suspended; + FillThread *m_fillThread; + + void deleteProcessingData(); + void fillColumn(size_t x); + void resume(); + + QString generateFileBasename() const; + static QString generateFileBasename(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar); + + typedef std::pair ServerCountPair; + typedef std::map ServerMap; + + static ServerMap m_servers; + static QMutex m_serverMapMutex; + static FFTDataServer *findServer(QString); // call with serverMapMutex held + static void purgeLimbo(int maxSize = 3); // call with serverMapMutex held +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FFTFileCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTFileCache.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,288 @@ +/* -*- 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 "FFTFileCache.h" + +#include "MatrixFile.h" + +#include "base/Profiler.h" + +#include + +#include + +// 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]. + +FFTFileCache::FFTFileCache(QString fileBase, MatrixFile::Mode mode, + StorageType storageType) : + m_writebuf(0), + m_readbuf(0), + m_readbufCol(0), + m_readbufWidth(0), + m_mfc(new MatrixFile + (fileBase, mode, + storageType == Compact ? sizeof(uint16_t) : sizeof(float), + mode == MatrixFile::ReadOnly)), + m_storageType(storageType) +{ + std::cerr << "FFTFileCache: storage type is " << (storageType == Compact ? "Compact" : storageType == Polar ? "Polar" : "Rectangular") << std::endl; +} + +FFTFileCache::~FFTFileCache() +{ + if (m_readbuf) delete[] m_readbuf; + if (m_writebuf) delete[] m_writebuf; + delete m_mfc; +} + +size_t +FFTFileCache::getWidth() const +{ + return m_mfc->getWidth(); +} + +size_t +FFTFileCache::getHeight() const +{ + size_t mh = m_mfc->getHeight(); + if (mh > 0) return (mh - 1) / 2; + else return 0; +} + +void +FFTFileCache::resize(size_t width, size_t height) +{ + QMutexLocker locker(&m_writeMutex); + + m_mfc->resize(width, height * 2 + 1); + if (m_readbuf) { + delete[] m_readbuf; + m_readbuf = 0; + } + if (m_writebuf) { + delete[] m_writebuf; + } + m_writebuf = new char[(height * 2 + 1) * m_mfc->getCellSize()]; +} + +void +FFTFileCache::reset() +{ + m_mfc->reset(); +} + +float +FFTFileCache::getMagnitudeAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = (getFromReadBufCompactUnsigned(x, y * 2) / 65535.0) + * getNormalizationFactor(x); + break; + + case Rectangular: + { + float real, imag; + getValuesAt(x, y, real, imag); + value = sqrtf(real * real + imag * imag); + break; + } + + case Polar: + value = getFromReadBufStandard(x, y * 2); + break; + } + + return value; +} + +float +FFTFileCache::getNormalizedMagnitudeAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = getFromReadBufCompactUnsigned(x, y * 2) / 65535.0; + break; + + default: + { + float mag = getMagnitudeAt(x, y); + float factor = getNormalizationFactor(x); + if (factor != 0) value = mag / factor; + else value = 0.f; + break; + } + } + + return value; +} + +float +FFTFileCache::getMaximumMagnitudeAt(size_t x) const +{ + return getNormalizationFactor(x); +} + +float +FFTFileCache::getPhaseAt(size_t x, size_t y) const +{ + float value = 0.f; + + switch (m_storageType) { + + case Compact: + value = (getFromReadBufCompactSigned(x, y * 2 + 1) / 32767.0) * M_PI; + break; + + case Rectangular: + { + float real, imag; + getValuesAt(x, y, real, imag); + value = princargf(atan2f(imag, real)); + break; + } + + case Polar: + value = getFromReadBufStandard(x, y * 2 + 1); + break; + } + + return value; +} + +void +FFTFileCache::getValuesAt(size_t x, size_t y, float &real, float &imag) const +{ + switch (m_storageType) { + + case Rectangular: + real = getFromReadBufStandard(x, y * 2); + imag = getFromReadBufStandard(x, y * 2 + 1); + return; + + default: + float mag = getMagnitudeAt(x, y); + float phase = getPhaseAt(x, y); + real = mag * cosf(phase); + imag = mag * sinf(phase); + return; + } +} + +bool +FFTFileCache::haveSetColumnAt(size_t x) const +{ + return m_mfc->haveSetColumnAt(x); +} + +void +FFTFileCache::setColumnAt(size_t x, float *mags, float *phases, float factor) +{ + QMutexLocker locker(&m_writeMutex); + + size_t h = getHeight(); + + switch (m_storageType) { + + case Compact: + for (size_t 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 Rectangular: + for (size_t 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 Polar: + for (size_t 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; +// std::cerr << "Normalization factor: " << factor << ", max " << maxFactor << " (height " << getHeight() << ")" << std::endl; + + if (m_storageType == Compact) { + ((uint16_t *)m_writebuf)[h * 2] = factor * 65535.0; + } else { + ((float *)m_writebuf)[h * 2] = factor; + } + m_mfc->setColumnAt(x, m_writebuf); +} + +void +FFTFileCache::setColumnAt(size_t x, float *real, float *imag) +{ + QMutexLocker locker(&m_writeMutex); + + size_t h = getHeight(); + + float max = 0.0f; + + switch (m_storageType) { + + case Compact: + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + } + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + float phase = princargf(atan2f(imag[y], real[y])); + ((uint16_t *)m_writebuf)[y * 2] = uint16_t((mag / max) * 65535.0); + ((uint16_t *)m_writebuf)[y * 2 + 1] = uint16_t(int16_t((phase * 32767) / M_PI)); + } + break; + + case Rectangular: + for (size_t 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 > max) max = mag; + } + break; + + case Polar: + for (size_t y = 0; y < h; ++y) { + float mag = sqrtf(real[y] * real[y] + imag[y] * imag[y]); + if (mag > max) max = mag; + ((float *)m_writebuf)[y * 2] = mag; + ((float *)m_writebuf)[y * 2 + 1] = princargf(atan2f(imag[y], real[y])); + } + break; + } + + ((float *)m_writebuf)[h * 2] = max; + m_mfc->setColumnAt(x, m_writebuf); +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FFTFileCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTFileCache.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,125 @@ +/* -*- 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_FILE_CACHE_H_ +#define _FFT_FILE_CACHE_H_ + +#include "FFTCache.h" +#include "MatrixFile.h" + +#include + +class FFTFileCache : public FFTCache +{ +public: + enum StorageType { + Compact, // 16 bits normalized polar + Rectangular, // floating point real+imag + Polar, // floating point mag+phase + }; + + FFTFileCache(QString fileBase, MatrixFile::Mode mode, + StorageType storageType); + virtual ~FFTFileCache(); + + MatrixFile::Mode getMode() const { return m_mfc->getMode(); } + + virtual size_t getWidth() const; + virtual size_t getHeight() const; + + virtual void resize(size_t width, size_t height); + virtual void reset(); // zero-fill or 1-fill as appropriate without changing size + + virtual float getMagnitudeAt(size_t x, size_t y) const; + virtual float getNormalizedMagnitudeAt(size_t x, size_t y) const; + virtual float getMaximumMagnitudeAt(size_t x) const; + virtual float getPhaseAt(size_t x, size_t y) const; + + virtual void getValuesAt(size_t x, size_t y, float &real, float &imag) const; + + virtual bool haveSetColumnAt(size_t x) const; + + virtual void setColumnAt(size_t x, float *mags, float *phases, float factor); + virtual void setColumnAt(size_t x, float *reals, float *imags); + + virtual void suspend() { m_mfc->suspend(); } + +protected: + char *m_writebuf; + mutable char *m_readbuf; + mutable size_t m_readbufCol; + mutable size_t m_readbufWidth; + + float getFromReadBufStandard(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((float *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufStandard(x, y); + } + } + + float getFromReadBufCompactUnsigned(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((uint16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufCompactUnsigned(x, y); + } + } + + float getFromReadBufCompactSigned(size_t x, size_t y) const { + if (m_readbuf && + (m_readbufCol == x || (m_readbufWidth > 1 && m_readbufCol+1 == x))) { + return ((int16_t *)m_readbuf)[(x - m_readbufCol) * m_mfc->getHeight() + y]; + } else { + populateReadBuf(x); + return getFromReadBufCompactSigned(x, y); + } + } + + void populateReadBuf(size_t x) const { + if (!m_readbuf) { + m_readbuf = new char[m_mfc->getHeight() * 2 * m_mfc->getCellSize()]; + } + 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_readbufCol = x; + } + + float getNormalizationFactor(size_t col) const { + if (m_storageType != Compact) { + return getFromReadBufStandard(col, m_mfc->getHeight() - 1); + } else { + float factor; + factor = getFromReadBufCompactUnsigned(col, m_mfc->getHeight() - 1); + return factor / 65535.0; + } + } + + MatrixFile *m_mfc; + QMutex m_writeMutex; + StorageType m_storageType; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FFTFuzzyAdapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTFuzzyAdapter.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,72 @@ +/* -*- 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 "FFTFuzzyAdapter.h" + +#include + +FFTFuzzyAdapter::FFTFuzzyAdapter(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn) : + m_server(0), + m_xshift(0), + m_yshift(0) +{ + m_server = FFTDataServer::getFuzzyInstance(model, + channel, + windowType, + windowSize, + windowIncrement, + fftSize, + polar, + fillFromColumn); + + size_t xratio = windowIncrement / m_server->getWindowIncrement(); + size_t yratio = m_server->getFFTSize() / fftSize; + + while (xratio > 1) { + if (xratio & 0x1) { + std::cerr << "ERROR: FFTFuzzyAdapter: Window increment ratio " + << windowIncrement << " / " + << m_server->getWindowIncrement() + << " must be a power of two" << std::endl; + assert(!(xratio & 0x1)); + } + ++m_xshift; + xratio >>= 1; + } + + while (yratio > 1) { + if (yratio & 0x1) { + std::cerr << "ERROR: FFTFuzzyAdapter: FFT size ratio " + << m_server->getFFTSize() << " / " << fftSize + << " must be a power of two" << std::endl; + assert(!(yratio & 0x1)); + } + ++m_yshift; + yratio >>= 1; + } +} + +FFTFuzzyAdapter::~FFTFuzzyAdapter() +{ + FFTDataServer::releaseInstance(m_server); +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FFTFuzzyAdapter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FFTFuzzyAdapter.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,80 @@ +/* -*- 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_FUZZY_ADAPTER_H_ +#define _FFT_FUZZY_ADAPTER_H_ + +#include "FFTDataServer.h" + +class FFTFuzzyAdapter +{ +public: + FFTFuzzyAdapter(const DenseTimeValueModel *model, + int channel, + WindowType windowType, + size_t windowSize, + size_t windowIncrement, + size_t fftSize, + bool polar, + size_t fillFromColumn = 0); + ~FFTFuzzyAdapter(); + + size_t getWidth() const { + return m_server->getWidth() >> m_xshift; + } + size_t getHeight() const { + return m_server->getHeight() >> m_yshift; + } + float getMagnitudeAt(size_t x, size_t y) { + return m_server->getMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getNormalizedMagnitudeAt(size_t x, size_t y) { + return m_server->getNormalizedMagnitudeAt(x << m_xshift, y << m_yshift); + } + float getMaximumMagnitudeAt(size_t x) { + return m_server->getMaximumMagnitudeAt(x << m_xshift); + } + float getPhaseAt(size_t x, size_t y) { + return m_server->getPhaseAt(x << m_xshift, y << m_yshift); + } + void getValuesAt(size_t x, size_t y, float &real, float &imaginary) { + m_server->getValuesAt(x << m_xshift, y << m_yshift, real, imaginary); + } + bool isColumnReady(size_t x) { + return m_server->isColumnReady(x << m_xshift); + } + bool isLocalPeak(size_t x, size_t 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(size_t x, size_t y, float threshold) { + return getMagnitudeAt(x, y) > threshold; + } + + size_t getFillCompletion() const { return m_server->getFillCompletion(); } + size_t getFillExtent() const { return m_server->getFillExtent(); } + +private: + FFTFuzzyAdapter(const FFTFuzzyAdapter &); // not implemented + FFTFuzzyAdapter &operator=(const FFTFuzzyAdapter &); // not implemented + + FFTDataServer *m_server; + int m_xshift; + int m_yshift; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FileReadThread.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FileReadThread.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,299 @@ +/* -*- 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 "FileReadThread.h" + +#include "base/Profiler.h" + +#include +#include + +//#define DEBUG_FILE_READ_THREAD 1 + +FileReadThread::FileReadThread() : + m_nextToken(0), + m_exiting(false) +{ +} + +void +FileReadThread::run() +{ + m_mutex.lock(); + + while (!m_exiting) { + if (m_queue.empty()) { + m_condition.wait(&m_mutex, 1000); + } else { + process(); + } + notifyCancelled(); + } + + notifyCancelled(); + m_mutex.unlock(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::run() exiting" << std::endl; +#endif +} + +void +FileReadThread::finish() +{ +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::finish()" << std::endl; +#endif + + m_mutex.lock(); + while (!m_queue.empty()) { + m_cancelledRequests[m_queue.begin()->first] = m_queue.begin()->second; + m_newlyCancelled.insert(m_queue.begin()->first); + m_queue.erase(m_queue.begin()); + } + + m_exiting = true; + m_mutex.unlock(); + + m_condition.wakeAll(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::finish() exiting" << std::endl; +#endif +} + +int +FileReadThread::request(const Request &request) +{ + m_mutex.lock(); + + int token = m_nextToken++; + m_queue[token] = request; + + m_mutex.unlock(); + m_condition.wakeAll(); + + return token; +} + +void +FileReadThread::cancel(int token) +{ + m_mutex.lock(); + + if (m_queue.find(token) != m_queue.end()) { + m_cancelledRequests[token] = m_queue[token]; + m_queue.erase(token); + m_newlyCancelled.insert(token); + } else if (m_readyRequests.find(token) != m_readyRequests.end()) { + m_cancelledRequests[token] = m_readyRequests[token]; + m_readyRequests.erase(token); + } else { + std::cerr << "WARNING: FileReadThread::cancel: token " << token << " not found" << std::endl; + } + + m_mutex.unlock(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::cancel(" << token << ") waking condition" << std::endl; +#endif + + m_condition.wakeAll(); +} + +bool +FileReadThread::isReady(int token) +{ + m_mutex.lock(); + + bool ready = m_readyRequests.find(token) != m_readyRequests.end(); + + m_mutex.unlock(); + return ready; +} + +bool +FileReadThread::isCancelled(int token) +{ + m_mutex.lock(); + + bool cancelled = + m_cancelledRequests.find(token) != m_cancelledRequests.end() && + m_newlyCancelled.find(token) == m_newlyCancelled.end(); + + m_mutex.unlock(); + return cancelled; +} + +bool +FileReadThread::getRequest(int token, Request &request) +{ + m_mutex.lock(); + + bool found = false; + + if (m_queue.find(token) != m_queue.end()) { + request = m_queue[token]; + found = true; + } else if (m_cancelledRequests.find(token) != m_cancelledRequests.end()) { + request = m_cancelledRequests[token]; + found = true; + } else if (m_readyRequests.find(token) != m_readyRequests.end()) { + request = m_readyRequests[token]; + found = true; + } + + m_mutex.unlock(); + + return found; +} + +void +FileReadThread::done(int token) +{ + m_mutex.lock(); + + bool found = false; + + if (m_cancelledRequests.find(token) != m_cancelledRequests.end()) { + m_cancelledRequests.erase(token); + m_newlyCancelled.erase(token); + found = true; + } else if (m_readyRequests.find(token) != m_readyRequests.end()) { + m_readyRequests.erase(token); + found = true; + } else if (m_queue.find(token) != m_queue.end()) { + std::cerr << "WARNING: FileReadThread::done(" << token << "): request is still in queue (wait or cancel it)" << std::endl; + } + + m_mutex.unlock(); + + if (!found) { + std::cerr << "WARNING: FileReadThread::done(" << token << "): request not found" << std::endl; + } +} + +void +FileReadThread::process() +{ + // entered with m_mutex locked and m_queue non-empty + +#ifdef DEBUG_FILE_READ_THREAD + Profiler profiler("FileReadThread::process()", true); +#endif + + int token = m_queue.begin()->first; + Request request = m_queue.begin()->second; + + m_mutex.unlock(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::process: reading " << request.start << ", " << request.size << " on " << request.fd << std::endl; +#endif + + bool successful = false; + bool seekFailed = false; + ssize_t r = 0; + + if (request.mutex) request.mutex->lock(); + + if (::lseek(request.fd, request.start, SEEK_SET) == (off_t)-1) { + seekFailed = true; + } else { + + // if request.size is large, we want to avoid making a single + // system call to read it all as it may block too much + + static const size_t blockSize = 256 * 1024; + + size_t size = request.size; + char *destination = request.data; + + while (size > 0) { + size_t readSize = size; + if (readSize > blockSize) readSize = blockSize; + ssize_t br = ::read(request.fd, destination, readSize); + if (br < 0) { + r = br; + break; + } else { + r += br; + if (br < ssize_t(readSize)) break; + } + destination += readSize; + size -= readSize; + } + } + + if (request.mutex) request.mutex->unlock(); + + if (seekFailed) { + ::perror("Seek failed"); + std::cerr << "ERROR: FileReadThread::process: seek to " + << request.start << " failed" << std::endl; + request.size = 0; + } else { + if (r < 0) { + ::perror("ERROR: FileReadThread::process: Read failed"); + request.size = 0; + } else if (r < ssize_t(request.size)) { + std::cerr << "WARNING: FileReadThread::process: read " + << request.size << " returned only " << r << " bytes" + << std::endl; + request.size = r; + usleep(100000); + } else { + successful = true; + } + } + + // Check that the token hasn't been cancelled and the thread + // hasn't been asked to finish + + m_mutex.lock(); + + request.successful = successful; + + if (m_queue.find(token) != m_queue.end() && !m_exiting) { + m_queue.erase(token); + m_readyRequests[token] = request; +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::process: done, marking as ready" << std::endl; +#endif + } else { +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::process: request disappeared or exiting" << std::endl; +#endif + } +} + +void +FileReadThread::notifyCancelled() +{ + // entered with m_mutex locked + + while (!m_newlyCancelled.empty()) { + + int token = *m_newlyCancelled.begin(); + +#ifdef DEBUG_FILE_READ_THREAD + std::cerr << "FileReadThread::notifyCancelled: token " << token << std::endl; +#endif + + m_newlyCancelled.erase(token); + } +} + + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/FileReadThread.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/FileReadThread.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,73 @@ +/* -*- 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 _FILE_READ_THREAD_H_ +#define _FILE_READ_THREAD_H_ + +#include "Thread.h" + +#include +#include + +#include +#include + +#include + +class FileReadThread : public Thread +{ + Q_OBJECT + +public: + FileReadThread(); + + virtual void run(); + virtual void finish(); + + struct Request { + int fd; + QMutex *mutex; // used to synchronise access to fd; may be null + off_t start; + size_t size; + char *data; // caller is responsible for allocating and deallocating + bool successful; // set by FileReadThread after processing request + }; + + virtual int request(const Request &request); + virtual void cancel(int token); + + virtual bool isReady(int token); + virtual bool isCancelled(int token); // and safe to delete + virtual bool getRequest(int token, Request &request); + virtual void done(int token); + +protected: + int m_nextToken; + bool m_exiting; + + typedef std::map RequestQueue; + RequestQueue m_queue; + RequestQueue m_cancelledRequests; + RequestQueue m_readyRequests; + std::set m_newlyCancelled; + + QMutex m_mutex; + QWaitCondition m_condition; + + void process(); + void notifyCancelled(); +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/MIDIFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MIDIFileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,1248 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Richard Bown and Chris Cannam. +*/ + + +#include +#include +#include +#include +#include + +#include "MIDIFileReader.h" + +#include "base/Model.h" +#include "base/Pitch.h" +#include "base/RealTime.h" +#include "model/NoteModel.h" + +#include +#include +#include + +#include + +using std::string; +using std::ifstream; +using std::stringstream; +using std::cerr; +using std::endl; +using std::ends; +using std::ios; +using std::vector; +using std::map; +using std::set; + +//#define MIDI_DEBUG 1 + +static const char *const MIDI_FILE_HEADER = "MThd"; +static const char *const MIDI_TRACK_HEADER = "MTrk"; + +static const MIDIFileReader::MIDIByte MIDI_STATUS_BYTE_MASK = 0x80; +static const MIDIFileReader::MIDIByte MIDI_MESSAGE_TYPE_MASK = 0xF0; +static const MIDIFileReader::MIDIByte MIDI_CHANNEL_NUM_MASK = 0x0F; +static const MIDIFileReader::MIDIByte MIDI_NOTE_OFF = 0x80; +static const MIDIFileReader::MIDIByte MIDI_NOTE_ON = 0x90; +static const MIDIFileReader::MIDIByte MIDI_POLY_AFTERTOUCH = 0xA0; +static const MIDIFileReader::MIDIByte MIDI_CTRL_CHANGE = 0xB0; +static const MIDIFileReader::MIDIByte MIDI_PROG_CHANGE = 0xC0; +static const MIDIFileReader::MIDIByte MIDI_CHNL_AFTERTOUCH = 0xD0; +static const MIDIFileReader::MIDIByte MIDI_PITCH_BEND = 0xE0; +static const MIDIFileReader::MIDIByte MIDI_SELECT_CHNL_MODE = 0xB0; +static const MIDIFileReader::MIDIByte MIDI_SYSTEM_EXCLUSIVE = 0xF0; +static const MIDIFileReader::MIDIByte MIDI_TC_QUARTER_FRAME = 0xF1; +static const MIDIFileReader::MIDIByte MIDI_SONG_POSITION_PTR = 0xF2; +static const MIDIFileReader::MIDIByte MIDI_SONG_SELECT = 0xF3; +static const MIDIFileReader::MIDIByte MIDI_TUNE_REQUEST = 0xF6; +static const MIDIFileReader::MIDIByte MIDI_END_OF_EXCLUSIVE = 0xF7; +static const MIDIFileReader::MIDIByte MIDI_TIMING_CLOCK = 0xF8; +static const MIDIFileReader::MIDIByte MIDI_START = 0xFA; +static const MIDIFileReader::MIDIByte MIDI_CONTINUE = 0xFB; +static const MIDIFileReader::MIDIByte MIDI_STOP = 0xFC; +static const MIDIFileReader::MIDIByte MIDI_ACTIVE_SENSING = 0xFE; +static const MIDIFileReader::MIDIByte MIDI_SYSTEM_RESET = 0xFF; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_NON_RT = 0x7E; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT = 0x7F; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_COMMAND = 0x06; +static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_RESPONSE = 0x07; +static const MIDIFileReader::MIDIByte MIDI_MMC_STOP = 0x01; +static const MIDIFileReader::MIDIByte MIDI_MMC_PLAY = 0x02; +static const MIDIFileReader::MIDIByte MIDI_MMC_DEFERRED_PLAY = 0x03; +static const MIDIFileReader::MIDIByte MIDI_MMC_FAST_FORWARD = 0x04; +static const MIDIFileReader::MIDIByte MIDI_MMC_REWIND = 0x05; +static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_STROBE = 0x06; +static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_EXIT = 0x07; +static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_PAUSE = 0x08; +static const MIDIFileReader::MIDIByte MIDI_MMC_PAUSE = 0x08; +static const MIDIFileReader::MIDIByte MIDI_MMC_EJECT = 0x0A; +static const MIDIFileReader::MIDIByte MIDI_MMC_LOCATE = 0x44; +static const MIDIFileReader::MIDIByte MIDI_FILE_META_EVENT = 0xFF; +static const MIDIFileReader::MIDIByte MIDI_SEQUENCE_NUMBER = 0x00; +static const MIDIFileReader::MIDIByte MIDI_TEXT_EVENT = 0x01; +static const MIDIFileReader::MIDIByte MIDI_COPYRIGHT_NOTICE = 0x02; +static const MIDIFileReader::MIDIByte MIDI_TRACK_NAME = 0x03; +static const MIDIFileReader::MIDIByte MIDI_INSTRUMENT_NAME = 0x04; +static const MIDIFileReader::MIDIByte MIDI_LYRIC = 0x05; +static const MIDIFileReader::MIDIByte MIDI_TEXT_MARKER = 0x06; +static const MIDIFileReader::MIDIByte MIDI_CUE_POINT = 0x07; +static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX = 0x20; +static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21; +static const MIDIFileReader::MIDIByte MIDI_END_OF_TRACK = 0x2F; +static const MIDIFileReader::MIDIByte MIDI_SET_TEMPO = 0x51; +static const MIDIFileReader::MIDIByte MIDI_SMPTE_OFFSET = 0x54; +static const MIDIFileReader::MIDIByte MIDI_TIME_SIGNATURE = 0x58; +static const MIDIFileReader::MIDIByte MIDI_KEY_SIGNATURE = 0x59; +static const MIDIFileReader::MIDIByte MIDI_SEQUENCER_SPECIFIC = 0x7F; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_MSB = 0x00; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_VOLUME = 0x07; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_LSB = 0x20; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_MODULATION = 0x01; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_PAN = 0x0A; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SUSTAIN = 0x40; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESONANCE = 0x47; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RELEASE = 0x48; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ATTACK = 0x49; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_FILTER = 0x4A; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_REVERB = 0x5B; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_CHORUS = 0x5D; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_1 = 0x62; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_2 = 0x63; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_1 = 0x64; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_2 = 0x65; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESET = 0x79; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_LOCAL = 0x7A; +static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B; +static const MIDIFileReader::MIDIByte MIDI_PERCUSSION_CHANNEL = 9; + +class MIDIEvent +{ +public: + typedef MIDIFileReader::MIDIByte MIDIByte; + + MIDIEvent(unsigned long deltaTime, + MIDIByte eventCode, + MIDIByte data1 = 0, + MIDIByte data2 = 0) : + m_deltaTime(deltaTime), + m_duration(0), + m_eventCode(eventCode), + m_data1(data1), + m_data2(data2), + m_metaEventCode(0) + { } + + MIDIEvent(unsigned long deltaTime, + MIDIByte eventCode, + MIDIByte metaEventCode, + const string &metaMessage) : + m_deltaTime(deltaTime), + m_duration(0), + m_eventCode(eventCode), + m_data1(0), + m_data2(0), + m_metaEventCode(metaEventCode), + m_metaMessage(metaMessage) + { } + + MIDIEvent(unsigned long deltaTime, + MIDIByte eventCode, + const string &sysEx) : + m_deltaTime(deltaTime), + m_duration(0), + m_eventCode(eventCode), + m_data1(0), + m_data2(0), + m_metaEventCode(0), + m_metaMessage(sysEx) + { } + + ~MIDIEvent() { } + + void setTime(const unsigned long &time) { m_deltaTime = time; } + void setDuration(const unsigned long& duration) { m_duration = duration;} + unsigned long addTime(const unsigned long &time) { + m_deltaTime += time; + return m_deltaTime; + } + + MIDIByte getMessageType() const + { return (m_eventCode & MIDI_MESSAGE_TYPE_MASK); } + + MIDIByte getChannelNumber() const + { return (m_eventCode & MIDI_CHANNEL_NUM_MASK); } + + unsigned long getTime() const { return m_deltaTime; } + unsigned long getDuration() const { return m_duration; } + + MIDIByte getPitch() const { return m_data1; } + MIDIByte getVelocity() const { return m_data2; } + MIDIByte getData1() const { return m_data1; } + MIDIByte getData2() const { return m_data2; } + MIDIByte getEventCode() const { return m_eventCode; } + + bool isMeta() const { return (m_eventCode == MIDI_FILE_META_EVENT); } + + MIDIByte getMetaEventCode() const { return m_metaEventCode; } + string getMetaMessage() const { return m_metaMessage; } + void setMetaMessage(const string &meta) { m_metaMessage = meta; } + + friend bool operator<(const MIDIEvent &a, const MIDIEvent &b); + +private: + MIDIEvent& operator=(const MIDIEvent); + + unsigned long m_deltaTime; + unsigned long m_duration; + MIDIByte m_eventCode; + MIDIByte m_data1; // or Note + MIDIByte m_data2; // or Velocity + MIDIByte m_metaEventCode; + string m_metaMessage; +}; + +// Comparator for sorting +// +struct MIDIEventCmp +{ + bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const + { return mE1.getTime() < mE2.getTime(); } + + bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const + { return mE1->getTime() < mE2->getTime(); } +}; + +class MIDIException : virtual public std::exception +{ +public: + MIDIException(QString message) throw() : m_message(message) { + cerr << "WARNING: MIDI exception: " + << message.toLocal8Bit().data() << endl; + } + virtual ~MIDIException() throw() { } + + virtual const char *what() const throw() { + return m_message.toLocal8Bit().data(); + } + +protected: + QString m_message; +}; + + +MIDIFileReader::MIDIFileReader(QString path, + size_t mainModelSampleRate) : + m_timingDivision(0), + m_format(MIDI_FILE_BAD_FORMAT), + m_numberOfTracks(0), + m_trackByteCount(0), + m_decrementCount(false), + m_path(path), + m_midiFile(0), + m_fileSize(0), + m_mainModelSampleRate(mainModelSampleRate) +{ + if (parseFile()) { + m_error = ""; + } +} + +MIDIFileReader::~MIDIFileReader() +{ + for (MIDIComposition::iterator i = m_midiComposition.begin(); + i != m_midiComposition.end(); ++i) { + + for (MIDITrack::iterator j = i->second.begin(); + j != i->second.end(); ++j) { + delete *j; + } + + i->second.clear(); + } + + m_midiComposition.clear(); +} + +bool +MIDIFileReader::isOK() const +{ + return (m_error == ""); +} + +QString +MIDIFileReader::getError() const +{ + return m_error; +} + +long +MIDIFileReader::midiBytesToLong(const string& bytes) +{ + if (bytes.length() != 4) { + throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4)); + } + + long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) | + ((long)(((MIDIByte)bytes[1]) << 16)) | + ((long)(((MIDIByte)bytes[2]) << 8)) | + ((long)((MIDIByte)(bytes[3]))); + + return longRet; +} + +int +MIDIFileReader::midiBytesToInt(const string& bytes) +{ + if (bytes.length() != 2) { + throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2)); + } + + int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) | + ((int)(((MIDIByte)bytes[1]))); + return(intRet); +} + + +// Gets a single byte from the MIDI byte stream. For each track +// section we can read only a specified number of bytes held in +// m_trackByteCount. +// +MIDIFileReader::MIDIByte +MIDIFileReader::getMIDIByte() +{ + if (!m_midiFile) { + throw MIDIException(tr("getMIDIByte called but no MIDI file open")); + } + + if (m_midiFile->eof()) { + throw MIDIException(tr("End of MIDI file encountered while reading")); + } + + if (m_decrementCount && m_trackByteCount <= 0) { + throw MIDIException(tr("Attempt to get more bytes than expected on Track")); + } + + char byte; + if (m_midiFile->read(&byte, 1)) { + --m_trackByteCount; + return (MIDIByte)byte; + } + + throw MIDIException(tr("Attempt to read past MIDI file end")); +} + + +// Gets a specified number of bytes from the MIDI byte stream. For +// each track section we can read only a specified number of bytes +// held in m_trackByteCount. +// +string +MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes) +{ + if (!m_midiFile) { + throw MIDIException(tr("getMIDIBytes called but no MIDI file open")); + } + + if (m_midiFile->eof()) { + throw MIDIException(tr("End of MIDI file encountered while reading")); + } + + if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { + throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount)); + } + + string stringRet; + char fileMIDIByte; + + while (stringRet.length() < numberOfBytes && + m_midiFile->read(&fileMIDIByte, 1)) { + stringRet += fileMIDIByte; + } + + // if we've reached the end of file without fulfilling the + // quota then panic as our parsing has performed incorrectly + // + if (stringRet.length() < numberOfBytes) { + stringRet = ""; + throw MIDIException(tr("Attempt to read past MIDI file end")); + } + + // decrement the byte count + if (m_decrementCount) + m_trackByteCount -= stringRet.length(); + + return stringRet; +} + + +// Get a long number of variable length from the MIDI byte stream. +// +long +MIDIFileReader::getNumberFromMIDIBytes(int firstByte) +{ + if (!m_midiFile) { + throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open")); + } + + long longRet = 0; + MIDIByte midiByte; + + if (firstByte >= 0) { + midiByte = (MIDIByte)firstByte; + } else if (m_midiFile->eof()) { + return longRet; + } else { + midiByte = getMIDIByte(); + } + + longRet = midiByte; + if (midiByte & 0x80) { + longRet &= 0x7F; + do { + midiByte = getMIDIByte(); + longRet = (longRet << 7) + (midiByte & 0x7F); + } while (!m_midiFile->eof() && (midiByte & 0x80)); + } + + return longRet; +} + + +// Seek to the next track in the midi file and set the number +// of bytes to be read in the counter m_trackByteCount. +// +bool +MIDIFileReader::skipToNextTrack() +{ + if (!m_midiFile) { + throw MIDIException(tr("skipToNextTrack called but no MIDI file open")); + } + + string buffer, buffer2; + m_trackByteCount = -1; + m_decrementCount = false; + + while (!m_midiFile->eof() && (m_decrementCount == false)) { + buffer = getMIDIBytes(4); + if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) { + m_trackByteCount = midiBytesToLong(getMIDIBytes(4)); + m_decrementCount = true; + } + } + + if (m_trackByteCount == -1) { // we haven't found a track + return false; + } else { + return true; + } +} + + +// Read in a MIDI file. The parsing process throws exceptions back up +// here if we run into trouble which we can then pass back out to +// whoever called us using a nice bool. +// +bool +MIDIFileReader::parseFile() +{ + m_error = ""; + +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl; +#endif + + // Open the file + m_midiFile = new ifstream(m_path.toLocal8Bit().data(), + ios::in | ios::binary); + + if (!*m_midiFile) { + m_error = "File not found or not readable."; + m_format = MIDI_FILE_BAD_FORMAT; + delete m_midiFile; + return false; + } + + bool retval = false; + + try { + + // Set file size so we can count it off + // + m_midiFile->seekg(0, ios::end); + m_fileSize = m_midiFile->tellg(); + m_midiFile->seekg(0, ios::beg); + + // Parse the MIDI header first. The first 14 bytes of the file. + if (!parseHeader(getMIDIBytes(14))) { + m_format = MIDI_FILE_BAD_FORMAT; + m_error = "Not a MIDI file."; + goto done; + } + + unsigned int i = 0; + + for (unsigned int j = 0; j < m_numberOfTracks; ++j) { + +#ifdef MIDI_DEBUG + cerr << "Parsing Track " << j << endl; +#endif + + if (!skipToNextTrack()) { +#ifdef MIDI_DEBUG + cerr << "Couldn't find Track " << j << endl; +#endif + m_error = "File corrupted or in non-standard format?"; + m_format = MIDI_FILE_BAD_FORMAT; + goto done; + } + +#ifdef MIDI_DEBUG + cerr << "Track has " << m_trackByteCount << " bytes" << endl; +#endif + + // Run through the events taking them into our internal + // representation. + if (!parseTrack(i)) { +#ifdef MIDI_DEBUG + cerr << "Track " << j << " parsing failed" << endl; +#endif + m_error = "File corrupted or in non-standard format?"; + m_format = MIDI_FILE_BAD_FORMAT; + goto done; + } + + ++i; // j is the source track number, i the destination + } + + m_numberOfTracks = i; + retval = true; + + } catch (MIDIException e) { + + cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl; + m_error = e.what(); + } + +done: + m_midiFile->close(); + delete m_midiFile; + + for (unsigned int track = 0; track < m_numberOfTracks; ++track) { + + // Convert the deltaTime to an absolute time since the track + // start. The addTime method returns the sum of the current + // MIDI Event delta time plus the argument. + + unsigned long acc = 0; + + for (MIDITrack::iterator i = m_midiComposition[track].begin(); + i != m_midiComposition[track].end(); ++i) { + acc = (*i)->addTime(acc); + } + + if (consolidateNoteOffEvents(track)) { // returns true if some notes exist + m_loadableTracks.insert(track); + } + } + + for (unsigned int track = 0; track < m_numberOfTracks; ++track) { + updateTempoMap(track); + } + + calculateTempoTimestamps(); + + return retval; +} + +// Parse and ensure the MIDI Header is legitimate +// +bool +MIDIFileReader::parseHeader(const string &midiHeader) +{ + if (midiHeader.size() < 14) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl; +#endif + return false; + } + + if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader()" + << "- file header not found or malformed" + << endl; +#endif + return false; + } + + if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader()" + << " - header length incorrect" + << endl; +#endif + return false; + } + + m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2)); + m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2)); + m_timingDivision = midiBytesToInt(midiHeader.substr(12,2)); + + if (m_format == MIDI_SEQUENTIAL_TRACK_FILE) { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseHeader()" + << "- can't load sequential track file" + << endl; +#endif + return false; + } + +#ifdef MIDI_DEBUG + if (m_timingDivision < 0) { + cerr << "MIDIFileReader::parseHeader()" + << " - file uses SMPTE timing" + << endl; + } +#endif + + return true; +} + +// Extract the contents from a MIDI file track and places it into +// our local map of MIDI events. +// +bool +MIDIFileReader::parseTrack(unsigned int &lastTrackNum) +{ + MIDIByte midiByte, metaEventCode, data1, data2; + MIDIByte eventCode = 0x80; + string metaMessage; + unsigned int messageLength; + unsigned long deltaTime; + unsigned long accumulatedTime = 0; + + // The trackNum passed in to this method is the default track for + // all events provided they're all on the same channel. If we find + // events on more than one channel, we increment trackNum and record + // the mapping from channel to trackNum in this channelTrackMap. + // We then return the new trackNum by reference so the calling + // method knows we've got more tracks than expected. + + // This would be a vector but we need -1 to indicate + // "not yet used" + vector channelTrackMap(16, -1); + + // This is used to store the last absolute time found on each track, + // allowing us to modify delta-times correctly when separating events + // out from one to multiple tracks + // + map trackTimeMap; + + // Meta-events don't have a channel, so we place them in a fixed + // track number instead + unsigned int metaTrack = lastTrackNum; + + // Remember the last non-meta status byte (-1 if we haven't seen one) + int runningStatus = -1; + + bool firstTrack = true; + + while (!m_midiFile->eof() && (m_trackByteCount > 0)) { + + if (eventCode < 0x80) { +#ifdef MIDI_DEBUG + cerr << "WARNING: Invalid event code " << eventCode + << " in MIDI file" << endl; +#endif + throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode))); + } + + deltaTime = getNumberFromMIDIBytes(); + +#ifdef MIDI_DEBUG + cerr << "read delta time " << deltaTime << endl; +#endif + + // Get a single byte + midiByte = getMIDIByte(); + + if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { + + if (runningStatus < 0) { + throw MIDIException(tr("Running status used for first event in track")); + } + + eventCode = (MIDIByte)runningStatus; + data1 = midiByte; + +#ifdef MIDI_DEBUG + cerr << "using running status (byte " << int(midiByte) << " found)" << endl; +#endif + } else { +#ifdef MIDI_DEBUG + cerr << "have new event code " << int(midiByte) << endl; +#endif + eventCode = midiByte; + data1 = getMIDIByte(); + } + + if (eventCode == MIDI_FILE_META_EVENT) { + + metaEventCode = data1; + messageLength = getNumberFromMIDIBytes(); + +//#ifdef MIDI_DEBUG + cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; +//#endif + metaMessage = getMIDIBytes(messageLength); + + long gap = accumulatedTime - trackTimeMap[metaTrack]; + accumulatedTime += deltaTime; + deltaTime += gap; + trackTimeMap[metaTrack] = accumulatedTime; + + MIDIEvent *e = new MIDIEvent(deltaTime, + MIDI_FILE_META_EVENT, + metaEventCode, + metaMessage); + + m_midiComposition[metaTrack].push_back(e); + + if (metaEventCode == MIDI_TRACK_NAME) { + m_trackNames[metaTrack] = metaMessage.c_str(); + } + + } else { // non-meta events + + runningStatus = eventCode; + + MIDIEvent *midiEvent; + + int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); + if (channelTrackMap[channel] == -1) { + if (!firstTrack) ++lastTrackNum; + else firstTrack = false; + channelTrackMap[channel] = lastTrackNum; + } + + unsigned int trackNum = channelTrackMap[channel]; + + // accumulatedTime is abs time of last event on any track; + // trackTimeMap[trackNum] is that of last event on this track + + long gap = accumulatedTime - trackTimeMap[trackNum]; + accumulatedTime += deltaTime; + deltaTime += gap; + trackTimeMap[trackNum] = accumulatedTime; + + switch (eventCode & MIDI_MESSAGE_TYPE_MASK) { + + case MIDI_NOTE_ON: + case MIDI_NOTE_OFF: + case MIDI_POLY_AFTERTOUCH: + case MIDI_CTRL_CHANGE: + data2 = getMIDIByte(); + + // create and store our event + midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); + + /* + cerr << "MIDI event for channel " << channel << " (track " + << trackNum << ")" << endl; + midiEvent->print(); + */ + + + m_midiComposition[trackNum].push_back(midiEvent); + + if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) { + m_percussionTracks.insert(trackNum); + } + + break; + + case MIDI_PITCH_BEND: + data2 = getMIDIByte(); + + // create and store our event + midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); + m_midiComposition[trackNum].push_back(midiEvent); + break; + + case MIDI_PROG_CHANGE: + case MIDI_CHNL_AFTERTOUCH: + // create and store our event + midiEvent = new MIDIEvent(deltaTime, eventCode, data1); + m_midiComposition[trackNum].push_back(midiEvent); + break; + + case MIDI_SYSTEM_EXCLUSIVE: + messageLength = getNumberFromMIDIBytes(data1); + +#ifdef MIDI_DEBUG + cerr << "SysEx of " << messageLength << " bytes found" << endl; +#endif + + metaMessage= getMIDIBytes(messageLength); + + if (MIDIByte(metaMessage[metaMessage.length() - 1]) != + MIDI_END_OF_EXCLUSIVE) + { +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseTrack() - " + << "malformed or unsupported SysEx type" + << endl; +#endif + continue; + } + + // chop off the EOX + // length fixed by Pedro Lopez-Cabanillas (20030523) + // + metaMessage = metaMessage.substr(0, metaMessage.length()-1); + + midiEvent = new MIDIEvent(deltaTime, + MIDI_SYSTEM_EXCLUSIVE, + metaMessage); + m_midiComposition[trackNum].push_back(midiEvent); + break; + + case MIDI_END_OF_EXCLUSIVE: +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseTrack() - " + << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl; +#endif + break; + + default: +#ifdef MIDI_DEBUG + cerr << "MIDIFileReader::parseTrack()" + << " - Unsupported MIDI Event Code: " + << (int)eventCode << endl; +#endif + break; + } + } + } + + if (lastTrackNum > metaTrack) { + for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) { + m_trackNames[track] = QString("%1 <%2>") + .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1); + } + } + + return true; +} + +// Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after +// reading them and modifying their relevant NOTE ONs. Return true +// if there are some notes in this track. +// +bool +MIDIFileReader::consolidateNoteOffEvents(unsigned int track) +{ + bool notesOnTrack = false; + bool noteOffFound; + + for (MIDITrack::iterator i = m_midiComposition[track].begin(); + i != m_midiComposition[track].end(); i++) { + + if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) { + + notesOnTrack = true; + noteOffFound = false; + + for (MIDITrack::iterator j = i; + j != m_midiComposition[track].end(); j++) { + + if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) && + ((*j)->getPitch() == (*i)->getPitch()) && + ((*j)->getMessageType() == MIDI_NOTE_OFF || + ((*j)->getMessageType() == MIDI_NOTE_ON && + (*j)->getVelocity() == 0x00))) { + + (*i)->setDuration((*j)->getTime() - (*i)->getTime()); + + delete *j; + m_midiComposition[track].erase(j); + + noteOffFound = true; + break; + } + } + + // If no matching NOTE OFF has been found then set + // Event duration to length of track + // + if (!noteOffFound) { + MIDITrack::iterator j = m_midiComposition[track].end(); + --j; + (*i)->setDuration((*j)->getTime() - (*i)->getTime()); + } + } + } + + return notesOnTrack; +} + +// Add any tempo events found in the given track to the global tempo map. +// +void +MIDIFileReader::updateTempoMap(unsigned int track) +{ + std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl; + + for (MIDITrack::iterator i = m_midiComposition[track].begin(); + i != m_midiComposition[track].end(); ++i) { + + if ((*i)->isMeta() && + (*i)->getMetaEventCode() == MIDI_SET_TEMPO) { + + MIDIByte m0 = (*i)->getMetaMessage()[0]; + MIDIByte m1 = (*i)->getMetaMessage()[1]; + MIDIByte m2 = (*i)->getMetaMessage()[2]; + + long tempo = (((m0 << 8) + m1) << 8) + m2; + + std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl; + + if (tempo != 0) { + double qpm = 60000000.0 / double(tempo); + m_tempoMap[(*i)->getTime()] = + TempoChange(RealTime::zeroTime, qpm); + } + } + } +} + +void +MIDIFileReader::calculateTempoTimestamps() +{ + unsigned long lastMIDITime = 0; + RealTime lastRealTime = RealTime::zeroTime; + double tempo = 120.0; + int td = m_timingDivision; + if (td == 0) td = 96; + + for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) { + + unsigned long mtime = i->first; + unsigned long melapsed = mtime - lastMIDITime; + double quarters = double(melapsed) / double(td); + double seconds = (60.0 * quarters) / tempo; + + RealTime t = lastRealTime + RealTime::fromSeconds(seconds); + + i->second.first = t; + + lastRealTime = t; + lastMIDITime = mtime; + tempo = i->second.second; + } +} + +RealTime +MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const +{ + unsigned long tempoMIDITime = 0; + RealTime tempoRealTime = RealTime::zeroTime; + double tempo = 120.0; + + TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime); + if (i != m_tempoMap.begin()) { + --i; + tempoMIDITime = i->first; + tempoRealTime = i->second.first; + tempo = i->second.second; + } + + int td = m_timingDivision; + if (td == 0) td = 96; + + unsigned long melapsed = midiTime - tempoMIDITime; + double quarters = double(melapsed) / double(td); + double seconds = (60.0 * quarters) / tempo; + +/* + std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")" + << std::endl; + std::cerr << "timing division = " << td << std::endl; + std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" + << tempoRealTime << ")" << std::endl; + std::cerr << "quarters since then = " << quarters << std::endl; + std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl; + std::cerr << "seconds since then = " << seconds << std::endl; + std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl; +*/ + + return tempoRealTime + RealTime::fromSeconds(seconds); +} + +Model * +MIDIFileReader::load() const +{ + if (!isOK()) return 0; + + if (m_loadableTracks.empty()) { + QMessageBox::critical(0, tr("No notes in MIDI file"), + tr("MIDI file \"%1\" has no notes in any track") + .arg(m_path)); + return 0; + } + + std::set tracksToLoad; + + if (m_loadableTracks.size() == 1) { + + tracksToLoad.insert(*m_loadableTracks.begin()); + + } else { + + QStringList available; + QString allTracks = tr("Merge all tracks"); + QString allNonPercussion = tr("Merge all non-percussion tracks"); + + int nonTrackItems = 1; + + available << allTracks; + + if (!m_percussionTracks.empty() && + (m_percussionTracks.size() < m_loadableTracks.size())) { + available << allNonPercussion; + ++nonTrackItems; + } + + for (set::iterator i = m_loadableTracks.begin(); + i != m_loadableTracks.end(); ++i) { + + unsigned int trackNo = *i; + QString label; + + QString perc; + if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) { + perc = tr(" - uses GM percussion channel"); + } + + if (m_trackNames.find(trackNo) != m_trackNames.end()) { + label = tr("Track %1 (%2)%3") + .arg(trackNo).arg(m_trackNames.find(trackNo)->second) + .arg(perc); + } else { + label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc); + } + available << label; + } + + bool ok = false; + QString selected = QInputDialog::getItem + (0, tr("Select track or tracks to import"), + tr("You can only import this file as a single annotation layer,\nbut the file contains more than one track,\nor notes on more than one channel.\n\nPlease select the track or merged tracks you wish to import:"), + available, 0, false, &ok); + + if (!ok || selected.isEmpty()) return 0; + + if (selected == allTracks || selected == allNonPercussion) { + + for (set::iterator i = m_loadableTracks.begin(); + i != m_loadableTracks.end(); ++i) { + + if (selected == allTracks || + m_percussionTracks.find(*i) == m_percussionTracks.end()) { + + tracksToLoad.insert(*i); + } + } + + } else { + + int j = nonTrackItems; + + for (set::iterator i = m_loadableTracks.begin(); + i != m_loadableTracks.end(); ++i) { + + if (selected == available[j]) { + tracksToLoad.insert(*i); + break; + } + + ++j; + } + } + } + + if (tracksToLoad.empty()) return 0; + + size_t n = tracksToLoad.size(), count = 0; + Model *model = 0; + + for (std::set::iterator i = tracksToLoad.begin(); + i != tracksToLoad.end(); ++i) { + + int minProgress = (100 * count) / n; + int progressAmount = 100 / n; + + model = loadTrack(*i, model, minProgress, progressAmount); + + ++count; + } + + if (dynamic_cast(model)) { + dynamic_cast(model)->setCompletion(100); + } + + return model; +} + +Model * +MIDIFileReader::loadTrack(unsigned int trackToLoad, + Model *existingModel, + int minProgress, + int progressAmount) const +{ + if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) { + return 0; + } + + NoteModel *model = 0; + + if (existingModel) { + model = dynamic_cast(existingModel); + if (!model) { + std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl; + } + } + + if (!model) { + model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false); + model->setValueQuantization(1.0); + } + + const MIDITrack &track = m_midiComposition.find(trackToLoad)->second; + + size_t totalEvents = track.size(); + size_t count = 0; + + bool minorKey = false; + bool sharpKey = true; + + for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) { + + RealTime rt = getTimeForMIDITime((*i)->getTime()); + + // We ignore most of these event types for now, though in + // theory some of the text ones could usefully be incorporated + + if ((*i)->isMeta()) { + + switch((*i)->getMetaEventCode()) { + + case MIDI_KEY_SIGNATURE: + minorKey = (int((*i)->getMetaMessage()[1]) != 0); + sharpKey = (int((*i)->getMetaMessage()[0]) >= 0); + break; + + case MIDI_TEXT_EVENT: + case MIDI_LYRIC: + case MIDI_TEXT_MARKER: + case MIDI_COPYRIGHT_NOTICE: + case MIDI_TRACK_NAME: + // The text events that we could potentially use + break; + + case MIDI_SET_TEMPO: + // Already dealt with in a separate pass previously + break; + + case MIDI_TIME_SIGNATURE: + // Not yet! + break; + + case MIDI_SEQUENCE_NUMBER: + case MIDI_CHANNEL_PREFIX_OR_PORT: + case MIDI_INSTRUMENT_NAME: + case MIDI_CUE_POINT: + case MIDI_CHANNEL_PREFIX: + case MIDI_SEQUENCER_SPECIFIC: + case MIDI_SMPTE_OFFSET: + default: + break; + } + + } else { + + switch ((*i)->getMessageType()) { + + case MIDI_NOTE_ON: + + if ((*i)->getVelocity() == 0) break; // effective note-off + else { + RealTime endRT = getTimeForMIDITime((*i)->getTime() + + (*i)->getDuration()); + + long startFrame = RealTime::realTime2Frame + (rt, model->getSampleRate()); + + long endFrame = RealTime::realTime2Frame + (endRT, model->getSampleRate()); + + QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(), + 0, + !sharpKey); + + QString noteLabel = tr("%1 - vel %2") + .arg(pitchLabel).arg(int((*i)->getVelocity())); + + Note note(startFrame, (*i)->getPitch(), + endFrame - startFrame, noteLabel); + +// std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl; + + model->addPoint(note); + break; + } + + case MIDI_PITCH_BEND: + // I guess we could make some use of this... + break; + + case MIDI_NOTE_OFF: + case MIDI_PROG_CHANGE: + case MIDI_CTRL_CHANGE: + case MIDI_SYSTEM_EXCLUSIVE: + case MIDI_POLY_AFTERTOUCH: + case MIDI_CHNL_AFTERTOUCH: + break; + + default: + break; + } + } + + model->setCompletion(minProgress + + (count * progressAmount) / totalEvents); + ++count; + } + + return model; +} + + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/MIDIFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MIDIFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -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. +*/ + + +/* + This is a modified version of a source file from the + Rosegarden MIDI and audio sequencer and notation editor. + This file copyright 2000-2006 Richard Bown and Chris Cannam. +*/ + +#ifndef _MIDI_FILE_READER_H_ +#define _MIDI_FILE_READER_H_ + +#include "DataFileReader.h" +#include "base/RealTime.h" + +#include +#include +#include + +#include + +class MIDIEvent; + +class MIDIFileReader : public DataFileReader, public QObject +{ +public: + MIDIFileReader(QString path, size_t mainModelSampleRate); + virtual ~MIDIFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + virtual Model *load() const; + + typedef unsigned char MIDIByte; + +protected: + typedef std::vector MIDITrack; + typedef std::map MIDIComposition; + typedef std::pair TempoChange; // time, qpm + typedef std::map TempoMap; // key is MIDI time + + typedef enum { + MIDI_SINGLE_TRACK_FILE = 0x00, + MIDI_SIMULTANEOUS_TRACK_FILE = 0x01, + MIDI_SEQUENTIAL_TRACK_FILE = 0x02, + MIDI_FILE_BAD_FORMAT = 0xFF + } MIDIFileFormatType; + + bool parseFile(); + bool parseHeader(const std::string &midiHeader); + bool parseTrack(unsigned int &trackNum); + + Model *loadTrack(unsigned int trackNum, + Model *existingModel = 0, + int minProgress = 0, + int progressAmount = 100) const; + + bool consolidateNoteOffEvents(unsigned int track); + void updateTempoMap(unsigned int track); + void calculateTempoTimestamps(); + RealTime getTimeForMIDITime(unsigned long midiTime) const; + + // Internal convenience functions + // + int midiBytesToInt(const std::string &bytes); + long midiBytesToLong(const std::string &bytes); + + long getNumberFromMIDIBytes(int firstByte = -1); + + MIDIByte getMIDIByte(); + std::string getMIDIBytes(unsigned long bytes); + + bool skipToNextTrack(); + + int m_timingDivision; // pulses per quarter note + MIDIFileFormatType m_format; + unsigned int m_numberOfTracks; + + long m_trackByteCount; + bool m_decrementCount; + + std::map m_trackNames; + std::set m_loadableTracks; + std::set m_percussionTracks; + MIDIComposition m_midiComposition; + TempoMap m_tempoMap; + + QString m_path; + std::ifstream *m_midiFile; + size_t m_fileSize; + QString m_error; + size_t m_mainModelSampleRate; +}; + + +#endif // _MIDI_FILE_READER_H_ diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/MP3FileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MP3FileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,227 @@ + +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_MAD + +#include "MP3FileReader.h" +#include "base/System.h" + +#include +#include +#include + +#include + +#include +#include + +MP3FileReader::MP3FileReader(QString path, bool showProgress, CacheMode mode) : + CodedAudioFileReader(mode), + m_path(path) +{ + m_frameCount = 0; + m_channelCount = 0; + m_sampleRate = 0; + m_fileSize = 0; + m_bitrateNum = 0; + m_bitrateDenom = 0; + m_frameCount = 0; + m_cancelled = false; + + struct stat stat; + if (::stat(path.toLocal8Bit().data(), &stat) == -1 || stat.st_size == 0) { + m_error = QString("File %1 does not exist.").arg(path); + return; + } + + m_fileSize = stat.st_size; + + int fd; + if ((fd = ::open(path.toLocal8Bit().data(), O_RDONLY, 0)) < 0) { + m_error = QString("Failed to open file %1 for reading.").arg(path); + return; + } + + unsigned char *filebuffer = 0; + + try { + filebuffer = new unsigned char[stat.st_size]; + } catch (...) { + m_error = QString("Out of memory"); + ::close(fd); + return; + } + + if (::read(fd, filebuffer, stat.st_size) < stat.st_size) { + m_error = QString("Failed to read file %1.").arg(path); + delete[] filebuffer; + ::close(fd); + return; + } + + ::close(fd); + + if (showProgress) { + m_progress = new QProgressDialog + (QObject::tr("Decoding MP3 file..."), + QObject::tr("Stop"), 0, 100); + m_progress->hide(); + } + + if (!decode(filebuffer, stat.st_size)) { + m_error = QString("Failed to decode file %1.").arg(path); + delete[] filebuffer; + return; + } + + if (isDecodeCacheInitialised()) finishDecodeCache(); + + if (showProgress) { + delete m_progress; + m_progress = 0; + } + + delete[] filebuffer; +} + +MP3FileReader::~MP3FileReader() +{ +} + +bool +MP3FileReader::decode(void *mm, size_t sz) +{ + DecoderData data; + struct mad_decoder decoder; + + data.start = (unsigned char const *)mm; + data.length = (unsigned long)sz; + data.reader = this; + + mad_decoder_init(&decoder, &data, input, 0, 0, output, error, 0); + mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC); + mad_decoder_finish(&decoder); + + return true; +} + +enum mad_flow +MP3FileReader::input(void *dp, struct mad_stream *stream) +{ + DecoderData *data = (DecoderData *)dp; + + if (!data->length) return MAD_FLOW_STOP; + mad_stream_buffer(stream, data->start, data->length); + data->length = 0; + + return MAD_FLOW_CONTINUE; +} + +enum mad_flow +MP3FileReader::output(void *dp, + struct mad_header const *header, + struct mad_pcm *pcm) +{ + DecoderData *data = (DecoderData *)dp; + return data->reader->accept(header, pcm); +} + +enum mad_flow +MP3FileReader::accept(struct mad_header const *header, + struct mad_pcm *pcm) +{ + int channels = pcm->channels; + int frames = pcm->length; + + if (header) { + m_bitrateNum += header->bitrate; + m_bitrateDenom ++; + } + + if (frames < 1) return MAD_FLOW_CONTINUE; + + if (m_channelCount == 0) { + m_channelCount = channels; + m_sampleRate = pcm->samplerate; + } + + if (m_bitrateDenom > 0) { + double bitrate = m_bitrateNum / m_bitrateDenom; + double duration = double(m_fileSize * 8) / bitrate; + double elapsed = double(m_frameCount) / m_sampleRate; + double percent = ((elapsed * 100.0) / duration); + int progress = int(percent); + if (progress < 1) progress = 1; + if (progress > 99) progress = 99; + if (progress > m_progress->value()) { + m_progress->setValue(progress); + m_progress->show(); + m_progress->raise(); + qApp->processEvents(); + if (m_progress->wasCanceled()) { + m_cancelled = true; + } + } + } + + if (m_cancelled) return MAD_FLOW_STOP; + + m_frameCount += frames; + + if (!isDecodeCacheInitialised()) { + initialiseDecodeCache(); + } + + for (int i = 0; i < frames; ++i) { + + for (int ch = 0; ch < channels; ++ch) { + mad_fixed_t sample = 0; + if (ch < int(sizeof(pcm->samples) / sizeof(pcm->samples[0]))) { + sample = pcm->samples[ch][i]; + } + float fsample = float(sample) / float(MAD_F_ONE); + addSampleToDecodeCache(fsample); + } + + if (! (i & 0xffff)) { + // periodically munlock to ensure we don't exhaust real memory + // if running with memory locked down + MUNLOCK_SAMPLEBLOCK(m_data); + } + } + + if (frames > 0) { + MUNLOCK_SAMPLEBLOCK(m_data); + } + + return MAD_FLOW_CONTINUE; +} + +enum mad_flow +MP3FileReader::error(void *dp, + struct mad_stream *stream, + struct mad_frame *) +{ + DecoderData *data = (DecoderData *)dp; + + fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n", + stream->error, mad_stream_errorstr(stream), + stream->this_frame - data->start); + + return MAD_FLOW_CONTINUE; +} + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/MP3FileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MP3FileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,62 @@ +/* -*- 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 _MP3_FILE_READER_H_ +#define _MP3_FILE_READER_H_ + +#ifdef HAVE_MAD + +#include "CodedAudioFileReader.h" + +#include + +class QProgressDialog; + +class MP3FileReader : public CodedAudioFileReader +{ +public: + MP3FileReader(QString path, bool showProgress, CacheMode cacheMode); + virtual ~MP3FileReader(); + + virtual QString getError() const { return m_error; } + +protected: + QString m_path; + QString m_error; + size_t m_fileSize; + double m_bitrateNum; + size_t m_bitrateDenom; + + QProgressDialog *m_progress; + bool m_cancelled; + + struct DecoderData + { + unsigned char const *start; + unsigned long length; + MP3FileReader *reader; + }; + + bool decode(void *mm, size_t sz); + enum mad_flow accept(struct mad_header const *, struct mad_pcm *); + + static enum mad_flow input(void *, struct mad_stream *); + static enum mad_flow output(void *, struct mad_header const *, struct mad_pcm *); + static enum mad_flow error(void *, struct mad_stream *, struct mad_frame *); +}; + +#endif + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/MatrixFile.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MatrixFile.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,633 @@ +/* -*- 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 "MatrixFile.h" +#include "base/TempDirectory.h" +#include "base/System.h" +#include "base/Profiler.h" +#include "base/Exceptions.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +//#define DEBUG_MATRIX_FILE 1 +//#define DEBUG_MATRIX_FILE_READ_SET 1 + +#ifdef DEBUG_MATRIX_FILE_READ_SET +#define DEBUG_MATRIX_FILE 1 +#endif + +std::map MatrixFile::m_refcount; +QMutex MatrixFile::m_refcountMutex; + +MatrixFile::ResizeableBitsetMap MatrixFile::m_columnBitsets; +QMutex MatrixFile::m_columnBitsetWriteMutex; + +FileReadThread *MatrixFile::m_readThread = 0; + +static size_t totalStorage = 0; +static size_t totalMemory = 0; +static size_t totalCount = 0; + +MatrixFile::MatrixFile(QString fileBase, Mode mode, + size_t cellSize, bool eagerCache) : + m_fd(-1), + m_mode(mode), + m_flags(0), + m_fmode(0), + m_cellSize(cellSize), + m_width(0), + m_height(0), + m_headerSize(2 * sizeof(size_t)), + m_defaultCacheWidth(1024), + m_prevX(0), + m_eagerCache(eagerCache), + m_requestToken(-1), + m_spareData(0), + m_columnBitset(0) +{ + Profiler profiler("MatrixFile::MatrixFile", true); + + if (!m_readThread) { + m_readThread = new FileReadThread; + m_readThread->start(); + } + + m_cache.data = 0; + + QDir tempDir(TempDirectory::getInstance()->getPath()); + QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); + bool newFile = !QFileInfo(fileName).exists(); + + if (newFile && m_mode == ReadOnly) { + std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode " + << "specified, but cache file does not exist" << std::endl; + throw FileNotFound(fileName); + } + + if (!newFile && m_mode == ReadWrite) { + std::cerr << "Note: MatrixFile::MatrixFile: Read/write mode " + << "specified, but file already exists; falling back to " + << "read-only mode" << std::endl; + m_mode = ReadOnly; + } + + if (!eagerCache && m_mode == ReadOnly) { + std::cerr << "WARNING: MatrixFile::MatrixFile: Eager cacheing not " + << "specified, but file is open in read-only mode -- cache " + << "will not be used" << std::endl; + } + + m_flags = 0; + m_fmode = S_IRUSR | S_IWUSR; + + if (m_mode == ReadWrite) { + m_flags = O_RDWR | O_CREAT; + } else { + m_flags = O_RDONLY; + } + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::MatrixFile: opening " << fileName.toStdString() << "..." << std::endl; +#endif + + if ((m_fd = ::open(fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) { + ::perror("Open failed"); + std::cerr << "ERROR: MatrixFile::MatrixFile: " + << "Failed to open cache file \"" + << fileName.toStdString() << "\""; + if (m_mode == ReadWrite) std::cerr << " for writing"; + std::cerr << std::endl; + throw FailedToOpenFile(fileName); + } + + if (newFile) { + resize(0, 0); // write header + } else { + size_t header[2]; + if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) { + perror("Read failed"); + std::cerr << "ERROR: MatrixFile::MatrixFile: " + << "Failed to read header (fd " << m_fd << ", file \"" + << fileName.toStdString() << "\")" << std::endl; + throw FileReadFailed(fileName); + } + m_width = header[0]; + m_height = header[1]; + seekTo(0, 0); + } + + m_fileName = fileName; + + m_columnBitsetWriteMutex.lock(); + + if (m_columnBitsets.find(m_fileName) == m_columnBitsets.end()) { + m_columnBitsets[m_fileName] = new ResizeableBitset; + } + m_columnBitset = m_columnBitsets[m_fileName]; + + m_columnBitsetWriteMutex.unlock(); + + QMutexLocker locker(&m_refcountMutex); + ++m_refcount[fileName]; + + std::cerr << "MatrixFile(" << this << "): fd " << m_fd << ", file " << fileName.toStdString() << ", ref " << m_refcount[fileName] << std::endl; + +// std::cerr << "MatrixFile::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl; + + ++totalCount; + +} + +MatrixFile::~MatrixFile() +{ + char *requestData = 0; + + if (m_requestToken >= 0) { + FileReadThread::Request request; + if (m_readThread->getRequest(m_requestToken, request)) { + requestData = request.data; + } + m_readThread->cancel(m_requestToken); + } + + if (requestData) free(requestData); + if (m_cache.data) free(m_cache.data); + if (m_spareData) free(m_spareData); + + if (m_fd >= 0) { + if (::close(m_fd) < 0) { + ::perror("MatrixFile::~MatrixFile: close failed"); + } + } + + if (m_fileName != "") { + + QMutexLocker locker(&m_refcountMutex); + + if (--m_refcount[m_fileName] == 0) { + + if (::unlink(m_fileName.toLocal8Bit())) { + ::perror("Unlink failed"); + std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl; + } else { + std::cerr << "deleted " << m_fileName.toStdString() << std::endl; + } + + QMutexLocker locker2(&m_columnBitsetWriteMutex); + m_columnBitsets.erase(m_fileName); + delete m_columnBitset; + } + } + + totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize)); + totalMemory -= (2 * m_defaultCacheWidth * m_height * m_cellSize); + totalCount --; + + std::cerr << "MatrixFile::~MatrixFile: " << std::endl; + std::cerr << "Total storage now " << totalStorage/1024 << "K, theoretical max memory " + << totalMemory/1024 << "K in " << totalCount << " instances" << std::endl; + +} + +void +MatrixFile::resize(size_t w, size_t h) +{ + Profiler profiler("MatrixFile::resize", true); + + assert(m_mode == ReadWrite); + + QMutexLocker locker(&m_fdMutex); + + totalStorage -= (m_headerSize + (m_width * m_height * m_cellSize)); + totalMemory -= (2 * m_defaultCacheWidth * m_height * m_cellSize); + + off_t off = m_headerSize + (w * h * m_cellSize); + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::resize(" << w << ", " << h << "): resizing file" << std::endl; +#endif + + if (w * h < m_width * m_height) { + if (::ftruncate(m_fd, off) < 0) { + ::perror("WARNING: MatrixFile::resize: ftruncate failed"); + throw FileOperationFailed(m_fileName, "ftruncate"); + } + } + + m_width = 0; + m_height = 0; + + if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) { + ::perror("ERROR: MatrixFile::resize: Seek to write header failed"); + throw FileOperationFailed(m_fileName, "lseek"); + } + + size_t header[2]; + header[0] = w; + header[1] = h; + if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) { + ::perror("ERROR: MatrixFile::resize: Failed to write header"); + throw FileOperationFailed(m_fileName, "write"); + } + + if (w > 0 && m_defaultCacheWidth > w) { + m_defaultCacheWidth = w; + } + + static size_t maxCacheMB = 16; + if (2 * m_defaultCacheWidth * h * m_cellSize > maxCacheMB * 1024 * 1024) { //!!! + m_defaultCacheWidth = (maxCacheMB * 1024 * 1024) / (2 * h * m_cellSize); + if (m_defaultCacheWidth < 16) m_defaultCacheWidth = 16; + } + + if (m_columnBitset) { + QMutexLocker locker(&m_columnBitsetWriteMutex); + m_columnBitset->resize(w); + } + + if (m_cache.data) { + free(m_cache.data); + m_cache.data = 0; + } + + if (m_spareData) { + free(m_spareData); + m_spareData = 0; + } + + m_width = w; + m_height = h; + + totalStorage += (m_headerSize + (m_width * m_height * m_cellSize)); + totalMemory += (2 * m_defaultCacheWidth * m_height * m_cellSize); + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::resize(" << w << ", " << h << "): cache width " + << m_defaultCacheWidth << ", storage " + << (m_headerSize + w * h * m_cellSize) << ", mem " + << (2 * h * m_defaultCacheWidth * m_cellSize) << std::endl; + + std::cerr << "Total storage " << totalStorage/1024 << "K, theoretical max memory " + << totalMemory/1024 << "K in " << totalCount << " instances" << std::endl; +#endif + + seekTo(0, 0); +} + +void +MatrixFile::reset() +{ + Profiler profiler("MatrixFile::reset", true); + + assert (m_mode == ReadWrite); + + if (m_eagerCache) { + void *emptyCol = calloc(m_height, m_cellSize); + for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol); + free(emptyCol); + } + + if (m_columnBitset) { + QMutexLocker locker(&m_columnBitsetWriteMutex); + m_columnBitset->resize(m_width); + } +} + +void +MatrixFile::getColumnAt(size_t x, void *data) +{ +// Profiler profiler("MatrixFile::getColumnAt"); + +// assert(haveSetColumnAt(x)); + + if (getFromCache(x, 0, m_height, data)) return; + +// Profiler profiler2("MatrixFile::getColumnAt (uncached)"); + + ssize_t r = 0; + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile::getColumnAt(" << x << ")" + << ": reading the slow way"; + + if (m_requestToken >= 0 && + x >= m_requestingX && + x < m_requestingX + m_requestingWidth) { + + std::cerr << " (awaiting " << m_requestingX << ", " << m_requestingWidth << " from disk)"; + } + + std::cerr << std::endl; +#endif + + m_fdMutex.lock(); + + if (seekTo(x, 0)) { + r = ::read(m_fd, data, m_height * m_cellSize); + } + + m_fdMutex.unlock(); + + if (r < 0) { + ::perror("MatrixFile::getColumnAt: read failed"); + throw FileReadFailed(m_fileName); + } + + return; +} + +bool +MatrixFile::getFromCache(size_t x, size_t ystart, size_t ycount, void *data) +{ + m_cacheMutex.lock(); + + if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) { + bool left = (m_cache.data && x < m_cache.x); + m_cacheMutex.unlock(); + primeCache(x, left); // this doesn't take effect until a later callback + m_prevX = x; + return false; + } + + memcpy(data, + m_cache.data + m_cellSize * ((x - m_cache.x) * m_height + ystart), + ycount * m_cellSize); + + m_cacheMutex.unlock(); + + if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) { + primeCache(x, true); + } + + if (m_cache.x + m_cache.width < m_width && + x > m_prevX && + x > m_cache.x + (m_cache.width * 3) / 4) { + primeCache(x, false); + } + + m_prevX = x; + return true; +} + +void +MatrixFile::setColumnAt(size_t x, const void *data) +{ + assert(m_mode == ReadWrite); + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "MatrixFile::setColumnAt(" << x << ")" << std::endl; +#endif + + ssize_t w = 0; + bool seekFailed = false; + + m_fdMutex.lock(); + + if (seekTo(x, 0)) { + w = ::write(m_fd, data, m_height * m_cellSize); + } else { + seekFailed = true; + } + + m_fdMutex.unlock(); + + if (!seekFailed && w != ssize_t(m_height * m_cellSize)) { + ::perror("WARNING: MatrixFile::setColumnAt: write failed"); + throw FileOperationFailed(m_fileName, "write"); + } else if (seekFailed) { + throw FileOperationFailed(m_fileName, "seek"); + } else { + QMutexLocker locker(&m_columnBitsetWriteMutex); + m_columnBitset->set(x); + } +} + +void +MatrixFile::suspend() +{ + QMutexLocker locker(&m_fdMutex); + QMutexLocker locker2(&m_cacheMutex); + + if (m_fd < 0) return; // already suspended + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile(" << this << ":" << m_fileName.toStdString() << ")::suspend(): fd was " << m_fd << std::endl; +#endif + + if (m_requestToken >= 0) { + void *data = 0; + FileReadThread::Request request; + if (m_readThread->getRequest(m_requestToken, request)) { + data = request.data; + } + m_readThread->cancel(m_requestToken); + if (data) free(data); + m_requestToken = -1; + } + + if (m_cache.data) { + free(m_cache.data); + m_cache.data = 0; + } + + if (m_spareData) { + free(m_spareData); + m_spareData = 0; + } + + if (::close(m_fd) < 0) { + ::perror("WARNING: MatrixFile::suspend: close failed"); + throw FileOperationFailed(m_fileName, "close"); + } + + m_fd = -1; +} + +void +MatrixFile::resume() +{ + if (m_fd >= 0) return; + +#ifdef DEBUG_MATRIX_FILE + std::cerr << "MatrixFile(" << this << ")::resume()" << std::endl; +#endif + + if ((m_fd = ::open(m_fileName.toLocal8Bit(), m_flags, m_fmode)) < 0) { + ::perror("Open failed"); + std::cerr << "ERROR: MatrixFile::resume: " + << "Failed to open cache file \"" + << m_fileName.toStdString() << "\""; + if (m_mode == ReadWrite) std::cerr << " for writing"; + std::cerr << std::endl; + throw FailedToOpenFile(m_fileName); + } + + std::cerr << "MatrixFile(" << this << ":" << m_fileName.toStdString() << ")::resume(): fd is " << m_fd << std::endl; +} + +void +MatrixFile::primeCache(size_t x, bool goingLeft) +{ +// Profiler profiler("MatrixFile::primeCache"); + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "MatrixFile::primeCache(" << x << ", " << goingLeft << ")" << std::endl; +#endif + + size_t rx = x; + size_t rw = m_defaultCacheWidth; + + size_t left = rw / 3; + if (goingLeft) left = (rw * 2) / 3; + + if (rx > left) rx -= left; + else rx = 0; + + if (rx + rw > m_width) rw = m_width - rx; + + if (!m_eagerCache) { + + size_t ti = 0; + + for (ti = 0; ti < rw; ++ti) { + if (!m_columnBitset->get(rx + ti)) break; + } + +#ifdef DEBUG_MATRIX_FILE + if (ti < rw) { + std::cerr << "eagerCache is false and there's a hole at " + << rx + ti << ", reducing rw from " << rw << " to " + << ti << std::endl; + } +#endif + + rw = std::min(rw, ti); + if (rw < 10 || rx + rw <= x) return; + } + + QMutexLocker locker(&m_cacheMutex); + + FileReadThread::Request request; + + if (m_requestToken >= 0 && + m_readThread->getRequest(m_requestToken, request)) { + + if (x >= m_requestingX && + x < m_requestingX + m_requestingWidth) { + + if (m_readThread->isReady(m_requestToken)) { + + if (!request.successful) { + std::cerr << "ERROR: MatrixFile::primeCache: Last request was unsuccessful" << std::endl; + throw FileReadFailed(m_fileName); + } + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl; +#endif + + m_cache.x = (request.start - m_headerSize) / (m_height * m_cellSize); + m_cache.width = request.size / (m_height * m_cellSize); + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "received last request: actual size is: " << m_cache.x << ", " << m_cache.width << std::endl; +#endif + + if (m_cache.data) { + if (m_spareData) free(m_spareData); + m_spareData = m_cache.data; + } + m_cache.data = request.data; + + m_readThread->done(m_requestToken); + m_requestToken = -1; + } + + // already requested something covering this area; wait for it + return; + } + + // the current request is no longer of any use + m_readThread->cancel(m_requestToken); + + // crude way to avoid leaking the data + while (!m_readThread->isCancelled(m_requestToken)) { + usleep(10000); + } + +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "cancelled " << m_requestToken << std::endl; +#endif + + if (m_spareData) free(m_spareData); + m_spareData = request.data; + m_readThread->done(m_requestToken); + + m_requestToken = -1; + } + + if (m_fd < 0) { + m_fdMutex.lock(); + if (m_fd < 0) resume(); + m_fdMutex.unlock(); + } + + request.fd = m_fd; + request.mutex = &m_fdMutex; + request.start = m_headerSize + rx * m_height * m_cellSize; + request.size = rw * m_height * m_cellSize; + request.data = (char *)realloc(m_spareData, rw * m_height * m_cellSize); + MUNLOCK(request.data, rw * m_height * m_cellSize); + m_spareData = 0; + + m_requestingX = rx; + m_requestingWidth = rw; + + int token = m_readThread->request(request); +#ifdef DEBUG_MATRIX_FILE_READ_SET + std::cerr << "MatrixFile::primeCache: request token is " + << token << " (x = [" << rx << "], w = [" << rw << "], left = [" << goingLeft << "])" << std::endl; +#endif + m_requestToken = token; +} + +bool +MatrixFile::seekTo(size_t x, size_t y) +{ + if (m_fd < 0) resume(); + + off_t off = m_headerSize + (x * m_height + y) * m_cellSize; + + if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { + ::perror("Seek failed"); + std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y + << ") failed" << std::endl; + return false; + } + + return true; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/MatrixFile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/MatrixFile.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,124 @@ +/* -*- 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 _MATRIX_FILE_CACHE_H_ +#define _MATRIX_FILE_CACHE_H_ + +#include "base/ResizeableBitset.h" + +#include "FileReadThread.h" + +#include +#include +#include +#include + +class MatrixFile : public QObject +{ + Q_OBJECT + +public: + enum Mode { ReadOnly, ReadWrite }; + + /** + * Construct a MatrixFile object reading from and/or writing to + * the matrix file with the given base name in the application's + * temporary directory. + * + * If mode is ReadOnly, the file must exist and be readable. + * + * If mode is ReadWrite and the file does not exist, it will be + * created. If mode is ReadWrite and the file does exist, the + * existing file will be used and the mode will be reset to + * ReadOnly. Call getMode() to check whether this has occurred + * after construction. + * + * cellSize specifies the size in bytes of the object type stored + * in the matrix. For example, use cellSize = sizeof(float) for a + * matrix of floats. The MatrixFile object doesn't care about the + * objects themselves, it just deals with raw data of a given size. + * + * If eagerCache is true, blocks from the file will be cached for + * read. If eagerCache is false, only columns that have been set + * by calling setColumnAt on this MatrixFile (i.e. columns for + * which haveSetColumnAt returns true) will be cached. + */ + MatrixFile(QString fileBase, Mode mode, size_t cellSize, bool eagerCache); + virtual ~MatrixFile(); + + Mode getMode() const { return m_mode; } + + size_t getWidth() const { return m_width; } + size_t getHeight() const { return m_height; } + size_t getCellSize() const { return m_cellSize; } + + void resize(size_t width, size_t height); + void reset(); + + bool haveSetColumnAt(size_t x) const { return m_columnBitset->get(x); } + void getColumnAt(size_t x, void *data); + void setColumnAt(size_t x, const void *data); + + void suspend(); + +protected: + int m_fd; + Mode m_mode; + int m_flags; + mode_t m_fmode; + size_t m_cellSize; + size_t m_width; + size_t m_height; + size_t m_headerSize; + QString m_fileName; + size_t m_defaultCacheWidth; + size_t m_prevX; + + struct Cache { + size_t x; + size_t width; + char *data; + }; + + Cache m_cache; + bool m_eagerCache; + + bool getFromCache(size_t x, size_t ystart, size_t ycount, void *data); + void primeCache(size_t x, bool left); + + void resume(); + + bool seekTo(size_t x, size_t y); + + static FileReadThread *m_readThread; + int m_requestToken; + + size_t m_requestingX; + size_t m_requestingWidth; + char *m_spareData; + + static std::map m_refcount; + static QMutex m_refcountMutex; + QMutex m_fdMutex; + QMutex m_cacheMutex; + + typedef std::map ResizeableBitsetMap; + static ResizeableBitsetMap m_columnBitsets; + static QMutex m_columnBitsetWriteMutex; + ResizeableBitset *m_columnBitset; +}; + +#endif + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/OggVorbisFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/OggVorbisFileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,159 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + +#include "OggVorbisFileReader.h" +#include "base/Profiler.h" +#include "base/System.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +static int instances = 0; + +OggVorbisFileReader::OggVorbisFileReader(QString path, bool showProgress, + CacheMode mode) : + CodedAudioFileReader(mode), + m_path(path), + m_progress(0), + m_fileSize(0), + m_bytesRead(0), + m_cancelled(false) +{ + m_frameCount = 0; + m_channelCount = 0; + m_sampleRate = 0; + + std::cerr << "OggVorbisFileReader::OggVorbisFileReader(" << path.toLocal8Bit().data() << "): now have " << (++instances) << " instances" << std::endl; + + Profiler profiler("OggVorbisFileReader::OggVorbisFileReader", true); + + QFileInfo info(path); + m_fileSize = info.size(); + + OGGZ *oggz; + if (!(oggz = oggz_open(path.toLocal8Bit().data(), OGGZ_READ))) { + m_error = QString("File %1 is not an OGG file.").arg(path); + return; + } + + FishSoundInfo fsinfo; + m_fishSound = fish_sound_new(FISH_SOUND_DECODE, &fsinfo); + + fish_sound_set_decoded_callback(m_fishSound, acceptFrames, this); + oggz_set_read_callback(oggz, -1, readPacket, this); + + if (showProgress) { + m_progress = new QProgressDialog + (QObject::tr("Decoding Ogg file..."), + QObject::tr("Stop"), 0, 100); + m_progress->hide(); + } + + while (oggz_read(oggz, 1024) > 0); + + fish_sound_delete(m_fishSound); + m_fishSound = 0; + oggz_close(oggz); + + if (isDecodeCacheInitialised()) finishDecodeCache(); + + if (showProgress) { + delete m_progress; + m_progress = 0; + } +} + +OggVorbisFileReader::~OggVorbisFileReader() +{ + std::cerr << "OggVorbisFileReader::~OggVorbisFileReader(" << m_path.toLocal8Bit().data() << "): now have " << (--instances) << " instances" << std::endl; +} + +int +OggVorbisFileReader::readPacket(OGGZ *, ogg_packet *packet, long, void *data) +{ + OggVorbisFileReader *reader = (OggVorbisFileReader *)data; + FishSound *fs = reader->m_fishSound; + + fish_sound_prepare_truncation(fs, packet->granulepos, packet->e_o_s); + fish_sound_decode(fs, packet->packet, packet->bytes); + + reader->m_bytesRead += packet->bytes; + + if (reader->m_fileSize > 0 && reader->m_progress) { + // The number of bytes read by this function is smaller than + // the file size because of the packet headers + int progress = lrint(double(reader->m_bytesRead) * 114 / + double(reader->m_fileSize)); + if (progress > 99) progress = 99; + if (progress > reader->m_progress->value()) { + reader->m_progress->setValue(progress); + reader->m_progress->show(); + reader->m_progress->raise(); + qApp->processEvents(); + if (reader->m_progress->wasCanceled()) { + reader->m_cancelled = true; + } + } + } + + if (reader->m_cancelled) return 1; + return 0; +} + +int +OggVorbisFileReader::acceptFrames(FishSound *fs, float **frames, long nframes, + void *data) +{ + OggVorbisFileReader *reader = (OggVorbisFileReader *)data; + + if (reader->m_channelCount == 0) { + FishSoundInfo fsinfo; + fish_sound_command(fs, FISH_SOUND_GET_INFO, + &fsinfo, sizeof(FishSoundInfo)); + reader->m_channelCount = fsinfo.channels; + reader->m_sampleRate = fsinfo.samplerate; + reader->initialiseDecodeCache(); + } + + if (nframes > 0) { + + reader->m_frameCount += nframes; + + for (long i = 0; i < nframes; ++i) { + for (size_t c = 0; c < reader->m_channelCount; ++c) { + reader->addSampleToDecodeCache(frames[c][i]); +// reader->m_data.push_back(frames[c][i]); + } + } + + MUNLOCK_SAMPLEBLOCK(reader->m_data); + } + + if (reader->m_cancelled) return 1; + return 0; +} + +#endif +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/OggVorbisFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/OggVorbisFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,54 @@ +/* -*- 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 _OGG_VORBIS_FILE_READER_H_ +#define _OGG_VORBIS_FILE_READER_H_ + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + +#include "CodedAudioFileReader.h" + +#include +#include + +class QProgressDialog; + +class OggVorbisFileReader : public CodedAudioFileReader +{ +public: + OggVorbisFileReader(QString path, bool showProgress, CacheMode cacheMode); + virtual ~OggVorbisFileReader(); + + virtual QString getError() const { return m_error; } + +protected: + QString m_path; + QString m_error; + + FishSound *m_fishSound; + QProgressDialog *m_progress; + size_t m_fileSize; + size_t m_bytesRead; + bool m_cancelled; + + static int readPacket(OGGZ *, ogg_packet *, long, void *); + static int acceptFrames(FishSound *, float **, long, void *); +}; + +#endif +#endif + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/RecentFiles.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/RecentFiles.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,123 @@ +/* -*- 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 "RecentFiles.h" +#include "ConfigFile.h" + +#include "base/Preferences.h" + +#include + +RecentFiles * +RecentFiles::m_instance = 0; + +RecentFiles * +RecentFiles::getInstance(int maxFileCount) +{ + if (!m_instance) { + m_instance = new RecentFiles(maxFileCount); + } + return m_instance; +} + +RecentFiles::RecentFiles(int maxFileCount) : + m_maxFileCount(maxFileCount) +{ + readFiles(); +} + +RecentFiles::~RecentFiles() +{ + // nothing +} + +void +RecentFiles::readFiles() +{ + m_files.clear(); + ConfigFile *cf = Preferences::getInstance()->getConfigFile(); + for (unsigned int i = 0; i < 100; ++i) { + QString key = QString("recent-file-%1").arg(i); + QString filename = cf->get(key); + if (filename == "") break; + if (i < m_maxFileCount) m_files.push_back(filename); + else cf->set(key, ""); + } + cf->commit(); +} + +void +RecentFiles::writeFiles() +{ + ConfigFile *cf = Preferences::getInstance()->getConfigFile(); + for (unsigned int i = 0; i < m_maxFileCount; ++i) { + QString key = QString("recent-file-%1").arg(i); + QString filename = ""; + if (i < m_files.size()) filename = m_files[i]; + cf->set(key, filename); + } + cf->commit(); +} + +void +RecentFiles::truncateAndWrite() +{ + while (m_files.size() > m_maxFileCount) { + m_files.pop_back(); + } + writeFiles(); +} + +std::vector +RecentFiles::getRecentFiles() const +{ + std::vector files; + for (unsigned int i = 0; i < m_maxFileCount; ++i) { + if (i < m_files.size()) { + files.push_back(m_files[i]); + } + } + return files; +} + +void +RecentFiles::addFile(QString filename) +{ + filename = QFileInfo(filename).absoluteFilePath(); + + bool have = false; + for (unsigned int i = 0; i < m_files.size(); ++i) { + if (m_files[i] == filename) { + have = true; + break; + } + } + + if (!have) { + m_files.push_front(filename); + } else { + std::deque newfiles; + newfiles.push_back(filename); + for (unsigned int i = 0; i < m_files.size(); ++i) { + if (m_files[i] == filename) continue; + newfiles.push_back(m_files[i]); + } + } + + truncateAndWrite(); + emit recentFilesChanged(); +} + + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/RecentFiles.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/RecentFiles.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,56 @@ +/* -*- 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 _RECENT_FILES_H_ +#define _RECENT_FILES_H_ + +#include +#include +#include +#include + +class RecentFiles : public QObject +{ + Q_OBJECT + +public: + // The maxFileCount argument will only be used the first time this is called + static RecentFiles *getInstance(int maxFileCount = 10); + + virtual ~RecentFiles(); + + int getMaxFileCount() const { return m_maxFileCount; } + + std::vector getRecentFiles() const; + + void addFile(QString filename); + +signals: + void recentFilesChanged(); + +protected: + RecentFiles(int maxFileCount); + + int m_maxFileCount; + std::deque m_files; + + void readFiles(); + void writeFiles(); + void truncateAndWrite(); + + static RecentFiles *m_instance; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/SVFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/SVFileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,1005 @@ +/* -*- 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 "SVFileReader.h" + +#include "base/Layer.h" +#include "base/View.h" +#include "base/PlayParameters.h" +#include "base/PlayParameterRepository.h" + +#include "AudioFileReaderFactory.h" + +#include "model/WaveFileModel.h" +#include "model/DenseThreeDimensionalModel.h" +#include "model/SparseOneDimensionalModel.h" +#include "model/SparseTimeValueModel.h" +#include "model/NoteModel.h" +#include "model/TextModel.h" + +#include "widgets/Pane.h" + +#include "main/Document.h" + +#include +#include +#include + +#include + +SVFileReader::SVFileReader(Document *document, + SVFileReaderPaneCallback &callback) : + m_document(document), + m_paneCallback(callback), + m_currentPane(0), + m_currentDataset(0), + m_currentDerivedModel(0), + m_currentPlayParameters(0), + m_datasetSeparator(" "), + m_inRow(false), + m_rowNumber(0), + m_ok(false) +{ +} + +void +SVFileReader::parse(const QString &xmlData) +{ + QXmlInputSource inputSource; + inputSource.setData(xmlData); + parse(inputSource); +} + +void +SVFileReader::parse(QXmlInputSource &inputSource) +{ + QXmlSimpleReader reader; + reader.setContentHandler(this); + reader.setErrorHandler(this); + m_ok = reader.parse(inputSource); +} + +bool +SVFileReader::isOK() +{ + return m_ok; +} + +SVFileReader::~SVFileReader() +{ + if (!m_awaitingDatasets.empty()) { + std::cerr << "WARNING: SV-XML: File ended with " + << m_awaitingDatasets.size() << " unfilled model dataset(s)" + << std::endl; + } + + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + if (!unaddedModels.empty()) { + std::cerr << "WARNING: SV-XML: File contained " + << unaddedModels.size() << " unused models" + << std::endl; + while (!unaddedModels.empty()) { + delete *unaddedModels.begin(); + unaddedModels.erase(unaddedModels.begin()); + } + } +} + +bool +SVFileReader::startElement(const QString &, const QString &, + const QString &qName, + const QXmlAttributes &attributes) +{ + QString name = qName.toLower(); + + bool ok = false; + + // Valid element names: + // + // sv + // data + // dataset + // display + // derivation + // playparameters + // layer + // model + // point + // row + // view + // window + + if (name == "sv") { + + // nothing needed + ok = true; + + } else if (name == "data") { + + // nothing needed + m_inData = true; + ok = true; + + } else if (name == "display") { + + // nothing needed + ok = true; + + } else if (name == "window") { + + ok = readWindow(attributes); + + } else if (name == "model") { + + ok = readModel(attributes); + + } else if (name == "dataset") { + + ok = readDatasetStart(attributes); + + } else if (name == "bin") { + + ok = addBinToDataset(attributes); + + } else if (name == "point") { + + ok = addPointToDataset(attributes); + + } else if (name == "row") { + + ok = addRowToDataset(attributes); + + } else if (name == "layer") { + + addUnaddedModels(); // all models must be specified before first layer + ok = readLayer(attributes); + + } else if (name == "view") { + + m_inView = true; + ok = readView(attributes); + + } else if (name == "derivation") { + + ok = readDerivation(attributes); + + } else if (name == "playparameters") { + + ok = readPlayParameters(attributes); + + } else if (name == "plugin") { + + ok = readPlugin(attributes); + + } else if (name == "selections") { + + m_inSelections = true; + ok = true; + + } else if (name == "selection") { + + ok = readSelection(attributes); + } + + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to completely process element \"" + << name.toLocal8Bit().data() << "\"" << std::endl; + } + + return true; +} + +bool +SVFileReader::characters(const QString &text) +{ + bool ok = false; + + if (m_inRow) { + ok = readRowData(text); + if (!ok) { + std::cerr << "WARNING: SV-XML: Failed to read row data content for row " << m_rowNumber << std::endl; + } + } + + return true; +} + +bool +SVFileReader::endElement(const QString &, const QString &, + const QString &qName) +{ + QString name = qName.toLower(); + + if (name == "dataset") { + + if (m_currentDataset) { + + bool foundInAwaiting = false; + + for (std::map::iterator i = m_awaitingDatasets.begin(); + i != m_awaitingDatasets.end(); ++i) { + if (m_models[i->second] == m_currentDataset) { + m_awaitingDatasets.erase(i); + foundInAwaiting = true; + break; + } + } + + if (!foundInAwaiting) { + std::cerr << "WARNING: SV-XML: Dataset precedes model, or no model uses dataset" << std::endl; + } + } + + m_currentDataset = 0; + + } else if (name == "data") { + + addUnaddedModels(); + m_inData = false; + + } else if (name == "derivation") { + + if (m_currentDerivedModel) { + m_document->addDerivedModel(m_currentTransform, + m_document->getMainModel(), //!!! + m_currentTransformChannel, + m_currentDerivedModel, + m_currentTransformConfiguration); + m_addedModels.insert(m_currentDerivedModel); + m_currentDerivedModel = 0; + m_currentTransform = ""; + m_currentTransformConfiguration = ""; + } + + } else if (name == "row") { + m_inRow = false; + } else if (name == "view") { + m_inView = false; + } else if (name == "selections") { + m_inSelections = false; + } else if (name == "playparameters") { + m_currentPlayParameters = 0; + } + + return true; +} + +bool +SVFileReader::error(const QXmlParseException &exception) +{ + m_errorString = + QString("ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::error(exception); +} + +bool +SVFileReader::fatalError(const QXmlParseException &exception) +{ + m_errorString = + QString("FATAL ERROR: SV-XML: %1 at line %2, column %3") + .arg(exception.message()) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()); + std::cerr << m_errorString.toLocal8Bit().data() << std::endl; + return QXmlDefaultHandler::fatalError(exception); +} + + +#define READ_MANDATORY(TYPE, NAME, CONVERSION) \ + TYPE NAME = attributes.value(#NAME).trimmed().CONVERSION(&ok); \ + if (!ok) { \ + std::cerr << "WARNING: SV-XML: Missing or invalid mandatory " #TYPE " attribute \"" #NAME "\"" << std::endl; \ + return false; \ + } + +bool +SVFileReader::readWindow(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, width, toInt); + READ_MANDATORY(int, height, toInt); + + m_paneCallback.setWindowSize(width, height); + return true; +} + +void +SVFileReader::addUnaddedModels() +{ + std::set unaddedModels; + + for (std::map::iterator i = m_models.begin(); + i != m_models.end(); ++i) { + if (m_addedModels.find(i->second) == m_addedModels.end()) { + unaddedModels.insert(i->second); + } + } + + for (std::set::iterator i = unaddedModels.begin(); + i != unaddedModels.end(); ++i) { + m_document->addImportedModel(*i); + m_addedModels.insert(*i); + } +} + +bool +SVFileReader::readModel(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + + if (m_models.find(id) != m_models.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate model id " << id + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + READ_MANDATORY(int, sampleRate, toInt); + + QString type = attributes.value("type").trimmed(); + bool mainModel = (attributes.value("mainModel").trimmed() == "true"); + + if (type == "wavefile") { + + QString file = attributes.value("file"); + WaveFileModel *model = new WaveFileModel(file); + + while (!model->isOK()) { + + delete model; + model = 0; + + if (QMessageBox::question(0, + QMessageBox::tr("Failed to open file"), + QMessageBox::tr("Audio file \"%1\" could not be opened.\nLocate it?").arg(file), + QMessageBox::Ok, + QMessageBox::Cancel) == QMessageBox::Ok) { + + QString path = QFileDialog::getOpenFileName + (0, QFileDialog::tr("Locate file \"%1\"").arg(QFileInfo(file).fileName()), file, + QFileDialog::tr("Audio files (%1)\nAll files (*.*)") + .arg(AudioFileReaderFactory::getKnownExtensions())); + + if (path != "") { + model = new WaveFileModel(path); + } else { + return false; + } + } else { + return false; + } + } + + m_models[id] = model; + if (mainModel) { + m_document->setMainModel(model); + m_addedModels.insert(model); + } + // Derived models will be added when their derivation + // is found. + + return true; + + } else if (type == "dense") { + + READ_MANDATORY(int, dimensions, toInt); + + // Currently the only dense model we support here + // is the dense 3d model. Dense time-value models + // are always file-backed waveform data, at this + // point, and they come in as the wavefile model + // type above. + + if (dimensions == 3) { + + READ_MANDATORY(int, windowSize, toInt); + READ_MANDATORY(int, yBinCount, toInt); + + DenseThreeDimensionalModel *model = + new DenseThreeDimensionalModel(sampleRate, windowSize, yBinCount); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + if (ok) model->setMinimumLevel(minimum); + + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + if (ok) model->setMaximumLevel(maximum); + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + m_models[id] = model; + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected dense model dimension (" + << dimensions << ")" << std::endl; + } + } else if (type == "sparse") { + + READ_MANDATORY(int, dimensions, toInt); + + if (dimensions == 1) { + + READ_MANDATORY(int, resolution, toInt); + + SparseOneDimensionalModel *model = new SparseOneDimensionalModel + (sampleRate, resolution); + m_models[id] = model; + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else if (dimensions == 2 || dimensions == 3) { + + READ_MANDATORY(int, resolution, toInt); + + float minimum = attributes.value("minimum").trimmed().toFloat(&ok); + float maximum = attributes.value("maximum").trimmed().toFloat(&ok); + float valueQuantization = + attributes.value("valueQuantization").trimmed().toFloat(&ok); + + bool notifyOnAdd = (attributes.value("notifyOnAdd") == "true"); + + QString units = attributes.value("units"); + + if (dimensions == 2) { + if (attributes.value("subtype") == "text") { + TextModel *model = new TextModel + (sampleRate, resolution, notifyOnAdd); + m_models[id] = model; + } else { + SparseTimeValueModel *model = new SparseTimeValueModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model->setScaleUnits(units); + m_models[id] = model; + } + } else { + NoteModel *model = new NoteModel + (sampleRate, resolution, minimum, maximum, notifyOnAdd); + model->setValueQuantization(valueQuantization); + model->setScaleUnits(units); + m_models[id] = model; + } + + int dataset = attributes.value("dataset").trimmed().toInt(&ok); + if (ok) m_awaitingDatasets[dataset] = id; + + return true; + + } else { + + std::cerr << "WARNING: SV-XML: Unexpected sparse model dimension (" + << dimensions << ")" << std::endl; + } + } else { + + std::cerr << "WARNING: SV-XML: Unexpected model type \"" + << type.toLocal8Bit().data() << "\" for model id" << id << std::endl; + } + + return false; +} + +bool +SVFileReader::readView(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + m_currentPane = 0; + + if (type != "pane") { + std::cerr << "WARNING: SV-XML: Unexpected view type \"" + << type.toLocal8Bit().data() << "\"" << std::endl; + return false; + } + + m_currentPane = m_paneCallback.addPane(); + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: Internal error: Failed to add pane!" + << std::endl; + return false; + } + + bool ok = false; + + View *view = m_currentPane; + + // The view properties first + + READ_MANDATORY(size_t, centre, toUInt); + READ_MANDATORY(size_t, zoom, toUInt); + READ_MANDATORY(int, followPan, toInt); + READ_MANDATORY(int, followZoom, toInt); + READ_MANDATORY(int, light, toInt); + QString tracking = attributes.value("tracking"); + + // Specify the follow modes before we set the actual values + view->setFollowGlobalPan(followPan); + view->setFollowGlobalZoom(followZoom); + view->setPlaybackFollow(tracking == "scroll" ? View::PlaybackScrollContinuous : + tracking == "page" ? View::PlaybackScrollPage + : View::PlaybackIgnore); + + // Then set these values + view->setCentreFrame(centre); + view->setZoomLevel(zoom); + view->setLightBackground(light); + + // And pane properties + READ_MANDATORY(int, centreLineVisible, toInt); + m_currentPane->setCentreLineVisible(centreLineVisible); + + int height = attributes.value("height").toInt(&ok); + if (ok) { + m_currentPane->resize(m_currentPane->width(), height); + } + + return true; +} + +bool +SVFileReader::readLayer(const QXmlAttributes &attributes) +{ + QString type = attributes.value("type"); + + int id; + bool ok = false; + id = attributes.value("id").trimmed().toInt(&ok); + + if (!ok) { + std::cerr << "WARNING: SV-XML: No layer id for layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + Layer *layer = 0; + bool isNewLayer = false; + + // Layers are expected to be defined in layer elements in the data + // section, and referred to in layer elements in the view + // sections. So if we're in the data section, we expect this + // layer not to exist already; if we're in the view section, we + // expect it to exist. + + if (m_inData) { + + if (m_layers.find(id) != m_layers.end()) { + std::cerr << "WARNING: SV-XML: Ignoring duplicate layer id " << id + << " in data section" << std::endl; + return false; + } + + layer = m_layers[id] = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + + } else { + + if (!m_currentPane) { + std::cerr << "WARNING: SV-XML: No current pane for layer " << id + << " in view section" << std::endl; + return false; + } + + if (m_layers.find(id) != m_layers.end()) { + + layer = m_layers[id]; + + } else { + std::cerr << "WARNING: SV-XML: Layer id " << id + << " in view section has not been defined -- defining it here" + << std::endl; + + layer = m_document->createLayer + (LayerFactory::getInstance()->getLayerTypeForName(type)); + + if (layer) { + m_layers[id] = layer; + isNewLayer = true; + } + } + } + + if (!layer) { + std::cerr << "WARNING: SV-XML: Failed to add layer of type \"" + << type.toLocal8Bit().data() + << "\"" << std::endl; + return false; + } + + if (isNewLayer) { + + QString name = attributes.value("name"); + layer->setObjectName(name); + + int modelId; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (modelOk) { + if (m_models.find(modelId) != m_models.end()) { + Model *model = m_models[modelId]; + m_document->setModel(layer, model); + } else { + std::cerr << "WARNING: SV-XML: Unknown model id " << modelId + << " in layer definition" << std::endl; + } + } + + layer->setProperties(attributes); + } + + if (!m_inData && m_currentPane) { + m_document->addLayerToView(m_currentPane, layer); + } + + return true; +} + +bool +SVFileReader::readDatasetStart(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, id, toInt); + READ_MANDATORY(int, dimensions, toInt); + + if (m_awaitingDatasets.find(id) == m_awaitingDatasets.end()) { + std::cerr << "WARNING: SV-XML: Unwanted dataset " << id << std::endl; + return false; + } + + int modelId = m_awaitingDatasets[id]; + + Model *model = 0; + if (m_models.find(modelId) != m_models.end()) { + model = m_models[modelId]; + } else { + std::cerr << "WARNING: SV-XML: Internal error: Unknown model " << modelId + << " expecting dataset " << id << std::endl; + return false; + } + + bool good = false; + + switch (dimensions) { + case 1: + if (dynamic_cast(model)) good = true; + break; + + case 2: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) good = true; + break; + + case 3: + if (dynamic_cast(model)) good = true; + else if (dynamic_cast(model)) { + m_datasetSeparator = attributes.value("separator"); + good = true; + } + break; + } + + if (!good) { + std::cerr << "WARNING: SV-XML: Model id " << modelId << " has wrong number of dimensions for " << dimensions << "-D dataset " << id << std::endl; + m_currentDataset = 0; + return false; + } + + m_currentDataset = model; + return true; +} + +bool +SVFileReader::addPointToDataset(const QXmlAttributes &attributes) +{ + bool ok = false; + + READ_MANDATORY(int, frame, toInt); + + SparseOneDimensionalModel *sodm = dynamic_cast + (m_currentDataset); + + if (sodm) { + QString label = attributes.value("label"); + sodm->addPoint(SparseOneDimensionalModel::Point(frame, label)); + return true; + } + + SparseTimeValueModel *stvm = dynamic_cast + (m_currentDataset); + + if (stvm) { + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + stvm->addPoint(SparseTimeValueModel::Point(frame, value, label)); + return ok; + } + + NoteModel *nm = dynamic_cast(m_currentDataset); + + if (nm) { + float value = 0.0; + value = attributes.value("value").trimmed().toFloat(&ok); + float duration = 0.0; + duration = attributes.value("duration").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + nm->addPoint(NoteModel::Point(frame, value, duration, label)); + return ok; + } + + TextModel *tm = dynamic_cast(m_currentDataset); + + if (tm) { + float height = 0.0; + height = attributes.value("height").trimmed().toFloat(&ok); + QString label = attributes.value("label"); + tm->addPoint(TextModel::Point(frame, height, label)); + return ok; + } + + std::cerr << "WARNING: SV-XML: Point element found in non-point dataset" << std::endl; + + return false; +} + +bool +SVFileReader::addBinToDataset(const QXmlAttributes &attributes) +{ + DenseThreeDimensionalModel *dtdm = dynamic_cast + (m_currentDataset); + + if (dtdm) { + + bool ok = false; + int n = attributes.value("number").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid bin number" + << std::endl; + return false; + } + + QString name = attributes.value("name"); + + dtdm->setBinName(n, name); + return true; + } + + std::cerr << "WARNING: SV-XML: Bin definition found in incompatible dataset" << std::endl; + + return false; +} + + +bool +SVFileReader::addRowToDataset(const QXmlAttributes &attributes) +{ + m_inRow = false; + + bool ok = false; + m_rowNumber = attributes.value("n").trimmed().toInt(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Missing or invalid row number" + << std::endl; + return false; + } + + m_inRow = true; + +// std::cerr << "SV-XML: In row " << m_rowNumber << std::endl; + + return true; +} + +bool +SVFileReader::readRowData(const QString &text) +{ + DenseThreeDimensionalModel *dtdm = dynamic_cast + (m_currentDataset); + + bool warned = false; + + if (dtdm) { + QStringList data = text.split(m_datasetSeparator); + + DenseThreeDimensionalModel::BinValueSet values; + + for (QStringList::iterator i = data.begin(); i != data.end(); ++i) { + + if (values.size() == dtdm->getYBinCount()) { + if (!warned) { + std::cerr << "WARNING: SV-XML: Too many y-bins in 3-D dataset row " + << m_rowNumber << std::endl; + warned = true; + } + } + + bool ok; + float value = i->toFloat(&ok); + if (!ok) { + std::cerr << "WARNING: SV-XML: Bad floating-point value " + << i->toLocal8Bit().data() + << " in row data" << std::endl; + } else { + values.push_back(value); + } + } + + size_t windowStartFrame = m_rowNumber * dtdm->getWindowSize(); + + dtdm->setBinValues(windowStartFrame, values); + return true; + } + + std::cerr << "WARNING: SV-XML: Row data found in non-row dataset" << std::endl; + + return false; +} + +bool +SVFileReader::readDerivation(const QXmlAttributes &attributes) +{ + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for derivation" << std::endl; + return false; + } + + QString transform = attributes.value("transform"); + + if (m_models.find(modelId) != m_models.end()) { + + m_currentDerivedModel = m_models[modelId]; + m_currentTransform = transform; + m_currentTransformConfiguration = ""; + + bool ok = false; + int channel = attributes.value("channel").trimmed().toInt(&ok); + if (ok) m_currentTransformChannel = channel; + else m_currentTransformChannel = -1; + + } else { + std::cerr << "WARNING: SV-XML: Unknown derived model " << modelId + << " for transform \"" << transform.toLocal8Bit().data() << "\"" + << std::endl; + return false; + } + + return true; +} + +bool +SVFileReader::readPlayParameters(const QXmlAttributes &attributes) +{ + m_currentPlayParameters = 0; + + int modelId = 0; + bool modelOk = false; + modelId = attributes.value("model").trimmed().toInt(&modelOk); + + if (!modelOk) { + std::cerr << "WARNING: SV-XML: No model id specified for play parameters" << std::endl; + return false; + } + + if (m_models.find(modelId) != m_models.end()) { + + bool ok = false; + + PlayParameters *parameters = PlayParameterRepository::getInstance()-> + getPlayParameters(m_models[modelId]); + + if (!parameters) { + std::cerr << "WARNING: SV-XML: Play parameters for model " + << modelId + << " not found - has model been added to document?" + << std::endl; + return false; + } + + bool muted = (attributes.value("mute").trimmed() == "true"); + if (ok) parameters->setPlayMuted(muted); + + float pan = attributes.value("pan").toFloat(&ok); + if (ok) parameters->setPlayPan(pan); + + float gain = attributes.value("gain").toFloat(&ok); + if (ok) parameters->setPlayGain(gain); + + QString pluginId = attributes.value("pluginId"); + if (pluginId != "") parameters->setPlayPluginId(pluginId); + + m_currentPlayParameters = parameters; + +// std::cerr << "Current play parameters for model: " << m_models[modelId] << ": " << m_currentPlayParameters << std::endl; + + } else { + + std::cerr << "WARNING: SV-XML: Unknown model " << modelId + << " for play parameters" << std::endl; + return false; + } + + return true; +} + +bool +SVFileReader::readPlugin(const QXmlAttributes &attributes) +{ + if (!m_currentDerivedModel && !m_currentPlayParameters) { + std::cerr << "WARNING: SV-XML: Plugin found outside derivation or play parameters" << std::endl; + return false; + } + + QString configurationXml = "setPlayPluginConfiguration(configurationXml); + } else { + m_currentTransformConfiguration += configurationXml; + } + + return true; +} + +bool +SVFileReader::readSelection(const QXmlAttributes &attributes) +{ + bool ok; + + READ_MANDATORY(int, start, toInt); + READ_MANDATORY(int, end, toInt); + + m_paneCallback.addSelection(start, end); + + return true; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/SVFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/SVFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -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 file copyright 2006 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _SV_FILE_READER_H_ +#define _SV_FILE_READER_H_ + +#include "layer/LayerFactory.h" +#include "transform/Transform.h" + +#include + +#include + +class Pane; +class Model; +class Document; +class PlayParameters; + +class SVFileReaderPaneCallback +{ +public: + virtual Pane *addPane() = 0; + virtual void setWindowSize(int width, int height) = 0; + virtual void addSelection(int start, int end) = 0; +}; + +class SVFileReader : public QXmlDefaultHandler +{ +public: + SVFileReader(Document *document, + SVFileReaderPaneCallback &callback); + virtual ~SVFileReader(); + + void parse(const QString &xmlData); + void parse(QXmlInputSource &source); + + bool isOK(); + QString getErrorString() const { return m_errorString; } + + // For loading a single layer onto an existing pane + void setCurrentPane(Pane *pane) { m_currentPane = pane; } + + virtual bool startElement(const QString &namespaceURI, + const QString &localName, + const QString &qName, + const QXmlAttributes& atts); + + virtual bool characters(const QString &); + + virtual bool endElement(const QString &namespaceURI, + const QString &localName, + const QString &qName); + + bool error(const QXmlParseException &exception); + bool fatalError(const QXmlParseException &exception); + +protected: + bool readWindow(const QXmlAttributes &); + bool readModel(const QXmlAttributes &); + bool readView(const QXmlAttributes &); + bool readLayer(const QXmlAttributes &); + bool readDatasetStart(const QXmlAttributes &); + bool addBinToDataset(const QXmlAttributes &); + bool addPointToDataset(const QXmlAttributes &); + bool addRowToDataset(const QXmlAttributes &); + bool readRowData(const QString &); + bool readDerivation(const QXmlAttributes &); + bool readPlayParameters(const QXmlAttributes &); + bool readPlugin(const QXmlAttributes &); + bool readSelection(const QXmlAttributes &); + void addUnaddedModels(); + + Document *m_document; + SVFileReaderPaneCallback &m_paneCallback; + Pane *m_currentPane; + std::map m_layers; + std::map m_models; + std::set m_addedModels; + std::map m_awaitingDatasets; // map dataset id -> model id + Model *m_currentDataset; + Model *m_currentDerivedModel; + PlayParameters *m_currentPlayParameters; + QString m_currentTransform; + int m_currentTransformChannel; + QString m_currentTransformConfiguration; + QString m_datasetSeparator; + bool m_inRow; + bool m_inView; + bool m_inData; + bool m_inSelections; + int m_rowNumber; + QString m_errorString; + bool m_ok; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/WavFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileReader.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,112 @@ +/* -*- 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 "WavFileReader.h" + +#include + +WavFileReader::WavFileReader(QString path) : + m_file(0), + m_path(path), + m_buffer(0), + m_bufsiz(0), + m_lastStart(0), + m_lastCount(0) +{ + m_frameCount = 0; + m_channelCount = 0; + m_sampleRate = 0; + + m_fileInfo.format = 0; + m_fileInfo.frames = 0; + m_file = sf_open(m_path.toLocal8Bit(), SFM_READ, &m_fileInfo); + + if (!m_file || m_fileInfo.frames <= 0 || m_fileInfo.channels <= 0) { + std::cerr << "WavFileReader::initialize: Failed to open file (" + << sf_strerror(m_file) << ")" << std::endl; + + if (m_file) { + m_error = QString("Couldn't load audio file '%1':\n%2") + .arg(m_path).arg(sf_strerror(m_file)); + } else { + m_error = QString("Failed to open audio file '%1'") + .arg(m_path); + } + return; + } + + m_frameCount = m_fileInfo.frames; + m_channelCount = m_fileInfo.channels; + m_sampleRate = m_fileInfo.samplerate; +} + +WavFileReader::~WavFileReader() +{ + if (m_file) sf_close(m_file); +} + +void +WavFileReader::getInterleavedFrames(size_t start, size_t count, + SampleBlock &results) const +{ + results.clear(); + if (!m_file || !m_channelCount) return; + if (count == 0) return; + + if ((long)start >= m_fileInfo.frames) { + return; + } + + if (long(start + count) > m_fileInfo.frames) { + count = m_fileInfo.frames - start; + } + + sf_count_t readCount = 0; + + m_mutex.lock(); + + if (start != m_lastStart || count != m_lastCount) { + + if (sf_seek(m_file, start, SEEK_SET) < 0) { + m_mutex.unlock(); + return; + } + + if (count * m_fileInfo.channels > m_bufsiz) { +// std::cerr << "WavFileReader: Reallocating buffer for " << count +// << " frames, " << m_fileInfo.channels << " channels: " +// << m_bufsiz << " floats" << std::endl; + m_bufsiz = count * m_fileInfo.channels; + delete[] m_buffer; + m_buffer = new float[m_bufsiz]; + } + + if ((readCount = sf_readf_float(m_file, m_buffer, count)) < 0) { + m_mutex.unlock(); + return; + } + + m_lastStart = start; + m_lastCount = readCount; + } + + for (size_t i = 0; i < count * m_fileInfo.channels; ++i) { + results.push_back(m_buffer[i]); + } + + m_mutex.unlock(); + return; +} + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/WavFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileReader.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,53 @@ +/* -*- 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 _WAV_FILE_READER_H_ +#define _WAV_FILE_READER_H_ + +#include "AudioFileReader.h" + +#include +#include + +class WavFileReader : public AudioFileReader +{ +public: + WavFileReader(QString path); + virtual ~WavFileReader(); + + virtual QString getError() const { return m_error; } + + /** + * Must be safe to call from multiple threads with different + * arguments on the same object at the same time. + */ + virtual void getInterleavedFrames(size_t start, size_t count, + SampleBlock &frames) const; + +protected: + SF_INFO m_fileInfo; + SNDFILE *m_file; + + QString m_path; + QString m_error; + + mutable QMutex m_mutex; + mutable float *m_buffer; + mutable size_t m_bufsiz; + mutable size_t m_lastStart; + mutable size_t m_lastCount; +}; + +#endif diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/WavFileWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileWriter.cpp Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,120 @@ +/* -*- 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 "WavFileWriter.h" + +#include "model/DenseTimeValueModel.h" +#include "base/Selection.h" + +#include +#include + +#include + +WavFileWriter::WavFileWriter(QString path, + size_t sampleRate, + DenseTimeValueModel *source, + MultiSelection *selection) : + m_path(path), + m_sampleRate(sampleRate), + m_model(source), + m_selection(selection) +{ +} + +WavFileWriter::~WavFileWriter() +{ +} + +bool +WavFileWriter::isOK() const +{ + return (m_error.isEmpty()); +} + +QString +WavFileWriter::getError() const +{ + return m_error; +} + +void +WavFileWriter::write() +{ + int channels = m_model->getChannelCount(); + + SF_INFO fileInfo; + fileInfo.samplerate = m_sampleRate; + fileInfo.channels = channels; + fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + + SNDFILE *file = sf_open(m_path.toLocal8Bit(), SFM_WRITE, &fileInfo); + if (!file) { + std::cerr << "WavFileWriter::write: Failed to open file (" + << sf_strerror(file) << ")" << std::endl; + m_error = QString("Failed to open audio file '%1' for writing") + .arg(m_path); + return; + } + + MultiSelection *selection = m_selection; + + if (!m_selection) { + selection = new MultiSelection; + selection->setSelection(Selection(m_model->getStartFrame(), + m_model->getEndFrame())); + } + + size_t bs = 2048; + float *ub = new float[bs]; // uninterleaved buffer (one channel) + float *ib = new float[bs * channels]; // interleaved buffer + + for (MultiSelection::SelectionList::iterator i = + selection->getSelections().begin(); + i != selection->getSelections().end(); ++i) { + + size_t f0(i->getStartFrame()), f1(i->getEndFrame()); + + for (size_t f = f0; f < f1; f += bs) { + + size_t n = std::min(bs, f1 - f); + + for (int c = 0; c < channels; ++c) { + m_model->getValues(c, f, f + n, ub); + for (size_t i = 0; i < n; ++i) { + ib[i * channels + c] = ub[i]; + } + } + + sf_count_t written = sf_writef_float(file, ib, n); + + if (written < n) { + m_error = QString("Only wrote %1 of %2 frames at file frame %3") + .arg(written).arg(n).arg(f); + break; + } + } + } + + sf_close(file); + + delete[] ub; + delete[] ib; + if (!m_selection) delete selection; +} + + + + diff -r 3a13b0d4934e -r 1a42221a1522 data/fileio/WavFileWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/WavFileWriter.h Mon Jul 31 11:49:58 2006 +0000 @@ -0,0 +1,47 @@ +/* -*- 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 _WAV_FILE_WRITER_H_ +#define _WAV_FILE_WRITER_H_ + +#include + +class DenseTimeValueModel; +class MultiSelection; + +class WavFileWriter +{ +public: + WavFileWriter(QString path, size_t sampleRate, + DenseTimeValueModel *source, + MultiSelection *selection); + virtual ~WavFileWriter(); + + bool isOK() const; + + virtual QString getError() const; + + void write(); + +protected: + QString m_path; + size_t m_sampleRate; + DenseTimeValueModel *m_model; + MultiSelection *m_selection; + QString m_error; +}; + + +#endif