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.
2412 lines
75 KiB
2412 lines
75 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>
|
|
|
|
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 <cstdio> // needed for sprintf()
|
|
#include "NotationRules.h"
|
|
#include "NotationTypes.h"
|
|
#include "BaseProperties.h"
|
|
#include <iostream>
|
|
#include <cstdlib> // for atoi
|
|
#include <limits.h> // for SHRT_MIN
|
|
#include <cassert>
|
|
#include <sstream>
|
|
|
|
//dmm This will make everything excruciatingly slow if defined:
|
|
//#define DEBUG_PITCH
|
|
|
|
namespace Rosegarden
|
|
{
|
|
using std::string;
|
|
using std::vector;
|
|
using std::cout;
|
|
using std::cerr;
|
|
using std::endl;
|
|
|
|
// This is the fundamental definition of the resolution used throughout.
|
|
// It must be a multiple of 16, and should ideally be a multiple of 96.
|
|
static const timeT basePPQ = 960;
|
|
|
|
const int MIN_SUBORDERING = SHRT_MIN;
|
|
|
|
namespace Accidentals
|
|
{
|
|
/**
|
|
* NoAccidental means the accidental will be inferred
|
|
* based on the performance pitch and current key at the
|
|
* location of the note.
|
|
*/
|
|
const Accidental NoAccidental = "no-accidental";
|
|
|
|
const Accidental Sharp = "sharp";
|
|
const Accidental Flat = "flat";
|
|
const Accidental Natural = "natural";
|
|
const Accidental DoubleSharp = "double-sharp";
|
|
const Accidental DoubleFlat = "double-flat";
|
|
|
|
AccidentalList getStandardAccidentals() {
|
|
|
|
static Accidental a[] = {
|
|
NoAccidental, Sharp, Flat, Natural, DoubleSharp, DoubleFlat
|
|
};
|
|
|
|
static AccidentalList v;
|
|
if (v.size() == 0) {
|
|
for (unsigned int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
|
|
v.push_back(a[i]);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
int getPitchOffset(const Accidental &acc) {
|
|
if (acc == DoubleSharp) return 2;
|
|
else if (acc == Sharp) return 1;
|
|
else if (acc == Flat) return -1;
|
|
else if (acc == DoubleFlat) return -2;
|
|
else return 0;
|
|
}
|
|
|
|
Accidental getAccidental(int pitchChange) {
|
|
if (pitchChange == -2) return DoubleFlat;
|
|
if (pitchChange == -1) return Flat;
|
|
// Yielding 'Natural' will add a natural-sign even if not needed, so for now
|
|
// just return NoAccidental
|
|
if (pitchChange == 0) return NoAccidental;
|
|
if (pitchChange == 1) return Sharp;
|
|
if (pitchChange == 2) return DoubleSharp;
|
|
|
|
// if we're getting into triple flats/sharps, we're probably atonal
|
|
// and don't case if the accidental is simplified
|
|
return NoAccidental;
|
|
}
|
|
}
|
|
|
|
using namespace Accidentals;
|
|
|
|
|
|
namespace Marks
|
|
{
|
|
const Mark NoMark = "no-mark";
|
|
const Mark Accent = "accent";
|
|
const Mark Tenuto = "tenuto";
|
|
const Mark Staccato = "staccato";
|
|
const Mark Staccatissimo = "staccatissimo";
|
|
const Mark Marcato = "marcato";
|
|
const Mark Sforzando = getTextMark("sf");
|
|
const Mark Rinforzando = getTextMark("rf");
|
|
const Mark Trill = "trill";
|
|
const Mark LongTrill = "long-trill";
|
|
const Mark TrillLine = "trill-line";
|
|
const Mark Turn = "turn";
|
|
const Mark Pause = "pause";
|
|
const Mark UpBow = "up-bow";
|
|
const Mark DownBow = "down-bow";
|
|
|
|
const Mark Mordent = "mordent";
|
|
const Mark MordentInverted = "mordent-inverted";
|
|
const Mark MordentLong = "mordent-long";
|
|
const Mark MordentLongInverted = "mordent-long-inverted";
|
|
|
|
string getTextMark(string text) {
|
|
return string("text_") + text;
|
|
}
|
|
|
|
bool isTextMark(Mark mark) {
|
|
return string(mark).substr(0, 5) == "text_";
|
|
}
|
|
|
|
string getTextFromMark(Mark mark) {
|
|
if (!isTextMark(mark)) return string();
|
|
else return string(mark).substr(5);
|
|
}
|
|
|
|
string getFingeringMark(string fingering) {
|
|
return string("finger_") + fingering;
|
|
}
|
|
|
|
bool isFingeringMark(Mark mark) {
|
|
return string(mark).substr(0, 7) == "finger_";
|
|
}
|
|
|
|
string getFingeringFromMark(Mark mark) {
|
|
if (!isFingeringMark(mark)) return string();
|
|
else return string(mark).substr(7);
|
|
}
|
|
|
|
int getMarkCount(const Event &e) {
|
|
long markCount = 0;
|
|
e.get<Int>(BaseProperties::MARK_COUNT, markCount);
|
|
return markCount;
|
|
}
|
|
|
|
std::vector<Mark> getMarks(const Event &e) {
|
|
|
|
std::vector<Mark> marks;
|
|
|
|
long markCount = 0;
|
|
e.get<Int>(BaseProperties::MARK_COUNT, markCount);
|
|
if (markCount == 0) return marks;
|
|
|
|
for (long j = 0; j < markCount; ++j) {
|
|
|
|
Mark mark(Marks::NoMark);
|
|
(void)e.get<String>(BaseProperties::getMarkPropertyName(j), mark);
|
|
|
|
marks.push_back(mark);
|
|
}
|
|
|
|
return marks;
|
|
}
|
|
|
|
Mark getFingeringMark(const Event &e) {
|
|
|
|
long markCount = 0;
|
|
e.get<Int>(BaseProperties::MARK_COUNT, markCount);
|
|
if (markCount == 0) return NoMark;
|
|
|
|
for (long j = 0; j < markCount; ++j) {
|
|
|
|
Mark mark(Marks::NoMark);
|
|
(void)e.get<String>(BaseProperties::getMarkPropertyName(j), mark);
|
|
|
|
if (isFingeringMark(mark)) return mark;
|
|
}
|
|
|
|
return NoMark;
|
|
}
|
|
|
|
void addMark(Event &e, const Mark &mark, bool unique) {
|
|
if (unique && hasMark(e, mark)) return;
|
|
|
|
long markCount = 0;
|
|
e.get<Int>(BaseProperties::MARK_COUNT, markCount);
|
|
e.set<Int>(BaseProperties::MARK_COUNT, markCount + 1);
|
|
|
|
PropertyName markProperty = BaseProperties::getMarkPropertyName(markCount);
|
|
e.set<String>(markProperty, mark);
|
|
}
|
|
|
|
bool removeMark(Event &e, const Mark &mark) {
|
|
|
|
long markCount = 0;
|
|
e.get<Int>(BaseProperties::MARK_COUNT, markCount);
|
|
|
|
for (long j = 0; j < markCount; ++j) {
|
|
PropertyName pn(BaseProperties::getMarkPropertyName(j));
|
|
std::string m;
|
|
if (e.get<String>(pn, m) && m == mark) {
|
|
e.unset(pn);
|
|
while (j < markCount - 1) {
|
|
PropertyName npn(BaseProperties::getMarkPropertyName(j+1));
|
|
if (e.get<String>(npn, m)) {
|
|
e.set<String>( pn, m);
|
|
}
|
|
pn = npn;
|
|
++j;
|
|
}
|
|
e.set<Int>(BaseProperties::MARK_COUNT, markCount - 1);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool hasMark(const Event &e, const Mark &mark) {
|
|
long markCount = 0;
|
|
e.get<Int>(BaseProperties::MARK_COUNT, markCount);
|
|
|
|
for (long j = 0; j < markCount; ++j) {
|
|
std::string m;
|
|
if (e.get<String>(BaseProperties::getMarkPropertyName(j), m) && m == mark) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::vector<Mark> getStandardMarks() {
|
|
|
|
static Mark a[] = {
|
|
NoMark, Accent, Tenuto, Staccato, Staccatissimo, Marcato,
|
|
Sforzando, Rinforzando, Trill, LongTrill, TrillLine,
|
|
Turn, Pause, UpBow, DownBow,
|
|
Mordent, MordentInverted, MordentLong, MordentLongInverted
|
|
};
|
|
|
|
static std::vector<Mark> v;
|
|
if (v.size() == 0) {
|
|
for (unsigned int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
|
|
v.push_back(a[i]);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
}
|
|
|
|
using namespace Marks;
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Clef
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const string Clef::EventType = "clefchange";
|
|
const int Clef::EventSubOrdering = -250;
|
|
const PropertyName Clef::ClefPropertyName = "clef";
|
|
const PropertyName Clef::OctaveOffsetPropertyName = "octaveoffset";
|
|
const string Clef::Treble = "treble";
|
|
const string Clef::French = "french";
|
|
const string Clef::Soprano = "soprano";
|
|
const string Clef::Mezzosoprano = "mezzosoprano";
|
|
const string Clef::Alto = "alto";
|
|
const string Clef::Tenor = "tenor";
|
|
const string Clef::Baritone = "baritone";
|
|
const string Clef::Varbaritone = "varbaritone";
|
|
const string Clef::Bass = "bass";
|
|
const string Clef::Subbass = "subbass";
|
|
|
|
const Clef Clef::DefaultClef = Clef("treble");
|
|
|
|
Clef::Clef(const Event &e) :
|
|
m_clef(DefaultClef.m_clef),
|
|
m_octaveOffset(0)
|
|
{
|
|
if (e.getType() != EventType) {
|
|
std::cerr << Event::BadType
|
|
("Clef model event", EventType, e.getType()).getMessage()
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
|
|
std::string s;
|
|
e.get<String>(ClefPropertyName, s);
|
|
|
|
if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) {
|
|
std::cerr << BadClefName("No such clef as \"" + s + "\"").getMessage()
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
|
|
long octaveOffset = 0;
|
|
(void)e.get<Int>(OctaveOffsetPropertyName, octaveOffset);
|
|
|
|
m_clef = s;
|
|
m_octaveOffset = octaveOffset;
|
|
}
|
|
|
|
Clef::Clef(const std::string &s, int octaveOffset)
|
|
// throw (BadClefName)
|
|
{
|
|
if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) {
|
|
throw BadClefName("No such clef as \"" + s + "\"");
|
|
}
|
|
m_clef = s;
|
|
m_octaveOffset = octaveOffset;
|
|
}
|
|
|
|
Clef &Clef::operator=(const Clef &c)
|
|
{
|
|
if (this != &c) {
|
|
m_clef = c.m_clef;
|
|
m_octaveOffset = c.m_octaveOffset;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool Clef::isValid(const Event &e)
|
|
{
|
|
if (e.getType() != EventType) return false;
|
|
|
|
std::string s;
|
|
e.get<String>(ClefPropertyName, s);
|
|
if (s != Treble && s != Soprano && s != French && s != Mezzosoprano && s != Alto && s != Tenor && s != Baritone && s != Bass && s != Varbaritone && s != Subbass) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
int Clef::getTranspose() const
|
|
{
|
|
//!!! plus or minus?
|
|
return getOctave() * 12 - getPitchOffset();
|
|
}
|
|
|
|
int Clef::getOctave() const
|
|
{
|
|
if (m_clef == Treble || m_clef == French) return 0 + m_octaveOffset;
|
|
else if (m_clef == Bass || m_clef == Varbaritone || m_clef == Subbass) return -2 + m_octaveOffset;
|
|
else return -1 + m_octaveOffset;
|
|
}
|
|
|
|
int Clef::getPitchOffset() const
|
|
{
|
|
if (m_clef == Treble) return 0;
|
|
else if (m_clef == French) return -2;
|
|
else if (m_clef == Soprano) return -5;
|
|
else if (m_clef == Mezzosoprano) return -3;
|
|
else if (m_clef == Alto) return -1;
|
|
else if (m_clef == Tenor) return 1;
|
|
else if (m_clef == Baritone) return 3;
|
|
else if (m_clef == Varbaritone) return -4;
|
|
else if (m_clef == Bass) return -2;
|
|
else if (m_clef == Subbass) return 0;
|
|
else return -2;
|
|
}
|
|
|
|
int Clef::getAxisHeight() const
|
|
{
|
|
if (m_clef == Treble) return 2;
|
|
else if (m_clef == French) return 0;
|
|
else if (m_clef == Soprano) return 0;
|
|
else if (m_clef == Mezzosoprano) return 2;
|
|
else if (m_clef == Alto) return 4;
|
|
else if (m_clef == Tenor) return 6;
|
|
else if (m_clef == Baritone) return 8;
|
|
else if (m_clef == Varbaritone) return 4;
|
|
else if (m_clef == Bass) return 6;
|
|
else if (m_clef == Subbass) return 8;
|
|
else return 6;
|
|
}
|
|
|
|
Clef::ClefList
|
|
Clef::getClefs()
|
|
{
|
|
ClefList clefs;
|
|
clefs.push_back(Clef(Bass));
|
|
clefs.push_back(Clef(Varbaritone));
|
|
clefs.push_back(Clef(Subbass));
|
|
clefs.push_back(Clef(Baritone));
|
|
clefs.push_back(Clef(Tenor));
|
|
clefs.push_back(Clef(Alto));
|
|
clefs.push_back(Clef(Mezzosoprano));
|
|
clefs.push_back(Clef(Soprano));
|
|
clefs.push_back(Clef(French));
|
|
clefs.push_back(Clef(Treble));
|
|
return clefs;
|
|
}
|
|
|
|
Event *Clef::getAsEvent(timeT absoluteTime) const
|
|
{
|
|
Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
|
|
e->set<String>(ClefPropertyName, m_clef);
|
|
e->set<Int>(OctaveOffsetPropertyName, m_octaveOffset);
|
|
return e;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Key
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
Key::KeyDetailMap Key::m_keyDetailMap = Key::KeyDetailMap();
|
|
|
|
const string Key::EventType = "keychange";
|
|
const int Key::EventSubOrdering = -200;
|
|
const PropertyName Key::KeyPropertyName = "key";
|
|
const Key Key::DefaultKey = Key("C major");
|
|
|
|
Key::Key() :
|
|
m_name(DefaultKey.m_name),
|
|
m_accidentalHeights(0)
|
|
{
|
|
checkMap();
|
|
}
|
|
|
|
|
|
Key::Key(const Event &e) :
|
|
m_name(""),
|
|
m_accidentalHeights(0)
|
|
{
|
|
checkMap();
|
|
if (e.getType() != EventType) {
|
|
std::cerr << Event::BadType
|
|
("Key model event", EventType, e.getType()).getMessage()
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
e.get<String>(KeyPropertyName, m_name);
|
|
if (m_keyDetailMap.find(m_name) == m_keyDetailMap.end()) {
|
|
std::cerr << BadKeyName
|
|
("No such key as \"" + m_name + "\"").getMessage() << std::endl;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Key::Key(const std::string &name) :
|
|
m_name(name),
|
|
m_accidentalHeights(0)
|
|
{
|
|
checkMap();
|
|
if (m_keyDetailMap.find(m_name) == m_keyDetailMap.end()) {
|
|
throw BadKeyName("No such key as \"" + m_name + "\"");
|
|
}
|
|
}
|
|
|
|
Key::Key(int accidentalCount, bool isSharp, bool isMinor) :
|
|
m_accidentalHeights(0)
|
|
{
|
|
checkMap();
|
|
for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin();
|
|
i != m_keyDetailMap.end(); ++i) {
|
|
if ((*i).second.m_sharpCount == accidentalCount &&
|
|
(*i).second.m_minor == isMinor &&
|
|
((*i).second.m_sharps == isSharp ||
|
|
(*i).second.m_sharpCount == 0)) {
|
|
m_name = (*i).first;
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::ostringstream os;
|
|
os << "No " << (isMinor ? "minor" : "major") << " key with "
|
|
<< accidentalCount << (isSharp ? " sharp(s)" : " flat(s)");
|
|
|
|
throw BadKeySpec(os.str());
|
|
}
|
|
|
|
// Unfortunately this is ambiguous -- e.g. B major / Cb major.
|
|
// We need an isSharp argument, but we already have a constructor
|
|
// with that signature. Not quite sure what's the best solution.
|
|
|
|
Key::Key(int tonicPitch, bool isMinor) :
|
|
m_accidentalHeights(0)
|
|
{
|
|
checkMap();
|
|
for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin();
|
|
i != m_keyDetailMap.end(); ++i) {
|
|
if ((*i).second.m_tonicPitch == tonicPitch &&
|
|
(*i).second.m_minor == isMinor) {
|
|
m_name = (*i).first;
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::ostringstream os;
|
|
|
|
os << "No " << (isMinor ? "minor" : "major") << " key with tonic pitch "
|
|
<< tonicPitch;
|
|
|
|
throw BadKeySpec(os.str());
|
|
}
|
|
|
|
|
|
Key::Key(const Key &kc) :
|
|
m_name(kc.m_name),
|
|
m_accidentalHeights(0)
|
|
{
|
|
}
|
|
|
|
Key& Key::operator=(const Key &kc)
|
|
{
|
|
m_name = kc.m_name;
|
|
m_accidentalHeights = 0;
|
|
return *this;
|
|
}
|
|
|
|
bool Key::isValid(const Event &e)
|
|
{
|
|
if (e.getType() != EventType) return false;
|
|
std::string name;
|
|
e.get<String>(KeyPropertyName, name);
|
|
if (m_keyDetailMap.find(name) == m_keyDetailMap.end()) return false;
|
|
return true;
|
|
}
|
|
|
|
Key::KeyList Key::getKeys(bool minor)
|
|
{
|
|
checkMap();
|
|
KeyList result;
|
|
for (KeyDetailMap::const_iterator i = m_keyDetailMap.begin();
|
|
i != m_keyDetailMap.end(); ++i) {
|
|
if ((*i).second.m_minor == minor) {
|
|
result.push_back(Key((*i).first));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Key Key::transpose(int pitchDelta, int heightDelta)
|
|
{
|
|
Pitch tonic(getTonicPitch());
|
|
Pitch newTonic = tonic.transpose(*this, pitchDelta, heightDelta);
|
|
int newTonicPitch = (newTonic.getPerformancePitch() % 12 + 12) % 12;
|
|
return Key (newTonicPitch, isMinor());
|
|
}
|
|
|
|
Accidental Key::getAccidentalAtHeight(int height, const Clef &clef) const
|
|
{
|
|
checkAccidentalHeights();
|
|
height = canonicalHeight(height);
|
|
for (unsigned int i = 0; i < m_accidentalHeights->size(); ++i) {
|
|
if (height ==static_cast<int>(canonicalHeight((*m_accidentalHeights)[i] +
|
|
clef.getPitchOffset()))) {
|
|
return isSharp() ? Sharp : Flat;
|
|
}
|
|
}
|
|
return NoAccidental;
|
|
}
|
|
|
|
Accidental Key::getAccidentalForStep(int step) const
|
|
{
|
|
if (isMinor()) {
|
|
step = (step + 5) % 7;
|
|
}
|
|
|
|
int accidentalCount = getAccidentalCount();
|
|
|
|
if (accidentalCount == 0) {
|
|
return NoAccidental;
|
|
}
|
|
|
|
bool sharp = isSharp();
|
|
|
|
int currentAccidentalPosition = sharp ? 6 : 3;
|
|
|
|
for (int i = 1; i <= accidentalCount; i++) {
|
|
if (step == currentAccidentalPosition) {
|
|
return sharp ? Sharp : Flat;
|
|
}
|
|
|
|
currentAccidentalPosition =
|
|
(currentAccidentalPosition + (sharp ? 3 : 4)) % 7;
|
|
}
|
|
|
|
return NoAccidental;
|
|
}
|
|
|
|
vector<int> Key::getAccidentalHeights(const Clef &clef) const
|
|
{
|
|
// staff positions of accidentals
|
|
checkAccidentalHeights();
|
|
vector<int> v(*m_accidentalHeights);
|
|
int offset = clef.getPitchOffset();
|
|
|
|
for (unsigned int i = 0; i < v.size(); ++i) {
|
|
v[i] += offset;
|
|
if (offset > 0)
|
|
if (v[i] > 8) v[i] -= 7;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
void Key::checkAccidentalHeights() const
|
|
{
|
|
if (m_accidentalHeights) return;
|
|
m_accidentalHeights = new vector<int>;
|
|
|
|
bool sharp = isSharp();
|
|
int accidentals = getAccidentalCount();
|
|
int height = sharp ? 8 : 4;
|
|
|
|
for (int i = 0; i < accidentals; ++i) {
|
|
m_accidentalHeights->push_back(height);
|
|
if (sharp) { height -= 3; if (height < 3) height += 7; }
|
|
else { height += 3; if (height > 7) height -= 7; }
|
|
}
|
|
}
|
|
|
|
int Key::convertFrom(int p, const Key &previousKey,
|
|
const Accidental &explicitAccidental) const
|
|
{
|
|
Pitch pitch(p, explicitAccidental);
|
|
int height = pitch.getHeightOnStaff(Clef(), previousKey);
|
|
Pitch newPitch(height, Clef(), *this, explicitAccidental);
|
|
return newPitch.getPerformancePitch();
|
|
}
|
|
|
|
int Key::transposeFrom(int pitch, const Key &previousKey) const
|
|
{
|
|
int delta = getTonicPitch() - previousKey.getTonicPitch();
|
|
if (delta > 6) delta -= 12;
|
|
if (delta < -6) delta += 12;
|
|
return pitch + delta;
|
|
}
|
|
|
|
Event *Key::getAsEvent(timeT absoluteTime) const
|
|
{
|
|
Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
|
|
e->set<String>(KeyPropertyName, m_name);
|
|
return e;
|
|
}
|
|
|
|
|
|
void Key::checkMap() {
|
|
if (!m_keyDetailMap.empty()) return;
|
|
|
|
m_keyDetailMap["A major" ] = KeyDetails(true, false, 3, "F# minor", "A maj / F# min", 9);
|
|
m_keyDetailMap["F# minor"] = KeyDetails(true, true, 3, "A major", "A maj / F# min", 6);
|
|
m_keyDetailMap["Ab major"] = KeyDetails(false, false, 4, "F minor", "Ab maj / F min", 8);
|
|
m_keyDetailMap["F minor" ] = KeyDetails(false, true, 4, "Ab major", "Ab maj / F min", 5);
|
|
m_keyDetailMap["B major" ] = KeyDetails(true, false, 5, "G# minor", "B maj / G# min", 11);
|
|
m_keyDetailMap["G# minor"] = KeyDetails(true, true, 5, "B major", "B maj / G# min", 8);
|
|
m_keyDetailMap["Bb major"] = KeyDetails(false, false, 2, "G minor", "Bb maj / G min", 10);
|
|
m_keyDetailMap["G minor" ] = KeyDetails(false, true, 2, "Bb major", "Bb maj / G min", 7);
|
|
m_keyDetailMap["C major" ] = KeyDetails(true, false, 0, "A minor", "C maj / A min", 0);
|
|
m_keyDetailMap["A minor" ] = KeyDetails(false, true, 0, "C major", "C maj / A min", 9);
|
|
m_keyDetailMap["Cb major"] = KeyDetails(false, false, 7, "Ab minor", "Cb maj / Ab min", 11);
|
|
m_keyDetailMap["Ab minor"] = KeyDetails(false, true, 7, "Cb major", "Cb maj / Ab min", 8);
|
|
m_keyDetailMap["C# major"] = KeyDetails(true, false, 7, "A# minor", "C# maj / A# min", 1);
|
|
m_keyDetailMap["A# minor"] = KeyDetails(true, true, 7, "C# major", "C# maj / A# min", 10);
|
|
m_keyDetailMap["D major" ] = KeyDetails(true, false, 2, "B minor", "D maj / B min", 2);
|
|
m_keyDetailMap["B minor" ] = KeyDetails(true, true, 2, "D major", "D maj / B min", 11);
|
|
m_keyDetailMap["Db major"] = KeyDetails(false, false, 5, "Bb minor", "Db maj / Bb min", 1);
|
|
m_keyDetailMap["Bb minor"] = KeyDetails(false, true, 5, "Db major", "Db maj / Bb min", 10);
|
|
m_keyDetailMap["E major" ] = KeyDetails(true, false, 4, "C# minor", "E maj / C# min", 4);
|
|
m_keyDetailMap["C# minor"] = KeyDetails(true, true, 4, "E major", "E maj / C# min", 1);
|
|
m_keyDetailMap["Eb major"] = KeyDetails(false, false, 3, "C minor", "Eb maj / C min", 3);
|
|
m_keyDetailMap["C minor" ] = KeyDetails(false, true, 3, "Eb major", "Eb maj / C min", 0);
|
|
m_keyDetailMap["F major" ] = KeyDetails(false, false, 1, "D minor", "F maj / D min", 5);
|
|
m_keyDetailMap["D minor" ] = KeyDetails(false, true, 1, "F major", "F maj / D min", 2);
|
|
m_keyDetailMap["F# major"] = KeyDetails(true, false, 6, "D# minor", "F# maj / D# min", 6);
|
|
m_keyDetailMap["D# minor"] = KeyDetails(true, true, 6, "F# major", "F# maj / D# min", 3);
|
|
m_keyDetailMap["G major" ] = KeyDetails(true, false, 1, "E minor", "G maj / E min", 7);
|
|
m_keyDetailMap["E minor" ] = KeyDetails(true, true, 1, "G major", "G maj / E min", 4);
|
|
m_keyDetailMap["Gb major"] = KeyDetails(false, false, 6, "Eb minor", "Gb maj / Eb min", 6);
|
|
m_keyDetailMap["Eb minor"] = KeyDetails(false, true, 6, "Gb major", "Gb maj / Eb min", 3);
|
|
}
|
|
|
|
|
|
Key::KeyDetails::KeyDetails()
|
|
: m_sharps(false), m_minor(false), m_sharpCount(0),
|
|
m_equivalence(""), m_rg2name(""), m_tonicPitch(0)
|
|
{
|
|
}
|
|
|
|
Key::KeyDetails::KeyDetails(bool sharps, bool minor, int sharpCount,
|
|
std::string equivalence, std::string rg2name,
|
|
int tonicPitch)
|
|
: m_sharps(sharps), m_minor(minor), m_sharpCount(sharpCount),
|
|
m_equivalence(equivalence), m_rg2name(rg2name), m_tonicPitch(tonicPitch)
|
|
{
|
|
}
|
|
|
|
Key::KeyDetails::KeyDetails(const Key::KeyDetails &d)
|
|
: m_sharps(d.m_sharps), m_minor(d.m_minor),
|
|
m_sharpCount(d.m_sharpCount), m_equivalence(d.m_equivalence),
|
|
m_rg2name(d.m_rg2name), m_tonicPitch(d.m_tonicPitch)
|
|
{
|
|
}
|
|
|
|
Key::KeyDetails& Key::KeyDetails::operator=(const Key::KeyDetails &d)
|
|
{
|
|
if (&d == this) return *this;
|
|
m_sharps = d.m_sharps; m_minor = d.m_minor;
|
|
m_sharpCount = d.m_sharpCount; m_equivalence = d.m_equivalence;
|
|
m_rg2name = d.m_rg2name; m_tonicPitch = d.m_tonicPitch;
|
|
return *this;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Indication
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const std::string Indication::EventType = "indication";
|
|
const int Indication::EventSubOrdering = -50;
|
|
const PropertyName Indication::IndicationTypePropertyName = "indicationtype";
|
|
//const PropertyName Indication::IndicationDurationPropertyName = "indicationduration";
|
|
static const PropertyName IndicationDurationPropertyName = "indicationduration";//!!!
|
|
|
|
const std::string Indication::Slur = "slur";
|
|
const std::string Indication::PhrasingSlur = "phrasingslur";
|
|
const std::string Indication::Crescendo = "crescendo";
|
|
const std::string Indication::Decrescendo = "decrescendo";
|
|
const std::string Indication::Glissando = "glissando";
|
|
const std::string Indication::QuindicesimaUp = "ottava2up";
|
|
const std::string Indication::OttavaUp = "ottavaup";
|
|
const std::string Indication::OttavaDown = "ottavadown";
|
|
const std::string Indication::QuindicesimaDown = "ottava2down";
|
|
|
|
Indication::Indication(const Event &e)
|
|
{
|
|
if (e.getType() != EventType) {
|
|
throw Event::BadType("Indication model event", EventType, e.getType());
|
|
}
|
|
std::string s;
|
|
e.get<String>(IndicationTypePropertyName, s);
|
|
if (!isValid(s)) {
|
|
throw BadIndicationName("No such indication as \"" + s + "\"");
|
|
}
|
|
m_indicationType = s;
|
|
|
|
m_duration = e.getDuration();
|
|
if (m_duration == 0) {
|
|
e.get<Int>(IndicationDurationPropertyName, m_duration); // obsolete property
|
|
}
|
|
}
|
|
|
|
Indication::Indication(const std::string &s, timeT indicationDuration)
|
|
{
|
|
if (!isValid(s)) {
|
|
throw BadIndicationName("No such indication as \"" + s + "\"");
|
|
}
|
|
m_indicationType = s;
|
|
m_duration = indicationDuration;
|
|
}
|
|
|
|
Indication &
|
|
Indication::operator=(const Indication &m)
|
|
{
|
|
if (&m != this) {
|
|
m_indicationType = m.m_indicationType;
|
|
m_duration = m.m_duration;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Event *
|
|
Indication::getAsEvent(timeT absoluteTime) const
|
|
{
|
|
Event *e = new Event(EventType, absoluteTime, m_duration, EventSubOrdering);
|
|
e->set<String>(IndicationTypePropertyName, m_indicationType);
|
|
|
|
// Set this obsolete property as well, as otherwise we could actually
|
|
// crash earlier versions of RG by loading files exported from this one!
|
|
e->set<Int>(IndicationDurationPropertyName, m_duration);
|
|
|
|
return e;
|
|
}
|
|
|
|
bool
|
|
Indication::isValid(const std::string &s) const
|
|
{
|
|
return
|
|
(s == Slur || s == PhrasingSlur ||
|
|
s == Crescendo || s == Decrescendo ||
|
|
s == Glissando ||
|
|
s == QuindicesimaUp || s == OttavaUp ||
|
|
s == OttavaDown || s == QuindicesimaDown);
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Text
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const std::string Text::EventType = "text";
|
|
const int Text::EventSubOrdering = -70;
|
|
const PropertyName Text::TextPropertyName = "text";
|
|
const PropertyName Text::TextTypePropertyName = "type";
|
|
const PropertyName Text::LyricVersePropertyName = "verse";
|
|
|
|
// text styles
|
|
const std::string Text::UnspecifiedType = "unspecified";
|
|
const std::string Text::StaffName = "staffname";
|
|
const std::string Text::ChordName = "chordname";
|
|
const std::string Text::KeyName = "keyname";
|
|
const std::string Text::Dynamic = "dynamic";
|
|
const std::string Text::Lyric = "lyric";
|
|
const std::string Text::Chord = "chord";
|
|
const std::string Text::Direction = "direction";
|
|
const std::string Text::LocalDirection = "local_direction";
|
|
const std::string Text::Tempo = "tempo";
|
|
const std::string Text::LocalTempo = "local_tempo";
|
|
const std::string Text::Annotation = "annotation";
|
|
const std::string Text::LilyPondDirective = "lilypond_directive";
|
|
|
|
// special LilyPond directives
|
|
const std::string Text::Segno = "Segno";
|
|
const std::string Text::Coda = "Coda";
|
|
const std::string Text::Alternate1 = "Alt1 ->";
|
|
const std::string Text::Alternate2 = "Alt2 ->";
|
|
const std::string Text::BarDouble = "|| ->";
|
|
const std::string Text::BarEnd = "|. ->";
|
|
const std::string Text::BarDot = ": ->";
|
|
const std::string Text::Gliss = "Gliss.";
|
|
const std::string Text::Arpeggio = "Arp.";
|
|
//const std::string Text::ArpeggioUp = "Arp.^";
|
|
//const std::string Text::ArpeggioDn = "Arp._";
|
|
const std::string Text::Tiny = "tiny ->";
|
|
const std::string Text::Small = "small ->";
|
|
const std::string Text::NormalSize = "norm. ->";
|
|
|
|
Text::Text(const Event &e) :
|
|
m_verse(0)
|
|
{
|
|
if (e.getType() != EventType) {
|
|
throw Event::BadType("Text model event", EventType, e.getType());
|
|
}
|
|
|
|
m_text = "";
|
|
m_type = Text::UnspecifiedType;
|
|
|
|
e.get<String>(TextPropertyName, m_text);
|
|
e.get<String>(TextTypePropertyName, m_type);
|
|
e.get<Int>(LyricVersePropertyName, m_verse);
|
|
}
|
|
|
|
Text::Text(const std::string &s, const std::string &type) :
|
|
m_text(s),
|
|
m_type(type),
|
|
m_verse(0)
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
Text::Text(const Text &t) :
|
|
m_text(t.m_text),
|
|
m_type(t.m_type),
|
|
m_verse(t.m_verse)
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
Text &
|
|
Text::operator=(const Text &t)
|
|
{
|
|
if (&t != this) {
|
|
m_text = t.m_text;
|
|
m_type = t.m_type;
|
|
m_verse = t.m_verse;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Text::~Text()
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
bool
|
|
Text::isTextOfType(Event *e, std::string type)
|
|
{
|
|
return (e->isa(EventType) &&
|
|
e->has(TextTypePropertyName) &&
|
|
e->get<String>(TextTypePropertyName) == type);
|
|
}
|
|
|
|
std::vector<std::string>
|
|
Text::getUserStyles()
|
|
{
|
|
std::vector<std::string> v;
|
|
|
|
v.push_back(Dynamic);
|
|
v.push_back(Direction);
|
|
v.push_back(LocalDirection);
|
|
v.push_back(Tempo);
|
|
v.push_back(LocalTempo);
|
|
v.push_back(Chord);
|
|
v.push_back(Lyric);
|
|
v.push_back(Annotation);
|
|
v.push_back(LilyPondDirective);
|
|
|
|
return v;
|
|
}
|
|
|
|
std::vector<std::string>
|
|
Text::getLilyPondDirectives()
|
|
{
|
|
std::vector<std::string> v;
|
|
|
|
v.push_back(Alternate1);
|
|
v.push_back(Alternate2);
|
|
v.push_back(Segno);
|
|
v.push_back(Coda);
|
|
v.push_back(BarDouble);
|
|
v.push_back(BarEnd);
|
|
v.push_back(BarDot);
|
|
v.push_back(Gliss);
|
|
v.push_back(Arpeggio);
|
|
// v.push_back(ArpeggioUp);
|
|
// v.push_back(ArpeggioDn);
|
|
v.push_back(Tiny);
|
|
v.push_back(Small);
|
|
v.push_back(NormalSize);
|
|
|
|
return v;
|
|
}
|
|
|
|
Event *
|
|
Text::getAsEvent(timeT absoluteTime) const
|
|
{
|
|
Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
|
|
e->set<String>(TextPropertyName, m_text);
|
|
e->set<String>(TextTypePropertyName, m_type);
|
|
if (m_type == Lyric) e->set<Int>(LyricVersePropertyName, m_verse);
|
|
return e;
|
|
}
|
|
|
|
bool
|
|
pitchInKey(int pitch, const Key& key)
|
|
{
|
|
int pitchOffset = (pitch - key.getTonicPitch() + 12) % 12;
|
|
|
|
static int pitchInMajor[] =
|
|
{ true, false, true, false, true, true, false, true, false, true, false, true };
|
|
static int pitchInMinor[] =
|
|
{ true, false, true, true, false, true, false, true, true, false, true, false };
|
|
|
|
if (key.isMinor()) {
|
|
return pitchInMinor[pitchOffset];
|
|
}
|
|
else {
|
|
return pitchInMajor[pitchOffset];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param pitch in the range 0..11 (C..B)
|
|
*
|
|
* @author Arnout Engelen
|
|
*/
|
|
Accidental
|
|
resolveNoAccidental(int pitch,
|
|
const Key &key,
|
|
NoAccidentalStrategy noAccidentalStrategy)
|
|
{
|
|
Accidental outputAccidental = "";
|
|
|
|
// Find out the accidental to use, based on the strategy specified
|
|
switch (noAccidentalStrategy) {
|
|
case UseKeySharpness:
|
|
noAccidentalStrategy =
|
|
key.isSharp() ? UseSharps : UseFlats;
|
|
// fall though
|
|
case UseFlats:
|
|
// shares code with UseSharps
|
|
case UseSharps:
|
|
if (pitchInKey(pitch, key)) {
|
|
outputAccidental = NoAccidental;
|
|
}
|
|
else {
|
|
if (noAccidentalStrategy == UseSharps) {
|
|
outputAccidental = Sharp;
|
|
}
|
|
else {
|
|
outputAccidental = Flat;
|
|
}
|
|
}
|
|
break;
|
|
case UseKey:
|
|
// the distance of the pitch from the tonic of the current
|
|
// key
|
|
int pitchOffset = (pitch - key.getTonicPitch() + 12) % 12;
|
|
// 0: major, 1: minor
|
|
int minor = key.isMinor();
|
|
static int pitchToHeight[2][12] =
|
|
{
|
|
{ 0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 },
|
|
// a ., b, c, ., d, ., e, f, ., g, .
|
|
{ 0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6 }
|
|
};
|
|
|
|
// map pitchOffset to the extra correction, on top of any
|
|
// accidentals in the key. Example: in F major, with a pitchOffset
|
|
// of 6, the resulting height would be 3 (Bb) and the correction
|
|
// would be +1, so the resulting note would be B-natural
|
|
static int pitchToCorrection[2][12] =
|
|
{
|
|
{ 0, +1, 0, -1, 0, 0, +1, 0, -1, 0, -1, 0 },
|
|
{ 0, -1, 0, 0, +1, 0, -1, 0, 0, +1, 0, +1 }
|
|
};
|
|
|
|
int correction = pitchToCorrection[minor][pitchOffset];
|
|
|
|
// Get the accidental normally associated with this height in this
|
|
// key.
|
|
Accidental normalAccidental = key.getAccidentalForStep(pitchToHeight[minor][pitchOffset]);
|
|
|
|
// Apply the pitchCorrection and get the outputAccidental
|
|
outputAccidental = Accidentals::getAccidental(
|
|
getPitchOffset(normalAccidental) + correction);
|
|
|
|
}
|
|
|
|
return outputAccidental;
|
|
}
|
|
|
|
/**
|
|
* @param pitch in the range 0..11 (C..B)
|
|
*
|
|
* @author Michael McIntyre
|
|
*/
|
|
void
|
|
resolveSpecifiedAccidental(int pitch,
|
|
const Clef &clef,
|
|
const Key &key,
|
|
int &height,
|
|
int &octave,
|
|
Accidental &inputAccidental,
|
|
Accidental &outputAccidental)
|
|
{
|
|
// 4. Get info from the Key
|
|
long accidentalCount = key.getAccidentalCount();
|
|
bool keyIsSharp = key.isSharp(), keyIsFlat = !keyIsSharp;
|
|
|
|
// Calculate the flags needed for resolving accidentals against the key.
|
|
// First we initialize them false...
|
|
bool keyHasSharpC = false, keyHasSharpD = false, keyHasSharpE = false,
|
|
keyHasSharpF = false, keyHasSharpG = false, keyHasSharpA = false,
|
|
keyHasSharpB = false, keyHasFlatC = false, keyHasFlatD = false,
|
|
keyHasFlatE = false, keyHasFlatF = false, keyHasFlatG = false,
|
|
keyHasFlatA = false, keyHasFlatB = false;
|
|
|
|
// Then we use "trip points" based on the flat/sharp state of the key and
|
|
// its number of accidentals to set the flags:
|
|
if (keyIsSharp) {
|
|
switch (accidentalCount) {
|
|
case 7: keyHasSharpB = true;
|
|
case 6: keyHasSharpE = true;
|
|
case 5: keyHasSharpA = true;
|
|
case 4: keyHasSharpD = true;
|
|
case 3: keyHasSharpG = true;
|
|
case 2: keyHasSharpC = true;
|
|
case 1: keyHasSharpF = true;
|
|
}
|
|
} else {
|
|
switch (accidentalCount) {
|
|
case 7: keyHasFlatF = true;
|
|
case 6: keyHasFlatC = true;
|
|
case 5: keyHasFlatG = true;
|
|
case 4: keyHasFlatD = true;
|
|
case 3: keyHasFlatA = true;
|
|
case 2: keyHasFlatE = true;
|
|
case 1: keyHasFlatB = true;
|
|
}
|
|
}
|
|
|
|
|
|
// 5. Determine height on staff and accidental note should display with for key...
|
|
//
|
|
// Every position on the staff is one of six accidental states:
|
|
//
|
|
// Natural, Sharp, Flat, DoubleSharp, DoubleFlat, NoAccidental
|
|
//
|
|
// DoubleSharp and DoubleFlat are always user-specified accidentals, so
|
|
// they are always used to decide how to draw the note, and they are
|
|
// always passed along unchanged.
|
|
//
|
|
// The Natural state indicates that a note is or might be going against
|
|
// the key. Since the Natural state will always be attached to a plain
|
|
// pitch that can never resolve to a "black key" note, it is not necessary
|
|
// to handle this case differently unless the key has "white key" notes
|
|
// that are supposed to take accidentals for the key. (eg. Cb Gb B C# major)
|
|
// For most keys we treat it the same as a NoAccidental, and use the key
|
|
// to decide where to draw the note, and what accidental to return.
|
|
//
|
|
// The Sharp and Flat states indicate that a user has specified an
|
|
// accidental for the note, and it might be "out of key." We check to see
|
|
// if that's the case. If the note is "in key" then the extra accidental
|
|
// property is removed, and we return NoAccidental. If the note is "out of
|
|
// key" then the Sharp or Flat is used to decide where to draw the note, and
|
|
// the accidental is passed along unchanged. (Incomplete? Will a failure
|
|
// to always pass along the accidental cause strange behavior if a user
|
|
// specifies an explicit Bb in key of F and then transposes to G, wishing
|
|
// the Bb to remain an explicit Bb? If someone complains, I'll know where
|
|
// to look.)
|
|
//
|
|
// The NoAccidental state is a default state. We have nothing else upon
|
|
// which to base a decision in this case, so we make the best decisions
|
|
// possible using only the pitch and key. Notes that are "in key" pass on
|
|
// with NoAccidental preserved, otherwise we return an appropriate
|
|
// accidental for the key.
|
|
|
|
// We calculate height on a virtual staff, and then make necessary adjustments to
|
|
// translate them onto a particular Clef later on...
|
|
//
|
|
// ---------F--------- Staff Height Note(semitone) for each of five states:
|
|
// E
|
|
// ---------D--------- Natural| Sharp | Flat |DblSharp| DblFlat
|
|
// C | | | |
|
|
// ---------B--------- height 4 B(11) | B#( 0) | Bb(10) | Bx( 1) | Bbb( 9)
|
|
// A height 3 A( 9) | A#(10) | Ab( 8) | Ax(11) | Abb( 7)
|
|
// ---------G--------- height 2 G( 7) | G#( 8) | Gb( 6) | Gx( 9) | Gbb( 5)
|
|
// F height 1 F( 5) | F#( 6) | Fb( 4) | Fx( 7) | Fbb( 3)
|
|
// ---------E--------- height 0 E( 4) | E#( 5) | Eb( 3) | Ex( 6) | Ebb( 2)
|
|
// D height -1 D( 2) | D#( 3) | Db( 1) | Dx( 4) | Dbb( 0)
|
|
// ---C---- height -2 C( 0) | C#( 1) | Cb(11) | Cx( 2) | Cbb(10)
|
|
|
|
|
|
// use these constants instead of numeric literals in order to reduce the
|
|
// chance of making incorrect height assignments...
|
|
const int C = -2, D = -1, E = 0, F = 1, G = 2, A = 3, B = 4;
|
|
|
|
// Here we do the actual work of making all the decisions explained above.
|
|
switch (pitch) {
|
|
case 0 :
|
|
if (inputAccidental == Sharp || // B#
|
|
(inputAccidental == NoAccidental && keyHasSharpB)) {
|
|
height = B;
|
|
octave--;
|
|
outputAccidental = (keyHasSharpB) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == DoubleFlat) { // Dbb
|
|
height = D;
|
|
outputAccidental = DoubleFlat;
|
|
} else {
|
|
height = C; // C or C-Natural
|
|
outputAccidental = (keyHasFlatC || keyHasSharpC ||
|
|
(keyHasSharpB &&
|
|
inputAccidental == Natural)) ? Natural : NoAccidental;
|
|
}
|
|
break;
|
|
case 1 :
|
|
if (inputAccidental == Sharp || // C#
|
|
(inputAccidental == NoAccidental && keyIsSharp)) {
|
|
height = C;
|
|
outputAccidental = (keyHasSharpC) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == Flat || // Db
|
|
(inputAccidental == NoAccidental && keyIsFlat)) {
|
|
height = D;
|
|
outputAccidental = (keyHasFlatD) ? NoAccidental : Flat;
|
|
} else if (inputAccidental == DoubleSharp) { // Bx
|
|
height = B;
|
|
octave--;
|
|
outputAccidental = DoubleSharp;
|
|
}
|
|
break;
|
|
case 2 :
|
|
if (inputAccidental == DoubleSharp) { // Cx
|
|
height = C;
|
|
outputAccidental = DoubleSharp;
|
|
} else if (inputAccidental == DoubleFlat) { // Ebb
|
|
height = E;
|
|
outputAccidental = DoubleFlat;
|
|
} else { // D or D-Natural
|
|
height = D;
|
|
outputAccidental = (keyHasSharpD || keyHasFlatD) ? Natural : NoAccidental;
|
|
}
|
|
break;
|
|
case 3 :
|
|
if (inputAccidental == Sharp || // D#
|
|
(inputAccidental == NoAccidental && keyIsSharp)) {
|
|
height = D;
|
|
outputAccidental = (keyHasSharpD) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == Flat || // Eb
|
|
(inputAccidental == NoAccidental && keyIsFlat)) {
|
|
height = E;
|
|
outputAccidental = (keyHasFlatE) ? NoAccidental : Flat;
|
|
} else if (inputAccidental == DoubleFlat) { // Fbb
|
|
height = F;
|
|
outputAccidental = DoubleFlat;
|
|
}
|
|
break;
|
|
case 4 :
|
|
if (inputAccidental == Flat || // Fb
|
|
(inputAccidental == NoAccidental && keyHasFlatF)) {
|
|
height = F;
|
|
outputAccidental = (keyHasFlatF) ? NoAccidental : Flat;
|
|
} else if (inputAccidental == DoubleSharp) { // Dx
|
|
height = D;
|
|
outputAccidental = DoubleSharp;
|
|
} else { // E or E-Natural
|
|
height = E;
|
|
outputAccidental = (keyHasSharpE || keyHasFlatE ||
|
|
(keyHasFlatF && inputAccidental==Natural)) ?
|
|
Natural : NoAccidental;
|
|
}
|
|
break;
|
|
case 5 :
|
|
if (inputAccidental == Sharp || // E#
|
|
(inputAccidental == NoAccidental && keyHasSharpE)) {
|
|
height = E;
|
|
outputAccidental = (keyHasSharpE) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == DoubleFlat) { // Gbb
|
|
height = G;
|
|
outputAccidental = DoubleFlat;
|
|
} else { // F or F-Natural
|
|
height = F;
|
|
outputAccidental = (keyHasSharpF || keyHasFlatF ||
|
|
(keyHasSharpE && inputAccidental==Natural))?
|
|
Natural : NoAccidental;
|
|
}
|
|
break;
|
|
case 6 :
|
|
if (inputAccidental == Sharp ||
|
|
(inputAccidental == NoAccidental && keyIsSharp)) { // F#
|
|
height = F;
|
|
outputAccidental = (keyHasSharpF) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == Flat || // Gb
|
|
(inputAccidental == NoAccidental && keyIsFlat)) {
|
|
height = G;
|
|
outputAccidental = (keyHasFlatG) ? NoAccidental : Flat;
|
|
} else if (inputAccidental == DoubleSharp) { // Ex
|
|
height = E;
|
|
outputAccidental = DoubleSharp;
|
|
}
|
|
break;
|
|
case 7 :
|
|
if (inputAccidental == DoubleSharp) { // Fx
|
|
height = F;
|
|
outputAccidental = DoubleSharp;
|
|
} else if (inputAccidental == DoubleFlat) { // Abb
|
|
height = A;
|
|
outputAccidental = DoubleFlat;
|
|
} else { // G or G-Natural
|
|
height = G;
|
|
outputAccidental = (keyHasSharpG || keyHasFlatG) ? Natural : NoAccidental;
|
|
}
|
|
break;
|
|
case 8 :
|
|
if (inputAccidental == Sharp ||
|
|
(inputAccidental == NoAccidental && keyIsSharp)) { // G#
|
|
height = G;
|
|
outputAccidental = (keyHasSharpG) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == Flat || // Ab
|
|
(inputAccidental == NoAccidental && keyIsFlat)) {
|
|
height = A;
|
|
outputAccidental = (keyHasFlatA) ? NoAccidental : Flat;
|
|
}
|
|
break;
|
|
case 9 :
|
|
if (inputAccidental == DoubleSharp) { // Gx
|
|
height = G;
|
|
outputAccidental = DoubleSharp;
|
|
} else if (inputAccidental == DoubleFlat) { // Bbb
|
|
height = B;
|
|
outputAccidental = DoubleFlat;
|
|
} else { // A or A-Natural
|
|
height = A;
|
|
outputAccidental = (keyHasSharpA || keyHasFlatA) ? Natural : NoAccidental;
|
|
}
|
|
break;
|
|
case 10:
|
|
if (inputAccidental == DoubleFlat) { // Cbb
|
|
height = C;
|
|
octave++; // tweak B/C divide
|
|
outputAccidental = DoubleFlat;
|
|
} else if (inputAccidental == Sharp || // A#
|
|
(inputAccidental == NoAccidental && keyIsSharp)) {
|
|
height = A;
|
|
outputAccidental = (keyHasSharpA) ? NoAccidental : Sharp;
|
|
} else if (inputAccidental == Flat || // Bb
|
|
(inputAccidental == NoAccidental && keyIsFlat)) {
|
|
height = B;
|
|
outputAccidental = (keyHasFlatB) ? NoAccidental : Flat;
|
|
}
|
|
break;
|
|
case 11:
|
|
if (inputAccidental == DoubleSharp) { // Ax
|
|
height = A;
|
|
outputAccidental = DoubleSharp;
|
|
} else if (inputAccidental == Flat || // Cb
|
|
(inputAccidental == NoAccidental && keyHasFlatC)) {
|
|
height = C;
|
|
octave++; // tweak B/C divide
|
|
outputAccidental = (keyHasFlatC) ? NoAccidental : Flat;
|
|
} else { // B or B-Natural
|
|
height = B;
|
|
outputAccidental = (keyHasSharpB || keyHasFlatB ||
|
|
(keyHasFlatC && inputAccidental==Natural)) ?
|
|
Natural : NoAccidental;
|
|
}
|
|
}
|
|
|
|
if (outputAccidental == NoAccidental && inputAccidental == Natural) {
|
|
outputAccidental = Natural;
|
|
}
|
|
|
|
}
|
|
|
|
bool
|
|
Pitch::validAccidental() const
|
|
{
|
|
// std::cout << "Checking whether accidental is valid " << std::endl;
|
|
if (m_accidental == NoAccidental)
|
|
{
|
|
return true;
|
|
}
|
|
int naturalPitch = (m_pitch -
|
|
Accidentals::getPitchOffset(m_accidental) + 12) % 12;
|
|
switch(naturalPitch)
|
|
{
|
|
case 0: //C
|
|
return true;
|
|
case 1:
|
|
return false;
|
|
case 2: //D
|
|
return true;
|
|
case 3:
|
|
return false;
|
|
case 4: //E
|
|
return true;
|
|
case 5: //F
|
|
return true;
|
|
case 6:
|
|
return false;
|
|
case 7: //G
|
|
return true;
|
|
case 8:
|
|
return false;
|
|
case 9: //A
|
|
return true;
|
|
case 10:
|
|
return false;
|
|
case 11: //B
|
|
return true;
|
|
};
|
|
std::cout << "Internal error in validAccidental" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Event *
|
|
Pitch::getAsNoteEvent(timeT absoluteTime, timeT duration) const
|
|
{
|
|
Event *e = new Event(Note::EventType, absoluteTime, duration);
|
|
e->set<Int>(BaseProperties::PITCH, m_pitch);
|
|
e->set<String>(BaseProperties::ACCIDENTAL, m_accidental);
|
|
return e;
|
|
}
|
|
|
|
/**
|
|
* Converts performance pitch to height on staff + correct accidentals
|
|
* for current key.
|
|
*
|
|
* This method takes a Clef, Key, Accidental and raw performance pitch, then
|
|
* applies this information to return a height on staff value and an
|
|
* accidental state. The pitch itself contains a lot of information, but we
|
|
* need to use the Key and user-specified Accidental to make an accurate
|
|
* decision just where to put it on the staff, and what accidental it should
|
|
* display for (or against) the key.
|
|
*
|
|
* This function originally written by Chris Cannam for Rosegarden 2.1
|
|
* Entirely rewritten by Chris Cannam for Rosegarden 4
|
|
* Entirely rewritten by Hans Kieserman
|
|
* Entirely rewritten by Michael McIntyre
|
|
* This version by Michael McIntyre <dmmcintyr@users.sourceforge.net>
|
|
* Resolving the accidental was refactored out by Arnout Engelen
|
|
*/
|
|
void
|
|
Pitch::rawPitchToDisplayPitch(int rawpitch,
|
|
const Clef &clef,
|
|
const Key &key,
|
|
int &height,
|
|
Accidental &accidental,
|
|
NoAccidentalStrategy noAccidentalStrategy)
|
|
{
|
|
|
|
// 1. Calculate the octave (for later):
|
|
int octave = rawpitch / 12;
|
|
|
|
// 2. Set initial height to 0
|
|
height = 0;
|
|
|
|
// 3. Calculate raw semitone number, yielding a value between 0 (C) and
|
|
// 11 (B)
|
|
int pitch = rawpitch % 12;
|
|
|
|
// clear the in-coming accidental so we can trap any failure to re-set
|
|
// it on the way out:
|
|
Accidental userAccidental = accidental;
|
|
accidental = "";
|
|
|
|
if (userAccidental == NoAccidental || !Pitch(rawpitch, userAccidental).validAccidental())
|
|
{
|
|
userAccidental = resolveNoAccidental(pitch, key, noAccidentalStrategy);
|
|
//std::cout << "Chose accidental " << userAccidental << " for pitch " << pitch <<
|
|
// " in key " << key.getName() << std::endl;
|
|
}
|
|
//else
|
|
//{
|
|
// std::cout << "Accidental was specified, as " << userAccidental << std::endl;
|
|
//}
|
|
|
|
resolveSpecifiedAccidental(pitch, clef, key, height, octave, userAccidental, accidental);
|
|
|
|
// Failsafe... If this ever executes, there's trouble to fix...
|
|
// WIP - DMM - munged up to explore #937389, which is temporarily deferred,
|
|
// owing to its non-critical nature, having been hacked around in the LilyPond
|
|
// code
|
|
#ifndef DEBUG_PITCH
|
|
if (accidental == "") {
|
|
std::cerr << "Pitch::rawPitchToDisplayPitch(): error! returning null accidental for:"
|
|
#else
|
|
std::cerr << "Pitch::rawPitchToDisplayPitch(): calculating: "
|
|
#endif
|
|
<< std::endl << "pitch: " << rawpitch << " (" << pitch << " in oct "
|
|
<< octave << ") userAcc: " << userAccidental
|
|
<< " clef: " << clef.getClefType() << " key: " << key.getName() << std::endl;
|
|
#ifndef DEBUG_PITCH
|
|
}
|
|
#endif
|
|
|
|
|
|
// 6. "Recenter" height in case it's been changed:
|
|
height = ((height + 2) % 7) - 2;
|
|
|
|
height += (octave - 5) * 7;
|
|
height += clef.getPitchOffset();
|
|
|
|
|
|
// 7. Transpose up or down for the clef:
|
|
height -= 7 * clef.getOctave();
|
|
}
|
|
|
|
void
|
|
Pitch::displayPitchToRawPitch(int height,
|
|
Accidental accidental,
|
|
const Clef &clef,
|
|
const Key &key,
|
|
int &pitch,
|
|
bool ignoreOffset)
|
|
{
|
|
int octave = 5;
|
|
|
|
// 1. Ask Key for accidental if necessary
|
|
if (accidental == NoAccidental) {
|
|
accidental = key.getAccidentalAtHeight(height, clef);
|
|
}
|
|
|
|
// 2. Get pitch and correct octave
|
|
|
|
if (!ignoreOffset) height -= clef.getPitchOffset();
|
|
|
|
while (height < 0) { octave -= 1; height += 7; }
|
|
while (height >= 7) { octave += 1; height -= 7; }
|
|
|
|
if (height > 4) ++octave;
|
|
|
|
// Height is now relative to treble clef lines
|
|
switch (height) {
|
|
|
|
case 0: pitch = 4; break; /* bottom line, treble clef: E */
|
|
case 1: pitch = 5; break; /* F */
|
|
case 2: pitch = 7; break; /* G */
|
|
case 3: pitch = 9; break; /* A, in next octave */
|
|
case 4: pitch = 11; break; /* B, likewise*/
|
|
case 5: pitch = 0; break; /* C, moved up an octave (see above) */
|
|
case 6: pitch = 2; break; /* D, likewise */
|
|
}
|
|
// Pitch is now "natural"-ized note at given height
|
|
|
|
// 3. Adjust pitch for accidental
|
|
|
|
if (accidental != NoAccidental &&
|
|
accidental != Natural) {
|
|
if (accidental == Sharp) { pitch++; }
|
|
else if (accidental == Flat) { pitch--; }
|
|
else if (accidental == DoubleSharp) { pitch += 2; }
|
|
else if (accidental == DoubleFlat) { pitch -= 2; }
|
|
}
|
|
|
|
// 4. Adjust for clef
|
|
octave += clef.getOctave();
|
|
|
|
pitch += 12 * octave;
|
|
}
|
|
|
|
|
|
|
|
Pitch::Pitch(const Event &e) :
|
|
// throw (Event::NoData)
|
|
m_accidental(NoAccidental)
|
|
{
|
|
m_pitch = e.get<Int>(BaseProperties::PITCH);
|
|
e.get<String>(BaseProperties::ACCIDENTAL, m_accidental);
|
|
}
|
|
|
|
Pitch::Pitch(int performancePitch, const Accidental &explicitAccidental) :
|
|
m_pitch(performancePitch),
|
|
m_accidental(explicitAccidental)
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
Pitch::Pitch(int pitchInOctave, int octave,
|
|
const Accidental &explicitAccidental, int octaveBase) :
|
|
m_pitch((octave - octaveBase) * 12 + pitchInOctave),
|
|
m_accidental(explicitAccidental)
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
Pitch::Pitch(int noteInScale, int octave, const Key &key,
|
|
const Accidental &explicitAccidental, int octaveBase) :
|
|
m_pitch(0),
|
|
m_accidental(explicitAccidental)
|
|
{
|
|
m_pitch = (key.getTonicPitch());
|
|
m_pitch = (octave - octaveBase) * 12 + m_pitch % 12;
|
|
|
|
if (key.isMinor()) m_pitch += scale_Cminor_harmonic[noteInScale];
|
|
else m_pitch += scale_Cmajor[noteInScale];
|
|
|
|
m_pitch += Accidentals::getPitchOffset(m_accidental);
|
|
}
|
|
|
|
Pitch::Pitch(int noteInCMajor, int octave, int pitch,
|
|
int octaveBase) :
|
|
m_pitch(pitch)
|
|
{
|
|
int natural = (octave - octaveBase) * 12 + scale_Cmajor[noteInCMajor];
|
|
m_accidental = Accidentals::getAccidental(pitch - natural);
|
|
}
|
|
|
|
|
|
Pitch::Pitch(char noteName, int octave, const Key &key,
|
|
const Accidental &explicitAccidental, int octaveBase) :
|
|
m_pitch(0),
|
|
m_accidental(explicitAccidental)
|
|
{
|
|
int height = getIndexForNote(noteName) - 2;
|
|
displayPitchToRawPitch(height, explicitAccidental,
|
|
Clef(), key, m_pitch);
|
|
|
|
// we now have the pitch within octave 5 (C == 60) -- though it
|
|
// might have spilled over at either end
|
|
if (m_pitch < 60) --octave;
|
|
if (m_pitch > 71) ++octave;
|
|
m_pitch = (octave - octaveBase) * 12 + m_pitch % 12;
|
|
}
|
|
|
|
Pitch::Pitch(int heightOnStaff, const Clef &clef, const Key &key,
|
|
const Accidental &explicitAccidental) :
|
|
m_pitch(0),
|
|
m_accidental(explicitAccidental)
|
|
{
|
|
displayPitchToRawPitch
|
|
(heightOnStaff, explicitAccidental, clef, key, m_pitch);
|
|
}
|
|
|
|
Pitch::Pitch(const Pitch &p) :
|
|
m_pitch(p.m_pitch),
|
|
m_accidental(p.m_accidental)
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
Pitch &
|
|
Pitch::operator=(const Pitch &p)
|
|
{
|
|
if (&p != this) {
|
|
m_pitch = p.m_pitch;
|
|
m_accidental = p.m_accidental;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
int
|
|
Pitch::getPerformancePitch() const
|
|
{
|
|
return m_pitch;
|
|
}
|
|
|
|
Accidental
|
|
Pitch::getAccidental(bool useSharps) const
|
|
{
|
|
return getDisplayAccidental(Key("C major"),
|
|
useSharps ? UseSharps : UseFlats);
|
|
}
|
|
|
|
Accidental
|
|
Pitch::getAccidental(const Key &key) const
|
|
{
|
|
if (m_accidental == NoAccidental || !validAccidental())
|
|
{
|
|
Accidental retval = resolveNoAccidental(m_pitch, key, UseKey);
|
|
//std::cout << "Resolved No/invalid accidental: chose " << retval << std::endl;
|
|
return retval;
|
|
}
|
|
else
|
|
{
|
|
//std::cout << "Returning specified accidental" << std::endl;
|
|
return m_accidental;
|
|
}
|
|
}
|
|
|
|
Accidental
|
|
Pitch::getDisplayAccidental(const Key &key) const
|
|
{
|
|
return getDisplayAccidental(key, UseKey);
|
|
}
|
|
|
|
Accidental
|
|
Pitch::getDisplayAccidental(const Key &key, NoAccidentalStrategy noAccidentalStrategy) const
|
|
{
|
|
int heightOnStaff;
|
|
Accidental accidental(m_accidental);
|
|
rawPitchToDisplayPitch(m_pitch, Clef(), key, heightOnStaff, accidental, noAccidentalStrategy);
|
|
return accidental;
|
|
}
|
|
|
|
int
|
|
Pitch::getNoteInScale(const Key &key) const
|
|
{
|
|
int p = m_pitch;
|
|
p -= key.getTonicPitch();
|
|
p -= Accidentals::getPitchOffset(getDisplayAccidental(key));
|
|
p += 24; // in case these calculations made it -ve
|
|
p %= 12;
|
|
|
|
if (key.isMinor()) return steps_Cminor_harmonic[p];
|
|
else return steps_Cmajor[p];
|
|
}
|
|
|
|
char
|
|
Pitch::getNoteName(const Key &key) const
|
|
{
|
|
int index = (getHeightOnStaff(Clef(Clef::Treble), key) + 72) % 7;
|
|
return getNoteForIndex(index);
|
|
}
|
|
|
|
int
|
|
Pitch::getHeightOnStaff(const Clef &clef, const Key &key) const
|
|
{
|
|
int heightOnStaff;
|
|
Accidental accidental(m_accidental);
|
|
rawPitchToDisplayPitch(m_pitch, clef, key, heightOnStaff, accidental, UseKey);
|
|
return heightOnStaff;
|
|
}
|
|
|
|
int
|
|
Pitch::getHeightOnStaff(const Clef &clef, bool useSharps) const
|
|
{
|
|
int heightOnStaff;
|
|
Accidental accidental(m_accidental);
|
|
rawPitchToDisplayPitch(m_pitch, clef, Key("C major"), heightOnStaff, accidental,
|
|
useSharps ? UseSharps : UseFlats);
|
|
return heightOnStaff;
|
|
}
|
|
|
|
int
|
|
Pitch::getOctave(int octaveBase) const
|
|
{
|
|
return m_pitch / 12 + octaveBase;
|
|
}
|
|
|
|
int
|
|
Pitch::getPitchInOctave() const
|
|
{
|
|
return m_pitch % 12;
|
|
}
|
|
|
|
bool
|
|
Pitch::isDiatonicInKey(const Key &key) const
|
|
{
|
|
if (getDisplayAccidental(key) == Accidentals::NoAccidental) return true;
|
|
|
|
// ### as used in the chord identifiers, this calls chords built on
|
|
// the raised sixth step diatonic -- may be correct, but it's
|
|
// misleading, as we're really looking for whether chords are
|
|
// often built on given tone
|
|
|
|
if (key.isMinor()) {
|
|
int stepsFromTonic = ((m_pitch - key.getTonicPitch() + 12) % 12);
|
|
if (stepsFromTonic == 9 || stepsFromTonic == 11) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::string
|
|
Pitch::getAsString(bool useSharps, bool inclOctave, int octaveBase) const
|
|
{
|
|
Accidental acc = getAccidental(useSharps);
|
|
|
|
std::string s;
|
|
s += getNoteName(useSharps ? Key("C major") : Key("A minor"));
|
|
|
|
if (acc == Accidentals::Sharp) s += "#";
|
|
else if (acc == Accidentals::Flat) s += "b";
|
|
|
|
if (!inclOctave) return s;
|
|
|
|
char tmp[10];
|
|
sprintf(tmp, "%s%d", s.c_str(), getOctave(octaveBase));
|
|
return std::string(tmp);
|
|
}
|
|
|
|
int
|
|
Pitch::getIndexForNote(char noteName)
|
|
{
|
|
if (islower(noteName)) noteName = toupper(noteName);
|
|
if (noteName < 'C') {
|
|
if (noteName < 'A') return 0; // error, really
|
|
else return noteName - 'A' + 5;
|
|
} else {
|
|
if (noteName > 'G') return 0; // error, really
|
|
else return noteName - 'C';
|
|
}
|
|
}
|
|
|
|
char
|
|
Pitch::getNoteForIndex(int index)
|
|
{
|
|
if (index < 0 || index > 6) return 'C'; // error, really
|
|
return "CDEFGAB"[index];
|
|
}
|
|
|
|
int
|
|
Pitch::getPerformancePitchFromRG21Pitch(int heightOnStaff,
|
|
const Accidental &accidental,
|
|
const Clef &clef,
|
|
const Key &)
|
|
{
|
|
// Rosegarden 2.1 pitches are a bit weird; see
|
|
// docs/data_struct/units.txt
|
|
|
|
// We pass the accidental and clef, a faked key of C major, and a
|
|
// flag telling displayPitchToRawPitch to ignore the clef offset
|
|
// and take only its octave into account
|
|
|
|
int p = 0;
|
|
displayPitchToRawPitch(heightOnStaff, accidental, clef, Key(), p, true);
|
|
return p;
|
|
}
|
|
|
|
Pitch Pitch::transpose(const Key &key, int pitchDelta, int heightDelta)
|
|
{
|
|
// get old accidental
|
|
Accidental oldAccidental = getAccidental(key);
|
|
|
|
// get old step
|
|
// TODO: maybe we should write an oldPitchObj.getOctave(0, key) that takes into account accidentals
|
|
// properly (e.g. yielding '0' instead of '1' for B#0). For now workaround here.
|
|
Pitch oldPitchWithoutAccidental(getPerformancePitch() - Accidentals::getPitchOffset(oldAccidental), Natural);
|
|
Key cmaj = Key();
|
|
int oldStep = oldPitchWithoutAccidental.getNoteInScale(cmaj) + oldPitchWithoutAccidental.getOctave(0) * 7;
|
|
|
|
// calculate new pitch and step
|
|
int newPitch = getPerformancePitch() + pitchDelta;
|
|
int newStep = oldStep + heightDelta;
|
|
|
|
// could happen for example when transposing the tonic of a key downwards
|
|
if (newStep < 0 || newPitch < 0) {
|
|
newStep += 7;
|
|
newPitch += 12;
|
|
}
|
|
|
|
// should not happen
|
|
if (newStep < 0 || newPitch < 0) {
|
|
std::cerr << "Internal error in NotationTypes, Pitch::transpose()"
|
|
<< std::endl;
|
|
}
|
|
|
|
// calculate new accidental for step
|
|
int pitchWithoutAccidental = ((newStep / 7) * 12 + scale_Cmajor[newStep % 7]);
|
|
int newAccidentalOffset = newPitch - pitchWithoutAccidental;
|
|
|
|
// construct pitch-object to return
|
|
Pitch newPitchObj(newPitch, Accidentals::getAccidental(newAccidentalOffset));
|
|
return newPitchObj;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Note
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const string Note::EventType = "note";
|
|
const string Note::EventRestType = "rest";
|
|
const int Note::EventRestSubOrdering = 10;
|
|
|
|
const timeT Note::m_shortestTime = basePPQ / 16;
|
|
|
|
Note& Note::operator=(const Note &n)
|
|
{
|
|
if (&n == this) return *this;
|
|
m_type = n.m_type;
|
|
m_dots = n.m_dots;
|
|
return *this;
|
|
}
|
|
|
|
timeT Note::getDurationAux() const
|
|
{
|
|
int duration = m_shortestTime * (1 << m_type);
|
|
int extra = duration / 2;
|
|
for (int dots = m_dots; dots > 0; --dots) {
|
|
duration += extra;
|
|
extra /= 2;
|
|
}
|
|
return duration;
|
|
}
|
|
|
|
|
|
Note Note::getNearestNote(timeT duration, int maxDots)
|
|
{
|
|
int tag = Shortest - 1;
|
|
timeT d(duration / m_shortestTime);
|
|
while (d > 0) { ++tag; d /= 2; }
|
|
|
|
// cout << "Note::getNearestNote: duration " << duration <<
|
|
// " leading to tag " << tag << endl;
|
|
if (tag < Shortest) return Note(Shortest);
|
|
if (tag > Longest) return Note(Longest, maxDots);
|
|
|
|
timeT prospective = Note(tag, 0).getDuration();
|
|
int dots = 0;
|
|
timeT extra = prospective / 2;
|
|
|
|
while (dots <= maxDots &&
|
|
dots <= tag) { // avoid TooManyDots exception from Note ctor
|
|
prospective += extra;
|
|
if (prospective > duration) return Note(tag, dots);
|
|
extra /= 2;
|
|
++dots;
|
|
// cout << "added another dot okay" << endl;
|
|
}
|
|
|
|
if (tag < Longest) return Note(tag + 1, 0);
|
|
else return Note(tag, std::max(maxDots, tag));
|
|
}
|
|
|
|
Event *Note::getAsNoteEvent(timeT absoluteTime, int pitch) const
|
|
{
|
|
Event *e = new Event(EventType, absoluteTime, getDuration());
|
|
e->set<Int>(BaseProperties::PITCH, pitch);
|
|
return e;
|
|
}
|
|
|
|
Event *Note::getAsRestEvent(timeT absoluteTime) const
|
|
{
|
|
Event *e = new Event(EventRestType, absoluteTime, getDuration());
|
|
return e;
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// TimeSignature
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
const string TimeSignature::EventType = "timesignature";
|
|
const int TimeSignature::EventSubOrdering = -150;
|
|
const PropertyName TimeSignature::NumeratorPropertyName = "numerator";
|
|
const PropertyName TimeSignature::DenominatorPropertyName = "denominator";
|
|
const PropertyName TimeSignature::ShowAsCommonTimePropertyName = "common";
|
|
const PropertyName TimeSignature::IsHiddenPropertyName = "hidden";
|
|
const PropertyName TimeSignature::HasHiddenBarsPropertyName = "hiddenbars";
|
|
const TimeSignature TimeSignature::DefaultTimeSignature = TimeSignature(4, 4);
|
|
|
|
TimeSignature::TimeSignature(int numerator, int denominator,
|
|
bool preferCommon, bool hidden, bool hiddenBars)
|
|
// throw (BadTimeSignature)
|
|
: m_numerator(numerator), m_denominator(denominator),
|
|
m_common(preferCommon &&
|
|
(m_denominator == m_numerator &&
|
|
(m_numerator == 2 || m_numerator == 4))),
|
|
m_hidden(hidden),
|
|
m_hiddenBars(hiddenBars)
|
|
{
|
|
if (numerator < 1 || denominator < 1) {
|
|
throw BadTimeSignature("Numerator and denominator must be positive");
|
|
}
|
|
}
|
|
|
|
TimeSignature::TimeSignature(const Event &e)
|
|
// throw (Event::NoData, Event::BadType, BadTimeSignature)
|
|
{
|
|
if (e.getType() != EventType) {
|
|
throw Event::BadType("TimeSignature model event", EventType, e.getType());
|
|
}
|
|
m_numerator = 4;
|
|
m_denominator = 4;
|
|
|
|
if (e.has(NumeratorPropertyName)) {
|
|
m_numerator = e.get<Int>(NumeratorPropertyName);
|
|
}
|
|
|
|
if (e.has(DenominatorPropertyName)) {
|
|
m_denominator = e.get<Int>(DenominatorPropertyName);
|
|
}
|
|
|
|
m_common = false;
|
|
e.get<Bool>(ShowAsCommonTimePropertyName, m_common);
|
|
|
|
m_hidden = false;
|
|
e.get<Bool>(IsHiddenPropertyName, m_hidden);
|
|
|
|
m_hiddenBars = false;
|
|
e.get<Bool>(HasHiddenBarsPropertyName, m_hiddenBars);
|
|
|
|
if (m_numerator < 1 || m_denominator < 1) {
|
|
throw BadTimeSignature("Numerator and denominator must be positive");
|
|
}
|
|
}
|
|
|
|
TimeSignature& TimeSignature::operator=(const TimeSignature &ts)
|
|
{
|
|
if (&ts == this) return *this;
|
|
m_numerator = ts.m_numerator;
|
|
m_denominator = ts.m_denominator;
|
|
m_common = ts.m_common;
|
|
m_hidden = ts.m_hidden;
|
|
m_hiddenBars = ts.m_hiddenBars;
|
|
return *this;
|
|
}
|
|
|
|
timeT TimeSignature::getBarDuration() const
|
|
{
|
|
setInternalDurations();
|
|
return m_barDuration;
|
|
}
|
|
|
|
timeT TimeSignature::getBeatDuration() const
|
|
{
|
|
setInternalDurations();
|
|
return m_beatDuration;
|
|
}
|
|
|
|
timeT TimeSignature::getUnitDuration() const
|
|
{
|
|
return m_crotchetTime * 4 / m_denominator;
|
|
}
|
|
|
|
Note::Type TimeSignature::getUnit() const
|
|
{
|
|
int c, d;
|
|
for (c = 0, d = m_denominator; d > 1; d /= 2) ++c;
|
|
return Note::Semibreve - c;
|
|
}
|
|
|
|
bool TimeSignature::isDotted() const
|
|
{
|
|
setInternalDurations();
|
|
return m_dotted;
|
|
}
|
|
|
|
Event *TimeSignature::getAsEvent(timeT absoluteTime) const
|
|
{
|
|
Event *e = new Event(EventType, absoluteTime, 0, EventSubOrdering);
|
|
e->set<Int>(NumeratorPropertyName, m_numerator);
|
|
e->set<Int>(DenominatorPropertyName, m_denominator);
|
|
e->set<Bool>(ShowAsCommonTimePropertyName, m_common);
|
|
e->set<Bool>(IsHiddenPropertyName, m_hidden);
|
|
e->set<Bool>(HasHiddenBarsPropertyName, m_hiddenBars);
|
|
return e;
|
|
}
|
|
|
|
// This doesn't consider subdivisions of the bar larger than a beat in
|
|
// any time other than 4/4, but it should handle the usual time signatures
|
|
// correctly (compound time included).
|
|
|
|
void TimeSignature::getDurationListForInterval(DurationList &dlist,
|
|
timeT duration,
|
|
timeT startOffset) const
|
|
{
|
|
setInternalDurations();
|
|
|
|
timeT offset = startOffset;
|
|
timeT durationRemaining = duration;
|
|
|
|
while (durationRemaining > 0) {
|
|
|
|
// Everything in this loop is of the form, "if we're on a
|
|
// [unit] boundary and there's a [unit] of space left to fill,
|
|
// insert a [unit] of time."
|
|
|
|
// See if we can insert a bar of time.
|
|
|
|
if (offset % m_barDuration == 0
|
|
&& durationRemaining >= m_barDuration) {
|
|
|
|
getDurationListForBar(dlist);
|
|
durationRemaining -= m_barDuration,
|
|
offset += m_barDuration;
|
|
|
|
}
|
|
|
|
// If that fails and we're in 4/4 time, see if we can insert a
|
|
// half-bar of time.
|
|
|
|
//_else_ if!
|
|
else if (m_numerator == 4 && m_denominator == 4
|
|
&& offset % (m_barDuration/2) == 0
|
|
&& durationRemaining >= m_barDuration/2) {
|
|
|
|
dlist.push_back(m_barDuration/2);
|
|
durationRemaining -= m_barDuration/2;
|
|
offset += m_barDuration;
|
|
|
|
}
|
|
|
|
// If that fails, see if we can insert a beat of time.
|
|
|
|
else if (offset % m_beatDuration == 0
|
|
&& durationRemaining >= m_beatDuration) {
|
|
|
|
dlist.push_back(m_beatDuration);
|
|
durationRemaining -= m_beatDuration;
|
|
offset += m_beatDuration;
|
|
|
|
}
|
|
|
|
// If that fails, see if we can insert a beat-division of time
|
|
// (half the beat in simple time, a third of the beat in compound
|
|
// time)
|
|
|
|
else if (offset % m_beatDivisionDuration == 0
|
|
&& durationRemaining >= m_beatDivisionDuration) {
|
|
|
|
dlist.push_back(m_beatDivisionDuration);
|
|
durationRemaining -= m_beatDivisionDuration;
|
|
offset += m_beatDivisionDuration;
|
|
|
|
}
|
|
|
|
// cc: In practice, if the time we have remaining is shorter
|
|
// than our shortest note then we should just insert a single
|
|
// unit of the correct time; we won't be able to do anything
|
|
// useful with any shorter units anyway.
|
|
|
|
else if (durationRemaining <= Note(Note::Shortest).getDuration()) {
|
|
|
|
dlist.push_back(durationRemaining);
|
|
offset += durationRemaining;
|
|
durationRemaining = 0;
|
|
|
|
}
|
|
|
|
// If that fails, keep halving the beat division until we
|
|
// find something to insert. (This could be part of the beat-division
|
|
// case; it's only in its own place for clarity.)
|
|
|
|
else {
|
|
|
|
timeT currentDuration = m_beatDivisionDuration;
|
|
|
|
while ( !(offset % currentDuration == 0
|
|
&& durationRemaining >= currentDuration) ) {
|
|
|
|
if (currentDuration <= Note(Note::Shortest).getDuration()) {
|
|
|
|
// okay, this isn't working. If our duration takes
|
|
// us past the next beat boundary, fill with an exact
|
|
// rest duration to there and then continue --cc
|
|
|
|
timeT toNextBeat =
|
|
m_beatDuration - (offset % m_beatDuration);
|
|
|
|
if (durationRemaining > toNextBeat) {
|
|
currentDuration = toNextBeat;
|
|
} else {
|
|
currentDuration = durationRemaining;
|
|
}
|
|
break;
|
|
}
|
|
|
|
currentDuration /= 2;
|
|
}
|
|
|
|
dlist.push_back(currentDuration);
|
|
durationRemaining -= currentDuration;
|
|
offset += currentDuration;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void TimeSignature::getDurationListForBar(DurationList &dlist) const
|
|
{
|
|
|
|
// If the bar's length can be represented with one long symbol, do it.
|
|
// Otherwise, represent it as individual beats.
|
|
|
|
if (m_barDuration == m_crotchetTime ||
|
|
m_barDuration == m_crotchetTime * 2 ||
|
|
m_barDuration == m_crotchetTime * 4 ||
|
|
m_barDuration == m_crotchetTime * 8 ||
|
|
m_barDuration == m_dottedCrotchetTime ||
|
|
m_barDuration == m_dottedCrotchetTime * 2 ||
|
|
m_barDuration == m_dottedCrotchetTime * 4 ||
|
|
m_barDuration == m_dottedCrotchetTime * 8) {
|
|
|
|
dlist.push_back(getBarDuration());
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < getBeatsPerBar(); ++i) {
|
|
dlist.push_back(getBeatDuration());
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
int TimeSignature::getEmphasisForTime(timeT offset)
|
|
{
|
|
setInternalDurations();
|
|
|
|
if (offset % m_barDuration == 0)
|
|
return 4;
|
|
else if (m_numerator == 4 && m_denominator == 4 &&
|
|
offset % (m_barDuration/2) == 0)
|
|
return 3;
|
|
else if (offset % m_beatDuration == 0)
|
|
return 2;
|
|
else if (offset % m_beatDivisionDuration == 0)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
void TimeSignature::getDivisions(int depth, std::vector<int> &divisions) const
|
|
{
|
|
divisions.clear();
|
|
|
|
if (depth <= 0) return;
|
|
timeT base = getBarDuration(); // calls setInternalDurations
|
|
/*
|
|
if (m_numerator == 4 && m_denominator == 4) {
|
|
divisions.push_back(2);
|
|
base /= 2;
|
|
--depth;
|
|
}
|
|
*/
|
|
if (depth <= 0) return;
|
|
|
|
divisions.push_back(base / m_beatDuration);
|
|
base = m_beatDuration;
|
|
--depth;
|
|
|
|
if (depth <= 0) return;
|
|
|
|
if (m_dotted) divisions.push_back(3);
|
|
else divisions.push_back(2);
|
|
--depth;
|
|
|
|
while (depth > 0) {
|
|
divisions.push_back(2);
|
|
--depth;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void TimeSignature::setInternalDurations() const
|
|
{
|
|
int unitLength = m_crotchetTime * 4 / m_denominator;
|
|
|
|
m_barDuration = m_numerator * unitLength;
|
|
|
|
// Is 3/8 dotted time? This will report that it isn't, because of
|
|
// the check for m_numerator > 3 -- but otherwise we'd get a false
|
|
// positive with 3/4
|
|
|
|
// [rf] That's an acceptable answer, according to my theory book. In
|
|
// practice, you can say it's dotted time iff it has 6, 9, or 12 on top.
|
|
|
|
m_dotted = (m_numerator % 3 == 0 &&
|
|
m_numerator > 3 &&
|
|
m_barDuration >= m_dottedCrotchetTime);
|
|
|
|
if (m_dotted) {
|
|
m_beatDuration = unitLength * 3;
|
|
m_beatDivisionDuration = unitLength;
|
|
}
|
|
else {
|
|
m_beatDuration = unitLength;
|
|
m_beatDivisionDuration = unitLength / 2;
|
|
}
|
|
|
|
}
|
|
|
|
const timeT TimeSignature::m_crotchetTime = basePPQ;
|
|
const timeT TimeSignature::m_dottedCrotchetTime = basePPQ + basePPQ/2;
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// AccidentalTable
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
AccidentalTable::AccidentalTable(const Key &key, const Clef &clef,
|
|
OctaveType octaves, BarResetType barReset) :
|
|
m_key(key), m_clef(clef),
|
|
m_octaves(octaves), m_barReset(barReset)
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
AccidentalTable::AccidentalTable(const AccidentalTable &t) :
|
|
m_key(t.m_key), m_clef(t.m_clef),
|
|
m_octaves(t.m_octaves), m_barReset(t.m_barReset),
|
|
m_accidentals(t.m_accidentals),
|
|
m_canonicalAccidentals(t.m_canonicalAccidentals),
|
|
m_newAccidentals(t.m_newAccidentals),
|
|
m_newCanonicalAccidentals(t.m_newCanonicalAccidentals)
|
|
{
|
|
// nothing else
|
|
}
|
|
|
|
AccidentalTable &
|
|
AccidentalTable::operator=(const AccidentalTable &t)
|
|
{
|
|
if (&t != this) {
|
|
m_key = t.m_key;
|
|
m_clef = t.m_clef;
|
|
m_octaves = t.m_octaves;
|
|
m_barReset = t.m_barReset;
|
|
m_accidentals = t.m_accidentals;
|
|
m_canonicalAccidentals = t.m_canonicalAccidentals;
|
|
m_newAccidentals = t.m_newAccidentals;
|
|
m_newCanonicalAccidentals = t.m_newCanonicalAccidentals;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Accidental
|
|
AccidentalTable::processDisplayAccidental(const Accidental &acc0, int height,
|
|
bool &cautionary)
|
|
{
|
|
Accidental acc = acc0;
|
|
|
|
int canonicalHeight = Key::canonicalHeight(height);
|
|
Accidental keyAcc = m_key.getAccidentalAtHeight(canonicalHeight, m_clef);
|
|
|
|
Accidental normalAcc = NoAccidental;
|
|
Accidental canonicalAcc = NoAccidental;
|
|
Accidental prevBarAcc = NoAccidental;
|
|
|
|
if (m_octaves == OctavesEquivalent ||
|
|
m_octaves == OctavesCautionary) {
|
|
|
|
AccidentalMap::iterator i = m_canonicalAccidentals.find(canonicalHeight);
|
|
if (i != m_canonicalAccidentals.end() && !i->second.previousBar) {
|
|
canonicalAcc = i->second.accidental;
|
|
}
|
|
}
|
|
|
|
if (m_octaves == OctavesEquivalent) {
|
|
normalAcc = canonicalAcc;
|
|
} else {
|
|
AccidentalMap::iterator i = m_accidentals.find(height);
|
|
if (i != m_accidentals.end() && !i->second.previousBar) {
|
|
normalAcc = i->second.accidental;
|
|
}
|
|
}
|
|
|
|
if (m_barReset != BarResetNone) {
|
|
AccidentalMap::iterator i = m_accidentals.find(height);
|
|
if (i != m_accidentals.end() && i->second.previousBar) {
|
|
prevBarAcc = i->second.accidental;
|
|
}
|
|
}
|
|
|
|
// std::cerr << "AccidentalTable::processDisplayAccidental: acc " << acc0 << ", h " << height << ", caut " << cautionary << ", ch " << canonicalHeight << ", keyacc " << keyAcc << " canacc " << canonicalAcc << " noracc " << normalAcc << " oct " << m_octaves << " barReset = " << m_barReset << " pbacc " << prevBarAcc << std::endl;
|
|
|
|
if (acc == NoAccidental) acc = keyAcc;
|
|
|
|
if (m_octaves == OctavesIndependent ||
|
|
m_octaves == OctavesEquivalent) {
|
|
|
|
if (normalAcc == NoAccidental) {
|
|
normalAcc = keyAcc;
|
|
}
|
|
|
|
if (acc == normalAcc) {
|
|
if (!cautionary) acc = NoAccidental;
|
|
} else if (acc == NoAccidental) {
|
|
if (normalAcc != Natural) {
|
|
acc = Natural;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (normalAcc != NoAccidental) {
|
|
if (acc != normalAcc) {
|
|
if (acc == NoAccidental) {
|
|
if (normalAcc != Natural) {
|
|
acc = Natural;
|
|
}
|
|
}
|
|
} else { // normalAcc != NoAccidental, acc == normalAcc
|
|
if (canonicalAcc != NoAccidental && canonicalAcc != normalAcc) {
|
|
cautionary = true;
|
|
} else { // canonicalAcc == NoAccidental || canonicalAcc == normalAcc
|
|
if (!cautionary) {
|
|
acc = NoAccidental;
|
|
}
|
|
}
|
|
}
|
|
} else { // normalAcc == NoAccidental
|
|
if (acc != keyAcc && keyAcc != Natural) {
|
|
if (acc == NoAccidental) {
|
|
acc = Natural;
|
|
}
|
|
} else { // normalAcc == NoAccidental, acc == keyAcc
|
|
if (canonicalAcc != NoAccidental && canonicalAcc != keyAcc) {
|
|
cautionary = true;
|
|
if (acc == NoAccidental) {
|
|
acc = Natural;
|
|
}
|
|
} else { // canonicalAcc == NoAccidental || canonicalAcc == keyAcc
|
|
if (!cautionary) {
|
|
acc = NoAccidental;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_barReset != BarResetNone) {
|
|
if (acc == NoAccidental) {
|
|
if (prevBarAcc != NoAccidental &&
|
|
prevBarAcc != keyAcc &&
|
|
!(prevBarAcc == Natural && keyAcc == NoAccidental)) {
|
|
cautionary = (m_barReset == BarResetCautionary);
|
|
if (keyAcc == NoAccidental) {
|
|
acc = Natural;
|
|
} else {
|
|
acc = keyAcc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (acc != NoAccidental) {
|
|
m_newAccidentals[height] = AccidentalRec(acc, false);
|
|
m_newCanonicalAccidentals[canonicalHeight] = AccidentalRec(acc, false);
|
|
}
|
|
|
|
return acc;
|
|
}
|
|
|
|
void
|
|
AccidentalTable::update()
|
|
{
|
|
m_accidentals = m_newAccidentals;
|
|
m_canonicalAccidentals = m_newCanonicalAccidentals;
|
|
}
|
|
|
|
void
|
|
AccidentalTable::newBar()
|
|
{
|
|
for (AccidentalMap::iterator i = m_accidentals.begin();
|
|
i != m_accidentals.end(); ) {
|
|
|
|
if (i->second.previousBar) {
|
|
AccidentalMap::iterator j = i;
|
|
++j;
|
|
m_accidentals.erase(i);
|
|
i = j;
|
|
} else {
|
|
i->second.previousBar = true;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
m_canonicalAccidentals.clear();
|
|
|
|
m_newAccidentals = m_accidentals;
|
|
m_newCanonicalAccidentals.clear();
|
|
}
|
|
|
|
void
|
|
AccidentalTable::newClef(const Clef &clef)
|
|
{
|
|
m_clef = clef;
|
|
}
|
|
|
|
|
|
} // close namespace
|