VexFlowMusicSheetCalculator.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import {MusicSheetCalculator} from "../MusicSheetCalculator";
  2. import {VexFlowGraphicalSymbolFactory} from "./VexFlowGraphicalSymbolFactory";
  3. import {StaffMeasure} from "../StaffMeasure";
  4. import {StaffLine} from "../StaffLine";
  5. import {VoiceEntry} from "../../VoiceData/VoiceEntry";
  6. import {MusicSystem} from "../MusicSystem";
  7. import {GraphicalNote} from "../GraphicalNote";
  8. import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
  9. import {GraphicalMusicPage} from "../GraphicalMusicPage";
  10. import {GraphicalTie} from "../GraphicalTie";
  11. import {Tie} from "../../VoiceData/Tie";
  12. import {SourceMeasure} from "../../VoiceData/SourceMeasure";
  13. import {MultiExpression} from "../../VoiceData/Expressions/MultiExpression";
  14. import {RepetitionInstruction} from "../../VoiceData/Instructions/RepetitionInstruction";
  15. import {Beam} from "../../VoiceData/Beam";
  16. import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
  17. import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
  18. import {Fraction} from "../../../Common/DataObjects/Fraction";
  19. import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
  20. import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
  21. import {ArticulationEnum} from "../../VoiceData/VoiceEntry";
  22. import {Tuplet} from "../../VoiceData/Tuplet";
  23. import {VexFlowMeasure} from "./VexFlowMeasure";
  24. import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
  25. import Vex = require("vexflow");
  26. import {Logging} from "../../../Common/Logging";
  27. import {unitInPixels} from "./VexFlowMusicSheetDrawer";
  28. import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
  29. import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
  30. import { GraphicalLabel } from "../GraphicalLabel";
  31. import { LyricsEntry } from "../../VoiceData/Lyrics/LyricsEntry";
  32. import { GraphicalLyricWord } from "../GraphicalLyricWord";
  33. import { VexFlowStaffEntry } from "./VexFlowStaffEntry";
  34. export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
  35. constructor() {
  36. super(new VexFlowGraphicalSymbolFactory());
  37. MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
  38. }
  39. protected clearRecreatedObjects(): void {
  40. super.clearRecreatedObjects();
  41. for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
  42. for (const staffMeasure of staffMeasures) {
  43. (<VexFlowMeasure>staffMeasure).clean();
  44. }
  45. }
  46. }
  47. protected formatMeasures(): void {
  48. for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
  49. for (const staffMeasure of staffMeasures) {
  50. (<VexFlowMeasure>staffMeasure).format();
  51. for (const staffEntry of staffMeasure.staffEntries) {
  52. (<VexFlowStaffEntry>staffEntry).calculateXPosition();
  53. }
  54. }
  55. }
  56. }
  57. //protected clearSystemsAndMeasures(): void {
  58. // for (let measure of measures) {
  59. //
  60. // }
  61. //}
  62. /**
  63. * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
  64. * All staff entries are x-aligned throughout all vertically aligned staff measures.
  65. * This method is called within calculateXLayout.
  66. * The staff entries are aligned with minimum needed x distances.
  67. * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
  68. * @param measures
  69. * @returns the minimum required x width of the source measure (=list of staff measures)
  70. */
  71. protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
  72. // Finalize beams
  73. /*for (let measure of measures) {
  74. (measure as VexFlowMeasure).finalizeBeams();
  75. (measure as VexFlowMeasure).finalizeTuplets();
  76. }*/
  77. // Format the voices
  78. const allVoices: Vex.Flow.Voice[] = [];
  79. const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({
  80. align_rests: true,
  81. });
  82. for (const measure of measures) {
  83. const mvoices: { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
  84. const voices: Vex.Flow.Voice[] = [];
  85. for (const voiceID in mvoices) {
  86. if (mvoices.hasOwnProperty(voiceID)) {
  87. voices.push(mvoices[voiceID]);
  88. allVoices.push(mvoices[voiceID]);
  89. }
  90. }
  91. if (voices.length === 0) {
  92. Logging.warn("Found a measure with no voices... Continuing anyway.", mvoices);
  93. continue;
  94. }
  95. formatter.joinVoices(voices);
  96. }
  97. let width: number = 200;
  98. if (allVoices.length > 0) {
  99. // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
  100. // FIXME: a more relaxed formatting of voices
  101. width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
  102. // firstMeasure.formatVoices = (w: number) => {
  103. // formatter.format(allVoices, w);
  104. // };
  105. for (const measure of measures) {
  106. measure.minimumStaffEntriesWidth = width;
  107. if (measure !== measures[0]) {
  108. (measure as VexFlowMeasure).formatVoices = undefined;
  109. } else {
  110. (measure as VexFlowMeasure).formatVoices = (w: number) => {
  111. formatter.format(allVoices, w);
  112. };
  113. }
  114. }
  115. }
  116. return width;
  117. }
  118. protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
  119. startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
  120. return new GraphicalTie(tie, startNote, endNote);
  121. }
  122. protected updateStaffLineBorders(staffLine: StaffLine): void {
  123. return;
  124. }
  125. protected calculateMeasureNumberPlacement(musicSystem: MusicSystem): void {
  126. return;
  127. }
  128. protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
  129. (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
  130. }
  131. /**
  132. * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
  133. * Is Excecuted per voice entry of a staff entry.
  134. * After that layoutStaffEntry is called.
  135. * @param voiceEntry
  136. * @param graphicalNotes
  137. * @param graphicalStaffEntry
  138. * @param hasPitchedNote
  139. * @param isGraceStaffEntry
  140. */
  141. protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
  142. hasPitchedNote: boolean, isGraceStaffEntry: boolean): void {
  143. return;
  144. }
  145. /**
  146. * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
  147. * This method is called after the voice entries are handled by layoutVoiceEntry().
  148. * @param graphicalStaffEntry
  149. */
  150. protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  151. (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
  152. }
  153. /**
  154. * calculates the y positions of the staff lines within a system and
  155. * furthermore the y positions of the systems themselves.
  156. */
  157. protected calculateSystemYLayout(): void {
  158. for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
  159. const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
  160. if (!this.leadSheet) {
  161. let globalY: number = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
  162. this.rules.TitleBottomDistance;
  163. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  164. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  165. // calculate y positions of stafflines within system
  166. let y: number = 0;
  167. for (const line of musicSystem.StaffLines) {
  168. line.PositionAndShape.RelativePosition.y = y;
  169. y += 10;
  170. }
  171. // set y positions of systems using the previous system and a fixed distance.
  172. musicSystem.PositionAndShape.BorderBottom = y + 0;
  173. musicSystem.PositionAndShape.RelativePosition.x = this.rules.PageLeftMargin + this.rules.SystemLeftMargin;
  174. musicSystem.PositionAndShape.RelativePosition.y = globalY;
  175. globalY += y + 5;
  176. }
  177. }
  178. }
  179. }
  180. /**
  181. * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
  182. */
  183. protected initStaffMeasuresCreation(): void {
  184. return;
  185. }
  186. protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
  187. const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
  188. let vfStartNote: Vex.Flow.StaveNote = undefined;
  189. if (startNote !== undefined) {
  190. vfStartNote = startNote.vfnote[0];
  191. }
  192. const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
  193. let vfEndNote: Vex.Flow.StaveNote = undefined;
  194. if (endNote !== undefined) {
  195. vfEndNote = endNote.vfnote[0];
  196. }
  197. if (tieIsAtSystemBreak) {
  198. // split tie into two ties:
  199. const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  200. first_note: vfStartNote,
  201. });
  202. const measure1: VexFlowMeasure = (startNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
  203. measure1.vfTies.push(vfTie1);
  204. const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  205. last_note : vfEndNote,
  206. });
  207. const measure2: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
  208. measure2.vfTies.push(vfTie2);
  209. } else {
  210. const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  211. first_note: vfStartNote,
  212. last_note : vfEndNote,
  213. });
  214. const measure: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
  215. measure.vfTies.push(vfTie);
  216. }
  217. }
  218. protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  219. return;
  220. }
  221. protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
  222. return;
  223. }
  224. protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  225. return;
  226. }
  227. protected handleTiedGraphicalNote( tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
  228. octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
  229. openTie: Tie, isLastTieNote: boolean): void {
  230. return;
  231. }
  232. /**
  233. * Is called if a note is part of a beam.
  234. * @param graphicalNote
  235. * @param beam
  236. * @param openBeams a list of all currently open beams
  237. */
  238. protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
  239. (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
  240. }
  241. protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
  242. voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
  243. const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
  244. graphicalStaffEntry,
  245. this.rules.LyricsHeight,
  246. this.rules.StaffHeight);
  247. graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
  248. // create corresponding GraphicalLabel
  249. const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
  250. graphicalLabel.setLabelPositionAndShapeBorders();
  251. if (lyricsEntry.Word !== undefined) {
  252. const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
  253. let index: number = lyricWords.indexOf(lyricsEntry.Word);
  254. if (index === -1) {
  255. lyricWords.push(lyricsEntry.Word);
  256. index = lyricWords.indexOf(lyricsEntry.Word);
  257. }
  258. if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
  259. const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
  260. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  261. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  262. this.graphicalLyricWords.push(graphicalLyricWord);
  263. } else {
  264. const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
  265. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  266. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  267. if (graphicalLyricWord.isFilled()) {
  268. lyricWords.splice(index, 1);
  269. this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
  270. }
  271. }
  272. }
  273. });
  274. }
  275. protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  276. return;
  277. }
  278. protected handleVoiceEntryArticulations(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  279. return;
  280. }
  281. /**
  282. * Is called if a note is part of a tuplet.
  283. * @param graphicalNote
  284. * @param tuplet
  285. * @param openTuplets a list of all currently open tuplets
  286. */
  287. protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
  288. (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
  289. }
  290. }