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.
1119 lines
31 KiB
1119 lines
31 KiB
// -*- 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);
|
|
|
|
}
|
|
|
|
}
|