VexFlowMusicSheetCalculator.ts 19 KB

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