jet2jet / js-synthesizer

Synthesizer library for web-based JS program, using with Web Audio or etc.
BSD 3-Clause "New" or "Revised" License
58 stars 8 forks source link
javascript midi midi-player soundfont synth webaudio

NPM version

js-synthesizer

js-synthesizer is a library that generates audio data (frames). WebAssembly (wasm) version of FluidSynth is used as a core synthesizer engine.

Demo

https://www.pg-fl.jp/music/js-synthesizer/index.en.htm

Install

npm install --save js-synthesizer

Usage

From main thread

Copies dist/js-synthesizer.js (or dist/js-synthesizer.min.js) and externals/libfluidsynth-2.3.0.js (libfluidsynth JS file) to your project, and writes <script> tags as following order:

<script src="https://github.com/jet2jet/js-synthesizer/raw/main/libfluidsynth-2.3.0.js"></script>
<script src="https://github.com/jet2jet/js-synthesizer/raw/main/js-synthesizer.js"></script>

If you want to use Soundfont version 3 (.sf3) files, use externals/libfluidsynth-2.3.0-with-libsndfile.js instead of externals/libfluidsynth-2.3.0.js.

When scripts are available, please check whether waitForReady resolves.

JSSynth.waitForReady().then(loadSynthesizer);

function loadSynthesizer() {
    // process with JSSynth...
}

After initialized, you can use APIs via JSSynth namespace object.

// Prepare the AudioContext instance
var context = new AudioContext();
var synth = new JSSynth.Synthesizer();
synth.init(context.sampleRate);

// Create AudioNode (ScriptProcessorNode) to output audio data
var node = synth.createAudioNode(context, 8192); // 8192 is the frame count of buffer
node.connect(context.destination);

// Load your SoundFont data (sfontBuffer: ArrayBuffer)
synth.loadSFont(sfontBuffer).then(function () {
    // Load your SMF file data (smfBuffer: ArrayBuffer)
    return synth.addSMFDataToPlayer(smfBuffer);
}).then(function () {
    // Play the loaded SMF data
    return synth.playPlayer();
}).then(function () {
    // Wait for finishing playing
    return synth.waitForPlayerStopped();
}).then(function () {
    // Wait for all voices stopped
    return synth.waitForVoicesStopped();
}).then(function () {
    // Releases the synthesizer
    synth.close();
}, function (err) {
    console.log('Failed:', err);
    // Releases the synthesizer
    synth.close();
});

(Above example uses Web Audio API, but you can use Synthesizer without Web Audio, by using render() method.)

js-synthesizer is built as UMD module. If you prefer to load js-synthesizer as a CommonJS / ES module, you can use import statement such as import * as JSSynth from 'js-synthesizer' by using bundlers (such as webpack).

Notes:

With AudioWorklet

js-synthesizer supports AudioWorklet process via dist/js-synthesizer.worklet.js (or dist/js-synthesizer.worklet.min.js). You can load js-synthesizer on the AudioWorklet as the following code:

var context = new AudioContext();
context.audioWorklet.addModule('libfluidsynth-2.3.0.js')
    .then(function () {
        return context.audioWorklet.addModule('js-synthesizer.worklet.js');
    })
    .then(function () {
        // Create the synthesizer instance for AudioWorkletNode
        var synth = new JSSynth.AudioWorkletNodeSynthesizer();
        synth.init(context.sampleRate);
        // You must create AudioWorkletNode before using other methods
        // (This is because the message port is not available until the
        // AudioWorkletNode is created)
        audioNode = synth.createAudioNode(context);
        audioNode.connect(context.destination); // or another node...
        // After node creation, you can use Synthesizer methods
        return synth.loadSFont(sfontBuffer).then(function () {
            return synth.addSMFDataToPlayer(smfBuffer);
        }).then(function () {
            return synth.playPlayer();
        }).then(function () {
            ...
        });
    });

With Web Worker

js-synthesizer and libfluidsynth can be executed on a Web Worker. Executing on a Web Worker prevents from blocking main thread while rendering.

To use js-synthesizer on a Web Worker, simply call importScripts as followings:

self.importScripts('libfluidsynth-2.3.0.js');
self.importScripts('js-synthesizer.js');

Note that since the Web Audio is not supported on the Web Worker, the APIs/methods related to the Web Audio will not work. If you want to use both Web Worker and AudioWorklet, you should implement AudioWorkletProcessor manually as followings:

API

Creation of Synthesizer instance

These classes implement the interface named JSSynth.ISynthesizer.

Creation of Sequencer instance

The Sequencer instance is created only via following methods:

Using hook / handle MIDI-related event data with user-defined callback

You can hook MIDI events posted by player. For JSSynth.Synthesizer instance, use hookPlayerMIDIEvents method as followings:

syn.hookPlayerMIDIEvents(function (s, type, event) {
    // hook '0xC0' event (Program Change event)
    if (type === 0xC0) {
        // if the 'program' value is 0, use another SoundFont
        if (event.getProgram() === 0) {
            syn.midiProgramSelect(event.getChannel(), secondSFont, 0, 0);
            return true;
        }
    }
    // return false to use default processings for other events
    return false;
});

For JSSynth.AudioWorkletNodeSynthesizer instance, use hookPlayerMIDIEventsByName as followings:

// We must add method to AudioWorkletGlobalScope to pass to another module.
AudioWorkletGlobalScope.myHookPlayerEvents = function (s, type, event, data) {
    if (type === 0xC0) {
        if (event.getProgram() === 0) {
            // 'secondSFont' will be passed from 'hookPlayerMIDIEventsByName'
            s.midiProgramSelect(event.getChannel(), data.secondSFont, 0, 0);
            return true;
        }
    }
    return false;
};
// before use this, 'worklet.js' above must be loaded as AudioWorklet completely, and
// syn.createAudioNode must be called to activate worklet.

// The first parameter is the method name added to 'AudioWorkletGlobalScope'.
// The second parameter will be passed to the worklet.
syn.hookPlayerMIDIEventsByName('myHookPlayerEvents', { secondSFont: secondSFont });

The sequencer also supports 'user-defined client' to handle event data.

JSSynth methods

waitForReady

Can be used to wait for the synthesizer engine's ready.

Return: Promise object (resolves when the synthesizer engine (libfluidsynth) is ready)

disableLogging / restoreLogging

Can be used to suppress logs from libfluidsynth.

JSSynth.ISynthesizer methods

(Not documented yet. Please see dist/lib/ISynthesizer.d.ts.)

License

js-synthesizer is licensed under BSD 3-Clause License except for the files in externals directory. For licenses of the files in externals directory, please read externals/README.md.