annotate base/Pitch.cpp @ 1459:3a128665fa6f horizontal-scale

Fixes to logarithmic scale tick intervals. The approach here is not right, though -- and I've left in a failing test or two to remind me of that
author Chris Cannam
date Wed, 02 May 2018 14:17:10 +0100
parents 48e9f538e6e9
children
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@1429 24 double centsOffset,
Chris@1429 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@1429 36 double *centsOffsetReturn,
Chris@1429 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@1429 48 midiPitch = midiPitch + 1;
Chris@1429 49 centsOffset = -(100.0 - centsOffset);
Chris@19 50 }
Chris@1025 51 if (centsOffset < -50.0) {
Chris@1429 52 midiPitch = midiPitch - 1;
Chris@1429 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@1429 83 midiPitch = midiPitch + 1;
Chris@1429 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@1429 123 while (midiPitch < 0) {
Chris@1429 124 midiPitch += 12;
Chris@1429 125 --octave;
Chris@1429 126 }
Chris@19 127 } else {
Chris@1429 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@1429 136 double centsOffset,
Chris@1429 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@1429 152 double concertA,
Chris@1429 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