Chris@139
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@139
|
2
|
Chris@139
|
3 /*
|
Chris@139
|
4 Sonic Visualiser
|
Chris@139
|
5 An audio file viewer and annotation editor.
|
Chris@139
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@139
|
7 This file copyright 2006 Chris Cannam.
|
Chris@139
|
8
|
Chris@139
|
9 This program is free software; you can redistribute it and/or
|
Chris@139
|
10 modify it under the terms of the GNU General Public License as
|
Chris@139
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@139
|
12 License, or (at your option) any later version. See the file
|
Chris@139
|
13 COPYING included with this distribution for more information.
|
Chris@139
|
14 */
|
Chris@139
|
15
|
Chris@139
|
16 #include "WindowShapePreview.h"
|
Chris@139
|
17
|
Chris@139
|
18 #include <QHBoxLayout>
|
Chris@139
|
19 #include <QLabel>
|
Chris@139
|
20 #include <QPainter>
|
Chris@139
|
21 #include <QPainterPath>
|
Chris@139
|
22 #include <QFont>
|
Chris@139
|
23 #include <QString>
|
Chris@139
|
24
|
Chris@1168
|
25 #include <bqfft/FFT.h>
|
Chris@139
|
26
|
Chris@1168
|
27 #include <vector>
|
Chris@1168
|
28 #include <complex>
|
Chris@139
|
29 #include <iostream>
|
Chris@139
|
30
|
Chris@1168
|
31 using namespace std;
|
Chris@1168
|
32
|
Chris@545
|
33
|
Chris@139
|
34 WindowShapePreview::WindowShapePreview(QWidget *parent) :
|
Chris@139
|
35 QFrame(parent),
|
Chris@908
|
36 m_windowType(HanningWindow)
|
Chris@139
|
37 {
|
Chris@139
|
38 QHBoxLayout *layout = new QHBoxLayout;
|
Chris@139
|
39 layout->setMargin(0);
|
Chris@139
|
40 setLayout(layout);
|
Chris@139
|
41 m_windowTimeExampleLabel = new QLabel;
|
Chris@139
|
42 m_windowFreqExampleLabel = new QLabel;
|
Chris@139
|
43 layout->addWidget(m_windowTimeExampleLabel);
|
Chris@139
|
44 layout->addWidget(m_windowFreqExampleLabel);
|
Chris@139
|
45 }
|
Chris@139
|
46
|
Chris@139
|
47 WindowShapePreview::~WindowShapePreview()
|
Chris@139
|
48 {
|
Chris@139
|
49 }
|
Chris@139
|
50
|
Chris@139
|
51 void
|
Chris@139
|
52 WindowShapePreview::updateLabels()
|
Chris@139
|
53 {
|
Chris@1157
|
54 float scaleRatio = float(QFontMetrics(font()).height()) / 14.f;
|
Chris@1154
|
55 if (scaleRatio < 1.f) scaleRatio = 1.f;
|
Chris@1154
|
56
|
Chris@1154
|
57 int step = int(24 * scaleRatio);
|
Chris@1157
|
58 float peak = float(48 * scaleRatio);
|
Chris@1154
|
59
|
Chris@1157
|
60 int w = step * 4, h = int((peak * 4) / 3);
|
Chris@1154
|
61
|
Chris@139
|
62 WindowType type = m_windowType;
|
Chris@139
|
63 Window<float> windower = Window<float>(type, step * 2);
|
Chris@1154
|
64
|
Chris@139
|
65 QPixmap timeLabel(w, h + 1);
|
Chris@139
|
66 timeLabel.fill(Qt::white);
|
Chris@139
|
67 QPainter timePainter(&timeLabel);
|
Chris@139
|
68
|
Chris@139
|
69 QPainterPath path;
|
Chris@139
|
70
|
Chris@908
|
71 path.moveTo(0, float(h) - peak + 1);
|
Chris@908
|
72 path.lineTo(w, float(h) - peak + 1);
|
Chris@139
|
73
|
Chris@139
|
74 timePainter.setPen(Qt::gray);
|
Chris@139
|
75 timePainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@139
|
76 timePainter.drawPath(path);
|
Chris@139
|
77
|
Chris@139
|
78 path = QPainterPath();
|
Chris@139
|
79
|
Chris@1153
|
80 float *acc = new float[w];
|
Chris@139
|
81 for (int i = 0; i < w; ++i) acc[i] = 0.f;
|
Chris@139
|
82 for (int j = 0; j < 3; ++j) {
|
Chris@139
|
83 for (int i = 0; i < step * 2; ++i) {
|
Chris@139
|
84 acc[j * step + i] += windower.getValue(i);
|
Chris@139
|
85 }
|
Chris@139
|
86 }
|
Chris@139
|
87 for (int i = 0; i < w; ++i) {
|
Chris@908
|
88 int y = h - int(peak * acc[i] + 0.001f) + 1;
|
Chris@139
|
89 if (i == 0) path.moveTo(i, y);
|
Chris@139
|
90 else path.lineTo(i, y);
|
Chris@139
|
91 }
|
Chris@1150
|
92 delete[] acc;
|
Chris@139
|
93
|
Chris@139
|
94 timePainter.drawPath(path);
|
Chris@139
|
95 timePainter.setRenderHint(QPainter::Antialiasing, false);
|
Chris@139
|
96
|
Chris@139
|
97 path = QPainterPath();
|
Chris@139
|
98
|
Chris@139
|
99 timePainter.setPen(Qt::black);
|
Chris@139
|
100
|
Chris@139
|
101 for (int i = 0; i < step * 2; ++i) {
|
Chris@139
|
102 int y = h - int(peak * windower.getValue(i) + 0.001) + 1;
|
Chris@139
|
103 if (i == 0) path.moveTo(i + step, float(y));
|
Chris@139
|
104 else path.lineTo(i + step, float(y));
|
Chris@139
|
105 }
|
Chris@139
|
106
|
Chris@139
|
107 if (type == RectangularWindow) {
|
Chris@139
|
108 timePainter.drawPath(path);
|
Chris@139
|
109 path = QPainterPath();
|
Chris@139
|
110 }
|
Chris@139
|
111
|
Chris@139
|
112 timePainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@139
|
113 path.addRect(0, 0, w, h + 1);
|
Chris@139
|
114 timePainter.drawPath(path);
|
Chris@139
|
115
|
Chris@1478
|
116 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
|
Chris@1478
|
117 // replacement (horizontalAdvance) was only added in Qt 5.11
|
Chris@1478
|
118 // which is too new for us
|
Chris@1478
|
119 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
Chris@1478
|
120
|
Chris@139
|
121 QFont font;
|
Chris@1154
|
122 font.setPixelSize(int(10 * scaleRatio));
|
Chris@139
|
123 font.setItalic(true);
|
Chris@139
|
124 timePainter.setFont(font);
|
Chris@139
|
125 QString label = tr("V / time");
|
Chris@139
|
126 timePainter.drawText(w - timePainter.fontMetrics().width(label) - 4,
|
Chris@139
|
127 timePainter.fontMetrics().ascent() + 1, label);
|
Chris@139
|
128
|
Chris@139
|
129 m_windowTimeExampleLabel->setPixmap(timeLabel);
|
Chris@139
|
130
|
Chris@1154
|
131 QPixmap freqLabel(w, h + 1);
|
Chris@139
|
132 freqLabel.fill(Qt::white);
|
Chris@139
|
133 QPainter freqPainter(&freqLabel);
|
Chris@139
|
134 path = QPainterPath();
|
Chris@139
|
135
|
Chris@139
|
136 int fftsize = 512;
|
Chris@139
|
137
|
Chris@1168
|
138 breakfastquay::FFT fft(fftsize);
|
Chris@1168
|
139
|
Chris@1168
|
140 vector<float> input(fftsize);
|
Chris@1168
|
141 vector<complex<float>> output(fftsize/2 + 1);
|
Chris@1168
|
142
|
Chris@139
|
143 for (int i = 0; i < fftsize; ++i) input[i] = 0.f;
|
Chris@139
|
144 for (int i = 0; i < step * 2; ++i) {
|
Chris@139
|
145 input[fftsize/2 - step + i] = windower.getValue(i);
|
Chris@139
|
146 }
|
Chris@1168
|
147
|
Chris@1168
|
148 fft.forwardInterleaved(input.data(), reinterpret_cast<float *>(output.data()));
|
Chris@139
|
149
|
Chris@139
|
150 float maxdb = 0.f;
|
Chris@139
|
151 float mindb = 0.f;
|
Chris@139
|
152 bool first = true;
|
Chris@139
|
153 for (int i = 0; i < fftsize/2; ++i) {
|
Chris@1168
|
154 float power =
|
Chris@1168
|
155 output[i].real() * output[i].real() +
|
Chris@1168
|
156 output[i].imag() * output[i].imag();
|
Chris@139
|
157 float db = mindb;
|
Chris@139
|
158 if (power > 0) {
|
Chris@908
|
159 db = 20.f * log10f(power);
|
Chris@139
|
160 if (first || db > maxdb) maxdb = db;
|
Chris@139
|
161 if (first || db < mindb) mindb = db;
|
Chris@139
|
162 first = false;
|
Chris@139
|
163 }
|
Chris@139
|
164 }
|
Chris@139
|
165
|
Chris@139
|
166 if (mindb > -80.f) mindb = -80.f;
|
Chris@139
|
167
|
Chris@139
|
168 // -- no, don't use the actual mindb -- it's easier to compare
|
Chris@139
|
169 // plots with a fixed min value
|
Chris@139
|
170 mindb = -170.f;
|
Chris@139
|
171
|
Chris@139
|
172 float maxval = maxdb + -mindb;
|
Chris@139
|
173
|
Chris@139
|
174 // float ly = h - ((-80.f + -mindb) / maxval) * peak + 1;
|
Chris@139
|
175
|
Chris@908
|
176 path.moveTo(0, float(h) - peak + 1);
|
Chris@1154
|
177 path.lineTo(w, float(h) - peak + 1);
|
Chris@139
|
178
|
Chris@139
|
179 freqPainter.setPen(Qt::gray);
|
Chris@139
|
180 freqPainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@139
|
181 freqPainter.drawPath(path);
|
Chris@139
|
182
|
Chris@139
|
183 path = QPainterPath();
|
Chris@139
|
184 freqPainter.setPen(Qt::black);
|
Chris@139
|
185
|
Chris@682
|
186 // cerr << "maxdb = " << maxdb << ", mindb = " << mindb << ", maxval = " <<maxval << endl;
|
Chris@139
|
187
|
Chris@139
|
188 for (int i = 0; i < fftsize/2; ++i) {
|
Chris@1168
|
189 float power =
|
Chris@1168
|
190 output[i].real() * output[i].real() +
|
Chris@1168
|
191 output[i].imag() * output[i].imag();
|
Chris@908
|
192 float db = 20.f * log10f(power);
|
Chris@139
|
193 float val = db + -mindb;
|
Chris@139
|
194 if (val < 0) val = 0;
|
Chris@139
|
195 float norm = val / maxval;
|
Chris@1154
|
196 float x = (float(w) / float(fftsize/2)) * float(i);
|
Chris@908
|
197 float y = float(h) - norm * peak + 1;
|
Chris@139
|
198 if (i == 0) path.moveTo(x, y);
|
Chris@139
|
199 else path.lineTo(x, y);
|
Chris@139
|
200 }
|
Chris@139
|
201
|
Chris@139
|
202 freqPainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@1154
|
203 path.addRect(0, 0, w, h + 1);
|
Chris@139
|
204 freqPainter.drawPath(path);
|
Chris@139
|
205
|
Chris@139
|
206 freqPainter.setFont(font);
|
Chris@139
|
207 label = tr("dB / freq");
|
Chris@1154
|
208 freqPainter.drawText(w - freqPainter.fontMetrics().width(label) - 4,
|
Chris@139
|
209 freqPainter.fontMetrics().ascent() + 1, label);
|
Chris@139
|
210
|
Chris@139
|
211 m_windowFreqExampleLabel->setPixmap(freqLabel);
|
Chris@139
|
212 }
|
Chris@139
|
213
|
Chris@139
|
214 void
|
Chris@139
|
215 WindowShapePreview::setWindowType(WindowType type)
|
Chris@139
|
216 {
|
Chris@139
|
217 m_windowType = type;
|
Chris@139
|
218 updateLabels();
|
Chris@139
|
219 }
|
Chris@139
|
220
|