diff data/fft/FFTDataServer.cpp @ 148:1a42221a1522

* Reorganising code base. This revision will not compile.
author Chris Cannam
date Mon, 31 Jul 2006 11:49:58 +0000
parents
children 4b2ea82fd0ed
line wrap: on
line diff
--- /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;
+}
+