1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2002-2016 Werner Schweer and others
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 #include "realizedharmony.h"
21 #include "pitchspelling.h"
22 #include "staff.h"
23 #include "chordlist.h"
24 #include "harmony.h"
25 #include "fraction.h"
26 #include "segment.h"
27 #include "chordrest.h"
28 
29 namespace Ms {
30 
31 //---------------------------------------------------
32 //   setVoicing
33 ///   sets the voicing and dirty flag if the passed
34 ///   voicing is different than current
35 //---------------------------------------------------
setVoicing(Voicing v)36 void RealizedHarmony::setVoicing(Voicing v)
37       {
38       if (_voicing == v)
39             return;
40       _voicing = v;
41       cascadeDirty(true);
42       }
43 
44 //---------------------------------------------------
45 //   setDuration
46 ///   sets the duration and dirty flag if the passed
47 ///   HDuration is different than current
48 //---------------------------------------------------
setDuration(HDuration d)49 void RealizedHarmony::setDuration(HDuration d)
50       {
51       if (_duration == d)
52             return;
53       _duration = d;
54       cascadeDirty(true);
55       }
56 
57 //---------------------------------------------------
58 //   setLiteral
59 ///   sets literal/jazz and dirty flag if the passed
60 ///   bool is different than current
61 //---------------------------------------------------
setLiteral(bool literal)62 void RealizedHarmony::setLiteral(bool literal)
63       {
64       if (_literal == literal)
65             return;
66       _literal = literal;
67       cascadeDirty(true);
68       }
69 
70 //---------------------------------------------------
71 //   notes
72 ///   returns the list of notes
73 //---------------------------------------------------
notes() const74 const RealizedHarmony::PitchMap& RealizedHarmony::notes() const
75       {
76       Q_ASSERT(!_dirty);
77       //with the way that the code is currently structured, there should be no way to
78       //get to this function with dirty flag set although in the future it may be
79       //better to just update if dirty here
80       return _notes;
81       }
82 
83 //---------------------------------------------------
84 //   generateNotes
85 ///   generates a note list based on the passed parameters
86 //---------------------------------------------------
generateNotes(int rootTpc,int bassTpc,bool literal,Voicing voicing,int transposeOffset) const87 const RealizedHarmony::PitchMap RealizedHarmony::generateNotes(int rootTpc, int bassTpc,
88                                                 bool literal, Voicing voicing, int transposeOffset) const
89       {
90       //The octave which to generate the body of the harmony, this is static const for now
91       //but may be user controlled in the future
92       static const int DEFAULT_OCTAVE = 5; //octave above middle C
93 
94       PitchMap notes;
95       int rootPitch = tpc2pitch(rootTpc) + transposeOffset;
96       //euclidian mod, we need to treat this new pitch as a pitch between
97       //0 and 11, so that voicing remains consistent across transposition
98       if (rootPitch < 0)
99             rootPitch += PITCH_DELTA_OCTAVE;
100       else
101             rootPitch %= PITCH_DELTA_OCTAVE;
102 
103       //create root note or bass note in second octave below middle C
104       if (bassTpc != Tpc::TPC_INVALID && voicing != Voicing::ROOT_ONLY)
105             notes.insert(tpc2pitch(bassTpc) + transposeOffset
106                         + (DEFAULT_OCTAVE-2)*PITCH_DELTA_OCTAVE, bassTpc);
107       else
108             notes.insert(rootPitch + (DEFAULT_OCTAVE-2)*PITCH_DELTA_OCTAVE, rootTpc);
109 
110       switch (voicing) {
111             case Voicing::ROOT_ONLY: //already added root/bass so we are good
112                   break;
113             case Voicing::AUTO: //auto is close voicing for now since it is the most robust
114                   //but just render the root if the harmony isn't understandable
115                   if (!_harmony->parsedForm()->understandable())
116                         break;
117                   // FALLTHROUGH
118             case Voicing::CLOSE://Voices notes in close position in the first octave above middle C
119                   {
120                   notes.insert(rootPitch + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, rootTpc);
121                   //ensure that notes fall under a specific range
122                   //for now this range is between 5*12 and 6*12
123                   PitchMap intervals = getIntervals(rootTpc, literal);
124                   PitchMapIterator i(intervals);
125                   while (i.hasNext()) {
126                         i.next();
127                         notes.insert((rootPitch + (i.key() % 128)) % PITCH_DELTA_OCTAVE +
128                                       DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value());
129                         }
130                   }
131                   break;
132             case Voicing::DROP_2:
133                   {
134                   //select 4 notes from list
135                   PitchMap intervals = normalizeNoteMap(getIntervals(rootTpc, literal), rootTpc, rootPitch, 4);
136                   PitchMapIterator i(intervals);
137                   i.toBack();
138 
139                   int counter = 0; //counter to drop the second note
140                   while (i.hasPrevious()) {
141                         i.previous();
142                         if (++counter == 2)
143                               notes.insert(i.key() + (DEFAULT_OCTAVE-1)*PITCH_DELTA_OCTAVE, i.value());
144                         else
145                               notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value());
146                         }
147                   }
148                   break;
149             case Voicing::THREE_NOTE:
150                   {
151                   //Insert 2 notes in the octave above middle C
152                   PitchMap intervals = normalizeNoteMap(getIntervals(rootTpc, literal), rootTpc, rootPitch, 2, true);
153                   PitchMapIterator i(intervals);
154 
155                   i.next();
156                   notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value());
157 
158                   i.next();
159                   notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value());
160                   }
161                   break;
162             case Voicing::FOUR_NOTE:
163             case Voicing::SIX_NOTE:
164                   //FALLTHROUGH
165                   {
166                   //four/six note voicing, drop every other note
167                   PitchMap relIntervals = getIntervals(rootTpc, literal);
168                   PitchMap intervals;
169                   if (voicing == Voicing::FOUR_NOTE)
170                         intervals = normalizeNoteMap(relIntervals, rootTpc, rootPitch, 3, true);
171                   else //voicing == Voicing::SIX_NOTE
172                         intervals = normalizeNoteMap(relIntervals, rootTpc, rootPitch, 5, true);
173 
174                   PitchMapIterator i(intervals);
175                   i.toBack();
176 
177                   int counter = 0; //how many notes have been added
178                   while (i.hasPrevious()) {
179                         i.previous();
180 
181                         if (counter % 2)
182                               notes.insert(i.key() + (DEFAULT_OCTAVE-1)*PITCH_DELTA_OCTAVE, i.value());
183                         else
184                               notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value());
185                         ++counter;
186                         }
187                   }
188                   break;
189             default:
190                   break;
191             }
192       return notes;
193       }
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 //---------------------------------------------------
update(int rootTpc,int bassTpc,int transposeOffset)208 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       if (!_dirty) {
215             Q_ASSERT(_harmony->harmonyType() != HarmonyType::STANDARD || (_notes.first() == rootTpc || _notes.first() == bassTpc));
216             return;
217             }
218 
219       if (tpcIsValid(rootTpc))
220             _notes = generateNotes(rootTpc, bassTpc, _literal, _voicing, transposeOffset);
221       _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 //--------------------------------------------------
getActualDuration(int utick,HDuration durationType) const242 Fraction RealizedHarmony::getActualDuration(int utick, HDuration durationType) const
243       {
244       HDuration dur;
245       if (durationType != HDuration::INVALID)
246             dur = durationType;
247       else
248             dur = _duration;
249       switch (dur)
250             {
251             case HDuration::UNTIL_NEXT_CHORD_SYMBOL:
252                   return _harmony->ticksTillNext(utick, false);
253                   break;
254             case HDuration::STOP_AT_MEASURE_END:
255                   return _harmony->ticksTillNext(utick, true);
256                   break;
257             case HDuration::SEGMENT_DURATION: {
258                   Segment* s = _harmony->getParentSeg();
259                   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                         return s->ticks();
268                         }
269                   else {
270                         return Fraction(0, 1);
271                         }
272                   break;
273                   }
274             default:
275                   return Fraction(0, 1);
276             }
277       }
278 
279 //---------------------------------------------------
280 //   getIntervals
281 ///   gets a weighted map from intervals to TPCs based on
282 ///   a passed root tpc (this allows for us to
283 ///   keep pitches, but transpose notes on the score)
284 ///
285 ///   Weighting System:
286 ///   - Rank 0: 3rd, altered 5th, suspensions (characteristic notes)
287 ///   - Rank 1: 7th
288 ///   - Rank 2: 9ths
289 ///   - Rank 3: 13ths where applicable and (in minor chords) 11ths
290 ///   - Rank 3: Other alterations and additions
291 ///   - Rank 4: 5th and (in major/dominant chords) 11th
292 //---------------------------------------------------
getIntervals(int rootTpc,bool literal) const293 RealizedHarmony::PitchMap RealizedHarmony::getIntervals(int rootTpc, bool literal) const
294       {
295       //RANKING SYSTEM
296       static const int RANK_MULT = 128; //used as multiplier and mod since MIDI pitch goes from 0-127
297       static const int RANK_3RD = 0;
298       static const int RANK_7TH = 1;
299       static const int RANK_9TH = 2;
300       static const int RANK_ADD = 3;
301       static const int RANK_OMIT = 4;
302 
303       static const int FIFTH = 7 + RANK_MULT*RANK_OMIT;
304 
305       PitchMap ret;
306 
307       const ParsedChord* p = _harmony->parsedForm();
308       QString quality = p->quality();
309       int ext = p->extension().toInt();
310       const QStringList& modList = p->modifierList();
311 
312       int omit = 0; //omit flags for which notes to omit (for notes that are altered
313                     //or specified to be omitted as a modification) so that they
314                     //are not later added
315       bool alt5 = false; //altered 5
316 
317       //handle modifiers
318       for (QString s : modList) {
319             //find number, split up mods
320             bool modded = false;
321             for (int c = 0; c < s.length(); ++c) {
322                   if (s[c].isDigit()) {
323                         int alter = 0;
324                         int cutoff = c;
325                         int deg = s.rightRef(s.length() - c).toInt();
326                         //account for if the flat/sharp is stuck to the end of add
327                         if (c) {
328                               if (s[c-1] == '#') {
329                                     cutoff -= 1;
330                                     alter = +1;
331                                     }
332                               else if (s[c-1] == 'b') {
333                                     cutoff -= 1;
334                                     alter = -1;
335                                     }
336                               }
337                         QString extType = s.left(cutoff);
338                         if (extType == "" || extType == "major") { //alteration
339                               if (deg == 9)
340                                     ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, deg, alter));
341                               else
342                                     ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, deg, alter));
343                               if (deg == 5)
344                                     alt5 = true;
345                               omit |= 1 << deg;
346                               modded = true;
347                               }
348                         else if (extType == "sus") {
349                               ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, deg, alter));
350                               omit |= 1 << 3;
351                               modded = true;
352                               }
353                         else if (extType == "no") {
354                               omit |= 1 << deg;
355                               modded = true;
356                               }
357                         else if (extType == "add") {
358                               ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, deg, alter));
359                               omit |= 1 << deg;
360                               modded = true;
361                               }
362                         break;
363                         }
364                   }
365 
366             //check for special chords, if we haven't modded anything yet there is a special or incomprehensible modifier
367             if (!modded) {
368                   if (s == "phryg") {
369                         ret.insert(step2pitchInterval(9, -1) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, -1));
370                         omit |= 1 << 9;
371                         }
372                   else if (s == "lyd") {
373                         ret.insert(step2pitchInterval(11, +1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 11, +1));
374                         omit |= 1 << 11;
375                         }
376                   else if (s == "blues") {
377                         ret.insert(step2pitchInterval(9, +1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 9, +1));
378                         omit |= 1 << 9;
379                         }
380                   else if (s == "alt") {
381                         ret.insert(step2pitchInterval(5, -1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 5, -1));
382                         ret.insert(step2pitchInterval(5, +1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 5, +1));
383                         omit |= 1 << 5;
384                         ret.insert(step2pitchInterval(9, -1) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, -1));
385                         ret.insert(step2pitchInterval(9, +1) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, +1));
386                         omit |= 1 << 9;
387                         }
388                   else  //no easy way to realize tristan chords since they are more analytical tools
389                         omit = ~0;
390                   }
391             }
392 
393       //handle ext = 5: power chord so omit the 3rd
394       if (ext == 5)
395             omit |= 1 << 3;
396 
397       //handle chord quality
398       if (quality == "minor") {
399             if (!(omit & (1 << 3)))
400                   ret.insert(step2pitchInterval(3, -1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, -1));     //min3
401             if (!(omit & (1 << 5)))
402                   ret.insert(step2pitchInterval(5, 0) + RANK_MULT*RANK_OMIT, tpcInterval(rootTpc, 5, 0));       //p5
403             }
404       else if (quality == "augmented") {
405             if (!(omit & (1 << 3)))
406                   ret.insert(step2pitchInterval(3, 0) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, 0));      //maj3
407             if (!(omit & (1 << 5)))
408                   ret.insert(step2pitchInterval(5, +1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 5, +1));    //p5
409             }
410       else if (quality == "diminished" || quality == "half-diminished") {
411             if (!(omit & (1 << 3)))
412                   ret.insert(step2pitchInterval(3, -1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, -1));     //min3
413             if (!(omit & (1 << 5)))
414                   ret.insert(step2pitchInterval(5, -1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 5, -1));     //dim5
415             alt5 = true;
416             }
417       else { //major or dominant
418             if (!(omit & (1 << 3)))
419                   ret.insert(step2pitchInterval(3, 0) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, 0));      //maj3
420             if (!(omit & (1 << 5)))
421                   ret.insert(step2pitchInterval(5, 0) + RANK_MULT*RANK_OMIT, tpcInterval(rootTpc, 5, 0));      //p5
422             }
423 
424       //handle extension
425       switch (ext) {
426             case 13:
427                   if (!(omit & (1 << 13))) {
428                         ret.insert(9 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 13, 0));     //maj13
429                         omit |= 1 << 13;
430                         }
431                   // FALLTHROUGH
432             case 11:
433                   if (!(omit & (1 << 11))) {
434                         if (quality == "minor")
435                               ret.insert(5 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 11, 0));     //maj11
436                         else if (literal)
437                               ret.insert(5 + RANK_MULT*RANK_OMIT, tpcInterval(rootTpc, 11, 0));     //maj11
438                         omit |= 1 << 11;
439                         }
440                   // FALLTHROUGH
441             case 9:
442                   if (!(omit & (1 << 9))) {
443                         ret.insert(2 + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, 0));     //maj9
444                         omit |= 1 << 9;
445                         }
446                   // FALLTHROUGH
447             case 7:
448                   if (!(omit & (1 << 7))) {
449                         if (quality == "major")
450                               ret.insert(11 + RANK_MULT*RANK_7TH, tpcInterval(rootTpc, 7, 0));
451                         else if (quality == "diminished")
452                               ret.insert(9 + RANK_MULT*RANK_7TH, tpcInterval(rootTpc, 7, -2));
453                         else //dominant or augmented or minor
454                               ret.insert(10 + RANK_MULT*RANK_7TH, tpcInterval(rootTpc, 7, -1));
455                         }
456                   break;
457             case 6:
458                   if (!(omit & (1 << 6))) {
459                         ret.insert(9 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 6, 0));     //maj6
460                         omit |= 1 << 13; //no need to add/alter 6 chords
461                         }
462                   break;
463             case 5:
464                   //omitted third already
465                   break;
466             case 4:
467                   if (!(omit & (1 << 4)))
468                         ret.insert(5 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 4, 0));     //p4
469                   break;
470             case 2:
471                   if (!(omit & (1 << 2)))
472                         ret.insert(2 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 2, 0));     //maj2
473                   omit |= 1 << 9; //make sure we don't add altered 9 when theres a 2
474                   break;
475             case 69:
476                   ret.insert(9 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 6, 0));     //maj6
477                   ret.insert(2 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 9, 0));     //maj6
478                   omit = ~0; //no additions/alterations for a 69 chord
479                   break;
480             default:
481                   break;
482             }
483 
484       Harmony* next = _harmony->findNext();
485       if (!literal && next && tpcIsValid(next->rootTpc())) {
486             //jazz interpretation
487             QString qNext = next->parsedForm()->quality();
488             //pitch from current to next harmony normalized to a range between 0 and 12
489             //add PITCH_DELTA_OCTAVE before modulo so that we can ensure arithmetic mod rather than computer mod
490             //int keyTpc = int(next->staff()->key(next->tick())) + 14; //tpc of key (ex. F# major would be Tpc::F_S)
491             //int keyTpcMinor = keyTpc + 3;
492             int pitchBetween = (tpc2pitch(next->rootTpc()) + PITCH_DELTA_OCTAVE - tpc2pitch(rootTpc)) % PITCH_DELTA_OCTAVE;
493             bool maj7 = qNext == "major" && next->parsedForm()->extension() >= 7; //whether or not the next chord has major 7
494 
495                                     //commented code: dont add 9 for diminished chords
496             if (!(omit & (1 << 9))) {// && !(alt5 && (quality == "minor" || quality == "diminished" || quality == "half-diminished"))) {
497                   if (quality == "dominant" && pitchBetween == 5 && (qNext == "minor" || maj7)) {
498                         //flat 9 when resolving a fourth up to a minor chord or major 7th
499                         ret.insert(1 + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, -1));
500                         }
501                   else //add major 9
502                         ret.insert(2 + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, 0));
503                   }
504 
505             if (!(omit & (1 << 13)) && !alt5) {
506                   if (quality == "dominant" && pitchBetween == 5 && (qNext == "minor" || false)) {
507                         //flat 13 for dominant to chord a P4 up
508                         //only for minor chords for now
509                         ret.remove(FIFTH);
510                         ret.insert(8 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 13, -1));
511                         }
512                   //major 13 considered, but too dependent on melody and voicing of other chord
513                   //no implementation for now
514                   }
515             }
516       return ret;
517       }
518 
519 //---------------------------------------------------
520 //   normalizeNoteMap
521 ///   normalize the pitch map from intervals to create pitches between 0 and 12
522 ///   and resolve any weighting system.
523 ///
524 ///   enforceMaxEquals - enforce the max as a goal so that the max is how many notes is inserted
525 //---------------------------------------------------
normalizeNoteMap(const PitchMap & intervals,int rootTpc,int rootPitch,int max,bool enforceMaxAsGoal) const526 RealizedHarmony::PitchMap RealizedHarmony::normalizeNoteMap(const PitchMap& intervals, int rootTpc, int rootPitch, int max, bool enforceMaxAsGoal) const
527       {
528       PitchMap ret;
529       PitchMapIterator itr(intervals);
530 
531       for (int i = 0; i < max; ++i) {
532             if (!itr.hasNext())
533                   break;
534             itr.next();
535             ret.insert((itr.key() % 128 + rootPitch) % PITCH_DELTA_OCTAVE, itr.value()); //128 is RANK_MULT
536             }
537 
538       //redo insertions if we must have a specific number of notes with insertMulti
539       if (enforceMaxAsGoal) {
540             while (ret.size() < max) {
541                   ret.insert(rootPitch, rootTpc); //duplicate root
542 
543                   int size = max - ret.size();
544                   itr = PitchMapIterator(intervals); //reset iterator
545                   for (int i = 0; i < size; ++i) {
546                         if (!itr.hasNext())
547                               break;
548                         itr.next();
549                         ret.insert((itr.key() % 128 + rootPitch) % PITCH_DELTA_OCTAVE, itr.value());
550                         }
551                   }
552             }
553       else if (ret.size() < max) //insert another root if we have room in general
554             ret.insert(rootPitch, rootTpc);
555       return ret;
556       }
557 
558 //---------------------------------------------------
559 //   cascadeDirty
560 ///   cascades the dirty flag backwards so that everything is properly set
561 ///   this is required since voicing algorithms may look ahead
562 ///
563 ///   NOTE: FOR NOW ALGORITHMS DO NOT LOOK BACKWARDS AND SO THERE IS NO
564 ///   REQUIREMENT TO CASCADE FORWARD, IN THE FUTURE IT MAY BECOME IMPORTANT
565 ///   TO CASCADE FORWARD AS WELL
566 //---------------------------------------------------
cascadeDirty(bool dirty)567 void RealizedHarmony::cascadeDirty(bool dirty)
568       {
569       if (dirty && !_dirty) { //only cascade when we want to set our clean realized harmony to dirty
570             Harmony* prev = _harmony->findPrev();
571             if (prev)
572                   prev->realizedHarmony().cascadeDirty(dirty);
573             }
574       _dirty = dirty;
575       }
576 }
577