Chris@148: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@148: Chris@148: /* Chris@148: Sonic Visualiser Chris@148: An audio file viewer and annotation editor. Chris@148: Centre for Digital Music, Queen Mary, University of London. Chris@202: This file copyright 2006 Chris Cannam and QMUL. Chris@148: Chris@148: This program is free software; you can redistribute it and/or Chris@148: modify it under the terms of the GNU General Public License as Chris@148: published by the Free Software Foundation; either version 2 of the Chris@148: License, or (at your option) any later version. See the file Chris@148: COPYING included with this distribution for more information. Chris@148: */ Chris@148: Chris@148: #include "FFTDataServer.h" Chris@148: Chris@537: #include "FFTFileCacheReader.h" Chris@537: #include "FFTFileCacheWriter.h" Chris@159: #include "FFTMemoryCache.h" Chris@148: Chris@148: #include "model/DenseTimeValueModel.h" Chris@148: Chris@150: #include "system/System.h" Chris@148: Chris@168: #include "base/StorageAdviser.h" Chris@200: #include "base/Exceptions.h" Chris@183: #include "base/Profiler.h" Chris@244: #include "base/Thread.h" // for debug mutex locker Chris@168: Chris@537: #include Chris@537: Chris@571: //#define DEBUG_FFT_SERVER 1 Chris@536: //#define DEBUG_FFT_SERVER_FILL 1 Chris@148: Chris@148: #ifdef DEBUG_FFT_SERVER_FILL Chris@153: #ifndef DEBUG_FFT_SERVER Chris@153: #define DEBUG_FFT_SERVER 1 Chris@153: #endif Chris@148: #endif Chris@148: Chris@244: Chris@148: FFTDataServer::ServerMap FFTDataServer::m_servers; Chris@215: FFTDataServer::ServerQueue FFTDataServer::m_releasedServers; Chris@148: QMutex FFTDataServer::m_serverMapMutex; Chris@148: Chris@148: FFTDataServer * Chris@148: FFTDataServer::getInstance(const DenseTimeValueModel *model, Chris@148: int channel, Chris@148: WindowType windowType, Chris@148: size_t windowSize, Chris@148: size_t windowIncrement, Chris@148: size_t fftSize, Chris@148: bool polar, Chris@334: StorageAdviser::Criteria criteria, Chris@148: size_t fillFromColumn) Chris@148: { Chris@148: QString n = generateFileBasename(model, Chris@148: channel, Chris@148: windowType, Chris@148: windowSize, Chris@148: windowIncrement, Chris@148: fftSize, Chris@148: polar); Chris@148: Chris@148: FFTDataServer *server = 0; Chris@148: Chris@408: MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getInstance::m_serverMapMutex"); Chris@148: Chris@148: if ((server = findServer(n))) { Chris@148: return server; Chris@148: } Chris@148: Chris@148: QString npn = generateFileBasename(model, Chris@148: channel, Chris@148: windowType, Chris@148: windowSize, Chris@148: windowIncrement, Chris@148: fftSize, Chris@148: !polar); Chris@148: Chris@148: if ((server = findServer(npn))) { Chris@148: return server; Chris@148: } Chris@148: Chris@200: try { Chris@200: server = new FFTDataServer(n, Chris@200: model, Chris@200: channel, Chris@200: windowType, Chris@200: windowSize, Chris@200: windowIncrement, Chris@200: fftSize, Chris@200: polar, Chris@334: criteria, Chris@200: fillFromColumn); Chris@200: } catch (InsufficientDiscSpace) { Chris@200: delete server; Chris@200: server = 0; Chris@200: } Chris@148: Chris@200: if (server) { Chris@200: m_servers[n] = ServerCountPair(server, 1); Chris@200: } Chris@200: Chris@200: return server; Chris@148: } Chris@148: Chris@148: FFTDataServer * Chris@148: FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model, Chris@148: int channel, Chris@148: WindowType windowType, Chris@148: size_t windowSize, Chris@148: size_t windowIncrement, Chris@148: size_t fftSize, Chris@148: bool polar, Chris@334: StorageAdviser::Criteria criteria, Chris@148: size_t fillFromColumn) Chris@148: { Chris@148: // Fuzzy matching: Chris@148: // Chris@148: // -- if we're asked for polar and have non-polar, use it (and Chris@148: // vice versa). This one is vital, and we do it for non-fuzzy as Chris@148: // well (above). Chris@148: // Chris@148: // -- if we're asked for an instance with a given fft size and we Chris@148: // have one already with a multiple of that fft size but the same Chris@148: // window size and type (and model), we can draw the results from Chris@148: // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the Chris@148: // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the Chris@148: // same window plus zero padding). Chris@148: // Chris@148: // -- if we're asked for an instance with a given window type and Chris@148: // size and fft size and we have one already the same but with a Chris@148: // smaller increment, we can draw the results from it (provided Chris@148: // our increment is a multiple of its) Chris@148: // Chris@152: // The FFTModel knows how to interpret these things. In Chris@148: // both cases we require that the larger one is a power-of-two Chris@148: // multiple of the smaller (e.g. even though in principle you can Chris@148: // draw the results at increment 256 from those at increment 768 Chris@152: // or 1536, the model doesn't support this). Chris@148: Chris@148: { Chris@408: MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getFuzzyInstance::m_serverMapMutex"); Chris@148: Chris@148: ServerMap::iterator best = m_servers.end(); Chris@148: int bestdist = -1; Chris@148: Chris@148: for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { Chris@148: Chris@148: FFTDataServer *server = i->second.first; Chris@148: Chris@148: if (server->getModel() == model && Chris@148: (server->getChannel() == channel || model->getChannelCount() == 1) && Chris@148: server->getWindowType() == windowType && Chris@148: server->getWindowSize() == windowSize && Chris@148: server->getWindowIncrement() <= windowIncrement && Chris@148: server->getFFTSize() >= fftSize) { Chris@148: Chris@148: if ((windowIncrement % server->getWindowIncrement()) != 0) continue; Chris@148: int ratio = windowIncrement / server->getWindowIncrement(); Chris@148: bool poweroftwo = true; Chris@148: while (ratio > 1) { Chris@148: if (ratio & 0x1) { Chris@148: poweroftwo = false; Chris@148: break; Chris@148: } Chris@148: ratio >>= 1; Chris@148: } Chris@148: if (!poweroftwo) continue; Chris@148: Chris@148: if ((server->getFFTSize() % fftSize) != 0) continue; Chris@148: ratio = server->getFFTSize() / fftSize; Chris@148: while (ratio > 1) { Chris@148: if (ratio & 0x1) { Chris@148: poweroftwo = false; Chris@148: break; Chris@148: } Chris@148: ratio >>= 1; Chris@148: } Chris@148: if (!poweroftwo) continue; Chris@148: Chris@148: int distance = 0; Chris@148: Chris@148: if (server->getPolar() != polar) distance += 1; Chris@148: Chris@148: distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15; Chris@148: distance += ((server->getFFTSize() / fftSize) - 1) * 10; Chris@148: Chris@148: if (server->getFillCompletion() < 50) distance += 100; Chris@148: Chris@148: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << endl; Chris@148: #endif Chris@148: Chris@148: if (bestdist == -1 || distance < bestdist) { Chris@148: bestdist = distance; Chris@148: best = i; Chris@148: } Chris@148: } Chris@148: } Chris@148: Chris@148: if (bestdist >= 0) { Chris@216: FFTDataServer *server = best->second.first; Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << endl; Chris@216: #endif Chris@216: claimInstance(server, false); Chris@216: return server; Chris@148: } Chris@148: } Chris@148: Chris@148: // Nothing found, make a new one Chris@148: Chris@148: return getInstance(model, Chris@148: channel, Chris@148: windowType, Chris@148: windowSize, Chris@148: windowIncrement, Chris@148: fftSize, Chris@148: polar, Chris@334: criteria, Chris@148: fillFromColumn); Chris@148: } Chris@148: Chris@148: FFTDataServer * Chris@148: FFTDataServer::findServer(QString n) Chris@148: { Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::findServer(\"" << n << "\")" << endl; Chris@216: #endif Chris@216: Chris@148: if (m_servers.find(n) != m_servers.end()) { Chris@216: Chris@216: FFTDataServer *server = m_servers[n].first; Chris@216: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::findServer(\"" << n << "\"): found " << server << endl; Chris@216: #endif Chris@216: Chris@216: claimInstance(server, false); Chris@216: Chris@216: return server; Chris@148: } Chris@148: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::findServer(\"" << n << "\"): not found" << endl; Chris@216: #endif Chris@216: Chris@148: return 0; Chris@148: } Chris@148: Chris@148: void Chris@152: FFTDataServer::claimInstance(FFTDataServer *server) Chris@152: { Chris@216: claimInstance(server, true); Chris@216: } Chris@216: Chris@216: void Chris@216: FFTDataServer::claimInstance(FFTDataServer *server, bool needLock) Chris@216: { Chris@244: MutexLocker locker(needLock ? &m_serverMapMutex : 0, Chris@408: "FFTDataServer::claimInstance::m_serverMapMutex"); Chris@216: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::claimInstance(" << server << ")" << endl; Chris@216: #endif Chris@152: Chris@152: for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { Chris@152: if (i->second.first == server) { Chris@215: Chris@215: for (ServerQueue::iterator j = m_releasedServers.begin(); Chris@215: j != m_releasedServers.end(); ++j) { Chris@216: Chris@215: if (*j == server) { Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::claimInstance: found in released server list, removing from it" << endl; Chris@216: #endif Chris@215: m_releasedServers.erase(j); Chris@215: break; Chris@215: } Chris@215: } Chris@215: Chris@152: ++i->second.second; Chris@216: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::claimInstance: new refcount is " << i->second.second << endl; Chris@216: #endif Chris@216: Chris@152: return; Chris@152: } Chris@152: } Chris@152: Chris@152: std::cerr << "ERROR: FFTDataServer::claimInstance: instance " Chris@152: << server << " unknown!" << std::endl; Chris@152: } Chris@152: Chris@152: void Chris@148: FFTDataServer::releaseInstance(FFTDataServer *server) Chris@148: { Chris@216: releaseInstance(server, true); Chris@216: } Chris@216: Chris@216: void Chris@216: FFTDataServer::releaseInstance(FFTDataServer *server, bool needLock) Chris@216: { Chris@244: MutexLocker locker(needLock ? &m_serverMapMutex : 0, Chris@408: "FFTDataServer::releaseInstance::m_serverMapMutex"); Chris@216: Chris@148: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::releaseInstance(" << server << ")" << endl; Chris@148: #endif Chris@148: Chris@148: // -- if ref count > 0, decrement and return Chris@148: // -- if the instance hasn't been used at all, delete it immediately Chris@148: // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts, Chris@148: // leave them hanging around Chris@148: // -- if N instances with zero refcounts remain, delete the one that Chris@148: // was last released first Chris@148: // -- if we run out of disk space when allocating an instance, go back Chris@148: // and delete the spare N instances before trying again Chris@148: // -- have an additional method to indicate that a model has been Chris@148: // destroyed, so that we can delete all of its fft server instances Chris@148: Chris@148: for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { Chris@148: if (i->second.first == server) { Chris@148: if (i->second.second == 0) { Chris@148: std::cerr << "ERROR: FFTDataServer::releaseInstance(" Chris@148: << server << "): instance not allocated" << std::endl; Chris@148: } else if (--i->second.second == 0) { Chris@537: /*!!! Chris@148: if (server->m_lastUsedCache == -1) { // never used Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::releaseInstance: instance " Chris@216: << server << " has never been used, erasing" Chris@687: << endl; Chris@216: #endif Chris@148: delete server; Chris@148: m_servers.erase(i); Chris@148: } else { Chris@537: */ Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::releaseInstance: instance " Chris@216: << server << " no longer in use, marking for possible collection" Chris@687: << endl; Chris@216: #endif Chris@216: bool found = false; Chris@216: for (ServerQueue::iterator j = m_releasedServers.begin(); Chris@216: j != m_releasedServers.end(); ++j) { Chris@216: if (*j == server) { Chris@216: std::cerr << "ERROR: FFTDataServer::releaseInstance(" Chris@216: << server << "): server is already in " Chris@216: << "released servers list" << std::endl; Chris@216: found = true; Chris@216: } Chris@216: } Chris@216: if (!found) m_releasedServers.push_back(server); Chris@148: server->suspend(); Chris@148: purgeLimbo(); Chris@537: //!!! } Chris@216: } else { Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::releaseInstance: instance " Chris@216: << server << " now has refcount " << i->second.second Chris@687: << endl; Chris@216: #endif Chris@148: } Chris@148: return; Chris@148: } Chris@148: } Chris@148: Chris@148: std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): " Chris@148: << "instance not found" << std::endl; Chris@148: } Chris@148: Chris@148: void Chris@148: FFTDataServer::purgeLimbo(int maxSize) Chris@148: { Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::purgeLimbo(" << maxSize << "): " Chris@687: << m_releasedServers.size() << " candidates" << endl; Chris@216: #endif Chris@216: Chris@259: while (int(m_releasedServers.size()) > maxSize) { Chris@148: Chris@215: FFTDataServer *server = *m_releasedServers.begin(); Chris@148: Chris@215: bool found = false; Chris@215: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::purgeLimbo: considering candidate " Chris@687: << server << endl; Chris@216: #endif Chris@216: Chris@215: for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { Chris@215: Chris@215: if (i->second.first == server) { Chris@215: found = true; Chris@215: if (i->second.second > 0) { Chris@215: std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server " Chris@215: << server << " is in released queue, but still has non-zero refcount " Chris@215: << i->second.second << std::endl; Chris@215: // ... so don't delete it Chris@215: break; Chris@215: } Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::purgeLimbo: looks OK, erasing it" Chris@687: << endl; Chris@216: #endif Chris@216: Chris@148: m_servers.erase(i); Chris@215: delete server; Chris@215: break; Chris@148: } Chris@148: } Chris@215: Chris@215: if (!found) { Chris@215: std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server " Chris@215: << server << " is in released queue, but not in server map!" Chris@215: << std::endl; Chris@215: delete server; Chris@215: } Chris@215: Chris@215: m_releasedServers.pop_front(); Chris@215: } Chris@216: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::purgeLimbo(" << maxSize << "): " Chris@687: << m_releasedServers.size() << " remain" << endl; Chris@216: #endif Chris@216: Chris@215: } Chris@215: Chris@215: void Chris@215: FFTDataServer::modelAboutToBeDeleted(Model *model) Chris@215: { Chris@244: MutexLocker locker(&m_serverMapMutex, Chris@408: "FFTDataServer::modelAboutToBeDeleted::m_serverMapMutex"); Chris@215: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::modelAboutToBeDeleted(" << model << ")" Chris@687: << endl; Chris@216: #endif Chris@216: Chris@215: for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { Chris@215: Chris@215: FFTDataServer *server = i->second.first; Chris@215: Chris@215: if (server->getModel() == model) { Chris@216: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: server is " Chris@687: << server << endl; Chris@216: #endif Chris@216: Chris@215: if (i->second.second > 0) { Chris@686: std::cerr << "WARNING: FFTDataServer::modelAboutToBeDeleted: Model " << model << " (\"" << model->objectName() << "\") is about to be deleted, but is still being referred to by FFT server " << server << " with non-zero refcount " << i->second.second << std::endl; Chris@497: server->suspendWrites(); Chris@362: return; Chris@215: } Chris@215: for (ServerQueue::iterator j = m_releasedServers.begin(); Chris@215: j != m_releasedServers.end(); ++j) { Chris@215: if (*j == server) { Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << endl; Chris@216: #endif Chris@215: m_releasedServers.erase(j); Chris@215: break; Chris@215: } Chris@215: } Chris@216: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::modelAboutToBeDeleted: erasing server" << endl; Chris@216: #endif Chris@215: m_servers.erase(i); Chris@215: delete server; Chris@215: return; Chris@215: } Chris@148: } Chris@148: } Chris@148: Chris@148: FFTDataServer::FFTDataServer(QString fileBaseName, Chris@148: const DenseTimeValueModel *model, Chris@148: int channel, Chris@148: WindowType windowType, Chris@148: size_t windowSize, Chris@148: size_t windowIncrement, Chris@148: size_t fftSize, Chris@148: bool polar, Chris@334: StorageAdviser::Criteria criteria, Chris@148: size_t fillFromColumn) : Chris@148: m_fileBaseName(fileBaseName), Chris@148: m_model(model), Chris@148: m_channel(channel), Chris@148: m_windower(windowType, windowSize), Chris@148: m_windowSize(windowSize), Chris@148: m_windowIncrement(windowIncrement), Chris@148: m_fftSize(fftSize), Chris@148: m_polar(polar), Chris@183: m_width(0), Chris@183: m_height(0), Chris@183: m_cacheWidth(0), Chris@359: m_cacheWidthPower(0), Chris@359: m_cacheWidthMask(0), Chris@359: m_criteria(criteria), Chris@148: m_fftInput(0), Chris@148: m_exiting(false), Chris@153: m_suspended(true), //!!! or false? Chris@148: m_fillThread(0) Chris@148: { Chris@193: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << std::endl; Chris@193: #endif Chris@193: Chris@272: //!!! end is not correct until model finished reading -- what to do??? Chris@272: Chris@148: size_t start = m_model->getStartFrame(); Chris@148: size_t end = m_model->getEndFrame(); Chris@148: Chris@148: m_width = (end - start) / m_windowIncrement + 1; Chris@203: m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2 Chris@148: Chris@216: #ifdef DEBUG_FFT_SERVER Chris@216: std::cerr << "FFTDataServer(" << this << "): dimensions are " Chris@216: << m_width << "x" << m_height << std::endl; Chris@216: #endif Chris@216: Chris@148: size_t maxCacheSize = 20 * 1024 * 1024; Chris@148: size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample); Chris@148: if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width; Chris@148: else m_cacheWidth = maxCacheSize / columnSize; Chris@148: Chris@359: #ifdef DEBUG_FFT_SERVER Chris@359: std::cerr << "FFTDataServer(" << this << "): cache width nominal " Chris@359: << m_cacheWidth << ", actual "; Chris@359: #endif Chris@359: Chris@148: int bits = 0; Chris@359: while (m_cacheWidth > 1) { m_cacheWidth >>= 1; ++bits; } Chris@359: m_cacheWidthPower = bits + 1; Chris@148: m_cacheWidth = 2; Chris@148: while (bits) { m_cacheWidth <<= 1; --bits; } Chris@359: m_cacheWidthMask = m_cacheWidth - 1; Chris@172: Chris@359: #ifdef DEBUG_FFT_SERVER Chris@359: std::cerr << m_cacheWidth << " (power " << m_cacheWidthPower << ", mask " Chris@359: << m_cacheWidthMask << ")" << std::endl; Chris@359: #endif Chris@359: Chris@359: if (m_criteria == StorageAdviser::NoCriteria) { Chris@172: Chris@334: // assume "spectrogram" criteria for polar ffts, and "feature Chris@334: // extraction" criteria for rectangular ones. Chris@334: Chris@334: if (m_polar) { Chris@359: m_criteria = StorageAdviser::Criteria Chris@334: (StorageAdviser::SpeedCritical | Chris@334: StorageAdviser::LongRetentionLikely); Chris@334: } else { Chris@359: m_criteria = StorageAdviser::Criteria Chris@334: (StorageAdviser::PrecisionCritical); Chris@334: } Chris@172: } Chris@172: Chris@148: for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) { Chris@148: m_caches.push_back(0); Chris@148: } Chris@148: Chris@148: m_fftInput = (fftsample *) Chris@226: fftf_malloc(fftSize * sizeof(fftsample)); Chris@148: Chris@226: m_fftOutput = (fftf_complex *) Chris@226: fftf_malloc((fftSize/2 + 1) * sizeof(fftf_complex)); Chris@148: Chris@148: m_workbuffer = (float *) Chris@226: fftf_malloc((fftSize+2) * sizeof(float)); Chris@148: Chris@226: m_fftPlan = fftf_plan_dft_r2c_1d(m_fftSize, Chris@334: m_fftInput, Chris@334: m_fftOutput, Chris@334: FFTW_MEASURE); Chris@148: Chris@148: if (!m_fftPlan) { Chris@226: std::cerr << "ERROR: fftf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl; Chris@148: throw(0); Chris@148: } Chris@148: Chris@148: m_fillThread = new FillThread(*this, fillFromColumn); Chris@148: } Chris@148: Chris@148: FFTDataServer::~FFTDataServer() Chris@148: { Chris@148: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << std::endl; Chris@148: #endif Chris@148: Chris@155: m_suspended = false; Chris@148: m_exiting = true; Chris@148: m_condition.wakeAll(); Chris@148: if (m_fillThread) { Chris@148: m_fillThread->wait(); Chris@148: delete m_fillThread; Chris@148: } Chris@148: Chris@549: // MutexLocker locker(&m_writeMutex, Chris@549: // "FFTDataServer::~FFTDataServer::m_writeMutex"); Chris@549: Chris@549: QMutexLocker mlocker(&m_fftBuffersLock); Chris@549: QWriteLocker wlocker(&m_cacheVectorLock); Chris@148: Chris@148: for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { Chris@205: if (*i) { Chris@205: delete *i; Chris@205: } Chris@148: } Chris@148: Chris@148: deleteProcessingData(); Chris@148: } Chris@148: Chris@148: void Chris@148: FFTDataServer::deleteProcessingData() Chris@148: { Chris@193: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << std::endl; Chris@193: #endif Chris@148: if (m_fftInput) { Chris@226: fftf_destroy_plan(m_fftPlan); Chris@226: fftf_free(m_fftInput); Chris@226: fftf_free(m_fftOutput); Chris@226: fftf_free(m_workbuffer); Chris@148: } Chris@148: m_fftInput = 0; Chris@148: } Chris@148: Chris@148: void Chris@148: FFTDataServer::suspend() Chris@148: { Chris@148: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << std::endl; Chris@148: #endif Chris@183: Profiler profiler("FFTDataServer::suspend", false); Chris@183: Chris@549: QMutexLocker locker(&m_fftBuffersLock); Chris@148: m_suspended = true; Chris@148: } Chris@148: Chris@148: void Chris@155: FFTDataServer::suspendWrites() Chris@155: { Chris@155: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << std::endl; Chris@155: #endif Chris@183: Profiler profiler("FFTDataServer::suspendWrites", false); Chris@183: Chris@155: m_suspended = true; Chris@155: } Chris@155: Chris@155: void Chris@148: FFTDataServer::resume() Chris@148: { Chris@154: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << std::endl; Chris@154: #endif Chris@183: Profiler profiler("FFTDataServer::resume", false); Chris@183: Chris@148: m_suspended = false; Chris@157: if (m_fillThread) { Chris@157: if (m_fillThread->isFinished()) { Chris@157: delete m_fillThread; Chris@157: m_fillThread = 0; Chris@157: deleteProcessingData(); Chris@411: } else if (!m_fillThread->isRunning()) { Chris@411: m_fillThread->start(); Chris@157: } else { Chris@157: m_condition.wakeAll(); Chris@157: } Chris@157: } Chris@148: } Chris@148: Chris@359: void Chris@359: FFTDataServer::getStorageAdvice(size_t w, size_t h, Chris@359: bool &memoryCache, bool &compactCache) Chris@359: { Chris@359: int cells = w * h; Chris@359: int minimumSize = (cells / 1024) * sizeof(uint16_t); // kb Chris@359: int maximumSize = (cells / 1024) * sizeof(float); // kb Chris@359: Chris@359: // We don't have a compact rectangular representation, and compact Chris@359: // of course is never precision-critical Chris@359: Chris@359: bool canCompact = true; Chris@359: if ((m_criteria & StorageAdviser::PrecisionCritical) || !m_polar) { Chris@359: canCompact = false; Chris@359: minimumSize = maximumSize; // don't use compact Chris@359: } Chris@359: Chris@359: StorageAdviser::Recommendation recommendation; Chris@359: Chris@359: try { Chris@359: Chris@359: recommendation = Chris@359: StorageAdviser::recommend(m_criteria, minimumSize, maximumSize); Chris@359: Chris@359: } catch (InsufficientDiscSpace s) { Chris@359: Chris@359: // Delete any unused servers we may have been leaving around Chris@359: // in case we wanted them again Chris@359: Chris@359: purgeLimbo(0); Chris@359: Chris@359: // This time we don't catch InsufficientDiscSpace -- we Chris@359: // haven't allocated anything yet and can safely let the Chris@359: // exception out to indicate to the caller that we can't Chris@359: // handle it. Chris@359: Chris@359: recommendation = Chris@359: StorageAdviser::recommend(m_criteria, minimumSize, maximumSize); Chris@359: } Chris@359: Chris@436: // std::cerr << "Recommendation was: " << recommendation << std::endl; Chris@359: Chris@359: memoryCache = false; Chris@359: Chris@359: if ((recommendation & StorageAdviser::UseMemory) || Chris@359: (recommendation & StorageAdviser::PreferMemory)) { Chris@538: memoryCache = true; Chris@359: } Chris@359: Chris@359: compactCache = canCompact && Chris@359: (recommendation & StorageAdviser::ConserveSpace); Chris@359: Chris@374: #ifdef DEBUG_FFT_SERVER Chris@359: std::cerr << "FFTDataServer: memory cache = " << memoryCache << ", compact cache = " << compactCache << std::endl; Chris@359: Chris@359: std::cerr << "Width " << w << " of " << m_width << ", height " << h << ", size " << w * h << std::endl; Chris@359: #endif Chris@359: } Chris@359: Chris@537: bool Chris@537: FFTDataServer::makeCache(int c) Chris@148: { Chris@548: // Creating the cache could take a significant amount of time. We Chris@548: // don't want to block readers on m_cacheVectorLock while this is Chris@548: // happening, but we do want to block any further calls to Chris@548: // makeCache. So we use this lock solely to serialise this Chris@548: // particular function -- it isn't used anywhere else. Chris@183: Chris@548: QMutexLocker locker(&m_cacheCreationMutex); Chris@548: Chris@548: m_cacheVectorLock.lockForRead(); Chris@537: if (m_caches[c]) { Chris@537: // someone else must have created the cache between our Chris@548: // testing for it and taking the mutex Chris@548: m_cacheVectorLock.unlock(); Chris@537: return true; Chris@148: } Chris@548: m_cacheVectorLock.unlock(); Chris@548: Chris@548: // Now m_cacheCreationMutex is held, but m_cacheVectorLock is not Chris@548: // -- readers can proceed, but callers to this function will block Chris@148: Chris@537: CacheBlock *cb = new CacheBlock; Chris@148: Chris@148: QString name = QString("%1-%2").arg(m_fileBaseName).arg(c); Chris@148: Chris@213: size_t width = m_cacheWidth; Chris@213: if (c * m_cacheWidth + width > m_width) { Chris@213: width = m_width - c * m_cacheWidth; Chris@213: } Chris@213: Chris@359: bool memoryCache = false; Chris@359: bool compactCache = false; Chris@359: Chris@359: getStorageAdvice(width, m_height, memoryCache, compactCache); Chris@359: Chris@537: bool success = false; Chris@264: Chris@537: if (memoryCache) { Chris@172: Chris@537: try { Chris@172: Chris@537: cb->memoryCache = new FFTMemoryCache Chris@537: (compactCache ? FFTCache::Compact : Chris@537: m_polar ? FFTCache::Polar : Chris@537: FFTCache::Rectangular, Chris@537: width, m_height); Chris@172: Chris@537: success = true; Chris@200: Chris@537: } catch (std::bad_alloc) { Chris@200: Chris@537: delete cb->memoryCache; Chris@537: cb->memoryCache = 0; Chris@213: Chris@537: std::cerr << "WARNING: Memory allocation failed when creating" Chris@537: << " FFT memory cache no. " << c << " of " << width Chris@213: << "x" << m_height << " (of total width " << m_width Chris@213: << "): falling back to disc cache" << std::endl; Chris@213: Chris@537: memoryCache = false; Chris@213: } Chris@172: } Chris@148: Chris@537: if (!memoryCache) { Chris@537: Chris@537: try { Chris@537: Chris@537: cb->fileCacheWriter = new FFTFileCacheWriter Chris@537: (name, Chris@537: compactCache ? FFTCache::Compact : Chris@537: m_polar ? FFTCache::Polar : Chris@537: FFTCache::Rectangular, Chris@537: width, m_height); Chris@537: Chris@537: success = true; Chris@537: Chris@678: } catch (std::exception &e) { Chris@537: Chris@537: delete cb->fileCacheWriter; Chris@537: cb->fileCacheWriter = 0; Chris@537: Chris@537: std::cerr << "ERROR: Failed to construct disc cache for FFT data: " Chris@537: << e.what() << std::endl; Chris@678: Chris@678: throw; Chris@537: } Chris@537: } Chris@537: Chris@548: m_cacheVectorLock.lockForWrite(); Chris@548: Chris@537: m_caches[c] = cb; Chris@537: Chris@548: m_cacheVectorLock.unlock(); Chris@548: Chris@537: return success; Chris@148: } Chris@537: Chris@537: bool Chris@537: FFTDataServer::makeCacheReader(int c) Chris@537: { Chris@537: // preconditions: m_caches[c] exists and contains a file writer; Chris@537: // m_cacheVectorLock is not locked by this thread Chris@537: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::makeCacheReader(" << c << ")" << endl; Chris@537: #endif Chris@148: Chris@537: QThread *me = QThread::currentThread(); Chris@537: QWriteLocker locker(&m_cacheVectorLock); Chris@537: CacheBlock *cb(m_caches.at(c)); Chris@537: if (!cb || !cb->fileCacheWriter) return false; Chris@537: Chris@537: try { Chris@537: Chris@537: cb->fileCacheReader[me] = new FFTFileCacheReader(cb->fileCacheWriter); Chris@537: Chris@678: } catch (std::exception &e) { Chris@537: Chris@537: delete cb->fileCacheReader[me]; Chris@537: cb->fileCacheReader.erase(me); Chris@537: Chris@537: std::cerr << "ERROR: Failed to construct disc cache reader for FFT data: " Chris@537: << e.what() << std::endl; Chris@537: return false; Chris@537: } Chris@537: Chris@537: // erase a reader that looks like it may no longer going to be Chris@537: // used by this thread for a while (leaving alone the current Chris@537: // and previous cache readers) Chris@537: int deleteCandidate = c - 2; Chris@537: if (deleteCandidate < 0) deleteCandidate = c + 2; Chris@537: if (deleteCandidate >= m_caches.size()) { Chris@537: return true; Chris@537: } Chris@537: Chris@537: cb = m_caches.at(deleteCandidate); Chris@537: if (cb && cb->fileCacheReader.find(me) != cb->fileCacheReader.end()) { Chris@537: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << endl; Chris@537: #endif Chris@537: delete cb->fileCacheReader[me]; Chris@537: cb->fileCacheReader.erase(me); Chris@537: } Chris@537: Chris@537: return true; Chris@537: } Chris@537: Chris@148: float Chris@148: FFTDataServer::getMagnitudeAt(size_t x, size_t y) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::getMagnitudeAt", false); Chris@183: Chris@217: if (x >= m_width || y >= m_height) return 0; Chris@217: Chris@678: float val = 0; Chris@148: Chris@678: try { Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return 0; Chris@678: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getMagnitudeAt: filling"); Chris@280: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::getMagnitudeAt: calling fillColumn(" Chris@687: << x << ")" << endl; Chris@280: #endif Chris@678: fillColumn(x); Chris@678: } Chris@678: Chris@678: val = cache->getMagnitudeAt(col, y); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@148: } Chris@678: Chris@678: return val; Chris@148: } Chris@148: Chris@408: bool Chris@408: FFTDataServer::getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) Chris@408: { Chris@408: Profiler profiler("FFTDataServer::getMagnitudesAt", false); Chris@408: Chris@408: if (x >= m_width) return false; Chris@408: Chris@408: if (minbin >= m_height) minbin = m_height - 1; Chris@408: if (count == 0) count = (m_height - minbin) / step; Chris@408: else if (minbin + count * step > m_height) { Chris@408: count = (m_height - minbin) / step; Chris@408: } Chris@408: Chris@678: try { Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return false; Chris@408: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getMagnitudesAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: Chris@678: cache->getMagnitudesAt(col, values, minbin, count, step); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@678: return false; Chris@408: } Chris@408: Chris@408: return true; Chris@408: } Chris@408: Chris@148: float Chris@148: FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false); Chris@183: Chris@217: if (x >= m_width || y >= m_height) return 0; Chris@217: Chris@678: float val = 0; Chris@148: Chris@678: try { Chris@678: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return 0; Chris@678: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: val = cache->getNormalizedMagnitudeAt(col, y); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@148: } Chris@678: Chris@678: return val; Chris@148: } Chris@148: Chris@408: bool Chris@408: FFTDataServer::getNormalizedMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) Chris@408: { Chris@408: Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt", false); Chris@408: Chris@408: if (x >= m_width) return false; Chris@408: Chris@408: if (minbin >= m_height) minbin = m_height - 1; Chris@408: if (count == 0) count = (m_height - minbin) / step; Chris@408: else if (minbin + count * step > m_height) { Chris@408: count = (m_height - minbin) / step; Chris@408: } Chris@408: Chris@678: try { Chris@408: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return false; Chris@408: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: Chris@678: for (size_t i = 0; i < count; ++i) { Chris@678: values[i] = cache->getNormalizedMagnitudeAt(col, i * step + minbin); Chris@678: } Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@678: return false; Chris@408: } Chris@408: Chris@408: return true; Chris@408: } Chris@408: Chris@148: float Chris@148: FFTDataServer::getMaximumMagnitudeAt(size_t x) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false); Chris@183: Chris@217: if (x >= m_width) return 0; Chris@217: Chris@678: float val = 0; Chris@148: Chris@678: try { Chris@678: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return 0; Chris@678: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getMaximumMagnitudeAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: val = cache->getMaximumMagnitudeAt(col); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@148: } Chris@678: Chris@678: return val; Chris@148: } Chris@148: Chris@148: float Chris@148: FFTDataServer::getPhaseAt(size_t x, size_t y) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::getPhaseAt", false); Chris@183: Chris@217: if (x >= m_width || y >= m_height) return 0; Chris@217: Chris@678: float val = 0; Chris@148: Chris@678: try { Chris@678: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return 0; Chris@678: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getPhaseAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: val = cache->getPhaseAt(col, y); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@148: } Chris@678: Chris@678: return val; Chris@148: } Chris@148: Chris@408: bool Chris@408: FFTDataServer::getPhasesAt(size_t x, float *values, size_t minbin, size_t count, size_t step) Chris@408: { Chris@408: Profiler profiler("FFTDataServer::getPhasesAt", false); Chris@408: Chris@408: if (x >= m_width) return false; Chris@408: Chris@408: if (minbin >= m_height) minbin = m_height - 1; Chris@408: if (count == 0) count = (m_height - minbin) / step; Chris@408: else if (minbin + count * step > m_height) { Chris@408: count = (m_height - minbin) / step; Chris@408: } Chris@408: Chris@678: try { Chris@408: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return false; Chris@408: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getPhasesAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: Chris@678: for (size_t i = 0; i < count; ++i) { Chris@678: values[i] = cache->getPhaseAt(col, i * step + minbin); Chris@678: } Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@678: return false; Chris@408: } Chris@408: Chris@408: return true; Chris@408: } Chris@408: Chris@148: void Chris@148: FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::getValuesAt", false); Chris@183: Chris@216: if (x >= m_width || y >= m_height) { Chris@216: real = 0; Chris@216: imaginary = 0; Chris@216: return; Chris@216: } Chris@216: Chris@678: try { Chris@216: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: Chris@678: if (!cache) { Chris@678: real = 0; Chris@678: imaginary = 0; Chris@678: return; Chris@678: } Chris@678: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getValuesAt: filling"); Chris@678: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << endl; Chris@678: #endif Chris@678: fillColumn(x); Chris@678: } Chris@678: Chris@678: cache->getValuesAt(col, y, real, imaginary); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@216: } Chris@148: } Chris@148: Chris@148: bool Chris@556: FFTDataServer::getValuesAt(size_t x, float *reals, float *imaginaries, size_t minbin, size_t count, size_t step) Chris@556: { Chris@556: Profiler profiler("FFTDataServer::getValuesAt", false); Chris@556: Chris@556: if (x >= m_width) return false; Chris@556: Chris@556: if (minbin >= m_height) minbin = m_height - 1; Chris@556: if (count == 0) count = (m_height - minbin) / step; Chris@556: else if (minbin + count * step > m_height) { Chris@556: count = (m_height - minbin) / step; Chris@556: } Chris@556: Chris@678: try { Chris@556: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return false; Chris@556: Chris@678: if (!cache->haveSetColumnAt(col)) { Chris@678: Profiler profiler("FFTDataServer::getValuesAt: filling"); Chris@678: fillColumn(x); Chris@678: } Chris@678: Chris@678: for (size_t i = 0; i < count; ++i) { Chris@678: cache->getValuesAt(col, i * step + minbin, reals[i], imaginaries[i]); Chris@678: } Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@678: return false; Chris@556: } Chris@556: Chris@556: return true; Chris@556: } Chris@556: Chris@556: bool Chris@148: FFTDataServer::isColumnReady(size_t x) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::isColumnReady", false); Chris@183: Chris@217: if (x >= m_width) return true; Chris@217: Chris@148: if (!haveCache(x)) { Chris@537: /*!!! Chris@148: if (m_lastUsedCache == -1) { Chris@183: if (m_suspended) { Chris@690: SVDEBUG << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << endl; Chris@183: resume(); Chris@183: } Chris@148: m_fillThread->start(); Chris@148: } Chris@537: */ Chris@148: return false; Chris@148: } Chris@148: Chris@678: try { Chris@148: Chris@678: size_t col; Chris@678: FFTCacheReader *cache = getCacheReader(x, col); Chris@678: if (!cache) return true; Chris@678: Chris@678: return cache->haveSetColumnAt(col); Chris@678: Chris@678: } catch (std::exception &e) { Chris@678: m_error = e.what(); Chris@678: return false; Chris@678: } Chris@148: } Chris@148: Chris@148: void Chris@550: FFTDataServer::fillColumn(size_t x) Chris@148: { Chris@183: Profiler profiler("FFTDataServer::fillColumn", false); Chris@183: Chris@272: if (!m_model->isReady()) { Chris@272: std::cerr << "WARNING: FFTDataServer::fillColumn(" Chris@272: << x << "): model not yet ready" << std::endl; Chris@272: return; Chris@272: } Chris@549: /* Chris@217: if (!m_fftInput) { Chris@217: std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " Chris@217: << "input has already been completed and discarded?" Chris@217: << std::endl; Chris@217: return; Chris@217: } Chris@549: */ Chris@217: if (x >= m_width) { Chris@217: std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " Chris@217: << "x > width (" << x << " > " << m_width << ")" Chris@217: << std::endl; Chris@217: return; Chris@217: } Chris@217: Chris@148: size_t col; Chris@148: #ifdef DEBUG_FFT_SERVER_FILL Chris@148: std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl; Chris@148: #endif Chris@537: FFTCacheWriter *cache = getCacheWriter(x, col); Chris@200: if (!cache) return; Chris@148: Chris@408: int winsize = m_windowSize; Chris@408: int fftsize = m_fftSize; Chris@408: int hs = fftsize/2; Chris@408: Chris@408: int pfx = 0; Chris@408: int off = (fftsize - winsize) / 2; Chris@148: Chris@148: int startFrame = m_windowIncrement * x; Chris@148: int endFrame = startFrame + m_windowSize; Chris@148: Chris@408: startFrame -= winsize / 2; Chris@408: endFrame -= winsize / 2; Chris@148: Chris@549: #ifdef DEBUG_FFT_SERVER_FILL Chris@690: SVDEBUG << "FFTDataServer::fillColumn: requesting frames " Chris@549: << startFrame + pfx << " -> " << endFrame << " ( = " Chris@549: << endFrame - (startFrame + pfx) << ") at index " Chris@549: << off + pfx << " in buffer of size " << m_fftSize Chris@549: << " with window size " << m_windowSize Chris@687: << " from channel " << m_channel << endl; Chris@549: #endif Chris@549: Chris@549: QMutexLocker locker(&m_fftBuffersLock); Chris@549: Chris@550: // We may have been called from a function that wanted to obtain a Chris@550: // column using an FFTCacheReader. Before calling us, it checked Chris@550: // whether the column was available already, and the reader Chris@550: // reported that it wasn't. Now we test again, with the mutex Chris@550: // held, to avoid a race condition in case another thread has Chris@550: // called fillColumn at the same time. Chris@550: if (cache->haveSetColumnAt(x & m_cacheWidthMask)) { Chris@550: return; Chris@549: } Chris@549: Chris@549: if (!m_fftInput) { Chris@549: std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): " Chris@549: << "input has already been completed and discarded?" Chris@549: << std::endl; Chris@549: return; Chris@549: } Chris@549: Chris@408: for (int i = 0; i < off; ++i) { Chris@408: m_fftInput[i] = 0.0; Chris@408: } Chris@148: Chris@408: for (int i = 0; i < off; ++i) { Chris@408: m_fftInput[fftsize - i - 1] = 0.0; Chris@148: } Chris@148: Chris@148: if (startFrame < 0) { Chris@408: pfx = -startFrame; Chris@408: for (int i = 0; i < pfx; ++i) { Chris@148: m_fftInput[off + i] = 0.0; Chris@148: } Chris@148: } Chris@148: Chris@408: int count = 0; Chris@300: if (endFrame > startFrame + pfx) count = endFrame - (startFrame + pfx); Chris@300: Chris@408: int got = m_model->getData(m_channel, startFrame + pfx, Chris@408: count, m_fftInput + off + pfx); Chris@148: Chris@408: while (got + pfx < winsize) { Chris@148: m_fftInput[off + got + pfx] = 0.0; Chris@148: ++got; Chris@148: } Chris@148: Chris@148: if (m_channel == -1) { Chris@148: int channels = m_model->getChannelCount(); Chris@148: if (channels > 1) { Chris@408: for (int i = 0; i < winsize; ++i) { Chris@148: m_fftInput[off + i] /= channels; Chris@148: } Chris@148: } Chris@148: } Chris@148: Chris@148: m_windower.cut(m_fftInput + off); Chris@148: Chris@408: for (int i = 0; i < hs; ++i) { Chris@148: fftsample temp = m_fftInput[i]; Chris@408: m_fftInput[i] = m_fftInput[i + hs]; Chris@408: m_fftInput[i + hs] = temp; Chris@148: } Chris@148: Chris@226: fftf_execute(m_fftPlan); Chris@148: Chris@408: float factor = 0.f; Chris@148: Chris@408: if (cache->getStorageType() == FFTCache::Compact || Chris@408: cache->getStorageType() == FFTCache::Polar) { Chris@408: Chris@408: for (int i = 0; i <= hs; ++i) { Chris@408: fftsample real = m_fftOutput[i][0]; Chris@408: fftsample imag = m_fftOutput[i][1]; Chris@408: float mag = sqrtf(real * real + imag * imag); Chris@408: m_workbuffer[i] = mag; Chris@408: m_workbuffer[i + hs + 1] = atan2f(imag, real); Chris@408: if (mag > factor) factor = mag; Chris@408: } Chris@408: Chris@408: } else { Chris@408: Chris@408: for (int i = 0; i <= hs; ++i) { Chris@408: m_workbuffer[i] = m_fftOutput[i][0]; Chris@408: m_workbuffer[i + hs + 1] = m_fftOutput[i][1]; Chris@408: } Chris@148: } Chris@148: Chris@408: Profiler subprof("FFTDataServer::fillColumn: set to cache"); Chris@408: Chris@549: if (cache->getStorageType() == FFTCache::Compact || Chris@549: cache->getStorageType() == FFTCache::Polar) { Chris@549: Chris@549: cache->setColumnAt(col, Chris@549: m_workbuffer, Chris@549: m_workbuffer + hs + 1, Chris@549: factor); Chris@408: Chris@549: } else { Chris@408: Chris@549: cache->setColumnAt(col, Chris@549: m_workbuffer, Chris@549: m_workbuffer + hs + 1); Chris@408: } Chris@154: Chris@183: if (m_suspended) { Chris@690: // SVDEBUG << "FFTDataServer::fillColumn(" << x << "): calling resume" << endl; Chris@183: // resume(); Chris@183: } Chris@148: } Chris@148: Chris@537: void Chris@537: FFTDataServer::fillComplete() Chris@537: { Chris@537: for (int i = 0; i < int(m_caches.size()); ++i) { Chris@678: if (!m_caches[i]) continue; Chris@537: if (m_caches[i]->memoryCache) { Chris@537: m_caches[i]->memoryCache->allColumnsWritten(); Chris@537: } Chris@537: if (m_caches[i]->fileCacheWriter) { Chris@537: m_caches[i]->fileCacheWriter->allColumnsWritten(); Chris@537: } Chris@537: } Chris@537: } Chris@537: Chris@678: QString Chris@678: FFTDataServer::getError() const Chris@678: { Chris@678: if (m_error != "") return m_error; Chris@678: else if (m_fillThread) return m_fillThread->getError(); Chris@678: else return ""; Chris@678: } Chris@678: Chris@148: size_t Chris@148: FFTDataServer::getFillCompletion() const Chris@148: { Chris@148: if (m_fillThread) return m_fillThread->getCompletion(); Chris@148: else return 100; Chris@148: } Chris@148: Chris@148: size_t Chris@148: FFTDataServer::getFillExtent() const Chris@148: { Chris@148: if (m_fillThread) return m_fillThread->getExtent(); Chris@148: else return m_model->getEndFrame(); Chris@148: } Chris@148: Chris@148: QString Chris@148: FFTDataServer::generateFileBasename() const Chris@148: { Chris@148: return generateFileBasename(m_model, m_channel, m_windower.getType(), Chris@148: m_windowSize, m_windowIncrement, m_fftSize, Chris@148: m_polar); Chris@148: } Chris@148: Chris@148: QString Chris@148: FFTDataServer::generateFileBasename(const DenseTimeValueModel *model, Chris@148: int channel, Chris@148: WindowType windowType, Chris@148: size_t windowSize, Chris@148: size_t windowIncrement, Chris@148: size_t fftSize, Chris@148: bool polar) Chris@148: { Chris@148: char buffer[200]; Chris@148: Chris@148: sprintf(buffer, "%u-%u-%u-%u-%u-%u%s", Chris@148: (unsigned int)XmlExportable::getObjectExportId(model), Chris@148: (unsigned int)(channel + 1), Chris@148: (unsigned int)windowType, Chris@148: (unsigned int)windowSize, Chris@148: (unsigned int)windowIncrement, Chris@148: (unsigned int)fftSize, Chris@148: polar ? "-p" : "-r"); Chris@148: Chris@148: return buffer; Chris@148: } Chris@148: Chris@148: void Chris@148: FFTDataServer::FillThread::run() Chris@148: { Chris@411: #ifdef DEBUG_FFT_SERVER_FILL Chris@690: SVDEBUG << "FFTDataServer::FillThread::run()" << endl; Chris@411: #endif Chris@411: Chris@148: m_extent = 0; Chris@148: m_completion = 0; Chris@148: Chris@272: while (!m_server.m_model->isReady() && !m_server.m_exiting) { Chris@411: #ifdef DEBUG_FFT_SERVER_FILL Chris@690: SVDEBUG << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << endl; Chris@411: #endif Chris@272: sleep(1); Chris@272: } Chris@272: if (m_server.m_exiting) return; Chris@272: Chris@148: size_t start = m_server.m_model->getStartFrame(); Chris@148: size_t end = m_server.m_model->getEndFrame(); Chris@148: size_t remainingEnd = end; Chris@148: Chris@148: int counter = 0; Chris@246: int updateAt = 1; Chris@246: int maxUpdateAt = (end / m_server.m_windowIncrement) / 20; Chris@246: if (maxUpdateAt < 100) maxUpdateAt = 100; Chris@148: Chris@148: if (m_fillFrom > start) { Chris@148: Chris@148: for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) { Chris@148: Chris@678: try { Chris@678: m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); Chris@678: } catch (std::exception &e) { Chris@690: SVDEBUG << "FFTDataServer::FillThread::run: exception: " << e.what() << endl; Chris@678: m_error = e.what(); Chris@678: m_server.fillComplete(); Chris@678: m_completion = 100; Chris@678: m_extent = end; Chris@678: return; Chris@678: } Chris@148: Chris@148: if (m_server.m_exiting) return; Chris@148: Chris@148: while (m_server.m_suspended) { Chris@148: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl; Chris@148: #endif Chris@549: MutexLocker locker(&m_server.m_fftBuffersLock, Chris@549: "FFTDataServer::run::m_fftBuffersLock [1]"); Chris@549: if (m_server.m_suspended && !m_server.m_exiting) { Chris@549: m_server.m_condition.wait(&m_server.m_fftBuffersLock, 10000); Chris@244: } Chris@159: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << std::endl; Chris@159: #endif Chris@148: if (m_server.m_exiting) return; Chris@148: } Chris@148: Chris@148: if (++counter == updateAt) { Chris@148: m_extent = f; Chris@148: m_completion = size_t(100 * fabsf(float(f - m_fillFrom) / Chris@148: float(end - start))); Chris@148: counter = 0; Chris@246: if (updateAt < maxUpdateAt) { Chris@246: updateAt *= 2; Chris@246: if (updateAt > maxUpdateAt) updateAt = maxUpdateAt; Chris@246: } Chris@148: } Chris@148: } Chris@148: Chris@148: remainingEnd = m_fillFrom; Chris@148: if (remainingEnd > start) --remainingEnd; Chris@148: else remainingEnd = start; Chris@148: } Chris@148: Chris@148: size_t baseCompletion = m_completion; Chris@148: Chris@148: for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) { Chris@148: Chris@678: try { Chris@678: m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); Chris@678: } catch (std::exception &e) { Chris@690: SVDEBUG << "FFTDataServer::FillThread::run: exception: " << e.what() << endl; Chris@678: m_error = e.what(); Chris@678: m_server.fillComplete(); Chris@678: m_completion = 100; Chris@678: m_extent = end; Chris@678: return; Chris@678: } Chris@148: Chris@148: if (m_server.m_exiting) return; Chris@148: Chris@148: while (m_server.m_suspended) { Chris@148: #ifdef DEBUG_FFT_SERVER Chris@193: std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl; Chris@148: #endif Chris@244: { Chris@549: MutexLocker locker(&m_server.m_fftBuffersLock, Chris@549: "FFTDataServer::run::m_fftBuffersLock [2]"); Chris@549: if (m_server.m_suspended && !m_server.m_exiting) { Chris@549: m_server.m_condition.wait(&m_server.m_fftBuffersLock, 10000); Chris@549: } Chris@244: } Chris@148: if (m_server.m_exiting) return; Chris@148: } Chris@148: Chris@148: if (++counter == updateAt) { Chris@148: m_extent = f; Chris@148: m_completion = baseCompletion + Chris@148: size_t(100 * fabsf(float(f - start) / Chris@148: float(end - start))); Chris@148: counter = 0; Chris@246: if (updateAt < maxUpdateAt) { Chris@246: updateAt *= 2; Chris@246: if (updateAt > maxUpdateAt) updateAt = maxUpdateAt; Chris@246: } Chris@148: } Chris@148: } Chris@148: Chris@537: m_server.fillComplete(); Chris@148: m_completion = 100; Chris@148: m_extent = end; Chris@537: Chris@537: #ifdef DEBUG_FFT_SERVER Chris@690: SVDEBUG << "FFTDataServer::FillThread::run exiting" << endl; Chris@537: #endif Chris@148: } Chris@148: