annotate data/fft/FFTDataServer.cpp @ 211:e2bbb58e6df6

Several changes related to referring to remote URLs for sessions and files: * Pull file dialog wrapper functions out from MainWindow into FileFinder * If a file referred to in a session is not found at its expected location, try a few other alternatives (same location as the session file or same location as the last audio file) before asking the user to locate it * Allow user to give a URL when locating an audio file, not just locate on the filesystem * Make wave file models remember the "original" location (e.g. URL) of the audio file, not just the actual location from which the data was loaded (e.g. local copy of that URL) -- when saving a session, use the original location so as not to refer to a temporary file * Clean up incompletely-downloaded local copies of files
author Chris Cannam
date Thu, 11 Jan 2007 13:29:58 +0000
parents 05154c7bb90b
children e0e7f6c5fda9
rev   line source
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@202 7 This file copyright 2006 Chris Cannam and QMUL.
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@159 19 #include "FFTMemoryCache.h"
Chris@148 20
Chris@148 21 #include "model/DenseTimeValueModel.h"
Chris@148 22
Chris@150 23 #include "system/System.h"
Chris@148 24
Chris@168 25 #include "base/StorageAdviser.h"
Chris@200 26 #include "base/Exceptions.h"
Chris@183 27 #include "base/Profiler.h"
Chris@168 28
Chris@200 29 #include <QMessageBox>
Chris@200 30 #include <QApplication>
Chris@200 31
Chris@168 32
Chris@194 33 //#define DEBUG_FFT_SERVER 1
Chris@194 34 //#define DEBUG_FFT_SERVER_FILL 1
Chris@148 35
Chris@148 36 #ifdef DEBUG_FFT_SERVER_FILL
Chris@153 37 #ifndef DEBUG_FFT_SERVER
Chris@153 38 #define DEBUG_FFT_SERVER 1
Chris@153 39 #endif
Chris@148 40 #endif
Chris@148 41
Chris@148 42 FFTDataServer::ServerMap FFTDataServer::m_servers;
Chris@148 43 QMutex FFTDataServer::m_serverMapMutex;
Chris@148 44
Chris@148 45 FFTDataServer *
Chris@148 46 FFTDataServer::getInstance(const DenseTimeValueModel *model,
Chris@148 47 int channel,
Chris@148 48 WindowType windowType,
Chris@148 49 size_t windowSize,
Chris@148 50 size_t windowIncrement,
Chris@148 51 size_t fftSize,
Chris@148 52 bool polar,
Chris@148 53 size_t fillFromColumn)
Chris@148 54 {
Chris@148 55 QString n = generateFileBasename(model,
Chris@148 56 channel,
Chris@148 57 windowType,
Chris@148 58 windowSize,
Chris@148 59 windowIncrement,
Chris@148 60 fftSize,
Chris@148 61 polar);
Chris@148 62
Chris@148 63 FFTDataServer *server = 0;
Chris@148 64
Chris@148 65 QMutexLocker locker(&m_serverMapMutex);
Chris@148 66
Chris@148 67 if ((server = findServer(n))) {
Chris@148 68 return server;
Chris@148 69 }
Chris@148 70
Chris@148 71 QString npn = generateFileBasename(model,
Chris@148 72 channel,
Chris@148 73 windowType,
Chris@148 74 windowSize,
Chris@148 75 windowIncrement,
Chris@148 76 fftSize,
Chris@148 77 !polar);
Chris@148 78
Chris@148 79 if ((server = findServer(npn))) {
Chris@148 80 return server;
Chris@148 81 }
Chris@148 82
Chris@200 83 try {
Chris@200 84 server = new FFTDataServer(n,
Chris@200 85 model,
Chris@200 86 channel,
Chris@200 87 windowType,
Chris@200 88 windowSize,
Chris@200 89 windowIncrement,
Chris@200 90 fftSize,
Chris@200 91 polar,
Chris@200 92 fillFromColumn);
Chris@200 93 } catch (InsufficientDiscSpace) {
Chris@200 94 delete server;
Chris@200 95 server = 0;
Chris@200 96 }
Chris@148 97
Chris@200 98 if (server) {
Chris@200 99 m_servers[n] = ServerCountPair(server, 1);
Chris@200 100 }
Chris@200 101
Chris@200 102 return server;
Chris@148 103 }
Chris@148 104
Chris@148 105 FFTDataServer *
Chris@148 106 FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model,
Chris@148 107 int channel,
Chris@148 108 WindowType windowType,
Chris@148 109 size_t windowSize,
Chris@148 110 size_t windowIncrement,
Chris@148 111 size_t fftSize,
Chris@148 112 bool polar,
Chris@148 113 size_t fillFromColumn)
Chris@148 114 {
Chris@148 115 // Fuzzy matching:
Chris@148 116 //
Chris@148 117 // -- if we're asked for polar and have non-polar, use it (and
Chris@148 118 // vice versa). This one is vital, and we do it for non-fuzzy as
Chris@148 119 // well (above).
Chris@148 120 //
Chris@148 121 // -- if we're asked for an instance with a given fft size and we
Chris@148 122 // have one already with a multiple of that fft size but the same
Chris@148 123 // window size and type (and model), we can draw the results from
Chris@148 124 // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the
Chris@148 125 // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the
Chris@148 126 // same window plus zero padding).
Chris@148 127 //
Chris@148 128 // -- if we're asked for an instance with a given window type and
Chris@148 129 // size and fft size and we have one already the same but with a
Chris@148 130 // smaller increment, we can draw the results from it (provided
Chris@148 131 // our increment is a multiple of its)
Chris@148 132 //
Chris@152 133 // The FFTModel knows how to interpret these things. In
Chris@148 134 // both cases we require that the larger one is a power-of-two
Chris@148 135 // multiple of the smaller (e.g. even though in principle you can
Chris@148 136 // draw the results at increment 256 from those at increment 768
Chris@152 137 // or 1536, the model doesn't support this).
Chris@148 138
Chris@148 139 {
Chris@148 140 QMutexLocker locker(&m_serverMapMutex);
Chris@148 141
Chris@148 142 ServerMap::iterator best = m_servers.end();
Chris@148 143 int bestdist = -1;
Chris@148 144
Chris@148 145 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@148 146
Chris@148 147 FFTDataServer *server = i->second.first;
Chris@148 148
Chris@148 149 if (server->getModel() == model &&
Chris@148 150 (server->getChannel() == channel || model->getChannelCount() == 1) &&
Chris@148 151 server->getWindowType() == windowType &&
Chris@148 152 server->getWindowSize() == windowSize &&
Chris@148 153 server->getWindowIncrement() <= windowIncrement &&
Chris@148 154 server->getFFTSize() >= fftSize) {
Chris@148 155
Chris@148 156 if ((windowIncrement % server->getWindowIncrement()) != 0) continue;
Chris@148 157 int ratio = windowIncrement / server->getWindowIncrement();
Chris@148 158 bool poweroftwo = true;
Chris@148 159 while (ratio > 1) {
Chris@148 160 if (ratio & 0x1) {
Chris@148 161 poweroftwo = false;
Chris@148 162 break;
Chris@148 163 }
Chris@148 164 ratio >>= 1;
Chris@148 165 }
Chris@148 166 if (!poweroftwo) continue;
Chris@148 167
Chris@148 168 if ((server->getFFTSize() % fftSize) != 0) continue;
Chris@148 169 ratio = server->getFFTSize() / fftSize;
Chris@148 170 while (ratio > 1) {
Chris@148 171 if (ratio & 0x1) {
Chris@148 172 poweroftwo = false;
Chris@148 173 break;
Chris@148 174 }
Chris@148 175 ratio >>= 1;
Chris@148 176 }
Chris@148 177 if (!poweroftwo) continue;
Chris@148 178
Chris@148 179 int distance = 0;
Chris@148 180
Chris@148 181 if (server->getPolar() != polar) distance += 1;
Chris@148 182
Chris@148 183 distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15;
Chris@148 184 distance += ((server->getFFTSize() / fftSize) - 1) * 10;
Chris@148 185
Chris@148 186 if (server->getFillCompletion() < 50) distance += 100;
Chris@148 187
Chris@148 188 #ifdef DEBUG_FFT_SERVER
Chris@148 189 std::cerr << "Distance " << distance << ", best is " << bestdist << std::endl;
Chris@148 190 #endif
Chris@148 191
Chris@148 192 if (bestdist == -1 || distance < bestdist) {
Chris@148 193 bestdist = distance;
Chris@148 194 best = i;
Chris@148 195 }
Chris@148 196 }
Chris@148 197 }
Chris@148 198
Chris@148 199 if (bestdist >= 0) {
Chris@148 200 ++best->second.second;
Chris@148 201 return best->second.first;
Chris@148 202 }
Chris@148 203 }
Chris@148 204
Chris@148 205 // Nothing found, make a new one
Chris@148 206
Chris@148 207 return getInstance(model,
Chris@148 208 channel,
Chris@148 209 windowType,
Chris@148 210 windowSize,
Chris@148 211 windowIncrement,
Chris@148 212 fftSize,
Chris@148 213 polar,
Chris@148 214 fillFromColumn);
Chris@148 215 }
Chris@148 216
Chris@148 217 FFTDataServer *
Chris@148 218 FFTDataServer::findServer(QString n)
Chris@148 219 {
Chris@148 220 if (m_servers.find(n) != m_servers.end()) {
Chris@148 221 ++m_servers[n].second;
Chris@148 222 return m_servers[n].first;
Chris@148 223 }
Chris@148 224
Chris@148 225 return 0;
Chris@148 226 }
Chris@148 227
Chris@148 228 void
Chris@152 229 FFTDataServer::claimInstance(FFTDataServer *server)
Chris@152 230 {
Chris@152 231
Chris@152 232 QMutexLocker locker(&m_serverMapMutex);
Chris@152 233
Chris@152 234 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@152 235 if (i->second.first == server) {
Chris@152 236 ++i->second.second;
Chris@152 237 return;
Chris@152 238 }
Chris@152 239 }
Chris@152 240
Chris@152 241 std::cerr << "ERROR: FFTDataServer::claimInstance: instance "
Chris@152 242 << server << " unknown!" << std::endl;
Chris@152 243 }
Chris@152 244
Chris@152 245 void
Chris@148 246 FFTDataServer::releaseInstance(FFTDataServer *server)
Chris@148 247 {
Chris@148 248 #ifdef DEBUG_FFT_SERVER
Chris@148 249 std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl;
Chris@148 250 #endif
Chris@148 251
Chris@148 252 QMutexLocker locker(&m_serverMapMutex);
Chris@148 253
Chris@148 254 //!!! not a good strategy. Want something like:
Chris@148 255
Chris@148 256 // -- if ref count > 0, decrement and return
Chris@148 257 // -- if the instance hasn't been used at all, delete it immediately
Chris@148 258 // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts,
Chris@148 259 // leave them hanging around
Chris@148 260 // -- if N instances with zero refcounts remain, delete the one that
Chris@148 261 // was last released first
Chris@148 262 // -- if we run out of disk space when allocating an instance, go back
Chris@148 263 // and delete the spare N instances before trying again
Chris@148 264 // -- have an additional method to indicate that a model has been
Chris@148 265 // destroyed, so that we can delete all of its fft server instances
Chris@148 266
Chris@148 267 // also:
Chris@148 268 //
Chris@148 269
Chris@148 270 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@148 271 if (i->second.first == server) {
Chris@148 272 if (i->second.second == 0) {
Chris@148 273 std::cerr << "ERROR: FFTDataServer::releaseInstance("
Chris@148 274 << server << "): instance not allocated" << std::endl;
Chris@148 275 } else if (--i->second.second == 0) {
Chris@148 276 if (server->m_lastUsedCache == -1) { // never used
Chris@148 277 delete server;
Chris@148 278 m_servers.erase(i);
Chris@148 279 } else {
Chris@148 280 server->suspend();
Chris@148 281 purgeLimbo();
Chris@148 282 }
Chris@148 283 }
Chris@148 284 return;
Chris@148 285 }
Chris@148 286 }
Chris@148 287
Chris@148 288 std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): "
Chris@148 289 << "instance not found" << std::endl;
Chris@148 290 }
Chris@148 291
Chris@148 292 void
Chris@148 293 FFTDataServer::purgeLimbo(int maxSize)
Chris@148 294 {
Chris@148 295 ServerMap::iterator i = m_servers.end();
Chris@148 296
Chris@148 297 int count = 0;
Chris@148 298
Chris@148 299 while (i != m_servers.begin()) {
Chris@148 300 --i;
Chris@148 301 if (i->second.second == 0) {
Chris@148 302 if (++count > maxSize) {
Chris@148 303 delete i->second.first;
Chris@148 304 m_servers.erase(i);
Chris@148 305 return;
Chris@148 306 }
Chris@148 307 }
Chris@148 308 }
Chris@148 309 }
Chris@148 310
Chris@148 311 FFTDataServer::FFTDataServer(QString fileBaseName,
Chris@148 312 const DenseTimeValueModel *model,
Chris@148 313 int channel,
Chris@148 314 WindowType windowType,
Chris@148 315 size_t windowSize,
Chris@148 316 size_t windowIncrement,
Chris@148 317 size_t fftSize,
Chris@148 318 bool polar,
Chris@148 319 size_t fillFromColumn) :
Chris@148 320 m_fileBaseName(fileBaseName),
Chris@148 321 m_model(model),
Chris@148 322 m_channel(channel),
Chris@148 323 m_windower(windowType, windowSize),
Chris@148 324 m_windowSize(windowSize),
Chris@148 325 m_windowIncrement(windowIncrement),
Chris@148 326 m_fftSize(fftSize),
Chris@148 327 m_polar(polar),
Chris@183 328 m_width(0),
Chris@183 329 m_height(0),
Chris@183 330 m_cacheWidth(0),
Chris@172 331 m_memoryCache(false),
Chris@172 332 m_compactCache(false),
Chris@148 333 m_lastUsedCache(-1),
Chris@148 334 m_fftInput(0),
Chris@148 335 m_exiting(false),
Chris@153 336 m_suspended(true), //!!! or false?
Chris@148 337 m_fillThread(0)
Chris@148 338 {
Chris@193 339 #ifdef DEBUG_FFT_SERVER
Chris@193 340 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << std::endl;
Chris@193 341 #endif
Chris@193 342
Chris@148 343 size_t start = m_model->getStartFrame();
Chris@148 344 size_t end = m_model->getEndFrame();
Chris@148 345
Chris@148 346 m_width = (end - start) / m_windowIncrement + 1;
Chris@203 347 m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2
Chris@148 348
Chris@148 349 size_t maxCacheSize = 20 * 1024 * 1024;
Chris@148 350 size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample);
Chris@148 351 if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width;
Chris@148 352 else m_cacheWidth = maxCacheSize / columnSize;
Chris@148 353
Chris@148 354 int bits = 0;
Chris@148 355 while (m_cacheWidth) { m_cacheWidth >>= 1; ++bits; }
Chris@148 356 m_cacheWidth = 2;
Chris@148 357 while (bits) { m_cacheWidth <<= 1; --bits; }
Chris@172 358
Chris@172 359 //!!! Need to pass in what this server is intended for
Chris@172 360 // (e.g. playback processing, spectrogram, feature extraction),
Chris@172 361 // or pass in something akin to the storage adviser criteria.
Chris@172 362 // That probably goes alongside the polar argument.
Chris@172 363 // For now we'll assume "spectrogram" criteria for polar ffts,
Chris@172 364 // and "feature extraction" criteria for rectangular ones.
Chris@172 365
Chris@172 366 StorageAdviser::Criteria criteria;
Chris@172 367 if (m_polar) {
Chris@172 368 criteria = StorageAdviser::Criteria
Chris@172 369 (StorageAdviser::SpeedCritical | StorageAdviser::LongRetentionLikely);
Chris@172 370 } else {
Chris@172 371 criteria = StorageAdviser::Criteria(StorageAdviser::PrecisionCritical);
Chris@172 372 }
Chris@172 373
Chris@172 374 int cells = m_width * m_height;
Chris@172 375 int minimumSize = (cells / 1024) * sizeof(uint16_t); // kb
Chris@172 376 int maximumSize = (cells / 1024) * sizeof(float); // kb
Chris@172 377
Chris@200 378 // This can throw InsufficientDiscSpace. We don't catch it here -- we
Chris@200 379 // haven't allocated anything yet and can safely let the exception out.
Chris@200 380 // Caller needs to check for it.
Chris@172 381
Chris@200 382 StorageAdviser::Recommendation recommendation =
Chris@172 383 StorageAdviser::recommend(criteria, minimumSize, maximumSize);
Chris@172 384
Chris@172 385 std::cerr << "Recommendation was: " << recommendation << std::endl;
Chris@172 386
Chris@172 387 m_memoryCache = ((recommendation & StorageAdviser::UseMemory) ||
Chris@172 388 (recommendation & StorageAdviser::PreferMemory));
Chris@172 389
Chris@172 390 m_compactCache = (recommendation & StorageAdviser::ConserveSpace);
Chris@148 391
Chris@148 392 #ifdef DEBUG_FFT_SERVER
Chris@148 393 std::cerr << "Width " << m_width << ", cache width " << m_cacheWidth << " (size " << m_cacheWidth * columnSize << ")" << std::endl;
Chris@148 394 #endif
Chris@148 395
Chris@205 396 StorageAdviser::notifyPlannedAllocation
Chris@205 397 (m_memoryCache ? StorageAdviser::MemoryAllocation :
Chris@205 398 StorageAdviser::DiscAllocation,
Chris@205 399 m_compactCache ? minimumSize : maximumSize);
Chris@205 400
Chris@148 401 for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) {
Chris@148 402 m_caches.push_back(0);
Chris@148 403 }
Chris@148 404
Chris@148 405 m_fftInput = (fftsample *)
Chris@148 406 fftwf_malloc(fftSize * sizeof(fftsample));
Chris@148 407
Chris@148 408 m_fftOutput = (fftwf_complex *)
Chris@203 409 fftwf_malloc((fftSize/2 + 1) * sizeof(fftwf_complex));
Chris@148 410
Chris@148 411 m_workbuffer = (float *)
Chris@203 412 fftwf_malloc((fftSize+2) * sizeof(float));
Chris@148 413
Chris@148 414 m_fftPlan = fftwf_plan_dft_r2c_1d(m_fftSize,
Chris@148 415 m_fftInput,
Chris@148 416 m_fftOutput,
Chris@148 417 FFTW_ESTIMATE);
Chris@148 418
Chris@148 419 if (!m_fftPlan) {
Chris@148 420 std::cerr << "ERROR: fftwf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl;
Chris@148 421 throw(0);
Chris@148 422 }
Chris@148 423
Chris@148 424 m_fillThread = new FillThread(*this, fillFromColumn);
Chris@148 425 }
Chris@148 426
Chris@148 427 FFTDataServer::~FFTDataServer()
Chris@148 428 {
Chris@148 429 #ifdef DEBUG_FFT_SERVER
Chris@193 430 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << std::endl;
Chris@148 431 #endif
Chris@148 432
Chris@155 433 m_suspended = false;
Chris@148 434 m_exiting = true;
Chris@148 435 m_condition.wakeAll();
Chris@148 436 if (m_fillThread) {
Chris@148 437 m_fillThread->wait();
Chris@148 438 delete m_fillThread;
Chris@148 439 }
Chris@148 440
Chris@148 441 QMutexLocker locker(&m_writeMutex);
Chris@148 442
Chris@148 443 for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) {
Chris@205 444 if (*i) {
Chris@205 445 delete *i;
Chris@205 446 } else {
Chris@205 447 StorageAdviser::notifyDoneAllocation
Chris@205 448 (m_memoryCache ? StorageAdviser::MemoryAllocation :
Chris@205 449 StorageAdviser::DiscAllocation,
Chris@205 450 m_cacheWidth * m_height *
Chris@205 451 (m_compactCache ? sizeof(uint16_t) : sizeof(float)) / 1024 + 1);
Chris@205 452 }
Chris@148 453 }
Chris@148 454
Chris@148 455 deleteProcessingData();
Chris@148 456 }
Chris@148 457
Chris@148 458 void
Chris@148 459 FFTDataServer::deleteProcessingData()
Chris@148 460 {
Chris@193 461 #ifdef DEBUG_FFT_SERVER
Chris@193 462 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << std::endl;
Chris@193 463 #endif
Chris@148 464 if (m_fftInput) {
Chris@148 465 fftwf_destroy_plan(m_fftPlan);
Chris@148 466 fftwf_free(m_fftInput);
Chris@148 467 fftwf_free(m_fftOutput);
Chris@148 468 fftwf_free(m_workbuffer);
Chris@148 469 }
Chris@148 470 m_fftInput = 0;
Chris@148 471 }
Chris@148 472
Chris@148 473 void
Chris@148 474 FFTDataServer::suspend()
Chris@148 475 {
Chris@148 476 #ifdef DEBUG_FFT_SERVER
Chris@193 477 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << std::endl;
Chris@148 478 #endif
Chris@183 479 Profiler profiler("FFTDataServer::suspend", false);
Chris@183 480
Chris@148 481 QMutexLocker locker(&m_writeMutex);
Chris@148 482 m_suspended = true;
Chris@148 483 for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) {
Chris@148 484 if (*i) (*i)->suspend();
Chris@148 485 }
Chris@148 486 }
Chris@148 487
Chris@148 488 void
Chris@155 489 FFTDataServer::suspendWrites()
Chris@155 490 {
Chris@155 491 #ifdef DEBUG_FFT_SERVER
Chris@193 492 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << std::endl;
Chris@155 493 #endif
Chris@183 494 Profiler profiler("FFTDataServer::suspendWrites", false);
Chris@183 495
Chris@155 496 m_suspended = true;
Chris@155 497 }
Chris@155 498
Chris@155 499 void
Chris@148 500 FFTDataServer::resume()
Chris@148 501 {
Chris@154 502 #ifdef DEBUG_FFT_SERVER
Chris@193 503 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << std::endl;
Chris@154 504 #endif
Chris@183 505 Profiler profiler("FFTDataServer::resume", false);
Chris@183 506
Chris@148 507 m_suspended = false;
Chris@157 508 if (m_fillThread) {
Chris@157 509 if (m_fillThread->isFinished()) {
Chris@157 510 delete m_fillThread;
Chris@157 511 m_fillThread = 0;
Chris@157 512 deleteProcessingData();
Chris@157 513 } else {
Chris@157 514 m_condition.wakeAll();
Chris@157 515 }
Chris@157 516 }
Chris@148 517 }
Chris@148 518
Chris@148 519 FFTCache *
Chris@148 520 FFTDataServer::getCacheAux(size_t c)
Chris@148 521 {
Chris@183 522 Profiler profiler("FFTDataServer::getCacheAux", false);
Chris@193 523 #ifdef DEBUG_FFT_SERVER
Chris@193 524 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::getCacheAux" << std::endl;
Chris@193 525 #endif
Chris@183 526
Chris@148 527 QMutexLocker locker(&m_writeMutex);
Chris@148 528
Chris@148 529 if (m_lastUsedCache == -1) {
Chris@148 530 m_fillThread->start();
Chris@148 531 }
Chris@148 532
Chris@148 533 if (int(c) != m_lastUsedCache) {
Chris@148 534
Chris@148 535 // std::cerr << "switch from " << m_lastUsedCache << " to " << c << std::endl;
Chris@148 536
Chris@148 537 for (IntQueue::iterator i = m_dormantCaches.begin();
Chris@148 538 i != m_dormantCaches.end(); ++i) {
Chris@148 539 if (*i == c) {
Chris@148 540 m_dormantCaches.erase(i);
Chris@148 541 break;
Chris@148 542 }
Chris@148 543 }
Chris@148 544
Chris@148 545 if (m_lastUsedCache >= 0) {
Chris@148 546 bool inDormant = false;
Chris@148 547 for (size_t i = 0; i < m_dormantCaches.size(); ++i) {
Chris@148 548 if (m_dormantCaches[i] == m_lastUsedCache) {
Chris@148 549 inDormant = true;
Chris@148 550 break;
Chris@148 551 }
Chris@148 552 }
Chris@148 553 if (!inDormant) {
Chris@148 554 m_dormantCaches.push_back(m_lastUsedCache);
Chris@148 555 }
Chris@148 556 while (m_dormantCaches.size() > 4) {
Chris@148 557 int dc = m_dormantCaches.front();
Chris@148 558 m_dormantCaches.pop_front();
Chris@148 559 m_caches[dc]->suspend();
Chris@148 560 }
Chris@148 561 }
Chris@148 562 }
Chris@148 563
Chris@148 564 if (m_caches[c]) {
Chris@148 565 m_lastUsedCache = c;
Chris@148 566 return m_caches[c];
Chris@148 567 }
Chris@148 568
Chris@148 569 QString name = QString("%1-%2").arg(m_fileBaseName).arg(c);
Chris@148 570
Chris@172 571 FFTCache *cache = 0;
Chris@172 572
Chris@200 573 try {
Chris@200 574
Chris@200 575 if (m_memoryCache) {
Chris@172 576
Chris@200 577 cache = new FFTMemoryCache();
Chris@172 578
Chris@200 579 } else if (m_compactCache) {
Chris@172 580
Chris@200 581 cache = new FFTFileCache(name, MatrixFile::ReadWrite,
Chris@200 582 FFTFileCache::Compact);
Chris@172 583
Chris@200 584 } else {
Chris@172 585
Chris@200 586 cache = new FFTFileCache(name, MatrixFile::ReadWrite,
Chris@200 587 m_polar ? FFTFileCache::Polar :
Chris@200 588 FFTFileCache::Rectangular);
Chris@200 589 }
Chris@200 590
Chris@200 591 size_t width = m_cacheWidth;
Chris@200 592 if (c * m_cacheWidth + width > m_width) {
Chris@200 593 width = m_width - c * m_cacheWidth;
Chris@200 594 }
Chris@200 595
Chris@200 596 cache->resize(width, m_height);
Chris@200 597 cache->reset();
Chris@200 598
Chris@205 599 StorageAdviser::notifyDoneAllocation
Chris@205 600 (m_memoryCache ? StorageAdviser::MemoryAllocation :
Chris@205 601 StorageAdviser::DiscAllocation,
Chris@205 602 width * m_height *
Chris@205 603 (m_compactCache ? sizeof(uint16_t) : sizeof(float)) / 1024 + 1);
Chris@205 604
Chris@200 605 } catch (std::bad_alloc) {
Chris@200 606 std::cerr << "ERROR: Memory allocation failed in FFTFileCache::resize:"
Chris@200 607 << " abandoning this cache" << std::endl;
Chris@200 608 //!!! Shouldn't be using QtGui here. Need a better way to report this.
Chris@200 609 QMessageBox::critical
Chris@200 610 (0, QApplication::tr("FFT cache resize failed"),
Chris@200 611 QApplication::tr
Chris@200 612 ("Failed to create or resize an FFT model slice.\n"
Chris@200 613 "There may be insufficient memory or disc space to continue."));
Chris@200 614 delete cache;
Chris@200 615 m_caches[c] = 0;
Chris@200 616 return 0;
Chris@172 617 }
Chris@148 618
Chris@148 619 m_caches[c] = cache;
Chris@148 620 m_lastUsedCache = c;
Chris@148 621
Chris@148 622 return cache;
Chris@148 623 }
Chris@148 624
Chris@148 625 float
Chris@148 626 FFTDataServer::getMagnitudeAt(size_t x, size_t y)
Chris@148 627 {
Chris@183 628 Profiler profiler("FFTDataServer::getMagnitudeAt", false);
Chris@183 629
Chris@148 630 size_t col;
Chris@148 631 FFTCache *cache = getCache(x, col);
Chris@200 632 if (!cache) return 0;
Chris@148 633
Chris@148 634 if (!cache->haveSetColumnAt(col)) {
Chris@183 635 std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn("
Chris@183 636 << x << ")" << std::endl;
Chris@148 637 fillColumn(x);
Chris@148 638 }
Chris@148 639 return cache->getMagnitudeAt(col, y);
Chris@148 640 }
Chris@148 641
Chris@148 642 float
Chris@148 643 FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y)
Chris@148 644 {
Chris@183 645 Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false);
Chris@183 646
Chris@148 647 size_t col;
Chris@148 648 FFTCache *cache = getCache(x, col);
Chris@200 649 if (!cache) return 0;
Chris@148 650
Chris@148 651 if (!cache->haveSetColumnAt(col)) {
Chris@148 652 fillColumn(x);
Chris@148 653 }
Chris@148 654 return cache->getNormalizedMagnitudeAt(col, y);
Chris@148 655 }
Chris@148 656
Chris@148 657 float
Chris@148 658 FFTDataServer::getMaximumMagnitudeAt(size_t x)
Chris@148 659 {
Chris@183 660 Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false);
Chris@183 661
Chris@148 662 size_t col;
Chris@148 663 FFTCache *cache = getCache(x, col);
Chris@200 664 if (!cache) return 0;
Chris@148 665
Chris@148 666 if (!cache->haveSetColumnAt(col)) {
Chris@148 667 fillColumn(x);
Chris@148 668 }
Chris@148 669 return cache->getMaximumMagnitudeAt(col);
Chris@148 670 }
Chris@148 671
Chris@148 672 float
Chris@148 673 FFTDataServer::getPhaseAt(size_t x, size_t y)
Chris@148 674 {
Chris@183 675 Profiler profiler("FFTDataServer::getPhaseAt", false);
Chris@183 676
Chris@148 677 size_t col;
Chris@148 678 FFTCache *cache = getCache(x, col);
Chris@200 679 if (!cache) return 0;
Chris@148 680
Chris@148 681 if (!cache->haveSetColumnAt(col)) {
Chris@148 682 fillColumn(x);
Chris@148 683 }
Chris@148 684 return cache->getPhaseAt(col, y);
Chris@148 685 }
Chris@148 686
Chris@148 687 void
Chris@148 688 FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary)
Chris@148 689 {
Chris@183 690 Profiler profiler("FFTDataServer::getValuesAt", false);
Chris@183 691
Chris@148 692 size_t col;
Chris@148 693 FFTCache *cache = getCache(x, col);
Chris@200 694 if (!cache) { real = 0; imaginary = 0; return; }
Chris@148 695
Chris@148 696 if (!cache->haveSetColumnAt(col)) {
Chris@148 697 #ifdef DEBUG_FFT_SERVER
Chris@148 698 std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl;
Chris@148 699 #endif
Chris@148 700 fillColumn(x);
Chris@148 701 }
Chris@148 702 float magnitude = cache->getMagnitudeAt(col, y);
Chris@148 703 float phase = cache->getPhaseAt(col, y);
Chris@148 704 real = magnitude * cosf(phase);
Chris@148 705 imaginary = magnitude * sinf(phase);
Chris@148 706 }
Chris@148 707
Chris@148 708 bool
Chris@148 709 FFTDataServer::isColumnReady(size_t x)
Chris@148 710 {
Chris@183 711 Profiler profiler("FFTDataServer::isColumnReady", false);
Chris@183 712
Chris@148 713 if (!haveCache(x)) {
Chris@148 714 if (m_lastUsedCache == -1) {
Chris@183 715 if (m_suspended) {
Chris@183 716 std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl;
Chris@183 717 resume();
Chris@183 718 }
Chris@148 719 m_fillThread->start();
Chris@148 720 }
Chris@148 721 return false;
Chris@148 722 }
Chris@148 723
Chris@148 724 size_t col;
Chris@148 725 FFTCache *cache = getCache(x, col);
Chris@200 726 if (!cache) return true;
Chris@148 727
Chris@148 728 return cache->haveSetColumnAt(col);
Chris@148 729 }
Chris@148 730
Chris@148 731 void
Chris@148 732 FFTDataServer::fillColumn(size_t x)
Chris@148 733 {
Chris@183 734 Profiler profiler("FFTDataServer::fillColumn", false);
Chris@183 735
Chris@148 736 size_t col;
Chris@148 737 #ifdef DEBUG_FFT_SERVER_FILL
Chris@148 738 std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl;
Chris@148 739 #endif
Chris@148 740 FFTCache *cache = getCache(x, col);
Chris@200 741 if (!cache) return;
Chris@148 742
Chris@148 743 QMutexLocker locker(&m_writeMutex);
Chris@148 744
Chris@148 745 if (cache->haveSetColumnAt(col)) return;
Chris@148 746
Chris@148 747 int startFrame = m_windowIncrement * x;
Chris@148 748 int endFrame = startFrame + m_windowSize;
Chris@148 749
Chris@148 750 startFrame -= int(m_windowSize - m_windowIncrement) / 2;
Chris@148 751 endFrame -= int(m_windowSize - m_windowIncrement) / 2;
Chris@148 752 size_t pfx = 0;
Chris@148 753
Chris@148 754 size_t off = (m_fftSize - m_windowSize) / 2;
Chris@148 755
Chris@148 756 for (size_t i = 0; i < off; ++i) {
Chris@148 757 m_fftInput[i] = 0.0;
Chris@148 758 m_fftInput[m_fftSize - i - 1] = 0.0;
Chris@148 759 }
Chris@148 760
Chris@148 761 if (startFrame < 0) {
Chris@148 762 pfx = size_t(-startFrame);
Chris@148 763 for (size_t i = 0; i < pfx; ++i) {
Chris@148 764 m_fftInput[off + i] = 0.0;
Chris@148 765 }
Chris@148 766 }
Chris@148 767
Chris@195 768 #ifdef DEBUG_FFT_SERVER_FILL
Chris@193 769 std::cerr << "FFTDataServer::fillColumn: requesting frames "
Chris@193 770 << startFrame + pfx << " -> " << endFrame << " ( = "
Chris@193 771 << endFrame - (startFrame + pfx) << ") at index "
Chris@193 772 << off + pfx << " in buffer of size " << m_fftSize
Chris@193 773 << " with window size " << m_windowSize
Chris@193 774 << " from channel " << m_channel << std::endl;
Chris@195 775 #endif
Chris@193 776
Chris@148 777 size_t got = m_model->getValues(m_channel, startFrame + pfx,
Chris@148 778 endFrame, m_fftInput + off + pfx);
Chris@148 779
Chris@148 780 while (got + pfx < m_windowSize) {
Chris@148 781 m_fftInput[off + got + pfx] = 0.0;
Chris@148 782 ++got;
Chris@148 783 }
Chris@148 784
Chris@148 785 if (m_channel == -1) {
Chris@148 786 int channels = m_model->getChannelCount();
Chris@148 787 if (channels > 1) {
Chris@148 788 for (size_t i = 0; i < m_windowSize; ++i) {
Chris@148 789 m_fftInput[off + i] /= channels;
Chris@148 790 }
Chris@148 791 }
Chris@148 792 }
Chris@148 793
Chris@148 794 m_windower.cut(m_fftInput + off);
Chris@148 795
Chris@148 796 for (size_t i = 0; i < m_fftSize/2; ++i) {
Chris@148 797 fftsample temp = m_fftInput[i];
Chris@148 798 m_fftInput[i] = m_fftInput[i + m_fftSize/2];
Chris@148 799 m_fftInput[i + m_fftSize/2] = temp;
Chris@148 800 }
Chris@148 801
Chris@148 802 fftwf_execute(m_fftPlan);
Chris@148 803
Chris@148 804 fftsample factor = 0.0;
Chris@148 805
Chris@203 806 for (size_t i = 0; i <= m_fftSize/2; ++i) {
Chris@148 807
Chris@148 808 fftsample mag = sqrtf(m_fftOutput[i][0] * m_fftOutput[i][0] +
Chris@148 809 m_fftOutput[i][1] * m_fftOutput[i][1]);
Chris@148 810 mag /= m_windowSize / 2;
Chris@148 811
Chris@148 812 if (mag > factor) factor = mag;
Chris@148 813
Chris@148 814 fftsample phase = atan2f(m_fftOutput[i][1], m_fftOutput[i][0]);
Chris@148 815 phase = princargf(phase);
Chris@148 816
Chris@148 817 m_workbuffer[i] = mag;
Chris@203 818 m_workbuffer[i + m_fftSize/2+1] = phase;
Chris@148 819 }
Chris@148 820
Chris@148 821 cache->setColumnAt(col,
Chris@148 822 m_workbuffer,
Chris@203 823 m_workbuffer + m_fftSize/2+1,
Chris@148 824 factor);
Chris@154 825
Chris@183 826 if (m_suspended) {
Chris@183 827 // std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::endl;
Chris@183 828 // resume();
Chris@183 829 }
Chris@148 830 }
Chris@148 831
Chris@148 832 size_t
Chris@148 833 FFTDataServer::getFillCompletion() const
Chris@148 834 {
Chris@148 835 if (m_fillThread) return m_fillThread->getCompletion();
Chris@148 836 else return 100;
Chris@148 837 }
Chris@148 838
Chris@148 839 size_t
Chris@148 840 FFTDataServer::getFillExtent() const
Chris@148 841 {
Chris@148 842 if (m_fillThread) return m_fillThread->getExtent();
Chris@148 843 else return m_model->getEndFrame();
Chris@148 844 }
Chris@148 845
Chris@148 846 QString
Chris@148 847 FFTDataServer::generateFileBasename() const
Chris@148 848 {
Chris@148 849 return generateFileBasename(m_model, m_channel, m_windower.getType(),
Chris@148 850 m_windowSize, m_windowIncrement, m_fftSize,
Chris@148 851 m_polar);
Chris@148 852 }
Chris@148 853
Chris@148 854 QString
Chris@148 855 FFTDataServer::generateFileBasename(const DenseTimeValueModel *model,
Chris@148 856 int channel,
Chris@148 857 WindowType windowType,
Chris@148 858 size_t windowSize,
Chris@148 859 size_t windowIncrement,
Chris@148 860 size_t fftSize,
Chris@148 861 bool polar)
Chris@148 862 {
Chris@148 863 char buffer[200];
Chris@148 864
Chris@148 865 sprintf(buffer, "%u-%u-%u-%u-%u-%u%s",
Chris@148 866 (unsigned int)XmlExportable::getObjectExportId(model),
Chris@148 867 (unsigned int)(channel + 1),
Chris@148 868 (unsigned int)windowType,
Chris@148 869 (unsigned int)windowSize,
Chris@148 870 (unsigned int)windowIncrement,
Chris@148 871 (unsigned int)fftSize,
Chris@148 872 polar ? "-p" : "-r");
Chris@148 873
Chris@148 874 return buffer;
Chris@148 875 }
Chris@148 876
Chris@148 877 void
Chris@148 878 FFTDataServer::FillThread::run()
Chris@148 879 {
Chris@148 880 m_extent = 0;
Chris@148 881 m_completion = 0;
Chris@148 882
Chris@148 883 size_t start = m_server.m_model->getStartFrame();
Chris@148 884 size_t end = m_server.m_model->getEndFrame();
Chris@148 885 size_t remainingEnd = end;
Chris@148 886
Chris@148 887 int counter = 0;
Chris@148 888 int updateAt = (end / m_server.m_windowIncrement) / 20;
Chris@148 889 if (updateAt < 100) updateAt = 100;
Chris@148 890
Chris@148 891 if (m_fillFrom > start) {
Chris@148 892
Chris@148 893 for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) {
Chris@148 894
Chris@148 895 m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
Chris@148 896
Chris@148 897 if (m_server.m_exiting) return;
Chris@148 898
Chris@148 899 while (m_server.m_suspended) {
Chris@148 900 #ifdef DEBUG_FFT_SERVER
Chris@193 901 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl;
Chris@148 902 #endif
Chris@148 903 m_server.m_writeMutex.lock();
Chris@148 904 m_server.m_condition.wait(&m_server.m_writeMutex, 10000);
Chris@148 905 m_server.m_writeMutex.unlock();
Chris@159 906 #ifdef DEBUG_FFT_SERVER
Chris@193 907 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << std::endl;
Chris@159 908 #endif
Chris@148 909 if (m_server.m_exiting) return;
Chris@148 910 }
Chris@148 911
Chris@148 912 if (++counter == updateAt) {
Chris@148 913 m_extent = f;
Chris@148 914 m_completion = size_t(100 * fabsf(float(f - m_fillFrom) /
Chris@148 915 float(end - start)));
Chris@148 916 counter = 0;
Chris@148 917 }
Chris@148 918 }
Chris@148 919
Chris@148 920 remainingEnd = m_fillFrom;
Chris@148 921 if (remainingEnd > start) --remainingEnd;
Chris@148 922 else remainingEnd = start;
Chris@148 923 }
Chris@148 924
Chris@148 925 size_t baseCompletion = m_completion;
Chris@148 926
Chris@148 927 for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) {
Chris@148 928
Chris@148 929 m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
Chris@148 930
Chris@148 931 if (m_server.m_exiting) return;
Chris@148 932
Chris@148 933 while (m_server.m_suspended) {
Chris@148 934 #ifdef DEBUG_FFT_SERVER
Chris@193 935 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl;
Chris@148 936 #endif
Chris@148 937 m_server.m_writeMutex.lock();
Chris@148 938 m_server.m_condition.wait(&m_server.m_writeMutex, 10000);
Chris@148 939 m_server.m_writeMutex.unlock();
Chris@148 940 if (m_server.m_exiting) return;
Chris@148 941 }
Chris@148 942
Chris@148 943 if (++counter == updateAt) {
Chris@148 944 m_extent = f;
Chris@148 945 m_completion = baseCompletion +
Chris@148 946 size_t(100 * fabsf(float(f - start) /
Chris@148 947 float(end - start)));
Chris@148 948 counter = 0;
Chris@148 949 }
Chris@148 950 }
Chris@148 951
Chris@148 952 m_completion = 100;
Chris@148 953 m_extent = end;
Chris@148 954 }
Chris@148 955