Chris@133
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@133
|
2
|
Chris@133
|
3 /*
|
Chris@133
|
4 Sonic Visualiser
|
Chris@133
|
5 An audio file viewer and annotation editor.
|
Chris@133
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@195
|
7 This file copyright 2006-2007 QMUL.
|
Chris@133
|
8
|
Chris@133
|
9 This program is free software; you can redistribute it and/or
|
Chris@133
|
10 modify it under the terms of the GNU General Public License as
|
Chris@133
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@133
|
12 License, or (at your option) any later version. See the file
|
Chris@133
|
13 COPYING included with this distribution for more information.
|
Chris@133
|
14 */
|
Chris@133
|
15
|
Chris@133
|
16 #include "SpectrumLayer.h"
|
Chris@133
|
17
|
Chris@133
|
18 #include "data/model/FFTModel.h"
|
Chris@133
|
19 #include "view/View.h"
|
Chris@153
|
20 #include "base/AudioLevel.h"
|
Chris@153
|
21 #include "base/Preferences.h"
|
Chris@167
|
22 #include "base/RangeMapper.h"
|
Chris@277
|
23 #include "base/Pitch.h"
|
Chris@1147
|
24 #include "base/Strings.h"
|
Chris@1078
|
25
|
Chris@376
|
26 #include "ColourMapper.h"
|
Chris@1078
|
27 #include "PaintAssistant.h"
|
Chris@254
|
28
|
Chris@254
|
29 #include <QPainter>
|
Chris@316
|
30 #include <QTextStream>
|
Chris@316
|
31
|
Chris@133
|
32
|
Chris@133
|
33 SpectrumLayer::SpectrumLayer() :
|
Chris@193
|
34 m_originModel(0),
|
Chris@153
|
35 m_channel(-1),
|
Chris@153
|
36 m_channelSet(false),
|
Chris@290
|
37 m_windowSize(4096),
|
Chris@153
|
38 m_windowType(HanningWindow),
|
Chris@290
|
39 m_windowHopLevel(3),
|
Chris@284
|
40 m_showPeaks(false),
|
Chris@275
|
41 m_newFFTNeeded(true)
|
Chris@133
|
42 {
|
Chris@153
|
43 Preferences *prefs = Preferences::getInstance();
|
Chris@153
|
44 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
|
Chris@153
|
45 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
|
Chris@153
|
46 setWindowType(prefs->getWindowType());
|
Chris@195
|
47
|
Chris@195
|
48 setBinScale(LogBins);
|
Chris@133
|
49 }
|
Chris@133
|
50
|
Chris@133
|
51 SpectrumLayer::~SpectrumLayer()
|
Chris@133
|
52 {
|
Chris@349
|
53 Model *m = const_cast<Model *>
|
Chris@349
|
54 (static_cast<const Model *>(m_sliceableModel));
|
Chris@458
|
55 if (m) m->aboutToDelete();
|
Chris@349
|
56 m_sliceableModel = 0;
|
Chris@349
|
57 delete m;
|
Chris@133
|
58 }
|
Chris@133
|
59
|
Chris@133
|
60 void
|
Chris@133
|
61 SpectrumLayer::setModel(DenseTimeValueModel *model)
|
Chris@133
|
62 {
|
Chris@587
|
63 SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl;
|
Chris@345
|
64
|
Chris@193
|
65 if (m_originModel == model) return;
|
Chris@349
|
66
|
Chris@193
|
67 m_originModel = model;
|
Chris@277
|
68
|
Chris@350
|
69 if (m_sliceableModel) {
|
Chris@350
|
70 Model *m = const_cast<Model *>
|
Chris@350
|
71 (static_cast<const Model *>(m_sliceableModel));
|
Chris@350
|
72 m->aboutToDelete();
|
Chris@350
|
73 setSliceableModel(0);
|
Chris@350
|
74 delete m;
|
Chris@350
|
75 }
|
Chris@350
|
76
|
Chris@349
|
77 m_newFFTNeeded = true;
|
Chris@349
|
78
|
Chris@349
|
79 emit layerParametersChanged();
|
Chris@349
|
80 }
|
Chris@349
|
81
|
Chris@349
|
82 void
|
Chris@349
|
83 SpectrumLayer::setChannel(int channel)
|
Chris@349
|
84 {
|
Chris@587
|
85 SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
|
Chris@349
|
86
|
Chris@349
|
87 m_channelSet = true;
|
Chris@349
|
88
|
Chris@349
|
89 if (m_channel == channel) return;
|
Chris@349
|
90
|
Chris@349
|
91 m_channel = channel;
|
Chris@349
|
92
|
Chris@349
|
93 m_newFFTNeeded = true;
|
Chris@349
|
94
|
Chris@349
|
95 emit layerParametersChanged();
|
Chris@153
|
96 }
|
Chris@153
|
97
|
Chris@153
|
98 void
|
Chris@193
|
99 SpectrumLayer::setupFFT()
|
Chris@153
|
100 {
|
Chris@349
|
101 if (m_sliceableModel) {
|
Chris@349
|
102 Model *m = const_cast<Model *>
|
Chris@349
|
103 (static_cast<const Model *>(m_sliceableModel));
|
Chris@349
|
104 m->aboutToDelete();
|
Chris@193
|
105 setSliceableModel(0);
|
Chris@349
|
106 delete m;
|
Chris@349
|
107 }
|
Chris@349
|
108
|
Chris@349
|
109 if (!m_originModel) {
|
Chris@349
|
110 return;
|
Chris@153
|
111 }
|
Chris@153
|
112
|
Chris@193
|
113 FFTModel *newFFT = new FFTModel(m_originModel,
|
Chris@193
|
114 m_channel,
|
Chris@193
|
115 m_windowType,
|
Chris@193
|
116 m_windowSize,
|
Chris@193
|
117 getWindowIncrement(),
|
Chris@975
|
118 m_windowSize);
|
Chris@153
|
119
|
Chris@193
|
120 setSliceableModel(newFFT);
|
Chris@193
|
121
|
Chris@254
|
122 m_biasCurve.clear();
|
Chris@805
|
123 for (int i = 0; i < m_windowSize; ++i) {
|
Chris@254
|
124 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
|
Chris@254
|
125 }
|
Chris@254
|
126
|
Chris@349
|
127 m_newFFTNeeded = false;
|
Chris@133
|
128 }
|
Chris@133
|
129
|
Chris@153
|
130 Layer::PropertyList
|
Chris@153
|
131 SpectrumLayer::getProperties() const
|
Chris@153
|
132 {
|
Chris@193
|
133 PropertyList list = SliceLayer::getProperties();
|
Chris@153
|
134 list.push_back("Window Size");
|
Chris@153
|
135 list.push_back("Window Increment");
|
Chris@284
|
136 list.push_back("Show Peak Frequencies");
|
Chris@153
|
137 return list;
|
Chris@153
|
138 }
|
Chris@153
|
139
|
Chris@153
|
140 QString
|
Chris@153
|
141 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
|
Chris@153
|
142 {
|
Chris@153
|
143 if (name == "Window Size") return tr("Window Size");
|
Chris@153
|
144 if (name == "Window Increment") return tr("Window Overlap");
|
Chris@284
|
145 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
|
Chris@193
|
146 return SliceLayer::getPropertyLabel(name);
|
Chris@153
|
147 }
|
Chris@153
|
148
|
Chris@335
|
149 QString
|
Chris@335
|
150 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
|
Chris@335
|
151 {
|
Chris@335
|
152 if (name == "Show Peak Frequencies") return "show-peaks";
|
Chris@335
|
153 return SliceLayer::getPropertyIconName(name);
|
Chris@335
|
154 }
|
Chris@335
|
155
|
Chris@153
|
156 Layer::PropertyType
|
Chris@153
|
157 SpectrumLayer::getPropertyType(const PropertyName &name) const
|
Chris@153
|
158 {
|
Chris@193
|
159 if (name == "Window Size") return ValueProperty;
|
Chris@193
|
160 if (name == "Window Increment") return ValueProperty;
|
Chris@284
|
161 if (name == "Show Peak Frequencies") return ToggleProperty;
|
Chris@193
|
162 return SliceLayer::getPropertyType(name);
|
Chris@153
|
163 }
|
Chris@153
|
164
|
Chris@153
|
165 QString
|
Chris@153
|
166 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
|
Chris@153
|
167 {
|
Chris@153
|
168 if (name == "Window Size" ||
|
Chris@153
|
169 name == "Window Increment") return tr("Window");
|
Chris@557
|
170 if (name == "Show Peak Frequencies") return tr("Bins");
|
Chris@193
|
171 return SliceLayer::getPropertyGroupName(name);
|
Chris@153
|
172 }
|
Chris@153
|
173
|
Chris@153
|
174 int
|
Chris@153
|
175 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
|
Chris@216
|
176 int *min, int *max, int *deflt) const
|
Chris@153
|
177 {
|
Chris@216
|
178 int val = 0;
|
Chris@153
|
179
|
Chris@216
|
180 int garbage0, garbage1, garbage2;
|
Chris@153
|
181 if (!min) min = &garbage0;
|
Chris@153
|
182 if (!max) max = &garbage1;
|
Chris@216
|
183 if (!deflt) deflt = &garbage2;
|
Chris@153
|
184
|
Chris@193
|
185 if (name == "Window Size") {
|
Chris@153
|
186
|
Chris@153
|
187 *min = 0;
|
Chris@254
|
188 *max = 15;
|
Chris@216
|
189 *deflt = 5;
|
Chris@153
|
190
|
Chris@216
|
191 val = 0;
|
Chris@153
|
192 int ws = m_windowSize;
|
Chris@216
|
193 while (ws > 32) { ws >>= 1; val ++; }
|
Chris@153
|
194
|
Chris@153
|
195 } else if (name == "Window Increment") {
|
Chris@153
|
196
|
Chris@153
|
197 *min = 0;
|
Chris@153
|
198 *max = 5;
|
Chris@216
|
199 *deflt = 2;
|
Chris@153
|
200
|
Chris@216
|
201 val = m_windowHopLevel;
|
Chris@153
|
202
|
Chris@284
|
203 } else if (name == "Show Peak Frequencies") {
|
Chris@284
|
204
|
Chris@284
|
205 return m_showPeaks ? 1 : 0;
|
Chris@284
|
206
|
Chris@153
|
207 } else {
|
Chris@193
|
208
|
Chris@216
|
209 val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
|
Chris@153
|
210 }
|
Chris@153
|
211
|
Chris@216
|
212 return val;
|
Chris@153
|
213 }
|
Chris@153
|
214
|
Chris@153
|
215 QString
|
Chris@153
|
216 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
|
Chris@153
|
217 int value) const
|
Chris@153
|
218 {
|
Chris@153
|
219 if (name == "Window Size") {
|
Chris@153
|
220 return QString("%1").arg(32 << value);
|
Chris@153
|
221 }
|
Chris@153
|
222 if (name == "Window Increment") {
|
Chris@153
|
223 switch (value) {
|
Chris@153
|
224 default:
|
Chris@153
|
225 case 0: return tr("None");
|
Chris@153
|
226 case 1: return tr("25 %");
|
Chris@153
|
227 case 2: return tr("50 %");
|
Chris@153
|
228 case 3: return tr("75 %");
|
Chris@153
|
229 case 4: return tr("87.5 %");
|
Chris@153
|
230 case 5: return tr("93.75 %");
|
Chris@153
|
231 }
|
Chris@153
|
232 }
|
Chris@193
|
233 return SliceLayer::getPropertyValueLabel(name, value);
|
Chris@153
|
234 }
|
Chris@153
|
235
|
Chris@167
|
236 RangeMapper *
|
Chris@167
|
237 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
|
Chris@167
|
238 {
|
Chris@193
|
239 return SliceLayer::getNewPropertyRangeMapper(name);
|
Chris@167
|
240 }
|
Chris@167
|
241
|
Chris@133
|
242 void
|
Chris@153
|
243 SpectrumLayer::setProperty(const PropertyName &name, int value)
|
Chris@133
|
244 {
|
Chris@193
|
245 if (name == "Window Size") {
|
Chris@153
|
246 setWindowSize(32 << value);
|
Chris@153
|
247 } else if (name == "Window Increment") {
|
Chris@153
|
248 setWindowHopLevel(value);
|
Chris@284
|
249 } else if (name == "Show Peak Frequencies") {
|
Chris@284
|
250 setShowPeaks(value ? true : false);
|
Chris@193
|
251 } else {
|
Chris@193
|
252 SliceLayer::setProperty(name, value);
|
Chris@153
|
253 }
|
Chris@153
|
254 }
|
Chris@153
|
255
|
Chris@153
|
256 void
|
Chris@805
|
257 SpectrumLayer::setWindowSize(int ws)
|
Chris@153
|
258 {
|
Chris@153
|
259 if (m_windowSize == ws) return;
|
Chris@153
|
260 m_windowSize = ws;
|
Chris@275
|
261 m_newFFTNeeded = true;
|
Chris@153
|
262 emit layerParametersChanged();
|
Chris@153
|
263 }
|
Chris@153
|
264
|
Chris@153
|
265 void
|
Chris@805
|
266 SpectrumLayer::setWindowHopLevel(int v)
|
Chris@153
|
267 {
|
Chris@153
|
268 if (m_windowHopLevel == v) return;
|
Chris@153
|
269 m_windowHopLevel = v;
|
Chris@275
|
270 m_newFFTNeeded = true;
|
Chris@153
|
271 emit layerParametersChanged();
|
Chris@153
|
272 }
|
Chris@153
|
273
|
Chris@153
|
274 void
|
Chris@153
|
275 SpectrumLayer::setWindowType(WindowType w)
|
Chris@153
|
276 {
|
Chris@153
|
277 if (m_windowType == w) return;
|
Chris@153
|
278 m_windowType = w;
|
Chris@275
|
279 m_newFFTNeeded = true;
|
Chris@153
|
280 emit layerParametersChanged();
|
Chris@153
|
281 }
|
Chris@153
|
282
|
Chris@153
|
283 void
|
Chris@284
|
284 SpectrumLayer::setShowPeaks(bool show)
|
Chris@284
|
285 {
|
Chris@284
|
286 if (m_showPeaks == show) return;
|
Chris@284
|
287 m_showPeaks = show;
|
Chris@284
|
288 emit layerParametersChanged();
|
Chris@284
|
289 }
|
Chris@284
|
290
|
Chris@284
|
291 void
|
Chris@153
|
292 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
|
Chris@153
|
293 {
|
Chris@153
|
294 if (name == "Window Type") {
|
Chris@153
|
295 setWindowType(Preferences::getInstance()->getWindowType());
|
Chris@153
|
296 return;
|
Chris@153
|
297 }
|
Chris@153
|
298 }
|
Chris@153
|
299
|
Chris@1238
|
300 double
|
Chris@1238
|
301 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
|
Chris@133
|
302 {
|
Chris@1238
|
303 if (!m_sliceableModel) return 0;
|
Chris@1238
|
304 double bin = getBinForX(v, x);
|
Chris@1238
|
305 return (m_sliceableModel->getSampleRate() * bin) /
|
Chris@1238
|
306 (m_sliceableModel->getHeight() * 2);
|
Chris@133
|
307 }
|
Chris@133
|
308
|
Chris@908
|
309 double
|
Chris@1238
|
310 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
|
Chris@265
|
311 {
|
Chris@280
|
312 if (!m_sliceableModel) return 0;
|
Chris@1238
|
313 double bin = (freq * m_sliceableModel->getHeight() * 2) /
|
Chris@1238
|
314 m_sliceableModel->getSampleRate();
|
Chris@1238
|
315 return getXForBin(v, bin);
|
Chris@254
|
316 }
|
Chris@254
|
317
|
Chris@260
|
318 bool
|
Chris@918
|
319 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
|
Chris@908
|
320 double &value, QString &unit) const
|
Chris@260
|
321 {
|
Chris@1238
|
322 value = getFrequencyForX(v, x);
|
Chris@260
|
323 unit = "Hz";
|
Chris@260
|
324 return true;
|
Chris@260
|
325 }
|
Chris@260
|
326
|
Chris@264
|
327 bool
|
Chris@918
|
328 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
|
Chris@908
|
329 double &value, QString &unit) const
|
Chris@274
|
330 {
|
Chris@1238
|
331 value = getValueForY(v, y);
|
Chris@274
|
332
|
Chris@274
|
333 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
|
Chris@274
|
334
|
Chris@908
|
335 if (value > 0.0) {
|
Chris@908
|
336 value = 10.0 * log10(value);
|
Chris@284
|
337 if (value < m_threshold) value = m_threshold;
|
Chris@284
|
338 } else value = m_threshold;
|
Chris@274
|
339
|
Chris@274
|
340 unit = "dBV";
|
Chris@274
|
341
|
Chris@274
|
342 } else {
|
Chris@274
|
343 unit = "V";
|
Chris@274
|
344 }
|
Chris@274
|
345
|
Chris@274
|
346 return true;
|
Chris@274
|
347 }
|
Chris@274
|
348
|
Chris@274
|
349 bool
|
Chris@918
|
350 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
|
Chris@908
|
351 double &diff, QString &unit) const
|
Chris@274
|
352 {
|
Chris@274
|
353 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
|
Chris@274
|
354 if (rv && (unit == "dBV")) unit = "dB";
|
Chris@274
|
355 return rv;
|
Chris@274
|
356 }
|
Chris@274
|
357
|
Chris@274
|
358
|
Chris@274
|
359 bool
|
Chris@918
|
360 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
|
Chris@264
|
361 QPoint cursorPos,
|
Chris@264
|
362 std::vector<QRect> &extents) const
|
Chris@264
|
363 {
|
Chris@918
|
364 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
|
Chris@264
|
365 extents.push_back(vertical);
|
Chris@264
|
366
|
Chris@918
|
367 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
|
Chris@264
|
368 extents.push_back(horizontal);
|
Chris@264
|
369
|
Chris@280
|
370 int hoffset = 2;
|
Chris@280
|
371 if (m_binScale == LogBins) hoffset = 13;
|
Chris@278
|
372
|
Chris@607
|
373 int sw = getVerticalScaleWidth(v, false, paint);
|
Chris@280
|
374
|
Chris@280
|
375 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
|
Chris@280
|
376 paint.fontMetrics().width("0.0000001 V") + 2,
|
Chris@264
|
377 paint.fontMetrics().height());
|
Chris@280
|
378 extents.push_back(value);
|
Chris@280
|
379
|
Chris@280
|
380 QRect log(sw, cursorPos.y() + 2,
|
Chris@280
|
381 paint.fontMetrics().width("-80.000 dBV") + 2,
|
Chris@280
|
382 paint.fontMetrics().height());
|
Chris@280
|
383 extents.push_back(log);
|
Chris@280
|
384
|
Chris@280
|
385 QRect freq(cursorPos.x(),
|
Chris@918
|
386 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
|
Chris@280
|
387 paint.fontMetrics().width("123456 Hz") + 2,
|
Chris@280
|
388 paint.fontMetrics().height());
|
Chris@280
|
389 extents.push_back(freq);
|
Chris@264
|
390
|
Chris@278
|
391 int w(paint.fontMetrics().width("C#10+50c") + 2);
|
Chris@278
|
392 QRect pitch(cursorPos.x() - w,
|
Chris@918
|
393 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
|
Chris@278
|
394 w,
|
Chris@278
|
395 paint.fontMetrics().height());
|
Chris@278
|
396 extents.push_back(pitch);
|
Chris@278
|
397
|
Chris@264
|
398 return true;
|
Chris@264
|
399 }
|
Chris@264
|
400
|
Chris@254
|
401 void
|
Chris@918
|
402 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
|
Chris@254
|
403 QPoint cursorPos) const
|
Chris@254
|
404 {
|
Chris@280
|
405 if (!m_sliceableModel) return;
|
Chris@280
|
406
|
Chris@254
|
407 paint.save();
|
Chris@282
|
408 QFont fn = paint.font();
|
Chris@282
|
409 if (fn.pointSize() > 8) {
|
Chris@282
|
410 fn.setPointSize(fn.pointSize() - 1);
|
Chris@282
|
411 paint.setFont(fn);
|
Chris@282
|
412 }
|
Chris@254
|
413
|
Chris@254
|
414 ColourMapper mapper(m_colourMap, 0, 1);
|
Chris@254
|
415 paint.setPen(mapper.getContrastingColour());
|
Chris@254
|
416
|
Chris@1238
|
417 int xorigin = m_xorigins[v->getId()];
|
Chris@918
|
418 int w = v->getPaintWidth() - xorigin - 1;
|
Chris@254
|
419
|
Chris@918
|
420 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
|
Chris@918
|
421 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
|
Chris@254
|
422
|
Chris@1238
|
423 double fundamental = getFrequencyForX(v, cursorPos.x());
|
Chris@254
|
424
|
Chris@280
|
425 int hoffset = 2;
|
Chris@280
|
426 if (m_binScale == LogBins) hoffset = 13;
|
Chris@278
|
427
|
Chris@1078
|
428 PaintAssistant::drawVisibleText(v, paint,
|
Chris@1238
|
429 cursorPos.x() + 2,
|
Chris@1238
|
430 v->getPaintHeight() - 2 - hoffset,
|
Chris@1238
|
431 QString("%1 Hz").arg(fundamental),
|
Chris@1238
|
432 PaintAssistant::OutlinedText);
|
Chris@278
|
433
|
Chris@278
|
434 if (Pitch::isFrequencyInMidiRange(fundamental)) {
|
Chris@278
|
435 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
|
Chris@1078
|
436 PaintAssistant::drawVisibleText(v, paint,
|
Chris@1238
|
437 cursorPos.x() -
|
Chris@1238
|
438 paint.fontMetrics().width(pitchLabel) - 2,
|
Chris@1238
|
439 v->getPaintHeight() - 2 - hoffset,
|
Chris@1238
|
440 pitchLabel,
|
Chris@1238
|
441 PaintAssistant::OutlinedText);
|
Chris@278
|
442 }
|
Chris@264
|
443
|
Chris@1238
|
444 double value = getValueForY(v, cursorPos.y());
|
Chris@908
|
445 double thresh = m_threshold;
|
Chris@908
|
446 double db = thresh;
|
Chris@908
|
447 if (value > 0.0) db = 10.0 * log10(value);
|
Chris@280
|
448 if (db < thresh) db = thresh;
|
Chris@280
|
449
|
Chris@1078
|
450 PaintAssistant::drawVisibleText(v, paint,
|
Chris@280
|
451 xorigin + 2,
|
Chris@280
|
452 cursorPos.y() - 2,
|
Chris@280
|
453 QString("%1 V").arg(value),
|
Chris@1078
|
454 PaintAssistant::OutlinedText);
|
Chris@280
|
455
|
Chris@1078
|
456 PaintAssistant::drawVisibleText(v, paint,
|
Chris@280
|
457 xorigin + 2,
|
Chris@280
|
458 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
|
Chris@280
|
459 QString("%1 dBV").arg(db),
|
Chris@1078
|
460 PaintAssistant::OutlinedText);
|
Chris@280
|
461
|
Chris@254
|
462 int harmonic = 2;
|
Chris@254
|
463
|
Chris@254
|
464 while (harmonic < 100) {
|
Chris@254
|
465
|
Chris@1238
|
466 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
|
Chris@254
|
467 hx += xorigin;
|
Chris@254
|
468
|
Chris@918
|
469 if (hx < xorigin || hx > v->getPaintWidth()) break;
|
Chris@254
|
470
|
Chris@254
|
471 int len = 7;
|
Chris@254
|
472
|
Chris@254
|
473 if (harmonic % 2 == 0) {
|
Chris@254
|
474 if (harmonic % 4 == 0) {
|
Chris@254
|
475 len = 12;
|
Chris@254
|
476 } else {
|
Chris@254
|
477 len = 10;
|
Chris@254
|
478 }
|
Chris@254
|
479 }
|
Chris@254
|
480
|
Chris@908
|
481 paint.drawLine(hx,
|
Chris@254
|
482 cursorPos.y(),
|
Chris@908
|
483 hx,
|
Chris@254
|
484 cursorPos.y() + len);
|
Chris@254
|
485
|
Chris@254
|
486 ++harmonic;
|
Chris@254
|
487 }
|
Chris@254
|
488
|
Chris@254
|
489 paint.restore();
|
Chris@254
|
490 }
|
Chris@254
|
491
|
Chris@199
|
492 QString
|
Chris@918
|
493 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
|
Chris@199
|
494 {
|
Chris@199
|
495 if (!m_sliceableModel) return "";
|
Chris@199
|
496
|
Chris@199
|
497 int minbin = 0, maxbin = 0, range = 0;
|
Chris@805
|
498 QString genericDesc = SliceLayer::getFeatureDescriptionAux
|
Chris@199
|
499 (v, p, false, minbin, maxbin, range);
|
Chris@199
|
500
|
Chris@199
|
501 if (genericDesc == "") return "";
|
Chris@199
|
502
|
Chris@1238
|
503 int i0 = minbin - m_minbin;
|
Chris@1238
|
504 int i1 = maxbin - m_minbin;
|
Chris@1238
|
505
|
Chris@1238
|
506 float minvalue = 0.0;
|
Chris@1238
|
507 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
|
Chris@199
|
508
|
Chris@1238
|
509 float maxvalue = minvalue;
|
Chris@1238
|
510 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
|
Chris@1238
|
511
|
Chris@199
|
512 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
|
Chris@199
|
513
|
Chris@199
|
514 QString binstr;
|
Chris@199
|
515 QString hzstr;
|
Chris@908
|
516 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
|
Chris@908
|
517 m_windowSize));
|
Chris@908
|
518 int maxfreq = int(lrint((std::max(maxbin, minbin+1)
|
Chris@908
|
519 * m_sliceableModel->getSampleRate()) /
|
Chris@908
|
520 m_windowSize));
|
Chris@199
|
521
|
Chris@199
|
522 if (maxbin != minbin) {
|
Chris@199
|
523 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
|
Chris@199
|
524 } else {
|
Chris@199
|
525 binstr = QString("%1").arg(minbin+1);
|
Chris@199
|
526 }
|
Chris@199
|
527 if (minfreq != maxfreq) {
|
Chris@199
|
528 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
|
Chris@199
|
529 } else {
|
Chris@199
|
530 hzstr = tr("%1 Hz").arg(minfreq);
|
Chris@199
|
531 }
|
Chris@199
|
532
|
Chris@199
|
533 QString valuestr;
|
Chris@199
|
534 if (maxvalue != minvalue) {
|
Chris@199
|
535 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
|
Chris@199
|
536 } else {
|
Chris@199
|
537 valuestr = QString("%1").arg(minvalue);
|
Chris@199
|
538 }
|
Chris@199
|
539
|
Chris@199
|
540 QString dbstr;
|
Chris@908
|
541 double mindb = AudioLevel::multiplier_to_dB(minvalue);
|
Chris@908
|
542 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
|
Chris@199
|
543 QString mindbstr;
|
Chris@199
|
544 QString maxdbstr;
|
Chris@199
|
545 if (mindb == AudioLevel::DB_FLOOR) {
|
Chris@1147
|
546 mindbstr = Strings::minus_infinity;
|
Chris@199
|
547 } else {
|
Chris@908
|
548 mindbstr = QString("%1").arg(lrint(mindb));
|
Chris@199
|
549 }
|
Chris@199
|
550 if (maxdb == AudioLevel::DB_FLOOR) {
|
Chris@1147
|
551 maxdbstr = Strings::minus_infinity;
|
Chris@199
|
552 } else {
|
Chris@908
|
553 maxdbstr = QString("%1").arg(lrint(maxdb));
|
Chris@199
|
554 }
|
Chris@908
|
555 if (lrint(mindb) != lrint(maxdb)) {
|
Chris@199
|
556 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
|
Chris@199
|
557 } else {
|
Chris@199
|
558 dbstr = tr("%1").arg(mindbstr);
|
Chris@199
|
559 }
|
Chris@199
|
560
|
Chris@199
|
561 QString description;
|
Chris@199
|
562
|
Chris@248
|
563 if (range > int(m_sliceableModel->getResolution())) {
|
Chris@199
|
564 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
|
Chris@199
|
565 .arg(genericDesc)
|
Chris@199
|
566 .arg(binstr)
|
Chris@199
|
567 .arg(hzstr)
|
Chris@199
|
568 .arg(m_samplingMode == NearestSample ? tr("First") :
|
Chris@199
|
569 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
|
Chris@199
|
570 .arg(valuestr)
|
Chris@199
|
571 .arg(dbstr);
|
Chris@199
|
572 } else {
|
Chris@199
|
573 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
|
Chris@199
|
574 .arg(genericDesc)
|
Chris@199
|
575 .arg(binstr)
|
Chris@199
|
576 .arg(hzstr)
|
Chris@199
|
577 .arg(valuestr)
|
Chris@199
|
578 .arg(dbstr);
|
Chris@199
|
579 }
|
Chris@199
|
580
|
Chris@199
|
581 return description;
|
Chris@199
|
582 }
|
Chris@199
|
583
|
Chris@254
|
584 void
|
Chris@916
|
585 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
|
Chris@275
|
586 {
|
Chris@275
|
587 if (!m_originModel || !m_originModel->isOK() ||
|
Chris@349
|
588 !m_originModel->isReady()) {
|
Chris@587
|
589 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
|
Chris@349
|
590 return;
|
Chris@349
|
591 }
|
Chris@275
|
592
|
Chris@275
|
593 if (m_newFFTNeeded) {
|
Chris@587
|
594 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
|
Chris@275
|
595 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
|
Chris@275
|
596 }
|
Chris@277
|
597
|
Chris@277
|
598 FFTModel *fft = dynamic_cast<FFTModel *>
|
Chris@277
|
599 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
|
Chris@277
|
600
|
Chris@908
|
601 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
|
Chris@277
|
602
|
Chris@607
|
603 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
|
Chris@918
|
604 int w = v->getPaintWidth() - xorigin - 1;
|
Chris@277
|
605
|
Chris@1231
|
606 int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
|
Chris@1231
|
607 if (pkh < 10) pkh = 10;
|
Chris@278
|
608
|
Chris@345
|
609 paint.save();
|
Chris@345
|
610
|
Chris@284
|
611 if (fft && m_showPeaks) {
|
Chris@277
|
612
|
Chris@277
|
613 // draw peak lines
|
Chris@277
|
614
|
Chris@587
|
615 // SVDEBUG << "Showing peaks..." << endl;
|
Chris@456
|
616
|
Chris@908
|
617 int col = int(v->getCentreFrame() / fft->getResolution());
|
Chris@277
|
618
|
Chris@277
|
619 paint.save();
|
Chris@277
|
620 paint.setRenderHint(QPainter::Antialiasing, false);
|
Chris@277
|
621 paint.setPen(QColor(160, 160, 160)); //!!!
|
Chris@277
|
622
|
Chris@290
|
623 int peakminbin = 0;
|
Chris@290
|
624 int peakmaxbin = fft->getHeight() - 1;
|
Chris@908
|
625 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
|
Chris@908
|
626 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate()));
|
Chris@290
|
627
|
Chris@280
|
628 FFTModel::PeakSet peaks = fft->getPeakFrequencies
|
Chris@290
|
629 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
|
Chris@280
|
630
|
Chris@280
|
631 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
|
Chris@277
|
632
|
Chris@277
|
633 BiasCurve curve;
|
Chris@277
|
634 getBiasCurve(curve);
|
Chris@908
|
635 int cs = int(curve.size());
|
Chris@280
|
636
|
Chris@908
|
637 std::vector<double> values;
|
Chris@277
|
638
|
Chris@805
|
639 for (int bin = 0; bin < fft->getHeight(); ++bin) {
|
Chris@908
|
640 double value = m_sliceableModel->getValueAt(col, bin);
|
Chris@280
|
641 if (bin < cs) value *= curve[bin];
|
Chris@280
|
642 values.push_back(value);
|
Chris@280
|
643 }
|
Chris@280
|
644
|
Chris@280
|
645 for (FFTModel::PeakSet::iterator i = peaks.begin();
|
Chris@280
|
646 i != peaks.end(); ++i) {
|
Chris@280
|
647
|
Chris@805
|
648 int bin = i->first;
|
Chris@277
|
649
|
Chris@682
|
650 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
|
Chris@280
|
651
|
Chris@908
|
652 if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
|
Chris@277
|
653
|
Chris@908
|
654 double freq = i->second;
|
Chris@280
|
655
|
Chris@1238
|
656 int x = int(lrint(getXForFrequency(v, freq)));
|
Chris@277
|
657
|
Chris@908
|
658 double norm = 0.f;
|
Chris@1238
|
659 (void)getYForValue(v, values[bin], norm); // don't need return value, need norm
|
Chris@277
|
660
|
Chris@277
|
661 paint.setPen(mapper.map(norm));
|
Chris@918
|
662 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
|
Chris@277
|
663 }
|
Chris@277
|
664
|
Chris@277
|
665 paint.restore();
|
Chris@277
|
666 }
|
Chris@275
|
667
|
Chris@275
|
668 SliceLayer::paint(v, paint, rect);
|
Chris@277
|
669
|
Chris@278
|
670 //!!! All of this stuff relating to depicting frequencies
|
Chris@1238
|
671 // (keyboard, crosshairs etc) should be applicable to any slice
|
Chris@1238
|
672 // layer whose model has a vertical scale unit of Hz. However,
|
Chris@1238
|
673 // the dense 3d model at the moment doesn't record its vertical
|
Chris@1238
|
674 // scale unit -- we need to fix that and hoist this code as
|
Chris@1238
|
675 // appropriate. Same really goes for any code in SpectrogramLayer
|
Chris@1238
|
676 // that could be relevant to Colour3DPlotLayer with unit Hz, but
|
Chris@1238
|
677 // that's a bigger proposition.
|
Chris@278
|
678
|
Chris@1238
|
679 int h = v->getPaintHeight();
|
Chris@277
|
680
|
Chris@1238
|
681 PianoScale().paintPianoHorizontal
|
Chris@1238
|
682 (v, this, paint, QRect(xorigin, h - pkh - 1, w + xorigin, pkh));
|
Chris@345
|
683
|
Chris@345
|
684 paint.restore();
|
Chris@275
|
685 }
|
Chris@275
|
686
|
Chris@275
|
687 void
|
Chris@254
|
688 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
|
Chris@254
|
689 {
|
Chris@254
|
690 curve = m_biasCurve;
|
Chris@254
|
691 }
|
Chris@199
|
692
|
Chris@316
|
693 void
|
Chris@316
|
694 SpectrumLayer::toXml(QTextStream &stream,
|
Chris@316
|
695 QString indent, QString extraAttributes) const
|
Chris@220
|
696 {
|
Chris@316
|
697 QString s = QString("windowSize=\"%1\" "
|
Chris@456
|
698 "windowHopLevel=\"%2\" "
|
Chris@456
|
699 "showPeaks=\"%3\" ")
|
Chris@220
|
700 .arg(m_windowSize)
|
Chris@456
|
701 .arg(m_windowHopLevel)
|
Chris@456
|
702 .arg(m_showPeaks ? "true" : "false");
|
Chris@220
|
703
|
Chris@316
|
704 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
|
Chris@220
|
705 }
|
Chris@220
|
706
|
Chris@220
|
707 void
|
Chris@220
|
708 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
|
Chris@220
|
709 {
|
Chris@220
|
710 SliceLayer::setProperties(attributes);
|
Chris@220
|
711
|
Chris@220
|
712 bool ok = false;
|
Chris@220
|
713
|
Chris@805
|
714 int windowSize = attributes.value("windowSize").toUInt(&ok);
|
Chris@220
|
715 if (ok) setWindowSize(windowSize);
|
Chris@220
|
716
|
Chris@805
|
717 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
|
Chris@220
|
718 if (ok) setWindowHopLevel(windowHopLevel);
|
Chris@456
|
719
|
Chris@456
|
720 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
|
Chris@456
|
721 setShowPeaks(showPeaks);
|
Chris@220
|
722 }
|
Chris@220
|
723
|
Chris@220
|
724
|