1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 3 /* 4 Sonic Visualiser 5 An audio file viewer and annotation editor. 6 Centre for Digital Music, Queen Mary, University of London. 7 This file copyright 2006 Chris Cannam. 8 9 This program is free software; you can redistribute it and/or 10 modify it under the terms of the GNU General Public License as 11 published by the Free Software Foundation; either version 2 of the 12 License, or (at your option) any later version. See the file 13 COPYING included with this distribution for more information. 14 */ 15 16 #include "Pitch.h" 17 #include "Preferences.h" 18 #include "system/System.h" 19 20 #include <cmath> 21 22 double 23 Pitch::getFrequencyForPitch(int midiPitch, 24 double centsOffset, 25 double concertA) 26 { 27 if (concertA <= 0.0) { 28 concertA = Preferences::getInstance()->getTuningFrequency(); 29 } 30 double p = double(midiPitch) + (centsOffset / 100); 31 return concertA * pow(2.0, (p - 69.0) / 12.0); 32 } 33 34 int 35 Pitch::getPitchForFrequency(double frequency, 36 double *centsOffsetReturn, 37 double concertA) 38 { 39 if (concertA <= 0.0) { 40 concertA = Preferences::getInstance()->getTuningFrequency(); 41 } 42 double p = 12.0 * (log(frequency / (concertA / 2.0)) / log(2.0)) + 57.0; 43 44 int midiPitch = int(round(p)); 45 double centsOffset = (p - midiPitch) * 100.0; 46 47 if (centsOffset >= 50.0) { 48 midiPitch = midiPitch + 1; 49 centsOffset = -(100.0 - centsOffset); 50 } 51 if (centsOffset < -50.0) { 52 midiPitch = midiPitch - 1; 53 centsOffset = (100.0 + centsOffset); 54 } 55 56 if (centsOffsetReturn) *centsOffsetReturn = centsOffset; 57 return midiPitch; 58 } 59 60 int 61 Pitch::getPitchForFrequencyDifference(double frequencyA, 62 double frequencyB, 63 double *centsOffsetReturn, 64 double concertA) 65 { 66 if (concertA <= 0.0) { 67 concertA = Preferences::getInstance()->getTuningFrequency(); 68 } 69 70 if (frequencyA > frequencyB) { 71 std::swap(frequencyA, frequencyB); 72 } 73 74 double pA = 12.0 * (log(frequencyA / (concertA / 2.0)) / log(2.0)) + 57.0; 75 double pB = 12.0 * (log(frequencyB / (concertA / 2.0)) / log(2.0)) + 57.0; 76 77 double p = pB - pA; 78 79 int midiPitch = int(p + 0.00001); 80 double centsOffset = (p - midiPitch) * 100.0; 81 82 if (centsOffset >= 50.0) { 83 midiPitch = midiPitch + 1; 84 centsOffset = -(100.0 - centsOffset); 85 } 86 87 if (centsOffsetReturn) *centsOffsetReturn = centsOffset; 88 return midiPitch; 89 } 90 91 static QString notes[] = { 92 "C%1", "C#%1", "D%1", "D#%1", 93 "E%1", "F%1", "F#%1", "G%1", 94 "G#%1", "A%1", "A#%1", "B%1" 95 }; 96 97 static QString flatNotes[] = { 98 "C%1", "Db%1", "D%1", "Eb%1", 99 "E%1", "F%1", "Gb%1", "G%1", 100 "Ab%1", "A%1", "Bb%1", "B%1" 101 }; 102 103 int 104 Pitch::getPitchForNoteAndOctave(int note, int octave) 105 { 106 int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote(); 107 return (octave - baseOctave) * 12 + note; 108 } 109 110 void 111 Pitch::getNoteAndOctaveForPitch(int midiPitch, int ¬e, int &octave) 112 { 113 int baseOctave = Preferences::getInstance()->getOctaveOfLowestMIDINote(); 114 115 octave = baseOctave; 116 117 // Note, this only gets the right octave number at octave 118 // boundaries because Cb is enharmonic with B (not B#) and B# is 119 // enharmonic with C (not Cb). So neither B# nor Cb will be 120 // spelled from a MIDI pitch + flats flag in isolation. 121 122 if (midiPitch < 0) { 123 while (midiPitch < 0) { 124 midiPitch += 12; 125 --octave; 126 } 127 } else { 128 octave = midiPitch / 12 + baseOctave; 129 } 130 131 note = midiPitch % 12; 132 } 133 134 QString 135 Pitch::getPitchLabel(int midiPitch, 136 double centsOffset, 137 bool useFlats) 138 { 139 int note, octave; 140 getNoteAndOctaveForPitch(midiPitch, note, octave); 141 142 QString plain = (useFlats ? flatNotes : notes)[note].arg(octave); 143 144 long ic = lrint(centsOffset); 145 if (ic == 0) return plain; 146 else if (ic > 0) return QString("%1+%2c").arg(plain).arg(ic); 147 else return QString("%1%2c").arg(plain).arg(ic); 148 } 149 150 QString 151 Pitch::getPitchLabelForFrequency(double frequency, 152 double concertA, 153 bool useFlats) 154 { 155 if (concertA <= 0.0) { 156 concertA = Preferences::getInstance()->getTuningFrequency(); 157 } 158 double centsOffset = 0.0; 159 int midiPitch = getPitchForFrequency(frequency, ¢sOffset, concertA); 160 return getPitchLabel(midiPitch, centsOffset, useFlats); 161 } 162 163 QString 164 Pitch::getLabelForPitchRange(int semis, double cents) 165 { 166 if (semis > 0) { 167 while (cents < 0.0) { 168 --semis; 169 cents += 100.0; 170 } 171 } 172 if (semis < 0) { 173 while (cents > 0.0) { 174 ++semis; 175 cents -= 100.0; 176 } 177 } 178 179 long ic = lrint(cents); 180 181 if (ic == 0) { 182 if (semis >= 12) { 183 return QString("%1'%2").arg(semis/12).arg(semis - 12*(semis/12)); 184 } else { 185 return QString("%1").arg(semis); 186 } 187 } else { 188 if (ic > 0) { 189 if (semis >= 12) { 190 return QString("%1'%2+%3c").arg(semis/12).arg(semis - 12*(semis/12)).arg(ic); 191 } else { 192 return QString("%1+%3c").arg(semis).arg(ic); 193 } 194 } else { 195 if (semis >= 12) { 196 return QString("%1'%2%3c").arg(semis/12).arg(semis - 12*(semis/12)).arg(ic); 197 } else { 198 return QString("%1%3c").arg(semis).arg(ic); 199 } 200 } 201 } 202 } 203 204 bool 205 Pitch::isFrequencyInMidiRange(double frequency, 206 double concertA) 207 { 208 double centsOffset = 0.0; 209 int midiPitch = getPitchForFrequency(frequency, ¢sOffset, concertA); 210 return (midiPitch >= 0 && midiPitch < 128); 211 } 212 213