Logo Search packages:      
Sourcecode: rosegarden version File versions  Download package

AnalysisTypes.C

// -*- c-basic-offset: 4 -*-

/*
    Rosegarden-4
    A sequencer and musical notation editor.

    This program is Copyright 2000-2006
        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"


00044 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
00190 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
00204 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);

}

}

Generated by  Doxygen 1.6.0   Back to index