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