Skip to content

Commit

Permalink
Merge pull request #204 from canorusmusic/matevz/feature/insert-sylla…
Browse files Browse the repository at this point in the history
…bles-chords

mainwin: Add support for shift+dblClick to insert syllable or chord
  • Loading branch information
matevz authored Nov 22, 2023
2 parents e213f6c + f5bdf85 commit 4303129
Show file tree
Hide file tree
Showing 18 changed files with 260 additions and 217 deletions.
27 changes: 12 additions & 15 deletions src/core/muselementfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -263,18 +263,18 @@ bool CAMusElementFactory::configureNote(int pitch,
if (voice->lastNote() == mpoMusElement) {
// note was appended, reposition elements in dependent contexts accordingly
for (CALyricsContext* lc : voice->lyricsContextList()) {
lc->repositSyllables();
lc->repositionElements();
}
for (CAContext* context : voice->staff()->sheet()->contextList()) {
switch (context->contextType()) {
case CAContext::FunctionMarkContext:
static_cast<CAFunctionMarkContext*>(context)->repositFunctions();
static_cast<CAFunctionMarkContext*>(context)->repositionElements();
break;
case CAContext::FiguredBassContext:
static_cast<CAFiguredBassContext*>(context)->repositFiguredBassMarks();
static_cast<CAFiguredBassContext*>(context)->repositionElements();
break;
case CAContext::ChordNameContext:
static_cast<CAChordNameContext*>(context)->repositChordNames();
static_cast<CAChordNameContext*>(context)->repositionElements();
break;
default:
break;
Expand All @@ -283,21 +283,16 @@ bool CAMusElementFactory::configureNote(int pitch,
} else {
// note was inserted somewhere inbetween, insert empty element in dependent contexts accordingly
for (CALyricsContext* lc : voice->lyricsContextList()) {
lc->addEmptySyllable(mpoMusElement->timeStart(), mpoMusElement->timeLength());
lc->insertEmptyElement(mpoMusElement->timeStart());
lc->repositionElements();
}
for (CAContext* context : voice->staff()->sheet()->contextList()) {
switch (context->contextType()) {
case CAContext::FunctionMarkContext:
static_cast<CAFunctionMarkContext*>(context)->addEmptyFunction(
mpoMusElement->timeStart(), mpoMusElement->timeLength());
break;
case CAContext::FiguredBassContext:
static_cast<CAFiguredBassContext*>(context)->addEmptyFiguredBassMark(
mpoMusElement->timeStart(), mpoMusElement->timeLength());
break;
case CAContext::ChordNameContext:
static_cast<CAChordNameContext*>(context)->addEmptyChordName(
mpoMusElement->timeStart(), mpoMusElement->timeLength());
context->insertEmptyElement(mpoMusElement->timeStart());
context->repositionElements();
break;
default:
break;
Expand Down Expand Up @@ -483,12 +478,14 @@ bool CAMusElementFactory::configureRest(CAVoice* voice, CAMusElement* right)
removeMusElem(true);
else {
for (CALyricsContext* lc : voice->lyricsContextList()) {
lc->repositSyllables();
lc->repositionElements();
}
for (CAContext* context : voice->staff()->sheet()->contextList()) {
switch (context->contextType()) {
case CAContext::FunctionMarkContext:
case CAContext::FiguredBassContext:
case CAContext::ChordNameContext:
static_cast<CAChordNameContext*>(context)->repositChordNames();
static_cast<CAChordNameContext*>(context)->repositionElements();
break;
default:
break;
Expand Down
2 changes: 1 addition & 1 deletion src/import/lilypondimport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ CALyricsContext* CALilyPondImport::importLyricsContextImpl()
lc->addSyllable(lastSyllable = new CASyllable(text, false, false, lc, timeSDummy, 0));
}
}
lc->repositSyllables(); // sets syllables timeStarts and timeLengths
lc->repositionElements(); // sets syllables timeStarts and timeLengths

return lc;
}
Expand Down
25 changes: 9 additions & 16 deletions src/score/chordnamecontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ CAChordNameContext::CAChordNameContext(QString name, CASheet* sheet)
: CAContext(name, sheet)
{
setContextType(ChordNameContext);
repositChordNames();
repositionElements();
}

CAChordNameContext::~CAChordNameContext()
Expand All @@ -52,27 +52,20 @@ void CAChordNameContext::addChordName(CAChordName* m, bool replace)
}
}

/*!
Inserts an empty chord name and shifts the chord names after.
This function is usually called when initializing the context.
*/
void CAChordNameContext::addEmptyChordName(int timeStart, int timeLength)
CAMusElement* CAChordNameContext::insertEmptyElement(int timeStart)
{
int i;
for (i = 0; i < _chordNameList.size() && _chordNameList[i]->timeStart() < timeStart; i++)
;
_chordNameList.insert(i, (new CAChordName(CADiatonicPitch::Undefined, "", this, timeStart, timeLength)));
CAChordName *newChord = new CAChordName(CADiatonicPitch::Undefined, "", this, timeStart, 1);
_chordNameList.insert(i, newChord);
for (i++; i < _chordNameList.size(); i++)
_chordNameList[i]->setTimeStart(_chordNameList[i]->timeStart() + timeLength);
}
_chordNameList[i]->setTimeStart(_chordNameList[i]->timeStart() + 1);

/*!
It repositions the existing chord names (sets timeStart and timeLength) one by one according to the playable music
elements above the context.
return newChord;
}

\sa CALyricsContext::repositSyllables(), CAFiguredBassContext::repositFiguredBassMarks(), CAFunctionMarkContext::repositFunctions()
*/
void CAChordNameContext::repositChordNames()
void CAChordNameContext::repositionElements()
{
int ts, tl;
int curIdx; // contains current position in _chordNameList
Expand All @@ -86,7 +79,7 @@ void CAChordNameContext::repositChordNames()

// add new empty chord names, if playables still exist above
if (curIdx == _chordNameList.size()) {
addEmptyChordName(ts, tl);
insertEmptyElement(ts);
}

// apply timeStart and timeLength to existing chord names
Expand Down
4 changes: 2 additions & 2 deletions src/score/chordnamecontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ class CAChordNameContext : public CAContext {
CAMusElement* next(CAMusElement* elt);
CAMusElement* previous(CAMusElement* elt);
bool remove(CAMusElement* elt);
CAMusElement *insertEmptyElement(int timeStart);
void repositionElements();

QList<CAChordName*>& chordNameList() { return _chordNameList; }
CAChordName* chordNameAtTimeStart(int timeStart);

void repositChordNames();
void addChordName(CAChordName*, bool replace = true);
void addEmptyChordName(int timeStart, int timeLength);

private:
QList<CAChordName*> _chordNameList;
Expand Down
17 changes: 17 additions & 0 deletions src/score/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,20 @@ CAContext::~CAContext()
\sa CAMusElement::clone(), CADocument::clone()
*/

/*!
\fn CAContext::insertEmptyElement(int timeStart)
Inserts an empty dependent element (syllable, chord name, figured bass mark, function mark) to the context.
After the call the elements need to be repositioned manually (subsequent timeStarts and timeLengths will be out of place).
This function is usually called when initializing the dependent context or inserting a new note.
\sa CAContext::repositionElements()
*/

/*!
\fn CAContext::repositionElements()
Repositions the existing dependent elements (syllables, chord names, figured bass marks, function marks) by setting timeStart and timeLength
one by one according to the playable music it depends on. The order is preserved.
\sa CAContext::insertEmptyElement(int)
*/
3 changes: 3 additions & 0 deletions src/score/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ class CAContext {
CASheet* sheet() { return _sheet; }
void setSheet(CASheet* sheet) { _sheet = sheet; }


virtual void clear() = 0;
virtual CAMusElement* next(CAMusElement* elt) = 0;
virtual CAMusElement* previous(CAMusElement* elt) = 0;
virtual bool remove(CAMusElement* elt) = 0;
virtual CAMusElement *insertEmptyElement(int timeStart) = 0;
virtual void repositionElements() = 0;

protected:
void setContextType(CAContextType t) { _contextType = t; }
Expand Down
131 changes: 62 additions & 69 deletions src/score/figuredbasscontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ CAFiguredBassContext::CAFiguredBassContext(QString name, CASheet* sheet)
: CAContext(name, sheet)
{
setContextType(FiguredBassContext);
repositFiguredBassMarks();
repositionElements();
}

CAFiguredBassContext::~CAFiguredBassContext()
Expand All @@ -51,74 +51,6 @@ void CAFiguredBassContext::addFiguredBassMark(CAFiguredBassMark* m, bool replace
_figuredBassMarkList[i]->setTimeStart(_figuredBassMarkList[i]->timeStart() + m->timeLength());
}

/*!
Inserts an empty figured bass mark and shifts the marks after.
This function is usually called when initializing the context.
*/
void CAFiguredBassContext::addEmptyFiguredBassMark(int timeStart, int timeLength)
{
int i;
for (i = 0; i < _figuredBassMarkList.size() && _figuredBassMarkList[i]->timeStart() < timeStart; i++)
;
_figuredBassMarkList.insert(i, (new CAFiguredBassMark(this, timeStart, timeLength)));
for (i++; i < _figuredBassMarkList.size(); i++)
_figuredBassMarkList[i]->setTimeStart(_figuredBassMarkList[i]->timeStart() + timeLength);
}

/*!
Updates timeStarts and timeLength of all figured bass marks according to the chords they belong.
Adds new empty figured bass marks at the end, if needed.
\sa CALyricsContext::repositSyllables(), CAFunctionMarkContext::repositFunctions(), CAChordNameContext::repositChordNames()
*/
void CAFiguredBassContext::repositFiguredBassMarks()
{
if (!sheet()) {
return;
}

QList<CAPlayable*> chord = sheet()->getChord(0);
int fbmIdx = 0;
while (chord.size()) {
int maxTimeStart = chord[0]->timeStart();
int minTimeEnd = chord[0]->timeEnd();
bool notes = false; // are notes present in the chord or only rests?
for (int i = 1; i < chord.size(); i++) {
if (chord[i]->musElementType() == CAMusElement::Note) {
notes = true;
}

if (chord[i]->timeStart() > maxTimeStart) {
maxTimeStart = chord[i]->timeStart();
}
if (chord[i]->timeEnd() < minTimeEnd) {
minTimeEnd = chord[i]->timeEnd();
}
}

// only assign figured bass marks under the notes
if (notes) {
// add new empty figured bass, if none exist
if (fbmIdx == _figuredBassMarkList.size()) {
addEmptyFiguredBassMark(maxTimeStart, minTimeEnd - maxTimeStart);
}

CAFiguredBassMark* mark = _figuredBassMarkList[fbmIdx];
mark->setTimeStart(maxTimeStart);
mark->setTimeLength(minTimeEnd - maxTimeStart);
fbmIdx++;
}

chord = sheet()->getChord(minTimeEnd);
}

// updated times for the figured bass marks at the end (after the score)
for (; fbmIdx < _figuredBassMarkList.size(); fbmIdx++) {
_figuredBassMarkList[fbmIdx]->setTimeStart(((fbmIdx > 0) ? _figuredBassMarkList[fbmIdx - 1] : _figuredBassMarkList[0])->timeEnd());
_figuredBassMarkList[fbmIdx]->setTimeLength(CAPlayableLength::Quarter);
}
}

/*!
Returns figured bass mark at the given \a time.
*/
Expand Down Expand Up @@ -188,3 +120,64 @@ bool CAFiguredBassContext::remove(CAMusElement* elt)

return success;
}

CAMusElement *CAFiguredBassContext::insertEmptyElement(int timeStart)
{
CAFiguredBassMark *newElt = new CAFiguredBassMark(this, timeStart, 1);
int i;
for (i = 0; i < _figuredBassMarkList.size() && _figuredBassMarkList[i]->timeStart() < timeStart; i++)
;
_figuredBassMarkList.insert(i, newElt);
for (i++; i < _figuredBassMarkList.size(); i++)
_figuredBassMarkList[i]->setTimeStart(_figuredBassMarkList[i]->timeStart() + 1);

return newElt;
}

void CAFiguredBassContext::repositionElements()
{
if (!sheet()) {
return;
}

QList<CAPlayable*> chord = sheet()->getChord(0);
int fbmIdx = 0;
while (chord.size()) {
int maxTimeStart = chord[0]->timeStart();
int minTimeEnd = chord[0]->timeEnd();
bool notes = false; // are notes present in the chord or only rests?
for (int i = 1; i < chord.size(); i++) {
if (chord[i]->musElementType() == CAMusElement::Note) {
notes = true;
}

if (chord[i]->timeStart() > maxTimeStart) {
maxTimeStart = chord[i]->timeStart();
}
if (chord[i]->timeEnd() < minTimeEnd) {
minTimeEnd = chord[i]->timeEnd();
}
}

// only assign figured bass marks under the notes
if (notes) {
// add new empty figured bass, if none exist
if (fbmIdx == _figuredBassMarkList.size()) {
insertEmptyElement(maxTimeStart);
}

CAFiguredBassMark* mark = _figuredBassMarkList[fbmIdx];
mark->setTimeStart(maxTimeStart);
mark->setTimeLength(minTimeEnd - maxTimeStart);
fbmIdx++;
}

chord = sheet()->getChord(minTimeEnd);
}

// updated times for the figured bass marks at the end (after the score)
for (; fbmIdx < _figuredBassMarkList.size(); fbmIdx++) {
_figuredBassMarkList[fbmIdx]->setTimeStart(((fbmIdx > 0) ? _figuredBassMarkList[fbmIdx - 1] : _figuredBassMarkList[0])->timeEnd());
_figuredBassMarkList[fbmIdx]->setTimeLength(CAPlayableLength::Quarter);
}
}
4 changes: 2 additions & 2 deletions src/score/figuredbasscontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ class CAFiguredBassContext : public CAContext {
CAMusElement* next(CAMusElement* elt);
CAMusElement* previous(CAMusElement* elt);
bool remove(CAMusElement* elt);
CAMusElement *insertEmptyElement(int timeStart);
void repositionElements();

QList<CAFiguredBassMark*>& figuredBassMarkList() { return _figuredBassMarkList; }
CAFiguredBassMark* figuredBassMarkAtTimeStart(int timeStart);

void repositFiguredBassMarks();
void addFiguredBassMark(CAFiguredBassMark*, bool replace = true);
void addEmptyFiguredBassMark(int timeStart, int timeLength);

private:
QList<CAFiguredBassMark*> _figuredBassMarkList;
Expand Down
Loading

0 comments on commit 4303129

Please sign in to comment.