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