@@ -71,7 +71,7 @@ export abstract class LineBarRenderer extends BarRendererBase {
7171
7272 protected updateFirstLineY ( ) {
7373 const fullLineHeight = this . lineOffset * ( this . heightLineCount - 1 ) ;
74- const actualLineHeight = ( this . drawnLineCount - 1 ) * this . lineOffset ;
74+ const actualLineHeight = this . drawnLineCount === 0 ? 0 : ( this . drawnLineCount - 1 ) * this . lineOffset ;
7575 const lineYOffset = this . smuflMetrics . staffLineThickness / 2 ;
7676
7777 this . firstLineY = ( ( ( fullLineHeight - actualLineHeight ) / 2 ) | 0 ) - lineYOffset ;
@@ -471,7 +471,7 @@ export abstract class LineBarRenderer extends BarRendererBase {
471471 beamsElement : BeatSubElement
472472 ) : void {
473473 canvas . color = h . voice ! . index === 0 ? this . resources . mainGlyphColor : this . resources . secondaryGlyphColor ;
474- if ( ! h . isRestBeamHelper ) {
474+ if ( this . shouldPaintBeamingHelper ( h ) ) {
475475 if ( this . drawBeamHelperAsFlags ( h ) ) {
476476 this . paintFlag ( cx , cy , canvas , h , flagsElement ) ;
477477 } else {
@@ -480,8 +480,13 @@ export abstract class LineBarRenderer extends BarRendererBase {
480480 }
481481 }
482482
483+ protected shouldPaintBeamingHelper ( h : BeamingHelper ) {
484+ return ! h . isRestBeamHelper ;
485+ }
486+
483487 protected abstract getFlagTopY ( beat : Beat , direction : BeamDirection ) : number ;
484488 protected abstract getFlagBottomY ( beat : Beat , direction : BeamDirection ) : number ;
489+
485490 protected shouldPaintFlag ( beat : Beat ) : boolean {
486491 // no flags for bend grace beats
487492 if ( beat . graceType === GraceType . BendGrace ) {
@@ -654,8 +659,8 @@ export abstract class LineBarRenderer extends BarRendererBase {
654659 const direction : BeamDirection = this . getBeamDirection ( h ) ;
655660 const isGrace : boolean = h . graceType !== GraceType . None ;
656661 const scaleMod : number = isGrace ? EngravingSettings . GraceScale : 1 ;
657- let barSpacing : number = ( this . smuflMetrics . beamSpacing + this . smuflMetrics . beamThickness ) * scaleMod ;
658- let barSize : number = this . smuflMetrics . beamThickness * scaleMod ;
662+ let barSpacing : number = ( this . beamSpacing + this . beamThickness ) * scaleMod ;
663+ let barSize : number = this . beamThickness * scaleMod ;
659664 if ( direction === BeamDirection . Down ) {
660665 barSpacing = - barSpacing ;
661666 barSize = - barSize ;
@@ -833,30 +838,49 @@ export abstract class LineBarRenderer extends BarRendererBase {
833838
834839 for ( const v of this . helpers . beamHelpers ) {
835840 for ( const h of v ) {
836- if ( h . isRestBeamHelper ) {
837- // no stems or beams to consider
841+ if ( ! this . shouldPaintBeamingHelper ( h ) ) {
842+ // no visible helper
838843 }
839844 // notes with stems
840845 else if ( h . beats . length === 1 && h . beats [ 0 ] . duration >= Duration . Half ) {
846+ const tupletDirection = this . getTupletBeamDirection ( h ) ;
841847 if ( h . direction === BeamDirection . Up ) {
842848 let topY = this . getFlagTopY ( h . beats [ 0 ] , h . direction ) ;
843- if ( h . hasTuplet ) {
849+ if ( h . hasTuplet && tupletDirection === h . direction ) {
844850 topY -= this . tupletSize + this . tupletOffset ;
845851 }
846852 if ( topY < maxNoteY ) {
847853 maxNoteY = topY ;
848854 }
849855
856+ if ( h . hasTuplet && tupletDirection !== h . direction ) {
857+ let bottomY = this . getFlagBottomY ( h . beats [ 0 ] , tupletDirection ) ;
858+ bottomY += this . tupletSize + this . tupletOffset ;
859+
860+ if ( bottomY > minNoteY ) {
861+ minNoteY = bottomY ;
862+ }
863+ }
864+
850865 // bottom handled via beat container bBox
851866 } else {
852867 let bottomY = this . getFlagBottomY ( h . beats [ 0 ] , h . direction ) ;
853- if ( h . hasTuplet ) {
868+ if ( h . hasTuplet && tupletDirection === h . direction ) {
854869 bottomY += this . tupletSize + this . tupletOffset ;
855870 }
856871 if ( bottomY > minNoteY ) {
857872 minNoteY = bottomY ;
858873 }
859874
875+ if ( h . hasTuplet && tupletDirection !== h . direction ) {
876+ let topY = this . getFlagTopY ( h . beats [ 0 ] , tupletDirection ) ;
877+ topY -= this . tupletSize + this . tupletOffset ;
878+
879+ if ( topY < maxNoteY ) {
880+ maxNoteY = topY ;
881+ }
882+ }
883+
860884 // top handled via beat container bBox
861885 }
862886 }
@@ -865,35 +889,43 @@ export abstract class LineBarRenderer extends BarRendererBase {
865889 else {
866890 this . ensureBeamDrawingInfo ( h , h . direction ) ;
867891 const drawingInfo = h . drawingInfos . get ( h . direction ) ! ;
892+ const tupletDirection = this . getTupletBeamDirection ( h ) ;
868893
869894 if ( h . direction === BeamDirection . Up ) {
870895 let topY = Math . min ( drawingInfo . startY , drawingInfo . endY ) ;
871- if ( h . hasTuplet ) {
896+ if ( h . hasTuplet && tupletDirection === h . direction ) {
872897 topY -= this . tupletSize + this . tupletOffset ;
873898 }
874899
875900 if ( topY < maxNoteY ) {
876901 maxNoteY = topY ;
877902 }
878903
879- const bottomY : number =
904+ let bottomY : number =
880905 this . getBarLineStart ( h . beatOfLowestNote , h . direction ) + noteOverflowPadding ;
906+ if ( h . hasTuplet && tupletDirection !== h . direction ) {
907+ bottomY += this . tupletSize + this . tupletOffset ;
908+ }
909+
881910 if ( bottomY > minNoteY ) {
882911 minNoteY = bottomY ;
883912 }
884913 } else {
885914 let bottomY = Math . max ( drawingInfo . startY , drawingInfo . endY ) ;
886915
887- if ( h . hasTuplet ) {
916+ if ( h . hasTuplet && tupletDirection === h . direction ) {
888917 bottomY += this . tupletSize + this . tupletOffset ;
889918 }
890919
891920 if ( bottomY > minNoteY ) {
892921 minNoteY = bottomY ;
893922 }
894923
895- const topY : number =
896- this . getBarLineStart ( h . beatOfHighestNote , h . direction ) - noteOverflowPadding ;
924+ let topY : number = this . getBarLineStart ( h . beatOfHighestNote , h . direction ) - noteOverflowPadding ;
925+ if ( h . hasTuplet && tupletDirection !== h . direction ) {
926+ topY -= this . tupletSize + this . tupletOffset ;
927+ }
928+
897929 if ( topY < maxNoteY ) {
898930 maxNoteY = topY ;
899931 }
@@ -911,27 +943,12 @@ export abstract class LineBarRenderer extends BarRendererBase {
911943 }
912944 }
913945
914- protected ensureBeamDrawingInfo ( h : BeamingHelper , direction : BeamDirection ) : void {
915- if ( h . drawingInfos . has ( direction ) ) {
916- return ;
917- }
918- const scale = h . graceType !== GraceType . None ? EngravingSettings . GraceScale : 1 ;
919- const barCount : number = ModelUtils . getIndex ( h . shortestDuration ) - 2 ;
920-
946+ protected initializeBeamDrawingInfo ( h : BeamingHelper , direction : BeamDirection ) {
921947 const drawingInfo = new BeamingHelperDrawInfo ( ) ;
922- h . drawingInfos . set ( direction , drawingInfo ) ;
923-
924- // the beaming logic works like this:
925- // 1. we take the first and last note, add the stem, and put a diagnal line between them.
926- // 2. the height of the diagonal line must not exceed a max height,
927- // - if this is the case, the line on the more distant note just gets longer
928- // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps
929948
930949 const firstBeat = h . beats [ 0 ] ;
931950 const lastBeat = h . beats [ h . beats . length - 1 ] ;
932951
933- const isRest = h . isRestBeamHelper ;
934-
935952 // 1. put direct diagonal line.
936953 drawingInfo . startBeat = firstBeat ;
937954 drawingInfo . startX = this . getBeatX ( firstBeat , BeatXPosition . Stem ) ;
@@ -980,13 +997,41 @@ export abstract class LineBarRenderer extends BarRendererBase {
980997 drawingInfo . startY = drawingInfo . endY + maxSlope ;
981998 }
982999
1000+ return drawingInfo ;
1001+ }
1002+
1003+ protected get beamSpacing ( ) {
1004+ return this . smuflMetrics . beamSpacing ;
1005+ }
1006+ protected get beamThickness ( ) {
1007+ return this . smuflMetrics . beamThickness ;
1008+ }
1009+
1010+ protected ensureBeamDrawingInfo ( h : BeamingHelper , direction : BeamDirection ) : void {
1011+ if ( h . drawingInfos . has ( direction ) ) {
1012+ return ;
1013+ }
1014+
1015+ // the beaming logic works like this:
1016+ // 1. we take the first and last note, add the stem, and put a diagnal line between them.
1017+ // 2. the height of the diagonal line must not exceed a max height,
1018+ // - if this is the case, the line on the more distant note just gets longer
1019+ // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps
1020+
1021+ const drawingInfo = this . initializeBeamDrawingInfo ( h , direction ) ;
1022+ h . drawingInfos . set ( direction , drawingInfo ) ;
1023+
1024+ const isRest = h . isRestBeamHelper ;
1025+ const scale = h . graceType !== GraceType . None ? EngravingSettings . GraceScale : 1 ;
1026+ const barCount : number = ModelUtils . getIndex ( h . shortestDuration ) - 2 ;
1027+
9831028 // 3. adjust beam drawing order
9841029 // we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
9851030 // here we shift accordingly
9861031 let barDrawingShift = 0 ;
9871032 if ( barCount > 2 && ! isRest ) {
988- const beamSpacing = this . smuflMetrics . beamSpacing * scale ;
989- const beamThickness = this . smuflMetrics . beamThickness * scale ;
1033+ const beamSpacing = this . beamSpacing * scale ;
1034+ const beamThickness = this . beamThickness * scale ;
9901035 const totalBarsHeight = barCount * beamThickness + ( barCount - 1 ) * beamSpacing ;
9911036
9921037 if ( direction === BeamDirection . Up ) {
@@ -1038,7 +1083,7 @@ export abstract class LineBarRenderer extends BarRendererBase {
10381083 if ( h . restBeats . length > 0 ) {
10391084 // space needed for the bars, rests need to be below them
10401085 const scaleMod : number = h . graceType !== GraceType . None ? EngravingSettings . GraceScale : 1 ;
1041- barSpacing = barCount * ( this . smuflMetrics . beamSpacing + this . smuflMetrics . beamThickness ) * scaleMod ;
1086+ barSpacing = barCount * ( this . beamSpacing + this . beamThickness ) * scaleMod ;
10421087 }
10431088
10441089 for ( const b of h . restBeats ) {
0 commit comments