Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : // This file contains the implementation of an pitch spelling
24 : // algorithmus from Emilios Cambouropoulos as published in:
25 : // "Automatic Pitch Spelling: From Numbers to Sharps and Flats"
26 :
27 : #include "pitchspelling.h"
28 :
29 : #include "translation.h"
30 : #include "types/typesconv.h"
31 :
32 : #include "key.h"
33 :
34 : #include "log.h"
35 :
36 : namespace mu::engraving {
37 : //---------------------------------------------------------
38 : // tpcIsValid
39 : //---------------------------------------------------------
40 :
41 338278 : bool tpcIsValid(int val)
42 : {
43 338278 : return val >= Tpc::TPC_MIN && val <= Tpc::TPC_MAX;
44 : }
45 :
46 : //---------------------------------------------------------
47 : // step2tpc
48 : //---------------------------------------------------------
49 :
50 11292 : int step2tpc(int step, AccidentalVal alter)
51 : {
52 : // TPC - tonal pitch classes
53 : // "line of fifth's" LOF
54 :
55 : static const int spellings[] = {
56 : // bbb bb b - # ## ###
57 : -7, 0, 7, 14, 21, 28, 35, // C
58 : -5, 2, 9, 16, 23, 30, 37, // D
59 : -3, 4, 11, 18, 25, 32, 39, // E
60 : -8, -1, 6, 13, 20, 27, 34, // F
61 : -6, 1, 8, 15, 22, 29, 36, // G
62 : -4, 3, 10, 17, 24, 31, 38, // A
63 : -2, 5, 12, 19, 26, 33, 40, // B
64 : };
65 :
66 11292 : int i = (step * TPCS_PER_STEP) + (int(alter) - int(AccidentalVal::MIN));
67 11292 : assert(i >= 0 && (i < int(sizeof(spellings) / sizeof(*spellings))));
68 11292 : return spellings[i];
69 : }
70 :
71 : static const int tpcByStepAndKey[int(Key::NUM_OF)][STEP_DELTA_OCTAVE] = {
72 : // step C D E F G A B Key
73 : { Tpc::TPC_C_B, Tpc::TPC_D_B, Tpc::TPC_E_B, Tpc::TPC_F_B, Tpc::TPC_G_B, Tpc::TPC_A_B, Tpc::TPC_B_B }, // Cb
74 : { Tpc::TPC_C_B, Tpc::TPC_D_B, Tpc::TPC_E_B, Tpc::TPC_F, Tpc::TPC_G_B, Tpc::TPC_A_B, Tpc::TPC_B_B }, // Gb
75 : { Tpc::TPC_C, Tpc::TPC_D_B, Tpc::TPC_E_B, Tpc::TPC_F, TPC_G_B, Tpc::TPC_A_B, Tpc::TPC_B_B }, // Db
76 : { Tpc::TPC_C, Tpc::TPC_D_B, Tpc::TPC_E_B, Tpc::TPC_F, Tpc::TPC_G, Tpc::TPC_A_B, Tpc::TPC_B_B }, // Ab
77 : { Tpc::TPC_C, Tpc::TPC_D, Tpc::TPC_E_B, Tpc::TPC_F, Tpc::TPC_G, Tpc::TPC_A_B, Tpc::TPC_B_B }, // Eb
78 : { Tpc::TPC_C, Tpc::TPC_D, Tpc::TPC_E_B, Tpc::TPC_F, Tpc::TPC_G, Tpc::TPC_A, Tpc::TPC_B_B }, // B
79 : { Tpc::TPC_C, Tpc::TPC_D, Tpc::TPC_E, Tpc::TPC_F, Tpc::TPC_G, Tpc::TPC_A, Tpc::TPC_B_B }, // F
80 : { Tpc::TPC_C, Tpc::TPC_D, Tpc::TPC_E, Tpc::TPC_F, Tpc::TPC_G, Tpc::TPC_A, Tpc::TPC_B }, // C
81 : { Tpc::TPC_C, Tpc::TPC_D, Tpc::TPC_E, Tpc::TPC_F_S, Tpc::TPC_G, Tpc::TPC_A, Tpc::TPC_B }, // G
82 : { Tpc::TPC_C_S, Tpc::TPC_D, Tpc::TPC_E, Tpc::TPC_F_S, Tpc::TPC_G, Tpc::TPC_A, Tpc::TPC_B }, // D
83 : { Tpc::TPC_C_S, Tpc::TPC_D, Tpc::TPC_E, Tpc::TPC_F_S, Tpc::TPC_G_S, Tpc::TPC_A, Tpc::TPC_B }, // A
84 : { Tpc::TPC_C_S, Tpc::TPC_D_S, Tpc::TPC_E, Tpc::TPC_F_S, Tpc::TPC_G_S, Tpc::TPC_A, Tpc::TPC_B }, // E
85 : { Tpc::TPC_C_S, Tpc::TPC_D_S, Tpc::TPC_E, Tpc::TPC_F_S, Tpc::TPC_G_S, Tpc::TPC_A_S, Tpc::TPC_B }, // H
86 : { Tpc::TPC_C_S, Tpc::TPC_D_S, Tpc::TPC_E_S, Tpc::TPC_F_S, Tpc::TPC_G_S, Tpc::TPC_A_S, Tpc::TPC_B }, // F#
87 : { Tpc::TPC_C_S, Tpc::TPC_D_S, Tpc::TPC_E_S, Tpc::TPC_F_S, Tpc::TPC_G_S, Tpc::TPC_A_S, Tpc::TPC_B_S }, // C#
88 : };
89 :
90 662 : int step2tpcByKey(int step, Key key)
91 : {
92 662 : while (step < 0) {
93 0 : step += STEP_DELTA_OCTAVE;
94 : }
95 :
96 662 : return tpcByStepAndKey[int(clampKey(key)) - int(Key::MIN)][step % STEP_DELTA_OCTAVE];
97 : }
98 :
99 : //---------------------------------------------------------
100 : // tpc2step
101 : //---------------------------------------------------------
102 :
103 220210 : int tpc2step(int tpc)
104 : {
105 : // 14 - C
106 : // 15 % 7 = 1
107 : // f c g d a e b
108 : static const int steps[STEP_DELTA_OCTAVE] = { 3, 0, 4, 1, 5, 2, 6 };
109 : // TODO: optimize -TCP_MIN
110 220210 : return steps[(tpc - Tpc::TPC_MIN) % STEP_DELTA_OCTAVE];
111 : // without a table, could also be rendered as:
112 : // return ((tpc-Tpc::TPC_MIN) * STEP_DELTA_TPC) / STEP_DELTA_OCTAVE + TPC_FIRST_STEP;
113 : }
114 :
115 : //---------------------------------------------------------
116 : // tpc2stepByKey
117 : //---------------------------------------------------------
118 :
119 0 : int tpc2stepByKey(int tpc, Key key, int& alter)
120 : {
121 0 : alter = tpc2alterByKey(tpc, key);
122 0 : return tpc2step(tpc);
123 : }
124 :
125 : //---------------------------------------------------------
126 : // step2tpc
127 : //---------------------------------------------------------
128 :
129 71 : int step2tpc(const String& stepName, AccidentalVal alter)
130 : {
131 71 : if (stepName.isEmpty()) {
132 0 : return Tpc::TPC_INVALID;
133 : }
134 : int r;
135 71 : switch (stepName.at(0).toLower().toAscii()) {
136 42 : case 'c': r = 0;
137 42 : break;
138 1 : case 'd': r = 1;
139 1 : break;
140 3 : case 'e': r = 2;
141 3 : break;
142 6 : case 'f': r = 3;
143 6 : break;
144 6 : case 'g': r = 4;
145 6 : break;
146 8 : case 'a': r = 5;
147 8 : break;
148 5 : case 'b': r = 6;
149 5 : break;
150 0 : default:
151 0 : return Tpc::TPC_INVALID;
152 : }
153 71 : return step2tpc(r, alter);
154 : }
155 :
156 : //---------------------------------------------------------
157 : // step2deltaPitchByKey
158 : //
159 : // returns the delta pitch above natural C for the given step in the given key
160 : // step: 0 - 6
161 : // key: -7 - +7
162 : //---------------------------------------------------------
163 :
164 : static const int pitchByStepAndKey[int(Key::NUM_OF)][STEP_DELTA_OCTAVE] = {
165 : // step C D E F G A B Key
166 : { -1, 1, 3, 4, 6, 8, 10 }, // Cb
167 : { -1, 1, 3, 5, 6, 8, 10 }, // Gb
168 : { 0, 1, 3, 5, 6, 8, 10 }, // Db
169 : { 0, 1, 3, 5, 7, 8, 10 }, // Ab
170 : { 0, 2, 3, 5, 7, 8, 10 }, // Eb
171 : { 0, 2, 3, 5, 7, 9, 10 }, // B
172 : { 0, 2, 4, 5, 7, 9, 10 }, // F
173 : { 0, 2, 4, 5, 7, 9, 11 }, // C
174 : { 0, 2, 4, 6, 7, 9, 11 }, // G
175 : { 1, 2, 4, 6, 7, 9, 11 }, // D
176 : { 1, 2, 4, 6, 8, 9, 11 }, // A
177 : { 1, 3, 4, 6, 8, 9, 11 }, // E
178 : { 1, 3, 4, 6, 8, 10, 11 }, // H
179 : { 1, 3, 5, 6, 8, 10, 11 }, // F#
180 : { 1, 3, 5, 6, 8, 10, 12 }, // C#
181 : };
182 :
183 662 : int step2deltaPitchByKey(int step, Key key)
184 : {
185 662 : while (step < MIN_STEP) {
186 0 : step += STEP_DELTA_OCTAVE;
187 : }
188 662 : return pitchByStepAndKey[int(clampKey(key)) - int(Key::MIN)][step % STEP_DELTA_OCTAVE];
189 : }
190 :
191 : //---------------------------------------------------------
192 : // tpc2pitch
193 : //---------------------------------------------------------
194 :
195 205666 : int tpc2pitch(int tpc)
196 : {
197 205666 : assert(tpcIsValid(tpc));
198 :
199 : static int pitches[] = {
200 : //step: F C G D A E B
201 : 2, -3, 4, -1, 6, 1, 8, // bbb
202 : 3, -2, 5, 0, 7, 2, 9, // bb
203 : 4, -1, 6, 1, 8, 3, 10, // b
204 : 5, 0, 7, 2, 9, 4, 11, // -
205 : 6, 1, 8, 3, 10, 5, 12, // #
206 : 7, 2, 9, 4, 11, 6, 13, // ##
207 : 8, 3, 10, 5, 12, 7, 14 // ###
208 : };
209 205666 : return pitches[tpc - Tpc::TPC_MIN];
210 : }
211 :
212 : //---------------------------------------------------------
213 : // tpc2alterByKey
214 : //
215 : // returns the alteration (-3 to 3) of a given tpc in the given key
216 : // to understand the formula:
217 : // in the highest key (C#Maj), each of the first 7 tpcs (Fbb to Bbb; tpc-Tpc::TPC_MIN: 0 to 7)
218 : // is 3 semitones below its key degree (alter = -3)
219 : // the second 7 tpcs (Fb to Bb; tpc-Tpc::TPC_MIN: 8 to 13) are 2 semitones below (alter = -2) and so on up to 1
220 : // thus, for C#Maj:
221 : // (1) (tpc-Tpc::TPC_MIN) - 0 = 0 to 34 (for tcp-TCP_MIN from 0 to 34)
222 : // (2) (tpc-Tpc::TPC_MIN) - 0) / 7 = 0 to 4 (for each settuple of tcp's) and finally
223 : // (3) (tcp-Tpc::TPC_MIN) - 0) / 7 -3 = -3 to 1 (for each settuple of tcp's)
224 : // where 0 = Key::C_S - Key::MAX
225 : // for each previous key, the result of (1) increases by 1 and the classes of alter are shifted 1 TPC 'up':
226 : // F#Maj: Fbb-Ebb => -3, Bbb to Eb => -2 and so on
227 : // BMaj: Fbb-Abb => -3, Ebb to Ab => -2 and so on
228 : // and so on
229 : // thus, for any 'key', the formula is:
230 : // ((tcp-Tpc::TPC_MIN) - (key-Key::MAX)) / TCP_DELTA_SEMITONE - 3
231 : //---------------------------------------------------------
232 :
233 7526 : int tpc2alterByKey(int tpc, Key key)
234 : {
235 7526 : return (tpc - int(key) - int(Tpc::TPC_MIN) + int(Key::MAX)) / TPC_DELTA_SEMITONE - (int(AccidentalVal::MAX) + 1);
236 : }
237 :
238 : //---------------------------------------------------------
239 : // tpc2name
240 : // return note name
241 : //---------------------------------------------------------
242 :
243 970 : String tpc2name(int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase, bool explicitAccidental, bool full)
244 : {
245 970 : String s;
246 970 : String acc;
247 970 : tpc2name(tpc, noteSpelling, noteCase, s, acc, explicitAccidental, full);
248 1940 : return s + (explicitAccidental ? u" " : u"") + acc;
249 970 : }
250 :
251 : //---------------------------------------------------------
252 : // tpc2name
253 : //---------------------------------------------------------
254 :
255 7324 : void tpc2name(int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase, String& s, String& acc, bool explicitAccidental, bool full)
256 : {
257 : AccidentalVal accVal;
258 7324 : tpc2name(tpc, noteSpelling, noteCase, s, accVal);
259 7324 : switch (accVal) {
260 0 : case AccidentalVal::FLAT3:
261 0 : if (explicitAccidental) {
262 0 : acc = muse::mtrc("engraving", TConv::userName(AccidentalVal::FLAT3, full));
263 0 : } else if (noteSpelling == NoteSpellingType::GERMAN_PURE) {
264 0 : switch (tpc) {
265 0 : case TPC_A_BBB: acc = u"sasas";
266 0 : break;
267 0 : case TPC_E_BBB: acc = u"seses";
268 0 : break;
269 0 : default: acc = u"eseses";
270 : }
271 : } else {
272 0 : acc = u"bbb";
273 : }
274 0 : break;
275 5 : case AccidentalVal::FLAT2:
276 5 : if (explicitAccidental) {
277 0 : acc = muse::mtrc("engraving", TConv::userName(AccidentalVal::FLAT2, full));
278 5 : } else if (noteSpelling == NoteSpellingType::GERMAN_PURE) {
279 0 : switch (tpc) {
280 0 : case TPC_A_BB: acc = u"sas";
281 0 : break;
282 0 : case TPC_E_BB: acc = u"ses";
283 0 : break;
284 0 : default: acc = u"eses";
285 : }
286 : } else {
287 5 : acc = u"bb";
288 : }
289 5 : break;
290 435 : case AccidentalVal::FLAT:
291 435 : if (explicitAccidental) {
292 0 : acc = muse::mtrc("engraving", TConv::userName(AccidentalVal::FLAT, full));
293 435 : } else if (noteSpelling == NoteSpellingType::GERMAN_PURE) {
294 0 : acc = (tpc == TPC_A_B || tpc == TPC_E_B) ? u"s" : u"es";
295 : } else {
296 435 : acc = u"b";
297 : }
298 435 : break;
299 5797 : case AccidentalVal::NATURAL: acc = u"";
300 5797 : break;
301 1083 : case AccidentalVal::SHARP:
302 1083 : if (explicitAccidental) {
303 0 : acc = muse::mtrc("engraving", TConv::userName(AccidentalVal::SHARP, full));
304 : } else {
305 1083 : acc = (noteSpelling == NoteSpellingType::GERMAN_PURE) ? u"is" : u"#";
306 : }
307 1083 : break;
308 4 : case AccidentalVal::SHARP2:
309 4 : if (explicitAccidental) {
310 0 : acc = muse::mtrc("engraving", TConv::userName(AccidentalVal::SHARP2, full));
311 : } else {
312 4 : acc = (noteSpelling == NoteSpellingType::GERMAN_PURE) ? u"isis" : u"##";
313 : }
314 4 : break;
315 0 : case AccidentalVal::SHARP3:
316 0 : if (explicitAccidental) {
317 0 : acc = muse::mtrc("engraving", TConv::userName(AccidentalVal::SHARP3, full));
318 : } else {
319 0 : acc = (noteSpelling == NoteSpellingType::GERMAN_PURE) ? u"isisis" : u"###";
320 : }
321 0 : break;
322 0 : default:
323 0 : LOGD("tpc2name(%d): acc %d", tpc, static_cast<int>(accVal));
324 0 : acc = u"";
325 0 : break;
326 : }
327 7324 : }
328 :
329 : //---------------------------------------------------------
330 : // tpc2name
331 : //---------------------------------------------------------
332 :
333 10127 : void tpc2name(int tpc, NoteSpellingType noteSpelling, NoteCaseType noteCase, String& s, AccidentalVal& acc)
334 : {
335 10127 : const char names[] = "FCGDAEB";
336 10127 : const char gnames[] = "FCGDAEH";
337 91143 : const String snames[] = { u"Fa", u"Do", u"Sol", u"Re", u"La", u"Mi", u"Si" };
338 :
339 10127 : acc = tpc2alter(tpc);
340 10127 : int idx = (tpc - Tpc::TPC_MIN) % TPC_DELTA_SEMITONE;
341 10127 : switch (noteSpelling) {
342 0 : case NoteSpellingType::GERMAN:
343 : case NoteSpellingType::GERMAN_PURE:
344 0 : s = String(Char::fromAscii(gnames[idx]));
345 0 : if (s == "H" && acc == AccidentalVal::FLAT) {
346 0 : s = u"B";
347 0 : if (noteSpelling == NoteSpellingType::GERMAN_PURE) {
348 0 : acc = AccidentalVal::NATURAL;
349 : }
350 : }
351 0 : break;
352 30 : case NoteSpellingType::SOLFEGGIO:
353 30 : s = snames[idx];
354 30 : break;
355 0 : case NoteSpellingType::FRENCH:
356 0 : s = snames[idx];
357 0 : if (s == u"Re") {
358 0 : s = u"Ré";
359 : }
360 0 : break;
361 10097 : default:
362 10097 : s = String(Char::fromAscii(names[idx]));
363 10097 : break;
364 : }
365 10127 : switch (noteCase) {
366 51 : case NoteCaseType::LOWER: s = s.toLower();
367 51 : break;
368 3551 : case NoteCaseType::UPPER: s = s.toUpper();
369 3551 : break;
370 6525 : case NoteCaseType::CAPITAL:
371 : case NoteCaseType::AUTO:
372 : default:
373 6525 : break;
374 : }
375 91143 : }
376 :
377 : //---------------------------------------------------------
378 : // tpc2stepName
379 : //---------------------------------------------------------
380 :
381 4610 : Char tpc2stepName(int tpc)
382 : {
383 4610 : static const String names = u"FCGDAEB";
384 4610 : return names.at((tpc - Tpc::TPC_MIN) % 7);
385 : }
386 :
387 0 : String tpcUserName(int tpc, int pitch, bool explicitAccidental, bool full)
388 : {
389 0 : String pitchStr = tpc2name(tpc, NoteSpellingType::STANDARD, NoteCaseType::AUTO, explicitAccidental, full);
390 0 : if (!explicitAccidental) {
391 0 : pitchStr.replace(u"b", u"♭");
392 0 : pitchStr.replace(u"#", u"♯");
393 : }
394 :
395 0 : const String octaveStr = String::number(((pitch - static_cast<int>(tpc2alter(tpc))) / PITCH_DELTA_OCTAVE) - 1);
396 :
397 0 : return pitchStr + octaveStr;
398 0 : }
399 :
400 : //---------------------------------------------------------
401 : // pitch2tpc2
402 : //---------------------------------------------------------
403 :
404 : // pitch2tpc2(pitch, false) replaced by pitch2tpc(pitch, Key::C, Prefer::FLATS)
405 : // pitch2tpc2(pitch, true) replaced by pitch2tpc(pitch, Key::C, Prefer::SHARPS)
406 :
407 : //---------------------------------------------------------
408 : // pitch2tpc
409 : // preferred pitch spelling depending on key
410 : // key -7 to +7
411 : //
412 : // The value of prefer sets the preferred mix of flats and sharps
413 : // for pitches that are non-diatonic in the key specified, by
414 : // positioning the window along the tpc sequence.
415 : //
416 : // Scale tones are the range shown in [ ].
417 : // A value of 8 (Prefer::FLATS) specifies 5b 2b 6b 3b 7b [4 1 5 2 6 3 7]
418 : // A value of 11 (Prefer::NEAREST) specifies 3b 7b [4 1 5 2 6 3 7] 4# 1# 5#
419 : // A value of 13 (Prefer::SHARPS) specifies [4 1 5 2 6 3 7] 4# 1# 5# 2# 6#
420 : //
421 : // Examples for Prefer::NEAREST (n indicates explicit natural):
422 : // C major will use Eb Bb [F C G D A E B] F# C# G#.
423 : // E major will use Gn Dn [A E B F# C# G# D#] A# E# B#.
424 : // F# major will use An En [B F# C# G# D# A# E#] B# Fx Cx.
425 : // Eb major will use Gb Db [Ab Eb Bb F C G D] An En Bn.
426 : // Gb major will use Bbb Fb [Cb Gb Db Ab Eb Bb F] Cn Gn Dn.
427 : //---------------------------------------------------------
428 :
429 6894 : int pitch2tpc(int pitch, Key key, Prefer prefer)
430 : {
431 6894 : return (pitch * 7 + 26 - (int(prefer) + int(key))) % 12 + (int(prefer) + int(key));
432 : }
433 :
434 : //---------------------------------------------------------
435 : // pitch2absStepByKey
436 : // absolute step (C0 = 0, D0 = 1, ... C1 = 7, D2 = 8, ...) for a pitch/tpc according to key
437 : // if pAlter not null, returns in it the alteration with respect to the corresponding key degree (-3 to 3)
438 : // (for instance, an F in GMaj yields alteration -1 i.e. 1 semitone below corresp. deg. of GMaj which is F#)
439 : // key: between Key::MIN and Key::MAX
440 : //---------------------------------------------------------
441 :
442 662 : int pitch2absStepByKey(int pitch, int tpc, Key key, int& alter)
443 : {
444 : // sanitize input data
445 662 : pitch = clampPitchOctaved(pitch);
446 662 : tpc = clampEnharmonic(tpc);
447 662 : key = clampKey(key);
448 :
449 662 : int octave = (pitch - int(tpc2alter(tpc))) / PITCH_DELTA_OCTAVE;
450 662 : int step = tpc2step(tpc);
451 662 : alter = tpc2alterByKey(tpc, key);
452 662 : return octave * STEP_DELTA_OCTAVE + step;
453 : }
454 :
455 : //---------------------------------------------------------
456 : // absStep2pitchByKey
457 : // the default pitch for the given absolute step in the given key
458 : //---------------------------------------------------------
459 :
460 662 : int absStep2pitchByKey(int step, Key key)
461 : {
462 : // sanitize input data
463 662 : if (step < MIN_STEP) {
464 0 : step += STEP_DELTA_OCTAVE;
465 : }
466 662 : if (step > MAX_STEP) {
467 0 : step -= STEP_DELTA_OCTAVE;
468 : }
469 662 : key = clampKey(key);
470 :
471 662 : int octave = step / STEP_DELTA_OCTAVE;
472 662 : int deltaPitch = step2deltaPitchByKey(step % STEP_DELTA_OCTAVE, key);
473 662 : return octave * PITCH_DELTA_OCTAVE + deltaPitch;
474 : }
475 :
476 : //---------------------------------------------------------
477 : // tpc2degree
478 : // the scale degree of a TPC for a given Key
479 : //---------------------------------------------------------
480 :
481 620 : int tpc2degree(int tpc, Key key)
482 : {
483 620 : static const String names(u"CDEFGAB");
484 620 : static const String scales(u"CGDAEBFCGDAEBFC");
485 620 : Char scale = scales.at(int(key) + 7);
486 620 : Char stepName = tpc2stepName(tpc);
487 1240 : return (names.indexOf(stepName) - names.indexOf(scale) + 28) % 7;
488 : }
489 :
490 : //---------------------------------------------------------
491 : // tpcInterval
492 : /// Finds tpc of a note based on an altered interval
493 : /// from a starting note
494 : //---------------------------------------------------------
495 :
496 1859 : int tpcInterval(int startTpc, int interval, int alter)
497 : {
498 1859 : assert(interval > 0);
499 : static const int intervals[7] = {
500 : // 1 2 3 4 5 6 7
501 : 0, 2, 4, -1, 1, 3, 5
502 : };
503 :
504 : //ensure that we don't have anything more than double sharp or double flat
505 : //(I know, breaking some convention, but it's the best we can do for now)
506 1859 : return clampEnharmonic(startTpc + intervals[(interval - 1) % 7] + alter * TPC_DELTA_SEMITONE);
507 : }
508 :
509 : //---------------------------------------------------------
510 : // step2pitchInterval
511 : /// Finds pitch between notes a specified altered interval away
512 : ///
513 : /// For example:
514 : /// step = 3, alter = 0 means major 3rd
515 : /// step = 5, alter = -1 means diminished 5
516 : /// step = 6, alter = 2 means augmented sixth
517 : //---------------------------------------------------------
518 :
519 1166 : int step2pitchInterval(int step, int alter)
520 : {
521 1166 : assert(step > 0);
522 : static const int intervals[7] = {
523 : // 1 2 3 4 5 6 7
524 : 0, 2, 4, 5, 7, 9, 11
525 : };
526 :
527 1166 : return intervals[(step - 1) % 7] + alter;
528 : }
529 :
530 232 : void tpc2Function(int tpc, Key key, String& accName, String& stepName)
531 : {
532 232 : if (key == Key::INVALID) {
533 0 : LOGD() << "Invalid key";
534 0 : return;
535 : }
536 232 : int step = tpc2degree(tpc, key);
537 232 : static const String stepNames = u"1234567";
538 232 : assert(step < (int)stepNames.size());
539 232 : stepName = stepNames.at(step);
540 :
541 232 : int alter = tpc2alterByKey(tpc, key);
542 232 : int accidentalNo = std::abs(alter);
543 232 : accName = String();
544 232 : bool flat = alter < 0;
545 342 : for (int i = 0; i < accidentalNo; i++) {
546 110 : accName.append(flat ? u"b" : u"#");
547 : }
548 : }
549 :
550 44 : String tpc2Function(int tpc, Key key)
551 : {
552 44 : String accStr;
553 44 : String stepStr;
554 44 : tpc2Function(tpc, key, accStr, stepStr);
555 88 : return accStr + stepStr;
556 44 : }
557 :
558 : //----------------------------------------------
559 : // function2Tpc
560 : /// might be temporary, just used to parse nashville notation now
561 : ///
562 : //----------------------------------------------
563 :
564 20 : int function2Tpc(const String& s, Key key)
565 : {
566 20 : size_t idx = 0;
567 40 : return function2Tpc(s, key, idx);
568 : }
569 :
570 20 : int function2Tpc(const String& s, Key key, size_t& idx)
571 : {
572 : // TODO - PHV: allow for alternate spellings
573 20 : int alter = 0;
574 : int step;
575 :
576 20 : if (!s.isEmpty() && s.at(0).isDigit()) {
577 18 : step = s.at(0).digitValue();
578 18 : idx = 1;
579 2 : } else if (s.size() > 1) {
580 2 : constexpr int NUM_LEN = 1;
581 2 : size_t accIdx = s.size() - NUM_LEN;
582 2 : String acc = s.left(accIdx);
583 2 : String num = s.right(NUM_LEN);
584 2 : if (num.size() > NUM_LEN || num.empty() || !num.at(0).isDigit()) {
585 0 : return Tpc::TPC_INVALID;
586 : }
587 :
588 2 : step = num.at(0).digitValue();
589 2 : idx = NUM_LEN;
590 2 : if (acc.startsWith(u"bb")) {
591 0 : alter = -2;
592 0 : idx += 2;
593 2 : } else if (acc.startsWith(u"b")) {
594 2 : alter = -1;
595 2 : idx += 1;
596 0 : } else if (acc.startsWith(u"#")) {
597 0 : alter = 1;
598 0 : idx += 1;
599 0 : } else if (acc.startsWith(u"##")) {
600 0 : alter = 2;
601 0 : idx += 2;
602 : }
603 2 : } else {
604 0 : return Tpc::TPC_INVALID;
605 : }
606 :
607 20 : int keyTpc = int(key) + 14; //tpc of key (ex. F# major would be Tpc::F_S)
608 20 : int tpc = tpcInterval(keyTpc, step, alter);
609 20 : return tpc;
610 : }
611 :
612 : //---------------------------------------------------------
613 : // convertNote
614 : // convert something like "C#" into tpc 21
615 : //---------------------------------------------------------
616 9989 : int convertNote(const String& s, NoteSpellingType noteSpelling, NoteCaseType& noteCase, size_t& idx)
617 : {
618 9989 : bool useGerman = false;
619 9989 : bool useSolfeggio = false;
620 : static const int spellings[] = {
621 : // bb b - # ##
622 : 0, 7, 14, 21, 28, // C
623 : 2, 9, 16, 23, 30, // D
624 : 4, 11, 18, 25, 32, // E
625 : -1, 6, 13, 20, 27, // F
626 : 1, 8, 15, 22, 29, // G
627 : 3, 10, 17, 24, 31, // A
628 : 5, 12, 19, 26, 33, // B
629 : };
630 9989 : if (s.empty()) {
631 0 : return Tpc::TPC_INVALID;
632 : }
633 9989 : noteCase = s.at(0).isLower() ? NoteCaseType::LOWER : NoteCaseType::CAPITAL;
634 : int acci;
635 9989 : switch (noteSpelling) {
636 7 : case NoteSpellingType::SOLFEGGIO:
637 : case NoteSpellingType::FRENCH:
638 7 : useSolfeggio = true;
639 7 : if (s.startsWith(u"sol", muse::CaseInsensitive)) {
640 0 : acci = 3;
641 : } else {
642 7 : acci = 2;
643 : }
644 7 : break;
645 0 : case NoteSpellingType::GERMAN:
646 : case NoteSpellingType::GERMAN_PURE:
647 0 : useGerman = true;
648 : // fall through
649 9982 : default:
650 9982 : acci = 1;
651 : }
652 9989 : idx = acci;
653 9989 : int alter = 0;
654 9989 : size_t n = s.size();
655 19978 : String acc = s.right(n - acci);
656 9989 : if (!acc.empty()) {
657 7793 : if (acc.startsWith(u"bb")) {
658 1 : alter = -2;
659 1 : idx += 2;
660 7792 : } else if (acc.startsWith(u"b")) {
661 2675 : alter = -1;
662 2675 : idx += 1;
663 5117 : } else if (useGerman && acc.startsWith(u"eses")) {
664 0 : alter = -2;
665 0 : idx += 4;
666 5117 : } else if (useGerman && (acc.startsWith(u"ses") || acc.startsWith(u"sas"))) {
667 0 : alter = -2;
668 0 : idx += 3;
669 5117 : } else if (useGerman && acc.startsWith(u"es")) {
670 0 : alter = -1;
671 0 : idx += 2;
672 5117 : } else if (useGerman && acc.startsWith(u"s") && !acc.startsWith(u"su")) {
673 0 : alter = -1;
674 0 : idx += 1;
675 5117 : } else if (acc.startsWith(u"##")) {
676 0 : alter = 2;
677 0 : idx += 2;
678 5117 : } else if (acc.startsWith(u"x")) {
679 0 : alter = 2;
680 0 : idx += 1;
681 5117 : } else if (acc.startsWith(u"#")) {
682 1512 : alter = 1;
683 1512 : idx += 1;
684 3605 : } else if (useGerman && acc.startsWith(u"isis")) {
685 0 : alter = 2;
686 0 : idx += 4;
687 3605 : } else if (useGerman && acc.startsWith(u"is")) {
688 0 : alter = 1;
689 0 : idx += 2;
690 : }
691 : }
692 : int r;
693 9989 : if (useGerman) {
694 0 : switch (s.at(0).toLower().toAscii()) {
695 0 : case 'c': r = 0;
696 0 : break;
697 0 : case 'd': r = 1;
698 0 : break;
699 0 : case 'e': r = 2;
700 0 : break;
701 0 : case 'f': r = 3;
702 0 : break;
703 0 : case 'g': r = 4;
704 0 : break;
705 0 : case 'a': r = 5;
706 0 : break;
707 0 : case 'h': r = 6;
708 0 : break;
709 0 : case 'b':
710 0 : if (alter && alter != -1) {
711 0 : return Tpc::TPC_INVALID;
712 : }
713 0 : r = 6;
714 0 : alter = -1;
715 0 : break;
716 0 : default:
717 0 : return Tpc::TPC_INVALID;
718 : }
719 9989 : } else if (useSolfeggio) {
720 7 : if (s.size() < 2) {
721 1 : return Tpc::TPC_INVALID;
722 : }
723 7 : if (s.at(1).isUpper()) {
724 1 : noteCase = NoteCaseType::UPPER;
725 : }
726 7 : String ss = s.toLower().left(2);
727 7 : if (ss == "do") {
728 0 : r = 0;
729 7 : } else if (ss == "re" || ss == "ré") {
730 0 : r = 1;
731 7 : } else if (ss == "mi") {
732 0 : r = 2;
733 7 : } else if (ss == "fa") {
734 0 : r = 3;
735 7 : } else if (ss == "so") { // sol, but only check first 2 characters
736 0 : r = 4;
737 7 : } else if (ss == "la") {
738 6 : r = 5;
739 1 : } else if (ss == "si") {
740 0 : r = 6;
741 : } else {
742 1 : return Tpc::TPC_INVALID;
743 : }
744 7 : } else {
745 9982 : switch (s.at(0).toLower().toAscii()) {
746 1343 : case 'c': r = 0;
747 1343 : break;
748 1431 : case 'd': r = 1;
749 1431 : break;
750 1423 : case 'e': r = 2;
751 1423 : break;
752 1395 : case 'f': r = 3;
753 1395 : break;
754 1504 : case 'g': r = 4;
755 1504 : break;
756 1478 : case 'a': r = 5;
757 1478 : break;
758 1386 : case 'b': r = 6;
759 1386 : break;
760 22 : default: return Tpc::TPC_INVALID;
761 : }
762 : }
763 9966 : r = spellings[r * 5 + alter + 2];
764 9966 : return r;
765 : }
766 :
767 5219 : int clampEnharmonic(int tpc, bool useDoubleSharpsFlats)
768 : {
769 5219 : while (tpc > (useDoubleSharpsFlats ? Tpc::TPC_MAX : Tpc::TPC_B_S)) {
770 0 : tpc -= TPC_DELTA_ENHARMONIC;
771 : }
772 5237 : while (tpc < (useDoubleSharpsFlats ? Tpc::TPC_MIN : Tpc::TPC_F_B)) {
773 18 : tpc += TPC_DELTA_ENHARMONIC;
774 : }
775 5219 : return tpc;
776 : }
777 :
778 20337 : int clampPitch(int pitch)
779 : {
780 20337 : return std::clamp(pitch, MIN_PITCH, MAX_PITCH);
781 : }
782 :
783 1384 : int clampPitchOctaved(int pitch)
784 : {
785 1384 : while (pitch > MAX_PITCH) {
786 0 : pitch -= PITCH_DELTA_OCTAVE;
787 : }
788 1384 : while (pitch < MIN_PITCH) {
789 0 : pitch += PITCH_DELTA_OCTAVE;
790 : }
791 1384 : return pitch;
792 : }
793 :
794 6812 : Key clampKey(Key key, PreferSharpFlat prefer)
795 : {
796 6812 : Key smallest = Key::G_B;
797 6812 : Key largest = Key::F_S;
798 :
799 6812 : if (prefer != PreferSharpFlat::AUTO) {
800 6007 : smallest = (prefer == PreferSharpFlat::SHARPS) ? Key::A_B : Key::MIN;
801 6007 : largest = (prefer == PreferSharpFlat::FLATS) ? Key::E : Key::MAX;
802 : }
803 :
804 6830 : while (key < smallest) {
805 18 : key += Key::DELTA_ENHARMONIC;
806 : }
807 6874 : while (key > largest) {
808 62 : key -= Key::DELTA_ENHARMONIC;
809 : }
810 :
811 6812 : return key;
812 : }
813 :
814 4164 : int key2Tpc(Key key)
815 : {
816 4164 : return (int)key + KEY_TO_TPC_OFFSET;
817 : }
818 :
819 4164 : Key tpc2Key(int tpc)
820 : {
821 4164 : return Key(tpc - KEY_TO_TPC_OFFSET);
822 : }
823 : }
|