VexFlowMusicSheetCalculator.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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, OctaveShift} 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 * as log from "loglevel";
  27. import {unitInPixels} from "./VexFlowMusicSheetDrawer";
  28. import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
  29. import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
  30. import {GraphicalLyricEntry} from "../GraphicalLyricEntry";
  31. import {GraphicalLabel} from "../GraphicalLabel";
  32. import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
  33. import {GraphicalLyricWord} from "../GraphicalLyricWord";
  34. import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
  35. import { VexFlowOctaveShift } from "./VexFlowOctaveShift";
  36. export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
  37. constructor() {
  38. super();
  39. MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
  40. MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
  41. }
  42. protected clearRecreatedObjects(): void {
  43. super.clearRecreatedObjects();
  44. for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
  45. for (const staffMeasure of staffMeasures) {
  46. (<VexFlowMeasure>staffMeasure).clean();
  47. }
  48. }
  49. }
  50. protected formatMeasures(): void {
  51. for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
  52. for (const staffMeasure of staffMeasures) {
  53. (<VexFlowMeasure>staffMeasure).format();
  54. for (const staffEntry of staffMeasure.staffEntries) {
  55. (<VexFlowStaffEntry>staffEntry).calculateXPosition();
  56. }
  57. }
  58. }
  59. }
  60. //protected clearSystemsAndMeasures(): void {
  61. // for (let measure of measures) {
  62. //
  63. // }
  64. //}
  65. /**
  66. * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
  67. * All staff entries are x-aligned throughout all vertically aligned staff measures.
  68. * This method is called within calculateXLayout.
  69. * The staff entries are aligned with minimum needed x distances.
  70. * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
  71. * @param measures
  72. * @returns the minimum required x width of the source measure (=list of staff measures)
  73. */
  74. protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
  75. // Finalize beams
  76. /*for (let measure of measures) {
  77. (measure as VexFlowMeasure).finalizeBeams();
  78. (measure as VexFlowMeasure).finalizeTuplets();
  79. }*/
  80. // Format the voices
  81. const allVoices: Vex.Flow.Voice[] = [];
  82. const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({align_rests: true,
  83. });
  84. for (const measure of measures) {
  85. const mvoices: { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
  86. const voices: Vex.Flow.Voice[] = [];
  87. for (const voiceID in mvoices) {
  88. if (mvoices.hasOwnProperty(voiceID)) {
  89. voices.push(mvoices[voiceID]);
  90. allVoices.push(mvoices[voiceID]);
  91. }
  92. }
  93. if (voices.length === 0) {
  94. log.warn("Found a measure with no voices... Continuing anyway.", mvoices);
  95. continue;
  96. }
  97. formatter.joinVoices(voices);
  98. }
  99. let width: number = 200;
  100. if (allVoices.length > 0) {
  101. // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
  102. // FIXME: a more relaxed formatting of voices
  103. width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
  104. // firstMeasure.formatVoices = (w: number) => {
  105. // formatter.format(allVoices, w);
  106. // };
  107. for (const measure of measures) {
  108. measure.minimumStaffEntriesWidth = width;
  109. if (measure !== measures[0]) {
  110. (measure as VexFlowMeasure).formatVoices = undefined;
  111. } else {
  112. (measure as VexFlowMeasure).formatVoices = (w: number) => {
  113. formatter.format(allVoices, w);
  114. };
  115. }
  116. }
  117. }
  118. return width;
  119. }
  120. protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
  121. startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
  122. return new GraphicalTie(tie, startNote, endNote);
  123. }
  124. protected updateStaffLineBorders(staffLine: StaffLine): void {
  125. return;
  126. }
  127. protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
  128. (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
  129. }
  130. /**
  131. * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
  132. * Is Excecuted per voice entry of a staff entry.
  133. * After that layoutStaffEntry is called.
  134. * @param voiceEntry
  135. * @param graphicalNotes
  136. * @param graphicalStaffEntry
  137. * @param hasPitchedNote
  138. * @param isGraceStaffEntry
  139. */
  140. protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
  141. hasPitchedNote: boolean, isGraceStaffEntry: boolean): void {
  142. return;
  143. }
  144. /**
  145. * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
  146. * This method is called after the voice entries are handled by layoutVoiceEntry().
  147. * @param graphicalStaffEntry
  148. */
  149. protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  150. (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
  151. }
  152. /**
  153. * calculates the y positions of the staff lines within a system and
  154. * furthermore the y positions of the systems themselves.
  155. */
  156. protected calculateSystemYLayout(): void {
  157. for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
  158. const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
  159. if (!this.leadSheet) {
  160. let globalY: number = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
  161. this.rules.TitleBottomDistance;
  162. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  163. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  164. // calculate y positions of stafflines within system
  165. let y: number = 0;
  166. for (const line of musicSystem.StaffLines) {
  167. line.PositionAndShape.RelativePosition.y = y;
  168. y += 10;
  169. }
  170. // set y positions of systems using the previous system and a fixed distance.
  171. musicSystem.PositionAndShape.BorderBottom = y + 0;
  172. musicSystem.PositionAndShape.RelativePosition.x = this.rules.PageLeftMargin + this.rules.SystemLeftMargin;
  173. musicSystem.PositionAndShape.RelativePosition.y = globalY;
  174. globalY += y + 5;
  175. }
  176. }
  177. }
  178. }
  179. /**
  180. * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
  181. */
  182. protected initStaffMeasuresCreation(): void {
  183. return;
  184. }
  185. /**
  186. * add here all given articulations to the VexFlowGraphicalStaffEntry and prepare them for rendering.
  187. * @param articulations
  188. * @param voiceEntry
  189. * @param graphicalStaffEntry
  190. */
  191. protected layoutArticulationMarks(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  192. // uncomment this when implementing:
  193. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  194. return;
  195. }
  196. /**
  197. * Calculate the shape (Bezier curve) for this tie.
  198. * @param tie
  199. * @param tieIsAtSystemBreak
  200. */
  201. protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
  202. const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
  203. const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
  204. let vfStartNote: Vex.Flow.StaveNote = undefined;
  205. let startNoteIndexInTie: number = 0;
  206. if (startNote !== undefined) {
  207. vfStartNote = startNote.vfnote[0];
  208. startNoteIndexInTie = startNote.vfnote[1];
  209. }
  210. let vfEndNote: Vex.Flow.StaveNote = undefined;
  211. let endNoteIndexInTie: number = 0;
  212. if (endNote !== undefined) {
  213. vfEndNote = endNote.vfnote[0];
  214. endNoteIndexInTie = endNote.vfnote[1];
  215. }
  216. if (tieIsAtSystemBreak) {
  217. // split tie into two ties:
  218. const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  219. first_indices: [startNoteIndexInTie],
  220. first_note: vfStartNote
  221. });
  222. const measure1: VexFlowMeasure = (startNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  223. measure1.vfTies.push(vfTie1);
  224. const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  225. last_indices: [endNoteIndexInTie],
  226. last_note: vfEndNote
  227. });
  228. const measure2: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  229. measure2.vfTies.push(vfTie2);
  230. } else {
  231. // normal case
  232. const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  233. first_indices: [startNoteIndexInTie],
  234. first_note: vfStartNote,
  235. last_indices: [endNoteIndexInTie],
  236. last_note: vfEndNote
  237. });
  238. const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  239. measure.vfTies.push(vfTie);
  240. }
  241. }
  242. /**
  243. * Calculate a single OctaveShift for a [[MultiExpression]].
  244. * @param sourceMeasure
  245. * @param multiExpression
  246. * @param measureIndex
  247. * @param staffIndex
  248. */
  249. protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  250. // calculate absolute Timestamp and startStaffLine (and EndStaffLine if needed)
  251. const octaveShift: OctaveShift = multiExpression.OctaveShiftStart;
  252. const startTimeStamp: Fraction = octaveShift.ParentStartMultiExpression.Timestamp;
  253. const endTimeStamp: Fraction = octaveShift.ParentEndMultiExpression.Timestamp;
  254. const startStaffLine: StaffLine = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex].ParentStaffLine;
  255. let endMeasure: StaffMeasure = undefined;
  256. if (octaveShift.ParentEndMultiExpression !== undefined) {
  257. endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentEndMultiExpression.SourceMeasureParent,
  258. staffIndex);
  259. }
  260. let startMeasure: StaffMeasure = undefined;
  261. if (octaveShift.ParentEndMultiExpression !== undefined) {
  262. startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentStartMultiExpression.SourceMeasureParent,
  263. staffIndex);
  264. }
  265. if (endMeasure !== undefined) {
  266. // calculate GraphicalOctaveShift and RelativePositions
  267. const graphicalOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, startStaffLine.PositionAndShape);
  268. startStaffLine.OctaveShifts.push(graphicalOctaveShift);
  269. // calculate RelativePosition and Dashes
  270. const startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
  271. const endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
  272. graphicalOctaveShift.setStartNote(startStaffEntry);
  273. graphicalOctaveShift.setEndNote(endStaffEntry);
  274. }
  275. }
  276. /**
  277. * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
  278. * @param repetitionInstruction
  279. * @param measureIndex
  280. */
  281. protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
  282. // find first visible StaffLine
  283. let uppermostMeasure: VexFlowMeasure = undefined;
  284. const measures: VexFlowMeasure[] = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
  285. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  286. const graphicalMeasure: VexFlowMeasure = measures[idx];
  287. if (graphicalMeasure.ParentStaffLine !== undefined && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
  288. uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
  289. break;
  290. }
  291. }
  292. // ToDo: feature/Repetitions
  293. // now create corresponding graphical symbol or Text in VexFlow:
  294. // use top measure and staffline for positioning.
  295. if (uppermostMeasure !== undefined) {
  296. uppermostMeasure.addWordRepetition(repetitionInstruction.type);
  297. }
  298. }
  299. protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  300. return;
  301. }
  302. /**
  303. * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
  304. * @param tiedGraphicalNote
  305. * @param beams
  306. * @param activeClef
  307. * @param octaveShiftValue
  308. * @param graphicalStaffEntry
  309. * @param duration
  310. * @param openTie
  311. * @param isLastTieNote
  312. */
  313. protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
  314. octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
  315. openTie: Tie, isLastTieNote: boolean): void {
  316. return;
  317. }
  318. /**
  319. * Is called if a note is part of a beam.
  320. * @param graphicalNote
  321. * @param beam
  322. * @param openBeams a list of all currently open beams
  323. */
  324. protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
  325. (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
  326. }
  327. protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
  328. voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
  329. const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
  330. graphicalStaffEntry,
  331. this.rules.LyricsHeight,
  332. this.rules.StaffHeight);
  333. graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
  334. // create corresponding GraphicalLabel
  335. const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
  336. graphicalLabel.setLabelPositionAndShapeBorders();
  337. if (lyricsEntry.Word !== undefined) {
  338. const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
  339. let index: number = lyricWords.indexOf(lyricsEntry.Word);
  340. if (index === -1) {
  341. lyricWords.push(lyricsEntry.Word);
  342. index = lyricWords.indexOf(lyricsEntry.Word);
  343. }
  344. if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
  345. const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
  346. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  347. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  348. this.graphicalLyricWords.push(graphicalLyricWord);
  349. } else {
  350. const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
  351. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  352. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  353. if (graphicalLyricWord.isFilled()) {
  354. lyricWords.splice(index, 1);
  355. this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
  356. }
  357. }
  358. }
  359. });
  360. }
  361. protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  362. return;
  363. }
  364. /**
  365. * Add articulations to the given vexflow staff entry.
  366. * @param articulations
  367. * @param voiceEntry
  368. * @param graphicalStaffEntry
  369. */
  370. protected handleVoiceEntryArticulations(articulations: ArticulationEnum[],
  371. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  372. // uncomment this when implementing:
  373. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  374. return;
  375. }
  376. /**
  377. * Add technical instructions to the given vexflow staff entry.
  378. * @param technicalInstructions
  379. * @param voiceEntry
  380. * @param staffEntry
  381. */
  382. protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
  383. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  384. // uncomment this when implementing:
  385. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  386. return;
  387. }
  388. /**
  389. * Is called if a note is part of a tuplet.
  390. * @param graphicalNote
  391. * @param tuplet
  392. * @param openTuplets a list of all currently open tuplets
  393. */
  394. protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
  395. (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
  396. }
  397. }