123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- import Vex from "vexflow";
- import { GraphicalNote } from "../GraphicalNote";
- import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
- import { VexFlowMeasure } from "./VexFlowMeasure";
- import { SourceStaffEntry } from "../../VoiceData/SourceStaffEntry";
- import { unitInPixels } from "./VexFlowMusicSheetDrawer";
- import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
- import { Note } from "../../VoiceData/Note";
- import { AccidentalEnum } from "../../../Common/DataObjects/Pitch";
- export class VexFlowStaffEntry extends GraphicalStaffEntry {
- constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
- super(measure, sourceStaffEntry, staffEntryParent);
- }
- // if there is a in-measure clef given before this staffEntry,
- // it will be converted to a Vex.Flow.ClefNote and assigned to this variable:
- public vfClefBefore: Vex.Flow.ClefNote;
- /**
- * Calculates the staff entry positions from the VexFlow stave information and the tickabels inside the staff.
- * This is needed in order to set the OSMD staff entries (which are almost the same as tickables) to the correct positions.
- * It is also needed to be done after formatting!
- */
- public calculateXPosition(): void {
- const stave: Vex.Flow.Stave = (this.parentMeasure as VexFlowMeasure).getVFStave();
- // sets the vexflow x positions back into the bounding boxes of the staff entries in the osmd object model.
- // The positions are needed for cursor placement and mouse/tap interactions
- let lastBorderLeft: number = 0;
- for (const gve of this.graphicalVoiceEntries as VexFlowVoiceEntry[]) {
- if (gve.vfStaveNote) {
- gve.vfStaveNote.setStave(stave);
- if (!gve.vfStaveNote.preFormatted) {
- continue;
- }
- gve.applyBordersFromVexflow();
- if (this.parentMeasure.ParentStaff.isTab) {
- // the x-position could be finetuned for the cursor.
- // somehow, gve.vfStaveNote.getBoundingBox() is null for a TabNote (which is a StemmableNote).
- this.PositionAndShape.RelativePosition.x = (gve.vfStaveNote.getAbsoluteX() + (<any>gve.vfStaveNote).glyph.getWidth()) / unitInPixels;
- } else {
- this.PositionAndShape.RelativePosition.x = gve.vfStaveNote.getBoundingBox().getX() / unitInPixels;
- }
- const sourceNote: Note = gve.notes[0].sourceNote;
- if (sourceNote.isRest() && sourceNote.Length.RealValue === this.parentMeasure.parentSourceMeasure.ActiveTimeSignature.RealValue) {
- // whole rest: length = measure length. (4/4 in a 4/4 time signature, 3/4 in a 3/4 time signature, 1/4 in a 1/4 time signature, etc.)
- // see Note.isWholeRest(), which is currently not safe
- this.PositionAndShape.RelativePosition.x +=
- this.parentMeasure.parentSourceMeasure.Rules.WholeRestXShiftVexflow - 0.1; // xShift from VexFlowConverter
- gve.PositionAndShape.BorderLeft = -0.7;
- gve.PositionAndShape.BorderRight = 0.7;
- }
- if (gve.PositionAndShape.BorderLeft < lastBorderLeft) {
- lastBorderLeft = gve.PositionAndShape.BorderLeft;
- }
- }
- }
- this.PositionAndShape.RelativePosition.x -= lastBorderLeft;
- this.PositionAndShape.calculateBoundingBox();
- }
- public setMaxAccidentals(): number {
- for (const gve of this.graphicalVoiceEntries) {
- for (const note of gve.notes) {
- if (note.DrawnAccidental !== AccidentalEnum.NONE) {
- //TODO continue checking for double accidentals in other notes?
- return this.MaxAccidentals = 1;
- }
- // live calculation if the note was changed:
- // let pitch: Pitch = note.sourceNote.Pitch;
- // pitch = (note as VexFlowGraphicalNote).drawPitch(pitch);
- // if (pitch) {
- // const accidental: AccidentalEnum = pitch.Accidental;
- // if (accidental !== AccidentalEnum.NONE) {
- // this.maxAccidentals = 1;
- // return this.maxAccidentals;
- // }
- // }
- }
- }
- return this.MaxAccidentals = 0;
- }
- // should be called after VexFlowConverter.StaveNote
- public setModifierXOffsets(): void {
- let notes: GraphicalNote[] = [];
- for (const gve of this.graphicalVoiceEntries) {
- notes = notes.concat(gve.notes);
- }
- const staffLines: number[] = notes.map(n => n.staffLine);
- const stringNumberOffsets: number[] = this.calculateModifierXOffsets(staffLines, 1);
- const fingeringOffsets: number[] = this.calculateModifierXOffsets(staffLines, 0.5);
- notes.forEach((note, i) => {
- note.baseFingeringXOffset = fingeringOffsets[i];
- note.baseStringNumberXOffset = stringNumberOffsets[i];
- });
- }
- /**
- * Calculate x offsets for overlapping string and fingering modifiers in a chord.
- */
- private calculateModifierXOffsets(staffLines: number[], collisionDistance: number): number[] {
- const offsets: number[] = [];
- for (let i: number = 0; i < staffLines.length; i++) {
- let offset: number = 0;
- let collisionFound: boolean = true;
- while (collisionFound) {
- for (let j: number = i; j >= 0; j--) {
- const lineDiff: number = Math.abs(staffLines[i] - staffLines[j]);
- if (lineDiff <= collisionDistance && offset === offsets[j]) {
- offset++;
- collisionFound = true;
- break;
- }
- collisionFound = false;
- }
- }
- offsets.push(offset);
- }
- return offsets;
- }
- }
|