Mercurial > hg > easaier-soundaccess
comparison layer/SpectrogramLayer.cpp @ 0:fc9323a41f5a
start base : Sonic Visualiser sv1-1.0rc1
author | lbajardsilogic |
---|---|
date | Fri, 11 May 2007 09:08:14 +0000 |
parents | |
children | d8e6709e9075 |
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 "SpectrogramLayer.h" | |
17 | |
18 #include "view/View.h" | |
19 #include "base/Profiler.h" | |
20 #include "base/AudioLevel.h" | |
21 #include "base/Window.h" | |
22 #include "base/Pitch.h" | |
23 #include "base/Preferences.h" | |
24 #include "base/RangeMapper.h" | |
25 #include "ColourMapper.h" | |
26 | |
27 #include <QPainter> | |
28 #include <QImage> | |
29 #include <QPixmap> | |
30 #include <QRect> | |
31 #include <QTimer> | |
32 #include <QApplication> | |
33 #include <QMessageBox> | |
34 | |
35 #include <iostream> | |
36 | |
37 #include <cassert> | |
38 #include <cmath> | |
39 | |
40 //#define DEBUG_SPECTROGRAM_REPAINT 1 | |
41 | |
42 SpectrogramLayer::SpectrogramLayer(Configuration config) : | |
43 m_model(0), | |
44 m_channel(0), | |
45 m_windowSize(1024), | |
46 m_windowType(HanningWindow), | |
47 m_windowHopLevel(2), | |
48 m_zeroPadLevel(0), | |
49 m_fftSize(1024), | |
50 m_gain(1.0), | |
51 m_initialGain(1.0), | |
52 m_threshold(0.0), | |
53 m_initialThreshold(0.0), | |
54 m_colourRotation(0), | |
55 m_initialRotation(0), | |
56 m_minFrequency(10), | |
57 m_maxFrequency(8000), | |
58 m_initialMaxFrequency(8000), | |
59 m_colourScale(dBColourScale), | |
60 m_colourMap(0), | |
61 m_frequencyScale(LinearFrequencyScale), | |
62 m_binDisplay(AllBins), | |
63 m_normalizeColumns(false), | |
64 m_normalizeVisibleArea(false), | |
65 m_lastEmittedZoomStep(-1), | |
66 m_lastPaintBlockWidth(0), | |
67 m_updateTimer(0), | |
68 m_candidateFillStartFrame(0), | |
69 m_exiting(false), | |
70 m_sliceableModel(0) | |
71 { | |
72 if (config == FullRangeDb) { | |
73 m_initialMaxFrequency = 0; | |
74 setMaxFrequency(0); | |
75 } else if (config == MelodicRange) { | |
76 setWindowSize(8192); | |
77 setWindowHopLevel(4); | |
78 m_initialMaxFrequency = 1500; | |
79 setMaxFrequency(1500); | |
80 setMinFrequency(40); | |
81 setColourScale(LinearColourScale); | |
82 setColourMap(ColourMapper::Sunset); | |
83 setFrequencyScale(LogFrequencyScale); | |
84 // setGain(20); | |
85 } else if (config == MelodicPeaks) { | |
86 setWindowSize(4096); | |
87 setWindowHopLevel(5); | |
88 m_initialMaxFrequency = 2000; | |
89 setMaxFrequency(2000); | |
90 setMinFrequency(40); | |
91 setFrequencyScale(LogFrequencyScale); | |
92 setColourScale(LinearColourScale); | |
93 setBinDisplay(PeakFrequencies); | |
94 setNormalizeColumns(true); | |
95 } | |
96 | |
97 Preferences *prefs = Preferences::getInstance(); | |
98 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), | |
99 this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); | |
100 setWindowType(prefs->getWindowType()); | |
101 | |
102 initialisePalette(); | |
103 } | |
104 | |
105 SpectrogramLayer::~SpectrogramLayer() | |
106 { | |
107 delete m_updateTimer; | |
108 m_updateTimer = 0; | |
109 | |
110 invalidateFFTModels(); | |
111 } | |
112 | |
113 void | |
114 SpectrogramLayer::setModel(const DenseTimeValueModel *model) | |
115 { | |
116 // std::cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << std::endl; | |
117 | |
118 if (model == m_model) return; | |
119 | |
120 m_model = model; | |
121 invalidateFFTModels(); | |
122 | |
123 if (!m_model || !m_model->isOK()) return; | |
124 | |
125 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); | |
126 connect(m_model, SIGNAL(modelChanged(size_t, size_t)), | |
127 this, SIGNAL(modelChanged(size_t, size_t))); | |
128 | |
129 connect(m_model, SIGNAL(completionChanged()), | |
130 this, SIGNAL(modelCompletionChanged())); | |
131 | |
132 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); | |
133 connect(m_model, SIGNAL(modelChanged(size_t, size_t)), | |
134 this, SLOT(cacheInvalid(size_t, size_t))); | |
135 | |
136 emit modelReplaced(); | |
137 } | |
138 | |
139 Layer::PropertyList | |
140 SpectrogramLayer::getProperties() const | |
141 { | |
142 PropertyList list; | |
143 list.push_back("Colour"); | |
144 list.push_back("Colour Scale"); | |
145 list.push_back("Window Size"); | |
146 list.push_back("Window Increment"); | |
147 list.push_back("Normalize Columns"); | |
148 list.push_back("Normalize Visible Area"); | |
149 list.push_back("Bin Display"); | |
150 list.push_back("Threshold"); | |
151 list.push_back("Gain"); | |
152 list.push_back("Colour Rotation"); | |
153 // list.push_back("Min Frequency"); | |
154 // list.push_back("Max Frequency"); | |
155 list.push_back("Frequency Scale"); | |
156 //// list.push_back("Zero Padding"); | |
157 return list; | |
158 } | |
159 | |
160 QString | |
161 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const | |
162 { | |
163 if (name == "Colour") return tr("Colour"); | |
164 if (name == "Colour Scale") return tr("Colour Scale"); | |
165 if (name == "Window Size") return tr("Window Size"); | |
166 if (name == "Window Increment") return tr("Window Overlap"); | |
167 if (name == "Normalize Columns") return tr("Normalize Columns"); | |
168 if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); | |
169 if (name == "Bin Display") return tr("Bin Display"); | |
170 if (name == "Threshold") return tr("Threshold"); | |
171 if (name == "Gain") return tr("Gain"); | |
172 if (name == "Colour Rotation") return tr("Colour Rotation"); | |
173 if (name == "Min Frequency") return tr("Min Frequency"); | |
174 if (name == "Max Frequency") return tr("Max Frequency"); | |
175 if (name == "Frequency Scale") return tr("Frequency Scale"); | |
176 if (name == "Zero Padding") return tr("Smoothing"); | |
177 return ""; | |
178 } | |
179 | |
180 Layer::PropertyType | |
181 SpectrogramLayer::getPropertyType(const PropertyName &name) const | |
182 { | |
183 if (name == "Gain") return RangeProperty; | |
184 if (name == "Colour Rotation") return RangeProperty; | |
185 if (name == "Normalize Columns") return ToggleProperty; | |
186 if (name == "Normalize Visible Area") return ToggleProperty; | |
187 if (name == "Threshold") return RangeProperty; | |
188 if (name == "Zero Padding") return ToggleProperty; | |
189 return ValueProperty; | |
190 } | |
191 | |
192 QString | |
193 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const | |
194 { | |
195 if (name == "Bin Display" || | |
196 name == "Frequency Scale") return tr("Bins"); | |
197 if (name == "Window Size" || | |
198 name == "Window Increment" || | |
199 name == "Zero Padding") return tr("Window"); | |
200 if (name == "Colour" || | |
201 name == "Threshold" || | |
202 name == "Colour Rotation") return tr("Colour"); | |
203 if (name == "Normalize Columns" || | |
204 name == "Normalize Visible Area" || | |
205 name == "Gain" || | |
206 name == "Colour Scale") return tr("Scale"); | |
207 return QString(); | |
208 } | |
209 | |
210 int | |
211 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, | |
212 int *min, int *max, int *deflt) const | |
213 { | |
214 int val = 0; | |
215 | |
216 int garbage0, garbage1, garbage2; | |
217 if (!min) min = &garbage0; | |
218 if (!max) max = &garbage1; | |
219 if (!deflt) deflt = &garbage2; | |
220 | |
221 if (name == "Gain") { | |
222 | |
223 *min = -50; | |
224 *max = 50; | |
225 | |
226 *deflt = lrintf(log10(m_initialGain) * 20.0);; | |
227 if (*deflt < *min) *deflt = *min; | |
228 if (*deflt > *max) *deflt = *max; | |
229 | |
230 val = lrintf(log10(m_gain) * 20.0); | |
231 if (val < *min) val = *min; | |
232 if (val > *max) val = *max; | |
233 | |
234 } else if (name == "Threshold") { | |
235 | |
236 *min = -50; | |
237 *max = 0; | |
238 | |
239 *deflt = lrintf(AudioLevel::multiplier_to_dB(m_initialThreshold)); | |
240 if (*deflt < *min) *deflt = *min; | |
241 if (*deflt > *max) *deflt = *max; | |
242 | |
243 val = lrintf(AudioLevel::multiplier_to_dB(m_threshold)); | |
244 if (val < *min) val = *min; | |
245 if (val > *max) val = *max; | |
246 | |
247 } else if (name == "Colour Rotation") { | |
248 | |
249 *min = 0; | |
250 *max = 256; | |
251 *deflt = m_initialRotation; | |
252 | |
253 val = m_colourRotation; | |
254 | |
255 } else if (name == "Colour Scale") { | |
256 | |
257 *min = 0; | |
258 *max = 4; | |
259 *deflt = int(dBColourScale); | |
260 | |
261 val = (int)m_colourScale; | |
262 | |
263 } else if (name == "Colour") { | |
264 | |
265 *min = 0; | |
266 *max = ColourMapper::getColourMapCount() - 1; | |
267 *deflt = 0; | |
268 | |
269 val = m_colourMap; | |
270 | |
271 } else if (name == "Window Size") { | |
272 | |
273 *min = 0; | |
274 *max = 10; | |
275 *deflt = 5; | |
276 | |
277 val = 0; | |
278 int ws = m_windowSize; | |
279 while (ws > 32) { ws >>= 1; val ++; } | |
280 | |
281 } else if (name == "Window Increment") { | |
282 | |
283 *min = 0; | |
284 *max = 5; | |
285 *deflt = 2; | |
286 | |
287 val = m_windowHopLevel; | |
288 | |
289 } else if (name == "Zero Padding") { | |
290 | |
291 *min = 0; | |
292 *max = 1; | |
293 *deflt = 0; | |
294 | |
295 val = m_zeroPadLevel > 0 ? 1 : 0; | |
296 | |
297 } else if (name == "Min Frequency") { | |
298 | |
299 *min = 0; | |
300 *max = 9; | |
301 *deflt = 1; | |
302 | |
303 switch (m_minFrequency) { | |
304 case 0: default: val = 0; break; | |
305 case 10: val = 1; break; | |
306 case 20: val = 2; break; | |
307 case 40: val = 3; break; | |
308 case 100: val = 4; break; | |
309 case 250: val = 5; break; | |
310 case 500: val = 6; break; | |
311 case 1000: val = 7; break; | |
312 case 4000: val = 8; break; | |
313 case 10000: val = 9; break; | |
314 } | |
315 | |
316 } else if (name == "Max Frequency") { | |
317 | |
318 *min = 0; | |
319 *max = 9; | |
320 *deflt = 6; | |
321 | |
322 switch (m_maxFrequency) { | |
323 case 500: val = 0; break; | |
324 case 1000: val = 1; break; | |
325 case 1500: val = 2; break; | |
326 case 2000: val = 3; break; | |
327 case 4000: val = 4; break; | |
328 case 6000: val = 5; break; | |
329 case 8000: val = 6; break; | |
330 case 12000: val = 7; break; | |
331 case 16000: val = 8; break; | |
332 default: val = 9; break; | |
333 } | |
334 | |
335 } else if (name == "Frequency Scale") { | |
336 | |
337 *min = 0; | |
338 *max = 1; | |
339 *deflt = int(LinearFrequencyScale); | |
340 val = (int)m_frequencyScale; | |
341 | |
342 } else if (name == "Bin Display") { | |
343 | |
344 *min = 0; | |
345 *max = 2; | |
346 *deflt = int(AllBins); | |
347 val = (int)m_binDisplay; | |
348 | |
349 } else if (name == "Normalize Columns") { | |
350 | |
351 *deflt = 0; | |
352 val = (m_normalizeColumns ? 1 : 0); | |
353 | |
354 } else if (name == "Normalize Visible Area") { | |
355 | |
356 *deflt = 0; | |
357 val = (m_normalizeVisibleArea ? 1 : 0); | |
358 | |
359 } else { | |
360 val = Layer::getPropertyRangeAndValue(name, min, max, deflt); | |
361 } | |
362 | |
363 return val; | |
364 } | |
365 | |
366 QString | |
367 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name, | |
368 int value) const | |
369 { | |
370 if (name == "Colour") { | |
371 return ColourMapper::getColourMapName(value); | |
372 } | |
373 if (name == "Colour Scale") { | |
374 switch (value) { | |
375 default: | |
376 case 0: return tr("Linear"); | |
377 case 1: return tr("Meter"); | |
378 case 2: return tr("dBV^2"); | |
379 case 3: return tr("dBV"); | |
380 case 4: return tr("Phase"); | |
381 } | |
382 } | |
383 if (name == "Window Size") { | |
384 return QString("%1").arg(32 << value); | |
385 } | |
386 if (name == "Window Increment") { | |
387 switch (value) { | |
388 default: | |
389 case 0: return tr("None"); | |
390 case 1: return tr("25 %"); | |
391 case 2: return tr("50 %"); | |
392 case 3: return tr("75 %"); | |
393 case 4: return tr("87.5 %"); | |
394 case 5: return tr("93.75 %"); | |
395 } | |
396 } | |
397 if (name == "Zero Padding") { | |
398 if (value == 0) return tr("None"); | |
399 return QString("%1x").arg(value + 1); | |
400 } | |
401 if (name == "Min Frequency") { | |
402 switch (value) { | |
403 default: | |
404 case 0: return tr("No min"); | |
405 case 1: return tr("10 Hz"); | |
406 case 2: return tr("20 Hz"); | |
407 case 3: return tr("40 Hz"); | |
408 case 4: return tr("100 Hz"); | |
409 case 5: return tr("250 Hz"); | |
410 case 6: return tr("500 Hz"); | |
411 case 7: return tr("1 KHz"); | |
412 case 8: return tr("4 KHz"); | |
413 case 9: return tr("10 KHz"); | |
414 } | |
415 } | |
416 if (name == "Max Frequency") { | |
417 switch (value) { | |
418 default: | |
419 case 0: return tr("500 Hz"); | |
420 case 1: return tr("1 KHz"); | |
421 case 2: return tr("1.5 KHz"); | |
422 case 3: return tr("2 KHz"); | |
423 case 4: return tr("4 KHz"); | |
424 case 5: return tr("6 KHz"); | |
425 case 6: return tr("8 KHz"); | |
426 case 7: return tr("12 KHz"); | |
427 case 8: return tr("16 KHz"); | |
428 case 9: return tr("No max"); | |
429 } | |
430 } | |
431 if (name == "Frequency Scale") { | |
432 switch (value) { | |
433 default: | |
434 case 0: return tr("Linear"); | |
435 case 1: return tr("Log"); | |
436 } | |
437 } | |
438 if (name == "Bin Display") { | |
439 switch (value) { | |
440 default: | |
441 case 0: return tr("All Bins"); | |
442 case 1: return tr("Peak Bins"); | |
443 case 2: return tr("Frequencies"); | |
444 } | |
445 } | |
446 return tr("<unknown>"); | |
447 } | |
448 | |
449 RangeMapper * | |
450 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const | |
451 { | |
452 if (name == "Gain") { | |
453 return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); | |
454 } | |
455 if (name == "Threshold") { | |
456 return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); | |
457 } | |
458 return 0; | |
459 } | |
460 | |
461 void | |
462 SpectrogramLayer::setProperty(const PropertyName &name, int value) | |
463 { | |
464 if (name == "Gain") { | |
465 setGain(pow(10, float(value)/20.0)); | |
466 } else if (name == "Threshold") { | |
467 if (value == -50) setThreshold(0.0); | |
468 else setThreshold(AudioLevel::dB_to_multiplier(value)); | |
469 } else if (name == "Colour Rotation") { | |
470 setColourRotation(value); | |
471 } else if (name == "Colour") { | |
472 setColourMap(value); | |
473 } else if (name == "Window Size") { | |
474 setWindowSize(32 << value); | |
475 } else if (name == "Window Increment") { | |
476 setWindowHopLevel(value); | |
477 } else if (name == "Zero Padding") { | |
478 setZeroPadLevel(value > 0.1 ? 3 : 0); | |
479 } else if (name == "Min Frequency") { | |
480 switch (value) { | |
481 default: | |
482 case 0: setMinFrequency(0); break; | |
483 case 1: setMinFrequency(10); break; | |
484 case 2: setMinFrequency(20); break; | |
485 case 3: setMinFrequency(40); break; | |
486 case 4: setMinFrequency(100); break; | |
487 case 5: setMinFrequency(250); break; | |
488 case 6: setMinFrequency(500); break; | |
489 case 7: setMinFrequency(1000); break; | |
490 case 8: setMinFrequency(4000); break; | |
491 case 9: setMinFrequency(10000); break; | |
492 } | |
493 int vs = getCurrentVerticalZoomStep(); | |
494 if (vs != m_lastEmittedZoomStep) { | |
495 emit verticalZoomChanged(); | |
496 m_lastEmittedZoomStep = vs; | |
497 } | |
498 } else if (name == "Max Frequency") { | |
499 switch (value) { | |
500 case 0: setMaxFrequency(500); break; | |
501 case 1: setMaxFrequency(1000); break; | |
502 case 2: setMaxFrequency(1500); break; | |
503 case 3: setMaxFrequency(2000); break; | |
504 case 4: setMaxFrequency(4000); break; | |
505 case 5: setMaxFrequency(6000); break; | |
506 case 6: setMaxFrequency(8000); break; | |
507 case 7: setMaxFrequency(12000); break; | |
508 case 8: setMaxFrequency(16000); break; | |
509 default: | |
510 case 9: setMaxFrequency(0); break; | |
511 } | |
512 int vs = getCurrentVerticalZoomStep(); | |
513 if (vs != m_lastEmittedZoomStep) { | |
514 emit verticalZoomChanged(); | |
515 m_lastEmittedZoomStep = vs; | |
516 } | |
517 } else if (name == "Colour Scale") { | |
518 switch (value) { | |
519 default: | |
520 case 0: setColourScale(LinearColourScale); break; | |
521 case 1: setColourScale(MeterColourScale); break; | |
522 case 2: setColourScale(dBSquaredColourScale); break; | |
523 case 3: setColourScale(dBColourScale); break; | |
524 case 4: setColourScale(PhaseColourScale); break; | |
525 } | |
526 } else if (name == "Frequency Scale") { | |
527 switch (value) { | |
528 default: | |
529 case 0: setFrequencyScale(LinearFrequencyScale); break; | |
530 case 1: setFrequencyScale(LogFrequencyScale); break; | |
531 } | |
532 } else if (name == "Bin Display") { | |
533 switch (value) { | |
534 default: | |
535 case 0: setBinDisplay(AllBins); break; | |
536 case 1: setBinDisplay(PeakBins); break; | |
537 case 2: setBinDisplay(PeakFrequencies); break; | |
538 } | |
539 } else if (name == "Normalize Columns") { | |
540 setNormalizeColumns(value ? true : false); | |
541 } else if (name == "Normalize Visible Area") { | |
542 setNormalizeVisibleArea(value ? true : false); | |
543 } | |
544 } | |
545 | |
546 void | |
547 SpectrogramLayer::invalidatePixmapCaches() | |
548 { | |
549 for (ViewPixmapCache::iterator i = m_pixmapCaches.begin(); | |
550 i != m_pixmapCaches.end(); ++i) { | |
551 i->second.validArea = QRect(); | |
552 } | |
553 } | |
554 | |
555 void | |
556 SpectrogramLayer::invalidatePixmapCaches(size_t startFrame, size_t endFrame) | |
557 { | |
558 for (ViewPixmapCache::iterator i = m_pixmapCaches.begin(); | |
559 i != m_pixmapCaches.end(); ++i) { | |
560 | |
561 //!!! when are views removed from the map? on setLayerDormant? | |
562 const View *v = i->first; | |
563 | |
564 if (startFrame < v->getEndFrame() && int(endFrame) >= v->getStartFrame()) { | |
565 i->second.validArea = QRect(); | |
566 } | |
567 } | |
568 } | |
569 | |
570 void | |
571 SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name) | |
572 { | |
573 std::cerr << "SpectrogramLayer::preferenceChanged(" << name.toStdString() << ")" << std::endl; | |
574 | |
575 if (name == "Window Type") { | |
576 setWindowType(Preferences::getInstance()->getWindowType()); | |
577 return; | |
578 } | |
579 if (name == "Spectrogram Smoothing") { | |
580 invalidatePixmapCaches(); | |
581 invalidateMagnitudes(); | |
582 emit layerParametersChanged(); | |
583 } | |
584 if (name == "Tuning Frequency") { | |
585 emit layerParametersChanged(); | |
586 } | |
587 } | |
588 | |
589 void | |
590 SpectrogramLayer::setChannel(int ch) | |
591 { | |
592 if (m_channel == ch) return; | |
593 | |
594 invalidatePixmapCaches(); | |
595 m_channel = ch; | |
596 invalidateFFTModels(); | |
597 | |
598 emit layerParametersChanged(); | |
599 } | |
600 | |
601 int | |
602 SpectrogramLayer::getChannel() const | |
603 { | |
604 return m_channel; | |
605 } | |
606 | |
607 void | |
608 SpectrogramLayer::setWindowSize(size_t ws) | |
609 { | |
610 if (m_windowSize == ws) return; | |
611 | |
612 invalidatePixmapCaches(); | |
613 | |
614 m_windowSize = ws; | |
615 m_fftSize = ws * (m_zeroPadLevel + 1); | |
616 | |
617 invalidateFFTModels(); | |
618 | |
619 emit layerParametersChanged(); | |
620 } | |
621 | |
622 size_t | |
623 SpectrogramLayer::getWindowSize() const | |
624 { | |
625 return m_windowSize; | |
626 } | |
627 | |
628 void | |
629 SpectrogramLayer::setWindowHopLevel(size_t v) | |
630 { | |
631 if (m_windowHopLevel == v) return; | |
632 | |
633 invalidatePixmapCaches(); | |
634 | |
635 m_windowHopLevel = v; | |
636 | |
637 invalidateFFTModels(); | |
638 | |
639 emit layerParametersChanged(); | |
640 | |
641 // fillCache(); | |
642 } | |
643 | |
644 size_t | |
645 SpectrogramLayer::getWindowHopLevel() const | |
646 { | |
647 return m_windowHopLevel; | |
648 } | |
649 | |
650 void | |
651 SpectrogramLayer::setZeroPadLevel(size_t v) | |
652 { | |
653 if (m_zeroPadLevel == v) return; | |
654 | |
655 invalidatePixmapCaches(); | |
656 | |
657 m_zeroPadLevel = v; | |
658 m_fftSize = m_windowSize * (v + 1); | |
659 | |
660 invalidateFFTModels(); | |
661 | |
662 emit layerParametersChanged(); | |
663 } | |
664 | |
665 size_t | |
666 SpectrogramLayer::getZeroPadLevel() const | |
667 { | |
668 return m_zeroPadLevel; | |
669 } | |
670 | |
671 void | |
672 SpectrogramLayer::setWindowType(WindowType w) | |
673 { | |
674 if (m_windowType == w) return; | |
675 | |
676 invalidatePixmapCaches(); | |
677 | |
678 m_windowType = w; | |
679 | |
680 invalidateFFTModels(); | |
681 | |
682 emit layerParametersChanged(); | |
683 } | |
684 | |
685 WindowType | |
686 SpectrogramLayer::getWindowType() const | |
687 { | |
688 return m_windowType; | |
689 } | |
690 | |
691 void | |
692 SpectrogramLayer::setGain(float gain) | |
693 { | |
694 // std::cerr << "SpectrogramLayer::setGain(" << gain << ") (my gain is now " | |
695 // << m_gain << ")" << std::endl; | |
696 | |
697 if (m_gain == gain) return; | |
698 | |
699 invalidatePixmapCaches(); | |
700 | |
701 m_gain = gain; | |
702 | |
703 emit layerParametersChanged(); | |
704 } | |
705 | |
706 float | |
707 SpectrogramLayer::getGain() const | |
708 { | |
709 return m_gain; | |
710 } | |
711 | |
712 void | |
713 SpectrogramLayer::setThreshold(float threshold) | |
714 { | |
715 if (m_threshold == threshold) return; | |
716 | |
717 invalidatePixmapCaches(); | |
718 | |
719 m_threshold = threshold; | |
720 | |
721 emit layerParametersChanged(); | |
722 } | |
723 | |
724 float | |
725 SpectrogramLayer::getThreshold() const | |
726 { | |
727 return m_threshold; | |
728 } | |
729 | |
730 void | |
731 SpectrogramLayer::setMinFrequency(size_t mf) | |
732 { | |
733 if (m_minFrequency == mf) return; | |
734 | |
735 // std::cerr << "SpectrogramLayer::setMinFrequency: " << mf << std::endl; | |
736 | |
737 invalidatePixmapCaches(); | |
738 invalidateMagnitudes(); | |
739 | |
740 m_minFrequency = mf; | |
741 | |
742 emit layerParametersChanged(); | |
743 } | |
744 | |
745 size_t | |
746 SpectrogramLayer::getMinFrequency() const | |
747 { | |
748 return m_minFrequency; | |
749 } | |
750 | |
751 void | |
752 SpectrogramLayer::setMaxFrequency(size_t mf) | |
753 { | |
754 if (m_maxFrequency == mf) return; | |
755 | |
756 // std::cerr << "SpectrogramLayer::setMaxFrequency: " << mf << std::endl; | |
757 | |
758 invalidatePixmapCaches(); | |
759 invalidateMagnitudes(); | |
760 | |
761 m_maxFrequency = mf; | |
762 | |
763 emit layerParametersChanged(); | |
764 } | |
765 | |
766 size_t | |
767 SpectrogramLayer::getMaxFrequency() const | |
768 { | |
769 return m_maxFrequency; | |
770 } | |
771 | |
772 void | |
773 SpectrogramLayer::setColourRotation(int r) | |
774 { | |
775 invalidatePixmapCaches(); | |
776 | |
777 if (r < 0) r = 0; | |
778 if (r > 256) r = 256; | |
779 int distance = r - m_colourRotation; | |
780 | |
781 if (distance != 0) { | |
782 rotatePalette(-distance); | |
783 m_colourRotation = r; | |
784 } | |
785 | |
786 emit layerParametersChanged(); | |
787 } | |
788 | |
789 void | |
790 SpectrogramLayer::setColourScale(ColourScale colourScale) | |
791 { | |
792 if (m_colourScale == colourScale) return; | |
793 | |
794 invalidatePixmapCaches(); | |
795 | |
796 m_colourScale = colourScale; | |
797 | |
798 emit layerParametersChanged(); | |
799 } | |
800 | |
801 SpectrogramLayer::ColourScale | |
802 SpectrogramLayer::getColourScale() const | |
803 { | |
804 return m_colourScale; | |
805 } | |
806 | |
807 void | |
808 SpectrogramLayer::setColourMap(int map) | |
809 { | |
810 if (m_colourMap == map) return; | |
811 | |
812 invalidatePixmapCaches(); | |
813 | |
814 m_colourMap = map; | |
815 initialisePalette(); | |
816 | |
817 emit layerParametersChanged(); | |
818 } | |
819 | |
820 int | |
821 SpectrogramLayer::getColourMap() const | |
822 { | |
823 return m_colourMap; | |
824 } | |
825 | |
826 void | |
827 SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) | |
828 { | |
829 if (m_frequencyScale == frequencyScale) return; | |
830 | |
831 invalidatePixmapCaches(); | |
832 m_frequencyScale = frequencyScale; | |
833 | |
834 emit layerParametersChanged(); | |
835 } | |
836 | |
837 SpectrogramLayer::FrequencyScale | |
838 SpectrogramLayer::getFrequencyScale() const | |
839 { | |
840 return m_frequencyScale; | |
841 } | |
842 | |
843 void | |
844 SpectrogramLayer::setBinDisplay(BinDisplay binDisplay) | |
845 { | |
846 if (m_binDisplay == binDisplay) return; | |
847 | |
848 invalidatePixmapCaches(); | |
849 m_binDisplay = binDisplay; | |
850 | |
851 emit layerParametersChanged(); | |
852 } | |
853 | |
854 SpectrogramLayer::BinDisplay | |
855 SpectrogramLayer::getBinDisplay() const | |
856 { | |
857 return m_binDisplay; | |
858 } | |
859 | |
860 void | |
861 SpectrogramLayer::setNormalizeColumns(bool n) | |
862 { | |
863 if (m_normalizeColumns == n) return; | |
864 | |
865 invalidatePixmapCaches(); | |
866 invalidateMagnitudes(); | |
867 m_normalizeColumns = n; | |
868 | |
869 emit layerParametersChanged(); | |
870 } | |
871 | |
872 bool | |
873 SpectrogramLayer::getNormalizeColumns() const | |
874 { | |
875 return m_normalizeColumns; | |
876 } | |
877 | |
878 void | |
879 SpectrogramLayer::setNormalizeVisibleArea(bool n) | |
880 { | |
881 if (m_normalizeVisibleArea == n) return; | |
882 | |
883 invalidatePixmapCaches(); | |
884 invalidateMagnitudes(); | |
885 m_normalizeVisibleArea = n; | |
886 | |
887 emit layerParametersChanged(); | |
888 } | |
889 | |
890 bool | |
891 SpectrogramLayer::getNormalizeVisibleArea() const | |
892 { | |
893 return m_normalizeVisibleArea; | |
894 } | |
895 | |
896 void | |
897 SpectrogramLayer::setLayerDormant(const View *v, bool dormant) | |
898 { | |
899 if (dormant) { | |
900 | |
901 if (isLayerDormant(v)) { | |
902 return; | |
903 } | |
904 | |
905 Layer::setLayerDormant(v, true); | |
906 | |
907 invalidatePixmapCaches(); | |
908 m_pixmapCaches.erase(v); | |
909 | |
910 if (m_fftModels.find(v) != m_fftModels.end()) { | |
911 | |
912 if (m_sliceableModel == m_fftModels[v].first) { | |
913 bool replaced = false; | |
914 for (ViewFFTMap::iterator i = m_fftModels.begin(); | |
915 i != m_fftModels.end(); ++i) { | |
916 if (i->second.first != m_sliceableModel) { | |
917 emit sliceableModelReplaced(m_sliceableModel, i->second.first); | |
918 replaced = true; | |
919 break; | |
920 } | |
921 } | |
922 if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); | |
923 } | |
924 | |
925 delete m_fftModels[v].first; | |
926 m_fftModels.erase(v); | |
927 } | |
928 | |
929 } else { | |
930 | |
931 Layer::setLayerDormant(v, false); | |
932 } | |
933 } | |
934 | |
935 void | |
936 SpectrogramLayer::cacheInvalid() | |
937 { | |
938 invalidatePixmapCaches(); | |
939 invalidateMagnitudes(); | |
940 } | |
941 | |
942 void | |
943 SpectrogramLayer::cacheInvalid(size_t, size_t) | |
944 { | |
945 // for now (or forever?) | |
946 cacheInvalid(); | |
947 } | |
948 | |
949 void | |
950 SpectrogramLayer::fillTimerTimedOut() | |
951 { | |
952 if (!m_model) return; | |
953 | |
954 bool allDone = true; | |
955 | |
956 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
957 std::cerr << "SpectrogramLayer::fillTimerTimedOut: have " << m_fftModels.size() << " FFT models associated with views" << std::endl; | |
958 #endif | |
959 | |
960 for (ViewFFTMap::iterator i = m_fftModels.begin(); | |
961 i != m_fftModels.end(); ++i) { | |
962 | |
963 const FFTModel *model = i->second.first; | |
964 size_t lastFill = i->second.second; | |
965 | |
966 if (model) { | |
967 | |
968 size_t fill = model->getFillExtent(); | |
969 | |
970 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
971 std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent for " << model << ": " << fill << ", last " << lastFill << ", total " << m_model->getEndFrame() << std::endl; | |
972 #endif | |
973 | |
974 if (fill >= lastFill) { | |
975 if (fill >= m_model->getEndFrame() && lastFill > 0) { | |
976 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
977 std::cerr << "complete!" << std::endl; | |
978 #endif | |
979 invalidatePixmapCaches(); | |
980 i->second.second = -1; | |
981 emit modelChanged(); | |
982 | |
983 } else if (fill > lastFill) { | |
984 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
985 std::cerr << "SpectrogramLayer: emitting modelChanged(" | |
986 << lastFill << "," << fill << ")" << std::endl; | |
987 #endif | |
988 invalidatePixmapCaches(lastFill, fill); | |
989 i->second.second = fill; | |
990 emit modelChanged(lastFill, fill); | |
991 } | |
992 } else { | |
993 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
994 std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged(" | |
995 << m_model->getStartFrame() << "," << m_model->getEndFrame() << ")" << std::endl; | |
996 #endif | |
997 invalidatePixmapCaches(); | |
998 i->second.second = fill; | |
999 emit modelChanged(m_model->getStartFrame(), m_model->getEndFrame()); | |
1000 } | |
1001 | |
1002 if (i->second.second >= 0) { | |
1003 allDone = false; | |
1004 } | |
1005 } | |
1006 } | |
1007 | |
1008 if (allDone) { | |
1009 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1010 std::cerr << "SpectrogramLayer: all complete!" << std::endl; | |
1011 #endif | |
1012 delete m_updateTimer; | |
1013 m_updateTimer = 0; | |
1014 } | |
1015 } | |
1016 | |
1017 bool | |
1018 SpectrogramLayer::hasLightBackground() const | |
1019 { | |
1020 return (m_colourMap == (int)ColourMapper::BlackOnWhite); | |
1021 } | |
1022 | |
1023 void | |
1024 SpectrogramLayer::initialisePalette() | |
1025 { | |
1026 int formerRotation = m_colourRotation; | |
1027 | |
1028 if (m_colourMap == (int)ColourMapper::BlackOnWhite) { | |
1029 m_palette.setColour(NO_VALUE, Qt::white); | |
1030 } else { | |
1031 m_palette.setColour(NO_VALUE, Qt::black); | |
1032 } | |
1033 | |
1034 ColourMapper mapper(m_colourMap, 1.f, 255.f); | |
1035 | |
1036 for (int pixel = 1; pixel < 256; ++pixel) { | |
1037 m_palette.setColour(pixel, mapper.map(pixel)); | |
1038 } | |
1039 | |
1040 m_crosshairColour = mapper.getContrastingColour(); | |
1041 | |
1042 m_colourRotation = 0; | |
1043 rotatePalette(m_colourRotation - formerRotation); | |
1044 m_colourRotation = formerRotation; | |
1045 } | |
1046 | |
1047 void | |
1048 SpectrogramLayer::rotatePalette(int distance) | |
1049 { | |
1050 QColor newPixels[256]; | |
1051 | |
1052 newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); | |
1053 | |
1054 for (int pixel = 1; pixel < 256; ++pixel) { | |
1055 int target = pixel + distance; | |
1056 while (target < 1) target += 255; | |
1057 while (target > 255) target -= 255; | |
1058 newPixels[target] = m_palette.getColour(pixel); | |
1059 } | |
1060 | |
1061 for (int pixel = 0; pixel < 256; ++pixel) { | |
1062 m_palette.setColour(pixel, newPixels[pixel]); | |
1063 } | |
1064 } | |
1065 | |
1066 float | |
1067 SpectrogramLayer::calculateFrequency(size_t bin, | |
1068 size_t windowSize, | |
1069 size_t windowIncrement, | |
1070 size_t sampleRate, | |
1071 float oldPhase, | |
1072 float newPhase, | |
1073 bool &steadyState) | |
1074 { | |
1075 // At frequency f, phase shift of 2pi (one cycle) happens in 1/f sec. | |
1076 // At hopsize h and sample rate sr, one hop happens in h/sr sec. | |
1077 // At window size w, for bin b, f is b*sr/w. | |
1078 // thus 2pi phase shift happens in w/(b*sr) sec. | |
1079 // We need to know what phase shift we expect from h/sr sec. | |
1080 // -> 2pi * ((h/sr) / (w/(b*sr))) | |
1081 // = 2pi * ((h * b * sr) / (w * sr)) | |
1082 // = 2pi * (h * b) / w. | |
1083 | |
1084 float frequency = (float(bin) * sampleRate) / windowSize; | |
1085 | |
1086 float expectedPhase = | |
1087 oldPhase + (2.0 * M_PI * bin * windowIncrement) / windowSize; | |
1088 | |
1089 float phaseError = princargf(newPhase - expectedPhase); | |
1090 | |
1091 if (fabsf(phaseError) < (1.1f * (windowIncrement * M_PI) / windowSize)) { | |
1092 | |
1093 // The new frequency estimate based on the phase error | |
1094 // resulting from assuming the "native" frequency of this bin | |
1095 | |
1096 float newFrequency = | |
1097 (sampleRate * (expectedPhase + phaseError - oldPhase)) / | |
1098 (2 * M_PI * windowIncrement); | |
1099 | |
1100 steadyState = true; | |
1101 return newFrequency; | |
1102 } | |
1103 | |
1104 steadyState = false; | |
1105 return frequency; | |
1106 } | |
1107 | |
1108 unsigned char | |
1109 SpectrogramLayer::getDisplayValue(View *v, float input) const | |
1110 { | |
1111 int value; | |
1112 | |
1113 float min = 0.f; | |
1114 float max = 1.f; | |
1115 | |
1116 if (m_normalizeVisibleArea) { | |
1117 min = m_viewMags[v].getMin(); | |
1118 max = m_viewMags[v].getMax(); | |
1119 } else if (!m_normalizeColumns) { | |
1120 if (m_colourScale == LinearColourScale //|| | |
1121 // m_colourScale == MeterColourScale) { | |
1122 ) { | |
1123 max = 0.1f; | |
1124 } | |
1125 } | |
1126 | |
1127 float thresh = -80.f; | |
1128 | |
1129 if (max == 0.f) max = 1.f; | |
1130 if (max == min) min = max - 0.0001f; | |
1131 | |
1132 switch (m_colourScale) { | |
1133 | |
1134 default: | |
1135 case LinearColourScale: | |
1136 value = int(((input - min) / (max - min)) * 255.f) + 1; | |
1137 break; | |
1138 | |
1139 case MeterColourScale: | |
1140 value = AudioLevel::multiplier_to_preview | |
1141 ((input - min) / (max - min), 254) + 1; | |
1142 break; | |
1143 | |
1144 case dBSquaredColourScale: | |
1145 input = ((input - min) * (input - min)) / ((max - min) * (max - min)); | |
1146 if (input > 0.f) { | |
1147 input = 10.f * log10f(input); | |
1148 } else { | |
1149 input = thresh; | |
1150 } | |
1151 if (min > 0.f) { | |
1152 thresh = 10.f * log10f(min * min); | |
1153 if (thresh < -80.f) thresh = -80.f; | |
1154 } | |
1155 input = (input - thresh) / (-thresh); | |
1156 if (input < 0.f) input = 0.f; | |
1157 if (input > 1.f) input = 1.f; | |
1158 value = int(input * 255.f) + 1; | |
1159 break; | |
1160 | |
1161 case dBColourScale: | |
1162 //!!! experiment with normalizing the visible area this way. | |
1163 //In any case, we need to have some indication of what the dB | |
1164 //scale is relative to. | |
1165 input = (input - min) / (max - min); | |
1166 if (input > 0.f) { | |
1167 input = 10.f * log10f(input); | |
1168 } else { | |
1169 input = thresh; | |
1170 } | |
1171 if (min > 0.f) { | |
1172 thresh = 10.f * log10f(min); | |
1173 if (thresh < -80.f) thresh = -80.f; | |
1174 } | |
1175 input = (input - thresh) / (-thresh); | |
1176 if (input < 0.f) input = 0.f; | |
1177 if (input > 1.f) input = 1.f; | |
1178 value = int(input * 255.f) + 1; | |
1179 break; | |
1180 | |
1181 case PhaseColourScale: | |
1182 value = int((input * 127.0 / M_PI) + 128); | |
1183 break; | |
1184 } | |
1185 | |
1186 if (value > UCHAR_MAX) value = UCHAR_MAX; | |
1187 if (value < 0) value = 0; | |
1188 return value; | |
1189 } | |
1190 | |
1191 float | |
1192 SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const | |
1193 { | |
1194 //!!! unused | |
1195 | |
1196 int value = uc; | |
1197 float input; | |
1198 | |
1199 //!!! incorrect for normalizing visible area (and also out of date) | |
1200 | |
1201 switch (m_colourScale) { | |
1202 | |
1203 default: | |
1204 case LinearColourScale: | |
1205 input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50); | |
1206 break; | |
1207 | |
1208 case MeterColourScale: | |
1209 input = AudioLevel::preview_to_multiplier(value - 1, 255) | |
1210 / (m_normalizeColumns ? 1.0 : 50.0); | |
1211 break; | |
1212 | |
1213 case dBSquaredColourScale: | |
1214 input = float(value - 1) / 255.0; | |
1215 input = (input * 80.0) - 80.0; | |
1216 input = powf(10.0, input) / 20.0; | |
1217 value = int(input); | |
1218 break; | |
1219 | |
1220 case dBColourScale: | |
1221 input = float(value - 1) / 255.0; | |
1222 input = (input * 80.0) - 80.0; | |
1223 input = powf(10.0, input) / 20.0; | |
1224 value = int(input); | |
1225 break; | |
1226 | |
1227 case PhaseColourScale: | |
1228 input = float(value - 128) * M_PI / 127.0; | |
1229 break; | |
1230 } | |
1231 | |
1232 return input; | |
1233 } | |
1234 | |
1235 float | |
1236 SpectrogramLayer::getEffectiveMinFrequency() const | |
1237 { | |
1238 int sr = m_model->getSampleRate(); | |
1239 float minf = float(sr) / m_fftSize; | |
1240 | |
1241 if (m_minFrequency > 0.0) { | |
1242 size_t minbin = size_t((double(m_minFrequency) * m_fftSize) / sr + 0.01); | |
1243 if (minbin < 1) minbin = 1; | |
1244 minf = minbin * sr / m_fftSize; | |
1245 } | |
1246 | |
1247 return minf; | |
1248 } | |
1249 | |
1250 float | |
1251 SpectrogramLayer::getEffectiveMaxFrequency() const | |
1252 { | |
1253 int sr = m_model->getSampleRate(); | |
1254 float maxf = float(sr) / 2; | |
1255 | |
1256 if (m_maxFrequency > 0.0) { | |
1257 size_t maxbin = size_t((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | |
1258 if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | |
1259 maxf = maxbin * sr / m_fftSize; | |
1260 } | |
1261 | |
1262 return maxf; | |
1263 } | |
1264 | |
1265 bool | |
1266 SpectrogramLayer::getYBinRange(View *v, int y, float &q0, float &q1) const | |
1267 { | |
1268 int h = v->height(); | |
1269 if (y < 0 || y >= h) return false; | |
1270 | |
1271 int sr = m_model->getSampleRate(); | |
1272 float minf = getEffectiveMinFrequency(); | |
1273 float maxf = getEffectiveMaxFrequency(); | |
1274 | |
1275 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | |
1276 | |
1277 //!!! wrong for smoothing -- wrong fft size for fft model | |
1278 | |
1279 q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); | |
1280 q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); | |
1281 | |
1282 // Now map these on to actual bins | |
1283 | |
1284 int b0 = int((q0 * m_fftSize) / sr); | |
1285 int b1 = int((q1 * m_fftSize) / sr); | |
1286 | |
1287 //!!! this is supposed to return fractions-of-bins, as it were, hence the floats | |
1288 q0 = b0; | |
1289 q1 = b1; | |
1290 | |
1291 // q0 = (b0 * sr) / m_fftSize; | |
1292 // q1 = (b1 * sr) / m_fftSize; | |
1293 | |
1294 return true; | |
1295 } | |
1296 | |
1297 bool | |
1298 SpectrogramLayer::getXBinRange(View *v, int x, float &s0, float &s1) const | |
1299 { | |
1300 size_t modelStart = m_model->getStartFrame(); | |
1301 size_t modelEnd = m_model->getEndFrame(); | |
1302 | |
1303 // Each pixel column covers an exact range of sample frames: | |
1304 int f0 = v->getFrameForX(x) - modelStart; | |
1305 int f1 = v->getFrameForX(x + 1) - modelStart - 1; | |
1306 | |
1307 if (f1 < int(modelStart) || f0 > int(modelEnd)) { | |
1308 return false; | |
1309 } | |
1310 | |
1311 // And that range may be drawn from a possibly non-integral | |
1312 // range of spectrogram windows: | |
1313 | |
1314 size_t windowIncrement = getWindowIncrement(); | |
1315 s0 = float(f0) / windowIncrement; | |
1316 s1 = float(f1) / windowIncrement; | |
1317 | |
1318 return true; | |
1319 } | |
1320 | |
1321 bool | |
1322 SpectrogramLayer::getXBinSourceRange(View *v, int x, RealTime &min, RealTime &max) const | |
1323 { | |
1324 float s0 = 0, s1 = 0; | |
1325 if (!getXBinRange(v, x, s0, s1)) return false; | |
1326 | |
1327 int s0i = int(s0 + 0.001); | |
1328 int s1i = int(s1); | |
1329 | |
1330 int windowIncrement = getWindowIncrement(); | |
1331 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2; | |
1332 int w1 = s1i * windowIncrement + windowIncrement + | |
1333 (m_windowSize - windowIncrement)/2 - 1; | |
1334 | |
1335 min = RealTime::frame2RealTime(w0, m_model->getSampleRate()); | |
1336 max = RealTime::frame2RealTime(w1, m_model->getSampleRate()); | |
1337 return true; | |
1338 } | |
1339 | |
1340 bool | |
1341 SpectrogramLayer::getYBinSourceRange(View *v, int y, float &freqMin, float &freqMax) | |
1342 const | |
1343 { | |
1344 float q0 = 0, q1 = 0; | |
1345 if (!getYBinRange(v, y, q0, q1)) return false; | |
1346 | |
1347 int q0i = int(q0 + 0.001); | |
1348 int q1i = int(q1); | |
1349 | |
1350 int sr = m_model->getSampleRate(); | |
1351 | |
1352 for (int q = q0i; q <= q1i; ++q) { | |
1353 if (q == q0i) freqMin = (sr * q) / m_fftSize; | |
1354 if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; | |
1355 } | |
1356 return true; | |
1357 } | |
1358 | |
1359 bool | |
1360 SpectrogramLayer::getAdjustedYBinSourceRange(View *v, int x, int y, | |
1361 float &freqMin, float &freqMax, | |
1362 float &adjFreqMin, float &adjFreqMax) | |
1363 const | |
1364 { | |
1365 FFTModel *fft = getFFTModel(v); | |
1366 if (!fft) return false; | |
1367 | |
1368 float s0 = 0, s1 = 0; | |
1369 if (!getXBinRange(v, x, s0, s1)) return false; | |
1370 | |
1371 float q0 = 0, q1 = 0; | |
1372 if (!getYBinRange(v, y, q0, q1)) return false; | |
1373 | |
1374 int s0i = int(s0 + 0.001); | |
1375 int s1i = int(s1); | |
1376 | |
1377 int q0i = int(q0 + 0.001); | |
1378 int q1i = int(q1); | |
1379 | |
1380 int sr = m_model->getSampleRate(); | |
1381 | |
1382 size_t windowSize = m_windowSize; | |
1383 size_t windowIncrement = getWindowIncrement(); | |
1384 | |
1385 bool haveAdj = false; | |
1386 | |
1387 bool peaksOnly = (m_binDisplay == PeakBins || | |
1388 m_binDisplay == PeakFrequencies); | |
1389 | |
1390 for (int q = q0i; q <= q1i; ++q) { | |
1391 | |
1392 for (int s = s0i; s <= s1i; ++s) { | |
1393 | |
1394 if (!fft->isColumnAvailable(s)) continue; | |
1395 | |
1396 float binfreq = (sr * q) / m_windowSize; | |
1397 if (q == q0i) freqMin = binfreq; | |
1398 if (q == q1i) freqMax = binfreq; | |
1399 | |
1400 if (peaksOnly && !fft->isLocalPeak(s, q)) continue; | |
1401 | |
1402 if (!fft->isOverThreshold(s, q, m_threshold)) continue; | |
1403 | |
1404 float freq = binfreq; | |
1405 bool steady = false; | |
1406 | |
1407 if (s < int(fft->getWidth()) - 1) { | |
1408 | |
1409 freq = calculateFrequency(q, | |
1410 windowSize, | |
1411 windowIncrement, | |
1412 sr, | |
1413 fft->getPhaseAt(s, q), | |
1414 fft->getPhaseAt(s+1, q), | |
1415 steady); | |
1416 | |
1417 if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq; | |
1418 if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq; | |
1419 | |
1420 haveAdj = true; | |
1421 } | |
1422 } | |
1423 } | |
1424 | |
1425 if (!haveAdj) { | |
1426 adjFreqMin = adjFreqMax = 0.0; | |
1427 } | |
1428 | |
1429 return haveAdj; | |
1430 } | |
1431 | |
1432 bool | |
1433 SpectrogramLayer::getXYBinSourceRange(View *v, int x, int y, | |
1434 float &min, float &max, | |
1435 float &phaseMin, float &phaseMax) const | |
1436 { | |
1437 float q0 = 0, q1 = 0; | |
1438 if (!getYBinRange(v, y, q0, q1)) return false; | |
1439 | |
1440 float s0 = 0, s1 = 0; | |
1441 if (!getXBinRange(v, x, s0, s1)) return false; | |
1442 | |
1443 int q0i = int(q0 + 0.001); | |
1444 int q1i = int(q1); | |
1445 | |
1446 int s0i = int(s0 + 0.001); | |
1447 int s1i = int(s1); | |
1448 | |
1449 bool rv = false; | |
1450 | |
1451 size_t zp = getZeroPadLevel(v); | |
1452 q0i *= zp + 1; | |
1453 q1i *= zp + 1; | |
1454 | |
1455 FFTModel *fft = getFFTModel(v); | |
1456 | |
1457 if (fft) { | |
1458 | |
1459 int cw = fft->getWidth(); | |
1460 int ch = fft->getHeight(); | |
1461 | |
1462 min = 0.0; | |
1463 max = 0.0; | |
1464 phaseMin = 0.0; | |
1465 phaseMax = 0.0; | |
1466 bool have = false; | |
1467 | |
1468 for (int q = q0i; q <= q1i; ++q) { | |
1469 for (int s = s0i; s <= s1i; ++s) { | |
1470 if (s >= 0 && q >= 0 && s < cw && q < ch) { | |
1471 | |
1472 if (!fft->isColumnAvailable(s)) continue; | |
1473 | |
1474 float value; | |
1475 | |
1476 value = fft->getPhaseAt(s, q); | |
1477 if (!have || value < phaseMin) { phaseMin = value; } | |
1478 if (!have || value > phaseMax) { phaseMax = value; } | |
1479 | |
1480 value = fft->getMagnitudeAt(s, q); | |
1481 if (!have || value < min) { min = value; } | |
1482 if (!have || value > max) { max = value; } | |
1483 | |
1484 have = true; | |
1485 } | |
1486 } | |
1487 } | |
1488 | |
1489 if (have) { | |
1490 rv = true; | |
1491 } | |
1492 } | |
1493 | |
1494 return rv; | |
1495 } | |
1496 | |
1497 size_t | |
1498 SpectrogramLayer::getZeroPadLevel(const View *v) const | |
1499 { | |
1500 //!!! tidy all this stuff | |
1501 | |
1502 if (m_binDisplay != AllBins) return 0; | |
1503 | |
1504 Preferences::SpectrogramSmoothing smoothing = | |
1505 Preferences::getInstance()->getSpectrogramSmoothing(); | |
1506 | |
1507 if (smoothing == Preferences::NoSpectrogramSmoothing || | |
1508 smoothing == Preferences::SpectrogramInterpolated) return 0; | |
1509 | |
1510 if (m_frequencyScale == LogFrequencyScale) return 3; | |
1511 | |
1512 int sr = m_model->getSampleRate(); | |
1513 | |
1514 size_t maxbin = m_fftSize / 2; | |
1515 if (m_maxFrequency > 0) { | |
1516 maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | |
1517 if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | |
1518 } | |
1519 | |
1520 size_t minbin = 1; | |
1521 if (m_minFrequency > 0) { | |
1522 minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); | |
1523 if (minbin < 1) minbin = 1; | |
1524 if (minbin >= maxbin) minbin = maxbin - 1; | |
1525 } | |
1526 | |
1527 float perPixel = | |
1528 float(v->height()) / | |
1529 float((maxbin - minbin) / (m_zeroPadLevel + 1)); | |
1530 | |
1531 if (perPixel > 2.8) { | |
1532 return 3; // 4x oversampling | |
1533 } else if (perPixel > 1.5) { | |
1534 return 1; // 2x | |
1535 } else { | |
1536 return 0; // 1x | |
1537 } | |
1538 } | |
1539 | |
1540 size_t | |
1541 SpectrogramLayer::getFFTSize(const View *v) const | |
1542 { | |
1543 return m_fftSize * (getZeroPadLevel(v) + 1); | |
1544 } | |
1545 | |
1546 FFTModel * | |
1547 SpectrogramLayer::getFFTModel(const View *v) const | |
1548 { | |
1549 if (!m_model) return 0; | |
1550 | |
1551 size_t fftSize = getFFTSize(v); | |
1552 | |
1553 if (m_fftModels.find(v) != m_fftModels.end()) { | |
1554 if (m_fftModels[v].first == 0) { | |
1555 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1556 std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << std::endl; | |
1557 #endif | |
1558 return 0; | |
1559 } | |
1560 if (m_fftModels[v].first->getHeight() != fftSize / 2 + 1) { | |
1561 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1562 std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[v].first->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << std::endl; | |
1563 #endif | |
1564 delete m_fftModels[v].first; | |
1565 m_fftModels.erase(v); | |
1566 } else { | |
1567 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1568 std::cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[v].first->getHeight() << std::endl; | |
1569 #endif | |
1570 return m_fftModels[v].first; | |
1571 } | |
1572 } | |
1573 | |
1574 if (m_fftModels.find(v) == m_fftModels.end()) { | |
1575 | |
1576 FFTModel *model = new FFTModel(m_model, | |
1577 m_channel, | |
1578 m_windowType, | |
1579 m_windowSize, | |
1580 getWindowIncrement(), | |
1581 fftSize, | |
1582 true, | |
1583 m_candidateFillStartFrame); | |
1584 | |
1585 if (!model->isOK()) { | |
1586 QMessageBox::critical | |
1587 (0, tr("FFT cache failed"), | |
1588 tr("Failed to create the FFT model for this spectrogram.\n" | |
1589 "There may be insufficient memory or disc space to continue.")); | |
1590 delete model; | |
1591 m_fftModels[v] = FFTFillPair(0, 0); | |
1592 return 0; | |
1593 } | |
1594 | |
1595 if (!m_sliceableModel) { | |
1596 #ifdef DEBUG_SPECTROGRAM | |
1597 std::cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << std::endl; | |
1598 #endif | |
1599 ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); | |
1600 m_sliceableModel = model; | |
1601 } | |
1602 | |
1603 m_fftModels[v] = FFTFillPair(model, 0); | |
1604 | |
1605 model->resume(); | |
1606 | |
1607 delete m_updateTimer; | |
1608 m_updateTimer = new QTimer((SpectrogramLayer *)this); | |
1609 connect(m_updateTimer, SIGNAL(timeout()), | |
1610 this, SLOT(fillTimerTimedOut())); | |
1611 m_updateTimer->start(200); | |
1612 } | |
1613 | |
1614 return m_fftModels[v].first; | |
1615 } | |
1616 | |
1617 const Model * | |
1618 SpectrogramLayer::getSliceableModel() const | |
1619 { | |
1620 if (m_sliceableModel) return m_sliceableModel; | |
1621 if (m_fftModels.empty()) return 0; | |
1622 m_sliceableModel = m_fftModels.begin()->second.first; | |
1623 return m_sliceableModel; | |
1624 } | |
1625 | |
1626 void | |
1627 SpectrogramLayer::invalidateFFTModels() | |
1628 { | |
1629 for (ViewFFTMap::iterator i = m_fftModels.begin(); | |
1630 i != m_fftModels.end(); ++i) { | |
1631 delete i->second.first; | |
1632 } | |
1633 | |
1634 m_fftModels.clear(); | |
1635 | |
1636 if (m_sliceableModel) { | |
1637 std::cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << std::endl; | |
1638 emit sliceableModelReplaced(m_sliceableModel, 0); | |
1639 m_sliceableModel = 0; | |
1640 } | |
1641 } | |
1642 | |
1643 void | |
1644 SpectrogramLayer::invalidateMagnitudes() | |
1645 { | |
1646 m_viewMags.clear(); | |
1647 for (std::vector<MagnitudeRange>::iterator i = m_columnMags.begin(); | |
1648 i != m_columnMags.end(); ++i) { | |
1649 *i = MagnitudeRange(); | |
1650 } | |
1651 } | |
1652 | |
1653 bool | |
1654 SpectrogramLayer::updateViewMagnitudes(View *v) const | |
1655 { | |
1656 MagnitudeRange mag; | |
1657 | |
1658 int x0 = 0, x1 = v->width(); | |
1659 float s00 = 0, s01 = 0, s10 = 0, s11 = 0; | |
1660 | |
1661 if (!getXBinRange(v, x0, s00, s01)) { | |
1662 s00 = s01 = m_model->getStartFrame() / getWindowIncrement(); | |
1663 } | |
1664 | |
1665 if (!getXBinRange(v, x1, s10, s11)) { | |
1666 s10 = s11 = m_model->getEndFrame() / getWindowIncrement(); | |
1667 } | |
1668 | |
1669 int s0 = int(min(s00, s10) + 0.0001); | |
1670 int s1 = int(max(s01, s11) + 0.0001); | |
1671 | |
1672 // std::cerr << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << std::endl; | |
1673 | |
1674 if (int(m_columnMags.size()) <= s1) { | |
1675 m_columnMags.resize(s1 + 1); | |
1676 } | |
1677 | |
1678 for (int s = s0; s <= s1; ++s) { | |
1679 if (m_columnMags[s].isSet()) { | |
1680 mag.sample(m_columnMags[s]); | |
1681 } | |
1682 } | |
1683 | |
1684 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1685 std::cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols " | |
1686 << s0 << " -> " << s1 << " inclusive" << std::endl; | |
1687 #endif | |
1688 | |
1689 if (!mag.isSet()) return false; | |
1690 if (mag == m_viewMags[v]) return false; | |
1691 m_viewMags[v] = mag; | |
1692 return true; | |
1693 } | |
1694 | |
1695 void | |
1696 SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const | |
1697 { | |
1698 Profiler profiler("SpectrogramLayer::paint", true); | |
1699 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1700 std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << std::endl; | |
1701 | |
1702 std::cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << std::endl; | |
1703 #endif | |
1704 | |
1705 long startFrame = v->getStartFrame(); | |
1706 if (startFrame < 0) m_candidateFillStartFrame = 0; | |
1707 else m_candidateFillStartFrame = startFrame; | |
1708 | |
1709 if (!m_model || !m_model->isOK() || !m_model->isReady()) { | |
1710 return; | |
1711 } | |
1712 | |
1713 if (isLayerDormant(v)) { | |
1714 std::cerr << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << std::endl; | |
1715 } | |
1716 | |
1717 // Need to do this even if !isLayerDormant, as that could mean v | |
1718 // is not in the dormancy map at all -- we need it to be present | |
1719 // and accountable for when determining whether we need the cache | |
1720 // in the cache-fill thread above. | |
1721 //!!! no longer use cache-fill thread | |
1722 const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false); | |
1723 | |
1724 size_t fftSize = getFFTSize(v); | |
1725 FFTModel *fft = getFFTModel(v); | |
1726 if (!fft) { | |
1727 std::cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << std::endl; | |
1728 return; | |
1729 } | |
1730 | |
1731 PixmapCache &cache = m_pixmapCaches[v]; | |
1732 | |
1733 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1734 std::cerr << "SpectrogramLayer::paint(): pixmap cache valid area " << cache.validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << std::endl; | |
1735 #endif | |
1736 | |
1737 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1738 bool stillCacheing = (m_updateTimer != 0); | |
1739 std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl; | |
1740 #endif | |
1741 | |
1742 int zoomLevel = v->getZoomLevel(); | |
1743 | |
1744 int x0 = 0; | |
1745 int x1 = v->width(); | |
1746 | |
1747 bool recreateWholePixmapCache = true; | |
1748 | |
1749 x0 = rect.left(); | |
1750 x1 = rect.right() + 1; | |
1751 | |
1752 if (cache.validArea.width() > 0) { | |
1753 | |
1754 if (int(cache.zoomLevel) == zoomLevel && | |
1755 cache.pixmap.width() == v->width() && | |
1756 cache.pixmap.height() == v->height()) { | |
1757 | |
1758 if (v->getXForFrame(cache.startFrame) == | |
1759 v->getXForFrame(startFrame) && | |
1760 cache.validArea.x() <= x0 && | |
1761 cache.validArea.x() + cache.validArea.width() >= x1) { | |
1762 | |
1763 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1764 std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl; | |
1765 #endif | |
1766 | |
1767 paint.drawPixmap(rect, cache.pixmap, rect); | |
1768 illuminateLocalFeatures(v, paint); | |
1769 return; | |
1770 | |
1771 } else { | |
1772 | |
1773 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1774 std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl; | |
1775 #endif | |
1776 | |
1777 recreateWholePixmapCache = false; | |
1778 | |
1779 int dx = v->getXForFrame(cache.startFrame) - | |
1780 v->getXForFrame(startFrame); | |
1781 | |
1782 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1783 std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << cache.pixmap.width() << "x" << cache.pixmap.height() << ")" << std::endl; | |
1784 #endif | |
1785 | |
1786 if (dx != 0 && | |
1787 dx > -cache.pixmap.width() && | |
1788 dx < cache.pixmap.width()) { | |
1789 | |
1790 #if defined(Q_WS_WIN32) || defined(Q_WS_MAC) | |
1791 // Copying a pixmap to itself doesn't work | |
1792 // properly on Windows or Mac (it only works when | |
1793 // moving in one direction). | |
1794 | |
1795 //!!! Need a utility function for this | |
1796 | |
1797 static QPixmap *tmpPixmap = 0; | |
1798 if (!tmpPixmap || | |
1799 tmpPixmap->width() != cache.pixmap.width() || | |
1800 tmpPixmap->height() != cache.pixmap.height()) { | |
1801 delete tmpPixmap; | |
1802 tmpPixmap = new QPixmap(cache.pixmap.width(), | |
1803 cache.pixmap.height()); | |
1804 } | |
1805 QPainter cachePainter; | |
1806 cachePainter.begin(tmpPixmap); | |
1807 cachePainter.drawPixmap(0, 0, cache.pixmap); | |
1808 cachePainter.end(); | |
1809 cachePainter.begin(&cache.pixmap); | |
1810 cachePainter.drawPixmap(dx, 0, *tmpPixmap); | |
1811 cachePainter.end(); | |
1812 #else | |
1813 QPainter cachePainter(&cache.pixmap); | |
1814 cachePainter.drawPixmap(dx, 0, cache.pixmap); | |
1815 cachePainter.end(); | |
1816 #endif | |
1817 | |
1818 int px = cache.validArea.x(); | |
1819 int pw = cache.validArea.width(); | |
1820 | |
1821 if (dx < 0) { | |
1822 x0 = cache.pixmap.width() + dx; | |
1823 x1 = cache.pixmap.width(); | |
1824 px += dx; | |
1825 if (px < 0) { | |
1826 pw += px; | |
1827 px = 0; | |
1828 if (pw < 0) pw = 0; | |
1829 } | |
1830 } else { | |
1831 x0 = 0; | |
1832 x1 = dx; | |
1833 px += dx; | |
1834 if (px + pw > cache.pixmap.width()) { | |
1835 pw = int(cache.pixmap.width()) - px; | |
1836 if (pw < 0) pw = 0; | |
1837 } | |
1838 } | |
1839 | |
1840 cache.validArea = | |
1841 QRect(px, cache.validArea.y(), | |
1842 pw, cache.validArea.height()); | |
1843 | |
1844 paint.drawPixmap(rect & cache.validArea, | |
1845 cache.pixmap, | |
1846 rect & cache.validArea); | |
1847 } | |
1848 } | |
1849 } else { | |
1850 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1851 std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl; | |
1852 if (int(cache.zoomLevel) != zoomLevel) { | |
1853 std::cerr << "(cache zoomLevel " << cache.zoomLevel | |
1854 << " != " << zoomLevel << ")" << std::endl; | |
1855 } | |
1856 if (cache.pixmap.width() != v->width()) { | |
1857 std::cerr << "(cache width " << cache.pixmap.width() | |
1858 << " != " << v->width(); | |
1859 } | |
1860 if (cache.pixmap.height() != v->height()) { | |
1861 std::cerr << "(cache height " << cache.pixmap.height() | |
1862 << " != " << v->height(); | |
1863 } | |
1864 #endif | |
1865 cache.validArea = QRect(); | |
1866 } | |
1867 } | |
1868 | |
1869 if (updateViewMagnitudes(v)) { | |
1870 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1871 std::cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl; | |
1872 #endif | |
1873 recreateWholePixmapCache = true; | |
1874 } else { | |
1875 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1876 std::cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl; | |
1877 #endif | |
1878 } | |
1879 | |
1880 if (recreateWholePixmapCache) { | |
1881 x0 = 0; | |
1882 x1 = v->width(); | |
1883 } | |
1884 | |
1885 struct timeval tv; | |
1886 (void)gettimeofday(&tv, 0); | |
1887 RealTime mainPaintStart = RealTime::fromTimeval(tv); | |
1888 | |
1889 int paintBlockWidth = m_lastPaintBlockWidth; | |
1890 | |
1891 if (paintBlockWidth == 0) { | |
1892 paintBlockWidth = (300000 / zoomLevel); | |
1893 } else { | |
1894 RealTime lastTime = m_lastPaintTime; | |
1895 while (lastTime > RealTime::fromMilliseconds(200) && | |
1896 paintBlockWidth > 50) { | |
1897 paintBlockWidth /= 2; | |
1898 lastTime = lastTime / 2; | |
1899 } | |
1900 while (lastTime < RealTime::fromMilliseconds(90) && | |
1901 paintBlockWidth < 1500) { | |
1902 paintBlockWidth *= 2; | |
1903 lastTime = lastTime * 2; | |
1904 } | |
1905 } | |
1906 | |
1907 if (paintBlockWidth < 20) paintBlockWidth = 20; | |
1908 | |
1909 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1910 std::cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << std::endl; | |
1911 #endif | |
1912 | |
1913 // We always paint the full height when refreshing the cache. | |
1914 // Smaller heights can be used when painting direct from cache | |
1915 // (further up in this function), but we want to ensure the cache | |
1916 // is coherent without having to worry about vertical matching of | |
1917 // required and valid areas as well as horizontal. | |
1918 | |
1919 int h = v->height(); | |
1920 | |
1921 if (cache.validArea.width() > 0) { | |
1922 | |
1923 int vx0 = 0, vx1 = 0; | |
1924 vx0 = cache.validArea.x(); | |
1925 vx1 = cache.validArea.x() + cache.validArea.width(); | |
1926 | |
1927 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1928 std::cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << std::endl; | |
1929 #endif | |
1930 if (x0 < vx0) { | |
1931 if (x0 + paintBlockWidth < vx0) { | |
1932 x0 = vx0 - paintBlockWidth; | |
1933 } else { | |
1934 x0 = 0; | |
1935 } | |
1936 } else if (x0 > vx1) { | |
1937 x0 = vx1; | |
1938 } | |
1939 | |
1940 if (x1 < vx0) { | |
1941 x1 = vx0; | |
1942 } else if (x1 > vx1) { | |
1943 if (vx1 + paintBlockWidth < x1) { | |
1944 x1 = vx1 + paintBlockWidth; | |
1945 } else { | |
1946 x1 = v->width(); | |
1947 } | |
1948 } | |
1949 | |
1950 cache.validArea = QRect | |
1951 (min(vx0, x0), cache.validArea.y(), | |
1952 max(vx1 - min(vx0, x0), | |
1953 x1 - min(vx0, x0)), | |
1954 cache.validArea.height()); | |
1955 | |
1956 } else { | |
1957 if (x1 > x0 + paintBlockWidth) { | |
1958 int sfx = x1; | |
1959 if (startFrame < 0) sfx = v->getXForFrame(0); | |
1960 if (sfx >= x0 && sfx + paintBlockWidth <= x1) { | |
1961 x0 = sfx; | |
1962 x1 = x0 + paintBlockWidth; | |
1963 } else { | |
1964 int mid = (x1 + x0) / 2; | |
1965 x0 = mid - paintBlockWidth/2; | |
1966 x1 = x0 + paintBlockWidth; | |
1967 } | |
1968 } | |
1969 cache.validArea = QRect(x0, 0, x1 - x0, h); | |
1970 } | |
1971 | |
1972 int w = x1 - x0; | |
1973 | |
1974 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
1975 std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl; | |
1976 #endif | |
1977 | |
1978 if (m_drawBuffer.width() < w || m_drawBuffer.height() < h) { | |
1979 m_drawBuffer = QImage(w, h, QImage::Format_RGB32); | |
1980 } | |
1981 | |
1982 m_drawBuffer.fill(m_palette.getColour(0).rgb()); | |
1983 | |
1984 int sr = m_model->getSampleRate(); | |
1985 | |
1986 // Set minFreq and maxFreq to the frequency extents of the possibly | |
1987 // zero-padded visible bin range, and displayMinFreq and displayMaxFreq | |
1988 // to the actual scale frequency extents (presumably not zero padded). | |
1989 | |
1990 size_t maxbin = fftSize / 2; | |
1991 if (m_maxFrequency > 0) { | |
1992 maxbin = int((double(m_maxFrequency) * fftSize) / sr + 0.1); | |
1993 if (maxbin > fftSize / 2) maxbin = fftSize / 2; | |
1994 } | |
1995 | |
1996 size_t minbin = 1; | |
1997 if (m_minFrequency > 0) { | |
1998 minbin = int((double(m_minFrequency) * fftSize) / sr + 0.1); | |
1999 if (minbin < 1) minbin = 1; | |
2000 if (minbin >= maxbin) minbin = maxbin - 1; | |
2001 } | |
2002 | |
2003 float minFreq = (float(minbin) * sr) / fftSize; | |
2004 float maxFreq = (float(maxbin) * sr) / fftSize; | |
2005 | |
2006 float displayMinFreq = minFreq; | |
2007 float displayMaxFreq = maxFreq; | |
2008 | |
2009 if (fftSize != m_fftSize) { | |
2010 displayMinFreq = getEffectiveMinFrequency(); | |
2011 displayMaxFreq = getEffectiveMaxFrequency(); | |
2012 } | |
2013 | |
2014 /*float ymag[h]; | |
2015 float ydiv[h]; | |
2016 float yval[maxbin + 1]; //!!! cache this?*/ | |
2017 float *ymag = (float*) malloc(h*sizeof(float)); | |
2018 float *ydiv = (float*) malloc(h*sizeof(float)); | |
2019 float *yval = (float*) malloc((maxbin + 1)*sizeof(float)); | |
2020 | |
2021 size_t increment = getWindowIncrement(); | |
2022 | |
2023 bool logarithmic = (m_frequencyScale == LogFrequencyScale); | |
2024 | |
2025 for (size_t q = minbin; q <= maxbin; ++q) { | |
2026 float f0 = (float(q) * sr) / fftSize; | |
2027 yval[q] = v->getYForFrequency(f0, displayMinFreq, displayMaxFreq, | |
2028 logarithmic); | |
2029 // std::cerr << "min: " << minFreq << ", max: " << maxFreq << ", yval[" << q << "]: " << yval[q] << std::endl; | |
2030 } | |
2031 | |
2032 MagnitudeRange overallMag = m_viewMags[v]; | |
2033 bool overallMagChanged = false; | |
2034 | |
2035 bool fftSuspended = false; | |
2036 | |
2037 bool interpolate = false; | |
2038 Preferences::SpectrogramSmoothing smoothing = | |
2039 Preferences::getInstance()->getSpectrogramSmoothing(); | |
2040 if (smoothing == Preferences::SpectrogramInterpolated || | |
2041 smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { | |
2042 if (m_binDisplay != PeakBins && | |
2043 m_binDisplay != PeakFrequencies) { | |
2044 interpolate = true; | |
2045 } | |
2046 } | |
2047 | |
2048 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2049 std::cerr << ((float(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << std::endl; | |
2050 #endif | |
2051 | |
2052 bool runOutOfData = false; | |
2053 | |
2054 for (int x = 0; x < w; ++x) { | |
2055 | |
2056 if (runOutOfData) break; | |
2057 | |
2058 for (int y = 0; y < h; ++y) { | |
2059 ymag[y] = 0.f; | |
2060 ydiv[y] = 0.f; | |
2061 } | |
2062 | |
2063 float s0 = 0, s1 = 0; | |
2064 | |
2065 if (!getXBinRange(v, x0 + x, s0, s1)) { | |
2066 assert(x <= m_drawBuffer.width()); | |
2067 continue; | |
2068 } | |
2069 | |
2070 int s0i = int(s0 + 0.001); | |
2071 int s1i = int(s1); | |
2072 | |
2073 if (s1i >= int(fft->getWidth())) { | |
2074 if (s0i >= int(fft->getWidth())) { | |
2075 continue; | |
2076 } else { | |
2077 s1i = s0i; | |
2078 } | |
2079 } | |
2080 | |
2081 for (int s = s0i; s <= s1i; ++s) { | |
2082 | |
2083 if (!fft->isColumnAvailable(s)) { | |
2084 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2085 std::cerr << "Met unavailable column at col " << s << std::endl; | |
2086 #endif | |
2087 // continue; | |
2088 runOutOfData = true; | |
2089 break; | |
2090 } | |
2091 | |
2092 if (!fftSuspended) { | |
2093 fft->suspendWrites(); | |
2094 fftSuspended = true; | |
2095 } | |
2096 | |
2097 MagnitudeRange mag; | |
2098 | |
2099 for (size_t q = minbin; q < maxbin; ++q) { | |
2100 | |
2101 float y0 = yval[q + 1]; | |
2102 float y1 = yval[q]; | |
2103 | |
2104 if (m_binDisplay == PeakBins || | |
2105 m_binDisplay == PeakFrequencies) { | |
2106 if (!fft->isLocalPeak(s, q)) continue; | |
2107 } | |
2108 | |
2109 if (m_threshold != 0.f && | |
2110 !fft->isOverThreshold(s, q, m_threshold)) { | |
2111 continue; | |
2112 } | |
2113 | |
2114 float sprop = 1.0; | |
2115 if (s == s0i) sprop *= (s + 1) - s0; | |
2116 if (s == s1i) sprop *= s1 - s; | |
2117 | |
2118 if (m_binDisplay == PeakFrequencies && | |
2119 s < int(fft->getWidth()) - 1) { | |
2120 | |
2121 bool steady = false; | |
2122 float f = calculateFrequency(q, | |
2123 m_windowSize, | |
2124 increment, | |
2125 sr, | |
2126 fft->getPhaseAt(s, q), | |
2127 fft->getPhaseAt(s+1, q), | |
2128 steady); | |
2129 | |
2130 y0 = y1 = v->getYForFrequency | |
2131 (f, displayMinFreq, displayMaxFreq, logarithmic); | |
2132 } | |
2133 | |
2134 int y0i = int(y0 + 0.001); | |
2135 int y1i = int(y1); | |
2136 | |
2137 float value; | |
2138 | |
2139 if (m_colourScale == PhaseColourScale) { | |
2140 value = fft->getPhaseAt(s, q); | |
2141 } else if (m_normalizeColumns) { | |
2142 value = fft->getNormalizedMagnitudeAt(s, q); | |
2143 mag.sample(value); | |
2144 value *= m_gain; | |
2145 } else { | |
2146 value = fft->getMagnitudeAt(s, q); | |
2147 mag.sample(value); | |
2148 value *= m_gain; | |
2149 } | |
2150 | |
2151 if (interpolate) { | |
2152 | |
2153 int ypi = y0i; | |
2154 if (q < maxbin - 1) ypi = int(yval[q + 2]); | |
2155 | |
2156 for (int y = ypi; y <= y1i; ++y) { | |
2157 | |
2158 if (y < 0 || y >= h) continue; | |
2159 | |
2160 float yprop = sprop; | |
2161 float iprop = yprop; | |
2162 | |
2163 if (ypi < y0i && y <= y0i) { | |
2164 | |
2165 float half = float(y0i - ypi) / 2; | |
2166 float dist = y - (ypi + half); | |
2167 | |
2168 if (dist >= 0) { | |
2169 iprop = (iprop * dist) / half; | |
2170 ymag[y] += iprop * value; | |
2171 } | |
2172 } else { | |
2173 if (y1i > y0i) { | |
2174 | |
2175 float half = float(y1i - y0i) / 2; | |
2176 float dist = y - (y0i + half); | |
2177 | |
2178 if (dist >= 0) { | |
2179 iprop = (iprop * (half - dist)) / half; | |
2180 } | |
2181 } | |
2182 | |
2183 ymag[y] += iprop * value; | |
2184 ydiv[y] += yprop; | |
2185 } | |
2186 } | |
2187 | |
2188 } else { | |
2189 | |
2190 for (int y = y0i; y <= y1i; ++y) { | |
2191 | |
2192 if (y < 0 || y >= h) continue; | |
2193 | |
2194 float yprop = sprop; | |
2195 if (y == y0i) yprop *= (y + 1) - y0; | |
2196 if (y == y1i) yprop *= y1 - y; | |
2197 | |
2198 for (int y = y0i; y <= y1i; ++y) { | |
2199 | |
2200 if (y < 0 || y >= h) continue; | |
2201 | |
2202 float yprop = sprop; | |
2203 if (y == y0i) yprop *= (y + 1) - y0; | |
2204 if (y == y1i) yprop *= y1 - y; | |
2205 ymag[y] += yprop * value; | |
2206 ydiv[y] += yprop; | |
2207 } | |
2208 } | |
2209 } | |
2210 } | |
2211 | |
2212 if (mag.isSet()) { | |
2213 | |
2214 if (s >= int(m_columnMags.size())) { | |
2215 std::cerr << "INTERNAL ERROR: " << s << " >= " | |
2216 << m_columnMags.size() << " at SpectrogramLayer.cpp:2087" << std::endl; | |
2217 } | |
2218 | |
2219 m_columnMags[s].sample(mag); | |
2220 | |
2221 if (overallMag.sample(mag)) { | |
2222 //!!! scaling would change here | |
2223 overallMagChanged = true; | |
2224 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2225 std::cerr << "Overall mag changed (again?) at column " << s << ", to [" << overallMag.getMin() << "->" << overallMag.getMax() << "]" << std::endl; | |
2226 #endif | |
2227 } | |
2228 } | |
2229 } | |
2230 | |
2231 for (int y = 0; y < h; ++y) { | |
2232 | |
2233 if (ydiv[y] > 0.0) { | |
2234 | |
2235 unsigned char pixel = 0; | |
2236 | |
2237 float avg = ymag[y] / ydiv[y]; | |
2238 pixel = getDisplayValue(v, avg); | |
2239 | |
2240 assert(x <= m_drawBuffer.width()); | |
2241 QColor c = m_palette.getColour(pixel); | |
2242 m_drawBuffer.setPixel(x, y, | |
2243 qRgb(c.red(), c.green(), c.blue())); | |
2244 } | |
2245 } | |
2246 } | |
2247 | |
2248 if (overallMagChanged) { | |
2249 m_viewMags[v] = overallMag; | |
2250 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2251 std::cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << std::endl; | |
2252 #endif | |
2253 } else { | |
2254 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2255 std::cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl; | |
2256 #endif | |
2257 } | |
2258 | |
2259 Profiler profiler2("SpectrogramLayer::paint: draw image", true); | |
2260 | |
2261 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2262 std::cerr << "Painting " << w << "x" << rect.height() | |
2263 << " from draw buffer at " << 0 << "," << rect.y() | |
2264 << " to window at " << x0 << "," << rect.y() << std::endl; | |
2265 #endif | |
2266 | |
2267 paint.drawImage(x0, rect.y(), m_drawBuffer, 0, rect.y(), w, rect.height()); | |
2268 | |
2269 if (recreateWholePixmapCache) { | |
2270 cache.pixmap = QPixmap(v->width(), h); | |
2271 } | |
2272 | |
2273 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2274 std::cerr << "Painting " << w << "x" << h | |
2275 << " from draw buffer at " << 0 << "," << 0 | |
2276 << " to cache at " << x0 << "," << 0 << std::endl; | |
2277 #endif | |
2278 | |
2279 QPainter cachePainter(&cache.pixmap); | |
2280 cachePainter.drawImage(x0, 0, m_drawBuffer, 0, 0, w, h); | |
2281 cachePainter.end(); | |
2282 | |
2283 if (!m_normalizeVisibleArea || !overallMagChanged) { | |
2284 | |
2285 cache.startFrame = startFrame; | |
2286 cache.zoomLevel = zoomLevel; | |
2287 | |
2288 if (cache.validArea.x() > 0) { | |
2289 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2290 std::cerr << "SpectrogramLayer::paint() updating left (0, " | |
2291 << cache.validArea.x() << ")" << std::endl; | |
2292 #endif | |
2293 v->update(0, 0, cache.validArea.x(), h); | |
2294 } | |
2295 | |
2296 if (cache.validArea.x() + cache.validArea.width() < | |
2297 cache.pixmap.width()) { | |
2298 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2299 std::cerr << "SpectrogramLayer::paint() updating right (" | |
2300 << cache.validArea.x() + cache.validArea.width() | |
2301 << ", " | |
2302 << cache.pixmap.width() - (cache.validArea.x() + | |
2303 cache.validArea.width()) | |
2304 << ")" << std::endl; | |
2305 #endif | |
2306 v->update(cache.validArea.x() + cache.validArea.width(), | |
2307 0, | |
2308 cache.pixmap.width() - (cache.validArea.x() + | |
2309 cache.validArea.width()), | |
2310 h); | |
2311 } | |
2312 } else { | |
2313 // overallMagChanged | |
2314 cache.validArea = QRect(); | |
2315 v->update(); | |
2316 } | |
2317 | |
2318 illuminateLocalFeatures(v, paint); | |
2319 | |
2320 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2321 std::cerr << "SpectrogramLayer::paint() returning" << std::endl; | |
2322 #endif | |
2323 | |
2324 m_lastPaintBlockWidth = paintBlockWidth; | |
2325 (void)gettimeofday(&tv, 0); | |
2326 m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart; | |
2327 | |
2328 if (fftSuspended) fft->resume(); | |
2329 } | |
2330 | |
2331 void | |
2332 SpectrogramLayer::illuminateLocalFeatures(View *v, QPainter &paint) const | |
2333 { | |
2334 QPoint localPos; | |
2335 if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) { | |
2336 return; | |
2337 } | |
2338 | |
2339 // std::cerr << "SpectrogramLayer: illuminateLocalFeatures(" | |
2340 // << localPos.x() << "," << localPos.y() << ")" << std::endl; | |
2341 | |
2342 float s0, s1; | |
2343 float f0, f1; | |
2344 | |
2345 if (getXBinRange(v, localPos.x(), s0, s1) && | |
2346 getYBinSourceRange(v, localPos.y(), f0, f1)) { | |
2347 | |
2348 int s0i = int(s0 + 0.001); | |
2349 int s1i = int(s1); | |
2350 | |
2351 int x0 = v->getXForFrame(s0i * getWindowIncrement()); | |
2352 int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement()); | |
2353 | |
2354 int y1 = int(getYForFrequency(v, f1)); | |
2355 int y0 = int(getYForFrequency(v, f0)); | |
2356 | |
2357 // std::cerr << "SpectrogramLayer: illuminate " | |
2358 // << x0 << "," << y1 << " -> " << x1 << "," << y0 << std::endl; | |
2359 | |
2360 paint.setPen(Qt::white); | |
2361 | |
2362 //!!! should we be using paintCrosshairs for this? | |
2363 | |
2364 paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1); | |
2365 } | |
2366 } | |
2367 | |
2368 float | |
2369 SpectrogramLayer::getYForFrequency(View *v, float frequency) const | |
2370 { | |
2371 return v->getYForFrequency(frequency, | |
2372 getEffectiveMinFrequency(), | |
2373 getEffectiveMaxFrequency(), | |
2374 m_frequencyScale == LogFrequencyScale); | |
2375 } | |
2376 | |
2377 float | |
2378 SpectrogramLayer::getFrequencyForY(View *v, int y) const | |
2379 { | |
2380 return v->getFrequencyForY(y, | |
2381 getEffectiveMinFrequency(), | |
2382 getEffectiveMaxFrequency(), | |
2383 m_frequencyScale == LogFrequencyScale); | |
2384 } | |
2385 | |
2386 int | |
2387 SpectrogramLayer::getCompletion(View *v) const | |
2388 { | |
2389 if (m_updateTimer == 0) return 100; | |
2390 if (m_fftModels.find(v) == m_fftModels.end()) return 100; | |
2391 | |
2392 size_t completion = m_fftModels[v].first->getCompletion(); | |
2393 #ifdef DEBUG_SPECTROGRAM_REPAINT | |
2394 std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl; | |
2395 #endif | |
2396 return completion; | |
2397 } | |
2398 | |
2399 bool | |
2400 SpectrogramLayer::getValueExtents(float &min, float &max, | |
2401 bool &logarithmic, QString &unit) const | |
2402 { | |
2403 if (!m_model) return false; | |
2404 | |
2405 int sr = m_model->getSampleRate(); | |
2406 min = float(sr) / m_fftSize; | |
2407 max = float(sr) / 2; | |
2408 | |
2409 logarithmic = (m_frequencyScale == LogFrequencyScale); | |
2410 unit = "Hz"; | |
2411 return true; | |
2412 } | |
2413 | |
2414 bool | |
2415 SpectrogramLayer::getDisplayExtents(float &min, float &max) const | |
2416 { | |
2417 min = getEffectiveMinFrequency(); | |
2418 max = getEffectiveMaxFrequency(); | |
2419 // std::cerr << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << std::endl; | |
2420 return true; | |
2421 } | |
2422 | |
2423 bool | |
2424 SpectrogramLayer::setDisplayExtents(float min, float max) | |
2425 { | |
2426 if (!m_model) return false; | |
2427 | |
2428 std::cerr << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << std::endl; | |
2429 | |
2430 if (min < 0) min = 0; | |
2431 if (max > m_model->getSampleRate()/2) max = m_model->getSampleRate()/2; | |
2432 | |
2433 size_t minf = lrintf(min); | |
2434 size_t maxf = lrintf(max); | |
2435 | |
2436 if (m_minFrequency == minf && m_maxFrequency == maxf) return true; | |
2437 | |
2438 invalidatePixmapCaches(); | |
2439 invalidateMagnitudes(); | |
2440 | |
2441 m_minFrequency = minf; | |
2442 m_maxFrequency = maxf; | |
2443 | |
2444 emit layerParametersChanged(); | |
2445 | |
2446 int vs = getCurrentVerticalZoomStep(); | |
2447 if (vs != m_lastEmittedZoomStep) { | |
2448 emit verticalZoomChanged(); | |
2449 m_lastEmittedZoomStep = vs; | |
2450 } | |
2451 | |
2452 return true; | |
2453 } | |
2454 | |
2455 bool | |
2456 SpectrogramLayer::snapToFeatureFrame(View *, int &frame, | |
2457 size_t &resolution, | |
2458 SnapType snap) const | |
2459 { | |
2460 resolution = getWindowIncrement(); | |
2461 int left = (frame / resolution) * resolution; | |
2462 int right = left + resolution; | |
2463 | |
2464 switch (snap) { | |
2465 case SnapLeft: frame = left; break; | |
2466 case SnapRight: frame = right; break; | |
2467 case SnapNearest: | |
2468 case SnapNeighbouring: | |
2469 if (frame - left > right - frame) frame = right; | |
2470 else frame = left; | |
2471 break; | |
2472 } | |
2473 | |
2474 return true; | |
2475 } | |
2476 | |
2477 bool | |
2478 SpectrogramLayer::getCrosshairExtents(View *v, QPainter &, | |
2479 QPoint cursorPos, | |
2480 std::vector<QRect> &extents) const | |
2481 { | |
2482 QRect vertical(cursorPos.x() - 12, 0, 12, v->height()); | |
2483 extents.push_back(vertical); | |
2484 | |
2485 QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1); | |
2486 extents.push_back(horizontal); | |
2487 | |
2488 return true; | |
2489 } | |
2490 | |
2491 void | |
2492 SpectrogramLayer::paintCrosshairs(View *v, QPainter &paint, | |
2493 QPoint cursorPos) const | |
2494 { | |
2495 paint.save(); | |
2496 paint.setPen(m_crosshairColour); | |
2497 | |
2498 paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y()); | |
2499 paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->height()); | |
2500 | |
2501 float fundamental = getFrequencyForY(v, cursorPos.y()); | |
2502 | |
2503 int harmonic = 2; | |
2504 | |
2505 while (harmonic < 100) { | |
2506 | |
2507 float hy = lrintf(getYForFrequency(v, fundamental * harmonic)); | |
2508 if (hy < 0 || hy > v->height()) break; | |
2509 | |
2510 int len = 7; | |
2511 | |
2512 if (harmonic % 2 == 0) { | |
2513 if (harmonic % 4 == 0) { | |
2514 len = 12; | |
2515 } else { | |
2516 len = 10; | |
2517 } | |
2518 } | |
2519 | |
2520 paint.drawLine(cursorPos.x() - len, | |
2521 int(hy), | |
2522 cursorPos.x(), | |
2523 int(hy)); | |
2524 | |
2525 ++harmonic; | |
2526 } | |
2527 | |
2528 paint.restore(); | |
2529 } | |
2530 | |
2531 QString | |
2532 SpectrogramLayer::getFeatureDescription(View *v, QPoint &pos) const | |
2533 { | |
2534 int x = pos.x(); | |
2535 int y = pos.y(); | |
2536 | |
2537 if (!m_model || !m_model->isOK()) return ""; | |
2538 | |
2539 float magMin = 0, magMax = 0; | |
2540 float phaseMin = 0, phaseMax = 0; | |
2541 float freqMin = 0, freqMax = 0; | |
2542 float adjFreqMin = 0, adjFreqMax = 0; | |
2543 QString pitchMin, pitchMax; | |
2544 RealTime rtMin, rtMax; | |
2545 | |
2546 bool haveValues = false; | |
2547 | |
2548 if (!getXBinSourceRange(v, x, rtMin, rtMax)) { | |
2549 return ""; | |
2550 } | |
2551 if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) { | |
2552 haveValues = true; | |
2553 } | |
2554 | |
2555 QString adjFreqText = "", adjPitchText = ""; | |
2556 | |
2557 if (m_binDisplay == PeakFrequencies) { | |
2558 | |
2559 if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, | |
2560 adjFreqMin, adjFreqMax)) { | |
2561 return ""; | |
2562 } | |
2563 | |
2564 if (adjFreqMin != adjFreqMax) { | |
2565 adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n") | |
2566 .arg(adjFreqMin).arg(adjFreqMax); | |
2567 } else { | |
2568 adjFreqText = tr("Peak Frequency:\t%1 Hz\n") | |
2569 .arg(adjFreqMin); | |
2570 } | |
2571 | |
2572 QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin); | |
2573 QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax); | |
2574 | |
2575 if (pmin != pmax) { | |
2576 adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax); | |
2577 } else { | |
2578 adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin); | |
2579 } | |
2580 | |
2581 } else { | |
2582 | |
2583 if (!getYBinSourceRange(v, y, freqMin, freqMax)) return ""; | |
2584 } | |
2585 | |
2586 QString text; | |
2587 | |
2588 if (rtMin != rtMax) { | |
2589 text += tr("Time:\t%1 - %2\n") | |
2590 .arg(rtMin.toText(true).c_str()) | |
2591 .arg(rtMax.toText(true).c_str()); | |
2592 } else { | |
2593 text += tr("Time:\t%1\n") | |
2594 .arg(rtMin.toText(true).c_str()); | |
2595 } | |
2596 | |
2597 if (freqMin != freqMax) { | |
2598 text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n") | |
2599 .arg(adjFreqText) | |
2600 .arg(freqMin) | |
2601 .arg(freqMax) | |
2602 .arg(adjPitchText) | |
2603 .arg(Pitch::getPitchLabelForFrequency(freqMin)) | |
2604 .arg(Pitch::getPitchLabelForFrequency(freqMax)); | |
2605 } else { | |
2606 text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n") | |
2607 .arg(adjFreqText) | |
2608 .arg(freqMin) | |
2609 .arg(adjPitchText) | |
2610 .arg(Pitch::getPitchLabelForFrequency(freqMin)); | |
2611 } | |
2612 | |
2613 if (haveValues) { | |
2614 float dbMin = AudioLevel::multiplier_to_dB(magMin); | |
2615 float dbMax = AudioLevel::multiplier_to_dB(magMax); | |
2616 QString dbMinString; | |
2617 QString dbMaxString; | |
2618 if (dbMin == AudioLevel::DB_FLOOR) { | |
2619 dbMinString = tr("-Inf"); | |
2620 } else { | |
2621 dbMinString = QString("%1").arg(lrintf(dbMin)); | |
2622 } | |
2623 if (dbMax == AudioLevel::DB_FLOOR) { | |
2624 dbMaxString = tr("-Inf"); | |
2625 } else { | |
2626 dbMaxString = QString("%1").arg(lrintf(dbMax)); | |
2627 } | |
2628 if (lrintf(dbMin) != lrintf(dbMax)) { | |
2629 text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString); | |
2630 } else { | |
2631 text += tr("dB:\t%1").arg(dbMinString); | |
2632 } | |
2633 if (phaseMin != phaseMax) { | |
2634 text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax); | |
2635 } else { | |
2636 text += tr("\nPhase:\t%1").arg(phaseMin); | |
2637 } | |
2638 } | |
2639 | |
2640 return text; | |
2641 } | |
2642 | |
2643 int | |
2644 SpectrogramLayer::getColourScaleWidth(QPainter &paint) const | |
2645 { | |
2646 int cw; | |
2647 | |
2648 cw = paint.fontMetrics().width("-80dB"); | |
2649 | |
2650 return cw; | |
2651 } | |
2652 | |
2653 int | |
2654 SpectrogramLayer::getVerticalScaleWidth(View *, QPainter &paint) const | |
2655 { | |
2656 if (!m_model || !m_model->isOK()) return 0; | |
2657 | |
2658 int cw = getColourScaleWidth(paint); | |
2659 | |
2660 int tw = paint.fontMetrics().width(QString("%1") | |
2661 .arg(m_maxFrequency > 0 ? | |
2662 m_maxFrequency - 1 : | |
2663 m_model->getSampleRate() / 2)); | |
2664 | |
2665 int fw = paint.fontMetrics().width(tr("43Hz")); | |
2666 if (tw < fw) tw = fw; | |
2667 | |
2668 int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); | |
2669 | |
2670 return cw + tickw + tw + 13; | |
2671 } | |
2672 | |
2673 void | |
2674 SpectrogramLayer::paintVerticalScale(View *v, QPainter &paint, QRect rect) const | |
2675 { | |
2676 if (!m_model || !m_model->isOK()) { | |
2677 return; | |
2678 } | |
2679 | |
2680 Profiler profiler("SpectrogramLayer::paintVerticalScale", true); | |
2681 | |
2682 //!!! cache this? | |
2683 | |
2684 int h = rect.height(), w = rect.width(); | |
2685 | |
2686 int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); | |
2687 int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); | |
2688 | |
2689 size_t bins = m_fftSize / 2; | |
2690 int sr = m_model->getSampleRate(); | |
2691 | |
2692 if (m_maxFrequency > 0) { | |
2693 bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | |
2694 if (bins > m_fftSize / 2) bins = m_fftSize / 2; | |
2695 } | |
2696 | |
2697 int cw = getColourScaleWidth(paint); | |
2698 int cbw = paint.fontMetrics().width("dB"); | |
2699 | |
2700 int py = -1; | |
2701 int textHeight = paint.fontMetrics().height(); | |
2702 int toff = -textHeight + paint.fontMetrics().ascent() + 2; | |
2703 | |
2704 if (h > textHeight * 3 + 10) { | |
2705 | |
2706 int topLines = 2; | |
2707 if (m_colourScale == PhaseColourScale) topLines = 1; | |
2708 | |
2709 int ch = h - textHeight * (topLines + 1) - 8; | |
2710 // paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); | |
2711 paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); | |
2712 | |
2713 QString top, bottom; | |
2714 float min = m_viewMags[v].getMin(); | |
2715 float max = m_viewMags[v].getMax(); | |
2716 | |
2717 float dBmin = AudioLevel::multiplier_to_dB(min); | |
2718 float dBmax = AudioLevel::multiplier_to_dB(max); | |
2719 | |
2720 if (dBmax < -60.f) dBmax = -60.f; | |
2721 else top = QString("%1").arg(lrintf(dBmax)); | |
2722 | |
2723 if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; | |
2724 bottom = QString("%1").arg(lrintf(dBmin)); | |
2725 | |
2726 //!!! & phase etc | |
2727 | |
2728 if (m_colourScale != PhaseColourScale) { | |
2729 paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, | |
2730 2 + textHeight + toff, "dBFS"); | |
2731 } | |
2732 | |
2733 // paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, | |
2734 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), | |
2735 2 + textHeight * topLines + toff + textHeight/2, top); | |
2736 | |
2737 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), | |
2738 h + toff - 3 - textHeight/2, bottom); | |
2739 | |
2740 paint.save(); | |
2741 paint.setBrush(Qt::NoBrush); | |
2742 | |
2743 int lasty = 0; | |
2744 int lastdb = 0; | |
2745 | |
2746 for (int i = 0; i < ch; ++i) { | |
2747 | |
2748 float dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); | |
2749 int idb = int(dBval); | |
2750 | |
2751 float value = AudioLevel::dB_to_multiplier(dBval); | |
2752 int colour = getDisplayValue(v, value * m_gain); | |
2753 | |
2754 paint.setPen(m_palette.getColour(colour)); | |
2755 | |
2756 int y = textHeight * topLines + 4 + ch - i; | |
2757 | |
2758 paint.drawLine(5 + cw - cbw, y, cw + 2, y); | |
2759 | |
2760 if (i == 0) { | |
2761 lasty = y; | |
2762 lastdb = idb; | |
2763 } else if (i < ch - paint.fontMetrics().ascent() && | |
2764 idb != lastdb && | |
2765 ((abs(y - lasty) > textHeight && | |
2766 idb % 10 == 0) || | |
2767 (abs(y - lasty) > paint.fontMetrics().ascent() && | |
2768 idb % 5 == 0))) { | |
2769 paint.setPen(Qt::black); | |
2770 QString text = QString("%1").arg(idb); | |
2771 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), | |
2772 y + toff + textHeight/2, text); | |
2773 paint.setPen(Qt::white); | |
2774 paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); | |
2775 lasty = y; | |
2776 lastdb = idb; | |
2777 } | |
2778 } | |
2779 paint.restore(); | |
2780 } | |
2781 | |
2782 paint.drawLine(cw + 7, 0, cw + 7, h); | |
2783 | |
2784 int bin = -1; | |
2785 | |
2786 for (int y = 0; y < v->height(); ++y) { | |
2787 | |
2788 float q0, q1; | |
2789 if (!getYBinRange(v, v->height() - y, q0, q1)) continue; | |
2790 | |
2791 int vy; | |
2792 | |
2793 if (int(q0) > bin) { | |
2794 vy = y; | |
2795 bin = int(q0); | |
2796 } else { | |
2797 continue; | |
2798 } | |
2799 | |
2800 int freq = (sr * bin) / m_fftSize; | |
2801 | |
2802 if (py >= 0 && (vy - py) < textHeight - 1) { | |
2803 if (m_frequencyScale == LinearFrequencyScale) { | |
2804 paint.drawLine(w - tickw, h - vy, w, h - vy); | |
2805 } | |
2806 continue; | |
2807 } | |
2808 | |
2809 QString text = QString("%1").arg(freq); | |
2810 if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC | |
2811 paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); | |
2812 | |
2813 if (h - vy - textHeight >= -2) { | |
2814 int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw); | |
2815 paint.drawText(tx, h - vy + toff, text); | |
2816 } | |
2817 | |
2818 py = vy; | |
2819 } | |
2820 | |
2821 if (m_frequencyScale == LogFrequencyScale) { | |
2822 | |
2823 paint.drawLine(w - pkw - 1, 0, w - pkw - 1, h); | |
2824 | |
2825 float minf = getEffectiveMinFrequency(); | |
2826 float maxf = getEffectiveMaxFrequency(); | |
2827 | |
2828 int py = h, ppy = h; | |
2829 paint.setBrush(paint.pen().color()); | |
2830 | |
2831 for (int i = 0; i < 128; ++i) { | |
2832 | |
2833 float f = Pitch::getFrequencyForPitch(i); | |
2834 int y = lrintf(v->getYForFrequency(f, minf, maxf, true)); | |
2835 | |
2836 if (y < -2) break; | |
2837 if (y > h + 2) { | |
2838 continue; | |
2839 } | |
2840 | |
2841 int n = (i % 12); | |
2842 | |
2843 if (n == 1) { | |
2844 // C# -- fill the C from here | |
2845 if (ppy - y > 2) { | |
2846 paint.fillRect(w - pkw, | |
2847 // y - (py - y) / 2 - (py - y) / 4, | |
2848 y, | |
2849 pkw, | |
2850 (py + ppy) / 2 - y, | |
2851 // py - y + 1, | |
2852 Qt::gray); | |
2853 } | |
2854 } | |
2855 | |
2856 if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) { | |
2857 // black notes | |
2858 paint.drawLine(w - pkw, y, w, y); | |
2859 int rh = ((py - y) / 4) * 2; | |
2860 if (rh < 2) rh = 2; | |
2861 paint.drawRect(w - pkw, y - (py-y)/4, pkw/2, rh); | |
2862 } else if (n == 0 || n == 5) { | |
2863 // C, F | |
2864 if (py < h) { | |
2865 paint.drawLine(w - pkw, (y + py) / 2, w, (y + py) / 2); | |
2866 } | |
2867 } | |
2868 | |
2869 ppy = py; | |
2870 py = y; | |
2871 } | |
2872 } | |
2873 } | |
2874 | |
2875 class SpectrogramRangeMapper : public RangeMapper | |
2876 { | |
2877 public: | |
2878 SpectrogramRangeMapper(int sr, int /* fftsize */) : | |
2879 m_dist(float(sr) / 2), | |
2880 m_s2(sqrtf(sqrtf(2))) { } | |
2881 ~SpectrogramRangeMapper() { } | |
2882 | |
2883 virtual int getPositionForValue(float value) const { | |
2884 | |
2885 float dist = m_dist; | |
2886 | |
2887 int n = 0; | |
2888 | |
2889 while (dist > (value + 0.00001) && dist > 0.1f) { | |
2890 dist /= m_s2; | |
2891 ++n; | |
2892 } | |
2893 | |
2894 return n; | |
2895 } | |
2896 | |
2897 virtual float getValueForPosition(int position) const { | |
2898 | |
2899 // Vertical zoom step 0 shows the entire range from DC -> | |
2900 // Nyquist frequency. Step 1 shows 2^(1/4) of the range of | |
2901 // step 0, and so on until the visible range is smaller than | |
2902 // the frequency step between bins at the current fft size. | |
2903 | |
2904 float dist = m_dist; | |
2905 | |
2906 int n = 0; | |
2907 while (n < position) { | |
2908 dist /= m_s2; | |
2909 ++n; | |
2910 } | |
2911 | |
2912 return dist; | |
2913 } | |
2914 | |
2915 virtual QString getUnit() const { return "Hz"; } | |
2916 | |
2917 protected: | |
2918 float m_dist; | |
2919 float m_s2; | |
2920 }; | |
2921 | |
2922 int | |
2923 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const | |
2924 { | |
2925 if (!m_model) return 0; | |
2926 | |
2927 int sr = m_model->getSampleRate(); | |
2928 | |
2929 SpectrogramRangeMapper mapper(sr, m_fftSize); | |
2930 | |
2931 // int maxStep = mapper.getPositionForValue((float(sr) / m_fftSize) + 0.001); | |
2932 int maxStep = mapper.getPositionForValue(0); | |
2933 int minStep = mapper.getPositionForValue(float(sr) / 2); | |
2934 | |
2935 defaultStep = mapper.getPositionForValue(m_initialMaxFrequency) - minStep; | |
2936 | |
2937 // std::cerr << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << m_initialMaxFrequency << ")" << std::endl; | |
2938 | |
2939 return maxStep - minStep; | |
2940 } | |
2941 | |
2942 int | |
2943 SpectrogramLayer::getCurrentVerticalZoomStep() const | |
2944 { | |
2945 if (!m_model) return 0; | |
2946 | |
2947 float dmin, dmax; | |
2948 getDisplayExtents(dmin, dmax); | |
2949 | |
2950 SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); | |
2951 int n = mapper.getPositionForValue(dmax - dmin); | |
2952 // std::cerr << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << std::endl; | |
2953 return n; | |
2954 } | |
2955 | |
2956 void | |
2957 SpectrogramLayer::setVerticalZoomStep(int step) | |
2958 { | |
2959 //!!! does not do the right thing for log scale | |
2960 | |
2961 if (!m_model) return; | |
2962 | |
2963 float dmin, dmax; | |
2964 getDisplayExtents(dmin, dmax); | |
2965 | |
2966 int sr = m_model->getSampleRate(); | |
2967 SpectrogramRangeMapper mapper(sr, m_fftSize); | |
2968 float ddist = mapper.getValueForPosition(step); | |
2969 | |
2970 float dmid = (dmax + dmin) / 2; | |
2971 float newmin = dmid - ddist / 2; | |
2972 float newmax = dmid + ddist / 2; | |
2973 | |
2974 float mmin, mmax; | |
2975 mmin = 0; | |
2976 mmax = float(sr) / 2; | |
2977 | |
2978 if (newmin < mmin) { | |
2979 newmax += (mmin - newmin); | |
2980 newmin = mmin; | |
2981 } | |
2982 if (newmax > mmax) { | |
2983 newmax = mmax; | |
2984 } | |
2985 | |
2986 // std::cerr << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << ddist << ")" << std::endl; | |
2987 | |
2988 setMinFrequency(int(newmin)); | |
2989 setMaxFrequency(int(newmax)); | |
2990 } | |
2991 | |
2992 RangeMapper * | |
2993 SpectrogramLayer::getNewVerticalZoomRangeMapper() const | |
2994 { | |
2995 if (!m_model) return 0; | |
2996 return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); | |
2997 } | |
2998 | |
2999 QString | |
3000 SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const | |
3001 { | |
3002 QString s; | |
3003 | |
3004 s += QString("channel=\"%1\" " | |
3005 "windowSize=\"%2\" " | |
3006 "windowHopLevel=\"%3\" " | |
3007 "gain=\"%4\" " | |
3008 "threshold=\"%5\" ") | |
3009 .arg(m_channel) | |
3010 .arg(m_windowSize) | |
3011 .arg(m_windowHopLevel) | |
3012 .arg(m_gain) | |
3013 .arg(m_threshold); | |
3014 | |
3015 s += QString("minFrequency=\"%1\" " | |
3016 "maxFrequency=\"%2\" " | |
3017 "colourScale=\"%3\" " | |
3018 "colourScheme=\"%4\" " | |
3019 "colourRotation=\"%5\" " | |
3020 "frequencyScale=\"%6\" " | |
3021 "binDisplay=\"%7\" " | |
3022 "normalizeColumns=\"%8\" " | |
3023 "normalizeVisibleArea=\"%9\"") | |
3024 .arg(m_minFrequency) | |
3025 .arg(m_maxFrequency) | |
3026 .arg(m_colourScale) | |
3027 .arg(m_colourMap) | |
3028 .arg(m_colourRotation) | |
3029 .arg(m_frequencyScale) | |
3030 .arg(m_binDisplay) | |
3031 .arg(m_normalizeColumns ? "true" : "false") | |
3032 .arg(m_normalizeVisibleArea ? "true" : "false"); | |
3033 | |
3034 return Layer::toXmlString(indent, extraAttributes + " " + s); | |
3035 } | |
3036 | |
3037 void | |
3038 SpectrogramLayer::setProperties(const QXmlAttributes &attributes) | |
3039 { | |
3040 bool ok = false; | |
3041 | |
3042 int channel = attributes.value("channel").toInt(&ok); | |
3043 if (ok) setChannel(channel); | |
3044 | |
3045 size_t windowSize = attributes.value("windowSize").toUInt(&ok); | |
3046 if (ok) setWindowSize(windowSize); | |
3047 | |
3048 size_t windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); | |
3049 if (ok) setWindowHopLevel(windowHopLevel); | |
3050 else { | |
3051 size_t windowOverlap = attributes.value("windowOverlap").toUInt(&ok); | |
3052 // a percentage value | |
3053 if (ok) { | |
3054 if (windowOverlap == 0) setWindowHopLevel(0); | |
3055 else if (windowOverlap == 25) setWindowHopLevel(1); | |
3056 else if (windowOverlap == 50) setWindowHopLevel(2); | |
3057 else if (windowOverlap == 75) setWindowHopLevel(3); | |
3058 else if (windowOverlap == 90) setWindowHopLevel(4); | |
3059 } | |
3060 } | |
3061 | |
3062 float gain = attributes.value("gain").toFloat(&ok); | |
3063 if (ok) setGain(gain); | |
3064 | |
3065 float threshold = attributes.value("threshold").toFloat(&ok); | |
3066 if (ok) setThreshold(threshold); | |
3067 | |
3068 size_t minFrequency = attributes.value("minFrequency").toUInt(&ok); | |
3069 if (ok) { | |
3070 std::cerr << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << std::endl; | |
3071 setMinFrequency(minFrequency); | |
3072 } | |
3073 | |
3074 size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok); | |
3075 if (ok) { | |
3076 std::cerr << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << std::endl; | |
3077 setMaxFrequency(maxFrequency); | |
3078 } | |
3079 | |
3080 ColourScale colourScale = (ColourScale) | |
3081 attributes.value("colourScale").toInt(&ok); | |
3082 if (ok) setColourScale(colourScale); | |
3083 | |
3084 int colourMap = attributes.value("colourScheme").toInt(&ok); | |
3085 if (ok) setColourMap(colourMap); | |
3086 | |
3087 int colourRotation = attributes.value("colourRotation").toInt(&ok); | |
3088 if (ok) setColourRotation(colourRotation); | |
3089 | |
3090 FrequencyScale frequencyScale = (FrequencyScale) | |
3091 attributes.value("frequencyScale").toInt(&ok); | |
3092 if (ok) setFrequencyScale(frequencyScale); | |
3093 | |
3094 BinDisplay binDisplay = (BinDisplay) | |
3095 attributes.value("binDisplay").toInt(&ok); | |
3096 if (ok) setBinDisplay(binDisplay); | |
3097 | |
3098 bool normalizeColumns = | |
3099 (attributes.value("normalizeColumns").trimmed() == "true"); | |
3100 setNormalizeColumns(normalizeColumns); | |
3101 | |
3102 bool normalizeVisibleArea = | |
3103 (attributes.value("normalizeVisibleArea").trimmed() == "true"); | |
3104 setNormalizeVisibleArea(normalizeVisibleArea); | |
3105 } | |
3106 |