Chris@885: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@885: Chris@885: /* Chris@885: Sonic Visualiser Chris@885: An audio file viewer and annotation editor. Chris@885: Centre for Digital Music, Queen Mary, University of London. Chris@885: Chris@885: This program is free software; you can redistribute it and/or Chris@885: modify it under the terms of the GNU General Public License as Chris@885: published by the Free Software Foundation; either version 2 of the Chris@885: License, or (at your option) any later version. See the file Chris@885: COPYING included with this distribution for more information. Chris@885: */ Chris@885: Chris@885: #include "UnitConverter.h" Chris@885: Chris@885: #include Chris@885: #include Chris@885: #include Chris@885: #include Chris@885: #include Chris@885: #include Chris@889: #include Chris@885: Chris@885: #include "base/Debug.h" Chris@885: #include "base/Pitch.h" Chris@888: #include "base/Preferences.h" Chris@885: Chris@885: using namespace std; Chris@885: Chris@886: static QString pianoNotes[] = { Chris@886: "C", "C# / Db", "D", "D# / Eb", "E", Chris@886: "F", "F# / Gb", "G", "G# / Ab", "A", "A# / Bb", "B" Chris@886: }; Chris@886: Chris@885: UnitConverter::UnitConverter(QWidget *parent) : Chris@885: QDialog(parent) Chris@885: { Chris@889: QGridLayout *maingrid = new QGridLayout; Chris@889: setLayout(maingrid); Chris@889: Chris@889: QTabWidget *tabs = new QTabWidget; Chris@889: maingrid->addWidget(tabs, 0, 0); Chris@889: Chris@889: QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close); Chris@889: maingrid->addWidget(bb, 1, 0); Chris@889: connect(bb, SIGNAL(rejected()), this, SLOT(close())); Chris@889: Chris@889: QFrame *frame = new QFrame; Chris@889: tabs->addTab(frame, tr("Pitch")); Chris@889: Chris@885: QGridLayout *grid = new QGridLayout; Chris@889: frame->setLayout(grid); Chris@885: Chris@889: m_freq = new QDoubleSpinBox; Chris@889: m_freq->setSuffix(QString(" Hz")); Chris@889: m_freq->setDecimals(6); Chris@889: m_freq->setMinimum(1e-3); Chris@889: m_freq->setMaximum(1e6); Chris@889: m_freq->setValue(440); Chris@889: connect(m_freq, SIGNAL(valueChanged(double)), Chris@893: this, SLOT(freqChanged())); Chris@885: Chris@888: // The min and max range values for all the remaining controls are Chris@888: // determined by the min and max Hz above Chris@888: Chris@885: m_midi = new QSpinBox; Chris@888: m_midi->setMinimum(-156); Chris@888: m_midi->setMaximum(203); Chris@885: connect(m_midi, SIGNAL(valueChanged(int)), Chris@893: this, SLOT(midiChanged())); Chris@885: Chris@885: m_note = new QComboBox; Chris@886: for (int i = 0; i < 12; ++i) { Chris@886: m_note->addItem(pianoNotes[i]); Chris@886: } Chris@886: connect(m_note, SIGNAL(currentIndexChanged(int)), Chris@893: this, SLOT(noteChanged())); Chris@885: Chris@885: m_octave = new QSpinBox; Chris@888: m_octave->setMinimum(-14); Chris@888: m_octave->setMaximum(15); Chris@886: connect(m_octave, SIGNAL(valueChanged(int)), Chris@893: this, SLOT(octaveChanged())); Chris@885: Chris@885: m_cents = new QDoubleSpinBox; Chris@885: m_cents->setSuffix(tr(" cents")); Chris@885: m_cents->setDecimals(4); Chris@885: m_cents->setMinimum(-50); Chris@885: m_cents->setMaximum(50); Chris@885: connect(m_cents, SIGNAL(valueChanged(double)), Chris@893: this, SLOT(centsChanged())); Chris@885: Chris@893: int row = 0; Chris@893: Chris@893: grid->addWidget(new QLabel(tr("In 12-tone Equal Temperament:")), row, 0, 1, 9); Chris@893: Chris@893: ++row; Chris@893: Chris@893: grid->setRowMinimumHeight(row, 8); Chris@893: Chris@893: ++row; Chris@886: Chris@892: grid->addWidget(m_freq, row, 0, 2, 1, Qt::AlignRight | Qt::AlignVCenter); Chris@892: grid->addWidget(new QLabel(tr("=")), row, 1, 2, 1, Qt::AlignHCenter | Qt::AlignVCenter); Chris@885: Chris@892: grid->addWidget(new QLabel(tr("+")), row, 7, 2, 1, Qt::AlignHCenter | Qt::AlignVCenter); Chris@892: grid->addWidget(m_cents, row, 8, 2, 1, Qt::AlignLeft | Qt::AlignVCenter); Chris@885: Chris@886: grid->addWidget(new QLabel(tr("Piano note")), row, 2, 1, 2); Chris@886: grid->addWidget(m_note, row, 4); Chris@886: grid->addWidget(new QLabel(tr("in octave")), row, 5); Chris@886: grid->addWidget(m_octave, row, 6); Chris@886: Chris@886: ++row; Chris@886: Chris@888: grid->addWidget(new QLabel(tr("MIDI pitch")), row, 2, 1, 2); Chris@886: grid->addWidget(m_midi, row, 4); Chris@886: Chris@886: ++row; Chris@885: Chris@893: grid->setRowStretch(row, 20); Chris@893: grid->setRowMinimumHeight(row, 8); Chris@893: Chris@893: ++row; Chris@893: Chris@891: m_pitchPrefsLabel = new QLabel; Chris@891: grid->addWidget(m_pitchPrefsLabel, row, 0, 1, 9); Chris@888: Chris@888: ++row; Chris@888: Chris@888: grid->addWidget Chris@888: (new QLabel(tr("Note that only pitches in the range 0 to 127 are valid " Chris@888: "in the MIDI protocol.")), Chris@888: row, 0, 1, 9); Chris@888: Chris@888: ++row; Chris@891: Chris@891: frame = new QFrame; Chris@891: tabs->addTab(frame, tr("Tempo")); Chris@891: Chris@891: grid = new QGridLayout; Chris@891: frame->setLayout(grid); Chris@885: Chris@892: m_samples = new QDoubleSpinBox; Chris@892: m_samples->setSuffix(QString(" samples")); Chris@893: m_samples->setDecimals(2); Chris@893: m_samples->setMinimum(1); Chris@893: m_samples->setMaximum(1e8); Chris@892: m_samples->setValue(22050); Chris@892: connect(m_samples, SIGNAL(valueChanged(double)), Chris@893: this, SLOT(samplesChanged())); Chris@892: Chris@892: m_period = new QDoubleSpinBox; Chris@892: m_period->setSuffix(QString(" ms")); Chris@892: m_period->setDecimals(4); Chris@893: m_period->setMinimum(1e-3); Chris@893: m_period->setMaximum(100000); Chris@892: m_period->setValue(500); Chris@892: connect(m_period, SIGNAL(valueChanged(double)), Chris@893: this, SLOT(periodChanged())); Chris@892: Chris@892: m_bpm = new QDoubleSpinBox; Chris@892: m_bpm->setSuffix(QString(" bpm")); Chris@892: m_bpm->setDecimals(4); Chris@892: m_bpm->setMinimum(0.1); Chris@892: m_bpm->setMaximum(1e6); Chris@892: m_bpm->setValue(120); Chris@892: connect(m_bpm, SIGNAL(valueChanged(double)), Chris@893: this, SLOT(bpmChanged())); Chris@892: Chris@892: m_tempofreq = new QDoubleSpinBox; Chris@892: m_tempofreq->setSuffix(QString(" beats/sec")); Chris@892: m_tempofreq->setDecimals(4); Chris@893: m_tempofreq->setMinimum(1e-3); Chris@893: m_tempofreq->setMaximum(1e5); Chris@892: m_tempofreq->setValue(0.5); Chris@893: Chris@892: connect(m_tempofreq, SIGNAL(valueChanged(double)), Chris@893: this, SLOT(tempofreqChanged())); Chris@892: Chris@892: m_samplerate = new QComboBox; Chris@892: QList rates; Chris@892: rates << 8000; Chris@892: for (int i = 1; i <= 16; i *= 2) { Chris@892: rates << 11025 * i << 12000 * i; Chris@892: } Chris@892: foreach (int r, rates) { Chris@892: m_samplerate->addItem(QString("%1 Hz").arg(r)); Chris@892: } Chris@893: connect(m_samplerate, SIGNAL(currentIndexChanged(int)), Chris@893: this, SLOT(samplerateChanged())); Chris@892: m_samplerate->setCurrentText("44100 Hz"); Chris@892: Chris@891: connect(Preferences::getInstance(), Chris@891: SIGNAL(propertyChanged(PropertyContainer::PropertyName)), Chris@891: this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); Chris@892: Chris@893: row = 0; Chris@892: Chris@893: grid->setRowStretch(row, 20); Chris@893: grid->setRowMinimumHeight(row, 8); Chris@893: Chris@893: ++row; Chris@893: Chris@893: grid->addWidget(new QLabel(tr("Beat period")), row, 0, 2, 1, Qt::AlignVCenter); Chris@893: grid->addWidget(m_period, row, 1); Chris@893: grid->addWidget(new QLabel(tr("=")), row, 2, 2, 1, Qt::AlignVCenter); Chris@893: Chris@893: grid->addWidget(m_tempofreq, row, 3); Chris@893: Chris@893: grid->addWidget(new QLabel(tr("at")), row, 4, 2, 1, Qt::AlignVCenter); Chris@893: grid->addWidget(m_samplerate, row, 5, 2, 1, Qt::AlignVCenter); Chris@893: Chris@893: ++row; Chris@893: Chris@892: grid->addWidget(m_samples, row, 1); Chris@892: grid->addWidget(m_bpm, row, 3); Chris@892: Chris@892: ++row; Chris@891: Chris@893: grid->setRowStretch(row, 20); Chris@893: grid->setRowMinimumHeight(row, 8); Chris@892: Chris@889: updatePitchesFromFreq(); Chris@891: updatePitchPrefsLabel(); Chris@891: updateTempiFromSamples(); Chris@885: } Chris@885: Chris@885: UnitConverter::~UnitConverter() Chris@885: { Chris@885: } Chris@885: Chris@885: void Chris@893: UnitConverter::setTo(QSpinBox *box, int value) Chris@893: { Chris@893: box->blockSignals(true); Chris@893: if (value < box->minimum() || value > box->maximum()) { Chris@893: QPalette p; Chris@893: p.setColor(QPalette::Text, Qt::red); Chris@893: box->setPalette(p); Chris@893: } else { Chris@893: box->setPalette(QPalette()); Chris@893: } Chris@893: box->setValue(value); Chris@893: box->blockSignals(false); Chris@893: } Chris@893: Chris@893: void Chris@893: UnitConverter::setTo(QDoubleSpinBox *box, double value) Chris@893: { Chris@893: box->blockSignals(true); Chris@893: if (value < box->minimum() || value > box->maximum()) { Chris@893: QPalette p; Chris@893: p.setColor(QPalette::Text, Qt::red); Chris@893: box->setPalette(p); Chris@893: } else { Chris@893: box->setPalette(QPalette()); Chris@893: } Chris@893: box->setValue(value); Chris@893: box->blockSignals(false); Chris@893: } Chris@893: Chris@893: void Chris@891: UnitConverter::preferenceChanged(PropertyContainer::PropertyName) Chris@891: { Chris@891: updatePitchesFromFreq(); Chris@891: updatePitchPrefsLabel(); Chris@891: } Chris@891: Chris@891: void Chris@891: UnitConverter::updatePitchPrefsLabel() Chris@891: { Chris@891: m_pitchPrefsLabel->setText Chris@891: (tr("With concert-A tuning frequency at %1 Hz, and " Chris@891: "middle C residing in octave %2.\n" Chris@891: "(These can be changed in the application preferences.)") Chris@891: .arg(Preferences::getInstance()->getTuningFrequency()) Chris@891: .arg(Preferences::getInstance()->getOctaveOfMiddleC())); Chris@891: } Chris@891: Chris@891: void Chris@893: UnitConverter::freqChanged() Chris@885: { Chris@889: updatePitchesFromFreq(); Chris@885: } Chris@885: Chris@885: void Chris@893: UnitConverter::midiChanged() Chris@885: { Chris@889: double freq = Pitch::getFrequencyForPitch(m_midi->value(), m_cents->value()); Chris@889: m_freq->setValue(freq); Chris@885: } Chris@885: Chris@885: void Chris@893: UnitConverter::noteChanged() Chris@885: { Chris@887: int pitch = Pitch::getPitchForNoteAndOctave(m_note->currentIndex(), Chris@887: m_octave->value()); Chris@889: double freq = Pitch::getFrequencyForPitch(pitch, m_cents->value()); Chris@889: m_freq->setValue(freq); Chris@885: } Chris@885: Chris@885: void Chris@893: UnitConverter::octaveChanged() Chris@885: { Chris@888: int pitch = Pitch::getPitchForNoteAndOctave(m_note->currentIndex(), Chris@888: m_octave->value()); Chris@889: double freq = Pitch::getFrequencyForPitch(pitch, m_cents->value()); Chris@889: m_freq->setValue(freq); Chris@885: } Chris@885: Chris@885: void Chris@893: UnitConverter::centsChanged() Chris@885: { Chris@889: double freq = Pitch::getFrequencyForPitch(m_midi->value(), m_cents->value()); Chris@889: m_freq->setValue(freq); Chris@885: } Chris@885: Chris@885: void Chris@889: UnitConverter::updatePitchesFromFreq() Chris@885: { Chris@892: double cents = 0; Chris@889: int pitch = Pitch::getPitchForFrequency(m_freq->value(), ¢s); Chris@887: int note, octave; Chris@887: Pitch::getNoteAndOctaveForPitch(pitch, note, octave); Chris@885: Chris@1158: // cerr << "pitch " << pitch << " note " << note << " octave " << octave << " cents " << cents << endl; Chris@893: Chris@893: setTo(m_midi, pitch); Chris@893: setTo(m_cents, cents); Chris@893: setTo(m_octave, octave); Chris@893: Chris@887: m_note->blockSignals(true); Chris@887: m_note->setCurrentIndex(note); Chris@887: m_note->blockSignals(false); Chris@885: } Chris@885: Chris@891: void Chris@893: UnitConverter::samplesChanged() Chris@892: { Chris@892: updateTempiFromSamples(); Chris@892: } Chris@892: Chris@892: void Chris@893: UnitConverter::periodChanged() Chris@892: { Chris@892: double rate = getSampleRate(); Chris@892: double sec = m_period->value() / 1000.0; Chris@892: double samples = rate * sec; Chris@892: m_samples->setValue(samples); Chris@892: } Chris@892: Chris@892: void Chris@893: UnitConverter::bpmChanged() Chris@892: { Chris@892: double rate = getSampleRate(); Chris@892: double sec = 60.0 / m_bpm->value(); Chris@892: double samples = rate * sec; Chris@892: m_samples->setValue(samples); Chris@892: } Chris@892: Chris@892: void Chris@893: UnitConverter::tempofreqChanged() Chris@892: { Chris@892: double rate = getSampleRate(); Chris@892: double samples = rate / m_tempofreq->value(); Chris@892: m_samples->setValue(samples); Chris@892: } Chris@892: Chris@892: void Chris@893: UnitConverter::samplerateChanged() Chris@892: { Chris@893: // Preserve the beat period in seconds, here, not in samples Chris@893: periodChanged(); Chris@892: } Chris@892: Chris@892: double Chris@892: UnitConverter::getSampleRate() Chris@892: { Chris@892: return double(atoi(m_samplerate->currentText().toLocal8Bit().data())); Chris@892: } Chris@892: Chris@892: void Chris@891: UnitConverter::updateTempiFromSamples() Chris@891: { Chris@892: double samples = m_samples->value(); Chris@892: double rate = getSampleRate(); Chris@892: Chris@1158: // cerr << samples << " samples at rate " << rate << endl; Chris@892: Chris@892: double sec = samples / rate; Chris@892: double hz = rate / samples; Chris@892: double bpm = 60.0 / sec; Chris@892: Chris@893: setTo(m_bpm, bpm); Chris@893: setTo(m_period, sec * 1000.0); Chris@893: setTo(m_tempofreq, hz); Chris@891: } Chris@885: Chris@885: