annotate data/fft/FFTDataServer.cpp @ 537:3cc4b7cd2aa5

* Merge from one-fftdataserver-per-fftmodel branch. This bit of reworking (which is not described very accurately by the title of the branch) turns the MatrixFile object into something that either reads or writes, but not both, and separates the FFT file cache reader and writer implementations separately. This allows the FFT data server to have a single thread owning writers and one reader per "customer" thread, and for all locking to be vastly simplified and concentrated in the data server alone (because none of the classes it makes use of is used in more than one thread at a time). The result is faster and more trustworthy code.
author Chris Cannam
date Tue, 27 Jan 2009 13:25:10 +0000
parents beb51f558e9c
children 87aef350f1dc
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@537 18 #include "FFTFileCacheReader.h"
Chris@537 19 #include "FFTFileCacheWriter.h"
Chris@159 20 #include "FFTMemoryCache.h"
Chris@148 21
Chris@148 22 #include "model/DenseTimeValueModel.h"
Chris@148 23
Chris@150 24 #include "system/System.h"
Chris@148 25
Chris@168 26 #include "base/StorageAdviser.h"
Chris@200 27 #include "base/Exceptions.h"
Chris@183 28 #include "base/Profiler.h"
Chris@244 29 #include "base/Thread.h" // for debug mutex locker
Chris@168 30
Chris@537 31 #include <QWriteLocker>
Chris@537 32
Chris@537 33 #define DEBUG_FFT_SERVER 1
Chris@536 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@244 42
Chris@148 43 FFTDataServer::ServerMap FFTDataServer::m_servers;
Chris@215 44 FFTDataServer::ServerQueue FFTDataServer::m_releasedServers;
Chris@148 45 QMutex FFTDataServer::m_serverMapMutex;
Chris@148 46
Chris@148 47 FFTDataServer *
Chris@148 48 FFTDataServer::getInstance(const DenseTimeValueModel *model,
Chris@148 49 int channel,
Chris@148 50 WindowType windowType,
Chris@148 51 size_t windowSize,
Chris@148 52 size_t windowIncrement,
Chris@148 53 size_t fftSize,
Chris@148 54 bool polar,
Chris@334 55 StorageAdviser::Criteria criteria,
Chris@148 56 size_t fillFromColumn)
Chris@148 57 {
Chris@148 58 QString n = generateFileBasename(model,
Chris@148 59 channel,
Chris@148 60 windowType,
Chris@148 61 windowSize,
Chris@148 62 windowIncrement,
Chris@148 63 fftSize,
Chris@148 64 polar);
Chris@148 65
Chris@148 66 FFTDataServer *server = 0;
Chris@148 67
Chris@408 68 MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getInstance::m_serverMapMutex");
Chris@148 69
Chris@148 70 if ((server = findServer(n))) {
Chris@148 71 return server;
Chris@148 72 }
Chris@148 73
Chris@148 74 QString npn = generateFileBasename(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
Chris@148 82 if ((server = findServer(npn))) {
Chris@148 83 return server;
Chris@148 84 }
Chris@148 85
Chris@200 86 try {
Chris@200 87 server = new FFTDataServer(n,
Chris@200 88 model,
Chris@200 89 channel,
Chris@200 90 windowType,
Chris@200 91 windowSize,
Chris@200 92 windowIncrement,
Chris@200 93 fftSize,
Chris@200 94 polar,
Chris@334 95 criteria,
Chris@200 96 fillFromColumn);
Chris@200 97 } catch (InsufficientDiscSpace) {
Chris@200 98 delete server;
Chris@200 99 server = 0;
Chris@200 100 }
Chris@148 101
Chris@200 102 if (server) {
Chris@200 103 m_servers[n] = ServerCountPair(server, 1);
Chris@200 104 }
Chris@200 105
Chris@200 106 return server;
Chris@148 107 }
Chris@148 108
Chris@148 109 FFTDataServer *
Chris@148 110 FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model,
Chris@148 111 int channel,
Chris@148 112 WindowType windowType,
Chris@148 113 size_t windowSize,
Chris@148 114 size_t windowIncrement,
Chris@148 115 size_t fftSize,
Chris@148 116 bool polar,
Chris@334 117 StorageAdviser::Criteria criteria,
Chris@148 118 size_t fillFromColumn)
Chris@148 119 {
Chris@148 120 // Fuzzy matching:
Chris@148 121 //
Chris@148 122 // -- if we're asked for polar and have non-polar, use it (and
Chris@148 123 // vice versa). This one is vital, and we do it for non-fuzzy as
Chris@148 124 // well (above).
Chris@148 125 //
Chris@148 126 // -- if we're asked for an instance with a given fft size and we
Chris@148 127 // have one already with a multiple of that fft size but the same
Chris@148 128 // window size and type (and model), we can draw the results from
Chris@148 129 // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the
Chris@148 130 // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the
Chris@148 131 // same window plus zero padding).
Chris@148 132 //
Chris@148 133 // -- if we're asked for an instance with a given window type and
Chris@148 134 // size and fft size and we have one already the same but with a
Chris@148 135 // smaller increment, we can draw the results from it (provided
Chris@148 136 // our increment is a multiple of its)
Chris@148 137 //
Chris@152 138 // The FFTModel knows how to interpret these things. In
Chris@148 139 // both cases we require that the larger one is a power-of-two
Chris@148 140 // multiple of the smaller (e.g. even though in principle you can
Chris@148 141 // draw the results at increment 256 from those at increment 768
Chris@152 142 // or 1536, the model doesn't support this).
Chris@148 143
Chris@148 144 {
Chris@408 145 MutexLocker locker(&m_serverMapMutex, "FFTDataServer::getFuzzyInstance::m_serverMapMutex");
Chris@148 146
Chris@148 147 ServerMap::iterator best = m_servers.end();
Chris@148 148 int bestdist = -1;
Chris@148 149
Chris@148 150 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@148 151
Chris@148 152 FFTDataServer *server = i->second.first;
Chris@148 153
Chris@148 154 if (server->getModel() == model &&
Chris@148 155 (server->getChannel() == channel || model->getChannelCount() == 1) &&
Chris@148 156 server->getWindowType() == windowType &&
Chris@148 157 server->getWindowSize() == windowSize &&
Chris@148 158 server->getWindowIncrement() <= windowIncrement &&
Chris@148 159 server->getFFTSize() >= fftSize) {
Chris@148 160
Chris@148 161 if ((windowIncrement % server->getWindowIncrement()) != 0) continue;
Chris@148 162 int ratio = windowIncrement / server->getWindowIncrement();
Chris@148 163 bool poweroftwo = true;
Chris@148 164 while (ratio > 1) {
Chris@148 165 if (ratio & 0x1) {
Chris@148 166 poweroftwo = false;
Chris@148 167 break;
Chris@148 168 }
Chris@148 169 ratio >>= 1;
Chris@148 170 }
Chris@148 171 if (!poweroftwo) continue;
Chris@148 172
Chris@148 173 if ((server->getFFTSize() % fftSize) != 0) continue;
Chris@148 174 ratio = server->getFFTSize() / fftSize;
Chris@148 175 while (ratio > 1) {
Chris@148 176 if (ratio & 0x1) {
Chris@148 177 poweroftwo = false;
Chris@148 178 break;
Chris@148 179 }
Chris@148 180 ratio >>= 1;
Chris@148 181 }
Chris@148 182 if (!poweroftwo) continue;
Chris@148 183
Chris@148 184 int distance = 0;
Chris@148 185
Chris@148 186 if (server->getPolar() != polar) distance += 1;
Chris@148 187
Chris@148 188 distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15;
Chris@148 189 distance += ((server->getFFTSize() / fftSize) - 1) * 10;
Chris@148 190
Chris@148 191 if (server->getFillCompletion() < 50) distance += 100;
Chris@148 192
Chris@148 193 #ifdef DEBUG_FFT_SERVER
Chris@216 194 std::cerr << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << std::endl;
Chris@148 195 #endif
Chris@148 196
Chris@148 197 if (bestdist == -1 || distance < bestdist) {
Chris@148 198 bestdist = distance;
Chris@148 199 best = i;
Chris@148 200 }
Chris@148 201 }
Chris@148 202 }
Chris@148 203
Chris@148 204 if (bestdist >= 0) {
Chris@216 205 FFTDataServer *server = best->second.first;
Chris@216 206 #ifdef DEBUG_FFT_SERVER
Chris@216 207 std::cerr << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << std::endl;
Chris@216 208 #endif
Chris@216 209 claimInstance(server, false);
Chris@216 210 return server;
Chris@148 211 }
Chris@148 212 }
Chris@148 213
Chris@148 214 // Nothing found, make a new one
Chris@148 215
Chris@148 216 return getInstance(model,
Chris@148 217 channel,
Chris@148 218 windowType,
Chris@148 219 windowSize,
Chris@148 220 windowIncrement,
Chris@148 221 fftSize,
Chris@148 222 polar,
Chris@334 223 criteria,
Chris@148 224 fillFromColumn);
Chris@148 225 }
Chris@148 226
Chris@148 227 FFTDataServer *
Chris@148 228 FFTDataServer::findServer(QString n)
Chris@148 229 {
Chris@216 230 #ifdef DEBUG_FFT_SERVER
Chris@216 231 std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\")" << std::endl;
Chris@216 232 #endif
Chris@216 233
Chris@148 234 if (m_servers.find(n) != m_servers.end()) {
Chris@216 235
Chris@216 236 FFTDataServer *server = m_servers[n].first;
Chris@216 237
Chris@216 238 #ifdef DEBUG_FFT_SERVER
Chris@216 239 std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\"): found " << server << std::endl;
Chris@216 240 #endif
Chris@216 241
Chris@216 242 claimInstance(server, false);
Chris@216 243
Chris@216 244 return server;
Chris@148 245 }
Chris@148 246
Chris@216 247 #ifdef DEBUG_FFT_SERVER
Chris@216 248 std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\"): not found" << std::endl;
Chris@216 249 #endif
Chris@216 250
Chris@148 251 return 0;
Chris@148 252 }
Chris@148 253
Chris@148 254 void
Chris@152 255 FFTDataServer::claimInstance(FFTDataServer *server)
Chris@152 256 {
Chris@216 257 claimInstance(server, true);
Chris@216 258 }
Chris@216 259
Chris@216 260 void
Chris@216 261 FFTDataServer::claimInstance(FFTDataServer *server, bool needLock)
Chris@216 262 {
Chris@244 263 MutexLocker locker(needLock ? &m_serverMapMutex : 0,
Chris@408 264 "FFTDataServer::claimInstance::m_serverMapMutex");
Chris@216 265
Chris@216 266 #ifdef DEBUG_FFT_SERVER
Chris@216 267 std::cerr << "FFTDataServer::claimInstance(" << server << ")" << std::endl;
Chris@216 268 #endif
Chris@152 269
Chris@152 270 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@152 271 if (i->second.first == server) {
Chris@215 272
Chris@215 273 for (ServerQueue::iterator j = m_releasedServers.begin();
Chris@215 274 j != m_releasedServers.end(); ++j) {
Chris@216 275
Chris@215 276 if (*j == server) {
Chris@216 277 #ifdef DEBUG_FFT_SERVER
Chris@216 278 std::cerr << "FFTDataServer::claimInstance: found in released server list, removing from it" << std::endl;
Chris@216 279 #endif
Chris@215 280 m_releasedServers.erase(j);
Chris@215 281 break;
Chris@215 282 }
Chris@215 283 }
Chris@215 284
Chris@152 285 ++i->second.second;
Chris@216 286
Chris@216 287 #ifdef DEBUG_FFT_SERVER
Chris@216 288 std::cerr << "FFTDataServer::claimInstance: new refcount is " << i->second.second << std::endl;
Chris@216 289 #endif
Chris@216 290
Chris@152 291 return;
Chris@152 292 }
Chris@152 293 }
Chris@152 294
Chris@152 295 std::cerr << "ERROR: FFTDataServer::claimInstance: instance "
Chris@152 296 << server << " unknown!" << std::endl;
Chris@152 297 }
Chris@152 298
Chris@152 299 void
Chris@148 300 FFTDataServer::releaseInstance(FFTDataServer *server)
Chris@148 301 {
Chris@216 302 releaseInstance(server, true);
Chris@216 303 }
Chris@216 304
Chris@216 305 void
Chris@216 306 FFTDataServer::releaseInstance(FFTDataServer *server, bool needLock)
Chris@216 307 {
Chris@244 308 MutexLocker locker(needLock ? &m_serverMapMutex : 0,
Chris@408 309 "FFTDataServer::releaseInstance::m_serverMapMutex");
Chris@216 310
Chris@148 311 #ifdef DEBUG_FFT_SERVER
Chris@148 312 std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl;
Chris@148 313 #endif
Chris@148 314
Chris@148 315 // -- if ref count > 0, decrement and return
Chris@148 316 // -- if the instance hasn't been used at all, delete it immediately
Chris@148 317 // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts,
Chris@148 318 // leave them hanging around
Chris@148 319 // -- if N instances with zero refcounts remain, delete the one that
Chris@148 320 // was last released first
Chris@148 321 // -- if we run out of disk space when allocating an instance, go back
Chris@148 322 // and delete the spare N instances before trying again
Chris@148 323 // -- have an additional method to indicate that a model has been
Chris@148 324 // destroyed, so that we can delete all of its fft server instances
Chris@148 325
Chris@148 326 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@148 327 if (i->second.first == server) {
Chris@148 328 if (i->second.second == 0) {
Chris@148 329 std::cerr << "ERROR: FFTDataServer::releaseInstance("
Chris@148 330 << server << "): instance not allocated" << std::endl;
Chris@148 331 } else if (--i->second.second == 0) {
Chris@537 332 /*!!!
Chris@148 333 if (server->m_lastUsedCache == -1) { // never used
Chris@216 334 #ifdef DEBUG_FFT_SERVER
Chris@216 335 std::cerr << "FFTDataServer::releaseInstance: instance "
Chris@216 336 << server << " has never been used, erasing"
Chris@216 337 << std::endl;
Chris@216 338 #endif
Chris@148 339 delete server;
Chris@148 340 m_servers.erase(i);
Chris@148 341 } else {
Chris@537 342 */
Chris@216 343 #ifdef DEBUG_FFT_SERVER
Chris@216 344 std::cerr << "FFTDataServer::releaseInstance: instance "
Chris@216 345 << server << " no longer in use, marking for possible collection"
Chris@216 346 << std::endl;
Chris@216 347 #endif
Chris@216 348 bool found = false;
Chris@216 349 for (ServerQueue::iterator j = m_releasedServers.begin();
Chris@216 350 j != m_releasedServers.end(); ++j) {
Chris@216 351 if (*j == server) {
Chris@216 352 std::cerr << "ERROR: FFTDataServer::releaseInstance("
Chris@216 353 << server << "): server is already in "
Chris@216 354 << "released servers list" << std::endl;
Chris@216 355 found = true;
Chris@216 356 }
Chris@216 357 }
Chris@216 358 if (!found) m_releasedServers.push_back(server);
Chris@148 359 server->suspend();
Chris@148 360 purgeLimbo();
Chris@537 361 //!!! }
Chris@216 362 } else {
Chris@216 363 #ifdef DEBUG_FFT_SERVER
Chris@216 364 std::cerr << "FFTDataServer::releaseInstance: instance "
Chris@216 365 << server << " now has refcount " << i->second.second
Chris@216 366 << std::endl;
Chris@216 367 #endif
Chris@148 368 }
Chris@148 369 return;
Chris@148 370 }
Chris@148 371 }
Chris@148 372
Chris@148 373 std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): "
Chris@148 374 << "instance not found" << std::endl;
Chris@148 375 }
Chris@148 376
Chris@148 377 void
Chris@148 378 FFTDataServer::purgeLimbo(int maxSize)
Chris@148 379 {
Chris@216 380 #ifdef DEBUG_FFT_SERVER
Chris@216 381 std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): "
Chris@216 382 << m_releasedServers.size() << " candidates" << std::endl;
Chris@216 383 #endif
Chris@216 384
Chris@259 385 while (int(m_releasedServers.size()) > maxSize) {
Chris@148 386
Chris@215 387 FFTDataServer *server = *m_releasedServers.begin();
Chris@148 388
Chris@215 389 bool found = false;
Chris@215 390
Chris@216 391 #ifdef DEBUG_FFT_SERVER
Chris@216 392 std::cerr << "FFTDataServer::purgeLimbo: considering candidate "
Chris@216 393 << server << std::endl;
Chris@216 394 #endif
Chris@216 395
Chris@215 396 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@215 397
Chris@215 398 if (i->second.first == server) {
Chris@215 399 found = true;
Chris@215 400 if (i->second.second > 0) {
Chris@215 401 std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server "
Chris@215 402 << server << " is in released queue, but still has non-zero refcount "
Chris@215 403 << i->second.second << std::endl;
Chris@215 404 // ... so don't delete it
Chris@215 405 break;
Chris@215 406 }
Chris@216 407 #ifdef DEBUG_FFT_SERVER
Chris@216 408 std::cerr << "FFTDataServer::purgeLimbo: looks OK, erasing it"
Chris@216 409 << std::endl;
Chris@216 410 #endif
Chris@216 411
Chris@148 412 m_servers.erase(i);
Chris@215 413 delete server;
Chris@215 414 break;
Chris@148 415 }
Chris@148 416 }
Chris@215 417
Chris@215 418 if (!found) {
Chris@215 419 std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server "
Chris@215 420 << server << " is in released queue, but not in server map!"
Chris@215 421 << std::endl;
Chris@215 422 delete server;
Chris@215 423 }
Chris@215 424
Chris@215 425 m_releasedServers.pop_front();
Chris@215 426 }
Chris@216 427
Chris@216 428 #ifdef DEBUG_FFT_SERVER
Chris@216 429 std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): "
Chris@216 430 << m_releasedServers.size() << " remain" << std::endl;
Chris@216 431 #endif
Chris@216 432
Chris@215 433 }
Chris@215 434
Chris@215 435 void
Chris@215 436 FFTDataServer::modelAboutToBeDeleted(Model *model)
Chris@215 437 {
Chris@244 438 MutexLocker locker(&m_serverMapMutex,
Chris@408 439 "FFTDataServer::modelAboutToBeDeleted::m_serverMapMutex");
Chris@215 440
Chris@216 441 #ifdef DEBUG_FFT_SERVER
Chris@216 442 std::cerr << "FFTDataServer::modelAboutToBeDeleted(" << model << ")"
Chris@216 443 << std::endl;
Chris@216 444 #endif
Chris@216 445
Chris@215 446 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
Chris@215 447
Chris@215 448 FFTDataServer *server = i->second.first;
Chris@215 449
Chris@215 450 if (server->getModel() == model) {
Chris@216 451
Chris@216 452 #ifdef DEBUG_FFT_SERVER
Chris@216 453 std::cerr << "FFTDataServer::modelAboutToBeDeleted: server is "
Chris@216 454 << server << std::endl;
Chris@216 455 #endif
Chris@216 456
Chris@215 457 if (i->second.second > 0) {
Chris@362 458 std::cerr << "WARNING: FFTDataServer::modelAboutToBeDeleted: Model " << model << " (\"" << model->objectName().toStdString() << "\") 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 459 server->suspendWrites();
Chris@362 460 return;
Chris@215 461 }
Chris@215 462 for (ServerQueue::iterator j = m_releasedServers.begin();
Chris@215 463 j != m_releasedServers.end(); ++j) {
Chris@215 464 if (*j == server) {
Chris@216 465 #ifdef DEBUG_FFT_SERVER
Chris@216 466 std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << std::endl;
Chris@216 467 #endif
Chris@215 468 m_releasedServers.erase(j);
Chris@215 469 break;
Chris@215 470 }
Chris@215 471 }
Chris@216 472 #ifdef DEBUG_FFT_SERVER
Chris@216 473 std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing server" << std::endl;
Chris@216 474 #endif
Chris@215 475 m_servers.erase(i);
Chris@215 476 delete server;
Chris@215 477 return;
Chris@215 478 }
Chris@148 479 }
Chris@148 480 }
Chris@148 481
Chris@148 482 FFTDataServer::FFTDataServer(QString fileBaseName,
Chris@148 483 const DenseTimeValueModel *model,
Chris@148 484 int channel,
Chris@148 485 WindowType windowType,
Chris@148 486 size_t windowSize,
Chris@148 487 size_t windowIncrement,
Chris@148 488 size_t fftSize,
Chris@148 489 bool polar,
Chris@334 490 StorageAdviser::Criteria criteria,
Chris@148 491 size_t fillFromColumn) :
Chris@148 492 m_fileBaseName(fileBaseName),
Chris@148 493 m_model(model),
Chris@148 494 m_channel(channel),
Chris@148 495 m_windower(windowType, windowSize),
Chris@148 496 m_windowSize(windowSize),
Chris@148 497 m_windowIncrement(windowIncrement),
Chris@148 498 m_fftSize(fftSize),
Chris@148 499 m_polar(polar),
Chris@183 500 m_width(0),
Chris@183 501 m_height(0),
Chris@183 502 m_cacheWidth(0),
Chris@359 503 m_cacheWidthPower(0),
Chris@359 504 m_cacheWidthMask(0),
Chris@359 505 m_criteria(criteria),
Chris@148 506 m_fftInput(0),
Chris@148 507 m_exiting(false),
Chris@153 508 m_suspended(true), //!!! or false?
Chris@148 509 m_fillThread(0)
Chris@148 510 {
Chris@193 511 #ifdef DEBUG_FFT_SERVER
Chris@193 512 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << std::endl;
Chris@193 513 #endif
Chris@193 514
Chris@272 515 //!!! end is not correct until model finished reading -- what to do???
Chris@272 516
Chris@148 517 size_t start = m_model->getStartFrame();
Chris@148 518 size_t end = m_model->getEndFrame();
Chris@148 519
Chris@148 520 m_width = (end - start) / m_windowIncrement + 1;
Chris@203 521 m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2
Chris@148 522
Chris@216 523 #ifdef DEBUG_FFT_SERVER
Chris@216 524 std::cerr << "FFTDataServer(" << this << "): dimensions are "
Chris@216 525 << m_width << "x" << m_height << std::endl;
Chris@216 526 #endif
Chris@216 527
Chris@148 528 size_t maxCacheSize = 20 * 1024 * 1024;
Chris@148 529 size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample);
Chris@148 530 if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width;
Chris@148 531 else m_cacheWidth = maxCacheSize / columnSize;
Chris@148 532
Chris@359 533 #ifdef DEBUG_FFT_SERVER
Chris@359 534 std::cerr << "FFTDataServer(" << this << "): cache width nominal "
Chris@359 535 << m_cacheWidth << ", actual ";
Chris@359 536 #endif
Chris@359 537
Chris@148 538 int bits = 0;
Chris@359 539 while (m_cacheWidth > 1) { m_cacheWidth >>= 1; ++bits; }
Chris@359 540 m_cacheWidthPower = bits + 1;
Chris@148 541 m_cacheWidth = 2;
Chris@148 542 while (bits) { m_cacheWidth <<= 1; --bits; }
Chris@359 543 m_cacheWidthMask = m_cacheWidth - 1;
Chris@172 544
Chris@359 545 #ifdef DEBUG_FFT_SERVER
Chris@359 546 std::cerr << m_cacheWidth << " (power " << m_cacheWidthPower << ", mask "
Chris@359 547 << m_cacheWidthMask << ")" << std::endl;
Chris@359 548 #endif
Chris@359 549
Chris@359 550 if (m_criteria == StorageAdviser::NoCriteria) {
Chris@172 551
Chris@334 552 // assume "spectrogram" criteria for polar ffts, and "feature
Chris@334 553 // extraction" criteria for rectangular ones.
Chris@334 554
Chris@334 555 if (m_polar) {
Chris@359 556 m_criteria = StorageAdviser::Criteria
Chris@334 557 (StorageAdviser::SpeedCritical |
Chris@334 558 StorageAdviser::LongRetentionLikely);
Chris@334 559 } else {
Chris@359 560 m_criteria = StorageAdviser::Criteria
Chris@334 561 (StorageAdviser::PrecisionCritical);
Chris@334 562 }
Chris@172 563 }
Chris@172 564
Chris@148 565 for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) {
Chris@148 566 m_caches.push_back(0);
Chris@148 567 }
Chris@148 568
Chris@148 569 m_fftInput = (fftsample *)
Chris@226 570 fftf_malloc(fftSize * sizeof(fftsample));
Chris@148 571
Chris@226 572 m_fftOutput = (fftf_complex *)
Chris@226 573 fftf_malloc((fftSize/2 + 1) * sizeof(fftf_complex));
Chris@148 574
Chris@148 575 m_workbuffer = (float *)
Chris@226 576 fftf_malloc((fftSize+2) * sizeof(float));
Chris@148 577
Chris@226 578 m_fftPlan = fftf_plan_dft_r2c_1d(m_fftSize,
Chris@334 579 m_fftInput,
Chris@334 580 m_fftOutput,
Chris@334 581 FFTW_MEASURE);
Chris@148 582
Chris@148 583 if (!m_fftPlan) {
Chris@226 584 std::cerr << "ERROR: fftf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl;
Chris@148 585 throw(0);
Chris@148 586 }
Chris@148 587
Chris@148 588 m_fillThread = new FillThread(*this, fillFromColumn);
Chris@148 589 }
Chris@148 590
Chris@148 591 FFTDataServer::~FFTDataServer()
Chris@148 592 {
Chris@148 593 #ifdef DEBUG_FFT_SERVER
Chris@193 594 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << std::endl;
Chris@148 595 #endif
Chris@148 596
Chris@155 597 m_suspended = false;
Chris@148 598 m_exiting = true;
Chris@148 599 m_condition.wakeAll();
Chris@148 600 if (m_fillThread) {
Chris@148 601 m_fillThread->wait();
Chris@148 602 delete m_fillThread;
Chris@148 603 }
Chris@148 604
Chris@244 605 MutexLocker locker(&m_writeMutex,
Chris@408 606 "FFTDataServer::~FFTDataServer::m_writeMutex");
Chris@148 607
Chris@148 608 for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) {
Chris@359 609
Chris@205 610 if (*i) {
Chris@205 611 delete *i;
Chris@205 612 }
Chris@148 613 }
Chris@148 614
Chris@148 615 deleteProcessingData();
Chris@148 616 }
Chris@148 617
Chris@148 618 void
Chris@148 619 FFTDataServer::deleteProcessingData()
Chris@148 620 {
Chris@193 621 #ifdef DEBUG_FFT_SERVER
Chris@193 622 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << std::endl;
Chris@193 623 #endif
Chris@148 624 if (m_fftInput) {
Chris@226 625 fftf_destroy_plan(m_fftPlan);
Chris@226 626 fftf_free(m_fftInput);
Chris@226 627 fftf_free(m_fftOutput);
Chris@226 628 fftf_free(m_workbuffer);
Chris@148 629 }
Chris@148 630 m_fftInput = 0;
Chris@148 631 }
Chris@148 632
Chris@148 633 void
Chris@148 634 FFTDataServer::suspend()
Chris@148 635 {
Chris@148 636 #ifdef DEBUG_FFT_SERVER
Chris@193 637 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << std::endl;
Chris@148 638 #endif
Chris@183 639 Profiler profiler("FFTDataServer::suspend", false);
Chris@183 640
Chris@244 641 MutexLocker locker(&m_writeMutex,
Chris@408 642 "FFTDataServer::suspend::m_writeMutex");
Chris@148 643 m_suspended = true;
Chris@148 644 }
Chris@148 645
Chris@148 646 void
Chris@155 647 FFTDataServer::suspendWrites()
Chris@155 648 {
Chris@155 649 #ifdef DEBUG_FFT_SERVER
Chris@193 650 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << std::endl;
Chris@155 651 #endif
Chris@183 652 Profiler profiler("FFTDataServer::suspendWrites", false);
Chris@183 653
Chris@155 654 m_suspended = true;
Chris@155 655 }
Chris@155 656
Chris@155 657 void
Chris@148 658 FFTDataServer::resume()
Chris@148 659 {
Chris@154 660 #ifdef DEBUG_FFT_SERVER
Chris@193 661 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << std::endl;
Chris@154 662 #endif
Chris@183 663 Profiler profiler("FFTDataServer::resume", false);
Chris@183 664
Chris@148 665 m_suspended = false;
Chris@157 666 if (m_fillThread) {
Chris@157 667 if (m_fillThread->isFinished()) {
Chris@157 668 delete m_fillThread;
Chris@157 669 m_fillThread = 0;
Chris@157 670 deleteProcessingData();
Chris@411 671 } else if (!m_fillThread->isRunning()) {
Chris@411 672 m_fillThread->start();
Chris@157 673 } else {
Chris@157 674 m_condition.wakeAll();
Chris@157 675 }
Chris@157 676 }
Chris@148 677 }
Chris@148 678
Chris@359 679 void
Chris@359 680 FFTDataServer::getStorageAdvice(size_t w, size_t h,
Chris@359 681 bool &memoryCache, bool &compactCache)
Chris@359 682 {
Chris@359 683 int cells = w * h;
Chris@359 684 int minimumSize = (cells / 1024) * sizeof(uint16_t); // kb
Chris@359 685 int maximumSize = (cells / 1024) * sizeof(float); // kb
Chris@359 686
Chris@359 687 // We don't have a compact rectangular representation, and compact
Chris@359 688 // of course is never precision-critical
Chris@359 689
Chris@359 690 bool canCompact = true;
Chris@359 691 if ((m_criteria & StorageAdviser::PrecisionCritical) || !m_polar) {
Chris@359 692 canCompact = false;
Chris@359 693 minimumSize = maximumSize; // don't use compact
Chris@359 694 }
Chris@359 695
Chris@359 696 StorageAdviser::Recommendation recommendation;
Chris@359 697
Chris@359 698 try {
Chris@359 699
Chris@359 700 recommendation =
Chris@359 701 StorageAdviser::recommend(m_criteria, minimumSize, maximumSize);
Chris@359 702
Chris@359 703 } catch (InsufficientDiscSpace s) {
Chris@359 704
Chris@359 705 // Delete any unused servers we may have been leaving around
Chris@359 706 // in case we wanted them again
Chris@359 707
Chris@359 708 purgeLimbo(0);
Chris@359 709
Chris@359 710 // This time we don't catch InsufficientDiscSpace -- we
Chris@359 711 // haven't allocated anything yet and can safely let the
Chris@359 712 // exception out to indicate to the caller that we can't
Chris@359 713 // handle it.
Chris@359 714
Chris@359 715 recommendation =
Chris@359 716 StorageAdviser::recommend(m_criteria, minimumSize, maximumSize);
Chris@359 717 }
Chris@359 718
Chris@436 719 // std::cerr << "Recommendation was: " << recommendation << std::endl;
Chris@359 720
Chris@359 721 memoryCache = false;
Chris@359 722
Chris@359 723 if ((recommendation & StorageAdviser::UseMemory) ||
Chris@359 724 (recommendation & StorageAdviser::PreferMemory)) {
Chris@537 725 //!!! memoryCache = true;
Chris@359 726 }
Chris@359 727
Chris@359 728 compactCache = canCompact &&
Chris@359 729 (recommendation & StorageAdviser::ConserveSpace);
Chris@359 730
Chris@374 731 #ifdef DEBUG_FFT_SERVER
Chris@359 732 std::cerr << "FFTDataServer: memory cache = " << memoryCache << ", compact cache = " << compactCache << std::endl;
Chris@359 733
Chris@359 734 std::cerr << "Width " << w << " of " << m_width << ", height " << h << ", size " << w * h << std::endl;
Chris@359 735 #endif
Chris@359 736 }
Chris@359 737
Chris@537 738 bool
Chris@537 739 FFTDataServer::makeCache(int c)
Chris@148 740 {
Chris@537 741 QWriteLocker locker(&m_cacheVectorLock);
Chris@183 742
Chris@537 743 if (m_caches[c]) {
Chris@537 744 // someone else must have created the cache between our
Chris@537 745 // testing for it and taking the write lock
Chris@537 746 return true;
Chris@148 747 }
Chris@148 748
Chris@537 749 CacheBlock *cb = new CacheBlock;
Chris@148 750
Chris@148 751 QString name = QString("%1-%2").arg(m_fileBaseName).arg(c);
Chris@148 752
Chris@213 753 size_t width = m_cacheWidth;
Chris@213 754 if (c * m_cacheWidth + width > m_width) {
Chris@213 755 width = m_width - c * m_cacheWidth;
Chris@213 756 }
Chris@213 757
Chris@359 758 bool memoryCache = false;
Chris@359 759 bool compactCache = false;
Chris@359 760
Chris@359 761 getStorageAdvice(width, m_height, memoryCache, compactCache);
Chris@359 762
Chris@537 763 bool success = false;
Chris@264 764
Chris@537 765 if (memoryCache) {
Chris@172 766
Chris@537 767 try {
Chris@172 768
Chris@537 769 cb->memoryCache = new FFTMemoryCache
Chris@537 770 (compactCache ? FFTCache::Compact :
Chris@537 771 m_polar ? FFTCache::Polar :
Chris@537 772 FFTCache::Rectangular,
Chris@537 773 width, m_height);
Chris@172 774
Chris@537 775 success = true;
Chris@200 776
Chris@537 777 } catch (std::bad_alloc) {
Chris@200 778
Chris@537 779 delete cb->memoryCache;
Chris@537 780 cb->memoryCache = 0;
Chris@213 781
Chris@537 782 std::cerr << "WARNING: Memory allocation failed when creating"
Chris@537 783 << " FFT memory cache no. " << c << " of " << width
Chris@213 784 << "x" << m_height << " (of total width " << m_width
Chris@213 785 << "): falling back to disc cache" << std::endl;
Chris@213 786
Chris@537 787 memoryCache = false;
Chris@213 788 }
Chris@172 789 }
Chris@148 790
Chris@537 791 if (!memoryCache) {
Chris@537 792
Chris@537 793 try {
Chris@537 794
Chris@537 795 cb->fileCacheWriter = new FFTFileCacheWriter
Chris@537 796 (name,
Chris@537 797 compactCache ? FFTCache::Compact :
Chris@537 798 m_polar ? FFTCache::Polar :
Chris@537 799 FFTCache::Rectangular,
Chris@537 800 width, m_height);
Chris@537 801
Chris@537 802 success = true;
Chris@537 803
Chris@537 804 } catch (std::exception e) {
Chris@537 805
Chris@537 806 delete cb->fileCacheWriter;
Chris@537 807 cb->fileCacheWriter = 0;
Chris@537 808
Chris@537 809 std::cerr << "ERROR: Failed to construct disc cache for FFT data: "
Chris@537 810 << e.what() << std::endl;
Chris@537 811 }
Chris@537 812 }
Chris@537 813
Chris@537 814 m_caches[c] = cb;
Chris@537 815
Chris@537 816 return success;
Chris@148 817 }
Chris@537 818
Chris@537 819 bool
Chris@537 820 FFTDataServer::makeCacheReader(int c)
Chris@537 821 {
Chris@537 822 // preconditions: m_caches[c] exists and contains a file writer;
Chris@537 823 // m_cacheVectorLock is not locked by this thread
Chris@537 824 #ifdef DEBUG_FFT_SERVER
Chris@537 825 std::cerr << "FFTDataServer::makeCacheReader(" << c << ")" << std::endl;
Chris@537 826 #endif
Chris@148 827
Chris@537 828 QThread *me = QThread::currentThread();
Chris@537 829 QWriteLocker locker(&m_cacheVectorLock);
Chris@537 830 CacheBlock *cb(m_caches.at(c));
Chris@537 831 if (!cb || !cb->fileCacheWriter) return false;
Chris@537 832
Chris@537 833 try {
Chris@537 834
Chris@537 835 cb->fileCacheReader[me] = new FFTFileCacheReader(cb->fileCacheWriter);
Chris@537 836
Chris@537 837 } catch (std::exception e) {
Chris@537 838
Chris@537 839 delete cb->fileCacheReader[me];
Chris@537 840 cb->fileCacheReader.erase(me);
Chris@537 841
Chris@537 842 std::cerr << "ERROR: Failed to construct disc cache reader for FFT data: "
Chris@537 843 << e.what() << std::endl;
Chris@537 844 return false;
Chris@537 845 }
Chris@537 846
Chris@537 847 // erase a reader that looks like it may no longer going to be
Chris@537 848 // used by this thread for a while (leaving alone the current
Chris@537 849 // and previous cache readers)
Chris@537 850 int deleteCandidate = c - 2;
Chris@537 851 if (deleteCandidate < 0) deleteCandidate = c + 2;
Chris@537 852 if (deleteCandidate >= m_caches.size()) {
Chris@537 853 return true;
Chris@537 854 }
Chris@537 855
Chris@537 856 cb = m_caches.at(deleteCandidate);
Chris@537 857 if (cb && cb->fileCacheReader.find(me) != cb->fileCacheReader.end()) {
Chris@537 858 #ifdef DEBUG_FFT_SERVER
Chris@537 859 std::cerr << "FFTDataServer::makeCacheReader: Deleting probably unpopular reader " << deleteCandidate << " for this thread (as I create reader " << c << ")" << std::endl;
Chris@537 860 #endif
Chris@537 861 delete cb->fileCacheReader[me];
Chris@537 862 cb->fileCacheReader.erase(me);
Chris@537 863 }
Chris@537 864
Chris@537 865 return true;
Chris@537 866 }
Chris@537 867
Chris@148 868 float
Chris@148 869 FFTDataServer::getMagnitudeAt(size_t x, size_t y)
Chris@148 870 {
Chris@183 871 Profiler profiler("FFTDataServer::getMagnitudeAt", false);
Chris@183 872
Chris@217 873 if (x >= m_width || y >= m_height) return 0;
Chris@217 874
Chris@148 875 size_t col;
Chris@537 876 FFTCacheReader *cache = getCacheReader(x, col);
Chris@200 877 if (!cache) return 0;
Chris@148 878
Chris@537 879 //!!! n.b. can throw
Chris@148 880 if (!cache->haveSetColumnAt(col)) {
Chris@408 881 Profiler profiler("FFTDataServer::getMagnitudeAt: filling");
Chris@280 882 #ifdef DEBUG_FFT_SERVER
Chris@183 883 std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn("
Chris@183 884 << x << ")" << std::endl;
Chris@280 885 #endif
Chris@408 886 // hold mutex so that write thread doesn't mess with class
Chris@408 887 // member data in fillColumn
Chris@408 888 MutexLocker locker(&m_writeMutex,
Chris@408 889 "FFTDataServer::getMagnitudeAt: m_writeMutex");
Chris@408 890 fillColumn(x, true);
Chris@148 891 }
Chris@148 892 return cache->getMagnitudeAt(col, y);
Chris@148 893 }
Chris@148 894
Chris@408 895 bool
Chris@408 896 FFTDataServer::getMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step)
Chris@408 897 {
Chris@408 898 Profiler profiler("FFTDataServer::getMagnitudesAt", false);
Chris@408 899
Chris@408 900 if (x >= m_width) return false;
Chris@408 901
Chris@408 902 if (minbin >= m_height) minbin = m_height - 1;
Chris@408 903 if (count == 0) count = (m_height - minbin) / step;
Chris@408 904 else if (minbin + count * step > m_height) {
Chris@408 905 count = (m_height - minbin) / step;
Chris@408 906 }
Chris@408 907
Chris@408 908 size_t col;
Chris@537 909 FFTCacheReader *cache = getCacheReader(x, col);
Chris@408 910 if (!cache) return false;
Chris@408 911
Chris@537 912 //!!! n.b. can throw
Chris@408 913 if (!cache->haveSetColumnAt(col)) {
Chris@408 914 Profiler profiler("FFTDataServer::getMagnitudesAt: filling");
Chris@408 915 MutexLocker locker(&m_writeMutex,
Chris@408 916 "FFTDataServer::getMagnitudesAt: m_writeMutex");
Chris@408 917 fillColumn(x, true);
Chris@408 918 }
Chris@408 919
Chris@509 920 cache->getMagnitudesAt(col, values, minbin, count, step);
Chris@509 921
Chris@408 922 return true;
Chris@408 923 }
Chris@408 924
Chris@148 925 float
Chris@148 926 FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y)
Chris@148 927 {
Chris@183 928 Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false);
Chris@183 929
Chris@217 930 if (x >= m_width || y >= m_height) return 0;
Chris@217 931
Chris@148 932 size_t col;
Chris@537 933 FFTCacheReader *cache = getCacheReader(x, col);
Chris@200 934 if (!cache) return 0;
Chris@148 935
Chris@537 936 //!!! n.b. can throw
Chris@148 937 if (!cache->haveSetColumnAt(col)) {
Chris@408 938 Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt: filling");
Chris@408 939 // hold mutex so that write thread doesn't mess with class
Chris@408 940 // member data in fillColumn
Chris@408 941 MutexLocker locker(&m_writeMutex,
Chris@408 942 "FFTDataServer::getNormalizedMagnitudeAt: m_writeMutex");
Chris@408 943 fillColumn(x, true);
Chris@148 944 }
Chris@148 945 return cache->getNormalizedMagnitudeAt(col, y);
Chris@148 946 }
Chris@148 947
Chris@408 948 bool
Chris@408 949 FFTDataServer::getNormalizedMagnitudesAt(size_t x, float *values, size_t minbin, size_t count, size_t step)
Chris@408 950 {
Chris@408 951 Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt", false);
Chris@408 952
Chris@408 953 if (x >= m_width) return false;
Chris@408 954
Chris@408 955 if (minbin >= m_height) minbin = m_height - 1;
Chris@408 956 if (count == 0) count = (m_height - minbin) / step;
Chris@408 957 else if (minbin + count * step > m_height) {
Chris@408 958 count = (m_height - minbin) / step;
Chris@408 959 }
Chris@408 960
Chris@408 961 size_t col;
Chris@537 962 FFTCacheReader *cache = getCacheReader(x, col);
Chris@408 963 if (!cache) return false;
Chris@408 964
Chris@537 965 //!!! n.b. can throw
Chris@408 966 if (!cache->haveSetColumnAt(col)) {
Chris@408 967 Profiler profiler("FFTDataServer::getNormalizedMagnitudesAt: filling");
Chris@408 968 MutexLocker locker(&m_writeMutex,
Chris@408 969 "FFTDataServer::getNormalizedMagnitudesAt: m_writeMutex");
Chris@408 970 fillColumn(x, true);
Chris@408 971 }
Chris@408 972
Chris@408 973 for (size_t i = 0; i < count; ++i) {
Chris@408 974 values[i] = cache->getNormalizedMagnitudeAt(col, i * step + minbin);
Chris@408 975 }
Chris@408 976
Chris@408 977 return true;
Chris@408 978 }
Chris@408 979
Chris@148 980 float
Chris@148 981 FFTDataServer::getMaximumMagnitudeAt(size_t x)
Chris@148 982 {
Chris@183 983 Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false);
Chris@183 984
Chris@217 985 if (x >= m_width) return 0;
Chris@217 986
Chris@148 987 size_t col;
Chris@537 988 FFTCacheReader *cache = getCacheReader(x, col);
Chris@200 989 if (!cache) return 0;
Chris@148 990
Chris@537 991 //!!! n.b. can throw
Chris@148 992 if (!cache->haveSetColumnAt(col)) {
Chris@408 993 Profiler profiler("FFTDataServer::getMaximumMagnitudeAt: filling");
Chris@408 994 // hold mutex so that write thread doesn't mess with class
Chris@408 995 // member data in fillColumn
Chris@408 996 MutexLocker locker(&m_writeMutex,
Chris@408 997 "FFTDataServer::getMaximumMagnitudeAt: m_writeMutex");
Chris@408 998 fillColumn(x, true);
Chris@148 999 }
Chris@148 1000 return cache->getMaximumMagnitudeAt(col);
Chris@148 1001 }
Chris@148 1002
Chris@148 1003 float
Chris@148 1004 FFTDataServer::getPhaseAt(size_t x, size_t y)
Chris@148 1005 {
Chris@183 1006 Profiler profiler("FFTDataServer::getPhaseAt", false);
Chris@183 1007
Chris@217 1008 if (x >= m_width || y >= m_height) return 0;
Chris@217 1009
Chris@148 1010 size_t col;
Chris@537 1011 FFTCacheReader *cache = getCacheReader(x, col);
Chris@200 1012 if (!cache) return 0;
Chris@148 1013
Chris@537 1014 //!!! n.b. can throw
Chris@148 1015 if (!cache->haveSetColumnAt(col)) {
Chris@408 1016 Profiler profiler("FFTDataServer::getPhaseAt: filling");
Chris@408 1017 // hold mutex so that write thread doesn't mess with class
Chris@408 1018 // member data in fillColumn
Chris@408 1019 MutexLocker locker(&m_writeMutex,
Chris@408 1020 "FFTDataServer::getPhaseAt: m_writeMutex");
Chris@408 1021 fillColumn(x, true);
Chris@148 1022 }
Chris@148 1023 return cache->getPhaseAt(col, y);
Chris@148 1024 }
Chris@148 1025
Chris@408 1026 bool
Chris@408 1027 FFTDataServer::getPhasesAt(size_t x, float *values, size_t minbin, size_t count, size_t step)
Chris@408 1028 {
Chris@408 1029 Profiler profiler("FFTDataServer::getPhasesAt", false);
Chris@408 1030
Chris@408 1031 if (x >= m_width) return false;
Chris@408 1032
Chris@408 1033 if (minbin >= m_height) minbin = m_height - 1;
Chris@408 1034 if (count == 0) count = (m_height - minbin) / step;
Chris@408 1035 else if (minbin + count * step > m_height) {
Chris@408 1036 count = (m_height - minbin) / step;
Chris@408 1037 }
Chris@408 1038
Chris@408 1039 size_t col;
Chris@537 1040 FFTCacheReader *cache = getCacheReader(x, col);
Chris@408 1041 if (!cache) return false;
Chris@408 1042
Chris@537 1043 //!!! n.b. can throw
Chris@408 1044 if (!cache->haveSetColumnAt(col)) {
Chris@408 1045 Profiler profiler("FFTDataServer::getPhasesAt: filling");
Chris@408 1046 MutexLocker locker(&m_writeMutex,
Chris@408 1047 "FFTDataServer::getPhasesAt: m_writeMutex");
Chris@408 1048 fillColumn(x, true);
Chris@408 1049 }
Chris@408 1050
Chris@408 1051 for (size_t i = 0; i < count; ++i) {
Chris@408 1052 values[i] = cache->getPhaseAt(col, i * step + minbin);
Chris@408 1053 }
Chris@408 1054
Chris@408 1055 return true;
Chris@408 1056 }
Chris@408 1057
Chris@148 1058 void
Chris@148 1059 FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary)
Chris@148 1060 {
Chris@183 1061 Profiler profiler("FFTDataServer::getValuesAt", false);
Chris@183 1062
Chris@216 1063 if (x >= m_width || y >= m_height) {
Chris@216 1064 real = 0;
Chris@216 1065 imaginary = 0;
Chris@216 1066 return;
Chris@216 1067 }
Chris@216 1068
Chris@148 1069 size_t col;
Chris@537 1070 FFTCacheReader *cache = getCacheReader(x, col);
Chris@216 1071
Chris@216 1072 if (!cache) {
Chris@216 1073 real = 0;
Chris@216 1074 imaginary = 0;
Chris@216 1075 return;
Chris@216 1076 }
Chris@148 1077
Chris@537 1078 //!!! n.b. can throw
Chris@148 1079 if (!cache->haveSetColumnAt(col)) {
Chris@408 1080 Profiler profiler("FFTDataServer::getValuesAt: filling");
Chris@148 1081 #ifdef DEBUG_FFT_SERVER
Chris@148 1082 std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl;
Chris@148 1083 #endif
Chris@408 1084 // hold mutex so that write thread doesn't mess with class
Chris@408 1085 // member data in fillColumn
Chris@408 1086 MutexLocker locker(&m_writeMutex,
Chris@408 1087 "FFTDataServer::getValuesAt: m_writeMutex");
Chris@408 1088 fillColumn(x, true);
Chris@148 1089 }
Chris@264 1090
Chris@264 1091 cache->getValuesAt(col, y, real, imaginary);
Chris@148 1092 }
Chris@148 1093
Chris@148 1094 bool
Chris@148 1095 FFTDataServer::isColumnReady(size_t x)
Chris@148 1096 {
Chris@183 1097 Profiler profiler("FFTDataServer::isColumnReady", false);
Chris@183 1098
Chris@217 1099 if (x >= m_width) return true;
Chris@217 1100
Chris@148 1101 if (!haveCache(x)) {
Chris@537 1102 /*!!!
Chris@148 1103 if (m_lastUsedCache == -1) {
Chris@183 1104 if (m_suspended) {
Chris@183 1105 std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl;
Chris@183 1106 resume();
Chris@183 1107 }
Chris@148 1108 m_fillThread->start();
Chris@148 1109 }
Chris@537 1110 */
Chris@148 1111 return false;
Chris@148 1112 }
Chris@148 1113
Chris@148 1114 size_t col;
Chris@537 1115 FFTCacheReader *cache = getCacheReader(x, col);
Chris@200 1116 if (!cache) return true;
Chris@148 1117
Chris@537 1118 //!!! n.b. can throw
Chris@148 1119 return cache->haveSetColumnAt(col);
Chris@148 1120 }
Chris@148 1121
Chris@148 1122 void
Chris@408 1123 FFTDataServer::fillColumn(size_t x, bool lockHeld)
Chris@148 1124 {
Chris@183 1125 Profiler profiler("FFTDataServer::fillColumn", false);
Chris@183 1126
Chris@272 1127 if (!m_model->isReady()) {
Chris@272 1128 std::cerr << "WARNING: FFTDataServer::fillColumn("
Chris@272 1129 << x << "): model not yet ready" << std::endl;
Chris@272 1130 return;
Chris@272 1131 }
Chris@272 1132
Chris@217 1133 if (!m_fftInput) {
Chris@217 1134 std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
Chris@217 1135 << "input has already been completed and discarded?"
Chris@217 1136 << std::endl;
Chris@217 1137 return;
Chris@217 1138 }
Chris@217 1139
Chris@217 1140 if (x >= m_width) {
Chris@217 1141 std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
Chris@217 1142 << "x > width (" << x << " > " << m_width << ")"
Chris@217 1143 << std::endl;
Chris@217 1144 return;
Chris@217 1145 }
Chris@217 1146
Chris@148 1147 size_t col;
Chris@148 1148 #ifdef DEBUG_FFT_SERVER_FILL
Chris@148 1149 std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl;
Chris@148 1150 #endif
Chris@537 1151 FFTCacheWriter *cache = getCacheWriter(x, col);
Chris@200 1152 if (!cache) return;
Chris@148 1153
Chris@408 1154 int winsize = m_windowSize;
Chris@408 1155 int fftsize = m_fftSize;
Chris@408 1156 int hs = fftsize/2;
Chris@408 1157
Chris@408 1158 int pfx = 0;
Chris@408 1159 int off = (fftsize - winsize) / 2;
Chris@148 1160
Chris@148 1161 int startFrame = m_windowIncrement * x;
Chris@148 1162 int endFrame = startFrame + m_windowSize;
Chris@148 1163
Chris@408 1164 startFrame -= winsize / 2;
Chris@408 1165 endFrame -= winsize / 2;
Chris@148 1166
Chris@408 1167 for (int i = 0; i < off; ++i) {
Chris@408 1168 m_fftInput[i] = 0.0;
Chris@408 1169 }
Chris@148 1170
Chris@408 1171 for (int i = 0; i < off; ++i) {
Chris@408 1172 m_fftInput[fftsize - i - 1] = 0.0;
Chris@148 1173 }
Chris@148 1174
Chris@148 1175 if (startFrame < 0) {
Chris@408 1176 pfx = -startFrame;
Chris@408 1177 for (int i = 0; i < pfx; ++i) {
Chris@148 1178 m_fftInput[off + i] = 0.0;
Chris@148 1179 }
Chris@148 1180 }
Chris@148 1181
Chris@195 1182 #ifdef DEBUG_FFT_SERVER_FILL
Chris@193 1183 std::cerr << "FFTDataServer::fillColumn: requesting frames "
Chris@193 1184 << startFrame + pfx << " -> " << endFrame << " ( = "
Chris@193 1185 << endFrame - (startFrame + pfx) << ") at index "
Chris@193 1186 << off + pfx << " in buffer of size " << m_fftSize
Chris@193 1187 << " with window size " << m_windowSize
Chris@193 1188 << " from channel " << m_channel << std::endl;
Chris@195 1189 #endif
Chris@193 1190
Chris@408 1191 int count = 0;
Chris@300 1192 if (endFrame > startFrame + pfx) count = endFrame - (startFrame + pfx);
Chris@300 1193
Chris@408 1194 int got = m_model->getData(m_channel, startFrame + pfx,
Chris@408 1195 count, m_fftInput + off + pfx);
Chris@148 1196
Chris@408 1197 while (got + pfx < winsize) {
Chris@148 1198 m_fftInput[off + got + pfx] = 0.0;
Chris@148 1199 ++got;
Chris@148 1200 }
Chris@148 1201
Chris@148 1202 if (m_channel == -1) {
Chris@148 1203 int channels = m_model->getChannelCount();
Chris@148 1204 if (channels > 1) {
Chris@408 1205 for (int i = 0; i < winsize; ++i) {
Chris@148 1206 m_fftInput[off + i] /= channels;
Chris@148 1207 }
Chris@148 1208 }
Chris@148 1209 }
Chris@148 1210
Chris@148 1211 m_windower.cut(m_fftInput + off);
Chris@148 1212
Chris@408 1213 for (int i = 0; i < hs; ++i) {
Chris@148 1214 fftsample temp = m_fftInput[i];
Chris@408 1215 m_fftInput[i] = m_fftInput[i + hs];
Chris@408 1216 m_fftInput[i + hs] = temp;
Chris@148 1217 }
Chris@148 1218
Chris@226 1219 fftf_execute(m_fftPlan);
Chris@148 1220
Chris@408 1221 // If our cache uses polar storage, it's more friendly for us to
Chris@408 1222 // do the conversion before taking the write mutex
Chris@148 1223
Chris@408 1224 float factor = 0.f;
Chris@148 1225
Chris@408 1226 if (cache->getStorageType() == FFTCache::Compact ||
Chris@408 1227 cache->getStorageType() == FFTCache::Polar) {
Chris@408 1228
Chris@408 1229 for (int i = 0; i <= hs; ++i) {
Chris@408 1230 fftsample real = m_fftOutput[i][0];
Chris@408 1231 fftsample imag = m_fftOutput[i][1];
Chris@408 1232 float mag = sqrtf(real * real + imag * imag);
Chris@408 1233 m_workbuffer[i] = mag;
Chris@408 1234 m_workbuffer[i + hs + 1] = atan2f(imag, real);
Chris@408 1235 if (mag > factor) factor = mag;
Chris@408 1236 }
Chris@408 1237
Chris@408 1238 } else {
Chris@408 1239
Chris@408 1240 for (int i = 0; i <= hs; ++i) {
Chris@408 1241 m_workbuffer[i] = m_fftOutput[i][0];
Chris@408 1242 m_workbuffer[i + hs + 1] = m_fftOutput[i][1];
Chris@408 1243 }
Chris@148 1244 }
Chris@148 1245
Chris@408 1246 Profiler subprof("FFTDataServer::fillColumn: set to cache");
Chris@408 1247
Chris@408 1248 {
Chris@408 1249 MutexLocker locker(lockHeld ? 0 : &m_writeMutex,
Chris@408 1250 "FFTDataServer::fillColumn: m_writeMutex [2]");
Chris@408 1251
Chris@408 1252 if (cache->getStorageType() == FFTCache::Compact ||
Chris@408 1253 cache->getStorageType() == FFTCache::Polar) {
Chris@408 1254
Chris@408 1255 cache->setColumnAt(col,
Chris@408 1256 m_workbuffer,
Chris@408 1257 m_workbuffer + hs + 1,
Chris@408 1258 factor);
Chris@408 1259
Chris@408 1260 } else {
Chris@408 1261
Chris@408 1262 cache->setColumnAt(col,
Chris@408 1263 m_workbuffer,
Chris@408 1264 m_workbuffer + hs + 1);
Chris@408 1265 }
Chris@408 1266 }
Chris@154 1267
Chris@183 1268 if (m_suspended) {
Chris@183 1269 // std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::endl;
Chris@183 1270 // resume();
Chris@183 1271 }
Chris@148 1272 }
Chris@148 1273
Chris@537 1274 void
Chris@537 1275 FFTDataServer::fillComplete()
Chris@537 1276 {
Chris@537 1277 for (int i = 0; i < int(m_caches.size()); ++i) {
Chris@537 1278 if (m_caches[i]->memoryCache) {
Chris@537 1279 m_caches[i]->memoryCache->allColumnsWritten();
Chris@537 1280 }
Chris@537 1281 if (m_caches[i]->fileCacheWriter) {
Chris@537 1282 m_caches[i]->fileCacheWriter->allColumnsWritten();
Chris@537 1283 }
Chris@537 1284 }
Chris@537 1285 }
Chris@537 1286
Chris@148 1287 size_t
Chris@148 1288 FFTDataServer::getFillCompletion() const
Chris@148 1289 {
Chris@148 1290 if (m_fillThread) return m_fillThread->getCompletion();
Chris@148 1291 else return 100;
Chris@148 1292 }
Chris@148 1293
Chris@148 1294 size_t
Chris@148 1295 FFTDataServer::getFillExtent() const
Chris@148 1296 {
Chris@148 1297 if (m_fillThread) return m_fillThread->getExtent();
Chris@148 1298 else return m_model->getEndFrame();
Chris@148 1299 }
Chris@148 1300
Chris@148 1301 QString
Chris@148 1302 FFTDataServer::generateFileBasename() const
Chris@148 1303 {
Chris@148 1304 return generateFileBasename(m_model, m_channel, m_windower.getType(),
Chris@148 1305 m_windowSize, m_windowIncrement, m_fftSize,
Chris@148 1306 m_polar);
Chris@148 1307 }
Chris@148 1308
Chris@148 1309 QString
Chris@148 1310 FFTDataServer::generateFileBasename(const DenseTimeValueModel *model,
Chris@148 1311 int channel,
Chris@148 1312 WindowType windowType,
Chris@148 1313 size_t windowSize,
Chris@148 1314 size_t windowIncrement,
Chris@148 1315 size_t fftSize,
Chris@148 1316 bool polar)
Chris@148 1317 {
Chris@148 1318 char buffer[200];
Chris@148 1319
Chris@148 1320 sprintf(buffer, "%u-%u-%u-%u-%u-%u%s",
Chris@148 1321 (unsigned int)XmlExportable::getObjectExportId(model),
Chris@148 1322 (unsigned int)(channel + 1),
Chris@148 1323 (unsigned int)windowType,
Chris@148 1324 (unsigned int)windowSize,
Chris@148 1325 (unsigned int)windowIncrement,
Chris@148 1326 (unsigned int)fftSize,
Chris@148 1327 polar ? "-p" : "-r");
Chris@148 1328
Chris@148 1329 return buffer;
Chris@148 1330 }
Chris@148 1331
Chris@148 1332 void
Chris@148 1333 FFTDataServer::FillThread::run()
Chris@148 1334 {
Chris@411 1335 #ifdef DEBUG_FFT_SERVER_FILL
Chris@411 1336 std::cerr << "FFTDataServer::FillThread::run()" << std::endl;
Chris@411 1337 #endif
Chris@411 1338
Chris@148 1339 m_extent = 0;
Chris@148 1340 m_completion = 0;
Chris@148 1341
Chris@272 1342 while (!m_server.m_model->isReady() && !m_server.m_exiting) {
Chris@411 1343 #ifdef DEBUG_FFT_SERVER_FILL
Chris@411 1344 std::cerr << "FFTDataServer::FillThread::run(): waiting for model " << m_server.m_model << " to be ready" << std::endl;
Chris@411 1345 #endif
Chris@272 1346 sleep(1);
Chris@272 1347 }
Chris@272 1348 if (m_server.m_exiting) return;
Chris@272 1349
Chris@148 1350 size_t start = m_server.m_model->getStartFrame();
Chris@148 1351 size_t end = m_server.m_model->getEndFrame();
Chris@148 1352 size_t remainingEnd = end;
Chris@148 1353
Chris@148 1354 int counter = 0;
Chris@246 1355 int updateAt = 1;
Chris@246 1356 int maxUpdateAt = (end / m_server.m_windowIncrement) / 20;
Chris@246 1357 if (maxUpdateAt < 100) maxUpdateAt = 100;
Chris@148 1358
Chris@148 1359 if (m_fillFrom > start) {
Chris@148 1360
Chris@148 1361 for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) {
Chris@148 1362
Chris@408 1363 m_server.fillColumn(int((f - start) / m_server.m_windowIncrement),
Chris@408 1364 false);
Chris@148 1365
Chris@148 1366 if (m_server.m_exiting) return;
Chris@148 1367
Chris@148 1368 while (m_server.m_suspended) {
Chris@148 1369 #ifdef DEBUG_FFT_SERVER
Chris@193 1370 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl;
Chris@148 1371 #endif
Chris@244 1372 {
Chris@244 1373 MutexLocker locker(&m_server.m_writeMutex,
Chris@408 1374 "FFTDataServer::run::m_writeMutex [1]");
Chris@244 1375 m_server.m_condition.wait(&m_server.m_writeMutex, 10000);
Chris@244 1376 }
Chris@159 1377 #ifdef DEBUG_FFT_SERVER
Chris@193 1378 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << std::endl;
Chris@159 1379 #endif
Chris@148 1380 if (m_server.m_exiting) return;
Chris@148 1381 }
Chris@148 1382
Chris@148 1383 if (++counter == updateAt) {
Chris@148 1384 m_extent = f;
Chris@148 1385 m_completion = size_t(100 * fabsf(float(f - m_fillFrom) /
Chris@148 1386 float(end - start)));
Chris@148 1387 counter = 0;
Chris@246 1388 if (updateAt < maxUpdateAt) {
Chris@246 1389 updateAt *= 2;
Chris@246 1390 if (updateAt > maxUpdateAt) updateAt = maxUpdateAt;
Chris@246 1391 }
Chris@148 1392 }
Chris@148 1393 }
Chris@148 1394
Chris@148 1395 remainingEnd = m_fillFrom;
Chris@148 1396 if (remainingEnd > start) --remainingEnd;
Chris@148 1397 else remainingEnd = start;
Chris@148 1398 }
Chris@148 1399
Chris@148 1400 size_t baseCompletion = m_completion;
Chris@148 1401
Chris@148 1402 for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) {
Chris@148 1403
Chris@408 1404 m_server.fillColumn(int((f - start) / m_server.m_windowIncrement),
Chris@408 1405 false);
Chris@148 1406
Chris@148 1407 if (m_server.m_exiting) return;
Chris@148 1408
Chris@148 1409 while (m_server.m_suspended) {
Chris@148 1410 #ifdef DEBUG_FFT_SERVER
Chris@193 1411 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl;
Chris@148 1412 #endif
Chris@244 1413 {
Chris@244 1414 MutexLocker locker(&m_server.m_writeMutex,
Chris@408 1415 "FFTDataServer::run::m_writeMutex [2]");
Chris@244 1416 m_server.m_condition.wait(&m_server.m_writeMutex, 10000);
Chris@244 1417 }
Chris@148 1418 if (m_server.m_exiting) return;
Chris@148 1419 }
Chris@148 1420
Chris@148 1421 if (++counter == updateAt) {
Chris@148 1422 m_extent = f;
Chris@148 1423 m_completion = baseCompletion +
Chris@148 1424 size_t(100 * fabsf(float(f - start) /
Chris@148 1425 float(end - start)));
Chris@148 1426 counter = 0;
Chris@246 1427 if (updateAt < maxUpdateAt) {
Chris@246 1428 updateAt *= 2;
Chris@246 1429 if (updateAt > maxUpdateAt) updateAt = maxUpdateAt;
Chris@246 1430 }
Chris@148 1431 }
Chris@148 1432 }
Chris@148 1433
Chris@537 1434 m_server.fillComplete();
Chris@148 1435 m_completion = 100;
Chris@148 1436 m_extent = end;
Chris@537 1437
Chris@537 1438 #ifdef DEBUG_FFT_SERVER
Chris@537 1439 std::cerr << "FFTDataServer::FillThread::run exiting" << std::endl;
Chris@537 1440 #endif
Chris@148 1441 }
Chris@148 1442