annotate base/Pitch.cpp @ 1412:b7a9edee85e0 scale-ticks

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