1 /** 2 3 In log scale, 0 is C5, -3 is A4 (440Hz) 4 5 */ 6 7 module soundtab.ui.noteutil; 8 9 import std..string : format; 10 import std.math : exp, log, exp2, log2, floor; 11 import dlangui.core.logger; 12 13 immutable double HALF_TONE = 1.05946309435929530980; 14 immutable double QUARTER_TONE = 1.02930223664349207440; 15 16 immutable double BASE_FREQUENCY = 440.0; 17 18 /// Convert frequency to log scale, 0 = BASE_FREQUENCY (440hz + 3halftones), +1 == + half tone, +12 == + octave (880Hz), -12 == -octave (220hz) 19 double toLogScale(double freq) { 20 if (freq < 8) 21 return -6 * 12; 22 return log2(freq / BASE_FREQUENCY) * 12 - 3; 23 } 24 25 /// Convert from note (log scale) to frequency (-3 -> 440, 9 -> 880, -15 -> 220) 26 double fromLogScale(double note) { 27 return exp2((note + 3) / 12) * BASE_FREQUENCY; 28 } 29 30 /// returns index 0..11 of nearest note (0=A, 1=A#, 2=B, .. 11=G#) 31 int getNoteIndex(double note) { 32 int nn = getNearestNote(note); 33 while (nn < 0) 34 nn += 12; 35 nn %= 12; 36 return nn; 37 } 38 39 /// returns nearest int note 40 int getNearestNote(double note) { 41 return cast(int)floor(note + 0.5); 42 } 43 44 int getNoteOctave(double note) { 45 int n = getNearestNote(note); 46 return (n + 12*5) / 12; 47 } 48 49 immutable dstring[10] OCTAVE_NAMES = [ 50 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" 51 ]; 52 53 dstring getNoteOctaveName(double note) { 54 int oct = getNoteOctave(note); 55 if (oct < 0 || oct > 8) 56 return " "; 57 return OCTAVE_NAMES[oct]; 58 } 59 60 // 0 1 2 3 4 5 6 7 8 9 10 11 61 // C C# D D# E F F# G G# A A# B 62 immutable static bool[12] BLACK_NOTES = [ 63 false, // C 64 true, // C# 65 false, // D 66 true, // D# 67 false, // E 68 false, // F 69 true, // F# 70 false, // G 71 true, // G# 72 false, // A 73 true, // A# 74 false, // B 75 ]; 76 77 /// returns true for "black" - sharp note, e.g. for A#, G# 78 bool isBlackNote(double note) { 79 int nn = getNoteIndex(note); 80 return BLACK_NOTES[nn]; 81 } 82 83 /// returns true for "black" - sharp note, e.g. for A#, G# 84 bool isBlackNote(int note) { 85 return BLACK_NOTES[(note + 12*8) % 12]; 86 } 87 88 immutable dstring[12] NOTE_NAMES = [ 89 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", 90 ]; 91 92 dstring getNoteName(double n) { 93 return NOTE_NAMES[getNoteIndex(n)]; 94 } 95 96 dstring noteToFullName(double note) { 97 return getNoteName(note) ~ getNoteOctaveName(note); 98 } 99 100 dstring noteToFullName(int note) { 101 return getNoteName(note) ~ getNoteOctaveName(note); 102 } 103 104 int fullNameToNote(dstring noteName) { 105 dstring note = noteName[0 .. $ - 1]; 106 int octaveNumber = cast(int)(noteName[$ - 1] - '0'); 107 foreach(idx, name; NOTE_NAMES) { 108 if (name == note) { 109 return cast(int)((octaveNumber - 5) * 12 + idx); 110 } 111 } 112 return 0; 113 } 114 115 int whiteNotesInRange(int start, int end) { 116 start += 12*8; 117 end += 12*8; 118 int res = 0; 119 for (int i = start; i <= end; ) { 120 int idx = i % 12; 121 if (idx == 0 && i + 12 <= end) { 122 res += 7; 123 i += 12; 124 } else { 125 if (!BLACK_NOTES[idx]) 126 res++; 127 i++; 128 } 129 } 130 return res; 131 } 132 133 struct PitchCorrector { 134 private int _amount; 135 @property int amount() { 136 return _amount; 137 } 138 @property void amount(int v) { 139 if (v < 0) 140 v = 0; 141 else if (v > 1000) 142 v = 1000; 143 _amount = v; 144 } 145 double correctNote(double note) { 146 import std.math : round, pow; 147 if (_amount <= 0) 148 return note; 149 if (_amount >= 1000) { 150 return round(note); 151 } 152 double v1 = note; 153 double v2 = round(note); 154 double diff = v1 - v2; // -0.5 .. 0.5 155 double diffsgn = diff > 0 ? 0.5 : -0.5; // -0.5 or 0.5 156 double diffabs = diff > 0 ? 2 * diff : - 2 * diff; // 0 .. 1 157 double p = ((_amount / 1000.0) + 1) * 1.5; 158 diffabs = pow(diffabs, p); 159 // correct diffabs 160 return v2 + diffabs * diffsgn; 161 //return v2 + diff * (1000 - _amount) / 1000; 162 } 163 164 double correctPitch(double pitch) { 165 return fromLogScale(correctNote(toLogScale(pitch))); 166 } 167 } 168 169 void buildNoteConversionTable() { 170 char[] buf; 171 buf.assumeSafeAppend; 172 173 double baseFreq = fromLogScale(0); 174 buf ~= "\nstatic const float SEMITONE_FRAC[256] = {\n"; 175 for (int i = 0; i < 256; i++) { 176 if ((i % 8) == 0) 177 buf ~= " "; 178 double n = i / 256.0; 179 double freq = fromLogScale(n); 180 double frac = freq / baseFreq; 181 buf ~= "%.7ff,".format(frac); 182 if ((i % 8) == 7) 183 buf ~= "\n"; 184 else 185 buf ~= " "; 186 } 187 buf ~= "};\n"; 188 buf ~= "\nstatic const float NOTE_FREQ[120] = {\n"; 189 for (int i = 0; i < 120; i++) { 190 if ((i % 6) == 0) 191 buf ~= " "; 192 double n = i - 57 - 3; 193 double freq = fromLogScale(n); 194 buf ~= "%.7ff,".format(freq); 195 if ((i % 6) == 5) { 196 if (i % 12 == 5) { 197 buf ~= " // C%d..F%d".format(i / 12, i / 12); 198 } 199 if (i % 12 == 11) { 200 buf ~= " // F#%d..B%d".format(i / 12, i / 12); 201 } 202 buf ~= "\n"; 203 } else 204 buf ~= " "; 205 } 206 buf ~= "};\n"; 207 208 209 Log.d(buf); 210 }