annotate widgets/UnitConverter.cpp @ 1553:76e4302a3fc2

Fix note numbering - ensure stable across whole track (as it used to be, but without scanning all notes in paint in order to do that)
author Chris Cannam
date Fri, 22 Nov 2019 14:12:50 +0000
parents a34a2a25907c
children
rev   line source
Chris@885 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@885 2
Chris@885 3 /*
Chris@885 4 Sonic Visualiser
Chris@885 5 An audio file viewer and annotation editor.
Chris@885 6 Centre for Digital Music, Queen Mary, University of London.
Chris@885 7
Chris@885 8 This program is free software; you can redistribute it and/or
Chris@885 9 modify it under the terms of the GNU General Public License as
Chris@885 10 published by the Free Software Foundation; either version 2 of the
Chris@885 11 License, or (at your option) any later version. See the file
Chris@885 12 COPYING included with this distribution for more information.
Chris@885 13 */
Chris@885 14
Chris@885 15 #include "UnitConverter.h"
Chris@885 16
Chris@885 17 #include <QSpinBox>
Chris@885 18 #include <QComboBox>
Chris@885 19 #include <QDoubleSpinBox>
Chris@885 20 #include <QLabel>
Chris@885 21 #include <QDialogButtonBox>
Chris@885 22 #include <QGridLayout>
Chris@889 23 #include <QTabWidget>
Chris@885 24
Chris@885 25 #include "base/Debug.h"
Chris@885 26 #include "base/Pitch.h"
Chris@888 27 #include "base/Preferences.h"
Chris@885 28
Chris@885 29 using namespace std;
Chris@885 30
Chris@886 31 static QString pianoNotes[] = {
Chris@886 32 "C", "C# / Db", "D", "D# / Eb", "E",
Chris@886 33 "F", "F# / Gb", "G", "G# / Ab", "A", "A# / Bb", "B"
Chris@886 34 };
Chris@886 35
Chris@885 36 UnitConverter::UnitConverter(QWidget *parent) :
Chris@885 37 QDialog(parent)
Chris@885 38 {
Chris@889 39 QGridLayout *maingrid = new QGridLayout;
Chris@889 40 setLayout(maingrid);
Chris@889 41
Chris@889 42 QTabWidget *tabs = new QTabWidget;
Chris@889 43 maingrid->addWidget(tabs, 0, 0);
Chris@889 44
Chris@889 45 QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close);
Chris@889 46 maingrid->addWidget(bb, 1, 0);
Chris@889 47 connect(bb, SIGNAL(rejected()), this, SLOT(close()));
Chris@889 48
Chris@889 49 QFrame *frame = new QFrame;
Chris@889 50 tabs->addTab(frame, tr("Pitch"));
Chris@889 51
Chris@885 52 QGridLayout *grid = new QGridLayout;
Chris@889 53 frame->setLayout(grid);
Chris@885 54
Chris@889 55 m_freq = new QDoubleSpinBox;
Chris@889 56 m_freq->setSuffix(QString(" Hz"));
Chris@889 57 m_freq->setDecimals(6);
Chris@889 58 m_freq->setMinimum(1e-3);
Chris@889 59 m_freq->setMaximum(1e6);
Chris@889 60 m_freq->setValue(440);
Chris@889 61 connect(m_freq, SIGNAL(valueChanged(double)),
Chris@1266 62 this, SLOT(freqChanged()));
Chris@885 63
Chris@888 64 // The min and max range values for all the remaining controls are
Chris@888 65 // determined by the min and max Hz above
Chris@888 66
Chris@885 67 m_midi = new QSpinBox;
Chris@888 68 m_midi->setMinimum(-156);
Chris@888 69 m_midi->setMaximum(203);
Chris@885 70 connect(m_midi, SIGNAL(valueChanged(int)),
Chris@1266 71 this, SLOT(midiChanged()));
Chris@885 72
Chris@885 73 m_note = new QComboBox;
Chris@886 74 for (int i = 0; i < 12; ++i) {
Chris@1266 75 m_note->addItem(pianoNotes[i]);
Chris@886 76 }
Chris@886 77 connect(m_note, SIGNAL(currentIndexChanged(int)),
Chris@1266 78 this, SLOT(noteChanged()));
Chris@885 79
Chris@885 80 m_octave = new QSpinBox;
Chris@888 81 m_octave->setMinimum(-14);
Chris@888 82 m_octave->setMaximum(15);
Chris@886 83 connect(m_octave, SIGNAL(valueChanged(int)),
Chris@1266 84 this, SLOT(octaveChanged()));
Chris@885 85
Chris@885 86 m_cents = new QDoubleSpinBox;
Chris@885 87 m_cents->setSuffix(tr(" cents"));
Chris@885 88 m_cents->setDecimals(4);
Chris@885 89 m_cents->setMinimum(-50);
Chris@885 90 m_cents->setMaximum(50);
Chris@885 91 connect(m_cents, SIGNAL(valueChanged(double)),
Chris@1266 92 this, SLOT(centsChanged()));
Chris@885 93
Chris@893 94 int row = 0;
Chris@893 95
Chris@893 96 grid->addWidget(new QLabel(tr("In 12-tone Equal Temperament:")), row, 0, 1, 9);
Chris@893 97
Chris@893 98 ++row;
Chris@893 99
Chris@893 100 grid->setRowMinimumHeight(row, 8);
Chris@893 101
Chris@893 102 ++row;
Chris@886 103
Chris@892 104 grid->addWidget(m_freq, row, 0, 2, 1, Qt::AlignRight | Qt::AlignVCenter);
Chris@892 105 grid->addWidget(new QLabel(tr("=")), row, 1, 2, 1, Qt::AlignHCenter | Qt::AlignVCenter);
Chris@885 106
Chris@892 107 grid->addWidget(new QLabel(tr("+")), row, 7, 2, 1, Qt::AlignHCenter | Qt::AlignVCenter);
Chris@892 108 grid->addWidget(m_cents, row, 8, 2, 1, Qt::AlignLeft | Qt::AlignVCenter);
Chris@885 109
Chris@886 110 grid->addWidget(new QLabel(tr("Piano note")), row, 2, 1, 2);
Chris@886 111 grid->addWidget(m_note, row, 4);
Chris@886 112 grid->addWidget(new QLabel(tr("in octave")), row, 5);
Chris@886 113 grid->addWidget(m_octave, row, 6);
Chris@886 114
Chris@886 115 ++row;
Chris@886 116
Chris@888 117 grid->addWidget(new QLabel(tr("MIDI pitch")), row, 2, 1, 2);
Chris@886 118 grid->addWidget(m_midi, row, 4);
Chris@886 119
Chris@886 120 ++row;
Chris@885 121
Chris@893 122 grid->setRowStretch(row, 20);
Chris@893 123 grid->setRowMinimumHeight(row, 8);
Chris@893 124
Chris@893 125 ++row;
Chris@893 126
Chris@891 127 m_pitchPrefsLabel = new QLabel;
Chris@891 128 grid->addWidget(m_pitchPrefsLabel, row, 0, 1, 9);
Chris@888 129
Chris@888 130 ++row;
Chris@888 131
Chris@888 132 grid->addWidget
Chris@1266 133 (new QLabel(tr("Note that only pitches in the range 0 to 127 are valid "
Chris@1266 134 "in the MIDI protocol.")),
Chris@1266 135 row, 0, 1, 9);
Chris@888 136
Chris@888 137 ++row;
Chris@891 138
Chris@891 139 frame = new QFrame;
Chris@891 140 tabs->addTab(frame, tr("Tempo"));
Chris@891 141
Chris@891 142 grid = new QGridLayout;
Chris@891 143 frame->setLayout(grid);
Chris@885 144
Chris@892 145 m_samples = new QDoubleSpinBox;
Chris@892 146 m_samples->setSuffix(QString(" samples"));
Chris@893 147 m_samples->setDecimals(2);
Chris@893 148 m_samples->setMinimum(1);
Chris@893 149 m_samples->setMaximum(1e8);
Chris@892 150 m_samples->setValue(22050);
Chris@892 151 connect(m_samples, SIGNAL(valueChanged(double)),
Chris@1266 152 this, SLOT(samplesChanged()));
Chris@892 153
Chris@892 154 m_period = new QDoubleSpinBox;
Chris@892 155 m_period->setSuffix(QString(" ms"));
Chris@892 156 m_period->setDecimals(4);
Chris@893 157 m_period->setMinimum(1e-3);
Chris@893 158 m_period->setMaximum(100000);
Chris@892 159 m_period->setValue(500);
Chris@892 160 connect(m_period, SIGNAL(valueChanged(double)),
Chris@1266 161 this, SLOT(periodChanged()));
Chris@892 162
Chris@892 163 m_bpm = new QDoubleSpinBox;
Chris@892 164 m_bpm->setSuffix(QString(" bpm"));
Chris@892 165 m_bpm->setDecimals(4);
Chris@892 166 m_bpm->setMinimum(0.1);
Chris@892 167 m_bpm->setMaximum(1e6);
Chris@892 168 m_bpm->setValue(120);
Chris@892 169 connect(m_bpm, SIGNAL(valueChanged(double)),
Chris@1266 170 this, SLOT(bpmChanged()));
Chris@892 171
Chris@892 172 m_tempofreq = new QDoubleSpinBox;
Chris@892 173 m_tempofreq->setSuffix(QString(" beats/sec"));
Chris@892 174 m_tempofreq->setDecimals(4);
Chris@893 175 m_tempofreq->setMinimum(1e-3);
Chris@893 176 m_tempofreq->setMaximum(1e5);
Chris@892 177 m_tempofreq->setValue(0.5);
Chris@893 178
Chris@892 179 connect(m_tempofreq, SIGNAL(valueChanged(double)),
Chris@1266 180 this, SLOT(tempofreqChanged()));
Chris@1266 181
Chris@892 182 m_samplerate = new QComboBox;
Chris@892 183 QList<int> rates;
Chris@892 184 rates << 8000;
Chris@892 185 for (int i = 1; i <= 16; i *= 2) {
Chris@1266 186 rates << 11025 * i << 12000 * i;
Chris@892 187 }
Chris@892 188 foreach (int r, rates) {
Chris@1266 189 m_samplerate->addItem(QString("%1 Hz").arg(r));
Chris@892 190 }
Chris@893 191 connect(m_samplerate, SIGNAL(currentIndexChanged(int)),
Chris@1266 192 this, SLOT(samplerateChanged()));
Chris@892 193 m_samplerate->setCurrentText("44100 Hz");
Chris@892 194
Chris@891 195 connect(Preferences::getInstance(),
Chris@1266 196 SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@1266 197 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@892 198
Chris@893 199 row = 0;
Chris@892 200
Chris@893 201 grid->setRowStretch(row, 20);
Chris@893 202 grid->setRowMinimumHeight(row, 8);
Chris@893 203
Chris@893 204 ++row;
Chris@893 205
Chris@893 206 grid->addWidget(new QLabel(tr("Beat period")), row, 0, 2, 1, Qt::AlignVCenter);
Chris@893 207 grid->addWidget(m_period, row, 1);
Chris@893 208 grid->addWidget(new QLabel(tr("=")), row, 2, 2, 1, Qt::AlignVCenter);
Chris@893 209
Chris@893 210 grid->addWidget(m_tempofreq, row, 3);
Chris@893 211
Chris@893 212 grid->addWidget(new QLabel(tr("at")), row, 4, 2, 1, Qt::AlignVCenter);
Chris@893 213 grid->addWidget(m_samplerate, row, 5, 2, 1, Qt::AlignVCenter);
Chris@893 214
Chris@893 215 ++row;
Chris@893 216
Chris@892 217 grid->addWidget(m_samples, row, 1);
Chris@892 218 grid->addWidget(m_bpm, row, 3);
Chris@892 219
Chris@892 220 ++row;
Chris@891 221
Chris@893 222 grid->setRowStretch(row, 20);
Chris@893 223 grid->setRowMinimumHeight(row, 8);
Chris@892 224
Chris@889 225 updatePitchesFromFreq();
Chris@891 226 updatePitchPrefsLabel();
Chris@891 227 updateTempiFromSamples();
Chris@885 228 }
Chris@885 229
Chris@885 230 UnitConverter::~UnitConverter()
Chris@885 231 {
Chris@885 232 }
Chris@885 233
Chris@885 234 void
Chris@893 235 UnitConverter::setTo(QSpinBox *box, int value)
Chris@893 236 {
Chris@893 237 box->blockSignals(true);
Chris@893 238 if (value < box->minimum() || value > box->maximum()) {
Chris@1266 239 QPalette p;
Chris@1266 240 p.setColor(QPalette::Text, Qt::red);
Chris@1266 241 box->setPalette(p);
Chris@893 242 } else {
Chris@1266 243 box->setPalette(QPalette());
Chris@893 244 }
Chris@893 245 box->setValue(value);
Chris@893 246 box->blockSignals(false);
Chris@893 247 }
Chris@893 248
Chris@893 249 void
Chris@893 250 UnitConverter::setTo(QDoubleSpinBox *box, double value)
Chris@893 251 {
Chris@893 252 box->blockSignals(true);
Chris@893 253 if (value < box->minimum() || value > box->maximum()) {
Chris@1266 254 QPalette p;
Chris@1266 255 p.setColor(QPalette::Text, Qt::red);
Chris@1266 256 box->setPalette(p);
Chris@893 257 } else {
Chris@1266 258 box->setPalette(QPalette());
Chris@893 259 }
Chris@893 260 box->setValue(value);
Chris@893 261 box->blockSignals(false);
Chris@893 262 }
Chris@893 263
Chris@893 264 void
Chris@891 265 UnitConverter::preferenceChanged(PropertyContainer::PropertyName)
Chris@891 266 {
Chris@891 267 updatePitchesFromFreq();
Chris@891 268 updatePitchPrefsLabel();
Chris@891 269 }
Chris@891 270
Chris@891 271 void
Chris@891 272 UnitConverter::updatePitchPrefsLabel()
Chris@891 273 {
Chris@891 274 m_pitchPrefsLabel->setText
Chris@1266 275 (tr("With concert-A tuning frequency at %1 Hz, and "
Chris@1266 276 "middle C residing in octave %2.\n"
Chris@1266 277 "(These can be changed in the application preferences.)")
Chris@1266 278 .arg(Preferences::getInstance()->getTuningFrequency())
Chris@1266 279 .arg(Preferences::getInstance()->getOctaveOfMiddleC()));
Chris@891 280 }
Chris@891 281
Chris@891 282 void
Chris@893 283 UnitConverter::freqChanged()
Chris@885 284 {
Chris@889 285 updatePitchesFromFreq();
Chris@885 286 }
Chris@885 287
Chris@885 288 void
Chris@893 289 UnitConverter::midiChanged()
Chris@885 290 {
Chris@889 291 double freq = Pitch::getFrequencyForPitch(m_midi->value(), m_cents->value());
Chris@889 292 m_freq->setValue(freq);
Chris@885 293 }
Chris@885 294
Chris@885 295 void
Chris@893 296 UnitConverter::noteChanged()
Chris@885 297 {
Chris@887 298 int pitch = Pitch::getPitchForNoteAndOctave(m_note->currentIndex(),
Chris@1266 299 m_octave->value());
Chris@889 300 double freq = Pitch::getFrequencyForPitch(pitch, m_cents->value());
Chris@889 301 m_freq->setValue(freq);
Chris@885 302 }
Chris@885 303
Chris@885 304 void
Chris@893 305 UnitConverter::octaveChanged()
Chris@885 306 {
Chris@888 307 int pitch = Pitch::getPitchForNoteAndOctave(m_note->currentIndex(),
Chris@1266 308 m_octave->value());
Chris@889 309 double freq = Pitch::getFrequencyForPitch(pitch, m_cents->value());
Chris@889 310 m_freq->setValue(freq);
Chris@885 311 }
Chris@885 312
Chris@885 313 void
Chris@893 314 UnitConverter::centsChanged()
Chris@885 315 {
Chris@889 316 double freq = Pitch::getFrequencyForPitch(m_midi->value(), m_cents->value());
Chris@889 317 m_freq->setValue(freq);
Chris@885 318 }
Chris@885 319
Chris@885 320 void
Chris@889 321 UnitConverter::updatePitchesFromFreq()
Chris@885 322 {
Chris@892 323 double cents = 0;
Chris@889 324 int pitch = Pitch::getPitchForFrequency(m_freq->value(), &cents);
Chris@887 325 int note, octave;
Chris@887 326 Pitch::getNoteAndOctaveForPitch(pitch, note, octave);
Chris@885 327
Chris@1158 328 // cerr << "pitch " << pitch << " note " << note << " octave " << octave << " cents " << cents << endl;
Chris@893 329
Chris@893 330 setTo(m_midi, pitch);
Chris@893 331 setTo(m_cents, cents);
Chris@893 332 setTo(m_octave, octave);
Chris@893 333
Chris@887 334 m_note->blockSignals(true);
Chris@887 335 m_note->setCurrentIndex(note);
Chris@887 336 m_note->blockSignals(false);
Chris@885 337 }
Chris@885 338
Chris@891 339 void
Chris@893 340 UnitConverter::samplesChanged()
Chris@892 341 {
Chris@892 342 updateTempiFromSamples();
Chris@892 343 }
Chris@892 344
Chris@892 345 void
Chris@893 346 UnitConverter::periodChanged()
Chris@892 347 {
Chris@892 348 double rate = getSampleRate();
Chris@892 349 double sec = m_period->value() / 1000.0;
Chris@892 350 double samples = rate * sec;
Chris@892 351 m_samples->setValue(samples);
Chris@892 352 }
Chris@892 353
Chris@892 354 void
Chris@893 355 UnitConverter::bpmChanged()
Chris@892 356 {
Chris@892 357 double rate = getSampleRate();
Chris@892 358 double sec = 60.0 / m_bpm->value();
Chris@892 359 double samples = rate * sec;
Chris@892 360 m_samples->setValue(samples);
Chris@892 361 }
Chris@892 362
Chris@892 363 void
Chris@893 364 UnitConverter::tempofreqChanged()
Chris@892 365 {
Chris@892 366 double rate = getSampleRate();
Chris@892 367 double samples = rate / m_tempofreq->value();
Chris@892 368 m_samples->setValue(samples);
Chris@892 369 }
Chris@892 370
Chris@892 371 void
Chris@893 372 UnitConverter::samplerateChanged()
Chris@892 373 {
Chris@893 374 // Preserve the beat period in seconds, here, not in samples
Chris@893 375 periodChanged();
Chris@892 376 }
Chris@892 377
Chris@892 378 double
Chris@892 379 UnitConverter::getSampleRate()
Chris@892 380 {
Chris@892 381 return double(atoi(m_samplerate->currentText().toLocal8Bit().data()));
Chris@892 382 }
Chris@892 383
Chris@892 384 void
Chris@891 385 UnitConverter::updateTempiFromSamples()
Chris@891 386 {
Chris@892 387 double samples = m_samples->value();
Chris@892 388 double rate = getSampleRate();
Chris@892 389
Chris@1158 390 // cerr << samples << " samples at rate " << rate << endl;
Chris@892 391
Chris@892 392 double sec = samples / rate;
Chris@892 393 double hz = rate / samples;
Chris@892 394 double bpm = 60.0 / sec;
Chris@892 395
Chris@893 396 setTo(m_bpm, bpm);
Chris@893 397 setTo(m_period, sec * 1000.0);
Chris@893 398 setTo(m_tempofreq, hz);
Chris@891 399 }
Chris@885 400
Chris@885 401