You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rosegarden/src/base/AnalysisTypes.cpp

1117 lines
31 KiB

/*
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);
}
}