diff --git a/src/braille/ibrailleconfiguration.h b/src/braille/ibrailleconfiguration.h index ada7b8a7af0fb..835fd28a925d7 100644 --- a/src/braille/ibrailleconfiguration.h +++ b/src/braille/ibrailleconfiguration.h @@ -49,6 +49,10 @@ class IBrailleConfiguration : MODULE_GLOBAL_INTERFACE virtual QString brailleTable() const = 0; virtual void setBrailleTable(const QString& table) = 0; virtual QStringList brailleTableList() const = 0; + + virtual muse::async::Notification signDoublingChanged() const = 0; + virtual bool signDoubling() const = 0; + virtual void setSignDoubling(const bool enabled) = 0; }; } diff --git a/src/braille/internal/braille.cpp b/src/braille/internal/braille.cpp index 47c110d1534bd..0b81204c79442 100644 --- a/src/braille/internal/braille.cpp +++ b/src/braille/internal/braille.cpp @@ -24,6 +24,8 @@ #include +#include + #include "containers.h" #include "engraving/dom/accidental.h" @@ -133,6 +135,11 @@ namespace mu::engraving { #define BRAILLE_BREVE_SUFFIX QString("k") #define BRAILLE_256TH_PREFIX QString(";<1") #define BRAILLE_DURATION_DOT QString("'") +#define BRAILLE_SOLID_NOTEHEAD_ONLY QString("5a") +#define BRAILLE_X_SHAPED_NOTEHEAD QString("5b") +#define BRAILLE_DIAMOND_SHAPED_NOTEHEAD QString("5l") +#define BRAILLE_INDETERMINATE_PITCH QString("5k") +#define BRAILLE_APPROX_PITCH_LINE_END QString("5'") // Table 3. Page 4. Music Braille Code 2015. #define BRAILLE_BELOW_FIRST_OCTAVE QString("@@") @@ -949,6 +956,134 @@ void Braille::resetOctaves() } } +// 22. Doubling of Signs. Music Braille Code 2015. +// When the same sign appears on more than three consecutive notes +// (ignoring any rests between them), it is written twice before the first note of the group +// and once after the last note, and omitted on the notes in between. This precomputes, per +// sign instance, whether it should be doubled, omitted, closed, or rendered singly +// (the default). +void Braille::computeSignDoubling() +{ + m_context.signDoublingComputed = true; + m_context.signDoubling.clear(); + + // When disabled, the empty maps mean every sign is rendered singly (legacy behaviour). + // The configuration may be unavailable (e.g. in unit tests that don't register the braille + // module); in that case fall back to the default of doubling enabled. + if (brailleConfiguration() && !brailleConfiguration()->signDoubling()) { + return; + } + + auto finalizeRun = [this](const std::vector& run) { + if (run.size() < SIGN_DOUBLING_MIN_GROUP) { + return; + } + // Double (prefix) on the first note, omit on the middle notes, and a single closing sign + // as a suffix after the last note. run.size() >= 4 here, so front and back differ. + m_context.signDoubling[run.front()] = SignDoubling::Double; + for (size_t i = 1; i + 1 < run.size(); ++i) { + m_context.signDoubling[run[i]] = SignDoubling::Omit; + } + m_context.signDoubling[run.back()] = SignDoubling::CloseSuffix; + }; + + const size_t ntracks = m_score->staves().size() * VOICES; + for (track_idx_t track = 0; track < ntracks; ++track) { + // Runs of consecutive chords sharing the same typed Braille sign. + std::map > openSignRuns; + + for (Segment* seg = m_score->firstSegment(SegmentType::ChordRest); seg; seg = seg->next1(SegmentType::ChordRest)) { + EngravingItem* el = seg->element(track); + if (!el) { + continue; + } + if (el->isRest()) { + // Rests never break a run. + continue; + } + if (!el->isChord()) { + continue; + } + + Chord* chord = toChord(el); + + std::set presentSigns; + for (Articulation* artic : chord->articulations()) { + const QString key = brailleArticulation(artic); + if (key.isEmpty()) { + continue; + } + presentSigns.insert(QStringLiteral("articulation:") + key); + } + + const QString arpeggioCode = brailleArpeggio(chord->arpeggio()); + if (!arpeggioCode.isEmpty()) { + presentSigns.insert(QStringLiteral("arpeggio:") + arpeggioCode); + } + + std::vector notes; + const ClefType clefType = chord->staff() ? chord->staff()->clef(chord->tick()) : ClefType::INVALID; + if (ascendingChords(clefType)) { + for (auto it = chord->notes().begin(); it != chord->notes().end(); ++it) { + notes.push_back(*it); + } + } else { + for (auto it = chord->notes().rbegin(); it != chord->notes().rend(); ++it) { + notes.push_back(*it); + } + } + + Note* rootNote = notes.empty() ? nullptr : notes.front(); + if (notes.size() > 1) { + for (auto it = notes.begin() + 1; it != notes.end(); ++it) { + const QString key = brailleIntervalSign(computeInterval(rootNote, *it, true)); + if (key.isEmpty()) { + continue; + } + presentSigns.insert(QStringLiteral("interval:") + key); + } + } + + // End any open run whose typed sign is not present on this chord. + for (auto it = openSignRuns.begin(); it != openSignRuns.end();) { + if (presentSigns.find(it->first) == presentSigns.end()) { + finalizeRun(it->second); + it = openSignRuns.erase(it); + } else { + ++it; + } + } + + // Extend or start a run for each sign instance on this chord. + for (Articulation* artic : chord->articulations()) { + const QString key = brailleArticulation(artic); + if (key.isEmpty()) { + continue; + } + openSignRuns[QStringLiteral("articulation:") + key].push_back(artic); + } + + if (!arpeggioCode.isEmpty()) { + openSignRuns[QStringLiteral("arpeggio:") + arpeggioCode].push_back(chord->arpeggio()); + } + + if (notes.size() > 1) { + for (auto it = notes.begin() + 1; it != notes.end(); ++it) { + const QString key = brailleIntervalSign(computeInterval(rootNote, *it, true)); + if (key.isEmpty()) { + continue; + } + openSignRuns[QStringLiteral("interval:") + key].push_back(*it); + } + } + } + + for (const auto& pair : openSignRuns) { + finalizeRun(pair.second); + } + } +} + void Braille::credits(QIODevice& device) { QTextStream out(&device); @@ -1761,9 +1896,67 @@ QString Braille::brailleBreath(Breath* breath) } } +static bool isGlissandoEndNote(Note* note) +{ + if (!note) { + return false; + } + + for (Spanner* spanner : note->spannerBack()) { + if (spanner->isGlissando()) { + return true; + } + } + + return false; +} + +static QString brailleSpecialNoteheadPrefix(Note* note) +{ + if (!note) { + return QString(); + } + + if (isGlissandoEndNote(note)) { + return BRAILLE_APPROX_PITCH_LINE_END; + } + + switch (note->headGroup()) { + case NoteHeadGroup::HEAD_CROSS: + case NoteHeadGroup::HEAD_WITHX: + case NoteHeadGroup::HEAD_XCIRCLE: + case NoteHeadGroup::HEAD_HEAVY_CROSS: + case NoteHeadGroup::HEAD_HEAVY_CROSS_HAT: + return BRAILLE_X_SHAPED_NOTEHEAD; + + case NoteHeadGroup::HEAD_DIAMOND: + case NoteHeadGroup::HEAD_DIAMOND_OLD: + case NoteHeadGroup::HEAD_LARGE_DIAMOND: + return BRAILLE_DIAMOND_SHAPED_NOTEHEAD; + + case NoteHeadGroup::HEAD_SLASH: + case NoteHeadGroup::HEAD_SLASHED1: + case NoteHeadGroup::HEAD_SLASHED2: + return BRAILLE_INDETERMINATE_PITCH; + + case NoteHeadGroup::HEAD_NORMAL: { + Chord* chord = note->chord(); + if (chord && chord->noStem() && chord->durationType().headType() == NoteHeadType::HEAD_QUARTER) { + return BRAILLE_SOLID_NOTEHEAD_ONLY; + } + break; + } + + default: + break; + } + + return QString(); +} + QString Braille::brailleChord(Chord* chord) { - // 9.3 Interval Doubling and 9.5 Moving notes methods are not implemented as they are optional methods + // 9.5 Moving notes methods are not implemented as they are optional methods if (!chord || chord->notes().empty()) { return QString(); @@ -1817,9 +2010,32 @@ QString Braille::brailleChord(Chord* chord) } } - QString articulationsBraille = QString(); + if (!m_context.signDoublingComputed) { + computeSignDoubling(); + } + QString articulationsBraille = QString(); // prefix, before the note + QString articulationsBrailleAfter = QString(); // suffix, after the note (doubling closing sign) for (Articulation* artic : chord->articulations()) { - articulationsBraille += brailleArticulation(artic); + auto it = m_context.signDoubling.find(artic); + SignDoubling state = (it != m_context.signDoubling.end()) ? it->second : SignDoubling::Single; + const QString code = brailleArticulation(artic); + switch (state) { + case SignDoubling::Omit: + // Middle of a doubled group: the sign is not written. + break; + case SignDoubling::Double: + // First note of a doubled group: write the sign twice, before the note. + articulationsBraille += code + code; + break; + case SignDoubling::CloseSuffix: + // Last note of a doubled group: write the closing sign once, after the note. + articulationsBrailleAfter += code; + break; + case SignDoubling::Single: + default: + articulationsBraille += code; + break; + } } std::vector chordSlurs = slurs(chord); @@ -1828,7 +2044,30 @@ QString Braille::brailleChord(Chord* chord) QString hairpinBrailleAfter = brailleHairpinAfter(chord, chordHairpins); - QString arpeggio = brailleArpeggio(chord->arpeggio()); + QString arpeggioBraille = QString(); // prefix, before the note + QString arpeggioBrailleAfter = QString(); // suffix, after the note (doubling closing sign) + if (Arpeggio* arpeggio = chord->arpeggio()) { + auto it = m_context.signDoubling.find(arpeggio); + SignDoubling state = (it != m_context.signDoubling.end()) ? it->second : SignDoubling::Single; + const QString code = brailleArpeggio(arpeggio); + switch (state) { + case SignDoubling::Omit: + // Middle of a doubled group: the sign is not written. + break; + case SignDoubling::Double: + // First note of a doubled group: write the sign twice, before the note. + arpeggioBraille += code + code; + break; + case SignDoubling::CloseSuffix: + // Last note of a doubled group: write the closing sign once, after the note. + arpeggioBrailleAfter += code; + break; + case SignDoubling::Single: + default: + arpeggioBraille += code; + break; + } + } QString tremoloBraille = brailleTremolo(chord); QString glissandoLastNoteBraille = brailleGlissando(notes.back()); @@ -1837,12 +2076,14 @@ QString Braille::brailleChord(Chord* chord) result += hairpinBrailleBefore; result += graceNotesBefore; result += graceNoteMarking; - result += arpeggio; + result += arpeggioBraille; result += articulationsBraille; result += slurBrailleBefore; result += tupletBraille; result += rootNoteBraille; result += intervals; + result += arpeggioBrailleAfter; + result += articulationsBrailleAfter; result += tremoloBraille; result += chordTieBraille; result += slurBrailleAfter; @@ -1856,6 +2097,21 @@ QString Braille::brailleChord(Chord* chord) return result; } +QString Braille::brailleIntervalSign(int interval) +{ + // Table 9. Intervals. Page 7. Music Braille Code 2015 + switch (interval) { + case 1: return BRAILLE_OCTAVE; + case 2: return BRAILLE_SECOND; + case 3: return BRAILLE_THIRD; + case 4: return BRAILLE_FOURTH; + case 5: return BRAILLE_FIFTH; + case 6: return BRAILLE_SIXTH; + case 7: return BRAILLE_SEVENTH; + default: return QString(); + } +} + //----------------------------------------------------------------------------------- // brailleChordInterval // rootNote: The root note of the chord. @@ -1870,24 +2126,7 @@ QString Braille::brailleChordInterval(Note* rootNote, const std::vector& } int interval = computeInterval(rootNote, note, true); - QString intervalBraille; - //Table 9. Intervals. Page 7. Music Braille Code 2015 - switch (interval) { - case 1: intervalBraille = BRAILLE_OCTAVE; - break; - case 2: intervalBraille = BRAILLE_SECOND; - break; - case 3: intervalBraille = BRAILLE_THIRD; - break; - case 4: intervalBraille = BRAILLE_FOURTH; - break; - case 5: intervalBraille = BRAILLE_FIFTH; - break; - case 6: intervalBraille = BRAILLE_SIXTH; - break; - case 7: intervalBraille = BRAILLE_SEVENTH; - break; - } + QString intervalBraille = brailleIntervalSign(interval); QString noteOctaveBraille = QString(); QString noteAccidentalBraille = QString(); if (note->accidental() != nullptr) { @@ -1916,6 +2155,7 @@ QString Braille::brailleChordInterval(Note* rootNote, const std::vector& } QString noteTieBraille = brailleTie(note); + QString specialNoteheadPrefix = brailleSpecialNoteheadPrefix(note); QString fingeringAfterBraille = QString(); for (EngravingItem* el : rootNote->el()) { @@ -1924,9 +2164,29 @@ QString Braille::brailleChordInterval(Note* rootNote, const std::vector& } } + if (!m_context.signDoublingComputed) { + computeSignDoubling(); + } + auto it = m_context.signDoubling.find(note); + SignDoubling state = (it != m_context.signDoubling.end()) ? it->second : SignDoubling::Single; + switch (state) { + case SignDoubling::Omit: + intervalBraille.clear(); + specialNoteheadPrefix.clear(); + break; + case SignDoubling::Double: + intervalBraille += intervalBraille; + break; + case SignDoubling::CloseSuffix: + case SignDoubling::Single: + default: + break; + } + return noteAccidentalBraille + noteOctaveBraille + + specialNoteheadPrefix + intervalBraille + fingeringAfterBraille + noteTieBraille; @@ -1963,6 +2223,7 @@ QString Braille::brailleChordRootNote(Chord* chord, Note* rootNote) accidentalBraille = brailleAccidentalType(rootNote->accidental()->accidentalType()); } + QString specialNoteheadPrefix = brailleSpecialNoteheadPrefix(rootNote); QString noteTieBraille = brailleTie(rootNote); QString fingeringAfterBraille = QString(); @@ -1975,6 +2236,7 @@ QString Braille::brailleChordRootNote(Chord* chord, Note* rootNote) return accidentalBraille + octaveBraille + + specialNoteheadPrefix + noteBraille + fingeringAfterBraille + noteTieBraille; diff --git a/src/braille/internal/braille.h b/src/braille/internal/braille.h index c2d92d802647d..98f5cb4b49bb6 100644 --- a/src/braille/internal/braille.h +++ b/src/braille/internal/braille.h @@ -23,8 +23,13 @@ #include +#include + #include "engraving/types/types.h" +#include "modularity/ioc.h" +#include "../ibrailleconfiguration.h" + namespace mu::engraving { class Arpeggio; class Articulation; @@ -148,10 +153,23 @@ class TextToUEBBraille QMap textToBrailleASCII; }; +// Rendering decision for a sign under the Music Braille Code 2015 doubling rule. +// Absence from the map means Single (render the sign once, the default behaviour). +enum class SignDoubling { + Single = 0, // default: write the sign once + Double = 1, // first note of a group: write the sign twice + Omit = 2, // middle notes of a group: do not write the sign + CloseSuffix = 3, // last note of a group: write the sign once after the note +}; + struct BrailleContext { std::vector previousNote; std::vector currentClefType; std::vector currentKey; + + // Per-sign-instance decisions for the doubling rule, computed once per Braille run. + std::map signDoubling; + bool signDoublingComputed = false; }; // Braille export is implemented according to Music Braille Code 2015 @@ -169,12 +187,19 @@ class Braille private: static constexpr int MAX_CHARS_PER_LINE = 40; + // Music Braille Code 2015: the same sign on this many notes in a row is doubled. + static constexpr size_t SIGN_DOUBLING_MIN_GROUP = 4; + + muse::GlobalInject brailleConfiguration; + Score* m_score = nullptr; BrailleContext m_context; void resetOctave(size_t stave); void resetOctaves(); + void computeSignDoubling(); + void credits(QIODevice& device); void instruments(QIODevice& device); @@ -188,6 +213,7 @@ class Braille bool isShortShortSlurConvergence(const std::vector& slurs); bool isLongLongSlurConvergence(const std::vector& slurs); bool hasTies(ChordRest* chordRest); + QString brailleIntervalSign(int interval); bool ascendingChords(ClefType clefType); BarLine* firstBarline(Measure* measure, track_idx_t track); BarLine* lastBarline(Measure* measure, track_idx_t track); diff --git a/src/braille/internal/brailleconfiguration.cpp b/src/braille/internal/brailleconfiguration.cpp index fae225dd6f793..d5cee53f96c07 100644 --- a/src/braille/internal/brailleconfiguration.cpp +++ b/src/braille/internal/brailleconfiguration.cpp @@ -33,6 +33,7 @@ static const std::string module_name("braille"); static const Settings::Key BRAILLE_STATUS(module_name, "score/braille/status"); static const Settings::Key BRAILLE_TABLE(module_name, "score/braille/table"); static const Settings::Key BRAILLE_INTERVAL_DIRECTION(module_name, "score/braille/intervalDirection"); +static const Settings::Key BRAILLE_SIGN_DOUBLING(module_name, "score/braille/signDoubling"); void BrailleConfiguration::init() { @@ -48,6 +49,10 @@ void BrailleConfiguration::init() settings()->valueChanged(BRAILLE_INTERVAL_DIRECTION).onReceive(this, [this](const Val&) { m_intervalDirectionChanged.notify(); }); + settings()->setDefaultValue(BRAILLE_SIGN_DOUBLING, Val(true)); + settings()->valueChanged(BRAILLE_SIGN_DOUBLING).onReceive(this, [this](const Val&) { + m_signDoublingChanged.notify(); + }); } muse::async::Notification BrailleConfiguration::braillePanelEnabledChanged() const @@ -95,6 +100,21 @@ void BrailleConfiguration::setBrailleTable(const QString& table) settings()->setSharedValue(BRAILLE_TABLE, Val(table)); } +muse::async::Notification BrailleConfiguration::signDoublingChanged() const +{ + return m_signDoublingChanged; +} + +bool BrailleConfiguration::signDoubling() const +{ + return settings()->value(BRAILLE_SIGN_DOUBLING).toBool(); +} + +void BrailleConfiguration::setSignDoubling(const bool enabled) +{ + settings()->setSharedValue(BRAILLE_SIGN_DOUBLING, Val(enabled)); +} + QStringList BrailleConfiguration::brailleTableList() const { return { diff --git a/src/braille/internal/brailleconfiguration.h b/src/braille/internal/brailleconfiguration.h index e0ef5fdeaf2a6..f5b673c3d648d 100644 --- a/src/braille/internal/brailleconfiguration.h +++ b/src/braille/internal/brailleconfiguration.h @@ -46,10 +46,15 @@ class BrailleConfiguration : public mu::braille::IBrailleConfiguration, public m void setBrailleTable(const QString& table) override; QStringList brailleTableList() const override; + muse::async::Notification signDoublingChanged() const override; + bool signDoubling() const override; + void setSignDoubling(const bool enabled) override; + private: muse::async::Notification m_braillePanelEnabledChanged; muse::async::Notification m_brailleTableChanged; muse::async::Notification m_intervalDirectionChanged; + muse::async::Notification m_signDoublingChanged; }; } diff --git a/src/braille/internal/notationbraille.cpp b/src/braille/internal/notationbraille.cpp index f1796662d4c95..be12189b38bf8 100644 --- a/src/braille/internal/notationbraille.cpp +++ b/src/braille/internal/notationbraille.cpp @@ -74,6 +74,12 @@ void NotationBraille::init() setIntervalDirection(direction); }); + // The Braille generator reads signDoubling directly from configuration, so no value + // needs threading here; we just force a re-render so the panel reflects the change immediately. + brailleConfiguration()->signDoublingChanged().onNotify(this, [this]() { + doBraille(true); + }); + globalContext()->currentNotationChanged().onNotify(this, [this]() { if (notation()) { notation()->interaction()->selectionChanged().onNotify(this, [this]() { diff --git a/src/braille/tests/braille_tests.cpp b/src/braille/tests/braille_tests.cpp index e714794372025..84fee7daf4f57 100644 --- a/src/braille/tests/braille_tests.cpp +++ b/src/braille/tests/braille_tests.cpp @@ -229,6 +229,7 @@ TEST_F(Braille_Tests, breath) { } TEST_F(Braille_Tests, articulations) { brailleSaveTest("testArticulations_Example_22.1_MBC2015"); + brailleSaveTest("testArticulationDoubling_1.12_MBC2015"); } TEST_F(Braille_Tests, hairpins) { // removed the 4th measure from the example as MuseScore does not have a representations for mordents with accidentals diff --git a/src/braille/tests/data/testArticulationDoubling_1.12_MBC2015.mscx b/src/braille/tests/data/testArticulationDoubling_1.12_MBC2015.mscx new file mode 100644 index 0000000000000..2905c811e78a7 --- /dev/null +++ b/src/braille/tests/data/testArticulationDoubling_1.12_MBC2015.mscx @@ -0,0 +1,1019 @@ + + + 4.5.1 + 603eca8 + + sYRhNibursL_6RNJIIDBQeD + 480 + 1 + 1 + 1 + 0 + 1 + + + Composer / arranger + + 2025-04-16 + + + + musicxml + Microsoft Windows + + + + + XML Test Score + + + + stdNormal + + 3 + + 1 + + + + stdNormal + + F + 3 + + Piano + none + + Piano + Pno. + Piano + 21 + 108 + 21 + 108 + keyboard.piano + F + + + Fluid + + + + + + 10 + CLJdpMlH9AM_xCQBTmRRjPM + + mF7lVzxA5OG_vJspfeE/LPN + + Doubling in Braille + + + 43zV5xGmqJF_bmAv1KHAEVC + + Composer / arranger + + + + GwVdf4fJKlE_3+L7T9mysBO + + + G + G + 1 + VpHSt1m/aHM_PWb/DjgILJO + + + iKjTx6kp3iN_sx6rpR6MlfB + 3 + + + H4wEIUBkHfO_6r+xq3Eh74I + 4 + 4 + + + SL8VdIVKGTE_cTZHQPI/69P + quarter + + articStaccatoBelow + gsU93VMS60O_avVbtwK8GbM + + + vFWSrPpwEwJ_ap62+yXRX2B + 69 + 17 + + + + IXJQpxMh1CD_jR3yr/h0PgH + quarter + + articStaccatoBelow + Z6TrtFN+PJK_0rGqHHj1M1B + + + QTApY05v5u_n+ParzcAAOE + 69 + 17 + + + + BaVTASHilaL_+bSHOUhAVjJ + quarter + + articStaccatoBelow + K8LGS6RsB4M_LdeA8DgyHrO + + + FKq83rmKvfH_7YVE3SWCwIK + 69 + 17 + + + + Z8V8KLZ+RmE_/C1XZGEmzKP + quarter + + articStaccatoBelow + 79181kNsKFC_Zu1+vmFVYWH + + + sNBSjNQsDSI_M2hveaVaDtG + 69 + 17 + + + + + + fpdlytC+xAI_c1GkJPINEDB + + + NuYtKzSK6jL_xmbUUrpH/DB + quarter + + articMarcatoAbove + KyzU7mnmHhN_V7mI4En0SpI + + + ULKUyRo3gAH_Fvrx58shfVD + 71 + 19 + + + + u87589Nz1iO_NdTqe9BlFL + quarter + + articMarcatoAbove + H8VFoKgTL8P_13UslxiiB/F + + + /7KxtIni0xO_xflzJBiKuyM + 71 + 19 + + + + EnNmuCgrFiE_MQKOCJqeH9N + quarter + + articMarcatoAbove + w7A14eMg7gM_txZJC6qtWoM + + + BIc6qUlPOyO_sx7BDiWTUrD + 71 + 19 + + + + S04bcLVS5wM_TFfojkhNnMF + quarter + + articMarcatoAbove + aurSKB4wLDM_IdA4Ycq3V+I + + + rA3ppljtGVI_vQgvspG0o8D + 71 + 19 + + + + + + FZH0ywHJaZ_9pA+1Ag9GDP + + D+tSLmNeKqN_V6vdp1Hui+N + line + + + + W8bmQzGJNIL_dpLmDBSTDeL + quarter + + articTenutoAbove + y3EhyKQjnjL_AEmiP5r5WTN + + + oTqhirhjdrK_ga4jBXuPJOE + 73 + 21 + + + + R8dbL8FVK1O_QsZlQ6on8WD + quarter + + articTenutoAbove + +CEJ2gcJ9bG_63bUINUZh9G + + + 1oNMFgzxeMK_2NfDAO3Ix1J + 73 + 21 + + + + hDU2Bwip1vE_a5a6MPxZPGO + quarter + + articTenutoAbove + UIYBBxXjXKC_EYxF0q2UsFL + + + Os18HEMzsmN_KEw0Dr6ZhkK + 73 + 21 + + + + vmKFDQBEdeJ_vlo7nf+tN/O + quarter + + articTenutoAbove + x8idXOJSYOF_hwAegnIxXGB + + + o/LCERN0fBI_4QcWmmsta6O + 73 + 21 + + + + + + C6Bl5AZAjw_9dQvX9xqd1F + 2 + + + 3ZQZOu4qo0M_HWgcXHX7hdJ + quarter + + articAccentAbove + IcmF9pTblF_h/TpNKB46cC + + + W0DcV1YvcSP_1NiEOurfS9I + 74 + 16 + + + + 0tYTfkkVEwC_acDUPLh9twI + quarter + + articAccentAbove + ophqvHJxOfN_km8/EvNmxdE + + + MoTOXxF07zP_JUeejMuNKbC + 74 + 16 + + + + IUEte789MBP_Zp0kW0E0y5 + quarter + + articAccentAbove + dEUcIFRqoUD_P9sDIzbSu6F + + + zFnjJpzUIEJ_1iq116aIOrF + 74 + 16 + + + + KNNWp5+Bk9L_ZY4+LvHWFYK + quarter + + articAccentAbove + GoIpTwe+lhH_weF4XmVOVTC + + + XM0Ks4sFUaG_uIr7jWZCmyP + 74 + 16 + + + + + + hqCtBlKplZD_0Ck3bloz3B + + + mZysz0r37QM_5jSRE07NzyO + whole + + ZB8iKchBUkO_SYUGS3WOYeL + 69 + 17 + + + + + + c4/h0CqvgxK_txx7MU+Ut4O + + + JimQBh7WciO_JXRrimdz8xD + quarter + + + yR7u5wBuidD_g22dB/5IBHL + + + + 1 + 3/4 + + + + + ZLX4rCMRNwO_UYY2TU5Da8G + 69 + 17 + + + + vTJ1W0b07gM_E7weoPpC9+G + quarter + + VjjVJaUD5fH_uQWyrf9dx2K + 71 + 19 + + + + ivyQ7yUl+bP_BLmPtyCrzOH + quarter + + nrg2IJK80eG_ZLz8eDq4TPD + 73 + 21 + + + + AjYbzf3XFdB_Ap417GtPFQO + quarter + + pXeCx8+ea1B_7LkSHLty3y + 74 + 16 + + + + + + JR/KbGLQetC_TVO0OMgpDLH + + + /HTT+/Xz8VF_oJgFXIyRnmI + quarter + + WXaZCgUnELC_ADjWFrHvkkF + 76 + 18 + + + + 1iFufYAVggF_FFoSmRy6W1L + quarter + + oY/q4+6rdFP_wDDp3wzZrFI + 78 + 20 + + + + C9iPTngqMbL_628OwGnfYnP + quarter + + DxKVFzgQiYN_fwHVEZRvFYI + 80 + 22 + + + + p/hs9a0TRuO_391xIQ8Q22C + quarter + + + + -1 + -3/4 + + + + + AF/FYJYBNYE_SlooekGA4bP + 81 + 17 + + + + + + QtjZEQK2UnG_waZIjsFQafP + + + JVsNw3Rbd2N_PnVnTkMYtpK + measure + 4/4 + + + + + w5Q2D/EZh1N_tVBtyQk7QPL + + + gJsiLZjVcd_ciW1thd14fC + measure + 4/4 + + + + + P286kjDDKC_QVtQGI7mYhL + + + +r3r0wPEkhN_iy3Qz6Uy/II + measure + 4/4 + + + + + yegZwvCp3uF_x/Ffs4UkjrM + + + SmkzpGElO3B_uPeRS8RhzEB + measure + 4/4 + + + + + KjD3a02hn4L_RmwgiWa70WB + + + juL56MRHYPC_K7bt1I8EwnI + measure + 4/4 + + + + + l+Sv2SilnwE_zNlADVLf0VK + + + R3+1qILv31O_wLGa8XeEDXN + measure + 4/4 + + + + + 59g7oigUUEL_aa/9N3UVCKH + + J5Tn5ucXW0C_Y11/97oI5AB + line + + + + 2Kt+N7jc2iC_IRuUClQyjkB + measure + 4/4 + + + + + DTqu0QuOQ0B_dWkh8t0p1MC + + + EMTIInFQHcD_SCrr4EXw2Y + measure + 4/4 + + + + + J6fuxV6BRrB_INlDpOq09vG + + + bL46BRVKMt_5pP3phiO4vP + measure + 4/4 + + + + + Z9VMGvzt+cG_q9X1pISvdPJ + + + b67EbHK54aE_p2OxBxgMJrK + measure + 4/4 + + + + + +2WioorS2RF_FLST1THX1mJ + + + /GnRXu4AMrF_EzXO/4qBTrH + measure + 4/4 + + + + + tOSjEzjGKwD_Z+YIM4DV9ON + + + big1GZwCplC_WfqXS1qxxdK + measure + 4/4 + + + + + PIEXpARUIEC_hSCyAxUSS4D + + + LU2S/XsKnEL_bPgFE0uoTe + measure + 4/4 + + + + + 8pXTTGdWjqP_bgDBdmSysJC + + + 2kAunJZYSBJ_2EhYQQVKzCJ + measure + 4/4 + + + + + BWuDaT+z97H_92kfK/GqnDK + + + uHe+VyYj5AG_EyJKkJWllQC + measure + 4/4 + + + + + Elw4p2AxmPI_wjh6VSjG0BL + + + nrH17zvM1cL_pK7YrycQ36J + measure + 4/4 + + + + + I6Z9zVttj9I_dpHiz68tflI + + + cJG99YMAtFL_C0UBQVdINKL + measure + 4/4 + + + + + W6k41IMfVYH_2YimtYTwSjC + + lKFdy3s9r2C_UmZYSlWqVAN + line + + + + 8kIfgmdzaWD_lCmQaSgZCi + measure + 4/4 + + + + + zA/846MPLZF_VY0a9y+dq0M + + + i3bBvbw55RE_OqQob81aQ2P + measure + 4/4 + + + + + uRAWGHQypOM_pVIwRctiYTI + + + AcOkgehvgxB_65ZF0OyFWDK + measure + 4/4 + + + + + Oa1IPAYOrOB_iSfGlkOHHUE + + + j5wGNpioNsO_vVbdiLDhSvO + measure + 4/4 + + + + + WvKSoWlwfQD_PKdO26tDc9H + + + wni57TCWraK_uk6rdhM5C0P + measure + 4/4 + + + + + BinA4EvCIbO_wKQN3pIzwg + + + gg+VNGeQiZ_BLnXPP7scLD + measure + 4/4 + + + + + uXXlw4/tLJJ_jU+ySEAYIOF + + + kTE5gf36i3K_xcTswEtGgVF + measure + 4/4 + + + + + A2I6m1SVVfJ_qEosw4DzQUC + + + 04vuHXKaa7O_NTeRCEIQGrG + measure + 4/4 + + + end + ngLNTngWhuC_ldjrHZivK8H + + + + + + + + + F + F + 1 + GBJKpA0Hl3B_8DepMELS/aK + + + tUdO1btIJAF_pYlEsrAcWUC + 3 + + + QMW3AnECM1J_giyq0sOWz9H + 4 + 4 + + + 9FK0cseAR5F_xWucJzA/0oI + measure + 4/4 + + + + + + + YQsyOmKyTlI_M8rKhXe8inF + measure + 4/4 + + + + + + + z4sCWckGi4_1omrfmX2hxG + measure + 4/4 + + + + + + + QAQKrMmeLwJ_c4kEx7gi+lM + measure + 4/4 + + + + + + + 8qXjw+OijiL_vGprvNIuE9B + measure + 4/4 + + + + + + + PHI9c4jDLVD_elVaxWXJMgG + measure + 4/4 + + + + + + + qfprmk5PFJB_NFcM4eddnxI + measure + 4/4 + + + + + + + XTSxMSs2Z1L_zFzZnxRSOeG + measure + 4/4 + + + + + + + lBCRflCF9aD_IVMoR1+N4pC + measure + 4/4 + + + + + + + 22F7tjyTC0L_An6ceXqs3TG + measure + 4/4 + + + + + + + XS8OxTpcVjE_RrX5g6+CnfH + measure + 4/4 + + + + + + + disBcGHZEpI_nPUxunmxPtN + measure + 4/4 + + + + + + + 7onRi0c6F9O_x7sKuHs3lUH + measure + 4/4 + + + + + + + 5I1bAJj9ckE_TO2/6LfCINM + measure + 4/4 + + + + + + + V0jFU1jOzzC_fu5NfeXyJHG + measure + 4/4 + + + + + + + XJaD+qgcvjB_UYtW7Vwx3sN + measure + 4/4 + + + + + + + Uixci9JIt7F_BLCfv+Fq6aJ + measure + 4/4 + + + + + + + 5bZDY4TY6/O_+nX60/02aNJ + measure + 4/4 + + + + + + + WWhN2DsMOaH_U2z0A0XgA/C + measure + 4/4 + + + + + + + RzDg0agjggK_j/xuViEr7cL + measure + 4/4 + + + + + + + +W1U+S0+znL_eBZMM+uMs9H + measure + 4/4 + + + + + + + UEfhHmMFRbM_GqYEmrSbOjP + measure + 4/4 + + + + + + + brdP0v/kIdH_TBcX0yxz5cH + measure + 4/4 + + + + + + + TQObxRd676G_w/H3x366ovI + measure + 4/4 + + + + + + + +wKhLrfy6UG_yZK1HdUPvK + measure + 4/4 + + + + + + + wfOZaE+y3WN_/r62zr1Ut6I + measure + 4/4 + + + + + + + xnAOPH/aPHG_8WGUNl4OzXH + measure + 4/4 + + + + + + + Hf+5y8erUoG_4WhglfEmLmJ + measure + 4/4 + + + + + + + ZVB2cawbFSP_m5hjJsL5utC + measure + 4/4 + + + + + + + Xvz/QCckEJH_2AtOC9B4VDL + measure + 4/4 + + + + + + + VLHDbRNqI7F_kwQkfntsiJK + measure + 4/4 + + + + + + + /jNpMhXV0SF_4333ksDvdHG + measure + 4/4 + + + end + xfsGKIN+s0M_eO/1k0aRazL + + + + + + + GwVdf4fJKlE_3+L7T9mysBO + GwVdf4fJKlE_3+L7T9mysBO + + + + diff --git a/src/braille/tests/data/testArticulationDoubling_1.12_MBC2015_ref.brf b/src/braille/tests/data/testArticulationDoubling_1.12_MBC2015_ref.brf new file mode 100644 index 0000000000000..092349bd96ce5 --- /dev/null +++ b/src/braille/tests/data/testArticulationDoubling_1.12_MBC2015_ref.brf @@ -0,0 +1,15 @@ +,doubling in ,braille +,composer / arranger +composer ,composer / arranger + +#a ,piano +#b ,piano + +a >/l%%%#d4 88"[[[[8 ;8;8WWWW;8 + >#l%%%#d4 m m +c _8_8.????_8 .8.8::::.8<2 "! ;b[W?: + m m<2 m m +g .$]\[^2 m m m m m m m m m m m m m m m + m m m m m m m m m m m m m m m m +bc m m m m m m m m m mbrailleTableChanged().onNotify(this, [this]() { emit brailleTableChanged(brailleTable()); }); + + brailleConfiguration()->signDoublingChanged().onNotify(this, [this]() { + emit signDoublingChanged(signDoubling()); + }); } bool BraillePreferencesModel::braillePanelEnabled() const @@ -64,6 +68,11 @@ int BraillePreferencesModel::intervalDirection() const return static_cast(brailleConfiguration()->intervalDirection()); } +bool BraillePreferencesModel::signDoubling() const +{ + return brailleConfiguration()->signDoubling(); +} + QStringList BraillePreferencesModel::brailleTables() const { return brailleConfiguration()->brailleTableList(); @@ -119,3 +128,13 @@ void BraillePreferencesModel::setIntervalDirection(int direction) brailleConfiguration()->setIntervalDirection(static_cast(direction)); emit intervalDirectionChanged(direction); } + +void BraillePreferencesModel::setSignDoubling(bool value) +{ + if (value == signDoubling()) { + return; + } + + brailleConfiguration()->setSignDoubling(value); + emit signDoublingChanged(value); +} diff --git a/src/preferences/qml/MuseScore/Preferences/braillepreferencesmodel.h b/src/preferences/qml/MuseScore/Preferences/braillepreferencesmodel.h index 35b66180b0337..8b9f3e9b30774 100644 --- a/src/preferences/qml/MuseScore/Preferences/braillepreferencesmodel.h +++ b/src/preferences/qml/MuseScore/Preferences/braillepreferencesmodel.h @@ -39,6 +39,7 @@ class BraillePreferencesModel : public QObject, public muse::Contextable, public Q_PROPERTY(bool braillePanelEnabled READ braillePanelEnabled WRITE setBraillePanelEnabled NOTIFY braillePanelEnabledChanged) Q_PROPERTY(QString brailleTable READ brailleTable WRITE setBrailleTable NOTIFY brailleTableChanged) Q_PROPERTY(int intervalDirection READ intervalDirection WRITE setIntervalDirection NOTIFY intervalDirectionChanged) + Q_PROPERTY(bool signDoubling READ signDoubling WRITE setSignDoubling NOTIFY signDoublingChanged) muse::GlobalInject brailleConfiguration; @@ -50,6 +51,7 @@ class BraillePreferencesModel : public QObject, public muse::Contextable, public bool braillePanelEnabled() const; QString brailleTable() const; int intervalDirection() const; + bool signDoubling() const; Q_INVOKABLE QStringList brailleTables() const; Q_INVOKABLE QVariantList intervalDirections() const; @@ -58,10 +60,12 @@ public slots: void setBraillePanelEnabled(bool value); void setBrailleTable(QString table); void setIntervalDirection(int direction); + void setSignDoubling(bool value); signals: void braillePanelEnabledChanged(bool value); void brailleTableChanged(QString value); void intervalDirectionChanged(int value); + void signDoublingChanged(bool value); }; } diff --git a/src/preferences/qml/MuseScore/Preferences/internal/BrailleAdvancedSection.qml b/src/preferences/qml/MuseScore/Preferences/internal/BrailleAdvancedSection.qml index fba0b78c91dfa..1c24af3f0a714 100644 --- a/src/preferences/qml/MuseScore/Preferences/internal/BrailleAdvancedSection.qml +++ b/src/preferences/qml/MuseScore/Preferences/internal/BrailleAdvancedSection.qml @@ -34,8 +34,11 @@ BaseSection { property var directions: null property int intervalDirection: -1 + property alias signDoubling: signDoublingBox.checked + signal brailleTableChangeRequested(string table) signal intervalDirectionChangeRequested(int direction) + signal signDoublingChangeRequested(bool enabled) ComboBoxWithTitle { title: qsTrc("preferences", "Braille table for lyrics") @@ -68,4 +71,19 @@ BaseSection { root.intervalDirectionChangeRequested(newValue); } } + + CheckBox { + id: signDoublingBox + width: parent.width + + text: qsTrc("preferences", "Double repeated Braille signs") + + navigation.name: "SignDoublingBox" + navigation.panel: root.navigation + navigation.row: 3 + + onClicked: { + root.signDoublingChangeRequested(!checked); + } + } }