annotate base/Pitch.cpp @ 1752:6d09d68165a4 by-id

Further review of ById: make IDs only available when adding a model to the ById store, not by querying the item directly. This means any id encountered in the wild must have been added to the store at some point (even if later released), which simplifies reasoning about lifecycles
author Chris Cannam
date Fri, 05 Jul 2019 15:28:07 +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