flat-embed
Version:
Interact and get events from Flat's Sheet Music Embed
1,481 lines (1,480 loc) • 56.4 kB
JavaScript
var d = Object.defineProperty;
var f = (r, e, t) => e in r ? d(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
var n = (r, e, t) => f(r, typeof e != "symbol" ? e + "" : e, t);
if (typeof window.postMessage == "undefined")
throw new Error("The Flat Embed JS API is not supported in this browser");
class b {
constructor(e) {
n(this, "embed");
n(this, "promises");
n(this, "eventCallbacks");
this.embed = e, this.promises = {}, this.eventCallbacks = {};
}
pushCall(e, t, s) {
this.promises[e] = this.promises[e] || [], this.promises[e].push({ resolve: t, reject: s });
}
/**
* Register a callback for a specified event
*
* @param event The name of the event.
* @param callback The function to call when receiving an event
* @return `true` if it is the first subscriber, `false otherwise`
*/
subscribeEvent(e, t) {
return this.eventCallbacks[e] = this.eventCallbacks[e] || [], this.eventCallbacks[e].push(t), this.eventCallbacks[e].length === 1;
}
/**
* Unregister a callback for a specified event
*
* @param event The name of the event.
* @param callback The function to call when receiving an event
* @return `true` if it is the last subscriber, `false otherwise`
*/
unsubscribeEvent(e, t) {
if (!this.eventCallbacks[e])
return !1;
if (t) {
const s = this.eventCallbacks[e].indexOf(t);
s >= 0 && this.eventCallbacks[e].splice(s, 1);
} else
this.eventCallbacks[e] = [];
return !t || this.eventCallbacks[e].length === 0;
}
/**
* Process a message received from postMessage
*
* @param {object} data The data received from postMessage
*/
process(e) {
"method" in e && e.method ? this.processMethodResponse(e) : "event" in e && e.event && this.processEvent(e);
}
/**
* Process a method response
*
* @param {object} data The data received from postMessage
*/
processMethodResponse(e) {
if (!this.promises[e.method])
return;
const t = this.promises[e.method].shift();
t && (e.error ? t.reject(e.error) : t.resolve(e.response));
}
/**
* Process a receieved event
*
* @param {object} data The data received from postMessage
*/
processEvent(e) {
!this.eventCallbacks[e.event] || this.eventCallbacks[e.event].length === 0 || this.eventCallbacks[e.event].forEach((t) => {
t.call(this.embed, e.parameters);
});
}
}
function u(r, e, t) {
if (!r.element.contentWindow || !r.element.contentWindow.postMessage)
throw new Error("No `contentWindow` or `contentWindow.postMessage` available on the element");
const s = {
method: e,
parameters: t
};
r.element.contentWindow.postMessage(s, r.origin);
}
function p(r) {
return typeof r == "string" && (r = JSON.parse(r)), r;
}
function M(r) {
if (typeof r == "string") {
const e = document.getElementById(r);
if (!e)
throw new TypeError(`The DOM element with the identifier "${r}" was not found.`);
r = e;
}
if (!(r instanceof window.HTMLElement))
throw new TypeError("The first parameter must be an existing DOM element or an identifier.");
if (r.nodeName !== "IFRAME") {
const e = r.querySelector("iframe");
e && (r = e);
}
return r;
}
function P(r) {
let e = r.baseUrl || "https://flat-embed.com";
r.isCustomUrl || (e += `/${r.score || "blank"}`);
const t = Object.assign(
{
jsapi: !0
},
r.embedParams
), s = Object.keys(t).map((i) => `${encodeURIComponent(i)}=${encodeURIComponent(t[i])}`).join("&");
return `${e}?${s}`;
}
function w(r, e) {
const t = P(e), s = document.createElement("iframe");
return s.setAttribute("src", t), s.setAttribute("width", e.width || "100%"), s.setAttribute("height", e.height || "100%"), s.setAttribute("allowfullscreen", "true"), s.setAttribute("allow", "autoplay; midi"), s.setAttribute("frameborder", "0"), e.lazy && s.setAttribute("loading", "lazy"), r.appendChild(s), s;
}
const a = /* @__PURE__ */ new WeakMap(), c = /* @__PURE__ */ new WeakMap();
class C {
/**
* Create a new Flat Embed
*
* @param element A reference to a Flat Embed iframe or a container for the new iframe
* @param parameters Parameters for the new iframe
*/
constructor(e, t = {}) {
n(this, "origin", "*");
n(this, "element");
n(this, "embedCallback");
const s = M(e);
if (s instanceof HTMLIFrameElement && a.has(s))
return a.get(s);
let i;
s.nodeName !== "IFRAME" ? i = w(s, t) : i = s, this.element = i, this.embedCallback = new b(this);
const h = new Promise((g) => {
const m = (l) => {
if (this.element.contentWindow !== l.source)
return;
this.origin === "*" && (this.origin = l.origin);
const o = p(l.data);
if (o.event === "ready" || o.method === "ping") {
g();
return;
}
this.embedCallback.process(o);
};
window.addEventListener("message", m, !1), u(this, "ping");
});
return a.set(this.element, this), c.set(this.element, h), this;
}
/**
* Wait for the embed to be ready
*
* Returns a promise that resolves when the embed iframe has fully loaded and
* communication with the Flat embed has been established. This method is automatically
* called by all other embed methods, so you typically don't need to call it directly.
* However, it can be useful when you want to know exactly when the embed is ready
* without performing any other action.
*
* @returns A promise that resolves when the embed is ready
*
* @example
* // Explicitly wait for embed to be ready
* const embed = new Embed('container', {
* score: '56ae21579a127715a02901a6'
* });
* await embed.ready();
* console.log('Embed is now ready!');
*
* @example
* // Note: Other methods automatically wait for ready state
* const embed = new Embed('container');
* // No need to call ready() - play() will wait automatically
* await embed.play();
*
* @note All embed methods automatically call ready() internally, so explicit calls are optional
*/
ready() {
return c.get(this.element) || Promise.resolve();
}
/**
* Call a method on the embed
*
* @param method Name of the method to call
* @param parameters Method parameters
* @returns Call result from Embed (if any)
*/
call(e, t = {}) {
return new Promise((s, i) => this.ready().then(() => {
this.embedCallback.pushCall(e, s, i), u(this, e, t);
}));
}
/**
* Subscribe to a specific event
*
* @param event The name of the event.
* @param callback The function to call when receiving an event
*/
on(e, t) {
if (typeof e != "string")
throw new TypeError("An event name (string) is required");
if (typeof t != "function")
throw new TypeError("An callback (function) is required");
this.embedCallback.subscribeEvent(e, t) && this.call("addEventListener", e).catch(() => {
});
}
/**
* Unsubscribe to a specific event
*
* @param event The name of the event.
* @param callback The function to unsubscribe
*/
off(e, t) {
if (typeof e != "string")
throw new TypeError("An event name (string) is required");
this.embedCallback.unsubscribeEvent(e, t) && this.call("removeEventListener", e).catch(() => {
});
}
/**
* Load a score hosted on Flat
*
* Loads a Flat score by its unique identifier. For private scores, you must provide
* the sharing key obtained from a private link.
*
* @param score - The score identifier as a string, or an object containing:
* - `score`: The unique identifier of the score (required)
* - `sharingKey`: The sharing key for private scores (optional)
* @returns A promise that resolves when the score is successfully loaded
* @throws {TypeError} If the score parameter is invalid
* @throws {Error} If the score cannot be loaded (e.g., not found, access denied)
*
* @example
* // Load a public score
* await embed.loadFlatScore('56ae21579a127715a02901a6');
*
* @example
* // Load a private score with sharing key
* await embed.loadFlatScore({
* score: '56ae21579a127715a02901a6',
* sharingKey: 'f79c3c0dd1fc76ed8b30d6f2c845c8c30f11fe88d9fc39ab96e8e407629d4885'
* });
*/
loadFlatScore(e) {
return typeof e == "string" && (e = { score: e }), this.call("loadFlatScore", e);
}
/**
* Load a MusicXML score
*
* Loads a MusicXML score from a string or binary data. The score will be converted
* to Flat's internal format and displayed in the embed.
*
* @param score - The MusicXML content as a string (XML) or Uint8Array (compressed MXL)
* @returns A promise that resolves when the score is successfully loaded
* @throws {TypeError} If the score format is invalid
* @throws {Error} If the MusicXML cannot be parsed or loaded
*
* @example
* // Load from XML string
* const xmlString = '<?xml version="1.0"?>...';
* await embed.loadMusicXML(xmlString);
*
* @example
* // Load from compressed MXL file
* const response = await fetch('score.mxl');
* const buffer = await response.arrayBuffer();
* await embed.loadMusicXML(new Uint8Array(buffer));
*/
loadMusicXML(e) {
return this.call("loadMusicXML", e);
}
/**
* Load a MIDI score
*
* Loads a MIDI file and converts it to sheet music notation. Note that MIDI files
* contain performance data rather than notation, so the conversion may not perfectly
* represent the original musical intent.
*
* @param score - The MIDI file as a Uint8Array
* @returns A promise that resolves when the score is successfully loaded
* @throws {TypeError} If the score format is invalid
* @throws {Error} If the MIDI file cannot be parsed or loaded
*
* @example
* // Load MIDI file from URL
* const response = await fetch('song.mid');
* const buffer = await response.arrayBuffer();
* await embed.loadMIDI(new Uint8Array(buffer));
*
* @example
* // Load MIDI file from file input
* const file = document.getElementById('fileInput').files[0];
* const buffer = await file.arrayBuffer();
* await embed.loadMIDI(new Uint8Array(buffer));
*/
loadMIDI(e) {
return this.call("loadMIDI", e);
}
/**
* Load a Flat JSON score
*
* Loads a score from Flat's native JSON format. This format preserves all score
* data and is the most reliable way to transfer scores between Flat applications.
*
* @param score - The score data as a JSON string or JavaScript object
* @returns A promise that resolves when the score is successfully loaded
* @throws {TypeError} If the JSON is invalid or cannot be parsed
* @throws {Error} If the score data is malformed or cannot be loaded
*
* @example
* // Load from JSON object
* const scoreData = await fetch('score.json').then(r => r.json());
* await embed.loadJSON(scoreData);
*
* @example
* // Load from JSON string
* const jsonString = '{"score-partwise": {...}}';
* await embed.loadJSON(jsonString);
*/
loadJSON(e) {
return this.call("loadJSON", e);
}
/**
* Get the score in Flat JSON format
*
* Exports the currently loaded score as Flat's native JSON format. This format
* preserves all score data including notation, layout, and metadata.
*
* @returns A promise that resolves with the score data as a JavaScript object
* @throws {Error} If no score is currently loaded
*
* @example
* // Export and save score data
* const scoreData = await embed.getJSON();
* const jsonString = JSON.stringify(scoreData);
*
* // Save to file
* const blob = new Blob([jsonString], { type: 'application/json' });
* const url = URL.createObjectURL(blob);
* const a = document.createElement('a');
* a.href = url;
* a.download = 'score.json';
* a.click();
*/
getJSON() {
return this.call("getJSON");
}
/**
* Convert the displayed score to MusicXML
*
* Exports the currently loaded score as MusicXML, the standard format for sheet music
* notation exchange. Supports both uncompressed XML and compressed MXL formats.
*
* @param options - Export options:
* - `compressed`: If true, returns compressed MusicXML (.mxl) as Uint8Array.
* If false (default), returns uncompressed XML as string.
* @returns A promise that resolves with the MusicXML data
* @throws {TypeError} If options parameter is invalid
* @throws {Error} If no score is loaded or conversion fails
*
* @example
* // Get uncompressed MusicXML
* const xml = await embed.getMusicXML();
* console.log(xml); // <?xml version="1.0"...
*
* @example
* // Get compressed MusicXML (.mxl)
* const mxl = await embed.getMusicXML({ compressed: true });
* const blob = new Blob([mxl], { type: 'application/vnd.recordare.musicxml' });
* const url = URL.createObjectURL(blob);
*/
getMusicXML(e) {
return new Promise((t, s) => {
if (e = e || {}, typeof e != "object")
return s(new TypeError("Options must be an object"));
this.call("getMusicXML", e).then((i) => t(typeof i == "string" ? i : new Uint8Array(i))).catch(s);
});
}
/**
* Convert the displayed score to PNG image
*
* Exports the currently loaded score as a PNG image. Supports various export options
* including resolution, layout mode, and output format.
*
* @param options - Export options:
* - `result`: Output format - 'Uint8Array' (default) or 'dataURL'
* - `dpi`: Resolution in dots per inch (50-300, default: 150)
* - `layout`: Layout mode - 'track' (default, horizontal single system) or 'page'
* @returns A promise that resolves with the PNG data
* @throws {TypeError} If options parameter is invalid
* @throws {Error} If no score is loaded or conversion fails
*
* @example
* // Get PNG as Uint8Array
* const pngData = await embed.getPNG();
* const blob = new Blob([pngData], { type: 'image/png' });
*
* @example
* // Get PNG as data URL for direct display
* const dataUrl = await embed.getPNG({ result: 'dataURL' });
* document.getElementById('preview').src = dataUrl;
*
* @example
* // High resolution export with page layout
* const hqPng = await embed.getPNG({ dpi: 300, layout: 'page' });
*/
getPNG(e) {
return new Promise((t, s) => {
if (e = e || {}, typeof e != "object")
return s(new TypeError("Options must be an object"));
this.call("getPNG", e).then((i) => {
if (typeof i == "string")
return t(i);
t(new Uint8Array(i));
}).catch(s);
});
}
/**
* Convert the displayed score to MIDI
*
* Exports the currently loaded score as a MIDI file. The MIDI file will contain
* performance data including notes, tempo, dynamics, and instrument information.
* Note that some notation elements may not be represented in MIDI format.
*
* @returns A promise that resolves with a Uint8Array containing the MIDI file data
* @throws {Error} If no score is loaded or conversion fails
*
* @example
* // Export score as MIDI
* const midiData = await embed.getMIDI();
*
* // Save as file
* const blob = new Blob([midiData], { type: 'audio/midi' });
* const url = URL.createObjectURL(blob);
* const a = document.createElement('a');
* a.href = url;
* a.download = 'score.mid';
* a.click();
*
* @example
* // Play MIDI in browser (requires Web MIDI API)
* const midiData = await embed.getMIDI();
* // ... use with Web MIDI API or MIDI player library
*/
getMIDI() {
return this.call("getMIDI").then((e) => new Uint8Array(e));
}
/**
* Get the metadata of the score (for scores hosted on Flat)
*
* Retrieves metadata for scores that are hosted on Flat, including title, composer,
* collaborators, creation date, and other information available through Flat's API.
* This method only works for scores loaded via `loadFlatScore()`.
*
* @returns A promise that resolves with the score metadata object
* @throws {Error} If no Flat score is loaded or metadata is unavailable
*
* @example
* // Get metadata after loading a Flat score
* await embed.loadFlatScore('56ae21579a127715a02901a6');
* const metadata = await embed.getFlatScoreMetadata();
* console.log(`Title: ${metadata.title}`);
* console.log(`Created by: ${metadata.user.username}`);
*
* @see {@link https://flat.io/developers/api/reference/#operation/getScore}
*/
getFlatScoreMetadata() {
return this.call("getFlatScoreMetadata");
}
/**
* Get the embed configuration
*
* Retrieves the complete configuration object for the embed, including display
* settings, permissions, editor configuration, and enabled features.
*
* @returns A promise that resolves with the embed configuration object
* @throws {Error} If the configuration cannot be retrieved
*
* @example
* // Get current embed configuration
* const config = await embed.getEmbedConfig();
* console.log(`Mode: ${config.mode}`);
* console.log(`Controls enabled: ${config.controlsPlay}`);
*/
getEmbedConfig() {
return this.call("getEmbedConfig");
}
/**
* Set the editor configuration
*
* Updates the editor configuration for the embed. These settings control various
* aspects of the editor interface and behavior. The configuration is applied
* when the next score is loaded.
*
* @param editor - Editor configuration object that may include:
* - displayMode: Score display mode
* - toolsetId: Active toolset identifier
* - hiddenTools: Array of tool identifiers to hide
* - Additional editor-specific settings
* @returns A promise that resolves when the configuration is updated
* @throws {Error} If the configuration is invalid
*
* @example
* // Configure editor before loading a score
* await embed.setEditorConfig({
* displayMode: 'responsive',
* hiddenTools: ['note-duration', 'note-pitch']
* });
* await embed.loadFlatScore('56ae21579a127715a02901a6');
*
* @note This configuration persists across score loads until changed
*/
setEditorConfig(e) {
return this.call("setEditorConfig", e);
}
/**
* Toggle fullscreen mode
*
* Switches the embed in or out of fullscreen mode. When in fullscreen, the embed
* expands to fill the entire screen, providing an immersive view of the score.
*
* @param active - true to enter fullscreen, false to exit fullscreen
* @returns A promise that resolves when the fullscreen state has changed
* @throws {Error} If fullscreen mode cannot be toggled (e.g., browser restrictions)
*
* @example
* // Enter fullscreen mode
* await embed.fullscreen(true);
*
* @example
* // Exit fullscreen mode
* await embed.fullscreen(false);
*
* @example
* // Toggle fullscreen with user interaction
* button.addEventListener('click', () => {
* embed.fullscreen(true);
* });
*
* @note Fullscreen requests may require user interaction due to browser policies
*/
fullscreen(e) {
return this.call("fullscreen", e);
}
/**
* Start playback
*
* Begins playing the score from the current cursor position. If playback was
* previously paused, it resumes from the pause position. If stopped, it starts
* from the beginning or the current cursor position.
*
* @returns A promise that resolves when playback has started
* @throws {Error} If no score is loaded or playback cannot start
*
* @example
* // Start playback
* await embed.play();
*
* @example
* // Play with event handling
* embed.on('play', () => console.log('Playback started'));
* await embed.play();
*
* @see {@link pause} - Pause playback
* @see {@link stop} - Stop playback
*/
play() {
return this.call("play");
}
/**
* Pause playback
*
* Pauses the score playback at the current position. The playback position
* is maintained, allowing you to resume from the same point using `play()`.
*
* @returns A promise that resolves when playback has been paused
* @throws {Error} If no score is loaded or playback cannot be paused
*
* @example
* // Pause playback
* await embed.pause();
*
* @example
* // Toggle play/pause
* if (isPlaying) {
* await embed.pause();
* } else {
* await embed.play();
* }
*
* @see {@link play} - Start or resume playback
* @see {@link stop} - Stop and reset playback
*/
pause() {
return this.call("pause");
}
/**
* Stop playback
*
* Stops the score playback and resets the playback position to the beginning
* of the score. Unlike `pause()`, the playback position is not maintained.
*
* @returns A promise that resolves when playback has been stopped
* @throws {Error} If no score is loaded or playback cannot be stopped
*
* @example
* // Stop playback
* await embed.stop();
*
* @example
* // Stop and restart from beginning
* await embed.stop();
* await embed.play(); // Starts from beginning
*
* @see {@link play} - Start playback
* @see {@link pause} - Pause playback
*/
stop() {
return this.call("stop");
}
/**
* Mute playback
*
* Mutes all audio output from the score playback. The playback continues
* but without sound. This is equivalent to setting the master volume to 0
* but preserves the previous volume setting.
*
* @returns A promise that resolves when audio has been muted
* @throws {Error} If muting fails
*
* @example
* // Mute audio
* await embed.mute();
*
* @example
* // Mute during playback
* await embed.play();
* await embed.mute();
*
* @see {@link setMasterVolume} - Set master volume level
* @note There is no unmute method; use setMasterVolume to restore audio
*/
mute() {
return this.call("mute");
}
/**
* Get the current master volume
*
* Retrieves the current master volume level for score playback.
*
* @returns A promise that resolves with the volume level (0-100)
* @throws {Error} If the volume cannot be retrieved
*
* @example
* // Get current volume
* const volume = await embed.getMasterVolume();
* console.log(`Current volume: ${volume}%`);
*
* @see {@link setMasterVolume} - Set the master volume
*/
getMasterVolume() {
return this.call("getMasterVolume");
}
/**
* Set the master volume
*
* Sets the master volume level for score playback. This affects all parts
* proportionally based on their individual volume settings.
*
* @param parameters - Volume settings
* @returns A promise that resolves when the volume has been set
* @throws {Error} If the volume value is invalid or cannot be set
*
* @example
* // Set volume to 50%
* await embed.setMasterVolume({ volume: 50 });
*
* @example
* // Mute audio
* await embed.setMasterVolume({ volume: 0 });
*
* @example
* // Maximum volume
* await embed.setMasterVolume({ volume: 100 });
*
* @see {@link getMasterVolume} - Get the current master volume
*/
setMasterVolume(e) {
return this.call("setMasterVolume", e);
}
/**
* Get the volume of a specific part
*
* Retrieves the current volume level for a specific instrument part in the score.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part
* @returns A promise that resolves with the part's volume level (0-100)
* @throws {Error} If the part UUID is invalid or volume cannot be retrieved
*
* @example
* // Get volume for a specific part
* const parts = await embed.getParts();
* const violinVolume = await embed.getPartVolume({
* partUuid: parts[0].uuid
* });
*
* @see {@link setPartVolume} - Set the volume for a part
* @see {@link getParts} - Get all parts information
*/
getPartVolume(e) {
return this.call("getPartVolume", e);
}
/**
* Set the volume of a specific part
*
* Sets the volume level for a specific instrument part in the score. Part volumes
* are independent but affected by the master volume.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part
* - `volume`: Volume level (0-100, where 0 is muted and 100 is maximum)
* @returns A promise that resolves when the volume has been set
* @throws {Error} If the part UUID or volume value is invalid
*
* @example
* // Set violin part to 75% volume
* const parts = await embed.getParts();
* await embed.setPartVolume({
* partUuid: parts[0].uuid,
* volume: 75
* });
*
* @example
* // Mute the bass part
* await embed.setPartVolume({
* partUuid: bassPartUuid,
* volume: 0
* });
*
* @see {@link getPartVolume} - Get the volume for a part
* @see {@link mutePart} - Mute a part
*/
setPartVolume(e) {
return this.call("setPartVolume", e);
}
/**
* Mute a specific part
*
* Mutes the audio output for a specific instrument part. The part's volume
* setting is preserved and can be restored using `unmutePart()`.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part to mute
* @returns A promise that resolves when the part has been muted
* @throws {Error} If the part UUID is invalid or muting fails
*
* @example
* // Mute the drums part
* const parts = await embed.getParts();
* const drumsPart = parts.find(p => p.instrument === 'drums');
* await embed.mutePart({ partUuid: drumsPart.uuid });
*
* @see {@link unmutePart} - Unmute a part
* @see {@link setPartVolume} - Set part volume to 0 (alternative)
*/
mutePart(e) {
return this.call("mutePart", e);
}
/**
* Unmute a specific part
*
* Restores the audio output for a previously muted part. The part returns
* to its previous volume level before it was muted.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part to unmute
* @returns A promise that resolves when the part has been unmuted
* @throws {Error} If the part UUID is invalid or unmuting fails
*
* @example
* // Unmute a previously muted part
* await embed.unmutePart({ partUuid: drumsPart.uuid });
*
* @see {@link mutePart} - Mute a part
*/
unmutePart(e) {
return this.call("unmutePart", e);
}
/**
* Enable solo mode for a part
*
* Enables solo mode for a specific part, which mutes all other parts while
* keeping the selected part audible. Multiple parts can be in solo mode
* simultaneously.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part to solo
* @returns A promise that resolves when solo mode has been enabled
* @throws {Error} If the part UUID is invalid or solo mode cannot be set
*
* @example
* // Solo the violin part
* const parts = await embed.getParts();
* const violinPart = parts.find(p => p.instrument === 'violin');
* await embed.setPartSoloMode({ partUuid: violinPart.uuid });
*
* @example
* // Solo multiple parts
* await embed.setPartSoloMode({ partUuid: violinUuid });
* await embed.setPartSoloMode({ partUuid: celloUuid });
*
* @see {@link unsetPartSoloMode} - Disable solo mode
* @see {@link getPartSoloMode} - Check solo mode status
*/
setPartSoloMode(e) {
return this.call("setPartSoloMode", e);
}
/**
* Disable solo mode for a part
*
* Disables solo mode for a specific part. If this was the only part in solo
* mode, all parts return to their normal volume/mute states. If other parts
* remain in solo mode, this part will be muted.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part
* @returns A promise that resolves when solo mode has been disabled
* @throws {Error} If the part UUID is invalid or solo mode cannot be unset
*
* @example
* // Remove solo from a part
* await embed.unsetPartSoloMode({ partUuid: violinPart.uuid });
*
* @see {@link setPartSoloMode} - Enable solo mode
* @see {@link getPartSoloMode} - Check solo mode status
*/
unsetPartSoloMode(e) {
return this.call("unsetPartSoloMode", e);
}
/**
* Get the solo mode status of a part
*
* Checks whether a specific part is currently in solo mode.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part
* @returns A promise that resolves with true if solo mode is enabled, false otherwise
* @throws {Error} If the part UUID is invalid
*
* @example
* // Check if violin is in solo mode
* const isSolo = await embed.getPartSoloMode({
* partUuid: violinPart.uuid
* });
* if (isSolo) {
* console.log('Violin is in solo mode');
* }
*
* @see {@link setPartSoloMode} - Enable solo mode
* @see {@link unsetPartSoloMode} - Disable solo mode
*/
getPartSoloMode(e) {
return this.call("getPartSoloMode", e);
}
/**
* Get the reverb level of a part
*
* Retrieves the current reverb (reverberation) effect level for a specific
* instrument part. Reverb adds spatial depth and ambience to the sound.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part
* @returns A promise that resolves with the reverb level (0-100)
* @throws {Error} If the part UUID is invalid or reverb cannot be retrieved
*
* @example
* // Get reverb level for piano part
* const parts = await embed.getParts();
* const pianoPart = parts.find(p => p.instrument === 'piano');
* const reverb = await embed.getPartReverb({
* partUuid: pianoPart.uuid
* });
* console.log(`Piano reverb: ${reverb}%`);
*
* @see {@link setPartReverb} - Set the reverb level
*/
getPartReverb(e) {
return this.call("getPartReverb", e);
}
/**
* Set the reverb level of a part
*
* Sets the reverb (reverberation) effect level for a specific instrument part.
* Higher values create more spacious, ambient sound.
*
* @param parameters - Object containing:
* - `partUuid`: The unique identifier of the part
* - `reverberation`: Reverb level (0-100, where 0 is dry and 100 is maximum reverb)
* @returns A promise that resolves when the reverb has been set
* @throws {Error} If the part UUID or reverb value is invalid
*
* @example
* // Add subtle reverb to piano
* await embed.setPartReverb({
* partUuid: pianoPart.uuid,
* reverberation: 30
* });
*
* @example
* // Remove reverb (dry sound)
* await embed.setPartReverb({
* partUuid: pianoPart.uuid,
* reverberation: 0
* });
*
* @see {@link getPartReverb} - Get the current reverb level
*/
setPartReverb(e) {
return this.call("setPartReverb", e);
}
/**
* Configure an audio or video track
*
* Sets up a new audio or video track to synchronize with the score playback.
* This allows you to play backing tracks, reference recordings, or video
* alongside the score.
*
* @param parameters - Track configuration object (see ScoreTrackConfiguration type)
* @returns A promise that resolves when the track has been configured
* @throws {Error} If the track configuration is invalid
*
* @example
* // Configure an audio backing track
* await embed.setTrack({
* id: 'backing-track-1',
* type: 'audio',
* url: 'https://example.com/backing-track.mp3',
* synchronizationPoints: [
* { type: 'measure', measure: 0, time: 0 },
* { type: 'measure', measure: 16, time: 32.5 }
* ]
* });
*
* @see {@link useTrack} - Enable a configured track
* @see {@link seekTrackTo} - Seek to a position in the track
*/
setTrack(e) {
return this.call("setTrack", e);
}
/**
* Enable a previously configured track
*
* Activates a track that was previously configured with `setTrack()`. Only one
* track can be active at a time.
*
* @param parameters - Object containing:
* - `id`: The identifier of the track to enable
* @returns A promise that resolves when the track has been enabled
* @throws {Error} If the track ID is invalid or track cannot be enabled
*
* @example
* // Enable a configured backing track
* await embed.useTrack({ id: 'backing-track-1' });
*
* @example
* // Switch between multiple tracks
* await embed.useTrack({ id: 'practice-tempo' });
* // Later...
* await embed.useTrack({ id: 'full-tempo' });
*
* @see {@link setTrack} - Configure a new track
*/
useTrack(e) {
return this.call("useTrack", e);
}
/**
* Seek to a position in the audio track
*
* Moves the playback position of the currently active audio/video track to
* a specific time. This is useful for synchronizing with score playback or
* jumping to specific sections.
*
* @param parameters - Object containing:
* - `time`: Time position in seconds
* @returns A promise that resolves when seeking is complete
* @throws {Error} If no track is active or seeking fails
*
* @example
* // Seek to 30 seconds
* await embed.seekTrackTo({ time: 30 });
*
* @example
* // Seek to beginning
* await embed.seekTrackTo({ time: 0 });
*
* @see {@link setTrack} - Configure a track
* @see {@link useTrack} - Enable a track
*/
seekTrackTo(e) {
return this.call("seekTrackTo", e);
}
/**
* Print the score
*
* Opens the browser's print dialog to print the currently loaded score. The score
* is formatted for optimal printing with proper page breaks and sizing.
*
* @returns A promise that resolves when the print dialog has been opened
* @throws {Error} If no score is loaded or printing cannot be initiated
*
* @example
* // Print the current score
* await embed.print();
*
* @example
* // Add print button
* document.getElementById('printBtn').addEventListener('click', () => {
* embed.print();
* });
*
* @note The actual printing is controlled by the browser's print dialog
*/
print() {
return this.call("print");
}
/**
* Get the current zoom ratio
*
* Retrieves the current zoom level of the score display. The zoom level affects
* how large the notation appears on screen.
*
* @returns A promise that resolves with the zoom ratio (0.5 to 3)
* @throws {Error} If the zoom level cannot be retrieved
*
* @example
* // Get current zoom level
* const zoom = await embed.getZoom();
* console.log(`Current zoom: ${zoom * 100}%`);
*
* @see {@link setZoom} - Set the zoom level
* @see {@link getAutoZoom} - Check if auto-zoom is enabled
*/
getZoom() {
return this.call("getZoom");
}
/**
* Set the zoom ratio
*
* Sets a specific zoom level for the score display. Setting a manual zoom level
* automatically disables auto-zoom mode.
*
* @param zoom - The zoom ratio (0.5 to 3)
* - 0.5 = 50% (minimum zoom)
* - 1 = 100% (normal size)
* - 3 = 300% (maximum zoom)
* @returns A promise that resolves with the actual zoom ratio applied
* @throws {Error} If the zoom value is outside the valid range
*
* @example
* // Set zoom to 150%
* await embed.setZoom(1.5);
*
* @example
* // Zoom in/out buttons
* const zoomIn = async () => {
* const current = await embed.getZoom();
* await embed.setZoom(Math.min(current + 0.1, 3));
* };
*
* @see {@link getZoom} - Get current zoom level
* @see {@link setAutoZoom} - Enable automatic zoom
*/
setZoom(e) {
return this.call("setZoom", e);
}
/**
* Get the auto-zoom status
*
* Checks whether auto-zoom mode is currently enabled. When auto-zoom is active,
* the score automatically adjusts its zoom level to fit the available space.
*
* @returns A promise that resolves with true if auto-zoom is enabled, false otherwise
* @throws {Error} If the status cannot be retrieved
*
* @example
* // Check auto-zoom status
* const isAutoZoom = await embed.getAutoZoom();
* if (isAutoZoom) {
* console.log('Score will auto-fit to container');
* }
*
* @see {@link setAutoZoom} - Enable or disable auto-zoom
*/
getAutoZoom() {
return this.call("getAutoZoom");
}
/**
* Enable or disable auto-zoom
*
* Controls the auto-zoom feature. When enabled, the score automatically adjusts
* its zoom level to fit the available container width. When disabled, the zoom
* level remains fixed at the last set value.
*
* @param state - true to enable auto-zoom, false to disable
* @returns A promise that resolves with the new auto-zoom state
* @throws {Error} If auto-zoom cannot be toggled
*
* @example
* // Enable auto-zoom
* await embed.setAutoZoom(true);
*
* @example
* // Disable auto-zoom and set fixed zoom
* await embed.setAutoZoom(false);
* await embed.setZoom(1.2);
*
* @see {@link getAutoZoom} - Check current auto-zoom status
* @see {@link setZoom} - Set a specific zoom level
*/
setAutoZoom(e) {
return this.call("setAutoZoom", e);
}
/**
* Set focus to the score
*
* Gives keyboard focus to the score iframe, allowing keyboard shortcuts and
* navigation to work. This is useful when embedding multiple interactive elements
* on a page.
*
* @returns A promise that resolves when focus has been set
*
* @example
* // Focus score for keyboard interaction
* await embed.focusScore();
*
* @example
* // Focus score when user clicks a button
* document.getElementById('editBtn').addEventListener('click', () => {
* embed.focusScore();
* });
*/
focusScore() {
return this.call("focusScore");
}
/**
* Get the cursor position
*
* Retrieves the current position of the cursor in the score, including the part,
* measure, voice, and note location.
*
* @returns A promise that resolves with the cursor position object
* @throws {Error} If cursor position cannot be retrieved
*
* @example
* // Get current cursor position
* const pos = await embed.getCursorPosition();
* console.log(`Cursor at measure ${pos.measureIdx + 1}`);
*
* @see {@link setCursorPosition} - Set cursor position
* @see {@link goLeft} - Move cursor left
* @see {@link goRight} - Move cursor right
*/
getCursorPosition() {
return this.call("getCursorPosition");
}
/**
* Set the cursor position
*
* Moves the cursor to a specific position in the score. You can specify any
* combination of part, measure, voice, staff, and note indices. Unspecified
* values remain at their current position.
*
* @param position - New cursor position with optional fields:
* - `partIdx`: Target part index (optional)
* - `measureIdx`: Target measure index (optional)
* - `voiceIdx`: Target voice index (optional)
* - `noteIdx`: Target note index (optional)
* - `staffIdx`: Target staff index (optional)
* @returns A promise that resolves when the cursor has been moved
* @throws {Error} If the position is invalid or cursor cannot be moved
*
* @example
* // Move to beginning of measure 5
* await embed.setCursorPosition({
* measureIdx: 4, // 0-based index
* noteIdx: 0
* });
*
* @example
* // Move to second voice of current measure
* await embed.setCursorPosition({ voiceIdx: 1 });
*
* @see {@link getCursorPosition} - Get current position
*/
setCursorPosition(e) {
return this.call("setCursorPosition", e);
}
/**
* Get information about all parts
*
* Retrieves detailed information about all instrument parts in the score,
* including their names, instruments, and unique identifiers.
*
* @returns A promise that resolves with an array of part configurations
* @throws {Error} If no score is loaded or parts cannot be retrieved
*
* @example
* // Get all parts
* const parts = await embed.getParts();
* parts.forEach(part => {
* console.log(`${part.name}: ${part.uuid}`);
* });
*
* @example
* // Find specific instrument
* const parts = await embed.getParts();
* const violin = parts.find(p => p.instrument === 'violin');
*
* @see {@link getDisplayedParts} - Get currently visible parts
* @see {@link setDisplayedParts} - Choose which parts to display
*/
getParts() {
return this.call("getParts");
}
/**
* Get the currently displayed parts
*
* Retrieves information about which parts are currently visible in the score.
* Some parts may be hidden for focused practice or simplified viewing.
*
* @returns A promise that resolves with an array of currently displayed part configurations
* @throws {Error} If displayed parts cannot be retrieved
*
* @example
* // Check which parts are visible
* const displayedParts = await embed.getDisplayedParts();
* console.log(`Showing ${displayedParts.length} parts`);
*
* @see {@link getParts} - Get all parts in the score
* @see {@link setDisplayedParts} - Change which parts are visible
*/
getDisplayedParts() {
return this.call("getDisplayedParts");
}
/**
* Set which parts to display
*
* Controls which instrument parts are visible in the score. Parts can be
* identified by their UUID, index, name, or abbreviation. Hidden parts
* are not displayed but still play during playback unless muted.
*
* @param parts - Array of part identifiers. Each can be:
* - UUID (recommended): The unique identifier
* - Index: Numeric position (0-based)
* - Name: Full part name (e.g., "Violin")
* - Abbreviation: Short name (e.g., "Vln")
* @returns A promise that resolves when parts visibility has been updated
* @throws {Error} If part identifiers are invalid
*
* @example
* // Show only violin and piano parts by UUID
* const parts = await embed.getParts();
* const violin = parts.find(p => p.instrument === 'violin');
* const piano = parts.find(p => p.instrument === 'piano');
* await embed.setDisplayedParts([violin.uuid, piano.uuid]);
*
* @example
* // Show parts by index
* await embed.setDisplayedParts(['0', '2', '3']);
*
* @example
* // Show all parts
* const allParts = await embed.getParts();
* await embed.setDisplayedParts(allParts.map(p => p.uuid));
*
* @see {@link getParts} - Get all available parts
* @see {@link getDisplayedParts} - Check which parts are visible
*/
setDisplayedParts(e) {
return this.call("setDisplayedParts", e);
}
/**
* Get the number of measures in the score
*
* Retrieves the total count of measures (bars) in the currently loaded score.
*
* @returns A promise that resolves with the number of measures
* @throws {Error} If no score is loaded
*
* @example
* // Get measure count
* const measureCount = await embed.getNbMeasures();
* console.log(`This score has ${measureCount} measures`);
*
* @see {@link getMeasuresUuids} - Get measure identifiers
* @see {@link getMeasureDetails} - Get detailed measure information
*/
getNbMeasures() {
return this.call("getNbMeasures");
}
/**
* Get the measure UUIDs of the score
*
* Retrieves the unique identifiers for all measures in the score. These UUIDs
* can be used to reference specific measures in other API calls.
*
* @returns A promise that resolves with an array of measure UUIDs in order
* @throws {Error} If no score is loaded
*
* @example
* // Get all measure UUIDs
* const measureUuids = await embed.getMeasuresUuids();
* console.log(`First measure UUID: ${measureUuids[0]}`);
*
* @see {@link getNbMeasures} - Get the total number of measures
* @see {@link getMeasureDetails} - Get details for a specific measure
*/
getMeasuresUuids() {
return this.call("getMeasuresUuids");
}
/**
* Get detailed measure information
*
* Retrieves comprehensive information about the current measure at the cursor
* position, including time signature, key signature, tempo, and other properties.
*
* @returns A promise that resolves with the measure details
* @throws {Error} If no score is loaded or details cannot be retrieved
*
* @example
* // Get current measure details
* const details = await embed.getMeasureDetails();
* console.log(`Time signature: ${details.time.beats}/${details.time['beat-type']}`);
* console.log(`Key signature: ${details.key.fifths} sharps/flats`);
* console.log(`Tempo: ${details.tempo.bpm} BPM`);
*
* @see {@link getNbMeasures} - Get measure count
* @see {@link getMeasuresUuids} - Get measure identifiers
*/
getMeasureDetails() {
return this.call("getMeasureDetails");
}
/**
* Get the number of parts in the score
*
* Retrieves the total count of instrument parts in the currently loaded score.
*
* @returns A promise that resolves with the number of parts
* @throws {Error} If no score is loaded
*
* @example
* // Get part count
* const partCount = await embed.getNbParts();
* console.log(`Score has ${partCount} instruments`);
*
* @see {@link getParts} - Get detailed part information
* @see {@link getPartsUuids} - Get part identifiers
*/
getNbParts() {
return this.call("getNbParts");
}
/**
* Get the part UUIDs of the score
*
* Retrieves the unique identifiers for all parts in the score. These UUIDs
* are used to reference specific parts in volume, mute, and other part-specific
* operations.
*
* @returns A promise that resolves with an array of part UUIDs in order
* @throws {Error} If no score is loaded
*
* @example
* // Get all part UUIDs
* const partUuids = await embed.getPartsUuids();
* // Mute the first part
* await embed.mutePart({ partUuid: partUuids[0] });
*
* @see {@link getNbParts} - Get the total number of parts
* @see {@link getParts} - Get detailed part information
*/
getPartsUuids() {
return this.call("getPartsUuids");
}
/**
* Get voice UUIDs for a specific measure
*
* Retrieves the unique identifiers of all voices present in a specific measure
* of a part. Voices represent independent melodic lines within a part.
*
* @param parameters - Object containing:
* - `partUuid`: UUID of the part
* - `measureUuid`: UUID of the measure
* @returns A promise that resolves with an array of voice UUIDs
* @throws {Error} If the part or measure UUID is invalid
*
* @example
* // Get voices in first measure of piano part
* const parts = await embed.getParts();
* const measures = await embed.getMeasuresUuids();
* const voices = await embed.getMeasureVoicesUuids({
* partUuid: parts[0].uuid,
* measureUuid: measures[0]
* });
* console.log(`Found ${voices.length} voices`);
*
* @see {@link getMeasureNbNotes} - Count notes in a voice
*/
getMeasureVoicesUuids(e) {
return this.call("getMeasureVoicesUuids", e);
}
/**
* Get the number of notes in a voice
*
* Counts the total number of notes (including rests) in a specific voice
* within a measure. This is useful for iterating through notes or determining
* voice complexity.
*
* @param parameters - Object containing:
* - `partUuid`: UUID of the part
* - `measureUuid`: UUID of the measure
* - `voiceUuid`: UUID of the voice
* @returns A promise that resolves with the number of notes
* @throws {Error} If any UUID is invalid
*
* @example
* // Count notes in first voice
* const noteCount = await embed.getMeasureNbNotes({
* partUuid: partUuid,
* measureUuid: measureUuid,
* voiceUuid: voiceUuids[0]
* });
*
* @see {@link getNoteData} - Get details about specific notes
* @see {@link getMeasureVoicesUuids} - Get voice UUIDs
*/
getMeasureNbNotes(e) {
return this.call("getMeasureNbNotes", e);
}
/**
* Get information about a specific note
*
* Retrieves detailed information about a note at a specific position, including
* pitch, duration, articulations, and other musical properties.
*
* @param parameters - Object containing:
* - `partUuid`: UUID of the part
* - `measureUuid`: UUID of the measure
* - `voiceUuid`: UUID of the voice
* - `noteIdx`: Index of the note (0-based)
* @returns A promise that resolves with the note details
* @throws {Error} If the position is invalid or note not found
*
* @example
* // Get first note details
* const noteData = await embed.getNoteData({
* partUuid: partUuid,
* measureUuid: measureUuid,
* voiceUuid: voiceUuid,
* noteIdx: 0
* });
* console.log(`Note: ${noteData.pitch}`);
*
* @see {@link getMeasureNbNotes} - Get total notes in voice
*/
getNoteData(e) {
return this.call("getNoteData", e);
}
/**
* Convert playback position to note index
*
* Finds the note index that corresponds to a specific playback position.
* This is useful for synchronizing visual elements with audio playback.
*
* @param parameters - Object containing:
* - `partUuid`: UUID of the part
* - `voiceUuid`: UUID of the voice
* - `playbackPosition`: Position object with measure and beat information
* @returns A promise that resolves with the note index (0-based)
* @throws {Error} If the position cannot be mapped to a note
*
* @example
* // Find note at current playback position
* const noteIdx = await embed.playbackPositionToNoteIdx({