LCOV - code coverage report
Current view: top level - src/engraving/dom - realizedharmony.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 75.9 % 278 211
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              : #include "realizedharmony.h"
      24              : 
      25              : #include "chordlist.h"
      26              : #include "harmony.h"
      27              : #include "pitchspelling.h"
      28              : #include "segment.h"
      29              : 
      30              : using namespace mu;
      31              : 
      32              : namespace mu::engraving {
      33              : //---------------------------------------------------
      34              : //   setVoicing
      35              : ///   sets the voicing and dirty flag if the passed
      36              : ///   voicing is different than current
      37              : //---------------------------------------------------
      38         1579 : void RealizedHarmony::setVoicing(Voicing v)
      39              : {
      40         1579 :     if (m_voicing == v) {
      41         1134 :         return;
      42              :     }
      43          445 :     m_voicing = v;
      44          445 :     cascadeDirty(true);
      45              : }
      46              : 
      47              : //---------------------------------------------------
      48              : //   setDuration
      49              : ///   sets the duration and dirty flag if the passed
      50              : ///   HDuration is different than current
      51              : //---------------------------------------------------
      52         1140 : void RealizedHarmony::setDuration(HDuration d)
      53              : {
      54         1140 :     if (m_duration == d) {
      55           63 :         return;
      56              :     }
      57         1077 :     m_duration = d;
      58         1077 :     cascadeDirty(true);
      59              : }
      60              : 
      61              : //---------------------------------------------------
      62              : //   setLiteral
      63              : ///   sets literal/jazz and dirty flag if the passed
      64              : ///   bool is different than current
      65              : //---------------------------------------------------
      66         1159 : void RealizedHarmony::setLiteral(bool literal)
      67              : {
      68         1159 :     if (m_literal == literal) {
      69           63 :         return;
      70              :     }
      71         1096 :     m_literal = literal;
      72         1096 :     cascadeDirty(true);
      73              : }
      74              : 
      75              : //---------------------------------------------------
      76              : //   notes
      77              : ///   returns the list of notes
      78              : //---------------------------------------------------
      79          485 : const RealizedHarmony::PitchMap& RealizedHarmony::notes() const
      80              : {
      81          485 :     assert(!m_dirty);
      82              :     //with the way that the code is currently structured, there should be no way to
      83              :     //get to this function with dirty flag set although in the future it may be
      84              :     //better to just update if dirty here
      85          485 :     return m_notes;
      86              : }
      87              : 
      88              : //---------------------------------------------------
      89              : //   generateNotes
      90              : ///   generates a note list based on the passed parameters
      91              : //---------------------------------------------------
      92          499 : const RealizedHarmony::PitchMap RealizedHarmony::generateNotes(int rootTpc, int bassTpc,
      93              :                                                                bool literal, Voicing voicing, int transposeOffset) const
      94              : {
      95              :     //cut octaves from offset
      96          499 :     transposeOffset %= PITCH_DELTA_OCTAVE;
      97              : 
      98              :     //The octave which to generate the body of the harmony, this is static const for now
      99              :     //but may be user controlled in the future
     100              :     static const int DEFAULT_OCTAVE = 5;   //octave above middle C
     101              : 
     102          499 :     PitchMap notes;
     103          499 :     int rootPitch = tpc2pitch(rootTpc) + transposeOffset;
     104              :     //we need to treat this new pitch as a pitch between 0 and 11,
     105              :     //so that voicing remains consistent across transposition
     106          499 :     if (rootPitch < 0) {
     107            3 :         rootPitch = (rootPitch % PITCH_DELTA_OCTAVE) + PITCH_DELTA_OCTAVE;
     108              :     } else {
     109          496 :         rootPitch %= PITCH_DELTA_OCTAVE;
     110              :     }
     111              : 
     112              :     //create root note or bass note in second octave below middle C
     113          499 :     if (bassTpc != Tpc::TPC_INVALID && voicing != Voicing::ROOT_ONLY) {
     114            0 :         notes.insert({ tpc2pitch(bassTpc) + transposeOffset + (DEFAULT_OCTAVE - 2) * PITCH_DELTA_OCTAVE, bassTpc });
     115              :     } else {
     116          499 :         notes.insert({ rootPitch + (DEFAULT_OCTAVE - 2) * PITCH_DELTA_OCTAVE, rootTpc });
     117              :     }
     118              : 
     119          499 :     switch (voicing) {
     120            7 :     case Voicing::ROOT_ONLY:         //already added root/bass so we are good
     121            7 :         break;
     122           47 :     case Voicing::AUTO:         //auto is close voicing for now since it is the most robust
     123              :         //but just render the root if the harmony isn't understandable
     124           47 :         if (!m_harmony->parsedForm()->understandable()) {
     125            0 :             break;
     126              :         }
     127              :         [[fallthrough]];
     128              :     case Voicing::CLOSE:        //Voices notes in close position in the first octave above middle C
     129              :     {
     130          131 :         notes.insert({ rootPitch + DEFAULT_OCTAVE * PITCH_DELTA_OCTAVE, rootTpc });
     131              :         //ensure that notes fall under a specific range
     132              :         //for now this range is between 5*12 and 6*12
     133          131 :         PitchMap intervals = getIntervals(rootTpc, literal);
     134          575 :         for (const auto& p : intervals) {
     135          444 :             notes.insert({ (rootPitch + (p.first % 128)) % PITCH_DELTA_OCTAVE + DEFAULT_OCTAVE * PITCH_DELTA_OCTAVE, p.second });
     136              :         }
     137          131 :     }
     138          131 :     break;
     139          109 :     case Voicing::DROP_2:
     140              :     {
     141              :         //select 4 notes from list
     142          109 :         PitchMap intervals = normalizeNoteMap(getIntervals(rootTpc, literal), rootTpc, rootPitch, 4);
     143              : 
     144          109 :         int counter = 0;           //counter to drop the second note
     145          533 :         for (auto it = intervals.rbegin(); it != intervals.rend(); ++it) {
     146          424 :             if (++counter == 2) {
     147          109 :                 notes.insert({ it->first + (DEFAULT_OCTAVE - 1) * PITCH_DELTA_OCTAVE, it->second });
     148              :             } else {
     149          315 :                 notes.insert({ it->first + DEFAULT_OCTAVE * PITCH_DELTA_OCTAVE, it->second });
     150              :             }
     151              :         }
     152          109 :     }
     153          109 :     break;
     154           84 :     case Voicing::THREE_NOTE:
     155              :     {
     156              :         //Insert 2 notes in the octave above middle C
     157           84 :         PitchMap intervals = normalizeNoteMap(getIntervals(rootTpc, literal), rootTpc, rootPitch, 2, true);
     158           84 :         auto it = intervals.begin();
     159           84 :         notes.insert({ it->first + DEFAULT_OCTAVE * PITCH_DELTA_OCTAVE, it->second });
     160              : 
     161           84 :         ++it;
     162           84 :         notes.insert({ it->first + DEFAULT_OCTAVE * PITCH_DELTA_OCTAVE, it->second });
     163           84 :     }
     164           84 :     break;
     165          168 :     case Voicing::FOUR_NOTE:
     166              :     case Voicing::SIX_NOTE:
     167              :         //FALLTHROUGH
     168              :     {
     169              :         //four/six note voicing, drop every other note
     170          168 :         PitchMap relIntervals = getIntervals(rootTpc, literal);
     171          168 :         PitchMap intervals;
     172          168 :         if (voicing == Voicing::FOUR_NOTE) {
     173           84 :             intervals = normalizeNoteMap(relIntervals, rootTpc, rootPitch, 3, true);
     174              :         } else {       //voicing == Voicing::SIX_NOTE
     175           84 :             intervals = normalizeNoteMap(relIntervals, rootTpc, rootPitch, 5, true);
     176              :         }
     177              : 
     178          168 :         int counter = 0;           //how many notes have been added
     179          840 :         for (auto it = intervals.rbegin(); it != intervals.rend(); ++it) {
     180          672 :             if (counter % 2) {
     181          252 :                 notes.insert({ it->first + (DEFAULT_OCTAVE - 1) * PITCH_DELTA_OCTAVE, it->second });
     182              :             } else {
     183          420 :                 notes.insert({ it->first + DEFAULT_OCTAVE * PITCH_DELTA_OCTAVE, it->second });
     184              :             }
     185          672 :             ++counter;
     186              :         }
     187          168 :     }
     188          168 :     break;
     189            0 :     default:
     190            0 :         break;
     191              :     }
     192          499 :     return notes;
     193            0 : }
     194              : 
     195              : //---------------------------------------------------
     196              : //   update
     197              : ///   updates the current note map, this is where all
     198              : ///   of the rhythm and voicing choices matter since
     199              : ///   the voicing algorithms depend on this.
     200              : ///
     201              : ///   transposeOffset -- is the necessary adjustment
     202              : ///   that is added to the root and bass
     203              : ///   to get the correct sounding pitch
     204              : ///
     205              : ///   TODO -: Don't worry about realized harmony for
     206              : ///         RNA harmony?
     207              : //---------------------------------------------------
     208          492 : void RealizedHarmony::update(int rootTpc, int bassTpc, int transposeOffset /*= 0*/)
     209              : {
     210              :     //on transposition the dirty flag is set by the harmony, but it's a little
     211              :     //bit risky design since these 3 parameters rely on the dirty bit and are not
     212              :     //otherwise checked by RealizedHarmony. This saves us 3 ints of space, but
     213              :     //has the added risk
     214          492 :     if (!m_dirty) {
     215            0 :         return;
     216              :     }
     217              : 
     218          492 :     if (tpcIsValid(rootTpc)) {
     219          492 :         m_notes = generateNotes(rootTpc, bassTpc, m_literal, m_voicing, transposeOffset);
     220              :     }
     221          492 :     m_dirty = false;
     222              : }
     223              : 
     224              : //--------------------------------------------------
     225              : //    getActualDuration
     226              : ///    gets the fraction duration for how long
     227              : ///    the harmony should be realized based
     228              : ///    on the HDuration set.
     229              : ///
     230              : ///    This is opposed to RealizedHarmony::duration()
     231              : ///    which returns the HDuration, which is the duration
     232              : ///    setting.
     233              : ///
     234              : ///    As the duration may depend on playback order the
     235              : ///    utick is required as a reference point
     236              : ///
     237              : ///    Specifying the durationType will overwrite the
     238              : ///    setting set by the user for the specific harmony object.
     239              : ///    Don't specify it (or as HDuration::INVALID) to honor
     240              : ///    the user setting.
     241              : //--------------------------------------------------
     242          492 : Fraction RealizedHarmony::getActualDuration(int utick, HDuration durationType) const
     243              : {
     244              :     HDuration dur;
     245          492 :     if (durationType != HDuration::INVALID) {
     246            7 :         dur = durationType;
     247              :     } else {
     248          485 :         dur = m_duration;
     249              :     }
     250          492 :     switch (dur) {
     251          479 :     case HDuration::UNTIL_NEXT_CHORD_SYMBOL:
     252          479 :         return m_harmony->ticksTillNext(utick, false);
     253              :         break;
     254            3 :     case HDuration::STOP_AT_MEASURE_END:
     255            3 :         return m_harmony->ticksTillNext(utick, true);
     256              :         break;
     257           10 :     case HDuration::SEGMENT_DURATION: {
     258           10 :         Segment* s = m_harmony->getParentSeg();
     259           10 :         if (s) {
     260              :             // TODO - use duration of chordrest on this segment / track
     261              :             // currently, this will result in too short of a duration
     262              :             // if there are shorter notes on other staves
     263              :             // but, we can only do this up to the next harmony that is being realized,
     264              :             // or elese we would get overlap
     265              :             // (e.g., if the notes are dotted half / quarter, but chords are half / half,
     266              :             // then we can't actually make the first chord a dotted half)
     267           10 :             return s->ticks();
     268              :         } else {
     269            0 :             return Fraction(0, 1);
     270              :         }
     271              :     }
     272              :     break;
     273            0 :     default:
     274            0 :         return Fraction(0, 1);
     275              :     }
     276              : }
     277              : 
     278              : //---------------------------------------------------
     279              : //   getIntervals
     280              : ///   gets a weighted map from intervals to TPCs based on
     281              : ///   a passed root tpc (this allows for us to
     282              : ///   keep pitches, but transpose notes on the score)
     283              : ///
     284              : ///   Weighting System:
     285              : ///   - Rank 0: 3rd, altered 5th, suspensions (characteristic notes)
     286              : ///   - Rank 1: 7th
     287              : ///   - Rank 2: 9ths
     288              : ///   - Rank 3: 13ths where applicable and (in minor chords) 11ths
     289              : ///   - Rank 3: Other alterations and additions
     290              : ///   - Rank 4: 5th and (in major/dominant chords) 11th
     291              : //---------------------------------------------------
     292          492 : RealizedHarmony::PitchMap RealizedHarmony::getIntervals(int rootTpc, bool literal) const
     293              : {
     294              :     //RANKING SYSTEM
     295              :     static const int RANK_MULT = 128;   //used as multiplier and mod since MIDI pitch goes from 0-127
     296              :     static const int RANK_3RD = 0;
     297              :     static const int RANK_7TH = 1;
     298              :     static const int RANK_9TH = 2;
     299              :     static const int RANK_ADD = 3;
     300              :     static const int RANK_OMIT = 4;
     301              : 
     302              :     static const int FIFTH = 7 + RANK_MULT * RANK_OMIT;
     303              : 
     304          492 :     PitchMap ret;
     305              : 
     306          492 :     const ParsedChord* p = m_harmony->parsedForm();
     307          492 :     String quality = p->quality();
     308          492 :     int ext = p->extension().toInt();
     309          492 :     const StringList& modList = p->modifierList();
     310              : 
     311          492 :     int omit = 0;   //omit flags for which notes to omit (for notes that are altered
     312              :                     //or specified to be omitted as a modification) so that they
     313              :                     //are not later added
     314          492 :     bool alt5 = false;   //altered 5
     315              : 
     316              :     //handle modifiers
     317          740 :     for (const String& s : modList) {
     318              :         //find number, split up mods
     319          248 :         bool modded = false;
     320          496 :         for (size_t c = 0; c < s.size(); ++c) {
     321          496 :             if (s.at(c).isDigit()) {
     322          248 :                 int alter = 0;
     323          248 :                 size_t cutoff = c;
     324          248 :                 String degreeString = s;
     325          248 :                 static const std::wregex NOT_DIGITS = std::wregex(L"[^0-9]+");
     326          248 :                 degreeString.remove(NOT_DIGITS);
     327          248 :                 int deg = degreeString.toInt();
     328              :                 //account for if the flat/sharp is stuck to the end of add
     329          248 :                 if (c) {
     330          248 :                     if (s.at(c - 1) == u'#') {
     331           64 :                         cutoff -= 1;
     332           64 :                         if (deg == 7) {
     333            0 :                             alter = 0;
     334              :                         } else {
     335           64 :                             alter = +1;
     336              :                         }
     337          184 :                     } else if (s.at(c - 1) == u'b') {
     338          184 :                         cutoff -= 1;
     339          184 :                         alter = -1;
     340              :                     }
     341              :                 }
     342              : 
     343          248 :                 String extType = s.left(cutoff);
     344          248 :                 if (extType == "" || extType == "major") {         //alteration
     345          248 :                     if (deg == 9) {
     346           62 :                         ret.insert({ step2pitchInterval(deg, alter) + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, deg, alter) });
     347              :                     } else {
     348          186 :                         ret.insert({ step2pitchInterval(deg, alter) + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, deg, alter) });
     349              :                     }
     350          248 :                     if (deg == 5) {
     351           66 :                         alt5 = true;
     352              :                     }
     353          248 :                     omit |= 1 << deg;
     354          248 :                     modded = true;
     355            0 :                 } else if (extType == "sus") {
     356            0 :                     ret.insert({ step2pitchInterval(deg, alter) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, deg, alter) });
     357            0 :                     omit |= 1 << 3;
     358            0 :                     modded = true;
     359            0 :                 } else if (extType == "no") {
     360            0 :                     omit |= 1 << deg;
     361            0 :                     modded = true;
     362            0 :                 } else if (extType == "add") {
     363            0 :                     ret.insert({ step2pitchInterval(deg, alter) + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, deg, alter) });
     364            0 :                     omit |= 1 << deg;
     365            0 :                     modded = true;
     366              :                 }
     367          248 :                 break;
     368          248 :             }
     369              :         }
     370              : 
     371              :         //check for special chords, if we haven't modded anything yet there is a special or incomprehensible modifier
     372          248 :         if (!modded) {
     373            0 :             if (s == "phryg") {
     374            0 :                 ret.insert({ step2pitchInterval(9, -1) + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, 9, -1) });
     375            0 :                 omit |= 1 << 9;
     376            0 :             } else if (s == "lyd") {
     377            0 :                 ret.insert({ step2pitchInterval(11, +1) + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 11, +1) });
     378            0 :                 omit |= 1 << 11;
     379            0 :             } else if (s == "blues") {
     380            0 :                 ret.insert({ step2pitchInterval(9, +1) + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 9, +1) });
     381            0 :                 omit |= 1 << 9;
     382            0 :             } else if (s == "alt") {
     383            0 :                 ret.insert({ step2pitchInterval(5, -1) + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 5, -1) });
     384            0 :                 ret.insert({ step2pitchInterval(5, +1) + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 5, +1) });
     385            0 :                 omit |= 1 << 5;
     386            0 :                 ret.insert({ step2pitchInterval(9, -1) + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, 9, -1) });
     387            0 :                 ret.insert({ step2pitchInterval(9, +1) + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, 9, +1) });
     388            0 :                 omit |= 1 << 9;
     389              :             } else {    //no easy way to realize tristan chords since they are more analytical tools
     390            0 :                 omit = ~0;
     391              :             }
     392              :         }
     393              :     }
     394              : 
     395              :     //handle ext = 5: power chord so omit the 3rd
     396          492 :     if (ext == 5) {
     397            0 :         omit |= 1 << 3;
     398              :     }
     399              : 
     400              :     //handle chord quality
     401          492 :     if (quality == "minor") {
     402           76 :         if (!(omit & (1 << 3))) {
     403           76 :             ret.insert({ step2pitchInterval(3, -1) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, 3, -1) });         //min3
     404              :         }
     405           76 :         if (!(omit & (1 << 5))) {
     406           12 :             ret.insert({ step2pitchInterval(5, 0) + RANK_MULT * RANK_OMIT, tpcInterval(rootTpc, 5, 0) });           //p5
     407              :         }
     408          416 :     } else if (quality == "augmented") {
     409            2 :         if (!(omit & (1 << 3))) {
     410            2 :             ret.insert({ step2pitchInterval(3, 0) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, 3, 0) });          //maj3
     411              :         }
     412            2 :         if (!(omit & (1 << 5))) {
     413            2 :             ret.insert({ step2pitchInterval(5, +1) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, 5, +1) });        //p5
     414              :         }
     415          414 :     } else if (quality == "diminished" || quality == "half-diminished") {
     416           60 :         if (!(omit & (1 << 3))) {
     417           60 :             ret.insert({ step2pitchInterval(3, -1) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, 3, -1) });         //min3
     418              :         }
     419           60 :         if (!(omit & (1 << 5))) {
     420           60 :             ret.insert({ step2pitchInterval(5, -1) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, 5, -1) });         //dim5
     421              :         }
     422           60 :         if (!(omit & (1 << 7)) && quality == "half-diminished") {
     423            0 :             ret.insert({ step2pitchInterval(7, -1) + RANK_MULT * RANK_7TH, tpcInterval(rootTpc, 7, -1) });           //min7
     424              :         }
     425           60 :         alt5 = true;
     426              :     } else { //major or dominant
     427          354 :         if (!(omit & (1 << 3))) {
     428          354 :             ret.insert({ step2pitchInterval(3, 0) + RANK_MULT * RANK_3RD, tpcInterval(rootTpc, 3, 0) });          //maj3
     429              :         }
     430          354 :         if (!(omit & (1 << 5))) {
     431          352 :             ret.insert({ step2pitchInterval(5, 0) + RANK_MULT * RANK_OMIT, tpcInterval(rootTpc, 5, 0) });          //p5
     432              :         }
     433              :     }
     434              : 
     435              :     //handle extension
     436          492 :     switch (ext) {
     437           60 :     case 13:
     438           60 :         if (!(omit & (1 << 13))) {
     439           60 :             ret.insert({ 9 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 13, 0) });               //maj13
     440           60 :             omit |= 1 << 13;
     441              :         }
     442              :         [[fallthrough]];
     443              :     case 11:
     444           60 :         if (!(omit & (1 << 11))) {
     445           60 :             if (quality == "minor") {
     446            0 :                 ret.insert({ 5 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 11, 0) });                 //maj11
     447           60 :             } else if (literal) {
     448           60 :                 ret.insert({ 5 + RANK_MULT * RANK_OMIT, tpcInterval(rootTpc, 11, 0) });                 //maj11
     449              :             }
     450           60 :             omit |= 1 << 11;
     451              :         }
     452              :         [[fallthrough]];
     453              :     case 9:
     454          120 :         if (!(omit & (1 << 9))) {
     455          120 :             ret.insert({ 2 + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, 9, 0) });               //maj9
     456          120 :             omit |= 1 << 9;
     457              :         }
     458              :         [[fallthrough]];
     459              :     case 7:
     460          409 :         if (!(omit & (1 << 7))) {
     461          409 :             if (quality == "major") {
     462           76 :                 ret.insert({ 11 + RANK_MULT * RANK_7TH, tpcInterval(rootTpc, 7, 0) });
     463          333 :             } else if (quality == "diminished") {
     464           60 :                 ret.insert({ 9 + RANK_MULT * RANK_7TH, tpcInterval(rootTpc, 7, -2) });
     465          273 :             } else if (quality == "half-diminished") {
     466              :                 // 7th note already added when handling chord quality
     467              :             } else {         //dominant or augmented or minor
     468          273 :                 ret.insert({ 10 + RANK_MULT * RANK_7TH, tpcInterval(rootTpc, 7, -1) });
     469              :             }
     470              :         }
     471          409 :         break;
     472            0 :     case 6:
     473            0 :         if (!(omit & (1 << 6))) {
     474            0 :             ret.insert({ 9 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 6, 0) });               //maj6
     475            0 :             omit |= 1 << 13;             //no need to add/alter 6 chords
     476              :         }
     477            0 :         break;
     478            0 :     case 5:
     479              :         //omitted third already
     480            0 :         break;
     481            0 :     case 4:
     482            0 :         if (!(omit & (1 << 4))) {
     483            0 :             ret.insert({ 5 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 4, 0) });               //p4
     484              :         }
     485            0 :         break;
     486            0 :     case 2:
     487            0 :         if (!(omit & (1 << 2))) {
     488            0 :             ret.insert({ 2 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 2, 0) });               //maj2
     489              :         }
     490            0 :         omit |= 1 << 9;           //make sure we don't add altered 9 when theres a 2
     491            0 :         break;
     492            0 :     case 69:
     493            0 :         ret.insert({ 9 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 6, 0) });             //maj6
     494            0 :         ret.insert({ 2 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 9, 0) });             //maj6
     495            0 :         omit = ~0;           //no additions/alterations for a 69 chord
     496            0 :         break;
     497           83 :     default:
     498           83 :         break;
     499              :     }
     500              : 
     501          492 :     Harmony* next = m_harmony->findNext();
     502          492 :     if (!literal && next && tpcIsValid(next->rootTpc())) {
     503              :         //jazz interpretation
     504           24 :         String qNext = next->parsedForm()->quality();
     505              :         //pitch from current to next harmony normalized to a range between 0 and 12
     506              :         //add PITCH_DELTA_OCTAVE before modulo so that we can ensure arithmetic mod rather than computer mod
     507              :         //int keyTpc = int(next->staff()->key(next->tick())) + 14; //tpc of key (ex. F# major would be Tpc::F_S)
     508              :         //int keyTpcMinor = keyTpc + 3;
     509           24 :         int pitchBetween = (tpc2pitch(next->rootTpc()) + PITCH_DELTA_OCTAVE - tpc2pitch(rootTpc)) % PITCH_DELTA_OCTAVE;
     510           24 :         bool maj7 = qNext == "major" && next->parsedForm()->extension().toInt() >= 7;     //whether or not the next chord has major 7
     511              : 
     512              :         //commented code: dont add 9 for diminished chords
     513           24 :         if (!(omit & (1 << 9))) {    // && !(alt5 && (quality == "minor" || quality == "diminished" || quality == "half-diminished"))) {
     514           23 :             if (quality == "dominant" && pitchBetween == 5 && (qNext == "minor" || maj7)) {
     515              :                 //flat 9 when resolving a fourth up to a minor chord or major 7th
     516            7 :                 ret.insert({ 1 + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, 9, -1) });
     517              :             } else {   //add major 9
     518           16 :                 ret.insert({ 2 + RANK_MULT * RANK_9TH, tpcInterval(rootTpc, 9, 0) });
     519              :             }
     520              :         }
     521              : 
     522           24 :         if (!(omit & (1 << 13)) && !alt5) {
     523           21 :             if (quality == "dominant" && pitchBetween == 5 && (qNext == "minor" || false)) {
     524              :                 //flat 13 for dominant to chord a P4 up
     525              :                 //only for minor chords for now
     526            1 :                 muse::remove(ret, FIFTH);
     527            1 :                 ret.insert({ 8 + RANK_MULT * RANK_ADD, tpcInterval(rootTpc, 13, -1) });
     528              :             }
     529              :             //major 13 considered, but too dependent on melody and voicing of other chord
     530              :             //no implementation for now
     531              :         }
     532           24 :     }
     533          492 :     return ret;
     534          492 : }
     535              : 
     536              : //---------------------------------------------------
     537              : //   normalizeNoteMap
     538              : ///   normalize the pitch map from intervals to create pitches between 0 and 12
     539              : ///   and resolve any weighting system.
     540              : ///
     541              : ///   enforceMaxEquals - enforce the max as a goal so that the max is how many notes is inserted
     542              : //---------------------------------------------------
     543          361 : RealizedHarmony::PitchMap RealizedHarmony::normalizeNoteMap(const PitchMap& intervals, int rootTpc, int rootPitch, size_t max,
     544              :                                                             bool enforceMaxAsGoal) const
     545              : {
     546          361 :     PitchMap ret;
     547          361 :     auto it = intervals.begin();
     548              : 
     549         1467 :     for (size_t i = 0; i < max; ++i) {
     550         1216 :         if (it == intervals.end()) {
     551          110 :             break;
     552              :         }
     553         1106 :         ret.insert({ (it->first % 128 + rootPitch) % PITCH_DELTA_OCTAVE, it->second });     //128 is RANK_MULT
     554         1106 :         ++it;
     555              :     }
     556              : 
     557              :     //redo insertions if we must have a specific number of notes with insertMulti
     558          361 :     if (enforceMaxAsGoal) {
     559          576 :         while (ret.size() < max) {
     560           72 :             ret.insert({ rootPitch, rootTpc });       //duplicate root
     561              : 
     562           72 :             size_t size = max - ret.size();
     563           72 :             it = intervals.begin();       //reset iterator
     564          120 :             for (size_t i = 0; i < size; ++i) {
     565           48 :                 if (it == intervals.end()) {
     566            0 :                     break;
     567              :                 }
     568           48 :                 ret.insert({ (it->first % 128 + rootPitch) % PITCH_DELTA_OCTAVE, it->second });
     569           48 :                 ++it;
     570              :             }
     571              :         }
     572          109 :     } else if (ret.size() < max) { //insert another root if we have room in general
     573           38 :         ret.insert({ rootPitch, rootTpc });
     574              :     }
     575          361 :     return ret;
     576            0 : }
     577              : 
     578              : //---------------------------------------------------
     579              : //   cascadeDirty
     580              : ///   cascades the dirty flag backwards so that everything is properly set
     581              : ///   this is required since voicing algorithms may look ahead
     582              : ///
     583              : ///   NOTE: FOR NOW ALGORITHMS DO NOT LOOK BACKWARDS AND SO THERE IS NO
     584              : ///   REQUIREMENT TO CASCADE FORWARD, IN THE FUTURE IT MAY BECOME IMPORTANT
     585              : ///   TO CASCADE FORWARD AS WELL
     586              : //---------------------------------------------------
     587         2786 : void RealizedHarmony::cascadeDirty(bool dirty)
     588              : {
     589         2786 :     if (dirty && !m_dirty) {   //only cascade when we want to set our clean realized harmony to dirty
     590            0 :         Harmony* prev = m_harmony->findPrev();
     591            0 :         if (prev) {
     592            0 :             prev->realizedHarmony().cascadeDirty(dirty);
     593              :         }
     594              :     }
     595         2786 :     m_dirty = dirty;
     596         2786 : }
     597              : }
        

Generated by: LCOV version 2.0-1