diff --git a/TODO b/TODO index 853a1cfa7f258837fd4b7880e432fc882169c659..4b24144e4a14875b4124c045aaced4376777cc51 100644 --- a/TODO +++ b/TODO @@ -14,6 +14,11 @@ TRANSLATION CONTEXT CHANGES TpitchView -> PitchView TvolumeView -> VolumeBar + Score actions + +============================================================================ +SCORE + - rich text for key names (accidentals would be in Nootka symbols) ============================================================================ Do we need Tmtr?? Make order with this! @@ -68,7 +73,7 @@ MINOR: - divide TexamExecutor class !!!!! - rhythms in score - in occasion of implementing rhythms, - instead of signaling from TscoreNote to TscoreStaff call some methods of TscoreStaff directly from TscoreNote + - dialog to configure generated melody and its rhythms - rhythms in level and exercise/exam - Tsound has to have a timer to determine rhythmical value of detected note (or a rest) @@ -95,7 +100,6 @@ MINOR: - proxy corner widgets may be animated (???) - add sound during wizard and About dialog (???) - options for adjusting pitch detection range to 1.score, 2. instrument scale (???) -- enable/disable animations option - text only tool bar - migrate on preferring 48000 sample rate (ogg files and audio methods) - move status label out as a separate class and implement animations there (can be used in settings windows as well) diff --git a/changes b/changes index ab78cbd2d9536b497622fe11d412f820482a740d..64d575a6e1f2ce975ca00145c99c1b77ea494b4c 100644 --- a/changes +++ b/changes @@ -1,5 +1,6 @@ 1.5.0 - porting visible part into QML + - option for enable/disable animations 1.4.2 - added standalone AppImage for Linux diff --git a/src/libs/core/score/tmeasureobject.cpp b/src/libs/core/score/tmeasureobject.cpp index 40495c340e19d42ab08a260e7a98834f11cd552b..56cc7aee10bf0d3d23e1f5725f30cf264c5b937e 100644 --- a/src/libs/core/score/tmeasureobject.cpp +++ b/src/libs/core/score/tmeasureobject.cpp @@ -37,6 +37,7 @@ TmeasureObject::TmeasureObject(int nr, TscoreObject* parent) : m_firstInGr(new qint8[1]), m_barLine(nullptr) { + clearAccidState(); m_duration = m_score->meter()->duration(); m_free = m_duration; } @@ -74,8 +75,9 @@ void TmeasureObject::appendNewNotes(int segmentId, int count) { auto noteObject = new TnoteObject(m_staff); noteObject->setIndex(np->index()); noteObject->setMeasure(this); - noteObject->setNote(*np->note()); np->setNoteObject(noteObject); + checkAccidentals(); + noteObject->setNote(*np->note()); } refresh(); m_staff->refresh(); @@ -98,6 +100,14 @@ void TmeasureObject::insertNote(int id, TnotePair* np) { } +void TmeasureObject::keySignatureChanged() { + for (int n = 0; n < m_notes.size(); ++n) { + m_notes[n]->object()->keySignatureChanged(); + } + refresh(); +} + + int TmeasureObject::firstNoteId() const { return m_notes.count() ? m_notes.first()->object()->index() : 0; } @@ -173,3 +183,23 @@ void TmeasureObject::refresh() { 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 + } +} + + +//################################################################################################# +//################### PRIVATE ############################################ +//################################################################################################# + +void TmeasureObject::clearAccidState() { + for (int i = 0; i < 7; ++i) + m_accidsState[i] = 100; // note doesn't occur in a measure +} diff --git a/src/libs/core/score/tmeasureobject.h b/src/libs/core/score/tmeasureobject.h index e171e6fbec45578d52a93d564c20cf3e343caa89..acf0b6819946caebc1b07b862c4650759576a261 100644 --- a/src/libs/core/score/tmeasureobject.h +++ b/src/libs/core/score/tmeasureobject.h @@ -38,7 +38,9 @@ class NOOTKACORE_EXPORT TmeasureObject : public QObject { Q_OBJECT + friend class TscoreObject; friend class TstaffObject; + friend class TnoteObject; public: @@ -114,6 +116,15 @@ protected: */ void refresh(); + void checkAccidentals(); + + void keySignatureChanged(); + + qint8 accidState(int noteNr) { return m_accidsState[noteNr]; } + +private: + void clearAccidState(); + private: int m_number; int m_duration; @@ -126,6 +137,7 @@ private: QQuickItem *m_barLine; qreal m_allNotesWidth = 0.0; qreal m_gapsSum = 0.0; + qint8 m_accidsState[7]; }; #endif // TMEASUREOBJECT_H diff --git a/src/libs/core/score/tnoteobject.cpp b/src/libs/core/score/tnoteobject.cpp index 00a237154ab2504319e54d7635b1c41d6adb1b24..d98138bca413ac9de12d5bb82663e393d63294e7 100644 --- a/src/libs/core/score/tnoteobject.cpp +++ b/src/libs/core/score/tnoteobject.cpp @@ -206,11 +206,8 @@ void TnoteObject::setNote(const Tnote& n) { m_stem->setVisible(false); } - m_alter->setProperty("text", getAccidText()); - if (m_note->alter) - m_alter->setX(-m_alter->width() - 0.1); - - setWidth(m_alter->width() + m_head->width() + (m_note->rtm.stemDown() ? 0.0 : m_flag->width() - 0.5)); + updateAlter(); + updateWidth(); // m_bg->setX(m_note->alter && m_alter->isVisible() ? -m_alter->width() - 0.4 : -0.4); // m_bg->setHeight(m_stem->height() + 4.0); @@ -222,12 +219,12 @@ void TnoteObject::setNote(const Tnote& n) { void TnoteObject::setX(qreal xx) { - QQuickItem::setX(xx + (m_note->alter ? m_alter->width() : 0.0)); + QQuickItem::setX(xx + (m_accidText.isEmpty() ? 0.0 : m_alter->width())); } qreal TnoteObject::rightX() { - return x() + width() + staff()->gapFactor() * rhythmFactor() - (m_note->alter ? m_alter->width() : 0.0); + return x() + width() + staff()->gapFactor() * rhythmFactor() - (m_accidText.isEmpty() ? 0.0 : m_alter->width()); } @@ -252,7 +249,51 @@ char TnoteObject::debug() { //################################################################################################# QString TnoteObject::getAccidText() { - return accCharTable[m_note->alter + 2]; + if (!m_note->isValid() || (m_note->rtm.tie() && m_note->rtm.tie() != Trhythm::e_tieStart)) // accidental only for starting tie + return QString(); + + QString a = accCharTable[m_note->alter + 2]; + qint8 accidInKey = m_staff->score()->accidInKey(m_note->note - 1); + if (accidInKey) { // key signature has an accidental on this note + if (m_note->alter == 0) // show neutral if note has not any accidental + a = accCharTable[5]; + else { + if (accidInKey == m_note->alter) { // accidental in key, do not show + if (m_staff->score()->showExtraAccids() && accidInKey) { // or user wants it at any cost + a.prepend(QStringLiteral("\ue26a")); + a.append(QStringLiteral("\ue26b")); + } else + a.clear(); + } + } + } + int id = index() - 1; // check the previous notes for accidentals + while (id >= 0 && m_staff->score()->noteSegment(id)->object()->measure() == measure()) { + if (m_staff->score()->noteSegment(id)->note()->note == m_note->note) { + char prevAlter = m_staff->score()->noteSegment(id)->note()->alter; + if (prevAlter != 0 && m_note->alter == 0) { + if (a.isEmpty()) + a = accCharTable[5]; // and add neutral when some of previous notes with the same step had an accidental + } else if (prevAlter == 0 && m_note->alter == 0) // There was a neutral, do not display it twice + a.clear(); + else if (accidInKey == m_note->alter && prevAlter != m_note->alter) + a = accCharTable[m_note->alter + 2]; // There is already accidental in key signature but some of the previous notes had another one, show it again + break; + } + id--; + } +// if (m_staff->score()->remindAccids() && m_measure->number() > 0) { TODO +// auto prevMeas = m_staff->score()->measure(m_measure->number() - 1); +// auto accidState = prevMeas->accidState(m_note->note - 1); +// if (accidState != 100 && accidState != 0 && a.isEmpty() && m_alter == 0) { +// a = a = accCharTable[5]; +// m_alter->setProperty("color", QColor(255, 0, 0)); +// } else if (accidState == 0 && accidInKey != 0) { +// a = accCharTable[m_note->alter + 2]; +// m_alter->setProperty("color", QColor(255, 0, 0)); +// } +// } + return a; } @@ -285,6 +326,17 @@ QString TnoteObject::getFlagText() { } +void TnoteObject::keySignatureChanged() { + updateAlter(); + updateWidth(); +} + + +//################################################################################################# +//################### PRIVATE ############################################ +//################################################################################################# + + QQuickItem* TnoteObject::createAddLine(QQmlComponent& comp) { auto line = qobject_cast<QQuickItem*>(comp.create()); line->setParentItem(this); @@ -295,3 +347,16 @@ QQuickItem* TnoteObject::createAddLine(QQmlComponent& comp) { line->setProperty("color", qApp->palette().text().color()); return line; } + + +void TnoteObject::updateAlter() { + m_accidText = getAccidText(); + m_alter->setProperty("text", m_accidText); + if (!m_accidText.isEmpty()) + m_alter->setX(-m_alter->width() - 0.1); +} + + +void TnoteObject::updateWidth() { + setWidth(m_alter->width() + m_head->width() + (m_note->rtm.stemDown() ? 0.0 : m_flag->width() - 0.5)); +} diff --git a/src/libs/core/score/tnoteobject.h b/src/libs/core/score/tnoteobject.h index 9871abd807394dee4062e87c7af2ee487dd1349c..300109a63b237746fd43e3ae8d9742cf15ddc80b 100644 --- a/src/libs/core/score/tnoteobject.h +++ b/src/libs/core/score/tnoteobject.h @@ -39,6 +39,8 @@ class NOOTKACORE_EXPORT TnoteObject : public QQuickItem Q_PROPERTY(qreal notePosY READ notePosY WRITE setNotePosY NOTIFY notePosYchanged) + friend class TmeasureObject; + public: explicit TnoteObject(TstaffObject* staffObj = nullptr); ~TnoteObject(); @@ -64,6 +66,7 @@ public: qreal stemHeight() const { return m_stemHeight; } void setStemHeight(qreal sh); + QColor color() { return m_head->property("color").value<QColor>(); } void setColor(const QColor& c); @@ -95,6 +98,8 @@ protected: QString getHeadText(); QString getFlagText(); + void keySignatureChanged(); + private: TstaffObject *m_staff; @@ -106,9 +111,12 @@ private: QQuickItem *m_head, *m_alter, *m_stem, *m_flag, *m_bg; QList<QQuickItem*> m_upperLines, m_lowerLines; qreal m_stemHeight; + QString m_accidText; private: QQuickItem* createAddLine(QQmlComponent& comp); + void updateAlter(); + void updateWidth(); }; #endif // TNOTEOBJECT_H diff --git a/src/libs/core/score/tscoreobject.cpp b/src/libs/core/score/tscoreobject.cpp index ac9714591c08429780f5420b704202d53c31598e..760fa32a19c996f23bb1a03ab0fdf5cadaa95417 100644 --- a/src/libs/core/score/tscoreobject.cpp +++ b/src/libs/core/score/tscoreobject.cpp @@ -35,6 +35,8 @@ TscoreObject::TscoreObject(QObject* parent) : QObject(parent), m_keySignEnabled(false), + m_showExtraAccids(false), + m_remindAccids(true), m_clefOffset(TclefOffset(3, 1)), m_width(0.0), m_adjustInProgress(false) { @@ -44,6 +46,9 @@ TscoreObject::TscoreObject(QObject* parent) : m_widthTimer = new QTimer(this); m_widthTimer->setSingleShot(true); connect(m_widthTimer, &QTimer::timeout, this, &TscoreObject::adjustScoreWidth); + + for (int i = 0; i < 7; i++) // reset accidentals array + m_accidInKeyArray[i] = 0; } @@ -71,27 +76,64 @@ void TscoreObject::setWidth(qreal w) { } +void TscoreObject::setKeySignature(int k) { + if (m_keySignEnabled) { + qint8 key = static_cast<qint8>(k); + if (key != m_keySignature) { + m_keySignature = key; + for (int i = 1; i < 8; i++) { + qint8 sign = k < 0 ? -1 : 1; + int startVal = k < 0 ? 38 : 48; + if (i <= qAbs(k)) + m_accidInKeyArray[(startVal + sign * (i * 4)) % 7] = sign; + else + m_accidInKeyArray[(startVal + sign * (i * 4)) % 7] = 0; + } + m_keyChanged = true; + for (TmeasureObject* m : m_measures) + m->keySignatureChanged(); + adjustScoreWidth(); + } + } +} + + /** @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) + 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); } + for (int r = 0; r < outList.count(); ++r) { // add ties (ligatures) + Trhythm::Etie tie = Trhythm::e_noTie; + if (r == 0) { + if (n.rtm.tie()) // there was tie before + tie = outList.count() == 1 ? Trhythm::e_tieEnd : Trhythm::e_tieCont; // continue it or finish + else // otherwise start a tie + tie = Trhythm::e_tieStart; + } else if (r == outList.count() - 1) + tie = Trhythm::e_tieEnd; + else + tie = Trhythm::e_tieCont; + outList[r].rtm.setTie(tie); + } } void TscoreObject::addNote(const Tnote& n) { // CHECKTIME ( + qDebug() << "note" << n.toText(); + auto lastMeasure = m_measures.last(); if (lastMeasure->free() == 0) { // new measure is needed lastMeasure = new TmeasureObject(m_measures.count(), this); @@ -204,6 +246,7 @@ void TscoreObject::addStaff(TstaffObject* st) { m_staves.append(st); if (m_staves.count() == 1) // initialize first measure of first staff st->appendMeasure(m_measures.first()); + // next staves position ca be set only when staffItem is set, see TstaffObject::setStaffItem() then connect(st, &TstaffObject::hiNotePosChanged, [=](int staffNr, qreal offset){ for (int i = staffNr; i < m_staves.size(); ++i) // move every staff about offset @@ -265,6 +308,7 @@ CHECKTIME ( void TscoreObject::updateStavesPos() { +CHECKTIME ( if (m_adjustInProgress) return; TstaffObject* prev = nullptr; @@ -275,6 +319,15 @@ void TscoreObject::updateStavesPos() { prev = curr; } emit stavesHeightChanged(); +) +} + + +void TscoreObject::onIndentChanged() { + if (m_keyChanged) { + m_keyChanged = false; + adjustScoreWidth(); + } } diff --git a/src/libs/core/score/tscoreobject.h b/src/libs/core/score/tscoreobject.h index f873311c7c4e65586bc18283898228acc115c516..d32a318641578607ce23aef5049ffb6f003a045d 100644 --- a/src/libs/core/score/tscoreobject.h +++ b/src/libs/core/score/tscoreobject.h @@ -60,6 +60,8 @@ class NOOTKACORE_EXPORT TscoreObject : public QObject Q_PROPERTY(int meter READ meterToInt WRITE setMeter NOTIFY meterChanged) Q_PROPERTY(qreal stavesHeight READ stavesHeight NOTIFY stavesHeightChanged) Q_PROPERTY(qreal width READ width WRITE setWidth) + Q_PROPERTY(int keySignature READ keySignature WRITE setKeySignature) + Q_PROPERTY(bool keySignatureEnabled READ keySignatureEnabled WRITE setKeySignatureEnabled) friend class TstaffObject; friend class TmeasureObject; @@ -80,6 +82,18 @@ public: bool keySignatureEnabled() const { return m_keySignEnabled; } void setKeySignatureEnabled(bool enKey); + int keySignature() { return static_cast<int>(m_keySignature); } + void setKeySignature(int k); + + bool showExtraAccids() { return m_showExtraAccids; } + void setShowExtraAccids(bool accShow); + + /** + * If set, reminds about accidentals changes occurred in previous measure + */ + bool remindAccids() { return m_remindAccids; } + void setRemindAccids(bool doRemaind); + Tmeter* meter() const { return m_meter; } void setMeter(int m); int meterToInt(); @@ -158,6 +172,13 @@ protected: void updateStavesPos(); + void onIndentChanged(); + + /** + * This array keeps values (-1, 0 or 1) for accidentals in key sign. + */ + qint8 accidInKey(int k) { return m_accidInKeyArray[k]; } + private: /** * Appends notes to @p m_notes list, creates corresponding @p TnotePair @@ -168,15 +189,20 @@ private: private: Tmeter *m_meter; bool m_keySignEnabled; + bool m_showExtraAccids; + bool m_remindAccids; TclefOffset m_clefOffset; qreal m_width; bool m_adjustInProgress; + bool m_keyChanged = false; QList<TnotePair*> m_segments; QList<TstaffObject*> m_staves; QList<TmeasureObject*> m_measures; QList<Tnote> m_notes; QList<quint8> m_meterGroups; QTimer *m_widthTimer; + qint8 m_keySignature = 0; + qint8 m_accidInKeyArray[7]; }; diff --git a/src/libs/core/score/tstaffobject.cpp b/src/libs/core/score/tstaffobject.cpp index 99a6f498bfaa37c658a96b6570f2d6a3953d4afa..4529f8b5ea8331298a958648fdd40f96231177db 100644 --- a/src/libs/core/score/tstaffobject.cpp +++ b/src/libs/core/score/tstaffobject.cpp @@ -81,10 +81,16 @@ int TstaffObject::firstMeasureNr() { } +/** + * Indent changes when key signature accidentals are added. It changes gap factor, can cause measure shifting. + * For multiple staves on the score, last staff gets indent change at the end of queue. + * Only then gap factor can be calculated. + */ void TstaffObject::setNotesIndent(qreal ni) { if (m_notesIndent != ni) { m_notesIndent = ni; -// fit(); + if (this == m_score->lastStaff()) + m_score->onIndentChanged(); } } @@ -256,7 +262,7 @@ void TstaffObject::findLowestNote() { // m_loNotePos = height(); // return; // } - m_loNotePos = upperLine() + 13.0; // TODO (isPianoStaff() ? lowerLinePos(): upperLinePos()) + 13.0; + m_loNotePos = upperLine() + 14.0; // TODO (isPianoStaff() ? lowerLinePos(): upperLinePos()) + 13.0; for (int m = m_firstMeasureId; m <= m_lastMeasureId; ++m) { auto measure = m_score->measure(m); for (int n = 0; n < measure->noteCount(); ++n) { diff --git a/src/qml/MainWindow.qml b/src/qml/MainWindow.qml index dedfa0f9528e70f1fcbbc1da9ae266c15028b0e7..2b4251f31c3f4e71c6cd94dedd5f833960340153 100644 --- a/src/qml/MainWindow.qml +++ b/src/qml/MainWindow.qml @@ -118,14 +118,14 @@ ApplicationWindow { // } // } function randNotes() { - var rest = (Math.random() * 100) % 6 > 4 - var accid = rest ? 0 : Math.min(Math.max(-2, -3 + Math.random() * 6), 2) + var rest = false //(Math.random() * 100) % 6 > 4 +// var accid = rest ? 0 : Math.min(Math.max(-2, -3 + Math.random() * 6), 2) + var accid = rest ? 0 : Math.min(Math.floor(Math.random() * 2), 1) var note = rest ? 0 : 1 + Math.random() * 7 - var octave = -2 + Math.random() * 5 + var octave = 0// -2 + Math.random() * 5 score.addNote(Noo.note(note, octave, accid, 2 + Math.random() * 4, rest)) // 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) } diff --git a/src/qml/score/Score.qml b/src/qml/score/Score.qml index 6330e986934c78997a79bfe65a7450022579a3a4..a052afaa61f5dd11cc20c2cae972a41719336cd4 100644 --- a/src/qml/score/Score.qml +++ b/src/qml/score/Score.qml @@ -32,11 +32,11 @@ Flickable { var lastStaff = c.createObject(score.contentItem, { "clef.type": score.clef }) staves.push(lastStaff) lastStaff.enableKeySignature(enableKeySign) -// score.contentY = score.contentHeight - score.height +// score.contentY = score.contentHeight - score.height TODO lastStaff.keySignature.onKeySignatureChanged.connect(setKeySignature) lastStaff.onDestroing.connect(removeStaff) if (enableKeySign) - lastStaff.keySignature.key = keySignature() + lastStaff.keySignature.key = staff0.keySignature.key } onStavesHeightChanged: score.contentHeight = Math.max(stavesHeight, score.height) function removeStaff(nr) { staves.splice(nr, 1) } @@ -51,13 +51,13 @@ Flickable { property var staves: [] property real scaleFactor: 1.0 - function keySignature() { return enableKeySign ? staff0.keySignature.key : 0 } - function setKeySignature(key) { if (enableKeySign) { - for (var s = 0; s < staves.length; ++s) + for (var s = 0; s < staves.length; ++s) { if (key !== staves[s].keySignature.key) staves[s].keySignature.key = key + } + scoreObj.keySignature = key } } @@ -99,6 +99,7 @@ Flickable { if (enableKeySign) staff0.keySignature.onKeySignatureChanged.connect(setKeySignature) } + scoreObj.keySignatureEnabled = enableKeySign } function addNote(n) { scoreObj.addNote(n) }