VexFlowStaffEntry.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import Vex from "vexflow";
  2. import { GraphicalNote } from "../GraphicalNote";
  3. import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
  4. import { VexFlowMeasure } from "./VexFlowMeasure";
  5. import { SourceStaffEntry } from "../../VoiceData/SourceStaffEntry";
  6. import { unitInPixels } from "./VexFlowMusicSheetDrawer";
  7. import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
  8. import { Note } from "../../VoiceData/Note";
  9. import { AccidentalEnum } from "../../../Common/DataObjects/Pitch";
  10. export class VexFlowStaffEntry extends GraphicalStaffEntry {
  11. constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
  12. super(measure, sourceStaffEntry, staffEntryParent);
  13. }
  14. // if there is a in-measure clef given before this staffEntry,
  15. // it will be converted to a Vex.Flow.ClefNote and assigned to this variable:
  16. public vfClefBefore: Vex.Flow.ClefNote;
  17. /**
  18. * Calculates the staff entry positions from the VexFlow stave information and the tickabels inside the staff.
  19. * This is needed in order to set the OSMD staff entries (which are almost the same as tickables) to the correct positions.
  20. * It is also needed to be done after formatting!
  21. */
  22. public calculateXPosition(): void {
  23. const stave: Vex.Flow.Stave = (this.parentMeasure as VexFlowMeasure).getVFStave();
  24. // sets the vexflow x positions back into the bounding boxes of the staff entries in the osmd object model.
  25. // The positions are needed for cursor placement and mouse/tap interactions
  26. let lastBorderLeft: number = 0;
  27. for (const gve of this.graphicalVoiceEntries as VexFlowVoiceEntry[]) {
  28. if (gve.vfStaveNote) {
  29. gve.vfStaveNote.setStave(stave);
  30. if (!gve.vfStaveNote.preFormatted) {
  31. continue;
  32. }
  33. gve.applyBordersFromVexflow();
  34. if (this.parentMeasure.ParentStaff.isTab) {
  35. // the x-position could be finetuned for the cursor.
  36. // somehow, gve.vfStaveNote.getBoundingBox() is null for a TabNote (which is a StemmableNote).
  37. this.PositionAndShape.RelativePosition.x = (gve.vfStaveNote.getAbsoluteX() + (<any>gve.vfStaveNote).glyph.getWidth()) / unitInPixels;
  38. } else {
  39. this.PositionAndShape.RelativePosition.x = gve.vfStaveNote.getBoundingBox().getX() / unitInPixels;
  40. }
  41. const sourceNote: Note = gve.notes[0].sourceNote;
  42. if (sourceNote.isRest() && sourceNote.Length.RealValue === this.parentMeasure.parentSourceMeasure.ActiveTimeSignature.RealValue) {
  43. // 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.)
  44. // see Note.isWholeRest(), which is currently not safe
  45. this.PositionAndShape.RelativePosition.x +=
  46. this.parentMeasure.parentSourceMeasure.Rules.WholeRestXShiftVexflow - 0.1; // xShift from VexFlowConverter
  47. gve.PositionAndShape.BorderLeft = -0.7;
  48. gve.PositionAndShape.BorderRight = 0.7;
  49. }
  50. if (gve.PositionAndShape.BorderLeft < lastBorderLeft) {
  51. lastBorderLeft = gve.PositionAndShape.BorderLeft;
  52. }
  53. }
  54. }
  55. this.PositionAndShape.RelativePosition.x -= lastBorderLeft;
  56. this.PositionAndShape.calculateBoundingBox();
  57. }
  58. public setMaxAccidentals(): number {
  59. for (const gve of this.graphicalVoiceEntries) {
  60. for (const note of gve.notes) {
  61. if (note.DrawnAccidental !== AccidentalEnum.NONE) {
  62. //TODO continue checking for double accidentals in other notes?
  63. return this.MaxAccidentals = 1;
  64. }
  65. // live calculation if the note was changed:
  66. // let pitch: Pitch = note.sourceNote.Pitch;
  67. // pitch = (note as VexFlowGraphicalNote).drawPitch(pitch);
  68. // if (pitch) {
  69. // const accidental: AccidentalEnum = pitch.Accidental;
  70. // if (accidental !== AccidentalEnum.NONE) {
  71. // this.maxAccidentals = 1;
  72. // return this.maxAccidentals;
  73. // }
  74. // }
  75. }
  76. }
  77. return this.MaxAccidentals = 0;
  78. }
  79. // should be called after VexFlowConverter.StaveNote
  80. public setModifierXOffsets(): void {
  81. let notes: GraphicalNote[] = [];
  82. for (const gve of this.graphicalVoiceEntries) {
  83. notes = notes.concat(gve.notes);
  84. }
  85. const staffLines: number[] = notes.map(n => n.staffLine);
  86. const stringNumberOffsets: number[] = this.calculateModifierXOffsets(staffLines, 1);
  87. const fingeringOffsets: number[] = this.calculateModifierXOffsets(staffLines, 0.5);
  88. notes.forEach((note, i) => {
  89. note.baseFingeringXOffset = fingeringOffsets[i];
  90. note.baseStringNumberXOffset = stringNumberOffsets[i];
  91. });
  92. }
  93. /**
  94. * Calculate x offsets for overlapping string and fingering modifiers in a chord.
  95. */
  96. private calculateModifierXOffsets(staffLines: number[], collisionDistance: number): number[] {
  97. const offsets: number[] = [];
  98. for (let i: number = 0; i < staffLines.length; i++) {
  99. let offset: number = 0;
  100. let collisionFound: boolean = true;
  101. while (collisionFound) {
  102. for (let j: number = i; j >= 0; j--) {
  103. const lineDiff: number = Math.abs(staffLines[i] - staffLines[j]);
  104. if (lineDiff <= collisionDistance && offset === offsets[j]) {
  105. offset++;
  106. collisionFound = true;
  107. break;
  108. }
  109. collisionFound = false;
  110. }
  111. }
  112. offsets.push(offset);
  113. }
  114. return offsets;
  115. }
  116. }