svcore  1.9
Pitch.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2006 Chris Cannam.
00008     
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "Pitch.h"
00017 #include "Preferences.h"
00018 #include "system/System.h"
00019 
00020 #include <cmath>
00021 
00022 float
00023 Pitch::getFrequencyForPitch(int midiPitch,
00024                             float centsOffset,
00025                             float concertA)
00026 {
00027     if (concertA <= 0.0) {
00028         concertA = Preferences::getInstance()->getTuningFrequency();
00029     }
00030     float p = float(midiPitch) + (centsOffset / 100);
00031     return concertA * powf(2.0, (p - 69.0) / 12.0);
00032 }
00033 
00034 int
00035 Pitch::getPitchForFrequency(float frequency,
00036                             float *centsOffsetReturn,
00037                             float concertA)
00038 {
00039     if (concertA <= 0.0) {
00040         concertA = Preferences::getInstance()->getTuningFrequency();
00041     }
00042     float p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0;
00043 
00044     int midiPitch = int(p + 0.00001);
00045     float centsOffset = (p - midiPitch) * 100.0;
00046 
00047     if (centsOffset >= 50.0) {
00048         midiPitch = midiPitch + 1;
00049         centsOffset = -(100.0 - centsOffset);
00050     }
00051     
00052     if (centsOffsetReturn) *centsOffsetReturn = centsOffset;
00053     return midiPitch;
00054 }
00055 
00056 int
00057 Pitch::getPitchForFrequencyDifference(float frequencyA,
00058                                       float frequencyB,
00059                                       float *centsOffsetReturn,
00060                                       float concertA)
00061 {
00062     if (concertA <= 0.0) {
00063         concertA = Preferences::getInstance()->getTuningFrequency();
00064     }
00065 
00066     if (frequencyA > frequencyB) {
00067         std::swap(frequencyA, frequencyB);
00068     }
00069 
00070     float pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0;
00071     float pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0;
00072 
00073     float p = pB - pA;
00074 
00075     int midiPitch = int(p + 0.00001);
00076     float centsOffset = (p - midiPitch) * 100.0;
00077 
00078     if (centsOffset >= 50.0) {
00079         midiPitch = midiPitch + 1;
00080         centsOffset = -(100.0 - centsOffset);
00081     }
00082     
00083     if (centsOffsetReturn) *centsOffsetReturn = centsOffset;
00084     return midiPitch;
00085 }
00086 
00087 static QString notes[] = {
00088     "C%1",  "C#%1", "D%1",  "D#%1",
00089     "E%1",  "F%1",  "F#%1", "G%1",
00090     "G#%1", "A%1",  "A#%1", "B%1"
00091 };
00092 
00093 static QString flatNotes[] = {
00094     "C%1",  "Db%1", "D%1",  "Eb%1",
00095     "E%1",  "F%1",  "Gb%1", "G%1",
00096     "Ab%1", "A%1",  "Bb%1", "B%1"
00097 };
00098 
00099 QString
00100 Pitch::getPitchLabel(int midiPitch,
00101                      float centsOffset,
00102                      bool useFlats)
00103 {
00104     int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote();
00105     int octave = baseOctave;
00106 
00107     // Note, this only gets the right octave number at octave
00108     // boundaries because Cb is enharmonic with B (not B#) and B# is
00109     // enharmonic with C (not Cb). So neither B# nor Cb will be
00110     // spelled from a MIDI pitch + flats flag in isolation.
00111 
00112     if (midiPitch < 0) {
00113         while (midiPitch < 0) {
00114             midiPitch += 12;
00115             --octave;
00116         }
00117     } else {
00118         octave = midiPitch / 12 + baseOctave;
00119     }
00120 
00121     QString plain = (useFlats ? flatNotes : notes)[midiPitch % 12].arg(octave);
00122 
00123     int ic = lrintf(centsOffset);
00124     if (ic == 0) return plain;
00125     else if (ic > 0) return QString("%1+%2c").arg(plain).arg(ic);
00126     else return QString("%1%2c").arg(plain).arg(ic);
00127 }
00128 
00129 QString
00130 Pitch::getPitchLabelForFrequency(float frequency,
00131                                  float concertA,
00132                                  bool useFlats)
00133 {
00134     if (concertA <= 0.0) {
00135         concertA = Preferences::getInstance()->getTuningFrequency();
00136     }
00137     float centsOffset = 0.0;
00138     int midiPitch = getPitchForFrequency(frequency, &centsOffset, concertA);
00139     return getPitchLabel(midiPitch, centsOffset, useFlats);
00140 }
00141 
00142 QString
00143 Pitch::getLabelForPitchRange(int semis, float cents)
00144 {
00145     if (semis > 0) {
00146         while (cents < 0.f) {
00147             --semis;
00148             cents += 100.f;
00149         }
00150     }
00151     if (semis < 0) {
00152         while (cents > 0.f) {
00153             ++semis;
00154             cents -= 100.f;
00155         }
00156     }
00157 
00158     int ic = lrintf(cents);
00159 
00160     if (ic == 0) {
00161         if (semis >= 12) {
00162             return QString("%1'%2").arg(semis/12).arg(semis - 12*(semis/12));
00163         } else {
00164             return QString("%1").arg(semis);
00165         }
00166     } else {
00167         if (ic > 0) {
00168             if (semis >= 12) {
00169                 return QString("%1'%2+%3c").arg(semis/12).arg(semis - 12*(semis/12)).arg(ic);
00170             } else {
00171                 return QString("%1+%3c").arg(semis).arg(ic);
00172             }
00173         } else {
00174             if (semis >= 12) {
00175                 return QString("%1'%2%3c").arg(semis/12).arg(semis - 12*(semis/12)).arg(ic);
00176             } else {
00177                 return QString("%1%3c").arg(semis).arg(ic);
00178             }
00179         }
00180     }
00181 }
00182 
00183 bool
00184 Pitch::isFrequencyInMidiRange(float frequency,
00185                               float concertA)
00186 {
00187     float centsOffset = 0.0;
00188     int midiPitch = getPitchForFrequency(frequency, &centsOffset, concertA);
00189     return (midiPitch >= 0 && midiPitch < 128);
00190 }
00191