UNPKG

opensheetmusicdisplay

Version:

An open source JavaScript engine for displaying MusicXML based on VexFlow.

321 lines (320 loc) 20.9 kB
import { VexFlowBackend } from "./../MusicalScore/Graphical/VexFlow/VexFlowBackend"; import { GraphicalMusicSheet } from "./../MusicalScore/Graphical/GraphicalMusicSheet"; import { VexFlowMusicSheetDrawer } from "./../MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer"; import { MusicSheet } from "./../MusicalScore/MusicSheet"; import { Cursor } from "./Cursor"; import { DrawingParameters } from "../MusicalScore/Graphical/DrawingParameters"; import { IOSMDOptions, BackendType, CursorOptions } from "./OSMDOptions"; import { EngravingRules, PageFormat } from "../MusicalScore/Graphical/EngravingRules"; import { GraphicalMusicPage } from "../MusicalScore/Graphical/GraphicalMusicPage"; import { GraphicalMeasure } from "../MusicalScore/Graphical/GraphicalMeasure"; import { ITransposeCalculator } from "../MusicalScore/Interfaces/ITransposeCalculator"; /** * The main class and control point of OpenSheetMusicDisplay.<br> * It can display MusicXML sheet music files in an HTML element container.<br> * After the constructor, use load() and render() to load and render a MusicXML file. */ export declare class OpenSheetMusicDisplay { protected version: string; /** * Creates and attaches an OpenSheetMusicDisplay object to an HTML element container.<br> * After the constructor, use load() and render() to load and render a MusicXML file. * @param container The container element OSMD will be rendered into.<br> * Either a string specifying the ID of an HTML container element,<br> * or a reference to the HTML element itself (e.g. div) * @param options An object for rendering options like the backend (svg/canvas) or autoResize.<br> * For defaults see the OSMDOptionsStandard method in the [[OSMDOptions]] class. */ constructor(container: string | HTMLElement, options?: IOSMDOptions); /** Options from which OSMD creates cursors in enableOrDisableCursors(). */ cursorsOptions: CursorOptions[]; cursors: Cursor[]; get cursor(): Cursor; get Cursor(): Cursor; zoom: number; protected zoomUpdated: boolean; /** Timeout in milliseconds used in osmd.load(string) when string is a URL. */ loadUrlTimeout: number; protected container: HTMLElement; protected backendType: BackendType; protected needBackendUpdate: boolean; protected sheet: MusicSheet; protected drawer: VexFlowMusicSheetDrawer; protected drawBoundingBox: string; protected drawSkyLine: boolean; protected drawBottomLine: boolean; protected graphic: GraphicalMusicSheet; protected drawingParameters: DrawingParameters; protected rules: EngravingRules; protected autoResizeEnabled: boolean; protected resizeHandlerAttached: boolean; protected followCursor: boolean; /** A function that is executed when the XML has been read. * The return value will be used as the actual XML OSMD parses, * so you can make modifications to the xml that OSMD will use. * Note that this is (re-)set on osmd.setOptions as `{return xml}`, unless you specify the function in the options. */ OnXMLRead: (xml: string) => string; /** * Load a MusicXML file * @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, or a file blob. * @param tempTitle is used as the title for the piece if there is no title in the XML. */ load(content: string | Document | Blob, tempTitle?: string): Promise<{}>; /** * (Re-)creates the graphic sheet from the music sheet */ updateGraphic(): void; /** Lazy rendering (LazyConsistentGraphic): number of systems already drawn into the shared * backend across prior batches. Greedy layout is *usually* forward-stable, so the next batch skips * redrawing these and draws from this index -- but some scores re-position earlier systems as the * prefix grows, so each batch verifies the drawn systems against lazyDrawnSystemY and redraws from * the topmost one that moved (reconciliation). */ private lazyDrawnSystemCount; /** Lazy rendering: the absolute Y (in units) each already-drawn system [index] was drawn at, * used to detect when a later batch's full-prefix layout moves an earlier system (forward-stability * is not universal) so it can be redrawn at its corrected position. */ private lazyDrawnSystemY; /** Lazy HORIZONTAL rendering (RenderSingleHorizontalStaffline): number of graphical measures already * drawn into the shared SVG (left-to-right). The next batch draws from here, deferring the prefix's * last measure (it carries an end-barline until it becomes interior), like the vertical path defers * its last system. */ private lazyDrawnHMeasureCount; /** Lazy HORIZONTAL: for a multi-staff score, lay the whole score out ONCE on the first batch and reuse that * final layout for every batch (only the drawn x-window grows). A growing partial re-layout would route * slurs/ties along each batch's boundary skyline and size the inter-staff gap to the prefix's max * clearance -- so lower stafflines would drift down batch to batch. A single horizontal staffline always * fits one SVG, so the full layout is safe; only the (expensive) DRAW stays lazy. Single-staff scores keep * the growing layout (lazy layout + draw; nothing below the top line to drift). */ private lazyHReuseLayout; /** Incremental rendering ({@link renderNext}): whether a session is in progress (started, not yet reset). */ private lazyIncrementalActive; /** Incremental rendering: source-measure index where the next batch continues (the drawn frontier). */ private lazyNextSourceIndex; /** Incremental rendering: the draw-measure range the lazy layout mutates, saved on begin and restored on * reset, so a later normal render() isn't left limited to the last batch's draw range. */ private lazySavedMinMeasureToDrawIndex; private lazySavedMaxMeasureToDrawIndex; /** Incremental rendering: the scroll listener + its target, while enableIncrementalRenderingOnScroll() is on. */ private lazyScrollHandler; private lazyScrollTarget; /** Render the loaded music sheet to the container. */ render(): void; /** Internal range-based engine behind {@link renderNext} (the public incremental API). Lays out the * whole prefix [0..toMeasureIndex] and APPENDS the newly-stable source measures below previously * rendered batches, without clearing the container, so a large score renders "system by system". * clearFirst=true starts a fresh session (clears prior content, resets the counters). Returns the * source-measure index at which the next batch should continue. fromMeasureIndex is informational -- * the drawn frontier is tracked internally. Targets the endless vertical-scroll format and * RenderSingleHorizontalStaffline. `targetNewSystems`, if set, draws that many whole systems this batch * (vertical path only; ignored for the single horizontal staffline, which is one system). */ private renderAppend; /** * Incrementally render the loaded sheet one batch at a time, appending each batch so a large score * paints progressively ("system by system") instead of blocking on a full render(). The first call -- * or the first after load(), render() or resetIncrementalRendering() -- starts a fresh session: it * clears the container and lays the score out from the first measure. Each later call appends the next * batch. Returns progress; once `done` is true the whole sheet is rendered and further calls are no-ops. * * Pair with {@link enableIncrementalRenderingOnScroll} for scroll-to-load, or {@link renderRemaining} * to finish synchronously (e.g. before PDF/image export). Works for the endless vertical-scroll page * format and for RenderSingleHorizontalStaffline (one staffline scrolling right). * * @param options batch options; defaults to 8 visual measures (a multi-rest counts as one). Pass * `systems` instead to advance by whole music systems (vertical only); see {@link IRenderNextOptions}. */ renderNext(options?: IRenderNextOptions): IRenderNextResult; /** * Finish an in-progress incremental render synchronously: render all remaining measures at once. Useful * before PDF/image export or printing, which need every system, not just the ones scrolled into view. * No-op if no incremental render is in progress, or it is already complete. */ renderRemaining(): void; /** Whether an incremental render ({@link renderNext}) is in progress (started and not yet reset). */ get IncrementalRenderingActive(): boolean; /** Whether the in-progress incremental render has rendered the whole sheet (its last measure). */ get IncrementalRenderingComplete(): boolean; /** Current incremental-render progress as a snapshot (same shape {@link renderNext} returns), queryable * at any time -- e.g. for a progress bar, or to re-render the same extent after a resize. Reports zero * rendered measures when no session is active. */ get IncrementalRenderProgress(): IRenderNextResult; /** All GraphicalMeasures (one per staff/instrument) at the measure position for source-measure index * `sourceIndex`, walking BACK over collapsed multi-rest members (which have no graphical measure of their * own) to the nearest real measure. Empty if there is none at/before it. Used to resolve the last- / * next-measure handles for renderNext(). */ private graphicalMeasuresAtOrBefore; /** * Abandon any in-progress incremental render and restore the draw-measure range it mutated, so a * following normal render() draws the whole sheet again. Called * automatically by render(), load() and clear(); call it directly only to cancel a session yourself. */ resetIncrementalRendering(): void; /** * Drive {@link renderNext} automatically from scrolling: render the next batch whenever the user scrolls * within ~1.5 viewports of the not-yet-rendered edge (the page bottom for the endless vertical format, * the right edge for RenderSingleHorizontalStaffline). Renders the first batch immediately if none has * been rendered yet, then keeps appending as the user scrolls, and detaches itself once the whole sheet * is rendered. Re-enabling replaces any previous listener; reset/render/load also detach it. * * @param options batch size (as {@link renderNext}) plus an optional `scrollElement` -- the element whose * scrolling drives loading. Defaults to the OSMD container for a single horizontal staffline (it scrolls * horizontally) and to `window` otherwise (the page scrolls vertically). */ enableIncrementalRenderingOnScroll(options?: IRenderNextOptions & { scrollElement?: HTMLElement | Window; }): void; /** Stop driving {@link renderNext} from scrolling (see {@link enableIncrementalRenderingOnScroll}). */ disableIncrementalRenderingOnScroll(): void; /** Start a fresh incremental session: save the draw-measure range the lazy layout mutates (restored on * reset). Lazy needs the greedy, forward-stable builder so earlier systems keep their position as the * prefix grows -- public OSMD is greedy-only, so that is already in effect (nothing to force). */ private beginIncrementalRendering; /** Number of VISUAL measures in the source-measure range [from, toExcl): a multi-rest renders as one * GraphicalMeasure when RenderMultipleRestMeasures collapses it, so it counts as one. */ private visualMeasureCount; /** Source-measure index (inclusive) at which a batch of `visualCount` visual measures starting at source * index `from` ends -- i.e. the toMeasureIndex to render this batch (multi-rests collapsed; see above). */ private visualBatchEndIndex; /** * Lazy rendering (EngravingRules.LazyConsistentGraphic). Lays out the WHOLE prefix * [0..toMeasureIndex] into ONE globally-consistent graphic and draws only the systems that became * stable since the previous batch, appending them into the shared backend below what's already drawn. * * There is no per-batch vertical offset or seam-distance * computation: the real Y-layout already stacks and crisp-snaps every system at its final absolute * position, and the title space / first-system instrument names fall out of laying the prefix from * measure 0 each time. The greedy builder is forward-stable -- every system except the LAST of a * prefix is *usually* forward-stable (an interior system keeps its position as the prefix grows), so * we skip drawing the already-drawn systems above and DEFER the last system of a non-final batch (it * is unstretched and shifts once it becomes an interior, stretched system next batch). But this is NOT * universal -- some scores re-position earlier systems by several px as later systems are added -- so * each batch first VERIFIES the drawn systems against their drawn Y (lazyDrawnSystemY) and, if any * moved, redraws the whole drawn range at the corrected positions (reconciliation). In the common * (stable) case nothing is redrawn. See export/inspect_prefix_stability.mjs / inspect_optionb_drawpos.mjs. * * @returns the source-measure index at which the next batch should continue (past the last drawn system). */ private renderAppendGrowing; /** * Lazy rendering for RenderSingleHorizontalStaffline (one continuous staffline, horizontal scroll). * Lays out the whole prefix [0..toMeasureIndex] as ONE system (greedy builder at SheetMaximumWidth so it * never breaks) and draws only the measures (and spanning elements) whose right edge first entered the * drawn frontier this batch -- the single SVG grows to the RIGHT (and taller if later measures are tall). * Measure X and Y are forward-stable here, so unlike the vertical path there is no reconciliation and no * deferred last unit: every batch simply appends. SVG backend only (Canvas keeps its existing width cap). * @returns the source-measure index at which the next batch should continue. */ private renderAppendGrowingHorizontal; protected createOrRefreshRenderBackend(): void; exportSVG(): void; /** States whether the render() function can be safely called. */ IsReadyToRender(): boolean; /** Clears what OSMD has drawn on its canvas. */ clear(): void; /** Set OSMD rendering options using an IOSMDOptions object. * Can be called during runtime. Also called by constructor. * For example, setOptions({autoResize: false}) will disable autoResize even during runtime. */ setOptions(options: IOSMDOptions): void; setColoringMode(options: IOSMDOptions): void; /** * Sets the logging level for this OSMD instance. By default, this is set to `warn`. * * @param: content can be `trace`, `debug`, `info`, `warn` or `error`. */ setLogLevel(level: string): void; getLogLevel(): number; /** * Initialize this object to default values * FIXME: Probably unnecessary */ protected reset(): void; /** * Attach the appropriate handler to the window.onResize event */ protected autoResize(): void; /** Re-render and scroll back to previous scroll bar y position in percent. * If the document keeps the same height/length, the scroll bar position will basically be unchanged. * For example, if you scroll to the bottom of the page, resize by one pixel (or enable dark mode) and call this, * for the human eye there will be no detectable scrolling or change in the scroll position at all. * If you just call render() instead of renderAndScrollBack(), * it will scroll you back to the top of the page, even if you were scrolled to the bottom before. */ renderAndScrollBack(): void; /** * Helper function for managing window's onResize events * @param startCallback is the function called when resizing starts * @param endCallback is the function called when resizing (kind-of) ends */ protected handleResize(startCallback: () => void, endCallback: () => void): void; /** Enable or disable (hide) the cursor. * @param enable whether to enable (true) or disable (false) the cursor */ enableOrDisableCursors(enable: boolean): void; createBackend(type: BackendType, page: GraphicalMusicPage, idOverride?: string): VexFlowBackend; /** Standard page format options like A4 or Letter, in portrait and landscape. E.g. PageFormatStandards["A4_P"] or PageFormatStandards["Letter_L"]. */ static PageFormatStandards: { [type: string]: PageFormat; }; static StringToPageFormat(pageFormatString: string): PageFormat; /** Sets page format by string. Used by setOptions({pageFormat: "A4_P"}) for example. */ setPageFormat(formatId: string): void; setCustomPageFormat(width: number, height: number): void; set DrawSkyLine(value: boolean); get DrawSkyLine(): boolean; set DrawBottomLine(value: boolean); get DrawBottomLine(): boolean; set DrawBoundingBox(value: string); get DrawBoundingBox(): string; setDrawBoundingBox(value: string, render?: boolean): void; get AutoResizeEnabled(): boolean; set AutoResizeEnabled(value: boolean); get Zoom(): number; set Zoom(value: number); set FollowCursor(value: boolean); get FollowCursor(): boolean; set TransposeCalculator(calculator: ITransposeCalculator); get TransposeCalculator(): ITransposeCalculator; get Sheet(): MusicSheet; get Drawer(): VexFlowMusicSheetDrawer; get GraphicSheet(): GraphicalMusicSheet; get DrawingParameters(): DrawingParameters; get EngravingRules(): EngravingRules; /** Returns the version of OSMD this object is built from (the version you are using). */ get Version(): string; } /** Options for {@link OpenSheetMusicDisplay.renderNext} (incremental, "system by system" rendering). */ export interface IRenderNextOptions { /** Target number of visual measures to advance per batch (a multi-rest counts as ONE -- it renders as a * single GraphicalMeasure). Defaults to 8. This is a TARGET, not an exact count: a batch always ends on a * whole music-system (line) boundary, so the system the measure count lands inside is deferred to the next * batch (and a batch always renders at least one whole system). The measures actually drawn may therefore * be somewhat fewer or more than this -- e.g. 8 lands part-way into a system, so only the complete systems * before it are drawn; or if 8 doesn't fill one system, the layout extends until one whole system is ready. * To see what was actually rendered, read the returned {@link IRenderNextResult}: `renderedMeasures` (total * visual measures drawn so far, cumulative) and `lastRenderedMeasure` (the GraphicalMeasures at the last * drawn measure position). Ignored when `systems` is set (and applicable). */ measures?: number; /** How many whole music systems (lines) to render in this batch, instead of advancing by `measures`. * Each batch then ends exactly on a system boundary. Takes precedence over `measures` when > 0. * VERTICAL ONLY: a single horizontal staffline (RenderSingleHorizontalStaffline) is one system, so * `systems` is ignored there and rendering falls back to `measures`. */ systems?: number; } /** Progress returned by {@link OpenSheetMusicDisplay.renderNext}. Measure counts are visual (a multi-rest * counts as one). */ export interface IRenderNextResult { /** True once the last measure of the sheet has been rendered -- no more batches remain. */ done: boolean; /** Visual measures rendered so far, cumulative across batches. */ renderedMeasures: number; /** Total visual measures in the sheet. */ totalMeasures: number; /** The last measure position rendered so far (highest measure index): all its GraphicalMeasures, one per * staff/instrument (e.g. 3 for a voice + piano score). Empty if nothing has been rendered yet. They * share one source measure -- reach it and its number/index via any element's `.parentSourceMeasure` and * `.parentSourceMeasure.measureListIndex` (0-based). */ lastRenderedMeasure: GraphicalMeasure[]; /** The next measure position not yet rendered (the frontier): all its GraphicalMeasures (one per staff), * or empty once `done`. Same `.parentSourceMeasure` / `.parentSourceMeasure.measureListIndex` accessors. */ nextUnrenderedMeasure: GraphicalMeasure[]; }