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 &note, 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, &centsOffset, 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, &centsOffset, concertA);
210     return (midiPitch >= 0 && midiPitch < 128);
211 }
212 
213