MuseScore/src/engraving/libmscore/utils.cpp
Casper Jeukendrup 98c7a77487 Replace uses of QRegExp with QRegularExpression
QRegExp is deprecated in Qt 6.
2021-07-07 15:12:16 +02:00

1120 lines
33 KiB
C++

/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "utils.h"
#include <cmath>
#include <QtMath>
#include <QRegularExpression>
#include "translation.h"
#include "config.h"
#include "score.h"
#include "page.h"
#include "segment.h"
#include "clef.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"
#include "sig.h"
#include "tuplet.h"
#include "symid.h"
using namespace mu;
namespace Ms {
//---------------------------------------------------------
// handleRect
//---------------------------------------------------------
RectF handleRect(const PointF& pos)
{
return RectF(pos.x() - 4, pos.y() - 4, 8, 8);
}
//---------------------------------------------------------
// tick2measure
//---------------------------------------------------------
Measure* Score::tick2measure(const Fraction& tick) const
{
if (tick == Fraction(-1, 1)) { // special number
return lastMeasure();
}
if (tick <= Fraction(0, 1)) {
return firstMeasure();
}
Measure* lm = 0;
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
if (tick < m->tick()) {
Q_ASSERT(lm);
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.ticks(), lm ? lm->tick().ticks() : -1);
return 0;
}
//---------------------------------------------------------
// tick2measureMM
//---------------------------------------------------------
Measure* Score::tick2measureMM(const Fraction& t) const
{
Fraction tick(t);
if (tick == Fraction(-1, 1)) {
return lastMeasureMM();
}
if (tick < Fraction(0, 1)) {
tick = Fraction(0, 1);
}
Measure* lm = 0;
for (Measure* m = firstMeasureMM(); m; m = m->nextMeasureMM()) {
if (tick < m->tick()) {
Q_ASSERT(lm);
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.ticks(), lm ? lm->tick().ticks() : -1);
return 0;
}
//---------------------------------------------------------
// tick2measureBase
//---------------------------------------------------------
MeasureBase* Score::tick2measureBase(const Fraction& tick) const
{
for (MeasureBase* mb = first(); mb; mb = mb->next()) {
Fraction st = mb->tick();
Fraction l = mb->ticks();
if (tick >= st && tick < (st + l)) {
return mb;
}
}
// qDebug("tick2measureBase %d not found", tick);
return 0;
}
//---------------------------------------------------------
// tick2segment
//---------------------------------------------------------
Segment* Score::tick2segmentMM(const Fraction& tick, bool first, SegmentType st) const
{
return tick2segment(tick, first, st, true);
}
Segment* Score::tick2segmentMM(const Fraction& tick) const
{
return tick2segment(tick, false, SegmentType::All, true);
}
Segment* Score::tick2segmentMM(const Fraction& tick, bool first) const
{
return tick2segment(tick, first, SegmentType::All, true);
}
Segment* Score::tick2segment(const Fraction& t, bool first, SegmentType st, bool useMMrest) const
{
Fraction tick(t);
Measure* m;
if (useMMrest) {
m = tick2measureMM(tick);
// When mmRest force tick to the first segment of mmRest.
if (m && m->isMMRest() && tick != m->endTick()) {
tick = m->tick();
}
} else {
m = tick2measure(tick);
}
if (m == 0) {
qDebug("no measure for tick %d", tick.ticks());
return 0;
}
for (Segment* segment = m->first(st); segment;) {
Fraction t1 = segment->tick();
Segment* nsegment = segment->next(st);
if (tick == t1) {
if (first) {
return segment;
} else {
if (!nsegment || tick < nsegment->tick()) {
return segment;
}
}
}
segment = nsegment;
}
qDebug("no segment for tick %d (start search at %d (measure %d))", tick.ticks(), t.ticks(), m->tick().ticks());
return 0;
}
Segment* Score::tick2segment(const Fraction& tick) const
{
return tick2segment(tick, false, SegmentType::All, false);
}
Segment* Score::tick2segment(const Fraction& tick, bool first) const
{
return tick2segment(tick, first, SegmentType::All, false);
}
//---------------------------------------------------------
// tick2leftSegment
/// return the segment at this tick position if any or
/// the first segment *before* this tick position
//---------------------------------------------------------
Segment* Score::tick2leftSegment(const Fraction& tick, bool useMMrest) const
{
Measure* m = useMMrest ? tick2measureMM(tick) : tick2measure(tick);
if (m == 0) {
qDebug("tick2leftSegment(): not found tick %d", tick.ticks());
return 0;
}
// loop over all segments
Segment* ps = 0;
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::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(const Fraction& tick, bool useMMrest) const
{
Measure* m = useMMrest ? tick2measureMM(tick) : tick2measure(tick);
if (m == 0) {
qDebug("tick2nearestSegment(): not found tick %d", tick.ticks());
return 0;
}
// loop over all segments
for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
if (tick <= s->tick()) {
return s;
}
}
return 0;
}
//---------------------------------------------------------
// tick2beatType
//---------------------------------------------------------
BeatType Score::tick2beatType(const Fraction& tick)
{
Measure* m = tick2measure(tick);
Fraction msrTick = m->tick();
TimeSigFrac timeSig = sigmap()->timesig(msrTick).nominal();
int rtick = (tick - msrTick).ticks();
if (m->isAnacrusis()) { // measure is incomplete (anacrusis)
rtick += timeSig.ticksPerMeasure() - m->ticks().ticks();
}
return timeSig.rtick2beatType(rtick);
}
//---------------------------------------------------------
// getStaff
//---------------------------------------------------------
int getStaff(System* system, const PointF& p)
{
PointF pp = p - system->page()->pos() - system->pos();
for (int i = 0; i < system->page()->score()->nstaves(); ++i) {
qreal sp = system->spatium();
RectF r = system->bboxStaff(i).adjusted(0.0, -sp, 0.0, sp);
if (r.contains(pp)) {
return i;
}
}
return -1;
}
//---------------------------------------------------------
// nextSeg
//---------------------------------------------------------
Fraction Score::nextSeg(const Fraction& tick, int track)
{
Segment* seg = tick2segment(tick);
while (seg) {
seg = seg->next1(SegmentType::ChordRest);
if (seg == 0) {
break;
}
if (seg->element(track)) {
break;
}
}
return seg ? seg->tick() : Fraction(-1, 1);
}
//---------------------------------------------------------
// nextSeg1
//---------------------------------------------------------
Segment* nextSeg1(Segment* seg, int& track)
{
int staffIdx = track / VOICES;
int startTrack = staffIdx * VOICES;
int endTrack = startTrack + VOICES;
while ((seg = seg->next1(SegmentType::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(SegmentType::ChordRest))) {
for (int t = startTrack; t < endTrack; ++t) {
if (seg->element(t)) {
track = t;
return seg;
}
}
}
return 0;
}
//---------------------------------------------------------
// next/prevChordNote
//
// returns the top note of the next/previous chord. If a
// chord exists in the same track as note,
// it is used. If not, the topmost existing chord is used.
// May return nullptr if there is no next/prev note
//---------------------------------------------------------
Note* nextChordNote(Note* note)
{
int track = note->track();
int fromTrack = (track / VOICES) * VOICES;
int toTrack = fromTrack + VOICES;
// TODO : limit to same instrument, not simply to same staff!
Segment* seg = note->chord()->segment()->nextCR(track, true);
while (seg) {
Element* targetElement = seg->elementAt(track);
// if a chord exists in the same track, return its top note
if (targetElement && targetElement->isChord()) {
return toChord(targetElement)->upNote();
}
// if not, return topmost chord in track range
for (int i = fromTrack; i < toTrack; i++) {
targetElement = seg->elementAt(i);
if (targetElement && targetElement->isChord()) {
return toChord(targetElement)->upNote();
}
}
seg = seg->nextCR(track, true);
}
return nullptr;
}
Note* prevChordNote(Note* note)
{
int track = note->track();
int fromTrack = (track / VOICES) * VOICES;
int toTrack = fromTrack + VOICES;
// TODO : limit to same instrument, not simply to same staff!
Segment* seg = note->chord()->segment()->prev1();
while (seg) {
if (seg->segmentType() == SegmentType::ChordRest) {
Element* targetElement = seg->elementAt(track);
// if a chord exists in the same track, return its top note
if (targetElement && targetElement->isChord()) {
return toChord(targetElement)->upNote();
}
// if not, return topmost chord in track range
for (int i = fromTrack; i < toTrack; i++) {
targetElement = seg->elementAt(i);
if (targetElement && targetElement->isChord()) {
return toChord(targetElement)->upNote();
}
}
}
seg = seg->prev1();
}
return nullptr;
}
//---------------------------------------------------------
// pitchKeyAdjust
// change entered note to sounding pitch dependent
// 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
}
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) - 1;
QString o;
o = QString::asprintf("%d", octave);
int i = v % 12;
return qtrc("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[intervalListSize] = {
// 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, _patchVersion;
/*!
* Returns the program version
*
* @return
* Version in the format: MMmmpp
* Where M=Major, m=minor, and p=patch
*/
int version()
{
QRegularExpression versionRegEx("(\\d+)\\.(\\d+)\\.(\\d+)");
QRegularExpressionMatch versionMatch = versionRegEx.match(VERSION);
if (versionMatch.hasMatch()) {
QStringList versionStringList = versionMatch.capturedTexts();
if (versionStringList.size() == 4) {
_majorVersion = versionStringList[1].toInt();
_minorVersion = versionStringList[2].toInt();
_patchVersion = versionStringList[3].toInt();
return _majorVersion * 10000 + _minorVersion * 100 + _patchVersion;
}
}
qDebug() << "Could not parse version:" << VERSION;
return 0;
}
//---------------------------------------------------------
// majorVersion
//---------------------------------------------------------
int majorVersion()
{
version();
return _majorVersion;
}
//---------------------------------------------------------
// minorVersion
//---------------------------------------------------------
int minorVersion()
{
version();
return _minorVersion;
}
//---------------------------------------------------------
// patchVersion
//---------------------------------------------------------
int patchVersion()
{
version();
return _patchVersion;
}
//---------------------------------------------------------
// compareVersion
/// Up to 4 digits X.X.X.X
/// Each digit can be double XX.XX.XX.XX
/// return true if v1 < v2
//---------------------------------------------------------
bool compareVersion(QString v1, QString v2)
{
auto v1l = v1.split(".");
auto v2l = v2.split(".");
int ma = qPow(100, qMax(v1l.size(), v2l.size()));
int m = ma;
int vv1 = 0;
for (int i = 0; i < v1l.size(); i++) {
vv1 += (m * v1l[i].toInt());
m /= 100;
}
m = ma;
int vv2 = 0;
for (int i = 0; i < v2l.size(); i++) {
vv2 += (m * v2l[i].toInt());
m /= 100;
}
return vv1 < vv2;
}
//---------------------------------------------------------
// 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 }, // Cb Ces
{ -1, 1, 3, 5, 6, 8, 10 }, // Gb Ges
{ 0, 1, 3, 5, 6, 8, 10 }, // Db Des
{ 0, 1, 3, 5, 7, 8, 10 }, // Ab As
{ 0, 2, 3, 5, 7, 8, 10 }, // Eb Es
{ 0, 2, 3, 5, 7, 9, 10 }, // Bb B
{ 0, 2, 4, 5, 7, 9, 10 }, // F F
{ 0, 2, 4, 5, 7, 9, 11 }, // C C
{ 0, 2, 4, 6, 7, 9, 11 }, // G G
{ 1, 2, 4, 6, 7, 9, 11 }, // D D
{ 1, 2, 4, 6, 8, 9, 11 }, // A A
{ 1, 3, 4, 6, 8, 9, 11 }, // E E
{ 1, 3, 4, 6, 8, 10, 11 }, // B H
{ 1, 3, 5, 6, 8, 10, 11 }, // F# Fis
{ 1, 3, 5, 6, 8, 10, 12 }, // C# 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)
{
if (!note) {
return nullptr;
}
Note* note2 = 0;
Chord* chord = note->chord();
Segment* seg = chord->segment();
Part* part = chord->part();
int strack = part->staves()->front()->idx() * VOICES;
int etrack = strack + part->staves()->size() * VOICES;
if (chord->isGraceBefore()) {
chord = toChord(chord->parent());
// try to tie to next grace note
int index = note->chord()->graceIndex();
for (Chord* c : chord->graceNotes()) {
if (c->graceIndex() == index + 1) {
note2 = c->findNote(note->pitch());
if (note2) {
//printf("found grace-grace tie\n");
return note2;
}
}
}
// try to tie to note in parent chord
note2 = chord->findNote(note->pitch());
if (note2) {
return note2;
}
} else if (chord->isGraceAfter()) {
// grace after
// we will try to tie to note in next normal chord, below
// meanwhile, set chord to parent chord so the endTick calculation will make sense
chord = toChord(chord->parent());
} else {
// normal chord
// try to tie to grace note after if present
QVector<Chord*> gna = chord->graceNotesAfter();
if (!gna.empty()) {
Chord* gc = gna[0];
note2 = gc->findNote(note->pitch());
if (note2) {
return note2;
}
}
}
// at this point, chord is a regular chord, not a grace chord
// and we are looking for a note in the *next* chord (grace or regular)
// calculate end of current note duration
// but err on the safe side in case there is roundoff in tick count
Fraction endTick = chord->tick() + chord->actualTicks() - Fraction(1, 4 * 480);
int idx1 = note->unisonIndex();
while ((seg = seg->next1(SegmentType::ChordRest))) {
// skip ahead to end of current note duration as calculated above
// but just in case, stop if we find element in current track
if (seg->tick() < endTick && !seg->element(chord->track())) {
continue;
}
for (int track = strack; track < etrack; ++track) {
Element* e = seg->element(track);
if (e == 0 || !e->isChord()) {
continue;
}
Chord* c = toChord(e);
const int staffIdx = c->staffIdx() + c->staffMove();
if (staffIdx != chord->staffIdx() + chord->staffMove()) {
// this check is needed as we are iterating over all staves to capture cross-staff chords
continue;
}
// if there are grace notes before, try to tie to first one
QVector<Chord*> gnb = c->graceNotesBefore();
if (!gnb.empty()) {
Chord* gc = gnb[0];
Note* gn2 = gc->findNote(note->pitch());
if (gn2) {
return gn2;
}
}
int idx2 = 0;
for (Note* n : c->notes()) {
if (n->pitch() == note->pitch()) {
if (idx1 == idx2) {
if (note2 == 0 || c->track() == chord->track()) {
note2 = n;
break;
}
} else {
++idx2;
}
}
}
}
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->part();
int strack = part->staves()->front()->idx() * VOICES;
int etrack = strack + part->staves()->size() * VOICES;
while ((seg = seg->next1(SegmentType::ChordRest))) {
for (int track = strack; track < etrack; ++track) {
Element* e = seg->element(track);
if (e == 0 || (!e->isChord()) || (e->track() != chord->track())) {
continue;
}
Chord* c = toChord(e);
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
// returns one of { 0, 1, 2, 3, 4, 5, 6 }
//---------------------------------------------------------
int pitch2step(int pitch)
{
// C C# D D# E F F# G G# A A# B
static const char tab[12] = { 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6 };
return tab[pitch % 12];
}
//---------------------------------------------------------
// step2pitch
// returns one of { 0, 2, 4, 5, 7, 9, 11 }
//---------------------------------------------------------
int step2pitch(int step)
{
static const char tab[7] = { 0, 2, 4, 5, 7, 9, 11 };
return tab[step % 7];
}
//---------------------------------------------------------
// skipTuplet
// return segment of rightmost chord/rest in a
// (possible nested) tuplet
//---------------------------------------------------------
Segment* skipTuplet(Tuplet* tuplet)
{
DurationElement* nde = tuplet->elements().back();
while (nde->isTuplet()) {
tuplet = toTuplet(nde);
nde = tuplet->elements().back();
}
return toChordRest(nde)->segment();
}
//---------------------------------------------------------
// toTimeSigString
// replace ascii with bravura symbols
//---------------------------------------------------------
std::vector<SymId> toTimeSigString(const QString& s)
{
struct Dict {
QChar code;
SymId id;
};
static const std::vector<Dict> dict = {
{ 43, SymId::timeSigPlusSmall }, // '+'
{ 48, SymId::timeSig0 }, // '0'
{ 49, SymId::timeSig1 }, // '1'
{ 50, SymId::timeSig2 }, // '2'
{ 51, SymId::timeSig3 }, // '3'
{ 52, SymId::timeSig4 }, // '4'
{ 53, SymId::timeSig5 }, // '5'
{ 54, SymId::timeSig6 }, // '6'
{ 55, SymId::timeSig7 }, // '7'
{ 56, SymId::timeSig8 }, // '8'
{ 57, SymId::timeSig9 }, // '9'
{ 67, SymId::timeSigCommon }, // 'C'
{ 40, SymId::timeSigParensLeftSmall }, // '('
{ 41, SymId::timeSigParensRightSmall }, // ')'
{ 162, SymId::timeSigCutCommon }, // '¢'
{ 189, SymId::timeSigFractionHalf },
{ 188, SymId::timeSigFractionQuarter },
{ 59664, SymId::mensuralProlation1 },
{ 79, SymId::mensuralProlation2 }, // 'O'
{ 59665, SymId::mensuralProlation2 },
{ 216, SymId::mensuralProlation3 }, // 'Ø'
{ 59666, SymId::mensuralProlation3 },
{ 59667, SymId::mensuralProlation4 },
{ 59668, SymId::mensuralProlation5 },
{ 59670, SymId::mensuralProlation7 },
{ 59671, SymId::mensuralProlation8 },
{ 59673, SymId::mensuralProlation10 },
{ 59674, SymId::mensuralProlation11 },
};
std::vector<SymId> d;
for (auto c : s) {
for (const Dict& e : dict) {
if (c == e.code) {
d.push_back(e.id);
break;
}
}
}
return d;
}
//---------------------------------------------------------
// actualTicks
//---------------------------------------------------------
Fraction actualTicks(Fraction duration, Tuplet* tuplet, Fraction timeStretch)
{
Fraction f = duration / timeStretch;
for (Tuplet* t = tuplet; t; t = t->tuplet()) {
f /= t->ratio();
}
return f;
}
}