//============================================================================= // MuseScore // Music Composition & Notation // // Copyright (C) 2002-2016 Werner Schweer and others // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. //============================================================================= #include "realizedharmony.h" #include "pitchspelling.h" #include "staff.h" #include "chordlist.h" #include "harmony.h" #include "fraction.h" #include "segment.h" namespace Ms { //--------------------------------------------------- // setVoicing /// sets the voicing and dirty flag if the passed /// voicing is different than current //--------------------------------------------------- void RealizedHarmony::setVoicing(Voicing v) { if (_voicing == v) return; _voicing = v; cascadeDirty(true); } //--------------------------------------------------- // setDuration /// sets the duration and dirty flag if the passed /// HDuration is different than current //--------------------------------------------------- void RealizedHarmony::setDuration(HDuration d) { if (_duration == d) return; _duration = d; cascadeDirty(true); } //--------------------------------------------------- // setLiteral /// sets literal/jazz and dirty flag if the passed /// bool is different than current //--------------------------------------------------- void RealizedHarmony::setLiteral(bool literal) { if (_literal == literal) return; _literal = literal; cascadeDirty(true); } //--------------------------------------------------- // notes /// returns the list of notes //--------------------------------------------------- const RealizedHarmony::PitchMap& RealizedHarmony::notes() const { Q_ASSERT(!_dirty); //with the way that the code is currently structured, there should be no way to //get to this function with dirty flag set although in the future it may be //better to just update if dirty here return _notes; } //--------------------------------------------------- // generateNotes /// generates a note list based on the passed parameters //--------------------------------------------------- const RealizedHarmony::PitchMap RealizedHarmony::generateNotes(int rootTpc, int bassTpc, bool literal, Voicing voicing, int transposeOffset) const { //The octave which to generate the body of the harmony, this is static const for now //but may be user controlled in the future static const int DEFAULT_OCTAVE = 5; //octave above middle C PitchMap notes; int rootPitch = tpc2pitch(rootTpc) + transposeOffset; //euclidian mod, we need to treat this new pitch as a pitch between //0 and 11, so that voicing remains consistent across transposition if (rootPitch < 0) rootPitch += PITCH_DELTA_OCTAVE; else rootPitch %= PITCH_DELTA_OCTAVE; //create root note or bass note in second octave below middle C if (bassTpc != Tpc::TPC_INVALID && voicing != Voicing::ROOT_ONLY) notes.insert(tpc2pitch(bassTpc) + transposeOffset + (DEFAULT_OCTAVE-2)*PITCH_DELTA_OCTAVE, bassTpc); else notes.insert(rootPitch + (DEFAULT_OCTAVE-2)*PITCH_DELTA_OCTAVE, rootTpc); switch (voicing) { case Voicing::ROOT_ONLY: //already added root/bass so we are good break; case Voicing::AUTO: //auto is close voicing for now since it is the most robust //but just render the root if the harmony isn't understandable if (!_harmony->parsedForm()->understandable()) break; // FALLTHROUGH case Voicing::CLOSE://Voices notes in close position in the first octave above middle C { notes.insert(rootPitch + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, rootTpc); //ensure that notes fall under a specific range //for now this range is between 5*12 and 6*12 PitchMap intervals = getIntervals(rootTpc, literal); PitchMapIterator i(intervals); while (i.hasNext()) { i.next(); notes.insert((rootPitch + (i.key() % 128)) % PITCH_DELTA_OCTAVE + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value()); } } break; case Voicing::DROP_2: { //select 4 notes from list PitchMap intervals = normalizeNoteMap(getIntervals(rootTpc, literal), rootTpc, rootPitch, 4); PitchMapIterator i(intervals); i.toBack(); int counter = 0; //counter to drop the second note while (i.hasPrevious()) { i.previous(); if (++counter == 2) notes.insert(i.key() + (DEFAULT_OCTAVE-1)*PITCH_DELTA_OCTAVE, i.value()); else notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value()); } } break; case Voicing::THREE_NOTE: { //Insert 2 notes in the octave above middle C PitchMap intervals = normalizeNoteMap(getIntervals(rootTpc, literal), rootTpc, rootPitch, 2, true); PitchMapIterator i(intervals); i.next(); notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value()); i.next(); notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value()); } break; case Voicing::FOUR_NOTE: case Voicing::SIX_NOTE: //FALLTHROUGH { //four/six note voicing, drop every other note PitchMap relIntervals = getIntervals(rootTpc, literal); PitchMap intervals; if (voicing == Voicing::FOUR_NOTE) intervals = normalizeNoteMap(relIntervals, rootTpc, rootPitch, 3, true); else //voicing == Voicing::SIX_NOTE intervals = normalizeNoteMap(relIntervals, rootTpc, rootPitch, 5, true); PitchMapIterator i(intervals); i.toBack(); int counter = 0; //how many notes have been added while (i.hasPrevious()) { i.previous(); if (counter % 2) notes.insert(i.key() + (DEFAULT_OCTAVE-1)*PITCH_DELTA_OCTAVE, i.value()); else notes.insert(i.key() + DEFAULT_OCTAVE*PITCH_DELTA_OCTAVE, i.value()); ++counter; } } break; default: break; } return notes; } //--------------------------------------------------- // update /// updates the current note map, this is where all /// of the rhythm and voicing choices matter since /// the voicing algorithms depend on this. /// /// transposeOffset -- is the necessary adjustment /// that is added to the root and bass /// to get the correct sounding pitch /// /// TODO -: Don't worry about realized harmony for /// RNA harmony? //--------------------------------------------------- void RealizedHarmony::update(int rootTpc, int bassTpc, int transposeOffset /*= 0*/) { //on transposition the dirty flag is set by the harmony, but it's a little //bit risky design since these 3 parameters rely on the dirty bit and are not //otherwise checked by RealizedHarmony. This saves us 3 ints of space, but //has the added risk if (!_dirty) { Q_ASSERT(_harmony->harmonyType() != HarmonyType::STANDARD || (_notes.first() == rootTpc || _notes.first() == bassTpc)); return; } _notes = generateNotes(rootTpc, bassTpc, _literal, _voicing, transposeOffset); _dirty = false; } //-------------------------------------------------- // getActualDuration /// gets the fraction duration for how long /// the harmony should be realized based /// on the HDuration set. /// /// This is opposed to RealizedHarmony::duration() /// which returns the HDuration, which is the duration /// setting. /// /// Specifying a parameter finds a duration based on the /// passed duration type while not specifying a parameter /// uses the setting set by the user for the specific /// harmony object. //-------------------------------------------------- Fraction RealizedHarmony::getActualDuration(HDuration durationType) const { HDuration dur; if (durationType != HDuration::INVALID) dur = durationType; else dur = _duration; switch (dur) { case HDuration::UNTIL_NEXT_CHORD_SYMBOL: return _harmony->ticksTilNext(false); break; case HDuration::STOP_AT_MEASURE_END: return _harmony->ticksTilNext(true); break; case HDuration::SEGMENT_DURATION: return toSegment(_harmony->parent())->ticks(); break; default: return Fraction(0, 1); } } //--------------------------------------------------- // getIntervals /// gets a weighted map from intervals to TPCs based on /// a passed root tpc (this allows for us to /// keep pitches, but transpose notes on the score) /// /// Weighting System: /// - Rank 0: 3rd, altered 5th, suspensions (characteristic notes) /// - Rank 1: 7th /// - Rank 2: 9ths /// - Rank 3: 13ths where applicable and (in minor chords) 11ths /// - Rank 3: Other alterations and additions /// - Rank 4: 5th and (in major/dominant chords) 11th //--------------------------------------------------- RealizedHarmony::PitchMap RealizedHarmony::getIntervals(int rootTpc, bool literal) const { //RANKING SYSTEM static const int RANK_MULT = 128; //used as multiplier and mod since MIDI pitch goes from 0-127 static const int RANK_3RD = 0; static const int RANK_7TH = 1; static const int RANK_9TH = 2; static const int RANK_ADD = 3; static const int RANK_OMIT = 4; static const int FIFTH = 7 + RANK_MULT*RANK_OMIT; PitchMap ret; const ParsedChord* p = _harmony->parsedForm(); QString quality = p->quality(); int ext = p->extension().toInt(); const QStringList& modList = p->modifierList(); int omit = 0; //omit flags for which notes to omit (for notes that are altered //or specified to be omitted as a modification) so that they //are not later added bool alt5 = false; //altered 5 //handle modifiers for (QString s : modList) { //find number, split up mods bool modded = false; for (int c = 0; c < s.length(); ++c) { if (s[c].isDigit()) { Q_ASSERT(c > 0); //we shouldn't have just a number int alter = 0; int cutoff = c; int deg = s.right(s.length() - c).toInt(); //account for if the flat/sharp is stuck to the end of add if (s[c-1] == '#') { cutoff -= 1; alter = +1; } else if (s[c-1] == 'b') { cutoff -= 1; alter = -1; } QString extType = s.left(cutoff); if (extType == "" || extType == "major") { //alteration if (deg == 9) ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, deg, alter)); else ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, deg, alter)); if (deg == 5) alt5 = true; omit |= 1 << deg; modded = true; } else if (extType == "sus") { ret.insert(step2pitchInterval(deg, alter) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, deg, alter)); omit |= 1 << 3; modded = true; } else if (extType == "no") { omit |= 1 << deg; modded = true; } } } //check for special chords, if we haven't modded anything yet there is a special or incomprehensible modifier if (!modded) { if (s == "phryg") { ret.insert(step2pitchInterval(9, -1) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, -1)); omit |= 1 << 9; } else if (s == "lyd") { ret.insert(step2pitchInterval(11, +1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 11, +1)); omit |= 1 << 11; } else if (s == "blues") { ret.insert(step2pitchInterval(9, +1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 9, +1)); omit |= 1 << 9; } else if (s == "alt") { ret.insert(step2pitchInterval(5, -1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 5, -1)); ret.insert(step2pitchInterval(5, +1) + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 5, +1)); omit |= 1 << 5; ret.insert(step2pitchInterval(9, -1) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, -1)); ret.insert(step2pitchInterval(9, +1) + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, +1)); omit |= 1 << 9; } else //no easy way to realize tristan chords since they are more analytical tools omit = ~0; } } //handle ext = 5: power chord so omit the 3rd if (ext == 5) omit |= 1 << 3; //handle chord quality if (quality == "minor") { if (!(omit & (1 << 3))) ret.insert(step2pitchInterval(3, -1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, -1)); //min3 if (!(omit & (1 << 5))) ret.insert(step2pitchInterval(5, 0) + RANK_MULT*RANK_OMIT, tpcInterval(rootTpc, 5, 0)); //p5 } else if (quality == "augmented") { if (!(omit & (1 << 3))) ret.insert(step2pitchInterval(3, 0) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, 0)); //maj3 if (!(omit & (1 << 5))) ret.insert(step2pitchInterval(5, +1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 5, +1)); //p5 } else if (quality == "diminished" || quality == "half-diminished") { if (!(omit & (1 << 3))) ret.insert(step2pitchInterval(3, -1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, -1)); //min3 if (!(omit & (1 << 5))) ret.insert(step2pitchInterval(5, -1) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 5, -1)); //dim5 alt5 = true; } else { //major or dominant if (!(omit & (1 << 3))) ret.insert(step2pitchInterval(3, 0) + RANK_MULT*RANK_3RD, tpcInterval(rootTpc, 3, 0)); //maj3 if (!(omit & (1 << 5))) ret.insert(step2pitchInterval(5, 0) + RANK_MULT*RANK_OMIT, tpcInterval(rootTpc, 5, 0)); //p5 } //handle extension switch (ext) { case 13: if (!(omit & (1 << 13))) { ret.insert(9 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 13, 0)); //maj13 omit |= 1 << 13; } // FALLTHROUGH case 11: if (!(omit & (1 << 11))) { if (quality == "minor") ret.insert(5 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 11, 0)); //maj11 else if (literal) ret.insert(5 + RANK_MULT*RANK_OMIT, tpcInterval(rootTpc, 11, 0)); //maj11 omit |= 1 << 11; } // FALLTHROUGH case 9: if (!(omit & (1 << 9))) { ret.insert(2 + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, 0)); //maj9 omit |= 1 << 9; } // FALLTHROUGH case 7: if (!(omit & (1 << 7))) { if (quality == "major") ret.insert(11 + RANK_MULT*RANK_7TH, tpcInterval(rootTpc, 7, 0)); else if (quality == "diminished") ret.insert(9 + RANK_MULT*RANK_7TH, tpcInterval(rootTpc, 7, -2)); else //dominant or augmented or minor ret.insert(10 + RANK_MULT*RANK_7TH, tpcInterval(rootTpc, 7, -1)); } break; case 6: if (!(omit & (1 << 6))) { ret.insert(9 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 6, 0)); //maj6 omit |= 1 << 13; //no need to add/alter 6 chords } break; case 5: //omitted third already break; case 4: if (!(omit & (1 << 4))) ret.insert(5 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 4, 0)); //p4 break; case 2: if (!(omit & (1 << 2))) ret.insert(2 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 2, 0)); //maj2 omit |= 1 << 9; //make sure we don't add altered 9 when theres a 2 break; case 69: ret.insert(9 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 6, 0)); //maj6 ret.insert(2 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 9, 0)); //maj6 omit = ~0; //no additions/alterations for a 69 chord break; default: break; } Harmony* next = _harmony->findNext(); if (!_literal && next && tpcIsValid(next->rootTpc())) { //jazz interpretation QString qNext = next->parsedForm()->quality(); //pitch from current to next harmony normalized to a range between 0 and 12 //add PITCH_DELTA_OCTAVE before modulo so that we can ensure arithmetic mod rather than computer mod //int keyTpc = int(next->staff()->key(next->tick())) + 14; //tpc of key (ex. F# major would be Tpc::F_S) //int keyTpcMinor = keyTpc + 3; int pitchBetween = (tpc2pitch(next->rootTpc()) + PITCH_DELTA_OCTAVE - tpc2pitch(rootTpc)) % PITCH_DELTA_OCTAVE; bool maj7 = qNext == "major" && next->parsedForm()->extension() >= 7; //whether or not the next chord has major 7 //commented code: dont add 9 for diminished chords if (!(omit & (1 << 9))) {// && !(alt5 && (quality == "minor" || quality == "diminished" || quality == "half-diminished"))) { if (quality == "dominant" && pitchBetween == 5 && (qNext == "minor" || maj7)) { //flat 9 when resolving a fourth up to a minor chord or major 7th ret.insert(1 + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, -1)); } else //add major 9 ret.insert(2 + RANK_MULT*RANK_9TH, tpcInterval(rootTpc, 9, 0)); } if (!(omit & (1 << 13)) && !alt5) { if (quality == "dominant" && pitchBetween == 5 && (qNext == "minor" || false)) { //flat 13 for dominant to chord a P4 up //only for minor chords for now ret.remove(FIFTH); ret.insert(8 + RANK_MULT*RANK_ADD, tpcInterval(rootTpc, 13, -1)); } //major 13 considered, but too dependent on melody and voicing of other chord //no implementation for now } } return ret; } //--------------------------------------------------- // normalizeNoteMap /// normalize the pitch map from intervals to create pitches between 0 and 12 /// and resolve any weighting system. /// /// enforceMaxEquals - enforce the max as a goal so that the max is how many notes is inserted //--------------------------------------------------- RealizedHarmony::PitchMap RealizedHarmony::normalizeNoteMap(const PitchMap& intervals, int rootTpc, int rootPitch, int max, bool enforceMaxAsGoal) const { PitchMap ret; PitchMapIterator itr(intervals); for (int i = 0; i < max; ++i) { if (!itr.hasNext()) break; itr.next(); ret.insert((itr.key() % 128 + rootPitch) % PITCH_DELTA_OCTAVE, itr.value()); //128 is RANK_MULT } //redo insertions if we must have a specific number of notes with insertMulti if (enforceMaxAsGoal) { while (ret.size() < max) { ret.insertMulti(rootPitch, rootTpc); //duplicate root int size = max - ret.size(); itr = PitchMapIterator(intervals); //reset iterator for (int i = 0; i < size; ++i) { if (!itr.hasNext()) break; itr.next(); ret.insertMulti((itr.key() % 128 + rootPitch) % PITCH_DELTA_OCTAVE, itr.value()); } } } else if (ret.size() < max) //insert another root if we have room in general ret.insertMulti(rootPitch, rootTpc); return ret; } //--------------------------------------------------- // cascadeDirty /// cascades the dirty flag backwards so that everything is properly set /// this is required since voicing algorithms may look ahead /// /// NOTE: FOR NOW ALGORITHMS DO NOT LOOK BACKWARDS AND SO THERE IS NO /// REQUIREMENT TO CASCADE FORWARD, IN THE FUTURE IT MAY BECOME IMPORTANT /// TO CASCADE FORWARD AS WELL //--------------------------------------------------- void RealizedHarmony::cascadeDirty(bool dirty) { if (dirty && !_dirty) { //only cascade when we want to set our clean realized harmony to dirty Harmony* prev = _harmony->findPrev(); if (prev) prev->realizedHarmony().cascadeDirty(dirty); } _dirty = dirty; } }