OSMD.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import {IXmlElement} from "./../Common/FileIO/Xml";
  2. import {VexFlowMusicSheetCalculator} from "./../MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator";
  3. import {MusicSheetReader} from "./../MusicalScore/ScoreIO/MusicSheetReader";
  4. import {GraphicalMusicSheet} from "./../MusicalScore/Graphical/GraphicalMusicSheet";
  5. import {MusicSheetCalculator} from "./../MusicalScore/Graphical/MusicSheetCalculator";
  6. import {VexFlowMusicSheetDrawer} from "./../MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer";
  7. import {MusicSheet} from "./../MusicalScore/MusicSheet";
  8. import {Cursor} from "./Cursor";
  9. import {MXLtoXMLstring} from "../Common/FileIO/Mxl";
  10. import {Promise} from "es6-promise";
  11. import {handleResize} from "./ResizeHandler";
  12. import {ajax} from "./AJAX";
  13. import {Logging} from "../Common/Logging";
  14. import {Fraction} from "../Common/DataObjects/Fraction";
  15. import {OutlineAndFillStyleEnum} from "../MusicalScore/Graphical/DrawingEnums";
  16. export class OSMD {
  17. /**
  18. * The easy way of displaying a MusicXML sheet music file
  19. * @param container is either the ID, or the actual "div" element which will host the music sheet
  20. * @autoResize automatically resize the sheet to full page width on window resize
  21. */
  22. constructor(container: string|HTMLElement, autoResize: boolean = false) {
  23. // Store container element
  24. if (typeof container === "string") {
  25. // ID passed
  26. this.container = document.getElementById(<string>container);
  27. } else if (container && "appendChild" in <any>container) {
  28. // Element passed
  29. this.container = <HTMLElement>container;
  30. }
  31. if (!this.container) {
  32. throw new Error("Please pass a valid div container to OSMD");
  33. }
  34. // Create the elements inside the container
  35. this.canvas = document.createElement("canvas");
  36. this.canvas.style.zIndex = "0";
  37. let inner: HTMLElement = document.createElement("div");
  38. inner.style.position = "relative";
  39. inner.appendChild(this.canvas);
  40. this.container.appendChild(inner);
  41. // Create the drawer
  42. this.drawer = new VexFlowMusicSheetDrawer(this.canvas);
  43. // Create the cursor
  44. this.cursor = new Cursor(inner, this);
  45. if (autoResize) {
  46. this.autoResize();
  47. }
  48. }
  49. public cursor: Cursor;
  50. public zoom: number = 1.0;
  51. private container: HTMLElement;
  52. private canvas: HTMLCanvasElement;
  53. private sheet: MusicSheet;
  54. private drawer: VexFlowMusicSheetDrawer;
  55. private graphic: GraphicalMusicSheet;
  56. /**
  57. * Load a MusicXML file
  58. * @param content is either the url of a file, or the root node of a MusicXML document, or the string content of a .xml/.mxl file
  59. */
  60. public load(content: string|Document): Promise<{}> {
  61. // Warning! This function is asynchronous! No error handling is done here.
  62. this.reset();
  63. if (typeof content === "string") {
  64. let str: string = <string>content;
  65. let self: OSMD = this;
  66. if (str.substr(0, 4) === "\x50\x4b\x03\x04") {
  67. // This is a zip file, unpack it first
  68. return MXLtoXMLstring(str).then(
  69. (str: string) => {
  70. return self.load(str);
  71. },
  72. (err: any) => {
  73. Logging.debug(err);
  74. throw new Error("OSMD: Invalid MXL file");
  75. }
  76. );
  77. }
  78. if (str.substr(0, 5) === "<?xml") {
  79. // Parse the string representing an xml file
  80. let parser: DOMParser = new DOMParser();
  81. content = parser.parseFromString(str, "text/xml");
  82. } else if (str.length < 2083) {
  83. // Assume now 'str' is a URL
  84. // Retrieve the file at the given URL
  85. return ajax(str).then(
  86. (s: string) => { return self.load(s); },
  87. (exc: Error) => { throw exc; }
  88. );
  89. }
  90. }
  91. if (!content || !(<any>content).nodeName) {
  92. return Promise.reject(new Error("OSMD: The document which was provided is invalid"));
  93. }
  94. let children: NodeList = (<Document>content).childNodes;
  95. let elem: Element;
  96. for (let i: number = 0, length: number = children.length; i < length; i += 1) {
  97. let node: Node = children[i];
  98. if (node.nodeType === Node.ELEMENT_NODE && node.nodeName.toLowerCase() === "score-partwise") {
  99. elem = <Element>node;
  100. break;
  101. }
  102. }
  103. if (!elem) {
  104. return Promise.reject(new Error("OSMD: Document is not a valid 'partwise' MusicXML"));
  105. }
  106. let score: IXmlElement = new IXmlElement(elem);
  107. let calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
  108. let reader: MusicSheetReader = new MusicSheetReader();
  109. this.sheet = reader.createMusicSheet(score, "Unknown path");
  110. this.graphic = new GraphicalMusicSheet(this.sheet, calc);
  111. this.cursor.init(this.sheet.MusicPartManager, this.graphic);
  112. return Promise.resolve({});
  113. }
  114. /**
  115. * Render the music sheet in the container
  116. */
  117. public render(): void {
  118. if (!this.graphic) {
  119. throw new Error("OSMD: Before rendering a music sheet, please load a MusicXML file");
  120. }
  121. let width: number = this.container.offsetWidth;
  122. // Before introducing the following optimization (maybe irrelevant), tests
  123. // have to be modified to ensure that width is > 0 when executed
  124. //if (isNaN(width) || width === 0) {
  125. // return;
  126. //}
  127. // Set page width
  128. this.sheet.pageWidth = width / this.zoom / 10.0;
  129. // Calculate again
  130. this.graphic.reCalculate();
  131. this.graphic.Cursors.length = 0;
  132. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(0, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  133. /*this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(1, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  134. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(2, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  135. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(3, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  136. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(4, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  137. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(5, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  138. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(6, 4), OutlineAndFillStyleEnum.PlaybackCursor));
  139. this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(7, 4), OutlineAndFillStyleEnum.PlaybackCursor));*/
  140. // Update Sheet Page
  141. let height: number = this.graphic.MusicPages[0].PositionAndShape.BorderBottom * 10.0 * this.zoom;
  142. this.drawer.resize(width, height);
  143. this.drawer.scale(this.zoom);
  144. // Finally, draw
  145. this.drawer.drawSheet(this.graphic);
  146. // Update the cursor position
  147. this.cursor.update();
  148. }
  149. /**
  150. * Initialize this object to default values
  151. * FIXME: Probably unnecessary
  152. */
  153. private reset(): void {
  154. this.cursor.hide();
  155. this.sheet = undefined;
  156. this.graphic = undefined;
  157. this.zoom = 1.0;
  158. this.canvas.width = 0;
  159. this.canvas.height = 0;
  160. }
  161. /**
  162. * Attach the appropriate handler to the window.onResize event
  163. */
  164. private autoResize(): void {
  165. let self: OSMD = this;
  166. handleResize(
  167. () => {
  168. // empty
  169. },
  170. () => {
  171. //let width: number = Math.max(
  172. // document.documentElement.clientWidth,
  173. // document.body.scrollWidth,
  174. // document.documentElement.scrollWidth,
  175. // document.body.offsetWidth,
  176. // document.documentElement.offsetWidth
  177. //);
  178. //self.container.style.width = width + "px";
  179. self.render();
  180. }
  181. );
  182. }
  183. }