MuseScore/libmscore/utils.cpp
2014-11-22 07:55:28 -07:00

843 lines
26 KiB
C++

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2002-2011 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "config.h"
#include "score.h"
#include "page.h"
#include "segment.h"
#include "clef.h"
#include "utils.h"
#include "system.h"
#include "measure.h"
#include "pitchspelling.h"
#include "chordrest.h"
#include "part.h"
#include "staff.h"
#include "note.h"
#include "chord.h"
#include "key.h"
namespace Ms {
//---------------------------------------------------------
// handleRect
//---------------------------------------------------------
QRectF handleRect(const QPointF& pos)
{
return QRectF(pos.x()-4, pos.y()-4, 8, 8);
}
//---------------------------------------------------------
// tick2measure
//---------------------------------------------------------
Measure* Score::tick2measure(int tick) const
{
if (tick == -1)
return lastMeasure();
Measure* lm = 0;
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
if (tick < m->tick())
return lm;
lm = m;
}
// check last measure
if (lm && (tick >= lm->tick()) && (tick <= lm->endTick()))
return lm;
qDebug("tick2measure %d (max %d) not found", tick, lm ? lm->tick() : -1);
return 0;
}
//---------------------------------------------------------
// tick2measureMM
//---------------------------------------------------------
Measure* Score::tick2measureMM(int tick) const
{
if (tick == -1)
return lastMeasureMM();
Measure* lm = 0;
for (Measure* m = firstMeasureMM(); m; m = m->nextMeasureMM()) {
if (tick < m->tick())
return lm;
lm = m;
}
// check last measure
if (lm && (tick >= lm->tick()) && (tick <= lm->endTick()))
return lm;
qDebug("tick2measureMM %d (max %d) not found", tick, lm ? lm->tick() : -1);
return 0;
}
//---------------------------------------------------------
// tick2measureBase
//---------------------------------------------------------
MeasureBase* Score::tick2measureBase(int tick) const
{
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
int st = mb->tick();
int l = mb->ticks();
if (tick >= st && tick < (st+l))
return mb;
}
// qDebug("tick2measureBase %d not found", tick);
return 0;
}
//---------------------------------------------------------
// tick2segment
//---------------------------------------------------------
Segment* Score::tick2segmentMM(int tick, bool first, Segment::Type st) const
{
return tick2segment(tick,first,st,true);
}
Segment* Score::tick2segment(int tick, bool first, Segment::Type st, bool useMMrest ) const
{
Measure* m;
if (useMMrest) {
m = tick2measureMM(tick);
// When mmRest force tick to the first segment of mmRest.
if (m && m->isMMRest())
tick = m->tick();
}
else
m = tick2measure(tick);
if (m == 0) {
qDebug(" no segment for tick %d", tick);
return 0;
}
for (Segment* segment = m->first(st); segment;) {
int t1 = segment->tick();
Segment* nsegment = segment->next(st);
int t2 = nsegment ? nsegment->tick() : INT_MAX;
if ((tick == t1) && (first || (tick < t2)))
return segment;
segment = nsegment;
}
return 0;
}
//---------------------------------------------------------
// tick2segmentEnd
//---------------------------------------------------------
/**
Find a segment containing a note or rest in \a track ending at \a tick
Return the segment or null
*/
Segment* Score::tick2segmentEnd(int track, int tick) const
{
Measure* m = tick2measure(tick);
if (m == 0) {
qDebug("tick2segment(): not found tick %d", tick);
return 0;
}
// loop over all segments
for (Segment* segment = m->first(Segment::Type::ChordRest); segment; segment = segment->next(Segment::Type::ChordRest)) {
ChordRest* cr = static_cast<ChordRest*>(segment->element(track));
if (!cr)
continue;
// TODO LVI: check if following is correct, see exceptions in
// ExportMusicXml::chord() and ExportMusicXml::rest()
int endTick = cr->tick() + cr->actualTicks();
if (endTick < tick)
continue; // not found yet
else if (endTick == tick) {
return segment; // found it
}
else {
// endTick > tick (beyond the tick we are looking for)
return 0;
}
}
return 0;
}
//---------------------------------------------------------
// tick2leftSegment
/// return the segment at this tick position if any or
/// the first segment *before* this tick position
//---------------------------------------------------------
Segment* Score::tick2leftSegment(int tick) const
{
Measure* m = tick2measure(tick);
if (m == 0) {
qDebug("tick2leftSegment(): not found tick %d", tick);
return 0;
}
// loop over all segments
Segment* ps = 0;
for (Segment* s = m->first(Segment::Type::ChordRest); s; s = s->next(Segment::Type::ChordRest)) {
if (tick < s->tick())
return ps;
else if (tick == s->tick())
return s;
ps = s;
}
return ps;
}
//---------------------------------------------------------
// tick2rightSegment
/// return the segment at this tick position if any or
/// the first segment *after* this tick position
//---------------------------------------------------------
Segment* Score::tick2rightSegment(int tick) const
{
Measure* m = tick2measure(tick);
if (m == 0) {
qDebug("tick2nearestSegment(): not found tick %d", tick);
return 0;
}
// loop over all segments
for (Segment* s = m->first(Segment::Type::ChordRest); s; s = s->next(Segment::Type::ChordRest)) {
if (tick <= s->tick())
return s;
}
return 0;
}
//---------------------------------------------------------
// getStaff
//---------------------------------------------------------
int getStaff(System* system, const QPointF& p)
{
QPointF pp = p - system->page()->pos() - system->pos();
for (int i = 0; i < system->page()->score()->nstaves(); ++i) {
qreal sp = system->spatium();
QRectF r = system->bboxStaff(i).adjusted(0.0, -sp, 0.0, sp);
if (r.contains(pp))
return i;
}
return -1;
}
//---------------------------------------------------------
// nextSeg
//---------------------------------------------------------
int Score::nextSeg(int tick, int track)
{
Segment* seg = tick2segment(tick);
while (seg) {
seg = seg->next1(Segment::Type::ChordRest);
if (seg == 0)
break;
if (seg->element(track))
break;
}
return seg ? seg->tick() : -1;
}
//---------------------------------------------------------
// nextSeg1
//---------------------------------------------------------
Segment* nextSeg1(Segment* seg, int& track)
{
int staffIdx = track / VOICES;
int startTrack = staffIdx * VOICES;
int endTrack = startTrack + VOICES;
while ((seg = seg->next1(Segment::Type::ChordRest))) {
for (int t = startTrack; t < endTrack; ++t) {
if (seg->element(t)) {
track = t;
return seg;
}
}
}
return 0;
}
//---------------------------------------------------------
// prevSeg1
//---------------------------------------------------------
Segment* prevSeg1(Segment* seg, int& track)
{
int staffIdx = track / VOICES;
int startTrack = staffIdx * VOICES;
int endTrack = startTrack + VOICES;
while ((seg = seg->prev1(Segment::Type::ChordRest))) {
for (int t = startTrack; t < endTrack; ++t) {
if (seg->element(t)) {
track = t;
return seg;
}
}
}
return 0;
}
//---------------------------------------------------------
// pitchKeyAdjust
// change entered note to sounding pitch dependend
// on key.
// Example: if F is entered in G-major, a Fis is played
// key -7 ... +7
//---------------------------------------------------------
int pitchKeyAdjust(int step, Key key)
{
static int ptab[15][7] = {
// c d e f g a b
{ -1, 1, 3, 4, 6, 8, 10 }, // Bes
{ -1, 1, 3, 5, 6, 8, 10 }, // Ges
{ 0, 1, 3, 5, 6, 8, 10 }, // Des
{ 0, 1, 3, 5, 7, 8, 10 }, // As
{ 0, 2, 3, 5, 7, 8, 10 }, // Es
{ 0, 2, 3, 5, 7, 9, 10 }, // B
{ 0, 2, 4, 5, 7, 9, 10 }, // F
{ 0, 2, 4, 5, 7, 9, 11 }, // C
{ 0, 2, 4, 6, 7, 9, 11 }, // G
{ 1, 2, 4, 6, 7, 9, 11 }, // D
{ 1, 2, 4, 6, 8, 9, 11 }, // A
{ 1, 3, 4, 6, 8, 9, 11 }, // E
{ 1, 3, 4, 6, 8, 10, 11 }, // H
{ 1, 3, 5, 6, 8, 10, 11 }, // Fis
{ 1, 3, 5, 6, 8, 10, 12 }, // Cis
};
return ptab[int(key)+7][step];
}
//---------------------------------------------------------
// y2pitch
//---------------------------------------------------------
int y2pitch(qreal y, ClefType clef, qreal _spatium)
{
int l = lrint(y / _spatium * 2.0);
return line2pitch(l, clef, Key::C);
}
//---------------------------------------------------------
// line2pitch
// key -7 ... +7
//---------------------------------------------------------
int line2pitch(int line, ClefType clef, Key key)
{
int l = ClefInfo::pitchOffset(clef) - line;
int octave = 0;
while (l < 0) {
l += 7;
octave++;
}
octave += l / 7;
l = l % 7;
int pitch = pitchKeyAdjust(l, key) + octave * 12;
if (pitch > 127)
pitch = 127;
else if (pitch < 0)
pitch = 0;
return pitch;
}
//---------------------------------------------------------
// quantizeLen
//---------------------------------------------------------
int quantizeLen(int len, int raster)
{
if (raster == 0)
return len;
return int( ((float)len/raster) + 0.5 ) * raster; //round to the closest multiple of raster
}
//---------------------------------------------------------
// selectNoteMessage
//---------------------------------------------------------
void selectNoteMessage()
{
if (!MScore::noGui)
QMessageBox::information(0,
QMessageBox::tr("MuseScore"),
QMessageBox::tr("No note selected:\n"
"Please select a single note and retry operation\n"),
QMessageBox::Ok, QMessageBox::NoButton);
}
void selectNoteRestMessage()
{
if (!MScore::noGui)
QMessageBox::information(0,
QMessageBox::tr("MuseScore"),
QMessageBox::tr("No note or rest selected:\n"
"Please select a single note or rest and retry operation\n"),
QMessageBox::Ok, QMessageBox::NoButton);
}
void selectNoteSlurMessage()
{
if (!MScore::noGui)
QMessageBox::information(0,
QMessageBox::tr("MuseScore"),
QMessageBox::tr("Please select a single note or slur and retry operation\n"),
QMessageBox::Ok, QMessageBox::NoButton);
}
void selectStavesMessage()
{
if (!MScore::noGui)
QMessageBox::information(0,
QMessageBox::tr("MuseScore"),
QMessageBox::tr("Please select one or more staves and retry operation\n"),
QMessageBox::Ok, QMessageBox::NoButton);
}
static const char* vall[] = {
QT_TRANSLATE_NOOP("utils", "c"),
QT_TRANSLATE_NOOP("utils", "c#"),
QT_TRANSLATE_NOOP("utils", "d"),
QT_TRANSLATE_NOOP("utils", "d#"),
QT_TRANSLATE_NOOP("utils", "e"),
QT_TRANSLATE_NOOP("utils", "f"),
QT_TRANSLATE_NOOP("utils", "f#"),
QT_TRANSLATE_NOOP("utils", "g"),
QT_TRANSLATE_NOOP("utils", "g#"),
QT_TRANSLATE_NOOP("utils", "a"),
QT_TRANSLATE_NOOP("utils", "a#"),
QT_TRANSLATE_NOOP("utils", "b")
};
static const char* valu[] = {
QT_TRANSLATE_NOOP("utils", "C"),
QT_TRANSLATE_NOOP("utils", "C#"),
QT_TRANSLATE_NOOP("utils", "D"),
QT_TRANSLATE_NOOP("utils", "D#"),
QT_TRANSLATE_NOOP("utils", "E"),
QT_TRANSLATE_NOOP("utils", "F"),
QT_TRANSLATE_NOOP("utils", "F#"),
QT_TRANSLATE_NOOP("utils", "G"),
QT_TRANSLATE_NOOP("utils", "G#"),
QT_TRANSLATE_NOOP("utils", "A"),
QT_TRANSLATE_NOOP("utils", "A#"),
QT_TRANSLATE_NOOP("utils", "B")
};
/*!
* Returns the string representation of the given pitch.
*
* Returns the latin letter name, accidental, and octave numeral.
* Uses upper case only for pitches 0-24.
*
* @param v
* The pitch number of the note.
*
* @return
* The string representation of the note.
*/
QString pitch2string(int v)
{
if (v < 0 || v > 127)
return QString("----");
int octave = (v / 12) - 2;
QString o;
o.sprintf("%d", octave);
int i = v % 12;
return qApp->translate("utils", octave < 0 ? valu[i] : vall[i]) + o;
}
/*!
* An array of all supported interval sorted by size.
*
* Because intervals can be spelled differently, this array
* tracks all the different valid intervals. They are arranged
* in diatonic then chromatic order.
*/
Interval intervalList[26] = {
// diatonic - chromatic
Interval(0, 0), // 0 Perfect Unison
Interval(0, 1), // 1 Augmented Unison
Interval(1, 0), // 2 Diminished Second
Interval(1, 1), // 3 Minor Second
Interval(1, 2), // 4 Major Second
Interval(1, 3), // 5 Augmented Second
Interval(2, 2), // 6 Diminished Third
Interval(2, 3), // 7 Minor Third
Interval(2, 4), // 8 Major Third
Interval(2, 5), // 9 Augmented Third
Interval(3, 4), // 10 Diminished Fourth
Interval(3, 5), // 11 Perfect Fourth
Interval(3, 6), // 12 Augmented Fourth
Interval(4, 6), // 13 Diminished Fifth
Interval(4, 7), // 14 Perfect Fifth
Interval(4, 8), // 15 Augmented Fifth
Interval(5, 7), // 16 Diminished Sixth
Interval(5, 8), // 17 Minor Sixth
Interval(5, 9), // 18 Major Sixth
Interval(5, 10), // 19 Augmented Sixth
Interval(6, 9), // 20 Diminished Seventh
Interval(6, 10), // 21 Minor Seventh
Interval(6, 11), // 22 Major Seventh
Interval(6, 12), // 23 Augmented Seventh
Interval(7, 11), // 24 Diminshed Octave
Interval(7, 12) // 25 Perfect Octave
};
/*!
* Finds the most likely diatonic interval for a semitone distance.
*
* Uses the most common diatonic intervals.
*
* @param
* The number of semitones in the chromatic interval.
* Negative semitones will simply be made positive.
*
* @return
* The number of diatonic steps in the interval.
*/
int chromatic2diatonic(int semitones)
{
static int il[12] = {
0, // Perfect Unison
3, // Minor Second
4, // Major Second
7, // Minor Third
8, // Major Third
11, // Perfect Fourth
12, // Augmented Fourth
14, // Perfect Fifth
17, // Minor Sixth
18, // Major Sixth
21, // Minor Seventh
22, // Major Seventh
// 25 Perfect Octave
};
bool down = semitones < 0;
if (down)
semitones = -semitones;
int val = semitones % 12;
int octave = semitones / 12;
int intervalIndex = il[val];
int steps = intervalList[intervalIndex].diatonic;
steps = steps + octave * 7;
return down ? -steps : steps;
}
//---------------------------------------------------------
// searchInterval
//---------------------------------------------------------
int searchInterval(int steps, int semitones)
{
unsigned n = sizeof(intervalList)/sizeof(*intervalList);
for (unsigned i = 0; i < n; ++i) {
if ((intervalList[i].diatonic == steps) && (intervalList[i].chromatic == semitones))
return i;
}
return -1;
}
static int _majorVersion, _minorVersion, _updateVersion;
/*!
* Returns the program version
*
* @return
* Version in the format: MMmmuu
* Where M=Major, m=minor, and u=update
*/
int version()
{
QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)");
if (re.indexIn(VERSION) != -1) {
QStringList sl = re.capturedTexts();
if (sl.size() == 4) {
_majorVersion = sl[1].toInt();
_minorVersion = sl[2].toInt();
_updateVersion = sl[3].toInt();
return _majorVersion * 10000 + _minorVersion * 100 + _updateVersion;
}
}
return 0;
}
//---------------------------------------------------------
// majorVersion
//---------------------------------------------------------
int majorVersion()
{
version();
return _majorVersion;
}
//---------------------------------------------------------
// minorVersion
//---------------------------------------------------------
int minorVersion()
{
version();
return _minorVersion;
}
//---------------------------------------------------------
// updateVersion
//---------------------------------------------------------
int updateVersion()
{
version();
return _updateVersion;
}
//---------------------------------------------------------
// diatonicUpDown
// used to find the second note of a trill, mordent etc.
// key -7 ... +7
//---------------------------------------------------------
int diatonicUpDown(Key k, int pitch, int steps)
{
static int ptab[15][7] = {
// c c# d d# e f f# g g# a a# b
{ -1, 1, 3, 4, 6, 8, 10 }, // Ces
{ -1, 1, 3, 5, 6, 8, 10 }, // Ges
{ 0, 1, 3, 5, 6, 8, 10 }, // Des
{ 0, 1, 3, 5, 7, 8, 10 }, // As
{ 0, 2, 3, 5, 7, 8, 10 }, // Es
{ 0, 2, 3, 5, 7, 9, 10 }, // B
{ 0, 2, 4, 5, 7, 9, 10 }, // F
{ 0, 2, 4, 5, 7, 9, 11 }, // C
{ 0, 2, 4, 6, 7, 9, 11 }, // G
{ 1, 2, 4, 6, 7, 9, 11 }, // D
{ 1, 2, 4, 6, 8, 9, 11 }, // A
{ 1, 3, 4, 6, 8, 9, 11 }, // E
{ 1, 3, 4, 6, 8, 10, 11 }, // H
{ 1, 3, 5, 6, 8, 10, 11 }, // Fis
{ 1, 3, 5, 6, 8, 10, 12 }, // Cis
};
int key = int(k) + 7;
int step = pitch % 12;
int octave = pitch / 12;
// loop through the diatonic steps of the key looking for the given note
// or the gap where it would fit
int i = 0;
while (i < 7) {
if (ptab[key][i] >= step)
break;
++i;
}
// neither step nor gap found
// reset to beginning
if (i == 7) {
++octave;
i = 0;
}
// if given step not found (gap found instead), and we are stepping up
// then we've already accounted for one step
if (ptab[key][i] > step && steps > 0)
--steps;
// now start counting diatonic steps up or down
if (steps > 0) {
// count up
while (steps--) {
++i;
if (i == 7) {
// hit last step; reset to beginning
++octave;
i = 0;
}
}
}
else if (steps < 0) {
// count down
while (steps++) {
--i;
if (i < 0) {
// hit first step; reset to end
--octave;
i = 6;
}
}
}
// convert step to pitch
step = ptab[key][i];
pitch = octave * 12 + step;
if (pitch < 0)
pitch = 0;
if (pitch > 127)
pitch = 128;
return pitch;
}
//---------------------------------------------------------
// searchTieNote
// search Note to tie to "note"
//---------------------------------------------------------
Note* searchTieNote(Note* note)
{
Note* note2 = 0;
Chord* chord = note->chord();
Segment* seg = chord->segment();
Part* part = chord->staff()->part();
int strack = part->staves()->front()->idx() * VOICES;
int etrack = strack + part->staves()->size() * VOICES;
while ((seg = seg->next1(Segment::Type::ChordRest))) {
for (int track = strack; track < etrack; ++track) {
Chord* c = static_cast<Chord*>(seg->element(track));
if (c == 0 || c->type() != Element::Type::CHORD)
continue;
int staffIdx = c->staffIdx() + c->staffMove();
if (staffIdx != chord->staffIdx() + chord->staffMove()) // cannot happen?
continue;
for (Note* n : c->notes()) {
if (n->pitch() == note->pitch()) {
if (note2 == 0 || c->track() == chord->track())
note2 = n;
}
}
}
if (note2)
break;
}
return note2;
}
//---------------------------------------------------------
// searchTieNote114
// search Note to tie to "note", tie to next note in
// same voice
//---------------------------------------------------------
Note* searchTieNote114(Note* note)
{
Note* note2 = 0;
Chord* chord = note->chord();
Segment* seg = chord->segment();
Part* part = chord->staff()->part();
int strack = part->staves()->front()->idx() * VOICES;
int etrack = strack + part->staves()->size() * VOICES;
while ((seg = seg->next1(Segment::Type::ChordRest))) {
for (int track = strack; track < etrack; ++track) {
Chord* c = static_cast<Chord*>(seg->element(track));
if (c == 0 || (c->type() != Element::Type::CHORD) || (c->track() != chord->track()))
continue;
int staffIdx = c->staffIdx() + c->staffMove();
if (staffIdx != chord->staffIdx() + chord->staffMove()) // cannot happen?
continue;
for (Note* n : c->notes()) {
if (n->pitch() == note->pitch()) {
if (note2 == 0 || c->track() == chord->track())
note2 = n;
}
}
}
if (note2)
break;
}
return note2;
}
//---------------------------------------------------------
// absStep
/// Compute absolute step.
/// C D E F G A B ....
//---------------------------------------------------------
int absStep(int tpc, int pitch)
{
int line = tpc2step(tpc) + (pitch / 12) * 7;
int tpcPitch = tpc2pitch(tpc);
if (tpcPitch < 0)
line += 7;
else
line -= (tpcPitch / 12) * 7;
return line;
}
int absStep(int pitch)
{
// TODO - does this need to be key-aware?
int tpc = pitch2tpc(pitch, Key::C, Prefer::NEAREST);
return absStep(tpc, pitch);
}
int absStep(int line, ClefType clef)
{
return ClefInfo::pitchOffset(clef) - line;
}
//---------------------------------------------------------
// relStep
/// Compute relative step from absolute step
/// which depends on actual clef. Step 0 starts on the
/// first (top) staff line.
//---------------------------------------------------------
int relStep(int line, ClefType clef)
{
return ClefInfo::pitchOffset(clef) - line;
}
int relStep(int pitch, int tpc, ClefType clef)
{
return relStep(absStep(tpc, pitch), clef);
}
//---------------------------------------------------------
// pitch2step
//---------------------------------------------------------
int pitch2step(int pitch)
{
static const char tab[12] = { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 };
return tab[pitch%12];
}
//---------------------------------------------------------
// step2pitch
//---------------------------------------------------------
int step2pitch(int step)
{
static const char tab[7] = { 0, 2, 4, 5, 7, 9, 11 };
return tab[step % 7];
}
}