liushengqiang 2 lat temu
rodzic
commit
befa098013

+ 1 - 0
.gitignore

@@ -10,3 +10,4 @@ coverage
 
 scripts/colexiu-xmls
 scripts/*.txt
+osmd-extended

+ 13 - 0
osmd-extended/src/MusicalScore/Graphical/GraphicalMusicSheet.ts

@@ -891,6 +891,7 @@ export class GraphicalMusicSheet {
             let fraction: number;
             let previousStaffEntryPositionX: number;
             let nextStaffEntryPositionX: number;
+            let x: number = 0;
             if (!previousStaffEntry) {
                 previousStaffEntryPositionX = nextStaffEntryPositionX = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
                 fraction = 0;
@@ -909,8 +910,20 @@ export class GraphicalMusicSheet {
                     fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) /
                         (nextStaffEntry.getAbsoluteTimestamp().RealValue - previousStaffEntry.getAbsoluteTimestamp().RealValue);
                 }
+
+                // 修改 如果开始合并的全休止小节, 获取正确的位置
+                const multiRestGMeasure: GraphicalMeasure = previousStaffEntry.parentMeasure;
+                const totalRestMeasures: number = previousStaffEntry.parentMeasure.parentSourceMeasure.multipleRestMeasures;
+                const currentRestMeasureNumber: number = multiRestGMeasure.MeasureNumber;
+                const progressRatio: number = currentRestMeasureNumber / (totalRestMeasures + 1);
+                const effectiveWidth: number = multiRestGMeasure.PositionAndShape.Size.width - multiRestGMeasure.beginInstructionsWidth;
+                x = multiRestGMeasure.PositionAndShape.AbsolutePosition.x + multiRestGMeasure.beginInstructionsWidth + progressRatio * effectiveWidth;
             }
             fraction = Math.min(1, Math.max(0, fraction));
+            // 增加如果开始时全休止小节,按照正确获取的位置返回
+            if (x && previousStaffEntry.parentMeasure.MeasureNumber === 1){
+                return [x, currentMusicSystem, previousStaffEntry]
+            }
             const interpolatedXPosition: number = previousStaffEntryPositionX + fraction * (nextStaffEntryPositionX - previousStaffEntryPositionX);
             return [interpolatedXPosition, currentMusicSystem, previousStaffEntry];
         } else {

+ 12 - 5
osmd-extended/src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -96,14 +96,21 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @returns {any}
      */
     public findTieGraphicalNoteFromNote(tieNote: Note): GraphicalNote {
+        const graceCustom = (window as any).GYM?.graceCustom || false
         for (const gve of this.graphicalVoiceEntries) {
             for (const graphicalNote of gve.notes) {
                 const note: Note = graphicalNote.sourceNote;
-                if (!note.isRest()
-                    && note.Pitch.FundamentalNote === tieNote.Pitch.FundamentalNote
-                    && note.Pitch.Octave === tieNote.Pitch.Octave
-                    && note.getAbsoluteTimestamp().Equals(tieNote.getAbsoluteTimestamp())) {
-                    return graphicalNote;
+                if (graceCustom) {
+                    if (tieNote.NoteToGraphicalNoteObjectId == note.NoteToGraphicalNoteObjectId){
+                        return graphicalNote
+                    }
+                } else {
+                    if (!note.isRest()
+                        && note.Pitch.FundamentalNote === tieNote.Pitch.FundamentalNote
+                        && note.Pitch.Octave === tieNote.Pitch.Octave
+                        && note.getAbsoluteTimestamp().Equals(tieNote.getAbsoluteTimestamp())) {
+                        return graphicalNote;
+                    }
                 }
             }
         }

+ 6 - 3
osmd-extended/src/MusicalScore/Graphical/JustifiedMusicSystemBuilder.ts

@@ -6,7 +6,7 @@ import { MusicSystemBuilder, SystemBuildParameters } from "./MusicSystemBuilder"
 
 export class JustifiedMusicSystemBuilder extends MusicSystemBuilder {
 
-    // 修改 换行规则
+    // 修改
     public buildMusicSystems(): MusicSystem[] {
         this.currentSystemParams = new SystemBuildParameters();
         const blocksToJustify: {startIndex: number, endIndex: number}[] = [];
@@ -148,8 +148,11 @@ export class JustifiedMusicSystemBuilder extends MusicSystemBuilder {
                 // console.log(idx , totalWidth, systemSpace )
                 const appName = (window as any)?.appName || '';
                 if (appName?.toLocaleUpperCase() === 'GYM') {
+                    // 修改 部分曲谱修改换行小节数
+                    let wrapNum = (window as any).GYM?.wrapNum;
+                    wrapNum = Number(wrapNum) > 0 ? Number(wrapNum) : 6
                     // 修改 偶数换行
-                    if (row === 6){
+                    if (row === wrapNum){
                         totalNum.push(row)
                         totalWidth = measureWidth
                         row = 1
@@ -192,7 +195,7 @@ export class JustifiedMusicSystemBuilder extends MusicSystemBuilder {
             systemsMeasureCount.push(currentSystemMeasureCount);
             totalNum.push(row)
             totalNum = totalNum.filter(Boolean)
-            // console.log(totalNum, systemsMeasureCount)
+            console.log(totalNum, systemsMeasureCount)
         }
         return this.buildPreparedMusicSystems(totalNum);
         // if ((window as any).appName && (window as any).appName.toLocaleUpperCase() === 'GYM') {

+ 99 - 5
osmd-extended/src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -173,11 +173,78 @@ export abstract class MusicSheetDrawer {
         }
         // 判断修休息符号,偏移至小节尾
         if (label.text === ",") {
-            // console.log(NearestNote.sourceNote.SourceMeasure.measureListIndex, NearestNote);
+            let y = 0
+            // 如果为 换气符号,重新寻找在那个小节上面
+            const graphicalMusicPage = this.graphicalMusicSheet?.MusicPages?.[0]?.MusicSystems || []
+            let hasMeasure: GraphicalMeasure
+            try {
+                
+                for(let i = 0; i < graphicalMusicPage.length; i++){
+                    const musicSystem = graphicalMusicPage[i]
+                    if (graphicalLabel.PositionAndShape.AbsolutePosition.x < 10) {
+                        console.log(graphicalLabel.PositionAndShape.AbsolutePosition.x)
+                        const vexFlowMeasure = this.graphicalMusicSheet.MeasureList[NearestNote.sourceNote.SourceMeasure.MeasureNumber - 2]
+                        if (vexFlowMeasure && vexFlowMeasure[0]){
+                            hasMeasure = vexFlowMeasure[0]
+                            y = hasMeasure.PositionAndShape.AbsolutePosition.y - hasMeasure.PositionAndShape.Size.height / 2
+                        }
+                        break;
+                    }
+                    if (musicSystem.PositionAndShape.AbsolutePosition.y - 10 < graphicalLabel.PositionAndShape.AbsolutePosition.y && graphicalLabel.PositionAndShape.AbsolutePosition.y < musicSystem.PositionAndShape.AbsolutePosition.y + 10){
+                        const graphicalMeasures = musicSystem?.GraphicalMeasures.map((_n) => _n[0]).filter(Boolean) || []
+                        // @ts-ignore
+                        const end_xList = []
+                        let isWhole = false
+                        for(let j = 0; j < graphicalMeasures.length; j++){
+                            const measure = graphicalMeasures[j]
+                            // 全音符小节
+                            // @ts-ignore
+                            if (measure.vfVoices?.['1']?.tickables?.length === 1) {
+                                if (measure.PositionAndShape.AbsolutePosition.x < graphicalLabel.PositionAndShape.AbsolutePosition.x && graphicalLabel.PositionAndShape.AbsolutePosition.x < measure.PositionAndShape.AbsolutePosition.x + measure.PositionAndShape.Size.width) {
+                                    hasMeasure = measure
+                                    isWhole = true
+                                    break;
+                                }
+                            }
+                            const end_x = measure.PositionAndShape.AbsolutePosition.x + measure.PositionAndShape.Size.width
+                            const label_x = graphicalLabel.PositionAndShape.AbsolutePosition.x
+                            if (end_x - 15 < label_x && label_x < end_x + 15){
+                                end_xList.push({
+                                    measure,
+                                    x: Math.abs(label_x - end_x)
+                                })
+                            }
+                        }
+                        if (isWhole) break;
+                        // const whole = end_xList.find((_n) => _n.measure.vfVoices?.['1']?.tickables?.length === 1)
+                        // if (whole) {
+                        //     hasMeasure = whole.measure
+                        // } else {
+                        //     hasMeasure = end_xList.sort((a, b) => a.x - b.x)?.[0]?.measure
+                        // }
+                        hasMeasure = end_xList.sort((a, b) => a.x - b.x)?.[0]?.measure
+                        // console.log("🚀 ~ whole", whole)
+                        // console.log("🚀 ~ end_xList", end_xList, hasMeasure)
+                        break;
+                    }
+                }
+            } catch (error) {
+                console.log("🚀 ~ error", error)
+            }
             let x: number = 0;
             // @ts-ignore
-            const VFStave: any = NearestNote.sourceNote.SourceMeasure.VerticalMeasureList[0]?.getVFStave();
-            x = VFStave.end_x;
+            if (hasMeasure){
+                //&& hasMeasure.vfVoices?.['1']?.tickables?.length === 1
+                // @ts-ignore
+                x = hasMeasure.stave?.end_x || 0;
+            } else {
+                // console.log(NearestNote)
+                // @ts-ignore
+                const VFStave: any = NearestNote.sourceNote.SourceMeasure.VerticalMeasureList[0]?.getVFStave();
+                x = VFStave.end_x;
+            }
+            // x = graphicalLabel.PositionAndShape.AbsolutePosition.x * 10
+            // console.log(NearestNote.sourceNote.SourceMeasure.measureListIndex, NearestNote);
             // for (const measure of (ParentObject?.measures || [])) {
             //     const itemx: number = graphicalLabel.PositionAndShape.RelativePosition.x * 10;
             //     /**
@@ -192,7 +259,33 @@ export abstract class MusicSheetDrawer {
             // }
             const drawMeasureNumbers: number = 0;//(index % 2 ? 0 : 1.5)// 同时显示小节编号时情况
             calcResults.ScreenPosition.x = x;
-            calcResults.ScreenPosition.y = (graphicalLabel.PositionAndShape.Parent.AbsolutePosition.y - 3 - drawMeasureNumbers) * 10;
+            calcResults.ScreenPosition.y = y === 0 ? (graphicalLabel.PositionAndShape.Parent.AbsolutePosition.y - 3 - drawMeasureNumbers) * 10 : y * 10;
+        }
+
+        // 修改 判断为音阶的话
+        const clefList = ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#', 'F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb', 'Fb', 'D#', 'A#', 'E#']
+        if (clefList.includes(label.text)){
+            calcResults.ScreenPosition.y = (graphicalLabel.PositionAndShape.Parent.AbsolutePosition.y + graphicalLabel.PositionAndShape.Parent.BorderTop + graphicalLabel.PositionAndShape.Parent.BorderMarginTop) * 10
+        }
+        // 修改 底部 play listen
+        if (['play', 'listen'].includes(label.text.toLocaleLowerCase()) && label.textAlignment === TextAlignmentEnum.LeftBottom){
+            calcResults.ScreenPosition.y = (graphicalLabel.PositionAndShape.Parent.AbsolutePosition.y - 1 + graphicalLabel.PositionAndShape.Parent.BorderBottom ) * 10
+        }
+        // 修改 部分文字需要识别为表情符号
+        const expressionList = ['ffp', 'p-f', 'sfzp', 'szf']
+        if (expressionList.includes(label.text)){
+            label.fontStyle = 3
+            if (label.text === 'sfzp'){
+                label.fontHeight = 2.3
+            }
+        }
+
+        // 修改 多声轨有R, L的子母向右偏移
+        if (label.text == 'R' || label.text == 'L') {
+            calcResults.ScreenPosition.x += 5
+        }
+        if (label.text === 'r' || label.text === 'l') {
+            calcResults.ScreenPosition.x += 6
         }
         /**
          *
@@ -215,7 +308,7 @@ export abstract class MusicSheetDrawer {
         //     graphicalLabel.Label.fontHeight = 1.8
         // }
         // 修改 不需要显示的标记
-        if (isSpecialMark(graphicalLabel.Label.text || "")) {
+        if (isSpecialMark(graphicalLabel.Label.text || "") && !clefList.includes(label.text)) {
             graphicalLabel.Label.fontHeight = 0;
         }
 
@@ -403,6 +496,7 @@ export abstract class MusicSheetDrawer {
                 this.drawSelectionEndSymbol(selectEndSymb);
             }
         }
+
         for (const staffLine of musicSystem.StaffLines) {
             this.drawStaffLine(staffLine);
 

+ 14 - 0
osmd-extended/src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -520,6 +520,7 @@ export class MusicSystemBuilder {
                 }
             }
         }
+        const multitrack = (window as any).GYM?.multitrack
         if (isSystemStartMeasure) {
             if (!currentClef) {
                 currentClef = this.activeClefs[visibleStaffIdx];
@@ -527,6 +528,10 @@ export class MusicSystemBuilder {
             if (!currentKey) {
                 currentKey = KeyInstruction.copy(this.activeKeys[visibleStaffIdx]);
             }
+            if (multitrack && currentKey && !currentKey.AlteratedNotes?.length) {
+                currentKey = KeyInstruction.copy(this.activeKeys[0]);
+                currentKey.PrintObject = false
+            }
             if (isFirstSourceMeasure && !currentRhythm) {
                 currentRhythm = this.activeRhythm[visibleStaffIdx];
             }
@@ -544,6 +549,15 @@ export class MusicSystemBuilder {
             currentKey = this.transposeKeyInstruction(currentKey, measure);
             const previousKey: KeyInstruction = isSystemStartMeasure ? undefined : this.activeKeys[visibleStaffIdx];
             measure.addKeyAtBegin(currentKey, previousKey, currentClef);
+
+            // 修改 单声部 多声轨
+            if (multitrack && !currentKey.PrintObject){
+                try {
+                    //@ts-ignore
+                    measure.stave?.getModifiers(undefined, 'keysignatures')?.[0]?.setDrawState(false)
+                } catch (error) {}
+
+            }
             keyAdded = true;
         }
         if (currentRhythm !== undefined && currentRhythm.PrintObject && this.rules.RenderTimeSignatures) {

+ 25 - 15
osmd-extended/src/MusicalScore/Graphical/VexFlow/AlignmentManager.ts

@@ -68,33 +68,43 @@ export class AlignmentManager {
                 //         console.dir(aes);
                 //     }
                 // }
-
+                const noNeedContinuousDynamic = (window as any).GYM?.noNeedContinuousDynamic || false
+                const noNeedGraphicalContinuousDynamic = (window as any).GYM?.noNeedGraphicalContinuousDynamic || false
                 for (let exprIdx: number = 0; exprIdx < aes.length; exprIdx++) {
                     const expr: AbstractGraphicalExpression = aes[exprIdx];
                     const centerOffset: number = centerYs[exprIdx] - yIdeal;
                     // TODO centerOffset is way too big sometimes, like 7.0 in An die Ferne Geliebte (measure 10, dim.)
                     // FIXME: Expressions should not behave differently.
                     if (expr instanceof VexFlowContinuousDynamicExpression) {
-                        (expr as VexFlowContinuousDynamicExpression).shiftYPosition(-centerOffset);
-                        (expr as VexFlowContinuousDynamicExpression).calcPsi();
+                        // 修改 部分曲谱使用默认计算
+                        if (!noNeedContinuousDynamic) {
+                            (expr as VexFlowContinuousDynamicExpression).shiftYPosition(-centerOffset);
+                            (expr as VexFlowContinuousDynamicExpression).calcPsi();
+                        }
                     } else {
-                        // TODO: The 0.8 are because the letters are a bit too far done
-                        expr.PositionAndShape.RelativePosition.y -= centerOffset * 0.8;
+                        // 修改 部分曲谱不偏移
+                        if (!noNeedGraphicalContinuousDynamic) {
+                            // TODO: The 0.8 are because the letters are a bit too far done
+                            expr.PositionAndShape.RelativePosition.y -= centerOffset * 0.8;
+                        }
                         // note: verbal GraphicalContinuousDynamicExpressions have a label, nonverbal ones don't.
                         // take care to update and take the right bounding box for skyline.
                         expr.PositionAndShape.calculateBoundingBox();
                     }
                     // Squeeze wedges
-                    if ((expr as VexFlowContinuousDynamicExpression).squeeze) {
-                        const nextExpression: AbstractGraphicalExpression = exprIdx < aes.length - 1 ? aes[exprIdx + 1] : undefined;
-                        const prevExpression: AbstractGraphicalExpression = exprIdx > 0 ? aes[exprIdx - 1] : undefined;
-                        if (nextExpression) {
-                            const overlapRight: PointF2D = this.getOverlap(expr.PositionAndShape, nextExpression.PositionAndShape);
-                            (expr as VexFlowContinuousDynamicExpression).squeeze(-(overlapRight.x + this.rules.DynamicExpressionSpacer));
-                        }
-                        if (prevExpression) {
-                            const overlapLeft: PointF2D = this.getOverlap(prevExpression.PositionAndShape, expr.PositionAndShape);
-                            (expr as VexFlowContinuousDynamicExpression).squeeze(overlapLeft.x + this.rules.DynamicExpressionSpacer);
+                    // 修改 部分曲谱使用默认计算
+                    if (!noNeedContinuousDynamic) {
+                        if ((expr as VexFlowContinuousDynamicExpression).squeeze) {
+                            const nextExpression: AbstractGraphicalExpression = exprIdx < aes.length - 1 ? aes[exprIdx + 1] : undefined;
+                            const prevExpression: AbstractGraphicalExpression = exprIdx > 0 ? aes[exprIdx - 1] : undefined;
+                            if (nextExpression) {
+                                const overlapRight: PointF2D = this.getOverlap(expr.PositionAndShape, nextExpression.PositionAndShape);
+                                (expr as VexFlowContinuousDynamicExpression).squeeze(-(overlapRight.x + this.rules.DynamicExpressionSpacer));
+                            }
+                            if (prevExpression) {
+                                const overlapLeft: PointF2D = this.getOverlap(prevExpression.PositionAndShape, expr.PositionAndShape);
+                                (expr as VexFlowContinuousDynamicExpression).squeeze(overlapLeft.x + this.rules.DynamicExpressionSpacer);
+                            }
                         }
                     }
                 }

+ 71 - 13
osmd-extended/src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -239,6 +239,7 @@ export class VexFlowConverter {
 
     public static noteTremoloList: VF.StaveNote[] = [];
     public static glissandoList: any[] = [];
+    public static slideNote: VF.StaveNote = null;
 
     /**
      * Convert a GraphicalVoiceEntry to a VexFlow StaveNote
@@ -534,6 +535,25 @@ export class VexFlowConverter {
             vfnote.getKeyProps()[0].line += lineShift;
         }
 
+        // 修改 打击乐多声部,双声部休止符重叠
+        const measureNumberXML = gve.notes[0].sourceNote.SourceMeasure.MeasureNumberXML
+        const drumSetMeasures = (window as any).GYM?.drumSetMeasures || []
+        if (drumSetMeasures.includes(measureNumberXML)) {
+            if (gve.notes[0].sourceNote.isRest()) {
+                if(gve.notes[0].sourceNote.ParentVoiceEntry.ParentVoice.VoiceId == 1){
+                    vfnote.getKeyProps()[0].line = 4.5;
+                }
+            }
+        }
+        const customBassDrum = (window as any).GYM?.customBassDrum
+        if (customBassDrum && vfnote.isRest()) {
+            if (vfnote.getDuration() === 'q') {
+                vfnote.getKeyProps()[0].line = customBassDrum;
+            }
+            if (vfnote.getDuration() === 'h') {
+                vfnote.getKeyProps()[0].line = customBassDrum - 1;
+            }
+        }
         // Annotate GraphicalNote with which line of the staff it appears on
         vfnote.getKeyProps().forEach(({ line }, i) => gve.notes[i].staffLine = line);
 
@@ -576,7 +596,12 @@ export class VexFlowConverter {
             gve.parentVoiceEntry.WantedStemDirection = gve.notes[0].sourceNote.NoteBeam.Notes[0].ParentVoiceEntry.WantedStemDirection;
         }
         if (gve.parentVoiceEntry) {
-            const wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.WantedStemDirection;
+            let wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.WantedStemDirection;
+            const stemDirectionNote = (window as any).GYM?.stemDirectionNote || []
+            const stemDirection = stemDirectionNote?.find((n: any) => n.id === gve.notes[0].sourceNote.NoteToGraphicalNoteObjectId)
+            if (stemDirection && typeof stemDirection?.direction === 'number') {
+                wantedStemDirection = stemDirection.direction
+            }
             switch (wantedStemDirection) {
                 case(StemDirectionType.Up):
                     vfnote.setStemDirection(VF.Stem.UP);
@@ -640,18 +665,26 @@ export class VexFlowConverter {
             // 波浪线
             if (this.glissandoList.length){
                 (vfnote as any).setVibrato(this.glissandoList[0].vfnote[0])
-                if (this.glissandoList[0]?.sourceNote?.SourceMeasure?.MeasureNumberXML != notes[i]?.sourceNote?.SourceMeasure?.MeasureNumberXML) {
-
-                    // console.log("🚀 ~ vfnoteend", notes[i])
-                }
                 this.glissandoList = []
             }
             if (notes[i].sourceNote.glissandoType === "start"){
                 this.glissandoList.push(notes[i]);
             }
             
-
         }
+        /** 修改 滑音 start */
+        if (this.slideNote) {
+            try {
+                (vfnote as any).setSlideNote(this.slideNote)
+            } catch (error) {
+                console.error(error)
+            }
+            this.slideNote = null
+        }
+        if (gve.notes[0].sourceNote.slideType === 'start') {
+            this.slideNote = vfnote
+        }
+        /** 滑音 end */
 
         // half note tremolo: set notehead to half note (Vexflow otherwise takes the notehead from duration) (Hack)
         if (firstNote.Length.RealValue === 0.25 && firstNote.Notehead && firstNote.Notehead.Filled === false) {
@@ -669,7 +702,8 @@ export class VexFlowConverter {
         if (keys.length > 1){
             let keyLines = vfnote.getKeyProps().filter(n => !n.code && n.line > 3)
             // 所有叠加音符都超过第三线才朝下
-            if(keyLines.length === keys.length){
+            const stemDirectionNote = (window as any).GYM?.stemDirectionNote
+            if(!stemDirectionNote && keyLines.length === keys.length){
                 vfnote.setStemDirection(-1)
             }
         }
@@ -812,12 +846,36 @@ export class VexFlowConverter {
                  // 修改 演奏法问题,重音^或v 应该在上面
                 if (articulationEnum === ArticulationEnum.strongaccent) {
                     vfArtPosition = VF.Modifier.Position.ABOVE;
-                    // if (vfArtPosition === VF.Modifier.Position.ABOVE) {
-                    //     vfArtPosition = VF.Modifier.Position.BELOW;
-                    //     vfArt = new VF.Articulation("a|");
-                    // } else if (vfArtPosition === VF.Modifier.Position.BELOW) {
-                    //     vfArtPosition = VF.Modifier.Position.ABOVE;
-                    // }
+
+                    // 修改 部分曲谱根据stem方向设置重音
+                    const customArtPosition = (window as any).GYM?.customArtPosition
+                    if (customArtPosition) {
+                        if (vfnote.getStemDirection() === 1){
+                            vfArtPosition = VF.Modifier.Position.ABOVE;
+                        } else {
+                            vfArtPosition = VF.Modifier.Position.BELOW;
+                            vfArt = new VF.Articulation("a|");
+                        }
+                    }
+                }
+
+                //修改 演奏技法 > 
+                const customAccentItem = (window as any).GYM?.customAccentItem
+                if (customAccentItem && articulationEnum === ArticulationEnum.accent) {
+                    vfArtPosition = VF.Modifier.Position.ABOVE;
+                }
+                //修改 演奏技法 +
+                const customLefthandpizzicatoItem = (window as any).GYM?.customLefthandpizzicatoItem
+                if (customLefthandpizzicatoItem && (articulationEnum === ArticulationEnum.lefthandpizzicato || articulationEnum === ArticulationEnum.naturalharmonic)) {
+                    vfArtPosition = VF.Modifier.Position.ABOVE;
+                }
+
+                // 修改 演奏技法 - & 全音符 在上面
+                if (articulationEnum === ArticulationEnum.tenuto && vfnote.getDuration() === 'w') {
+                    const customTenutoItem = (window as any).GYM?.customTenutoItem
+                    if (customTenutoItem) {
+                        vfArtPosition = VF.Modifier.Position.ABOVE;
+                    }
                 }
                 vfArt.setPosition(vfArtPosition);
                 (vfnote as StaveNote).addModifier(0, vfArt);

+ 1 - 0
osmd-extended/src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -598,6 +598,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
         (staveLineNode as SVGGElement)?.classList?.add("vf-stave");
         (staveLineNode as SVGGElement)?.setAttribute("id", (this.stave as any)?.attrs?.id);
         // Draw stave lines
+        
         this.stave.setContext(ctx).draw();
         ctx.closeGroup();
         const voicesNode: Node = ctx.openGroup();

+ 60 - 14
osmd-extended/src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -70,6 +70,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
   private dashSpace: number;
   public beamsNeedUpdate: boolean = false;
+  public isFirstZero: boolean = false
 
   constructor(rules: EngravingRules) {
     super();
@@ -515,20 +516,26 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   public calculateMeasureWidthFromStaffEntries(measuresVertical: GraphicalMeasure[], oldMinimumStaffEntriesWidth: number): number {
     let elongationFactorForMeasureWidth: number = 1;
 
-    for (const measure of measuresVertical) {
-      if (!measure || measure.staffEntries.length === 0) {
-        continue;
-      }
-
-      elongationFactorForMeasureWidth =
-        this.calculateElongationFactorFromStaffEntries(
-          measure.staffEntries,
-          oldMinimumStaffEntriesWidth,
-          elongationFactorForMeasureWidth,
-          measure.MeasureNumber,
-        );
+    const multitrack = (window as any).GYM?.multitrack
 
+    // 修改 单声轨多声部, 平均分配
+    if (!multitrack) {
+      for (const measure of measuresVertical) {
+        if (!measure || measure.staffEntries.length === 0) {
+          continue;
+        }
+        
+        elongationFactorForMeasureWidth =
+          this.calculateElongationFactorFromStaffEntries(
+            measure.staffEntries,
+            oldMinimumStaffEntriesWidth,
+            elongationFactorForMeasureWidth,
+            measure.MeasureNumber,
+          );
+          elongationFactorForMeasureWidth = 1
+      }
     }
+    
     elongationFactorForMeasureWidth = Math.min(elongationFactorForMeasureWidth, this.rules.MaximumLyricsElongationFactor);
     // TODO check when this is > 2.0. there seems to be an error here where this is unnecessarily > 2 in Beethoven Geliebte.
 
@@ -629,6 +636,25 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           first_note: vfStartNote
         });
         const measure1: VexFlowMeasure = (startNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
+        // 修改 跨行的延音线前面的方向和后面的保持一致
+        if (vfEndNote) {
+          if (vfEndNote.getDuration() === 'w' && (vfEndNote as any).keyProps?.[0]?.line >= 3) {
+            vfEndNote.setStemDirection(-1)
+          }
+          let tieDirection = (window as any).GYM?.tieDirection || 0;
+          if (Math.abs(tieDirection) === 1) {
+            if ((vfEndNote as any).keyProps?.[0]?.line >= 3) {
+              vfEndNote.setStemDirection(-tieDirection)
+            } else {
+              vfEndNote.setStemDirection(tieDirection)
+            }
+          }
+          (vfTie1 as any)?.setDirection(vfEndNote.getStemDirection());
+        }
+        if (measure1.vfTies.length > 0) {
+          const direction = (measure1.vfTies[0] as any).direction
+          ;(vfTie1 as any)?.setDirection(direction ? -direction : 1);
+        }
         measure1.addStaveTie(vfTie1, tie);
       }
 
@@ -638,6 +664,10 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           last_note: vfEndNote
         });
         const measure2: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
+        if (measure2.vfTies.length > 0) {
+          const direction = (measure2.vfTies[0] as any).direction
+          ;(vfTie2 as any)?.setDirection(direction ? -direction : 1);
+        }
         measure2.addStaveTie(vfTie2, tie);
       }
     } else {
@@ -699,6 +729,10 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         }
 
         const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
+        const graceCustom = (window as any).GYM?.graceCustom || false
+        if (graceCustom){
+          vfTie.setDirection(graceCustom.direction)
+        }
         measure.addStaveTie(vfTie, tie);
       }
     }
@@ -932,9 +966,13 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       // somehow this is called repeatedly in Clementi, so skyline[0] = Math.min instead of -=
     }
   }
-
+  
   protected calculateRehearsalMark(measure: SourceMeasure): void {
     const rehearsalExpression: RehearsalExpression = measure.rehearsalExpression;
+    // 修改 判断小节是否是从1开始
+    if (!this.isFirstZero && measure.MeasureNumber === 0){
+      this.isFirstZero = true;
+    }
     if (!rehearsalExpression) {
       return;
     }
@@ -964,7 +1002,12 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       repetition.setShiftY(repetition.y_shift - 10)
     }
 
-    (vfStave as any).setSection(rehearsalExpression.label, yOffset, xOffset, fontSize); // fontSize is an extra argument from VexFlowPatch
+    // 修改 小节数不对问题
+    if (this.isFirstZero) {
+      (vfStave as any).setSection(measure.MeasureNumberXML + 1 + '', yOffset, xOffset, fontSize); // fontSize is an extra argument from VexFlowPatch
+    } else {
+      (vfStave as any).setSection(rehearsalExpression.label, yOffset, xOffset, fontSize); // fontSize is an extra argument from VexFlowPatch
+    }
 
     //修改 存在方块数字时,如果当前行有文字,修改文字Y轴位置偏移
     if (this.graphicalMusicSheet.MeasureList[measureNumber][staffNumber]?.ParentStaffLine.AbstractExpressions){
@@ -1167,6 +1210,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       //return; // also possible: don't handle faulty pedal without end
       endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true); // get last rendered measure
     }
+    if (!endMeasure){
+      endMeasure = pedal.ParentStartMultiExpression.SourceMeasureParent.VerticalMeasureList[staffIndex]
+    }
     if (endMeasure.MeasureNumber > maxMeasureToDrawIndex + 1) { //  ends in measure not rendered
       endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true);
     }

+ 23 - 4
osmd-extended/src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -134,18 +134,37 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
     }
 
     private drawSlurs(vfstaffLine: VexFlowStaffLine, absolutePos: PointF2D): void {
-        // console.log(vfstaffLine)
-        for (const graphicalSlur of vfstaffLine.GraphicalSlurs) {
-            // don't draw crossed slurs, as their curve calculation is not implemented yet:
+        const vfcurveItem: any[] = (window as any).GYM?.vfcurveItem
+        for(let i = 0; i < vfstaffLine.GraphicalSlurs.length; i++){
+            const graphicalSlur = vfstaffLine.GraphicalSlurs[i]
             if (graphicalSlur.slur.isCrossed()) {
                 continue;
             }
+            if (vfcurveItem && vfcurveItem.length) {
+                try {
+                    const measure = vfcurveItem.find((n: any, _i: number) => (n.index ? n.index === i : true) && n.MeasureNumberXML == graphicalSlur.staffEntries[graphicalSlur.staffEntries.length - 1].parentMeasure.MeasureNumber)
+                    if (measure){
+                        graphicalSlur.bezierEndControlPt.y += measure?.bezierEndControlPt?.y ? measure.bezierEndControlPt.y : 0
+                        graphicalSlur.bezierEndPt.y += measure?.bezierEndPt?.y ? measure.bezierEndPt.y : 0
+                        graphicalSlur.bezierStartControlPt.y += measure?.bezierStartControlPt?.y ? measure.bezierStartControlPt.y : 0
+                    }
+                } catch (error) {
+                    console.log(error)
+                }
+            }
             this.drawSlur(graphicalSlur, absolutePos);
         }
+        // for (const graphicalSlur of vfstaffLine.GraphicalSlurs) {
+        //     // don't draw crossed slurs, as their curve calculation is not implemented yet:
+        //     if (graphicalSlur.slur.isCrossed()) {
+        //         continue;
+        //     }
+        //     this.drawSlur(graphicalSlur, absolutePos);
+        // }
     }
 
     private drawSlur(graphicalSlur: GraphicalSlur, abs: PointF2D): void {
-        // console.log(this, graphicalSlur, abs)
+        
         const curvePointsInPixels: PointF2D[] = [];
         let p1: PointF2D;
         let p2: PointF2D;

+ 14 - 8
osmd-extended/src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -319,7 +319,8 @@ export class InstrumentReader {
           // check Tremolo
           let tremoloStrokes: number = 0;
           let noteTremoloType: string = '';
-          let glissandoType: string = ''
+          let glissandoType: string = '';
+          let slideType: string = '';
           //let vibratoStrokes: boolean = false; // not necessary, handled by wavy-line
           if (notationsNode) {
             const ornamentsNode: IXmlElement = notationsNode.element("ornaments");
@@ -359,13 +360,17 @@ export class InstrumentReader {
                }
               }
             }
-            const glissandoNode : IXmlElement = notationsNode.element("glissando");
-            
+            const glissandoNodes : IXmlElement[] = notationsNode.elements("glissando");
+            const glissandoNode : IXmlElement = glissandoNodes.find((ele: IXmlElement) => ele.attribute('type').value === 'start')
             if (glissandoNode) {
-              const glissando = glissandoNode.attribute("type")
-              if (glissando.value === 'start'){
-                glissandoType = 'start'
-              }
+              glissandoType = 'start'
+            }
+
+            // 修改 增加滑音
+            const slideNodes : IXmlElement[] = notationsNode.elements("slide"); 
+            const slideNode : IXmlElement = slideNodes.find((ele: IXmlElement) => ele.attribute('type').value === 'start')
+            if (slideNode){
+              slideType = 'start'
             }
           }
 
@@ -450,7 +455,8 @@ export class InstrumentReader {
             printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml,
             dots,
             noteTremoloType,
-            glissandoType
+            glissandoType,
+            slideType
           );
 
           // notationsNode created further up for multiple checks

+ 4 - 3
osmd-extended/src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -155,7 +155,7 @@ export class VoiceGenerator {
               measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, octavePlusOne: boolean,
               printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
               stemColorXml: string, noteheadColorXml: string,
-              dotsXml: number, noteTremoloType: string, glissandoType: string): Note {
+              dotsXml: number, noteTremoloType: string, glissandoType: string, slideType: string): Note {
     this.currentStaffEntry = parentStaffEntry;
     this.currentMeasure = parentMeasure;
     //log.debug("read called:", restNote);
@@ -164,7 +164,7 @@ export class VoiceGenerator {
       this.currentNote = restNote
         ? this.addRestNote(noteNode.element("rest"), noteDuration, noteTypeXml, normalNotes, printObject, isCueNote, noteheadColorXml)
         : this.addSingleNote(noteNode, noteDuration, noteTypeXml, typeDuration, normalNotes, chord, octavePlusOne,
-                             printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml, noteTremoloType, glissandoType);
+                             printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml, noteTremoloType, glissandoType, slideType);
       this.currentNote.DotsXml = dotsXml;
       // read lyrics
       const lyricElements: IXmlElement[] = noteNode.elements("lyric");
@@ -369,7 +369,7 @@ export class VoiceGenerator {
   private addSingleNote(node: IXmlElement, noteDuration: Fraction, noteTypeXml: NoteType, typeDuration: Fraction,
                         normalNotes: number, chord: boolean, octavePlusOne: boolean,
                         printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
-                        stemColorXml: string, noteheadColorXml: string, noteTremoloType: string, glissandoType: string): Note {
+                        stemColorXml: string, noteheadColorXml: string, noteTremoloType: string, glissandoType: string, slideType: string): Note {
     //log.debug("addSingleNote called");
     let noteAlter: number = 0;
     let accidentalValue: string;
@@ -542,6 +542,7 @@ export class VoiceGenerator {
     note.PlaybackInstrumentId = playbackInstrumentId;
     note.TremoloType = noteTremoloType;
     note.glissandoType = glissandoType;
+    note.slideType = slideType;
     this.currentVoiceEntry.addNote(note);
     if (stemDirectionXml === StemDirectionType.None) {
       stemColorXml = "#00000000";  // just setting this to transparent for now

+ 2 - 0
osmd-extended/src/MusicalScore/VoiceData/Note.ts

@@ -30,6 +30,7 @@ export class Note {
         this.isRestFlag = isRest ?? false;
         this.noteTremoloType = '';
         this.glissandoType = '';
+        this.slideType = '';
         if (pitch) {
             this.halfTone = pitch.getHalfTone();
         } else {
@@ -42,6 +43,7 @@ export class Note {
      */
     public noteTremoloType: string;
     public glissandoType: string;
+    public slideType: string;
     public halfTone: number;
     public state: NoteState;
     private voiceEntry: VoiceEntry;

+ 4 - 0
osmd-extended/src/OpenSheetMusicDisplay/Cursor.ts

@@ -320,6 +320,10 @@ export class Cursor implements IPlaybackListener {
         newWidth = 3 * 10.0 * this.openSheetMusicDisplay.zoom;
         break;
     }
+    if ((window as any).GYM?.multitrack) {
+      cursorElement.height = heightCalc + 24;
+      cursorElement.style.height = heightCalc + 24 + "px";
+    }
 
     if (newWidth !== cursorElement.width) {
       cursorElement.width = newWidth;

+ 39 - 25
osmd-extended/src/VexFlowPatch/src/formatter.js

@@ -312,40 +312,54 @@ export class Formatter {
       if (note instanceof StaveNote && note.isRest()) {
         if (note.tuplet && !alignTuplets) return;
 
-        // If activated rests not on default can be rendered as specified.
-        const position = note.getGlyph().position.toUpperCase();
-        // console.log(note)
         //修改 8分休止符是否在横梁内
-        if (note.glyph.code_head === "va5" || note.glyph.code_head === "v3c") {
-          const base_height = note.glyph.code_head === "va5" ? 10 : 20
+        if (["va5", "v3c"].includes(note.glyph.code_head)) {
+          const base_height = note.glyph.code_head === "va5" ? 10 : 14
           const preNote = notes[index - 1];
           const nextNote = getNext(notes, index);
           let isThrough = false
           if (preNote && nextNote && preNote.beam && nextNote.beam) {
             isThrough = preNote.beam.notes.includes(nextNote)
           }
-          
-          // if (position !== 'R/4' && position !== 'B/4') return;
-          if (alignAllNotes || note.beam != null || isThrough) {
+          if (isThrough) {
+            note.setKeyLine(0, 3);
+            let _notes = [...preNote.beam.notes, ...nextNote.beam.notes]
+            if (note.beam && note.beam.notes){
+              _notes.push(...note.beam.notes)
+            }
+            _notes = Array.from(new Set(_notes))
+            for (let i = 0; i < _notes.length; i += 1) {
+              const nextRestLine = _notes[i].getKeyProps()[0].line
+              const stem = _notes[i].stem
+                if (stem) {
+                  if (stem.stem_direction > 0) {
+                    const height = nextRestLine < 1.5 ? Math.abs(nextRestLine - 1.5) * base_height : 0
+                    stem.setResetHeight(height)
+                  } else {
+                    const height = nextRestLine > 4.5 ? Math.abs(nextRestLine - 4.5) * base_height : 0
+                    stem.setResetHeight(height)
+                  }
+                }
+            }
             // Align rests with previous/next notes.
-            const props = note.getKeyProps()[0];
-            if (index === 0) {
-              props.line = lookAhead(notes, props.line, index, false, base_height);
-              note.setKeyLine(0, props.line);
-            } else if (index > 0 && index < notes.length) {
-              // If previous note is a rest, use its line number.
-              let restLine;
-              if (notes[index - 1].isRest()) {
-                restLine = notes[index - 1].getKeyProps()[0].line;
-                props.line = restLine;
-              } else {
-                restLine = notes[index - 1].getLineForRest();
-                // Get the rest line for next valid non-rest note group.
-                props.line = lookAhead(notes, restLine, index, true, base_height);
-              }
+            // const props = note.getKeyProps()[0];
+            // if (index === 0) {
+            //   props.line = lookAhead(notes, props.line, index, false, base_height);
               
-              note.setKeyLine(0, props.line);
-            }
+            // } else if (index > 0 && index < notes.length) {
+            //   // If previous note is a rest, use its line number.
+            //   let restLine;
+            //   if (notes[index - 1].isRest()) {
+            //     restLine = notes[index - 1].getKeyProps()[0].line;
+            //     props.line = restLine;
+            //   } else {
+            //     restLine = notes[index - 1].getLineForRest();
+            //     // Get the rest line for next valid non-rest note group.
+            //     props.line = lookAhead(notes, restLine, index, true, base_height);
+            //   }
+              
+            //   note.setKeyLine(0, props.line);
+            // }
           }
         };
       }

+ 5 - 0
osmd-extended/src/VexFlowPatch/src/keysignature.js

@@ -88,9 +88,13 @@ export class KeySignature extends StaveModifier {
     this.glyphs = [];
     this.xPositions = []; // relative to this.x
     this.paddingForced = false;
+    this.isDraw = true
   }
 
   getCategory() { return KeySignature.CATEGORY; }
+  setDrawState(_draw){
+    this.isDraw = _draw
+  }
 
   // Add an accidental glyph to the `KeySignature` instance which represents
   // the provided `acc`. If `nextAcc` is also provided, the appropriate
@@ -313,6 +317,7 @@ export class KeySignature extends StaveModifier {
 
     if (!this.formatted) this.format();
     this.setRendered();
+    if (!this.isDraw) return;
 
     if (this.glyphs.length > 0) {
         this.stave.context.openGroup("keysignature");

+ 6 - 6
osmd-extended/src/VexFlowPatch/src/numbered_note.js

@@ -299,10 +299,10 @@ export class NumberedNote extends StaveNote {
     if (k === "b") {
       k = "R"
     }
-    k = k.replaceAll("#", "");
-    k = k.replaceAll("n", "");
+    k = k.replace(new RegExp('#', 'g'), "");
+    k = k.replace(new RegExp("n", 'g'), "");
     if (k.length > 1) {
-      k = k.replaceAll("b", "");
+      k = k.replace(new RegExp("b", 'g'), "");
     }
     const key = NumberedNoteHead.numberedNotationMapping[k.toUpperCase()]
 
@@ -1034,10 +1034,10 @@ class NumberedNoteHead {
     if (k === "b") {
       k = "R"
     }
-    k = k.replaceAll("#", "");
-    k = k.replaceAll("n", "");
+    k = k.replace(new RegExp("#", 'g'), "");
+    k = k.replace(new RegExp("n", 'g'), "");
     if (k.length > 1) {
-      k = k.replaceAll("b", "");
+      k = k.replace(new RegExp("b", 'g'), "");
     }
     // var h = parseInt(spl[1]);
     // var t = h - 4 - 1;

+ 1 - 1
osmd-extended/src/VexFlowPatch/src/stave.js

@@ -734,7 +734,7 @@ export class Stave extends Element {
       }
 
       let isOpenGroup = false
-      if(["StaveSection", "Repetition"].includes(this.modifiers[i].attrs.type)) {
+      if(["StaveSection", "Repetition", "Volta"].includes(this.modifiers[i].attrs.type)) {
         isOpenGroup = true
         this.context.openGroup(this.modifiers[i].attrs.type)
       }

+ 75 - 29
osmd-extended/src/VexFlowPatch/src/stavenote.js

@@ -53,16 +53,18 @@ export class StaveNote extends StemmableNote {
 
   // Static rendering method that can be called from
   // other classes (e.g. VibratoBracket)
-  static renderVibrato(ctx, x, y, opts, y2) {
-    const {vibrato_width, wave_width, wave_girth, wave_height } = opts;
-    const num_waves = vibrato_width / wave_width;
-    const step = Math.abs(y2 - y) / num_waves + 2
+  static renderVibrato(ctx, x, y, opts) {
+    let {width, height, wave_width, wave_girth, wave_height, stem_direction } = opts;
+    const num_waves = Math.floor(width / wave_width);
+    let step = height / (num_waves / 2) * stem_direction
+    if (step == 0){
+      stem_direction = 1
+      step = 0.3
+    }
 
     ctx.beginPath();
-
-    let i;
     ctx.moveTo(x, y + wave_girth);
-    for (i = 0; i < num_waves / 2; ++i) {
+    for (let i = 0; i < num_waves / 2; ++i) {
       ctx.quadraticCurveTo(x + (wave_width / 2), y - (wave_height / 2), x + wave_width, y);
       x += wave_width;
       y += step;
@@ -70,22 +72,28 @@ export class StaveNote extends StemmableNote {
       x += wave_width;
     }
 
-    for (i = 0; i < num_waves / 2; ++i) {
-      ctx.quadraticCurveTo(
-        x - (wave_width / 2),
-        (y + (wave_height / 2)) + wave_girth,
-        x - wave_width, y + wave_girth);
+    for (let i = 0; i < num_waves / 2; ++i) {
+      ctx.quadraticCurveTo( x - (wave_width / 2), (y + (wave_height / 2)) + wave_girth, x - wave_width, y + wave_girth);
       x -= wave_width;
       y -= step;
-      ctx.quadraticCurveTo(
-        x - (wave_width / 2),
-        (y - (wave_height / 2)) + wave_girth,
-        x - wave_width, y + wave_girth);
+      ctx.quadraticCurveTo( x - (wave_width / 2), (y - (wave_height / 2)) + wave_girth, x - wave_width, y + wave_girth);
       x -= wave_width;
     }
+    if(stem_direction < 0){
+      ctx.stroke()
+    }
     ctx.fill();
   }
 
+  /** 绘制滑音 */
+  static renderSlideNote(ctx, x, y, x1, y1){
+    ctx.beginPath();
+    ctx.moveTo(x, y);
+    ctx.lineTo(x1,y1);
+    ctx.lineWidth = 2
+    ctx.stroke();
+  }
+
   // ## Static Methods
   //
   // Format notes inside a ModifierContext.
@@ -414,6 +422,8 @@ export class StaveNote extends StemmableNote {
     this.octave_shift = noteStruct.octave_shift;
     this.beam = null;
     this.vibrato = null;
+    this.slideNote = null;
+
 
     // Pull note rendering properties
     this.glyph = Flow.getGlyphProps(this.duration, this.noteType);
@@ -483,6 +493,11 @@ export class StaveNote extends StemmableNote {
     this.vibrato = vibrato
   }
 
+  /** 增加滑音 */
+  setSlideNote(slideNote){
+    this.slideNote = slideNote
+  }
+
   getCategory() { return StaveNote.CATEGORY; }
 
   // Builds a `Stem` for the note
@@ -878,9 +893,6 @@ export class StaveNote extends StemmableNote {
   }
 
   setKeyLine(index, line) {
-    if (this.glyph.code_head == "va5" || this.glyph.code_head == "v3c") {
-      line = 3
-    }
     this.keyProps[index].line = line;
     this.reset();
     return this;
@@ -1154,22 +1166,47 @@ export class StaveNote extends StemmableNote {
   drawVibrato() {
     if (this.vibrato) {
       const ctx = this.context;
-      ctx.openGroup('vibrato');
       const x = this.vibrato.getNoteHeadEndX() + 2
-      const { y_top: y } = this.vibrato.getNoteHeadBounds();
-      let { y_bottom: y2 } = this.getNoteHeadBounds();
-      let width = Math.abs(this.getNoteHeadBeginX() - x) - 10
+      let { y_bottom: y } = this.vibrato.getNoteHeadBounds();
+      let { y_top: y2 } = this.getNoteHeadBounds();
+      const stem_direction = y < y2 ? 1 : -1;
+      let width = Math.abs(this.getNoteHeadBeginX() - 5 - x)
+      let height = Math.abs(y2 - y);
+      if (this.duration === 'w' || this.vibrato.stave.y !== this.stave.y) {
+        width = this.vibrato.stave.getNoteEndX() - 5 - x
+      }
       if (this.vibrato.stave.y !== this.stave.y) {
-        width = 30
-        y2 = y + 10
+        // 不在统一行
+        height = 0
       }
+      ctx.openGroup('vibrato');
       StaveNote.renderVibrato(this.context, x, y, {
         harsh: true,
-        vibrato_width: width,
+        width,
+        height,
         wave_height: 6,
         wave_width: 4,
-        wave_girth: 2,
-      }, y2)
+        wave_girth: 4,
+        stem_direction
+      })
+      ctx.closeGroup();
+    }
+
+  }
+
+  /** 绘制滑音 */
+  drawSlide() {
+    if (this.slideNote) {
+      const ctx = this.context;
+      const x = this.slideNote.getNoteHeadEndX() + 2
+      let {y_bottom: y } = this.slideNote.getNoteHeadBounds();
+      let x1 = this.getNoteHeadBeginX() - 4
+      let { y_top: y1 } = this.getNoteHeadBounds();
+      if (this.duration === 'w' || this.slideNote.stave.y !== this.stave.y) {
+        x1 = this.slideNote.stave.getNoteEndX() - 2
+      }
+      ctx.openGroup('slide');
+      StaveNote.renderSlideNote(ctx, x, y, x1, y1)
       ctx.closeGroup();
     }
 
@@ -1210,6 +1247,7 @@ export class StaveNote extends StemmableNote {
     }
     ctx.closeGroup();
     this.drawVibrato();
+    this.drawSlide();
   }
 
 
@@ -1318,7 +1356,15 @@ export class StaveNote extends StemmableNote {
           this.note_heads[i].setLine(line + _top)
         } else {
           if (lines === 2){
-            this.note_heads[i].setLine(3)
+            if (this.duration === 'q') {
+              this.note_heads[i].setLine(2)
+            } else if(this.duration === 'w') {
+              this.note_heads[i].setLine(4)
+            } else if (this.duration === 'h') {
+              this.note_heads[i].setLine(2.5)
+            } else {
+              this.note_heads[i].setLine(2)
+            }
           }
         }
       }