diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 18:37:05 +0000 |
commit | 145364a8af6a1fec06556221e66d4b724a62fc9a (patch) | |
tree | 53bd71a544008c518034f208d64c932dc2883f50 /src/base/AnalysisTypes.cpp | |
download | rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.tar.gz rosegarden-145364a8af6a1fec06556221e66d4b724a62fc9a.zip |
Added old abandoned KDE3 version of the RoseGarden MIDI tool
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/rosegarden@1097595 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/base/AnalysisTypes.cpp')
-rw-r--r-- | src/base/AnalysisTypes.cpp | 1118 |
1 files changed, 1118 insertions, 0 deletions
diff --git a/src/base/AnalysisTypes.cpp b/src/base/AnalysisTypes.cpp new file mode 100644 index 0000000..b2d8727 --- /dev/null +++ b/src/base/AnalysisTypes.cpp @@ -0,0 +1,1118 @@ +// -*- c-basic-offset: 4 -*- + +/* + Rosegarden + A sequencer and musical notation editor. + + This program is Copyright 2000-2008 + Guillaume Laurent <glaurent@telegraph-road.org>, + Chris Cannam <cannam@all-day-breakfast.com>, + Richard Bown <bownie@bownie.com> + + This file is Copyright 2002 + Randall Farmer <rfarme@simons-rock.edu> + + The moral right of the authors to claim authorship of this work + has been asserted. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include <iostream> +#include <string> +#include <map> +#include <algorithm> +#include <cmath> // fabs, pow + +#include "NotationTypes.h" +#include "AnalysisTypes.h" +#include "Event.h" +#include "Segment.h" +#include "CompositionTimeSliceAdapter.h" +#include "BaseProperties.h" +#include "Composition.h" +#include "Profiler.h" + +#include "Sets.h" +#include "Quantizer.h" + + +namespace Rosegarden +{ + +using std::string; +using std::cerr; +using std::endl; +using std::multimap; +using std::vector; +using std::partial_sort_copy; + +/////////////////////////////////////////////////////////////////////////// +// Miscellany (doesn't analyze anything) +/////////////////////////////////////////////////////////////////////////// + +Key +AnalysisHelper::getKeyForEvent(Event *e, Segment &s) +{ + Segment::iterator i = + e ? s.findNearestTime(e->getAbsoluteTime()) //cc + : s.begin(); + + if (i==s.end()) return Key(); + + // This is an ugly loop. Is there a better way to iterate backwards + // through an STL container? + + while (true) { + if ((*i)->isa(Key::EventType)) { + return Key(**i); + } + if (i != s.begin()) --i; + else break; + } + + return Key(); +} + +/////////////////////////////////////////////////////////////////////////// +// Simple chord identification +/////////////////////////////////////////////////////////////////////////// + +void +AnalysisHelper::labelChords(CompositionTimeSliceAdapter &c, Segment &s, + const Rosegarden::Quantizer *quantizer) +{ + + Key key; + if (c.begin() != c.end()) key = getKeyForEvent(*c.begin(), s); + else key = getKeyForEvent(0, s); + + Profiler profiler("AnalysisHelper::labelChords", true); + + for (CompositionTimeSliceAdapter::iterator i = c.begin(); i != c.end(); ++i) { + + timeT time = (*i)->getAbsoluteTime(); + +// std::cerr << "AnalysisHelper::labelChords: time is " << time << ", type is " << (*i)->getType() << ", event is " << *i << " (itr is " << &i << ")" << std::endl; + + if ((*i)->isa(Key::EventType)) { + key = Key(**i); + Text text(key.getName(), Text::KeyName); + s.insert(text.getAsEvent(time)); + continue; + } + + if ((*i)->isa(Note::EventType)) { + + int bass = 999; + int mask = 0; + + GlobalChord chord(c, i, quantizer); + if (chord.size() == 0) continue; + + for (GlobalChord::iterator j = chord.begin(); j != chord.end(); ++j) { + long pitch = 999; + if ((**j)->get<Int>(BaseProperties::PITCH, pitch)) { + if (pitch < bass) { + assert(bass == 999); // should be in ascending order already + bass = pitch; + } + mask |= 1 << (pitch % 12); + } + } + + i = chord.getFinalElement(); + + if (mask == 0) continue; + + ChordLabel ch(key, mask, bass); + + if (ch.isValid()) + { + //std::cerr << ch.getName(key) << " at time " << time << std::endl; + + Text text(ch.getName(key), Text::ChordName); + s.insert(text.getAsEvent(time)); + } + } + + } +} + + +// ChordLabel +///////////////////////////////////////////////// + +ChordLabel::ChordMap ChordLabel::m_chordMap; + +ChordLabel::ChordLabel() +{ + checkMap(); +} + +ChordLabel::ChordLabel(Key key, int mask, int /* bass */) : + m_data() +{ + checkMap(); + + // Look for a chord built on an unaltered scale step of the current key. + + for (ChordMap::iterator i = m_chordMap.find(mask); + i != m_chordMap.end() && i->first==mask; ++i) + { + + if (Pitch(i->second.m_rootPitch).isDiatonicInKey(key)) + { + m_data = i->second; + } + + } + + /* + int rootBassInterval = ((bass - m_data.m_rootPitch + 12) % 12); + + // Pretend nobody cares about second and third inversions + // (i.e., bass must always be either root or third of chord) + if (rootBassInterval > 7) m_data.m_type=ChordTypes::NoChord; + else if (rootBassInterval > 4) m_data.m_type=ChordTypes::NoChord; + // Mark first-inversion and root-position chords as such + else if (rootBassInterval > 0) m_data.m_inversion=1; + else m_data.m_inversion=0; + */ + +} + +std::string +ChordLabel::getName(Key key) const +{ + return Pitch(m_data.m_rootPitch).getAsString(key.isSharp(), false) + + m_data.m_type; + // + (m_data.m_inversion>0 ? " in first inversion" : ""); +} + +int +ChordLabel::rootPitch() +{ + return m_data.m_rootPitch; +} + +bool +ChordLabel::isValid() const +{ + return m_data.m_type != ChordTypes::NoChord; +} + +bool +ChordLabel::operator<(const ChordLabel& other) const +{ + if (!isValid()) return true; + return getName(Key()) < other.getName(Key()); +} + +bool +ChordLabel::operator==(const ChordLabel& other) const +{ + return getName(Key()) == other.getName(Key()); +} + +void +ChordLabel::checkMap() +{ + if (!m_chordMap.empty()) return; + + const ChordType basicChordTypes[8] = + {ChordTypes::Major, ChordTypes::Minor, ChordTypes::Diminished, + ChordTypes::MajorSeventh, ChordTypes::DominantSeventh, + ChordTypes::MinorSeventh, ChordTypes::HalfDimSeventh, + ChordTypes::DimSeventh}; + + // What the basicChordMasks mean: each bit set in each one represents + // a pitch class (pitch%12) in a chord. C major has three pitch + // classes, C, E, and G natural; if you take the MIDI pitches + // of those notes modulo 12, you get 0, 4, and 7, so the mask for + // major triads is (1<<0)+(1<<4)+(1<<7). All the masks are for chords + // built on C. + + const int basicChordMasks[8] = + { + 1 + (1<<4) + (1<<7), // major + 1 + (1<<3) + (1<<7), // minor + 1 + (1<<3) + (1<<6), // diminished + 1 + (1<<4) + (1<<7) + (1<<11), // major 7th + 1 + (1<<4) + (1<<7) + (1<<10), // dominant 7th + 1 + (1<<3) + (1<<7) + (1<<10), // minor 7th + 1 + (1<<3) + (1<<6) + (1<<10), // half-diminished 7th + 1 + (1<<3) + (1<<6) + (1<<9) // diminished 7th + }; + + // Each mask is inserted into the map rotated twelve ways; each + // rotation is a mask you would get by transposing the chord + // to have a new root (i.e., C, C#, D, D#, E, F...) + + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 12; ++j) + { + + m_chordMap.insert + ( + std::pair<int, ChordData> + ( + (basicChordMasks[i] << j | basicChordMasks[i] >> (12-j)) + & ((1<<12) - 1), + ChordData(basicChordTypes[i], j) + ) + ); + + } + } + +} + +/////////////////////////////////////////////////////////////////////////// +// Harmony guessing +/////////////////////////////////////////////////////////////////////////// + +void +AnalysisHelper::guessHarmonies(CompositionTimeSliceAdapter &c, Segment &s) +{ + HarmonyGuessList l; + + // 1. Get the list of possible harmonies + makeHarmonyGuessList(c, l); + + // 2. Refine the list of possible harmonies by preferring chords in the + // current key and looking for familiar progressions and + // tonicizations. + refineHarmonyGuessList(c, l, s); + + // 3. Put labels in the Segment. For the moment we just do the + // really naive thing with the segment arg to refineHarmonyGuessList: + // could do much better here +} + +// #### explain how this works: +// in terms of other functions (simple chord labelling, key guessing) +// in terms of basic concepts (pitch profile, harmony guess) +// in terms of flow + +void +AnalysisHelper::makeHarmonyGuessList(CompositionTimeSliceAdapter &c, + HarmonyGuessList &l) +{ + if (*c.begin() == *c.end()) return; + + checkHarmonyTable(); + + PitchProfile p; // defaults to all zeroes + TimeSignature timeSig; + timeT timeSigTime = 0; + timeT nextSigTime = (*c.begin())->getAbsoluteTime(); + + // Walk through the piece labelChords style + + // no increment (the first inner loop does the incrementing) + for (CompositionTimeSliceAdapter::iterator i = c.begin(); i != c.end(); ) + { + + // 2. Update the pitch profile + + timeT time = (*i)->getAbsoluteTime(); + + if (time >= nextSigTime) { + Composition *comp = c.getComposition(); + int sigNo = comp->getTimeSignatureNumberAt(time); + if (sigNo >= 0) { + std::pair<timeT, TimeSignature> sig = comp->getTimeSignatureChange(sigNo); + timeSigTime = sig.first; + timeSig = sig.second; + } + if (sigNo < comp->getTimeSignatureCount() - 1) { + nextSigTime = comp->getTimeSignatureChange(sigNo + 1).first; + } else { + nextSigTime = comp->getEndMarker(); + } + } + + double emphasis = + double(timeSig.getEmphasisForTime(time - timeSigTime)); + + // Scale all the components of the pitch profile down so that + // 1. notes that are a beat/bar away have less weight than notes + // from this beat/bar + // 2. the difference in weight depends on the metrical importance + // of the boundary between the notes: the previous beat should + // get less weight if this is the first beat of a new bar + + // ### possibly too much fade here + // also, fade should happen w/reference to how many notes played? + + PitchProfile delta; + int noteCount = 0; + + // no initialization + for ( ; i != c.end() && (*i)->getAbsoluteTime() == time; ++i) + { + if ((*i)->isa(Note::EventType)) + { + try { + int pitch = (*i)->get<Int>(BaseProperties::PITCH); + delta[pitch % 12] += 1 << int(emphasis); + ++noteCount; + } catch (...) { + std::cerr << "No pitch for note at " << time << "!" << std::endl; + } + } + } + + p *= 1. / (pow(2, emphasis) + 1 + noteCount); + p += delta; + + // 1. If there could have been a chord change, compare the current + // pitch profile with all of the profiles in the table to figure + // out which chords we are now nearest. + + // (If these events weren't on a beat boundary, assume there was no + // chord change and continue -- ### will need this back) +/* if ((!(i != c.end())) || + timeSig.getEmphasisForTime((*i)->getAbsoluteTime() - timeSigTime) < 3) + { + continue; + }*/ + + // (If the pitch profile hasn't changed much, continue) + + PitchProfile np = p.normalized(); + + HarmonyGuess possibleChords; + + possibleChords.reserve(m_harmonyTable.size()); + + for (HarmonyTable::iterator j = m_harmonyTable.begin(); + j != m_harmonyTable.end(); + ++j) + { + double score = np.productScorer(j->first); + possibleChords.push_back(ChordPossibility(score, j->second)); + } + + // 3. Save a short list of the nearest chords in the + // HarmonyGuessList passed in from guessHarmonies() + + l.push_back(std::pair<timeT, HarmonyGuess>(time, HarmonyGuess())); + + HarmonyGuess& smallerGuess = l.back().second; + + // Have to learn to love this: + + smallerGuess.resize(10); + + partial_sort_copy(possibleChords.begin(), + possibleChords.end(), + smallerGuess.begin(), + smallerGuess.end(), + cp_less()); + +#ifdef GIVE_HARMONYGUESS_DETAILS + std::cerr << "Time: " << time << std::endl; + + std::cerr << "Profile: "; + for (int k = 0; k < 12; ++k) + std::cerr << np[k] << " "; + std::cerr << std::endl; + + std::cerr << "Best guesses: " << std::endl; + for (HarmonyGuess::iterator debugi = smallerGuess.begin(); + debugi != smallerGuess.end(); + ++debugi) + { + std::cerr << debugi->first << ": " << debugi->second.getName(Key()) << std::endl; + } +#endif + + } + +} + +// Comparison function object -- can't declare this in the headers because +// this only works with pair<double, ChordLabel> instantiated, +// pair<double, ChordLabel> can't be instantiated while ChordLabel is an +// incomplete type, and ChordLabel is still an incomplete type at that +// point in the headers. + +bool +AnalysisHelper::cp_less::operator()(ChordPossibility l, ChordPossibility r) +{ + // Change name from "less?" + return l.first > r.first; +} + + +void +AnalysisHelper::refineHarmonyGuessList(CompositionTimeSliceAdapter &/* c */, + HarmonyGuessList &l, Segment &segment) +{ + // (Fetch the piece's starting key from the key guesser) + Key key; + + checkProgressionMap(); + + if (l.size() < 2) + { + l.clear(); + return; + } + + // Look at the list of harmony guesses two guesses at a time. + + HarmonyGuessList::iterator i = l.begin(); + // j stays ahead of i + HarmonyGuessList::iterator j = i + 1; + + ChordLabel bestGuessForFirstChord, bestGuessForSecondChord; + while (j != l.end()) + { + + double highestScore = 0; + + // For each possible pair of chords (i.e., two for loops here) + for (HarmonyGuess::iterator k = i->second.begin(); + k != i->second.end(); + ++k) + { + for (HarmonyGuess::iterator l = j->second.begin(); + l != j->second.end(); + ++l) + { + // Print the guess being processed: + + // std::cerr << k->second.getName(Key()) << "->" << l->second.getName(Key()) << std::endl; + + // For a first approximation, let's say the probability that + // a chord guess is correct is proportional to its score. Then + // the probability that a pair is correct is the product of + // its scores. + + double currentScore; + currentScore = k->first * l->first; + + // std::cerr << currentScore << std::endl; + + // Is this a familiar progression? Bonus if so. + + bool isFamiliar = false; + + // #### my ways of breaking up long function calls are haphazard + // also, does this code belong here? + + ProgressionMap::iterator pmi = + m_progressionMap.lower_bound( + ChordProgression(k->second, l->second) + ); + + // no initialization + for ( ; + pmi != m_progressionMap.end() + && pmi->first == k->second + && pmi->second == l->second; + ++pmi) + { + // key doesn't have operator== defined + if (key.getName() == pmi->homeKey.getName()) + { +// std::cerr << k->second.getName(Key()) << "->" << l->second.getName(Key()) << " is familiar" << std::endl; + isFamiliar = true; + break; + } + } + + if (isFamiliar) currentScore *= 5; // #### arbitrary + + // (Are voice-leading rules followed? Penalty if not) + + // Is this better than any pair examined so far? If so, set + // some variables that should end up holding the best chord + // progression + if (currentScore > highestScore) + { + bestGuessForFirstChord = k->second; + bestGuessForSecondChord = l->second; + highestScore = currentScore; + } + + } + } + + // Since we're not returning any results right now, print them + std::cerr << "Time: " << j->first << std::endl; + std::cerr << "Best chords: " + << bestGuessForFirstChord.getName(Key()) << ", " + << bestGuessForSecondChord.getName(Key()) << std::endl; + std::cerr << "Best score: " << highestScore << std::endl; + + // Using the best pair of chords: + + // Is the first chord diatonic? + + // If not, is it a secondary function? + // If so, change the current key + // If not, set an "implausible progression" flag + + // (Is the score of the best pair of chords reasonable? + // If not, set the flag.) + + // Was the progression plausible? + + // If so, replace the ten or so chords in the first guess with the + // first chord of the best pair, set + // first-iterator=second-iterator, and ++second-iterator + // (and possibly do the real key-setting) + + // If not, h.erase(second-iterator++) + + // Temporary hack to get _something_ interesting out: + Event *e; + e = Text(bestGuessForFirstChord.getName(Key()), Text::ChordName). + getAsEvent(j->first); + segment.insert(new Event(*e, e->getAbsoluteTime(), + e->getDuration(), e->getSubOrdering()-1)); + delete e; + + e = Text(bestGuessForSecondChord.getName(Key()), Text::ChordName). + getAsEvent(j->first); + segment.insert(e); + + // For now, just advance: + i = j; + ++j; + } +} + +AnalysisHelper::HarmonyTable AnalysisHelper::m_harmonyTable; + +void +AnalysisHelper::checkHarmonyTable() +{ + if (!m_harmonyTable.empty()) return; + + // Identical to basicChordTypes in ChordLabel::checkMap + const ChordType basicChordTypes[8] = + {ChordTypes::Major, ChordTypes::Minor, ChordTypes::Diminished, + ChordTypes::MajorSeventh, ChordTypes::DominantSeventh, + ChordTypes::MinorSeventh, ChordTypes::HalfDimSeventh, + ChordTypes::DimSeventh}; + + // Like basicChordMasks in ChordLabel::checkMap(), only with + // ints instead of bits + const int basicChordProfiles[8][12] = + { + // 0 1 2 3 4 5 6 7 8 9 10 11 + {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0}, // major + {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}, // minor + {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0}, // diminished + {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1}, // major 7th + {1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0}, // dominant 7th + {1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0}, // minor 7th + {1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0}, // half-diminished 7th + {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0} // diminished 7th + }; + + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 12; ++j) + { + PitchProfile p; + + for (int k = 0; k < 12; ++k) + p[(j + k) % 12] = (basicChordProfiles[i][k] == 1) + ? 1. + : -1.; + + PitchProfile np = p.normalized(); + + ChordLabel c(basicChordTypes[i], j); + + m_harmonyTable.push_back(std::pair<PitchProfile, ChordLabel>(np, c)); + } + } + +} + +AnalysisHelper::ProgressionMap AnalysisHelper::m_progressionMap; + +void +AnalysisHelper::checkProgressionMap() +{ + if (!m_progressionMap.empty()) return; + // majorProgressionFirsts[0] = 5, majorProgressionSeconds[0]=1, so 5->1 is + // a valid progression. Note that the chord numbers are 1-based, like the + // Roman numeral symbols + const int majorProgressionFirsts[9] = + {5, 2, 6, 3, 7, 4, 4, 3, 5}; + const int majorProgressionSeconds[9] = + {1, 5, 2, 6, 1, 2, 5, 4, 6}; + + // For each major key + for (int i = 0; i < 12; ++i) + { + // Make the key + Key k(0, false); // tonicPitch = i, isMinor = false + // Add the common progressions + for (int j = 0; j < 9; ++j) + { + std::cerr << majorProgressionFirsts[j] << ", " << majorProgressionSeconds[j] << std::endl; + addProgressionToMap(k, + majorProgressionFirsts[j], + majorProgressionSeconds[j]); + } + // Add I->everything + for (int j = 1; j < 8; ++j) + { + addProgressionToMap(k, 1, j); + } + // (Add the progressions involving seventh chords) + // (Add I->seventh chords) + } + // (For each minor key) + // (Do what we just did for major keys) + +} + +void +AnalysisHelper::addProgressionToMap(Key k, + int firstChordNumber, + int secondChordNumber) +{ + // majorScalePitches[1] should be the pitch of the first step of + // the scale, so there's padding at the beginning of both these + // arrays. + const int majorScalePitches[] = {0, 0, 2, 4, 5, 7, 9, 11}; + const ChordType majorDiationicTriadTypes[] = + {ChordTypes::NoChord, ChordTypes::Major, ChordTypes::Minor, + ChordTypes::Minor, ChordTypes::Major, ChordTypes::Major, + ChordTypes::Minor, ChordTypes::Diminished}; + + int offset = k.getTonicPitch(); + + if (!k.isMinor()) + { + ChordLabel firstChord + ( + majorDiationicTriadTypes[firstChordNumber], + (majorScalePitches[firstChordNumber] + offset) % 12 + ); + ChordLabel secondChord + ( + majorDiationicTriadTypes[secondChordNumber], + (majorScalePitches[secondChordNumber] + offset) % 12 + ); + ChordProgression p(firstChord, secondChord, k); + m_progressionMap.insert(p); + } + // else handle minor-key chords + +} + +// AnalysisHelper::ChordProgression +///////////////////////////////////////////////// + +AnalysisHelper::ChordProgression::ChordProgression(ChordLabel first_, + ChordLabel second_, + Key key_) : + first(first_), + second(second_), + homeKey(key_) +{ + // nothing else +} + +bool +AnalysisHelper::ChordProgression::operator<(const AnalysisHelper::ChordProgression& other) const +{ + // no need for: + // if (first == other.first) return second < other.second; + return first < other.first; +} + +// AnalysisHelper::PitchProfile +///////////////////////////////////////////////// + +AnalysisHelper::PitchProfile::PitchProfile() +{ + for (int i = 0; i < 12; ++i) m_data[i] = 0; +} + +double& +AnalysisHelper::PitchProfile::operator[](int i) +{ + return m_data[i]; +} + +const double& +AnalysisHelper::PitchProfile::operator[](int i) const +{ + return m_data[i]; +} + +double +AnalysisHelper::PitchProfile::distance(const PitchProfile &other) +{ + double distance = 0; + + for (int i = 0; i < 12; ++i) + { + distance += fabs(other[i] - m_data[i]); + } + + return distance; +} + +double +AnalysisHelper::PitchProfile::dotProduct(const PitchProfile &other) +{ + double product = 0; + + for (int i = 0; i < 12; ++i) + { + product += other[i] * m_data[i]; + } + + return product; +} + +double +AnalysisHelper::PitchProfile::productScorer(const PitchProfile &other) +{ + double cumulativeProduct = 1; + double numbersInProduct = 0; + + for (int i = 0; i < 12; ++i) + { + if (other[i] > 0) + { + cumulativeProduct *= m_data[i]; + ++numbersInProduct; + } + } + + if (numbersInProduct > 0) + return pow(cumulativeProduct, 1/numbersInProduct); + + return 0; +} + +AnalysisHelper::PitchProfile +AnalysisHelper::PitchProfile::normalized() +{ + double size = 0; + PitchProfile normedProfile; + + for (int i = 0; i < 12; ++i) + { + size += fabs(m_data[i]); + } + + if (size == 0) size = 1; + + for (int i = 0; i < 12; ++i) + { + normedProfile[i] = m_data[i] / size; + } + + return normedProfile; +} + +AnalysisHelper::PitchProfile& +AnalysisHelper::PitchProfile::operator*=(double d) +{ + + for (int i = 0; i < 12; ++i) + { + m_data[i] *= d; + } + + return *this; +} + +AnalysisHelper::PitchProfile& +AnalysisHelper::PitchProfile::operator+=(const PitchProfile& d) +{ + + for (int i = 0; i < 12; ++i) + { + m_data[i] += d[i]; + } + + return *this; +} + +/////////////////////////////////////////////////////////////////////////// +// Time signature guessing +/////////////////////////////////////////////////////////////////////////// + +// #### this is too long +// should use constants for basic lengths, not numbers + +TimeSignature +AnalysisHelper::guessTimeSignature(CompositionTimeSliceAdapter &c) +{ + bool haveNotes = false; + + // 1. Guess the duration of the beat. The right beat length is going + // to be a common note length, and beat boundaries should be likely + // to have notes starting on them. + + vector<int> beatScores(4, 0); + + // durations of quaver, dotted quaver, crotchet, dotted crotchet: + static const int commonBeatDurations[4] = {48, 72, 96, 144}; + + int j = 0; + for (CompositionTimeSliceAdapter::iterator i = c.begin(); + i != c.end() && j < 100; + ++i, ++j) + { + + // Skip non-notes + if (!(*i)->isa(Note::EventType)) continue; + haveNotes = true; + + for (int k = 0; k < 4; ++k) + { + + // Points for any note of the right length + if ((*i)->getDuration() == commonBeatDurations[k]) + beatScores[k]++; + + // Score for the probability that a note starts on a beat + // boundary. + + // Normally, to get the probability that a random beat boundary + // has a note on it, you'd add a constant for each note on a + // boundary and divide by the number of beat boundaries. + // Instead, this multiplies the constant (1/24) by + // commonBeatDurations[k], which is inversely proportional to + // the number of beat boundaries. + + if ((*i)->getAbsoluteTime() % commonBeatDurations[k] == 0) + beatScores[k] += commonBeatDurations[k] / 24; + + } + + } + + if (!haveNotes) return TimeSignature(); + + int beatDuration = 0, + bestScore = 0; + + for (int j = 0; j < 4; ++j) + { + if (beatScores[j] >= bestScore) + { + bestScore = beatScores[j]; + beatDuration = commonBeatDurations[j]; + } + } + + // 2. Guess whether the measure has two, three or four beats. The right + // measure length should make notes rarely cross barlines and have a + // high average length for notes at the start of bars. + + vector<int> measureLengthScores(5, 0); + + for (CompositionTimeSliceAdapter::iterator i = c.begin(); + i != c.end() && j < 100; + ++i, ++j) + { + + if (!(*i)->isa(Note::EventType)) continue; + + // k is the guess at the number of beats in a measure + for (int k = 2; k < 5; ++k) + { + + // Determine whether this note crosses a barline; points for the + // measure length if it does NOT. + + int noteOffset = ((*i)->getAbsoluteTime() % (beatDuration * k)); + int noteEnd = noteOffset + (*i)->getDuration(); + if ( !(noteEnd > (beatDuration * k)) ) + measureLengthScores[k] += 10; + + + // Average length of notes at measure starts + + // Instead of dividing by the number of measure starts, this + // multiplies by the number of beats per measure, which is + // inversely proportional to the number of measure starts. + + if ((*i)->getAbsoluteTime() % (beatDuration * k) == 0) + measureLengthScores[k] += + (*i)->getDuration() * k / 24; + + } + + } + + int measureLength = 0; + + bestScore = 0; // reused from earlier + + for (int j = 2; j < 5; ++j) + { + if (measureLengthScores[j] >= bestScore) + { + bestScore = measureLengthScores[j]; + measureLength = j; + } + } + + // + // 3. Put the result in a TimeSignature object. + // + + int numerator = 0, denominator = 0; + + if (beatDuration % 72 == 0) + { + + numerator = 3 * measureLength; + + // 144 means the beat is a dotted crotchet, so the beat division + // is a quaver, so you want 8 on bottom + denominator = (144 * 8) / beatDuration; + + } + else + { + + numerator = measureLength; + + // 96 means that the beat is a crotchet, so you want 4 on bottom + denominator = (96 * 4) / beatDuration; + + } + + TimeSignature ts(numerator, denominator); + + return ts; + +} + +/////////////////////////////////////////////////////////////////////////// +// Key guessing +/////////////////////////////////////////////////////////////////////////// + +Key +AnalysisHelper::guessKey(CompositionTimeSliceAdapter &c) +{ + if (c.begin() == c.end()) return Key(); + + // 1. Figure out the distribution of emphasis over the twelve + // pitch clases in the first few bars. Pitches that occur + // more often have greater emphasis, and pitches that occur + // at stronger points in the bar have greater emphasis. + + vector<int> weightedNoteCount(12, 0); + TimeSignature timeSig; + timeT timeSigTime = 0; + timeT nextSigTime = (*c.begin())->getAbsoluteTime(); + + int j = 0; + for (CompositionTimeSliceAdapter::iterator i = c.begin(); + i != c.end() && j < 100; ++i, ++j) + { + timeT time = (*i)->getAbsoluteTime(); + + if (time >= nextSigTime) { + Composition *comp = c.getComposition(); + int sigNo = comp->getTimeSignatureNumberAt(time); + if (sigNo >= 0) { + std::pair<timeT, TimeSignature> sig = comp->getTimeSignatureChange(sigNo); + timeSigTime = sig.first; + timeSig = sig.second; + } + if (sigNo < comp->getTimeSignatureCount() - 1) { + nextSigTime = comp->getTimeSignatureChange(sigNo + 1).first; + } else { + nextSigTime = comp->getEndMarker(); + } + } + + // Skip any other non-notes + if (!(*i)->isa(Note::EventType)) continue; + + try { + // Get pitch, metric strength of this event + int pitch = (*i)->get<Int>(BaseProperties::PITCH)%12; + int emphasis = + 1 << timeSig.getEmphasisForTime((*i)->getAbsoluteTime() - timeSigTime); + + // Count notes + weightedNoteCount[pitch] += emphasis; + + } catch (...) { + std::cerr << "No pitch for note at " << time << "!" << std::endl; + } + } + + // 2. Figure out what key best fits the distribution of emphasis. + // Notes outside a piece's key are rarely heavily emphasized, + // and the tonic and dominant of the key are likely to appear. + + // This part is longer than it should be. + + int bestTonic = -1; + bool bestKeyIsMinor = false; + int lowestCost = 999999999; + + for (int k = 0; k < 12; ++k) + { + int cost = + // accidentals are costly + weightedNoteCount[(k + 1 ) % 12] + + weightedNoteCount[(k + 3 ) % 12] + + weightedNoteCount[(k + 6 ) % 12] + + weightedNoteCount[(k + 8 ) % 12] + + weightedNoteCount[(k + 10) % 12] + // tonic is very good + - weightedNoteCount[ k ] * 5 + // dominant is good + - weightedNoteCount[(k + 7 ) % 12]; + if (cost < lowestCost) + { + bestTonic = k; + lowestCost = cost; + } + } + + for (int k = 0; k < 12; ++k) + { + int cost = + // accidentals are costly + weightedNoteCount[(k + 1 ) % 12] + + weightedNoteCount[(k + 4 ) % 12] + + weightedNoteCount[(k + 6 ) % 12] + // no cost for raised steps 6/7 (k+9, k+11) + // tonic is very good + - weightedNoteCount[ k ] * 5 + // dominant is good + - weightedNoteCount[(k + 7 ) % 12]; + if (cost < lowestCost) + { + bestTonic = k; + bestKeyIsMinor = true; + lowestCost = cost; + } + } + + return Key(bestTonic, bestKeyIsMinor); + +} + +} |