comparison data/fft/FFTDataServer.cpp @ 0:fc9323a41f5a

start base : Sonic Visualiser sv1-1.0rc1
author lbajardsilogic
date Fri, 11 May 2007 09:08:14 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:fc9323a41f5a
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006 Chris Cannam and QMUL.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "FFTDataServer.h"
17
18 #include "FFTFileCache.h"
19 #include "FFTMemoryCache.h"
20
21 #include "model/DenseTimeValueModel.h"
22
23 #include "system/System.h"
24
25 #include "base/StorageAdviser.h"
26 #include "base/Exceptions.h"
27 #include "base/Profiler.h"
28 #include "base/Thread.h" // for debug mutex locker
29
30 #include <QMessageBox>
31 #include <QApplication>
32
33 //#define DEBUG_FFT_SERVER 1
34 //#define DEBUG_FFT_SERVER_FILL 1
35
36 #ifdef DEBUG_FFT_SERVER_FILL
37 #ifndef DEBUG_FFT_SERVER
38 #define DEBUG_FFT_SERVER 1
39 #endif
40 #endif
41
42
43 FFTDataServer::ServerMap FFTDataServer::m_servers;
44 FFTDataServer::ServerQueue FFTDataServer::m_releasedServers;
45 QMutex FFTDataServer::m_serverMapMutex;
46
47 FFTDataServer *
48 FFTDataServer::getInstance(const DenseTimeValueModel *model,
49 int channel,
50 WindowType windowType,
51 size_t windowSize,
52 size_t windowIncrement,
53 size_t fftSize,
54 bool polar,
55 size_t fillFromColumn)
56 {
57 QString n = generateFileBasename(model,
58 channel,
59 windowType,
60 windowSize,
61 windowIncrement,
62 fftSize,
63 polar);
64
65 FFTDataServer *server = 0;
66
67 MutexLocker locker(&m_serverMapMutex, "FFTDataServer::m_serverMapMutex[getInstance]");
68
69 if ((server = findServer(n))) {
70 return server;
71 }
72
73 QString npn = generateFileBasename(model,
74 channel,
75 windowType,
76 windowSize,
77 windowIncrement,
78 fftSize,
79 !polar);
80
81 if ((server = findServer(npn))) {
82 return server;
83 }
84
85 try {
86 server = new FFTDataServer(n,
87 model,
88 channel,
89 windowType,
90 windowSize,
91 windowIncrement,
92 fftSize,
93 polar,
94 fillFromColumn);
95 } catch (InsufficientDiscSpace) {
96 delete server;
97 server = 0;
98 }
99
100 if (server) {
101 m_servers[n] = ServerCountPair(server, 1);
102 }
103
104 return server;
105 }
106
107 FFTDataServer *
108 FFTDataServer::getFuzzyInstance(const DenseTimeValueModel *model,
109 int channel,
110 WindowType windowType,
111 size_t windowSize,
112 size_t windowIncrement,
113 size_t fftSize,
114 bool polar,
115 size_t fillFromColumn)
116 {
117 // Fuzzy matching:
118 //
119 // -- if we're asked for polar and have non-polar, use it (and
120 // vice versa). This one is vital, and we do it for non-fuzzy as
121 // well (above).
122 //
123 // -- if we're asked for an instance with a given fft size and we
124 // have one already with a multiple of that fft size but the same
125 // window size and type (and model), we can draw the results from
126 // it (e.g. the 1st, 2nd, 3rd etc bins of a 512-sample FFT are the
127 // same as the the 1st, 5th, 9th etc of a 2048-sample FFT of the
128 // same window plus zero padding).
129 //
130 // -- if we're asked for an instance with a given window type and
131 // size and fft size and we have one already the same but with a
132 // smaller increment, we can draw the results from it (provided
133 // our increment is a multiple of its)
134 //
135 // The FFTModel knows how to interpret these things. In
136 // both cases we require that the larger one is a power-of-two
137 // multiple of the smaller (e.g. even though in principle you can
138 // draw the results at increment 256 from those at increment 768
139 // or 1536, the model doesn't support this).
140
141 {
142 MutexLocker locker(&m_serverMapMutex, "FFTDataServer::m_serverMapMutex[getFuzzyInstance]");
143
144 ServerMap::iterator best = m_servers.end();
145 int bestdist = -1;
146
147 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
148
149 FFTDataServer *server = i->second.first;
150
151 if (server->getModel() == model &&
152 (server->getChannel() == channel || model->getChannelCount() == 1) &&
153 server->getWindowType() == windowType &&
154 server->getWindowSize() == windowSize &&
155 server->getWindowIncrement() <= windowIncrement &&
156 server->getFFTSize() >= fftSize) {
157
158 if ((windowIncrement % server->getWindowIncrement()) != 0) continue;
159 int ratio = windowIncrement / server->getWindowIncrement();
160 bool poweroftwo = true;
161 while (ratio > 1) {
162 if (ratio & 0x1) {
163 poweroftwo = false;
164 break;
165 }
166 ratio >>= 1;
167 }
168 if (!poweroftwo) continue;
169
170 if ((server->getFFTSize() % fftSize) != 0) continue;
171 ratio = server->getFFTSize() / fftSize;
172 while (ratio > 1) {
173 if (ratio & 0x1) {
174 poweroftwo = false;
175 break;
176 }
177 ratio >>= 1;
178 }
179 if (!poweroftwo) continue;
180
181 int distance = 0;
182
183 if (server->getPolar() != polar) distance += 1;
184
185 distance += ((windowIncrement / server->getWindowIncrement()) - 1) * 15;
186 distance += ((server->getFFTSize() / fftSize) - 1) * 10;
187
188 if (server->getFillCompletion() < 50) distance += 100;
189
190 #ifdef DEBUG_FFT_SERVER
191 std::cerr << "FFTDataServer::getFuzzyInstance: Distance for server " << server << " is " << distance << ", best is " << bestdist << std::endl;
192 #endif
193
194 if (bestdist == -1 || distance < bestdist) {
195 bestdist = distance;
196 best = i;
197 }
198 }
199 }
200
201 if (bestdist >= 0) {
202 FFTDataServer *server = best->second.first;
203 #ifdef DEBUG_FFT_SERVER
204 std::cerr << "FFTDataServer::getFuzzyInstance: We like server " << server << " (with distance " << bestdist << ")" << std::endl;
205 #endif
206 claimInstance(server, false);
207 return server;
208 }
209 }
210
211 // Nothing found, make a new one
212
213 return getInstance(model,
214 channel,
215 windowType,
216 windowSize,
217 windowIncrement,
218 fftSize,
219 polar,
220 fillFromColumn);
221 }
222
223 FFTDataServer *
224 FFTDataServer::findServer(QString n)
225 {
226 #ifdef DEBUG_FFT_SERVER
227 std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\")" << std::endl;
228 #endif
229
230 if (m_servers.find(n) != m_servers.end()) {
231
232 FFTDataServer *server = m_servers[n].first;
233
234 #ifdef DEBUG_FFT_SERVER
235 std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\"): found " << server << std::endl;
236 #endif
237
238 claimInstance(server, false);
239
240 return server;
241 }
242
243 #ifdef DEBUG_FFT_SERVER
244 std::cerr << "FFTDataServer::findServer(\"" << n.toStdString() << "\"): not found" << std::endl;
245 #endif
246
247 return 0;
248 }
249
250 void
251 FFTDataServer::claimInstance(FFTDataServer *server)
252 {
253 claimInstance(server, true);
254 }
255
256 void
257 FFTDataServer::claimInstance(FFTDataServer *server, bool needLock)
258 {
259 MutexLocker locker(needLock ? &m_serverMapMutex : 0,
260 "FFTDataServer::m_serverMapMutex[claimInstance]");
261
262 #ifdef DEBUG_FFT_SERVER
263 std::cerr << "FFTDataServer::claimInstance(" << server << ")" << std::endl;
264 #endif
265
266 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
267 if (i->second.first == server) {
268
269 for (ServerQueue::iterator j = m_releasedServers.begin();
270 j != m_releasedServers.end(); ++j) {
271
272 if (*j == server) {
273 #ifdef DEBUG_FFT_SERVER
274 std::cerr << "FFTDataServer::claimInstance: found in released server list, removing from it" << std::endl;
275 #endif
276 m_releasedServers.erase(j);
277 break;
278 }
279 }
280
281 ++i->second.second;
282
283 #ifdef DEBUG_FFT_SERVER
284 std::cerr << "FFTDataServer::claimInstance: new refcount is " << i->second.second << std::endl;
285 #endif
286
287 return;
288 }
289 }
290
291 std::cerr << "ERROR: FFTDataServer::claimInstance: instance "
292 << server << " unknown!" << std::endl;
293 }
294
295 void
296 FFTDataServer::releaseInstance(FFTDataServer *server)
297 {
298 releaseInstance(server, true);
299 }
300
301 void
302 FFTDataServer::releaseInstance(FFTDataServer *server, bool needLock)
303 {
304 MutexLocker locker(needLock ? &m_serverMapMutex : 0,
305 "FFTDataServer::m_serverMapMutex[releaseInstance]");
306
307 #ifdef DEBUG_FFT_SERVER
308 std::cerr << "FFTDataServer::releaseInstance(" << server << ")" << std::endl;
309 #endif
310
311 // -- if ref count > 0, decrement and return
312 // -- if the instance hasn't been used at all, delete it immediately
313 // -- if fewer than N instances (N = e.g. 3) remain with zero refcounts,
314 // leave them hanging around
315 // -- if N instances with zero refcounts remain, delete the one that
316 // was last released first
317 // -- if we run out of disk space when allocating an instance, go back
318 // and delete the spare N instances before trying again
319 // -- have an additional method to indicate that a model has been
320 // destroyed, so that we can delete all of its fft server instances
321
322 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
323 if (i->second.first == server) {
324 if (i->second.second == 0) {
325 std::cerr << "ERROR: FFTDataServer::releaseInstance("
326 << server << "): instance not allocated" << std::endl;
327 } else if (--i->second.second == 0) {
328 if (server->m_lastUsedCache == -1) { // never used
329 #ifdef DEBUG_FFT_SERVER
330 std::cerr << "FFTDataServer::releaseInstance: instance "
331 << server << " has never been used, erasing"
332 << std::endl;
333 #endif
334 delete server;
335 m_servers.erase(i);
336 } else {
337 #ifdef DEBUG_FFT_SERVER
338 std::cerr << "FFTDataServer::releaseInstance: instance "
339 << server << " no longer in use, marking for possible collection"
340 << std::endl;
341 #endif
342 bool found = false;
343 for (ServerQueue::iterator j = m_releasedServers.begin();
344 j != m_releasedServers.end(); ++j) {
345 if (*j == server) {
346 std::cerr << "ERROR: FFTDataServer::releaseInstance("
347 << server << "): server is already in "
348 << "released servers list" << std::endl;
349 found = true;
350 }
351 }
352 if (!found) m_releasedServers.push_back(server);
353 server->suspend();
354 purgeLimbo();
355 }
356 } else {
357 #ifdef DEBUG_FFT_SERVER
358 std::cerr << "FFTDataServer::releaseInstance: instance "
359 << server << " now has refcount " << i->second.second
360 << std::endl;
361 #endif
362 }
363 return;
364 }
365 }
366
367 std::cerr << "ERROR: FFTDataServer::releaseInstance(" << server << "): "
368 << "instance not found" << std::endl;
369 }
370
371 void
372 FFTDataServer::purgeLimbo(int maxSize)
373 {
374 #ifdef DEBUG_FFT_SERVER
375 std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): "
376 << m_releasedServers.size() << " candidates" << std::endl;
377 #endif
378
379 while (int(m_releasedServers.size()) > maxSize) {
380
381 FFTDataServer *server = *m_releasedServers.begin();
382
383 bool found = false;
384
385 #ifdef DEBUG_FFT_SERVER
386 std::cerr << "FFTDataServer::purgeLimbo: considering candidate "
387 << server << std::endl;
388 #endif
389
390 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
391
392 if (i->second.first == server) {
393 found = true;
394 if (i->second.second > 0) {
395 std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server "
396 << server << " is in released queue, but still has non-zero refcount "
397 << i->second.second << std::endl;
398 // ... so don't delete it
399 break;
400 }
401 #ifdef DEBUG_FFT_SERVER
402 std::cerr << "FFTDataServer::purgeLimbo: looks OK, erasing it"
403 << std::endl;
404 #endif
405
406 m_servers.erase(i);
407 delete server;
408 break;
409 }
410 }
411
412 if (!found) {
413 std::cerr << "ERROR: FFTDataServer::purgeLimbo: Server "
414 << server << " is in released queue, but not in server map!"
415 << std::endl;
416 delete server;
417 }
418
419 m_releasedServers.pop_front();
420 }
421
422 #ifdef DEBUG_FFT_SERVER
423 std::cerr << "FFTDataServer::purgeLimbo(" << maxSize << "): "
424 << m_releasedServers.size() << " remain" << std::endl;
425 #endif
426
427 }
428
429 void
430 FFTDataServer::modelAboutToBeDeleted(Model *model)
431 {
432 MutexLocker locker(&m_serverMapMutex,
433 "FFTDataServer::m_serverMapMutex[modelAboutToBeDeleted]");
434
435 #ifdef DEBUG_FFT_SERVER
436 std::cerr << "FFTDataServer::modelAboutToBeDeleted(" << model << ")"
437 << std::endl;
438 #endif
439
440 for (ServerMap::iterator i = m_servers.begin(); i != m_servers.end(); ++i) {
441
442 FFTDataServer *server = i->second.first;
443
444 if (server->getModel() == model) {
445
446 #ifdef DEBUG_FFT_SERVER
447 std::cerr << "FFTDataServer::modelAboutToBeDeleted: server is "
448 << server << std::endl;
449 #endif
450
451 if (i->second.second > 0) {
452 std::cerr << "ERROR: 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;
453 }
454 for (ServerQueue::iterator j = m_releasedServers.begin();
455 j != m_releasedServers.end(); ++j) {
456 if (*j == server) {
457 #ifdef DEBUG_FFT_SERVER
458 std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing from released servers" << std::endl;
459 #endif
460 m_releasedServers.erase(j);
461 break;
462 }
463 }
464 #ifdef DEBUG_FFT_SERVER
465 std::cerr << "FFTDataServer::modelAboutToBeDeleted: erasing server" << std::endl;
466 #endif
467 m_servers.erase(i);
468 delete server;
469 return;
470 }
471 }
472 }
473
474 FFTDataServer::FFTDataServer(QString fileBaseName,
475 const DenseTimeValueModel *model,
476 int channel,
477 WindowType windowType,
478 size_t windowSize,
479 size_t windowIncrement,
480 size_t fftSize,
481 bool polar,
482 size_t fillFromColumn) :
483 m_fileBaseName(fileBaseName),
484 m_model(model),
485 m_channel(channel),
486 m_windower(windowType, windowSize),
487 m_windowSize(windowSize),
488 m_windowIncrement(windowIncrement),
489 m_fftSize(fftSize),
490 m_polar(polar),
491 m_width(0),
492 m_height(0),
493 m_cacheWidth(0),
494 m_memoryCache(false),
495 m_compactCache(false),
496 m_lastUsedCache(-1),
497 m_fftInput(0),
498 m_exiting(false),
499 m_suspended(true), //!!! or false?
500 m_fillThread(0)
501 {
502 #ifdef DEBUG_FFT_SERVER
503 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::FFTDataServer" << std::endl;
504 #endif
505
506 size_t start = m_model->getStartFrame();
507 size_t end = m_model->getEndFrame();
508
509 m_width = (end - start) / m_windowIncrement + 1;
510 m_height = m_fftSize / 2 + 1; // DC == 0, Nyquist == fftsize/2
511
512 #ifdef DEBUG_FFT_SERVER
513 std::cerr << "FFTDataServer(" << this << "): dimensions are "
514 << m_width << "x" << m_height << std::endl;
515 #endif
516
517 size_t maxCacheSize = 20 * 1024 * 1024;
518 size_t columnSize = m_height * sizeof(fftsample) * 2 + sizeof(fftsample);
519 if (m_width * columnSize < maxCacheSize * 2) m_cacheWidth = m_width;
520 else m_cacheWidth = maxCacheSize / columnSize;
521
522 int bits = 0;
523 while (m_cacheWidth) { m_cacheWidth >>= 1; ++bits; }
524 m_cacheWidth = 2;
525 while (bits) { m_cacheWidth <<= 1; --bits; }
526
527 //!!! Need to pass in what this server is intended for
528 // (e.g. playback processing, spectrogram, feature extraction),
529 // or pass in something akin to the storage adviser criteria.
530 // That probably goes alongside the polar argument.
531 // For now we'll assume "spectrogram" criteria for polar ffts,
532 // and "feature extraction" criteria for rectangular ones.
533
534 StorageAdviser::Criteria criteria;
535 if (m_polar) {
536 criteria = StorageAdviser::Criteria
537 (StorageAdviser::SpeedCritical | StorageAdviser::LongRetentionLikely);
538 } else {
539 criteria = StorageAdviser::Criteria(StorageAdviser::PrecisionCritical);
540 }
541
542 int cells = m_width * m_height;
543 int minimumSize = (cells / 1024) * sizeof(uint16_t); // kb
544 int maximumSize = (cells / 1024) * sizeof(float); // kb
545
546 StorageAdviser::Recommendation recommendation;
547
548 try {
549
550 recommendation =
551 StorageAdviser::recommend(criteria, minimumSize, maximumSize);
552
553 } catch (InsufficientDiscSpace s) {
554
555 // Delete any unused servers we may have been leaving around
556 // in case we wanted them again
557
558 purgeLimbo(0);
559
560 // This time we don't catch InsufficientDiscSpace -- we
561 // haven't allocated anything yet and can safely let the
562 // exception out to indicate to the caller that we can't
563 // handle it.
564
565 recommendation =
566 StorageAdviser::recommend(criteria, minimumSize, maximumSize);
567 }
568
569 // std::cerr << "Recommendation was: " << recommendation << std::endl;
570
571 m_memoryCache = ((recommendation & StorageAdviser::UseMemory) ||
572 (recommendation & StorageAdviser::PreferMemory));
573
574 m_compactCache = (recommendation & StorageAdviser::ConserveSpace);
575
576 #ifdef DEBUG_FFT_SERVER
577 std::cerr << "Width " << m_width << ", cache width " << m_cacheWidth << " (size " << m_cacheWidth * columnSize << ")" << std::endl;
578 #endif
579
580 StorageAdviser::notifyPlannedAllocation
581 (m_memoryCache ? StorageAdviser::MemoryAllocation :
582 StorageAdviser::DiscAllocation,
583 m_compactCache ? minimumSize : maximumSize);
584
585 for (size_t i = 0; i <= m_width / m_cacheWidth; ++i) {
586 m_caches.push_back(0);
587 }
588
589 m_fftInput = (fftsample *)
590 fftf_malloc(fftSize * sizeof(fftsample));
591
592 m_fftOutput = (fftf_complex *)
593 fftf_malloc((fftSize/2 + 1) * sizeof(fftf_complex));
594
595 m_workbuffer = (float *)
596 fftf_malloc((fftSize+2) * sizeof(float));
597
598 m_fftPlan = fftf_plan_dft_r2c_1d(m_fftSize,
599 m_fftInput,
600 m_fftOutput,
601 FFTW_ESTIMATE);
602
603 if (!m_fftPlan) {
604 std::cerr << "ERROR: fftf_plan_dft_r2c_1d(" << m_windowSize << ") failed!" << std::endl;
605 throw(0);
606 }
607
608 m_fillThread = new FillThread(*this, fillFromColumn);
609 }
610
611 FFTDataServer::~FFTDataServer()
612 {
613 #ifdef DEBUG_FFT_SERVER
614 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::~FFTDataServer()" << std::endl;
615 #endif
616
617 m_suspended = false;
618 m_exiting = true;
619 m_condition.wakeAll();
620 if (m_fillThread) {
621 m_fillThread->wait();
622 delete m_fillThread;
623 }
624
625 MutexLocker locker(&m_writeMutex,
626 "FFTDataServer::m_writeMutex[~FFTDataServer]");
627
628 for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) {
629 if (*i) {
630 delete *i;
631 } else {
632 StorageAdviser::notifyDoneAllocation
633 (m_memoryCache ? StorageAdviser::MemoryAllocation :
634 StorageAdviser::DiscAllocation,
635 m_cacheWidth * m_height *
636 (m_compactCache ? sizeof(uint16_t) : sizeof(float)) / 1024 + 1);
637 }
638 }
639
640 deleteProcessingData();
641 }
642
643 void
644 FFTDataServer::deleteProcessingData()
645 {
646 #ifdef DEBUG_FFT_SERVER
647 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): deleteProcessingData" << std::endl;
648 #endif
649 if (m_fftInput) {
650 fftf_destroy_plan(m_fftPlan);
651 fftf_free(m_fftInput);
652 fftf_free(m_fftOutput);
653 fftf_free(m_workbuffer);
654 }
655 m_fftInput = 0;
656 }
657
658 void
659 FFTDataServer::suspend()
660 {
661 #ifdef DEBUG_FFT_SERVER
662 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspend" << std::endl;
663 #endif
664 Profiler profiler("FFTDataServer::suspend", false);
665
666 MutexLocker locker(&m_writeMutex,
667 "FFTDataServer::m_writeMutex[suspend]");
668 m_suspended = true;
669 for (CacheVector::iterator i = m_caches.begin(); i != m_caches.end(); ++i) {
670 if (*i) (*i)->suspend();
671 }
672 }
673
674 void
675 FFTDataServer::suspendWrites()
676 {
677 #ifdef DEBUG_FFT_SERVER
678 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspendWrites" << std::endl;
679 #endif
680 Profiler profiler("FFTDataServer::suspendWrites", false);
681
682 m_suspended = true;
683 }
684
685 void
686 FFTDataServer::resume()
687 {
688 #ifdef DEBUG_FFT_SERVER
689 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): resume" << std::endl;
690 #endif
691 Profiler profiler("FFTDataServer::resume", false);
692
693 m_suspended = false;
694 if (m_fillThread) {
695 if (m_fillThread->isFinished()) {
696 delete m_fillThread;
697 m_fillThread = 0;
698 deleteProcessingData();
699 } else {
700 m_condition.wakeAll();
701 }
702 }
703 }
704
705 FFTCache *
706 FFTDataServer::getCacheAux(size_t c)
707 {
708 Profiler profiler("FFTDataServer::getCacheAux", false);
709 #ifdef DEBUG_FFT_SERVER
710 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "])::getCacheAux" << std::endl;
711 #endif
712
713 MutexLocker locker(&m_writeMutex,
714 "FFTDataServer::m_writeMutex[getCacheAux]");
715
716 if (m_lastUsedCache == -1) {
717 m_fillThread->start();
718 }
719
720 if (int(c) != m_lastUsedCache) {
721
722 // std::cerr << "switch from " << m_lastUsedCache << " to " << c << std::endl;
723
724 for (IntQueue::iterator i = m_dormantCaches.begin();
725 i != m_dormantCaches.end(); ++i) {
726 if (*i == int(c)) {
727 m_dormantCaches.erase(i);
728 break;
729 }
730 }
731
732 if (m_lastUsedCache >= 0) {
733 bool inDormant = false;
734 for (size_t i = 0; i < m_dormantCaches.size(); ++i) {
735 if (m_dormantCaches[i] == m_lastUsedCache) {
736 inDormant = true;
737 break;
738 }
739 }
740 if (!inDormant) {
741 m_dormantCaches.push_back(m_lastUsedCache);
742 }
743 while (m_dormantCaches.size() > 4) {
744 int dc = m_dormantCaches.front();
745 m_dormantCaches.pop_front();
746 m_caches[dc]->suspend();
747 }
748 }
749 }
750
751 if (m_caches[c]) {
752 m_lastUsedCache = c;
753 return m_caches[c];
754 }
755
756 QString name = QString("%1-%2").arg(m_fileBaseName).arg(c);
757
758 FFTCache *cache = 0;
759
760 size_t width = m_cacheWidth;
761 if (c * m_cacheWidth + width > m_width) {
762 width = m_width - c * m_cacheWidth;
763 }
764
765 try {
766
767 if (m_memoryCache) {
768
769 cache = new FFTMemoryCache();
770
771 } else if (m_compactCache) {
772
773 cache = new FFTFileCache(name, MatrixFile::ReadWrite,
774 FFTFileCache::Compact);
775
776 } else {
777
778 cache = new FFTFileCache(name, MatrixFile::ReadWrite,
779 m_polar ? FFTFileCache::Polar :
780 FFTFileCache::Rectangular);
781 }
782
783 cache->resize(width, m_height);
784 cache->reset();
785
786 } catch (std::bad_alloc) {
787
788 delete cache;
789 cache = 0;
790
791 if (m_memoryCache) {
792
793 std::cerr << "WARNING: Memory allocation failed when resizing"
794 << " FFT memory cache no. " << c << " to " << width
795 << "x" << m_height << " (of total width " << m_width
796 << "): falling back to disc cache" << std::endl;
797
798 try {
799
800 cache = new FFTFileCache(name, MatrixFile::ReadWrite,
801 FFTFileCache::Compact);
802
803 cache->resize(width, m_height);
804 cache->reset();
805
806 } catch (std::bad_alloc) {
807
808 delete cache;
809 cache = 0;
810 }
811 }
812
813 if (cache) {
814 std::cerr << "ERROR: Memory allocation failed when resizing"
815 << " FFT file cache no. " << c << " to " << width
816 << "x" << m_height << " (of total width " << m_width
817 << "): abandoning this cache" << std::endl;
818 }
819
820 //!!! Shouldn't be using QtGui here. Need a better way to report this.
821 QMessageBox::critical
822 (0, QApplication::tr("FFT cache resize failed"),
823 QApplication::tr
824 ("Failed to create or resize an FFT model slice.\n"
825 "There may be insufficient memory or disc space to continue."));
826 }
827
828 StorageAdviser::notifyDoneAllocation
829 (m_memoryCache ? StorageAdviser::MemoryAllocation :
830 StorageAdviser::DiscAllocation,
831 width * m_height *
832 (m_compactCache ? sizeof(uint16_t) : sizeof(float)) / 1024 + 1);
833
834 m_caches[c] = cache;
835 m_lastUsedCache = c;
836 return cache;
837 }
838
839 float
840 FFTDataServer::getMagnitudeAt(size_t x, size_t y)
841 {
842 Profiler profiler("FFTDataServer::getMagnitudeAt", false);
843
844 if (x >= m_width || y >= m_height) return 0;
845
846 size_t col;
847 FFTCache *cache = getCache(x, col);
848 if (!cache) return 0;
849
850 if (!cache->haveSetColumnAt(col)) {
851 std::cerr << "FFTDataServer::getMagnitudeAt: calling fillColumn("
852 << x << ")" << std::endl;
853 fillColumn(x);
854 }
855 return cache->getMagnitudeAt(col, y);
856 }
857
858 float
859 FFTDataServer::getNormalizedMagnitudeAt(size_t x, size_t y)
860 {
861 Profiler profiler("FFTDataServer::getNormalizedMagnitudeAt", false);
862
863 if (x >= m_width || y >= m_height) return 0;
864
865 size_t col;
866 FFTCache *cache = getCache(x, col);
867 if (!cache) return 0;
868
869 if (!cache->haveSetColumnAt(col)) {
870 fillColumn(x);
871 }
872 return cache->getNormalizedMagnitudeAt(col, y);
873 }
874
875 float
876 FFTDataServer::getMaximumMagnitudeAt(size_t x)
877 {
878 Profiler profiler("FFTDataServer::getMaximumMagnitudeAt", false);
879
880 if (x >= m_width) return 0;
881
882 size_t col;
883 FFTCache *cache = getCache(x, col);
884 if (!cache) return 0;
885
886 if (!cache->haveSetColumnAt(col)) {
887 fillColumn(x);
888 }
889 return cache->getMaximumMagnitudeAt(col);
890 }
891
892 float
893 FFTDataServer::getPhaseAt(size_t x, size_t y)
894 {
895 Profiler profiler("FFTDataServer::getPhaseAt", false);
896
897 if (x >= m_width || y >= m_height) return 0;
898
899 size_t col;
900 FFTCache *cache = getCache(x, col);
901 if (!cache) return 0;
902
903 if (!cache->haveSetColumnAt(col)) {
904 fillColumn(x);
905 }
906 return cache->getPhaseAt(col, y);
907 }
908
909 void
910 FFTDataServer::getValuesAt(size_t x, size_t y, float &real, float &imaginary)
911 {
912 Profiler profiler("FFTDataServer::getValuesAt", false);
913
914 if (x >= m_width || y >= m_height) {
915 real = 0;
916 imaginary = 0;
917 return;
918 }
919
920 size_t col;
921 FFTCache *cache = getCache(x, col);
922
923 if (!cache) {
924 real = 0;
925 imaginary = 0;
926 return;
927 }
928
929 if (!cache->haveSetColumnAt(col)) {
930 #ifdef DEBUG_FFT_SERVER
931 std::cerr << "FFTDataServer::getValuesAt(" << x << ", " << y << "): filling" << std::endl;
932 #endif
933 fillColumn(x);
934 }
935 float magnitude = cache->getMagnitudeAt(col, y);
936 float phase = cache->getPhaseAt(col, y);
937 real = magnitude * cosf(phase);
938 imaginary = magnitude * sinf(phase);
939 }
940
941 bool
942 FFTDataServer::isColumnReady(size_t x)
943 {
944 Profiler profiler("FFTDataServer::isColumnReady", false);
945
946 if (x >= m_width) return true;
947
948 if (!haveCache(x)) {
949 if (m_lastUsedCache == -1) {
950 if (m_suspended) {
951 std::cerr << "FFTDataServer::isColumnReady(" << x << "): no cache, calling resume" << std::endl;
952 resume();
953 }
954 m_fillThread->start();
955 }
956 return false;
957 }
958
959 size_t col;
960 FFTCache *cache = getCache(x, col);
961 if (!cache) return true;
962
963 return cache->haveSetColumnAt(col);
964 }
965
966 void
967 FFTDataServer::fillColumn(size_t x)
968 {
969 Profiler profiler("FFTDataServer::fillColumn", false);
970
971 if (!m_fftInput) {
972 std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
973 << "input has already been completed and discarded?"
974 << std::endl;
975 return;
976 }
977
978 if (x >= m_width) {
979 std::cerr << "WARNING: FFTDataServer::fillColumn(" << x << "): "
980 << "x > width (" << x << " > " << m_width << ")"
981 << std::endl;
982 return;
983 }
984
985 size_t col;
986 #ifdef DEBUG_FFT_SERVER_FILL
987 std::cout << "FFTDataServer::fillColumn(" << x << ")" << std::endl;
988 #endif
989 FFTCache *cache = getCache(x, col);
990 if (!cache) return;
991
992 MutexLocker locker(&m_writeMutex,
993 "FFTDataServer::m_writeMutex[fillColumn]");
994
995 if (cache->haveSetColumnAt(col)) return;
996
997 int startFrame = m_windowIncrement * x;
998 int endFrame = startFrame + m_windowSize;
999
1000 startFrame -= int(m_windowSize - m_windowIncrement) / 2;
1001 endFrame -= int(m_windowSize - m_windowIncrement) / 2;
1002 size_t pfx = 0;
1003
1004 size_t off = (m_fftSize - m_windowSize) / 2;
1005
1006 for (size_t i = 0; i < off; ++i) {
1007 m_fftInput[i] = 0.0;
1008 m_fftInput[m_fftSize - i - 1] = 0.0;
1009 }
1010
1011 if (startFrame < 0) {
1012 pfx = size_t(-startFrame);
1013 for (size_t i = 0; i < pfx; ++i) {
1014 m_fftInput[off + i] = 0.0;
1015 }
1016 }
1017
1018 #ifdef DEBUG_FFT_SERVER_FILL
1019 std::cerr << "FFTDataServer::fillColumn: requesting frames "
1020 << startFrame + pfx << " -> " << endFrame << " ( = "
1021 << endFrame - (startFrame + pfx) << ") at index "
1022 << off + pfx << " in buffer of size " << m_fftSize
1023 << " with window size " << m_windowSize
1024 << " from channel " << m_channel << std::endl;
1025 #endif
1026
1027 size_t got = m_model->getValues(m_channel, startFrame + pfx,
1028 endFrame, m_fftInput + off + pfx);
1029
1030 while (got + pfx < m_windowSize) {
1031 m_fftInput[off + got + pfx] = 0.0;
1032 ++got;
1033 }
1034
1035 if (m_channel == -1) {
1036 int channels = m_model->getChannelCount();
1037 if (channels > 1) {
1038 for (size_t i = 0; i < m_windowSize; ++i) {
1039 m_fftInput[off + i] /= channels;
1040 }
1041 }
1042 }
1043
1044 m_windower.cut(m_fftInput + off);
1045
1046 for (size_t i = 0; i < m_fftSize/2; ++i) {
1047 fftsample temp = m_fftInput[i];
1048 m_fftInput[i] = m_fftInput[i + m_fftSize/2];
1049 m_fftInput[i + m_fftSize/2] = temp;
1050 }
1051
1052 fftf_execute(m_fftPlan);
1053
1054 fftsample factor = 0.0;
1055
1056 for (size_t i = 0; i <= m_fftSize/2; ++i) {
1057
1058 fftsample mag = sqrtf(m_fftOutput[i][0] * m_fftOutput[i][0] +
1059 m_fftOutput[i][1] * m_fftOutput[i][1]);
1060 mag /= m_windowSize / 2;
1061
1062 if (mag > factor) factor = mag;
1063
1064 fftsample phase = atan2f(m_fftOutput[i][1], m_fftOutput[i][0]);
1065 phase = princargf(phase);
1066
1067 m_workbuffer[i] = mag;
1068 m_workbuffer[i + m_fftSize/2+1] = phase;
1069 }
1070
1071 cache->setColumnAt(col,
1072 m_workbuffer,
1073 m_workbuffer + m_fftSize/2+1,
1074 factor);
1075
1076 if (m_suspended) {
1077 // std::cerr << "FFTDataServer::fillColumn(" << x << "): calling resume" << std::endl;
1078 // resume();
1079 }
1080 }
1081
1082 size_t
1083 FFTDataServer::getFillCompletion() const
1084 {
1085 if (m_fillThread) return m_fillThread->getCompletion();
1086 else return 100;
1087 }
1088
1089 size_t
1090 FFTDataServer::getFillExtent() const
1091 {
1092 if (m_fillThread) return m_fillThread->getExtent();
1093 else return m_model->getEndFrame();
1094 }
1095
1096 QString
1097 FFTDataServer::generateFileBasename() const
1098 {
1099 return generateFileBasename(m_model, m_channel, m_windower.getType(),
1100 m_windowSize, m_windowIncrement, m_fftSize,
1101 m_polar);
1102 }
1103
1104 QString
1105 FFTDataServer::generateFileBasename(const DenseTimeValueModel *model,
1106 int channel,
1107 WindowType windowType,
1108 size_t windowSize,
1109 size_t windowIncrement,
1110 size_t fftSize,
1111 bool polar)
1112 {
1113 char buffer[200];
1114
1115 sprintf(buffer, "%u-%u-%u-%u-%u-%u%s",
1116 (unsigned int)XmlExportable::getObjectExportId(model),
1117 (unsigned int)(channel + 1),
1118 (unsigned int)windowType,
1119 (unsigned int)windowSize,
1120 (unsigned int)windowIncrement,
1121 (unsigned int)fftSize,
1122 polar ? "-p" : "-r");
1123
1124 return buffer;
1125 }
1126
1127 void
1128 FFTDataServer::FillThread::run()
1129 {
1130 m_extent = 0;
1131 m_completion = 0;
1132
1133 size_t start = m_server.m_model->getStartFrame();
1134 size_t end = m_server.m_model->getEndFrame();
1135 size_t remainingEnd = end;
1136
1137 int counter = 0;
1138 int updateAt = 1;
1139 int maxUpdateAt = (end / m_server.m_windowIncrement) / 20;
1140 if (maxUpdateAt < 100) maxUpdateAt = 100;
1141
1142 if (m_fillFrom > start) {
1143
1144 for (size_t f = m_fillFrom; f < end; f += m_server.m_windowIncrement) {
1145
1146 m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
1147
1148 if (m_server.m_exiting) return;
1149
1150 while (m_server.m_suspended) {
1151 #ifdef DEBUG_FFT_SERVER
1152 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl;
1153 #endif
1154 {
1155 MutexLocker locker(&m_server.m_writeMutex,
1156 "FFTDataServer::m_writeMutex[run/1]");
1157 m_server.m_condition.wait(&m_server.m_writeMutex, 10000);
1158 }
1159 #ifdef DEBUG_FFT_SERVER
1160 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): waited" << std::endl;
1161 #endif
1162 if (m_server.m_exiting) return;
1163 }
1164
1165 if (++counter == updateAt) {
1166 m_extent = f;
1167 m_completion = size_t(100 * fabsf(float(f - m_fillFrom) /
1168 float(end - start)));
1169 counter = 0;
1170 if (updateAt < maxUpdateAt) {
1171 updateAt *= 2;
1172 if (updateAt > maxUpdateAt) updateAt = maxUpdateAt;
1173 }
1174 }
1175 }
1176
1177 remainingEnd = m_fillFrom;
1178 if (remainingEnd > start) --remainingEnd;
1179 else remainingEnd = start;
1180 }
1181
1182 size_t baseCompletion = m_completion;
1183
1184 for (size_t f = start; f < remainingEnd; f += m_server.m_windowIncrement) {
1185
1186 m_server.fillColumn(int((f - start) / m_server.m_windowIncrement));
1187
1188 if (m_server.m_exiting) return;
1189
1190 while (m_server.m_suspended) {
1191 #ifdef DEBUG_FFT_SERVER
1192 std::cerr << "FFTDataServer(" << this << " [" << (void *)QThread::currentThreadId() << "]): suspended, waiting..." << std::endl;
1193 #endif
1194 {
1195 MutexLocker locker(&m_server.m_writeMutex,
1196 "FFTDataServer::m_writeMutex[run/2]");
1197 m_server.m_condition.wait(&m_server.m_writeMutex, 10000);
1198 }
1199 if (m_server.m_exiting) return;
1200 }
1201
1202 if (++counter == updateAt) {
1203 m_extent = f;
1204 m_completion = baseCompletion +
1205 size_t(100 * fabsf(float(f - start) /
1206 float(end - start)));
1207 counter = 0;
1208 if (updateAt < maxUpdateAt) {
1209 updateAt *= 2;
1210 if (updateAt > maxUpdateAt) updateAt = maxUpdateAt;
1211 }
1212 }
1213 }
1214
1215 m_completion = 100;
1216 m_extent = end;
1217 }
1218