| Chris@148 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@148 | 2 | 
| Chris@148 | 3 /* | 
| Chris@148 | 4     Sonic Visualiser | 
| Chris@148 | 5     An audio file viewer and annotation editor. | 
| Chris@148 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@148 | 7     This file copyright 2006 Chris Cannam. | 
| Chris@148 | 8 | 
| Chris@148 | 9     This program is free software; you can redistribute it and/or | 
| Chris@148 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@148 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@148 | 12     License, or (at your option) any later version.  See the file | 
| Chris@148 | 13     COPYING included with this distribution for more information. | 
| Chris@148 | 14 */ | 
| Chris@148 | 15 | 
| Chris@148 | 16 #include "FFTDataServer.h" | 
| Chris@148 | 17 | 
| Chris@148 | 18 #include "FFTFileCache.h" | 
| Chris@148 | 19 | 
| Chris@148 | 20 #include "model/DenseTimeValueModel.h" | 
| Chris@148 | 21 | 
| Chris@148 | 22 #include "base/System.h" | 
| Chris@148 | 23 | 
| Chris@148 | 24 //#define DEBUG_FFT_SERVER 1 | 
| Chris@148 | 25 //#define DEBUG_FFT_SERVER_FILL 1 | 
| Chris@148 | 26 | 
| Chris@148 | 27 #ifdef DEBUG_FFT_SERVER_FILL | 
| Chris@148 | 28 #define DEBUG_FFT_SERVER | 
| Chris@148 | 29 #endif | 
| Chris@148 | 30 | 
| Chris@148 | 31 FFTDataServer::ServerMap FFTDataServer::m_servers; | 
| Chris@148 | 32 QMutex FFTDataServer::m_serverMapMutex; | 
| Chris@148 | 33 | 
| Chris@148 | 34 FFTDataServer * | 
| Chris@148 | 35 FFTDataServer::getInstance(const DenseTimeValueModel *model, | 
| Chris@148 | 36                            int channel, | 
| Chris@148 | 37                            WindowType windowType, | 
| Chris@148 | 38                            size_t windowSize, | 
| Chris@148 | 39                            size_t windowIncrement, | 
| Chris@148 | 40                            size_t fftSize, | 
| Chris@148 | 41                            bool polar, | 
| Chris@148 | 42                            size_t fillFromColumn) | 
| Chris@148 | 43 { | 
| Chris@148 | 44     QString n = generateFileBasename(model, | 
| Chris@148 | 45                                      channel, | 
| Chris@148 | 46                                      windowType, | 
| Chris@148 | 47                                      windowSize, | 
| Chris@148 | 48                                      windowIncrement, | 
| Chris@148 | 49                                      fftSize, | 
| Chris@148 | 50                                      polar); | 
| Chris@148 | 51 | 
| Chris@148 | 52     FFTDataServer *server = 0; | 
| Chris@148 | 53 | 
| Chris@148 | 54     QMutexLocker locker(&m_serverMapMutex); | 
| Chris@148 | 55 | 
| Chris@148 | 56     if ((server = findServer(n))) { | 
| Chris@148 | 57         return server; | 
| Chris@148 | 58     } | 
| Chris@148 | 59 | 
| Chris@148 | 60     QString npn = generateFileBasename(model, | 
| Chris@148 | 61                                        channel, | 
| Chris@148 | 62                                        windowType, | 
| Chris@148 | 63                                        windowSize, | 
| Chris@148 | 64                                        windowIncrement, | 
| Chris@148 | 65                                        fftSize, | 
| Chris@148 | 66                                        !polar); | 
| Chris@148 | 67 | 
| Chris@148 | 68     if ((server = findServer(npn))) { | 
| Chris@148 | 69         return server; | 
| Chris@148 | 70     } | 
| Chris@148 | 71 | 
| Chris@148 | 72     m_servers[n] = ServerCountPair | 
| Chris@148 | 73         (new FFTDataServer(n, | 
| Chris@148 | 74                            model, | 
| Chris@148 | 75                            channel, | 
| Chris@148 | 76                            windowType, | 
| Chris@148 | 77                            windowSize, | 
| Chris@148 | 78                            windowIncrement, | 
| Chris@148 | 79                            fftSize, | 
| Chris@148 | 80                            polar, | 
| Chris@148 | 81                            fillFromColumn), | 
| Chris@148 | 82          1); | 
| Chris@148 | 83 | 
| Chris@148 | 84     return m_servers[n].first; | 
| Chris@148 | 85 } | 
| Chris@148 | 86 | 
| Chris@148 | 87 FFTDataServer * | 
| Chris@148 | 88 FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model, | 
| Chris@148 | 89                                 int channel, | 
| Chris@148 | 90                                 WindowType windowType, | 
| Chris@148 | 91                                 size_t windowSize, | 
| Chris@148 | 92                                 size_t windowIncrement, | 
| Chris@148 | 93                                 size_t fftSize, | 
| Chris@148 | 94                                 bool polar, | 
| Chris@148 | 95                                 size_t fillFromColumn) | 
| Chris@148 | 96 { | 
| Chris@148 | 97     // Fuzzy matching: | 
| Chris@148 | 98     // | 
| Chris@148 | 99     // -- if we're asked for polar and have non-polar, use it (and | 
| Chris@148 | 100     // vice versa).  This one is vital, and we do it for non-fuzzy as | 
| Chris@148 | 101     // well (above). | 
| Chris@148 | 102     // | 
| Chris@148 | 103     // -- if we're asked for an instance with a given fft size and we | 
| Chris@148 | 104     // have one already with a multiple of that fft size but the same | 
| Chris@148 | 105     // window size and type (and model), we can draw the results from | 
| Chris@148 | 106     // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the | 
| Chris@148 | 107     // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the | 
| Chris@148 | 108     // same window plus zero padding). | 
| Chris@148 | 109     // | 
| Chris@148 | 110     // -- if we're asked for an instance with a given window type and | 
| Chris@148 | 111     // size and fft size and we have one already the same but with a | 
| Chris@148 | 112     // smaller increment, we can draw the results from it (provided | 
| Chris@148 | 113     // our increment is a multiple of its) | 
| Chris@148 | 114     // | 
| Chris@148 | 115     // The FFTFuzzyAdapter knows how to interpret these things.  In | 
| Chris@148 | 116     // both cases we require that the larger one is a power-of-two | 
| Chris@148 | 117     // multiple of the smaller (e.g. even though in principle you can | 
| Chris@148 | 118     // draw the results at increment 256 from those at increment 768 | 
| Chris@148 | 119     // or 1536, the fuzzy adapter doesn't support this). | 
| Chris@148 | 120 | 
| Chris@148 | 121     { | 
| Chris@148 | 122         QMutexLocker locker(&m_serverMapMutex); | 
| Chris@148 | 123 | 
| Chris@148 | 124         ServerMap::iterator best = m_servers.end(); | 
| Chris@148 | 125         int bestdist = -1; | 
| Chris@148 | 126 | 
| Chris@148 | 127         for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { | 
| Chris@148 | 128 | 
| Chris@148 | 129             FFTDataServer *server = i->second.first; | 
| Chris@148 | 130 | 
| Chris@148 | 131             if (server->getModel() == model && | 
| Chris@148 | 132                 (server->getChannel() == channel || model->getChannelCount() == 1) && | 
| Chris@148 | 133                 server->getWindowType() == windowType && | 
| Chris@148 | 134                 server->getWindowSize() == windowSize && | 
| Chris@148 | 135                 server->getWindowIncrement() <= windowIncrement && | 
| Chris@148 | 136                 server->getFFTSize() >= fftSize) { | 
| Chris@148 | 137 | 
| Chris@148 | 138                 if ((windowIncrement % server->getWindowIncrement()) != 0) continue; | 
| Chris@148 | 139                 int ratio = windowIncrement / server->getWindowIncrement(); | 
| Chris@148 | 140                 bool poweroftwo = true; | 
| Chris@148 | 141                 while (ratio > 1) { | 
| Chris@148 | 142                     if (ratio & 0x1) { | 
| Chris@148 | 143                         poweroftwo = false; | 
| Chris@148 | 144                         break; | 
| Chris@148 | 145                     } | 
| Chris@148 | 146                     ratio >>= 1; | 
| Chris@148 | 147                 } | 
| Chris@148 | 148                 if (!poweroftwo) continue; | 
| Chris@148 | 149 | 
| Chris@148 | 150                 if ((server->getFFTSize() % fftSize) != 0) continue; | 
| Chris@148 | 151                 ratio = server->getFFTSize() / fftSize; | 
| Chris@148 | 152                 while (ratio > 1) { | 
| Chris@148 | 153                     if (ratio & 0x1) { | 
| Chris@148 | 154                         poweroftwo = false; | 
| Chris@148 | 155                         break; | 
| Chris@148 | 156                     } | 
| Chris@148 | 157                     ratio >>= 1; | 
| Chris@148 | 158                 } | 
| Chris@148 | 159                 if (!poweroftwo) continue; | 
| Chris@148 | 160 | 
| Chris@148 | 161                 int distance = 0; | 
| Chris@148 | 162 | 
| Chris@148 | 163                 if (server->getPolar() != polar) distance += 1; | 
| Chris@148 | 164 | 
| Chris@148 | 165                 distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15; | 
| Chris@148 | 166                 distance += ((server->getFFTSize() / fftSize) - 1) * 10; | 
| Chris@148 | 167 | 
| Chris@148 | 168                 if (server->getFillCompletion() < 50) distance += 100; | 
| Chris@148 | 169 | 
| Chris@148 | 170 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 171                 std::cerr << "Distance " << distance << ", best is " << bestdist << std::endl; | 
| Chris@148 | 172 #endif | 
| Chris@148 | 173 | 
| Chris@148 | 174                 if (bestdist == -1 || distance < bestdist) { | 
| Chris@148 | 175                     bestdist = distance; | 
| Chris@148 | 176                     best = i; | 
| Chris@148 | 177                 } | 
| Chris@148 | 178             } | 
| Chris@148 | 179         } | 
| Chris@148 | 180 | 
| Chris@148 | 181         if (bestdist >= 0) { | 
| Chris@148 | 182             ++best->second.second; | 
| Chris@148 | 183             return best->second.first; | 
| Chris@148 | 184         } | 
| Chris@148 | 185     } | 
| Chris@148 | 186 | 
| Chris@148 | 187     // Nothing found, make a new one | 
| Chris@148 | 188 | 
| Chris@148 | 189     return getInstance(model, | 
| Chris@148 | 190                        channel, | 
| Chris@148 | 191                        windowType, | 
| Chris@148 | 192                        windowSize, | 
| Chris@148 | 193                        windowIncrement, | 
| Chris@148 | 194                        fftSize, | 
| Chris@148 | 195                        polar, | 
| Chris@148 | 196                        fillFromColumn); | 
| Chris@148 | 197 } | 
| Chris@148 | 198 | 
| Chris@148 | 199 FFTDataServer * | 
| Chris@148 | 200 FFTDataServer::findServer(QString n) | 
| Chris@148 | 201 { | 
| Chris@148 | 202     if (m_servers.find(n) != m_servers.end()) { | 
| Chris@148 | 203         ++m_servers[n].second; | 
| Chris@148 | 204         return m_servers[n].first; | 
| Chris@148 | 205     } | 
| Chris@148 | 206 | 
| Chris@148 | 207     return 0; | 
| Chris@148 | 208 } | 
| Chris@148 | 209 | 
| Chris@148 | 210 void | 
| Chris@148 | 211 FFTDataServer::releaseInstance(FFTDataServer *server) | 
| Chris@148 | 212 { | 
| Chris@148 | 213 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 214     std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl; | 
| Chris@148 | 215 #endif | 
| Chris@148 | 216 | 
| Chris@148 | 217     QMutexLocker locker(&m_serverMapMutex); | 
| Chris@148 | 218 | 
| Chris@148 | 219     //!!! not a good strategy.  Want something like: | 
| Chris@148 | 220 | 
| Chris@148 | 221     // -- if ref count > 0, decrement and return | 
| Chris@148 | 222     // -- if the instance hasn't been used at all, delete it immediately | 
| Chris@148 | 223     // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts, | 
| Chris@148 | 224     //    leave them hanging around | 
| Chris@148 | 225     // -- if N instances with zero refcounts remain, delete the one that | 
| Chris@148 | 226     //    was last released first | 
| Chris@148 | 227     // -- if we run out of disk space when allocating an instance, go back | 
| Chris@148 | 228     //    and delete the spare N instances before trying again | 
| Chris@148 | 229     // -- have an additional method to indicate that a model has been | 
| Chris@148 | 230     //    destroyed, so that we can delete all of its fft server instances | 
| Chris@148 | 231 | 
| Chris@148 | 232     // also: | 
| Chris@148 | 233     // | 
| Chris@148 | 234 | 
| Chris@148 | 235     for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) { | 
| Chris@148 | 236         if (i->second.first == server) { | 
| Chris@148 | 237             if (i->second.second == 0) { | 
| Chris@148 | 238                 std::cerr << "ERROR: FFTDataServer::releaseInstance(" | 
| Chris@148 | 239                           << server << "): instance not allocated" << std::endl; | 
| Chris@148 | 240             } else if (--i->second.second == 0) { | 
| Chris@148 | 241                 if (server->m_lastUsedCache == -1) { // never used | 
| Chris@148 | 242                     delete server; | 
| Chris@148 | 243                     m_servers.erase(i); | 
| Chris@148 | 244                 } else { | 
| Chris@148 | 245                     server->suspend(); | 
| Chris@148 | 246                     purgeLimbo(); | 
| Chris@148 | 247                 } | 
| Chris@148 | 248             } | 
| Chris@148 | 249             return; | 
| Chris@148 | 250         } | 
| Chris@148 | 251     } | 
| Chris@148 | 252 | 
| Chris@148 | 253     std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): " | 
| Chris@148 | 254               << "instance not found" << std::endl; | 
| Chris@148 | 255 } | 
| Chris@148 | 256 | 
| Chris@148 | 257 void | 
| Chris@148 | 258 FFTDataServer::purgeLimbo(int maxSize) | 
| Chris@148 | 259 { | 
| Chris@148 | 260     ServerMap::iterator i = m_servers.end(); | 
| Chris@148 | 261 | 
| Chris@148 | 262     int count = 0; | 
| Chris@148 | 263 | 
| Chris@148 | 264     while (i != m_servers.begin()) { | 
| Chris@148 | 265         --i; | 
| Chris@148 | 266         if (i->second.second == 0) { | 
| Chris@148 | 267             if (++count > maxSize) { | 
| Chris@148 | 268                 delete i->second.first; | 
| Chris@148 | 269                 m_servers.erase(i); | 
| Chris@148 | 270                 return; | 
| Chris@148 | 271             } | 
| Chris@148 | 272         } | 
| Chris@148 | 273     } | 
| Chris@148 | 274 } | 
| Chris@148 | 275 | 
| Chris@148 | 276 FFTDataServer::FFTDataServer(QString fileBaseName, | 
| Chris@148 | 277                              const DenseTimeValueModel *model, | 
| Chris@148 | 278                              int channel, | 
| Chris@148 | 279 			     WindowType windowType, | 
| Chris@148 | 280 			     size_t windowSize, | 
| Chris@148 | 281 			     size_t windowIncrement, | 
| Chris@148 | 282 			     size_t fftSize, | 
| Chris@148 | 283                              bool polar, | 
| Chris@148 | 284                              size_t fillFromColumn) : | 
| Chris@148 | 285     m_fileBaseName(fileBaseName), | 
| Chris@148 | 286     m_model(model), | 
| Chris@148 | 287     m_channel(channel), | 
| Chris@148 | 288     m_windower(windowType, windowSize), | 
| Chris@148 | 289     m_windowSize(windowSize), | 
| Chris@148 | 290     m_windowIncrement(windowIncrement), | 
| Chris@148 | 291     m_fftSize(fftSize), | 
| Chris@148 | 292     m_polar(polar), | 
| Chris@148 | 293     m_lastUsedCache(-1), | 
| Chris@148 | 294     m_fftInput(0), | 
| Chris@148 | 295     m_exiting(false), | 
| Chris@148 | 296     m_fillThread(0) | 
| Chris@148 | 297 { | 
| Chris@148 | 298     size_t start = m_model->getStartFrame(); | 
| Chris@148 | 299     size_t end = m_model->getEndFrame(); | 
| Chris@148 | 300 | 
| Chris@148 | 301     m_width = (end - start) / m_windowIncrement + 1; | 
| Chris@148 | 302     m_height = m_fftSize / 2; | 
| Chris@148 | 303 | 
| Chris@148 | 304     size_t maxCacheSize = 20 * 1024 * 1024; | 
| Chris@148 | 305     size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample); | 
| Chris@148 | 306     if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width; | 
| Chris@148 | 307     else m_cacheWidth = maxCacheSize / columnSize; | 
| Chris@148 | 308 | 
| Chris@148 | 309     int bits = 0; | 
| Chris@148 | 310     while (m_cacheWidth) { m_cacheWidth >>= 1; ++bits; } | 
| Chris@148 | 311     m_cacheWidth = 2; | 
| Chris@148 | 312     while (bits) { m_cacheWidth <<= 1; --bits; } | 
| Chris@148 | 313 | 
| Chris@148 | 314 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 315     std::cerr << "Width " << m_width << ", cache width " << m_cacheWidth << " (size " << m_cacheWidth * columnSize << ")" << std::endl; | 
| Chris@148 | 316 #endif | 
| Chris@148 | 317 | 
| Chris@148 | 318     for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) { | 
| Chris@148 | 319         m_caches.push_back(0); | 
| Chris@148 | 320     } | 
| Chris@148 | 321 | 
| Chris@148 | 322     m_fftInput = (fftsample *) | 
| Chris@148 | 323         fftwf_malloc(fftSize * sizeof(fftsample)); | 
| Chris@148 | 324 | 
| Chris@148 | 325     m_fftOutput = (fftwf_complex *) | 
| Chris@148 | 326         fftwf_malloc(fftSize * sizeof(fftwf_complex)); | 
| Chris@148 | 327 | 
| Chris@148 | 328     m_workbuffer = (float *) | 
| Chris@148 | 329         fftwf_malloc(fftSize * sizeof(float)); | 
| Chris@148 | 330 | 
| Chris@148 | 331     m_fftPlan = fftwf_plan_dft_r2c_1d(m_fftSize, | 
| Chris@148 | 332                                       m_fftInput, | 
| Chris@148 | 333                                       m_fftOutput, | 
| Chris@148 | 334                                       FFTW_ESTIMATE); | 
| Chris@148 | 335 | 
| Chris@148 | 336     if (!m_fftPlan) { | 
| Chris@148 | 337         std::cerr << "ERROR: fftwf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl; | 
| Chris@148 | 338         throw(0); | 
| Chris@148 | 339     } | 
| Chris@148 | 340 | 
| Chris@148 | 341     m_fillThread = new FillThread(*this, fillFromColumn); | 
| Chris@148 | 342 | 
| Chris@148 | 343     //!!! respond appropriately when thread exits (deleteProcessingData etc) | 
| Chris@148 | 344 } | 
| Chris@148 | 345 | 
| Chris@148 | 346 FFTDataServer::~FFTDataServer() | 
| Chris@148 | 347 { | 
| Chris@148 | 348 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 349     std::cerr << "FFTDataServer(" << this << ")::~FFTDataServer()" << std::endl; | 
| Chris@148 | 350 #endif | 
| Chris@148 | 351 | 
| Chris@148 | 352     m_exiting = true; | 
| Chris@148 | 353     m_condition.wakeAll(); | 
| Chris@148 | 354     if (m_fillThread) { | 
| Chris@148 | 355         m_fillThread->wait(); | 
| Chris@148 | 356         delete m_fillThread; | 
| Chris@148 | 357     } | 
| Chris@148 | 358 | 
| Chris@148 | 359     QMutexLocker locker(&m_writeMutex); | 
| Chris@148 | 360 | 
| Chris@148 | 361     for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { | 
| Chris@148 | 362         delete *i; | 
| Chris@148 | 363     } | 
| Chris@148 | 364 | 
| Chris@148 | 365     deleteProcessingData(); | 
| Chris@148 | 366 } | 
| Chris@148 | 367 | 
| Chris@148 | 368 void | 
| Chris@148 | 369 FFTDataServer::deleteProcessingData() | 
| Chris@148 | 370 { | 
| Chris@148 | 371     if (m_fftInput) { | 
| Chris@148 | 372         fftwf_destroy_plan(m_fftPlan); | 
| Chris@148 | 373         fftwf_free(m_fftInput); | 
| Chris@148 | 374         fftwf_free(m_fftOutput); | 
| Chris@148 | 375         fftwf_free(m_workbuffer); | 
| Chris@148 | 376     } | 
| Chris@148 | 377     m_fftInput = 0; | 
| Chris@148 | 378 } | 
| Chris@148 | 379 | 
| Chris@148 | 380 void | 
| Chris@148 | 381 FFTDataServer::suspend() | 
| Chris@148 | 382 { | 
| Chris@148 | 383 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 384     std::cerr << "FFTDataServer(" << this << "): suspend" << std::endl; | 
| Chris@148 | 385 #endif | 
| Chris@148 | 386     QMutexLocker locker(&m_writeMutex); | 
| Chris@148 | 387     m_suspended = true; | 
| Chris@148 | 388     for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) { | 
| Chris@148 | 389         if (*i) (*i)->suspend(); | 
| Chris@148 | 390     } | 
| Chris@148 | 391 } | 
| Chris@148 | 392 | 
| Chris@148 | 393 void | 
| Chris@148 | 394 FFTDataServer::resume() | 
| Chris@148 | 395 { | 
| Chris@148 | 396     m_suspended = false; | 
| Chris@148 | 397     m_condition.wakeAll(); | 
| Chris@148 | 398 } | 
| Chris@148 | 399 | 
| Chris@148 | 400 FFTCache * | 
| Chris@148 | 401 FFTDataServer::getCacheAux(size_t c) | 
| Chris@148 | 402 { | 
| Chris@148 | 403     QMutexLocker locker(&m_writeMutex); | 
| Chris@148 | 404 | 
| Chris@148 | 405     if (m_lastUsedCache == -1) { | 
| Chris@148 | 406         m_fillThread->start(); | 
| Chris@148 | 407     } | 
| Chris@148 | 408 | 
| Chris@148 | 409     if (int(c) != m_lastUsedCache) { | 
| Chris@148 | 410 | 
| Chris@148 | 411 //        std::cerr << "switch from " << m_lastUsedCache << " to " << c << std::endl; | 
| Chris@148 | 412 | 
| Chris@148 | 413         for (IntQueue::iterator i = m_dormantCaches.begin(); | 
| Chris@148 | 414              i != m_dormantCaches.end(); ++i) { | 
| Chris@148 | 415             if (*i == c) { | 
| Chris@148 | 416                 m_dormantCaches.erase(i); | 
| Chris@148 | 417                 break; | 
| Chris@148 | 418             } | 
| Chris@148 | 419         } | 
| Chris@148 | 420 | 
| Chris@148 | 421         if (m_lastUsedCache >= 0) { | 
| Chris@148 | 422             bool inDormant = false; | 
| Chris@148 | 423             for (size_t i = 0; i < m_dormantCaches.size(); ++i) { | 
| Chris@148 | 424                 if (m_dormantCaches[i] == m_lastUsedCache) { | 
| Chris@148 | 425                     inDormant = true; | 
| Chris@148 | 426                     break; | 
| Chris@148 | 427                 } | 
| Chris@148 | 428             } | 
| Chris@148 | 429             if (!inDormant) { | 
| Chris@148 | 430                 m_dormantCaches.push_back(m_lastUsedCache); | 
| Chris@148 | 431             } | 
| Chris@148 | 432             while (m_dormantCaches.size() > 4) { | 
| Chris@148 | 433                 int dc = m_dormantCaches.front(); | 
| Chris@148 | 434                 m_dormantCaches.pop_front(); | 
| Chris@148 | 435                 m_caches[dc]->suspend(); | 
| Chris@148 | 436             } | 
| Chris@148 | 437         } | 
| Chris@148 | 438     } | 
| Chris@148 | 439 | 
| Chris@148 | 440     if (m_caches[c]) { | 
| Chris@148 | 441         m_lastUsedCache = c; | 
| Chris@148 | 442         return m_caches[c]; | 
| Chris@148 | 443     } | 
| Chris@148 | 444 | 
| Chris@148 | 445     QString name = QString("%1-%2").arg(m_fileBaseName).arg(c); | 
| Chris@148 | 446 | 
| Chris@148 | 447     FFTCache *cache = new FFTFileCache(name, MatrixFile::ReadWrite, | 
| Chris@148 | 448                                        m_polar ? FFTFileCache::Polar : | 
| Chris@148 | 449                                                  FFTFileCache::Rectangular); | 
| Chris@148 | 450 | 
| Chris@148 | 451     size_t width = m_cacheWidth; | 
| Chris@148 | 452     if (c * m_cacheWidth + width > m_width) { | 
| Chris@148 | 453         width = m_width - c * m_cacheWidth; | 
| Chris@148 | 454     } | 
| Chris@148 | 455 | 
| Chris@148 | 456     cache->resize(width, m_height); | 
| Chris@148 | 457     cache->reset(); | 
| Chris@148 | 458 | 
| Chris@148 | 459     m_caches[c] = cache; | 
| Chris@148 | 460     m_lastUsedCache = c; | 
| Chris@148 | 461 | 
| Chris@148 | 462     return cache; | 
| Chris@148 | 463 } | 
| Chris@148 | 464 | 
| Chris@148 | 465 float | 
| Chris@148 | 466 FFTDataServer::getMagnitudeAt(size_t x, size_t y) | 
| Chris@148 | 467 { | 
| Chris@148 | 468     size_t col; | 
| Chris@148 | 469     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 470 | 
| Chris@148 | 471     if (!cache->haveSetColumnAt(col)) { | 
| Chris@148 | 472         fillColumn(x); | 
| Chris@148 | 473     } | 
| Chris@148 | 474     return cache->getMagnitudeAt(col, y); | 
| Chris@148 | 475 } | 
| Chris@148 | 476 | 
| Chris@148 | 477 float | 
| Chris@148 | 478 FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y) | 
| Chris@148 | 479 { | 
| Chris@148 | 480     size_t col; | 
| Chris@148 | 481     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 482 | 
| Chris@148 | 483     if (!cache->haveSetColumnAt(col)) { | 
| Chris@148 | 484         fillColumn(x); | 
| Chris@148 | 485     } | 
| Chris@148 | 486     return cache->getNormalizedMagnitudeAt(col, y); | 
| Chris@148 | 487 } | 
| Chris@148 | 488 | 
| Chris@148 | 489 float | 
| Chris@148 | 490 FFTDataServer::getMaximumMagnitudeAt(size_t x) | 
| Chris@148 | 491 { | 
| Chris@148 | 492     size_t col; | 
| Chris@148 | 493     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 494 | 
| Chris@148 | 495     if (!cache->haveSetColumnAt(col)) { | 
| Chris@148 | 496         fillColumn(x); | 
| Chris@148 | 497     } | 
| Chris@148 | 498     return cache->getMaximumMagnitudeAt(col); | 
| Chris@148 | 499 } | 
| Chris@148 | 500 | 
| Chris@148 | 501 float | 
| Chris@148 | 502 FFTDataServer::getPhaseAt(size_t x, size_t y) | 
| Chris@148 | 503 { | 
| Chris@148 | 504     size_t col; | 
| Chris@148 | 505     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 506 | 
| Chris@148 | 507     if (!cache->haveSetColumnAt(col)) { | 
| Chris@148 | 508         fillColumn(x); | 
| Chris@148 | 509     } | 
| Chris@148 | 510     return cache->getPhaseAt(col, y); | 
| Chris@148 | 511 } | 
| Chris@148 | 512 | 
| Chris@148 | 513 void | 
| Chris@148 | 514 FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary) | 
| Chris@148 | 515 { | 
| Chris@148 | 516     size_t col; | 
| Chris@148 | 517     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 518 | 
| Chris@148 | 519     if (!cache->haveSetColumnAt(col)) { | 
| Chris@148 | 520 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 521         std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl; | 
| Chris@148 | 522 #endif | 
| Chris@148 | 523         fillColumn(x); | 
| Chris@148 | 524     } | 
| Chris@148 | 525     float magnitude = cache->getMagnitudeAt(col, y); | 
| Chris@148 | 526     float phase = cache->getPhaseAt(col, y); | 
| Chris@148 | 527     real = magnitude * cosf(phase); | 
| Chris@148 | 528     imaginary = magnitude * sinf(phase); | 
| Chris@148 | 529 } | 
| Chris@148 | 530 | 
| Chris@148 | 531 bool | 
| Chris@148 | 532 FFTDataServer::isColumnReady(size_t x) | 
| Chris@148 | 533 { | 
| Chris@148 | 534     if (!haveCache(x)) { | 
| Chris@148 | 535         if (m_lastUsedCache == -1) { | 
| Chris@148 | 536             m_fillThread->start(); | 
| Chris@148 | 537         } | 
| Chris@148 | 538         return false; | 
| Chris@148 | 539     } | 
| Chris@148 | 540 | 
| Chris@148 | 541     size_t col; | 
| Chris@148 | 542     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 543 | 
| Chris@148 | 544     return cache->haveSetColumnAt(col); | 
| Chris@148 | 545 } | 
| Chris@148 | 546 | 
| Chris@148 | 547 void | 
| Chris@148 | 548 FFTDataServer::fillColumn(size_t x) | 
| Chris@148 | 549 { | 
| Chris@148 | 550     size_t col; | 
| Chris@148 | 551 #ifdef DEBUG_FFT_SERVER_FILL | 
| Chris@148 | 552     std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl; | 
| Chris@148 | 553 #endif | 
| Chris@148 | 554     FFTCache *cache = getCache(x, col); | 
| Chris@148 | 555 | 
| Chris@148 | 556     QMutexLocker locker(&m_writeMutex); | 
| Chris@148 | 557 | 
| Chris@148 | 558     if (cache->haveSetColumnAt(col)) return; | 
| Chris@148 | 559 | 
| Chris@148 | 560     int startFrame = m_windowIncrement * x; | 
| Chris@148 | 561     int endFrame = startFrame + m_windowSize; | 
| Chris@148 | 562 | 
| Chris@148 | 563     startFrame -= int(m_windowSize - m_windowIncrement) / 2; | 
| Chris@148 | 564     endFrame   -= int(m_windowSize - m_windowIncrement) / 2; | 
| Chris@148 | 565     size_t pfx = 0; | 
| Chris@148 | 566 | 
| Chris@148 | 567     size_t off = (m_fftSize - m_windowSize) / 2; | 
| Chris@148 | 568 | 
| Chris@148 | 569     for (size_t i = 0; i < off; ++i) { | 
| Chris@148 | 570         m_fftInput[i] = 0.0; | 
| Chris@148 | 571         m_fftInput[m_fftSize - i - 1] = 0.0; | 
| Chris@148 | 572     } | 
| Chris@148 | 573 | 
| Chris@148 | 574     if (startFrame < 0) { | 
| Chris@148 | 575 	pfx = size_t(-startFrame); | 
| Chris@148 | 576 	for (size_t i = 0; i < pfx; ++i) { | 
| Chris@148 | 577 	    m_fftInput[off + i] = 0.0; | 
| Chris@148 | 578 	} | 
| Chris@148 | 579     } | 
| Chris@148 | 580 | 
| Chris@148 | 581     size_t got = m_model->getValues(m_channel, startFrame + pfx, | 
| Chris@148 | 582 				    endFrame, m_fftInput + off + pfx); | 
| Chris@148 | 583 | 
| Chris@148 | 584     while (got + pfx < m_windowSize) { | 
| Chris@148 | 585 	m_fftInput[off + got + pfx] = 0.0; | 
| Chris@148 | 586 	++got; | 
| Chris@148 | 587     } | 
| Chris@148 | 588 | 
| Chris@148 | 589     if (m_channel == -1) { | 
| Chris@148 | 590 	int channels = m_model->getChannelCount(); | 
| Chris@148 | 591 	if (channels > 1) { | 
| Chris@148 | 592 	    for (size_t i = 0; i < m_windowSize; ++i) { | 
| Chris@148 | 593 		m_fftInput[off + i] /= channels; | 
| Chris@148 | 594 	    } | 
| Chris@148 | 595 	} | 
| Chris@148 | 596     } | 
| Chris@148 | 597 | 
| Chris@148 | 598     m_windower.cut(m_fftInput + off); | 
| Chris@148 | 599 | 
| Chris@148 | 600     for (size_t i = 0; i < m_fftSize/2; ++i) { | 
| Chris@148 | 601 	fftsample temp = m_fftInput[i]; | 
| Chris@148 | 602 	m_fftInput[i] = m_fftInput[i + m_fftSize/2]; | 
| Chris@148 | 603 	m_fftInput[i + m_fftSize/2] = temp; | 
| Chris@148 | 604     } | 
| Chris@148 | 605 | 
| Chris@148 | 606     fftwf_execute(m_fftPlan); | 
| Chris@148 | 607 | 
| Chris@148 | 608     fftsample factor = 0.0; | 
| Chris@148 | 609 | 
| Chris@148 | 610     for (size_t i = 0; i < m_fftSize/2; ++i) { | 
| Chris@148 | 611 | 
| Chris@148 | 612 	fftsample mag = sqrtf(m_fftOutput[i][0] * m_fftOutput[i][0] + | 
| Chris@148 | 613                               m_fftOutput[i][1] * m_fftOutput[i][1]); | 
| Chris@148 | 614 	mag /= m_windowSize / 2; | 
| Chris@148 | 615 | 
| Chris@148 | 616 	if (mag > factor) factor = mag; | 
| Chris@148 | 617 | 
| Chris@148 | 618 	fftsample phase = atan2f(m_fftOutput[i][1], m_fftOutput[i][0]); | 
| Chris@148 | 619 	phase = princargf(phase); | 
| Chris@148 | 620 | 
| Chris@148 | 621         m_workbuffer[i] = mag; | 
| Chris@148 | 622         m_workbuffer[i + m_fftSize/2] = phase; | 
| Chris@148 | 623     } | 
| Chris@148 | 624 | 
| Chris@148 | 625     cache->setColumnAt(col, | 
| Chris@148 | 626                        m_workbuffer, | 
| Chris@148 | 627                        m_workbuffer + m_fftSize/2, | 
| Chris@148 | 628                        factor); | 
| Chris@148 | 629 } | 
| Chris@148 | 630 | 
| Chris@148 | 631 size_t | 
| Chris@148 | 632 FFTDataServer::getFillCompletion() const | 
| Chris@148 | 633 { | 
| Chris@148 | 634     if (m_fillThread) return m_fillThread->getCompletion(); | 
| Chris@148 | 635     else return 100; | 
| Chris@148 | 636 } | 
| Chris@148 | 637 | 
| Chris@148 | 638 size_t | 
| Chris@148 | 639 FFTDataServer::getFillExtent() const | 
| Chris@148 | 640 { | 
| Chris@148 | 641     if (m_fillThread) return m_fillThread->getExtent(); | 
| Chris@148 | 642     else return m_model->getEndFrame(); | 
| Chris@148 | 643 } | 
| Chris@148 | 644 | 
| Chris@148 | 645 QString | 
| Chris@148 | 646 FFTDataServer::generateFileBasename() const | 
| Chris@148 | 647 { | 
| Chris@148 | 648     return generateFileBasename(m_model, m_channel, m_windower.getType(), | 
| Chris@148 | 649                                 m_windowSize, m_windowIncrement, m_fftSize, | 
| Chris@148 | 650                                 m_polar); | 
| Chris@148 | 651 } | 
| Chris@148 | 652 | 
| Chris@148 | 653 QString | 
| Chris@148 | 654 FFTDataServer::generateFileBasename(const DenseTimeValueModel *model, | 
| Chris@148 | 655                                     int channel, | 
| Chris@148 | 656                                     WindowType windowType, | 
| Chris@148 | 657                                     size_t windowSize, | 
| Chris@148 | 658                                     size_t windowIncrement, | 
| Chris@148 | 659                                     size_t fftSize, | 
| Chris@148 | 660                                     bool polar) | 
| Chris@148 | 661 { | 
| Chris@148 | 662     char buffer[200]; | 
| Chris@148 | 663 | 
| Chris@148 | 664     sprintf(buffer, "%u-%u-%u-%u-%u-%u%s", | 
| Chris@148 | 665             (unsigned int)XmlExportable::getObjectExportId(model), | 
| Chris@148 | 666             (unsigned int)(channel + 1), | 
| Chris@148 | 667             (unsigned int)windowType, | 
| Chris@148 | 668             (unsigned int)windowSize, | 
| Chris@148 | 669             (unsigned int)windowIncrement, | 
| Chris@148 | 670             (unsigned int)fftSize, | 
| Chris@148 | 671             polar ? "-p" : "-r"); | 
| Chris@148 | 672 | 
| Chris@148 | 673     return buffer; | 
| Chris@148 | 674 } | 
| Chris@148 | 675 | 
| Chris@148 | 676 void | 
| Chris@148 | 677 FFTDataServer::FillThread::run() | 
| Chris@148 | 678 { | 
| Chris@148 | 679     m_extent = 0; | 
| Chris@148 | 680     m_completion = 0; | 
| Chris@148 | 681 | 
| Chris@148 | 682     size_t start = m_server.m_model->getStartFrame(); | 
| Chris@148 | 683     size_t end = m_server.m_model->getEndFrame(); | 
| Chris@148 | 684     size_t remainingEnd = end; | 
| Chris@148 | 685 | 
| Chris@148 | 686     int counter = 0; | 
| Chris@148 | 687     int updateAt = (end / m_server.m_windowIncrement) / 20; | 
| Chris@148 | 688     if (updateAt < 100) updateAt = 100; | 
| Chris@148 | 689 | 
| Chris@148 | 690     if (m_fillFrom > start) { | 
| Chris@148 | 691 | 
| Chris@148 | 692         for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) { | 
| Chris@148 | 693 | 
| Chris@148 | 694             m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); | 
| Chris@148 | 695 | 
| Chris@148 | 696             if (m_server.m_exiting) return; | 
| Chris@148 | 697 | 
| Chris@148 | 698             while (m_server.m_suspended) { | 
| Chris@148 | 699 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 700                 std::cerr << "FFTDataServer(" << this << "): suspended, waiting..." << std::endl; | 
| Chris@148 | 701 #endif | 
| Chris@148 | 702                 m_server.m_writeMutex.lock(); | 
| Chris@148 | 703                 m_server.m_condition.wait(&m_server.m_writeMutex, 10000); | 
| Chris@148 | 704                 m_server.m_writeMutex.unlock(); | 
| Chris@148 | 705                 if (m_server.m_exiting) return; | 
| Chris@148 | 706             } | 
| Chris@148 | 707 | 
| Chris@148 | 708             if (++counter == updateAt) { | 
| Chris@148 | 709                 m_extent = f; | 
| Chris@148 | 710                 m_completion = size_t(100 * fabsf(float(f - m_fillFrom) / | 
| Chris@148 | 711                                                   float(end - start))); | 
| Chris@148 | 712                 counter = 0; | 
| Chris@148 | 713             } | 
| Chris@148 | 714         } | 
| Chris@148 | 715 | 
| Chris@148 | 716         remainingEnd = m_fillFrom; | 
| Chris@148 | 717         if (remainingEnd > start) --remainingEnd; | 
| Chris@148 | 718         else remainingEnd = start; | 
| Chris@148 | 719     } | 
| Chris@148 | 720 | 
| Chris@148 | 721     size_t baseCompletion = m_completion; | 
| Chris@148 | 722 | 
| Chris@148 | 723     for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) { | 
| Chris@148 | 724 | 
| Chris@148 | 725         m_server.fillColumn(int((f - start) / m_server.m_windowIncrement)); | 
| Chris@148 | 726 | 
| Chris@148 | 727         if (m_server.m_exiting) return; | 
| Chris@148 | 728 | 
| Chris@148 | 729         while (m_server.m_suspended) { | 
| Chris@148 | 730 #ifdef DEBUG_FFT_SERVER | 
| Chris@148 | 731             std::cerr << "FFTDataServer(" << this << "): suspended, waiting..." << std::endl; | 
| Chris@148 | 732 #endif | 
| Chris@148 | 733             m_server.m_writeMutex.lock(); | 
| Chris@148 | 734             m_server.m_condition.wait(&m_server.m_writeMutex, 10000); | 
| Chris@148 | 735             m_server.m_writeMutex.unlock(); | 
| Chris@148 | 736             if (m_server.m_exiting) return; | 
| Chris@148 | 737         } | 
| Chris@148 | 738 | 
| Chris@148 | 739         if (++counter == updateAt) { | 
| Chris@148 | 740             m_extent = f; | 
| Chris@148 | 741             m_completion = baseCompletion + | 
| Chris@148 | 742                 size_t(100 * fabsf(float(f - start) / | 
| Chris@148 | 743                                    float(end - start))); | 
| Chris@148 | 744             counter = 0; | 
| Chris@148 | 745         } | 
| Chris@148 | 746     } | 
| Chris@148 | 747 | 
| Chris@148 | 748     m_completion = 100; | 
| Chris@148 | 749     m_extent = end; | 
| Chris@148 | 750 } | 
| Chris@148 | 751 |