From f026e2731edee1117356f10897af5c61d2def605 Mon Sep 17 00:00:00 2001 From: XiaoMigros Date: Sun, 31 Aug 2025 16:48:40 +0200 Subject: [PATCH 1/7] Refactor naturals in key signatures --- .../rendering/score/measurelayout.cpp | 2 +- src/engraving/rendering/score/tlayout.cpp | 135 ++++++------------ 2 files changed, 44 insertions(+), 93 deletions(-) diff --git a/src/engraving/rendering/score/measurelayout.cpp b/src/engraving/rendering/score/measurelayout.cpp index bcc11c8de8f67..c38414cfb7be7 100644 --- a/src/engraving/rendering/score/measurelayout.cpp +++ b/src/engraving/rendering/score/measurelayout.cpp @@ -2561,7 +2561,7 @@ Segment* MeasureLayout::addHeaderKeySig(Measure* m, bool isFirstKeysig, const St KeySigEvent keyIdx = staff->keySigEvent(m->tick()); KeySig* ksAnnounce = 0; if ((isFirstKeysig || ctx.conf().styleB(Sid::genKeysig)) && (keyIdx.key() == Key::C)) { - Measure* pm = m->prevMeasure(); + Measure* pm = m->prevMeasureMM(); if (pm && pm->hasCourtesyKeySig()) { Segment* ks = pm->first(SegmentType::KeySigAnnounce); if (ks) { diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index d4c0fec38864c..ffb47365c800f 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -3653,7 +3653,7 @@ static void keySigAddLayout(const KeySig* item, const LayoutConfiguration& conf, KeySym ks; ks.sym = sym; double x = 0.0; - if (ldata->keySymbols.size() > 0) { + if (!ldata->keySymbols.empty()) { const KeySym& previous = ldata->keySymbols.back(); double accidentalGap = conf.styleS(Sid::keysigAccidentalDistance).val(); if (previous.sym != sym) { @@ -3688,6 +3688,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const // return; // } + const Segment* s = item->segment(); double spatium = item->spatium(); double step = spatium * (item->staff() ? item->staff()->staffTypeForElement(item)->lineDistance().val() * 0.5 : 0.5); @@ -3703,11 +3704,10 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const if (item->staff()) { // Look for a clef before the key signature at the same tick Clef* c = nullptr; - if (item->segment()) { - for (Segment* seg = item->segment()->prev1(); !c && seg && seg->tick() == item->tick(); seg = seg->prev1()) { - const bool isClefSeg - = (seg->isClefType() || seg->isHeaderClefType() - || (seg->isClefRepeatAnnounceType() && item->segment()->isKeySigRepeatAnnounceType())); + if (s) { + for (Segment* seg = s->prev1(); !c && seg && seg->tick() == item->tick(); seg = seg->prev1()) { + const bool isClefSeg = seg->isClefType() || seg->isHeaderClefType() + || (seg->isClefRepeatAnnounceType() && s->isKeySigRepeatAnnounceType()); if (seg->enabled() && isClefSeg) { c = toClef(seg->element(item->track())); } @@ -3741,7 +3741,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const int lineIndexOffset = t1 > 0 ? -1 : 6; ks.sym = t1 > 0 ? SymId::accidentalSharp : SymId::accidentalFlat; ks.line = ClefInfo::lines(clef)[lineIndexOffset + i]; - if (ldata->keySymbols.size() > 0) { + if (!ldata->keySymbols.empty()) { const KeySym& previous = ldata->keySymbols.back(); double previousWidth = item->symWidth(previous.sym) / spatium; ks.xPos = previous.xPos + previousWidth + accidentalGap; @@ -3760,7 +3760,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const accIdx = flat ? 13 - accIdx : accIdx; int line = ClefInfo::lines(clef)[accIdx] + cd.octAlt * 7; double xpos = cd.xAlt; - if (ldata->keySymbols.size() > 0) { + if (!ldata->keySymbols.empty()) { const KeySym& previous = ldata->keySymbols.back(); double previousWidth = item->symWidth(previous.sym) / spatium; xpos += previous.xPos + previousWidth + accidentalGap; @@ -3791,62 +3791,41 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const } } } else { - int accidentals = 0, naturals = 0; - switch (std::abs(t1)) { - case 7: accidentals = 0x7f; - break; - case 6: accidentals = 0x3f; - break; - case 5: accidentals = 0x1f; - break; - case 4: accidentals = 0xf; - break; - case 3: accidentals = 0x7; - break; - case 2: accidentals = 0x3; - break; - case 1: accidentals = 0x1; - break; - case 0: accidentals = 0; - break; - default: - LOGD("illegal t1 key %d", t1); - break; - } - - // manage display of naturals: - // naturals are shown if there is some natural AND prev. measure has no section break - // AND style says they are not off - // OR key sig is CMaj/Amin (in which case they are always shown) - - bool naturalsOn = false; - Measure* prevMeasure = item->measure() ? item->measure()->prevMeasure() : 0; - - // If we're not force hiding naturals (Continuous panel), use score style settings - if (!item->hideNaturals()) { - const bool newSection = (!item->segment() - || (item->segment()->rtick().isZero() && (!prevMeasure || prevMeasure->sectionBreak())) - ); - naturalsOn = !newSection && (conf.styleI(Sid::keySigNaturals) != int(KeySigNatural::NONE) || (t1 == 0)); - } - - // Don't repeat naturals if shown in courtesy - if (item->measure() && item->measure()->system() && item->measure()->isFirstInSystem() - && prevMeasure && prevMeasure->findSegment(SegmentType::KeySigAnnounce, item->tick()) - && !item->segment()->isKeySigAnnounceType()) { - naturalsOn = false; - } - if (item->track() == muse::nidx) { - naturalsOn = false; - } + auto key2accidentals = [](int key) -> int { + switch (std::abs(key)) { + case 7: return 0x7f; + case 6: return 0x3f; + case 5: return 0x1f; + case 4: return 0xf; + case 3: return 0x7; + case 2: return 0x3; + case 1: return 0x1; + case 0: return 0; + default: + LOGD("illegal key %d", key); + return 0; + } + }; + int accidentals = key2accidentals(t1); + int naturals = 0; + + // Naturals are shown if: + // Key signature is courtesy, mid-measure or prev. measure has no section break and no courtesy keysig. + // AND we're not force hiding naturals (continuous mode) + // AND key sig is CMaj/Amin OR style says they are on + const Measure* prevMeasure = item->measure() ? item->measure()->prevMeasureMM() : nullptr; + bool naturalsOn = !item->hideNaturals() && item->track() != muse::nidx + && (conf.styleI(Sid::keySigNaturals) != int(KeySigNatural::NONE) || (t1 == 0)) + && ((s && (s->isType(SegmentType::CourtesyKeySigType) || !s->rtick().isZero())) + || (prevMeasure && !prevMeasure->sectionBreak() && !prevMeasure->hasCourtesyKeySig())); int coffset = 0; - Key t2 = Key::C; + Key t2 = Key::C; if (naturalsOn) { if (item->staff()) { t2 = item->staff()->key(item->tick() - Fraction::eps()); } - if (item->segment() && item->segment()->isType(SegmentType::KeySigStartRepeatAnnounce)) { + if (s && s->isType(SegmentType::KeySigStartRepeatAnnounce)) { // Handle naturals in continuation courtesy Segment* prevCourtesySeg = prevMeasure ? prevMeasure->findSegmentR(SegmentType::KeySigRepeatAnnounce, prevMeasure->ticks()) : nullptr; @@ -3856,27 +3835,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const if (t2 == Key::C) { naturalsOn = false; } else { - switch (std::abs(int(t2))) { - case 7: naturals = 0x7f; - break; - case 6: naturals = 0x3f; - break; - case 5: naturals = 0x1f; - break; - case 4: naturals = 0xf; - break; - case 3: naturals = 0x7; - break; - case 2: naturals = 0x3; - break; - case 1: naturals = 0x1; - break; - case 0: naturals = 0; - break; - default: - LOGD("illegal t2 key %d", int(t2)); - break; - } + naturals = key2accidentals(int(t2)); // remove redundant naturals if (!((t1 > 0) ^ (t2 > 0))) { naturals &= ~accidentals; @@ -3887,19 +3846,13 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const } } - // naturals should go BEFORE accidentals if style says so - // OR going from sharps to flats or vice versa (i.e. t1 & t2 have opposite signs) - - bool prefixNaturals = naturalsOn - && (conf.styleI(Sid::keySigNaturals) == int(KeySigNatural::BEFORE) - || t1 * int(t2) < 0); - - // naturals should go AFTER accidentals if they should not go before! - bool suffixNaturals = naturalsOn && !prefixNaturals; + // Naturals should go BEFORE accidentals if style says so + // or going from sharps to flats or vice versa (i.e. t1 & t2 have opposite signs) + const bool natBefore = conf.styleI(Sid::keySigNaturals) == int(KeySigNatural::BEFORE) || t1 * int(t2) < 0; const signed char* lines = ClefInfo::lines(clef); - if (prefixNaturals) { + if (naturalsOn && natBefore) { for (int i = 0; i < 7; ++i) { if (naturals & (1 << i)) { keySigAddLayout(item, conf, SymId::accidentalNatural, lines[i + coffset], ldata); @@ -3915,9 +3868,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const } else { LOGD("illegal t1 key %d", t1); } - - // add suffixed naturals, if any - if (suffixNaturals) { + if (naturalsOn && !natBefore) { for (int i = 0; i < 7; ++i) { if (naturals & (1 << i)) { keySigAddLayout(item, conf, SymId::accidentalNatural, lines[i + coffset], ldata); From a65da1209e60e39b2001de160b6e0738574c9002 Mon Sep 17 00:00:00 2001 From: XiaoMigros Date: Mon, 22 Sep 2025 21:15:39 +0200 Subject: [PATCH 2/7] Option to hide naturals for changing #/b --- src/engraving/rendering/score/tlayout.cpp | 5 ++++- src/engraving/style/styledef.cpp | 1 + src/engraving/style/styledef.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index ffb47365c800f..475eb2856a94a 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -3846,9 +3846,12 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const } } + const bool sameAccidentals = t1 * int(t2) > 0; + // Don't show naturals when going from sharps to flats, if style says so + naturalsOn = sameAccidentals || conf.styleB(Sid::keySigShowNaturalsChangingSharpsFlats); // Naturals should go BEFORE accidentals if style says so // or going from sharps to flats or vice versa (i.e. t1 & t2 have opposite signs) - const bool natBefore = conf.styleI(Sid::keySigNaturals) == int(KeySigNatural::BEFORE) || t1 * int(t2) < 0; + const bool natBefore = conf.styleI(Sid::keySigNaturals) == int(KeySigNatural::BEFORE) || !sameAccidentals; const signed char* lines = ClefInfo::lines(clef); diff --git a/src/engraving/style/styledef.cpp b/src/engraving/style/styledef.cpp index fe59d2730a7be..b0bc693bad695 100644 --- a/src/engraving/style/styledef.cpp +++ b/src/engraving/style/styledef.cpp @@ -701,6 +701,7 @@ const std::array StyleDef::styleValue styleDef(linearStretch, PropertyValue(double(1.5))), styleDef(crossMeasureValues, false), styleDef(keySigNaturals, PropertyValue(int(KeySigNatural::NONE))), + styleDef(keySigShowNaturalsChangingSharpsFlats, true), styleDef(tupletMaxSlope, PropertyValue(double(0.5))), styleDef(tupletOutOfStaff, true), diff --git a/src/engraving/style/styledef.h b/src/engraving/style/styledef.h index 329c1bcfd5940..1f8903416d25a 100644 --- a/src/engraving/style/styledef.h +++ b/src/engraving/style/styledef.h @@ -719,6 +719,7 @@ enum class Sid { linearStretch, crossMeasureValues, keySigNaturals, + keySigShowNaturalsChangingSharpsFlats, tupletMaxSlope, tupletOutOfStaff, From a66ad785de3299ae8d241da6ca4fc4ea1a067505 Mon Sep 17 00:00:00 2001 From: XiaoMigros Date: Mon, 1 Sep 2025 09:21:53 +0200 Subject: [PATCH 3/7] Simplify keysig read/write --- src/engraving/rw/read400/tread.cpp | 28 ++------------------------- src/engraving/rw/read410/tread.cpp | 31 +++--------------------------- src/engraving/rw/read460/tread.cpp | 31 +++--------------------------- src/engraving/rw/write/twrite.cpp | 9 ++------- 4 files changed, 10 insertions(+), 89 deletions(-) diff --git a/src/engraving/rw/read400/tread.cpp b/src/engraving/rw/read400/tread.cpp index 27b30c96fa51f..8278f9c3997f8 100644 --- a/src/engraving/rw/read400/tread.cpp +++ b/src/engraving/rw/read400/tread.cpp @@ -1207,8 +1207,7 @@ void TRead::read(KeySig* s, XmlReader& e, ReadContext& ctx) } } sig.customKeyDefs().push_back(cd); - } else if (tag == "showCourtesySig") { - s->setShowCourtesy(e.readInt()); + } else if (TRead::readProperty(s, tag, e, ctx, Pid::SHOW_COURTESY)) { } else if (tag == "showNaturals") { // obsolete e.readInt(); } else if (tag == "accidental") { // we need to guess proper concert key @@ -1228,30 +1227,7 @@ void TRead::read(KeySig* s, XmlReader& e, ReadContext& ctx) e.readInt(); sig.setCustom(true); } else if (tag == "mode") { - String m(e.readText()); - if (m == "none") { - sig.setMode(KeyMode::NONE); - } else if (m == "major") { - sig.setMode(KeyMode::MAJOR); - } else if (m == "minor") { - sig.setMode(KeyMode::MINOR); - } else if (m == "dorian") { - sig.setMode(KeyMode::DORIAN); - } else if (m == "phrygian") { - sig.setMode(KeyMode::PHRYGIAN); - } else if (m == "lydian") { - sig.setMode(KeyMode::LYDIAN); - } else if (m == "mixolydian") { - sig.setMode(KeyMode::MIXOLYDIAN); - } else if (m == "aeolian") { - sig.setMode(KeyMode::AEOLIAN); - } else if (m == "ionian") { - sig.setMode(KeyMode::IONIAN); - } else if (m == "locrian") { - sig.setMode(KeyMode::LOCRIAN); - } else { - sig.setMode(KeyMode::UNKNOWN); - } + sig.setMode(TConv::fromXml(e.readAsciiText(), KeyMode::UNKNOWN)); } else if (tag == "subtype") { subtype = e.readInt(); } else if (tag == "forInstrumentChange") { diff --git a/src/engraving/rw/read410/tread.cpp b/src/engraving/rw/read410/tread.cpp index 05cc2ff14cf07..03418373dcbbf 100644 --- a/src/engraving/rw/read410/tread.cpp +++ b/src/engraving/rw/read410/tread.cpp @@ -1323,8 +1323,7 @@ void TRead::read(KeySig* s, XmlReader& e, ReadContext& ctx) } } sig.customKeyDefs().push_back(cd); - } else if (tag == "showCourtesySig") { - s->setShowCourtesy(e.readInt()); + } else if (TRead::readProperty(s, tag, e, ctx, Pid::SHOW_COURTESY)) { } else if (tag == "showNaturals") { // obsolete e.readInt(); } else if (tag == "accidental") { // older files; we need to guess proper concert key @@ -1348,36 +1347,12 @@ void TRead::read(KeySig* s, XmlReader& e, ReadContext& ctx) e.readInt(); sig.setCustom(true); } else if (tag == "mode") { - String m(e.readText()); - if (m == "none") { - sig.setMode(KeyMode::NONE); - } else if (m == "major") { - sig.setMode(KeyMode::MAJOR); - } else if (m == "minor") { - sig.setMode(KeyMode::MINOR); - } else if (m == "dorian") { - sig.setMode(KeyMode::DORIAN); - } else if (m == "phrygian") { - sig.setMode(KeyMode::PHRYGIAN); - } else if (m == "lydian") { - sig.setMode(KeyMode::LYDIAN); - } else if (m == "mixolydian") { - sig.setMode(KeyMode::MIXOLYDIAN); - } else if (m == "aeolian") { - sig.setMode(KeyMode::AEOLIAN); - } else if (m == "ionian") { - sig.setMode(KeyMode::IONIAN); - } else if (m == "locrian") { - sig.setMode(KeyMode::LOCRIAN); - } else { - sig.setMode(KeyMode::UNKNOWN); - } + sig.setMode(TConv::fromXml(e.readAsciiText(), KeyMode::UNKNOWN)); } else if (tag == "subtype") { subtype = e.readInt(); } else if (tag == "forInstrumentChange") { sig.setForInstrumentChange(e.readBool()); - } else if (tag == "isCourtesy") { - s->setIsCourtesy(e.readBool()); + } else if (TRead::readProperty(s, tag, e, ctx, Pid::IS_COURTESY)) { } else if (!readItemProperties(s, e, ctx)) { e.unknown(); } diff --git a/src/engraving/rw/read460/tread.cpp b/src/engraving/rw/read460/tread.cpp index 222590b628f1b..2d7fef028aae0 100644 --- a/src/engraving/rw/read460/tread.cpp +++ b/src/engraving/rw/read460/tread.cpp @@ -1321,8 +1321,7 @@ void TRead::read(KeySig* s, XmlReader& e, ReadContext& ctx) } } sig.customKeyDefs().push_back(cd); - } else if (tag == "showCourtesySig") { - s->setShowCourtesy(e.readInt()); + } else if (TRead::readProperty(s, tag, e, ctx, Pid::SHOW_COURTESY)) { } else if (tag == "concertKey") { sig.setConcertKey(Key(e.readInt())); } else if (tag == "actualKey") { @@ -1331,36 +1330,12 @@ void TRead::read(KeySig* s, XmlReader& e, ReadContext& ctx) e.readInt(); sig.setCustom(true); } else if (tag == "mode") { - String m(e.readText()); - if (m == "none") { - sig.setMode(KeyMode::NONE); - } else if (m == "major") { - sig.setMode(KeyMode::MAJOR); - } else if (m == "minor") { - sig.setMode(KeyMode::MINOR); - } else if (m == "dorian") { - sig.setMode(KeyMode::DORIAN); - } else if (m == "phrygian") { - sig.setMode(KeyMode::PHRYGIAN); - } else if (m == "lydian") { - sig.setMode(KeyMode::LYDIAN); - } else if (m == "mixolydian") { - sig.setMode(KeyMode::MIXOLYDIAN); - } else if (m == "aeolian") { - sig.setMode(KeyMode::AEOLIAN); - } else if (m == "ionian") { - sig.setMode(KeyMode::IONIAN); - } else if (m == "locrian") { - sig.setMode(KeyMode::LOCRIAN); - } else { - sig.setMode(KeyMode::UNKNOWN); - } + sig.setMode(TConv::fromXml(e.readAsciiText(), KeyMode::UNKNOWN)); } else if (tag == "subtype") { subtype = e.readInt(); } else if (tag == "forInstrumentChange") { sig.setForInstrumentChange(e.readBool()); - } else if (tag == "isCourtesy") { - s->setIsCourtesy(e.readBool()); + } else if (TRead::readProperty(s, tag, e, ctx, Pid::IS_COURTESY)) { } else if (!readItemProperties(s, e, ctx)) { e.unknown(); } diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index e7702353c4c0a..4f515602d227f 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -2212,14 +2212,9 @@ void TWrite::write(const KeySig* item, XmlWriter& xml, WriteContext& ctx) xml.tag("mode", TConv::toXml(item->mode())); } - if (item->isCourtesy()) { - xml.tag("isCourtesy", item->isCourtesy()); - } - - if (!item->showCourtesy()) { - xml.tag("showCourtesySig", item->showCourtesy()); - } + writeProperty(item, xml, Pid::SHOW_COURTESY); writeProperty(item, xml, Pid::IS_COURTESY); + if (item->forInstrumentChange()) { xml.tag("forInstrumentChange", true); } From 98602ef19558fc9f414332e6680394cea07b4b9e Mon Sep 17 00:00:00 2001 From: XiaoMigros Date: Mon, 22 Sep 2025 22:19:55 +0200 Subject: [PATCH 4/7] Further refactor --- src/engraving/rendering/score/tlayout.cpp | 168 ++++++++++------------ 1 file changed, 76 insertions(+), 92 deletions(-) diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index 475eb2856a94a..27835b2421aa3 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -3688,16 +3688,17 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const // return; // } - const Segment* s = item->segment(); - double spatium = item->spatium(); - double step = spatium * (item->staff() ? item->staff()->staffTypeForElement(item)->lineDistance().val() * 0.5 : 0.5); - ldata->setBbox(RectF()); - ldata->keySymbols.clear(); - if (item->staff() && !item->staff()->staffType(item->tick())->genKeysig()) { + + const StaffType* st = item->staffType(); + if (st && !st->genKeysig()) { return; } + const Segment* s = item->segment(); + track_idx_t track = item->track(); + double spatium = item->spatium(); + double step = spatium * (st ? st->lineDistance().val() * 0.5 : 0.5); // determine current clef for this staff ClefType clef = ClefType::G; @@ -3709,19 +3710,16 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const const bool isClefSeg = seg->isClefType() || seg->isHeaderClefType() || (seg->isClefRepeatAnnounceType() && s->isKeySigRepeatAnnounceType()); if (seg->enabled() && isClefSeg) { - c = toClef(seg->element(item->track())); + c = toClef(seg->element(track)); } } } - if (c) { - clef = c->clefType(); - } else { - // no clef found, so get the clef type from the clefs list, using the previous tick - clef = item->staff()->clef(item->tick() - Fraction::eps()); - } + // If no clef found, get the clef type from the clefs list (using the previous tick) + clef = c ? c->clefType() : item->staff()->clef(item->tick() - Fraction::eps()); } int t1 = int(item->key()); + const signed char* lines = ClefInfo::lines(clef); if (item->isCustom() && !item->isAtonal()) { double accidentalGap = conf.styleS(Sid::keysigAccidentalDistance).val(); @@ -3740,7 +3738,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const KeySym ks; int lineIndexOffset = t1 > 0 ? -1 : 6; ks.sym = t1 > 0 ? SymId::accidentalSharp : SymId::accidentalFlat; - ks.line = ClefInfo::lines(clef)[lineIndexOffset + i]; + ks.line = lines[lineIndexOffset + i]; if (!ldata->keySymbols.empty()) { const KeySym& previous = ldata->keySymbols.back(); double previousWidth = item->symWidth(previous.sym) / spatium; @@ -3758,7 +3756,7 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const bool flat = std::string(SymNames::nameForSymId(sym).ascii()).find("Flat") != std::string::npos; int accIdx = (degree * 2 + 1) % 7; // C D E F ... index to F C G D index accIdx = flat ? 13 - accIdx : accIdx; - int line = ClefInfo::lines(clef)[accIdx] + cd.octAlt * 7; + int line = lines[accIdx] + cd.octAlt * 7; double xpos = cd.xAlt; if (!ldata->keySymbols.empty()) { const KeySym& previous = ldata->keySymbols.back(); @@ -3791,101 +3789,87 @@ void TLayout::layoutKeySig(const KeySig* item, KeySig::LayoutData* ldata, const } } } else { - auto key2accidentals = [](int key) -> int { - switch (std::abs(key)) { - case 7: return 0x7f; - case 6: return 0x3f; - case 5: return 0x1f; - case 4: return 0xf; - case 3: return 0x7; - case 2: return 0x3; - case 1: return 0x1; - case 0: return 0; - default: - LOGD("illegal key %d", key); - return 0; + auto layoutSharpsFlats = [&]() { + if (std::abs(t1) > 7) { + LOGD("illegal t1 key %d", t1); + return; + } + SymId symbol = t1 > 0 ? SymId::accidentalSharp : SymId::accidentalFlat; + int lineIndexOffset = t1 > 0 ? 0 : 7; + for (int i = 0; i < std::abs(t1); ++i) { + keySigAddLayout(item, conf, symbol, lines[lineIndexOffset + i], ldata); } }; - int accidentals = key2accidentals(t1); - int naturals = 0; // Naturals are shown if: // Key signature is courtesy, mid-measure or prev. measure has no section break and no courtesy keysig. // AND we're not force hiding naturals (continuous mode) // AND key sig is CMaj/Amin OR style says they are on - const Measure* prevMeasure = item->measure() ? item->measure()->prevMeasureMM() : nullptr; - bool naturalsOn = !item->hideNaturals() && item->track() != muse::nidx - && (conf.styleI(Sid::keySigNaturals) != int(KeySigNatural::NONE) || (t1 == 0)) - && ((s && (s->isType(SegmentType::CourtesyKeySigType) || !s->rtick().isZero())) - || (prevMeasure && !prevMeasure->sectionBreak() && !prevMeasure->hasCourtesyKeySig())); - - int coffset = 0; - Key t2 = Key::C; - if (naturalsOn) { - if (item->staff()) { - t2 = item->staff()->key(item->tick() - Fraction::eps()); - } - if (s && s->isType(SegmentType::KeySigStartRepeatAnnounce)) { - // Handle naturals in continuation courtesy - Segment* prevCourtesySeg - = prevMeasure ? prevMeasure->findSegmentR(SegmentType::KeySigRepeatAnnounce, prevMeasure->ticks()) : nullptr; - EngravingItem* prevCourtesy = prevCourtesySeg ? prevCourtesySeg->element(item->track()) : nullptr; - t2 = prevCourtesy && prevCourtesy->isKeySig() ? toKeySig(prevCourtesy)->key() : t2; - } - if (t2 == Key::C) { - naturalsOn = false; - } else { - naturals = key2accidentals(int(t2)); - // remove redundant naturals - if (!((t1 > 0) ^ (t2 > 0))) { - naturals &= ~accidentals; - } - if (t2 < 0) { - coffset = 7; + const Measure* pm = item->measure() ? item->measure()->prevMeasureMM() : nullptr; + if (!item->hideNaturals() && track != muse::nidx + && (conf.styleI(Sid::keySigNaturals) != int(KeySigNatural::NONE) || (t1 == 0)) + && ((s && (s->isType(SegmentType::CourtesyKeySigType) || !s->rtick().isZero())) + || (pm && !pm->sectionBreak() && !pm->hasCourtesyKeySig()))) { + int t2 = item->staff() ? int(item->staff()->key(item->tick() - Fraction::eps())) : 0; + + // Handle naturals in continuation courtesy + if (pm && s && s->isType(SegmentType::KeySigStartRepeatAnnounce)) { + Segment* prevCourtesySeg = pm->findSegmentR(SegmentType::KeySigRepeatAnnounce, pm->ticks()); + if (prevCourtesySeg && prevCourtesySeg->element(track)) { + t2 = int(toKeySig(prevCourtesySeg->element(track))->key()); } } - } - - const bool sameAccidentals = t1 * int(t2) > 0; - // Don't show naturals when going from sharps to flats, if style says so - naturalsOn = sameAccidentals || conf.styleB(Sid::keySigShowNaturalsChangingSharpsFlats); - // Naturals should go BEFORE accidentals if style says so - // or going from sharps to flats or vice versa (i.e. t1 & t2 have opposite signs) - const bool natBefore = conf.styleI(Sid::keySigNaturals) == int(KeySigNatural::BEFORE) || !sameAccidentals; - - const signed char* lines = ClefInfo::lines(clef); + // Don't show naturals when going from sharps to flats, if style says so + const bool sameAccidentals = t1 * t2 >= 0; + if (t2 != 0 && (sameAccidentals || conf.styleB(Sid::keySigShowNaturalsChangingSharpsFlats))) { + auto key2accidentals = [](int key) -> int { + switch (std::abs(key)) { + case 7: return 0x7f; + case 6: return 0x3f; + case 5: return 0x1f; + case 4: return 0xf; + case 3: return 0x7; + case 2: return 0x3; + case 1: return 0x1; + case 0: return 0; + default: + LOGD("illegal key %d", key); + return 0; + } + }; - if (naturalsOn && natBefore) { - for (int i = 0; i < 7; ++i) { - if (naturals & (1 << i)) { - keySigAddLayout(item, conf, SymId::accidentalNatural, lines[i + coffset], ldata); + int naturals = key2accidentals(t2); + // remove redundant naturals + if (!((t1 > 0) ^ (t2 > 0))) { + naturals &= ~key2accidentals(t1); } - } - } - if (std::abs(t1) <= 7) { - SymId symbol = t1 > 0 ? SymId::accidentalSharp : SymId::accidentalFlat; - int lineIndexOffset = t1 > 0 ? 0 : 7; - for (int i = 0; i < std::abs(t1); ++i) { - keySigAddLayout(item, conf, symbol, lines[lineIndexOffset + i], ldata); - } - } else { - LOGD("illegal t1 key %d", t1); - } - if (naturalsOn && !natBefore) { - for (int i = 0; i < 7; ++i) { - if (naturals & (1 << i)) { - keySigAddLayout(item, conf, SymId::accidentalNatural, lines[i + coffset], ldata); + auto layoutNaturals = [&]() { + int lineIndexOffset = t2 > 0 ? 0 : 7; + for (int i = 0; i < 7; ++i) { + if (naturals & (1 << i)) { + keySigAddLayout(item, conf, SymId::accidentalNatural, lines[i + lineIndexOffset], ldata); + } + } + }; + // Naturals should go BEFORE accidentals if style says so + // or going from sharps to flats or vice versa (i.e. t1 & t2 have opposite signs) + if (conf.styleI(Sid::keySigNaturals) == int(KeySigNatural::BEFORE) || !sameAccidentals) { + layoutNaturals(); + layoutSharpsFlats(); + } else { + layoutSharpsFlats(); + layoutNaturals(); } } } - // Follow stepOffset - if (item->staffType()) { - ldata->setPosY(item->staffType()->stepOffset() * 0.5 * spatium); + // No naturals were added, so just create a regular keysig + if (ldata->keySymbols.empty()) { + layoutSharpsFlats(); } } - ldata->moveY(item->staffOffsetY()); + ldata->setPosY((st ? step * st->stepOffset() : 0.0) + item->staffOffsetY()); Shape keySigShape; for (const KeySym& ks : ldata->keySymbols) { From 8637a26947030800aca925ce5cd6afb85c1f9958 Mon Sep 17 00:00:00 2001 From: XiaoMigros Date: Thu, 16 Oct 2025 12:26:05 +0200 Subject: [PATCH 5/7] Correctly set properties for courtesy keysigs --- src/engraving/dom/keysig.cpp | 31 ++++++++++++++++++++++++++++++- src/engraving/dom/keysig.h | 1 + 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/engraving/dom/keysig.cpp b/src/engraving/dom/keysig.cpp index a51794cbbaef5..d1575af8899f3 100644 --- a/src/engraving/dom/keysig.cpp +++ b/src/engraving/dom/keysig.cpp @@ -152,7 +152,7 @@ bool KeySig::isChange() const if (!segment() || segment()->segmentType() != SegmentType::KeySig) { return false; } - Fraction keyTick = tick(); + const Fraction keyTick = tick(); return staff()->currentKeyTick(keyTick) == keyTick; } @@ -197,12 +197,38 @@ PointF KeySig::staffOffset() const return PointF(0.0, 0.0); } +EngravingObject* KeySig::propertyDelegate(Pid propertyId) const +{ + if (!_isCourtesy) { + return nullptr; + } + switch (propertyId) { + case Pid::KEY: + case Pid::KEY_CONCERT: + case Pid::SHOW_COURTESY: + case Pid::KEYSIG_MODE: + case Pid::IS_COURTESY: { + if (Segment* s = segment()->next1(SegmentType::KeySig)) { + return s->tick() == segment()->tick() ? toKeySig(s->element(track())) : nullptr; + } + break; + } + default: + break; + } + + return nullptr; +} + //--------------------------------------------------------- // getProperty //--------------------------------------------------------- PropertyValue KeySig::getProperty(Pid propertyId) const { + if (EngravingObject* e = propertyDelegate(propertyId)) { + return e->getProperty(propertyId); + } switch (propertyId) { case Pid::KEY: return int(key()); @@ -225,6 +251,9 @@ PropertyValue KeySig::getProperty(Pid propertyId) const bool KeySig::setProperty(Pid propertyId, const PropertyValue& v) { + if (EngravingObject* e = propertyDelegate(propertyId)) { + return e->setProperty(propertyId, v); + } switch (propertyId) { case Pid::KEY: if (generated()) { diff --git a/src/engraving/dom/keysig.h b/src/engraving/dom/keysig.h index 75b8757bce4cd..a0957688c38b5 100644 --- a/src/engraving/dom/keysig.h +++ b/src/engraving/dom/keysig.h @@ -94,6 +94,7 @@ class KeySig final : public EngravingItem PropertyValue getProperty(Pid propertyId) const override; bool setProperty(Pid propertyId, const PropertyValue&) override; PropertyValue propertyDefault(Pid id) const override; + EngravingObject* propertyDelegate(Pid) const override; EngravingItem* nextSegmentElement() override; EngravingItem* prevSegmentElement() override; From dc36a1daf7232506b68216c1993257519660eb7c Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Fri, 17 Oct 2025 11:11:52 +0200 Subject: [PATCH 6/7] Fix crash --- src/engraving/dom/keysig.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/engraving/dom/keysig.cpp b/src/engraving/dom/keysig.cpp index d1575af8899f3..1d6d21e58df66 100644 --- a/src/engraving/dom/keysig.cpp +++ b/src/engraving/dom/keysig.cpp @@ -207,9 +207,12 @@ EngravingObject* KeySig::propertyDelegate(Pid propertyId) const case Pid::KEY_CONCERT: case Pid::SHOW_COURTESY: case Pid::KEYSIG_MODE: - case Pid::IS_COURTESY: { - if (Segment* s = segment()->next1(SegmentType::KeySig)) { - return s->tick() == segment()->tick() ? toKeySig(s->element(track())) : nullptr; + case Pid::IS_COURTESY: + { + Segment* thisSeg = segment(); + Segment* nextKSSeg = thisSeg ? thisSeg->next1(SegmentType::KeySig) : nullptr; + if (nextKSSeg && nextKSSeg->tick() == thisSeg->tick()) { + return nextKSSeg->element(track()); } break; } From ef7dc605cff987f0b31f85048d17064fc9d72d20 Mon Sep 17 00:00:00 2001 From: Michele Spagnolo Date: Fri, 17 Oct 2025 11:50:44 +0200 Subject: [PATCH 7/7] UI for new key sig natural option --- .../internal/EditStyle/AccidentalsPage.qml | 24 ------------ .../internal/EditStyle/ClefKeyTimeSigPage.qml | 37 +++++++++++++++++++ .../view/styledialog/accidentalspagemodel.cpp | 7 ---- .../view/styledialog/accidentalspagemodel.h | 5 --- .../styledialog/clefkeytimesigpagemodel.cpp | 14 ++++++- .../styledialog/clefkeytimesigpagemodel.h | 6 +++ 6 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/AccidentalsPage.qml b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/AccidentalsPage.qml index 8ee99fa51c8e6..d2fc6b657539c 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/AccidentalsPage.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/AccidentalsPage.qml @@ -60,30 +60,6 @@ StyledFlickable { } } - StyledGroupBox { - Layout.fillWidth: true - title: qsTrc("notation/editstyle/accidentals", "♮ in key signature changes") - - RadioButtonGroup { - width: parent.width - orientation: ListView.Vertical - spacing: 8 - - model: [ - { text: qsTrc("notation/editstyle/accidentals", "Only for a change to C major / A minor"), value: /* KeySigNatural::NONE */ 0 }, - { text: qsTrc("notation/editstyle/accidentals", "Before key signature if changing to fewer ♯ or ♭"), value: /* KeySigNatural::BEFORE */ 1 }, - { text: qsTrc("notation/editstyle/accidentals", "After key signature if changing to fewer ♯ or ♭; before if changing between ♯ and ♭"), value: /* KeySigNatural::AFTER */ 2 } - ] - - delegate: RoundedRadioButton { - width: ListView.view.width - text: modelData.text - checked: accidentalsPageModel.keySigNaturals.value === modelData.value - onToggled: accidentalsPageModel.keySigNaturals.value = modelData.value - } - } - } - StyledGroupBox { Layout.fillWidth: true title: qsTrc("notation/editstyle/accidentals", "Multiple accidentals in chords") diff --git a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/ClefKeyTimeSigPage.qml b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/ClefKeyTimeSigPage.qml index 43349c07f8340..9aee34efb29d8 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/ClefKeyTimeSigPage.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/EditStyle/ClefKeyTimeSigPage.qml @@ -448,6 +448,7 @@ StyledFlickable { ColumnLayout { spacing: 8 + anchors.fill: parent ItemWithTitle { spacing: 8 @@ -475,6 +476,42 @@ StyledFlickable { checked: pageModel.genCourtesyKeysig.value === true onClicked: pageModel.genCourtesyKeysig.value = !pageModel.genCourtesyKeysig.value } + + StyledGroupBox { + Layout.fillWidth: true + title: qsTrc("notation/editstyle/accidentals", "When changing to a key signature containing fewer sharps or flats") + + ColumnLayout { + spacing: 6 + anchors.fill: parent + + RadioButtonGroup { + Layout.fillWidth: true + orientation: ListView.Vertical + spacing: 6 + + model: [ + { text: qsTrc("notation/editstyle/accidentals", "Don’t show any naturals"), value: 0 }, + { text: qsTrc("notation/editstyle/accidentals", "Show naturals before the new key signature"), value: 1 }, + { text: qsTrc("notation/editstyle/accidentals", "Show naturals after the new key signature"), value: 2 } + ] + + delegate: RoundedRadioButton { + width: ListView.view.width + text: modelData.text + checked: pageModel.keySigNaturals.value === modelData.value + onToggled: pageModel.keySigNaturals.value = modelData.value + } + } + + CheckBox { + enabled: pageModel.keySigNaturals.value !== 0 + text: qsTrc("notation/editstyle/timesignatures", "Show naturals when switching between sharps and flats") + checked: pageModel.keySigShowNaturalsChangingSharpsFlats.value === true + onClicked: pageModel.keySigShowNaturalsChangingSharpsFlats.value = !pageModel.keySigShowNaturalsChangingSharpsFlats.value + } + } + } } } diff --git a/src/notation/view/styledialog/accidentalspagemodel.cpp b/src/notation/view/styledialog/accidentalspagemodel.cpp index 11abed5449085..a7dc6e2a28c55 100644 --- a/src/notation/view/styledialog/accidentalspagemodel.cpp +++ b/src/notation/view/styledialog/accidentalspagemodel.cpp @@ -27,8 +27,6 @@ AccidentalsPageModel::AccidentalsPageModel(QObject* parent) : AbstractStyleDialogModel(parent, { StyleId::bracketedAccidentalPadding, - StyleId::keySigNaturals, - StyleId::accidentalOrderFollowsNoteDisplacement, StyleId::alignAccidentalOctavesAcrossSubChords, StyleId::keepAccidentalSecondsTogether, @@ -42,11 +40,6 @@ StyleItem* AccidentalsPageModel::bracketedAccidentalPadding() const return styleItem(StyleId::bracketedAccidentalPadding); } -StyleItem* AccidentalsPageModel::keySigNaturals() const -{ - return styleItem(StyleId::keySigNaturals); -} - StyleItem* AccidentalsPageModel::accidFollowNoteOffset() const { return styleItem(StyleId::accidentalOrderFollowsNoteDisplacement); diff --git a/src/notation/view/styledialog/accidentalspagemodel.h b/src/notation/view/styledialog/accidentalspagemodel.h index 02d5a13cb48a9..0a0c37c1cf308 100644 --- a/src/notation/view/styledialog/accidentalspagemodel.h +++ b/src/notation/view/styledialog/accidentalspagemodel.h @@ -31,8 +31,6 @@ class AccidentalsPageModel : public AbstractStyleDialogModel Q_PROPERTY(StyleItem * bracketedAccidentalPadding READ bracketedAccidentalPadding CONSTANT) - Q_PROPERTY(StyleItem * keySigNaturals READ keySigNaturals CONSTANT) - Q_PROPERTY(StyleItem * accidFollowNoteOffset READ accidFollowNoteOffset CONSTANT) Q_PROPERTY(StyleItem * alignAccidentalOctavesAcrossSubChords READ alignAccidentalOctavesAcrossSubChords CONSTANT) Q_PROPERTY(StyleItem * keepAccidentalSecondsTogether READ keepAccidentalSecondsTogether CONSTANT) @@ -42,9 +40,6 @@ class AccidentalsPageModel : public AbstractStyleDialogModel explicit AccidentalsPageModel(QObject* parent = nullptr); StyleItem* bracketedAccidentalPadding() const; - - StyleItem* keySigNaturals() const; - StyleItem* accidFollowNoteOffset() const; StyleItem* alignAccidentalOctavesAcrossSubChords() const; StyleItem* keepAccidentalSecondsTogether() const; diff --git a/src/notation/view/styledialog/clefkeytimesigpagemodel.cpp b/src/notation/view/styledialog/clefkeytimesigpagemodel.cpp index 60ee32bcb4d39..0c4ab6b3e85aa 100644 --- a/src/notation/view/styledialog/clefkeytimesigpagemodel.cpp +++ b/src/notation/view/styledialog/clefkeytimesigpagemodel.cpp @@ -64,7 +64,9 @@ ClefKeyTimeSigPageModel::ClefKeyTimeSigPageModel(QObject* parent) StyleId::useParensRepeatCourtesiesAfterCancelling, StyleId::showCourtesiesAfterCancellingOtherJumps, StyleId::useParensOtherJumpCourtesiesAfterCancelling, - StyleId::smallParens }) + StyleId::smallParens, + StyleId::keySigNaturals, + StyleId::keySigShowNaturalsChangingSharpsFlats, }) { } @@ -306,3 +308,13 @@ StyleItem* ClefKeyTimeSigPageModel::smallParens() const { return styleItem(StyleId::smallParens); } + +StyleItem* ClefKeyTimeSigPageModel::keySigNaturals() const +{ + return styleItem(StyleId::keySigNaturals); +} + +StyleItem* ClefKeyTimeSigPageModel::keySigShowNaturalsChangingSharpsFlats() const +{ + return styleItem(StyleId::keySigShowNaturalsChangingSharpsFlats); +} diff --git a/src/notation/view/styledialog/clefkeytimesigpagemodel.h b/src/notation/view/styledialog/clefkeytimesigpagemodel.h index fd271b783543d..5cd46ca3f36ae 100644 --- a/src/notation/view/styledialog/clefkeytimesigpagemodel.h +++ b/src/notation/view/styledialog/clefkeytimesigpagemodel.h @@ -81,6 +81,9 @@ class ClefKeyTimeSigPageModel : public AbstractStyleDialogModel Q_PROPERTY(StyleItem * smallParens READ smallParens CONSTANT) + Q_PROPERTY(StyleItem * keySigNaturals READ keySigNaturals CONSTANT) + Q_PROPERTY(StyleItem * keySigShowNaturalsChangingSharpsFlats READ keySigShowNaturalsChangingSharpsFlats CONSTANT) + public: explicit ClefKeyTimeSigPageModel(QObject* parent = nullptr); @@ -137,5 +140,8 @@ class ClefKeyTimeSigPageModel : public AbstractStyleDialogModel StyleItem* useParensOtherJumpCourtesiesAfterCancelling() const; StyleItem* smallParens() const; + + StyleItem* keySigNaturals() const; + StyleItem* keySigShowNaturalsChangingSharpsFlats() const; }; }