summaryrefslogtreecommitdiffstats
path: root/src/base/AnalysisTypes.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 18:37:05 +0000
commit145364a8af6a1fec06556221e66d4b724a62fc9a (patch)
tree53bd71a544008c518034f208d64c932dc2883f50 /src/base/AnalysisTypes.cpp
downloadrosegarden-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.cpp1118
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);
+
+}
+
+}