@@ -7,34 +7,33 @@ import { SinceBadge } from '@site/src/components/SinceBadge';
77<SinceBadge since = " 1.2.3" />
88
99The JavaScript version of alphaTab is primarily optimized for usage in Browsers. At least the top level ` AlphaTabApi `
10- which is a UI focused interface to alphaTab. But this does not mean that alphaTab cannot be used in other
10+ which is a UI focused interface to alphaTab. But alphaTab can also be used in other
1111JavaScript environments like Node.js or headless JavaScript engines. The core of alphaTab is platform independent
12- and simply requires certain APIs to be available. If you are able to make a HTML5 canvas compatible API available
13- to the runtime (e.g. via node-canvas, skia-canvas) the built-in HTML5 renderer might also work.
12+ and simply requires certain APIs to be available. With [ alphaSkia ] ( https://github.com/CoderLine/alphaSkia ) you can
13+ even produce raster graphics output.
1414
15- In this guide we will setup a Node.js example which is using alphaTab to render a given input file to an SVG.
16- This SVG could then be sent to a browser for display.
15+ 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.
1716
1817## 1. Setup project
1918
2019The start is quite simple:
2120
22211 . Run ` npm init ` in a new directory
23222 . Run ` npm install @coderline/alphatab ` to install alphaTab
24- 3 . Create a new index.js into which our code will go .
23+ 3 . Create a new ` index.mjs ` into which our code will go .
2524
2625## 2. The basic code structure
2726
2827In our code we will first load alphaTab and load the ` Score ` from a given input file path:
2928
3029``` js
3130// load alphaTab
32- const alphaTab = require ( " @coderline/alphatab" ) ;
31+ import * as alphaTab from " @coderline/alphatab" ;
3332// needed for file load
34- const fs = require ( " fs " ) ;
33+ import * as fs from ' fs ' ;
3534
3635// Load the file
37- const fileData = fs .readFileSync (process .argv [2 ]);
36+ const fileData = await fs .promises . readFile (process .argv [2 ]);
3837const settings = new alphaTab.Settings ();
3938const score = alphaTab .importer .ScoreLoader .loadScoreFromBytes (
4039 new Uint8Array (fileData),
@@ -56,11 +55,13 @@ special needs for your project.
5655The code below is mainly taken from the [ Low Level API guide] ( /docs/guides/lowlevel-apis ) .
5756
5857``` js
59- const alphaTab = require (" @coderline/alphatab" );
60- const fs = require (" fs" );
58+ // load alphaTab
59+ import * as alphaTab from " @coderline/alphatab" ;
60+ // needed for file load
61+ import * as fs from ' fs' ;
6162
6263// 1. Load File
63- const fileData = fs .readFileSync (process .argv [2 ]);
64+ const fileData = await fs .promises . readFile (process .argv [2 ]);
6465const settings = new alphaTab.Settings ();
6566const score = alphaTab .importer .ScoreLoader .loadScoreFromBytes (
6667 new Uint8Array (fileData),
@@ -74,33 +75,41 @@ renderer.width = 1200;
7475
7576// 3. Listen to Events
7677let svgChunks = [];
78+
79+ // clear on new rendering
7780renderer .preRender .on ((isResize ) => {
78- svgChunks = []; // clear on new rendering
81+ svgChunks = [];
7982});
83+
84+ // Since 1.3.0:
85+ // alphaTab separates "layouting" and "rendering" of the individual
86+ // partial results. in this case we want to render directly any partial layouted
87+ // (in UI scenarios the rendering might be delayed to the point when partials become visible)
88+ renderer .partialLayoutFinished .on ((r ) => {
89+ renderer .renderResult (r .id );
90+ })
91+
92+ // whenever the rendering of a partial is finished, we remember all individual outputs.
8093renderer .partialRenderFinished .on ((r ) => {
8194 svgChunks .push ({
8295 svg: r .renderResult , // svg string
8396 width: r .width ,
8497 height: r .height ,
8598 });
8699});
87- renderer .renderFinished .on ((r ) => {
88- displayResult (svgChunks, r .totalWidth , r .totalHeight );
89- });
90100
91- // 4. Fire off rendering
101+ // 4. Fire off rendering, this is synchronous so we can assume all results to be available after this call.
92102renderer .renderScore (score, [0 ]);
93103
94- // log the SVG chunks
104+ // 5. log the SVG chunks
95105console .log (svgChunks .map ((c ) => c .svg ).join (" \n " ));
96106```
97107
98108Running the script again, we can see the output.
99109
100110<img src = " /img/guides/nodejs/svg.png" />
101111
102-
103- ### 4. The CSS dependency
112+ ## 4. The CSS dependency
104113The rendered SVG assumes some CSS specific parts to be available for the Music Font. Normally alphaTab adds
105114a ` style ` tag to the page which loads the Music Symbol Font (Bravura) via as Web Font. Without these
106115styles the SVG will not display the symbols correctly.
@@ -138,3 +147,266 @@ The related CSS template is:
138147 overflow : visible !important ;
139148}
140149```
150+
151+ ## 5. PNG rendering <SinceBadge since = " 1.3.0" inline = " true" />
152+
153+ While SVG can be nice for some use-cases, having PNGs as output is easier to handle in some environments.
154+ [ ` alphaSkia ` ] ( https://github.com/CoderLine/alphaSkia ) is a custom wrapper we created around [ Skia] ( https://skia.org/ ) to have best compatibility and consistent rendering
155+ across platforms.
156+
157+ First we need to install main alphaSkia library via ` npm install @coderline/alphaskia ` .
158+ Second we need to install the native operating system dependencies (skia libs) on which we plan to run our application:
159+
160+ * ` npm install @coderline/alphaskia-windows `
161+ * ` npm install @coderline/alphaskia-linux `
162+ * ` npm install @coderline/alphaskia-macos `
163+
164+ This done we can update our alphaTab code with following parts:
165+
166+ 1 . We connect alphaTab and alphaSkia to enable the raster graphics rendering.
167+ 2 . We load the fonts we plan to use into alphaSkia.
168+ 3 . We change the rendering settings to produce PNG with our custom fonts.
169+ 3 . We combine all individual PNGs into one final one and save it.
170+
171+
172+ ### Connecting alphaTab and alphaSkia
173+
174+ To enable alphaSkia we can call ` alphaTab.Environment.enableAlphaSkia ` where we pass in the Bravura font data to have the music notation font available.
175+
176+ ``` js
177+ import * as alphaTab from ' @coderline/alphatab' ;
178+ import * as fs from ' fs' ;
179+ import * as alphaSkia from ' @coderline/alphaskia' ;
180+
181+ // 1. Initialize alphaSkia
182+ alphaTab .Environment .enableAlphaSkia (
183+ (await fs .promises .readFile (' node_modules/@coderline/alphatab/dist/font/Bravura.ttf' )).buffer /* needs an ArrayBuffer */ ,
184+ alphaSkia
185+ );
186+ ```
187+
188+ ### Load the fonts we plan to use.
189+
190+ We want to use some custom fonts in alphaTab for normal texts so we load them for later use.
191+
192+ ``` js
193+ import * as alphaTab from ' @coderline/alphatab' ;
194+ import * as fs from ' fs' ;
195+ import * as alphaSkia from ' @coderline/alphaskia' ;
196+
197+ // 1. Initialize alphaSkia
198+ alphaTab .Environment .enableAlphaSkia (
199+ (await fs .promises .readFile (' node_modules/@coderline/alphatab/dist/font/Bravura.ttf' )).buffer /* needs an ArrayBuffer */ ,
200+ alphaSkia
201+ );
202+
203+ // 2. Load custom fonts
204+ const fontFiles = [
205+ ' font/roboto/Roboto-Regular.ttf' ,
206+ ' font/roboto/Roboto-Italic.ttf' ,
207+ ' font/roboto/Roboto-Bold.ttf' ,
208+ ' font/roboto/Roboto-BoldItalic.ttf' ,
209+ ' font/ptserif/PTSerif-Regular.ttf' ,
210+ ' font/ptserif/PTSerif-Italic.ttf' ,
211+ ' font/ptserif/PTSerif-Bold.ttf' ,
212+ ' font/ptserif/PTSerif-BoldItalic.ttf' ,
213+ ];
214+
215+ const fontInfo = [];
216+ for (const fontFile of fontFiles) {
217+ const fontData = await fs .promises .readFile (fontFile);
218+ fontInfo .push (alphaTab .Environment .registerAlphaSkiaCustomFont (new Uint8Array (fontData)));
219+ }
220+
221+ // 3. Load File
222+ const fileData = await fs .promises .readFile (process .argv [2 ]);
223+ const settings = new alphaTab.Settings ();
224+ const score = alphaTab .importer .ScoreLoader .loadScoreFromBytes (
225+ new Uint8Array (fileData),
226+ settings
227+ );
228+ ```
229+
230+ ### Change the rendering settings to produce PNG with our custom fonts.
231+
232+ ``` js
233+ import * as alphaTab from ' @coderline/alphatab' ;
234+ import * as fs from ' fs' ;
235+ import * as alphaSkia from ' @coderline/alphaskia' ;
236+
237+ // 1. Initialize alphaSkia
238+ alphaTab .Environment .enableAlphaSkia (
239+ (await fs .promises .readFile (' node_modules/@coderline/alphatab/dist/font/Bravura.ttf' )).buffer /* needs an ArrayBuffer */ ,
240+ alphaSkia
241+ );
242+
243+ // 2. Load custom fonts
244+ const fontFiles = [
245+ ' path-to-fonts/roboto/Roboto-Regular.ttf' ,
246+ ' path-to-fonts/roboto/Roboto-Italic.ttf' ,
247+ ' path-to-fonts/roboto/Roboto-Bold.ttf' ,
248+ ' path-to-fonts/roboto/Roboto-BoldItalic.ttf' ,
249+ ' path-to-fonts/ptserif/PTSerif-Regular.ttf' ,
250+ ' path-to-fonts/ptserif/PTSerif-Italic.ttf' ,
251+ ' path-to-fonts/ptserif/PTSerif-Bold.ttf' ,
252+ ' path-to-fonts/ptserif/PTSerif-BoldItalic.ttf' ,
253+ ];
254+
255+ const fontInfo = [];
256+ for (const fontFile of fontFiles) {
257+ const fontData = await fs .promises .readFile (fontFile);
258+ fontInfo .push (alphaTab .Environment .registerAlphaSkiaCustomFont (new Uint8Array (fontData)));
259+ }
260+
261+ // 3. Load File
262+ const fileData = await fs .promises .readFile (process .argv [2 ]);
263+ const settings = new alphaTab.Settings ();
264+ const score = alphaTab .importer .ScoreLoader .loadScoreFromBytes (
265+ new Uint8Array (fileData),
266+ settings
267+ );
268+
269+ // 4. Setup renderer
270+ settings .core .engine = " skia" ; // ask for skia rendering
271+
272+ const sansFontName = fontInfo[0 ].families ;
273+ const serifFontName = fontInfo[4 ].families ;
274+
275+ settings .display .resources .copyrightFont .families = sansFontName;
276+ settings .display .resources .titleFont .families = serifFontName;
277+ settings .display .resources .subTitleFont .families = serifFontName;
278+ settings .display .resources .wordsFont .families = serifFontName;
279+ settings .display .resources .effectFont .families = serifFontName;
280+ settings .display .resources .fretboardNumberFont .families = sansFontName;
281+ settings .display .resources .tablatureFont .families = sansFontName;
282+ settings .display .resources .graceFont .families = sansFontName;
283+ settings .display .resources .barNumberFont .families = sansFontName;
284+ settings .display .resources .fingeringFont .families = serifFontName;
285+ settings .display .resources .markerFont .families = serifFontName;
286+
287+ const renderer = new alphaTab.rendering.ScoreRenderer (settings);
288+ renderer .width = 1200 ;
289+ ```
290+
291+ ### Combine all individual PNGs into one final one and save it.
292+
293+ Here it gets a bit more tricky: To draw the final image, we need to know the final size of it.
294+
295+ Technically we could collect all partial results and draw the full image at the end but this might result
296+ in quite some memory consumption as all partials have to be kept in-memory until combining them. But we want to do better by
297+ drawing any partial directly into the final image and then cleanup the partial.
298+
299+ 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.
300+ Then we request all partials to be rendered. This logic is implemented in the following part:
301+
302+
303+ ``` js
304+ import * as alphaTab from ' @coderline/alphatab' ;
305+ import * as fs from ' fs' ;
306+ import * as alphaSkia from ' @coderline/alphaskia' ;
307+
308+ // 1. Initialize alphaSkia
309+ alphaTab .Environment .enableAlphaSkia (
310+ (await fs .promises .readFile (' node_modules/@coderline/alphatab/dist/font/Bravura.ttf' )).buffer /* needs an ArrayBuffer */ ,
311+ alphaSkia
312+ );
313+
314+ // 2. Load custom fonts
315+ const fontFiles = [
316+ ' font/roboto/Roboto-Regular.ttf' ,
317+ ' font/roboto/Roboto-Italic.ttf' ,
318+ ' font/roboto/Roboto-Bold.ttf' ,
319+ ' font/roboto/Roboto-BoldItalic.ttf' ,
320+ ' font/ptserif/PTSerif-Regular.ttf' ,
321+ ' font/ptserif/PTSerif-Italic.ttf' ,
322+ ' font/ptserif/PTSerif-Bold.ttf' ,
323+ ' font/ptserif/PTSerif-BoldItalic.ttf' ,
324+ ];
325+
326+ const fontInfo = [];
327+ for (const fontFile of fontFiles) {
328+ const fontData = await fs .promises .readFile (fontFile);
329+ fontInfo .push (alphaTab .Environment .registerAlphaSkiaCustomFont (new Uint8Array (fontData)));
330+ }
331+
332+ // 3. Load File
333+ const fileData = await fs .promises .readFile (process .argv [2 ]);
334+ const settings = new alphaTab.Settings ();
335+ const score = alphaTab .importer .ScoreLoader .loadScoreFromBytes (
336+ new Uint8Array (fileData),
337+ settings
338+ );
339+
340+ // 4. Setup renderer
341+ settings .core .engine = " skia" ; // ask for skia rendering
342+
343+ const sansFontName = fontInfo[0 ].families ;
344+ const serifFontName = fontInfo[4 ].families ;
345+
346+ settings .display .resources .copyrightFont .families = sansFontName;
347+ settings .display .resources .titleFont .families = serifFontName;
348+ settings .display .resources .subTitleFont .families = serifFontName;
349+ settings .display .resources .wordsFont .families = serifFontName;
350+ settings .display .resources .effectFont .families = serifFontName;
351+ settings .display .resources .fretboardNumberFont .families = sansFontName;
352+ settings .display .resources .tablatureFont .families = sansFontName;
353+ settings .display .resources .graceFont .families = sansFontName;
354+ settings .display .resources .barNumberFont .families = sansFontName;
355+ settings .display .resources .fingeringFont .families = serifFontName;
356+ settings .display .resources .markerFont .families = serifFontName;
357+
358+ const renderer = new alphaTab.rendering.ScoreRenderer (settings);
359+ renderer .width = 1200 ;
360+
361+ // 5. Listen to Events
362+
363+ // clear on new rendering
364+ let partialIds = [];
365+ renderer .preRender .on ((isResize ) => {
366+ partialIds = [];
367+ });
368+
369+ // alphaTab separates "layouting" and "rendering" of the individual
370+ // partial results. in this case we want to render directly any partial layouted
371+ // (in UI scenarios the rendering might be delayed to the point when partials become visible)
372+ renderer .partialLayoutFinished .on ((r ) => {
373+ partialIds .push (r .id );
374+ });
375+
376+ // due to the historic behavior of `renderFinished` the name can be misleading. But this event is
377+ // fired as soon the layouting of the song finished and all partials are available and ready for rendering.
378+ // there is no event anymore indicating that all partials have been rendered.
379+ const finalImageCanvas = new alphaSkia.AlphaSkiaCanvas ();
380+ renderer .renderFinished .on ((r ) => {
381+ finalImageCanvas .beginRender (r .totalWidth , r .totalHeight );
382+ // just for visibility in this demo we fill the canvas with a white background, but you can keep it transparent if you like
383+ finalImageCanvas .color = alphaSkia .AlphaSkiaCanvas .rgbaToColor (255 , 255 , 255 , 255 );
384+ finalImageCanvas .fillRect (0 ,0 , r .totalWidth , r .totalHeight );
385+
386+ for (const id of partialIds) {
387+ renderer .renderResult (id);
388+ }
389+ });
390+
391+ // whenever the rendering of a partial is finished, we draw it into the final image.
392+ renderer .partialRenderFinished .on ((r ) => {
393+ finalImageCanvas .drawImage (r .renderResult , r .x , r .y , r .width , r .height );
394+
395+ // free the partial image and release the memory used by it
396+ r .renderResult [Symbol .dispose ]();
397+ });
398+
399+ // 6. Fire off rendering, this is synchronous so we can assume all results to be available after this call.
400+ renderer .renderScore (score, [0 ]);
401+
402+ // 7. save the final image as png
403+ const finalImage = finalImageCanvas .endRender ();
404+ const outputFilePath = process .argv [2 ] + ' .png' ;
405+ // encode as PNG and free the memory of the final image
406+ const png = finalImage .toPng ();
407+ finalImage[Symbol .dispose ]();
408+ finalImageCanvas[Symbol .dispose ]();
409+ await fs .promises .writeFile (outputFilePath, new Uint8Array (png));
410+ ```
411+
412+ <img src = " /img/guides/nodejs/png.png" />
0 commit comments