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