From 7091c042bd7b89d66f0ce61ccde4286d1f73e801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20B=C3=BChler?= Date: Mon, 3 Nov 2025 22:58:46 +0100 Subject: [PATCH 01/14] fw/ui: Save custom colors of QColorDialog in settings Resolves: #30273 Save & restore the custom colors of the `QColorDialog`. Qt versions before 6.9 saved these per user in a `QSettings` instance under the organization name "QtProject". This functionality must be replaced by client code (us). Legacy values are read and used as default values. --- src/framework/ui/internal/uiconfiguration.cpp | 50 ++++++++++++++++++- src/framework/ui/internal/uiconfiguration.h | 3 ++ src/framework/ui/iuiconfiguration.h | 5 ++ .../ui/tests/mocks/uiconfigurationmock.h | 3 ++ src/framework/ui/view/interactiveprovider.cpp | 24 +++++++++ src/framework/ui/view/interactiveprovider.h | 7 ++- 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/framework/ui/internal/uiconfiguration.cpp b/src/framework/ui/internal/uiconfiguration.cpp index 92cdfce04d478..446388565bfd7 100644 --- a/src/framework/ui/internal/uiconfiguration.cpp +++ b/src/framework/ui/internal/uiconfiguration.cpp @@ -26,11 +26,12 @@ #include "settings.h" #include "themeconverter.h" -#include #include +#include #include #include -#include +#include +#include #ifdef Q_OS_WIN #include @@ -40,12 +41,14 @@ #include "log.h" +using namespace Qt::Literals; using namespace muse; using namespace muse::ui; using namespace muse::async; static const Settings::Key UI_THEMES_KEY("ui", "ui/application/themes"); static const Settings::Key UI_CURRENT_THEME_CODE_KEY("ui", "ui/application/currentThemeCode"); +static const Settings::Key UI_CUSTOM_COLORS_KEY("ui", "ui/application/customColors"); static const Settings::Key UI_FOLLOW_SYSTEM_THEME_KEY("ui", "ui/application/followSystemTheme"); static const Settings::Key UI_FONT_FAMILY_KEY("ui", "ui/theme/fontFamily"); static const Settings::Key UI_FONT_SIZE_KEY("ui", "ui/theme/fontSize"); @@ -65,11 +68,30 @@ static const int FLICKABLE_MAX_VELOCITY = 1500; static const int TOOLTIP_DELAY = 500; +// read custom colors saved by Qt < 6.9 +// see: https://github.com/qt/qtbase/blob/v6.2.4/src/gui/kernel/qplatformdialoghelper.cpp#L292-L302 +static std::vector readLegacyCustomColors() +{ + constexpr size_t customColorCount = 16; + + QSettings settings(QSettings::UserScope, u"QtProject"_s); + std::vector legacyValues(customColorCount, Val(QColorConstants::White)); + for (size_t i = 0; i < customColorCount; ++i) { + const QVariant value = settings.value(u"Qt/customColors/"_s + QString::number(i)); + if (value.isValid()) { + legacyValues[i] = Val(QColor::fromRgb(value.toUInt())); + } + } + + return legacyValues; +} + void UiConfiguration::init() { m_config = ConfigReader::read(":/configs/ui.cfg"); settings()->setDefaultValue(UI_CURRENT_THEME_CODE_KEY, Val(LIGHT_THEME_CODE)); + settings()->setDefaultValue(UI_CUSTOM_COLORS_KEY, Val(readLegacyCustomColors())); settings()->setDefaultValue(UI_FOLLOW_SYSTEM_THEME_KEY, Val(false)); settings()->setDefaultValue(UI_FONT_FAMILY_KEY, Val(defaultFontFamily())); settings()->setDefaultValue(UI_FONT_SIZE_KEY, Val(defaultFontSize())); @@ -862,3 +884,27 @@ int UiConfiguration::tooltipDelay() const { return TOOLTIP_DELAY; } + +std::vector UiConfiguration::colorDialogCustomColors() const +{ + const ValList colorVals = settings()->value(UI_CUSTOM_COLORS_KEY).toList(); + + std::vector customColors; + customColors.reserve(colorVals.size()); + for (const auto& colorVal : colorVals) { + customColors.push_back(colorVal.toQColor()); + } + + return customColors; +} + +void UiConfiguration::setColorDialogCustomColors(const std::vector& customColors) +{ + ValList colorVals; + colorVals.reserve(customColors.size()); + for (const auto& color: customColors) { + colorVals.emplace_back(color); + } + + settings()->setLocalValue(UI_CUSTOM_COLORS_KEY, Val(colorVals)); +} diff --git a/src/framework/ui/internal/uiconfiguration.h b/src/framework/ui/internal/uiconfiguration.h index c9d53a81fdb90..e1da7a5a6678e 100644 --- a/src/framework/ui/internal/uiconfiguration.h +++ b/src/framework/ui/internal/uiconfiguration.h @@ -130,6 +130,9 @@ class UiConfiguration : public IUiConfiguration, public Injectable, public async int tooltipDelay() const override; + std::vector colorDialogCustomColors() const override; + void setColorDialogCustomColors(const std::vector&) override; + private: void initThemes(); void correctUserFontIfNeeded(); diff --git a/src/framework/ui/iuiconfiguration.h b/src/framework/ui/iuiconfiguration.h index 5fce8fa551833..a27e8de4c49b2 100644 --- a/src/framework/ui/iuiconfiguration.h +++ b/src/framework/ui/iuiconfiguration.h @@ -24,6 +24,8 @@ #include +#include + #include "modularity/imoduleinterface.h" #include "global/types/retval.h" @@ -124,5 +126,8 @@ class IUiConfiguration : MODULE_EXPORT_INTERFACE virtual int flickableMaxVelocity() const = 0; virtual int tooltipDelay() const = 0; + + virtual std::vector colorDialogCustomColors() const = 0; + virtual void setColorDialogCustomColors(const std::vector&) = 0; }; } diff --git a/src/framework/ui/tests/mocks/uiconfigurationmock.h b/src/framework/ui/tests/mocks/uiconfigurationmock.h index 0d8ff57cbfa4c..54b5ffbaa2bc8 100644 --- a/src/framework/ui/tests/mocks/uiconfigurationmock.h +++ b/src/framework/ui/tests/mocks/uiconfigurationmock.h @@ -108,5 +108,8 @@ class UiConfigurationMock : public IUiConfiguration MOCK_METHOD(int, flickableMaxVelocity, (), (const, override)); MOCK_METHOD(int, tooltipDelay, (), (const, override)); + + MOCK_METHOD(std::vector, colorDialogCustomColors, (), (const, override)); + MOCK_METHOD(void, setColorDialogCustomColors, (const std::vector&), (override)); }; } diff --git a/src/framework/ui/view/interactiveprovider.cpp b/src/framework/ui/view/interactiveprovider.cpp index 0e027f9d091ac..8a8b6f2e4b1d4 100644 --- a/src/framework/ui/view/interactiveprovider.cpp +++ b/src/framework/ui/view/interactiveprovider.cpp @@ -79,6 +79,26 @@ void InteractiveProvider::raiseWindowInStack(QObject* newActiveWindow) } } +static std::vector getCustomColors() +{ + const int customColorCount = QColorDialog::customCount(); + std::vector customColors; + customColors.reserve(customColorCount); + for (int i = 0; i < customColorCount; ++i) { + customColors.push_back(QColorDialog::customColor(i)); + } + + return customColors; +} + +static void setCustomColors(const std::vector& customColors) +{ + const int customColorCount = std::min(QColorDialog::customCount(), static_cast(customColors.size())); + for (int i = 0; i < customColorCount; ++i) { + QColorDialog::setCustomColor(i, customColors[i]); + } +} + async::Promise InteractiveProvider::selectColor(const Color& color, const std::string& title) { if (m_isSelectColorOpened) { @@ -91,6 +111,8 @@ async::Promise InteractiveProvider::selectColor(const Color& color, const m_isSelectColorOpened = true; + setCustomColors(config()->colorDialogCustomColors()); + return async::make_promise([this, color, title](auto resolve, auto reject) { //! FIX https://github.com/musescore/MuseScore/issues/23208 shortcutsRegister()->setActive(false); @@ -105,6 +127,8 @@ async::Promise InteractiveProvider::selectColor(const Color& color, const QObject::connect(dlg, &QColorDialog::finished, [this, dlg, resolve, reject](int result) { dlg->deleteLater(); + config()->setColorDialogCustomColors(getCustomColors()); + m_isSelectColorOpened = false; shortcutsRegister()->setActive(true); diff --git a/src/framework/ui/view/interactiveprovider.h b/src/framework/ui/view/interactiveprovider.h index 9302f6d87e8c8..fc31f716bd800 100644 --- a/src/framework/ui/view/interactiveprovider.h +++ b/src/framework/ui/view/interactiveprovider.h @@ -19,8 +19,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef MUSE_UI_INTERACTIVEPROVIDER_H -#define MUSE_UI_INTERACTIVEPROVIDER_H +#pragma once #include #include @@ -30,6 +29,7 @@ #include "global/async/asyncable.h" #include "modularity/ioc.h" +#include "ui/iuiconfiguration.h" #include "../iinteractiveprovider.h" #include "../iinteractiveuriregister.h" #include "../imainwindow.h" @@ -56,6 +56,7 @@ class InteractiveProvider : public QObject, public IInteractiveProvider, public { Q_OBJECT + Inject config = { this }; Inject uriRegister = { this }; Inject mainWindow = { this }; Inject extensionsProvider = { this }; @@ -149,5 +150,3 @@ class InteractiveProvider : public QObject, public IInteractiveProvider, public bool m_isSelectColorOpened = false; }; } - -#endif // MUSE_UI_INTERACTIVEPROVIDER_H From d53a1bfd955b9df03ed4464563920a7c6a9edf61 Mon Sep 17 00:00:00 2001 From: Leon Vinken Date: Thu, 2 Oct 2025 06:52:28 +0200 Subject: [PATCH 02/14] fix #30806: [TablEdit import] pickup measures imported as corrupt --- src/importexport/tabledit/CMakeLists.txt | 4 + .../tabledit/internal/importtef.cpp | 55 +++- .../tabledit/internal/importtef.h | 23 +- .../tabledit/internal/measurehandler.cpp | 222 ++++++++++++++++ .../tabledit/internal/measurehandler.h | 47 ++++ src/importexport/tabledit/internal/note.cpp | 59 +++++ src/importexport/tabledit/internal/note.h | 26 ++ .../tabledit/internal/voiceallocator.cpp | 35 +-- .../tabledit/tests/data/gaps_1.mscx | 239 ++++++++++++++++++ .../tabledit/tests/data/gaps_1.tef | Bin 0 -> 911 bytes .../tabledit/tests/data/notes_dotted.mscx | 80 ++---- .../tabledit/tests/data/notes_dotted.tef | Bin 965 -> 953 bytes .../tabledit/tests/data/pickup_measure.mscx | 227 +++++++++++++++++ .../tabledit/tests/data/pickup_measure.tef | Bin 0 -> 911 bytes .../tabledit/tests/data/rests_dotted.mscx | 49 +--- .../tabledit/tests/data/rests_dotted.tef | Bin 965 -> 953 bytes .../tabledit/tests/tabledit_tests.cpp | 8 + 17 files changed, 921 insertions(+), 153 deletions(-) create mode 100644 src/importexport/tabledit/internal/measurehandler.cpp create mode 100644 src/importexport/tabledit/internal/measurehandler.h create mode 100644 src/importexport/tabledit/internal/note.cpp create mode 100644 src/importexport/tabledit/internal/note.h create mode 100644 src/importexport/tabledit/tests/data/gaps_1.mscx create mode 100644 src/importexport/tabledit/tests/data/gaps_1.tef create mode 100644 src/importexport/tabledit/tests/data/pickup_measure.mscx create mode 100644 src/importexport/tabledit/tests/data/pickup_measure.tef diff --git a/src/importexport/tabledit/CMakeLists.txt b/src/importexport/tabledit/CMakeLists.txt index 916422b3f1e23..ff3f29a5ca6c4 100644 --- a/src/importexport/tabledit/CMakeLists.txt +++ b/src/importexport/tabledit/CMakeLists.txt @@ -28,6 +28,10 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/internal/importtef.cpp ${CMAKE_CURRENT_LIST_DIR}/internal/importtef.h + ${CMAKE_CURRENT_LIST_DIR}/internal/measurehandler.cpp + ${CMAKE_CURRENT_LIST_DIR}/internal/measurehandler.h + ${CMAKE_CURRENT_LIST_DIR}/internal/note.cpp + ${CMAKE_CURRENT_LIST_DIR}/internal/note.h ${CMAKE_CURRENT_LIST_DIR}/internal/tuplethandler.cpp ${CMAKE_CURRENT_LIST_DIR}/internal/tuplethandler.h ${CMAKE_CURRENT_LIST_DIR}/internal/voiceallocator.cpp diff --git a/src/importexport/tabledit/internal/importtef.cpp b/src/importexport/tabledit/internal/importtef.cpp index 5861a5e1927a4..6e82535d363fe 100644 --- a/src/importexport/tabledit/internal/importtef.cpp +++ b/src/importexport/tabledit/internal/importtef.cpp @@ -20,6 +20,7 @@ * along with this program. If not, see . */ #include "importtef.h" +#include "measurehandler.h" #include "tuplethandler.h" #include "engraving/dom/box.h" @@ -277,7 +278,7 @@ static void addRest(Segment* segment, track_idx_t track, TDuration tDuration, Fr } } -void TablEdit::createContents() +void TablEdit::createContents(const MeasureHandler& measureHandler) { if (tefInstruments.size() == 0) { LOGD("error: no instruments"); @@ -315,10 +316,14 @@ void TablEdit::createContents() if (firstNote->dots) { tDuration.setDots(firstNote->dots); } + + const auto idx { measureHandler.measureIndex(firstNote->position, tefMeasures) }; + const Fraction gapCorrection { measureHandler.sumPreviousGaps(idx), 64 }; const auto positionCorrection = tupletHandler.doTuplet(firstNote); Fraction tick { firstNote->position, 64 }; // position is in 64th tick += positionCorrection; + tick -= gapCorrection; LOGN(" positionCorrection %d/%d tick %d/%d length %d/%d", positionCorrection.numerator(), positionCorrection.denominator(), tick.numerator(), tick.denominator(), @@ -413,19 +418,38 @@ void TablEdit::createLinkedTabs() } } -void TablEdit::createMeasures() +static Fraction reducedActualLength(const int actual, const int nominalDenominator) +{ + Fraction res { actual, 64 }; + while (res.denominator() >= 2 * nominalDenominator && res.numerator() % 2 == 0) { + res.setNumerator(res.numerator() / 2); + res.setDenominator(res.denominator() / 2); + } + LOGN("actual %d nominalDenominator %d res %d/%d", actual, nominalDenominator, res.numerator(), res.denominator()); + return res; +} + +void TablEdit::createMeasures(const MeasureHandler& measureHandler) { int lastKey { 0 }; // safe default Fraction lastTimeSig { -1, -1 }; // impossible value Fraction tick { 0, 1 }; - for (const auto& tefMeasure : tefMeasures) { + for (size_t idx = 0; idx < tefMeasures.size(); ++idx) { + TefMeasure& tefMeasure { tefMeasures.at(idx) }; // create measure auto measure = Factory::createMeasure(score->dummy()->system()); measure->setTick(tick); - Fraction length{ tefMeasure.numerator, tefMeasure.denominator }; - measure->setTimesig(length); - measure->setTicks(length); + Fraction nominalLength{ tefMeasure.numerator, tefMeasure.denominator }; + Fraction actualLength{ reducedActualLength(measureHandler.actualSize(tefMeasures, idx), tefMeasure.denominator) }; + measure->setTimesig(nominalLength); + measure->setTicks(actualLength); measure->setEndBarLineType(BarLineType::NORMAL, 0); + LOGN("measure %p tick %d/%d nominalLength %d/%d actualLength %d/%d", + measure, + tick.numerator(), tick.denominator(), + nominalLength.numerator(), nominalLength.denominator(), + actualLength.numerator(), actualLength.denominator() + ); score->measures()->add(measure); if (tick == Fraction { 0, 1 }) { @@ -441,11 +465,11 @@ void TablEdit::createMeasures() auto s2 = measure->getSegment(mu::engraving::SegmentType::TimeSig, tick); for (size_t i = 0; i < tefInstruments.size(); ++i) { mu::engraving::TimeSig* timesig = Factory::createTimeSig(s2); - timesig->setSig(length); + timesig->setSig(nominalLength); timesig->setTrack(i * VOICES); s2->add(timesig); } - lastTimeSig = length; + lastTimeSig = nominalLength; createTempo(); } else { if (tefMeasure.key != lastKey) { @@ -458,19 +482,19 @@ void TablEdit::createMeasures() } lastKey = tefMeasure.key; } - if (length != lastTimeSig) { + if (nominalLength != lastTimeSig) { auto s2 = measure->getSegment(mu::engraving::SegmentType::TimeSig, tick); for (size_t i = 0; i < tefInstruments.size(); ++i) { mu::engraving::TimeSig* timesig = Factory::createTimeSig(s2); - timesig->setSig(length); + timesig->setSig(nominalLength); timesig->setTrack(i * VOICES); s2->add(timesig); } - lastTimeSig = length; + lastTimeSig = nominalLength; } } - tick += length; + tick += actualLength; } score->setUpTempoMap(); } @@ -571,12 +595,14 @@ static void setInstrumentIDs(const std::vector& parts) void TablEdit::createScore() { + MeasureHandler measureHandler; + measureHandler.calculate(tefContents, tefMeasures); createProperties(); createParts(); createTitleFrame(); - createMeasures(); + createMeasures(measureHandler); createNotesFrame(); - createContents(); + createContents(measureHandler); createRepeats(); createTexts(); createLinkedTabs(); @@ -877,6 +903,7 @@ void TablEdit::readTefMeasures() for (uint16_t i = 0; i < numberOfMeasures; ++i) { TefMeasure measure; measure.flag = readUInt8(); + measure.isPickup = measure.flag & 0x08; /* uint8_t uTmp = */ readUInt8(); measure.key = readInt8(); measure.size = readUInt8(); diff --git a/src/importexport/tabledit/internal/importtef.h b/src/importexport/tabledit/internal/importtef.h index 39ff2dd2e8bff..acf16fa6c4e34 100644 --- a/src/importexport/tabledit/internal/importtef.h +++ b/src/importexport/tabledit/internal/importtef.h @@ -32,6 +32,8 @@ #include "voiceallocator.h" namespace mu::iex::tabledit { +class MeasureHandler; + // offsets into the file header static const uint8_t OFFSET_TBED = 0x38; static const uint8_t OFFSET_CONTENTS = 0x3C; @@ -54,6 +56,15 @@ enum class Voice : uint8_t { LOWER = 3 // lower set }; +struct TefMeasure { + int flag { 0 }; + bool isPickup { false }; + int key { 0 }; + int size { 0 }; + int numerator { 0 }; + int denominator { 0 }; +}; + struct TefNote { int position { 0 }; int string { 0 }; @@ -120,14 +131,6 @@ class TablEdit std::string name; }; - struct TefMeasure { - int flag { 0 }; - int key { 0 }; - int size { 0 }; - int numerator { 0 }; - int denominator { 0 }; - }; - struct TefReadingListItem { int firstMeasure { 0 }; int lastMeasure { 0 }; @@ -140,9 +143,9 @@ class TablEdit }; void allocateVoices(std::vector& allocator); - void createContents(); + void createContents(const MeasureHandler& measureHandler); void createLinkedTabs(); - void createMeasures(); + void createMeasures(const MeasureHandler& measureHandler); void createNotesFrame(); void createParts(); void createProperties(); diff --git a/src/importexport/tabledit/internal/measurehandler.cpp b/src/importexport/tabledit/internal/measurehandler.cpp new file mode 100644 index 0000000000000..95362d08a7af7 --- /dev/null +++ b/src/importexport/tabledit/internal/measurehandler.cpp @@ -0,0 +1,222 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "measurehandler.h" +#include "note.h" + +using namespace mu::engraving; +namespace mu::iex::tabledit { +//--------------------------------------------------------- +// MeasureHandler +// Calculate measure-level timing corrections for pickup measures +// A TablEdit pickup measure contains gaps at either the left or right side, +// that must be removed. Also the measure's actual size must be calculated. +//--------------------------------------------------------- + +static int nominalSize(const std::vector& tefMeasures, const size_t idx); + +// debug support + +static void dumpIntVec(const char* name, const std::vector& gaps) +{ + std::string s { name }; + for (const auto gap : gaps) { + s += ' '; + s += std::to_string(gap); + } + LOGN("%s", s.c_str()); +} + +void MeasureHandler::dumpActualsAndSumGaps(const std::vector& tefMeasures) const +{ + std::vector actuals; + for (unsigned int i = 0; i < tefMeasures.size(); ++i) { + actuals.push_back(actualSize(tefMeasures, i)); + } + dumpIntVec("actuals", actuals); + + std::vector sumGaps; + for (unsigned int i = 0; i < tefMeasures.size(); ++i) { + sumGaps.push_back(sumPreviousGaps(i)); + } + dumpIntVec("sumGaps", sumGaps); +} + +static void dumpNominals(const std::vector& tefMeasures) +{ + std::vector nominals; + for (unsigned int i = 0; i < tefMeasures.size(); ++i) { + nominals.push_back(nominalSize(tefMeasures, i)); + } + dumpIntVec("nominals", nominals); +} + +static void dumpPickups(const std::vector& tefMeasures) +{ + std::vector pickups; + for (unsigned int i = 0; i < tefMeasures.size(); ++i) { + pickups.push_back(tefMeasures.at(i).isPickup ? 1 : 0); + } + dumpIntVec("pickups", pickups); +} + +// return the nominal size of measure idx, based on time signature + +static int nominalSize(const std::vector& tefMeasures, const size_t idx) +{ + return 64 * tefMeasures.at(idx).numerator / tefMeasures.at(idx).denominator; +} + +// return the actual size of measure idx +// normal measure: based on time signature +// pickup measure: based on time signature and left and right gaps + +int MeasureHandler::actualSize(const std::vector& tefMeasures, const size_t idx) const +{ + int size { nominalSize(tefMeasures, idx) }; + if (tefMeasures.at(idx).isPickup) { + size -= gapsLeft.at(idx) + gapsRight.at(idx); + } + LOGN("idx %zu size %d", idx, size); + return size; +} + +void MeasureHandler::initializeMeasureStartsAndGaps(const std::vector& tefMeasures) +{ + int measureStart { 0 }; + for (size_t i = 0; i < tefMeasures.size(); ++i) { + nominalMeasureStarts.push_back(measureStart); + int measureSize { nominalSize(tefMeasures, i) }; + measureStart += measureSize; + // also initialize the gaps + // normal measure: set to 0 to ignore gaps + // pickup measure: set to the measure size (to be corrected later when a smaller gap is found) + const int gap { tefMeasures.at(i).isPickup ? measureSize : 0 }; + gapsLeft.push_back(gap); + gapsRight.push_back(gap); + } +} + +// return the index of the measure containing tstart +// note O2 behaviour in score size + +int MeasureHandler::measureIndex(int tstart, const std::vector& tefMeasures) const +{ + for (size_t i = 0; i < tefMeasures.size(); ++i) { + auto start { nominalMeasureStarts.at(i) }; + auto size { 64 * tefMeasures.at(i).numerator / tefMeasures.at(i).denominator }; + if (start <= tstart && tstart < start + size) { + return i; + } + } + return -1; // not found +} + +// return the offset of tstart (distance from its measure's start) + +int MeasureHandler::offsetInMeasure(int tstart, const std::vector& tefMeasures) +{ + auto index { measureIndex(tstart, tefMeasures) }; + if (0 <= index) { + return tstart - nominalMeasureStarts.at(index); + } + return -1; // not found +} + +// find the smallest offset of any note in a pickup measure + +void MeasureHandler::updateGapLeft(std::vector& gapLeft, const int position, const std::vector& tefMeasures) +{ + auto index { measureIndex(position, tefMeasures) }; + if (tefMeasures.at(index).isPickup) { + auto offset { offsetInMeasure(position, tefMeasures) }; + if (0 <= index && 0 <= offset) { + if (offset < gapLeft[index]) { + gapLeft[index] = offset; + } + } + } + return; +} + +// find the largest end time of any note in a pickup measure + +void MeasureHandler::updateGapRight(std::vector& gapRight, const TefNote& note, const std::vector& tefMeasures) +{ + auto pos { note.position }; + auto index { measureIndex(pos, tefMeasures) }; + if (tefMeasures.at(index).isPickup) { + auto offset { offsetInMeasure(pos, tefMeasures) }; + LOGN("pos %d index %d offset %d", pos, index, offset); + if (0 <= index && 0 <= offset) { + auto dur { durationToInt(note.duration) }; + auto end { offset + dur }; + auto size { 64 * tefMeasures.at(index).numerator / tefMeasures.at(index).denominator }; + auto gap { size - end }; + LOGN("dur %d end %d size %d gap %d", dur, end, size, gap); + if (gap < gapRight[index]) { + gapRight[index] = gap; + } + } + } + return; +} + +// start time correction to be subtracted from note position due to gaps: +// sum of gaps in previous measure(s) plus left gap in current measure +// note only pickup measures count, regular measures always have gaps set to 0 + +int MeasureHandler::sumPreviousGaps(const size_t idx) const +{ + auto corr { 0 }; + for (unsigned int j = 0; j < idx; ++j) { + corr += gapsLeft.at(j) + gapsRight.at(j); + } + corr += gapsLeft.at(idx); + LOGN("idx %zu corr %d", idx, corr); + return corr; +} + +void MeasureHandler::updateGaps(const std::vector& tefContents, const std::vector& tefMeasures) +{ + for (const TefNote& note : tefContents) { + updateGapLeft(gapsLeft, note.position, tefMeasures); + updateGapRight(gapsRight, note, tefMeasures); + } +} + +void MeasureHandler::calculate(const std::vector& tefContents, const std::vector& tefMeasures) +{ + initializeMeasureStartsAndGaps(tefMeasures); + updateGaps(tefContents, tefMeasures); + + // debug: dump result + dumpIntVec("starts", nominalMeasureStarts); + dumpIntVec("gapsLeft", gapsLeft); + dumpIntVec("gapsRight", gapsRight); + dumpNominals(tefMeasures); + dumpPickups(tefMeasures); + dumpActualsAndSumGaps(tefMeasures); +} +} // namespace mu::iex::tabledit diff --git a/src/importexport/tabledit/internal/measurehandler.h b/src/importexport/tabledit/internal/measurehandler.h new file mode 100644 index 0000000000000..584a84118bc3b --- /dev/null +++ b/src/importexport/tabledit/internal/measurehandler.h @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +#include "importtef.h" + +namespace mu::iex::tabledit { +class MeasureHandler +{ +public: + int actualSize(const std::vector& tefMeasures, const size_t idx) const; + void calculate(const std::vector& tefContents, const std::vector& tefMeasures); + int sumPreviousGaps(const size_t idx) const; + int measureIndex(int tstart, const std::vector& tefMeasures) const; +private: + void dumpActualsAndSumGaps(const std::vector& tefMeasures) const; + void initializeMeasureStartsAndGaps(const std::vector& tefMeasures); + int offsetInMeasure(int tstart, const std::vector& tefMeasures); + void updateGapLeft(std::vector& gapLeft, const int position, const std::vector& tefMeasures); + void updateGapRight(std::vector& gapRight, const TefNote& note, const std::vector& tefMeasures); + void updateGaps(const std::vector& tefContents, const std::vector& tefMeasures); + std::vector gapsLeft; + std::vector gapsRight; + std::vector nominalMeasureStarts; +}; +} // namespace mu::iex::tabledit diff --git a/src/importexport/tabledit/internal/note.cpp b/src/importexport/tabledit/internal/note.cpp new file mode 100644 index 0000000000000..1be92fc3a50cf --- /dev/null +++ b/src/importexport/tabledit/internal/note.cpp @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "note.h" + +namespace mu::iex::tabledit { +// return TablEdit note length in 64th (including triplets rounded down to nearest note length) +// TODO: remove code duplication with importtef.cpp duration2length() + +int durationToInt(uint8_t duration) +{ + switch (duration) { + case 0: return 64; //"whole"; + case 1: return 48; //"half dotted"; + case 2: return 32; //"whole triplet"; + case 3: return 32; //"half"; + case 4: return 24; //"quarter dotted"; + case 5: return 16; //"half triplet"; + case 6: return 16; //"quarter"; + case 7: return 12; //"eighth dotted"; + case 8: return 8; //"quarter triplet"; + case 9: return 8; //"eighth"; + case 10: return 6; //"16th dotted"; + case 11: return 4; //"eighth triplet"; + case 12: return 4; //"16th"; + case 13: return 3; //"32nd dotted"; + case 14: return 2; //"16th triplet"; + case 15: return 2; //"32nd"; + //case 16: return "64th dotted"; + case 17: return 1; //"32nd triplet"; + case 18: return 1; //"64th"; + case 19: return 56; //"half double dotted"; + //case 20: return "16th quintuplet"; + case 22: return 28; //"quarter double dotted"; + case 25: return 14; //"eighth double dotted"; + case 28: return 7; //"16th double dotted"; + default: return 0; //"undefined"; + } +} +} // namespace mu::iex::tabledit diff --git a/src/importexport/tabledit/internal/note.h b/src/importexport/tabledit/internal/note.h new file mode 100644 index 0000000000000..bfcfcc74107ba --- /dev/null +++ b/src/importexport/tabledit/internal/note.h @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +namespace mu::iex::tabledit { +int durationToInt(uint8_t duration); +} // namespace mu::iex::tabledit diff --git a/src/importexport/tabledit/internal/voiceallocator.cpp b/src/importexport/tabledit/internal/voiceallocator.cpp index 5149a592edfbb..ccf385ce378e8 100644 --- a/src/importexport/tabledit/internal/voiceallocator.cpp +++ b/src/importexport/tabledit/internal/voiceallocator.cpp @@ -21,6 +21,7 @@ */ #include "importtef.h" +#include "note.h" #include "voiceallocator.h" #include "engraving/dom/mscore.h" @@ -55,40 +56,6 @@ int VoiceAllocator::findFirstPossibleVoice(const TefNote* const note, const std: return -1; } -// return TablEdit note length in 64th (including triplets rounded down to nearest note length) -// TODO: remove code duplication with importtef.cpp duration2length() - -static int durationToInt(uint8_t duration) // TODO duplicated code ? -{ - switch (duration) { - case 0: return 64; //"whole"; - case 1: return 48; //"half dotted"; - case 2: return 32; //"whole triplet"; - case 3: return 32; //"half"; - case 4: return 24; //"quarter dotted"; - case 5: return 16; //"half triplet"; - case 6: return 16; //"quarter"; - case 7: return 12; //"eighth dotted"; - case 8: return 8; //"quarter triplet"; - case 9: return 8; //"eighth"; - case 10: return 6; //"16th dotted"; - case 11: return 4; //"eighth triplet"; - case 12: return 4; //"16th"; - case 13: return 3; //"32nd dotted"; - case 14: return 2; //"16th triplet"; - case 15: return 2; //"32nd"; - //case 16: return "64th dotted"; - case 17: return 1; //"32nd triplet"; - case 18: return 1; //"64th"; - case 19: return 56; //"half double dotted"; - //case 20: return "16th quintuplet"; - case 22: return 28; //"quarter double dotted"; - case 25: return 14; //"eighth double dotted"; - case 28: return 7; //"16th double dotted"; - default: return 0; //"undefined"; - } -} - int VoiceAllocator::stopPosition(const size_t voice) { if (mu::engraving::VOICES <= voice) { diff --git a/src/importexport/tabledit/tests/data/gaps_1.mscx b/src/importexport/tabledit/tests/data/gaps_1.mscx new file mode 100644 index 0000000000000..c3d15f7972dfe --- /dev/null +++ b/src/importexport/tabledit/tests/data/gaps_1.mscx @@ -0,0 +1,239 @@ + + + + B_B + 480 + + 1 + 1 + 1 + 0 + + TablEdited by + + + + + + tef + + + + + + + C_C + + stdNormal + + G8vb + + + D_D + C_C + + tab6StrSimple + 6 + 1.5 + 1 + 0 + 0 + MuseScore Tab Modern + 15 + 0 + 1 + tab_fret_number + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 1 + + + + + + pluck.guitar + + 25 + 40 + 45 + 50 + 55 + 59 + 64 + + + + + + + + + 10 + E_E + + + F_F + + + G_G + 0 + + + H_H + 4 + 4 + + + 2 + 1 + I_I + metNoteQuarterUp = 120 + + + 3/4 + + + J_J + quarter + + K_K + 48 + 14 + 3 + 4 + + + + L_L + + + + + M_M + + + N_N + whole + + O_O + 48 + 14 + 3 + 4 + + + + P_P + + + + + Q_Q + + + R_R + 1 + half + + S_S + 48 + 14 + 3 + 4 + + + + 1/4 + + + T_T + + + + + + + + + U_U + G_G + 0 + + + 3/4 + + + V_V + J_J + quarter + + W_W + K_K + 48 + 14 + 3 + 4 + + + + X_X + L_L + + + + + + + Y_Y + N_N + whole + + Z_Z + O_O + 48 + 14 + 3 + 4 + + + + a_a + P_P + + + + + + + b_b + R_R + 1 + half + + c_c + S_S + 48 + 14 + 3 + 4 + + + + 1/4 + + + d_d + T_T + + + + + + diff --git a/src/importexport/tabledit/tests/data/gaps_1.tef b/src/importexport/tabledit/tests/data/gaps_1.tef new file mode 100644 index 0000000000000000000000000000000000000000..b5298d6ea705441bc6f345d05e38608542dd24b8 GIT binary patch literal 911 zcmWe&U}4_IP{Du=FfwBkg@~o3CY7Xvl(K z76ukBT&6PNk^ng$i1-;o5|eUVQ!-0ZQxuXa6`;aA4Db+eU||89fkL|g)v-a1mVnX* z Q_Q - -12 - -12 + -6 + -6 R_R @@ -178,7 +178,7 @@ X_X 1 - 64th + 32nd Y_Y 55 @@ -187,26 +187,8 @@ 2 - - -1/128 - - - Z_Z - 1 - 64th - - a_a - 55 - 15 - 0 - 2 - - - - 1/128 - - b_b + Z_Z @@ -215,17 +197,17 @@ - c_c + a_a H_H 0 - d_d + b_b K_K 1 half - e_e + c_c L_L 55 15 @@ -234,7 +216,7 @@ - f_f + d_d M_M @@ -242,12 +224,12 @@ - g_g + e_e O_O 1 quarter - h_h + f_f P_P 55 15 @@ -256,12 +238,12 @@ - i_i + g_g R_R 1 eighth - j_j + h_h S_S 55 15 @@ -270,12 +252,12 @@ - k_k + i_i T_T 1 16th - l_l + j_j U_U 55 15 @@ -284,12 +266,12 @@ - m_m + k_k V_V 1 32nd - n_n + l_l W_W 55 15 @@ -298,12 +280,12 @@ - o_o + m_m X_X 1 - 64th + 32nd - p_p + n_n Y_Y 55 15 @@ -311,29 +293,9 @@ 2 - - -1/128 - - - q_q - Z_Z - 1 - 64th - - r_r - a_a - 55 - 15 - 0 - 2 - - - - 1/128 - - s_s - b_b + o_o + Z_Z diff --git a/src/importexport/tabledit/tests/data/notes_dotted.tef b/src/importexport/tabledit/tests/data/notes_dotted.tef index fad8210aba26bd2431788da3f75760cecdfd668c..0de07a652a914a5ead3c5e10625d7d0c68b2555d 100644 GIT binary patch delta 56 zcmX@gzLR~zGe)L~g_4sPXG<8ETiIDyTH4r|yBQc51Q-|^1smw=8yRfg%=nv;+m`_h H{sRF3+QAW2 delta 68 zcmdnVew2N}Ge*XVg_4sPXG>(5TiIDyTH4r|yBQc51{fF`1smw=8yRli%=nv8Jb(cV OHV85>Fhbe?fdBxPSrV=Q diff --git a/src/importexport/tabledit/tests/data/pickup_measure.mscx b/src/importexport/tabledit/tests/data/pickup_measure.mscx new file mode 100644 index 0000000000000..f12c20c2bdf3d --- /dev/null +++ b/src/importexport/tabledit/tests/data/pickup_measure.mscx @@ -0,0 +1,227 @@ + + + + B_B + 480 + + 1 + 1 + 1 + 0 + + TablEdited by + + + + + + tef + + + + + + + C_C + + stdNormal + + G8vb + + + D_D + C_C + + tab6StrSimple + 6 + 1.5 + 1 + 0 + 0 + MuseScore Tab Modern + 15 + 0 + 1 + tab_fret_number + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 1 + + + + + + pluck.guitar + + 25 + 40 + 45 + 50 + 55 + 59 + 64 + + + + + + + + + 10 + E_E + + + F_F + + + G_G + 0 + + + H_H + 4 + 4 + + + 2 + 1 + I_I + metNoteQuarterUp = 120 + + + J_J + quarter + + K_K + 48 + 14 + 3 + 4 + + + + L_L + + + + + M_M + + + N_N + whole + + O_O + 48 + 14 + 3 + 4 + + + + P_P + + + + + Q_Q + + + R_R + 1 + half + + S_S + 48 + 14 + 3 + 4 + + + + T_T + + + + + + + + + U_U + G_G + 0 + + + V_V + J_J + quarter + + W_W + K_K + 48 + 14 + 3 + 4 + + + + X_X + L_L + + + + + + + Y_Y + N_N + whole + + Z_Z + O_O + 48 + 14 + 3 + 4 + + + + a_a + P_P + + + + + + + b_b + R_R + 1 + half + + c_c + S_S + 48 + 14 + 3 + 4 + + + + d_d + T_T + + + + + + diff --git a/src/importexport/tabledit/tests/data/pickup_measure.tef b/src/importexport/tabledit/tests/data/pickup_measure.tef new file mode 100644 index 0000000000000000000000000000000000000000..1d1333119d52b05134fe170af301ae54865a4ae2 GIT binary patch literal 911 zcmWe&U}4_IP{Du=FfwBkg@~o3CY7Xvl(K z76ukBT&6PNk^ng$i1-;o5|eUVQ!-0ZQxuXa6`;aA43H4uU|?`yVF8-~reS;+pgK0F z(GpPFpqzn0K~+=F*aA!_K!x#A4;T#$41vfHNEjH{8XA~e*;!ax+Sr-90r>$2CPu*q z`uav7NhmP1axl;bY5?i8vbVDGumNj@NgGU=0%BTBfC&U7rl%^X6)5N{sO8~x3XLSy zlGTjVN)&*UnOd=eT9KNuTCM_(?80U&*v1kCT?L@U#q_io>KFx11_l;42uFbj$aa9T Nfysu&5yJit1ONpQGg|-v literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/data/rests_dotted.mscx b/src/importexport/tabledit/tests/data/rests_dotted.mscx index fddcfb99c58c5..e3635d588a4e3 100644 --- a/src/importexport/tabledit/tests/data/rests_dotted.mscx +++ b/src/importexport/tabledit/tests/data/rests_dotted.mscx @@ -138,21 +138,10 @@ R_R 1 - 64th - - - -1/128 - - - S_S - 1 - 64th + 32nd - - 1/128 - - T_T + S_S @@ -161,18 +150,18 @@ - U_U + T_T H_H 0 - V_V + U_U K_K 1 half - W_W + V_V L_L @@ -180,50 +169,38 @@ - X_X + W_W N_N 1 quarter - Y_Y + X_X O_O 1 eighth - Z_Z + Y_Y P_P 1 16th - a_a + Z_Z Q_Q 1 32nd - b_b + a_a R_R 1 - 64th - - - -1/128 - - - c_c - S_S - 1 - 64th + 32nd - - 1/128 - - d_d - T_T + b_b + S_S diff --git a/src/importexport/tabledit/tests/data/rests_dotted.tef b/src/importexport/tabledit/tests/data/rests_dotted.tef index 00eff416e47506ad46b2ace8d64ea30137f1eace..2635a4018718b5f08bb3d5df2fb666c2c69b9906 100644 GIT binary patch delta 56 zcmX@gzLR~zGe)L~g_4sPXG<8ETiIDyTH4r|yBQc51Q-|^1smw=8yRfg%=nv;o0kC$ H{sRF3*Z~m~ delta 68 zcmdnVew2N}Ge*XVg_4sPXG>(5TiIDyTH4r|yBQc51{fF`1smw=8yRli%=nv8Tz~-# OHV85>7(?0rfdBxJF%n<^ diff --git a/src/importexport/tabledit/tests/tabledit_tests.cpp b/src/importexport/tabledit/tests/tabledit_tests.cpp index 8f335f3f6c56f..37e9727c5b9e6 100644 --- a/src/importexport/tabledit/tests/tabledit_tests.cpp +++ b/src/importexport/tabledit/tests/tabledit_tests.cpp @@ -70,6 +70,10 @@ TEST_F(TablEdit_Tests, tef_dynamic) { tefReadTest("dynamic"); } +TEST_F(TablEdit_Tests, tef_gaps_1) { + tefReadTest("gaps_1"); +} + TEST_F(TablEdit_Tests, tef_grace_1) { tefReadTest("grace_1"); } @@ -114,6 +118,10 @@ TEST_F(TablEdit_Tests, tef_notes_normal) { tefReadTest("notes_normal"); } +TEST_F(TablEdit_Tests, tef_pickup_measure) { + tefReadTest("pickup_measure"); +} + TEST_F(TablEdit_Tests, tef_positions) { tefReadTest("positions"); } From a3c56d02fc72ca0aa8336f37b2f6729e3fa07613 Mon Sep 17 00:00:00 2001 From: Joachim Schmitz Date: Thu, 6 Nov 2025 16:41:58 +0100 Subject: [PATCH 03/14] Fix MSVC compiler warning reg.: 'return': conversion from 'size_t' to 'int', possible loss of data (C4267) Co-Authored-By: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> --- .../tabledit/internal/measurehandler.cpp | 32 +++++++++++-------- .../tabledit/internal/measurehandler.h | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/importexport/tabledit/internal/measurehandler.cpp b/src/importexport/tabledit/internal/measurehandler.cpp index 95362d08a7af7..d680a9e1bfa76 100644 --- a/src/importexport/tabledit/internal/measurehandler.cpp +++ b/src/importexport/tabledit/internal/measurehandler.cpp @@ -22,6 +22,8 @@ #include +#include "global/containers.h" + #include "measurehandler.h" #include "note.h" @@ -121,7 +123,7 @@ void MeasureHandler::initializeMeasureStartsAndGaps(const std::vector& tefMeasures) const +size_t MeasureHandler::measureIndex(int tstart, const std::vector& tefMeasures) const { for (size_t i = 0; i < tefMeasures.size(); ++i) { auto start { nominalMeasureStarts.at(i) }; @@ -130,34 +132,36 @@ int MeasureHandler::measureIndex(int tstart, const std::vector& tefM return i; } } - return -1; // not found + return muse::nidx; // not found } // return the offset of tstart (distance from its measure's start) int MeasureHandler::offsetInMeasure(int tstart, const std::vector& tefMeasures) { - auto index { measureIndex(tstart, tefMeasures) }; - if (0 <= index) { - return tstart - nominalMeasureStarts.at(index); + size_t index { measureIndex(tstart, tefMeasures) }; + if (index >= tefMeasures.size()) { + return -1; // not found } - return -1; // not found + return tstart - nominalMeasureStarts.at(index); } // find the smallest offset of any note in a pickup measure void MeasureHandler::updateGapLeft(std::vector& gapLeft, const int position, const std::vector& tefMeasures) { - auto index { measureIndex(position, tefMeasures) }; + size_t index { measureIndex(position, tefMeasures) }; + if (index >= tefMeasures.size()) { + return; // not found + } if (tefMeasures.at(index).isPickup) { auto offset { offsetInMeasure(position, tefMeasures) }; - if (0 <= index && 0 <= offset) { + if (0 <= offset) { if (offset < gapLeft[index]) { gapLeft[index] = offset; } } } - return; } // find the largest end time of any note in a pickup measure @@ -165,11 +169,14 @@ void MeasureHandler::updateGapLeft(std::vector& gapLeft, const int position void MeasureHandler::updateGapRight(std::vector& gapRight, const TefNote& note, const std::vector& tefMeasures) { auto pos { note.position }; - auto index { measureIndex(pos, tefMeasures) }; + size_t index { measureIndex(pos, tefMeasures) }; + if (index >= tefMeasures.size()) { + return; // not found + } if (tefMeasures.at(index).isPickup) { auto offset { offsetInMeasure(pos, tefMeasures) }; - LOGN("pos %d index %d offset %d", pos, index, offset); - if (0 <= index && 0 <= offset) { + LOGN("pos %d index %zu offset %d", pos, index, offset); + if (0 <= offset) { auto dur { durationToInt(note.duration) }; auto end { offset + dur }; auto size { 64 * tefMeasures.at(index).numerator / tefMeasures.at(index).denominator }; @@ -180,7 +187,6 @@ void MeasureHandler::updateGapRight(std::vector& gapRight, const TefNote& n } } } - return; } // start time correction to be subtracted from note position due to gaps: diff --git a/src/importexport/tabledit/internal/measurehandler.h b/src/importexport/tabledit/internal/measurehandler.h index 584a84118bc3b..eb9f7e3835823 100644 --- a/src/importexport/tabledit/internal/measurehandler.h +++ b/src/importexport/tabledit/internal/measurehandler.h @@ -32,7 +32,7 @@ class MeasureHandler int actualSize(const std::vector& tefMeasures, const size_t idx) const; void calculate(const std::vector& tefContents, const std::vector& tefMeasures); int sumPreviousGaps(const size_t idx) const; - int measureIndex(int tstart, const std::vector& tefMeasures) const; + size_t measureIndex(int tstart, const std::vector& tefMeasures) const; private: void dumpActualsAndSumGaps(const std::vector& tefMeasures) const; void initializeMeasureStartsAndGaps(const std::vector& tefMeasures); From cd72afd54ab47ea62b72a6e1b740a0dcb6406a9f Mon Sep 17 00:00:00 2001 From: Leon Vinken Date: Mon, 27 Oct 2025 08:00:00 +0100 Subject: [PATCH 04/14] fix #30807: [TablEdit import] measures with gaps in voice 1 imported as corrupt --- .../tabledit/internal/importtef.cpp | 68 +- .../tabledit/tests/data/gaps_1.mscx | 104 +-- .../tabledit/tests/data/gaps_2.mscx | 598 ++++++++++++++++++ .../tabledit/tests/data/gaps_2.tef | Bin 0 -> 1079 bytes .../tabledit/tests/tabledit_tests.cpp | 4 + 5 files changed, 731 insertions(+), 43 deletions(-) create mode 100644 src/importexport/tabledit/tests/data/gaps_2.mscx create mode 100644 src/importexport/tabledit/tests/data/gaps_2.tef diff --git a/src/importexport/tabledit/internal/importtef.cpp b/src/importexport/tabledit/internal/importtef.cpp index 6e82535d363fe..9eeb03aa02298 100644 --- a/src/importexport/tabledit/internal/importtef.cpp +++ b/src/importexport/tabledit/internal/importtef.cpp @@ -266,7 +266,7 @@ static void addGraceNotesToChord(mu::engraving::Chord* chord, int pitch, int fre chord->add(cr); } -static void addRest(Segment* segment, track_idx_t track, TDuration tDuration, Fraction length, muse::draw::Color color) +static void addRest(Segment* segment, track_idx_t track, TDuration tDuration, Fraction length, muse::draw::Color color, bool visible = true) { mu::engraving::Rest* rest = Factory::createRest(segment); if (rest) { @@ -274,6 +274,7 @@ static void addRest(Segment* segment, track_idx_t track, TDuration tDuration, Fr rest->setDurationType(tDuration); rest->setTicks(length); rest->setColor(color); + rest->setVisible(visible); segment->add(rest); } } @@ -593,6 +594,70 @@ static void setInstrumentIDs(const std::vector& parts) } } +//--------------------------------------------------------- +// fillGap +//--------------------------------------------------------- + +// Fill one gap (tstart - tend) in this track in this measure with rest(s). + +static void fillGap(Measure* measure, track_idx_t track, const Fraction& tstart, const Fraction& tend) +{ + Fraction ctick = tstart; + Fraction restLen = tend - tstart; + LOGN("measure %p track %zu tstart %d tend %d restLen %d len", + measure, track, tstart.ticks(), tend.ticks(), restLen.ticks()); + auto durList = toDurationList(restLen, true); + LOGN("durList.size %zu", durList.size()); + for (const auto& dur : durList) { + LOGN("type %d dots %d fraction %d/%d", dur.type(), dur.dots(), dur.fraction().numerator(), dur.fraction().denominator()); + Segment* s = measure->getSegment(SegmentType::ChordRest, ctick); + addRest(s, track, dur, dur.fraction(), muse::draw::Color::BLACK, false); + ctick += dur.fraction(); + } +} + +//--------------------------------------------------------- +// fillGapsInFirstVoices +//--------------------------------------------------------- + +// Fill gaps in first voice of every staff in this measure for this part with rest(s). + +static void fillGapsInFirstVoices(MasterScore* score) +{ + IF_ASSERT_FAILED(score) { + return; + } + + for (staff_idx_t idx = 0; idx < score->nstaves(); ++idx) { + for (Measure* measure = score->firstMeasure(); measure; measure = measure->nextMeasure()) { + Fraction measTick = measure->tick(); + Fraction measLen = measure->ticks(); + Fraction nextMeasTick = measTick + measLen; + LOGN("measure %p idx %zu tick %d - %d (len %d)", + measure, idx, measTick.ticks(), nextMeasTick.ticks(), measLen.ticks()); + track_idx_t track = idx * VOICES; + Fraction endOfLastCR = measTick; + for (Segment* s = measure->first(); s; s = s->next()) { + EngravingItem* el = s->element(track); + if (el) { + if (s->isChordRestType()) { + ChordRest* cr = static_cast(el); + Fraction crTick = cr->tick(); + Fraction crLen = cr->globalTicks(); + if (crTick > endOfLastCR) { + fillGap(measure, track, endOfLastCR, crTick); + } + endOfLastCR = crTick + crLen; + } + } + } + if (nextMeasTick > endOfLastCR) { + fillGap(measure, track, endOfLastCR, nextMeasTick); + } + } + } +} + void TablEdit::createScore() { MeasureHandler measureHandler; @@ -603,6 +668,7 @@ void TablEdit::createScore() createMeasures(measureHandler); createNotesFrame(); createContents(measureHandler); + fillGapsInFirstVoices(score); createRepeats(); createTexts(); createLinkedTabs(); diff --git a/src/importexport/tabledit/tests/data/gaps_1.mscx b/src/importexport/tabledit/tests/data/gaps_1.mscx index c3d15f7972dfe..e17bf29513f7b 100644 --- a/src/importexport/tabledit/tests/data/gaps_1.mscx +++ b/src/importexport/tabledit/tests/data/gaps_1.mscx @@ -96,14 +96,21 @@ I_I metNoteQuarterUp = 120 - - 3/4 - - + J_J + 0 + 1 + half + + K_K + 0 + + + + L_L quarter - K_K + M_M 48 14 3 @@ -111,18 +118,18 @@ - L_L + N_N - M_M + O_O - N_N + P_P whole - O_O + Q_Q 48 14 3 @@ -130,30 +137,32 @@ - P_P + R_R - Q_Q + S_S - R_R + T_T 1 half - S_S + U_U 48 14 3 4 - - 1/4 - + + V_V + 0 + quarter + - T_T + W_W @@ -162,20 +171,28 @@ - U_U + X_X G_G 0 - - 3/4 - - - V_V + + Y_Y J_J + 0 + 1 + half + + Z_Z + 0 + + + + a_a + L_L quarter - W_W - K_K + b_b + M_M 48 14 3 @@ -183,20 +200,20 @@ - X_X - L_L + c_c + N_N - Y_Y - N_N + d_d + P_P whole - Z_Z - O_O + e_e + Q_Q 48 14 3 @@ -204,33 +221,36 @@ - a_a - P_P + f_f + R_R - b_b - R_R + g_g + T_T 1 half - c_c - S_S + h_h + U_U 48 14 3 4 - - 1/4 - + + i_i + V_V + 0 + quarter + - d_d - T_T + j_j + W_W diff --git a/src/importexport/tabledit/tests/data/gaps_2.mscx b/src/importexport/tabledit/tests/data/gaps_2.mscx new file mode 100644 index 0000000000000..db678b19e6c10 --- /dev/null +++ b/src/importexport/tabledit/tests/data/gaps_2.mscx @@ -0,0 +1,598 @@ + + + + B_B + 480 + + 1 + 1 + 1 + 0 + + TablEdited by + + + + + + tef + + + + + + + C_C + + stdNormal + + G8vb + + + D_D + C_C + + tab6StrSimple + 6 + 1.5 + 1 + 0 + 0 + MuseScore Tab Modern + 15 + 0 + 1 + tab_fret_number + 0 + 0 + 1 + 0 + 1 + 0 + 0 + 1 + + + + + + pluck.guitar + + 25 + 40 + 45 + 50 + 55 + 59 + 64 + + + + + + + + + 10 + E_E + + + F_F + + + G_G + 0 + + + H_H + 4 + 4 + + + 2 + 1 + I_I + metNoteQuarterUp = 120 + + + J_J + quarter + + K_K + 64 + 18 + 0 + 0 + + + + L_L + quarter + + M_M + 64 + 18 + 0 + 0 + + + + N_N + quarter + + O_O + 64 + 18 + 0 + 0 + + + + P_P + 0 + eighth + + + Q_Q + eighth + + R_R + 64 + 18 + 0 + 0 + + + + S_S + + + + + T_T + quarter + + U_U + 40 + 18 + 0 + 5 + + + + V_V + quarter + + W_W + 40 + 18 + 0 + 5 + + + + X_X + quarter + + Y_Y + 40 + 18 + 0 + 5 + + + + Z_Z + quarter + + a_a + 40 + 18 + 0 + 5 + + + + + + b_b + + + c_c + 3 + 4 + + + d_d + 0 + quarter + + + e_e + half + + + f_f + + + + + g_g + quarter + + + h_h + quarter + + i_i + 40 + 18 + 0 + 5 + + + + j_j + quarter + + k_k + 40 + 18 + 0 + 5 + + + + + + 1/2 + + + l_l + 0 + 0 + + + m_m + eighth + + n_n + 55 + 15 + 0 + 2 + + + + o_o + eighth + + p_p + 55 + 15 + 0 + 2 + + + + + + q_q + + + r_r + 0 + quarter + + + s_s + quarter + + t_t + 59 + 19 + 0 + 1 + + + + u_u + quarter + + v_v + 64 + 18 + 0 + 0 + + + + w_w + + + + + x_x + 1 + half + + y_y + 40 + 18 + 0 + 5 + + + + + + + + + + z_z + G_G + 0 + + + 0_0 + J_J + quarter + + 1_1 + K_K + 64 + 18 + 0 + 0 + + + + 2_2 + L_L + quarter + + 3_3 + M_M + 64 + 18 + 0 + 0 + + + + 4_4 + N_N + quarter + + 5_5 + O_O + 64 + 18 + 0 + 0 + + + + 6_6 + P_P + 0 + eighth + + + 7_7 + Q_Q + eighth + + 8_8 + R_R + 64 + 18 + 0 + 0 + + + + 9_9 + S_S + + + + + +_+ + T_T + quarter + + /_/ + U_U + 40 + 18 + 0 + 5 + + + + AB_AB + V_V + quarter + + BB_BB + W_W + 40 + 18 + 0 + 5 + + + + CB_CB + X_X + quarter + + DB_DB + Y_Y + 40 + 18 + 0 + 5 + + + + EB_EB + Z_Z + quarter + + FB_FB + a_a + 40 + 18 + 0 + 5 + + + + + + + + GB_GB + d_d + 0 + quarter + + + HB_HB + e_e + half + + + IB_IB + f_f + + + + + JB_JB + g_g + quarter + + + KB_KB + h_h + quarter + + LB_LB + i_i + 40 + 18 + 0 + 5 + + + + MB_MB + j_j + quarter + + NB_NB + k_k + 40 + 18 + 0 + 5 + + + + + + 1/2 + + + OB_OB + m_m + eighth + + PB_PB + n_n + 55 + 15 + 0 + 2 + + + + QB_QB + o_o + eighth + + RB_RB + p_p + 55 + 15 + 0 + 2 + + + + + + + + SB_SB + r_r + 0 + quarter + + + TB_TB + s_s + quarter + + UB_UB + t_t + 59 + 19 + 0 + 1 + + + + VB_VB + u_u + quarter + + WB_WB + v_v + 64 + 18 + 0 + 0 + + + + XB_XB + w_w + + + + + YB_YB + x_x + 1 + half + + ZB_ZB + y_y + 40 + 18 + 0 + 5 + + + + + + + diff --git a/src/importexport/tabledit/tests/data/gaps_2.tef b/src/importexport/tabledit/tests/data/gaps_2.tef new file mode 100644 index 0000000000000000000000000000000000000000..f20729defbb0546280b7f610cef57a85247f20f2 GIT binary patch literal 1079 zcmcgq%}T>S5T2M2s}x!kQ50b=F7cq*Bx2Lh)(RT*R?rt{O(}v(u{r3?i}xP-EIyBK zqO(6qb4Yu#4m&&Felz>cvP>N;^9$af%nY-PRa`P0q$80x08Db5e3yKmJhMZ>ZSv0o zm*xIGT3~H1Qe9h=5YMMogZuvA={k8#hY1eeQPWk(f1qnwQiEHHlJSb_Y%RT~BVPx= zqvO*clpAV#Y4|oc_h@;vao+ZLBkF|BW~D=RI{vCH+UnI48QP*?ng18r5MA1!4 zUd^}JOcaJwUCw*`(Gb}fU1TpypR%GPdtg3Gk-|BfAbVv2d&U*@DwCBRQ#?UxJo(SY zg~u4NIye+EECwvYP63Ot%K0MR5ImPcA2ja9ElOOe0tB|=>ovlr;vK?SeSK?Q?+C1Q YrrQO6hw!D+snAFpvC@HqIsPI01<{c@&Hw-a literal 0 HcmV?d00001 diff --git a/src/importexport/tabledit/tests/tabledit_tests.cpp b/src/importexport/tabledit/tests/tabledit_tests.cpp index 37e9727c5b9e6..44594314111b6 100644 --- a/src/importexport/tabledit/tests/tabledit_tests.cpp +++ b/src/importexport/tabledit/tests/tabledit_tests.cpp @@ -74,6 +74,10 @@ TEST_F(TablEdit_Tests, tef_gaps_1) { tefReadTest("gaps_1"); } +TEST_F(TablEdit_Tests, tef_gaps_2) { + tefReadTest("gaps_2"); +} + TEST_F(TablEdit_Tests, tef_grace_1) { tefReadTest("grace_1"); } From 9fa864c4c8470afef3d6cdf339b7eb8ade647170 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 22 Oct 2025 08:50:17 +0100 Subject: [PATCH 05/14] Follow section break courtesy property --- src/engraving/rendering/score/measurelayout.cpp | 14 +++++++++++++- vtest/scores/courtesy-jump-section-1.mscz | Bin 0 -> 21940 bytes vtest/scores/courtesy-jump-section-2.mscz | Bin 0 -> 23740 bytes 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 vtest/scores/courtesy-jump-section-1.mscz create mode 100644 vtest/scores/courtesy-jump-section-2.mscz diff --git a/src/engraving/rendering/score/measurelayout.cpp b/src/engraving/rendering/score/measurelayout.cpp index 2036ce74b4d3a..d61340c171ed0 100644 --- a/src/engraving/rendering/score/measurelayout.cpp +++ b/src/engraving/rendering/score/measurelayout.cpp @@ -2435,13 +2435,25 @@ void MeasureLayout::addRepeatCourtesies(Measure* m, LayoutContext& ctx) return; } + bool hasCourtesies = false; for (Measure* repeatStartMeasure : measures) { - if (repeatStartMeasure == m->nextMeasure()) { + // Follow section break courtesy property + const Measure* prevMeasure = repeatStartMeasure->prevMeasure(); + const LayoutBreak* sectionBreak = prevMeasure ? prevMeasure->sectionBreakElement() : nullptr; + const bool sectionBreakHideCourtesies = sectionBreak && !sectionBreak->showCourtesy(); + + if (repeatStartMeasure == m->nextMeasure() || sectionBreakHideCourtesies) { continue; } setCourtesyClef(m, repeatStartMeasure->tick(), m->endTick(), SegmentType::ClefRepeatAnnounce, ctx); setCourtesyKeySig(m, repeatStartMeasure->tick(), m->endTick(), SegmentType::KeySigRepeatAnnounce, ctx); setCourtesyTimeSig(m, repeatStartMeasure->tick(), m->endTick(), SegmentType::TimeSigRepeatAnnounce, ctx); + + hasCourtesies = true; + } + + if (!hasCourtesies) { + removeRepeatCourtesies(m); } } diff --git a/vtest/scores/courtesy-jump-section-1.mscz b/vtest/scores/courtesy-jump-section-1.mscz new file mode 100644 index 0000000000000000000000000000000000000000..f0f42671f22691d1a8f3fdb19490507c37ce7497 GIT binary patch literal 21940 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 literal 0 HcmV?d00001 diff --git a/vtest/scores/courtesy-jump-section-2.mscz b/vtest/scores/courtesy-jump-section-2.mscz new file mode 100644 index 0000000000000000000000000000000000000000..46cbcf35794e8975071366b98360eb565520bf6c GIT binary patch literal 23740 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%0Aooq_62SgD4Gu5(q62t!b33 zmRsftHh4yCDJ{pUM>ROPB(O&jjM(DBrocdw+ztg=%F^?%ganFsuV905?emBN*y(zZ z5pgH|w^7gQ_2c~>KR)~SrW@n+T1BSMO`zwI48I7j2Sh{gASTlH6tRB0Map`IHz)h# z;RybTsyJEA#R4EWz{}tim z>_uS298AcfLoGK>%4;W$_L+W4N8Lutl{Lh{@{~7$v*B5AO>(n9hp+dlzaVmk;z$7p z%?}mMI~-_dk{J!V#l84Un`P$q@^^182v)*qtl=gPD`I9H4yE;V97SH3szid^GTG2W z!*M~Kz~!1~^M_4m8TTy4zfg|;?OxX2hk9i7v&Qo@wiFir)7zF+j_U&=CJkusU(;<( z)JQo~t{O>6?$9uUUb#kJn@=z{szkT+WsVatrnJ5ruHX11z5;J+M1%1ZpQGWxD=H+5`EzOY)f-s`35fvipr`44OP%NCFap91yc+%B` zWipgPr;tiQlMNV+3o|vP+SRT8rtAq->8;lLnhd=q|2kK9tREjx5|hwFK!(xteHKPL_CsHqn6*>57{(v@+jv1cDal^-metlP@bUNgM<>8x}Ff zMad#kqCUHICulBtx21V!Q%I*Hf1sTQ7Oly&pJ;;HB)TeZJbs@GWl9q8&mxaILY zY~F2kXS(IQaenP$GIzRYBpOdwHsIZaZMPnLj}mUz`=LO2cJ_!5(7fE3vDMj9Lo%J3 z?wjT>`?jLLrbF9q^w9%N@SjxMgj#j-H}=`Y-MR=fAnyIl9 z2!cp264_dt)Oh+Hezi+S4TxnOy9oFS} zlH18Y-;c;5Bz*UT*}F#f_Lu*zc&%D{p4VC)r-n*F)Qk_jZ1lmE0xdlE+^p4y1Sb?e zhOVK+w>ZxrYT(u39-1(&nzH1Bj!^wkc-^C9-CB7B21zrJ#)fw)srM!L&BncC7Hz~v zJ=C*q80$>?GIrNGNm)JQAb0TVy}{w0{nc*Ksz-D2zTJzwqh+Y-%(SX9r%F)W=6zYr zFZ%u5a?!fN;~vly@{b98cjK3+>n6L~yQ$3j;ZPkCCQ9onSCTu`sJV=mo_9rZ*aozb zgGuGTlg%*f7ugOy&aTFN1x@YjciY4!mn;OuNxc2SRqMb&u=#E z9%VcN1ZO4 zG^7sq#RjBnyM519BO`TED0>o2G~hty-jA*K$2XJx_*yOYTg@Oc+)Kx5C-oTjOXkz; z+wYyzAv3)Q(x$p)i}~>50xzdCLF-Gb3ZGfDm_iVDH}|dYudZs1`YV0mwrf2mt2TTr zneXKE1-(Y^gO^UvsOGjh_m|5{hnx0Mc~*L<7s9BeUKg2Q-SX;+PgQluVug%~IyyG} zUvEtTB$hPFU2=^d9`$QHr8-Q$>ka=5(Jx!y#cwvbkBjX_!)$`D!t5X}_tI+q>}Rk@ z{tZrg(1)&mp0_^(6(qLVjXQTe)x{oI1?I*4r*c#8TeivcvcjH*0281*)_Il*>8I1d zqqqD;2xFbk)XXWBm*k~t*Rx~`$X*ZG%|-WbzIR)EyU+Z(%t8ktcaRP=UU@iDKgAXE zWIT>s5;PlFY!j5?d*t1JV&;yvkxCx7<-kIS2iI!Rs>%*~Ca0lmiPwGn{8!OatmMe} z2NOG|X%0oRorhpkZ;H$$#)b&i)i z+xb9L%<``BJvp2F{akq4V0vZ-lPXc5r-t`dxzYA2qkX-A6PdFTs{Uznt6Da)(p&mz zMfOaOvL?XKgX5Gw!*^&)w)-M;xtAgGpP|rO$-Ix~PNIE39OQ4ep364r(7}a&rN z67=`@m`yok{as?K0gb+%i9T*WxQJW%Q=jEOfq`eg z@0Y)18Q&lCuf4-`xI<~pJ^&M|afEISWI;^lI*A(d1OKEHOg|TF*&d^!#-$lbR#<_y z9=sB9SHl`yvds zXn&j8e?Q%AtNTbtf22g6OI!A;J;hFbIhb<-OoIk`inIKn&RLsvrO(B4IqsOvR5`}| zMk`*ZTf9GFv*>L*->t&`kqr=d&>Vs&U!m{nzfPna~mFx5ZuWdrg-^-QKVTT#tA?tUp;e1h;&0z^f?S5 znl$hV;3r%0b5MjE&^^c1jb2#Gx#`a5Gn^t-ZP!IBUEGIJ8plNcA=+uTXN9OoqFPO{ zx5r@*kZosg;SeTF7{)L(*O-%}mJCeDz|i>5JESieP3TThfGdu)-Pld@M){(pCs&d( zJs(86)tj@x8-4KE# z+u4-VrD|SDO0`C*^yrg3|ACFHT(maC@xgL+Z-qV5xQLqkFwmHbI{D^P$rW7L^|2vI zGMy%Y%Yvo9IuJ?;Ylm@Y*j0;Wn_h}?d;;hjmXh|coLYgan5C!M5K0MWhcVK46nlA;g%1fD}O0_5eX+?sc|t8>wI7-tXd?L9o*ehkjSm>p~1jQIbKjO z(w*~wb4)%|Z_^(GUXLgw#u_R~6(nWXOTda8iQHOQ%CZ6x)cd3khc#hZ6m`5bwCRbY z@K)iW51fsKpt!c()|)DKVfx{ciC{!)5gk`9XST9~(c&Mkfoi~t!b%lH<$QQTD9=uG z@nD+$@JTRJc z-HK~j$Vn*UA<}#H#_lf892eWWS0P!#TK4a{;y)CIQ#Z;|S z1Vr!3oU9nH>ZawVr>@P?t;PrPU>frJ;)YGk3r4KR1Lr?RsSBsWPvNvHF1&EC!UcKT zCe_JFAkf5(nf>eXVC=GkVS>YtIzcLvL?J(^@Ztk8Di@-mTF#Rs(eXj_^68!}^luo+ z@fYe($^`wF^3(nY;U1w-jf4+vkFrK|2$a5dv?AkIHO#W2H}x0r;`mfgpdo3UpmB2d za?GkKmYviN8PpxP zC-Z)r`6vIMVQ7Z@8HNguvL~PbWiJ7PNUjcE0T!=j$Ur$-VRVMWP+8vA%OCsv4(T~5 zIaW2{u|bQ{BR~Yp!wyluZIsZH$r+I;HyFlpT`_I|_Begbz>{&aJ+SrG_s8Qn||$nx53L#yGY*wL32BK9)M9scJqk z`3_;%y$d?a?0Fv>EbWS+PSBaCW`xJ^o#d-_at8ZPIA{lReJa7mPoI@9KC7*=9~U%` zh9vZri#FU~6ZTSUf`;pie3Z*1?vdNl>R9p_TC;(0^;{>Nc3(2Ot`cb=;O$BL!Nxfo ztoAD!!;(F9XEsP#vY=L=nBkyA$W`F48WR4FT--*QD2pIecfG3NZ5ef10>0Ns4^78wPNNNZ8n{Y++)ByBb63F0lL-}T|X zKXDBwNd_AUD(MglQA0H$nnX(D;$nUFUo$58DT`>OO2%t4rza@jO4Q#n3a11Zv|(3`vb>!xa{6y0#8JwRihTx8irAnTnRvnF@ow1#!CpH zVx|39F2*7vY?>MSVQ$!yi2{Gq`dO0Esf=e(l%iOwPRn=96%xfU(W%xdqeSvI^G(2V ztzN~TM@Mfv;qTAlHz z6;eW=A8p_|NW5^Ej-c|RZ9+NdaG7sKgZ~AqV~va+Erg;Xb%QT0?S~iLs^tWdW?usD zliSV60iY!l9!K{3>+j&9g%=lSz>&@e3OniwcVwsZ%Az8i2r z%B6tY0D%y^90$tCBjedC;t|Gt1kpLB`)9$4g5(AA-R+X_W$~k{r4!?f0*N|fjIRV6 zlcdE1ODb{v#)1}uWJ$e5pOv&+riv(76JA4kM_$}Kw@oXSVcZgalx_a=9-{Qm)_h(H z)EtO-^}E@6>2ZcCIGbcb^NhdqM%Nq%nu7MT2R}iESz+>!}F7 zkaUXIYQPwJmrKFUqb!@U>U1$x-&0 zKa(_LI)S#6n~jL$#5LJCQ^B4g$#~s7TvU>7O?p{;+hnDX#OSJSsYajmz=)sPAGX|F z28V|Fu!#b*ij?y^d?36aiu1ta151$hB6HSM&t09n+`@QXg39jqWai0=TNi&RnQ*}R>Q(m(&VjOmZk$)XW_ddCGxv#q zX+Qs(w~e0H(bs1`!>@BN4 zK3L)ZZbtdIB{d&t*foK2pp8cz2pwy~a38xoz^a)OVPr9%U5St;4!Bg41%j5j&MBA3 z%eqZw<2$#~glEkD9AXnrGYhC6ii^&LPZ|F-4USmzbx}yk;T9Xt({{P)I?3cy+`Z?o zUB7CnOtE6`K58g3FHP;)q=b5$nGhjP0kCtOGsI{2Z zJQebH0OA4)-__!R5?7eIh_N-0_BTeXEt>;&l>oz-1dH9V)^MOklMQTCAnH;M>m@j3 z5xkWa>>ck~wLrJo-()X}W>rpQU%?FqoV~ezPk|I|D})Okg-#tQu=~I+K2~Ur`kJJUyL0YS`F1sbHAuR}Plo@Uoh;B(VYQwQ)u%0Q15Am1Mmo0`S z3}`a#hY%jLB%Zenmz*+MfitMEfcr~?tJ6qvsLKkYy3Pu1644JP>rhmp!*_&eQzq$Y z-CX36{hN&_J1d<%`bcy(>%%tXlx}Jmmy86D`*lLZ9tEEvuBA>TfjkpKWWpHc6|CFl zhnvbhCM+ZGi@Hphi}y?Sn_YX`-AmM!&sT87*>cuwjWBs9tRt0o8?6;6NAPSaeGFLc zI08+%W%grJ;H5?o(j3sKLgGLcLl~LkUA2&8GieWlQo#c+0}%`|Oto_$nPdxT@%gi$ z2j8R39U&Vew2nCRHIpNY44R89_|pu8xD@eh3Oy9-aP`3^oY~PtCxU~26R={A(GHGA zI=e!nNtS@mFr^EreZaWR15C;!+%=f+3IkY456-_@#&q{Ga#~lV zoD75jOE|f3JM%)U&n)opB#DpnENf? z+8?Ix1$4 zH&dhmNNXqijTjDsiEDm|w82s_sMvJ1a#*)z9M{oANoriFX8PfQZiCQoPEjbu?c#q+ z1xRG^qR0#+&BO~F6qLpc%<&Qid1C}|NxX6bFdWke{KXh?l&)mVd^TVi1d-{5K-6JW zvYNp4Mya&GVO$3z(+87{bH&loT>A=LNkKytu#+S(B_gcGru);~4~cQi+wv?keK9I# zr0}Gjfg!|bl0!7p>!L8NP5u>p!f+yA=`GCPD}n23=zAQHTY#X_4PCqw&XhUtNfU*F zkMGq7U`h>5qyAaQtkG$KRn4{lMJlS0DGa2oU<@yW^#{&j0_MaHbpfI}`3=f&t7!tF zAecJ9*H5_eq%Yrtto0iRZC2C9UsgiQ62w>_K?RoZhyIp<{`dR^4Ermd!yhge|4i+gYYotlbL6vErn6#87mUTn0*8+=DhndZQ zaLO{rD1@`Esse=x^4I&^e!pmmW`2B+d#zL;gD_-fhqfB-j1onIYrE8b05=6Ge{m#A zf-9M&`l6=kwexlEoZ@WhC*kjNQsAsXBTE=DT29d9r`wqG^EIzjVs+soRJtXVtmI0X zcgl;hPLA1%K_shE=0DX$({k$cl_zbnsmtyU9+|4SWTNtaV;?>D**3ZhA7X2M?=zPg zm(uYN^A#FvB*`qg{sR*&Z-&&T5pbDCfN@RAu!K@isK1Rz&+8(<=D9!c6+(q)F;^&% zwjl!N21Y%Mp{vKe;2~La|1(gAXg75`L+9*gsD#U|F-rZ34=No_QP%dAx zyJj>wo~Ymk68jws(Y`KVkc`3dveLXddXWE|@yHX+Hi2+XOmPI!fO-0YfwpdtZbZ9v zqaG|Cd?|_j((PzIiS=}{o+OINi)#dAZ@!FBm)eE=N#YP?NceKCf)r@Ri%`-Y=%K)J$A!95o^ zJ&ZrGxyzNErbDtKH=F|vUsQKj7tvB^7$3$iCT*TxHvr9F>xF3*#_SpCmfkp^$hY44 zA5(oqY^Q{MB76_WrYu2|0S z@Zu%tB8eZ)55i6cR?eb>^O<`?8l~bk)=>Nsu)U93>*tHxFztKm=gZqL?JgUB!9<{G z&1q0IGnc!WZ?7eGJGcC*IpdqC0b;}HZ}NlF_Ls_uwqC7*Entp!OQ10ufRP*kF*X8h z7ULM9;n*v1oq}Z;)El=$cjRTHs{I+-LKi+=^Y6S5dRc{{;X7-3n2^D`6G55pJ;Fosl=ctkX; zK&tt&G@1x0nk!2uk`P1xqJv4Z;U`t} z-6X}v{z-%X_~Xv80m)~ioswNL@L4LBm}6wqnrAwNAj=P3coKnGm9CMNSR7$qBCxJI z#I(RlgygYXo0untIV?USy6Npv7oQ{+O58%Pdq`dmFzy^{P#ER@kl;~6F67)fz=UUy z2X=UWr7Fo0oVB24f58VlGt6Wpci#^qWFPE9_Q5Gzm5IGBS!ic3gvSwW>bO`w^zjJ@{ULrf>7`+e3@*?Hb0UjLiw-#Qq{`S3H6gws0!|`9iaao0 z-}&{Rp&Q0LArhs+0V%O53nWOP?RmTS+eP36AARzH7zwJNySyWL-5Nr{!v`CSf=s0u zghq&%>L)%SS)+?YWA|A8dFB*3tPKMCT0=kBfNzLZ3g3+Qo=sUW9UpfCZizEbRy5Ff zBtRBT0~!Mp2GvM}df72p8mRUg4~az0gF+=vGMR`$PzF6j0L;euF%HdZ5l)Vj3LN5A zAzu}EWOVc!Bt1k&PtuR zO%&y41&~@(D}m>BnV`-zqN}JU%DMhtEZyDcU3Kj8@l|L8w^!PcajHxSMpZr_KJ>F` zUtl?(cLzP(4x>gYuUCeQL>lv17eU@wjM8%{;xJC*T|+_=y5xeQB=Y5e9Ze1V=^rJ6 z&gn-YW*Eu!CCs4NxLvB(CFeIAG2oC^b@>iSB?nGbE}R7uJa=GT1(Gb!f^Vgr%o9Bd z6OJ+x!{N!KH@D9Z#pP}$+=x#ZH5K|$*QW_gNlcKl*oHC#tQA4!h+mMLRuDr_G?now zX3;GdQsEE!+@r(mX5Jx->#rZK2@lf{{wMtRH=*wY*nx4lp+C&qC}iRbLsp2Y#6{lp zSfK;Ff{58{^pD44h=={-O>+G=Il7!yb%}C|sLq;6DbyAV9mdh?2qm$2c$2(cKfd*m zc4Oghh;K%}i9tKCF|3I-V8jThzBT5-7n^R0Y?KtaR`^NdQb53j12`rL*7D+h?IF@A zh#*`;awJ_inDftHdgy=WsU8BS1(u9$yZe*(WVmYQ9AV1*UW(jPV#1_D2qhG|g`o>G zG{&uWKlq)>#Nizvxe}l=O_C_fx+*68M@t8CG$eX9p;~Pv8EQDO5Iw?%CS0>v$rvpw z4yHt(o)S-+DB3Sn=3%=vJDby9R+yvN@V!v**oOf`4RQs8x`WCT#$yzrI@8=Pg zbj17)CUsOYm?IkHU8filj)4xIc^V_-M3oWfZ#ZW##DDUa4BrB(7Wg^fQgt>{SCl9v z{Ii8o0Shg;cM?e~{Zv8PCDYZ&nc|_00#HT+Y^JR!F{i~mbE`EyF?{~l9@|M&bx z0f-al#47L*1i14l-Fk!Vy+RG%BgY&U{vYLhLbu*cBf=a%f@J@-3=bgAl@qJaLs0Ji z9}>L(I{Kpo4Is{t6YI=F(C5y##-9_bivbWP%!w7?AxL!R!?^X1+Iz(syswWuHbMf7 z`~VI-1gU%2xW#G1_l%Lp`@H!5Kf(wbFmmY5w{+_bxBsd!^dDk`2N)6KAt-d`3%&KO z+JD6wzQ6st_5NsZ=Y#lpZSOUB@IE5^7!?gbPM9OF!jtRo&If$!9k~Bmga%ll7-Gqh zSK!ILxJ8ot5u@&Wlq2^MF~_J_0CKATAv^ATjC-$OL-){8$Ed#mB1hhUC-=;qFXYy{!;dGohbBi}kS90Lo$vRpH`(4R+|WIC z_%Sm&z>F{lAi$Hm>&{0##LOne7=6r)1~BvD3I0zo-g8DAJ0Sy(DRKZm)m6IlLEd^J?7iZR+w z)GJ0>@Z|pV?DjrU){jWN_129!cKQW4X2=1k{tp?s^|t*{5_#-|1vuu)0sJ)LngDS8 zGk2Vj0LO$m^b$OJ;qE;Gx8A^euR=rj;Ni!xNB~TZ9D1qSsAwgb{a28o`yqV9-5(JW zb^O%%I?mh0BJbM1_JwUhK9QmkJ zsGNmN0m@XUNb|r4i9cd743(vtiSbC4Y$yWVTGf%QnMttThhx=DOdrHdoy^kHq`}D7 z1cXe3%stekpj3m5V)%Cjxh9!dl6X*-Y5G5oScY8l^nVhuH@?cWiI~&PGBd^#V4~ca zd@A>0?jXq1gn^VI9AQx8$km#IcPx9;9wH3!;bth$8B&8z5uQvA%htphax6!veZj;z zicKYGQ=Fna<>r%A1s0-4y0@gLf9bO|gO&f9+up{>(czD;4heH~yN4fPB0hrc6J;3i zh$u&e4dd+_I3�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 Date: Mon, 20 Oct 2025 15:43:57 +0200 Subject: [PATCH 06/14] Fix cue note from Trill lines ignored in horizontal spacing --- src/engraving/dom/chord.cpp | 3 +++ src/engraving/dom/segment.cpp | 32 ++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/engraving/dom/chord.cpp b/src/engraving/dom/chord.cpp index b510acd690901..ec633f4205343 100644 --- a/src/engraving/dom/chord.cpp +++ b/src/engraving/dom/chord.cpp @@ -2720,6 +2720,9 @@ Ornament* Chord::findOrnament(bool forPlayback) const } } if (forPlayback) { + // TODO: cleanup. + // We shouldn't do this kind of special cases, and the DOM shouldn't know anything about playback. + // This should be in a different function that returns the ornament from the Trill ending on this chord. [MS] for (Spanner* spanner : m_endingSpanners) { if (spanner->isTrill()) { return toTrill(spanner)->ornament(); diff --git a/src/engraving/dom/segment.cpp b/src/engraving/dom/segment.cpp index 9b0f4a950ad6c..220ecaa983ebb 100644 --- a/src/engraving/dom/segment.cpp +++ b/src/engraving/dom/segment.cpp @@ -2738,21 +2738,25 @@ void Segment::addArticulationsToShape(const Chord* chord, Shape& shape) }; for (Articulation* art : chord->articulations()) { - if (art->isOrnament()) { - Chord* cueNoteChord = toOrnament(art)->cueNoteChord(); - if (cueNoteChord && cueNoteChord->upNote()->visible()) { - shape.add(cueNoteChord->shape().translate(cueNoteChord->pos() + cueNoteChord->staffOffset())); - } - } else if (art->addToSkyline()) { - shape.add(art->shape().translated(art->pos() + chord->pos())); - if (art->isTapping()) { - if (TappingHalfSlur* halfSlur = toTapping(art)->halfSlurAbove()) { - addTappingHalfSlurToShape(halfSlur); - } - if (TappingHalfSlur* halfSlur = toTapping(art)->halfSlurBelow()) { - addTappingHalfSlurToShape(halfSlur); - } + if (art->isOrnament() || !art->addToSkyline()) { + continue; + } + shape.add(art->shape().translated(art->pos() + chord->pos())); + if (art->isTapping()) { + if (TappingHalfSlur* halfSlur = toTapping(art)->halfSlurAbove()) { + addTappingHalfSlurToShape(halfSlur); } + if (TappingHalfSlur* halfSlur = toTapping(art)->halfSlurBelow()) { + addTappingHalfSlurToShape(halfSlur); + } + } + } + + Ornament* ornament = chord->findOrnament(); + if (ornament) { + Chord* cueNoteChord = ornament->cueNoteChord(); + if (cueNoteChord && cueNoteChord->upNote()->addToSkyline()) { + shape.add(cueNoteChord->shape().translate(cueNoteChord->pos() + cueNoteChord->staffOffset())); } } } From 4507261ce6d8741e5ec6679caefe341382f19352 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Mon, 20 Oct 2025 15:58:40 +0200 Subject: [PATCH 07/14] vtest --- vtest/scores/ornament-11.mscz | Bin 0 -> 22163 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 vtest/scores/ornament-11.mscz diff --git a/vtest/scores/ornament-11.mscz b/vtest/scores/ornament-11.mscz new file mode 100644 index 0000000000000000000000000000000000000000..56f1da75f10695707893545cedbb7ef9319d5093 GIT binary patch literal 22163 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 literal 0 HcmV?d00001 From f803ff08f2ed58138d8254ac185a1cb48f99ad1a Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Mon, 27 Oct 2025 15:24:50 +0100 Subject: [PATCH 08/14] Fix measure number collides with bracket Should use effectiveStaffIdx as opposed to checking its "original" staff because staves may be hidden in between. --- src/engraving/rendering/score/measurenumberlayout.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engraving/rendering/score/measurenumberlayout.cpp b/src/engraving/rendering/score/measurenumberlayout.cpp index 2ee42a2ad3b22..1c7bcc3cb85a9 100644 --- a/src/engraving/rendering/score/measurenumberlayout.cpp +++ b/src/engraving/rendering/score/measurenumberlayout.cpp @@ -136,7 +136,9 @@ void MeasureNumberLayout::layoutMeasureNumberBase(MeasureNumberBase* item, Measu double yoff = 0.0; // If there is only one line, the barline spans outside the staff lines, so the default position is not correct. - if (item->staff()->constStaffType(item->measure()->tick())->lines() == 1) { + staff_idx_t effectiveStaffIdx = item->effectiveStaffIdx(); + Staff* staff = item->score()->staff(effectiveStaffIdx); + if (staff && staff->lines(item->tick()) == 1) { yoff -= 2.0 * item->spatium(); } From 84566be15d86db55d52ba7089031e74ceb6d7df5 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Mon, 27 Oct 2025 13:19:08 +0100 Subject: [PATCH 09/14] Update grip anchor lines on edit --- src/notation/internal/notationinteraction.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index c0e356ce9b27c..d03333aaed979 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -4802,6 +4802,10 @@ void NotationInteraction::editElement(QKeyEvent* event) } apply(); + + if (isGripEditStarted()) { + updateGripAnchorLines(); + } } else { rollback(); } From 0721c2b3dc7168719acf7bafc83f726c345d13cc Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Tue, 28 Oct 2025 13:40:34 +0100 Subject: [PATCH 10/14] Don't reanchor line on arrow movements --- src/engraving/dom/line.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engraving/dom/line.cpp b/src/engraving/dom/line.cpp index 7f07309f573bc..384cb227acfbe 100644 --- a/src/engraving/dom/line.cpp +++ b/src/engraving/dom/line.cpp @@ -594,7 +594,7 @@ void LineSegment::rebaseAnchors(EditData& ed, Grip grip) // don't change anchors on keyboard adjustment or if Ctrl is pressed // (Ctrl+Left/Right is handled elsewhere!) - if (ed.key == Key_Left || ed.key == Key_Right || ed.modifiers & ControlModifier) { + if (ed.key == Key_Left || ed.key == Key_Right || ed.key == Key_Up || ed.key == Key_Down || ed.modifiers & ControlModifier) { return; } From d004109db7f6465055c0253a941eb88844dc0b80 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Mon, 27 Oct 2025 11:14:09 +0100 Subject: [PATCH 11/14] Ensure gap rests are properly created on move note to second voice --- src/engraving/dom/edit.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/engraving/dom/edit.cpp b/src/engraving/dom/edit.cpp index aa270c566c66b..1c0568224c71f 100644 --- a/src/engraving/dom/edit.cpp +++ b/src/engraving/dom/edit.cpp @@ -499,9 +499,10 @@ std::vector Score::setRests(const Fraction& _tick, track_idx_t track, con f = l; } - // Don't fill with rests a non-zero voice, *unless* it has links in voice zero - bool emptyNonZeroVoice = track2voice(track) != 0 && !measure->hasVoice(track) && tick == measure->tick(); - if (emptyNonZeroVoice && !staff->trackHasLinksInVoiceZero(track)) { + // Don't fill a full measure with rests on a non-zero voice, *unless* it has links in voice zero + bool fullMeasure = tick == measure->tick() && f == measure->stretchedLen(staff); + bool emptyNonZeroVoice = track2voice(track) != 0 && !measure->hasVoice(track); + if (emptyNonZeroVoice && fullMeasure && !staff->trackHasLinksInVoiceZero(track)) { l -= f; measure = measure->nextMeasure(); if (!measure) { @@ -512,10 +513,9 @@ std::vector Score::setRests(const Fraction& _tick, track_idx_t track, con } if ((measure->timesig() == measure->ticks()) // not in pickup measure - && (measure->tick() == tick) - && (measure->stretchedLen(staff) == f) + && fullMeasure && !tuplet - && (useFullMeasureRest)) { + && useFullMeasureRest) { Rest* rest = addRest(tick, track, TDuration(DurationType::V_MEASURE), tuplet); tick += rest->actualTicks(); rests.push_back(rest); @@ -538,6 +538,8 @@ std::vector Score::setRests(const Fraction& _tick, track_idx_t track, con Rest* rest = 0; for (const TDuration& d : dList) { rest = addRest(tick, track, d, tuplet); + // If we're filling an empty non-zero voice make these gaps + rest->setGap(emptyNonZeroVoice && !fullMeasure); rests.push_back(rest); tick += rest->actualTicks(); } From 260beed3fda6949f0d2427c5179873f433723389 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Mon, 27 Oct 2025 11:23:46 +0100 Subject: [PATCH 12/14] Use ChordRest ticks, not Segment ticks to determine interruption points for rest alignement --- src/engraving/rendering/score/restlayout.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engraving/rendering/score/restlayout.cpp b/src/engraving/rendering/score/restlayout.cpp index 34cb4d724d7c8..247aded579dc8 100644 --- a/src/engraving/rendering/score/restlayout.cpp +++ b/src/engraving/rendering/score/restlayout.cpp @@ -509,7 +509,7 @@ InterruptionPoints RestLayout::computeInterruptionPoints(const Measure* measure, if (gapRest || hasMergedRest || invisible) { for (voice_idx_t voice = 0; voice < VOICES; ++voice) { interruptionPointSets[voice].insert(segment->rtick()); - interruptionPointSets[voice].insert(segment->rtick() + segment->ticks()); + interruptionPointSets[voice].insert(segment->rtick() + toChordRest(item)->actualTicks()); } break; } From 81d1be5e5fc27b703494c50152efb21edb7023b0 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Mon, 27 Oct 2025 11:54:17 +0100 Subject: [PATCH 13/14] Add unit test incl. fixing a merge conflict --- .../voiceswitching_data/voiceswitching-2.mscx | 192 ++++++++++++++++++ src/engraving/tests/voiceswitching_tests.cpp | 29 +++ 2 files changed, 221 insertions(+) create mode 100755 src/engraving/tests/voiceswitching_data/voiceswitching-2.mscx diff --git a/src/engraving/tests/voiceswitching_data/voiceswitching-2.mscx b/src/engraving/tests/voiceswitching_data/voiceswitching-2.mscx new file mode 100755 index 0000000000000..e6459e7700e90 --- /dev/null +++ b/src/engraving/tests/voiceswitching_data/voiceswitching-2.mscx @@ -0,0 +1,192 @@ + + + 4.7.0 + + + sFMlmwzzOnG_ILNRNamr+pG + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2025-10-27 + + + + Linux + + + 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 +
+ +
+ + + yLTbtpuYjrL_2Upa4+LsM9L + + stdNormal + + + 1 + + Piano + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + 100 + 95 + + + 100 + 33 + + + 100 + 50 + + + 100 + 67 + + + 100 + 100 + + + 120 + 67 + + + 150 + 100 + + + 150 + 50 + + + 120 + 50 + + + 120 + 100 + + + + Fluid + + + + + + R0Ms9HgYTs_Z6F7QI7s5JN + + + D/xXUPh7WdE_reZf5mJn+rN + 0 + + + EFsX1i3AOML_g3rEJvJe0FL + 4 + 4 + + + LhFBK1lGKgE_4Oqi75gCRpL + quarter + + kEiL+JIyKaB_Br8MHf3MwqC + 67 + 15 + + + + NbZC+YdlrQE_aV2BwgrVuDN + quarter + + BCUbEFHVj2M_gg/VFZrm7sO + 67 + 15 + + + + 9XshPS74OBP_hssb+Zs/VLI + quarter + + 1Pe/TGT9rzK_VdIu9Juc2F + 67 + 15 + + + + UjXt9EC39YL_LJGi/kylO4C + quarter + + nBCZaNYI51D_PXlNUGn76+N + 67 + 15 + + + + + +
+
diff --git a/src/engraving/tests/voiceswitching_tests.cpp b/src/engraving/tests/voiceswitching_tests.cpp index 129d58a4a98df..a7a08d388cfa3 100644 --- a/src/engraving/tests/voiceswitching_tests.cpp +++ b/src/engraving/tests/voiceswitching_tests.cpp @@ -24,6 +24,7 @@ #include "dom/chord.h" #include "dom/note.h" +#include "dom/rest.h" #include "utils/scorerw.h" @@ -100,3 +101,31 @@ TEST_F(Engraving_VoiceSwitchingTests, voiceSwitching) delete score; } + +TEST_F(Engraving_VoiceSwitchingTests, voicesSwitchingGapRests) +{ + Score* score = ScoreRW::readScore(VOICESWITCHING_DATA_DIR + "voiceswitching-2.mscx"); + EXPECT_TRUE(score); + + Segment* segment = score->tick2segment(Fraction(3, 4), true, SegmentType::ChordRest); + EXPECT_TRUE(segment); + + //! [GIVEN] A measure with some notes in voice zero + Chord* chord = toChord(segment->element(0)); + EXPECT_TRUE(chord); + + //! [WHEN] The last note of the measure is selected and moved to voice one + score->select(chord->upNote()); + score->startCmd(TranslatableString("undoableAction", "Change voice")); + score->changeSelectedElementsVoice(1); + score->endCmd(); + + //! [THEN] Voice 1 should be filled with gap rests from the start of the measure + Segment* firstSeg = score->firstSegment(SegmentType::ChordRest); + EXPECT_TRUE(firstSeg); + + EngravingItem* item = firstSeg->element(1); + EXPECT_TRUE(item && item->isRest() && toRest(item)->isGap()); + + delete score; +} From 54c9b0a9b1c0affe12626e5bb28af33ae7c1ca93 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Fri, 24 Oct 2025 11:10:51 +0200 Subject: [PATCH 14/14] Don't show measure number if all staves invisible --- src/engraving/dom/measure.cpp | 2 +- src/engraving/dom/score.cpp | 11 +++++++++++ src/engraving/dom/score.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/engraving/dom/measure.cpp b/src/engraving/dom/measure.cpp index ff45c94dd9deb..9d2f5bc05dedf 100644 --- a/src/engraving/dom/measure.cpp +++ b/src/engraving/dom/measure.cpp @@ -600,7 +600,7 @@ bool Measure::showMeasureNumberOnStaff(staff_idx_t staffIdx) const return false; } - return showMeasureNumber() && score()->staff(staffIdx)->shouldShowMeasureNumbers(); + return showMeasureNumber() && score()->staff(staffIdx)->shouldShowMeasureNumbers() && !score()->allStavesInvisible(); } //--------------------------------------------------------- diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index 97d82d1f4aab4..14451e389f23a 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -5874,6 +5874,17 @@ size_t Score::visibleStavesCount() const return count; } +bool Score::allStavesInvisible() const +{ + for (const Staff* staff : m_staves) { + if (staff->show()) { + return false; + } + } + + return true; +} + ShadowNote* Score::shadowNote() const { return m_shadowNote; diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index ef54541083906..f841c8e178c9f 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -466,6 +466,7 @@ class Score : public EngravingObject, public muse::Injectable const std::vector& staves() const { return m_staves; } size_t nstaves() const { return m_staves.size(); } size_t visibleStavesCount() const; + bool allStavesInvisible() const; size_t ntracks() const { return m_staves.size() * VOICES; } staff_idx_t staffIdx(const Staff*) const;