LCOV - code coverage report
Current view: top level - src/engraving/dom - pitchspelling.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 67.2 % 384 258
Test Date: 2026-11-05 04:07:02

            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              : }
        

Generated by: LCOV version 2.0-1