Chris@0
|
1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 A waveform viewer and audio annotation editor.
|
Chris@5
|
5 Chris Cannam, Queen Mary University of London, 2005-2006
|
Chris@0
|
6
|
Chris@0
|
7 This is experimental software. Not for distribution.
|
Chris@0
|
8 */
|
Chris@0
|
9
|
Chris@0
|
10 #include "SpectrogramLayer.h"
|
Chris@0
|
11
|
Chris@0
|
12 #include "base/View.h"
|
Chris@0
|
13 #include "base/Profiler.h"
|
Chris@0
|
14 #include "base/AudioLevel.h"
|
Chris@0
|
15 #include "base/Window.h"
|
Chris@24
|
16 #include "base/Pitch.h"
|
Chris@0
|
17
|
Chris@35
|
18 #include "dsp/maths/MathUtilities.h"
|
Chris@35
|
19
|
Chris@0
|
20 #include <QPainter>
|
Chris@0
|
21 #include <QImage>
|
Chris@0
|
22 #include <QPixmap>
|
Chris@0
|
23 #include <QRect>
|
Chris@0
|
24 #include <QTimer>
|
Chris@0
|
25
|
Chris@0
|
26 #include <iostream>
|
Chris@0
|
27
|
Chris@0
|
28 #include <cassert>
|
Chris@0
|
29 #include <cmath>
|
Chris@0
|
30
|
Chris@0
|
31 //#define DEBUG_SPECTROGRAM_REPAINT 1
|
Chris@0
|
32
|
Chris@0
|
33
|
Chris@0
|
34 SpectrogramLayer::SpectrogramLayer(View *w, Configuration config) :
|
Chris@0
|
35 Layer(w),
|
Chris@0
|
36 m_model(0),
|
Chris@0
|
37 m_channel(0),
|
Chris@0
|
38 m_windowSize(1024),
|
Chris@0
|
39 m_windowType(HanningWindow),
|
Chris@0
|
40 m_windowOverlap(50),
|
Chris@0
|
41 m_gain(1.0),
|
Chris@37
|
42 m_threshold(0.0),
|
Chris@9
|
43 m_colourRotation(0),
|
Chris@37
|
44 m_minFrequency(0),
|
Chris@0
|
45 m_maxFrequency(8000),
|
Chris@0
|
46 m_colourScale(dBColourScale),
|
Chris@0
|
47 m_colourScheme(DefaultColours),
|
Chris@0
|
48 m_frequencyScale(LinearFrequencyScale),
|
Chris@37
|
49 m_binDisplay(AllBins),
|
Chris@36
|
50 m_normalizeColumns(false),
|
Chris@0
|
51 m_cache(0),
|
Chris@35
|
52 m_phaseAdjustCache(0),
|
Chris@0
|
53 m_cacheInvalid(true),
|
Chris@0
|
54 m_pixmapCache(0),
|
Chris@0
|
55 m_pixmapCacheInvalid(true),
|
Chris@0
|
56 m_fillThread(0),
|
Chris@0
|
57 m_updateTimer(0),
|
Chris@0
|
58 m_lastFillExtent(0),
|
Chris@0
|
59 m_exiting(false)
|
Chris@0
|
60 {
|
Chris@0
|
61 if (config == MelodicRange) {
|
Chris@0
|
62 setWindowSize(8192);
|
Chris@0
|
63 setWindowOverlap(90);
|
Chris@0
|
64 setWindowType(ParzenWindow);
|
Chris@0
|
65 setMaxFrequency(1000);
|
Chris@0
|
66 setColourScale(LinearColourScale);
|
Chris@37
|
67 } else if (config == MelodicPeaks) {
|
Chris@37
|
68 setWindowSize(4096);
|
Chris@37
|
69 setWindowOverlap(90);
|
Chris@37
|
70 setWindowType(BlackmanWindow);
|
Chris@37
|
71 setMaxFrequency(1500);
|
Chris@37
|
72 setMinFrequency(40);
|
Chris@37
|
73 setFrequencyScale(LogFrequencyScale);
|
Chris@37
|
74 setColourScale(dBColourScale);
|
Chris@37
|
75 setBinDisplay(PeakFrequencies);
|
Chris@37
|
76 setNormalizeColumns(true);
|
Chris@0
|
77 }
|
Chris@0
|
78
|
Chris@0
|
79 if (m_view) m_view->setLightBackground(false);
|
Chris@0
|
80 m_view->addLayer(this);
|
Chris@0
|
81 }
|
Chris@0
|
82
|
Chris@0
|
83 SpectrogramLayer::~SpectrogramLayer()
|
Chris@0
|
84 {
|
Chris@0
|
85 delete m_updateTimer;
|
Chris@0
|
86 m_updateTimer = 0;
|
Chris@0
|
87
|
Chris@0
|
88 m_exiting = true;
|
Chris@0
|
89 m_condition.wakeAll();
|
Chris@0
|
90 if (m_fillThread) m_fillThread->wait();
|
Chris@0
|
91 delete m_fillThread;
|
Chris@0
|
92
|
Chris@0
|
93 delete m_cache;
|
Chris@35
|
94 delete m_phaseAdjustCache;
|
Chris@0
|
95 }
|
Chris@0
|
96
|
Chris@0
|
97 void
|
Chris@0
|
98 SpectrogramLayer::setModel(const DenseTimeValueModel *model)
|
Chris@0
|
99 {
|
Chris@34
|
100 std::cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << std::endl;
|
Chris@34
|
101
|
Chris@0
|
102 m_mutex.lock();
|
Chris@35
|
103 m_cacheInvalid = true;
|
Chris@0
|
104 m_model = model;
|
Chris@34
|
105 delete m_cache; //!!! hang on, this isn't safe to do here is it?
|
Chris@34
|
106 // we need some sort of guard against the fill
|
Chris@34
|
107 // thread trying to read the defunct model too.
|
Chris@34
|
108 // should we use a scavenger?
|
Chris@31
|
109 m_cache = 0;
|
Chris@35
|
110 delete m_phaseAdjustCache; //!!! likewise
|
Chris@35
|
111 m_phaseAdjustCache = 0;
|
Chris@0
|
112 m_mutex.unlock();
|
Chris@0
|
113
|
Chris@0
|
114 if (!m_model || !m_model->isOK()) return;
|
Chris@0
|
115
|
Chris@0
|
116 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
|
Chris@0
|
117 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
|
Chris@0
|
118 this, SIGNAL(modelChanged(size_t, size_t)));
|
Chris@0
|
119
|
Chris@0
|
120 connect(m_model, SIGNAL(completionChanged()),
|
Chris@0
|
121 this, SIGNAL(modelCompletionChanged()));
|
Chris@0
|
122
|
Chris@0
|
123 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
|
Chris@0
|
124 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
|
Chris@0
|
125 this, SLOT(cacheInvalid(size_t, size_t)));
|
Chris@0
|
126
|
Chris@0
|
127 emit modelReplaced();
|
Chris@0
|
128 fillCache();
|
Chris@0
|
129 }
|
Chris@0
|
130
|
Chris@0
|
131 Layer::PropertyList
|
Chris@0
|
132 SpectrogramLayer::getProperties() const
|
Chris@0
|
133 {
|
Chris@0
|
134 PropertyList list;
|
Chris@0
|
135 list.push_back(tr("Colour"));
|
Chris@0
|
136 list.push_back(tr("Colour Scale"));
|
Chris@0
|
137 list.push_back(tr("Window Type"));
|
Chris@0
|
138 list.push_back(tr("Window Size"));
|
Chris@0
|
139 list.push_back(tr("Window Overlap"));
|
Chris@36
|
140 list.push_back(tr("Normalize"));
|
Chris@37
|
141 list.push_back(tr("Bin Display"));
|
Chris@37
|
142 list.push_back(tr("Threshold"));
|
Chris@0
|
143 list.push_back(tr("Gain"));
|
Chris@9
|
144 list.push_back(tr("Colour Rotation"));
|
Chris@37
|
145 list.push_back(tr("Min Frequency"));
|
Chris@0
|
146 list.push_back(tr("Max Frequency"));
|
Chris@0
|
147 list.push_back(tr("Frequency Scale"));
|
Chris@0
|
148 return list;
|
Chris@0
|
149 }
|
Chris@0
|
150
|
Chris@0
|
151 Layer::PropertyType
|
Chris@0
|
152 SpectrogramLayer::getPropertyType(const PropertyName &name) const
|
Chris@0
|
153 {
|
Chris@0
|
154 if (name == tr("Gain")) return RangeProperty;
|
Chris@9
|
155 if (name == tr("Colour Rotation")) return RangeProperty;
|
Chris@36
|
156 if (name == tr("Normalize")) return ToggleProperty;
|
Chris@37
|
157 if (name == tr("Threshold")) return RangeProperty;
|
Chris@0
|
158 return ValueProperty;
|
Chris@0
|
159 }
|
Chris@0
|
160
|
Chris@0
|
161 QString
|
Chris@0
|
162 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const
|
Chris@0
|
163 {
|
Chris@0
|
164 if (name == tr("Window Size") ||
|
Chris@35
|
165 name == tr("Window Type") ||
|
Chris@0
|
166 name == tr("Window Overlap")) return tr("Window");
|
Chris@35
|
167 if (name == tr("Colour") ||
|
Chris@35
|
168 name == tr("Colour Rotation")) return tr("Colour");
|
Chris@0
|
169 if (name == tr("Gain") ||
|
Chris@37
|
170 name == tr("Threshold") ||
|
Chris@36
|
171 name == tr("Normalize") ||
|
Chris@37
|
172 name == tr("Bin Display") ||
|
Chris@0
|
173 name == tr("Colour Scale")) return tr("Scale");
|
Chris@0
|
174 if (name == tr("Max Frequency") ||
|
Chris@37
|
175 name == tr("Min Frequency") ||
|
Chris@35
|
176 name == tr("Frequency Scale") ||
|
Chris@37
|
177 name == tr("Frequency Adjustment")) return tr("Range");
|
Chris@0
|
178 return QString();
|
Chris@0
|
179 }
|
Chris@0
|
180
|
Chris@0
|
181 int
|
Chris@0
|
182 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name,
|
Chris@0
|
183 int *min, int *max) const
|
Chris@0
|
184 {
|
Chris@0
|
185 int deft = 0;
|
Chris@0
|
186
|
Chris@10
|
187 int throwaway;
|
Chris@10
|
188 if (!min) min = &throwaway;
|
Chris@10
|
189 if (!max) max = &throwaway;
|
Chris@10
|
190
|
Chris@0
|
191 if (name == tr("Gain")) {
|
Chris@0
|
192
|
Chris@0
|
193 *min = -50;
|
Chris@0
|
194 *max = 50;
|
Chris@0
|
195
|
Chris@0
|
196 deft = lrint(log10(m_gain) * 20.0);
|
Chris@0
|
197 if (deft < *min) deft = *min;
|
Chris@0
|
198 if (deft > *max) deft = *max;
|
Chris@0
|
199
|
Chris@37
|
200 } else if (name == tr("Threshold")) {
|
Chris@37
|
201
|
Chris@37
|
202 *min = -50;
|
Chris@37
|
203 *max = 0;
|
Chris@37
|
204
|
Chris@37
|
205 deft = lrintf(AudioLevel::multiplier_to_dB(m_threshold));
|
Chris@37
|
206 if (deft < *min) deft = *min;
|
Chris@37
|
207 if (deft > *max) deft = *max;
|
Chris@37
|
208
|
Chris@9
|
209 } else if (name == tr("Colour Rotation")) {
|
Chris@9
|
210
|
Chris@9
|
211 *min = 0;
|
Chris@9
|
212 *max = 256;
|
Chris@9
|
213
|
Chris@9
|
214 deft = m_colourRotation;
|
Chris@9
|
215
|
Chris@0
|
216 } else if (name == tr("Colour Scale")) {
|
Chris@0
|
217
|
Chris@0
|
218 *min = 0;
|
Chris@0
|
219 *max = 3;
|
Chris@0
|
220
|
Chris@0
|
221 deft = (int)m_colourScale;
|
Chris@0
|
222
|
Chris@0
|
223 } else if (name == tr("Colour")) {
|
Chris@0
|
224
|
Chris@0
|
225 *min = 0;
|
Chris@0
|
226 *max = 5;
|
Chris@0
|
227
|
Chris@0
|
228 deft = (int)m_colourScheme;
|
Chris@0
|
229
|
Chris@0
|
230 } else if (name == tr("Window Type")) {
|
Chris@0
|
231
|
Chris@0
|
232 *min = 0;
|
Chris@0
|
233 *max = 6;
|
Chris@0
|
234
|
Chris@0
|
235 deft = (int)m_windowType;
|
Chris@0
|
236
|
Chris@0
|
237 } else if (name == tr("Window Size")) {
|
Chris@0
|
238
|
Chris@0
|
239 *min = 0;
|
Chris@0
|
240 *max = 10;
|
Chris@0
|
241
|
Chris@0
|
242 deft = 0;
|
Chris@0
|
243 int ws = m_windowSize;
|
Chris@0
|
244 while (ws > 32) { ws >>= 1; deft ++; }
|
Chris@0
|
245
|
Chris@0
|
246 } else if (name == tr("Window Overlap")) {
|
Chris@0
|
247
|
Chris@0
|
248 *min = 0;
|
Chris@0
|
249 *max = 4;
|
Chris@0
|
250
|
Chris@0
|
251 deft = m_windowOverlap / 25;
|
Chris@0
|
252 if (m_windowOverlap == 90) deft = 4;
|
Chris@0
|
253
|
Chris@37
|
254 } else if (name == tr("Min Frequency")) {
|
Chris@37
|
255
|
Chris@37
|
256 *min = 0;
|
Chris@37
|
257 *max = 9;
|
Chris@37
|
258
|
Chris@37
|
259 switch (m_minFrequency) {
|
Chris@37
|
260 case 0: default: deft = 0; break;
|
Chris@37
|
261 case 10: deft = 1; break;
|
Chris@37
|
262 case 20: deft = 2; break;
|
Chris@37
|
263 case 40: deft = 3; break;
|
Chris@37
|
264 case 100: deft = 4; break;
|
Chris@37
|
265 case 250: deft = 5; break;
|
Chris@37
|
266 case 500: deft = 6; break;
|
Chris@37
|
267 case 1000: deft = 7; break;
|
Chris@37
|
268 case 4000: deft = 8; break;
|
Chris@37
|
269 case 10000: deft = 9; break;
|
Chris@37
|
270 }
|
Chris@37
|
271
|
Chris@0
|
272 } else if (name == tr("Max Frequency")) {
|
Chris@0
|
273
|
Chris@0
|
274 *min = 0;
|
Chris@0
|
275 *max = 9;
|
Chris@0
|
276
|
Chris@0
|
277 switch (m_maxFrequency) {
|
Chris@0
|
278 case 500: deft = 0; break;
|
Chris@0
|
279 case 1000: deft = 1; break;
|
Chris@0
|
280 case 1500: deft = 2; break;
|
Chris@0
|
281 case 2000: deft = 3; break;
|
Chris@0
|
282 case 4000: deft = 4; break;
|
Chris@0
|
283 case 6000: deft = 5; break;
|
Chris@0
|
284 case 8000: deft = 6; break;
|
Chris@0
|
285 case 12000: deft = 7; break;
|
Chris@0
|
286 case 16000: deft = 8; break;
|
Chris@0
|
287 default: deft = 9; break;
|
Chris@0
|
288 }
|
Chris@0
|
289
|
Chris@0
|
290 } else if (name == tr("Frequency Scale")) {
|
Chris@0
|
291
|
Chris@0
|
292 *min = 0;
|
Chris@0
|
293 *max = 1;
|
Chris@0
|
294 deft = (int)m_frequencyScale;
|
Chris@0
|
295
|
Chris@37
|
296 } else if (name == tr("Bin Display")) {
|
Chris@35
|
297
|
Chris@35
|
298 *min = 0;
|
Chris@35
|
299 *max = 2;
|
Chris@37
|
300 deft = (int)m_binDisplay;
|
Chris@35
|
301
|
Chris@36
|
302 } else if (name == tr("Normalize")) {
|
Chris@36
|
303
|
Chris@36
|
304 deft = (m_normalizeColumns ? 1 : 0);
|
Chris@36
|
305
|
Chris@0
|
306 } else {
|
Chris@0
|
307 deft = Layer::getPropertyRangeAndValue(name, min, max);
|
Chris@0
|
308 }
|
Chris@0
|
309
|
Chris@0
|
310 return deft;
|
Chris@0
|
311 }
|
Chris@0
|
312
|
Chris@0
|
313 QString
|
Chris@0
|
314 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name,
|
Chris@9
|
315 int value) const
|
Chris@0
|
316 {
|
Chris@0
|
317 if (name == tr("Colour")) {
|
Chris@0
|
318 switch (value) {
|
Chris@0
|
319 default:
|
Chris@0
|
320 case 0: return tr("Default");
|
Chris@0
|
321 case 1: return tr("White on Black");
|
Chris@0
|
322 case 2: return tr("Black on White");
|
Chris@0
|
323 case 3: return tr("Red on Blue");
|
Chris@0
|
324 case 4: return tr("Yellow on Black");
|
Chris@0
|
325 case 5: return tr("Red on Black");
|
Chris@0
|
326 }
|
Chris@0
|
327 }
|
Chris@0
|
328 if (name == tr("Colour Scale")) {
|
Chris@0
|
329 switch (value) {
|
Chris@0
|
330 default:
|
Chris@37
|
331 case 0: return tr("Linear");
|
Chris@37
|
332 case 1: return tr("Meter");
|
Chris@37
|
333 case 2: return tr("dB");
|
Chris@0
|
334 case 3: return tr("Phase");
|
Chris@0
|
335 }
|
Chris@0
|
336 }
|
Chris@0
|
337 if (name == tr("Window Type")) {
|
Chris@0
|
338 switch ((WindowType)value) {
|
Chris@0
|
339 default:
|
Chris@35
|
340 case RectangularWindow: return tr("Rectangle");
|
Chris@0
|
341 case BartlettWindow: return tr("Bartlett");
|
Chris@0
|
342 case HammingWindow: return tr("Hamming");
|
Chris@0
|
343 case HanningWindow: return tr("Hanning");
|
Chris@0
|
344 case BlackmanWindow: return tr("Blackman");
|
Chris@0
|
345 case GaussianWindow: return tr("Gaussian");
|
Chris@0
|
346 case ParzenWindow: return tr("Parzen");
|
Chris@0
|
347 }
|
Chris@0
|
348 }
|
Chris@0
|
349 if (name == tr("Window Size")) {
|
Chris@0
|
350 return QString("%1").arg(32 << value);
|
Chris@0
|
351 }
|
Chris@0
|
352 if (name == tr("Window Overlap")) {
|
Chris@0
|
353 switch (value) {
|
Chris@0
|
354 default:
|
Chris@35
|
355 case 0: return tr("0%");
|
Chris@35
|
356 case 1: return tr("25%");
|
Chris@35
|
357 case 2: return tr("50%");
|
Chris@35
|
358 case 3: return tr("75%");
|
Chris@35
|
359 case 4: return tr("90%");
|
Chris@0
|
360 }
|
Chris@0
|
361 }
|
Chris@37
|
362 if (name == tr("Min Frequency")) {
|
Chris@37
|
363 switch (value) {
|
Chris@37
|
364 default:
|
Chris@37
|
365 case 0: return tr("None");
|
Chris@37
|
366 case 1: return tr("10 Hz");
|
Chris@37
|
367 case 2: return tr("20 Hz");
|
Chris@37
|
368 case 3: return tr("40 Hz");
|
Chris@37
|
369 case 4: return tr("100 Hz");
|
Chris@37
|
370 case 5: return tr("250 Hz");
|
Chris@37
|
371 case 6: return tr("500 Hz");
|
Chris@37
|
372 case 7: return tr("1 KHz");
|
Chris@37
|
373 case 8: return tr("4 KHz");
|
Chris@37
|
374 case 9: return tr("10 KHz");
|
Chris@37
|
375 }
|
Chris@37
|
376 }
|
Chris@0
|
377 if (name == tr("Max Frequency")) {
|
Chris@0
|
378 switch (value) {
|
Chris@0
|
379 default:
|
Chris@0
|
380 case 0: return tr("500 Hz");
|
Chris@0
|
381 case 1: return tr("1 KHz");
|
Chris@0
|
382 case 2: return tr("1.5 KHz");
|
Chris@0
|
383 case 3: return tr("2 KHz");
|
Chris@0
|
384 case 4: return tr("4 KHz");
|
Chris@0
|
385 case 5: return tr("6 KHz");
|
Chris@0
|
386 case 6: return tr("8 KHz");
|
Chris@0
|
387 case 7: return tr("12 KHz");
|
Chris@0
|
388 case 8: return tr("16 KHz");
|
Chris@0
|
389 case 9: return tr("All");
|
Chris@0
|
390 }
|
Chris@0
|
391 }
|
Chris@0
|
392 if (name == tr("Frequency Scale")) {
|
Chris@0
|
393 switch (value) {
|
Chris@0
|
394 default:
|
Chris@0
|
395 case 0: return tr("Linear");
|
Chris@0
|
396 case 1: return tr("Log");
|
Chris@0
|
397 }
|
Chris@0
|
398 }
|
Chris@37
|
399 if (name == tr("Bin Display")) {
|
Chris@35
|
400 switch (value) {
|
Chris@35
|
401 default:
|
Chris@37
|
402 case 0: return tr("All Bins");
|
Chris@37
|
403 case 1: return tr("Peak Bins");
|
Chris@37
|
404 case 2: return tr("Frequencies");
|
Chris@35
|
405 }
|
Chris@35
|
406 }
|
Chris@0
|
407 return tr("<unknown>");
|
Chris@0
|
408 }
|
Chris@0
|
409
|
Chris@0
|
410 void
|
Chris@0
|
411 SpectrogramLayer::setProperty(const PropertyName &name, int value)
|
Chris@0
|
412 {
|
Chris@0
|
413 if (name == tr("Gain")) {
|
Chris@0
|
414 setGain(pow(10, float(value)/20.0));
|
Chris@37
|
415 } else if (name == tr("Threshold")) {
|
Chris@37
|
416 if (value == -50) setThreshold(0.0);
|
Chris@37
|
417 else setThreshold(AudioLevel::dB_to_multiplier(value));
|
Chris@9
|
418 } else if (name == tr("Colour Rotation")) {
|
Chris@9
|
419 setColourRotation(value);
|
Chris@0
|
420 } else if (name == tr("Colour")) {
|
Chris@0
|
421 if (m_view) m_view->setLightBackground(value == 2);
|
Chris@0
|
422 switch (value) {
|
Chris@0
|
423 default:
|
Chris@0
|
424 case 0: setColourScheme(DefaultColours); break;
|
Chris@0
|
425 case 1: setColourScheme(WhiteOnBlack); break;
|
Chris@0
|
426 case 2: setColourScheme(BlackOnWhite); break;
|
Chris@0
|
427 case 3: setColourScheme(RedOnBlue); break;
|
Chris@0
|
428 case 4: setColourScheme(YellowOnBlack); break;
|
Chris@0
|
429 case 5: setColourScheme(RedOnBlack); break;
|
Chris@0
|
430 }
|
Chris@0
|
431 } else if (name == tr("Window Type")) {
|
Chris@0
|
432 setWindowType(WindowType(value));
|
Chris@0
|
433 } else if (name == tr("Window Size")) {
|
Chris@0
|
434 setWindowSize(32 << value);
|
Chris@0
|
435 } else if (name == tr("Window Overlap")) {
|
Chris@0
|
436 if (value == 4) setWindowOverlap(90);
|
Chris@0
|
437 else setWindowOverlap(25 * value);
|
Chris@37
|
438 } else if (name == tr("Min Frequency")) {
|
Chris@37
|
439 switch (value) {
|
Chris@37
|
440 default:
|
Chris@37
|
441 case 0: setMinFrequency(0); break;
|
Chris@37
|
442 case 1: setMinFrequency(10); break;
|
Chris@37
|
443 case 2: setMinFrequency(20); break;
|
Chris@37
|
444 case 3: setMinFrequency(40); break;
|
Chris@37
|
445 case 4: setMinFrequency(100); break;
|
Chris@37
|
446 case 5: setMinFrequency(250); break;
|
Chris@37
|
447 case 6: setMinFrequency(500); break;
|
Chris@37
|
448 case 7: setMinFrequency(1000); break;
|
Chris@37
|
449 case 8: setMinFrequency(4000); break;
|
Chris@37
|
450 case 9: setMinFrequency(10000); break;
|
Chris@37
|
451 }
|
Chris@0
|
452 } else if (name == tr("Max Frequency")) {
|
Chris@0
|
453 switch (value) {
|
Chris@0
|
454 case 0: setMaxFrequency(500); break;
|
Chris@0
|
455 case 1: setMaxFrequency(1000); break;
|
Chris@0
|
456 case 2: setMaxFrequency(1500); break;
|
Chris@0
|
457 case 3: setMaxFrequency(2000); break;
|
Chris@0
|
458 case 4: setMaxFrequency(4000); break;
|
Chris@0
|
459 case 5: setMaxFrequency(6000); break;
|
Chris@0
|
460 case 6: setMaxFrequency(8000); break;
|
Chris@0
|
461 case 7: setMaxFrequency(12000); break;
|
Chris@0
|
462 case 8: setMaxFrequency(16000); break;
|
Chris@0
|
463 default:
|
Chris@0
|
464 case 9: setMaxFrequency(0); break;
|
Chris@0
|
465 }
|
Chris@0
|
466 } else if (name == tr("Colour Scale")) {
|
Chris@0
|
467 switch (value) {
|
Chris@0
|
468 default:
|
Chris@0
|
469 case 0: setColourScale(LinearColourScale); break;
|
Chris@0
|
470 case 1: setColourScale(MeterColourScale); break;
|
Chris@0
|
471 case 2: setColourScale(dBColourScale); break;
|
Chris@0
|
472 case 3: setColourScale(PhaseColourScale); break;
|
Chris@0
|
473 }
|
Chris@0
|
474 } else if (name == tr("Frequency Scale")) {
|
Chris@0
|
475 switch (value) {
|
Chris@0
|
476 default:
|
Chris@0
|
477 case 0: setFrequencyScale(LinearFrequencyScale); break;
|
Chris@0
|
478 case 1: setFrequencyScale(LogFrequencyScale); break;
|
Chris@0
|
479 }
|
Chris@37
|
480 } else if (name == tr("Bin Display")) {
|
Chris@35
|
481 switch (value) {
|
Chris@35
|
482 default:
|
Chris@37
|
483 case 0: setBinDisplay(AllBins); break;
|
Chris@37
|
484 case 1: setBinDisplay(PeakBins); break;
|
Chris@37
|
485 case 2: setBinDisplay(PeakFrequencies); break;
|
Chris@35
|
486 }
|
Chris@36
|
487 } else if (name == "Normalize") {
|
Chris@36
|
488 setNormalizeColumns(value ? true : false);
|
Chris@0
|
489 }
|
Chris@0
|
490 }
|
Chris@0
|
491
|
Chris@0
|
492 void
|
Chris@0
|
493 SpectrogramLayer::setChannel(int ch)
|
Chris@0
|
494 {
|
Chris@0
|
495 if (m_channel == ch) return;
|
Chris@0
|
496
|
Chris@0
|
497 m_mutex.lock();
|
Chris@0
|
498 m_cacheInvalid = true;
|
Chris@0
|
499 m_pixmapCacheInvalid = true;
|
Chris@0
|
500
|
Chris@0
|
501 m_channel = ch;
|
Chris@9
|
502
|
Chris@9
|
503 m_mutex.unlock();
|
Chris@9
|
504
|
Chris@0
|
505 emit layerParametersChanged();
|
Chris@9
|
506
|
Chris@0
|
507 fillCache();
|
Chris@0
|
508 }
|
Chris@0
|
509
|
Chris@0
|
510 int
|
Chris@0
|
511 SpectrogramLayer::getChannel() const
|
Chris@0
|
512 {
|
Chris@0
|
513 return m_channel;
|
Chris@0
|
514 }
|
Chris@0
|
515
|
Chris@0
|
516 void
|
Chris@0
|
517 SpectrogramLayer::setWindowSize(size_t ws)
|
Chris@0
|
518 {
|
Chris@0
|
519 if (m_windowSize == ws) return;
|
Chris@0
|
520
|
Chris@0
|
521 m_mutex.lock();
|
Chris@0
|
522 m_cacheInvalid = true;
|
Chris@0
|
523 m_pixmapCacheInvalid = true;
|
Chris@0
|
524
|
Chris@0
|
525 m_windowSize = ws;
|
Chris@0
|
526
|
Chris@0
|
527 m_mutex.unlock();
|
Chris@9
|
528
|
Chris@9
|
529 emit layerParametersChanged();
|
Chris@9
|
530
|
Chris@0
|
531 fillCache();
|
Chris@0
|
532 }
|
Chris@0
|
533
|
Chris@0
|
534 size_t
|
Chris@0
|
535 SpectrogramLayer::getWindowSize() const
|
Chris@0
|
536 {
|
Chris@0
|
537 return m_windowSize;
|
Chris@0
|
538 }
|
Chris@0
|
539
|
Chris@0
|
540 void
|
Chris@0
|
541 SpectrogramLayer::setWindowOverlap(size_t wi)
|
Chris@0
|
542 {
|
Chris@0
|
543 if (m_windowOverlap == wi) return;
|
Chris@0
|
544
|
Chris@0
|
545 m_mutex.lock();
|
Chris@0
|
546 m_cacheInvalid = true;
|
Chris@0
|
547 m_pixmapCacheInvalid = true;
|
Chris@0
|
548
|
Chris@0
|
549 m_windowOverlap = wi;
|
Chris@0
|
550
|
Chris@0
|
551 m_mutex.unlock();
|
Chris@9
|
552
|
Chris@9
|
553 emit layerParametersChanged();
|
Chris@9
|
554
|
Chris@0
|
555 fillCache();
|
Chris@0
|
556 }
|
Chris@0
|
557
|
Chris@0
|
558 size_t
|
Chris@0
|
559 SpectrogramLayer::getWindowOverlap() const
|
Chris@0
|
560 {
|
Chris@0
|
561 return m_windowOverlap;
|
Chris@0
|
562 }
|
Chris@0
|
563
|
Chris@0
|
564 void
|
Chris@0
|
565 SpectrogramLayer::setWindowType(WindowType w)
|
Chris@0
|
566 {
|
Chris@0
|
567 if (m_windowType == w) return;
|
Chris@0
|
568
|
Chris@0
|
569 m_mutex.lock();
|
Chris@0
|
570 m_cacheInvalid = true;
|
Chris@0
|
571 m_pixmapCacheInvalid = true;
|
Chris@0
|
572
|
Chris@0
|
573 m_windowType = w;
|
Chris@0
|
574
|
Chris@0
|
575 m_mutex.unlock();
|
Chris@9
|
576
|
Chris@9
|
577 emit layerParametersChanged();
|
Chris@9
|
578
|
Chris@0
|
579 fillCache();
|
Chris@0
|
580 }
|
Chris@0
|
581
|
Chris@0
|
582 WindowType
|
Chris@0
|
583 SpectrogramLayer::getWindowType() const
|
Chris@0
|
584 {
|
Chris@0
|
585 return m_windowType;
|
Chris@0
|
586 }
|
Chris@0
|
587
|
Chris@0
|
588 void
|
Chris@0
|
589 SpectrogramLayer::setGain(float gain)
|
Chris@0
|
590 {
|
Chris@0
|
591 if (m_gain == gain) return; //!!! inadequate for floats!
|
Chris@0
|
592
|
Chris@0
|
593 m_mutex.lock();
|
Chris@0
|
594 m_cacheInvalid = true;
|
Chris@0
|
595 m_pixmapCacheInvalid = true;
|
Chris@0
|
596
|
Chris@0
|
597 m_gain = gain;
|
Chris@0
|
598
|
Chris@0
|
599 m_mutex.unlock();
|
Chris@9
|
600
|
Chris@9
|
601 emit layerParametersChanged();
|
Chris@9
|
602
|
Chris@0
|
603 fillCache();
|
Chris@0
|
604 }
|
Chris@0
|
605
|
Chris@0
|
606 float
|
Chris@0
|
607 SpectrogramLayer::getGain() const
|
Chris@0
|
608 {
|
Chris@0
|
609 return m_gain;
|
Chris@0
|
610 }
|
Chris@0
|
611
|
Chris@0
|
612 void
|
Chris@37
|
613 SpectrogramLayer::setThreshold(float threshold)
|
Chris@37
|
614 {
|
Chris@37
|
615 if (m_threshold == threshold) return; //!!! inadequate for floats!
|
Chris@37
|
616
|
Chris@37
|
617 m_mutex.lock();
|
Chris@37
|
618 m_cacheInvalid = true;
|
Chris@37
|
619 m_pixmapCacheInvalid = true;
|
Chris@37
|
620
|
Chris@37
|
621 m_threshold = threshold;
|
Chris@37
|
622
|
Chris@37
|
623 m_mutex.unlock();
|
Chris@37
|
624
|
Chris@37
|
625 emit layerParametersChanged();
|
Chris@37
|
626
|
Chris@37
|
627 fillCache();
|
Chris@37
|
628 }
|
Chris@37
|
629
|
Chris@37
|
630 float
|
Chris@37
|
631 SpectrogramLayer::getThreshold() const
|
Chris@37
|
632 {
|
Chris@37
|
633 return m_threshold;
|
Chris@37
|
634 }
|
Chris@37
|
635
|
Chris@37
|
636 void
|
Chris@37
|
637 SpectrogramLayer::setMinFrequency(size_t mf)
|
Chris@37
|
638 {
|
Chris@37
|
639 if (m_minFrequency == mf) return;
|
Chris@37
|
640
|
Chris@37
|
641 m_mutex.lock();
|
Chris@37
|
642 // don't need to invalidate main cache here
|
Chris@37
|
643 m_pixmapCacheInvalid = true;
|
Chris@37
|
644
|
Chris@37
|
645 m_minFrequency = mf;
|
Chris@37
|
646
|
Chris@37
|
647 m_mutex.unlock();
|
Chris@37
|
648
|
Chris@37
|
649 emit layerParametersChanged();
|
Chris@37
|
650 }
|
Chris@37
|
651
|
Chris@37
|
652 size_t
|
Chris@37
|
653 SpectrogramLayer::getMinFrequency() const
|
Chris@37
|
654 {
|
Chris@37
|
655 return m_minFrequency;
|
Chris@37
|
656 }
|
Chris@37
|
657
|
Chris@37
|
658 void
|
Chris@0
|
659 SpectrogramLayer::setMaxFrequency(size_t mf)
|
Chris@0
|
660 {
|
Chris@0
|
661 if (m_maxFrequency == mf) return;
|
Chris@0
|
662
|
Chris@0
|
663 m_mutex.lock();
|
Chris@1
|
664 // don't need to invalidate main cache here
|
Chris@0
|
665 m_pixmapCacheInvalid = true;
|
Chris@0
|
666
|
Chris@0
|
667 m_maxFrequency = mf;
|
Chris@0
|
668
|
Chris@0
|
669 m_mutex.unlock();
|
Chris@9
|
670
|
Chris@9
|
671 emit layerParametersChanged();
|
Chris@0
|
672 }
|
Chris@0
|
673
|
Chris@0
|
674 size_t
|
Chris@0
|
675 SpectrogramLayer::getMaxFrequency() const
|
Chris@0
|
676 {
|
Chris@0
|
677 return m_maxFrequency;
|
Chris@0
|
678 }
|
Chris@0
|
679
|
Chris@0
|
680 void
|
Chris@9
|
681 SpectrogramLayer::setColourRotation(int r)
|
Chris@9
|
682 {
|
Chris@9
|
683 m_mutex.lock();
|
Chris@9
|
684 // don't need to invalidate main cache here
|
Chris@9
|
685 m_pixmapCacheInvalid = true;
|
Chris@9
|
686
|
Chris@9
|
687 if (r < 0) r = 0;
|
Chris@9
|
688 if (r > 256) r = 256;
|
Chris@9
|
689 int distance = r - m_colourRotation;
|
Chris@9
|
690
|
Chris@9
|
691 if (distance != 0) {
|
Chris@9
|
692 rotateCacheColourmap(-distance);
|
Chris@9
|
693 m_colourRotation = r;
|
Chris@9
|
694 }
|
Chris@9
|
695
|
Chris@9
|
696 m_mutex.unlock();
|
Chris@9
|
697
|
Chris@9
|
698 emit layerParametersChanged();
|
Chris@9
|
699 }
|
Chris@9
|
700
|
Chris@9
|
701 void
|
Chris@0
|
702 SpectrogramLayer::setColourScale(ColourScale colourScale)
|
Chris@0
|
703 {
|
Chris@0
|
704 if (m_colourScale == colourScale) return;
|
Chris@0
|
705
|
Chris@0
|
706 m_mutex.lock();
|
Chris@0
|
707 m_cacheInvalid = true;
|
Chris@0
|
708 m_pixmapCacheInvalid = true;
|
Chris@0
|
709
|
Chris@0
|
710 m_colourScale = colourScale;
|
Chris@0
|
711
|
Chris@0
|
712 m_mutex.unlock();
|
Chris@0
|
713 fillCache();
|
Chris@9
|
714
|
Chris@9
|
715 emit layerParametersChanged();
|
Chris@0
|
716 }
|
Chris@0
|
717
|
Chris@0
|
718 SpectrogramLayer::ColourScale
|
Chris@0
|
719 SpectrogramLayer::getColourScale() const
|
Chris@0
|
720 {
|
Chris@0
|
721 return m_colourScale;
|
Chris@0
|
722 }
|
Chris@0
|
723
|
Chris@0
|
724 void
|
Chris@0
|
725 SpectrogramLayer::setColourScheme(ColourScheme scheme)
|
Chris@0
|
726 {
|
Chris@0
|
727 if (m_colourScheme == scheme) return;
|
Chris@0
|
728
|
Chris@0
|
729 m_mutex.lock();
|
Chris@0
|
730 // don't need to invalidate main cache here
|
Chris@0
|
731 m_pixmapCacheInvalid = true;
|
Chris@0
|
732
|
Chris@0
|
733 m_colourScheme = scheme;
|
Chris@0
|
734 setCacheColourmap();
|
Chris@9
|
735
|
Chris@9
|
736 m_mutex.unlock();
|
Chris@9
|
737
|
Chris@0
|
738 emit layerParametersChanged();
|
Chris@0
|
739 }
|
Chris@0
|
740
|
Chris@0
|
741 SpectrogramLayer::ColourScheme
|
Chris@0
|
742 SpectrogramLayer::getColourScheme() const
|
Chris@0
|
743 {
|
Chris@0
|
744 return m_colourScheme;
|
Chris@0
|
745 }
|
Chris@0
|
746
|
Chris@0
|
747 void
|
Chris@0
|
748 SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale)
|
Chris@0
|
749 {
|
Chris@0
|
750 if (m_frequencyScale == frequencyScale) return;
|
Chris@0
|
751
|
Chris@0
|
752 m_mutex.lock();
|
Chris@35
|
753
|
Chris@0
|
754 // don't need to invalidate main cache here
|
Chris@0
|
755 m_pixmapCacheInvalid = true;
|
Chris@0
|
756
|
Chris@0
|
757 m_frequencyScale = frequencyScale;
|
Chris@0
|
758
|
Chris@0
|
759 m_mutex.unlock();
|
Chris@9
|
760
|
Chris@9
|
761 emit layerParametersChanged();
|
Chris@0
|
762 }
|
Chris@0
|
763
|
Chris@0
|
764 SpectrogramLayer::FrequencyScale
|
Chris@0
|
765 SpectrogramLayer::getFrequencyScale() const
|
Chris@0
|
766 {
|
Chris@0
|
767 return m_frequencyScale;
|
Chris@0
|
768 }
|
Chris@0
|
769
|
Chris@0
|
770 void
|
Chris@37
|
771 SpectrogramLayer::setBinDisplay(BinDisplay binDisplay)
|
Chris@35
|
772 {
|
Chris@37
|
773 if (m_binDisplay == binDisplay) return;
|
Chris@35
|
774
|
Chris@35
|
775 m_mutex.lock();
|
Chris@35
|
776
|
Chris@35
|
777 m_cacheInvalid = true;
|
Chris@35
|
778 m_pixmapCacheInvalid = true;
|
Chris@35
|
779
|
Chris@37
|
780 m_binDisplay = binDisplay;
|
Chris@35
|
781
|
Chris@35
|
782 m_mutex.unlock();
|
Chris@35
|
783
|
Chris@35
|
784 fillCache();
|
Chris@35
|
785
|
Chris@35
|
786 emit layerParametersChanged();
|
Chris@35
|
787 }
|
Chris@35
|
788
|
Chris@37
|
789 SpectrogramLayer::BinDisplay
|
Chris@37
|
790 SpectrogramLayer::getBinDisplay() const
|
Chris@35
|
791 {
|
Chris@37
|
792 return m_binDisplay;
|
Chris@35
|
793 }
|
Chris@35
|
794
|
Chris@35
|
795 void
|
Chris@36
|
796 SpectrogramLayer::setNormalizeColumns(bool n)
|
Chris@36
|
797 {
|
Chris@36
|
798 if (m_normalizeColumns == n) return;
|
Chris@36
|
799 m_mutex.lock();
|
Chris@36
|
800
|
Chris@36
|
801 m_cacheInvalid = true;
|
Chris@36
|
802 m_pixmapCacheInvalid = true;
|
Chris@36
|
803 m_normalizeColumns = n;
|
Chris@36
|
804 m_mutex.unlock();
|
Chris@36
|
805
|
Chris@36
|
806 fillCache();
|
Chris@36
|
807 emit layerParametersChanged();
|
Chris@36
|
808 }
|
Chris@36
|
809
|
Chris@36
|
810 bool
|
Chris@36
|
811 SpectrogramLayer::getNormalizeColumns() const
|
Chris@36
|
812 {
|
Chris@36
|
813 return m_normalizeColumns;
|
Chris@36
|
814 }
|
Chris@36
|
815
|
Chris@36
|
816 void
|
Chris@33
|
817 SpectrogramLayer::setLayerDormant(bool dormant)
|
Chris@29
|
818 {
|
Chris@33
|
819 if (dormant == m_dormant) return;
|
Chris@33
|
820
|
Chris@33
|
821 if (dormant) {
|
Chris@33
|
822
|
Chris@33
|
823 m_mutex.lock();
|
Chris@33
|
824 m_dormant = true;
|
Chris@33
|
825
|
Chris@34
|
826 // delete m_cache;
|
Chris@34
|
827 // m_cache = 0;
|
Chris@33
|
828
|
Chris@34
|
829 m_cacheInvalid = true;
|
Chris@33
|
830 m_pixmapCacheInvalid = true;
|
Chris@34
|
831 m_cachedInitialVisibleArea = false;
|
Chris@33
|
832 delete m_pixmapCache;
|
Chris@33
|
833 m_pixmapCache = 0;
|
Chris@33
|
834
|
Chris@33
|
835 m_mutex.unlock();
|
Chris@33
|
836
|
Chris@33
|
837 } else {
|
Chris@33
|
838
|
Chris@33
|
839 m_dormant = false;
|
Chris@33
|
840 fillCache();
|
Chris@33
|
841 }
|
Chris@29
|
842 }
|
Chris@29
|
843
|
Chris@29
|
844 void
|
Chris@0
|
845 SpectrogramLayer::cacheInvalid()
|
Chris@0
|
846 {
|
Chris@0
|
847 m_cacheInvalid = true;
|
Chris@0
|
848 m_pixmapCacheInvalid = true;
|
Chris@0
|
849 m_cachedInitialVisibleArea = false;
|
Chris@0
|
850 fillCache();
|
Chris@0
|
851 }
|
Chris@0
|
852
|
Chris@0
|
853 void
|
Chris@0
|
854 SpectrogramLayer::cacheInvalid(size_t, size_t)
|
Chris@0
|
855 {
|
Chris@0
|
856 // for now (or forever?)
|
Chris@0
|
857 cacheInvalid();
|
Chris@0
|
858 }
|
Chris@0
|
859
|
Chris@0
|
860 void
|
Chris@0
|
861 SpectrogramLayer::fillCache()
|
Chris@0
|
862 {
|
Chris@0
|
863 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
864 std::cerr << "SpectrogramLayer::fillCache" << std::endl;
|
Chris@0
|
865 #endif
|
Chris@0
|
866 QMutexLocker locker(&m_mutex);
|
Chris@0
|
867
|
Chris@0
|
868 m_lastFillExtent = 0;
|
Chris@0
|
869
|
Chris@0
|
870 delete m_updateTimer;
|
Chris@0
|
871 m_updateTimer = new QTimer(this);
|
Chris@0
|
872 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
|
Chris@0
|
873 m_updateTimer->start(200);
|
Chris@0
|
874
|
Chris@0
|
875 if (!m_fillThread) {
|
Chris@0
|
876 std::cerr << "SpectrogramLayer::fillCache creating thread" << std::endl;
|
Chris@0
|
877 m_fillThread = new CacheFillThread(*this);
|
Chris@0
|
878 m_fillThread->start();
|
Chris@0
|
879 }
|
Chris@0
|
880
|
Chris@0
|
881 m_condition.wakeAll();
|
Chris@0
|
882 }
|
Chris@0
|
883
|
Chris@0
|
884 void
|
Chris@0
|
885 SpectrogramLayer::fillTimerTimedOut()
|
Chris@0
|
886 {
|
Chris@0
|
887 if (m_fillThread && m_model) {
|
Chris@0
|
888 size_t fillExtent = m_fillThread->getFillExtent();
|
Chris@0
|
889 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
890 std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent " << fillExtent << ", last " << m_lastFillExtent << ", total " << m_model->getEndFrame() << std::endl;
|
Chris@0
|
891 #endif
|
Chris@0
|
892 if (fillExtent >= m_lastFillExtent) {
|
Chris@0
|
893 if (fillExtent >= m_model->getEndFrame() && m_lastFillExtent > 0) {
|
Chris@0
|
894 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
895 std::cerr << "complete!" << std::endl;
|
Chris@0
|
896 #endif
|
Chris@0
|
897 emit modelChanged();
|
Chris@0
|
898 m_pixmapCacheInvalid = true;
|
Chris@0
|
899 delete m_updateTimer;
|
Chris@0
|
900 m_updateTimer = 0;
|
Chris@0
|
901 m_lastFillExtent = 0;
|
Chris@0
|
902 } else if (fillExtent > m_lastFillExtent) {
|
Chris@0
|
903 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
904 std::cerr << "SpectrogramLayer: emitting modelChanged("
|
Chris@0
|
905 << m_lastFillExtent << "," << fillExtent << ")" << std::endl;
|
Chris@0
|
906 #endif
|
Chris@0
|
907 emit modelChanged(m_lastFillExtent, fillExtent);
|
Chris@0
|
908 m_pixmapCacheInvalid = true;
|
Chris@0
|
909 m_lastFillExtent = fillExtent;
|
Chris@0
|
910 }
|
Chris@0
|
911 } else {
|
Chris@0
|
912 if (m_view) {
|
Chris@0
|
913 size_t sf = 0;
|
Chris@0
|
914 if (m_view->getStartFrame() > 0) sf = m_view->getStartFrame();
|
Chris@0
|
915 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
916 std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged("
|
Chris@0
|
917 << sf << "," << m_view->getEndFrame() << ")" << std::endl;
|
Chris@0
|
918 #endif
|
Chris@0
|
919 emit modelChanged(sf, m_view->getEndFrame());
|
Chris@0
|
920 m_pixmapCacheInvalid = true;
|
Chris@0
|
921 }
|
Chris@0
|
922 m_lastFillExtent = fillExtent;
|
Chris@0
|
923 }
|
Chris@0
|
924 }
|
Chris@0
|
925 }
|
Chris@0
|
926
|
Chris@0
|
927 void
|
Chris@0
|
928 SpectrogramLayer::setCacheColourmap()
|
Chris@0
|
929 {
|
Chris@0
|
930 if (m_cacheInvalid || !m_cache) return;
|
Chris@0
|
931
|
Chris@10
|
932 int formerRotation = m_colourRotation;
|
Chris@10
|
933
|
Chris@37
|
934 m_cache->setColour(NO_VALUE, Qt::white);
|
Chris@0
|
935
|
Chris@0
|
936 for (int pixel = 1; pixel < 256; ++pixel) {
|
Chris@0
|
937
|
Chris@0
|
938 QColor colour;
|
Chris@0
|
939 int hue, px;
|
Chris@0
|
940
|
Chris@0
|
941 switch (m_colourScheme) {
|
Chris@0
|
942
|
Chris@0
|
943 default:
|
Chris@0
|
944 case DefaultColours:
|
Chris@0
|
945 hue = 256 - pixel;
|
Chris@0
|
946 colour = QColor::fromHsv(hue, pixel/2 + 128, pixel);
|
Chris@0
|
947 break;
|
Chris@0
|
948
|
Chris@0
|
949 case WhiteOnBlack:
|
Chris@0
|
950 colour = QColor(pixel, pixel, pixel);
|
Chris@0
|
951 break;
|
Chris@0
|
952
|
Chris@0
|
953 case BlackOnWhite:
|
Chris@0
|
954 colour = QColor(256-pixel, 256-pixel, 256-pixel);
|
Chris@0
|
955 break;
|
Chris@0
|
956
|
Chris@0
|
957 case RedOnBlue:
|
Chris@0
|
958 colour = QColor(pixel > 128 ? (pixel - 128) * 2 : 0, 0,
|
Chris@0
|
959 pixel < 128 ? pixel : (256 - pixel));
|
Chris@0
|
960 break;
|
Chris@0
|
961
|
Chris@0
|
962 case YellowOnBlack:
|
Chris@0
|
963 px = 256 - pixel;
|
Chris@0
|
964 colour = QColor(px < 64 ? 255 - px/2 :
|
Chris@0
|
965 px < 128 ? 224 - (px - 64) :
|
Chris@0
|
966 px < 192 ? 160 - (px - 128) * 3 / 2 :
|
Chris@0
|
967 256 - px,
|
Chris@0
|
968 pixel,
|
Chris@0
|
969 pixel / 4);
|
Chris@0
|
970 break;
|
Chris@0
|
971
|
Chris@0
|
972 case RedOnBlack:
|
Chris@0
|
973 colour = QColor::fromHsv(10, pixel, pixel);
|
Chris@0
|
974 break;
|
Chris@0
|
975 }
|
Chris@0
|
976
|
Chris@31
|
977 m_cache->setColour(pixel, colour);
|
Chris@0
|
978 }
|
Chris@9
|
979
|
Chris@9
|
980 m_colourRotation = 0;
|
Chris@10
|
981 rotateCacheColourmap(m_colourRotation - formerRotation);
|
Chris@10
|
982 m_colourRotation = formerRotation;
|
Chris@9
|
983 }
|
Chris@9
|
984
|
Chris@9
|
985 void
|
Chris@9
|
986 SpectrogramLayer::rotateCacheColourmap(int distance)
|
Chris@9
|
987 {
|
Chris@10
|
988 if (!m_cache) return;
|
Chris@10
|
989
|
Chris@31
|
990 QColor newPixels[256];
|
Chris@9
|
991
|
Chris@37
|
992 newPixels[NO_VALUE] = m_cache->getColour(NO_VALUE);
|
Chris@9
|
993
|
Chris@9
|
994 for (int pixel = 1; pixel < 256; ++pixel) {
|
Chris@9
|
995 int target = pixel + distance;
|
Chris@9
|
996 while (target < 1) target += 255;
|
Chris@9
|
997 while (target > 255) target -= 255;
|
Chris@31
|
998 newPixels[target] = m_cache->getColour(pixel);
|
Chris@9
|
999 }
|
Chris@9
|
1000
|
Chris@9
|
1001 for (int pixel = 0; pixel < 256; ++pixel) {
|
Chris@31
|
1002 m_cache->setColour(pixel, newPixels[pixel]);
|
Chris@9
|
1003 }
|
Chris@0
|
1004 }
|
Chris@0
|
1005
|
Chris@0
|
1006 bool
|
Chris@0
|
1007 SpectrogramLayer::fillCacheColumn(int column, double *input,
|
Chris@0
|
1008 fftw_complex *output,
|
Chris@0
|
1009 fftw_plan plan,
|
Chris@9
|
1010 size_t windowSize,
|
Chris@9
|
1011 size_t increment,
|
Chris@0
|
1012 const Window<double> &windower,
|
Chris@35
|
1013 bool resetStoredPhase) const
|
Chris@0
|
1014 {
|
Chris@35
|
1015 static std::vector<double> storedPhase;
|
Chris@35
|
1016
|
Chris@37
|
1017 bool phaseAdjust = (m_binDisplay == PeakFrequencies);
|
Chris@35
|
1018 bool haveStoredPhase = true;
|
Chris@35
|
1019 size_t sampleRate = 0;
|
Chris@35
|
1020
|
Chris@35
|
1021 if (phaseAdjust) {
|
Chris@35
|
1022 if (resetStoredPhase || (storedPhase.size() != windowSize / 2)) {
|
Chris@35
|
1023 haveStoredPhase = false;
|
Chris@35
|
1024 storedPhase.clear();
|
Chris@35
|
1025 for (size_t i = 0; i < windowSize / 2; ++i) {
|
Chris@35
|
1026 storedPhase.push_back(0.0);
|
Chris@35
|
1027 }
|
Chris@35
|
1028 }
|
Chris@35
|
1029 sampleRate = m_model->getSampleRate();
|
Chris@35
|
1030 }
|
Chris@35
|
1031
|
Chris@0
|
1032 int startFrame = increment * column;
|
Chris@9
|
1033 int endFrame = startFrame + windowSize;
|
Chris@0
|
1034
|
Chris@9
|
1035 startFrame -= int(windowSize - increment) / 2;
|
Chris@9
|
1036 endFrame -= int(windowSize - increment) / 2;
|
Chris@0
|
1037 size_t pfx = 0;
|
Chris@0
|
1038
|
Chris@0
|
1039 if (startFrame < 0) {
|
Chris@0
|
1040 pfx = size_t(-startFrame);
|
Chris@0
|
1041 for (size_t i = 0; i < pfx; ++i) {
|
Chris@0
|
1042 input[i] = 0.0;
|
Chris@0
|
1043 }
|
Chris@0
|
1044 }
|
Chris@0
|
1045
|
Chris@0
|
1046 size_t got = m_model->getValues(m_channel, startFrame + pfx,
|
Chris@0
|
1047 endFrame, input + pfx);
|
Chris@9
|
1048 while (got + pfx < windowSize) {
|
Chris@0
|
1049 input[got + pfx] = 0.0;
|
Chris@0
|
1050 ++got;
|
Chris@0
|
1051 }
|
Chris@0
|
1052
|
Chris@0
|
1053 if (m_gain != 1.0) {
|
Chris@9
|
1054 for (size_t i = 0; i < windowSize; ++i) {
|
Chris@0
|
1055 input[i] *= m_gain;
|
Chris@0
|
1056 }
|
Chris@0
|
1057 }
|
Chris@0
|
1058
|
Chris@37
|
1059 if (m_channel == -1) {
|
Chris@37
|
1060 int channels = m_model->getChannelCount();
|
Chris@37
|
1061 if (channels > 1) {
|
Chris@37
|
1062 for (size_t i = 0; i < windowSize; ++i) {
|
Chris@37
|
1063 input[i] /= channels;
|
Chris@37
|
1064 }
|
Chris@37
|
1065 }
|
Chris@37
|
1066 }
|
Chris@37
|
1067
|
Chris@0
|
1068 windower.cut(input);
|
Chris@0
|
1069
|
Chris@35
|
1070 for (size_t i = 0; i < windowSize/2; ++i) {
|
Chris@35
|
1071 double temp = input[i];
|
Chris@35
|
1072 input[i] = input[i + windowSize/2];
|
Chris@35
|
1073 input[i + windowSize/2] = temp;
|
Chris@35
|
1074 }
|
Chris@35
|
1075
|
Chris@0
|
1076 fftw_execute(plan);
|
Chris@0
|
1077
|
Chris@0
|
1078 bool interrupted = false;
|
Chris@0
|
1079
|
Chris@35
|
1080 double prevMag = 0.0;
|
Chris@36
|
1081 double maxMag = 0.0;
|
Chris@37
|
1082
|
Chris@36
|
1083 if (m_normalizeColumns) {
|
Chris@36
|
1084 for (size_t i = 0; i < windowSize/2; ++i) {
|
Chris@36
|
1085 double mag = sqrt(output[i][0] * output[i][0] +
|
Chris@36
|
1086 output[i][1] * output[i][1]);
|
Chris@36
|
1087 mag /= windowSize / 2;
|
Chris@36
|
1088 if (mag > maxMag) maxMag = mag;
|
Chris@36
|
1089 }
|
Chris@36
|
1090 }
|
Chris@36
|
1091
|
Chris@37
|
1092 if (maxMag == 0.0) maxMag = 1.0;
|
Chris@37
|
1093
|
Chris@37
|
1094 bool peaksOnly = (m_binDisplay == PeakBins ||
|
Chris@37
|
1095 m_binDisplay == PeakFrequencies);
|
Chris@37
|
1096
|
Chris@9
|
1097 for (size_t i = 0; i < windowSize / 2; ++i) {
|
Chris@0
|
1098
|
Chris@0
|
1099 int value = 0;
|
Chris@35
|
1100 double phase = 0.0;
|
Chris@35
|
1101
|
Chris@36
|
1102 double mag = sqrt(output[i][0] * output[i][0] +
|
Chris@36
|
1103 output[i][1] * output[i][1]);
|
Chris@37
|
1104
|
Chris@37
|
1105 mag /= (windowSize / 2);
|
Chris@37
|
1106 if (m_normalizeColumns) mag /= maxMag;
|
Chris@37
|
1107
|
Chris@37
|
1108 bool showThis = true;
|
Chris@37
|
1109
|
Chris@37
|
1110 if (peaksOnly) {
|
Chris@37
|
1111 if (mag < prevMag) showThis = false;
|
Chris@37
|
1112 else {
|
Chris@37
|
1113 double nextMag = 0.0;
|
Chris@37
|
1114 if (i < windowSize / 2 - 1) {
|
Chris@37
|
1115 nextMag = sqrt(output[i+1][0] * output[i+1][0] +
|
Chris@37
|
1116 output[i+1][1] * output[i+1][1]);
|
Chris@37
|
1117 nextMag /= windowSize / 2;
|
Chris@37
|
1118 if (m_normalizeColumns) nextMag /= maxMag;
|
Chris@37
|
1119 }
|
Chris@37
|
1120 if (mag < nextMag) showThis = false;
|
Chris@37
|
1121 }
|
Chris@37
|
1122 prevMag = mag;
|
Chris@36
|
1123 }
|
Chris@35
|
1124
|
Chris@37
|
1125 if (mag < m_threshold) showThis = false;
|
Chris@37
|
1126
|
Chris@35
|
1127 if (phaseAdjust || (m_colourScale == PhaseColourScale)) {
|
Chris@37
|
1128 phase = atan2(output[i][1], output[i][0]);
|
Chris@37
|
1129 phase = MathUtilities::princarg(phase);
|
Chris@35
|
1130 }
|
Chris@35
|
1131
|
Chris@37
|
1132 if (phaseAdjust && m_phaseAdjustCache) {
|
Chris@37
|
1133 m_phaseAdjustCache->setValueAt(column, i, 0);
|
Chris@37
|
1134 }
|
Chris@37
|
1135
|
Chris@37
|
1136 if (phaseAdjust && m_phaseAdjustCache && haveStoredPhase && showThis) {
|
Chris@37
|
1137
|
Chris@37
|
1138 double freq = (double(i) * sampleRate) / m_windowSize;
|
Chris@37
|
1139 double prevPhase = storedPhase[i];
|
Chris@37
|
1140
|
Chris@37
|
1141 // At frequency f, phase shift of 2pi (one cycle) happens in 1/f sec.
|
Chris@37
|
1142 // At hopsize h and sample rate sr, one hop happens in h/sr sec.
|
Chris@37
|
1143 // At window size w, for bin i, f is i*sr/w.
|
Chris@37
|
1144 // thus 2pi phase shift happens in w/(b*sr) sec.
|
Chris@37
|
1145 // We need to know what phase shift we expect from h/sr sec.
|
Chris@37
|
1146 // -> 2pi * ((h/sr) / (w/(i*sr)))
|
Chris@37
|
1147 // = 2pi * ((h * i * sr) / (w * sr))
|
Chris@37
|
1148 // = 2pi * (h * i) / w.
|
Chris@37
|
1149
|
Chris@37
|
1150 double expectedPhase =
|
Chris@37
|
1151 prevPhase + (2.0 * M_PI * i * increment) / m_windowSize;
|
Chris@37
|
1152
|
Chris@37
|
1153 double phaseError = MathUtilities::princarg(phase - expectedPhase);
|
Chris@37
|
1154
|
Chris@37
|
1155 if (fabs(phaseError) < (1.1 * (increment * M_PI) / m_windowSize)) {
|
Chris@37
|
1156
|
Chris@37
|
1157 // The new frequency estimate based on the phase error
|
Chris@37
|
1158 // resulting from assuming the "native" frequency of this bin
|
Chris@37
|
1159
|
Chris@37
|
1160 double newFreq =
|
Chris@37
|
1161 (sampleRate *
|
Chris@37
|
1162 (expectedPhase + phaseError - prevPhase)) /
|
Chris@37
|
1163 (2 * M_PI * increment);
|
Chris@37
|
1164
|
Chris@37
|
1165 // Convert the frequency difference to an unsigned char
|
Chris@37
|
1166 // for storage in the phase adjust cache
|
Chris@37
|
1167
|
Chris@37
|
1168 double binRange = (double(i + 1) * sampleRate) / windowSize - freq;
|
Chris@37
|
1169
|
Chris@37
|
1170 int offset = lrint(((newFreq - freq) / binRange) * 100);
|
Chris@37
|
1171
|
Chris@35
|
1172 if (m_cacheInvalid || m_exiting) {
|
Chris@35
|
1173 interrupted = true;
|
Chris@35
|
1174 break;
|
Chris@35
|
1175 }
|
Chris@37
|
1176
|
Chris@37
|
1177 if (offset >= SCHAR_MIN && offset <= SCHAR_MAX) {
|
Chris@37
|
1178 signed char coff = offset;
|
Chris@37
|
1179 m_phaseAdjustCache->setValueAt(column, i, (unsigned char)coff);
|
Chris@37
|
1180 } else {
|
Chris@37
|
1181
|
Chris@37
|
1182 if (fabs(phaseError) < ((increment * M_PI) / m_windowSize)) {
|
Chris@37
|
1183 std::cerr << "WARNING: Phase error " << phaseError << " ( < " << ((increment * M_PI) / m_windowSize) << ") but offset " << offset << " out of range" << std::endl;
|
Chris@37
|
1184 }
|
Chris@37
|
1185 }
|
Chris@37
|
1186 }
|
Chris@37
|
1187 }
|
Chris@37
|
1188
|
Chris@37
|
1189 if (phaseAdjust) storedPhase[i] = phase;
|
Chris@0
|
1190
|
Chris@0
|
1191 if (m_colourScale == PhaseColourScale) {
|
Chris@0
|
1192
|
Chris@37
|
1193 value = int((phase * 127 / M_PI) + 128);
|
Chris@0
|
1194
|
Chris@0
|
1195 } else {
|
Chris@1
|
1196
|
Chris@0
|
1197 switch (m_colourScale) {
|
Chris@0
|
1198
|
Chris@0
|
1199 default:
|
Chris@0
|
1200 case LinearColourScale:
|
Chris@37
|
1201 value = int(mag * 50 * 255) + 1;
|
Chris@0
|
1202 break;
|
Chris@0
|
1203
|
Chris@0
|
1204 case MeterColourScale:
|
Chris@37
|
1205 value = AudioLevel::multiplier_to_preview(mag * 50, 255) + 1;
|
Chris@0
|
1206 break;
|
Chris@0
|
1207
|
Chris@0
|
1208 case dBColourScale:
|
Chris@0
|
1209 mag = 20.0 * log10(mag);
|
Chris@0
|
1210 mag = (mag + 80.0) / 80.0;
|
Chris@0
|
1211 if (mag < 0.0) mag = 0.0;
|
Chris@0
|
1212 if (mag > 1.0) mag = 1.0;
|
Chris@37
|
1213 value = int(mag * 255) + 1;
|
Chris@0
|
1214 }
|
Chris@0
|
1215 }
|
Chris@0
|
1216
|
Chris@37
|
1217 if (value > UCHAR_MAX) value = UCHAR_MAX;
|
Chris@0
|
1218 if (value < 0) value = 0;
|
Chris@0
|
1219
|
Chris@0
|
1220 if (m_cacheInvalid || m_exiting) {
|
Chris@0
|
1221 interrupted = true;
|
Chris@0
|
1222 break;
|
Chris@0
|
1223 }
|
Chris@0
|
1224
|
Chris@37
|
1225 if (showThis) {
|
Chris@37
|
1226 m_cache->setValueAt(column, i, value);
|
Chris@37
|
1227 } else {
|
Chris@37
|
1228 m_cache->setValueAt(column, i, NO_VALUE);
|
Chris@37
|
1229 }
|
Chris@0
|
1230 }
|
Chris@0
|
1231
|
Chris@0
|
1232 return !interrupted;
|
Chris@0
|
1233 }
|
Chris@0
|
1234
|
Chris@31
|
1235 SpectrogramLayer::Cache::Cache(size_t width, size_t height) :
|
Chris@31
|
1236 m_width(width),
|
Chris@31
|
1237 m_height(height)
|
Chris@31
|
1238 {
|
Chris@35
|
1239 // use malloc rather than new[], because we want to be able to use realloc
|
Chris@35
|
1240 m_values = (unsigned char *)
|
Chris@35
|
1241 malloc(m_width * m_height * sizeof(unsigned char));
|
Chris@35
|
1242 if (!m_values) throw std::bad_alloc();
|
Chris@31
|
1243 MUNLOCK(m_values, m_width * m_height * sizeof(unsigned char));
|
Chris@31
|
1244 }
|
Chris@31
|
1245
|
Chris@31
|
1246 SpectrogramLayer::Cache::~Cache()
|
Chris@31
|
1247 {
|
Chris@35
|
1248 if (m_values) free(m_values);
|
Chris@31
|
1249 }
|
Chris@31
|
1250
|
Chris@35
|
1251 void
|
Chris@35
|
1252 SpectrogramLayer::Cache::resize(size_t width, size_t height)
|
Chris@35
|
1253 {
|
Chris@37
|
1254 std::cerr << "SpectrogramLayer::Cache[" << this << "]::resize(" << width << "x" << height << ")" << std::endl;
|
Chris@35
|
1255 m_values = (unsigned char *)
|
Chris@35
|
1256 realloc(m_values, m_width * m_height * sizeof(unsigned char));
|
Chris@35
|
1257 if (!m_values) throw std::bad_alloc();
|
Chris@35
|
1258 MUNLOCK(m_values, m_width * m_height * sizeof(unsigned char));
|
Chris@35
|
1259 }
|
Chris@35
|
1260
|
Chris@31
|
1261 size_t
|
Chris@31
|
1262 SpectrogramLayer::Cache::getWidth() const
|
Chris@31
|
1263 {
|
Chris@31
|
1264 return m_width;
|
Chris@31
|
1265 }
|
Chris@31
|
1266
|
Chris@31
|
1267 size_t
|
Chris@31
|
1268 SpectrogramLayer::Cache::getHeight() const
|
Chris@31
|
1269 {
|
Chris@31
|
1270 return m_height;
|
Chris@31
|
1271 }
|
Chris@31
|
1272
|
Chris@31
|
1273 unsigned char
|
Chris@31
|
1274 SpectrogramLayer::Cache::getValueAt(size_t x, size_t y) const
|
Chris@31
|
1275 {
|
Chris@31
|
1276 if (x >= m_width || y >= m_height) return 0;
|
Chris@31
|
1277 return m_values[y * m_width + x];
|
Chris@31
|
1278 }
|
Chris@31
|
1279
|
Chris@31
|
1280 void
|
Chris@31
|
1281 SpectrogramLayer::Cache::setValueAt(size_t x, size_t y, unsigned char value)
|
Chris@31
|
1282 {
|
Chris@31
|
1283 if (x >= m_width || y >= m_height) return;
|
Chris@31
|
1284 m_values[y * m_width + x] = value;
|
Chris@31
|
1285 }
|
Chris@31
|
1286
|
Chris@31
|
1287 QColor
|
Chris@31
|
1288 SpectrogramLayer::Cache::getColour(unsigned char index) const
|
Chris@31
|
1289 {
|
Chris@31
|
1290 return m_colours[index];
|
Chris@31
|
1291 }
|
Chris@31
|
1292
|
Chris@31
|
1293 void
|
Chris@31
|
1294 SpectrogramLayer::Cache::setColour(unsigned char index, QColor colour)
|
Chris@31
|
1295 {
|
Chris@31
|
1296 m_colours[index] = colour;
|
Chris@31
|
1297 }
|
Chris@31
|
1298
|
Chris@31
|
1299 void
|
Chris@31
|
1300 SpectrogramLayer::Cache::fill(unsigned char value)
|
Chris@31
|
1301 {
|
Chris@37
|
1302 std::cerr << "SpectrogramLayer::Cache[" << this << "]::fill(" << value << ")" << std::endl;
|
Chris@31
|
1303 for (size_t i = 0; i < m_width * m_height; ++i) {
|
Chris@31
|
1304 m_values[i] = value;
|
Chris@31
|
1305 }
|
Chris@31
|
1306 }
|
Chris@31
|
1307
|
Chris@0
|
1308 void
|
Chris@0
|
1309 SpectrogramLayer::CacheFillThread::run()
|
Chris@0
|
1310 {
|
Chris@0
|
1311 // std::cerr << "SpectrogramLayer::CacheFillThread::run" << std::endl;
|
Chris@0
|
1312
|
Chris@0
|
1313 m_layer.m_mutex.lock();
|
Chris@0
|
1314
|
Chris@0
|
1315 while (!m_layer.m_exiting) {
|
Chris@0
|
1316
|
Chris@0
|
1317 bool interrupted = false;
|
Chris@0
|
1318
|
Chris@0
|
1319 // std::cerr << "SpectrogramLayer::CacheFillThread::run in loop" << std::endl;
|
Chris@0
|
1320
|
Chris@34
|
1321 if (m_layer.m_dormant) {
|
Chris@34
|
1322
|
Chris@34
|
1323 if (m_layer.m_cacheInvalid) {
|
Chris@34
|
1324 delete m_layer.m_cache;
|
Chris@35
|
1325 delete m_layer.m_phaseAdjustCache;
|
Chris@34
|
1326 m_layer.m_cache = 0;
|
Chris@35
|
1327 m_layer.m_phaseAdjustCache = 0;
|
Chris@34
|
1328 }
|
Chris@34
|
1329
|
Chris@34
|
1330 } else if (m_layer.m_model && m_layer.m_cacheInvalid) {
|
Chris@0
|
1331
|
Chris@0
|
1332 // std::cerr << "SpectrogramLayer::CacheFillThread::run: something to do" << std::endl;
|
Chris@0
|
1333
|
Chris@0
|
1334 while (!m_layer.m_model->isReady()) {
|
Chris@0
|
1335 m_layer.m_condition.wait(&m_layer.m_mutex, 100);
|
Chris@0
|
1336 }
|
Chris@0
|
1337
|
Chris@0
|
1338 m_layer.m_cachedInitialVisibleArea = false;
|
Chris@0
|
1339 m_layer.m_cacheInvalid = false;
|
Chris@0
|
1340 m_fillExtent = 0;
|
Chris@0
|
1341 m_fillCompletion = 0;
|
Chris@0
|
1342
|
Chris@0
|
1343 std::cerr << "SpectrogramLayer::CacheFillThread::run: model is ready" << std::endl;
|
Chris@0
|
1344
|
Chris@0
|
1345 size_t start = m_layer.m_model->getStartFrame();
|
Chris@0
|
1346 size_t end = m_layer.m_model->getEndFrame();
|
Chris@9
|
1347
|
Chris@9
|
1348 WindowType windowType = m_layer.m_windowType;
|
Chris@0
|
1349 size_t windowSize = m_layer.m_windowSize;
|
Chris@0
|
1350 size_t windowIncrement = m_layer.getWindowIncrement();
|
Chris@0
|
1351
|
Chris@0
|
1352 size_t visibleStart = start;
|
Chris@0
|
1353 size_t visibleEnd = end;
|
Chris@0
|
1354
|
Chris@0
|
1355 if (m_layer.m_view) {
|
Chris@0
|
1356 if (m_layer.m_view->getStartFrame() < 0) {
|
Chris@0
|
1357 visibleStart = 0;
|
Chris@0
|
1358 } else {
|
Chris@0
|
1359 visibleStart = m_layer.m_view->getStartFrame();
|
Chris@0
|
1360 visibleStart = (visibleStart / windowIncrement) *
|
Chris@0
|
1361 windowIncrement;
|
Chris@0
|
1362 }
|
Chris@0
|
1363 visibleEnd = m_layer.m_view->getEndFrame();
|
Chris@0
|
1364 }
|
Chris@0
|
1365
|
Chris@9
|
1366 size_t width = (end - start) / windowIncrement + 1;
|
Chris@9
|
1367 size_t height = windowSize / 2;
|
Chris@35
|
1368
|
Chris@35
|
1369 if (!m_layer.m_cache) {
|
Chris@35
|
1370 m_layer.m_cache = new Cache(width, height);
|
Chris@35
|
1371 } else if (width != m_layer.m_cache->getWidth() ||
|
Chris@35
|
1372 height != m_layer.m_cache->getHeight()) {
|
Chris@35
|
1373 m_layer.m_cache->resize(width, height);
|
Chris@35
|
1374 }
|
Chris@9
|
1375
|
Chris@0
|
1376 m_layer.setCacheColourmap();
|
Chris@37
|
1377 m_layer.m_cache->fill(NO_VALUE);
|
Chris@37
|
1378
|
Chris@37
|
1379 if (m_layer.m_binDisplay == PeakFrequencies) {
|
Chris@35
|
1380
|
Chris@35
|
1381 if (!m_layer.m_phaseAdjustCache) {
|
Chris@35
|
1382 m_layer.m_phaseAdjustCache = new Cache(width, height);
|
Chris@35
|
1383 } else if (width != m_layer.m_phaseAdjustCache->getWidth() ||
|
Chris@35
|
1384 height != m_layer.m_phaseAdjustCache->getHeight()) {
|
Chris@35
|
1385 m_layer.m_phaseAdjustCache->resize(width, height);
|
Chris@35
|
1386 }
|
Chris@35
|
1387
|
Chris@35
|
1388 m_layer.m_phaseAdjustCache->fill(0);
|
Chris@35
|
1389
|
Chris@35
|
1390 } else {
|
Chris@35
|
1391 delete m_layer.m_phaseAdjustCache;
|
Chris@35
|
1392 m_layer.m_phaseAdjustCache = 0;
|
Chris@35
|
1393 }
|
Chris@35
|
1394
|
Chris@33
|
1395 // We don't need a lock when writing to or reading from
|
Chris@33
|
1396 // the pixels in the cache, because it's a fixed size
|
Chris@33
|
1397 // array. We do need to ensure we have the width and
|
Chris@35
|
1398 // height of the cache and the FFT parameters known before
|
Chris@33
|
1399 // we unlock, in case they change in the model while we
|
Chris@33
|
1400 // aren't holding a lock. It's safe for us to continue to
|
Chris@33
|
1401 // use the "old" values if that happens, because they will
|
Chris@33
|
1402 // continue to match the dimensions of the actual cache
|
Chris@33
|
1403 // (which we manage, not the model).
|
Chris@0
|
1404 m_layer.m_mutex.unlock();
|
Chris@0
|
1405
|
Chris@0
|
1406 double *input = (double *)
|
Chris@0
|
1407 fftw_malloc(windowSize * sizeof(double));
|
Chris@0
|
1408
|
Chris@0
|
1409 fftw_complex *output = (fftw_complex *)
|
Chris@0
|
1410 fftw_malloc(windowSize * sizeof(fftw_complex));
|
Chris@0
|
1411
|
Chris@0
|
1412 fftw_plan plan = fftw_plan_dft_r2c_1d(windowSize, input,
|
Chris@1
|
1413 output, FFTW_ESTIMATE);
|
Chris@0
|
1414
|
Chris@9
|
1415 Window<double> windower(windowType, windowSize);
|
Chris@0
|
1416
|
Chris@0
|
1417 if (!plan) {
|
Chris@1
|
1418 std::cerr << "WARNING: fftw_plan_dft_r2c_1d(" << windowSize << ") failed!" << std::endl;
|
Chris@0
|
1419 fftw_free(input);
|
Chris@0
|
1420 fftw_free(output);
|
Chris@37
|
1421 m_layer.m_mutex.lock();
|
Chris@0
|
1422 continue;
|
Chris@0
|
1423 }
|
Chris@0
|
1424
|
Chris@0
|
1425 int counter = 0;
|
Chris@0
|
1426 int updateAt = (end / windowIncrement) / 20;
|
Chris@0
|
1427 if (updateAt < 100) updateAt = 100;
|
Chris@0
|
1428
|
Chris@0
|
1429 bool doVisibleFirst = (visibleStart != start && visibleEnd != end);
|
Chris@0
|
1430
|
Chris@0
|
1431 if (doVisibleFirst) {
|
Chris@0
|
1432
|
Chris@0
|
1433 for (size_t f = visibleStart; f < visibleEnd; f += windowIncrement) {
|
Chris@0
|
1434
|
Chris@0
|
1435 m_layer.fillCacheColumn(int((f - start) / windowIncrement),
|
Chris@9
|
1436 input, output, plan,
|
Chris@9
|
1437 windowSize, windowIncrement,
|
Chris@35
|
1438 //!!! actually if we're doing phase adjustment we also want to fill the column preceding the visible area so that we have the right values for the first visible one (also applies below)
|
Chris@35
|
1439 windower, f == visibleStart);
|
Chris@0
|
1440
|
Chris@0
|
1441 if (m_layer.m_cacheInvalid || m_layer.m_exiting) {
|
Chris@0
|
1442 interrupted = true;
|
Chris@0
|
1443 m_fillExtent = 0;
|
Chris@0
|
1444 break;
|
Chris@0
|
1445 }
|
Chris@0
|
1446
|
Chris@37
|
1447 if (++counter == updateAt || f == visibleEnd - 1) {
|
Chris@0
|
1448 if (f < end) m_fillExtent = f;
|
Chris@0
|
1449 m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
|
Chris@0
|
1450 float(end - start)));
|
Chris@0
|
1451 counter = 0;
|
Chris@0
|
1452 }
|
Chris@0
|
1453 }
|
Chris@37
|
1454
|
Chris@37
|
1455 m_layer.m_cachedInitialVisibleArea = true;
|
Chris@37
|
1456 std::cerr << "SpectrogramLayer::CacheFillThread::run: visible bit done" << std::endl;
|
Chris@0
|
1457 }
|
Chris@0
|
1458
|
Chris@0
|
1459 if (!interrupted && doVisibleFirst) {
|
Chris@0
|
1460
|
Chris@0
|
1461 for (size_t f = visibleEnd; f < end; f += windowIncrement) {
|
Chris@0
|
1462
|
Chris@0
|
1463 if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement),
|
Chris@9
|
1464 input, output, plan,
|
Chris@9
|
1465 windowSize, windowIncrement,
|
Chris@35
|
1466 windower, f == visibleEnd)) {
|
Chris@0
|
1467 interrupted = true;
|
Chris@0
|
1468 m_fillExtent = 0;
|
Chris@0
|
1469 break;
|
Chris@0
|
1470 }
|
Chris@0
|
1471
|
Chris@0
|
1472
|
Chris@37
|
1473 if (++counter == updateAt || f == end - 1) {
|
Chris@37
|
1474 m_fillExtent = f;
|
Chris@0
|
1475 m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
|
Chris@0
|
1476 float(end - start)));
|
Chris@0
|
1477 counter = 0;
|
Chris@0
|
1478 }
|
Chris@0
|
1479 }
|
Chris@0
|
1480 }
|
Chris@0
|
1481
|
Chris@0
|
1482 if (!interrupted) {
|
Chris@0
|
1483
|
Chris@0
|
1484 size_t remainingEnd = end;
|
Chris@0
|
1485 if (doVisibleFirst) {
|
Chris@0
|
1486 remainingEnd = visibleStart;
|
Chris@0
|
1487 if (remainingEnd > start) --remainingEnd;
|
Chris@0
|
1488 else remainingEnd = start;
|
Chris@0
|
1489 }
|
Chris@0
|
1490 size_t baseCompletion = m_fillCompletion;
|
Chris@0
|
1491
|
Chris@0
|
1492 for (size_t f = start; f < remainingEnd; f += windowIncrement) {
|
Chris@0
|
1493
|
Chris@0
|
1494 if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement),
|
Chris@9
|
1495 input, output, plan,
|
Chris@9
|
1496 windowSize, windowIncrement,
|
Chris@35
|
1497 windower, f == start)) {
|
Chris@0
|
1498 interrupted = true;
|
Chris@0
|
1499 m_fillExtent = 0;
|
Chris@0
|
1500 break;
|
Chris@0
|
1501 }
|
Chris@0
|
1502
|
Chris@37
|
1503 if (++counter == updateAt ||
|
Chris@37
|
1504 f == visibleEnd - 1 ||
|
Chris@37
|
1505 f == remainingEnd - 1) {
|
Chris@0
|
1506 m_fillExtent = f;
|
Chris@0
|
1507 m_fillCompletion = baseCompletion +
|
Chris@0
|
1508 size_t(100 * fabsf(float(f - start) /
|
Chris@0
|
1509 float(end - start)));
|
Chris@0
|
1510 counter = 0;
|
Chris@0
|
1511 }
|
Chris@0
|
1512 }
|
Chris@0
|
1513 }
|
Chris@0
|
1514
|
Chris@0
|
1515 fftw_destroy_plan(plan);
|
Chris@0
|
1516 fftw_free(output);
|
Chris@0
|
1517 fftw_free(input);
|
Chris@0
|
1518
|
Chris@0
|
1519 if (!interrupted) {
|
Chris@0
|
1520 m_fillExtent = end;
|
Chris@0
|
1521 m_fillCompletion = 100;
|
Chris@0
|
1522 }
|
Chris@0
|
1523
|
Chris@0
|
1524 m_layer.m_mutex.lock();
|
Chris@0
|
1525 }
|
Chris@0
|
1526
|
Chris@0
|
1527 if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000);
|
Chris@0
|
1528 }
|
Chris@0
|
1529 }
|
Chris@0
|
1530
|
Chris@0
|
1531 bool
|
Chris@0
|
1532 SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const
|
Chris@0
|
1533 {
|
Chris@0
|
1534 int h = m_view->height();
|
Chris@0
|
1535 if (y < 0 || y >= h) return false;
|
Chris@0
|
1536
|
Chris@0
|
1537 // Each pixel in a column is drawn from a possibly non-
|
Chris@0
|
1538 // integral set of frequency bins.
|
Chris@0
|
1539
|
Chris@0
|
1540 if (m_frequencyScale == LinearFrequencyScale) {
|
Chris@0
|
1541
|
Chris@0
|
1542 size_t bins = m_windowSize / 2;
|
Chris@0
|
1543
|
Chris@0
|
1544 if (m_maxFrequency > 0) {
|
Chris@0
|
1545 int sr = m_model->getSampleRate();
|
Chris@0
|
1546 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
|
Chris@0
|
1547 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
|
Chris@0
|
1548 }
|
Chris@0
|
1549
|
Chris@0
|
1550 q0 = float(h - y - 1) * bins / h;
|
Chris@0
|
1551 q1 = float(h - y) * bins / h;
|
Chris@0
|
1552
|
Chris@0
|
1553 } else {
|
Chris@0
|
1554
|
Chris@0
|
1555 // This is all most ad-hoc. I'm not at my brightest.
|
Chris@0
|
1556
|
Chris@0
|
1557 int sr = m_model->getSampleRate();
|
Chris@0
|
1558
|
Chris@0
|
1559 float maxf = m_maxFrequency;
|
Chris@0
|
1560 if (maxf == 0.0) maxf = float(sr) / 2;
|
Chris@0
|
1561
|
Chris@0
|
1562 float minf = float(sr) / m_windowSize;
|
Chris@0
|
1563
|
Chris@0
|
1564 float maxlogf = log10f(maxf);
|
Chris@0
|
1565 float minlogf = log10f(minf);
|
Chris@35
|
1566
|
Chris@0
|
1567 float logf0 = minlogf + ((maxlogf - minlogf) * (h - y - 1)) / h;
|
Chris@0
|
1568 float logf1 = minlogf + ((maxlogf - minlogf) * (h - y)) / h;
|
Chris@0
|
1569
|
Chris@0
|
1570 float f0 = pow(10.f, logf0);
|
Chris@0
|
1571 float f1 = pow(10.f, logf1);
|
Chris@0
|
1572
|
Chris@0
|
1573 q0 = ((f0 * m_windowSize) / sr) - 1;
|
Chris@0
|
1574 q1 = ((f1 * m_windowSize) / sr) - 1;
|
Chris@0
|
1575
|
Chris@0
|
1576 // std::cout << "y=" << y << " h=" << h << " maxf=" << maxf << " maxlogf="
|
Chris@0
|
1577 // << maxlogf << " logf0=" << logf0 << " f0=" << f0 << " q0="
|
Chris@0
|
1578 // << q0 << std::endl;
|
Chris@0
|
1579 }
|
Chris@0
|
1580
|
Chris@0
|
1581 return true;
|
Chris@0
|
1582 }
|
Chris@0
|
1583
|
Chris@0
|
1584 bool
|
Chris@20
|
1585 SpectrogramLayer::getXBinRange(int x, float &s0, float &s1) const
|
Chris@0
|
1586 {
|
Chris@21
|
1587 size_t modelStart = m_model->getStartFrame();
|
Chris@21
|
1588 size_t modelEnd = m_model->getEndFrame();
|
Chris@0
|
1589
|
Chris@0
|
1590 // Each pixel column covers an exact range of sample frames:
|
Chris@20
|
1591 int f0 = getFrameForX(x) - modelStart;
|
Chris@20
|
1592 int f1 = getFrameForX(x + 1) - modelStart - 1;
|
Chris@20
|
1593
|
Chris@0
|
1594 if (f1 < int(modelStart) || f0 > int(modelEnd)) return false;
|
Chris@20
|
1595
|
Chris@0
|
1596 // And that range may be drawn from a possibly non-integral
|
Chris@0
|
1597 // range of spectrogram windows:
|
Chris@0
|
1598
|
Chris@0
|
1599 size_t windowIncrement = getWindowIncrement();
|
Chris@0
|
1600 s0 = float(f0) / windowIncrement;
|
Chris@0
|
1601 s1 = float(f1) / windowIncrement;
|
Chris@0
|
1602
|
Chris@0
|
1603 return true;
|
Chris@0
|
1604 }
|
Chris@0
|
1605
|
Chris@0
|
1606 bool
|
Chris@0
|
1607 SpectrogramLayer::getXBinSourceRange(int x, RealTime &min, RealTime &max) const
|
Chris@0
|
1608 {
|
Chris@0
|
1609 float s0 = 0, s1 = 0;
|
Chris@0
|
1610 if (!getXBinRange(x, s0, s1)) return false;
|
Chris@0
|
1611
|
Chris@0
|
1612 int s0i = int(s0 + 0.001);
|
Chris@0
|
1613 int s1i = int(s1);
|
Chris@0
|
1614
|
Chris@0
|
1615 int windowIncrement = getWindowIncrement();
|
Chris@0
|
1616 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
|
Chris@0
|
1617 int w1 = s1i * windowIncrement + windowIncrement +
|
Chris@0
|
1618 (m_windowSize - windowIncrement)/2 - 1;
|
Chris@0
|
1619
|
Chris@0
|
1620 min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
|
Chris@0
|
1621 max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
|
Chris@0
|
1622 return true;
|
Chris@0
|
1623 }
|
Chris@0
|
1624
|
Chris@0
|
1625 bool
|
Chris@0
|
1626 SpectrogramLayer::getYBinSourceRange(int y, float &freqMin, float &freqMax)
|
Chris@0
|
1627 const
|
Chris@0
|
1628 {
|
Chris@0
|
1629 float q0 = 0, q1 = 0;
|
Chris@0
|
1630 if (!getYBinRange(y, q0, q1)) return false;
|
Chris@0
|
1631
|
Chris@0
|
1632 int q0i = int(q0 + 0.001);
|
Chris@0
|
1633 int q1i = int(q1);
|
Chris@0
|
1634
|
Chris@0
|
1635 int sr = m_model->getSampleRate();
|
Chris@0
|
1636
|
Chris@0
|
1637 for (int q = q0i; q <= q1i; ++q) {
|
Chris@35
|
1638 int binfreq = (sr * q) / m_windowSize;
|
Chris@0
|
1639 if (q == q0i) freqMin = binfreq;
|
Chris@0
|
1640 if (q == q1i) freqMax = binfreq;
|
Chris@0
|
1641 }
|
Chris@0
|
1642 return true;
|
Chris@0
|
1643 }
|
Chris@35
|
1644
|
Chris@35
|
1645 bool
|
Chris@35
|
1646 SpectrogramLayer::getAdjustedYBinSourceRange(int x, int y,
|
Chris@35
|
1647 float &freqMin, float &freqMax,
|
Chris@35
|
1648 float &adjFreqMin, float &adjFreqMax)
|
Chris@35
|
1649 const
|
Chris@35
|
1650 {
|
Chris@35
|
1651 float s0 = 0, s1 = 0;
|
Chris@35
|
1652 if (!getXBinRange(x, s0, s1)) return false;
|
Chris@35
|
1653
|
Chris@35
|
1654 float q0 = 0, q1 = 0;
|
Chris@35
|
1655 if (!getYBinRange(y, q0, q1)) return false;
|
Chris@35
|
1656
|
Chris@35
|
1657 int s0i = int(s0 + 0.001);
|
Chris@35
|
1658 int s1i = int(s1);
|
Chris@35
|
1659
|
Chris@35
|
1660 int q0i = int(q0 + 0.001);
|
Chris@35
|
1661 int q1i = int(q1);
|
Chris@35
|
1662
|
Chris@35
|
1663 int sr = m_model->getSampleRate();
|
Chris@35
|
1664
|
Chris@35
|
1665 bool haveAdj = false;
|
Chris@35
|
1666
|
Chris@37
|
1667 bool peaksOnly = (m_binDisplay == PeakBins ||
|
Chris@37
|
1668 m_binDisplay == PeakFrequencies);
|
Chris@37
|
1669
|
Chris@35
|
1670 for (int q = q0i; q <= q1i; ++q) {
|
Chris@35
|
1671
|
Chris@35
|
1672 for (int s = s0i; s <= s1i; ++s) {
|
Chris@35
|
1673
|
Chris@35
|
1674 float binfreq = (sr * q) / m_windowSize;
|
Chris@35
|
1675 if (q == q0i) freqMin = binfreq;
|
Chris@35
|
1676 if (q == q1i) freqMax = binfreq;
|
Chris@37
|
1677
|
Chris@37
|
1678 if (m_cache->getValueAt(s, q) == NO_VALUE) {
|
Chris@37
|
1679 continue;
|
Chris@37
|
1680 }
|
Chris@35
|
1681
|
Chris@37
|
1682 if (m_binDisplay == PeakFrequencies &&
|
Chris@35
|
1683 m_phaseAdjustCache) {
|
Chris@35
|
1684
|
Chris@35
|
1685 unsigned char cadj = m_phaseAdjustCache->getValueAt(s, q);
|
Chris@35
|
1686 int adjust = int((signed char)cadj);
|
Chris@35
|
1687
|
Chris@35
|
1688 float nextBinFreq = (sr * (q + 1)) / m_windowSize;
|
Chris@37
|
1689 float fadjust = (adjust * (nextBinFreq - binfreq)) / 100.0;//!!!
|
Chris@35
|
1690 float f = binfreq + fadjust;
|
Chris@35
|
1691 if (!haveAdj || f < adjFreqMin) adjFreqMin = f;
|
Chris@35
|
1692 if (!haveAdj || f > adjFreqMax) adjFreqMax = f;
|
Chris@35
|
1693 haveAdj = true;
|
Chris@35
|
1694 }
|
Chris@35
|
1695 }
|
Chris@35
|
1696 }
|
Chris@35
|
1697
|
Chris@35
|
1698 if (!haveAdj) {
|
Chris@35
|
1699 adjFreqMin = adjFreqMax = 0.0f;
|
Chris@35
|
1700 }
|
Chris@35
|
1701
|
Chris@35
|
1702 return haveAdj;
|
Chris@35
|
1703 }
|
Chris@0
|
1704
|
Chris@0
|
1705 bool
|
Chris@0
|
1706 SpectrogramLayer::getXYBinSourceRange(int x, int y, float &dbMin, float &dbMax) const
|
Chris@0
|
1707 {
|
Chris@0
|
1708 float q0 = 0, q1 = 0;
|
Chris@0
|
1709 if (!getYBinRange(y, q0, q1)) return false;
|
Chris@0
|
1710
|
Chris@0
|
1711 float s0 = 0, s1 = 0;
|
Chris@0
|
1712 if (!getXBinRange(x, s0, s1)) return false;
|
Chris@0
|
1713
|
Chris@0
|
1714 int q0i = int(q0 + 0.001);
|
Chris@0
|
1715 int q1i = int(q1);
|
Chris@0
|
1716
|
Chris@0
|
1717 int s0i = int(s0 + 0.001);
|
Chris@0
|
1718 int s1i = int(s1);
|
Chris@0
|
1719
|
Chris@37
|
1720 bool rv = false;
|
Chris@37
|
1721
|
Chris@0
|
1722 if (m_mutex.tryLock()) {
|
Chris@0
|
1723 if (m_cache && !m_cacheInvalid) {
|
Chris@0
|
1724
|
Chris@31
|
1725 int cw = m_cache->getWidth();
|
Chris@31
|
1726 int ch = m_cache->getHeight();
|
Chris@0
|
1727
|
Chris@0
|
1728 int min = -1, max = -1;
|
Chris@0
|
1729
|
Chris@0
|
1730 for (int q = q0i; q <= q1i; ++q) {
|
Chris@0
|
1731 for (int s = s0i; s <= s1i; ++s) {
|
Chris@0
|
1732 if (s >= 0 && q >= 0 && s < cw && q < ch) {
|
Chris@31
|
1733 int value = int(m_cache->getValueAt(s, q));
|
Chris@37
|
1734 if (value == NO_VALUE) continue;
|
Chris@0
|
1735 if (min == -1 || value < min) min = value;
|
Chris@0
|
1736 if (max == -1 || value > max) max = value;
|
Chris@0
|
1737 }
|
Chris@0
|
1738 }
|
Chris@0
|
1739 }
|
Chris@0
|
1740
|
Chris@37
|
1741 if (min >= 0) {
|
Chris@37
|
1742 dbMin = (float(min) / 256.0) * 80.0 - 80.0;
|
Chris@37
|
1743 dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1;
|
Chris@37
|
1744 rv = true;
|
Chris@37
|
1745 }
|
Chris@0
|
1746 }
|
Chris@0
|
1747
|
Chris@0
|
1748 m_mutex.unlock();
|
Chris@0
|
1749 }
|
Chris@0
|
1750
|
Chris@37
|
1751 return rv;
|
Chris@0
|
1752 }
|
Chris@0
|
1753
|
Chris@0
|
1754 void
|
Chris@0
|
1755 SpectrogramLayer::paint(QPainter &paint, QRect rect) const
|
Chris@0
|
1756 {
|
Chris@0
|
1757 // Profiler profiler("SpectrogramLayer::paint", true);
|
Chris@0
|
1758 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1759 std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << m_view->getZoomLevel() << ", m_updateTimer " << m_updateTimer << ", pixmap cache invalid " << m_pixmapCacheInvalid << std::endl;
|
Chris@0
|
1760 #endif
|
Chris@0
|
1761
|
Chris@0
|
1762 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
|
Chris@0
|
1763 return;
|
Chris@0
|
1764 }
|
Chris@0
|
1765
|
Chris@29
|
1766 if (m_dormant) {
|
Chris@33
|
1767 std::cerr << "SpectrogramLayer::paint(): Layer is dormant" << std::endl;
|
Chris@29
|
1768 return;
|
Chris@29
|
1769 }
|
Chris@29
|
1770
|
Chris@0
|
1771 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1772 std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl;
|
Chris@0
|
1773 #endif
|
Chris@0
|
1774
|
Chris@37
|
1775 m_mutex.lock();
|
Chris@0
|
1776
|
Chris@0
|
1777 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1778 std::cerr << "SpectrogramLayer::paint(): locked" << std::endl;
|
Chris@0
|
1779 #endif
|
Chris@0
|
1780
|
Chris@0
|
1781 if (m_cacheInvalid) { // lock the mutex before checking this
|
Chris@0
|
1782 m_mutex.unlock();
|
Chris@0
|
1783 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1784 std::cerr << "SpectrogramLayer::paint(): Cache invalid, returning" << std::endl;
|
Chris@0
|
1785 #endif
|
Chris@0
|
1786 return;
|
Chris@0
|
1787 }
|
Chris@0
|
1788
|
Chris@0
|
1789 bool stillCacheing = (m_updateTimer != 0);
|
Chris@0
|
1790
|
Chris@0
|
1791 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1792 std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl;
|
Chris@0
|
1793 #endif
|
Chris@0
|
1794
|
Chris@0
|
1795 long startFrame = m_view->getStartFrame();
|
Chris@0
|
1796 int zoomLevel = m_view->getZoomLevel();
|
Chris@0
|
1797
|
Chris@0
|
1798 int x0 = 0;
|
Chris@0
|
1799 int x1 = m_view->width();
|
Chris@0
|
1800 int y0 = 0;
|
Chris@0
|
1801 int y1 = m_view->height();
|
Chris@0
|
1802
|
Chris@0
|
1803 bool recreateWholePixmapCache = true;
|
Chris@0
|
1804
|
Chris@0
|
1805 if (!m_pixmapCacheInvalid) {
|
Chris@0
|
1806
|
Chris@0
|
1807 //!!! This cache may have been obsoleted entirely by the
|
Chris@0
|
1808 //scrolling cache in View. Perhaps experiment with
|
Chris@0
|
1809 //removing it and see if it makes things even quicker (or else
|
Chris@0
|
1810 //make it optional)
|
Chris@0
|
1811
|
Chris@0
|
1812 if (int(m_pixmapCacheZoomLevel) == zoomLevel &&
|
Chris@0
|
1813 m_pixmapCache->width() == m_view->width() &&
|
Chris@0
|
1814 m_pixmapCache->height() == m_view->height()) {
|
Chris@0
|
1815
|
Chris@20
|
1816 if (getXForFrame(m_pixmapCacheStartFrame) ==
|
Chris@20
|
1817 getXForFrame(startFrame)) {
|
Chris@0
|
1818
|
Chris@0
|
1819 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1820 std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl;
|
Chris@0
|
1821 #endif
|
Chris@0
|
1822
|
Chris@0
|
1823 m_mutex.unlock();
|
Chris@0
|
1824 paint.drawPixmap(rect, *m_pixmapCache, rect);
|
Chris@0
|
1825 return;
|
Chris@0
|
1826
|
Chris@0
|
1827 } else {
|
Chris@0
|
1828
|
Chris@0
|
1829 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1830 std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl;
|
Chris@0
|
1831 #endif
|
Chris@0
|
1832
|
Chris@0
|
1833 recreateWholePixmapCache = false;
|
Chris@0
|
1834
|
Chris@20
|
1835 int dx = getXForFrame(m_pixmapCacheStartFrame) -
|
Chris@20
|
1836 getXForFrame(startFrame);
|
Chris@0
|
1837
|
Chris@0
|
1838 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1839 std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << m_pixmapCache->width() << "x" << m_pixmapCache->height() << ")" << std::endl;
|
Chris@0
|
1840 #endif
|
Chris@0
|
1841
|
Chris@0
|
1842 if (dx > -m_pixmapCache->width() && dx < m_pixmapCache->width()) {
|
Chris@0
|
1843
|
Chris@0
|
1844 #if defined(Q_WS_WIN32) || defined(Q_WS_MAC)
|
Chris@0
|
1845 // Copying a pixmap to itself doesn't work
|
Chris@0
|
1846 // properly on Windows or Mac (it only works when
|
Chris@0
|
1847 // moving in one direction).
|
Chris@0
|
1848
|
Chris@0
|
1849 //!!! Need a utility function for this
|
Chris@0
|
1850
|
Chris@0
|
1851 static QPixmap *tmpPixmap = 0;
|
Chris@0
|
1852 if (!tmpPixmap ||
|
Chris@0
|
1853 tmpPixmap->width() != m_pixmapCache->width() ||
|
Chris@0
|
1854 tmpPixmap->height() != m_pixmapCache->height()) {
|
Chris@0
|
1855 delete tmpPixmap;
|
Chris@0
|
1856 tmpPixmap = new QPixmap(m_pixmapCache->width(),
|
Chris@0
|
1857 m_pixmapCache->height());
|
Chris@0
|
1858 }
|
Chris@0
|
1859 QPainter cachePainter;
|
Chris@0
|
1860 cachePainter.begin(tmpPixmap);
|
Chris@0
|
1861 cachePainter.drawPixmap(0, 0, *m_pixmapCache);
|
Chris@0
|
1862 cachePainter.end();
|
Chris@0
|
1863 cachePainter.begin(m_pixmapCache);
|
Chris@0
|
1864 cachePainter.drawPixmap(dx, 0, *tmpPixmap);
|
Chris@0
|
1865 cachePainter.end();
|
Chris@0
|
1866 #else
|
Chris@0
|
1867 QPainter cachePainter(m_pixmapCache);
|
Chris@0
|
1868 cachePainter.drawPixmap(dx, 0, *m_pixmapCache);
|
Chris@0
|
1869 cachePainter.end();
|
Chris@0
|
1870 #endif
|
Chris@0
|
1871
|
Chris@0
|
1872 paint.drawPixmap(rect, *m_pixmapCache, rect);
|
Chris@0
|
1873
|
Chris@0
|
1874 if (dx < 0) {
|
Chris@0
|
1875 x0 = m_pixmapCache->width() + dx;
|
Chris@0
|
1876 x1 = m_pixmapCache->width();
|
Chris@0
|
1877 } else {
|
Chris@0
|
1878 x0 = 0;
|
Chris@0
|
1879 x1 = dx;
|
Chris@0
|
1880 }
|
Chris@0
|
1881 }
|
Chris@0
|
1882 }
|
Chris@0
|
1883 } else {
|
Chris@0
|
1884 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
1885 std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl;
|
Chris@0
|
1886 #endif
|
Chris@0
|
1887 }
|
Chris@0
|
1888 }
|
Chris@0
|
1889
|
Chris@0
|
1890 if (stillCacheing) {
|
Chris@0
|
1891 x0 = rect.left();
|
Chris@0
|
1892 x1 = rect.right() + 1;
|
Chris@0
|
1893 y0 = rect.top();
|
Chris@0
|
1894 y1 = rect.bottom() + 1;
|
Chris@0
|
1895 }
|
Chris@0
|
1896
|
Chris@0
|
1897 int w = x1 - x0;
|
Chris@0
|
1898 int h = y1 - y0;
|
Chris@0
|
1899
|
Chris@0
|
1900 // std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl;
|
Chris@0
|
1901
|
Chris@0
|
1902 QImage scaled(w, h, QImage::Format_RGB32);
|
Chris@35
|
1903 scaled.fill(0);
|
Chris@35
|
1904
|
Chris@35
|
1905 float ymag[h];
|
Chris@35
|
1906 float ydiv[h];
|
Chris@37
|
1907
|
Chris@37
|
1908 int sr = m_model->getSampleRate();
|
Chris@35
|
1909
|
Chris@35
|
1910 size_t bins = m_windowSize / 2;
|
Chris@35
|
1911 if (m_maxFrequency > 0) {
|
Chris@35
|
1912 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
|
Chris@35
|
1913 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
|
Chris@35
|
1914 }
|
Chris@35
|
1915
|
Chris@37
|
1916 size_t minbin = 0;
|
Chris@37
|
1917 if (m_minFrequency > 0) {
|
Chris@37
|
1918 minbin = int((double(m_minFrequency) * m_windowSize) / sr + 0.1);
|
Chris@37
|
1919 if (minbin >= bins) minbin = bins - 1;
|
Chris@37
|
1920 }
|
Chris@37
|
1921
|
Chris@37
|
1922 float minFreq = (float(minbin) * sr) / m_windowSize;
|
Chris@35
|
1923 float maxFreq = (float(bins) * sr) / m_windowSize;
|
Chris@0
|
1924
|
Chris@0
|
1925 m_mutex.unlock();
|
Chris@0
|
1926
|
Chris@35
|
1927 for (int x = 0; x < w; ++x) {
|
Chris@35
|
1928
|
Chris@35
|
1929 m_mutex.lock();
|
Chris@35
|
1930 if (m_cacheInvalid) {
|
Chris@35
|
1931 m_mutex.unlock();
|
Chris@35
|
1932 break;
|
Chris@35
|
1933 }
|
Chris@35
|
1934
|
Chris@35
|
1935 for (int y = 0; y < h; ++y) {
|
Chris@35
|
1936 ymag[y] = 0.0f;
|
Chris@35
|
1937 ydiv[y] = 0.0f;
|
Chris@35
|
1938 }
|
Chris@35
|
1939
|
Chris@35
|
1940 float s0 = 0, s1 = 0;
|
Chris@35
|
1941
|
Chris@35
|
1942 if (!getXBinRange(x0 + x, s0, s1)) {
|
Chris@35
|
1943 assert(x <= scaled.width());
|
Chris@35
|
1944 for (int y = 0; y < h; ++y) {
|
Chris@35
|
1945 scaled.setPixel(x, y, qRgb(0, 0, 0));
|
Chris@35
|
1946 }
|
Chris@35
|
1947 m_mutex.unlock();
|
Chris@35
|
1948 continue;
|
Chris@35
|
1949 }
|
Chris@35
|
1950
|
Chris@35
|
1951 int s0i = int(s0 + 0.001);
|
Chris@35
|
1952 int s1i = int(s1);
|
Chris@35
|
1953
|
Chris@37
|
1954 for (int q = minbin; q < bins; ++q) {
|
Chris@35
|
1955
|
Chris@35
|
1956 for (int s = s0i; s <= s1i; ++s) {
|
Chris@35
|
1957
|
Chris@35
|
1958 float sprop = 1.0;
|
Chris@35
|
1959 if (s == s0i) sprop *= (s + 1) - s0;
|
Chris@35
|
1960 if (s == s1i) sprop *= s1 - s;
|
Chris@35
|
1961
|
Chris@35
|
1962 float f0 = (float(q) * sr) / m_windowSize;
|
Chris@35
|
1963 float f1 = (float(q + 1) * sr) / m_windowSize;
|
Chris@35
|
1964
|
Chris@37
|
1965 if ((m_binDisplay == PeakFrequencies) &&
|
Chris@35
|
1966 m_phaseAdjustCache) {
|
Chris@35
|
1967
|
Chris@35
|
1968 unsigned char cadj = m_phaseAdjustCache->getValueAt(s, q);
|
Chris@35
|
1969 int adjust = int((signed char)cadj);
|
Chris@37
|
1970 float fadjust = (adjust * (f1 - f0)) / 100.0;
|
Chris@35
|
1971 f0 = f1 = f0 + fadjust;
|
Chris@35
|
1972 }
|
Chris@35
|
1973
|
Chris@37
|
1974 float y0 = h - (h * (f1 - minFreq)) / maxFreq;
|
Chris@37
|
1975 float y1 = h - (h * (f0 - minFreq)) / maxFreq;
|
Chris@37
|
1976
|
Chris@37
|
1977 //!!! We want a general View::getYForFrequency and inverse
|
Chris@37
|
1978 // that can be passed min freq, max freq and log/linear. We
|
Chris@37
|
1979 // can then introduce a central correspondence between, say,
|
Chris@37
|
1980 // spectrogram layer and a frequency-scaled MIDI layer on the
|
Chris@37
|
1981 // same view.
|
Chris@35
|
1982
|
Chris@35
|
1983 if (m_frequencyScale == LogFrequencyScale) {
|
Chris@35
|
1984
|
Chris@37
|
1985 //!!! also, shouldn't be recalculating this every time!
|
Chris@37
|
1986
|
Chris@37
|
1987 // float maxf = m_maxFrequency;
|
Chris@37
|
1988 // if (maxf == 0.0) maxf = float(sr) / 2;
|
Chris@35
|
1989
|
Chris@37
|
1990 // float minf = float(sr) / m_windowSize;
|
Chris@35
|
1991
|
Chris@37
|
1992 float maxlogf = log10f(maxFreq);
|
Chris@37
|
1993 float minlogf;
|
Chris@37
|
1994
|
Chris@37
|
1995 if (minFreq > 0) minlogf = log10f(minFreq);
|
Chris@37
|
1996 else minlogf = log10f(float(sr) / m_windowSize);
|
Chris@35
|
1997
|
Chris@35
|
1998 y0 = h - (h * (log10f(f1) - minlogf)) / (maxlogf - minlogf);
|
Chris@35
|
1999 y1 = h - (h * (log10f(f0) - minlogf)) / (maxlogf - minlogf);
|
Chris@35
|
2000 }
|
Chris@35
|
2001
|
Chris@35
|
2002 int y0i = int(y0 + 0.001);
|
Chris@35
|
2003 int y1i = int(y1);
|
Chris@35
|
2004
|
Chris@35
|
2005 for (int y = y0i; y <= y1i; ++y) {
|
Chris@35
|
2006
|
Chris@35
|
2007 if (y < 0 || y >= h) continue;
|
Chris@35
|
2008
|
Chris@35
|
2009 float yprop = sprop;
|
Chris@35
|
2010 if (y == y0i) yprop *= (y + 1) - y0;
|
Chris@35
|
2011 if (y == y1i) yprop *= y1 - y;
|
Chris@37
|
2012
|
Chris@37
|
2013 int value = m_cache->getValueAt(s, q);
|
Chris@37
|
2014 if (value == NO_VALUE) continue;
|
Chris@37
|
2015
|
Chris@37
|
2016 ymag[y] += yprop * value;
|
Chris@35
|
2017 ydiv[y] += yprop;
|
Chris@35
|
2018 }
|
Chris@35
|
2019 }
|
Chris@35
|
2020 }
|
Chris@35
|
2021
|
Chris@35
|
2022 for (int y = 0; y < h; ++y) {
|
Chris@35
|
2023
|
Chris@35
|
2024 int pixel = 1;
|
Chris@35
|
2025
|
Chris@35
|
2026 if (ydiv[y] > 0.0) {
|
Chris@35
|
2027 pixel = int(ymag[y] / ydiv[y]);
|
Chris@35
|
2028 if (pixel > 255) pixel = 255;
|
Chris@35
|
2029 if (pixel < 1) pixel = 1;
|
Chris@35
|
2030 }
|
Chris@35
|
2031
|
Chris@35
|
2032 assert(x <= scaled.width());
|
Chris@35
|
2033 QColor c = m_cache->getColour(pixel);
|
Chris@35
|
2034 scaled.setPixel(x, y,
|
Chris@35
|
2035 qRgb(c.red(), c.green(), c.blue()));
|
Chris@35
|
2036 }
|
Chris@35
|
2037
|
Chris@35
|
2038
|
Chris@35
|
2039 m_mutex.unlock();
|
Chris@35
|
2040 }
|
Chris@35
|
2041
|
Chris@0
|
2042 paint.drawImage(x0, y0, scaled);
|
Chris@0
|
2043
|
Chris@0
|
2044 if (recreateWholePixmapCache) {
|
Chris@0
|
2045 delete m_pixmapCache;
|
Chris@0
|
2046 m_pixmapCache = new QPixmap(w, h);
|
Chris@0
|
2047 }
|
Chris@0
|
2048
|
Chris@0
|
2049 QPainter cachePainter(m_pixmapCache);
|
Chris@0
|
2050 cachePainter.drawImage(x0, y0, scaled);
|
Chris@0
|
2051 cachePainter.end();
|
Chris@0
|
2052
|
Chris@0
|
2053 m_pixmapCacheInvalid = false;
|
Chris@0
|
2054 m_pixmapCacheStartFrame = startFrame;
|
Chris@0
|
2055 m_pixmapCacheZoomLevel = zoomLevel;
|
Chris@0
|
2056
|
Chris@0
|
2057 #ifdef DEBUG_SPECTROGRAM_REPAINT
|
Chris@0
|
2058 std::cerr << "SpectrogramLayer::paint() returning" << std::endl;
|
Chris@0
|
2059 #endif
|
Chris@0
|
2060 }
|
Chris@0
|
2061
|
Chris@0
|
2062 int
|
Chris@0
|
2063 SpectrogramLayer::getCompletion() const
|
Chris@0
|
2064 {
|
Chris@0
|
2065 if (m_updateTimer == 0) return 100;
|
Chris@0
|
2066 size_t completion = m_fillThread->getFillCompletion();
|
Chris@0
|
2067 // std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl;
|
Chris@0
|
2068 return completion;
|
Chris@0
|
2069 }
|
Chris@0
|
2070
|
Chris@28
|
2071 bool
|
Chris@28
|
2072 SpectrogramLayer::snapToFeatureFrame(int &frame,
|
Chris@28
|
2073 size_t &resolution,
|
Chris@28
|
2074 SnapType snap) const
|
Chris@13
|
2075 {
|
Chris@13
|
2076 resolution = getWindowIncrement();
|
Chris@28
|
2077 int left = (frame / resolution) * resolution;
|
Chris@28
|
2078 int right = left + resolution;
|
Chris@28
|
2079
|
Chris@28
|
2080 switch (snap) {
|
Chris@28
|
2081 case SnapLeft: frame = left; break;
|
Chris@28
|
2082 case SnapRight: frame = right; break;
|
Chris@28
|
2083 case SnapNearest:
|
Chris@28
|
2084 case SnapNeighbouring:
|
Chris@28
|
2085 if (frame - left > right - frame) frame = right;
|
Chris@28
|
2086 else frame = left;
|
Chris@28
|
2087 break;
|
Chris@28
|
2088 }
|
Chris@28
|
2089
|
Chris@28
|
2090 return true;
|
Chris@28
|
2091 }
|
Chris@13
|
2092
|
Chris@25
|
2093 QString
|
Chris@25
|
2094 SpectrogramLayer::getFeatureDescription(QPoint &pos) const
|
Chris@25
|
2095 {
|
Chris@25
|
2096 int x = pos.x();
|
Chris@25
|
2097 int y = pos.y();
|
Chris@0
|
2098
|
Chris@25
|
2099 if (!m_model || !m_model->isOK()) return "";
|
Chris@0
|
2100
|
Chris@0
|
2101 float dbMin = 0, dbMax = 0;
|
Chris@0
|
2102 float freqMin = 0, freqMax = 0;
|
Chris@35
|
2103 float adjFreqMin = 0, adjFreqMax = 0;
|
Chris@25
|
2104 QString pitchMin, pitchMax;
|
Chris@0
|
2105 RealTime rtMin, rtMax;
|
Chris@0
|
2106
|
Chris@25
|
2107 bool haveDb = false;
|
Chris@0
|
2108
|
Chris@25
|
2109 if (!getXBinSourceRange(x, rtMin, rtMax)) return "";
|
Chris@25
|
2110 if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true;
|
Chris@0
|
2111
|
Chris@35
|
2112 QString adjFreqText = "", adjPitchText = "";
|
Chris@35
|
2113
|
Chris@37
|
2114 if ((m_binDisplay == PeakFrequencies) &&
|
Chris@35
|
2115 m_phaseAdjustCache) {
|
Chris@35
|
2116
|
Chris@35
|
2117 if (!getAdjustedYBinSourceRange(x, y, freqMin, freqMax,
|
Chris@35
|
2118 adjFreqMin, adjFreqMax)) return "";
|
Chris@35
|
2119
|
Chris@35
|
2120 if (adjFreqMin != adjFreqMax) {
|
Chris@35
|
2121 adjFreqText = tr("Adjusted Frequency:\t%1 - %2 Hz\n")
|
Chris@35
|
2122 .arg(adjFreqMin).arg(adjFreqMax);
|
Chris@35
|
2123 adjPitchText = tr("Adjusted Pitch:\t%3 - %4\n")
|
Chris@35
|
2124 .arg(Pitch::getPitchLabelForFrequency(adjFreqMin))
|
Chris@35
|
2125 .arg(Pitch::getPitchLabelForFrequency(adjFreqMax));
|
Chris@35
|
2126 } else {
|
Chris@35
|
2127 adjFreqText = tr("Adjusted Frequency:\t%1 Hz\n")
|
Chris@35
|
2128 .arg(adjFreqMin);
|
Chris@35
|
2129 adjPitchText = tr("Adjusted Pitch:\t%2\n")
|
Chris@35
|
2130 .arg(Pitch::getPitchLabelForFrequency(adjFreqMin));
|
Chris@35
|
2131 }
|
Chris@35
|
2132
|
Chris@35
|
2133 } else {
|
Chris@35
|
2134
|
Chris@35
|
2135 if (!getYBinSourceRange(y, freqMin, freqMax)) return "";
|
Chris@35
|
2136 }
|
Chris@35
|
2137
|
Chris@25
|
2138 //!!! want to actually do a one-off FFT to recalculate the dB value!
|
Chris@25
|
2139
|
Chris@25
|
2140 QString text;
|
Chris@25
|
2141
|
Chris@25
|
2142 if (rtMin != rtMax) {
|
Chris@25
|
2143 text += tr("Time:\t%1 - %2\n")
|
Chris@25
|
2144 .arg(rtMin.toText(true).c_str())
|
Chris@25
|
2145 .arg(rtMax.toText(true).c_str());
|
Chris@25
|
2146 } else {
|
Chris@25
|
2147 text += tr("Time:\t%1\n")
|
Chris@25
|
2148 .arg(rtMin.toText(true).c_str());
|
Chris@0
|
2149 }
|
Chris@0
|
2150
|
Chris@25
|
2151 if (freqMin != freqMax) {
|
Chris@35
|
2152 text += tr("Frequency:\t%1 - %2 Hz\n%3Pitch:\t%4 - %5\n%6")
|
Chris@25
|
2153 .arg(freqMin)
|
Chris@25
|
2154 .arg(freqMax)
|
Chris@35
|
2155 .arg(adjFreqText)
|
Chris@25
|
2156 .arg(Pitch::getPitchLabelForFrequency(freqMin))
|
Chris@35
|
2157 .arg(Pitch::getPitchLabelForFrequency(freqMax))
|
Chris@35
|
2158 .arg(adjPitchText);
|
Chris@25
|
2159 } else {
|
Chris@35
|
2160 text += tr("Frequency:\t%1 Hz\n%2Pitch:\t%3\n%4")
|
Chris@25
|
2161 .arg(freqMin)
|
Chris@35
|
2162 .arg(adjFreqText)
|
Chris@35
|
2163 .arg(Pitch::getPitchLabelForFrequency(freqMin))
|
Chris@35
|
2164 .arg(adjPitchText);
|
Chris@25
|
2165 }
|
Chris@25
|
2166
|
Chris@25
|
2167 if (haveDb) {
|
Chris@25
|
2168 if (lrintf(dbMin) != lrintf(dbMax)) {
|
Chris@25
|
2169 text += tr("dB:\t%1 - %2").arg(lrintf(dbMin)).arg(lrintf(dbMax));
|
Chris@25
|
2170 } else {
|
Chris@25
|
2171 text += tr("dB:\t%1").arg(lrintf(dbMin));
|
Chris@25
|
2172 }
|
Chris@25
|
2173 }
|
Chris@25
|
2174
|
Chris@25
|
2175 return text;
|
Chris@0
|
2176 }
|
Chris@25
|
2177
|
Chris@0
|
2178 int
|
Chris@0
|
2179 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const
|
Chris@0
|
2180 {
|
Chris@0
|
2181 if (!m_model || !m_model->isOK()) return 0;
|
Chris@0
|
2182
|
Chris@0
|
2183 int tw = paint.fontMetrics().width(QString("%1")
|
Chris@0
|
2184 .arg(m_maxFrequency > 0 ?
|
Chris@0
|
2185 m_maxFrequency - 1 :
|
Chris@0
|
2186 m_model->getSampleRate() / 2));
|
Chris@0
|
2187
|
Chris@0
|
2188 int fw = paint.fontMetrics().width(QString("43Hz"));
|
Chris@0
|
2189 if (tw < fw) tw = fw;
|
Chris@0
|
2190
|
Chris@0
|
2191 return tw + 13;
|
Chris@0
|
2192 }
|
Chris@0
|
2193
|
Chris@0
|
2194 void
|
Chris@0
|
2195 SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const
|
Chris@0
|
2196 {
|
Chris@0
|
2197 if (!m_model || !m_model->isOK()) {
|
Chris@0
|
2198 return;
|
Chris@0
|
2199 }
|
Chris@0
|
2200
|
Chris@0
|
2201 int h = rect.height(), w = rect.width();
|
Chris@0
|
2202
|
Chris@0
|
2203 size_t bins = m_windowSize / 2;
|
Chris@0
|
2204 int sr = m_model->getSampleRate();
|
Chris@0
|
2205
|
Chris@0
|
2206 if (m_maxFrequency > 0) {
|
Chris@0
|
2207 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
|
Chris@0
|
2208 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
|
Chris@0
|
2209 }
|
Chris@0
|
2210
|
Chris@0
|
2211 int py = -1;
|
Chris@0
|
2212 int textHeight = paint.fontMetrics().height();
|
Chris@0
|
2213 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
|
Chris@0
|
2214
|
Chris@0
|
2215 int bin = -1;
|
Chris@0
|
2216
|
Chris@0
|
2217 for (int y = 0; y < m_view->height(); ++y) {
|
Chris@0
|
2218
|
Chris@0
|
2219 float q0, q1;
|
Chris@0
|
2220 if (!getYBinRange(m_view->height() - y, q0, q1)) continue;
|
Chris@0
|
2221
|
Chris@0
|
2222 int vy;
|
Chris@0
|
2223
|
Chris@0
|
2224 if (int(q0) > bin) {
|
Chris@0
|
2225 vy = y;
|
Chris@0
|
2226 bin = int(q0);
|
Chris@0
|
2227 } else {
|
Chris@0
|
2228 continue;
|
Chris@0
|
2229 }
|
Chris@0
|
2230
|
Chris@0
|
2231 int freq = (sr * (bin + 1)) / m_windowSize;
|
Chris@0
|
2232
|
Chris@0
|
2233 if (py >= 0 && (vy - py) < textHeight - 1) {
|
Chris@0
|
2234 paint.drawLine(w - 4, h - vy, w, h - vy);
|
Chris@0
|
2235 continue;
|
Chris@0
|
2236 }
|
Chris@0
|
2237
|
Chris@0
|
2238 QString text = QString("%1").arg(freq);
|
Chris@0
|
2239 if (bin == 0) text = QString("%1Hz").arg(freq);
|
Chris@0
|
2240 paint.drawLine(0, h - vy, w, h - vy);
|
Chris@0
|
2241
|
Chris@0
|
2242 if (h - vy - textHeight >= -2) {
|
Chris@0
|
2243 int tx = w - 10 - paint.fontMetrics().width(text);
|
Chris@0
|
2244 paint.drawText(tx, h - vy + toff, text);
|
Chris@0
|
2245 }
|
Chris@0
|
2246
|
Chris@0
|
2247 py = vy;
|
Chris@0
|
2248 }
|
Chris@0
|
2249 }
|
Chris@0
|
2250
|
Chris@6
|
2251 QString
|
Chris@6
|
2252 SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const
|
Chris@6
|
2253 {
|
Chris@6
|
2254 QString s;
|
Chris@6
|
2255
|
Chris@6
|
2256 s += QString("channel=\"%1\" "
|
Chris@6
|
2257 "windowSize=\"%2\" "
|
Chris@6
|
2258 "windowType=\"%3\" "
|
Chris@6
|
2259 "windowOverlap=\"%4\" "
|
Chris@37
|
2260 "gain=\"%5\" "
|
Chris@37
|
2261 "threshold=\"%6\" ")
|
Chris@6
|
2262 .arg(m_channel)
|
Chris@6
|
2263 .arg(m_windowSize)
|
Chris@6
|
2264 .arg(m_windowType)
|
Chris@6
|
2265 .arg(m_windowOverlap)
|
Chris@37
|
2266 .arg(m_gain)
|
Chris@37
|
2267 .arg(m_threshold);
|
Chris@37
|
2268
|
Chris@37
|
2269 s += QString("minFrequency=\"%1\" "
|
Chris@37
|
2270 "maxFrequency=\"%2\" "
|
Chris@37
|
2271 "colourScale=\"%3\" "
|
Chris@37
|
2272 "colourScheme=\"%4\" "
|
Chris@37
|
2273 "colourRotation=\"%5\" "
|
Chris@37
|
2274 "frequencyScale=\"%6\" "
|
Chris@37
|
2275 "binDisplay=\"%7\" "
|
Chris@37
|
2276 "normalizeColumns=\"%8\"")
|
Chris@37
|
2277 .arg(m_minFrequency)
|
Chris@6
|
2278 .arg(m_maxFrequency)
|
Chris@6
|
2279 .arg(m_colourScale)
|
Chris@6
|
2280 .arg(m_colourScheme)
|
Chris@37
|
2281 .arg(m_colourRotation)
|
Chris@35
|
2282 .arg(m_frequencyScale)
|
Chris@37
|
2283 .arg(m_binDisplay)
|
Chris@36
|
2284 .arg(m_normalizeColumns ? "true" : "false");
|
Chris@6
|
2285
|
Chris@6
|
2286 return Layer::toXmlString(indent, extraAttributes + " " + s);
|
Chris@6
|
2287 }
|
Chris@6
|
2288
|
Chris@11
|
2289 void
|
Chris@11
|
2290 SpectrogramLayer::setProperties(const QXmlAttributes &attributes)
|
Chris@11
|
2291 {
|
Chris@11
|
2292 bool ok = false;
|
Chris@11
|
2293
|
Chris@11
|
2294 int channel = attributes.value("channel").toInt(&ok);
|
Chris@11
|
2295 if (ok) setChannel(channel);
|
Chris@11
|
2296
|
Chris@11
|
2297 size_t windowSize = attributes.value("windowSize").toUInt(&ok);
|
Chris@11
|
2298 if (ok) setWindowSize(windowSize);
|
Chris@11
|
2299
|
Chris@11
|
2300 WindowType windowType = (WindowType)
|
Chris@11
|
2301 attributes.value("windowType").toInt(&ok);
|
Chris@11
|
2302 if (ok) setWindowType(windowType);
|
Chris@11
|
2303
|
Chris@11
|
2304 size_t windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
|
Chris@11
|
2305 if (ok) setWindowOverlap(windowOverlap);
|
Chris@11
|
2306
|
Chris@11
|
2307 float gain = attributes.value("gain").toFloat(&ok);
|
Chris@11
|
2308 if (ok) setGain(gain);
|
Chris@11
|
2309
|
Chris@37
|
2310 float threshold = attributes.value("threshold").toFloat(&ok);
|
Chris@37
|
2311 if (ok) setThreshold(threshold);
|
Chris@37
|
2312
|
Chris@37
|
2313 size_t minFrequency = attributes.value("minFrequency").toUInt(&ok);
|
Chris@37
|
2314 if (ok) setMinFrequency(minFrequency);
|
Chris@37
|
2315
|
Chris@11
|
2316 size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
|
Chris@11
|
2317 if (ok) setMaxFrequency(maxFrequency);
|
Chris@11
|
2318
|
Chris@11
|
2319 ColourScale colourScale = (ColourScale)
|
Chris@11
|
2320 attributes.value("colourScale").toInt(&ok);
|
Chris@11
|
2321 if (ok) setColourScale(colourScale);
|
Chris@11
|
2322
|
Chris@11
|
2323 ColourScheme colourScheme = (ColourScheme)
|
Chris@11
|
2324 attributes.value("colourScheme").toInt(&ok);
|
Chris@11
|
2325 if (ok) setColourScheme(colourScheme);
|
Chris@11
|
2326
|
Chris@37
|
2327 int colourRotation = attributes.value("colourRotation").toInt(&ok);
|
Chris@37
|
2328 if (ok) setColourRotation(colourRotation);
|
Chris@37
|
2329
|
Chris@11
|
2330 FrequencyScale frequencyScale = (FrequencyScale)
|
Chris@11
|
2331 attributes.value("frequencyScale").toInt(&ok);
|
Chris@11
|
2332 if (ok) setFrequencyScale(frequencyScale);
|
Chris@35
|
2333
|
Chris@37
|
2334 BinDisplay binDisplay = (BinDisplay)
|
Chris@37
|
2335 attributes.value("binDisplay").toInt(&ok);
|
Chris@37
|
2336 if (ok) setBinDisplay(binDisplay);
|
Chris@36
|
2337
|
Chris@36
|
2338 bool normalizeColumns =
|
Chris@36
|
2339 (attributes.value("normalizeColumns").trimmed() == "true");
|
Chris@36
|
2340 setNormalizeColumns(normalizeColumns);
|
Chris@11
|
2341 }
|
Chris@11
|
2342
|
Chris@11
|
2343
|
Chris@0
|
2344 #ifdef INCLUDE_MOCFILES
|
Chris@0
|
2345 #include "SpectrogramLayer.moc.cpp"
|
Chris@0
|
2346 #endif
|
Chris@0
|
2347
|