import Vex from "vexflow" ; import { MusicSheetDrawer } from "../MusicSheetDrawer" ; import { RectangleF2D } from "../../../Common/DataObjects/RectangleF2D" ; import { VexFlowMeasure } from "./VexFlowMeasure" ; import { PointF2D } from "../../../Common/DataObjects/PointF2D" ; import { GraphicalLabel } from "../GraphicalLabel" ; import { VexFlowTextMeasurer } from "./VexFlowTextMeasurer" ; import { MusicSystem } from "../MusicSystem" ; import { GraphicalObject } from "../GraphicalObject" ; import { GraphicalLayers } from "../DrawingEnums" ; import { GraphicalStaffEntry } from "../GraphicalStaffEntry" ; import { VexFlowBackend } from "./VexFlowBackend" ; import { VexFlowOctaveShift } from "./VexFlowOctaveShift" ; import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression" ; import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket" ; import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace" ; import { GraphicalLyricEntry } from "../GraphicalLyricEntry" ; import { VexFlowStaffLine } from "./VexFlowStaffLine" ; import { StaffLine } from "../StaffLine" ; import { GraphicalSlur } from "../GraphicalSlur" ; import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression" ; import { GraphicalInstantaneousTempoExpression } from "../GraphicalInstantaneousTempoExpression" ; import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneousDynamicExpression" ; import log from "loglevel" ; import { GraphicalContinuousDynamicExpression } from "../GraphicalContinuousDynamicExpression" ; import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression" ; import { DrawingParameters } from "../DrawingParameters" ; import { GraphicalMusicPage } from "../GraphicalMusicPage" ; import { GraphicalMusicSheet } from "../GraphicalMusicSheet" ; import { GraphicalUnknownExpression } from "../GraphicalUnknownExpression" ; type number export const unitInPixels = 10 ; export class VexFlowMusicSheetDrawer extends MusicSheetDrawer { private backend : VexFlowBackend ; private backends : VexFlowBackend [ ] = [ ] ; private zoom : number = 1.0 ; private pageIdx : number = 0 ; constructor ( drawingParameters = new DrawingParameters ( ) ) { super ( new VexFlowTextMeasurer ( drawingParameters . Rules ) , drawingParameters ) ; } public get Backends ( ) : VexFlowBackend [ ] { return this . backends ; } public drawSheet ( graphicalMusicSheet ) { if ( this . rules . DefaultVexFlowNoteFont === "gonville" ) { ( Vex . Flow as any ) . DEFAULT_FONT_STACK = [ ( Vex . Flow as any ) . Fonts ?. Gonville , ( Vex . Flow as any ) . Fonts ?. Bravura , ( Vex . Flow as any ) . Fonts ?. Custom ] ; } ( Vex . Flow as any ) . STAVE_LINE_THICKNESS = this . rules . StaffLineWidth * unitInPixels ; ( Vex . Flow as any ) . STEM_WIDTH = this . rules . StemWidth * unitInPixels ; ( Vex . Flow as any ) . DEFAULT_NOTATION_FONT_SCALE = this . rules . VexFlowDefaultNotationFontScale ; ( Vex . Flow as any ) . DEFAULT_TAB_FONT_SCALE = this . rules . VexFlowDefaultTabFontScale ; this . pageIdx = 0 ; for ( const graphicalMusicPage of graphicalMusicSheet . MusicPages ) { if ( graphicalMusicPage . PageNumber > this . rules . MaxPageToDrawNumber ) { break ; } const backend = this . backends [ this . pageIdx ] ; backend . graphicalMusicPage = graphicalMusicPage ; backend . scale ( this . zoom ) ; this . pageIdx += 1 ; } this . pageIdx = 0 ; this . backend = this . backends [ 0 ] ; super . drawSheet ( graphicalMusicSheet ) ; } protected drawPage ( page ) { if ( ! page ) { return ; } this . backend = this . backends [ page . PageNumber - 1 ] ; super . drawPage ( page ) ; this . pageIdx += 1 ; } public clear ( ) { for ( const backend of this . backends ) { backend . clear ( ) ; } } public setZoom ( zoom ) { this . zoom = zoom ; } param unitDistance returns { number } public calculatePixelDistance ( unitDistance ) { return unitDistance * unitInPixels ; } protected drawStaffLine ( staffLine ) { super . drawStaffLine ( staffLine ) ; const absolutePos = staffLine . PositionAndShape . AbsolutePosition ; if ( this . rules . RenderSlurs ) { this . drawSlurs ( staffLine as VexFlowStaffLine , absolutePos ) ; } } private drawSlurs ( vfstaffLine , absolutePos ) { for ( const graphicalSlur of vfstaffLine . GraphicalSlurs ) { if ( graphicalSlur . slur . isCrossed ( ) ) { continue ; } this . drawSlur ( graphicalSlur , absolutePos ) ; } } private drawSlur ( graphicalSlur , abs ) { const curvePointsInPixels = [ ] ; const p1 = new PointF2D ( graphicalSlur . bezierStartPt . x + abs . x , graphicalSlur . bezierStartPt . y + abs . y ) ; const p2 = new PointF2D ( graphicalSlur . bezierStartControlPt . x + abs . x , graphicalSlur . bezierStartControlPt . y + abs . y ) ; const p3 = new PointF2D ( graphicalSlur . bezierEndControlPt . x + abs . x , graphicalSlur . bezierEndControlPt . y + abs . y ) ; const p4 = new PointF2D ( graphicalSlur . bezierEndPt . x + abs . x , graphicalSlur . bezierEndPt . y + abs . y ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p1 ) ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p2 ) ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p3 ) ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p4 ) ) ; if ( graphicalSlur . placement === PlacementEnum . Above ) { p1 . y -= 0.05 ; p2 . y -= 0.3 ; p3 . y -= 0.3 ; p4 . y -= 0.05 ; } else { p1 . y += 0.05 ; p2 . y += 0.3 ; p3 . y += 0.3 ; p4 . y += 0.05 ; } curvePointsInPixels . push ( this . applyScreenTransformation ( p1 ) ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p2 ) ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p3 ) ) ; curvePointsInPixels . push ( this . applyScreenTransformation ( p4 ) ) ; graphicalSlur . SVGElement = this . backend . renderCurve ( curvePointsInPixels ) ; } protected drawMeasure ( measure ) { measure . setAbsoluteCoordinates ( measure . PositionAndShape . AbsolutePosition . x * unitInPixels , measure . PositionAndShape . AbsolutePosition . y * unitInPixels ) ; try { measure . draw ( this . backend . getContext ( ) ) ; } catch ( ex ) { log . warn ( "VexFlowMusicSheetDrawer.drawMeasure" , ex ) ; } for ( const staffEntry of measure . staffEntries ) { this . drawStaffEntry ( staffEntry ) ; } } /** Draws a line in the current backend. Only usable while pages are drawn sequentially, because backend reference is updated in that process.
     *  To add your own lines after rendering, use DrawOverlayLine.
     */ protected drawLine ( start , stop , color = "#FF0000FF" , lineWidth = 0.2 ) { start = this . applyScreenTransformation ( start ) ; stop = this . applyScreenTransformation ( stop ) ; return this . backend . renderLine ( start , stop , color , lineWidth * unitInPixels ) ; } /** Lets a user/developer draw an overlay line on the score. Use this instead of drawLine, which is for OSMD internally only.
     *  The MusicPage has to be specified, because each page and Vexflow backend has its own relative coordinates.
     *  (the AbsolutePosition of a GraphicalNote is relative to its backend)
     *  To get a MusicPage, use GraphicalNote.ParentMusicPage.
     */ public DrawOverlayLine ( start , stop , musicPage , color = "#FF0000FF" , lineWidth = 0.2 ) { if ( ! musicPage . PageNumber || musicPage . PageNumber > this . backends . length || musicPage . PageNumber < 1 ) { console . log ( "VexFlowMusicSheetDrawer.drawOverlayLine: invalid page number / music page number doesn't correspond to an existing backend." ) ; return ; } const musicPageIndex = musicPage . PageNumber - 1 ; const backendToUse = this . backends [ musicPageIndex ] ; start = this . applyScreenTransformation ( start ) ; stop = this . applyScreenTransformation ( stop ) ; return backendToUse . renderLine ( start , stop , color , lineWidth * unitInPixels ) ; } protected drawSkyLine ( staffline ) { const startPosition = staffline . PositionAndShape . AbsolutePosition ; const width = staffline . PositionAndShape . Size . width ; this . drawSampledLine ( staffline . SkyLine , startPosition , width ) ; } protected drawBottomLine ( staffline ) { const startPosition = new PointF2D ( staffline . PositionAndShape . AbsolutePosition . x , staffline . PositionAndShape . AbsolutePosition . y ) ; const width = staffline . PositionAndShape . Size . width ; this . drawSampledLine ( staffline . BottomLine , startPosition , width , "#0000FFFF" ) ; } param line param startPosition param width param color private drawSampledLine ( line , startPosition , width , color = "#FF0000FF" ) { const indices = [ ] ; let currentValue = 0 ; for ( let i = 0 ; i < line . length ; i ++ ) { if ( line [ i ] !== currentValue ) { indices . push ( i ) ; currentValue = line [ i ] ; } } const absolute = startPosition ; if ( indices . length > 0 ) { const samplingUnit = this . rules . SamplingUnit ; let horizontalStart = new PointF2D ( absolute . x , absolute . y ) ; let horizontalEnd = new PointF2D ( indices [ 0 ] / samplingUnit + absolute . x , absolute . y ) ; this . drawLine ( horizontalStart , horizontalEnd , color ) ; let verticalStart ; let verticalEnd ; if ( line [ 0 ] >= 0 ) { verticalStart = new PointF2D ( indices [ 0 ] / samplingUnit + absolute . x , absolute . y ) ; verticalEnd = new PointF2D ( indices [ 0 ] / samplingUnit + absolute . x , absolute . y + line [ indices [ 0 ] ] ) ; this . drawLine ( verticalStart , verticalEnd , color ) ; } for ( let i = 1 ; i < indices . length ; i ++ ) { horizontalStart = new PointF2D ( indices [ i - 1 ] / samplingUnit + absolute . x , absolute . y + line [ indices [ i - 1 ] ] ) ; horizontalEnd = new PointF2D ( indices [ i ] / samplingUnit + absolute . x , absolute . y + line [ indices [ i - 1 ] ] ) ; this . drawLine ( horizontalStart , horizontalEnd , color ) ; verticalStart = new PointF2D ( indices [ i ] / samplingUnit + absolute . x , absolute . y + line [ indices [ i - 1 ] ] ) ; verticalEnd = new PointF2D ( indices [ i ] / samplingUnit + absolute . x , absolute . y + line [ indices [ i ] ] ) ; this . drawLine ( verticalStart , verticalEnd , color ) ; } if ( indices [ indices . length - 1 ] < line . length ) { horizontalStart = new PointF2D ( indices [ indices . length - 1 ] / samplingUnit + absolute . x , absolute . y + line [ indices [ indices . length - 1 ] ] ) ; horizontalEnd = new PointF2D ( absolute . x + width , absolute . y + line [ indices [ indices . length - 1 ] ] ) ; this . drawLine ( horizontalStart , horizontalEnd , color ) ; } else { horizontalStart = new PointF2D ( indices [ indices . length - 1 ] / samplingUnit + absolute . x , absolute . y ) ; horizontalEnd = new PointF2D ( absolute . x + width , absolute . y ) ; this . drawLine ( horizontalStart , horizontalEnd , color ) ; } } else { const start = new PointF2D ( absolute . x , absolute . y ) ; const end = new PointF2D ( absolute . x + width , absolute . y ) ; this . drawLine ( start , end , color ) ; } } private drawStaffEntry ( staffEntry ) { if ( staffEntry . FingeringEntries . length > 0 ) { for ( const fingeringEntry of staffEntry . FingeringEntries ) { fingeringEntry . SVGNode = this . drawLabel ( fingeringEntry , GraphicalLayers . Notes ) ; } } if ( staffEntry . graphicalChordContainers !== undefined && staffEntry . graphicalChordContainers . length > 0 ) { for ( const graphicalChordContainer of staffEntry . graphicalChordContainers ) { const label = graphicalChordContainer . GraphicalLabel ; label . SVGNode = this . drawLabel ( label , < number > GraphicalLayers . Notes ) ; } } if ( this . rules . RenderLyrics ) { if ( staffEntry . LyricsEntries . length > 0 ) { this . drawLyrics ( staffEntry . LyricsEntries , < number > GraphicalLayers . Notes ) ; } } } param lyricEntries param layer private drawLyrics ( lyricEntries , layer ) { lyricEntries . forEach ( lyricsEntry => { const label = lyricsEntry . GraphicalLabel ; label . SVGNode = this . drawLabel ( label , layer ) ; } ) ; } protected drawInstrumentBrace ( brace , system ) { const vexBrace = ( brace as VexFlowInstrumentBrace ) ; vexBrace . draw ( this . backend . getContext ( ) ) ; } protected drawGroupBracket ( bracket , system ) { const vexBrace = ( bracket as VexFlowInstrumentBracket ) ; vexBrace . draw ( this . backend . getContext ( ) ) ; } protected drawOctaveShifts ( staffLine ) { for ( const graphicalOctaveShift of staffLine . OctaveShifts ) { if ( graphicalOctaveShift ) { const vexFlowOctaveShift = graphicalOctaveShift as VexFlowOctaveShift ; const ctx = this . backend . getContext ( ) ; const textBracket = vexFlowOctaveShift . getTextBracket ( ) ; textBracket . setContext ( ctx ) ; try { textBracket . draw ( ) ; } catch ( ex ) { log . warn ( ex ) ; } } } } protected drawExpressions ( staffline ) { for ( const abstractGraphicalExpression of staffline . AbstractExpressions ) { if ( abstractGraphicalExpression instanceof GraphicalInstantaneousDynamicExpression ) { this . drawInstantaneousDynamic ( ( abstractGraphicalExpression as VexFlowInstantaneousDynamicExpression ) ) ; } else if ( abstractGraphicalExpression instanceof GraphicalInstantaneousTempoExpression ) { const label = ( abstractGraphicalExpression as GraphicalInstantaneousTempoExpression ) . GraphicalLabel ; label . SVGNode = this . drawLabel ( label , GraphicalLayers . Notes ) ; } else if ( abstractGraphicalExpression instanceof GraphicalContinuousDynamicExpression ) { this . drawContinuousDynamic ( ( abstractGraphicalExpression as VexFlowContinuousDynamicExpression ) ) ; } else if ( abstractGraphicalExpression instanceof GraphicalUnknownExpression ) { const label = abstractGraphicalExpression . Label ; label . SVGNode = this . drawLabel ( label , < number > GraphicalLayers . Notes ) ; } else { log . warn ( "Unkown type of expression!" ) ; } } } protected drawInstantaneousDynamic ( instantaneousDynamic ) { const label = ( instantaneousDynamic as VexFlowInstantaneousDynamicExpression ) . Label ; label . SVGNode = this . drawLabel ( label , < number > GraphicalLayers . Notes ) ; } protected drawContinuousDynamic ( graphicalExpression ) { if ( graphicalExpression . IsVerbal ) { const label = graphicalExpression . Label ; label . SVGNode = this . drawLabel ( label , < number > GraphicalLayers . Notes ) ; } else { for ( const line of graphicalExpression . Lines ) { const start = new PointF2D ( graphicalExpression . ParentStaffLine . PositionAndShape . AbsolutePosition . x + line . Start . x , graphicalExpression . ParentStaffLine . PositionAndShape . AbsolutePosition . y + line . Start . y ) ; const end = new PointF2D ( graphicalExpression . ParentStaffLine . PositionAndShape . AbsolutePosition . x + line . End . x , graphicalExpression . ParentStaffLine . PositionAndShape . AbsolutePosition . y + line . End . y ) ; this . drawLine ( start , end , "black" , line . Width ) ; } } } param graphicalLabel param layer param bitmapWidth param bitmapHeight param heightInPixel param screenPosition protected renderLabel ( graphicalLabel , layer , bitmapWidth , bitmapHeight , fontHeightInPixel , screenPosition ) { if ( ! graphicalLabel . Label . print ) { return undefined ; } const height = graphicalLabel . Label . fontHeight * unitInPixels ; const { font } = graphicalLabel . Label ; let color ; if ( this . rules . ColoringEnabled ) { color = graphicalLabel . Label . colorDefault ; if ( ! color ) { color = this . rules . DefaultColorLabel ; } } let { fontStyle , fontFamily } = graphicalLabel . Label ; if ( ! fontStyle ) { fontStyle = this . rules . DefaultFontStyle ; } if ( ! fontFamily ) { fontFamily = this . rules . DefaultFontFamily ; } let node ; for ( let i = 0 ; i < graphicalLabel . TextLines ?. length ; i ++ ) { const currLine = graphicalLabel . TextLines [ i ] ; const xOffsetInPixel = this . calculatePixelDistance ( currLine . xOffset ) ; const linePosition = new PointF2D ( screenPosition . x + xOffsetInPixel , screenPosition . y ) ; const newNode = this . backend . renderText ( height , fontStyle , font , currLine . text , fontHeightInPixel , linePosition , color , graphicalLabel . Label . fontFamily ) ; if ( ! node ) { node = newNode ; } else { node . appendChild ( newNode ) ; } screenPosition . y = screenPosition . y + fontHeightInPixel ; if ( graphicalLabel . TextLines . length > 1 ) { screenPosition . y += this . rules . SpacingBetweenTextLines ; } } return node ; } param rectangle param layer param styleId param alpha protected renderRectangle ( rectangle , layer , styleId , colorHex , alpha ) { return this . backend . renderRectangle ( rectangle , styleId , colorHex , alpha ) ; } param point returns { PointF2D } protected applyScreenTransformation ( point ) { return new PointF2D ( point . x * unitInPixels , point . y * unitInPixels ) ; } param rectangle returns { RectangleF2D } protected applyScreenTransformationForRect ( rectangle ) { return new RectangleF2D ( rectangle . x * unitInPixels , rectangle . y * unitInPixels , rectangle . width * unitInPixels , rectangle . height * unitInPixels ) ; } }