Do you have friends who play musical instruments, and you want to get in on the fun? Music is made up of a variety of symbols, the most basic of which are the staff, the clefs and the notes. Theoretically is simple to draw those symbols, but is very complex to understand them and create a melody. If you're a web developer and you need to work in some music project that needs to render music notation on the browser, then today is your lucky day as you won't need to write your own script to achieve it. To draw a music sheet with Javascript, we recommend you to use the awesome VexFlow library.
VexFlow is an open-source web-based music notation rendering API. It is written completely in JavaScript, and runs right in the browser. VexFlow supports HTML5 Canvas and SVG, and runs on all modern browsers. VexFlow has support for standard music, guitar tablature, and percussion notation. While it is a goal to support the vast majority of western music notation, VexFlow also supports a few alternative elements, such as, microtonal notation.
In this tutorial we will cover all the basic tasks that you can do with the VexFlow library in Javascript.
Requirements
As mentioned previously, we are going to use VexFlow 2 to achieve our goal of creating awesome music sheets with Javascript. You can get this library using NPM executing the following command in the Node command prompt:
npm install vexflow
Or you can obviously get a .js file distribution (debug or minified, the releases are published and updated automatically in unpkg) in the official repository in Github of VexFlow here and include it in your document with a script tag:
<script src="path-to-scripts/vexflow.min.js"></script>
This library is open source released under the MIT license.
Getting started
In the same way that the official tutorial of VexFlow say, we expect you to have some JavaScript programming experience and a basic understanding of music notation terminology, otherwise you will want to cry while trying to implement it.
The generation of a music sheet will be basically based in Staves. The VexFlow API allow you to add easy Staves with some customizable options:
Note
In most of our examples, we are going to work with SVG. Most scenarios where scalability is a plus are going to be better served using SVGs than <canvas>
. High fidelity complex graphics like building and engineering diagrams, music sheets, biological diagrams, etc. are examples of this. However, you're free to work in the way you want as VexFlow allows it.
1. Creating a stave with Clef and time signature
A clef is a musical symbol that indicates the pitch of the written notes. It is placed on one of the lines at the beginning of the stave, it indicates the name and pitch of the notes on that line. Only one clef that references a note in a space rather than on a line has ever been used.
To add the clef and a time signature, you need to create as first a stave where you want to put it. As we said previously you can work with VexFlow using either canvas or SVG:
A. Canvas
As first you need to create a canvas
element with HTML where Vex Flow will work:
<canvas id="some-canvas-id"></canvas>
Then create a new instance of the Vex Flow renderer. The renderer expects as first argument the canvas
element that you have previously created and as second parameter the Canvas type stored as a constant in the VF.Renderer.Backends.CANVAS
. Then use the resize method of the render to provide the dimensions of the Canvas element. To create a stave, you will need the context of the Canvas that you're working with, retrieve the context from the renderer object (not from the canvas directly) using the getContext
method and store it into a variable.
As final step, create a new instance of the VX Stave class. This method expects 3 arguments, the first will be an integer that specifies the location of the stave in the x-axis of the Canvas, the second argument is an integer too that specifies the location of the stave in the y-axis and the third argument, an integer too that specifies the width of the stave. From the created object, you will be able to invoke the addClef
method (that expects as first argument a string with the type of clef) and the addTimeSignature
(that expects as first argument the time signature identifier e.g 4/4 , 4/2 etc). Then use the method setContext
that expects as argument the context of the renderer object previously stored in a variable and from it execute the draw
method to draw the stave.
VF = Vex.Flow;
// We created an object to store the information about the workspace
var WorkspaceInformation = {
// The <canvas> element in which you're going to work
canvas: document.getElementById("some-canvas-id"),
// Vex creates a canvas with specific dimensions
canvasWidth: 500,
canvasHeight: 500
};
// Create a renderer with Canvas
var renderer = new VF.Renderer(
WorkspaceInformation.canvas,
VF.Renderer.Backends.CANVAS
);
// Use the renderer to give the dimensions to the canvas
renderer.resize(WorkspaceInformation.canvasWidth, WorkspaceInformation.canvasHeight);
// Expose the context of the renderer
var context = renderer.getContext();
// And give some style to our canvas
context.setFont("Arial", 10, "").setBackgroundFillStyle("#eed");
/**
* Creating a new stave
*/
// Create a stave of width 400 at position x10, y40 on the canvas.
var stave = new VF.Stave(10, 40, 400);
// Add a clef and time signature.
stave.addClef("treble").addTimeSignature("4/4");
// Set the context of the stave our previous exposed context and execute the method draw !
stave.setContext(context).draw();
B. SVG
As first you need to create a div
element with HTML where Vex Flow will work:
<div id="some-div-id"></div>
Then create a new instance of the Vex Flow renderer. The renderer expects as first argument the div
element that you have previously created and as second parameter the SVG type stored as a constant in the VF.Renderer.Backends.SVG
. Then use the resize method of the render to provide the dimensions of the SVG element. To create a stave, you will need the context of the SVG that you're working with, retrieve the context from the renderer object using the getContext
method and store it into a variable.
As final step, create a new instance of the VX Stave class. This method expects 3 arguments, the first will be an integer that specifies the location of the stave in the x-axis of the SVG, the second argument is an integer too that specifies the location of the stave in the y-axis and the third argument, an integer too that specifies the width of the stave. From the created object, you will be able to invoke the addClef
method (that expects as first argument a string with the type of clef) and the addTimeSignature
(that expects as first argument the time signature identifier e.g 4/4 , 4/2 etc). Then use the method setContext
that expects as argument the context of the renderer object previously stored in a variable and from it execute the draw
method to draw the stave.
VF = Vex.Flow;
// We created an object to store the information about the workspace
var WorkspaceInformation = {
// The div in which you're going to work
div: document.getElementById("some-div-id"),
// Vex creates a svg with specific dimensions
canvasWidth: 500,
canvasHeight: 500
};
// Create a renderer with SVG
var renderer = new VF.Renderer(
WorkspaceInformation.div,
VF.Renderer.Backends.SVG
);
// Use the renderer to give the dimensions to the SVG
renderer.resize(WorkspaceInformation.canvasWidth, WorkspaceInformation.canvasHeight);
// Expose the context of the renderer
var context = renderer.getContext();
// And give some style to our SVG
context.setFont("Arial", 10, "").setBackgroundFillStyle("#eed");
/**
* Creating a new stave
*/
// Create a stave of width 400 at position x10, y40 on the SVG.
var stave = new VF.Stave(10, 40, 400);
// Add a clef and time signature.
stave.addClef("treble").addTimeSignature("4/4");
// Set the context of the stave our previous exposed context and execute the method draw !
stave.setContext(context).draw();
Although it seems complicated at first view, it isn't, you just need to be patient, read carefully all the examples, practice and you're ready to go. The execution of any of the previous example (SVG or Canvas) will generate the following stave:
You will want (and need) to draw many staves (with different information) within a single workspace (SVG or Canvas) and you can easily achieve it if you handle the location values correctly. Analyze the following example to see how to render many staves in a single workspace:
<div id="boo"></div>
<script>
VF = Vex.Flow;
// Create an SVG renderer and attach it to the DIV element named "boo".
var div = document.getElementById("boo")
var renderer = new VF.Renderer(div, VF.Renderer.Backends.SVG);
var canvasDimensions = {
width: 820,
height: 300
};
// Configure the rendering context.
renderer.resize(canvasDimensions.width, canvasDimensions.height);
var context = renderer.getContext();
context.setFont("Arial", 10, "").setBackgroundFillStyle("#eed");
/**
* Left side staves
*/
// Create a stave of width 400 at position x10, y40 on the canvas.
var stave = new VF.Stave(10, 40, 400);
// Add a clef and time signature.
stave.addClef("treble").addTimeSignature("4/4");
// Connect it to the rendering context and draw!
stave.setContext(context).draw();
// Create a stave with soprano clef width 400 at position x10, y120 on the canvas.
var stave2 = new VF.Stave(10, 120, 400);
stave2.addClef("soprano").addTimeSignature("4/4");
stave2.setContext(context).draw();
/**
* Right side staves
*/
// Create a stave with baritone-f clef width 400 at position x420, y40 on the canvas.
var stave3 = new VF.Stave(420, 40, 400);
stave3.addClef("baritone-f").addTimeSignature("4/2");
stave3.setContext(context).draw();
// Create a stave with mezzo-soprano clef width 400 at position x420, y120 on the canvas without time signature
var stave4 = new VF.Stave(420, 120, 400);
stave4.addClef("mezzo-soprano");
stave4.setContext(context).draw();
</script>
The execution of the previous script, should generate the following content on the canvas:
2. Writing notes to a stave
A StaveNote is a group of note heads representing a chord. It can consist of one or more notes, with or without a stem and flag. A sequence of notes is represented by a Voice, and multiple voices can be grouped within a VoiceGroup.
VF = Vex.Flow;
// We created an object to store the information about the workspace
var WorkspaceInformation = {
// The div in which you're going to work
div: document.getElementById("some-div-id"),
// Vex creates a svg with specific dimensions
canvasWidth: 500,
canvasHeight: 500
};
// Create a renderer with SVG
var renderer = new VF.Renderer(
WorkspaceInformation.div,
VF.Renderer.Backends.SVG
);
// Use the renderer to give the dimensions to the SVG
renderer.resize(WorkspaceInformation.canvasWidth, WorkspaceInformation.canvasHeight);
// Expose the context of the renderer
var context = renderer.getContext();
// And give some style to our SVG
context.setFont("Arial", 10, "").setBackgroundFillStyle("#eed");
/**
* Creating a new stave
*/
// Create a stave of width 400 at position x10, y40 on the SVG.
var stave = new VF.Stave(10, 40, 400);
// Add a clef and time signature.
stave.addClef("treble").addTimeSignature("4/4");
// Set the context of the stave our previous exposed context and execute the method draw !
stave.setContext(context).draw();
/**
* Draw notes
*/
var notes = [
// A quarter-note C.
new VF.StaveNote({clef: "treble", keys: ["c/4"], duration: "q" }),
// A quarter-note D.
new VF.StaveNote({clef: "treble", keys: ["d/4"], duration: "q" }),
// A quarter-note rest. Note that the key (b/4) specifies the vertical
// position of the rest.
new VF.StaveNote({clef: "treble", keys: ["b/4"], duration: "qr" }),
// A C-Major chord.
new VF.StaveNote({clef: "treble", keys: ["c/4", "e/4", "g/4"], duration: "q" })
];
// Create a voice in 4/4 and add above notes
var voice = new VF.Voice({num_beats: 4, beat_value: 4});
voice.addTickables(notes);
// Format and justify the notes to 400 pixels.
var formatter = new VF.Formatter().joinVoices([voice]).format([voice], 400);
// Render voice
voice.draw(context, stave);
The execution of the previous script, should generate the following content on the canvas:
3. Creating a guitar tablature
For guitars and other fretted instruments, it is possible to notate tablature in place of ordinary notes. In this case, a TAB-sign is often written instead of a clef. The number of lines of the stave is not necessarily five: one line is used for each string of the instrument
VF = Vex.Flow;
// Create an SVG renderer and attach it to the DIV element named "boo".
var div = document.getElementById("boo");
// Create a renderer that uses SVG
var renderer = new VF.Renderer(div, VF.Renderer.Backends.SVG);
// Configure the rendering context.
renderer.resize(500, 500);
var context = renderer.getContext();
// Create a tab stave of width 400 at position 10, 40 on the canvas.
var stave = new VF.TabStave(10, 40, 400);
stave.addClef("tab").setContext(context).draw();
var notes = [
// A single note
new VF.TabNote({
positions: [
{str: 3, fret: 7}
],
duration: "q"
}),
// A chord with the note on the 3rd string bent
new VF.TabNote({
positions: [
{str: 2, fret: 10},
{str: 3, fret: 9},
{str: 4, fret: 8},
],
duration: "q"
}).addModifier(
new VF.Bend("Full"), 1
),
// A single note with a harsh vibrato
new VF.TabNote({
positions: [{str: 2, fret: 5}],
duration: "h"
}).addModifier(
new VF.Vibrato().setHarsh(true).setVibratoWidth(50), 0
)
];
VF.Formatter.FormatAndDraw(context, stave, notes);
The execution of the previous script should generate the following stave:
Using EasyScore
If you already reach this point of the article, then you probably have said to yourself "damn that's a lot of code", that's why VexFlow offers a utility to make the writing of musical notation much simpler name EasyScore. EasyScore is a tiny language that you can use to generate all the VexFlow elements necessary for a sequence of musical notation. The language supports notes, accidentals, beams, dots, tuplets, and other common notational elements.
For example, the step 3 of the getting started tutorial could be replaced with the following code using EasyScore:
// Create an SVG renderer and attach it to the DIV element named "boo".
var vf = new Vex.Flow.Factory({
renderer: {
selector: 'some-div-id'
}
});
var score = vf.EasyScore();
var system = vf.System();
system.addStave({
voices: [
score.voice(score.notes('C4/q, d4, b4/r, (C4 E4 G4)/q'))
]
}).addClef('treble').addTimeSignature('4/4');
vf.draw();
It's clearly less code and in some way easy to read, don't you think? The execution of the previous code would produce the same result as in step 3:
EasyScore even allows to add another stave which renders two more voices:
// Create an SVG renderer and attach it to the DIV element named "boo".
var vf = new Vex.Flow.Factory({
renderer: {
selector: 'boo',
height: 800,
width: 800
}
});
var score = vf.EasyScore();
var system = vf.System();
system.addStave({
voices: [
score.voice(score.notes('C#5/q, B4, A4, G#4', {
stem: 'up'
})),
score.voice(score.notes('C#4/h, C#4', {
stem: 'down'
}))
]
}).addClef('treble').addTimeSignature('4/4');
system.addConnector();
system.addStave({
voices: [
score.voice(score.notes('C#3/q, B2, A2/8, B2, C#3, D3', {
clef: 'bass',
stem: 'up'
})),
score.voice(score.notes('C#2/h, C#2', {
clef: 'bass',
stem: 'down'
}))
]
}).addClef('bass').addTimeSignature('4/4');
vf.draw();
Which would generate:
Sandbox
If you're willing to try VexFlow in the Browser, they offer a fiddle that uses always the latest build of the library, check it out here. Finally we encourage the reading of the Wiki of VexFlow.
Happy coding !