diff --git a/src/libs/core/score/tmeasureobject.cpp b/src/libs/core/score/tmeasureobject.cpp index 865d76572402e27430135879848283e8ae03ce00..cd94fead00d0baa7ef9ca09026ebdfa1d5bebe4e 100644 --- a/src/libs/core/score/tmeasureobject.cpp +++ b/src/libs/core/score/tmeasureobject.cpp @@ -21,15 +21,24 @@ #include "tstaffobject.h" #include "tnoteobject.h" #include "tnotepair.h" +#include "music/tmeter.h" +#include "music/tnote.h" + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> #include <QtCore/qdebug.h> -TmeasureObject::TmeasureObject(TscoreObject* parent) : +TmeasureObject::TmeasureObject(int nr, TscoreObject* parent) : QObject(parent), + m_number(nr), m_score(parent), m_staff(nullptr), - m_number(-1) // undefined + m_firstInGr(new qint8[1]), + m_barLine(nullptr) { + m_duration = m_score->meter()->duration(); + m_free = m_duration; } @@ -45,14 +54,36 @@ void TmeasureObject::setStaff(TstaffObject* st) { } +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)"; + for (int n = segmentId; n < segmentId + count; ++n) + m_notes.append(m_score->noteSegment(n)); + updateRhythmicGroups(); + // resolve beaming + for (int n = segmentId; n < segmentId + count; ++n) { + auto np = m_score->noteSegment(n); + auto noteObject = new TnoteObject(m_staff); + noteObject->setMeasure(this); + noteObject->setNote(*np->note()); + np->setNoteObject(noteObject); + } + m_staff->appendNewNotes(segmentId, count); + if (m_free == 0) + checkBarLine(); +} + + void TmeasureObject::insertNote(int id, TnotePair* np) { - qDebug() << "[TmeasureObject] inserting note at" << id; +// qDebug() << debug() << "inserting note at" << id; + + m_notes.append(np); + updateRhythmicGroups(); if (np->object() == nullptr) { auto noteObject = new TnoteObject(m_staff); noteObject->setMeasure(this); noteObject->setNote(*np->note()); np->setNoteObject(noteObject); - m_notes.append(np); m_staff->addNote(np); } } @@ -68,4 +99,56 @@ int TmeasureObject::lastNoteId() const { } +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::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); + notePos += 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; + +// qDebug() << debug() << "Updating rhythmic groups" << m_free; +} + + +void TmeasureObject::checkBarLine() { + if (!m_barLine) { + QQmlEngine engine; + QQmlComponent comp(&engine, this); + + comp.setData("import QtQuick 2.7; Rectangle { width: 0.3; height: 8 }", QUrl()); + m_barLine = qobject_cast<QQuickItem*>(comp.create()); + } + auto lastNote = last()->object(); + m_barLine->setParentItem(m_staff->staffItem()); + m_barLine->setProperty("color", lastNote->color()); + m_barLine->setX(lastNote->rightX() - 0.6); + m_barLine->setY(m_staff->upperLine()); +// qDebug() << debug() << "check barline"; +} diff --git a/src/libs/core/score/tmeasureobject.h b/src/libs/core/score/tmeasureobject.h index 477984c8483a247754f79c6917700ce9d0845abc..99b419aa3e27745da7e8bd636fb38ece226f5911 100644 --- a/src/libs/core/score/tmeasureobject.h +++ b/src/libs/core/score/tmeasureobject.h @@ -24,6 +24,7 @@ #include <QtCore/qobject.h> +class QQuickItem; class TscoreObject; class TstaffObject; class TnoteObject; @@ -39,11 +40,27 @@ class NOOTKACORE_EXPORT TmeasureObject : public QObject public: - explicit TmeasureObject(TscoreObject* parent = nullptr); + explicit TmeasureObject(int nr = -1, TscoreObject* parent = nullptr); int number() const { return m_number; } void setNumber(int nr); + /** + * Actual duration of all notes in the measure + */ + int duration() const { return m_duration; } + + /** + * Free 'rhythm space' remained in the measure + */ + int free() const { return m_free; } + + /** + * Adds list of @p count notes at the measure end starting from @p segmentId note number in the score + * Measure has to have already space for the whole list! + */ + void appendNewNotes(int segmentId, int count); + void insertNote(int id, TnotePair* np); TscoreObject* score() { return m_score; } @@ -64,12 +81,28 @@ public: */ int lastNoteId() const; + /** + * Prints debug message with [number MEASURE] + */ + char debug(); + +protected: + /** + * Sets appropriate @p setRhythmGroup of every note in the measure. + */ + void updateRhythmicGroups(); + void checkBarLine(); + private: + int m_number; + int m_duration; + int m_id; TscoreObject *m_score; TstaffObject *m_staff; - int m_number; + int m_free; QList<TnotePair*> m_notes; - + qint8 *m_firstInGr; /**< qint8 is sufficient - measure never has more than 127 notes */ + QQuickItem *m_barLine; }; #endif // TMEASUREOBJECT_H diff --git a/src/libs/core/score/tnoteobject.cpp b/src/libs/core/score/tnoteobject.cpp index 578b4ef07941bcb0bf434bedb2786462e3b7fb45..8aab76b20912236398ffc15b530cd585d2c92b3c 100644 --- a/src/libs/core/score/tnoteobject.cpp +++ b/src/libs/core/score/tnoteobject.cpp @@ -24,6 +24,7 @@ #include <QtQml/qqmlengine.h> #include <QtGui/qguiapplication.h> #include <QtGui/qpalette.h> + #include <QtCore/qdebug.h> @@ -146,13 +147,17 @@ void TnoteObject::setColor(const QColor& c) { * - width */ void TnoteObject::setNote(const Tnote& n) { - bool updateHead = n.rhythm() != m_note->rhythm() || n.isRest() != m_note->isRest(); + bool updateHead = n.rhythm() != m_note->rhythm() || n.isRest() != m_note->isRest() || n.hasDot() != m_note->hasDot(); bool updateStem = updateHead || (n.rtm.beam() != Trhythm::e_noBeam) != (m_note->rtm.beam() != Trhythm::e_noBeam); *m_note = n; - if (updateHead) - m_head->setProperty("text", getHeadText()); + if (updateHead) { + QString headText = getHeadText(); + if (m_note->hasDot()) + headText.append(QStringLiteral("\ue1e7")); + m_head->setProperty("text", headText); + } if (m_note->isRest()) m_notePosY = staff()->upperLine() + (m_note->rhythm() == Trhythm::Whole ? 2.0 : 4.0); @@ -201,7 +206,7 @@ void TnoteObject::setNote(const Tnote& n) { m_bg->setY(m_stem->y() - (m_note->rtm.stemDown() ? 3.25 : 1.25)); m_bg->setWidth(width()); - qDebug() << "[TnoteObject] set note" << m_note->toText() << m_note->rtm.string() << "note pos" << m_notePosY << "width:" << width(); + qDebug() << debug() << "set note" << m_note->toText() << m_note->rtm.string() << "note pos" << m_notePosY << "width:" << width(); } @@ -224,6 +229,13 @@ qreal TnoteObject::rhythmFactor() { } +char TnoteObject::debug() { + QTextStream o(stdout); + o << " \033[01;29m[NOTE" << index() << "]\033[01;00m"; + return 32; // fake +} + + //################################################################################################# //################### PROTECTED ############################################ //################################################################################################# diff --git a/src/libs/core/score/tnoteobject.h b/src/libs/core/score/tnoteobject.h index aa0c9b16183a4fc1ae42c1e1eab7f2e4d21b00f7..0fbf281b31601e08707f6219934b2409026c3739 100644 --- a/src/libs/core/score/tnoteobject.h +++ b/src/libs/core/score/tnoteobject.h @@ -29,6 +29,9 @@ class TmeasureObject; class Tnote; +/** + * @class TnoteObject is @class QQuickItem derivative representing single note on the score + */ class NOOTKACORE_EXPORT TnoteObject : public QQuickItem { @@ -64,20 +67,25 @@ public: void setColor(const QColor& c); /** - * Overrides standard @p setX() method to shift note segment about accidental symbol width (if it is set) - */ + * Overrides standard @p setX() method to shift note segment about accidental symbol width (if it is set) + */ void setX(qreal xx); /** - * shortcut to X coordinate of right note corner plus gap related to rhythm and staff gap factor - */ + * shortcut to X coordinate of right note corner plus gap related to rhythm and staff gap factor + */ qreal rightX(); /** - * Returns gap factor after this note item depends on current rhythm value - */ + * Returns gap factor after this note item depends on current rhythm value + */ qreal rhythmFactor(); + /** + * Prints to std out debug info about this note: [NOTE number] in color + */ + char debug(); + signals: void notePosYchanged(); diff --git a/src/libs/core/score/tnotepair.h b/src/libs/core/score/tnotepair.h index db6998891572d2c61949ed67c314c97d32793483..3e86929ceb83479bae2799a2c0d13c255787923d 100644 --- a/src/libs/core/score/tnotepair.h +++ b/src/libs/core/score/tnotepair.h @@ -20,6 +20,9 @@ #define TNOTEPAIR_H +#include <QtCore/qobject.h> + + class Tnote; class TnoteObject; @@ -37,9 +40,17 @@ public: void setNoteObject(TnoteObject* ob); + /** + * Number of rhythmical group in the measure, -1 (undefined) by default + */ + qint8 rhythmGroup() { return m_group; } + void setRhythmGroup(qint8 g) { m_group = g; } + + private: Tnote *m_note; TnoteObject *m_noteObj; + qint8 m_group = -1; }; #endif // TNOTEPAIR_H diff --git a/src/libs/core/score/tscoreobject.cpp b/src/libs/core/score/tscoreobject.cpp index 6ac441f61e10f7cc4d77041160d0e0d243dd1cec..25d2ad4a755260d0713dd82e45c5425e6456c18f 100644 --- a/src/libs/core/score/tscoreobject.cpp +++ b/src/libs/core/score/tscoreobject.cpp @@ -22,8 +22,9 @@ #include "tnotepair.h" #include "music/tmeter.h" #include "music/tnote.h" -#include <QtCore/qdebug.h> +#include <QtCore/qdebug.h> +#include "checktime.h" TscoreObject::TscoreObject(QObject* parent) : @@ -33,7 +34,7 @@ TscoreObject::TscoreObject(QObject* parent) : { m_meter = new Tmeter(Tmeter::Meter_4_4); setMeter(4); // Tmeter::Meter_4_4 - m_measures << new TmeasureObject(this); + m_measures << new TmeasureObject(0, this); } @@ -50,12 +51,68 @@ void TscoreObject::setParent(QObject* p) { } +/** @p static + * This method creates @p outList of notes that have pitch of @p n note + * but splits @p dur duration into possible rhythmic values. + */ +void solveList(const Tnote& n, int dur, QList<Tnote>& outList) { + // TODO: add ties + Trhythm rtm(dur); // try to obtain rhythm value + if (!rtm.isValid()) { // if it is not possible to express the duration in single rhythm - resolve it in multiple values + TrhythmList solvList; + Trhythm::resolve(dur, solvList); + for (int r = 0; r < solvList.count(); ++r) + outList << Tnote(n, Trhythm(solvList[r].rhythm(), n.isRest(), solvList[r].hasDot(), solvList[r].isTriplet())); + } else { // just single note in the list + rtm.setRest(n.isRest()); + outList << Tnote(n, rtm); + } +} + void TscoreObject::addNote(const Tnote& n) { - m_notes << n; - auto newSeg = new TnotePair(&m_notes.last()); - m_segments << newSeg; +CHECKTIME ( + auto lastMeasure = m_measures.last(); - lastMeasure->insertNote(lastMeasure->lastNoteId() - lastMeasure->firstNoteId(), newSeg); + if (lastMeasure->free() == 0) { + lastMeasure = new TmeasureObject(m_measures.count(), this); + lastMeasure->setStaff(lastStaff()); + m_measures << lastMeasure; + } + int noteDur = n.duration(); + if (noteDur > lastMeasure->free()) { // split note that is adding + int leftDuration = noteDur - lastMeasure->free(); + QList<Tnote> notesToCurrent; + QList<Tnote> notesToNext; + int lastNoteId = m_segments.count(); + + solveList(n, lastMeasure->free(), notesToCurrent); // solve free duration in current measure + if (notesToCurrent.isEmpty()) + qDebug() << "[TscoreObject] can't resolve duration of" << lastMeasure->free(); + else { + appendNoteList(notesToCurrent); + lastMeasure->appendNewNotes(lastNoteId, notesToCurrent.count()); + } + + solveList(n, leftDuration, notesToNext); // solve remaining duration for the next measure + lastNoteId = m_segments.count(); // update id of the last note segment + if (notesToNext.isEmpty()) + qDebug() << "[TscoreObject] can't resolve duration" << leftDuration; + else { + appendNoteList(notesToNext); + auto newLastMeasure = new TmeasureObject(m_measures.count(), this); // add a new measure + newLastMeasure->setStaff(lastStaff()); + m_measures << newLastMeasure; + newLastMeasure->appendNewNotes(lastNoteId, notesToNext.count()); + } + } else { // just add new note to the measure + m_notes << n; + int lastNoteId = m_segments.count(); + auto newSeg = new TnotePair(&m_notes.last()); + m_segments << newSeg; + lastMeasure->appendNewNotes(lastNoteId, 1); + } + +) } @@ -120,3 +177,16 @@ void TscoreObject::addStaff(TstaffObject* st) { if (st->number() == 0) // initialize first measure staff m_measures.first()->setStaff(st); } + + +//################################################################################################# +//################### PRIVATE ############################################ +//################################################################################################# + +void TscoreObject::appendNoteList(QList<Tnote>& l) { + for (Tnote n : l) { + m_notes << n; + m_segments << new TnotePair(&m_notes.last()); + } +} + diff --git a/src/libs/core/score/tscoreobject.h b/src/libs/core/score/tscoreobject.h index 927d8300ecd9f5c6c5a79ce1221b9f4f41595c20..180b4c0d04e725730ca0a4dd39c7523679999555 100644 --- a/src/libs/core/score/tscoreobject.h +++ b/src/libs/core/score/tscoreobject.h @@ -77,6 +77,14 @@ public: void setMeter(int m); int meterToInt(); + TnotePair* noteSegment(int id) { return m_segments[id]; } + TnotePair* firstSegment() { return m_segments.first(); } + TnotePair* lastSegment() { return m_segments.last(); } + + TstaffObject* staff(int id) { return m_staves[id]; } + TstaffObject* firstStaff() { return m_staves.first(); } + TstaffObject* lastStaff() { return m_staves.last(); } + /** * Returns duration of given @param grNr group starting from measure beginning * Describes grouping (beaming - beam connections) of notes in a single measure for current meter. @@ -101,6 +109,13 @@ protected: TclefOffset clefOffset() const { return m_clefOffset; } +private: + /** + * Appends notes to @p m_notes list, creates corresponding @p TnotePair + * and adds them to @p m_segments list + */ + void appendNoteList(QList<Tnote>& l); + private: Tmeter *m_meter; bool m_keySignEnabled; diff --git a/src/libs/core/score/tstaffobject.cpp b/src/libs/core/score/tstaffobject.cpp index c2e2a370b537a47aff2d184a04366602d2d7526c..ff13d6d52b3e271bc22e80a9e83351c0cf3cdd16 100644 --- a/src/libs/core/score/tstaffobject.cpp +++ b/src/libs/core/score/tstaffobject.cpp @@ -36,14 +36,14 @@ TstaffObject::TstaffObject(QObject* parent) : TstaffObject::~TstaffObject() { - qDebug() << "[TstaffObject] is going delete"; + qDebug() << debug() << "is going delete"; } void TstaffObject::setScore(TscoreObject* s) { m_score = s; setParent(s); - qDebug() << "TstaffObject got a score parent" << s; + qDebug() << debug() << "got a score parent" << s; m_score->addStaff(this); } @@ -55,9 +55,19 @@ void TstaffObject::addNote(TnotePair* np) { } +void TstaffObject::appendNewNotes(int segmentId, int count) { + for (int n = segmentId; n < segmentId + count; ++n) { + auto np = m_score->noteSegment(n); + np->object()->setIndex(m_notes.count()); + m_notes.append(np); + } + fit(); +} + + void TstaffObject::setNote(int noteNr, const Tnote& n) { if (noteNr < 0 || noteNr >= m_notes.count()) { - qDebug() << "[TstaffObject] There is no note with number" << noteNr; + qDebug() << debug() << "There is no note with number" << noteNr; return; } // m_notes[noteNr]->setNote(n); @@ -93,13 +103,20 @@ void TstaffObject::setNotesIndent(qreal ni) { } +char TstaffObject::debug() { + QTextStream o(stdout); + o << "\033[01;34m[" << number() + 1 << " STAFF]\033[01;00m"; + return 32; // fake +} + + //################################################################################################# //################### PROTECTED ############################################ //################################################################################################# void TstaffObject::fit() { if (m_notes.isEmpty()) { - qDebug() << "[TstaffObject] Empty staff - nothing to fit"; + qDebug() << debug() << "Empty staff - nothing to fit"; return; } @@ -124,7 +141,7 @@ void TstaffObject::fit() { void TstaffObject::updateNotesPos(int startId) { - qDebug() << "updating notes positions from" << startId; + qDebug() << debug() << "updating notes positions from" << startId; if (startId == 0) m_notes[0]->object()->setX(m_notesIndent); diff --git a/src/libs/core/score/tstaffobject.h b/src/libs/core/score/tstaffobject.h index 9404d24d980ce8d605e9894bfcfaae7029d2b74b..de1188092efa3a4260ae96cb6552fb49d106ec39 100644 --- a/src/libs/core/score/tstaffobject.h +++ b/src/libs/core/score/tstaffobject.h @@ -55,6 +55,11 @@ public: void addNote(TnotePair* np); + /** + * Adds list of @p count notes at the staff end starting from @p segmentId note number in the score + */ + void appendNewNotes(int segmentId, int count); + void setNote(int noteNr, const Tnote& n); /** @@ -83,11 +88,16 @@ public: TmeasureObject* lastMeasure() { return m_measures.last(); } /** - * Multiplexer of rhythm gaps between notes. - * It changes to place all notes nicely over entire staff width - */ + * Multiplexer of rhythm gaps between notes. + * It changes to place all notes nicely over entire staff width + */ qreal gapFactor() { return m_gapFactor; } + /** + * Prints to std out debug info about this staff: [nr STAFF] in color + */ + char debug(); + signals: void upperLineChanged(); void firstMeasureNrChanged(); diff --git a/src/libs/core/tinitcorelib.cpp b/src/libs/core/tinitcorelib.cpp index d368992c1a7b1909c74e14c1650d1272849af59b..9551f161a60b0e1bb1f93962bffa5e64ecee4376 100644 --- a/src/libs/core/tinitcorelib.cpp +++ b/src/libs/core/tinitcorelib.cpp @@ -53,7 +53,9 @@ bool initCoreLibrary() { Tcore::androidStyle = QStyleFactory::create(QStringLiteral("android")); #endif + Trhythm::initialize(); Tcolor::setShadow(qApp->palette()); + #if defined(Q_OS_MAC) QDir dir(qApp->applicationDirPath()); dir.cdUp(); diff --git a/src/qml/MainWindow.qml b/src/qml/MainWindow.qml index e23f371fe32ce9c3a33bf5783c4edc8b5d36973e..957fec3e10fcb9a886f2b6063ad2b6001541ae3a 100644 --- a/src/qml/MainWindow.qml +++ b/src/qml/MainWindow.qml @@ -81,12 +81,6 @@ ApplicationWindow { width: parent.width height: parent.height - header.height - instrument.height enableKeySign: true - Component.onCompleted: { - for (var n = 1; n < 8; ++n) { - addNote(Noo.note(1 + Math.random() * 7, -2 + Math.random() * 5, Math.min(Math.max(-2, -3 + Math.random() * 6), 2), - 3 + Math.random() * 3)) - } - } } Instrument { @@ -97,11 +91,20 @@ ApplicationWindow { } } +// Component.onCompleted: { +// for (var n = 1; n < 8; ++n) { +// score.addNote(Noo.note(1 + Math.random() * 7, -2 + Math.random() * 5, Math.min(Math.max(-2, -3 + Math.random() * 6), 2), +// 2 + Math.random() * 4)) +// } +// } + // Timer { -// interval: 1000 +// interval: 5000 // running: true // repeat: true // onTriggered: { +// score.addNote(Noo.note(1 + Math.random() * 7, -2 + Math.random() * 5, Math.min(Math.max(-2, -3 + Math.random() * 6), 2), +// 2 + Math.random() * 4)) // var noteNr = Math.random() * 7 // var rest = Math.floor((Math.random() * 100) % 2) // var accid = rest ? 0 : Math.min(Math.max(-2, -3 + Math.random() * 6), 2) @@ -116,11 +119,13 @@ ApplicationWindow { // } // } function randNotes() { - var noteNr = Math.random() * 7 - var rest = Math.floor((Math.random() * 100) % 2) - var accid = rest ? 0 : Math.min(Math.max(-2, -3 + Math.random() * 6), 2) - var note = Noo.note(1 + Math.random() * 7, -3 + Math.random() * 7, accid, 1 + Math.random() * 5, rest) - score.setNote(0, noteNr, note) + score.addNote(Noo.note(1 + Math.random() * 7, -2 + Math.random() * 5, Math.min(Math.max(-2, -3 + Math.random() * 6), 2), + 2 + Math.random() * 4)) +// var noteNr = Math.random() * 7 +// var rest = Math.floor((Math.random() * 100) % 2) +// var accid = rest ? 0 : Math.min(Math.max(-2, -3 + Math.random() * 6), 2) +// var note = Noo.note(1 + Math.random() * 7, -3 + Math.random() * 7, accid, 1 + Math.random() * 5, rest) +// score.setNote(0, noteNr, note) } }