506 lines
18 KiB
C++
506 lines
18 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2009-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 "repeatlist.h"
|
|
#include "score.h"
|
|
#include "measure.h"
|
|
#include "tempo.h"
|
|
#include "volta.h"
|
|
#include "segment.h"
|
|
#include "marker.h"
|
|
#include "jump.h"
|
|
|
|
namespace Ms {
|
|
|
|
//---------------------------------------------------------
|
|
// searchVolta
|
|
// return volta at tick
|
|
//---------------------------------------------------------
|
|
|
|
Volta* Score::searchVolta(int tick) const
|
|
{
|
|
for (const std::pair<int,Spanner*>& p : _spanner.map()) {
|
|
Spanner* s = p.second;
|
|
if (s->type() != Element::Type::VOLTA)
|
|
continue;
|
|
Volta* volta = static_cast<Volta*>(s);
|
|
if (tick >= volta->tick() && tick < volta->tick2())
|
|
return volta;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// searchLabel
|
|
//---------------------------------------------------------
|
|
|
|
Measure* Score::searchLabel(const QString& s)
|
|
{
|
|
if (s == "start")
|
|
return firstMeasure();
|
|
else if (s == "end")
|
|
return lastMeasure();
|
|
for (Measure* m = firstMeasure(); m; m = m->nextMeasure()) {
|
|
for (auto e : m->el()) {
|
|
if (e->type() == Element::Type::MARKER) {
|
|
const Marker* marker = static_cast<const Marker*>(e);
|
|
if (marker->label() == s)
|
|
return m;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// searchLabelWithinSectionFirst
|
|
//---------------------------------------------------------
|
|
|
|
Measure* Score::searchLabelWithinSectionFirst(const QString& s, Measure* sectionStartMeasure, Measure* sectionEndMeasure)
|
|
{
|
|
if (s == "start")
|
|
return sectionStartMeasure;
|
|
else if (s == "end")
|
|
return sectionEndMeasure;
|
|
for (Measure* m = sectionStartMeasure; m && (m != sectionEndMeasure->nextMeasure()); m = m->nextMeasure()) {
|
|
for (auto e : m->el()) {
|
|
if (e->type() == Element::Type::MARKER) {
|
|
const Marker* marker = static_cast<const Marker*>(e);
|
|
if (marker->label() == s)
|
|
return m;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if did not find label within section, then search for label in entire score
|
|
return searchLabel(s);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// RepeatLoop
|
|
//---------------------------------------------------------
|
|
|
|
struct RepeatLoop {
|
|
enum class LoopType : char { REPEAT, JUMP };
|
|
|
|
LoopType type;
|
|
Measure* m; // start measure of LoopType::REPEAT
|
|
int count;
|
|
QString stop, cont;
|
|
|
|
RepeatLoop() {}
|
|
RepeatLoop(Measure* _m) {
|
|
m = _m;
|
|
count = 0;
|
|
type = LoopType::REPEAT;
|
|
}
|
|
RepeatLoop(const QString s, const QString c)
|
|
: stop(s), cont(c)
|
|
{
|
|
m = 0;
|
|
type = LoopType::JUMP;
|
|
}
|
|
};
|
|
|
|
//---------------------------------------------------------
|
|
// RepeatSegment
|
|
//---------------------------------------------------------
|
|
|
|
RepeatSegment::RepeatSegment()
|
|
{
|
|
tick = 0;
|
|
len = 0;
|
|
utick = 0;
|
|
utime = 0.0;
|
|
timeOffset = 0.0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// RepeatList
|
|
//---------------------------------------------------------
|
|
|
|
RepeatList::RepeatList(Score* s)
|
|
{
|
|
_score = s;
|
|
idx1 = 0;
|
|
idx2 = 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// ticks
|
|
//---------------------------------------------------------
|
|
|
|
int RepeatList::ticks()
|
|
{
|
|
if (length() > 0) {
|
|
RepeatSegment* s = last();
|
|
return s->utick + s->len;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// update
|
|
//---------------------------------------------------------
|
|
|
|
void RepeatList::update()
|
|
{
|
|
const TempoMap* tl = _score->tempomap();
|
|
|
|
int utick = 0;
|
|
qreal t = 0;
|
|
|
|
for(RepeatSegment* s : *this) {
|
|
s->utick = utick;
|
|
s->utime = t;
|
|
qreal ct = tl->tick2time(s->tick);
|
|
s->timeOffset = t - ct;
|
|
utick += s->len;
|
|
t += tl->tick2time(s->tick + s->len) - ct;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// utick2tick
|
|
//---------------------------------------------------------
|
|
|
|
int RepeatList::utick2tick(int tick) const
|
|
{
|
|
unsigned n = size();
|
|
if (n == 0)
|
|
return tick;
|
|
if (tick < 0)
|
|
return 0;
|
|
unsigned ii = (idx1 < n) && (tick >= at(idx1)->utick) ? idx1 : 0;
|
|
for (unsigned i = ii; i < n; ++i) {
|
|
if ((tick >= at(i)->utick) && ((i + 1 == n) || (tick < at(i+1)->utick))) {
|
|
idx1 = i;
|
|
return tick - (at(i)->utick - at(i)->tick);
|
|
}
|
|
}
|
|
if (MScore::debugMode) {
|
|
qFatal("tick %d not found in RepeatList", tick);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// tick2utick
|
|
//---------------------------------------------------------
|
|
|
|
int RepeatList::tick2utick(int tick) const
|
|
{
|
|
for (const RepeatSegment* s : *this) {
|
|
if (tick >= s->tick && tick < (s->tick + s->len))
|
|
return s->utick + (tick - s->tick);
|
|
}
|
|
return last()->utick + (tick - last()->tick);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// utick2utime
|
|
//---------------------------------------------------------
|
|
|
|
qreal RepeatList::utick2utime(int tick) const
|
|
{
|
|
unsigned n = size();
|
|
unsigned ii = (idx1 < n) && (tick >= at(idx1)->utick) ? idx1 : 0;
|
|
for (unsigned i = ii; i < n; ++i) {
|
|
if ((tick >= at(i)->utick) && ((i + 1 == n) || (tick < at(i+1)->utick))) {
|
|
int t = tick - (at(i)->utick - at(i)->tick);
|
|
qreal tt = _score->tempomap()->tick2time(t) + at(i)->timeOffset;
|
|
return tt;
|
|
}
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// utime2utick
|
|
//---------------------------------------------------------
|
|
|
|
int RepeatList::utime2utick(qreal t) const
|
|
{
|
|
unsigned n = size();
|
|
unsigned ii = (idx2 < n) && (t >= at(idx2)->utime) ? idx2 : 0;
|
|
for (unsigned i = ii; i < n; ++i) {
|
|
if ((t >= at(i)->utime) && ((i + 1 == n) || (t < at(i+1)->utime))) {
|
|
idx2 = i;
|
|
return _score->tempomap()->time2tick(t - at(i)->timeOffset) + (at(i)->utick - at(i)->tick);
|
|
}
|
|
}
|
|
if (MScore::debugMode) {
|
|
qFatal("time %f not found in RepeatList", t);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// dump
|
|
//---------------------------------------------------------
|
|
|
|
void RepeatList::dump() const
|
|
{
|
|
#if 0
|
|
qDebug("==Dump Repeat List:==");
|
|
foreach(const RepeatSegment* s, *this) {
|
|
qDebug("%p tick: %3d(%d) %3d(%d) len %d(%d) beats %f + %f", s,
|
|
s->utick / MScore::division,
|
|
s->utick / MScore::division / 4,
|
|
s->tick / MScore::division,
|
|
s->tick / MScore::division / 4,
|
|
s->len / MScore::division,
|
|
s->len / MScore::division / 4,
|
|
s->utime, s->timeOffset);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// unwind
|
|
// implements:
|
|
// - repeats
|
|
// - volta
|
|
// - d.c. al fine
|
|
// - d.s. al fine
|
|
// - d.s. al coda
|
|
//---------------------------------------------------------
|
|
|
|
void RepeatList::unwind()
|
|
{
|
|
qDeleteAll(*this);
|
|
clear();
|
|
Measure* fm = _score->firstMeasure();
|
|
if (!fm)
|
|
return;
|
|
|
|
//qDebug("unwind===================");
|
|
|
|
for (Measure* m = fm; m; m = m->nextMeasure())
|
|
m->setPlaybackCount(0);
|
|
|
|
MeasureBase* sectionStartMeasureBase = NULL; // NULL indicates haven't discovered starting Measure of section
|
|
MeasureBase* sectionEndMeasureBase = NULL;
|
|
|
|
// partition score by section breaks and unwind individual sections seperately
|
|
// note: section breaks may occur on non-Measure frames, so must seach list of all MeasureBases
|
|
for (MeasureBase* mb = _score->first(); mb; mb = mb->next()) {
|
|
|
|
// unwindSection only deals with real Measures, so sectionEndMeasureBase and sectionStartMeasureBase will only point to real Measures
|
|
if (mb->isMeasure()) {
|
|
sectionEndMeasureBase = mb; // ending measure of section is the most recently encountered actual Measure
|
|
|
|
// starting measure of section will be the first non-NULL actual Measure encountered
|
|
if (sectionStartMeasureBase == NULL)
|
|
sectionStartMeasureBase = mb;
|
|
}
|
|
|
|
// if found section break or reached final MeasureBase of score, then unwind
|
|
if (mb->sectionBreak() || !mb->nextMeasure()) {
|
|
|
|
// only unwind if section starts and ends with actual real measure
|
|
if (sectionStartMeasureBase && sectionEndMeasureBase) {
|
|
unwindSection(reinterpret_cast<Measure*>(sectionStartMeasureBase), reinterpret_cast<Measure*>(sectionEndMeasureBase));
|
|
sectionStartMeasureBase = 0; // reset to NULL to indicate that don't know starting Measure of next section after starting new section
|
|
sectionEndMeasureBase = 0;
|
|
}
|
|
else {
|
|
qDebug( "Will not unroll a section that doesn't start or end with an actual measure. sectionStartMeasureBase = %p, sectionEndMeasureBase = %p",
|
|
sectionStartMeasureBase, sectionEndMeasureBase);
|
|
}
|
|
}
|
|
}
|
|
|
|
update();
|
|
dump();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// unwindSection
|
|
// unwinds from sectionStartMeasure through sectionEndMeasure
|
|
// appends repeat segments to rs
|
|
//---------------------------------------------------------
|
|
|
|
void RepeatList::unwindSection(Measure* sectionStartMeasure, Measure* sectionEndMeasure)
|
|
{
|
|
// qDebug("unwind %d-measure section starting %p through %p", sectionEndMeasure->no()+1, sectionStartMeasure, sectionEndMeasure);
|
|
|
|
QList<Jump*> jumps; // take the jumps only once so store them
|
|
|
|
rs = new RepeatSegment;
|
|
rs->tick = sectionStartMeasure->tick(); // prepare initial repeat segment for start of this section
|
|
|
|
Measure* endRepeat = 0; // measure where the current repeat should stop
|
|
Measure* continueAt = 0; // measure where the playback should continue after the repeat (To coda)
|
|
Measure* m = 0;
|
|
int loop = 0; // keeps track of how many times have repeated a :| (Repeat::END)
|
|
int repeatCount = 0;
|
|
bool isGoto = false;
|
|
bool playRepeats = false;
|
|
|
|
for (Measure* nm = sectionStartMeasure; nm; ) {
|
|
m = nm;
|
|
m->setPlaybackCount(m->playbackCount() + 1);
|
|
bool doJump = false; // process jump after endrepeat
|
|
|
|
// during any DC or DS, will take last time through repeat
|
|
if (isGoto && !playRepeats && m->repeatEnd())
|
|
loop = m->repeatCount() - 1;
|
|
|
|
if (endRepeat) {
|
|
Volta* volta = _score->searchVolta(m->tick());
|
|
if (volta && !volta->hasEnding(m->playbackCount())) {
|
|
// skip measure
|
|
if (rs->tick < m->tick()) {
|
|
rs->len = m->tick() - rs->tick;
|
|
append(rs);
|
|
rs = new RepeatSegment;
|
|
}
|
|
rs->tick = m->endTick();
|
|
}
|
|
else if (m->repeatJump()) {
|
|
doJump = true;
|
|
isGoto = false;
|
|
}
|
|
}
|
|
else if (m->repeatJump()) // Jumps are only accepted outside of other repeats
|
|
doJump = true;
|
|
|
|
if (isGoto && (endRepeat == m)) {
|
|
if (continueAt == 0)
|
|
break;
|
|
rs->len = m->endTick() - rs->tick;
|
|
append(rs);
|
|
rs = new RepeatSegment;
|
|
rs->tick = continueAt->tick();
|
|
nm = continueAt;
|
|
isGoto = false;
|
|
endRepeat = 0;
|
|
continue;
|
|
}
|
|
else if (m->repeatEnd()) {
|
|
if (endRepeat == m) {
|
|
++loop;
|
|
if (loop >= repeatCount) {
|
|
endRepeat = 0;
|
|
loop = 0;
|
|
}
|
|
else {
|
|
nm = jumpToStartRepeat(m);
|
|
continue;
|
|
}
|
|
}
|
|
else if (endRepeat == 0) {
|
|
if (m->playbackCount() >= m->repeatCount())
|
|
break;
|
|
endRepeat = m;
|
|
repeatCount = m->repeatCount();
|
|
loop = 1;
|
|
nm = jumpToStartRepeat(m);
|
|
continue;
|
|
}
|
|
else {
|
|
++loop;
|
|
if (loop < repeatCount) {
|
|
nm = jumpToStartRepeat(m);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (doJump && !isGoto) {
|
|
Jump* jump = 0;
|
|
foreach (Element* e, m->el()) {
|
|
if (e->isJump()) {
|
|
jump = toJump(e);
|
|
break;
|
|
}
|
|
}
|
|
// jump only once
|
|
if (jumps.contains(jump)) {
|
|
if (endRepeat == _score->searchLabelWithinSectionFirst(jump->playUntil(), sectionStartMeasure, sectionEndMeasure))
|
|
endRepeat = 0;
|
|
|
|
nm = m->nextMeasure();
|
|
if (nm == sectionEndMeasure->nextMeasure())
|
|
break;
|
|
else
|
|
continue;
|
|
}
|
|
jumps.append(jump);
|
|
if (jump) {
|
|
nm = _score->searchLabelWithinSectionFirst(jump->jumpTo() , sectionStartMeasure, sectionEndMeasure);
|
|
endRepeat = _score->searchLabelWithinSectionFirst(jump->playUntil() , sectionStartMeasure, sectionEndMeasure);
|
|
continueAt = _score->searchLabelWithinSectionFirst(jump->continueAt(), sectionStartMeasure, sectionEndMeasure);
|
|
|
|
if (nm && endRepeat) {
|
|
isGoto = true;
|
|
playRepeats = jump->playRepeats();
|
|
rs->len = m->endTick() - rs->tick;
|
|
append(rs);
|
|
rs = new RepeatSegment;
|
|
rs->tick = nm->tick();
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
qDebug("Jump not found");
|
|
}
|
|
|
|
// keep looping until reach end of score or end of the section
|
|
nm = m->nextMeasure();
|
|
if (nm == sectionEndMeasure->nextMeasure())
|
|
break;
|
|
}
|
|
|
|
// append the final repeat segment of that section
|
|
if (rs) {
|
|
rs->len = m->endTick() - rs->tick;
|
|
if (rs->len)
|
|
append(rs);
|
|
else
|
|
delete rs;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// jumpToStartRepeat
|
|
//---------------------------------------------------------
|
|
|
|
Measure* RepeatList::jumpToStartRepeat(Measure* m)
|
|
{
|
|
// finalize the previous repeat segment
|
|
rs->len = m->tick() + m->ticks() - rs->tick;
|
|
append(rs);
|
|
|
|
// search backwards until find start of repeat
|
|
while (true) {
|
|
|
|
if (m->repeatStart())
|
|
break;
|
|
|
|
if (m == _score->firstMeasure())
|
|
break;
|
|
|
|
if (m->prevMeasure()->sectionBreak())
|
|
break;
|
|
|
|
m = m->prevMeasure();
|
|
}
|
|
|
|
// initialize the next repeat segment
|
|
rs = new RepeatSegment;
|
|
rs->tick = m->tick();
|
|
return m;
|
|
}
|
|
|
|
}
|
|
|