From abb3d5de489b5967b7e46395fc91282266636257 Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Thu, 23 Nov 2023 16:29:30 +0200 Subject: [PATCH 01/29] fix #19264: reset the layout range after reading elements When reading elements, they actively call triggerLayout() (usually from setProperty). But because the elements don't have the correct parent tree at the time of reading, they can't provide the correct layout range for triggerLayout(). Since PlaybackModel also depends on this range, we can't correctly render playback events from it. So reset the range when the elements are read --- src/engraving/rw/read400/read400.cpp | 6 ++++++ src/engraving/rw/read410/read410.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/engraving/rw/read400/read400.cpp b/src/engraving/rw/read400/read400.cpp index 02216e097db1b..7b9e329a99b4b 100644 --- a/src/engraving/rw/read400/read400.cpp +++ b/src/engraving/rw/read400/read400.cpp @@ -704,6 +704,12 @@ bool Read400::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract if (endStaff > score->nstaves()) { endStaff = score->nstaves(); } + + if (score->cmdState().layoutRange()) { + score->cmdState().reset(); + score->setLayout(dstTick, dstTick + tickLen, dstStaff, endStaff, dst); + } + //check and add truly invisible rests instead of gaps //TODO: look if this could be done different Measure* dstM = score->tick2measure(dstTick); diff --git a/src/engraving/rw/read410/read410.cpp b/src/engraving/rw/read410/read410.cpp index ce9f9b6ab93fd..05c57694d7198 100644 --- a/src/engraving/rw/read410/read410.cpp +++ b/src/engraving/rw/read410/read410.cpp @@ -713,6 +713,12 @@ bool Read410::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract if (endStaff > score->nstaves()) { endStaff = score->nstaves(); } + + if (score->cmdState().layoutRange()) { + score->cmdState().reset(); + score->setLayout(dstTick, dstTick + tickLen, dstStaff, endStaff, dst); + } + //check and add truly invisible rests instead of gaps //TODO: look if this could be done different Measure* dstM = score->tick2measure(dstTick); From e507d0bf0de0e427a1668acaaa6b1bdd021bf405 Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Sat, 25 Nov 2023 08:00:24 +0200 Subject: [PATCH 02/29] Send changed items through changesChannel() This way we will know exactly what needs to be updated in PlaybackModel --- src/engraving/dom/cmd.cpp | 11 +++++++++-- src/engraving/dom/types.h | 3 +++ src/engraving/dom/undo.cpp | 2 +- src/engraving/dom/undo.h | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index 4c9300bcb9798..07447d7385475 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -108,7 +108,7 @@ static UndoMacro::ChangesInfo changesInfo(const UndoStack* stack) return actualMacro->changesInfo(); } -static std::pair changedTicksRange(const CmdState& cmdState, const std::vector& changedItems) +static std::pair changedTicksRange(const CmdState& cmdState, const std::set& changedItems) { int startTick = cmdState.startTick().ticks(); int endTick = cmdState.endTick().ticks(); @@ -318,6 +318,10 @@ void Score::undoRedo(bool undo, EditData* ed) ScoreChangesRange range = changesRange(); + if (range.changedItems.empty()) { + range.changedItems = std::move(changes.changedItems); + } + if (range.changedTypes.empty()) { range.changedTypes = std::move(changes.changedObjectTypes); } @@ -387,7 +391,10 @@ ScoreChangesRange Score::changesRange() const return { ticksRange.first, ticksRange.second, cmdState.startStaff(), cmdState.endStaff(), - changes.changedObjectTypes, changes.changedPropertyIdSet, changes.changedStyleIdSet }; + std::move(changes.changedItems), + std::move(changes.changedObjectTypes), + std::move(changes.changedPropertyIdSet), + std::move(changes.changedStyleIdSet) }; } #ifndef NDEBUG diff --git a/src/engraving/dom/types.h b/src/engraving/dom/types.h index 4f34584dc44f7..974a15fbefc6f 100644 --- a/src/engraving/dom/types.h +++ b/src/engraving/dom/types.h @@ -32,6 +32,8 @@ #include "style/styledef.h" namespace mu::engraving { +class EngravingItem; + enum class CommandType { Unknown = -1, @@ -508,6 +510,7 @@ struct ScoreChangesRange { staff_idx_t staffIdxFrom = mu::nidx; staff_idx_t staffIdxTo = mu::nidx; + std::set changedItems; ElementTypeSet changedTypes; PropertyIdSet changedPropertyIdSet; StyleIdSet changedStyleIdSet; diff --git a/src/engraving/dom/undo.cpp b/src/engraving/dom/undo.cpp index 0b82519586d6a..5d075616154a0 100644 --- a/src/engraving/dom/undo.cpp +++ b/src/engraving/dom/undo.cpp @@ -672,7 +672,7 @@ UndoMacro::ChangesInfo UndoMacro::changesInfo() const continue; } - result.changedItems.push_back(item); + result.changedItems.insert(item); } } diff --git a/src/engraving/dom/undo.h b/src/engraving/dom/undo.h index 921921b934896..53021fd6bee16 100644 --- a/src/engraving/dom/undo.h +++ b/src/engraving/dom/undo.h @@ -174,7 +174,7 @@ class UndoMacro : public UndoCommand struct ChangesInfo { ElementTypeSet changedObjectTypes; - std::vector changedItems; + std::set changedItems; StyleIdSet changedStyleIdSet; PropertyIdSet changedPropertyIdSet; }; From 50f729e76b5b5e73843f6f27d3a62da9f6d1e88e Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Sat, 25 Nov 2023 10:50:42 +0200 Subject: [PATCH 03/29] fix #15914: update events for tied notes --- src/engraving/playback/playbackmodel.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/engraving/playback/playbackmodel.cpp b/src/engraving/playback/playbackmodel.cpp index 574d63a770881..cd5b69233993c 100644 --- a/src/engraving/playback/playbackmodel.cpp +++ b/src/engraving/playback/playbackmodel.cpp @@ -766,6 +766,26 @@ PlaybackModel::TickBoundaries PlaybackModel::tickBoundaries(const ScoreChangesRa const Measure* lastMeasure = m_score->lastMeasure(); result.tickFrom = 0; result.tickTo = lastMeasure ? lastMeasure->endTick().ticks() : 0; + + return result; + } + + for (const EngravingItem* item : changesRange.changedItems) { + if (item->isTie()) { + const Tie* tie = toTie(item); + const Note* startNote = tie->startNote(); + const Note* endNote = tie->endNote(); + + IF_ASSERT_FAILED(startNote && endNote) { + return result; + } + + const Note* firstTiedNote = startNote->firstTiedNote(); + const Note* lastTiedNote = startNote->lastTiedNote(); + + result.tickFrom = std::min(result.tickFrom, firstTiedNote->tick().ticks()); + result.tickTo = std::max(result.tickTo, lastTiedNote->tick().ticks()); + } } return result; From 799c0f7db84eb7905089a30c1c9e01f39e902daf Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Sat, 25 Nov 2023 11:04:54 +0200 Subject: [PATCH 04/29] fix #15250: update events for tremolo with two notes --- src/engraving/playback/playbackmodel.cpp | 28 +++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/engraving/playback/playbackmodel.cpp b/src/engraving/playback/playbackmodel.cpp index cd5b69233993c..793dcca08bfc1 100644 --- a/src/engraving/playback/playbackmodel.cpp +++ b/src/engraving/playback/playbackmodel.cpp @@ -32,6 +32,8 @@ #include "dom/repeatlist.h" #include "dom/segment.h" #include "dom/tempo.h" +#include "dom/tie.h" +#include "dom/tremolo.h" #include "log.h" @@ -771,17 +773,37 @@ PlaybackModel::TickBoundaries PlaybackModel::tickBoundaries(const ScoreChangesRa } for (const EngravingItem* item : changesRange.changedItems) { - if (item->isTie()) { + if (item->isNote()) { + const Note* note = toNote(item); + const Chord* chord = note->chord(); + const Tremolo* tremolo = chord->tremolo(); + + if (tremolo && tremolo->twoNotes()) { + const Chord* startChord = tremolo->chord1(); + const Chord* endChord = tremolo->chord2(); + + IF_ASSERT_FAILED(startChord && endChord) { + continue; + } + + result.tickFrom = std::min(result.tickFrom, startChord->tick().ticks()); + result.tickTo = std::max(result.tickTo, endChord->tick().ticks()); + } + } else if (item->isTie()) { const Tie* tie = toTie(item); const Note* startNote = tie->startNote(); const Note* endNote = tie->endNote(); IF_ASSERT_FAILED(startNote && endNote) { - return result; + continue; } const Note* firstTiedNote = startNote->firstTiedNote(); - const Note* lastTiedNote = startNote->lastTiedNote(); + const Note* lastTiedNote = endNote->lastTiedNote(); + + IF_ASSERT_FAILED(firstTiedNote && lastTiedNote) { + continue; + } result.tickFrom = std::min(result.tickFrom, firstTiedNote->tick().ticks()); result.tickTo = std::max(result.tickTo, lastTiedNote->tick().ticks()); From 3bd357a3e81491763f0c5e82e8cef376c675a6e7 Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Mon, 27 Nov 2023 13:14:33 +0200 Subject: [PATCH 05/29] fix #20108: take into account already parsed articulations --- src/engraving/playback/renderers/bendsrenderer.cpp | 1 + src/engraving/tests/bendsrenderer_tests.cpp | 11 +++++++---- .../guitar_bends/guitar_bends.mscx | 5 +++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/engraving/playback/renderers/bendsrenderer.cpp b/src/engraving/playback/renderers/bendsrenderer.cpp index 86d8038213a54..cacad47d12635 100644 --- a/src/engraving/playback/renderers/bendsrenderer.cpp +++ b/src/engraving/playback/renderers/bendsrenderer.cpp @@ -244,6 +244,7 @@ mu::mpe::NoteEvent BendsRenderer::buildBendEvent(const Note* startNote, const Re NominalNoteCtx noteCtx(startNote, startNoteCtx); const mpe::NoteEvent& startNoteEvent = std::get(bendNoteEvents.front()); + noteCtx.chordCtx.commonArticulations = startNoteEvent.expressionCtx().articulations; noteCtx.timestamp = startNoteEvent.arrangementCtx().actualTimestamp; PitchOffsets pitchOffsets; diff --git a/src/engraving/tests/bendsrenderer_tests.cpp b/src/engraving/tests/bendsrenderer_tests.cpp index b2f2ffddcf89d..f95145ff77734 100644 --- a/src/engraving/tests/bendsrenderer_tests.cpp +++ b/src/engraving/tests/bendsrenderer_tests.cpp @@ -87,7 +87,8 @@ class Engraving_BendsRendererTests : public ::testing::Test return pattern; } - RenderingContext buildCtx(const Chord* chord, ArticulationsProfilePtr profile) + RenderingContext buildCtx(const Chord* chord, ArticulationsProfilePtr profile, + ArticulationType persistentArticulationType = ArticulationType::Standard) { int chordPosTick = chord->tick().ticks(); int chordDurationTicks = chord->actualTicks().ticks(); @@ -103,7 +104,7 @@ class Engraving_BendsRendererTests : public ::testing::Test chordDurationTicks, bps, timeSignatureFraction, - ArticulationType::Undefined, + persistentArticulationType, ArticulationMap(), profile); @@ -130,7 +131,8 @@ TEST_F(Engraving_BendsRendererTests, Multibend) profile->setPattern(ArticulationType::Standard, buildTestArticulationPattern()); profile->setPattern(ArticulationType::Multibend, buildTestArticulationPattern()); profile->setPattern(ArticulationType::PreAppoggiatura, buildTestArticulationPattern()); - RenderingContext ctx = buildCtx(startChord, profile); + profile->setPattern(ArticulationType::Distortion, buildTestArticulationPattern()); + RenderingContext ctx = buildCtx(startChord, profile, ArticulationType::Distortion); // [THEN] BendsRenderer can render the multibend articulation EXPECT_TRUE(BendsRenderer::isAbleToRender(ArticulationType::Multibend)); @@ -145,7 +147,8 @@ TEST_F(Engraving_BendsRendererTests, Multibend) const mu::mpe::NoteEvent& noteEvent = std::get(events.front()); - //EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Multibend)); + EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Multibend)); + EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Distortion)); // persistent articulation applied EXPECT_EQ(noteEvent.arrangementCtx().actualTimestamp, 500000); // starts after a quarter rest EXPECT_EQ(noteEvent.arrangementCtx().actualDuration, 3000000); // quarters: F3 + G3 + F3 + A3 + A3 + G3 EXPECT_EQ(noteEvent.pitchCtx().nominalPitchLevel, 2050); // F3 diff --git a/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx b/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx index 3c3925ccb0073..437e74d65eda3 100755 --- a/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx +++ b/src/engraving/tests/playbackeventsrenderer_data/guitar_bends/guitar_bends.mscx @@ -168,6 +168,11 @@ Multibend + + distortion + 167503724594 + distort + 25769803890 From 484a0ccba0780155d4b31f47e15961f2ce4cc74c Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Mon, 27 Nov 2023 15:48:14 +0200 Subject: [PATCH 06/29] fix #18654: parse articulations for grace notes --- src/engraving/playback/renderers/gracechordsrenderer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engraving/playback/renderers/gracechordsrenderer.cpp b/src/engraving/playback/renderers/gracechordsrenderer.cpp index b9e5d4c6195da..756b1cad048d6 100644 --- a/src/engraving/playback/renderers/gracechordsrenderer.cpp +++ b/src/engraving/playback/renderers/gracechordsrenderer.cpp @@ -26,6 +26,8 @@ #include "chordarticulationsrenderer.h" +#include "playback/metaparsers/notearticulationsparser.h" + using namespace mu::engraving; using namespace mu::mpe; @@ -122,6 +124,7 @@ void GraceChordsRenderer::renderGraceNoteEvents(const std::vector& grace noteCtx.duration = duration; noteCtx.timestamp = timestamp; + NoteArticulationsParser::buildNoteArticulationMap(graceNote, ctx, noteCtx.chordCtx.commonArticulations); updateArticulationBoundaries(graceCtx.type, noteCtx.timestamp, noteCtx.duration, noteCtx.chordCtx.commonArticulations); result.emplace_back(buildNoteEvent(std::move(noteCtx))); From ce4cda2f6b89374c2495add5b5879cf5f8952278 Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Mon, 27 Nov 2023 18:22:08 +0200 Subject: [PATCH 07/29] fix #19266: parse articulations for tremolo/arpeggio notes --- src/engraving/playback/renderers/arpeggiorenderer.cpp | 4 ++++ src/engraving/playback/renderers/tremolorenderer.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/engraving/playback/renderers/arpeggiorenderer.cpp b/src/engraving/playback/renderers/arpeggiorenderer.cpp index 9b238ea0704b4..89a3e4e2eacda 100644 --- a/src/engraving/playback/renderers/arpeggiorenderer.cpp +++ b/src/engraving/playback/renderers/arpeggiorenderer.cpp @@ -25,6 +25,8 @@ #include "dom/chord.h" #include "dom/arpeggio.h" +#include "playback/metaparsers/notearticulationsparser.h" + using namespace mu::engraving; using namespace mu::mpe; @@ -121,6 +123,8 @@ std::map ArpeggioRenderer::arpeggioNotes(const Ch } NominalNoteCtx noteCtx(note, ctx); + NoteArticulationsParser::buildNoteArticulationMap(note, ctx, noteCtx.chordCtx.commonArticulations); + result.emplace(noteCtx.pitchLevel, std::move(noteCtx)); } diff --git a/src/engraving/playback/renderers/tremolorenderer.cpp b/src/engraving/playback/renderers/tremolorenderer.cpp index 48b845f77bc6d..d90649a51daec 100644 --- a/src/engraving/playback/renderers/tremolorenderer.cpp +++ b/src/engraving/playback/renderers/tremolorenderer.cpp @@ -25,6 +25,8 @@ #include "dom/chord.h" #include "dom/tremolo.h" +#include "playback/metaparsers/notearticulationsparser.h" + using namespace mu::engraving; using namespace mu::mpe; @@ -114,7 +116,9 @@ void TremoloRenderer::buildAndAppendEvents(const Chord* chord, const Articulatio noteCtx.duration = stepDuration; noteCtx.timestamp += timestampOffset; + NoteArticulationsParser::buildNoteArticulationMap(note, context, noteCtx.chordCtx.commonArticulations); updateArticulationBoundaries(type, noteCtx.timestamp, noteCtx.duration, noteCtx.chordCtx.commonArticulations); + result.emplace_back(buildNoteEvent(std::move(noteCtx))); } } From 11c285e9e18a5a58924f26b0d07a7a65de017c22 Mon Sep 17 00:00:00 2001 From: Roman Pudashkin Date: Tue, 28 Nov 2023 12:25:55 +0200 Subject: [PATCH 08/29] fixed arpeggio duration --- .../playback/renderers/arpeggiorenderer.cpp | 29 +++++--- .../playback/renderers/arpeggiorenderer.h | 2 +- .../tests/playbackeventsrendering_tests.cpp | 70 +++++++++++++++---- 3 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/engraving/playback/renderers/arpeggiorenderer.cpp b/src/engraving/playback/renderers/arpeggiorenderer.cpp index 89a3e4e2eacda..1485565e4cb00 100644 --- a/src/engraving/playback/renderers/arpeggiorenderer.cpp +++ b/src/engraving/playback/renderers/arpeggiorenderer.cpp @@ -46,7 +46,6 @@ void ArpeggioRenderer::doRender(const EngravingItem* item, const mpe::Articulati mpe::PlaybackEventList& result) { const Chord* chord = toChord(item); - IF_ASSERT_FAILED(chord) { return; } @@ -58,11 +57,16 @@ void ArpeggioRenderer::doRender(const EngravingItem* item, const mpe::Articulati int stepsCount = static_cast(chord->notes().size()); mpe::percentage_t percentageStep = mpe::HUNDRED_PERCENT / stepsCount; + msecs_t offsetStep = timestampOffsetStep(context, stepsCount); + double stretch = arpeggio->Stretch(); auto buildEvent = [&](NominalNoteCtx& noteCtx, const int stepNumber) { noteCtx.chordCtx.commonArticulations.updateOccupiedRange(preferredType, stepNumber * percentageStep, (stepNumber + 1) * percentageStep); - noteCtx.timestamp += timestampOffsetStep(context) * stepNumber * arpeggio->Stretch(); + timestamp_t offset = offsetStep * stepNumber * stretch; + noteCtx.timestamp += offset; + noteCtx.duration -= offset; + result.emplace_back(buildNoteEvent(std::move(noteCtx))); }; @@ -94,35 +98,44 @@ bool ArpeggioRenderer::isDirectionUp(const mpe::ArticulationType type) } } -msecs_t ArpeggioRenderer::timestampOffsetStep(const RenderingContext& ctx) +msecs_t ArpeggioRenderer::timestampOffsetStep(const RenderingContext& ctx, int stepCount) { - constexpr int MINIMAL_TIMESTAMP_OFFSET_STEP = 60000; + constexpr int MAX_TIMESTAMP_OFFSET_STEP = 60000; + + int offsetStep = ctx.nominalDuration / stepCount; + if (offsetStep < MAX_TIMESTAMP_OFFSET_STEP) { + return offsetStep; + } if (RealIsEqualOrMore(ctx.beatsPerSecond.val, PRESTISSIMO_BPS_BOUND)) { - return MINIMAL_TIMESTAMP_OFFSET_STEP * 1.5; + return MAX_TIMESTAMP_OFFSET_STEP * 1.5; } if (RealIsEqualOrMore(ctx.beatsPerSecond.val, PRESTO_BPS_BOUND)) { - return MINIMAL_TIMESTAMP_OFFSET_STEP * 1.25; + return MAX_TIMESTAMP_OFFSET_STEP * 1.25; } if (RealIsEqualOrMore(ctx.beatsPerSecond.val, MODERATO_BPS_BOUND)) { - return MINIMAL_TIMESTAMP_OFFSET_STEP; + return MAX_TIMESTAMP_OFFSET_STEP; } - return MINIMAL_TIMESTAMP_OFFSET_STEP; + return MAX_TIMESTAMP_OFFSET_STEP; } std::map ArpeggioRenderer::arpeggioNotes(const Chord* chord, const RenderingContext& ctx) { std::map result; + const Score* score = chord->score(); + for (const Note* note : chord->notes()) { if (!isNotePlayable(note, ctx.commonArticulations)) { continue; } NominalNoteCtx noteCtx(note, ctx); + noteCtx.duration = timestampFromTicks(score, note->playTicks()); + NoteArticulationsParser::buildNoteArticulationMap(note, ctx, noteCtx.chordCtx.commonArticulations); result.emplace(noteCtx.pitchLevel, std::move(noteCtx)); diff --git a/src/engraving/playback/renderers/arpeggiorenderer.h b/src/engraving/playback/renderers/arpeggiorenderer.h index ffea8aedcd72d..46fe0b10c2757 100644 --- a/src/engraving/playback/renderers/arpeggiorenderer.h +++ b/src/engraving/playback/renderers/arpeggiorenderer.h @@ -38,7 +38,7 @@ class ArpeggioRenderer : public RenderBase private: static bool isDirectionUp(const mpe::ArticulationType type); - static mpe::msecs_t timestampOffsetStep(const RenderingContext& context); + static mpe::msecs_t timestampOffsetStep(const RenderingContext& context, int stepCount); static std::map arpeggioNotes(const Chord* chord, const RenderingContext& ctx); }; } diff --git a/src/engraving/tests/playbackeventsrendering_tests.cpp b/src/engraving/tests/playbackeventsrendering_tests.cpp index 280fbfe5f1515..cdeee3f01d291 100644 --- a/src/engraving/tests/playbackeventsrendering_tests.cpp +++ b/src/engraving/tests/playbackeventsrendering_tests.cpp @@ -1460,11 +1460,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1492,7 +1499,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::Arpeggio)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1522,11 +1530,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Up) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1554,7 +1569,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Up) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioUp)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1584,11 +1600,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Down) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1616,7 +1639,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Down) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioDown)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1647,11 +1671,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Down) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1679,7 +1710,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Down) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioStraightDown)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure @@ -1710,11 +1742,18 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Up) // [GIVEN] Expected disclosure int expectedSubNotesCount = 3; + int expectedOffset = 60000; + + std::vector expectedTimestamp = { + 0, + expectedOffset, + expectedOffset* 2, + }; std::vector expectedDurations = { QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION, - QUARTER_NOTE_DURATION + QUARTER_NOTE_DURATION - expectedOffset, + QUARTER_NOTE_DURATION - expectedOffset * 2 }; std::vector expectedPitches = { @@ -1742,7 +1781,8 @@ TEST_F(Engraving_PlaybackEventsRendererTests, Chord_Arpeggio_Straight_Up) EXPECT_EQ(noteEvent.expressionCtx().articulations.size(), 1); EXPECT_TRUE(noteEvent.expressionCtx().articulations.contains(ArticulationType::ArpeggioStraightUp)); - // [THEN] We expect that each sub-note has expected duration + // [THEN] We expect that each sub-note has expected timestamp and duration + EXPECT_EQ(noteEvent.arrangementCtx().nominalTimestamp, expectedTimestamp.at(i)); EXPECT_EQ(noteEvent.arrangementCtx().nominalDuration, expectedDurations.at(i)); // [THEN] We expect that each note event will match expected pitch disclosure From 4b2b0453fc6abd5b360a28f625f025f5bf97ccd6 Mon Sep 17 00:00:00 2001 From: Eism Date: Wed, 29 Nov 2023 11:15:07 +0200 Subject: [PATCH 09/29] Added the ability to change sharps to flats --- share/instruments/string_tunings_presets.json | 26 +++++++---- src/engraving/dom/stringdata.h | 1 + src/engraving/dom/stringtunings.cpp | 10 +++-- src/engraving/dom/utils.cpp | 45 +++++++++++-------- src/engraving/dom/utils.h | 2 +- src/engraving/rw/read410/tread.cpp | 1 + src/engraving/rw/write/twrite.cpp | 12 +++-- .../internal/instrumentsrepository.cpp | 6 ++- src/notation/notationtypes.h | 1 + .../internal/stringtuningssettingsmodel.cpp | 42 +++++++++++++---- .../internal/stringtuningssettingsmodel.h | 5 +++ 11 files changed, 106 insertions(+), 45 deletions(-) diff --git a/share/instruments/string_tunings_presets.json b/share/instruments/string_tunings_presets.json index 1c057f4c1dece..eaf61dda14836 100644 --- a/share/instruments/string_tunings_presets.json +++ b/share/instruments/string_tunings_presets.json @@ -24,28 +24,36 @@ "presets": [ { "name": "Standard", - "value": [40, 45, 50, 55, 59, 64] + "value": [40, 45, 50, 55, 59, 64], + "useFlats": true },{ "name": "Tune down 1/2 step", - "value": [39, 44, 49, 54, 58, 63] + "value": [39, 44, 49, 54, 58, 63], + "useFlats": true },{ "name": "Tune down 1 step", - "value": [38, 43, 48, 53, 57, 62] + "value": [38, 43, 48, 53, 57, 62], + "useFlats": true },{ "name": "Tune down 2 step", - "value": [36, 41, 46, 51, 55, 60] + "value": [36, 41, 46, 51, 55, 60], + "useFlats": true },{ "name": "Dropped D tune down 1/2 step", - "value": [37, 44, 49, 54, 58, 63] + "value": [37, 44, 49, 54, 58, 63], + "useFlats": true },{ "name": "Dropped D Variant", - "value": [38, 45, 50, 55, 57, 64] + "value": [38, 45, 50, 55, 57, 64], + "useFlats": true },{ "name": "Double Dropped D", - "value": [38, 45, 50, 55, 59, 62] + "value": [38, 45, 50, 55, 59, 62], + "useFlats": true },{ "name": "Dropped C", - "value": [36, 43, 48, 53, 57, 62] + "value": [36, 43, 48, 53, 57, 62], + "useFlats": true },{ "name": "Dropped E", "value": [40, 47, 52, 57, 61, 66] @@ -305,4 +313,4 @@ } ] } -] \ No newline at end of file +] diff --git a/src/engraving/dom/stringdata.h b/src/engraving/dom/stringdata.h index 9446c176b0f5b..14c08708a173e 100644 --- a/src/engraving/dom/stringdata.h +++ b/src/engraving/dom/stringdata.h @@ -45,6 +45,7 @@ struct instrString { int pitch = 0; // the pitch of the string bool open = false; // true: string is open | false: string is fretted int startFret = 0; // banjo 5th string starts on 5th fret + bool useFlat = false; bool operator==(const instrString& d) const { return d.pitch == pitch && d.open == open; } }; diff --git a/src/engraving/dom/stringtunings.cpp b/src/engraving/dom/stringtunings.cpp index 419c63b903f85..290062c47de9f 100644 --- a/src/engraving/dom/stringtunings.cpp +++ b/src/engraving/dom/stringtunings.cpp @@ -164,9 +164,10 @@ String StringTunings::accessibleInfo() const for (int i = 0; i < numOfStrings; ++i) { string_idx_t index = numOfStrings - i - 1; if (mu::contains(m_visibleStrings, index)) { - String pitchStr = pitch2string(stringList[index].pitch); + const instrString str = stringList[index]; + String pitchStr = pitch2string(str.pitch, str.useFlat); if (pitchStr.empty()) { - LOGE() << "Invalid get pitch name for " << stringList[index].pitch; + LOGE() << "Invalid get pitch name for " << str.pitch; continue; } @@ -252,9 +253,10 @@ String StringTunings::generateText() const for (int i = 0; i < numOfStrings; ++i) { string_idx_t index = numOfStrings - i - 1; if (mu::contains(m_visibleStrings, index)) { - String pitchStr = pitch2string(stringList[index].pitch); + const instrString str = stringList[index]; + String pitchStr = pitch2string(str.pitch, str.useFlat); if (pitchStr.empty()) { - LOGE() << "Invalid get pitch name for " << stringList[index].pitch; + LOGE() << "Invalid get pitch name for " << str.pitch; continue; } diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 88e4d52c3c597..faa861e812d6b 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -485,7 +485,7 @@ int quantizeLen(int len, int raster) return int(((float)len / raster) + 0.5) * raster; //round to the closest multiple of raster } -static const char16_t* vall[] = { +static const char16_t* valSharp[] = { u"c", u"c♯", u"d", @@ -499,19 +499,19 @@ static const char16_t* vall[] = { u"a♯", u"b" }; -static const char16_t* valu[] = { - u"C", - u"C♯", - u"D", - u"D♯", - u"E", - u"F", - u"F♯", - u"G", - u"G♯", - u"A", - u"A♯", - u"B" +static const char16_t* valFlat[] = { + u"c", + u"d♭", + u"d", + u"e♭", + u"e", + u"f", + u"g♭", + u"g", + u"a♭", + u"a", + u"b♭", + u"b" }; /*! @@ -526,7 +526,7 @@ static const char16_t* valu[] = { * @return * The string representation of the note. */ -String pitch2string(int v) +String pitch2string(int v, bool useFlats) { if (v < 0 || v > 127) { return String(u"----"); @@ -535,7 +535,13 @@ String pitch2string(int v) String o; o = String::number(octave); int i = v % PITCH_DELTA_OCTAVE; - return (octave < 0 ? valu[i] : vall[i]) + o; + + String pitchStr = useFlats ? valFlat[i] : valSharp[i]; + if (octave < 0) { + pitchStr = pitchStr.toUpper(); + } + + return pitchStr + o; } /*! @@ -553,7 +559,7 @@ int string2pitch(const String& s) return -1; } - String origin = s; + String value = s; bool negative = s.contains(u'-'); int octave = String(s[s.size() - 1]).toInt() * (negative ? -1 : 1); @@ -561,11 +567,12 @@ int string2pitch(const String& s) return -1; } - origin = origin.mid(0, origin.size() - (negative ? 2 : 1)); + value = value.mid(0, value.size() - (negative ? 2 : 1)); + value = value.toLower(); int pitchIndex = -1; for (int i = 0; i < PITCH_DELTA_OCTAVE; ++i) { - if (origin.toLower() == String(octave < 0 ? valu[i] : vall[i]).toLower()) { + if (value == valFlat[i] || value == valSharp[i]) { pitchIndex = i; break; } diff --git a/src/engraving/dom/utils.h b/src/engraving/dom/utils.h index f380d06f06d72..1538a03a0bcfd 100644 --- a/src/engraving/dom/utils.h +++ b/src/engraving/dom/utils.h @@ -48,7 +48,7 @@ extern int pitchKeyAdjust(int note, Key); extern int line2pitch(int line, ClefType clef, Key); extern int y2pitch(double y, ClefType clef, double spatium); extern int quantizeLen(int, int); -extern String pitch2string(int v); +extern String pitch2string(int v, bool useFlats = false); extern int string2pitch(const String& s); extern void transposeInterval(int pitch, int tpc, int* rpitch, int* rtpc, Interval, bool useDoubleSharpsFlats); extern int transposeTpc(int tpc, Interval interval, bool useDoubleSharpsFlats); diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 31e4c81bf90c7..53e28e1114356 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -3971,6 +3971,7 @@ void TRead::read(StringData* item, XmlReader& e) } else if (tag == "string") { instrString strg; strg.open = e.intAttribute("open", 0); + strg.useFlat = e.intAttribute("useFlat", 0); strg.pitch = e.readInt(); item->stringList().push_back(strg); } else { diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index f81769e4ea883..3535a604e8b24 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -2609,11 +2609,17 @@ void TWrite::write(const StringData* item, XmlWriter& xml) xml.startElement("StringData"); xml.tag("frets", item->frets()); for (const instrString& strg : item->stringList()) { + XmlWriter::Attributes attrs; + if (strg.open) { - xml.tag("string", { { "open", "1" } }, strg.pitch); - } else { - xml.tag("string", strg.pitch); + attrs.push_back({ "open", "1" }); } + + if (strg.useFlat) { + attrs.push_back({ "useFlat", "1" }); + } + + xml.tag("string", attrs, strg.pitch); } xml.endElement(); } diff --git a/src/notation/internal/instrumentsrepository.cpp b/src/notation/internal/instrumentsrepository.cpp index e82bc5a830118..cff2d533403b4 100644 --- a/src/notation/internal/instrumentsrepository.cpp +++ b/src/notation/internal/instrumentsrepository.cpp @@ -192,11 +192,15 @@ bool InstrumentsRepository::loadStringTuningsPresets(const io::path_t& path) preset.value.push_back(valueVal.toInt()); } - if (info.number != static_cast(preset.value.size())) { + if (info.number != preset.value.size()) { LOGE() << "Invalid preset " << preset.name; continue; } + if (presetObj.contains("useFlats")) { + preset.useFlats = presetObj.value("useFlats").toBool(); + } + info.presets.emplace_back(std::move(preset)); } diff --git a/src/notation/notationtypes.h b/src/notation/notationtypes.h index 97f7fb1de8fb7..70ff5086c975b 100644 --- a/src/notation/notationtypes.h +++ b/src/notation/notationtypes.h @@ -686,6 +686,7 @@ struct StringTuningPreset { std::string name; std::vector value; + bool useFlats = false; }; struct StringTuningsInfo diff --git a/src/notation/view/internal/stringtuningssettingsmodel.cpp b/src/notation/view/internal/stringtuningssettingsmodel.cpp index 066fe4fe81c8d..597ae6e3ac9d7 100644 --- a/src/notation/view/internal/stringtuningssettingsmodel.cpp +++ b/src/notation/view/internal/stringtuningssettingsmodel.cpp @@ -88,6 +88,7 @@ void StringTuningsSettingsModel::init() item->setShow(contains(visibleStrings, instrStringIndex)); item->setNumber(QString::number(i + 1)); item->setValue(string.pitch); + item->setUseFlat(string.useFlat); item->blockSignals(false); m_strings.push_back(item); @@ -134,6 +135,9 @@ bool StringTuningsSettingsModel::setStringValue(int stringIndex, const QString& item->setValue(value); + bool useFlat = _stringValue.contains("♭"); + item->setUseFlat(useFlat); + beginMultiCommands(); updateCurrentPreset(); @@ -153,7 +157,7 @@ bool StringTuningsSettingsModel::canIncreaseStringValue(const QString& stringVal QString StringTuningsSettingsModel::increaseStringValue(const QString& stringValue) { QString value = convertToUnicode(stringValue); - return engraving::pitch2string(engraving::string2pitch(value) + 1); + return engraving::pitch2string(engraving::string2pitch(value) + 1, false /* useFlats */); } bool StringTuningsSettingsModel::canDecreaseStringValue(const QString& stringValue) const @@ -165,7 +169,7 @@ bool StringTuningsSettingsModel::canDecreaseStringValue(const QString& stringVal QString StringTuningsSettingsModel::decreaseStringValue(const QString& stringValue) { QString value = convertToUnicode(stringValue); - return engraving::pitch2string(engraving::string2pitch(value) - 1); + return engraving::pitch2string(engraving::string2pitch(value) - 1, true /* useFlats */); } QVariantList StringTuningsSettingsModel::presets(bool withCustom) const @@ -184,8 +188,9 @@ QVariantList StringTuningsSettingsModel::presets(bool withCustom) const customMap.insert("text", custom); QVariantList valueList; - for (const StringTuningsItem* item : m_strings) { - valueList << item->value(); + int numOfStrings = static_cast(m_strings.size()); + for (int i = 0; i < numOfStrings; ++i) { + valueList << m_strings.at(numOfStrings - i - 1)->value(); } customMap.insert("value", valueList); @@ -210,6 +215,7 @@ QVariantList StringTuningsSettingsModel::presets(bool withCustom) const } presetMap.insert("value", valueList); + presetMap.insert("useFlats", preset.useFlats); presetsList.push_back(presetMap); } @@ -313,6 +319,7 @@ void StringTuningsSettingsModel::updateStrings() for (const QVariant& _preset : presets) { if (_preset.toMap()["text"].toString() == currentPreset) { QVariantList valueList = _preset.toMap()["value"].toList(); + bool useFlats = _preset.toMap()["useFlats"].toBool(); int numOfStrings = valueList.size(); for (int i = 0; i < numOfStrings; ++i) { int valueIndex = numOfStrings - i - 1; @@ -322,6 +329,7 @@ void StringTuningsSettingsModel::updateStrings() item->setShow(true); item->setNumber(QString::number(i + 1)); item->setValue(valueList[valueIndex].toInt()); + item->setUseFlat(useFlats); item->blockSignals(false); m_strings.push_back(item); @@ -348,7 +356,9 @@ void StringTuningsSettingsModel::saveStrings() int numOfStrings = m_strings.size(); for (int i = 0; i < numOfStrings; ++i) { - stringList[i].pitch = m_strings[numOfStrings - i - 1]->value(); + const StringTuningsItem* item = m_strings.at(numOfStrings - i - 1); + stringList[i].pitch = item->value(); + stringList[i].useFlat = item->useFlat(); } beginCommand(); @@ -378,8 +388,9 @@ void StringTuningsSettingsModel::saveStringsVisibleState() void StringTuningsSettingsModel::updateCurrentPreset() { QVariantList currentValueList; - for (int i = 0; i < m_strings.size(); ++i) { - currentValueList << m_strings[i]->value(); + int numOfStrings = static_cast(m_strings.size()); + for (int i = 0; i < numOfStrings; ++i) { + currentValueList << m_strings.at(numOfStrings - i - 1)->value(); } const QVariantList presets = this->presets(false /*withCustom*/); @@ -471,7 +482,7 @@ int StringTuningsItem::value() const QString StringTuningsItem::valueStr() const { - return engraving::pitch2string(m_value).toUpper(); + return engraving::pitch2string(m_value, m_useFlat).toUpper(); } void StringTuningsItem::setValue(int value) @@ -483,3 +494,18 @@ void StringTuningsItem::setValue(int value) m_value = value; emit valueChanged(); } + +bool StringTuningsItem::useFlat() const +{ + return m_useFlat; +} + +void StringTuningsItem::setUseFlat(bool use) +{ + if (m_useFlat == use) { + return; + } + + m_useFlat = use; + emit valueChanged(); +} diff --git a/src/notation/view/internal/stringtuningssettingsmodel.h b/src/notation/view/internal/stringtuningssettingsmodel.h index 545ead31c50e5..ea5e619eb0f95 100644 --- a/src/notation/view/internal/stringtuningssettingsmodel.h +++ b/src/notation/view/internal/stringtuningssettingsmodel.h @@ -107,6 +107,7 @@ class StringTuningsItem : public QObject Q_PROPERTY(QString number READ number NOTIFY numberChanged) Q_PROPERTY(int value READ value NOTIFY valueChanged) Q_PROPERTY(QString valueStr READ valueStr NOTIFY valueChanged) + Q_PROPERTY(bool useFlat READ useFlat WRITE setUseFlat NOTIFY valueChanged) public: explicit StringTuningsItem(QObject* parent = nullptr); @@ -121,6 +122,9 @@ class StringTuningsItem : public QObject QString valueStr() const; void setValue(int value); + bool useFlat() const; + void setUseFlat(bool use); + signals: void showChanged(); void numberChanged(); @@ -130,6 +134,7 @@ class StringTuningsItem : public QObject bool m_show = false; QString m_number; int m_value = 0; + bool m_useFlat = false; }; } //namespace mu::notation From 7ddf7791819bbe623847f1680859a474fcfc82ce Mon Sep 17 00:00:00 2001 From: Eism Date: Tue, 28 Nov 2023 16:54:18 +0200 Subject: [PATCH 10/29] Added Dropped D preset --- share/instruments/string_tunings_presets.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/share/instruments/string_tunings_presets.json b/share/instruments/string_tunings_presets.json index eaf61dda14836..b6f5e6bd2dd3e 100644 --- a/share/instruments/string_tunings_presets.json +++ b/share/instruments/string_tunings_presets.json @@ -38,6 +38,10 @@ "name": "Tune down 2 step", "value": [36, 41, 46, 51, 55, 60], "useFlats": true + },{ + "name": "Dropped D", + "value": [38, 45, 50, 55, 59, 64], + "useFlats": true },{ "name": "Dropped D tune down 1/2 step", "value": [37, 44, 49, 54, 58, 63], From ccc1c3cb5c921442f83c60a7c613c761634f45ad Mon Sep 17 00:00:00 2001 From: James Date: Fri, 17 Nov 2023 09:11:27 +0000 Subject: [PATCH 11/29] Update chord direction under slur --- src/engraving/dom/slur.cpp | 44 +++----------- src/engraving/rendering/dev/slurtielayout.cpp | 55 +++++++++++++++++- src/engraving/rendering/dev/slurtielayout.h | 2 + vtest/scores/slurs-25.mscz | Bin 0 -> 24031 bytes 4 files changed, 62 insertions(+), 39 deletions(-) create mode 100644 vtest/scores/slurs-25.mscz diff --git a/src/engraving/dom/slur.cpp b/src/engraving/dom/slur.cpp index b6383964a5cc2..cab0c122c1ac9 100644 --- a/src/engraving/dom/slur.cpp +++ b/src/engraving/dom/slur.cpp @@ -338,8 +338,12 @@ Shape SlurSegment::getSegmentShape(Segment* seg, ChordRest* startCR, ChordRest* if (!e || !e->isChordRest()) { continue; } - // Gets ties shapes + // Gets tie and 2 note tremolo shapes if (e->isChord()) { + Chord* chord = toChord(e); + if (chord->tremolo() && chord->tremolo()->twoNotes()) { + segShape.add(chord->tremolo()->shape()); + } for (Note* note : toChord(e)->notes()) { Tie* tieFor = note->tieFor(); Tie* tieBack = note->tieBack(); @@ -913,41 +917,9 @@ int Slur::calcStemArrangement(EngravingItem* start, EngravingItem* end) bool Slur::isDirectionMixture(Chord* c1, Chord* c2) { - if (c1->track() != c2->track()) { - return false; - } - bool up = c1->up(); - if (c2->isGrace() && c2->up() != up) { - return true; - } - if (c1->isGraceBefore() && c2->isGraceAfter() && c1->parentItem() == c2->parentItem()) { - if (toChord(c1->parentItem())->stem() && toChord(c1->parentItem())->up() != up) { - return true; - } - } - track_idx_t track = c1->track(); - for (Measure* m = c1->measure(); m; m = m->nextMeasure()) { - for (Segment* seg = m->first(); seg; seg = seg->next(SegmentType::ChordRest)) { - if (!seg || seg->tick() < c1->tick() || !seg->isChordRestType()) { - continue; - } - if (seg->tick() > c2->tick()) { - return false; - } - if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() >= c2->tick()) { - // if slur ends at a grace-note-before, we don't need to look at the main note - return false; - } - EngravingItem* e = seg->element(track); - if (!e || !e->isChord()) { - continue; - } - Chord* c = toChord(e); - if (c->up() != up) { - return true; - } - } - } + UNUSED(c1); + UNUSED(c2); + UNREACHABLE; return false; } diff --git a/src/engraving/rendering/dev/slurtielayout.cpp b/src/engraving/rendering/dev/slurtielayout.cpp index 5dc051ea603e3..d671ec157fc74 100644 --- a/src/engraving/rendering/dev/slurtielayout.cpp +++ b/src/engraving/rendering/dev/slurtielayout.cpp @@ -118,7 +118,7 @@ void SlurTieLayout::layout(Slur* item, LayoutContext& ctx) item->setUp(!(item->startCR()->up())); } - if (c1 && c2 && Slur::isDirectionMixture(c1, c2) && (c1->noteType() == NoteType::NORMAL)) { + if (c1 && c2 && isDirectionMixture(c1, c2, ctx) && (c1->noteType() == NoteType::NORMAL)) { // slurs go above if start and end note have different stem directions, // but grace notes are exceptions item->setUp(true); @@ -1741,11 +1741,11 @@ void SlurTieLayout::computeUp(Slur* slur, LayoutContext& ctx) } else { slur->setUp(true); } - } else if (chord1 && chord2 && !chord1->isGrace() && slur->isDirectionMixture(chord1, chord2)) { + } else if (chord1 && chord2 && !chord1->isGrace() && isDirectionMixture(chord1, chord2, ctx)) { // slurs go above if there are mixed direction stems between c1 and c2 // but grace notes are exceptions slur->setUp(true); - } else if (chord1 && chord2 && chord1->isGrace() && chord2 != chord1->parent() && slur->isDirectionMixture(chord1, chord2)) { + } else if (chord1 && chord2 && chord1->isGrace() && chord2 != chord1->parent() && isDirectionMixture(chord1, chord2, ctx)) { slur->setUp(true); } } @@ -1767,6 +1767,55 @@ double SlurTieLayout::defaultStemLengthEnd(Tremolo* tremolo) tremolo->chord2()->defaultStemLength()).second; } +bool SlurTieLayout::isDirectionMixture(Chord* c1, Chord* c2, LayoutContext& ctx) +{ + if (c1->track() != c2->track()) { + return false; + } + bool up = c1->up(); + if (c2->isGrace() && c2->up() != up) { + return true; + } + if (c1->isGraceBefore() && c2->isGraceAfter() && c1->parentItem() == c2->parentItem()) { + if (toChord(c1->parentItem())->stem() && toChord(c1->parentItem())->up() != up) { + return true; + } + } + track_idx_t track = c1->track(); + for (Measure* m = c1->measure(); m; m = m->nextMeasure()) { + for (Segment* seg = m->first(); seg; seg = seg->next(SegmentType::ChordRest)) { + if (!seg || seg->tick() < c1->tick() || !seg->isChordRestType()) { + continue; + } + if (seg->tick() > c2->tick()) { + return false; + } + if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() >= c2->tick()) { + // if slur ends at a grace-note-before, we don't need to look at the main note + return false; + } + EngravingItem* e = seg->element(track); + if (!e || !e->isChord()) { + continue; + } + Chord* c = toChord(e); + + if (!c->staff()->isDrumStaff(c->tick()) && c1->measure()->system() != m->system()) { + // This chord is on a different system and may not have been laid out yet + for (Note* note : c->notes()) { + note->updateLine(); // because chord direction is based on note lines + } + ChordLayout::computeUp(c, ctx); + } + + if (c->up() != up) { + return true; + } + } + } + return false; +} + void SlurTieLayout::layoutSegment(SlurSegment* item, LayoutContext& ctx, const PointF& p1, const PointF& p2) { SlurSegment::LayoutData* ldata = item->mutldata(); diff --git a/src/engraving/rendering/dev/slurtielayout.h b/src/engraving/rendering/dev/slurtielayout.h index 986786e4b42c9..39c171abe5d88 100644 --- a/src/engraving/rendering/dev/slurtielayout.h +++ b/src/engraving/rendering/dev/slurtielayout.h @@ -74,6 +74,8 @@ class SlurTieLayout static double defaultStemLengthStart(Tremolo* tremolo); static double defaultStemLengthEnd(Tremolo* tremolo); + static bool isDirectionMixture(Chord* c1, Chord* c2, LayoutContext& ctx); + static void layoutSegment(SlurSegment* item, LayoutContext& ctx, const PointF& p1, const PointF& p2); }; } diff --git a/vtest/scores/slurs-25.mscz b/vtest/scores/slurs-25.mscz new file mode 100644 index 0000000000000000000000000000000000000000..89c1b9277a414fc1c681821ddba5ba255b209f29 GIT binary patch literal 24031 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 literal 0 HcmV?d00001 From 99ff5240b116dcc7fa2ff82193a1cb813e9d89c1 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 24 Nov 2023 09:52:40 +0000 Subject: [PATCH 12/29] Re-layout whole spanner when change made to 1 segment --- src/engraving/dom/score.cpp | 15 +++++- src/engraving/rendering/dev/slurtielayout.cpp | 52 ++++++++----------- src/engraving/rendering/dev/slurtielayout.h | 2 +- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index c987c7e654299..6d26b444201a8 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -5597,6 +5597,19 @@ void Score::doLayoutRange(const Fraction& st, const Fraction& et) { TRACEFUNC; + Fraction start = st; + Fraction end = et; + + auto spanners = score()->spannerMap().findOverlapping(st.ticks(), et.ticks()); + for (auto interval : spanners) { + Spanner* spanner = interval.value; + if (!spanner->staff()->visible()) { + continue; + } + start = std::min(st, spanner->tick()); + end = std::max(et, spanner->tick2()); + } + m_engravingFont = engravingFonts()->fontByName(style().value(Sid::MusicalSymbolFont).value().toStdString()); m_layoutOptions.noteHeadWidth = m_engravingFont->width(SymId::noteheadBlack, style().spatium() / SPATIUM20); @@ -5610,7 +5623,7 @@ void Score::doLayoutRange(const Fraction& st, const Fraction& et) this->updateVelo(); } - renderer()->layoutScore(this, st, et); + renderer()->layoutScore(this, start, end); if (m_resetAutoplace) { m_resetAutoplace = false; diff --git a/src/engraving/rendering/dev/slurtielayout.cpp b/src/engraving/rendering/dev/slurtielayout.cpp index d671ec157fc74..7fcbe82ff7157 100644 --- a/src/engraving/rendering/dev/slurtielayout.cpp +++ b/src/engraving/rendering/dev/slurtielayout.cpp @@ -1767,12 +1767,12 @@ double SlurTieLayout::defaultStemLengthEnd(Tremolo* tremolo) tremolo->chord2()->defaultStemLength()).second; } -bool SlurTieLayout::isDirectionMixture(Chord* c1, Chord* c2, LayoutContext& ctx) +bool SlurTieLayout::isDirectionMixture(const Chord* c1, const Chord* c2, LayoutContext& ctx) { if (c1->track() != c2->track()) { return false; } - bool up = c1->up(); + const bool up = c1->up(); if (c2->isGrace() && c2->up() != up) { return true; } @@ -1781,36 +1781,28 @@ bool SlurTieLayout::isDirectionMixture(Chord* c1, Chord* c2, LayoutContext& ctx) return true; } } - track_idx_t track = c1->track(); - for (Measure* m = c1->measure(); m; m = m->nextMeasure()) { - for (Segment* seg = m->first(); seg; seg = seg->next(SegmentType::ChordRest)) { - if (!seg || seg->tick() < c1->tick() || !seg->isChordRestType()) { - continue; - } - if (seg->tick() > c2->tick()) { - return false; - } - if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() >= c2->tick()) { - // if slur ends at a grace-note-before, we don't need to look at the main note - return false; - } - EngravingItem* e = seg->element(track); - if (!e || !e->isChord()) { - continue; - } - Chord* c = toChord(e); - - if (!c->staff()->isDrumStaff(c->tick()) && c1->measure()->system() != m->system()) { - // This chord is on a different system and may not have been laid out yet - for (Note* note : c->notes()) { - note->updateLine(); // because chord direction is based on note lines - } - ChordLayout::computeUp(c, ctx); + const track_idx_t track = c1->track(); + for (const Segment* seg = c1->segment(); seg && seg->tick() <= c2->tick(); seg = seg->next1(SegmentType::ChordRest)) { + if ((c1->isGrace() || c2->isGraceBefore()) && seg->tick() == c2->tick()) { + // if slur ends at a grace-note-before, we don't need to look at the main note + return false; + } + EngravingItem* e = seg->element(track); + if (!e || !e->isChord()) { + continue; + } + Chord* c = toChord(e); + const Measure* m = c->measure(); + if (!c->staff()->isDrumStaff(c->tick()) && c1->measure()->system() != m->system()) { + // This chord is on a different system and may not have been laid out yet + for (Note* note : c->notes()) { + note->updateLine(); // because chord direction is based on note lines } + ChordLayout::computeUp(c, ctx); + } - if (c->up() != up) { - return true; - } + if (c->up() != up) { + return true; } } return false; diff --git a/src/engraving/rendering/dev/slurtielayout.h b/src/engraving/rendering/dev/slurtielayout.h index 39c171abe5d88..67c4c560cabb3 100644 --- a/src/engraving/rendering/dev/slurtielayout.h +++ b/src/engraving/rendering/dev/slurtielayout.h @@ -74,7 +74,7 @@ class SlurTieLayout static double defaultStemLengthStart(Tremolo* tremolo); static double defaultStemLengthEnd(Tremolo* tremolo); - static bool isDirectionMixture(Chord* c1, Chord* c2, LayoutContext& ctx); + static bool isDirectionMixture(const Chord* c1, const Chord* c2, LayoutContext& ctx); static void layoutSegment(SlurSegment* item, LayoutContext& ctx, const PointF& p1, const PointF& p2); }; From a41c40841b320a5292e2af55d7a26a80b0e03df9 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 23 Nov 2023 15:59:23 +0000 Subject: [PATCH 13/29] Add symbols to parts & mmrests --- src/engraving/dom/chordrest.cpp | 6 +----- src/engraving/dom/rest.cpp | 12 ------------ src/engraving/rendering/dev/measurelayout.cpp | 6 ++++-- vtest/scores/mmrest-10.mscz | Bin 0 -> 21113 bytes 4 files changed, 5 insertions(+), 19 deletions(-) create mode 100644 vtest/scores/mmrest-10.mscz diff --git a/src/engraving/dom/chordrest.cpp b/src/engraving/dom/chordrest.cpp index 99fb7f9448dac..cdc8499d93d02 100644 --- a/src/engraving/dom/chordrest.cpp +++ b/src/engraving/dom/chordrest.cpp @@ -241,6 +241,7 @@ EngravingItem* ChordRest::drop(EditData& data) case ElementType::FRET_DIAGRAM: case ElementType::TREMOLOBAR: case ElementType::SYMBOL: + case ElementType::IMAGE: e->setTrack(track()); e->setParent(segment()); score()->undoAddElement(e); @@ -356,11 +357,6 @@ EngravingItem* ChordRest::drop(EditData& data) return e; } - case ElementType::IMAGE: - e->setParent(segment()); - score()->undoAddElement(e); - return e; - case ElementType::ACTION_ICON: { switch (toActionIcon(e)->actionType()) { diff --git a/src/engraving/dom/rest.cpp b/src/engraving/dom/rest.cpp index 9f766d6eb767d..f00e6332f43bd 100644 --- a/src/engraving/dom/rest.cpp +++ b/src/engraving/dom/rest.cpp @@ -272,12 +272,6 @@ EngravingItem* Rest::drop(EditData& data) } break; } - case ElementType::SYMBOL: - case ElementType::IMAGE: - e->setParent(this); - score()->undoAddElement(e); - return e; - case ElementType::STRING_TUNINGS: return measure()->drop(data); @@ -790,12 +784,6 @@ void Rest::add(EngravingItem* e) break; case ElementType::DEAD_SLAPPED: m_deadSlapped = toDeadSlapped(e); - // fallthrough - case ElementType::SYMBOL: - case ElementType::IMAGE: - addEl(e); - e->added(); - break; default: ChordRest::add(e); break; diff --git a/src/engraving/rendering/dev/measurelayout.cpp b/src/engraving/rendering/dev/measurelayout.cpp index 2673f83b9e2a7..3ddcde36fe382 100644 --- a/src/engraving/rendering/dev/measurelayout.cpp +++ b/src/engraving/rendering/dev/measurelayout.cpp @@ -137,7 +137,8 @@ static const std::unordered_set BREAK_TYPES { ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, ElementType::INSTRUMENT_CHANGE, - ElementType::STRING_TUNINGS + ElementType::STRING_TUNINGS, + ElementType::SYMBOL }; static const std::unordered_set ALWAYS_BREAK_TYPES { @@ -154,7 +155,8 @@ static const std::unordered_set CONDITIONAL_BREAK_TYPES { ElementType::PLAYTECH_ANNOTATION, ElementType::CAPO, ElementType::INSTRUMENT_CHANGE, - ElementType::STRING_TUNINGS + ElementType::STRING_TUNINGS, + ElementType::SYMBOL }; //--------------------------------------------------------- diff --git a/vtest/scores/mmrest-10.mscz b/vtest/scores/mmrest-10.mscz new file mode 100644 index 0000000000000000000000000000000000000000..12161bf0a32790fcd7c499f651887bb180bd3e96 GIT binary patch literal 21113 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 Date: Fri, 24 Nov 2023 14:56:19 +0000 Subject: [PATCH 14/29] Fix reading <420 files --- src/engraving/rw/read400/tread.cpp | 10 ++++++---- src/engraving/rw/read410/tread.cpp | 14 ++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/engraving/rw/read400/tread.cpp b/src/engraving/rw/read400/tread.cpp index c1938e14b03a1..65e9effbabec6 100644 --- a/src/engraving/rw/read400/tread.cpp +++ b/src/engraving/rw/read400/tread.cpp @@ -3405,18 +3405,20 @@ void TRead::read(Rest* r, XmlReader& e, ReadContext& ctx) while (e.readNextStartElement()) { const AsciiStringView tag(e.name()); if (tag == "Symbol") { - Symbol* s = new Symbol(r); + Segment* seg = toSegment(r->parent()); + Symbol* s = new Symbol(seg); s->setTrack(r->track()); TRead::read(s, e, ctx); - r->add(s); + seg->add(s); } else if (tag == "Image") { if (MScore::noImages) { e.skipCurrentElement(); } else { - Image* image = new Image(r); + Segment* seg = toSegment(r->parent()); + Image* image = new Image(seg); image->setTrack(r->track()); TRead::read(image, e, ctx); - r->add(image); + seg->add(image); } } else if (tag == "NoteDot") { NoteDot* dot = Factory::createNoteDot(r); diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 53e28e1114356..c8a1a465e20f0 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -3517,19 +3517,21 @@ void TRead::read(Rest* r, XmlReader& e, ReadContext& ctx) { while (e.readNextStartElement()) { const AsciiStringView tag(e.name()); - if (tag == "Symbol") { - Symbol* s = new Symbol(r); + if (tag == "Symbol" && r->score()->mscVersion() < 420) { + Segment* seg = toSegment(r->parent()); + Symbol* s = new Symbol(seg); s->setTrack(r->track()); TRead::read(s, e, ctx); - r->add(s); - } else if (tag == "Image") { + seg->add(s); + } else if (tag == "Image" && r->score()->mscVersion() < 420) { if (MScore::noImages) { e.skipCurrentElement(); } else { - Image* image = new Image(r); + Segment* seg = toSegment(r->parent()); + Image* image = new Image(seg); image->setTrack(r->track()); TRead::read(image, e, ctx); - r->add(image); + seg->add(image); } } else if (tag == "NoteDot") { NoteDot* dot = Factory::createNoteDot(r); From 011f38c6d9dd9cfb1c52e66edc6fd58e34ee6e9f Mon Sep 17 00:00:00 2001 From: James Mizen Date: Fri, 1 Dec 2023 11:17:08 +0000 Subject: [PATCH 15/29] prevent local ts crash and fix paste behaviour --- src/engraving/dom/durationelement.cpp | 8 +- src/engraving/dom/durationelement.h | 1 + src/engraving/dom/paste.cpp | 18 +- src/engraving/rw/read410/read410.cpp | 2 +- .../tests/copypaste_data/copypaste27-ref.mscx | 234 ++++++++++++++++++ .../tests/copypaste_data/copypaste27.mscx | 217 ++++++++++++++++ src/engraving/tests/copypaste_tests.cpp | 5 + 7 files changed, 475 insertions(+), 10 deletions(-) create mode 100644 src/engraving/tests/copypaste_data/copypaste27-ref.mscx create mode 100644 src/engraving/tests/copypaste_data/copypaste27.mscx diff --git a/src/engraving/dom/durationelement.cpp b/src/engraving/dom/durationelement.cpp index 00bc5734b2b01..ca09d8e8714b9 100644 --- a/src/engraving/dom/durationelement.cpp +++ b/src/engraving/dom/durationelement.cpp @@ -116,9 +116,15 @@ float DurationElement::timeStretchFactor() const // actualTicks //--------------------------------------------------------- +Fraction DurationElement::actualTicksAt(const Fraction& tick) const +{ + // Use when tick() is unreliable, for example when pasting + return globalTicks() / staff()->timeStretch(tick); +} + Fraction DurationElement::actualTicks() const { - return globalTicks() / staff()->timeStretch(tick()); + return actualTicksAt(tick()); } //--------------------------------------------------------- diff --git a/src/engraving/dom/durationelement.h b/src/engraving/dom/durationelement.h index 0689a92669547..a54152fb1802d 100644 --- a/src/engraving/dom/durationelement.h +++ b/src/engraving/dom/durationelement.h @@ -53,6 +53,7 @@ class DurationElement : public EngravingItem Tuplet* topTuplet() const; virtual Beam* beam() const { return nullptr; } + Fraction actualTicksAt(const Fraction& tick) const; Fraction actualTicks() const; // Length expressed as a fraction of a whole note diff --git a/src/engraving/dom/paste.cpp b/src/engraving/dom/paste.cpp index 63faec126cb54..26ee2a2a25bd7 100644 --- a/src/engraving/dom/paste.cpp +++ b/src/engraving/dom/paste.cpp @@ -49,6 +49,7 @@ #include "sig.h" #include "staff.h" #include "tie.h" +#include "timesig.h" #include "tremolo.h" #include "tuplet.h" #include "undo.h" @@ -124,7 +125,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) transposeChord(toChord(cr), tick); if (toChord(cr)->tremolo() && toChord(cr)->tremolo()->twoNotes()) { twoNoteTremoloFactor = 2; - } else if (cr->durationTypeTicks() == (cr->actualTicks() * 2)) { + } else if (cr->durationTypeTicks() == (cr->actualTicksAt(tick) * 2)) { // this could be the 2nd note of a two-note tremolo // check previous CR on same track, if it has a two-note tremolo, then set twoNoteTremoloFactor to 2 Segment* seg = measure->undoGetSegment(SegmentType::ChordRest, tick); @@ -167,7 +168,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) if (cr->isMeasureRepeat()) { partialCopy = toMeasureRepeat(cr)->actualTicks() != measure->ticks(); } else if (!isGrace && !cr->tuplet()) { - partialCopy = cr->durationTypeTicks() != (cr->actualTicks() * twoNoteTremoloFactor); + partialCopy = cr->durationTypeTicks() != (cr->actualTicksAt(tick) * twoNoteTremoloFactor); } // if note is too long to fit in measure, split it up with a tie across the barline @@ -175,11 +176,11 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) // we have already disallowed a tuplet from crossing the barline, so there is no problem here // but due to rounding, it might appear from actualTicks() that the last note is too long by a couple of ticks - if (!isGrace && !cr->tuplet() && (tick + cr->actualTicks() > measureEnd || partialCopy || convertMeasureRest)) { + if (!isGrace && !cr->tuplet() && (tick + cr->actualTicksAt(tick) > measureEnd || partialCopy || convertMeasureRest)) { if (cr->isChord()) { // split Chord Chord* c = toChord(cr); - Fraction rest = c->actualTicks(); + Fraction rest = c->actualTicksAt(tick); bool firstpart = true; while (rest.isNotZero()) { measure = tick2measure(tick); @@ -192,9 +193,10 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) std::vector dl = toRhythmicDurationList(len, false, tick - measure->tick(), sigmap()->timesig( tick).nominal(), measure, MAX_DOTS); TDuration d = dl[0]; + Fraction c2Tick(tick + c->tick()); c2->setDurationType(d); c2->setTicks(d.fraction()); - rest -= c2->actualTicks(); + rest -= c2->actualTicksAt(c2Tick); undoAddCR(c2, measure, tick); std::vector nl1 = c->notes(); @@ -219,7 +221,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) } c = c2; firstpart = false; - tick += c->actualTicks(); + tick += c->actualTicksAt(c2Tick); } } else if (cr->isRest()) { // split Rest @@ -239,7 +241,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) r2->setTicks(d.isMeasure() ? measure->ticks() : d.fraction()); undoAddCR(r2, measure, tick); rest -= r2->ticks(); - tick += r2->actualTicks(); + tick += r2->actualTicksAt(tick); firstpart = false; } } else if (cr->isMeasureRepeat()) { @@ -260,7 +262,7 @@ void Score::pasteChordRest(ChordRest* cr, const Fraction& t) r2->setDurationType(d); undoAddCR(r2, measure, tick); rest -= d.fraction(); - tick += r2->actualTicks(); + tick += r2->actualTicksAt(tick); } delete r; } diff --git a/src/engraving/rw/read410/read410.cpp b/src/engraving/rw/read410/read410.cpp index 05c57694d7198..d0022ed590f74 100644 --- a/src/engraving/rw/read410/read410.cpp +++ b/src/engraving/rw/read410/read410.cpp @@ -465,7 +465,7 @@ bool Read410::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract if (tuplet) { cr->readAddTuplet(tuplet); } - ctx.incTick(cr->actualTicks()); + ctx.incTick(cr->actualTicksAt(tick)); if (doScale) { Fraction d = cr->durationTypeTicks(); cr->setTicks(cr->ticks() * scale); diff --git a/src/engraving/tests/copypaste_data/copypaste27-ref.mscx b/src/engraving/tests/copypaste_data/copypaste27-ref.mscx new file mode 100644 index 0000000000000..ef1d9182e481f --- /dev/null +++ b/src/engraving/tests/copypaste_data/copypaste27-ref.mscx @@ -0,0 +1,234 @@ + + + + 480 + + 1 + 1 + 1 + 0 + + Composer / arranger + + + + + + Subtitle + + + Untitled score + + Orchestral + + Keyboards + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + 1 + + + + stdNormal + + F + + Piano + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + + + + + + + + + 0 + + + 3 + 2 + 3 + 2 + + + measure + 3/2 + + + + + + + 3 + 8 + + + quarter + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + + measure + 3/8 + + + + + + + quarter + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + + + + 0 + + + 4 + 4 + + + measure + 4/4 + + + + + + + 3 + 8 + + + quarter + + 59 + 19 + + + + eighth + + 59 + 19 + + + + + + + + measure + 3/8 + + + + + + + quarter + + 59 + 19 + + + + eighth + + 59 + 19 + + + + + +
+
diff --git a/src/engraving/tests/copypaste_data/copypaste27.mscx b/src/engraving/tests/copypaste_data/copypaste27.mscx new file mode 100644 index 0000000000000..8c18d431f7c52 --- /dev/null +++ b/src/engraving/tests/copypaste_data/copypaste27.mscx @@ -0,0 +1,217 @@ + + + 4.3.0 + + + 480 + 1 + 1 + 1 + 0 + 1 + + Composer / arranger + + 2023-12-01 + + + + Apple Macintosh + + Subtitle + + + Untitled score + + Orchestral + + Keyboards + +
+ flutes + oboes + clarinets + saxophones + bassoons + +
+
+ horns + trumpets + cornets + flugelhorns + trombones + tubas + +
+
+ timpani +
+
+ keyboard-percussion + + drums + unpitched-metal-percussion + unpitched-wooden-percussion + other-percussion + +
+ keyboards + harps + organs + synths + + +
+ voices + voice-groups +
+
+ orchestral-strings +
+
+ + + + stdNormal + + + 1 + + + + stdNormal + + F + + Piano + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + + Fluid + + + + + + + + 0 + + + 3 + 2 + 3 + 2 + + + measure + 3/2 + + + + + + + 3 + 8 + + + quarter + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + + measure + 3/8 + + + + + + + measure + 3/8 + + + + + + + + + 0 + + + 4 + 4 + + + measure + 4/4 + + + + + + + 3 + 8 + + + quarter + + 59 + 19 + + + + eighth + + 59 + 19 + + + + + + + + measure + 3/8 + + + + + + + measure + 3/8 + + + + +
+
diff --git a/src/engraving/tests/copypaste_tests.cpp b/src/engraving/tests/copypaste_tests.cpp index 4a734df33ad5d..56a82f2e67a80 100644 --- a/src/engraving/tests/copypaste_tests.cpp +++ b/src/engraving/tests/copypaste_tests.cpp @@ -188,6 +188,11 @@ TEST_F(Engraving_CopyPasteTests, copypaste26) copypaste("26"); // Copy chords (#298541) } +TEST_F(Engraving_CopyPasteTests, copypaste27) +{ + copypaste("27"); // Paste after local time signature (#18940) +} + //--------------------------------------------------------- // copy measure 2 from first staff, paste into staff 2 //--------------------------------------------------------- From a57628e86c622a142f05001800568ee1d253a1f4 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 29 Nov 2023 14:54:38 +0000 Subject: [PATCH 16/29] Arpeggio crash fixes --- src/engraving/dom/arpeggio.cpp | 2 +- src/engraving/dom/chord.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engraving/dom/arpeggio.cpp b/src/engraving/dom/arpeggio.cpp index 13e2c98055b05..b566779ceb837 100644 --- a/src/engraving/dom/arpeggio.cpp +++ b/src/engraving/dom/arpeggio.cpp @@ -62,7 +62,7 @@ Arpeggio::~Arpeggio() { // Remove reference to this arpeggio in any chords it may have spanned Chord* _chord = chord(); - if (!_chord) { + if (!_chord || !_chord->segment()) { return; } for (track_idx_t _track = track(); _track <= track() + m_span; _track++) { diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index 17008d51fab0a..3ec363209584f 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -778,6 +778,9 @@ void Chord::remove(EngravingItem* e) break; case ElementType::ARPEGGIO: + if (m_spanArpeggio == m_arpeggio) { + m_spanArpeggio = 0; + } m_arpeggio = 0; break; case ElementType::TREMOLO: From 7a637385a346cca471e488a8413ef574c900cd75 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:28:42 +0100 Subject: [PATCH 17/29] Code review fix --- src/engraving/dom/chord.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index 3ec363209584f..9039976295b4b 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -779,9 +779,9 @@ void Chord::remove(EngravingItem* e) case ElementType::ARPEGGIO: if (m_spanArpeggio == m_arpeggio) { - m_spanArpeggio = 0; + m_spanArpeggio = nullptr; } - m_arpeggio = 0; + m_arpeggio = nullptr; break; case ElementType::TREMOLO: setTremolo(nullptr); From 381c7be95c52c51106f8c7a9eb8f86deebfb924d Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 28 Nov 2023 16:50:11 +0000 Subject: [PATCH 18/29] Return correct start chord for lyriclines --- src/engraving/dom/spanner.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/engraving/dom/spanner.cpp b/src/engraving/dom/spanner.cpp index 6e9194fe452d1..2f07f9ee63d73 100644 --- a/src/engraving/dom/spanner.cpp +++ b/src/engraving/dom/spanner.cpp @@ -734,14 +734,19 @@ void Spanner::computeStartElement() if (!seg || seg->empty()) { seg = score()->tick2segment(tick(), false, SegmentType::ChordRest); } - track_idx_t strack = (track() / VOICES) * VOICES; - track_idx_t etrack = strack + VOICES; - m_startElement = 0; + m_startElement = nullptr; if (seg) { - for (track_idx_t t = strack; t < etrack; ++t) { - if (seg->element(t)) { - m_startElement = seg->element(t); - break; + EngravingItem* e = seg->element(track()); + if (e) { + m_startElement = e; + } else { + track_idx_t strack = (track() / VOICES) * VOICES; + track_idx_t etrack = strack + VOICES; + for (track_idx_t t = strack; t < etrack; ++t) { + if (seg->element(t)) { + m_startElement = seg->element(t); + break; + } } } } From b59978723c786d0ba529ad49ae1f1e6e7c1437d1 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 29 Nov 2023 14:18:17 +0000 Subject: [PATCH 19/29] Remove code to merge articulations on xml import --- .../internal/musicxml/importmxmlpass2.cpp | 112 +---- .../internal/musicxml/importmxmlpass2.h | 3 - .../data/testArticulationCombination_ref.xml | 454 ++++++++++++++++++ .../musicxml/tests/data/testColors.xml | 2 +- .../tests/data/testNoteAttributes2_ref.xml | 3 +- .../musicxml/tests/musicxml_tests.cpp | 2 +- 6 files changed, 468 insertions(+), 108 deletions(-) create mode 100644 src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp index d70cbd3b93416..c8b851439cf84 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.cpp @@ -6247,9 +6247,16 @@ void MusicXMLParserNotations::articulations() while (_e.readNextStartElement()) { SymId id { SymId::noSym }; if (convertArticulationToSymId(_e.name().toString(), id)) { - Notation artic = Notation::notationWithAttributes(_e.name().toString(), - _e.attributes(), "articulations", id); - _notations.push_back(artic); + if (_e.name() == "detached-legato") { + _notations.push_back(Notation::notationWithAttributes("tenuto", + _e.attributes(), "articulations", SymId::articTenutoAbove)); + _notations.push_back(Notation::notationWithAttributes("staccato", + _e.attributes(), "articulations", SymId::articStaccatoAbove)); + } else { + Notation artic = Notation::notationWithAttributes(_e.name().toString(), + _e.attributes(), "articulations", id); + _notations.push_back(artic); + } _e.skipCurrentElement(); // skip but don't log } else if (_e.name() == "breath-mark") { auto value = _e.readElementText(); @@ -6752,35 +6759,6 @@ Notation Notation::notationWithAttributes(const QString& name, const QXmlStreamA return notation; } -//--------------------------------------------------------- -// mergeNotations -//--------------------------------------------------------- - -/** - Helper function to merge two Notations. Used to combine articulations in combineArticulations. - */ - -Notation Notation::mergeNotations(const Notation& n1, const Notation& n2, const SymId& symId) -{ - // Sort and combine the names - std::vector names{ n1.name(), n2.name() }; - std::sort(names.begin(), names.end()); - QString name = names[0] + " " + names[1]; - - // Parents should match (and will both be "articulation") - Q_ASSERT(n1.parent() == n2.parent()); - QString parent = n1.parent(); - - Notation mergedNotation{ name, parent, symId }; - for (const auto& attr : n1.attributes()) { - mergedNotation.addAttribute(attr.first, attr.second); - } - for (const auto& attr : n2.attributes()) { - mergedNotation.addAttribute(attr.first, attr.second); - } - return mergedNotation; -} - //--------------------------------------------------------- // addAttribute //--------------------------------------------------------- @@ -6872,75 +6850,6 @@ void MusicXMLParserNotations::skipLogCurrElem() _e.skipCurrentElement(); } -//--------------------------------------------------------- -// skipCombine -//--------------------------------------------------------- - -/** - Helper function to hold conditions under which a potential combine should be skipped. - */ - -bool MusicXMLParserNotations::skipCombine(const Notation& n1, const Notation& n2) -{ - // at this point, if only one placement is specified, don't combine. - // we may revisit this in the future once we have a better idea of how we want to combine - // things by default. - bool placementsSpecifiedAndDifferent = n1.attribute("placement") != n2.attribute("placement"); - bool upMarcatoDownOther = (n1.name() == "strong-accent" && n1.attribute("type") == "up" - && n2.attribute("placement") == "below") - || (n2.name() == "strong-accent" && n2.attribute("type") == "up" - && n1.attribute("placement") == "below"); - bool downMarcatoUpOther = (n1.name() == "strong-accent" && n1.attribute("type") == "down" - && n2.attribute("placement") == "above") - || (n2.name() == "strong-accent" && n2.attribute("type") == "down" - && n1.attribute("placement") == "above"); - bool slurEndpoint = _slurStart || _slurStop; - return placementsSpecifiedAndDifferent || upMarcatoDownOther || downMarcatoUpOther || slurEndpoint; -} - -//--------------------------------------------------------- -// combineArticulations -//--------------------------------------------------------- - -/** - Combine any eligible articulations. - i.e. accent + staccato = staccato accent - */ - -void MusicXMLParserNotations::combineArticulations() -{ - QMap, SymId> map; // map set of symbols to combined symbol - map[{ SymId::articAccentAbove, SymId::articStaccatoAbove }] = SymId::articAccentStaccatoAbove; - map[{ SymId::articMarcatoAbove, SymId::articStaccatoAbove }] = SymId::articMarcatoStaccatoAbove; - map[{ SymId::articMarcatoAbove, SymId::articTenutoAbove }] = SymId::articMarcatoTenutoAbove; - map[{ SymId::articAccentAbove, SymId::articTenutoAbove }] = SymId::articTenutoAccentAbove; - map[{ SymId::articSoftAccentAbove, SymId::articStaccatoAbove }] = SymId::articSoftAccentStaccatoAbove; - map[{ SymId::articSoftAccentAbove, SymId::articTenutoAbove }] = SymId::articSoftAccentTenutoAbove; - map[{ SymId::articSoftAccentAbove, SymId::articTenutoStaccatoAbove }] = SymId::articSoftAccentTenutoStaccatoAbove; - - // Iterate through each distinct pair (backwards, to allow for deletions) - for (std::vector::reverse_iterator n1 = _notations.rbegin(), n1Next = n1; n1 != _notations.rend(); n1 = n1Next) { - n1Next = std::next(n1); - if (n1->parent() != "articulations") { - continue; - } - for (std::vector::reverse_iterator n2 = n1 + 1, n2Next = n1; n2 != _notations.rend(); n2 = n2Next) { - n2Next = std::next(n2); - if (n2->parent() != "articulations" || skipCombine(*n1, *n2)) { - continue; - } - // Combine and remove articulations if present in map - std::set currentPair = { n1->symId(), n2->symId() }; - if (map.contains(currentPair)) { - Notation mergedNotation = Notation::mergeNotations(*n1, *n2, map.value(currentPair)); - n1Next = decltype(n1){ _notations.erase(std::next(n1).base()) }; - n2Next = decltype(n2){ _notations.erase(std::next(n2).base()) }; - _notations.push_back(mergedNotation); - } - } - } -} - //--------------------------------------------------------- // parse //--------------------------------------------------------- @@ -6987,7 +6896,6 @@ void MusicXMLParserNotations::parse() LOGD("%s", qPrintable(notation.print())); } */ - combineArticulations(); addError(checkAtEndElement(_e, "notations")); } diff --git a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h index c5e9eb436bea6..8323e9c2c17ed 100644 --- a/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h +++ b/src/importexport/musicxml/internal/musicxml/importmxmlpass2.h @@ -170,7 +170,6 @@ class Notation QString text() const { return _text; } static Notation notationWithAttributes(const QString& name, const QXmlStreamAttributes attributes, const QString& parent = "", const SymId& symId = SymId::noSym); - static Notation mergeNotations(const Notation& n1, const Notation& n2, const SymId& symId = SymId::noSym); private: QString _name; QString _parent; @@ -217,12 +216,10 @@ class MusicXMLParserNotations QString tremoloType() const { return _tremoloType; } int tremoloNr() const { return _tremoloNr; } bool mustStopGraceAFter() const { return _slurStop || _wavyLineStop; } - bool skipCombine(const Notation& n1, const Notation& n2); private: void addError(const QString& error); ///< Add an error to be shown in the GUI void addNotation(const Notation& notation, ChordRest* const cr, Note* const note); void addTechnical(const Notation& notation, Note* note); - void combineArticulations(); void harmonic(); void articulations(); void dynamics(); diff --git a/src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml b/src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml new file mode 100644 index 0000000000000..2ef77d032a465 --- /dev/null +++ b/src/importexport/musicxml/tests/data/testArticulationCombination_ref.xml @@ -0,0 +1,454 @@ + + + + + Title + + + Composer + + MuseScore 0.7.0 + 2007-09-10 + + + + + + + + + + Piano + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + 1 + + 0 + + + + G + 2 + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + D + 5 + + 1 + 1 + quarter + down + + + + + + + + + + E + 5 + + 1 + 1 + quarter + down + + + + + + + + + + F + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + + G + 5 + + 1 + 1 + quarter + down + + + + + + + + + F + 5 + + 1 + 1 + quarter + down + + + + + + + + + + E + 5 + + 1 + 1 + quarter + down + + + + + + + + + + D + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + + + + A + 4 + + 1 + 1 + quarter + up + + + + + + + + + + G + 4 + + 1 + 1 + quarter + up + + + + + + + + + + F + 4 + + 1 + 1 + quarter + up + + + + + + + + + + E + 4 + + 1 + 1 + quarter + up + + + + + + + + + + + + D + 4 + + 1 + 1 + quarter + up + + + + + + + + + E + 4 + + 1 + 1 + quarter + up + + + + + + + + + + F + 4 + + 1 + 1 + quarter + up + + + + + + + + + + G + 4 + + 1 + 1 + quarter + up + + + + + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + + C + 5 + + 1 + 1 + quarter + down + + + + + + + + + + light-heavy + + + + diff --git a/src/importexport/musicxml/tests/data/testColors.xml b/src/importexport/musicxml/tests/data/testColors.xml index 90cc1a5ee7610..da020898bdb66 100644 --- a/src/importexport/musicxml/tests/data/testColors.xml +++ b/src/importexport/musicxml/tests/data/testColors.xml @@ -72,8 +72,8 @@ down - + diff --git a/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml b/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml index b2ec46a6823ac..3c04cc65c868f 100644 --- a/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml +++ b/src/importexport/musicxml/tests/data/testNoteAttributes2_ref.xml @@ -448,7 +448,8 @@ up - + + diff --git a/src/importexport/musicxml/tests/musicxml_tests.cpp b/src/importexport/musicxml/tests/musicxml_tests.cpp index cf85348dd0809..1d7b33e93def0 100644 --- a/src/importexport/musicxml/tests/musicxml_tests.cpp +++ b/src/importexport/musicxml/tests/musicxml_tests.cpp @@ -378,7 +378,7 @@ TEST_F(Musicxml_Tests, arpGliss3) { mxmlIoTest("testArpGliss3"); } TEST_F(Musicxml_Tests, articulationCombination) { - mxmlIoTest("testArticulationCombination"); + mxmlIoTestRef("testArticulationCombination"); } TEST_F(Musicxml_Tests, backupRoundingError) { mxmlImportTestRef("testBackupRoundingError"); From 30702d9325a69f624b4a1287c0cb96783deebe77 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:54:30 +0100 Subject: [PATCH 20/29] Fix undo editing chord symbol text Basically reverts a particular change from c35963f27dc6338b9cd49117145ccb6274c2b227, but in a slightly different way, to make clearer why the old way was correct. --- src/engraving/dom/textbase.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engraving/dom/textbase.cpp b/src/engraving/dom/textbase.cpp index 9809d6a603996..fe8d11ac01182 100644 --- a/src/engraving/dom/textbase.cpp +++ b/src/engraving/dom/textbase.cpp @@ -3422,7 +3422,14 @@ void TextBase::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags } } - if (propertyGroup(id) != PropertyGroup::TEXT) { + static const PropertyIdSet CHARACTER_SPECIFIC_PROPERTIES { + Pid::FONT_STYLE, + Pid::FONT_FACE, + Pid::FONT_SIZE, + Pid::TEXT_SCRIPT_ALIGN + }; + + if (!mu::contains(CHARACTER_SPECIFIC_PROPERTIES, id)) { EngravingItem::undoChangeProperty(id, v, ps); return; } From 160a875b4b44e51137b0be05fa0ce3745760e593 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Fri, 24 Nov 2023 20:24:03 +0100 Subject: [PATCH 21/29] =?UTF-8?q?Fix=20crash=20when=20creating=20parts=20w?= =?UTF-8?q?hen=20bend=20is=20present=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …on non-first staff of full score The `setTrack` calls were crucial to fix the crash; I've added the rest "just to be sure" and "for consistency". --- src/engraving/dom/excerpt.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engraving/dom/excerpt.cpp b/src/engraving/dom/excerpt.cpp index 4336d81ec4f79..8980b1d879f3b 100644 --- a/src/engraving/dom/excerpt.cpp +++ b/src/engraving/dom/excerpt.cpp @@ -1522,6 +1522,9 @@ void Excerpt::cloneStaff2(Staff* srcStaff, Staff* dstStaff, const Fraction& star if (bendBack && newStartNote) { GuitarBend* newBend = toGuitarBend(bendBack->linkedClone()); newBend->setScore(score); + newBend->setParent(newStartNote); + newBend->setTrack(newStartNote->track()); + newBend->setTrack2(nn->track()); newBend->setStartElement(newStartNote); newBend->setEndElement(nn); newStartNote->addSpannerFor(newBend); @@ -1532,6 +1535,9 @@ void Excerpt::cloneStaff2(Staff* srcStaff, Staff* dstStaff, const Fraction& star // Because slight bends aren't detected as "bendBack" GuitarBend* newBend = toGuitarBend(bendFor->linkedClone()); newBend->setScore(score); + newBend->setParent(nn); + newBend->setTrack(nn->track()); + newBend->setTrack2(nn->track()); newBend->setStartElement(nn); newBend->setEndElement(nn); nn->addSpannerFor(newBend); From 82c2eb69ad3048999fac8edafd3fef9acbb6e748 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:34:20 +0100 Subject: [PATCH 22/29] For URLs specified on command line, assume local files by default Otherwise, myscore.mscz would be interpreted as `https://myscore.mscz/`. --- src/app/commandlineparser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/commandlineparser.cpp b/src/app/commandlineparser.cpp index 66f67626577b6..3df6dc40f7039 100644 --- a/src/app/commandlineparser.cpp +++ b/src/app/commandlineparser.cpp @@ -21,6 +21,8 @@ */ #include "commandlineparser.h" +#include + #include "global/io/dir.h" #include "global/muversion.h" @@ -471,7 +473,7 @@ void CommandLineParser::parse(int argc, char** argv) // Startup if (m_runMode == IApplication::RunMode::GuiApp) { if (!scorefiles.isEmpty()) { - m_options.startup.scoreUrl = QUrl::fromUserInput(scorefiles[0]); + m_options.startup.scoreUrl = QUrl::fromUserInput(scorefiles[0], QDir::currentPath(), QUrl::AssumeLocalFile); } if (m_parser.isSet("score-display-name-override")) { From dcb1fbbeaad621e0bd0af95842b02bf8bb15b2a3 Mon Sep 17 00:00:00 2001 From: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> Date: Sun, 26 Nov 2023 14:51:15 +0100 Subject: [PATCH 23/29] Fix crash when opening score where arpeggio spans to a Rest Resolves: https://github.com/musescore/MuseScore/issues/20203 In the score in this issue, there is an Arpeggio with a staff span of 2, which in 4.2 gets converted to a track span of 5. That's fine, but on the staff below the Arpeggio, there is a Rest, so the Arpeggio would end on that Rest. That leads to a crash because of a missing `isChord` check. --- src/engraving/rendering/dev/arpeggiolayout.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/engraving/rendering/dev/arpeggiolayout.cpp b/src/engraving/rendering/dev/arpeggiolayout.cpp index 9cef711c70a91..33ad39a4d903a 100644 --- a/src/engraving/rendering/dev/arpeggiolayout.cpp +++ b/src/engraving/rendering/dev/arpeggiolayout.cpp @@ -123,10 +123,9 @@ void ArpeggioLayout::clearAccidentals(Arpeggio* item, LayoutContext& ctx) } const Segment* seg = item->chord()->segment(); - const Chord* endChord = toChord(seg->elementAt(item->endTrack())); - if (!endChord) { - endChord = item->chord(); - } + const EngravingItem* endEl = seg->elementAt(item->endTrack()); + const Chord* endChord = endEl && endEl->isChord() ? toChord(endEl) : item->chord(); + const Part* part = item->part(); const track_idx_t partStartTrack = part->startTrack(); const track_idx_t partEndTrack = part->endTrack(); From 09d51f3f10b6930b121f635d0ffda65bfab40137 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 27 Nov 2023 14:22:59 +0000 Subject: [PATCH 24/29] Use undoChangeParent on arpeggios --- src/engraving/dom/arpeggio.cpp | 6 +++--- src/engraving/dom/chord.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engraving/dom/arpeggio.cpp b/src/engraving/dom/arpeggio.cpp index b566779ceb837..d01145eaa64be 100644 --- a/src/engraving/dom/arpeggio.cpp +++ b/src/engraving/dom/arpeggio.cpp @@ -121,7 +121,7 @@ void Arpeggio::rebaseStartAnchor(AnchorRebaseDirection direction) track_idx_t newSpan = m_span + track() - curTrack; if (newSpan != 0) { undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); - score()->undo(new ChangeParent(this, e, e->staffIdx())); + score()->undoChangeParent(this, e, e->staffIdx()); break; } } @@ -136,7 +136,7 @@ void Arpeggio::rebaseStartAnchor(AnchorRebaseDirection direction) if (newSpan != 0) { chord()->undoChangeSpanArpeggio(nullptr); undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); - score()->undo(new ChangeParent(this, e, e->staffIdx())); + score()->undoChangeParent(this, e, e->staffIdx()); break; } } @@ -217,7 +217,7 @@ void Arpeggio::editDrag(EditData& ed) detachFromChords(track(), c->track() - 1); } undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); - score()->undo(new ChangeParent(this, c, c->staffIdx())); + score()->undoChangeParent(this, c, c->staffIdx()); m_userLen1 = 0.0; } } diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index 9039976295b4b..02c8af92820ed 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -1820,7 +1820,7 @@ void Chord::undoChangeSpanArpeggio(Arpeggio* a) } Chord* chord = toChord(linkedObject); Score* score = chord->score(); - EngravingItem* linkedArp = a->findLinkedInScore(score); + EngravingItem* linkedArp = chord->spanArpeggio(); if (score && linkedArp) { score->undo(new ChangeSpanArpeggio(chord, toArpeggio(linkedArp))); } From 2ec3574def3aee742fe308dc9330756da090e99f Mon Sep 17 00:00:00 2001 From: James Date: Mon, 27 Nov 2023 09:45:05 +0000 Subject: [PATCH 25/29] Correct arpeggio span on layout --- src/engraving/dom/arpeggio.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engraving/dom/arpeggio.cpp b/src/engraving/dom/arpeggio.cpp index d01145eaa64be..532ec0a24bd52 100644 --- a/src/engraving/dom/arpeggio.cpp +++ b/src/engraving/dom/arpeggio.cpp @@ -83,13 +83,20 @@ void Arpeggio::findAndAttachToChords() Chord* _chord = chord(); track_idx_t strack = track(); track_idx_t etrack = track() + (m_span - 1); + track_idx_t lastTrack = strack; for (track_idx_t track = strack; track <= etrack; track++) { EngravingItem* e = _chord->segment()->element(track); if (e && e->isChord()) { toChord(e)->undoChangeSpanArpeggio(this); + lastTrack = track; } } + + if (lastTrack != etrack) { + int newSpan = lastTrack - track() + 1; + undoChangeProperty(Pid::ARPEGGIO_SPAN, newSpan); + } } void Arpeggio::detachFromChords(track_idx_t strack, track_idx_t etrack) From eb53336c042b57d842cc742477e53b74ab2bdb7d Mon Sep 17 00:00:00 2001 From: James Date: Mon, 27 Nov 2023 13:43:45 +0000 Subject: [PATCH 26/29] Fix mei import --- src/importexport/mei/internal/meiexporter.cpp | 2 +- src/importexport/mei/internal/meiimporter.cpp | 2 +- src/importexport/mei/tests/data/arpeg-01.mscx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/importexport/mei/internal/meiexporter.cpp b/src/importexport/mei/internal/meiexporter.cpp index f4302ee3b840c..1c9a35846c4ac 100644 --- a/src/importexport/mei/internal/meiexporter.cpp +++ b/src/importexport/mei/internal/meiexporter.cpp @@ -2055,7 +2055,7 @@ void MeiExporter::fillControlEventMap(const std::string& xmlId, const ChordRest* // The arpeggio is spanning to a lower staff if (arpeggio->span() > 1) { // We need to retrieve the chord it is spanning to - track_idx_t bottomTrack = arpeggio->track() + (arpeggio->span() - 1) * VOICES; + track_idx_t bottomTrack = arpeggio->track() + (arpeggio->span() - 1); const EngravingItem* element = chord->segment()->element(bottomTrack); // We do not know the xml:id of the chord yet, keep it in a map if (element && element->isChord()) { diff --git a/src/importexport/mei/internal/meiimporter.cpp b/src/importexport/mei/internal/meiimporter.cpp index b7c44d3c1aad1..abcf779530566 100644 --- a/src/importexport/mei/internal/meiimporter.cpp +++ b/src/importexport/mei/internal/meiimporter.cpp @@ -3112,7 +3112,7 @@ void MeiImporter::addSpannerEnds() // Go through the list of chord rest and check if they are on a staff below for (auto chordRest : plistChordRests) { Arpeggio* arpeggio = arpegMapEntry.first; - int span = static_cast(chordRest->staffIdx() - arpeggio->staffIdx()) + 1; + int span = static_cast(chordRest->track() - arpeggio->track()) + 1; // Adjust the span if it is currently smaller if (arpeggio->span() < span) { arpeggio->setSpan(span); diff --git a/src/importexport/mei/tests/data/arpeg-01.mscx b/src/importexport/mei/tests/data/arpeg-01.mscx index 289abc8d4ffef..63098d80dccd8 100644 --- a/src/importexport/mei/tests/data/arpeg-01.mscx +++ b/src/importexport/mei/tests/data/arpeg-01.mscx @@ -126,7 +126,7 @@ 0 - 2 + 5
@@ -145,7 +145,7 @@ 1 - 2 + 5 @@ -164,7 +164,7 @@ 3 - 2 + 5 From 49230318afe05b4aae24b8a10b5884fe05646199 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Fri, 1 Dec 2023 11:08:07 +0100 Subject: [PATCH 27/29] Fix barlines disappearing on first measure of next page during layout --- src/engraving/rendering/dev/pagelayout.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/engraving/rendering/dev/pagelayout.cpp b/src/engraving/rendering/dev/pagelayout.cpp index 6c4a87f3e3beb..9d6a868b3194b 100644 --- a/src/engraving/rendering/dev/pagelayout.cpp +++ b/src/engraving/rendering/dev/pagelayout.cpp @@ -359,6 +359,23 @@ void PageLayout::collectPage(LayoutContext& ctx) } } + // If this is the last page we layout, we must also relayout the first barlines of the + // next page, because they may have been altered while collecting the systems. + MeasureBase* lastOfThisPage = ctx.mutState().page()->systems().back()->measures().back(); + MeasureBase* firstOfNextPage = lastOfThisPage ? lastOfThisPage->next() : nullptr; + if (firstOfNextPage && firstOfNextPage->isMeasure() && firstOfNextPage->tick() > ctx.state().endTick()) { + for (Segment& segment : toMeasure(firstOfNextPage)->segments()) { + if (!segment.isType(SegmentType::BarLineType)) { + continue; + } + for (EngravingItem* item : segment.elist()) { + if (item && item->isBarLine()) { + rendering::dev::TLayout::layoutBarLine2(toBarLine(item), ctx); + } + } + } + } + if (ctx.conf().isMode(LayoutMode::SYSTEM)) { const System* s = ctx.state().page()->systems().back(); double height = s ? s->pos().y() + s->height() + s->minBottom() : ctx.state().page()->tm(); From 7f857f4416c7cf74f7a18ea30933473491583567 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Wed, 29 Nov 2023 10:08:01 +0100 Subject: [PATCH 28/29] Allow up-down edit on the ending note of bend in TAB staves --- src/engraving/dom/cmd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index 07447d7385475..1beff50f6acfd 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -1910,7 +1910,7 @@ void Score::upDown(bool up, UpDownMode mode) } // update pitch and tpc's and check it matches stringData upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2); - if (newPitch != stringData->getPitch(string, fret, staff)) { + if (newPitch != stringData->getPitch(string, fret, staff) && !oNote->bendBack()) { // oh-oh: something went very wrong! LOGD("upDown tab in-string: pitch mismatch"); return; From ea9533b5a69a3d73765b031844e9606617314d7e Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Wed, 29 Nov 2023 13:27:55 +0100 Subject: [PATCH 29/29] Allow up-down edit in TAB with ottava lines --- src/engraving/dom/cmd.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engraving/dom/cmd.cpp b/src/engraving/dom/cmd.cpp index 1beff50f6acfd..a774c922fa3d3 100644 --- a/src/engraving/dom/cmd.cpp +++ b/src/engraving/dom/cmd.cpp @@ -1847,6 +1847,7 @@ void Score::upDown(bool up, UpDownMode mode) int tpc1 = oNote->tpc1(); int tpc2 = oNote->tpc2(); int pitch = oNote->pitch(); + int pitchOffset = staff->pitchOffset(tick); int newTpc1 = tpc1; // default to unchanged int newTpc2 = tpc2; // default to unchanged int newPitch = pitch; // default to unchanged @@ -1883,7 +1884,7 @@ void Score::upDown(bool up, UpDownMode mode) return; // no next string to move to } string = stt->visualStringToPhys(string); - fret = stringData->fret(pitch, string, staff); + fret = stringData->fret(pitch + pitchOffset, string, staff); if (fret == -1) { // can't have that note on that string return; } @@ -1910,7 +1911,7 @@ void Score::upDown(bool up, UpDownMode mode) } // update pitch and tpc's and check it matches stringData upDownChromatic(up, pitch, oNote, key, tpc1, tpc2, newPitch, newTpc1, newTpc2); - if (newPitch != stringData->getPitch(string, fret, staff) && !oNote->bendBack()) { + if (newPitch + pitchOffset != stringData->getPitch(string, fret, staff) && !oNote->bendBack()) { // oh-oh: something went very wrong! LOGD("upDown tab in-string: pitch mismatch"); return;