Mercurial > hg > easaier-soundaccess
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 |