a JS library to process MIDI messages, to render audio frames via js-synthesizer
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
build
src
typings/AudioWorklet
.editorconfig
.gitignore
LICENSE
README.md
license.template.txt
package.json
yarn.lock

README.md

js-sequencer

js-sequencer is a JS library sending MIDI-based messages to js-synthesizer, to render audio frames with Web Audio. js-sequencer also parses and serializes Standard MIDI Format (.smf; also known as .mid) files. Since js-sequencer processes MIDI-based messages, js-sequencer can play those files.

Currently js-sequencer is not published as an NPM package, and is intended to use on pg-fl.jp web site. You can use js-sequencer by building manually from this repository, but incompatible update may be applied in the future.

Note that some source codes have non-English (Japanese) comments.

Build

npm install
npm run build           # for normal library
npm run build:minified  # for minified library

Usage

js-synthesizer and Web Audio feature are required to play MIDI messages. If you want only to use parsing and/or serializing SMF files, they are not required.

Initialize engine

var engine = new JSSeq.Core.Engine();

Load/Export SMF files

load from existing binary

var bin;  // loaded SMF binary data
engine.loadSMFData(bin, 0, function (err) {
    if (err) {
        // 'err' is an object (mostly Error object)
        alert('Error: ' + err);
    } else {
        // load done
        console.log('Part count:', engine.parts.length);
        console.log('Duration [sec]:', engine.calculateDuration());
    }
});
// --- or ---
engine.loadSMFDataPromise(bin, 0).then(function () {
    // load done
    console.log('Part count:', engine.parts.length);
    console.log('Duration [sec]:', engine.calculateDuration());
}, function (err) {
    // 'err' is an object (mostly Error object)
    alert('Error: ' + err);
});

load from file element

var elem = document.getElementById('MyFileElement');
elem.addEventListener('change', function () {
    engine.loadFromFile(elem, function (err) {
        if (err) {
            // 'err' is an object (mostly Error object)
            alert('Error: ' + err);
        } else {
            // load done
            console.log('Part count:', engine.parts.length);
            console.log('Duration [sec]:', engine.calculateDuration());
        }
    });
    // --- or ---
    engine.loadFromFilePromise(elem).then(function () {
        // load done
        console.log('Part count:', engine.parts.length);
        console.log('Duration [sec]:', engine.calculateDuration());
    }, function (err) {
        // 'err' is an object (mostly Error object)
        alert('Error: ' + err);
    });
});

generate SMF data

var bin = engine.exportSMFToArrayBuffer(); // 'bin' will be ArrayBuffer
var blobUrl = engine.makeSMFBlobURL();     // 'blob:' URL

Check if the playing feature is available

if (!JSSeq.Core.Player.isSupported()) {
    throw new Error('Not supported on this browser.');	
}

Initialize player (to play MIDI messages)

// the worker file of js-sequencer (dist/js-sequencer.worker.*.js)
const JSFile_SeqWorker = 'js-sequencer.worker.min.js';
// the dependencies of js-sequencer (js-synthesizer and its dependencies)
// these paths must be relative to JSFile_SeqWorker
const JSFile_SeqWorker = [
	'libfluidsynth-2.0.2.js',
	'js-synthesizer.min.js'
];
// the worklet files of js-sequencer (used if supported)
// these paths must be relative to the document
const JSFile_SeqWorklet = [
	'js-sequencer.worklet.min.js'
];

let player;

JSSeq.Core.Player.instantiate(
    // JSSeq.Core.Engine instance
    engine,
    // worker script path
    JSFile_SeqWorker,
    // dependencies of worker
    JSFile_Deps,
    // if you create multiple player instance and
    // want to share worker between instances, set true
    // (default: false)
    false,
    // timer interval of worker processing
    // (default: 30)
    30,
    // frame count per one render process
    // (default: 8192)
    8192,
    // sample rate (default: 48000)
    48000
).then((p) => {
    player = p;
    // if you want not to use AudioWorklet even if supported,
    // call with null parameter
    player.setAudioWorkletScripts(JSFile_SeqWorklet);
})

Play notes manually

const notes = [
    // parameters: posNum, posDen, lenNum, lenDen, value, channel
    //  - posNum: numerator of position (used by sequencer process and SMF data)
    //  - posDen: denominator of position (used by sequencer process and SMF data)
    //    - e.g. (posNum,posDen)=(1,4) represents the position after one quarter note from beginning
    //  - lenNum: numerator of length (used by sequencer process and SMF data)
    //  - lenDen: denominator of length (used by sequencer process and SMF data)
    //    - e.g. (lenNum,lenDen)=(1,4) represents the length of one quarter note
    //  - value: MIDI note value
    //  - channel: MIDI channel (zero-based index)
    new JSSeq.Core.NoteObject(0, 1, 0, 1, 60, 0),
    new JSSeq.Core.NoteObject(0, 1, 0, 1, 64, 0),
];
player.playNoteMultiple(notes);
window.setTimeout(() => {
    // playNoteMultiple doesn't stop notes automatically even if
    // 'noteLength's are specified.
    player.stopNoteMultiple(
        notes,
        true // set true to release internal player data after a few seconds
    );
}, 5000);

Play sequential notes

// (usage of 'loadFromFilePromise' is described above)
engine.loadFromFilePromise(elem).then(function () {
    player.playSequence();
});

License

BSD 3-Clause License