OpenSheetMusicDisplay.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. import { IXmlElement } from "./../Common/FileIO/Xml";
  2. import { VexFlowMusicSheetCalculator } from "./../MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator";
  3. import { VexFlowBackend } from "./../MusicalScore/Graphical/VexFlow/VexFlowBackend";
  4. import { MusicSheetReader } from "./../MusicalScore/ScoreIO/MusicSheetReader";
  5. import { GraphicalMusicSheet } from "./../MusicalScore/Graphical/GraphicalMusicSheet";
  6. import { MusicSheetCalculator } from "./../MusicalScore/Graphical/MusicSheetCalculator";
  7. import { VexFlowMusicSheetDrawer } from "./../MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer";
  8. import { SvgVexFlowBackend } from "./../MusicalScore/Graphical/VexFlow/SvgVexFlowBackend";
  9. import { CanvasVexFlowBackend } from "./../MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend";
  10. import { MusicSheet } from "./../MusicalScore/MusicSheet";
  11. import { Cursor } from "./Cursor";
  12. import { MXLHelper } from "../Common/FileIO/Mxl";
  13. import { Promise } from "es6-promise";
  14. import { AJAX } from "./AJAX";
  15. import * as log from "loglevel";
  16. import { DrawingParametersEnum, DrawingParameters, ColoringModes } from "../MusicalScore/Graphical/DrawingParameters";
  17. import { IOSMDOptions, OSMDOptions, AutoBeamOptions } from "./OSMDOptions";
  18. import { EngravingRules } from "../MusicalScore/Graphical/EngravingRules";
  19. import { AbstractExpression } from "../MusicalScore/VoiceData/Expressions/AbstractExpression";
  20. import { Dictionary } from "typescript-collections";
  21. import { NoteEnum } from "..";
  22. import { AutoColorSet } from "../MusicalScore";
  23. /**
  24. * The main class and control point of OpenSheetMusicDisplay.<br>
  25. * It can display MusicXML sheet music files in an HTML element container.<br>
  26. * After the constructor, use load() and render() to load and render a MusicXML file.
  27. */
  28. export class OpenSheetMusicDisplay {
  29. private version: string = "0.7.1d-dev"; // getter: this.Version
  30. // at release, bump version and change to -release, afterwards to -dev again
  31. /**
  32. * Creates and attaches an OpenSheetMusicDisplay object to an HTML element container.<br>
  33. * After the constructor, use load() and render() to load and render a MusicXML file.
  34. * @param container The container element OSMD will be rendered into.<br>
  35. * Either a string specifying the ID of an HTML container element,<br>
  36. * or a reference to the HTML element itself (e.g. div)
  37. * @param options An object for rendering options like the backend (svg/canvas) or autoResize.<br>
  38. * For defaults see the OSMDOptionsStandard method in the [[OSMDOptions]] class.
  39. */
  40. constructor(container: string | HTMLElement,
  41. options: IOSMDOptions = OSMDOptions.OSMDOptionsStandard()) {
  42. // Store container element
  43. if (typeof container === "string") {
  44. // ID passed
  45. this.container = document.getElementById(<string>container);
  46. } else if (container && "appendChild" in <any>container) {
  47. // Element passed
  48. this.container = <HTMLElement>container;
  49. }
  50. if (!this.container) {
  51. throw new Error("Please pass a valid div container to OpenSheetMusicDisplay");
  52. }
  53. if (options.autoResize === undefined) {
  54. options.autoResize = true;
  55. }
  56. this.setOptions(options);
  57. }
  58. public cursor: Cursor;
  59. public zoom: number = 1.0;
  60. private container: HTMLElement;
  61. private canvas: HTMLElement;
  62. private backend: VexFlowBackend;
  63. private innerElement: HTMLElement;
  64. private sheet: MusicSheet;
  65. private drawer: VexFlowMusicSheetDrawer;
  66. private graphic: GraphicalMusicSheet;
  67. private drawingParameters: DrawingParameters;
  68. private autoResizeEnabled: boolean;
  69. private resizeHandlerAttached: boolean;
  70. private followCursor: boolean;
  71. /**
  72. * Load a MusicXML file
  73. * @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
  74. */
  75. public load(content: string | Document): Promise<{}> {
  76. // Warning! This function is asynchronous! No error handling is done here.
  77. this.reset();
  78. if (typeof content === "string") {
  79. const str: string = <string>content;
  80. const self: OpenSheetMusicDisplay = this;
  81. if (str.substr(0, 4) === "\x50\x4b\x03\x04") {
  82. log.debug("[OSMD] This is a zip file, unpack it first: " + str);
  83. // This is a zip file, unpack it first
  84. return MXLHelper.MXLtoXMLstring(str).then(
  85. (x: string) => {
  86. return self.load(x);
  87. },
  88. (err: any) => {
  89. log.debug(err);
  90. throw new Error("OpenSheetMusicDisplay: Invalid MXL file");
  91. }
  92. );
  93. }
  94. // Javascript loads strings as utf-16, which is wonderful BS if you want to parse UTF-8 :S
  95. if (str.substr(0, 3) === "\uf7ef\uf7bb\uf7bf") {
  96. log.debug("[OSMD] UTF with BOM detected, truncate first three bytes and pass along: " + str);
  97. // UTF with BOM detected, truncate first three bytes and pass along
  98. return self.load(str.substr(3));
  99. }
  100. if (str.substr(0, 5) === "<?xml") {
  101. log.debug("[OSMD] Finally parsing XML content, length: " + str.length);
  102. // Parse the string representing an xml file
  103. const parser: DOMParser = new DOMParser();
  104. content = parser.parseFromString(str, "application/xml");
  105. } else if (str.length < 2083) {
  106. log.debug("[OSMD] Retrieve the file at the given URL: " + str);
  107. // Assume now "str" is a URL
  108. // Retrieve the file at the given URL
  109. return AJAX.ajax(str).then(
  110. (s: string) => { return self.load(s); },
  111. (exc: Error) => { throw exc; }
  112. );
  113. } else {
  114. console.error("Missing else branch?");
  115. }
  116. }
  117. if (!content || !(<any>content).nodeName) {
  118. return Promise.reject(new Error("OpenSheetMusicDisplay: The document which was provided is invalid"));
  119. }
  120. const xmlDocument: Document = (<Document>content);
  121. const xmlDocumentNodes: NodeList = xmlDocument.childNodes;
  122. log.debug("[OSMD] load(), Document url: " + xmlDocument.URL);
  123. let scorePartwiseElement: Element;
  124. for (let i: number = 0, length: number = xmlDocumentNodes.length; i < length; i += 1) {
  125. const node: Node = xmlDocumentNodes[i];
  126. if (node.nodeType === Node.ELEMENT_NODE && node.nodeName.toLowerCase() === "score-partwise") {
  127. scorePartwiseElement = <Element>node;
  128. break;
  129. }
  130. }
  131. if (!scorePartwiseElement) {
  132. console.error("Could not parse MusicXML, no valid partwise element found");
  133. return Promise.reject(new Error("OpenSheetMusicDisplay: Document is not a valid 'partwise' MusicXML"));
  134. }
  135. const score: IXmlElement = new IXmlElement(scorePartwiseElement);
  136. const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
  137. const reader: MusicSheetReader = new MusicSheetReader();
  138. this.sheet = reader.createMusicSheet(score, "Untitled Score");
  139. if (this.sheet === undefined) {
  140. // error loading sheet, probably already logged, do nothing
  141. return Promise.reject(new Error("given music sheet was incomplete or could not be loaded."));
  142. }
  143. this.graphic = new GraphicalMusicSheet(this.sheet, calc);
  144. if (this.drawingParameters.drawCursors && this.cursor) {
  145. this.cursor.init(this.sheet.MusicPartManager, this.graphic);
  146. }
  147. log.info(`[OSMD] Loaded sheet ${this.sheet.TitleString} successfully.`);
  148. return Promise.resolve({});
  149. }
  150. /**
  151. * Render the music sheet in the container
  152. */
  153. public render(): void {
  154. if (!this.graphic) {
  155. throw new Error("OpenSheetMusicDisplay: Before rendering a music sheet, please load a MusicXML file");
  156. }
  157. this.drawer.clear(); // clear canvas before setting width
  158. // Set page width
  159. const width: number = this.container.offsetWidth;
  160. this.sheet.pageWidth = width / this.zoom / 10.0;
  161. // Before introducing the following optimization (maybe irrelevant), tests
  162. // have to be modified to ensure that width is > 0 when executed
  163. //if (isNaN(width) || width === 0) {
  164. // return;
  165. //}
  166. // Calculate again
  167. this.graphic.reCalculate();
  168. const height: number = this.graphic.MusicPages[0].PositionAndShape.BorderBottom * 10.0 * this.zoom;
  169. if (this.drawingParameters.drawCursors) {
  170. this.graphic.Cursors.length = 0;
  171. }
  172. // Update Sheet Page
  173. this.drawer.resize(width, height);
  174. this.drawer.scale(this.zoom);
  175. // Finally, draw
  176. this.drawer.drawSheet(this.graphic);
  177. if (this.drawingParameters.drawCursors && this.cursor) {
  178. // Update the cursor position
  179. this.cursor.update();
  180. }
  181. }
  182. /** States whether the render() function can be safely called. */
  183. public IsReadyToRender(): boolean {
  184. return this.graphic !== undefined;
  185. }
  186. /** Clears what OSMD has drawn on its canvas. */
  187. public clear(): void {
  188. this.drawer.clear();
  189. this.reset(); // without this, resize will draw loaded sheet again
  190. }
  191. /** Set OSMD rendering options using an IOSMDOptions object.
  192. * Can be called during runtime. Also called by constructor.
  193. * For example, setOptions({autoResize: false}) will disable autoResize even during runtime.
  194. */
  195. public setOptions(options: IOSMDOptions): void {
  196. if (!this.drawingParameters) {
  197. this.drawingParameters = new DrawingParameters();
  198. }
  199. if (options === undefined || options === null) {
  200. log.warn("warning: osmd.setOptions() called without an options parameter, has no effect."
  201. + "\n" + "example usage: osmd.setOptions({drawCredits: false, drawPartNames: false})");
  202. return;
  203. }
  204. if (options.drawingParameters) {
  205. this.drawingParameters.DrawingParametersEnum =
  206. (<any>DrawingParametersEnum)[options.drawingParameters.toLowerCase()];
  207. }
  208. const updateExistingBackend: boolean = this.backend !== undefined;
  209. if (options.backend !== undefined || this.backend === undefined) {
  210. if (updateExistingBackend) {
  211. // TODO doesn't work yet, still need to create a new OSMD object
  212. this.drawer.clear();
  213. // musicSheetCalculator.clearSystemsAndMeasures() // maybe? don't have reference though
  214. // musicSheetCalculator.clearRecreatedObjects();
  215. }
  216. if (options.backend === undefined || options.backend.toLowerCase() === "svg") {
  217. this.backend = new SvgVexFlowBackend();
  218. } else {
  219. this.backend = new CanvasVexFlowBackend();
  220. }
  221. this.backend.initialize(this.container);
  222. this.canvas = this.backend.getCanvas();
  223. this.innerElement = this.backend.getInnerElement();
  224. this.enableOrDisableCursor(this.drawingParameters.drawCursors);
  225. // Create the drawer
  226. this.drawer = new VexFlowMusicSheetDrawer(this.canvas, this.backend, this.drawingParameters);
  227. }
  228. // individual drawing parameters options
  229. if (options.autoBeam !== undefined) {
  230. EngravingRules.Rules.AutoBeamNotes = options.autoBeam;
  231. }
  232. const autoBeamOptions: AutoBeamOptions = options.autoBeamOptions;
  233. if (autoBeamOptions) {
  234. if (autoBeamOptions.maintain_stem_directions === undefined) {
  235. autoBeamOptions.maintain_stem_directions = false;
  236. }
  237. EngravingRules.Rules.AutoBeamOptions = autoBeamOptions;
  238. if (autoBeamOptions.groups && autoBeamOptions.groups.length) {
  239. for (const fraction of autoBeamOptions.groups) {
  240. if (fraction.length !== 2) {
  241. throw new Error("Each fraction in autoBeamOptions.groups must be of length 2, e.g. [3,4] for beaming three fourths");
  242. }
  243. }
  244. }
  245. }
  246. if (options.alignRests !== undefined) {
  247. EngravingRules.Rules.AlignRests = options.alignRests;
  248. }
  249. if (options.coloringMode !== undefined) {
  250. this.setColoringMode(options);
  251. }
  252. if (options.coloringEnabled !== undefined) {
  253. EngravingRules.Rules.ColoringEnabled = options.coloringEnabled;
  254. }
  255. if (options.colorStemsLikeNoteheads !== undefined) {
  256. EngravingRules.Rules.ColorStemsLikeNoteheads = options.colorStemsLikeNoteheads;
  257. }
  258. if (options.disableCursor) {
  259. this.drawingParameters.drawCursors = false;
  260. this.enableOrDisableCursor(this.drawingParameters.drawCursors);
  261. }
  262. // alternative to if block: this.drawingsParameters.drawCursors = options.drawCursors !== false. No if, but always sets drawingParameters.
  263. // note that every option can be undefined, which doesn't mean the option should be set to false.
  264. if (options.drawHiddenNotes) {
  265. this.drawingParameters.drawHiddenNotes = true;
  266. }
  267. if (options.drawCredits !== undefined) {
  268. this.drawingParameters.DrawCredits = options.drawCredits; // sets DrawComposer, DrawTitle, DrawSubtitle, DrawLyricist.
  269. }
  270. if (options.drawComposer !== undefined) {
  271. this.drawingParameters.DrawComposer = options.drawComposer;
  272. }
  273. if (options.drawTitle !== undefined) {
  274. this.drawingParameters.DrawTitle = options.drawTitle;
  275. }
  276. if (options.drawSubtitle !== undefined) {
  277. this.drawingParameters.DrawSubtitle = options.drawSubtitle;
  278. }
  279. if (options.drawLyricist !== undefined) {
  280. this.drawingParameters.DrawLyricist = options.drawLyricist;
  281. }
  282. if (options.drawPartNames !== undefined) {
  283. this.drawingParameters.DrawPartNames = options.drawPartNames; // indirectly writes to EngravingRules
  284. }
  285. if (options.drawPartAbbreviations !== undefined) {
  286. EngravingRules.Rules.RenderPartAbbreviations = options.drawPartAbbreviations;
  287. }
  288. if (options.drawFingerings === false) {
  289. EngravingRules.Rules.RenderFingerings = false;
  290. }
  291. if (options.drawMeasureNumbers !== undefined) {
  292. EngravingRules.Rules.RenderMeasureNumbers = options.drawMeasureNumbers;
  293. }
  294. if (options.drawLyrics !== undefined) {
  295. EngravingRules.Rules.RenderLyrics = options.drawLyrics;
  296. }
  297. if (options.drawSlurs !== undefined) {
  298. EngravingRules.Rules.DrawSlurs = options.drawSlurs;
  299. }
  300. if (options.measureNumberInterval !== undefined) {
  301. EngravingRules.Rules.MeasureNumberLabelOffset = options.measureNumberInterval;
  302. }
  303. if (options.fingeringPosition !== undefined) {
  304. EngravingRules.Rules.FingeringPosition = AbstractExpression.PlacementEnumFromString(options.fingeringPosition);
  305. }
  306. if (options.fingeringInsideStafflines !== undefined) {
  307. EngravingRules.Rules.FingeringInsideStafflines = options.fingeringInsideStafflines;
  308. }
  309. if (options.followCursor !== undefined) {
  310. this.FollowCursor = options.followCursor;
  311. }
  312. if (options.setWantedStemDirectionByXml !== undefined) {
  313. EngravingRules.Rules.SetWantedStemDirectionByXml = options.setWantedStemDirectionByXml;
  314. }
  315. if (options.defaultColorNotehead) {
  316. EngravingRules.Rules.DefaultColorNotehead = options.defaultColorNotehead;
  317. }
  318. if (options.defaultColorRest) {
  319. EngravingRules.Rules.DefaultColorRest = options.defaultColorRest;
  320. }
  321. if (options.defaultColorStem) {
  322. EngravingRules.Rules.DefaultColorStem = options.defaultColorStem;
  323. }
  324. if (options.defaultColorLabel) {
  325. EngravingRules.Rules.DefaultColorLabel = options.defaultColorLabel;
  326. }
  327. if (options.defaultColorTitle) {
  328. EngravingRules.Rules.DefaultColorTitle = options.defaultColorTitle;
  329. }
  330. if (options.defaultFontFamily) {
  331. EngravingRules.Rules.DefaultFontFamily = options.defaultFontFamily; // default "Times New Roman", also used if font family not found
  332. }
  333. if (options.drawUpToMeasureNumber) {
  334. EngravingRules.Rules.MaxMeasureToDrawIndex = options.drawUpToMeasureNumber - 1;
  335. }
  336. if (options.drawFromMeasureNumber) {
  337. EngravingRules.Rules.MinMeasureToDrawIndex = options.drawFromMeasureNumber - 1;
  338. }
  339. if (options.tupletsRatioed) {
  340. EngravingRules.Rules.TupletsRatioed = true;
  341. }
  342. if (options.tupletsBracketed) {
  343. EngravingRules.Rules.TupletsBracketed = true;
  344. }
  345. if (options.tripletsBracketed) {
  346. EngravingRules.Rules.TripletsBracketed = true;
  347. }
  348. if (options.autoResize) {
  349. if (!this.resizeHandlerAttached) {
  350. this.autoResize();
  351. }
  352. this.autoResizeEnabled = true;
  353. } else if (options.autoResize === false) { // not undefined
  354. this.autoResizeEnabled = false;
  355. // we could remove the window EventListener here, but not necessary.
  356. }
  357. }
  358. public setColoringMode(options: IOSMDOptions): void {
  359. if (options.coloringMode === ColoringModes.XML) {
  360. EngravingRules.Rules.ColoringMode = ColoringModes.XML;
  361. return;
  362. }
  363. const noteIndices: NoteEnum[] = [NoteEnum.C, NoteEnum.D, NoteEnum.E, NoteEnum.F, NoteEnum.G, NoteEnum.A, NoteEnum.B, -1];
  364. let colorSetString: string[];
  365. if (options.coloringMode === ColoringModes.CustomColorSet) {
  366. if (!options.coloringSetCustom || options.coloringSetCustom.length !== 8) {
  367. throw new Error("Invalid amount of colors: With coloringModes.customColorSet, " +
  368. "you have to provide a coloringSetCustom parameter with 8 strings (C to B, rest note).");
  369. }
  370. // validate strings input
  371. for (const colorString of options.coloringSetCustom) {
  372. const regExp: RegExp = /^\#[0-9a-fA-F]{6}$/;
  373. if (!regExp.test(colorString)) {
  374. throw new Error(
  375. "One of the color strings in options.coloringSetCustom was not a valid HTML Hex color:\n" + colorString);
  376. }
  377. }
  378. colorSetString = options.coloringSetCustom;
  379. } else if (options.coloringMode === ColoringModes.AutoColoring) {
  380. colorSetString = [];
  381. const keys: string[] = Object.keys(AutoColorSet);
  382. for (let i: number = 0; i < keys.length; i++) {
  383. colorSetString.push(AutoColorSet[keys[i]]);
  384. }
  385. } // for both cases:
  386. const coloringSetCurrent: Dictionary<NoteEnum | number, string> = new Dictionary<NoteEnum | number, string>();
  387. for (let i: number = 0; i < noteIndices.length; i++) {
  388. coloringSetCurrent.setValue(noteIndices[i], colorSetString[i]);
  389. }
  390. coloringSetCurrent.setValue(-1, colorSetString[7]);
  391. EngravingRules.Rules.ColoringSetCurrent = coloringSetCurrent;
  392. EngravingRules.Rules.ColoringMode = options.coloringMode;
  393. }
  394. /**
  395. * Sets the logging level for this OSMD instance. By default, this is set to `warn`.
  396. *
  397. * @param: content can be `trace`, `debug`, `info`, `warn` or `error`.
  398. */
  399. public setLogLevel(level: string): void {
  400. switch (level) {
  401. case "trace":
  402. log.setLevel(log.levels.TRACE);
  403. break;
  404. case "debug":
  405. log.setLevel(log.levels.DEBUG);
  406. break;
  407. case "info":
  408. log.setLevel(log.levels.INFO);
  409. break;
  410. case "warn":
  411. log.setLevel(log.levels.WARN);
  412. break;
  413. case "error":
  414. log.setLevel(log.levels.ERROR);
  415. break;
  416. default:
  417. log.warn(`Could not set log level to ${level}. Using warn instead.`);
  418. log.setLevel(log.levels.WARN);
  419. break;
  420. }
  421. }
  422. public getLogLevel(): number {
  423. return log.getLevel();
  424. }
  425. /**
  426. * Initialize this object to default values
  427. * FIXME: Probably unnecessary
  428. */
  429. private reset(): void {
  430. if (this.drawingParameters.drawCursors && this.cursor) {
  431. this.cursor.hide();
  432. }
  433. this.sheet = undefined;
  434. this.graphic = undefined;
  435. this.zoom = 1.0;
  436. }
  437. /**
  438. * Attach the appropriate handler to the window.onResize event
  439. */
  440. private autoResize(): void {
  441. const self: OpenSheetMusicDisplay = this;
  442. this.handleResize(
  443. () => {
  444. // empty
  445. },
  446. () => {
  447. // The following code is probably not needed
  448. // (the width should adapt itself to the max allowed)
  449. //let width: number = Math.max(
  450. // document.documentElement.clientWidth,
  451. // document.body.scrollWidth,
  452. // document.documentElement.scrollWidth,
  453. // document.body.offsetWidth,
  454. // document.documentElement.offsetWidth
  455. //);
  456. //self.container.style.width = width + "px";
  457. if (self.IsReadyToRender()) {
  458. self.render();
  459. }
  460. }
  461. );
  462. }
  463. /**
  464. * Helper function for managing window's onResize events
  465. * @param startCallback is the function called when resizing starts
  466. * @param endCallback is the function called when resizing (kind-of) ends
  467. */
  468. private handleResize(startCallback: () => void, endCallback: () => void): void {
  469. let rtime: number;
  470. let timeout: number = undefined;
  471. const delta: number = 200;
  472. const self: OpenSheetMusicDisplay = this;
  473. function resizeStart(): void {
  474. if (!self.AutoResizeEnabled) {
  475. return;
  476. }
  477. rtime = (new Date()).getTime();
  478. if (!timeout) {
  479. startCallback();
  480. rtime = (new Date()).getTime();
  481. timeout = window.setTimeout(resizeEnd, delta);
  482. }
  483. }
  484. function resizeEnd(): void {
  485. timeout = undefined;
  486. window.clearTimeout(timeout);
  487. if ((new Date()).getTime() - rtime < delta) {
  488. timeout = window.setTimeout(resizeEnd, delta);
  489. } else {
  490. endCallback();
  491. }
  492. }
  493. if ((<any>window).attachEvent) {
  494. // Support IE<9
  495. (<any>window).attachEvent("onresize", resizeStart);
  496. } else {
  497. window.addEventListener("resize", resizeStart);
  498. }
  499. this.resizeHandlerAttached = true;
  500. window.setTimeout(startCallback, 0);
  501. window.setTimeout(endCallback, 1);
  502. }
  503. /** Enable or disable (hide) the cursor.
  504. * @param enable whether to enable (true) or disable (false) the cursor
  505. */
  506. public enableOrDisableCursor(enable: boolean): void {
  507. this.drawingParameters.drawCursors = enable;
  508. if (enable) {
  509. if (!this.cursor) {
  510. this.cursor = new Cursor(this.innerElement, this);
  511. if (this.sheet && this.graphic) { // else init is called in load()
  512. this.cursor.init(this.sheet.MusicPartManager, this.graphic);
  513. }
  514. }
  515. } else { // disable cursor
  516. if (!this.cursor) {
  517. return;
  518. }
  519. this.cursor.hide();
  520. // this.cursor = undefined;
  521. // TODO cursor should be disabled, not just hidden. otherwise user can just call osmd.cursor.hide().
  522. // however, this could cause null calls (cursor.next() etc), maybe that needs some solution.
  523. }
  524. }
  525. //#region GETTER / SETTER
  526. public set DrawSkyLine(value: boolean) {
  527. if (this.drawer) {
  528. this.drawer.skyLineVisible = value;
  529. this.render();
  530. }
  531. }
  532. public get DrawSkyLine(): boolean {
  533. return this.drawer.skyLineVisible;
  534. }
  535. public set DrawBottomLine(value: boolean) {
  536. if (this.drawer) {
  537. this.drawer.bottomLineVisible = value;
  538. this.render();
  539. }
  540. }
  541. public get DrawBottomLine(): boolean {
  542. return this.drawer.bottomLineVisible;
  543. }
  544. public set DrawBoundingBox(value: string) {
  545. this.drawer.drawableBoundingBoxElement = value;
  546. this.render();
  547. }
  548. public get DrawBoundingBox(): string {
  549. return this.drawer.drawableBoundingBoxElement;
  550. }
  551. public get AutoResizeEnabled(): boolean {
  552. return this.autoResizeEnabled;
  553. }
  554. public set AutoResizeEnabled(value: boolean) {
  555. this.autoResizeEnabled = value;
  556. }
  557. public set FollowCursor(value: boolean) {
  558. this.followCursor = value;
  559. }
  560. public get FollowCursor(): boolean {
  561. return this.followCursor;
  562. }
  563. public get Sheet(): MusicSheet {
  564. return this.sheet;
  565. }
  566. public get Drawer(): VexFlowMusicSheetDrawer {
  567. return this.drawer;
  568. }
  569. public get GraphicSheet(): GraphicalMusicSheet {
  570. return this.graphic;
  571. }
  572. public get DrawingParameters(): DrawingParameters {
  573. return this.drawingParameters;
  574. }
  575. public get EngravingRules(): EngravingRules { // custom getter, useful for engraving parameter setting in Demo
  576. return EngravingRules.Rules;
  577. }
  578. /** Returns the version of OSMD this object is built from (the version you are using). */
  579. public get Version(): string {
  580. return this.version;
  581. }
  582. //#endregion
  583. }