comparison data/fft/FFTDataServer.cpp @ 148:1a42221a1522

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