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@893
|
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@893
|
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@886
|
75 m_note->addItem(pianoNotes[i]);
|
Chris@886
|
76 }
|
Chris@886
|
77 connect(m_note, SIGNAL(currentIndexChanged(int)),
|
Chris@893
|
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@893
|
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@893
|
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@888
|
133 (new QLabel(tr("Note that only pitches in the range 0 to 127 are valid "
|
Chris@888
|
134 "in the MIDI protocol.")),
|
Chris@888
|
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@893
|
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@893
|
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@893
|
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@893
|
180 this, SLOT(tempofreqChanged()));
|
Chris@892
|
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@892
|
186 rates << 11025 * i << 12000 * i;
|
Chris@892
|
187 }
|
Chris@892
|
188 foreach (int r, rates) {
|
Chris@892
|
189 m_samplerate->addItem(QString("%1 Hz").arg(r));
|
Chris@892
|
190 }
|
Chris@893
|
191 connect(m_samplerate, SIGNAL(currentIndexChanged(int)),
|
Chris@893
|
192 this, SLOT(samplerateChanged()));
|
Chris@892
|
193 m_samplerate->setCurrentText("44100 Hz");
|
Chris@892
|
194
|
Chris@891
|
195 connect(Preferences::getInstance(),
|
Chris@891
|
196 SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
|
Chris@891
|
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@893
|
239 QPalette p;
|
Chris@893
|
240 p.setColor(QPalette::Text, Qt::red);
|
Chris@893
|
241 box->setPalette(p);
|
Chris@893
|
242 } else {
|
Chris@893
|
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@893
|
254 QPalette p;
|
Chris@893
|
255 p.setColor(QPalette::Text, Qt::red);
|
Chris@893
|
256 box->setPalette(p);
|
Chris@893
|
257 } else {
|
Chris@893
|
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@891
|
275 (tr("With concert-A tuning frequency at %1 Hz, and "
|
Chris@891
|
276 "middle C residing in octave %2.\n"
|
Chris@891
|
277 "(These can be changed in the application preferences.)")
|
Chris@891
|
278 .arg(Preferences::getInstance()->getTuningFrequency())
|
Chris@891
|
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@887
|
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@888
|
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(), ¢s);
|
Chris@887
|
325 int note, octave;
|
Chris@887
|
326 Pitch::getNoteAndOctaveForPitch(pitch, note, octave);
|
Chris@885
|
327
|
Chris@888
|
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@892
|
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
|