Use in Node.js
The JavaScript version of alphaTab is primarily optimized for usage in Browsers. At least the top level AlphaTabApi
which is a UI focused interface to alphaTab. But alphaTab can also be used in other
JavaScript environments like Node.js or headless JavaScript engines. The core of alphaTab is platform independent
and simply requires certain APIs to be available. With alphaSkia you can
even produce raster graphics output.
In this guide we will setup a Node.js example which is using alphaTab to render a given input file to an SVG and PNG.
1. Setup project​
The start is quite simple:
- Run
npm init
in a new directory - Run
npm install @coderline/alphatab
to install alphaTab - Create a new
index.mjs
into which our code will go .
2. The basic code structure​
In our code we will first load alphaTab and load the Score
from a given input file path:
// load alphaTab
import * as alphaTab from "@coderline/alphatab";
// needed for file load
import * as fs from 'fs';
// Load the file
const fileData = await fs.promises.readFile(process.argv[2]);
const settings = new alphaTab.Settings();
const score = alphaTab.importer.ScoreLoader.loadScoreFromBytes(
new Uint8Array(fileData),
settings
);
console.log(score.title);
And when we run this file on the command line we already have a loaded data model:

3. Using the Low Level APIs​
From this point on we will use the Low Level APIs to render the Score into an SVG. As of now there are no render engines shipped with alphaTab which would allow rendering into raster graphics so we will render it to an SVG. Feel free to open new discussions on GitHub if you have special needs for your project.
The code below is mainly taken from the Low Level API guide.
// load alphaTab
import * as alphaTab from "@coderline/alphatab";
// needed for file load
import * as fs from 'fs';
// 1. Load File
const fileData = await fs.promises.readFile(process.argv[2]);
const settings = new alphaTab.Settings();
const score = alphaTab.importer.ScoreLoader.loadScoreFromBytes(
new Uint8Array(fileData),
settings
);
// 2. Setup renderer
settings.core.engine = "svg";
const renderer = new alphaTab.rendering.ScoreRenderer(settings);
renderer.width = 1200;
// 3. Listen to Events
let svgChunks = [];
// clear on new rendering
renderer.preRender.on((isResize) => {
svgChunks = [];
});
// Since 1.3.0:
// alphaTab separates "layouting" and "rendering" of the individual
// partial results. in this case we want to render directly any partial layouted
// (in UI scenarios the rendering might be delayed to the point when partials become visible)
renderer.partialLayoutFinished.on((r) => {
renderer.renderResult(r.id);
})
// whenever the rendering of a partial is finished, we remember all individual outputs.
renderer.partialRenderFinished.on((r) => {
svgChunks.push({
svg: r.renderResult, // svg string
width: r.width,
height: r.height,
});
});
// 4. Fire off rendering, this is synchronous so we can assume all results to be available after this call.
renderer.renderScore(score, [0]);
// 5. log the SVG chunks
console.log(svgChunks.map((c) => c.svg).join("\n"));
Running the script again, we can see the output.

4. The CSS dependency​
The rendered SVG assumes some CSS specific parts to be available for the Music Font. Normally alphaTab adds
a style
tag to the page which loads the Music Symbol Font (Bravura) via as Web Font. Without these
styles the SVG will not display the symbols correctly.
The related CSS template is:
@font-face {
font-family: 'alphaTab';
src: url('path-to-font/Bravura.eot');
src: url('path-to-font/Bravura.eot?#iefix') format('embedded-opentype')
, url('path-to-font/Bravura.woff') format('woff')
, url('path-to-font/Bravura.otf') format('opentype')
, url('path-to-font/Bravura.svg#Bravura') format('svg');
font-weight: normal;
font-style: normal;
}
.at-surface * {
cursor: default;
vertical-align: top;
overflow: visible;
}
.at {
font-family: 'alphaTab';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 34px;
overflow: visible !important;
}
5. PNG rendering since 1.3.0​
While SVG can be nice for some use-cases, having PNGs as output is easier to handle in some environments.
alphaSkia
is a custom wrapper we created around Skia to have best compatibility and consistent rendering
across platforms.
First we need to install main alphaSkia library via npm install @coderline/alphaskia
.
Second we need to install the native operating system dependencies (skia libs) on which we plan to run our application:
npm install @coderline/alphaskia-windows
npm install @coderline/alphaskia-linux
npm install @coderline/alphaskia-macos
This done we can update our alphaTab code with following parts:
- We connect alphaTab and alphaSkia to enable the raster graphics rendering.
- We load the fonts we plan to use into alphaSkia.
- We change the rendering settings to produce PNG with our custom fonts.
- We combine all individual PNGs into one final one and save it.
Connecting alphaTab and alphaSkia​
To enable alphaSkia we can call alphaTab.Environment.enableAlphaSkia
where we pass in the Bravura font data to have the music notation font available.
import * as alphaTab from '@coderline/alphatab';
import * as fs from 'fs';
import * as alphaSkia from '@coderline/alphaskia';
// 1. Initialize alphaSkia
alphaTab.Environment.enableAlphaSkia(
(await fs.promises.readFile('node_modules/@coderline/alphatab/dist/font/Bravura.ttf')).buffer /* needs an ArrayBuffer */,
alphaSkia
);
Load the fonts we plan to use.​
We want to use some custom fonts in alphaTab for normal texts so we load them for later use.
import * as alphaTab from '@coderline/alphatab';
import * as fs from 'fs';
import * as alphaSkia from '@coderline/alphaskia';
// 1. Initialize alphaSkia
alphaTab.Environment.enableAlphaSkia(
(await fs.promises.readFile('node_modules/@coderline/alphatab/dist/font/Bravura.ttf')).buffer /* needs an ArrayBuffer */,
alphaSkia
);
// 2. Load custom fonts
const fontFiles = [
'font/roboto/Roboto-Regular.ttf',
'font/roboto/Roboto-Italic.ttf',
'font/roboto/Roboto-Bold.ttf',
'font/roboto/Roboto-BoldItalic.ttf',
'font/ptserif/PTSerif-Regular.ttf',
'font/ptserif/PTSerif-Italic.ttf',
'font/ptserif/PTSerif-Bold.ttf',
'font/ptserif/PTSerif-BoldItalic.ttf',
];
const fontInfo = [];
for (const fontFile of fontFiles) {
const fontData = await fs.promises.readFile(fontFile);
fontInfo.push(alphaTab.Environment.registerAlphaSkiaCustomFont(new Uint8Array(fontData)));
}
// 3. Load File
const fileData = await fs.promises.readFile(process.argv[2]);
const settings = new alphaTab.Settings();
const score = alphaTab.importer.ScoreLoader.loadScoreFromBytes(
new Uint8Array(fileData),
settings
);
Change the rendering settings to produce PNG with our custom fonts.​
import * as alphaTab from '@coderline/alphatab';
import * as fs from 'fs';
import * as alphaSkia from '@coderline/alphaskia';
// 1. Initialize alphaSkia
alphaTab.Environment.enableAlphaSkia(
(await fs.promises.readFile('node_modules/@coderline/alphatab/dist/font/Bravura.ttf')).buffer /* needs an ArrayBuffer */,
alphaSkia
);
// 2. Load custom fonts
const fontFiles = [
'path-to-fonts/roboto/Roboto-Regular.ttf',
'path-to-fonts/roboto/Roboto-Italic.ttf',
'path-to-fonts/roboto/Roboto-Bold.ttf',
'path-to-fonts/roboto/Roboto-BoldItalic.ttf',
'path-to-fonts/ptserif/PTSerif-Regular.ttf',
'path-to-fonts/ptserif/PTSerif-Italic.ttf',
'path-to-fonts/ptserif/PTSerif-Bold.ttf',
'path-to-fonts/ptserif/PTSerif-BoldItalic.ttf',
];
const fontInfo = [];
for (const fontFile of fontFiles) {
const fontData = await fs.promises.readFile(fontFile);
fontInfo.push(alphaTab.Environment.registerAlphaSkiaCustomFont(new Uint8Array(fontData)));
}
// 3. Load File
const fileData = await fs.promises.readFile(process.argv[2]);
const settings = new alphaTab.Settings();
const score = alphaTab.importer.ScoreLoader.loadScoreFromBytes(
new Uint8Array(fileData),
settings
);
// 4. Setup renderer
settings.core.engine = "skia"; // ask for skia rendering
const sansFontName = fontInfo[0].families;
const serifFontName = fontInfo[4].families;
settings.display.resources.copyrightFont.families = sansFontName;
settings.display.resources.titleFont.families = serifFontName;
settings.display.resources.subTitleFont.families = serifFontName;
settings.display.resources.wordsFont.families = serifFontName;
settings.display.resources.effectFont.families = serifFontName;
settings.display.resources.fretboardNumberFont.families = sansFontName;
settings.display.resources.tablatureFont.families = sansFontName;
settings.display.resources.graceFont.families = sansFontName;
settings.display.resources.barNumberFont.families = sansFontName;
settings.display.resources.fingeringFont.families = serifFontName;
settings.display.resources.markerFont.families = serifFontName;
const renderer = new alphaTab.rendering.ScoreRenderer(settings);
renderer.width = 1200;
Combine all individual PNGs into one final one and save it.​
Here it gets a bit more tricky: To draw the final image, we need to know the final size of it.
Technically we could collect all partial results and draw the full image at the end but this might result in quite some memory consumption as all partials have to be kept in-memory until combining them. But we want to do better by drawing any partial directly into the final image and then cleanup the partial.
Therefore we cannot directly ask for drawing when the layouting finished, instead we first wait for the layouting to be fully finished to have the total size. Then we request all partials to be rendered. This logic is implemented in the following part:
import * as alphaTab from '@coderline/alphatab';
import * as fs from 'fs';
import * as alphaSkia from '@coderline/alphaskia';
// 1. Initialize alphaSkia
alphaTab.Environment.enableAlphaSkia(
(await fs.promises.readFile('node_modules/@coderline/alphatab/dist/font/Bravura.ttf')).buffer /* needs an ArrayBuffer */,
alphaSkia
);
// 2. Load custom fonts
const fontFiles = [
'font/roboto/Roboto-Regular.ttf',
'font/roboto/Roboto-Italic.ttf',
'font/roboto/Roboto-Bold.ttf',
'font/roboto/Roboto-BoldItalic.ttf',
'font/ptserif/PTSerif-Regular.ttf',
'font/ptserif/PTSerif-Italic.ttf',
'font/ptserif/PTSerif-Bold.ttf',
'font/ptserif/PTSerif-BoldItalic.ttf',
];
const fontInfo = [];
for (const fontFile of fontFiles) {
const fontData = await fs.promises.readFile(fontFile);
fontInfo.push(alphaTab.Environment.registerAlphaSkiaCustomFont(new Uint8Array(fontData)));
}
// 3. Load File
const fileData = await fs.promises.readFile(process.argv[2]);
const settings = new alphaTab.Settings();
const score = alphaTab.importer.ScoreLoader.loadScoreFromBytes(
new Uint8Array(fileData),
settings
);
// 4. Setup renderer
settings.core.engine = "skia"; // ask for skia rendering
const sansFontName = fontInfo[0].families;
const serifFontName = fontInfo[4].families;
settings.display.resources.copyrightFont.families = sansFontName;
settings.display.resources.titleFont.families = serifFontName;
settings.display.resources.subTitleFont.families = serifFontName;
settings.display.resources.wordsFont.families = serifFontName;
settings.display.resources.effectFont.families = serifFontName;
settings.display.resources.fretboardNumberFont.families = sansFontName;
settings.display.resources.tablatureFont.families = sansFontName;
settings.display.resources.graceFont.families = sansFontName;
settings.display.resources.barNumberFont.families = sansFontName;
settings.display.resources.fingeringFont.families = serifFontName;
settings.display.resources.markerFont.families = serifFontName;
const renderer = new alphaTab.rendering.ScoreRenderer(settings);
renderer.width = 1200;
// 5. Listen to Events
// clear on new rendering
let partialIds = [];
renderer.preRender.on((isResize) => {
partialIds = [];
});
// alphaTab separates "layouting" and "rendering" of the individual
// partial results. in this case we want to render directly any partial layouted
// (in UI scenarios the rendering might be delayed to the point when partials become visible)
renderer.partialLayoutFinished.on((r) => {
partialIds.push(r.id);
});
// due to the historic behavior of `renderFinished` the name can be misleading. But this event is
// fired as soon the layouting of the song finished and all partials are available and ready for rendering.
// there is no event anymore indicating that all partials have been rendered.
const finalImageCanvas = new alphaSkia.AlphaSkiaCanvas();
renderer.renderFinished.on((r) => {
finalImageCanvas.beginRender(r.totalWidth, r.totalHeight);
// just for visibility in this demo we fill the canvas with a white background, but you can keep it transparent if you like
finalImageCanvas.color = alphaSkia.AlphaSkiaCanvas.rgbaToColor(255, 255, 255, 255);
finalImageCanvas.fillRect(0,0, r.totalWidth, r.totalHeight);
for(const id of partialIds) {
renderer.renderResult(id);
}
});
// whenever the rendering of a partial is finished, we draw it into the final image.
renderer.partialRenderFinished.on((r) => {
finalImageCanvas.drawImage(r.renderResult, r.x, r.y, r.width, r.height);
// free the partial image and release the memory used by it
r.renderResult[Symbol.dispose]();
});
// 6. Fire off rendering, this is synchronous so we can assume all results to be available after this call.
renderer.renderScore(score, [0]);
// 7. save the final image as png
const finalImage = finalImageCanvas.endRender();
const outputFilePath = process.argv[2] + '.png';
// encode as PNG and free the memory of the final image
const png = finalImage.toPng();
finalImage[Symbol.dispose]();
finalImageCanvas[Symbol.dispose]();
await fs.promises.writeFile(outputFilePath, new Uint8Array(png));
