Skip to content
Snippets Groups Projects
Commit 285347d4 authored by SeeLook's avatar SeeLook
Browse files

Implemented beams. Single paint() method of QQuickPaintedItem derivative...

Implemented beams. Single paint() method of QQuickPaintedItem derivative TbeamObject paints either 8th beam and 16th beam(s) as well
parent 856dc3b1
No related branches found
No related tags found
No related merge requests found
......@@ -36,6 +36,7 @@ set(LIB_NOOTKACORE_SRC
score/tmeasureobject.cpp
score/tnoteobject.cpp
score/tnotepair.cpp
score/tbeamobject.cpp
instruments/tguitarbg.cpp
......
/***************************************************************************
* Copyright (C) 2016 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 "tbeamobject.h"
#include "tscoreobject.h"
#include "tstaffobject.h"
#include "tmeasureobject.h"
#include "tnoteobject.h"
#include "tnotepair.h"
#include <music/tnote.h>
#include <qmath.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpalette.h>
#include <QtGui/qpainter.h>
#include <QtCore/qdebug.h>
#include "checktime.h"
/**
* Simple structure to describe second beam line (sixteenth)
* which can be chunked when there are rests or eights in the group
*/
class T16beam {
public:
T16beam(int firstStemNr = 0) : startStem(firstStemNr) {}
int startStem = -1; /**< Undefined by default (-1) */
int endStem = -1; /**< When remains undefined (-1) - beam is partial */
/** @p TRUE when beam is not connected to another stem */
bool isHalf() { return endStem == -1; }
};
#define MIN_STEM_HEIGHT (4.0) // minimal stem height (distance of note head to staff boundary)
#define BEAM_THICK (0.8) // thickness of a beam
#define HALF_THICK (0.4) // half of beam thickness
TbeamObject::TbeamObject(TnotePair* sn, TmeasureObject* m) :
QQuickPaintedItem(m->staff()->staffItem()),
m_measure(m)
{
addNote(sn);
setParent(m_measure->score());
}
TbeamObject::~TbeamObject()
{
qDebug() << " [BEAM] deleted of id" << first()->index();
for (TnotePair* note : m_notes) {
note->note()->rtm.setBeam(Trhythm::e_noBeam); // restore beams
}
}
void TbeamObject::addNote(TnotePair* np) {
if (np->beam() == nullptr)
np->setBeam(this);
else
qDebug() << " [BEAM] note" << np->index() << "has already a beam";
if (m_notes.count() > 1)
m_notes.last()->note()->rtm.setBeam(Trhythm::e_beamCont); // no need to be updated
if (m_notes.isEmpty())
np->note()->rtm.setBeam(Trhythm::e_beamStart);
else
np->note()->rtm.setBeam(Trhythm::e_beamEnd);
if (np->item()) // mark it changed only when object exists, otherwise its note will be updated during creation
np->addChange(TnotePair::e_beamChanged);
m_notes << np;
if (np->note()->rhythm() == Trhythm::Sixteenth) {
Tnote* beforeLastNote = m_notes.count() > 1 ? m_notes[m_notes.count() - 2]->note() : nullptr;
if (m_16beams.isEmpty() || (beforeLastNote && beforeLastNote->rhythm() != Trhythm::Sixteenth)) {
// is first in beam or previous note was not a sixteenth
m_16beams << T16beam(m_notes.count() - 1); // then create new beam segment
} else if (!m_16beams.isEmpty() && (beforeLastNote && beforeLastNote->rhythm() == Trhythm::Sixteenth)) {
// there is 16 beam and previous note and this notes are 16th
m_16beams.last().endStem = m_notes.count() - 1; // then set end stem for it
}
}
}
/**
* With @p stemDirStrength (strength of stem direction) we trying to determine preferred common direction of stems in the group.
* More far from middle of the staff (18) note is placed - stronger its direction has to be preserved
* to keep beaming more natural and looking good
*
* @p stemsUpPossible keep true when there is enough space between note head and staff border for whole stem
* if not stems go down.
* @p stemTop it top stem position of all notes. It is set to middle of the staff if necessary (to look well)
*/
void TbeamObject::prepareBeam() {
int stemDirStrength = 0;
bool stemsUpPossible = true;
qreal hiNote = 99.0, loNote = 0.0;
for (TnotePair* np : m_notes) {
stemDirStrength += np->item()->notePosY() - 18;
if (np->item()->notePosY() < MIN_STEM_HEIGHT)
stemsUpPossible = false;
hiNote = qMin(hiNote, np->item()->notePosY());
loNote = qMax(loNote, np->item()->notePosY());
}
bool allStemsDown = !stemsUpPossible;
qreal minStemHeight = MIN_STEM_HEIGHT + (m_16beams.empty() ? 0.0 : 1.0);
if (stemDirStrength < 0)
allStemsDown = true; // stems down are always possible
qreal stemTop = allStemsDown ? loNote + minStemHeight : hiNote - minStemHeight;
if ((allStemsDown && stemTop < m_measure->staff()->upperLine() + 4.0) || (!allStemsDown && stemTop > m_measure->staff()->upperLine() + 4.0))
stemTop = m_measure->staff()->upperLine() + 4.0; // keep beam on staff middle line
for (TnotePair* np : m_notes) {
np->note()->rtm.setStemDown(allStemsDown);
np->addChange(TnotePair::e_stemDirChanged);
np->item()->setStemHeight(qAbs(np->item()->notePosY() - stemTop));
np->approve();
}
}
/**
* Poligons are painted, single for 8ths and possible a few for 16ths.
* Paiter canvas orientation depends on are stems up or down,
* for stems-up, top beam is 8ths and bottom are 16ths,
* for stems-down the opposite.
*/
void TbeamObject::paint(QPainter* painter) {
CHECKTIME (
if (count() > 1) {
qreal s = first()->note()->rtm.stemDown() ? -1.0 : 1.0;
qreal t = first()->note()->rtm.stemDown() ? height() : 0.0;
painter->setPen(Qt::NoPen);
painter->setBrush(qApp->palette().text().color());
QPolygonF topBeam;
topBeam << QPointF(0.0, t) << QPointF(0.0, t + s * BEAM_THICK) << QPointF(width(), t + s * BEAM_THICK) << QPointF(width(), t) << QPointF(0.0, t);
painter->drawPolygon(topBeam);
for (int b = 0; b < m_16beams.count(); ++b) {
T16beam& b16 = m_16beams[b];
qreal startX = m_notes[b16.startStem]->item()->stemTop().x() - x();
// 16th beam of fist stem is right-sided (2.0) others are left-sided (-2.0)
qreal endX = (b16.isHalf() ? startX + BEAM_THICK * (b16.startStem == 0 ? 2.0 : -2.0) : m_notes[b16.endStem]->item()->stemTop().x() - x()) + 0.3;
QPolygonF polyB;
polyB << QPointF(startX, t + s * 1.5 * BEAM_THICK) << QPointF(startX, t + s * 2.5 * BEAM_THICK) << QPointF(endX, t + s * 2.5 * BEAM_THICK)
<< QPointF(endX, t + s * 1.5 * BEAM_THICK) << QPointF(startX, t + s * 1.5 * BEAM_THICK);
painter->drawPolygon(polyB);
}
}
)
}
//#################################################################################################
//################### PROTECTED ############################################
//#################################################################################################
/**
* According to first and last notes in the beam, this method sets item geometry.
* @p setTextureSize() is called to make texture big enough and avoid pixelization
*/
void TbeamObject::drawBeam() {
auto p1 = first()->item()->stemTop();
auto p2 = last()->item()->stemTop();
setWidth(qAbs(p2.x() - p1.x()) + 0.3);
setHeight(qAbs(p1.y() - p2.y()) + BEAM_THICK * 2.5);
setX(p1.x());
setY(qMin(p1.y(), p2.y()) - (first()->note()->rtm.stemDown() ? 2.0 * BEAM_THICK : HALF_THICK));
setTextureSize(QSize(qCeil(width() * m_measure->staff()->scale()), qCeil(height() * m_measure->staff()->scale())));
/** @p update should be call here to invoke @p paint()
* but it is invoked by Qt itself whenever width, height, x or y change */
}
void TbeamObject::changeStaff(TstaffObject* st) {
setParentItem(st->staffItem());
}
/***************************************************************************
* Copyright (C) 2016-2017 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/>. *
***************************************************************************/
#ifndef TBEAMOBJECT_H
#define TBEAMOBJECT_H
#include <nootkacoreglobal.h>
#include <QtQuick/qquickpainteditem.h>
class TstaffObject;
class TmeasureObject;
class TnoteObject;
class TnotePair;
class T16beam;
/**
* This class manages displaying beams of rhythmic group.
* It paints either eight beam line and 16th beam line(s)
* By @p addNote() a beam flag of @p TnotePair is set.
* It that moment @p TnotePair::objeject() can be empty.
* After note objects initialization @p prepareBeam() is called
* where common stem direction of rhythm group is set and stems length.
* When @p TnoteObject::setX() is called (note and its stem changes position)
* @p drawBeam() is invoked to refresh beam size and call @p update()
* to repaint the beam if necessary.
*/
class NOOTKACORE_EXPORT TbeamObject : public QQuickPaintedItem
{
Q_OBJECT
friend class TstaffObject;
friend class TmeasureObject;
friend class TnoteObject;
public:
explicit TbeamObject(TnotePair* sn, TmeasureObject* m);
virtual ~TbeamObject();
/** Adds @p TnotePair to beam group
* and according to adding order sets appropriate beam flag.
* It changes stem direction of note(s) when necessary.
* It does not perform painting yet
*/
void addNote(TnotePair* np);
/**
* Returns note @p id in beam group
*/
TnotePair* note(int id) { return m_notes[id]; }
TnotePair* first() { return m_notes.first(); }
TnotePair* last() { return m_notes.last(); }
/**
* Number of notes in the beam group
*/
int count() { return m_notes.count(); }
void paint(QPainter* painter) override;
protected:
void prepareBeam();
void drawBeam();
/**
* Because beams are parented with staff it is important
* to change their staff when measure is shifted between staves
*/
void changeStaff(TstaffObject* st);
private:
TmeasureObject *m_measure;
QList<TnotePair*> m_notes;
QList<T16beam> m_16beams; /**< list of lines of sixteenths */
};
#endif // TBEAMOBJECT_H
......@@ -21,6 +21,7 @@
#include "tstaffobject.h"
#include "tnoteobject.h"
#include "tnotepair.h"
#include "tbeamobject.h"
#include "music/tmeter.h"
#include "music/tnote.h"
......@@ -58,7 +59,7 @@ void TmeasureObject::setStaff(TstaffObject* st) {
if (m_staff != st) {
m_staff = st;
for (TnotePair* np : m_notes)
np->object()->setStaff(m_staff);
np->item()->setStaff(m_staff);
}
}
......@@ -69,16 +70,25 @@ void TmeasureObject::appendNewNotes(int segmentId, int count) {
for (int n = segmentId; n < segmentId + count; ++n)
m_notes.append(m_score->noteSegment(n));
updateRhythmicGroups();
// resolve beaming
int grWithBeam = beamGroup(segmentId);
for (int n = segmentId; n < segmentId + count; ++n) {
auto np = m_score->noteSegment(n);
auto noteObject = new TnoteObject(m_staff);
noteObject->setIndex(np->index());
auto noteObject = new TnoteObject(m_staff, np);
noteObject->setMeasure(this);
np->setNoteObject(noteObject);
checkAccidentals();
noteObject->setNote(*np->note());
}
if (grWithBeam > -1) {
auto firstInGrId = m_score->noteSegment(firstNoteId() + m_firstInGr[grWithBeam])->index();
while (firstInGrId < m_score->notesCount()) {
auto ns = m_score->noteSegment(firstInGrId);
if (ns->beam()) {
ns->beam()->prepareBeam();
break;
}
}
}
refresh();
m_staff->refresh();
checkBarLine();
......@@ -102,19 +112,19 @@ void TmeasureObject::insertNote(int id, TnotePair* np) {
void TmeasureObject::keySignatureChanged() {
for (int n = 0; n < m_notes.size(); ++n) {
m_notes[n]->object()->keySignatureChanged();
m_notes[n]->item()->keySignatureChanged();
}
refresh();
}
int TmeasureObject::firstNoteId() const {
return m_notes.count() ? m_notes.first()->object()->index() : 0;
return m_notes.count() ? m_notes.first()->index() : 0;
}
int TmeasureObject::lastNoteId() const {
return m_notes.count() ? m_notes.last()->object()->index() : 0;
return m_notes.count() ? m_notes.last()->index() : 0;
}
......@@ -158,7 +168,7 @@ void TmeasureObject::updateRhythmicGroups() {
void TmeasureObject::checkBarLine() {
if (m_free == 0) {
auto lastNote = last()->object();
auto lastNote = last()->item();
if (!m_barLine) {
QQmlEngine engine;
QQmlComponent comp(&engine, this);
......@@ -168,7 +178,7 @@ void TmeasureObject::checkBarLine() {
m_barLine->setProperty("color", lastNote->color());
m_barLine->setY(m_staff->upperLine());
}
qreal xOff = lastNote == m_staff->lastMeasure()->last()->object() ? 0.2 : 0.0; // fit line at the staff end
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);
}
}
......@@ -178,7 +188,7 @@ void TmeasureObject::refresh() {
m_gapsSum = 0.0;
m_allNotesWidth = 0.0;
for (int n = 0; n < m_notes.size(); ++n) {
auto noteObj = note(n)->object();
auto noteObj = note(n)->item();
m_gapsSum += noteObj->rhythmFactor();
m_allNotesWidth += noteObj->width();
}
......@@ -195,6 +205,29 @@ void TmeasureObject::checkAccidentals() {
}
int TmeasureObject::beamGroup(int segmentId) {
int currGr = m_score->noteSegment(segmentId)->rhythmGroup();
int segId = m_firstInGr[currGr] + 1;
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)
{
if (prevSeg->note()->rtm.beam() == Trhythm::e_noBeam) // start beam group
prevSeg->setBeam(new TbeamObject(prevSeg, this));
auto beam = prevSeg->beam();
if (noteSeg->beam() == nullptr)
beam->addNote(noteSeg);
grWithBeam = currGr;
}
segId++;
}
return grWithBeam;
}
//#################################################################################################
//################### PRIVATE ############################################
//#################################################################################################
......
......@@ -29,6 +29,7 @@ class TscoreObject;
class TstaffObject;
class TnoteObject;
class TnotePair;
class TbeamObject;
/**
......@@ -122,6 +123,13 @@ protected:
qint8 accidState(int noteNr) { return m_accidsState[noteNr]; }
/**
* Checks notes rhythms of group @p segmentId belongs to
* for 8ths and 16ths and crates beams (@p TbeamObject) if they occurs
* It can be called before @p TnoteObject will be created
*/
int beamGroup(int segmentId);
private:
void clearAccidState();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment