Flat Sheet Music Embed Client
Use this JavaScript Client to interact and receive events from our Sheet Music Embed.
If you have any feedback or questions regarding this product, please feel free to contact our developers' support.
Installation
You can install our ES/TypeScript Embed Client using npm, pnpm, or yarn:
npm install flat-embed
pnpm add flat-embed
yarn add flat-embed
Or use the latest UMD version hosted on our CDN:
<script src="https://prod.flat-cdn.com/embed-js/v2.0.0/embed.min.js"></script>
Getting Started
The simplest way to get started is to pass a DOM element to our embed that will be used as container. By default, this one will completely fit its container:
<div id="embed-container"></div>
<script src="https://prod.flat-cdn.com/embed-js/v2.0.0/embed.min.js"></script>
<script>
var container = document.getElementById('embed-container');
var embed = new Flat.Embed(container, {
score: '<score-id-you-want-to-load>',
embedParams: {
appId: '<your-app-id>',
controlsPosition: 'bottom',
},
});
</script>
Otherwise, if you are using our embed in an ES6 project:
import Embed from 'flat-embed';
const container = document.getElementById('embed-container');
const embed = new Embed(container, {
score: '<score-id-you-want-to-load>',
embedParams: {
appId: '<your-app-id>',
controlsPosition: 'bottom',
},
});
>> Open this demo in JSFiddle
✨ Demos
Some demos of this Embed API are available in a dedicated repository: https://github.com/FlatIO/embed-examples.
App ID
Our Embed JS API requires an App ID (appId
) to use it:
- In development, you can try and use this client without limits on
localhost
/*.localhost
. - To use it in production or with a custom domain, create a new app on our website, then go to the Embed > Settings and add your domains to the whitelist. Your app ID will also be displayed on this page.
Embed construction
DOM element or existing iframe
When instantiating Flat.Embed
, the first argument will always refer to a DOM element. It can take:
- A DOM element (e.g. selected using
document.getElementById('embed-container')
). - The string identifier of the element (e.g.
"embed-container"
). - An existing embed iframe element. In this case, this one will need to have our JS API loaded using the query string
jsapi=true
.
If you instance a different Flat.Embed
for the same element, you will always get the same instance of the object.
Options and URL parameters
When instantiating Flat.Embed
, you can pass options in the second parameter. To use the different methods available and events subscriptions, you will need to pass at least embedParams.appId
.
Option | Description | Values | Default |
---|
score | The score identifier that will load initially | Unique score id | blank |
width | The width of your embed | A width of the embed | 100% |
height | The height of your embed | A height of the embed | 100% |
embedParams | Object containing the loading options for the embed | Any URL parameters | {} |
JavaScript API
Viewer API
You can call the methods using any Flat.Embed
object. By default, the methods (except on
and off
) return a Promise that will be resolved once the method is called, the value is set or get:
var embed = new Flat.Embed('container');
embed
.loadFlatScore('12234')
.then(function () {
console.log('Score loaded');
})
.catch(function (err) {
console.log('Error', err);
});
ready(): Promise<void, Error>
Promises resolved when the embed is loaded and the JavaScript API is ready to use. All the methods will implicitly use this method, so you don't have to worry about waiting for the loading before calling the different methods.
embed.ready().then(function () {
});
on(event: string, callback: function): void
Add an event listener for the specified event. When receiving the event, the client will call the specified function with zero or one parameter depending on the event received.
embed.on('playbackPosition', function (position) {
console.log(position);
});
off(event: string, callback?: function): void
Remove an event listener for the specified event. If no callback
is specified, all the listeners for the event will be removed.
function positionChanged(position) {
console.log(position);
embed.off('play', positionChanged);
embed.off('play');
}
embed.on('positionChanged', positionChanged);
getEmbedConfig(): Promise<object, Error>
Fetch the global config of the embed. This will include the URL parameters, the editor config and the default config set by the embed.
embed.getEmbedConfig().then(function (config) {
console.log(config);
});
loadFlatScore(score: mixed): Promise<void, ApiError>
Load a score hosted on Flat using its identifier. For example to load https://flat.io/score/56ae21579a127715a02901a6-house-of-the-rising-sun
, you can call:
embed
.loadFlatScore('56ae21579a127715a02901a6')
.then(function () {
})
.catch(function (error) {
});
If the score has a private sharing link (privateLink
), you can pass an object with the sharingKey
property:
embed
.loadFlatScore({
score: '5ce56f7c019fd41f5b17b72d',
sharingKey:
'3f70cc5ecf5e4248055bbe7502a9514cfe619c53b4e248144e470bb5f08c5ecf880cf3eda5679c6b19f646a98ec0bd06d892ee1fd6896e20de0365ed0a42fc00',
})
.then(function () {
})
.catch(function (error) {
});
loadMusicXML(score: mixed): Promise<void, Error>
Load a MusicXML score, compressed (MXL) or not (plain XML). The compressed files (.mxl) must be passed as ArrayBuffer
, and the plain XML (.xml/.musicxml) as String
.
Example to load a compressed MXL file:
fetch('https://api.flat.io/v2/scores/56ae21579a127715a02901a6/revisions/last/mxl')
.then(function (response) {
return response.arrayBuffer();
})
.then(function (mxl) {
return embed.loadMusicXML(mxl);
})
.then(function () {
})
.catch(function (error) {
});
Example to load a plain XML file:
fetch('https://api.flat.io/v2/scores/56ae21579a127715a02901a6/revisions/last/xml')
.then(function (response) {
return response.text();
})
.then(function (mxl) {
return embed.loadMusicXML(mxl);
})
.then(function () {
})
.catch(function (error) {
});
loadJSON(score: object): Promise<void, Error>
Load a score using Flat's JSON Format
fetch('https://api.flat.io/v2/scores/56ae21579a127715a02901a6/revisions/last/json')
.then(function (response) {
return response.json();
})
.then(function (json) {
return embed.loadJSON(json);
})
.then(function () {
})
.catch(function (error) {
});
getMusicXML(options?: object): Promise<string|Uint8Array, Error>
Convert the currently displayed score into a MusicXML file, compressed (.mxl
) or not (.xml
).
embed.getMusicXML().then(function (xml) {
console.log(xml);
});
Example: Retrieve the score as a compressed MusicXML, then convert it to a Blob and download it:
embed.getMusicXML({ compressed: true }).then(function (buffer) {
var blobUrl = window.URL.createObjectURL(
new Blob([buffer], {
type: 'application/vnd.recordare.musicxml+xml',
}),
);
var a = document.createElement('a');
a.href = blobUrl;
a.download = 'My Music Score.mxl';
document.body.appendChild(a);
a.style = 'display: none';
a.click();
a.remove();
});
getJSON(): object
Get the data of the score in the "Flat JSON" format (a MusicXML-like as a JavaScript object).
embed
.getJSON()
.then(function (data) {
console.log(data);
})
.catch(function (error) {
});
getPNG(options?: object): Promise<string|Uint8Array, Error>
Get the currently displayed score as a PNG file. This API method accepts the following options:
result
: Choose how the PNG file is returned, either dataURL
or Uint8Array
. Default value is Uint8Array
.layout
: The score can either exported as one horizontal system (track
), or the first page (page
). Default value is track
dpi
: The dpi used to export the PNG, between 50
and 300
. Default value is 150
.
embed.getPNG().then(function (png) {
console.log(png);
});
embed
.getPNG({
result: 'dataURL',
layout: 'layout',
dpi: 300,
})
.then(function (png) {
console.log(png);
});
getMIDI(): Promise<Uint8Array, Error>
Get the currently displayed score as a MIDI file
embed.getMIDI().then(function (midi) {
console.log(midi);
});
getScoreMeta(): object
Get the score metadata of the hosted score. The object will have the same format that the one returned by our API GET /v2/scores/{score}
.
embed
.getScoreMeta()
.then(function (metadata) {
console.log(metadata);
})
.catch(function (error) {
});
fullscreen(state: bool): Promise<void, Error>
Display the embed in fullscreen (state = true
) or return to the regular display (state = false
).
embed.fullscreen(true).then(function () {
});
play(): Promise<void, Error>
Load the playback and play the score.
embed.play().then(function () {
});
pause(): Promise<void, Error>
Pause the playback
embed.pause().then(function () {
});
stop(): Promise<void, Error>
Stop the playback
embed.stop().then(function () {
});
mute(): Promise<void, Error>
Mute the playback
embed.mute().then(function () {
});
getMasterVolume(): Promise<Number, Error>
Get the master volume
embed.getMasterVolume().then(function (volume) {
console.log(volume);
});
setMasterVolume({ volume: number }): Promise<void, Error>
Set the master volume (volume
between 0 and 100)
embed.setMasterVolume({ volume: 50 }).then(function () {
});
getPartVolume({ partUuid: string }): Promise<Number, Error>
Get a part volume (partUuid
can be retrieved with getParts
)
embed.getPartVolume({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function (volume) {
console.log(volume);
});
setPartVolume({ partUuid: string, volume: number }): Promise<void, Error>
Set a part volume (volume
between 0 and 100, partUuid
can be retrieved with getParts
)
embed
.getPartVolume({
partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c',
volume: 50,
})
.then(function () {
});
mutePart({ partUuid: string }): Promise<void, Error>
Mute a part
embed.mutePart({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function () {
});
unmutePart({ partUuid: string }): Promise<void, Error>
Unmute a part
embed.unmutePart({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function () {
});
setPartSoloMode({ partUuid: string }): Promise<void, Error>
Enable the solo mode for a part
embed.setPartSoloMode({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function () {
});
unsetPartSoloMode({ partUuid: string }): Promise<void, Error>
Disable the solo mode for a part
embed.unsetPartSoloMode({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function () {
});
getPartSoloMode({ partUuid: string }): Promise<Boolean, Error>
Get the state of the solo mode of a part (partUuid
can be retrieved with getParts
)
embed.getPartSoloMode({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function (isSolo) {
console.log(isSolo);
});
getPartReverb({ partUuid: string }): Promise<Number, Error>
Get a part reverberation (partUuid
can be retrieved with getParts
)
embed.getPartReverb({ partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c' }).then(function (reverb) {
console.log(reverb);
});
setPartReverb({ partUuid: string, reverberation: number }): Promise<void, Error>
Set a part reverberation (reverberation
between 0 and 100, partUuid
can be retrieved with getParts
)
embed
.setPartReverb({
partUuid: 'c86be847-a7a1-54fb-44fc-a6564d7eb75c',
reverberation: 50,
})
.then(function () {
});
getMetronomeMode(): Promise<Number, Error>
Get the value of the metronome count-in mode.
Mode is defined as:
const METRONOME_MODES = {
COUNT_IN: 0,
CONTINUOUS: 1,
DISABLED: 2,
};
embed.getMetronomeMode().then(function (metronomeMode) {
assert.strictEqual(metronomeMode, METRONOME_MODES.COUNT_IN);
assert.strictEqual(metronomeMode, 0);
});
setMetronomeMode(number): Promise<void, Error>
Set the metronome count-in mode.
embed.setMetronomeMode(1).then(function () {
});
getPlaybackSpeed(): Promise<Number, Error>
Get the playback speed.
embed.getPlaybackSpeed().then(function (playbackSpeed) {
assert.strictEqual(playbackSpeed, 1);
});
setPlaybackSpeed(number): Promise<void, Error>
Set the playback speed. Normal value is 1. The value can be between 0.2 and 2.
embed.setPlaybackSpeed(1.5).then(function () {
});
scrollToCursor(): Promise<void, Error>
For the display to scroll at the position of the cursor in the score
embed.scrollToCursor().then(function () {
});
setTrack(object): Promise<void, Error>
Configure a new video or audio track for the current embedded score. This method uses the same system as our audio tracks manager in our editor app, but allows you to dynamically configure any track or connect an external player to an embedded score.
This method takes the following options:
id
(required): An unique identifier for the configuration, that can be used later for the method useTrack
.type
(required): The type of track to configure, using one of the following types: youtube
, soundcloud
, vimeo
, audio
, external
url
(required for soundcloud
and audio
): the URL of the Souncloud or the audio file to loadmediaId
(required for youtube
, vimeo
): the video identifier to embedtotalTime
(required for external
): the total time of the media playedsynchronizationPoints
(required): the list of synchronization points to use. Each point can have the following information:
type
: measure
or end
of the scoretime
in seconds, the position of the synchronization pointlocation.measureIdx
: for measure
point, the index of the score where the point is located.
Once a track is configured, you must call the method useTrack
to enable it.
Two implementation examples are available in our example repository:
The synchronizationPoints
also use the same formats as our REST API. If you previously configured some tracks using our web editor, you can fetch their configuration using our REST API.
embed
.setTrack({
id: 'yt-cucaracha',
type: 'youtube',
mediaId: 'jp9vFhyhNd8',
synchronizationPoints: [
{ type: 'measure', time: 0, location: { measureIdx: 0 } },
{ type: 'end', time: 19 },
],
})
.then(function () {
});
useTrack({ id }): Promise<void, Error>
Enable a previously configured audio or video track. The id
can be an identifier chosen from a track configured using setTrack
or from Flat's REST API.
embed
.useTrack({
id: 'yt-cucaracha',
})
.then(function () {
});
seekTrackTo({ time }): Promise<void, Error>
Seek the current played track to a provided time
, in seconds.
embed.useTrack({
time: 5,
});
print(): Promise<void, Error>
Print the score
embed
.print()
.then(function () {
})
.catch(function (error) {
});
getZoom(): Promise<number, Error>
Get the current zoom ration applied for the score (between 0.5 and 3).
embed.getZoom().then(function (zoom) {
console.log(zoom);
});
setZoom(number): Promise<number, Error>
Set a new zoom ration for the display (between 0.5 and 3).
embed.setZoom(2).then(function (zoom) {
console.log(zoom);
});
getAutoZoom(): Promise(<boolean, Error>)
Get the state of the auto-zoom mode. Auto-zoom is enabled by default for page mode or when the URL parameter zoom
is set to auto
.
This getter will return true
if the auto-zoom is enabled, and false
when disabled. Setting a zoom value with setZoom
will disable this mode.
embed.getAutoZoom().then(function (state) {
console.log(state);
});
setAutoZoom(boolean): Promise(<boolean, Error>)
Enable (true
) or disable (false
) the auto-zoom. Auto-zoom is enabled by default for page mode or when the URL parameter zoom
is set to auto
. Setting a zoom value with setZoom
will disable this mode.
embed.setAutoZoom(false).then(function (state) {
console.log(state);
});
focusScore(): Promise(<void, Error>)
Unlike the web version on https://flat.io, the embed doesn't catch the focus. This avoids to mess with the parent window, and avoid the browser to do a forced scrolling to the embed iframe.
If the end-users' goal is the usage of the embed to play or write notation, you can use this method to set the focus on the score and allowing them to use the keyboard bindings.
embed.focusScore().then(function () {
});
getCursorPosition(): Promise(<object, Error>)
Return the current position of the cursor (on a specific note).
embed.getCursorPosition().then(function (position) {
});
setCursorPosition(position: object): Promise(<object, Error>)
Set the current position of the cursor (on a specific note).
embed
.setCursorPosition({
partIdx: 0,
staffIdx: 1,
voiceIdx: 0,
measureIdx: 2,
noteIdx: 1,
})
.then(function (position) {
});
getParts(): Promise(<Array, Error>)
Get the list of all the parts of the current score.
embed.getParts().then(function (parts) {
});
getDisplayedParts(): Promise(<Array, Error>)
Get the list of the displayed parts. You can update the displayed parts with setDisplayedParts()
.
embed.getDisplayedParts().then(function (parts) {
});
setDisplayedParts(parts): Promise(<void, Error>)
Set the list of the parts to display. This list (array) can either contain the UUIDs of the parts to display, their indexes (idx) starting from 0, the names of the parts or their abbreviations.
embed.setDisplayedParts(['Violin', 'Viola']).then(function () {
});
getMeasureDetails(): Promise(<object, Error>)
Retrieve details about the current measure. You can listen to the measureDetails
event to get the same details returned every time the cursor is moved or the measure is modified.
embed.getMeasureDetails().then(function (measure) {
});
getNoteDetails(): Promise(<object, Error>)
Retrieve details about the current note. You can listen to the noteDetails
event to get the same details returned every time the cursor is moved or the note is modified.
embed.getNoteDetails().then(function (measure) {
});
getNbMeasures(): Promise(<number, Error>)
Get the number of measures within the score
embed.getNoteDetails().then(function (nbMeasures) {
assert.strictEqual(nbMeasures, 5);
});
getMeasuresUuids(): Promise(<array, Error>)
Get the number of measures within the score
embed.getMeasuresUuids().then(function (measuresUuids) {
assert.strictEqual(measuresUuids, [
'05a4daec-bc78-5987-81e4-2467e234dfb2',
'08b9110b-82bb-11e5-f57c-7b0f47a6a69a',
'3c176017-31ff-cc91-7ad6-a2ea4a510200',
'833ca409-04e9-0b76-52db-105777bd7a56',
]);
});
goLeft(): Promise(<void, Error>)
Get the number of measures within the score
embed.goLeft().then(function () {
});
goRight(): Promise(<void, Error>)
Get the number of measures within the score
embed.goRight().then(function () {
});
Editor API
You can enable the editor mode by setting the mode
to edit
when creating the embed:
var embed = new Flat.Embed(container, {
embedParams: {
appId: '<your-app-id>',
mode: 'edit',
},
});
Check out an implementation example of the editor.
Events API
Events are broadcasted following actions made by the end-user or you with the JavaScript API. You can subscribe to an event using the method on
, and unsubscribe using off
. When an event includes some data, this data will be available in the first parameter of the listener callback.
Event: scoreLoaded
This event is triggered once a new score has been loaded. This event doesn't include any data.
Event: cursorPosition
This event is triggered when the position of the user's cursor changes.
{
"partIdx": 0,
"staffIdx": 1,
"voiceIdx": 0,
"measureIdx": 2,
"noteIdx": 1
}
Event: cursorContext
This event is triggered when the position or context of the user's cursor changes.
{
"isRest": false,
"isGrace": false,
"isUnpitchedPart": false,
"isPitchedPart": true,
"isPitched": true,
"isChord": true,
"isTab": false,
"hasTab": true,
"hasTabFrame": false,
"isEndOfScore": false,
"isSameLineThanNextNote": false,
"hasSlashInConnection": false,
"canTieWithNextNote": false,
"canSwitchEnharmonic": false,
"isNextRest": false,
"hasTie": false,
"isHeadTied": false
}
Event: measureDetails
This event is triggered when the position or context of the user's cursor changes.
The payload of this event is the same as the returned value from getMeasureDetails
.
{
"clef": {
"sign": "G",
"line": 2,
"clef-octave-change": -1
},
"key": {
"fifths": 0
},
"time": {
"beats": 4,
"beat-type": 4
},
"displayedTime": {
"beats": 4,
"beat-type": 4
},
"tempo": {
"qpm": 80,
"bpm": 80,
"durationType": "quarter",
"nbDots": 0
},
"transpose": {
"chromatic": "0"
},
"voicesData": {
"voices": [0],
"mainVoiceIdx": 0
}
}
Event: noteDetails
This event is triggered when the position or context of the user's cursor changes.
The payload of this event is the same as the returned value from getNoteDetails
.
{
"articulations": [],
"classicHarmony": null,
"dynamicStyle": null,
"ghostNotes": [false],
"hammerOnPullOffs": [false],
"harmony": null,
"hasArpeggio": false,
"hasGlissando": false,
"isChord": false,
"isInSlur": false,
"isRest": false,
"isTied": false,
"lines": [-2.5],
"lyrics": [],
"nbDots": 0,
"nbGraces": 0,
"ornaments": [],
"pitches": [
{
"step": "E",
"octave": 2,
"alter": 0
}
],
"technical": [],
"tupletType": null,
"wedgeType": null,
"durationType": "eighth"
}
Event: rangeSelection
This event is triggered when a range of notes is selected or the selection changed.
{
"from": {
"partIdx": 0,
"measureIdx": 1,
"staffIdx": 0,
"voiceIdx": 0,
"noteIdx": 2
},
"to": {
"partIdx": 0,
"measureIdx": 3,
"staffIdx": 0,
"voiceIdx": 0,
"noteIdx": 5
}
}
Event: fullscreen
This event is triggered when the state of the fullscreen changed. The callback will take a boolean as the first parameter that will be true
if the fullscreen mode is enabled, and false
is the display is back to normal (fullscreen exited).
Event: play
This event is triggered when you or the end-user starts the playback. This event doesn't include any data.
Event: pause
This event is triggered when you or the end-user pauses the playback. This event doesn't include any data.
Event: stop
This event is triggered when you or the end-user stops the playback. This event doesn't include any data.
Event: playbackPosition
This event is triggered when the playback slider moves. It is usually triggered at the beginning of every measure and will contain an object with information regarding the position of the playback in the score:
{
"beat": "4",
"beatType": "4",
"tempo": 120,
"currentMeasure": 1,
"timePerMeasure": 2
}