diff --git a/include/vrv/beam.h b/include/vrv/beam.h index a6f3aa7..fc75c46 100644 --- a/include/vrv/beam.h +++ b/include/vrv/beam.h @@ -134,7 +134,8 @@ private: // Helper to adjust position of starting point to make sure that beam start-/endpoints touch the staff lines void CalcAdjustPosition(const Staff *staff, const Doc *doc, const BeamDrawingInterface *beamInterface); - void CalcBeamPlace(const Layer *layer, BeamDrawingInterface *beamInterface, data_BEAMPLACE place); + void CalcBeamPlace(const Layer *layer, const Staff *staff, BeamDrawingInterface *beamInterface, data_BEAMPLACE place); + bool IgnoreEncodedStemDir(const Layer *layer, const Staff *staff) const; /** * Helper to calculate the beam position for a beam in tablature. diff --git a/include/vrv/clef.h b/include/vrv/clef.h index bdd1fe1..ec3f7ad 100644 --- a/include/vrv/clef.h +++ b/include/vrv/clef.h @@ -69,7 +69,12 @@ public: /** * Return the offset of the clef */ - int GetClefLocOffset() const; + int GetClefLocOffset(const Staff *staff = NULL) const; + + /** + * The octave number shown by a banded clef: the octave of the C on the staff's middle line. + */ + int GetBandedOctave(const Staff *staff) const; //----------------// // Static methods // diff --git a/include/vrv/options.h b/include/vrv/options.h index 582de8c..146c669 100644 --- a/include/vrv/options.h +++ b/include/vrv/options.h @@ -730,6 +730,9 @@ public: OptionDbl m_justificationSystem; OptionDbl m_justificationMaxVertical; OptionDbl m_ledgerLineThickness; + OptionBool m_bandedStaff; + OptionString m_bandedStaffColor; + OptionInt m_bandedStaffFClefOctave; OptionDbl m_ledgerLineExtension; OptionIntMap m_lyricElision; OptionDbl m_lyricHeightFactor; diff --git a/include/vrv/staff.h b/include/vrv/staff.h index c6162f4..80cdba3 100644 --- a/include/vrv/staff.h +++ b/include/vrv/staff.h @@ -243,6 +243,31 @@ public: */ int CalcPitchPosYRel(const Doc *doc, int loc) const; + /** + * @name Banded staff helpers. + * On a banded staff, loc 0 is the E on the bottom line and lines sit on C, E and G. + * Positions are measured in half-spaces above the bottom line; A and B sit at fractional positions. + */ + ///@{ + bool IsBanded() const { return m_drawingBanded; } + static double GetBandedPos(int loc); + static bool IsBandedLineLoc(int loc); + static bool IsBandedPatchLoc(int loc); + /** The loc of the top staff line (8 on a standard 5-line staff, 9 on a banded staff) */ + int GetTopLineLoc() const; + /** The staff height in drawing units (half-spaces) */ + int GetHeightInUnits() const { return m_drawingBanded ? 9 : (m_drawingLines - 1) * 2; } + /** + * Whether a stem direction encoded for a standard staff should be recalculated at this loc. + * Standard and banded stem rules only disagree at the B and C either side of the banded staff's centre. + */ + bool IsBandedStemRecalcLoc(int loc) const { return m_drawingBanded && (loc == 4 || loc == 5); } + /** Whether a loc falls on a staff or ledger line */ + bool IsLocOnLine(int loc) const; + /** The loc of the n-th ledger element (1-based) above or below the staff, and whether it is a band patch */ + int GetBandedLedgerLoc(int n, bool below, bool &isPatch) const; + ///@} + /** * Getter and setter for the StaffAlignment */ @@ -325,6 +350,16 @@ public: */ int m_drawingLines; + /** + * Whether the staff is drawn as a banded staff + */ + bool m_drawingBanded; + + /** + * The octave a banded staff takes for an F clef (3, or 4 to bring a piano left hand onto the staff) + */ + int m_drawingBandedFClefOctave; + /** * Notation type (CMN, mensural, black mensural, etc.), from the staffDef */ diff --git a/include/vrv/view.h b/include/vrv/view.h index 7557569..5b43608 100644 --- a/include/vrv/view.h +++ b/include/vrv/view.h @@ -207,6 +207,7 @@ protected: void DrawBarLine(DeviceContext *dc, int yTop, int yBottom, BarLine *barLine, data_BARRENDITION form, bool inStaffSpace = false, bool eraseIntersections = false); void DrawBarLineDots(DeviceContext *dc, Staff *staff, BarLine *barLine); + void DrawBandPatch(DeviceContext *dc, Staff *staff, int x1, int x2, int patchLoc); void DrawLedgerLines(DeviceContext *dc, Staff *staff, const ArrayOfLedgerLines &lines, bool below, bool cueSize); void DrawMeasure(DeviceContext *dc, Measure *measure, System *system); void DrawMeterSigGrp(DeviceContext *dc, Layer *layer, Staff *staff); @@ -337,6 +338,7 @@ protected: ///@{ void DrawAcciaccaturaSlash(DeviceContext *dc, Stem *stem, Staff *staff); void DrawChordCluster(DeviceContext *dc, Chord *chord, Layer *layer, Staff *staff, Measure *measure); + void DrawBandedClef(DeviceContext *dc, Clef *clef, Staff *staff, int x, int y); void DrawClefEnclosing(DeviceContext *dc, Clef *clef, Staff *staff, char32_t glyph, int x, int y); void DrawDotsPart(DeviceContext *dc, int x, int y, unsigned char dots, const Staff *staff, bool dimin = false); void DrawKeySigCancellation( diff --git a/src/adjustarticfunctor.cpp b/src/adjustarticfunctor.cpp index 9257b65..a8dfb70 100644 --- a/src/adjustarticfunctor.cpp +++ b/src/adjustarticfunctor.cpp @@ -35,7 +35,7 @@ FunctorCode AdjustArticFunctor::VisitArtic(Artic *artic) Staff *staff = artic->GetAncestorStaff(RESOLVE_CROSS_STAFF); const Beam *beam = artic->GetAncestorBeam(); - const int staffHeight = m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); + const int staffHeight = staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); const Stem *stem = vrv_cast(m_parent->FindDescendantByType(STEM)); const Flag *flag = vrv_cast(m_parent->FindDescendantByType(FLAG)); diff --git a/src/adjusttupletsyfunctor.cpp b/src/adjusttupletsyfunctor.cpp index 058c64b..ca1aa87 100644 --- a/src/adjusttupletsyfunctor.cpp +++ b/src/adjusttupletsyfunctor.cpp @@ -73,7 +73,7 @@ void AdjustTupletsYFunctor::AdjustTupletBracketY(Tuplet *tuplet, const Staff *st const data_STAFFREL_basic bracketPos = tuplet->GetDrawingBracketPos(); // Default position is above or below the staff - const int staffHeight = (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + const int staffHeight = staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); const int staffBoundary = (bracketPos == STAFFREL_basic_above) ? 0 : -staffHeight; const int bracketMidX = (tupletBracket->GetDrawingXLeft() + tupletBracket->GetDrawingXRight()) / 2; const Point referencePos(bracketMidX, staff->GetDrawingY() + staffBoundary); @@ -172,7 +172,7 @@ void AdjustTupletsYFunctor::AdjustTupletNumY(Tuplet *tuplet, const Staff *staff) // The num is on its own const data_STAFFREL_basic numPos = tuplet->GetDrawingNumPos(); const int numVerticalMargin = (numPos == STAFFREL_basic_above) ? doubleUnit : -doubleUnit; - const int staffHeight = (tupletNumStaff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + const int staffHeight = tupletNumStaff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); const int adjustedPosition = (numPos == STAFFREL_basic_above) ? 0 : -staffHeight; if (!beam) { tupletNum->SetDrawingYRel(adjustedPosition); @@ -290,7 +290,7 @@ void AdjustTupletsYFunctor::AdjustTupletBracketBeamY( // Make sure that there are no overlaps with staff lines const int staffMargin = (bracketPos == STAFFREL_basic_above) ? yReference + doubleUnit - : yReference - (staff->m_drawingLines - 1) * doubleUnit - doubleUnit; + : yReference - staff->GetHeightInUnits() * doubleUnit / 2 - doubleUnit; const int leftMargin = sign * (staffMargin - bracket->GetDrawingYLeft()); const int rightMargin = sign * (staffMargin - bracket->GetDrawingYRight()); diff --git a/src/beam.cpp b/src/beam.cpp index c019f7f..99cfc5d 100644 --- a/src/beam.cpp +++ b/src/beam.cpp @@ -116,7 +116,7 @@ void BeamSegment::CalcBeam(const Layer *layer, Staff *staff, const Doc *doc, Bea if (doc->GetOptions()->m_beamMaxSlope.GetValue()) horizontal = beamInterface->IsHorizontal(); // Beam@place has precedence - however, in some cases, CalcBeam is called recursively because we need to change // the place This occurs when mixed makes no sense and the beam is placed above or below instead. - this->CalcBeamPlace(layer, beamInterface, place); + this->CalcBeamPlace(layer, staff, beamInterface, place); } if (BEAMPLACE_mixed == beamInterface->m_drawingPlace) { @@ -392,7 +392,7 @@ bool BeamSegment::NeedToResetPosition(Staff *staff, const Doc *doc, BeamDrawingI // find top and bottom of the staff const int staffTop = staff->GetDrawingY(); const int staffBottom - = staffTop - doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); + = staffTop - staff->GetHeightInUnits() * doc->GetDrawingUnit(staff->m_drawingStaffSize); const int topBorder = staffTop + topOffset + unit; const int bottomBorder = staffBottom - bottomOffset - unit; @@ -1116,7 +1116,27 @@ void BeamSegment::CalcAdjustPosition(const Staff *staff, const Doc *doc, const B this->CalcSetValues(); } -void BeamSegment::CalcBeamPlace(const Layer *layer, BeamDrawingInterface *beamInterface, data_BEAMPLACE place) +bool BeamSegment::IgnoreEncodedStemDir(const Layer *layer, const Staff *staff) const +{ + if (!staff->IsBanded() || this->StemSameas()) return false; + if (layer->GetDrawingStemDir(&m_beamElementCoordRefs) != STEMDIRECTION_NONE) return false; + // Only when every note of the beam is in the middle of the staff (see Staff::IsBandedStemRecalcLoc) + for (const BeamElementCoord *coord : m_beamElementCoordRefs) { + if (!coord->m_element) continue; + if (coord->m_element->Is(NOTE)) { + if (!staff->IsBandedStemRecalcLoc(vrv_cast(coord->m_element)->GetDrawingLoc())) return false; + } + else if (coord->m_element->Is(CHORD)) { + const Chord *chord = vrv_cast(coord->m_element); + if (!staff->IsBandedStemRecalcLoc(chord->GetTopNote()->GetDrawingLoc())) return false; + if (!staff->IsBandedStemRecalcLoc(chord->GetBottomNote()->GetDrawingLoc())) return false; + } + } + return true; +} + +void BeamSegment::CalcBeamPlace( + const Layer *layer, const Staff *staff, BeamDrawingInterface *beamInterface, data_BEAMPLACE place) { assert(layer); assert(beamInterface); @@ -1129,10 +1149,11 @@ void BeamSegment::CalcBeamPlace(const Layer *layer, BeamDrawingInterface *beamIn beamInterface->m_drawingPlace = BEAMPLACE_mixed; } // Now look at the stem direction of the notes within the beam - else if (beamInterface->m_notesStemDir == STEMDIRECTION_up) { + // (encoded directions are ignored on a single-voice banded staff, see CalcStemFunctor) + else if ((beamInterface->m_notesStemDir == STEMDIRECTION_up) && !this->IgnoreEncodedStemDir(layer, staff)) { beamInterface->m_drawingPlace = BEAMPLACE_above; } - else if (beamInterface->m_notesStemDir == STEMDIRECTION_down) { + else if ((beamInterface->m_notesStemDir == STEMDIRECTION_down) && !this->IgnoreEncodedStemDir(layer, staff)) { beamInterface->m_drawingPlace = BEAMPLACE_below; } else if (beamInterface->m_crossStaffContent) { diff --git a/src/calcalignmentpitchposfunctor.cpp b/src/calcalignmentpitchposfunctor.cpp index 5dfab98..e697417 100644 --- a/src/calcalignmentpitchposfunctor.cpp +++ b/src/calcalignmentpitchposfunctor.cpp @@ -147,6 +147,8 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE Staff *staff = mRest->GetAncestorStaff(); loc = staff->m_drawingLines - 1; if (loc % 2 != 0) --loc; + // On a banded staff the C line is the closest to the middle + if (staff->IsBanded()) loc = 5; if (staff->m_drawingLines > 1) loc += 2; // Limitation: GetLayerCount does not take into account editorial markup // should be refined later @@ -182,6 +184,8 @@ FunctorCode CalcAlignmentPitchPosFunctor::VisitLayerElement(LayerElement *layerE Staff *staff = layerElement->GetAncestorStaff(); loc = staff->m_drawingLines - 1; if ((durInterface->GetDur() < DURATION_4) && (loc % 2 != 0)) --loc; + // On a banded staff the C line is the closest to the middle + if (staff->IsBanded()) loc = 5; // Adjust special cases if ((durInterface->GetDur() == DURATION_1) && (staff->m_drawingLines > 1)) loc += 2; if ((durInterface->GetDur() == DURATION_breve) && (staff->m_drawingLines < 2)) loc -= 2; diff --git a/src/calcdotsfunctor.cpp b/src/calcdotsfunctor.cpp index a944cbd..2c68caa 100644 --- a/src/calcdotsfunctor.cpp +++ b/src/calcdotsfunctor.cpp @@ -149,7 +149,7 @@ FunctorCode CalcDotsFunctor::VisitRest(Rest *rest) int loc = rest->GetDrawingLoc(); // if it's on a staff line to start with, we need to compensate here and add a full unit like DrawDots would - if ((loc % 2) == 0) { + if (staff->IsLocOnLine(loc)) { loc += 1; } diff --git a/src/calcstemfunctor.cpp b/src/calcstemfunctor.cpp index 0a4f3e9..a4570df 100644 --- a/src/calcstemfunctor.cpp +++ b/src/calcstemfunctor.cpp @@ -145,12 +145,18 @@ FunctorCode CalcStemFunctor::VisitChord(Chord *chord) int staffY = staff->GetDrawingY(); int staffSize = staff->m_drawingStaffSize; - m_verticalCenter = staffY - m_doc->GetDrawingDoubleUnit(staffSize) * 2; + m_verticalCenter = staffY - m_doc->GetDrawingUnit(staffSize) * staff->GetHeightInUnits() / 2; data_STEMDIRECTION layerStemDir; data_STEMDIRECTION stemDir = STEMDIRECTION_NONE; - if (stem->HasDir()) { + // Encoded stem directions were chosen for a standard staff; a banded staff recalculates them for chords + // in the middle of the staff unless the layer imposes a direction (multiple voices) + const bool ignoreEncodedDir = staff->IsBandedStemRecalcLoc(chord->GetTopNote()->GetDrawingLoc()) + && staff->IsBandedStemRecalcLoc(chord->GetBottomNote()->GetDrawingLoc()) + && (layer->GetDrawingStemDir(chord) == STEMDIRECTION_NONE); + + if (stem->HasDir() && !ignoreEncodedDir) { stemDir = stem->GetDir(); } else if ((layerStemDir = layer->GetDrawingStemDir(chord)) != STEMDIRECTION_NONE) { @@ -256,7 +262,7 @@ FunctorCode CalcStemFunctor::VisitNote(Note *note) const int staffSize = staff->m_drawingStaffSize; - m_verticalCenter = staff->GetDrawingY() - m_doc->GetDrawingUnit(staffSize) * (staff->m_drawingLines - 1); + m_verticalCenter = staff->GetDrawingY() - m_doc->GetDrawingUnit(staffSize) * staff->GetHeightInUnits() / 2; /************ Set the direction ************/ @@ -266,7 +272,11 @@ FunctorCode CalcStemFunctor::VisitNote(Note *note) if (note->HasStemSameasNote()) { stemDir = note->CalcStemDirForSameasNote(m_verticalCenter); } - else if (stem->HasDir()) { + else if (stem->HasDir() + && !(staff->IsBandedStemRecalcLoc(note->GetDrawingLoc()) + && (layer->GetDrawingStemDir(note) == STEMDIRECTION_NONE) && !note->IsGraceNote())) { + // Encoded stem directions were chosen for a standard staff; a banded staff recalculates them for notes + // in the middle of the staff unless the layer imposes a direction (multiple voices) stemDir = stem->GetDir(); } else if (note->IsGraceNote()) { diff --git a/src/chord.cpp b/src/chord.cpp index 494ecc9..81bd73e 100644 --- a/src/chord.cpp +++ b/src/chord.cpp @@ -39,7 +39,7 @@ namespace vrv { // Helper template function to calculate optimal dot locations based on note locations in the chord. Takes iterators // for the begin/end of the range; reverse iterators should be passed if reverse order is specified -template std::set CalculateDotLocations(Iterator begin, Iterator end, bool isReverseOrder) +template std::set CalculateDotLocations(Iterator begin, Iterator end, bool isReverseOrder, const Staff *staff) { // location adjustment that should be applied when looking for optimal position std::vector locAdjust{ 0, 1, -1, -2, 2 }; @@ -49,7 +49,7 @@ template std::set CalculateDotLocations(Iterator begin, for (auto iter = begin; iter != end; ++iter) { bool result = false; for (int adjust : locAdjust) { - if ((*iter + adjust) % 2 == 0) continue; + if (staff->IsLocOnLine(*iter + adjust)) continue; if ((prev != iter) && (*prev == *iter) && (adjust == -2)) continue; std::tie(std::ignore, result) = dotLocations.insert(*iter + adjust); if (result) break; @@ -585,10 +585,10 @@ MapOfDotLocs Chord::CalcDotLocations(int layerCount, bool primary) const MapOfDotLocs dotLocs; for (const auto &mapEntry : noteLocs) { if (useReverseOrder) { - dotLocs[mapEntry.first] = CalculateDotLocations(mapEntry.second.rbegin(), mapEntry.second.rend(), true); + dotLocs[mapEntry.first] = CalculateDotLocations(mapEntry.second.rbegin(), mapEntry.second.rend(), true, mapEntry.first); } else { - dotLocs[mapEntry.first] = CalculateDotLocations(mapEntry.second.begin(), mapEntry.second.end(), false); + dotLocs[mapEntry.first] = CalculateDotLocations(mapEntry.second.begin(), mapEntry.second.end(), false, mapEntry.first); } } return dotLocs; diff --git a/src/clef.cpp b/src/clef.cpp index b8c40fb..ba83deb 100644 --- a/src/clef.cpp +++ b/src/clef.cpp @@ -82,17 +82,36 @@ void Clef::Reset() this->ResetVisibility(); } -int Clef::GetClefLocOffset() const +int Clef::GetClefLocOffset(const Staff *staff) const { // Only resolve simple sameas links to avoid infinite recursion const Clef *sameas = dynamic_cast(this->GetSameasLink()); if (sameas && !sameas->HasSameasLink()) { - return sameas->GetClefLocOffset(); + return sameas->GetClefLocOffset(staff); } int offset = 0; int defaultOct = 4; // C clef - if (this->GetShape() == CLEFSHAPE_G) { + const bool banded = (staff && staff->IsBanded()); + if (banded) { + // On a banded staff every clef only names the octave: G clefs map to octave 5, C clefs to 4 and + // F clefs to 3. The E below that octave's C sits on the bottom line (loc 0); the clef line is ignored. + int bandedOct = 4; + if (this->GetShape() == CLEFSHAPE_G) { + bandedOct = 5; + defaultOct = 4; + } + else if (this->GetShape() == CLEFSHAPE_GG) { + bandedOct = 4; + defaultOct = 3; + } + else if (this->GetShape() == CLEFSHAPE_F) { + bandedOct = staff->m_drawingBandedFClefOctave; + defaultOct = 3; + } + offset = -2 - 7 * (bandedOct - 5); + } + else if (this->GetShape() == CLEFSHAPE_G) { defaultOct = 4; offset = -4; } @@ -111,7 +130,7 @@ int Clef::GetClefLocOffset() const offset -= octDifference * 7; } - offset += (this->GetLine() - 1) * 2; + if (!banded) offset += (this->GetLine() - 1) * 2; int disPlace = 0; if (this->HasDisPlace()) { @@ -125,6 +144,12 @@ int Clef::GetClefLocOffset() const return offset; } +int Clef::GetBandedOctave(const Staff *staff) const +{ + // offset = -2 - 7 * (oct - 5) + return 5 - (this->GetClefLocOffset(staff) + 2) / 7; +} + //---------------------------------------------------------------------------- // Static methods for Clef //---------------------------------------------------------------------------- diff --git a/src/layer.cpp b/src/layer.cpp index 4ce085b..c522eb6 100644 --- a/src/layer.cpp +++ b/src/layer.cpp @@ -282,7 +282,8 @@ int Layer::GetClefLocOffset(const LayerElement *test) const { const Clef *clef = this->GetClef(test); if (!clef) return 0; - return clef->GetClefLocOffset(); + const Staff *staff = vrv_cast(this->GetFirstAncestor(STAFF)); + return clef->GetClefLocOffset(staff); } int Layer::GetCrossStaffClefLocOffset(const LayerElement *element, int currentOffset) const @@ -292,7 +293,8 @@ int Layer::GetCrossStaffClefLocOffset(const LayerElement *element, int currentOf if (!element->Is(CLEF)) { const Clef *clef = vrv_cast(GetListFirstBackward(element, CLEF)); if (clef && clef->m_crossStaff) { - return clef->GetClefLocOffset(); + const Staff *staff = vrv_cast(this->GetFirstAncestor(STAFF)); + return clef->GetClefLocOffset(staff); } } } diff --git a/src/note.cpp b/src/note.cpp index 0ed6755..297e708 100644 --- a/src/note.cpp +++ b/src/note.cpp @@ -580,7 +580,7 @@ int Note::CalcStemLenInThirdUnits(const Staff *staff, data_STEMDIRECTION stemDir int shortening = 0; - int unitToLine = (stemDir == STEMDIRECTION_up) ? -this->GetDrawingLoc() + (staff->m_drawingLines - 1) * 2 + int unitToLine = (stemDir == STEMDIRECTION_up) ? -this->GetDrawingLoc() + staff->GetTopLineLoc() : this->GetDrawingLoc(); if (unitToLine < 5) { switch (unitToLine) { @@ -950,7 +950,7 @@ bool Note::HandleLedgerLineStemCollision(const Doc *doc, const Staff *staff, con const Chord *upperChord = upperNote->IsChordTone(); const Stem *upperStem = upperChord ? upperChord->GetDrawingStem() : upperNote->GetDrawingStem(); if (upperStem) { - const int staffBottom = staff->GetDrawingY() - 2 * unit * (staff->m_drawingLines - 1); + const int staffBottom = staff->GetDrawingY() - staff->GetHeightInUnits() * unit; const int stemBottom = upperStem->GetSelfBottom(); if (stemBottom < staffBottom - unit) { return true; @@ -1032,7 +1032,7 @@ MapOfDotLocs Note::CalcDotLocations(int layerCount, bool primary) const MapOfDotLocs dotLocs; const Staff *staff = noteLocs.cbegin()->first; int loc = *noteLocs.cbegin()->second.cbegin(); - if (loc % 2 == 0) loc += (shiftUpwards ? 1 : -1); + if (staff->IsLocOnLine(loc)) loc += (shiftUpwards ? 1 : -1); dotLocs[staff] = { loc }; return dotLocs; } diff --git a/src/options.cpp b/src/options.cpp index aa16fdb..bf550e0 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -1383,6 +1383,19 @@ Options::Options() m_ledgerLineThickness.Init(0.25, 0.10, 0.50); this->Register(&m_ledgerLineThickness, "ledgerLineThickness", &m_generalLayout); + m_bandedStaff.SetInfo("Banded staff", "Draw 5-line CMN staves as banded staves (lines on C, E and G, tinted band below C)"); + m_bandedStaff.Init(false); + this->Register(&m_bandedStaff, "bandedStaff", &m_generalLayout); + + m_bandedStaffColor.SetInfo("Banded staff color", "The fill color of the band on banded staves"); + m_bandedStaffColor.Init("#ABC1E3"); + this->Register(&m_bandedStaffColor, "bandedStaffColor", &m_generalLayout); + + m_bandedStaffFClefOctave.SetInfo("Banded staff F clef octave", + "The octave an F clef maps to on a banded staff (3 like a bass staff, or 4 for a mid-register left hand)"); + m_bandedStaffFClefOctave.Init(3, 2, 4); + this->Register(&m_bandedStaffFClefOctave, "bandedStaffFClefOctave", &m_generalLayout); + m_ledgerLineExtension.SetInfo( "Ledger line extension", "The amount by which a ledger line should extend either side of a notehead"); m_ledgerLineExtension.Init(0.54, 0.20, 1.00); diff --git a/src/positioninterface.cpp b/src/positioninterface.cpp index 0a20079..0d4ce5e 100644 --- a/src/positioninterface.cpp +++ b/src/positioninterface.cpp @@ -87,6 +87,28 @@ bool PositionInterface::HasLedgerLines(int &linesAbove, int &linesBelow, const S return false; } + if (staff->IsBanded()) { + // Every line and band patch between the staff and the note counts, including its own. + // An A above the staff also gets the patch above it, a C below the staff the patch beneath it. + const int loc = this->GetDrawingLoc(); + const int top = staff->GetTopLineLoc(); + linesAbove = 0; + linesBelow = 0; + if (loc > top) { + const int target = Staff::IsBandedPatchLoc(loc + 1) ? loc + 1 : loc; + for (int l = top + 1; l <= target; ++l) { + if (Staff::IsBandedLineLoc(l) || Staff::IsBandedPatchLoc(l)) ++linesAbove; + } + } + else if (loc < 0) { + const int target = Staff::IsBandedPatchLoc(loc - 1) ? loc - 1 : loc; + for (int l = -1; l >= target; --l) { + if (Staff::IsBandedLineLoc(l) || Staff::IsBandedPatchLoc(l)) ++linesBelow; + } + } + return ((linesAbove > 0) || (linesBelow > 0)); + } + linesAbove = (this->GetDrawingLoc() - staff->m_drawingLines * 2 + 2) / 2; linesBelow = -(this->GetDrawingLoc()) / 2; diff --git a/src/setscoredeffunctor.cpp b/src/setscoredeffunctor.cpp index 6c34f73..9190020 100644 --- a/src/setscoredeffunctor.cpp +++ b/src/setscoredeffunctor.cpp @@ -322,6 +322,9 @@ FunctorCode ScoreDefSetCurrentFunctor::VisitStaff(Staff *staff) staff->m_drawingTuning = vrv_cast(m_currentStaffDef->FindDescendantByType(TUNING)); staff->m_drawingLines = m_currentStaffDef->GetLines(); staff->m_drawingNotationType = m_currentStaffDef->GetNotationtype(); + staff->m_drawingBanded = m_doc->GetOptions()->m_bandedStaff.GetValue() && (staff->m_drawingLines == 5) + && (staff->m_drawingNotationType == NOTATIONTYPE_NONE || staff->m_drawingNotationType == NOTATIONTYPE_cmn); + staff->m_drawingBandedFClefOctave = m_doc->GetOptions()->m_bandedStaffFClefOctave.GetValue(); staff->m_drawingStaffSize = 100; if (m_currentStaffDef->HasScale()) { staff->m_drawingStaffSize = m_currentStaffDef->GetScale(); @@ -767,6 +770,9 @@ FunctorCode ScoreDefSetOssiaFunctor::VisitStaff(Staff *staff) staff->m_drawingTuning = vrv_cast(m_currentStaffDef->FindDescendantByType(TUNING)); staff->m_drawingLines = m_currentStaffDef->GetLines(); staff->m_drawingNotationType = m_currentStaffDef->GetNotationtype(); + staff->m_drawingBanded = m_doc->GetOptions()->m_bandedStaff.GetValue() && (staff->m_drawingLines == 5) + && (staff->m_drawingNotationType == NOTATIONTYPE_NONE || staff->m_drawingNotationType == NOTATIONTYPE_cmn); + staff->m_drawingBandedFClefOctave = m_doc->GetOptions()->m_bandedStaffFClefOctave.GetValue(); staff->m_drawingStaffSize = 100; if (m_currentStaffDef->HasScale()) { staff->m_drawingStaffSize = m_currentStaffDef->GetScale(); diff --git a/src/slur.cpp b/src/slur.cpp index 4f45cbd..0e9a32b 100644 --- a/src/slur.cpp +++ b/src/slur.cpp @@ -1048,7 +1048,7 @@ int Slur::CalcPitchDifference(const Staff *staff, int startLoc, int endLoc) cons { assert(staff); - const int staffTopLoc = 2 * (staff->m_drawingLines - 1); + const int staffTopLoc = staff->GetTopLineLoc(); const int loc1 = this->HasEndpointAboveStart() ? std::max(startLoc, staffTopLoc - 1) : std::min(startLoc, 1); const int loc2 = this->HasEndpointAboveEnd() ? std::max(endLoc, staffTopLoc - 1) : std::min(endLoc, 1); diff --git a/src/staff.cpp b/src/staff.cpp index 71a7cb4..818a1c8 100644 --- a/src/staff.cpp +++ b/src/staff.cpp @@ -84,6 +84,8 @@ void Staff::Reset() m_drawingStaffSize = 100; m_drawingLines = 5; + m_drawingBanded = false; + m_drawingBandedFClefOctave = 3; m_drawingNotationType = NOTATIONTYPE_NONE; m_staffAlignment = NULL; m_timeSpanningElements.clear(); @@ -100,6 +102,8 @@ void Staff::CloneReset() m_drawingStaffSize = 100; m_drawingLines = 5; + m_drawingBanded = false; + m_drawingBandedFClefOctave = 3; m_drawingNotationType = NOTATIONTYPE_NONE; m_staffAlignment = NULL; m_timeSpanningElements.clear(); @@ -281,11 +285,66 @@ int Staff::CalcPitchPosYRel(const Doc *doc, int loc) const { assert(doc); + if (m_drawingBanded) { + // top line is at banded position 9 + return std::round((GetBandedPos(loc) - 9.0) * doc->GetDrawingUnit(m_drawingStaffSize)); + } + // the staff loc offset is based on the number of lines: 0 with 1 line, 2 with 2, etc int staffLocOffset = (m_drawingLines - 1) * 2; return (loc - staffLocOffset) * doc->GetDrawingUnit(m_drawingStaffSize); } +namespace { + // Degree of a banded loc counted from C (loc 0 is E) + int BandedDegree(int loc) { return (((loc + 2) % 7) + 7) % 7; } +} // namespace + +double Staff::GetBandedPos(int loc) +{ + // Half-space offsets of C D E F G A B from C: the band between G and C is three half-spaces tall, + // A sits in its lower (clear) half, B in its upper (tinted) half + static const double offsets[7] = { 0.0, 1.0, 2.0, 3.0, 4.0, 4.75, 6.25 }; + const int d = loc + 2; + const int degree = BandedDegree(loc); + const int octave = (d - degree) / 7; + return octave * 7 + offsets[degree] - 2.0; +} + +bool Staff::IsBandedLineLoc(int loc) +{ + const int degree = BandedDegree(loc); + return (degree == 0 || degree == 2 || degree == 4); +} + +bool Staff::IsBandedPatchLoc(int loc) +{ + return (BandedDegree(loc) == 6); +} + +int Staff::GetTopLineLoc() const +{ + return m_drawingBanded ? 9 : (m_drawingLines - 1) * 2; +} + +bool Staff::IsLocOnLine(int loc) const +{ + if (m_drawingBanded) return IsBandedLineLoc(loc); + return ((loc % 2) == 0); +} + +int Staff::GetBandedLedgerLoc(int n, bool below, bool &isPatch) const +{ + int loc = below ? 0 : this->GetTopLineLoc(); + int count = 0; + while (count < n) { + loc += below ? -1 : 1; + if (IsBandedLineLoc(loc) || IsBandedPatchLoc(loc)) ++count; + } + isPatch = IsBandedPatchLoc(loc); + return loc; +} + void Staff::AddLedgerLineAbove(int count, int left, int right, int extension, bool cueSize, const Object *event) { this->AddLedgerLines(cueSize ? m_ledgerLinesAboveCue : m_ledgerLinesAbove, count, left, right, extension, event); @@ -348,6 +407,14 @@ bool Staff::IsOnStaffLine(int y, const Doc *doc) const { assert(doc); + if (m_drawingBanded) { + const int unit = doc->GetDrawingUnit(m_drawingStaffSize); + for (int loc = -30; loc < 40; ++loc) { + if (!IsBandedLineLoc(loc)) continue; + if (std::abs(y - this->GetDrawingY() - this->CalcPitchPosYRel(doc, loc)) < unit / 4) return true; + } + return false; + } return ((y - this->GetDrawingY()) % (2 * doc->GetDrawingUnit(m_drawingStaffSize)) == 0); } diff --git a/src/verticalaligner.cpp b/src/verticalaligner.cpp index 6f6a1f7..ae7559d 100644 --- a/src/verticalaligner.cpp +++ b/src/verticalaligner.cpp @@ -403,7 +403,7 @@ void StaffAlignment::SetStaff(Staff *staff, const Doc *doc, SystemAligner::Spaci m_staff = staff; m_spacingType = spacingType; if (staff && doc) { - m_staffHeight = (staff->m_drawingLines - 1) * doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + m_staffHeight = staff->GetHeightInUnits() * doc->GetDrawingUnit(staff->m_drawingStaffSize); } } diff --git a/src/view_element.cpp b/src/view_element.cpp index a367a75..1550bb0 100644 --- a/src/view_element.cpp +++ b/src/view_element.cpp @@ -312,7 +312,7 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta Note *note = vrv_cast(accid->GetFirstAncestor(NOTE, MAX_ACCID_DEPTH)); if (!editorialAccid && note) { const int staffTop = staff->GetDrawingY(); - const int staffBottom = staffTop - (staff->m_drawingLines - 1) * unit * 2; + const int staffBottom = staffTop - staff->GetHeightInUnits() * unit; const data_DURATION drawingDur = note->GetDrawingDur(); int noteTop = note->GetDrawingTop(m_doc, staff->m_drawingStaffSize); int noteBottom = note->GetDrawingBottom(m_doc, staff->m_drawingStaffSize); @@ -321,7 +321,7 @@ void View::DrawAccid(DeviceContext *dc, LayerElement *element, Layer *layer, Sta // Adjust position to mensural stems if (note->IsMensuralDur()) { if (accid->GetFunc() != accidLog_FUNC_edit) onStaff = (accid->GetOnstaff() != BOOLEAN_false); - const int verticalCenter = staffTop - (staff->m_drawingLines - 1) * unit; + const int verticalCenter = staffTop - staff->GetHeightInUnits() * unit / 2; const data_STEMDIRECTION stemDir = this->GetMensuralStemDir(layer, note, verticalCenter); if ((drawingDur > DURATION_1) || (drawingDur < DURATION_breve)) { if (stemDir == STEMDIRECTION_up) { @@ -478,7 +478,7 @@ void View::DrawBarLine(DeviceContext *dc, LayerElement *element, Layer *layer, S dc->StartGraphic(element, "", element->GetID()); int yTop = staff->GetDrawingY(); - int yBottom = yTop - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize); + int yBottom = yTop - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); if (method == BARMETHOD_takt) { yTop += m_doc->GetDrawingUnit(staff->m_drawingStaffSize); @@ -510,7 +510,7 @@ void View::DrawBeatRpt(DeviceContext *dc, LayerElement *element, Layer *layer, S const int staffSize = staff->m_drawingStaffSize; const int xSymbol = element->GetDrawingX(); - const int ySymbol = element->GetDrawingY() - (staff->m_drawingLines - 1) * m_doc->GetDrawingUnit(staffSize); + const int ySymbol = element->GetDrawingY() - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize) / 2; if (beatRpt->GetSlash() == BEATRPT_REND_mixed) { this->DrawSmuflCode(dc, xSymbol, ySymbol, SMUFL_E501_repeat2Bars, staffSize, false); @@ -541,7 +541,7 @@ void View::DrawBTrem(DeviceContext *dc, LayerElement *element, Layer *layer, Sta const int staffSize = staff->m_drawingStaffSize; int xOffset = 0; int yTop = staff->GetDrawingY(); - int yBottom = yTop - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + int yBottom = yTop - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); // Get the chord or note child Object *bTremElement = bTrem->FindDescendantByType(CHORD); @@ -664,7 +664,7 @@ void View::DrawChordCluster(DeviceContext *dc, Chord *chord, Layer *layer, Staff if (chord->GetCluster() != CLUSTER_chromatic) { const int staffTop = staff->GetDrawingY(); - const int staffBottom = staffTop - (staff->m_drawingLines - 1) * unit * 2; + const int staffBottom = staffTop - staff->GetHeightInUnits() * unit; const int accidGlyph = (chord->GetCluster() == CLUSTER_black) ? SMUFL_E260_accidentalFlat : SMUFL_E261_accidentalNatural; const int accidX = x + (width - m_doc->GetGlyphWidth(accidGlyph, staffSize, true)) / 2; @@ -731,6 +731,11 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf this->CalcOffset(dc, x, y); + if (staff->IsBanded() && (clef->GetShape() != CLEFSHAPE_perc)) { + this->DrawBandedClef(dc, clef, staff, x, y); + return; + } + char32_t sym = clef->GetClefGlyph(staff->m_drawingNotationType); if (sym == 0) { @@ -745,7 +750,7 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf } } else if (clef->GetShape() == CLEFSHAPE_perc) { - y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); + y -= m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * staff->GetHeightInUnits() / 2; } else { // we have no information where to put it @@ -774,6 +779,43 @@ void View::DrawClef(DeviceContext *dc, LayerElement *element, Layer *layer, Staf dc->EndGraphic(element, this); } +void View::DrawBandedClef(DeviceContext *dc, Clef *clef, Staff *staff, int x, int y) +{ + // A banded clef only names the octave: a thick and a thin bar, a wedge pointing at the C line, + // and the octave number of that C above the bars + const int staffSize = staff->m_drawingStaffSize; + const int unit = m_doc->GetDrawingUnit(staffSize); + const int yTop = y + unit * 2 / 5; + const int yBottom = y - staff->GetHeightInUnits() * unit - unit * 2 / 5; + const int yC = y + staff->CalcPitchPosYRel(m_doc, 5); + + dc->StartGraphic(clef, "", clef->GetID()); + + this->DrawFilledRectangle(dc, x, yBottom, x + unit * 7 / 10, yTop); + this->DrawFilledRectangle(dc, x + unit * 11 / 10, yBottom, x + unit * 13 / 10 + unit / 10, yTop); + + Point p[3]; + p[0] = Point(this->ToDeviceContextX(x + unit * 18 / 10), this->ToDeviceContextY(yC + unit * 14 / 10)); + p[1] = Point(this->ToDeviceContextX(x + unit * 38 / 10), this->ToDeviceContextY(yC)); + p[2] = Point(this->ToDeviceContextX(x + unit * 18 / 10), this->ToDeviceContextY(yC - unit * 14 / 10)); + dc->SetPen(0, PEN_SOLID); + dc->DrawPolygon(3, p); + dc->ResetPen(); + + const int octave = clef->GetBandedOctave(staff); + if ((octave >= 0) && (octave <= 9)) { + const char32_t digit = SMUFL_E080_timeSig0 + octave; + const int digitSize = staffSize * 6 / 10; + const int digitWidth = m_doc->GetGlyphWidth(digit, digitSize, false); + const int digitBottom = m_doc->GetGlyphBottom(digit, digitSize, false); + dc->SetFont(m_doc->GetDrawingSmuflFont(digitSize, false)); + this->DrawSmuflCode(dc, x + unit * 7 / 10 - digitWidth / 2, yTop + unit * 3 / 4 - digitBottom, digit, digitSize, false); + dc->ResetFont(); + } + + dc->EndGraphic(clef, this); +} + void View::DrawClefEnclosing(DeviceContext *dc, Clef *clef, Staff *staff, char32_t glyph, int x, int y) { if ((clef->GetEnclose() == ENCLOSURE_brack) || (clef->GetEnclose() == ENCLOSURE_box)) { @@ -901,7 +943,7 @@ void View::DrawDots(DeviceContext *dc, LayerElement *element, Layer *layer, Staf for (const auto &mapEntry : dots->GetMapOfDotLocs()) { const Staff *dotStaff = (mapEntry.first) ? mapEntry.first : staff; int y = dotStaff->GetDrawingY() - - m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * (dotStaff->m_drawingLines - 1); + - dotStaff->GetHeightInUnits() * m_doc->GetDrawingUnit(staff->m_drawingStaffSize); int x = dots->GetDrawingX() + m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * offsetFactor; this->CalcOffset(dc, x, y); @@ -1039,7 +1081,15 @@ void View::DrawKeySig(DeviceContext *dc, LayerElement *element, Layer *layer, St keySig->SetEmptyBB(); return; } - const int clefLocOffset = clef->GetClefLocOffset(); + int clefLocOffset = clef->GetClefLocOffset(); + // Every banded staff is identical, so the key signature always takes the treble pattern + if (staff->IsBanded()) { + static Clef bandedKeySigClef; + bandedKeySigClef.SetShape(CLEFSHAPE_G); + bandedKeySigClef.SetLine(2); + clef = &bandedKeySigClef; + clefLocOffset = -2; + } // hidden key signature if (!showHidden && (keySig->GetVisible() == BOOLEAN_false)) { @@ -1187,7 +1237,7 @@ void View::DrawMeterSig(DeviceContext *dc, MeterSig *meterSig, Staff *staff, int resources.SetCurrentFont(meterSig->GetFontname()); } - int y = staff->GetDrawingY() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); + int y = staff->GetDrawingY() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * staff->GetHeightInUnits() / 2; int x = meterSig->GetDrawingX() + horizOffset; const int glyphSize = staff->GetDrawingStaffNotationSize(); @@ -1261,7 +1311,7 @@ void View::DrawMRest(DeviceContext *dc, LayerElement *element, Layer *layer, Sta // single legder line for whole rest glyphs if (!isDouble && (y > staff->GetDrawingY() - || y < staff->GetDrawingY() - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize))) { + || y < staff->GetDrawingY() - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize))) { const int width = m_doc->GetGlyphWidth(rest, staffSize, drawingCueSize); int ledgerLineThickness = m_doc->GetOptions()->m_ledgerLineThickness.GetValue() * m_doc->GetDrawingUnit(staffSize); @@ -1310,7 +1360,7 @@ void View::DrawMRpt(DeviceContext *dc, LayerElement *element, Layer *layer, Staf TextExtend extend; const std::u32string figures = this->IntToTupletFigures(mRptNum); dc->GetSmuflTextExtent(figures, &extend); - const int staffHeight = (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + const int staffHeight = staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); const int offset = std::max(m_doc->GetGlyphHeight(SMUFL_E500_repeat1Bar, staffSize, false) - staffHeight, 0); int yNum = staff->GetDrawingY() + m_doc->GetDrawingUnit(staffSize) + offset / 2; if (mRpt->GetNumPlace() == STAFFREL_basic_below) { @@ -1402,7 +1452,7 @@ void View::DrawMultiRest(DeviceContext *dc, LayerElement *element, Layer *layer, const int multiRestThickness = m_doc->GetDrawingUnit(staffNotationSize) * m_doc->GetOptions()->m_multiRestThickness.GetValue(); // Position centered in staff - int y2 = staff->GetDrawingY() - m_doc->GetDrawingUnit(staffSize) * (staff->m_drawingLines - 1) + int y2 = staff->GetDrawingY() - m_doc->GetDrawingUnit(staffSize) * staff->GetHeightInUnits() / 2 - multiRestThickness / 2; if (multiRest->HasLoc()) { y2 -= m_doc->GetDrawingUnit(staffSize) * (staff->m_drawingLines - 1 - multiRest->GetLoc()); @@ -1473,7 +1523,7 @@ void View::DrawMultiRest(DeviceContext *dc, LayerElement *element, Layer *layer, dc->StartCustomGraphic("multiRestNum", (multiRest->GetNumVisible() == BOOLEAN_false) ? CSS_SHOW_HIDDEN : ""); dc->SetFont(m_doc->GetDrawingSmuflFont(staffNotationSize, false)); - const int staffHeight = (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + const int staffHeight = staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); const int offset = 3 * m_doc->GetDrawingUnit(staffNotationSize); const int y = (multiRest->GetNumPlace() == STAFFREL_basic_below) @@ -1695,7 +1745,7 @@ void View::DrawRest(DeviceContext *dc, LayerElement *element, Layer *layer, Staf const int ledgerLineExtension = m_doc->GetOptions()->m_ledgerLineExtension.GetValue() * drawingUnit; const int topMargin = staff->GetDrawingY(); const int bottomMargin - = staff->GetDrawingY() - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + = staff->GetDrawingY() - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); dc->StartCustomGraphic("ledgerLines"); // single legder line for half and whole rests @@ -1761,7 +1811,7 @@ void View::DrawStem(DeviceContext *dc, LayerElement *element, Layer *layer, Staf if (parent->GetDrawingDur() > DURATION_1) { /************** Stem/notehead direction: **************/ const int staffCenter - = staff->GetDrawingY() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * (staff->m_drawingLines - 1); + = staff->GetDrawingY() - m_doc->GetDrawingUnit(staff->m_drawingStaffSize) * staff->GetHeightInUnits() / 2; const data_STEMDIRECTION stemDir = (stem->HasDir()) ? stem->GetDir() : this->GetMensuralStemDir(layer, parent, staffCenter); /************** Draw stem: **************/ @@ -2288,12 +2338,12 @@ void View::DrawMRptPart(DeviceContext *dc, int xCentered, int y, char32_t rptGly const int staffNotationSize = staff->GetDrawingStaffNotationSize(); const int staffSize = staff->m_drawingStaffSize; const int xSymbol = xCentered - m_doc->GetGlyphWidth(rptGlyph, staffNotationSize, false) / 2; - const int ySymbol = y - (staff->m_drawingLines - 1) * m_doc->GetDrawingUnit(staffSize); + const int ySymbol = y - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize) / 2; this->DrawSmuflCode(dc, xSymbol, ySymbol, rptGlyph, staffNotationSize, false); if (line) { - const int yBottom = y - (staff->m_drawingLines - 1) * m_doc->GetDrawingDoubleUnit(staffSize); + const int yBottom = y - staff->GetHeightInUnits() * m_doc->GetDrawingUnit(staffSize); const int offset = (y == ySymbol) ? m_doc->GetDrawingDoubleUnit(staffSize) : 0; this->DrawVerticalLine( dc, y + offset, yBottom - offset, xCentered, m_doc->GetDrawingBarLineWidth(staffNotationSize)); diff --git a/src/view_page.cpp b/src/view_page.cpp index ccb7483..1298bd7 100644 --- a/src/view_page.cpp +++ b/src/view_page.cpp @@ -1105,7 +1105,7 @@ void View::DrawMeterSigGrp(DeviceContext *dc, Layer *layer, Staff *staff) assert(meterSig); this->DrawMeterSig(dc, meterSig, staff, offset); - const int y = staff->GetDrawingY() - unit * (staff->m_drawingLines - 1); + const int y = staff->GetDrawingY() - unit * staff->GetHeightInUnits() / 2; const int x = meterSig->GetDrawingX() + offset; const int width = meterSig->GetContentRight() - meterSig->GetContentLeft(); if ((meterSigGrp->GetFunc() == meterSigGrpLog_FUNC_mixed) && (iter != std::prev(childList.end()))) { @@ -1210,7 +1210,7 @@ void View::DrawOssia(DeviceContext *dc, Ossia *ossia, Measure *measure, System * const int x = topOStaff->GetDrawingX() + topOStaff->GetOssiaDrawingShift(measure, m_doc); const int y1 = topOStaff->GetDrawingY(); const int doubleUnit = m_doc->GetDrawingDoubleUnit(staffSize); - const int y2 = bottomOStaff->GetDrawingY() - doubleUnit * (bottomOStaff->m_drawingLines - 1); + const int y2 = bottomOStaff->GetDrawingY() - bottomOStaff->GetHeightInUnits() * doubleUnit / 2; // Bar lines always 100 const int barLineWidth = m_doc->GetDrawingBarLineWidth(100); this->DrawVerticalLine(dc, y1, y2, x + barLineWidth / 2, barLineWidth); @@ -1368,6 +1368,14 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, StaffDef *staffDef, M y1 -= (m_doc->GetDrawingDoubleUnit(staff->m_drawingStaffSize) * staff->m_drawingLines) * 11 / 10; this->DrawHorizontalSegmentedLine(dc, y1, line, lineWidth / 2); } + // Banded staff: the band below the C line, then lines on E, G, C, E, G + else if (staff->IsBanded() && (y1 == y2)) { + this->DrawBandPatch(dc, staff, x1, x2, 4); + for (const int loc : { 0, 2, 5, 7, 9 }) { + SegmentedLine line(x1, x2); + this->DrawHorizontalSegmentedLine(dc, staff->GetDrawingY() + staff->CalcPitchPosYRel(m_doc, loc), line, lineWidth); + } + } // Normal staff lines else { // draw staff lines @@ -1411,6 +1419,34 @@ void View::DrawStaffLines(DeviceContext *dc, Staff *staff, StaffDef *staffDef, M return; } +void View::DrawBandPatch(DeviceContext *dc, Staff *staff, int x1, int x2, int patchLoc) +{ + assert(dc); + assert(staff); + + // The band is three half-spaces tall, centred on the B (patch) position + const int unit = m_doc->GetDrawingUnit(staff->m_drawingStaffSize); + const int yCenter = staff->GetDrawingY() + staff->CalcPitchPosYRel(m_doc, patchLoc); + const int yTop = yCenter + 3 * unit / 4; + const int yBottom = yCenter - 3 * unit / 4; + + int color = 0xABC1E3; + const std::string colorStr = m_doc->GetOptions()->m_bandedStaffColor.GetValue(); + if (colorStr.size() == 7 && colorStr.at(0) == '#') { + color = (int)std::strtol(colorStr.c_str() + 1, NULL, 16); + } + + // No custom graphic here: the SVG output places primitives before nested groups, so the patch would be + // drawn over the (ledger) lines + // Zero stroke opacity: the SVG stylesheet sets every rect's stroke to currentColor, which would outline the patch + dc->SetPen(0, PEN_SOLID, 0, 0, LINECAP_DEFAULT, LINEJOIN_DEFAULT, 0.0, color); + dc->SetBrush(1.0, color); + dc->DrawRectangle(this->ToDeviceContextX(x1), this->ToDeviceContextY(yTop), this->ToDeviceContextX(x2 - x1), + this->ToDeviceContextX(yTop - yBottom)); + dc->ResetBrush(); + dc->ResetPen(); +} + void View::DrawLedgerLines(DeviceContext *dc, Staff *staff, const ArrayOfLedgerLines &lines, bool below, bool cueSize) { assert(dc); @@ -1442,6 +1478,30 @@ void View::DrawLedgerLines(DeviceContext *dc, Staff *staff, const ArrayOfLedgerL bool svgHtml5 = (m_doc->GetOptions()->m_svgHtml5.GetValue()); + if (staff->IsBanded()) { + // Band patches first so that the ledger lines are drawn over them + for (const bool drawPatches : { true, false }) { + for (int i = 0; i < (int)lines.size(); ++i) { + bool isPatch = false; + const int loc = staff->GetBandedLedgerLoc(i + 1, below, isPatch); + if (isPatch != drawPatches) continue; + const int yLoc = staff->GetDrawingY() + staff->CalcPitchPosYRel(m_doc, loc); + for (const LedgerLine::Dash &dash : lines.at(i).m_dashes) { + if (isPatch) { + this->DrawBandPatch(dc, staff, x + dash.m_x1, x + dash.m_x2, loc); + } + else { + dc->DrawLine(this->ToDeviceContextX(x + dash.m_x1), this->ToDeviceContextY(yLoc), + this->ToDeviceContextX(x + dash.m_x2), this->ToDeviceContextY(yLoc)); + } + } + } + } + dc->ResetPen(); + dc->EndCustomGraphic(); + return; + } + for (const LedgerLine &line : lines) { for (const LedgerLine::Dash &dash : line.m_dashes) { if (svgHtml5) { @@ -1648,8 +1708,8 @@ void View::DrawSystemDivider(DeviceContext *dc, System *system, Measure *firstMe // substract offset of the system divider symbol itself (added to y2 and y4) if (bottomStaff) { previousSystemBottomMarginY = bottomStaff->GetDrawingY() - - (bottomStaff->m_drawingLines - 1) - * m_doc->GetDrawingDoubleUnit(bottomStaff->m_drawingStaffSize) + - bottomStaff->GetHeightInUnits() + * m_doc->GetDrawingUnit(bottomStaff->m_drawingStaffSize) - m_doc->GetDrawingUnit(100) * 5; } }