MeasureSizeCalculator.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import Vex = require("vexflow");
  2. import StaveNote = Vex.Flow.StaveNote;
  3. // The type PositionAndShapeInfo is still to be ported in TypeScript
  4. type PositionAndShapeInfo = any;
  5. declare var PositionAndShapeInfo: any;
  6. /* TODO
  7. * Complete support for StaveModifiers
  8. * Take into account Ties and Slurs
  9. */
  10. /* Measure Size Calculator
  11. * Given a stave, voices and a formatter, calculates
  12. * through VexFlow the size of a measure.
  13. * !!! before using this, call the methods
  14. * !!! joinVoices and preCalculateMinTotalWidth
  15. * !!! of the formatter!
  16. *
  17. * Usage:
  18. * let stave: Vex.Flow.Stave = ...;
  19. * let formatter = new Vex.Flow.Formatter()
  20. * let voices: Vex.Flor.Voice[] = ...;
  21. * formatter.preCalculateMinTotalWidth(voices);
  22. * let calc = new MeasureSizeCalculator(stave, voices, formatter);
  23. * calc.???
  24. */
  25. export class MeasureSizeCalculator {
  26. private stave: Vex.Flow.Stave;
  27. private voices: Vex.Flow.Voice[];
  28. private formatter: any;
  29. private offsetLeft: number;
  30. private offsetRight: number;
  31. private voicesWidth: number;
  32. private topBorder: number;
  33. private bottomBorder: number;
  34. constructor(
  35. stave: Vex.Flow.Stave,
  36. voices: Vex.Flow.Voice[],
  37. formatter: Vex.Flow.Formatter
  38. ) {
  39. this.stave = stave;
  40. this.voices = voices;
  41. this.formatter = formatter;
  42. // the stave must be initialized with width, x, y 0
  43. // the voices must be already joined and (pre)formatted
  44. if (!formatter.hasMinTotalWidth) {
  45. throw "Must first call Formatter.preCalculateMinTotalWidth " +
  46. "with all the voices in the measure (vertical)";
  47. }
  48. this.format();
  49. }
  50. // Returns the shape of the note head at position _index_ inside _note_.
  51. // Remember: in VexFlow, StaveNote correspond to PhonicScore's VoiceEntries.
  52. // public static getVexFlowNoteHeadShape(note: StaveNote, index: number): PositionAndShapeInfo {
  53. // // note_heads is not public in StaveNote, but we access it anyway...
  54. // let bb = note.note_heads[index].getBoundingBox();
  55. // let info: any = new PositionAndShapeInfo();
  56. // let x: number = bb.getX();
  57. // let y: number = bb.getY();
  58. // let w: number = bb.getW();
  59. // let h: number = bb.getH();
  60. // info.Left = info.Right = bb.getW() / 2;
  61. // info.Top = info.Bottom = bb.getH() / 2;
  62. // info.X = bb.getX() + info.Left;
  63. // info.Y = bb.getY() + info.Bottom;
  64. // return info;
  65. //}
  66. // Returns the shape of all the note heads inside a StaveNote.
  67. // Remember: in VexFlow, StaveNote correspond to PhonicScore's VoiceEntries.
  68. public static getVexFlowStaveNoteShape(note: StaveNote): PositionAndShapeInfo {
  69. let info: any = new PositionAndShapeInfo();
  70. let bounds: any = note.getNoteHeadBounds();
  71. let beginX: number = note.getNoteHeadBeginX();
  72. let endX: number = note.getNoteHeadEndX();
  73. info.Left = info.Right = (endX - beginX) / 2;
  74. info.Top = info.Bottom = (bounds.y_top - bounds.y_bottom) / 2;
  75. info.X = beginX + info.Left;
  76. info.Y = bounds.y_bottom + info.Bottom;
  77. return info;
  78. }
  79. public static getClefBoundingBox(clef: Vex.Flow.Clef): Vex.Flow.BoundingBox {
  80. let clef2: any = clef;
  81. clef2.placeGlyphOnLine(clef2.glyph, clef2.stave, clef2.clef.line);
  82. let glyph: any = clef.glyph;
  83. let posX: number = clef.x + glyph.x_shift;
  84. let posY: number = clef.stave.getYForGlyphs() + glyph.y_shift;
  85. let scale: number = glyph.scale;
  86. let outline: any[] = glyph.metrics.outline;
  87. let xmin: number = 0, xmax: number = 0, ymin: number = 0, ymax: number = 0;
  88. function update(i: number): void {
  89. let x: number = outline[i + 1];
  90. let y: number = outline[i + 2];
  91. xmin = Math.min(xmin, x);
  92. xmax = Math.max(xmax, x);
  93. ymin = Math.min(ymin, y);
  94. ymax = Math.max(ymax, y);
  95. }
  96. for (let i: number = 0, len: number = outline.length; i < len; i += 3) {
  97. switch (outline[i] as string) {
  98. case "m": update(i); break;
  99. case "l": update(i); break;
  100. case "q": i += 2; update(i); break;
  101. case "b": i += 4; update(i); break;
  102. default: break;
  103. }
  104. }
  105. return new Vex.Flow.BoundingBox(
  106. posX + xmin * scale,
  107. posY - ymin * scale,
  108. (xmax - xmin) * scale,
  109. (ymin - ymax) * scale
  110. );
  111. }
  112. public static getKeySignatureBoundingBox(sig: any): Vex.Flow.BoundingBox {
  113. // FIXME: Maybe use Vex.Flow.keySignature(this.keySpec);
  114. let stave: Vex.Flow.Stave = sig.getStave();
  115. let width: number = sig.getWidth();
  116. let maxLine: number = 1;
  117. let minLine: number = 1;
  118. for (let acc of sig.accList) {
  119. maxLine = Math.max(acc.line, maxLine);
  120. minLine = Math.min(acc.line, minLine);
  121. }
  122. let y: number = sig.getStave().getYForLine(minLine);
  123. let height: number = stave.getSpacingBetweenLines() * (maxLine - minLine);
  124. let x: number = 0; // FIXME
  125. return new Vex.Flow.BoundingBox(x, y, width, height);
  126. }
  127. public getWidth(): number {
  128. // begin_modifiers + voices + end_modifiers
  129. return this.offsetLeft + this.voicesWidth + this.offsetRight;
  130. // = stave.end_x - stave.x
  131. }
  132. public getHeight(): number {
  133. // FIXME this formula does not take into account
  134. // other things like staves and ties!
  135. return this.stave.getSpacingBetweenLines()
  136. * (this.topBorder - this.bottomBorder);
  137. }
  138. // The following methods return a number
  139. // where 0 is the upper line of the stave.
  140. public getTopBorder(): number {
  141. return this.topBorder;
  142. }
  143. public getBottomBorder(): number {
  144. return this.bottomBorder;
  145. }
  146. private format(): void {
  147. let stave: Vex.Flow.Stave = this.stave;
  148. let voices: Vex.Flow.Voice[] = this.voices;
  149. let voicesBoundingBox: Vex.Flow.BoundingBox;
  150. let bb: Vex.Flow.BoundingBox;
  151. // Compute widths
  152. this.voicesWidth = this.formatter.minTotalWidth;
  153. stave.setWidth(this.voicesWidth);
  154. stave.format();
  155. this.offsetLeft = stave.getNoteStartX() - stave.x;
  156. this.offsetRight = stave.end_x - stave.getWidth() - stave.start_x;
  157. // Compute heights
  158. // Height is:
  159. //// height of StaveModifiers + BoundingBox of notes + height of NoteMod's
  160. for (let i: number = 0; i < this.voices.length; i ++) {
  161. voices[i].setStave(stave);
  162. bb = voices[i].getBoundingBox();
  163. if (voicesBoundingBox === undefined) {
  164. voicesBoundingBox = bb;
  165. } else {
  166. voicesBoundingBox = voicesBoundingBox.mergeWith(bb);
  167. }
  168. }
  169. // TODO voicesBoundingBox.getW() should be similar to this.voicesWidth?
  170. //console.log("this.width", this.voicesWidth);
  171. //console.log("voicesBB", voicesBoundingBox.getW());
  172. //this.height = voicesBoundingBox.getH(); FIXME
  173. // Consider clefs
  174. let clefs: Vex.Flow.Clef[] = stave.getModifiers(
  175. Vex.Flow.Modifier.Position.LEFT,
  176. Vex.Flow.Clef.category
  177. );
  178. for (let clef of clefs) {
  179. voicesBoundingBox = voicesBoundingBox.mergeWith(
  180. MeasureSizeCalculator.getClefBoundingBox(clef)
  181. );
  182. }
  183. this.topBorder = Math.min(
  184. 0,
  185. Math.floor(stave.getLineForY(voicesBoundingBox.getY()))
  186. );
  187. this.bottomBorder = Math.max(
  188. stave.getNumLines(),
  189. Math.ceil(stave.getLineForY(voicesBoundingBox.getY() + voicesBoundingBox.getH()))
  190. );
  191. }
  192. }