Chris@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 Sonic Visualiser
|
Chris@0
|
5 An audio file viewer and annotation editor.
|
Chris@0
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@0
|
7 This file copyright 2006 Chris Cannam.
|
Chris@0
|
8
|
Chris@0
|
9 This program is free software; you can redistribute it and/or
|
Chris@0
|
10 modify it under the terms of the GNU General Public License as
|
Chris@0
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@0
|
12 License, or (at your option) any later version. See the file
|
Chris@0
|
13 COPYING included with this distribution for more information.
|
Chris@0
|
14 */
|
Chris@0
|
15
|
Chris@0
|
16 #include "PreferencesDialog.h"
|
Chris@0
|
17
|
Chris@0
|
18 #include <QGridLayout>
|
Chris@0
|
19 #include <QComboBox>
|
Chris@0
|
20 #include <QCheckBox>
|
Chris@0
|
21 #include <QGroupBox>
|
Chris@0
|
22 #include <QDoubleSpinBox>
|
Chris@0
|
23 #include <QLabel>
|
Chris@0
|
24 #include <QPushButton>
|
Chris@0
|
25 #include <QHBoxLayout>
|
Chris@0
|
26 #include <QPainter>
|
Chris@0
|
27 #include <QPainterPath>
|
Chris@0
|
28 #include <QFont>
|
Chris@0
|
29 #include <QString>
|
Chris@0
|
30
|
Chris@0
|
31 #include <fftw3.h>
|
Chris@0
|
32
|
Chris@0
|
33 #include "base/Preferences.h"
|
Chris@0
|
34
|
Chris@0
|
35 PreferencesDialog::PreferencesDialog(QWidget *parent, Qt::WFlags flags) :
|
Chris@0
|
36 QDialog(parent, flags)
|
Chris@0
|
37 {
|
Chris@0
|
38 setWindowTitle(tr("Application Preferences"));
|
Chris@0
|
39
|
Chris@0
|
40 Preferences *prefs = Preferences::getInstance();
|
Chris@0
|
41
|
Chris@0
|
42 QGridLayout *grid = new QGridLayout;
|
Chris@0
|
43 setLayout(grid);
|
Chris@0
|
44
|
Chris@0
|
45 QGroupBox *groupBox = new QGroupBox;
|
Chris@0
|
46 groupBox->setTitle(tr("Sonic Visualiser Application Preferences"));
|
Chris@0
|
47 grid->addWidget(groupBox, 0, 0);
|
Chris@0
|
48
|
Chris@0
|
49 QGridLayout *subgrid = new QGridLayout;
|
Chris@0
|
50 groupBox->setLayout(subgrid);
|
Chris@0
|
51
|
Chris@0
|
52 // Create this first, as slots that get called from the ctor will
|
Chris@0
|
53 // refer to it
|
Chris@0
|
54 m_applyButton = new QPushButton(tr("Apply"));
|
Chris@0
|
55
|
Chris@0
|
56 // The WindowType enum is in rather a ragbag order -- reorder it here
|
Chris@0
|
57 // in a more sensible order
|
Chris@0
|
58 m_windows = new WindowType[9];
|
Chris@0
|
59 m_windows[0] = HanningWindow;
|
Chris@0
|
60 m_windows[1] = HammingWindow;
|
Chris@0
|
61 m_windows[2] = BlackmanWindow;
|
Chris@0
|
62 m_windows[3] = BlackmanHarrisWindow;
|
Chris@0
|
63 m_windows[4] = NuttallWindow;
|
Chris@0
|
64 m_windows[5] = GaussianWindow;
|
Chris@0
|
65 m_windows[6] = ParzenWindow;
|
Chris@0
|
66 m_windows[7] = BartlettWindow;
|
Chris@0
|
67 m_windows[8] = RectangularWindow;
|
Chris@0
|
68
|
Chris@0
|
69 QComboBox *windowCombo = new QComboBox;
|
Chris@0
|
70 int min, max, i;
|
Chris@0
|
71 int window = prefs->getPropertyRangeAndValue("Window Type", &min, &max);
|
Chris@0
|
72 m_windowType = window;
|
Chris@0
|
73 int index = 0;
|
Chris@0
|
74
|
Chris@0
|
75 for (i = 0; i <= 8; ++i) {
|
Chris@0
|
76 windowCombo->addItem(prefs->getPropertyValueLabel("Window Type",
|
Chris@0
|
77 m_windows[i]));
|
Chris@0
|
78 if (m_windows[i] == window) index = i;
|
Chris@0
|
79 }
|
Chris@0
|
80
|
Chris@0
|
81 windowCombo->setCurrentIndex(index);
|
Chris@0
|
82
|
Chris@0
|
83 m_windowTimeExampleLabel = new QLabel;
|
Chris@0
|
84 m_windowFreqExampleLabel = new QLabel;
|
Chris@0
|
85
|
Chris@0
|
86 connect(windowCombo, SIGNAL(currentIndexChanged(int)),
|
Chris@0
|
87 this, SLOT(windowTypeChanged(int)));
|
Chris@0
|
88 windowTypeChanged(index);
|
Chris@0
|
89
|
Chris@0
|
90 QCheckBox *smoothing = new QCheckBox;
|
Chris@0
|
91 m_smoothSpectrogram = prefs->getSmoothSpectrogram();
|
Chris@0
|
92 smoothing->setCheckState(m_smoothSpectrogram ?
|
Chris@0
|
93 Qt::Checked : Qt::Unchecked);
|
Chris@0
|
94
|
Chris@0
|
95 connect(smoothing, SIGNAL(stateChanged(int)),
|
Chris@0
|
96 this, SLOT(smoothSpectrogramChanged(int)));
|
Chris@0
|
97
|
Chris@0
|
98 QComboBox *propertyLayout = new QComboBox;
|
Chris@0
|
99 int pl = prefs->getPropertyRangeAndValue("Property Box Layout", &min, &max);
|
Chris@0
|
100 m_propertyLayout = pl;
|
Chris@0
|
101
|
Chris@0
|
102 for (i = min; i <= max; ++i) {
|
Chris@0
|
103 propertyLayout->addItem(prefs->getPropertyValueLabel("Property Box Layout", i));
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 propertyLayout->setCurrentIndex(pl);
|
Chris@0
|
107
|
Chris@0
|
108 connect(propertyLayout, SIGNAL(currentIndexChanged(int)),
|
Chris@0
|
109 this, SLOT(propertyLayoutChanged(int)));
|
Chris@0
|
110
|
Chris@0
|
111 m_tuningFrequency = prefs->getTuningFrequency();
|
Chris@0
|
112
|
Chris@0
|
113 QDoubleSpinBox *frequency = new QDoubleSpinBox;
|
Chris@0
|
114 frequency->setMinimum(100.0);
|
Chris@0
|
115 frequency->setMaximum(5000.0);
|
Chris@0
|
116 frequency->setSuffix(" Hz");
|
Chris@0
|
117 frequency->setSingleStep(1);
|
Chris@0
|
118 frequency->setValue(m_tuningFrequency);
|
Chris@0
|
119 frequency->setDecimals(2);
|
Chris@0
|
120
|
Chris@0
|
121 connect(frequency, SIGNAL(valueChanged(double)),
|
Chris@0
|
122 this, SLOT(tuningFrequencyChanged(double)));
|
Chris@0
|
123
|
Chris@0
|
124 int row = 0;
|
Chris@0
|
125
|
Chris@0
|
126 subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
|
Chris@0
|
127 ("Property Box Layout"))),
|
Chris@0
|
128 row, 0);
|
Chris@0
|
129 subgrid->addWidget(propertyLayout, row++, 1, 1, 2);
|
Chris@0
|
130
|
Chris@0
|
131 subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
|
Chris@0
|
132 ("Tuning Frequency"))),
|
Chris@0
|
133 row, 0);
|
Chris@0
|
134 subgrid->addWidget(frequency, row++, 1, 1, 2);
|
Chris@0
|
135
|
Chris@0
|
136 subgrid->addWidget(new QLabel(prefs->getPropertyLabel
|
Chris@0
|
137 ("Smooth Spectrogram")),
|
Chris@0
|
138 row, 0, 1, 2);
|
Chris@0
|
139 subgrid->addWidget(smoothing, row++, 2);
|
Chris@0
|
140
|
Chris@0
|
141 subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel
|
Chris@0
|
142 ("Window Type"))),
|
Chris@0
|
143 row, 0);
|
Chris@0
|
144 subgrid->addWidget(windowCombo, row++, 1, 1, 2);
|
Chris@0
|
145
|
Chris@0
|
146 subgrid->addWidget(m_windowTimeExampleLabel, row, 1);
|
Chris@0
|
147 subgrid->addWidget(m_windowFreqExampleLabel, row, 2);
|
Chris@0
|
148
|
Chris@0
|
149 QHBoxLayout *hbox = new QHBoxLayout;
|
Chris@0
|
150 grid->addLayout(hbox, 1, 0);
|
Chris@0
|
151
|
Chris@0
|
152 QPushButton *ok = new QPushButton(tr("OK"));
|
Chris@0
|
153 QPushButton *cancel = new QPushButton(tr("Cancel"));
|
Chris@0
|
154 hbox->addStretch(10);
|
Chris@0
|
155 hbox->addWidget(ok);
|
Chris@0
|
156 hbox->addWidget(m_applyButton);
|
Chris@0
|
157 hbox->addWidget(cancel);
|
Chris@0
|
158 connect(ok, SIGNAL(clicked()), this, SLOT(okClicked()));
|
Chris@0
|
159 connect(m_applyButton, SIGNAL(clicked()), this, SLOT(applyClicked()));
|
Chris@0
|
160 connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked()));
|
Chris@0
|
161
|
Chris@0
|
162 m_applyButton->setEnabled(false);
|
Chris@0
|
163 }
|
Chris@0
|
164
|
Chris@0
|
165 PreferencesDialog::~PreferencesDialog()
|
Chris@0
|
166 {
|
Chris@0
|
167 std::cerr << "PreferencesDialog::~PreferencesDialog()" << std::endl;
|
Chris@0
|
168
|
Chris@0
|
169 delete[] m_windows;
|
Chris@0
|
170 }
|
Chris@0
|
171
|
Chris@0
|
172 void
|
Chris@0
|
173 PreferencesDialog::windowTypeChanged(int value)
|
Chris@0
|
174 {
|
Chris@0
|
175 int step = 24;
|
Chris@0
|
176 int peak = 48;
|
Chris@0
|
177 int w = step * 4, h = 64;
|
Chris@0
|
178 WindowType type = m_windows[value];
|
Chris@0
|
179 Window<float> windower = Window<float>(type, step * 2);
|
Chris@0
|
180
|
Chris@0
|
181 QPixmap timeLabel(w, h + 1);
|
Chris@0
|
182 timeLabel.fill(Qt::white);
|
Chris@0
|
183 QPainter timePainter(&timeLabel);
|
Chris@0
|
184
|
Chris@0
|
185 QPainterPath path;
|
Chris@0
|
186
|
Chris@0
|
187 path.moveTo(0, h - peak + 1);
|
Chris@0
|
188 path.lineTo(w, h - peak + 1);
|
Chris@0
|
189
|
Chris@0
|
190 timePainter.setPen(Qt::gray);
|
Chris@0
|
191 timePainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@0
|
192 timePainter.drawPath(path);
|
Chris@0
|
193
|
Chris@0
|
194 path = QPainterPath();
|
Chris@0
|
195
|
Chris@0
|
196 float acc[w];
|
Chris@0
|
197 for (int i = 0; i < w; ++i) acc[i] = 0.f;
|
Chris@0
|
198 for (int j = 0; j < 3; ++j) {
|
Chris@0
|
199 for (int i = 0; i < step * 2; ++i) {
|
Chris@0
|
200 acc[j * step + i] += windower.getValue(i);
|
Chris@0
|
201 }
|
Chris@0
|
202 }
|
Chris@0
|
203 for (int i = 0; i < w; ++i) {
|
Chris@0
|
204 int y = h - int(peak * acc[i] + 0.001) + 1;
|
Chris@0
|
205 if (i == 0) path.moveTo(i, y);
|
Chris@0
|
206 else path.lineTo(i, y);
|
Chris@0
|
207 }
|
Chris@0
|
208
|
Chris@0
|
209 timePainter.drawPath(path);
|
Chris@0
|
210 timePainter.setRenderHint(QPainter::Antialiasing, false);
|
Chris@0
|
211
|
Chris@0
|
212 path = QPainterPath();
|
Chris@0
|
213
|
Chris@0
|
214 timePainter.setPen(Qt::black);
|
Chris@0
|
215
|
Chris@0
|
216 for (int i = 0; i < step * 2; ++i) {
|
Chris@0
|
217 int y = h - int(peak * windower.getValue(i) + 0.001) + 1;
|
Chris@0
|
218 if (i == 0) path.moveTo(i + step, float(y));
|
Chris@0
|
219 else path.lineTo(i + step, float(y));
|
Chris@0
|
220 }
|
Chris@0
|
221
|
Chris@0
|
222 if (type == RectangularWindow) {
|
Chris@0
|
223 timePainter.drawPath(path);
|
Chris@0
|
224 path = QPainterPath();
|
Chris@0
|
225 }
|
Chris@0
|
226
|
Chris@0
|
227 timePainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@0
|
228 path.addRect(0, 0, w, h + 1);
|
Chris@0
|
229 timePainter.drawPath(path);
|
Chris@0
|
230
|
Chris@0
|
231 QFont font;
|
Chris@0
|
232 font.setPixelSize(10);
|
Chris@0
|
233 font.setItalic(true);
|
Chris@0
|
234 timePainter.setFont(font);
|
Chris@0
|
235 QString label = tr("V / time");
|
Chris@0
|
236 timePainter.drawText(w - timePainter.fontMetrics().width(label) - 4,
|
Chris@0
|
237 timePainter.fontMetrics().ascent() + 1, label);
|
Chris@0
|
238
|
Chris@0
|
239 m_windowTimeExampleLabel->setPixmap(timeLabel);
|
Chris@0
|
240
|
Chris@0
|
241 int fw = 100;
|
Chris@0
|
242
|
Chris@0
|
243 QPixmap freqLabel(fw, h + 1);
|
Chris@0
|
244 freqLabel.fill(Qt::white);
|
Chris@0
|
245 QPainter freqPainter(&freqLabel);
|
Chris@0
|
246 path = QPainterPath();
|
Chris@0
|
247
|
Chris@0
|
248 size_t fftsize = 512;
|
Chris@0
|
249
|
Chris@0
|
250 float *input = (float *)fftwf_malloc(fftsize * sizeof(float));
|
Chris@0
|
251 fftwf_complex *output =
|
Chris@0
|
252 (fftwf_complex *)fftwf_malloc(fftsize * sizeof(fftwf_complex));
|
Chris@0
|
253 fftwf_plan plan = fftwf_plan_dft_r2c_1d(fftsize, input, output,
|
Chris@0
|
254 FFTW_ESTIMATE);
|
Chris@0
|
255 for (int i = 0; i < fftsize; ++i) input[i] = 0.f;
|
Chris@0
|
256 for (int i = 0; i < step * 2; ++i) {
|
Chris@0
|
257 input[fftsize/2 - step + i] = windower.getValue(i);
|
Chris@0
|
258 }
|
Chris@0
|
259
|
Chris@0
|
260 fftwf_execute(plan);
|
Chris@0
|
261 fftwf_destroy_plan(plan);
|
Chris@0
|
262
|
Chris@0
|
263 float maxdb = 0.f;
|
Chris@0
|
264 float mindb = 0.f;
|
Chris@0
|
265 bool first = true;
|
Chris@0
|
266 for (int i = 0; i < fftsize/2; ++i) {
|
Chris@0
|
267 float power = output[i][0] * output[i][0] + output[i][1] * output[i][1];
|
Chris@0
|
268 float db = mindb;
|
Chris@0
|
269 if (power > 0) {
|
Chris@0
|
270 db = 20 * log10(power);
|
Chris@0
|
271 if (first || db > maxdb) maxdb = db;
|
Chris@0
|
272 if (first || db < mindb) mindb = db;
|
Chris@0
|
273 first = false;
|
Chris@0
|
274 }
|
Chris@0
|
275 }
|
Chris@0
|
276
|
Chris@0
|
277 if (mindb > -80.f) mindb = -80.f;
|
Chris@0
|
278
|
Chris@0
|
279 // -- no, don't use the actual mindb -- it's easier to compare
|
Chris@0
|
280 // plots with a fixed min value
|
Chris@0
|
281 mindb = -170.f;
|
Chris@0
|
282
|
Chris@0
|
283 float maxval = maxdb + -mindb;
|
Chris@0
|
284
|
Chris@0
|
285 float ly = h - ((-80.f + -mindb) / maxval) * peak + 1;
|
Chris@0
|
286
|
Chris@0
|
287 path.moveTo(0, h - peak + 1);
|
Chris@0
|
288 path.lineTo(fw, h - peak + 1);
|
Chris@0
|
289
|
Chris@0
|
290 freqPainter.setPen(Qt::gray);
|
Chris@0
|
291 freqPainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@0
|
292 freqPainter.drawPath(path);
|
Chris@0
|
293
|
Chris@0
|
294 path = QPainterPath();
|
Chris@0
|
295 freqPainter.setPen(Qt::black);
|
Chris@0
|
296
|
Chris@0
|
297 // std::cerr << "maxdb = " << maxdb << ", mindb = " << mindb << ", maxval = " <<maxval << std::endl;
|
Chris@0
|
298
|
Chris@0
|
299 for (int i = 0; i < fftsize/2; ++i) {
|
Chris@0
|
300 float power = output[i][0] * output[i][0] + output[i][1] * output[i][1];
|
Chris@0
|
301 float db = 20 * log10(power);
|
Chris@0
|
302 float val = db + -mindb;
|
Chris@0
|
303 if (val < 0) val = 0;
|
Chris@0
|
304 float norm = val / maxval;
|
Chris@0
|
305 float x = (fw / float(fftsize/2)) * i;
|
Chris@0
|
306 float y = h - norm * peak + 1;
|
Chris@0
|
307 if (i == 0) path.moveTo(x, y);
|
Chris@0
|
308 else path.lineTo(x, y);
|
Chris@0
|
309 }
|
Chris@0
|
310
|
Chris@0
|
311 freqPainter.setRenderHint(QPainter::Antialiasing, true);
|
Chris@0
|
312 path.addRect(0, 0, fw, h + 1);
|
Chris@0
|
313 freqPainter.drawPath(path);
|
Chris@0
|
314
|
Chris@0
|
315 fftwf_free(input);
|
Chris@0
|
316 fftwf_free(output);
|
Chris@0
|
317
|
Chris@0
|
318 freqPainter.setFont(font);
|
Chris@0
|
319 label = tr("dB / freq");
|
Chris@0
|
320 freqPainter.drawText(fw - freqPainter.fontMetrics().width(label) - 4,
|
Chris@0
|
321 freqPainter.fontMetrics().ascent() + 1, label);
|
Chris@0
|
322
|
Chris@0
|
323 m_windowFreqExampleLabel->setPixmap(freqLabel);
|
Chris@0
|
324
|
Chris@0
|
325 m_windowType = type;
|
Chris@0
|
326 m_applyButton->setEnabled(true);
|
Chris@0
|
327 }
|
Chris@0
|
328
|
Chris@0
|
329 void
|
Chris@0
|
330 PreferencesDialog::smoothSpectrogramChanged(int state)
|
Chris@0
|
331 {
|
Chris@0
|
332 m_smoothSpectrogram = (state == Qt::Checked);
|
Chris@0
|
333 m_applyButton->setEnabled(true);
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 void
|
Chris@0
|
337 PreferencesDialog::propertyLayoutChanged(int layout)
|
Chris@0
|
338 {
|
Chris@0
|
339 m_propertyLayout = layout;
|
Chris@0
|
340 m_applyButton->setEnabled(true);
|
Chris@0
|
341 }
|
Chris@0
|
342
|
Chris@0
|
343 void
|
Chris@0
|
344 PreferencesDialog::tuningFrequencyChanged(double freq)
|
Chris@0
|
345 {
|
Chris@0
|
346 m_tuningFrequency = freq;
|
Chris@0
|
347 m_applyButton->setEnabled(true);
|
Chris@0
|
348 }
|
Chris@0
|
349
|
Chris@0
|
350 void
|
Chris@0
|
351 PreferencesDialog::okClicked()
|
Chris@0
|
352 {
|
Chris@0
|
353 applyClicked();
|
Chris@0
|
354 accept();
|
Chris@0
|
355 }
|
Chris@0
|
356
|
Chris@0
|
357 void
|
Chris@0
|
358 PreferencesDialog::applyClicked()
|
Chris@0
|
359 {
|
Chris@0
|
360 Preferences *prefs = Preferences::getInstance();
|
Chris@0
|
361 prefs->setWindowType(WindowType(m_windowType));
|
Chris@0
|
362 prefs->setSmoothSpectrogram(m_smoothSpectrogram);
|
Chris@0
|
363 prefs->setPropertyBoxLayout(Preferences::PropertyBoxLayout
|
Chris@0
|
364 (m_propertyLayout));
|
Chris@0
|
365 prefs->setTuningFrequency(m_tuningFrequency);
|
Chris@0
|
366 m_applyButton->setEnabled(false);
|
Chris@0
|
367 }
|
Chris@0
|
368
|
Chris@0
|
369 void
|
Chris@0
|
370 PreferencesDialog::cancelClicked()
|
Chris@0
|
371 {
|
Chris@0
|
372 reject();
|
Chris@0
|
373 }
|
Chris@0
|
374
|