Newer
Older
/***************************************************************************
* Copyright (C) 2017-2021 by Tomasz Bojczuk *
* seelook@gmail.com *
* *
* 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 3 of the License, or *
* (at your option) any later version. *
* *
* 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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include "tmeasureobject.h"
SeeLook
committed
#include "music/tmeter.h"
#include "music/tnote.h"
#include "tbeamobject.h"
#include "tnoteitem.h"
#include "tnotepair.h"
#include "tscoreobject.h"
#include "tstaffitem.h"
SeeLook
committed
#include <QtCore/qdebug.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpalette.h>
void content(TmeasureObject *m)
{
QString c = QStringLiteral("| ");
for (int n = 0; n < m->noteCount(); ++n)
c += QString("(%1)").arg(m->note(n)->index()) + QLatin1String("<") + m->note(n)->note()->toText() + QLatin1String(">")
+ m->note(n)->note()->rtm.string() + QLatin1String(" ");
c += QStringLiteral("|");
qDebug() << m->debug() << c;
}
TmeasureObject::TmeasureObject(int nr, TscoreObject *parent)
: QObject(parent)
, m_number(nr)
, m_score(parent)
, m_staff(nullptr)
, m_firstInGr(new qint8[1])
, m_barLine(nullptr)
{
clearAccidState();
m_duration = m_score->meter()->duration();
m_free = m_duration;
}
TmeasureObject::~TmeasureObject()
{
if (m_barLine)
delete m_barLine;
delete[] m_firstInGr;
// qDebug() << debug() << "is going delete";
}
void TmeasureObject::setNumber(int nr)
m_number = nr;
void TmeasureObject::setStaff(TstaffItem *st)
{
if (m_staff != st) {
m_staff = st;
for (TnotePair *np : std::as_const(m_notes))
np->item()->setStaff(m_staff);
}
}
int TmeasureObject::durationFrom(int id)
{
int dur = m_free;
if (id < noteCount()) {
for (int n = id; n < noteCount(); ++n)
dur += note(n)->note()->duration();
} else // TODO It should never occur, delete if so
qDebug() << debug() << "FIXME! This note doesn't belong to this measure or doesn't exist at all!" << id;
return dur;
}
int TmeasureObject::durationBefore(TnoteItem *it)
{
int dur = 0;
bool found = false;
for (int n = 0; n < noteCount(); ++n) {
if (note(n)->item() != it)
dur += note(n)->note()->duration();
else {
found = true;
break;
}
}
return found ? dur : 0;
SeeLook
committed
}
void TmeasureObject::appendNewNotes(int segmentId, int count)
{
// so far we are sure there is enough space for whole note list in this measure
// qDebug() << debug() << "append" << count << "note(s) from" << segmentId << "measure duration" << duration();
for (int n = segmentId; n < segmentId + count; ++n)
m_notes.append(m_score->noteSegment(n));
updateRhythmicGroups();
const int grWithBeam = beamGroup(segmentId);
for (int n = segmentId; n < segmentId + count; ++n) {
auto np = m_score->noteSegment(n);
if (np->item() == nullptr)
np->setNoteItem(new TnoteItem(m_staff, np));
else
np->item()->setStaff(m_staff);
np->item()->setMeasure(this);
checkAccidentals();
np->item()->setNote(*np->note());
if (m_score->showNoteNames())
np->item()->setNoteNameVisible(true);
}
if (grWithBeam > -1) {
auto firstInGrId = m_score->noteSegment(firstNoteId() + m_firstInGr[grWithBeam])->index();
TbeamObject *curBeam = nullptr;
while (firstInGrId < m_score->notesCount()) {
auto ns = m_score->noteSegment(firstInGrId);
if (ns->beam()) {
if (ns->beam() != curBeam) {
ns->beam()->prepareBeam();
curBeam = ns->beam();
}
}
++firstInGrId;
}
}
refresh();
m_staff->refresh();
checkBarLine();
}
void TmeasureObject::insertNotes(Tpairs &nList, int startId)
{
int listDur = TnotePair::pairsDuration(nList);
Tpairs outNotes;
if (listDur > m_score->meter()->duration())
qDebug() << debug() << "FIXME! Ooh, notes to insert are longer than entire measure can contain!" << listDur;
if (listDur > m_free) {
int leftDur = releaseAtEnd(listDur - m_free, outNotes, startId);
if (leftDur) // TODO it should never happen - DELETE IT!
qDebug() << debug() << "FIXME! Can't insert" << listDur << leftDur << listDur - m_free;
}
for (int n = 0; n < nList.count(); ++n) {
auto noteIn = nList[n];
m_notes.insert(startId, noteIn);
if (noteIn->item() == nullptr)
noteIn->setNoteItem(new TnoteItem(m_staff, noteIn));
else if (m_staff != noteIn->item()->staff())
noteIn->item()->setStaff(m_staff);
noteIn->item()->setMeasure(this);
}
update(m_notes[startId]->rhythmGroup()); // nList.first()->rhythmGroup()
shiftReleased(outNotes);
}
void TmeasureObject::insertNote(TnoteItem *afterItem)
{
if (afterItem) {
int afterIdInBar = afterItem->index() - afterItem->measure()->firstNoteId();
int possibleDur = afterItem->measure()->durationFrom(afterIdInBar);
int workDur = m_score->workRhythm().duration();
// cut note duration if it is longer than possible measure duration starting from the item
auto newRtmList = Trhythm::resolve(workDur > possibleDur ? possibleDur : workDur);
Tpairs nl;
for (Trhythm rtm : std::as_const(newRtmList)) {
Tnote newNote(0, 0, 0, Trhythm(rtm.rhythm(), true));
auto np = m_score->insertSilently(afterItem->index(), newNote, this);
m_notes.removeAt(afterIdInBar);
nl << np;
SeeLook
committed
}
insertNotes(nl, afterIdInBar);
}
void TmeasureObject::removeNote(TnotePair *n)
{
m_free += n->item()->note()->duration(); // n->note() is already null here
m_notes.takeAt(n->index() - firstNoteId());
fill();
}
void TmeasureObject::removeLastNote()
{
if (m_free == 0 && m_barLine) {
m_barLine->setVisible(false);
m_barLine->setParentItem(nullptr);
SeeLook
committed
}
auto noteToRemove = m_notes.takeLast();
updateRhythmicGroups();
// TODO Try to use resolveBeaming here
if (noteToRemove->beam()) {
if (noteToRemove->beam()->count() < 3)
noteToRemove->beam()->deleteBeam();
else
noteToRemove->beam()->removeNote(noteToRemove);
int segId = m_firstInGr[noteToRemove->rhythmGroup()];
while (segId < m_notes.count()) { // update notes of entire rhythm group
m_notes[segId]->approve();
segId++;
}
}
refresh();
m_staff->refresh();
}
void TmeasureObject::keySignatureChanged()
{
for (int n = 0; n < m_notes.size(); ++n) {
m_notes[n]->item()->keySignatureChanged();
}
refresh();
}
int TmeasureObject::firstNoteId() const
{
return m_notes.isEmpty() ? 0 : m_notes.first()->index();
}
int TmeasureObject::lastNoteId() const
{
return m_notes.isEmpty() ? 0 : m_notes.last()->index();
}
char TmeasureObject::debug()
{
QTextStream o(stdout);
o << " \033[01;33m[" << QString("%1/%2").arg(number() + 1).arg(m_staff ? m_staff->number() + 1 : -1) << " MEASURE]\033[01;00m";
return 32; // fake
}
// #################################################################################################
// ################### PROTECTED ############################################
// #################################################################################################
void TmeasureObject::flush()
{
if (m_barLine)
m_barLine->setVisible(false);
m_notes.clear();
setStaff(nullptr);
m_allNotesWidth = 0.0;
m_gapsSum = 0.0;
delete[] m_firstInGr;
m_firstInGr = new qint8[1];
}
void TmeasureObject::updateRhythmicGroups()
{
if (duration() == 0)
return;
int notePos = 0, grNr = 0, currGr = 0;
delete[] m_firstInGr;
m_firstInGr = new qint8[m_score->groupCount()];
m_firstInGr[0] = 0; // first note in measure also begins first rhythmic group
for (int i = 0; i < m_notes.size(); ++i) {
if (currGr != grNr) {
m_firstInGr[grNr] = i;
currGr = grNr;
}
m_notes[i]->setRhythmGroup(grNr);
/** We are cheating here: no-rhythm-note gets duration of 1 - such a rhythm doesn't exists */
notePos += m_notes[i]->note()->rhythm() == Trhythm::NoRhythm ? 1 : m_notes[i]->note()->duration();
while (grNr < m_score->groupCount() && notePos >= m_score->groupPos(grNr))
grNr++;
}
if (currGr < m_score->groupCount() - 1) { // fill the rest of the array
for (int gr = currGr + 1; gr < m_score->groupCount(); ++gr)
m_firstInGr[gr] = -1; // with '-1' - means no notes yet
}
m_free = m_duration - notePos;
SeeLook
committed
}
void TmeasureObject::checkBarLine()
{
if (m_free == 0 && m_score->meter()->meter() != Tmeter::NoMeter) {
// qDebug() << debug() << "check bar line";
auto lastNote = last()->item();
if (!m_barLine) {
m_staff->score()->component()->setData("import QtQuick 2.9; Rectangle { width: 0.3 }", QUrl());
m_barLine = qobject_cast<QQuickItem *>(m_staff->score()->component()->create());
m_barLine->setProperty("color", qApp->palette().text().color());
m_barLine->setParentItem(lastNote);
}
m_barLine->setParentItem(lastNote);
m_barLine->setVisible(true);
qreal xOff = lastNote == m_staff->lastMeasure()->last()->item() ? 0.2 : 0.0; // fit line at the staff end
m_barLine->setX(lastNote->rightX() - lastNote->x() + xOff);
m_barLine->setY(m_staff->upperLine());
m_barLine->setHeight(m_score->isPianoStaff() ? 30.0 : 8.0);
}
}
SeeLook
committed
void TmeasureObject::meterChanged()
{
m_duration = m_score->meter()->duration();
updateRhythmicGroups();
}
void TmeasureObject::refresh()
{
m_gapsSum = 0.0;
m_allNotesWidth = 0.0;
for (int n = 0; n < m_notes.size(); ++n) {
auto noteObj = note(n)->item();
m_gapsSum += noteObj->rhythmFactor();
m_allNotesWidth += noteObj->width();
}
}
void TmeasureObject::checkAccidentals()
{
clearAccidState();
for (int n = 0; n < m_notes.size(); ++n) {
auto np = note(n);
if (np->note()->isValid() && !np->note()->isRest())
m_accidsState[np->note()->note() - 1] = np->note()->alter(); // register accidental of a note
}
}
SeeLook
committed
int TmeasureObject::beamGroup(int segmentId)
{
int currGr = m_score->noteSegment(segmentId)->rhythmGroup();
int segId = m_firstInGr[currGr] + 1;
if (segId < 0) { // TODO it should never happen - DELETE IT!
qDebug() << debug() << "FIXME! beamGroup()";
return -1;
SeeLook
committed
}
int grWithBeam = -1;
while (segId < m_notes.count() && m_notes[segId]->rhythmGroup() == currGr) {
auto noteSeg = m_notes[segId];
auto prevSeg = m_notes[segId - 1];
if (!noteSeg->note()->isRest() && !prevSeg->note()->isRest() // not a rest
&& noteSeg->note()->rhythm() > Trhythm::Quarter // sixteenth or eighth
&& prevSeg->note()->rhythm() > Trhythm::Quarter
&& (!m_score->isPianoStaff() || noteSeg->note()->onUpperStaff() == prevSeg->note()->onUpperStaff())) {
if (prevSeg->note()->rtm.beam() == Trhythm::e_noBeam) // start beam group
prevSeg->setBeam(m_score->getBeam(prevSeg, this));
auto beam = prevSeg->beam();
if (noteSeg->beam() == nullptr)
beam->addNote(noteSeg);
grWithBeam = currGr;
}
segId++;
}
return grWithBeam;
}
void TmeasureObject::noteGoingRest(TnotePair *np)
{
if (np->beam()) {
if (np->beam()->count() > 2) {
if (np->beam()->removeNote(np))
np->beam()->deleteBeam();
} else
np->beam()->deleteBeam();
int segId = m_firstInGr[np->rhythmGroup()];
while (segId < m_notes.count() && m_notes[segId]->rhythmGroup() == np->rhythmGroup()) { // update notes of entire rhythm group
m_notes[segId]->approve();
segId++;
}
}
void TmeasureObject::restGoingNote(TnotePair *np)
{
QList<QList<int>> beamLists;
QList<int> l1;
beamLists << l1;
for (int bb = m_firstInGr[np->rhythmGroup()]; bb < m_notes.count() && m_notes[bb]->rhythmGroup() == np->rhythmGroup(); ++bb) {
auto prevSeg = m_notes[bb];
if (!prevSeg->note()->isRest() && prevSeg->note()->rhythm() > Trhythm::Quarter) {
auto lastBeam = beamLists.last();
if (!lastBeam.isEmpty()) {
if (lastBeam.last() < bb - 1) { // a new beam if there was a rest in between notes in current rhythm group
QList<int> newBeam;
beamLists << newBeam;
}
}
beamLists.last() << bb;
}
int noteId = np->index() - firstNoteId();
for (int bl = 0; bl < beamLists.size(); ++bl) {
QList<int> &beam = beamLists[bl];
if (beam.size() > 1 && noteId >= beam.first() && noteId <= beam.last()) {
TbeamObject *newBeam = nullptr;
for (int b = beam.first(); b <= beam.last(); ++b) {
auto noteInBeam = m_notes[b];
if (noteInBeam->beam())
delete noteInBeam->beam();
if (newBeam)
newBeam->addNote(noteInBeam);
else
newBeam = m_score->getBeam(noteInBeam, this);
}
newBeam->prepareBeam();
newBeam->drawBeam();
}
}
}
void TmeasureObject::changeNoteDuration(TnotePair *np, const Tnote &newNote)
{
int prevDur = np->note()->duration();
int newDur = newNote.duration();
if (prevDur == newDur) {
qDebug() << debug() << "FIXME! calling changeNoteDuration() when duration doesn't changed";
return;
Tnote nn = newNote;
Tpairs notesToOut;
if (m_free - (newDur - prevDur) < 0) { // There is not enough space for new note - its duration is longer than possible free space in the measure
/** 1. Try to release measure (move notes after this @p np one to the next measure) */
int leftDur = releaseAtEnd(newDur - prevDur - m_free, notesToOut, np->index() - firstNoteId() + 1);
if (leftDur) {
/** 2. There is still not enough space for new duration - so cut duration of this @p np note */
auto thisBarRtms = Trhythm::resolve(m_free + prevDur);
nn.setRhythm(thisBarRtms.first());
for (int r = 1; r < thisBarRtms.count(); ++r) {
if (!newNote.isRest())
thisBarRtms[r].setTie(Trhythm::e_tieCont);
m_score->insertSilently(np->index() + r, Tnote(newNote, thisBarRtms[r]), this);
}
}
update(np->rhythmGroup());
checkBarLine();
} else { // measure duration is less than meter - take notes from the next measure
m_free += prevDur - newDur;
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
fill(); // it updates measure
}
shiftReleased(notesToOut);
}
void TmeasureObject::resolveBeaming(int firstGroup, int endGroup)
{
if (noteCount() < 2)
return;
if (endGroup == -1)
endGroup = m_score->groupCount() - 1;
// delete beams in group range of [firstGroup to endGroup]
int noteId = m_firstInGr[firstGroup];
if (noteId < 0) { // TODO it should never happen - DELETE IT!
qDebug() << debug() << "FIXME! wrong grouping";
return;
}
int grId = firstGroup;
while (noteId < noteCount() && grId <= endGroup) {
if (m_notes[noteId]->beam())
m_notes[noteId]->beam()->deleteBeam();
noteId++;
if (noteId < noteCount())
grId = m_notes[noteId]->rhythmGroup();
}
for (int g = firstGroup; g <= endGroup; ++g) {
if (m_firstInGr[g] < 0 || m_firstInGr[g] > noteCount() - 1)
break;
int gr = beamGroup(m_firstInGr[g] + firstNoteId());
if (gr > -1 && gr < m_score->groupCount()) {
TbeamObject *beam = nullptr;
TbeamObject *prevBeam = nullptr;
for (int n = m_firstInGr[gr]; n < noteCount(); ++n) {
if (m_notes[n]->rhythmGroup() != gr)
break;
beam = m_notes[n]->beam();
if (beam && beam != prevBeam) {
beam->prepareBeam();
prevBeam = beam;
}
}
}
// approve beam changes for all eighths and sixteenths that have no beam just flag - to revert the flag
if (m_firstInGr[firstGroup] >= 0 && m_firstInGr[firstGroup] < noteCount()) {
for (int n = m_firstInGr[firstGroup]; n < noteCount(); ++n) {
auto note = m_notes[n];
if (!note->beam() && !note->note()->isRest() && note->note()->rhythm() > Trhythm::Quarter)
note->approve();
}
}
}
/**
* - iterate through notes in backward order (right to left), take note by note to release required duration
* - create a list from taken notes to send it to the next measure
* - split the latest note (the most right one in the measure) if necessary
* - half of the duration remains in current measure at the end tied with
* a new note that has to be created and push to the beginning of the next measure
*/
int TmeasureObject::releaseAtEnd(int dur, Tpairs ¬esToOut, int endNote)
{
int noteNr = m_notes.count() - 1;
while (noteNr >= endNote && dur > 0) {
auto lastNote = last();
int lastDur = lastNote->note()->duration();
if (lastDur > dur) { // last note is longer than required space - split it and create and move the rest of its duration to the next measure
auto rList = Trhythm::resolve(lastDur - dur);
auto lastTie = lastNote->note()->rtm.tie();
if (!lastNote->note()->isRest()) {
rList.first().setTie(lastTie > Trhythm::e_tieStart ? Trhythm::e_tieCont : Trhythm::e_tieStart);
}
for (int r = 1; r < rList.count(); ++r) {
if (!lastNote->note()->isRest())
rList[r].setTie(Trhythm::e_tieCont);
m_score->insertSilently(lastNote->index() + r, Tnote(*lastNote->note(), rList[r]), this);
}
Tnote n(Tnote(*lastNote->note(), rList.first()));
lastNote->setPairNotes(n);
score()->updateNoteInList(lastNote, n);
// remaining part of the note that goes to next measure
auto rtmToNext = Trhythm::resolve(dur);
int indexToInsert = rtmToNext.count() > 1 ? 0 : notesToOut.count();
for (int r = 0; r < rtmToNext.count(); ++r) {
if (!lastNote->note()->isRest()) {
if (r < rtmToNext.count() - 1)
rtmToNext[r].setTie(Trhythm::e_tieCont);
else
rtmToNext[r].setTie(lastTie == Trhythm::e_tieCont ? lastTie : Trhythm::e_tieEnd);
}
m_score->insertSilently(last()->index() + r + 1, Tnote(*lastNote->note(), rtmToNext[r]), this);
notesToOut.insert(indexToInsert, m_notes.takeLast());
}
lastDur = dur; // instead of: dur = 0; m_free += lastDur; lastDur = 0;
} else { // last note is the same long or smaller than required space - so move it to the next measure
notesToOut << m_notes.takeLast();
// TODO maybe clear beams here
}
dur -= lastDur;
m_free += lastDur; // note was taken out so there is more free space in the measure
noteNr--;
}
return dur;
}
void TmeasureObject::releaseAtStart(int dur, Tpairs ¬esToOut)
{
int retDur = 0;
TnotePair *firstNote;
Trhythm::Etie firstTie;
while (!m_notes.isEmpty() && dur > 0) {
firstNote = first();
int firstDur = firstNote->note()->duration();
if (firstDur > dur) { // first measure note is longer than required duration - shrink it and create new one
auto rList = Trhythm::resolve(firstDur - dur);
firstTie = firstNote->note()->rtm.tie();
if (!firstNote->note()->isRest())
rList.first().setTie(firstTie > Trhythm::e_tieStart ? Trhythm::e_tieCont : Trhythm::e_tieEnd);
Tnote n(Tnote(*firstNote->note(), rList.first()));
firstNote->setPairNotes(n);
score()->updateNoteInList(firstNote, n);
for (int r = 1; r < rList.count(); ++r) {
if (!firstNote->note()->isRest())
rList[r].setTie(Trhythm::e_tieCont);
m_score->insertSilently(firstNoteId(), Tnote(*firstNote->note(), rList[r]), this);
}
firstDur = dur;
retDur = dur;
dur = 0;
} else { // first note is the same long or smaller than required space - so move it to the next measure
notesToOut << m_notes.takeFirst();
dur -= firstDur;
// TODO maybe clear beams here
}
m_free += firstDur;
}
if (m_free)
fill();
if (retDur) {
// remaining part of the note that goes to previous measure
auto rtmToPrev = Trhythm::resolve(retDur);
for (int r = 0; r < rtmToPrev.count(); ++r) {
if (!firstNote->note()->isRest()) {
if (r < rtmToPrev.count() - 1)
rtmToPrev[r].setTie(Trhythm::e_tieCont);
else
rtmToPrev[r].setTie(firstTie == Trhythm::e_tieCont ? firstTie : Trhythm::e_tieStart);
}
m_score->insertSilently(firstNoteId(), Tnote(*firstNote->note(), rtmToPrev[r]), this);
notesToOut.append(m_notes.takeFirst());
}
}
if (m_free && m_barLine) {
m_barLine->setVisible(false);
m_barLine->setParentItem(nullptr);
}
}
void TmeasureObject::insertSilently(int id, TnotePair *np)
{
m_notes.insert(qBound(0, id, m_notes.size()), np);
if (np->item() == nullptr)
np->setNoteItem(new TnoteItem(m_staff, np));
else if (m_staff != np->item()->staff())
np->item()->setStaff(m_staff);
np->item()->setMeasure(this);
np->item()->setNote(*np->note());
if (m_score->showNoteNames())
np->item()->setNoteNameVisible(true);
}
bool TmeasureObject::eventFilter(QObject *obj, QEvent *event)
{
if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) {
if (m_barLine)
m_barLine->setProperty("color", qApp->palette().text().color());
}
return QObject::eventFilter(obj, event);
}
// #################################################################################################
// ################### PRIVATE ############################################
// #################################################################################################
void TmeasureObject::clearAccidState()
{
for (int i = 0; i < 7; ++i)
m_accidsState[i] = 100; // note doesn't occur in a measure
void TmeasureObject::shiftReleased(Tpairs ¬esToOut)
{
if (!notesToOut.isEmpty())
m_staff->shiftToMeasure(m_number + 1, notesToOut);
}
void TmeasureObject::fill()
{
Tpairs notesToShift;
m_staff->shiftFromMeasure(m_number + 1, m_free, notesToShift);
int lastId = lastNoteId() + 1;
for (int i = 0; i < notesToShift.count(); ++i)
insertSilently(lastId + i, notesToShift[i]);
update();
if (m_free && m_barLine) {
m_barLine->setVisible(false);
m_barLine->setParentItem(nullptr);
}
SeeLook
committed
}
void TmeasureObject::update(int beamGrToResolve)
{
updateRhythmicGroups();
checkAccidentals();
resolveBeaming(beamGrToResolve);
refresh();