import Vex from "vexflow"; import VF = Vex.Flow; import { Staff } from "../../VoiceData/Staff"; import { SourceMeasure } from "../../VoiceData/SourceMeasure"; import { VexFlowMeasure } from "./VexFlowMeasure"; import { VexFlowStaffEntry } from "./VexFlowStaffEntry"; import { VexFlowConverter } from "./VexFlowConverter"; import { StaffLine } from "../StaffLine"; import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry"; import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry"; import { Arpeggio } from "../../VoiceData/Arpeggio"; import { Voice } from "../../VoiceData/Voice"; import log from "loglevel"; export class VexFlowTabMeasure extends VexFlowMeasure { constructor(staff: Staff, sourceMeasure: SourceMeasure = undefined, staffLine: StaffLine = undefined) { super(staff, sourceMeasure, staffLine); this.isTabMeasure = true; } /** * Reset all the geometric values and parameters of this measure and put it in an initialized state. * This is needed to evaluate a measure a second time by system builder. */ public resetLayout(): void { // Take into account some space for the begin and end lines of the stave // Will be changed when repetitions will be implemented //this.beginInstructionsWidth = 20 / UnitInPixels; //this.endInstructionsWidth = 20 / UnitInPixels; const stafflineCount: number = this.ParentStaff.StafflineCount ?? 6; // if undefined, 6 by default (same as Vexflow default) this.stave = new VF.TabStave(0, 0, 0, { space_above_staff_ln: 0, space_below_staff_ln: 0, num_lines: stafflineCount }); // also see VexFlowMusicSheetDrawer.drawSheet() for some other vexflow default value settings (like default font scale) this.updateInstructionWidth(); } public graphicalMeasureCreatedCalculations(): void { for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) { const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry); // create vex flow Notes: for (const gve of graphicalStaffEntry.graphicalVoiceEntries) { if (gve.notes[0].sourceNote.isRest()) { const ghostNotes: VF.GhostNote[] = VexFlowConverter.GhostNotes(gve.notes[0].sourceNote.Length); (gve as VexFlowVoiceEntry).vfStaveNote = ghostNotes[0]; (gve as VexFlowVoiceEntry).vfGhostNotes = ghostNotes; // we actually need multiple ghost notes sometimes, see #1062 Sep. 23 2021 comment } else { (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.CreateTabNote(gve); } } } if (this.rules.TupletNumbersInTabs) { // default false, don't show tuplets in tab measures this.finalizeTuplets(); } const voices: Voice[] = this.getVoicesWithinMeasure(); for (const voice of voices) { if (!voice) { continue; } // add a vexFlow voice for this voice: this.vfVoices[voice.VoiceId] = new VF.Voice({ beat_value: this.parentSourceMeasure.Duration.Denominator, num_beats: this.parentSourceMeasure.Duration.Numerator, resolution: VF.RESOLUTION, }).setMode(VF.Voice.Mode.SOFT); const restFilledEntries: GraphicalVoiceEntry[] = this.getRestFilledVexFlowStaveNotesPerVoice(voice); // create vex flow voices and add tickables to it: for (const voiceEntry of restFilledEntries) { if (voiceEntry.parentVoiceEntry) { if (voiceEntry.parentVoiceEntry.IsGrace && !voiceEntry.parentVoiceEntry.GraceAfterMainNote) { continue; } } const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry; if (voiceEntry.notes.length === 0 || !voiceEntry.notes[0] || !voiceEntry.notes[0].sourceNote.PrintObject) { // GhostNote, don't add modifiers like in-measure clefs if (vexFlowVoiceEntry.vfGhostNotes) { for (const ghostNote of vexFlowVoiceEntry.vfGhostNotes) { this.vfVoices[voice.VoiceId].addTickable(ghostNote); } } else { this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote); } continue; } // don't add non-tab fingerings for tab measures (doesn't work yet for tabnotes in vexflow, see VexFlowMeasure.createFingerings()) // if (voiceEntry.parentVoiceEntry && this.rules.RenderFingerings) { // this.createFingerings(voiceEntry); // } // add Arpeggio if (voiceEntry.parentVoiceEntry && voiceEntry.parentVoiceEntry.Arpeggio) { const arpeggio: Arpeggio = voiceEntry.parentVoiceEntry.Arpeggio; // TODO right now our arpeggio object has all arpeggio notes from arpeggios across all voices. // see VoiceGenerator. Doesn't matter for Vexflow for now though if (voiceEntry.notes && voiceEntry.notes.length > 1) { const type: VF.Stroke.Type = VexFlowConverter.StrokeTypeFromArpeggioType(arpeggio.type); const stroke: VF.Stroke = new VF.Stroke(type, { all_voices: this.rules.ArpeggiosGoAcrossVoices // default: false. This causes arpeggios to always go across all voices, which is often unwanted. // also, this can cause infinite height of stroke, see #546 }); //if (arpeggio.notes.length === vexFlowVoiceEntry.notes.length) { // different workaround for endless y bug if (this.rules.RenderArpeggios) { vexFlowVoiceEntry.vfStaveNote.addStroke(0, stroke); } } else { log.debug(`[OSMD] arpeggio in measure ${this.MeasureNumber} could not be drawn. voice entry had less than two notes, arpeggio is likely between voice entries, not currently supported in Vexflow.`); // TODO: create new arpeggio with all the arpeggio's notes (arpeggio.notes), perhaps with GhostNotes in a new vfStaveNote. not easy. } } if (vexFlowVoiceEntry.vfGhostNotes) { for (const ghostNote of vexFlowVoiceEntry.vfGhostNotes) { this.vfVoices[voice.VoiceId].addTickable(ghostNote); } } else { this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote); } } } //this.createArticulations(); //this.createOrnaments(); } }