OpenSheetMusicDisplay.ts 23 KB

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