spessasynth_core
v1.1.3
Published
SoundFont2 synthesizer library for node.js
Downloads
37
Maintainers
Readme
spessasynth_core
A SoundFont2 synthesizer library, made for use with node.js. A fork of SpessaSynth.
npm install --save spessasynth_core
Looking for a browser version? Try SpessaSynth.
Features:
- SoundFont2 support (both modulators and generators)
- SoundFont3 support (vorbis compressed sf2)
- GS, XG, GM2, GM1 system exclusive support
- NRPN, RPN support
- Integrated sequencer
- Additional custom modulators
- Multi-port MIDIs support (more than 16 channels)
- No SoundFont size limit
- No dependencies
Example: Render a MIDI file to a .wav file
const fs = require('fs');
// spessasynth_core is an es6 module
import("spessasynth_core").then(core => {
// usage: node test.js <sf path> <midi path> <output path>
const [,, soundfontName, midiName, outputName] = process.argv;
// read the input files
const soundfont = fs.readFileSync(soundfontName);
const mid = new core.MIDI(fs.readFileSync(midiName))
// initialize synth and sequencer
const synth = new core.Synthesizer(soundfont, 44100);
const seq = new core.Sequencer(synth);
// load new song and disable the loop
seq.loadNewSongList([mid]);
seq.loop = false;
// calculate length and allocate buffers
const lengthSamples = mid.duration * 44100;
const outLeft = new Float32Array(lengthSamples);
const outRight = new Float32Array(lengthSamples);
// wait for sf3 support to load and render
synth.sf3supportReady.then(() => {
// note: this discards reverb and chorus outputs!
synth.render([outLeft, outRight]);
// write output data
const wav = core.rawDataToWav(44100, outLeft, outRight);
fs.writeFileSync(outputName, Buffer.from(wav));
});
});
Example 2: play a MIDI file to speakers
via npm package speaker
const fs = require('fs');
const Speaker = require("speaker");
const SAMPLE_RATE = 44100; // hertz
const BLOCK_SIZE = 128; // samples
// usage: node test.js <sf path> <midi path>
import("spessasynth_core").then(core => {
// Disable logging
core.SpessaSynthLogging(false, false, false, false);
// Read arguments and load the input files
const [,, soundfontName, midiName] = process.argv;
const soundfont = fs.readFileSync(soundfontName);
const mid = new core.MIDI(fs.readFileSync(midiName)); // parse the midi
// Initialize synth and sequencer
const synth = new core.Synthesizer(soundfont, SAMPLE_RATE, BLOCK_SIZE);
const seq = new core.Sequencer(synth);
// Load new song and disable the loop
seq.loadNewSongList([mid]);
seq.loop = false;
// make the program stop after the sequence is finished
const time = seq.duration + 1
let isPlaying = true;
setTimeout(() => isPlaying = false, time * 1000);
console.log(`Playing "${mid.midiName || "<unnamed song>"}"`);
// Calculate length and allocate buffers
const outLeft = new Float32Array(BLOCK_SIZE);
const outRight = new Float32Array(BLOCK_SIZE);
// Wait for sf3 support to load and render
synth.sf3supportReady.then(() => {
const speakerOut = new Speaker({
channels: 2,
bitDepth: 16,
sampleRate: SAMPLE_RATE,
});
function render()
{
// Render audio samples from the synthesizer
synth.render([outLeft, outRight]);
// Prepare buffer for WAV output
const wavData = new Int16Array(outLeft.length * 2); // 2 channels
// Interleave audio data
let offset = 0;
for (let i = 0; i < outLeft.length; i++)
{
// Float ranges from -1 to 1, int16 ranges from -32768 to 32767, convert it here
const sampleL = Math.max(-1, Math.min(1, outLeft[i])) * 32767;
const sampleR = Math.max(-1, Math.min(1, outRight[i])) * 32767;
// Interleave data: L, R, L, R, etc...
wavData[offset++] = sampleL;
wavData[offset++] = sampleR;
}
// Write WAV data to speaker
speakerOut.write(wavData);
if(isPlaying)
{
setImmediate(render);
}
else
{
process.exit(0);
}
}
render();
});
});
API reference
Contents
Importing the library
spessasynth_core is an es6 package.
// es6
import {Synthesizer} from "spessasynth_core"
// commonjs
import("spessasynth_core").then(core => {
// use core.Synthesizer
})
Synthesizer
The main synth module.
Initialization
const synth = new Synthesizer(soundFontBuffer, sampleRate, blockSize)
- soundFontBuffer - a
Buffer
orArrayBufferLike
, represents the soundfont file. - sampleRate - number, the output sample rate in hertz.
- blockSize - optional, a number. Sets the interval of the synth updating parameters like the sequencer tick processing and modulation envelope. Default value is 128, and it's recommended to leave it as the default.
sf3supportReady
A promise that gets resolved when the vorbis decoder is ready. You must await it if you use sf3 soundfonts.
await synth.sf3supportReady;
render
Synthesizes audio the output buffers
synth.render(outputChannels, reverbOutputChannels, chorusOutputChannels);
- outputChannels - two
Float32Arrays
that get filled with the audio data. Left is the left channel and right is the right channel. Can be any length. (except zero) - reverbOutputChannels - two
Float32Arrays
that get filled with the unprocessed audio data for reverb processing. Left is the left channel and right is the right channel. Can be undefined. - reverbOutputChannels - two
Float32Arrays
that get filled with the unprocessed audio data for chorus processing. Left is the left channel and right is the right channel. Can be undefined.
All arrays must be the same length.
noteOn
Plays the given note.
synth.noteOn(channel, midiNote, velocity, enableDebugging);
- channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
- midiNote - the note to play. Ranges from 0 to 127.
- velocity - controls how loud the note is. 127 is normal loudness and 1 is the quietest. Note that velocity of 0 has the same effect as using
noteOff
. Ranges from 0 to 127. - enableDebugging - boolean, used only for debugging. When
true
, the console will print out tables of the soundfont generator data used to play the note.
noteOff
Stops the given note.
synth.noteOff(channel, midiNote);
- channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
- midiNote - the note to play. Ranges from 0 to 127.
To stop a note instantly, use
synth.killNote
(takes the same arguments)
stopAllChannels
Stops all notes. Equivalent of MIDI "panic".
synth.stopAllChannels(force);
- force -
boolean
, if true, ignores the release time and stops everything instantly. Defaults to false.
To stop all notes on a specific channel, use
synth.stopAll(channel, force)
. channel is the channel number.
programChange
Changes the preset for the given channel.
synth.programChange(channel, programNumber);
- channel - the MIDI channel to change. Usually ranges from 0 to 15, but it depends on the channel count.
- programNumber - the MIDI program number to use. Ranges from 0 to 127. To use other banks, go to controllerChange.
To lock the preset (prevent MIDI file from changing it) use
synth.workletProcessorChannels[channel].lockPreset = true;
pitchWheel
Changes the channel's pitch, including the currently playing notes.
synth.pitchWheel(channel, MSB, LSB);
- channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
- MSB and LSB. 7-bit numbers that form a 14-bit pitch bend value.
systemExclusive
Handles a MIDI System Exclusive message.
synth.systemExclusive(messageData);
- message data - Uint8Array, the message byte data Excluding the 0xF0 byte!
Refer to this table for the list of supported System Exclusives.
setMainVolume
Sets the main volume of the synthesizer.
synth.setMainVolume(volume);
- volume - the volume, ranges from 0 to 1.
setMasterPan
Sets the master panning of the synthesizer.
synth.setMasterPan(pan);
- pan - ranges from -1 to 1, -1 is left, 0 is middle, 1 is right.
lockController
Causes the given midi channel to ignore controller messages for the given controller number.
synth.lockController(channel, controllerNumber, isLocked);
- channel - the channel to lock. Usually ranges from 0 to 15, but it depends on the channel count.
- controllerNumber - the MIDI CC to lock. Ranges from 0 to 127.
- isLocked - boolean, if true then locked, if false then unlocked.
muteChannel
Mutes or unmutes a given channel
synth.muteChannel(channel, isMuted);
- channel - the channel to mute/unmute. Usually ranges from 0 to 15, but it depends on the channel count.
- isMuted - if the channel should be muted. boolean.
transposeAllChannels
Transposes the synth up or down in semitones. Floating point values can be used for more precise tuning.
synth.transposeAllChannels(semitones);
- semitones - the amount of semitones to transpose the synth by. Can be positive or negative or zero. Zero resets the pitch.
controllerChange
Sets a given MIDI controller to a given value.
synth.controllerChange(channel, controllerNumber, controllerValue);
- channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
- controllerName - the MIDI CC number. Refer to this table for the list of controllers supported by default.
- controllerValue - the value to set the given controller to. Ranges from 0 to 127.
Note that theoreticallly all controllers are supported as it depends on the SoundFont's modulators.
resetAllControllers
Resets all controllers and programs to their default values. Also resets the system.
synth.resetAllControllers();
addNewChannel
Adds a new channel.
synth.addNewChannel();
reloadSoundfont
Changes the soundfont of a Synthesizer's instance.
synth.reloadSoundFont(soundFontBuffer);
- soundFont - the soundfont to change to, an
ArrayBuffer
instance of the file.
setDrums
Sets the given channel to a drum channel.
synth.setDrums(channel, isDrum);
- channel - the channel to change. Usually ranges from 0 to 15, but it depends on the channel count.
- isDrum -
boolean
indicates if the channel should be a drum channel.
Accesing controller values
use synth.workletProcessorChannels
to get the current values. A single channel is defined as follows:
/**
* @typedef {Object} WorkletProcessorChannel
* @property {Int16Array} midiControllers - array of MIDI controller values
* @property {boolean[]} lockedControllers - array indicating if a controller is locked
* @property {boolean} holdPedal - indicates whether the hold pedal is active
* @property {boolean} drumChannel - indicates whether the channel is a drum channel
*
* @property {Preset} preset - the channel's preset
* @property {boolean} lockPreset - indicates whether the program on the channel is locked
*
* @property {boolean} lockVibrato - indicates whether the custom vibrato is locked
* @property {Object} channelVibrato - vibrato settings for the channel
* @property {number} channelVibrato.depth - depth of the vibrato effect (cents)
* @property {number} channelVibrato.delay - delay before the vibrato effect starts (seconds)
* @property {number} channelVibrato.rate - rate of the vibrato oscillation (Hz)
* @property {boolean} isMuted - indicates whether the channel is muted
* @property {WorkletVoice[]} voices - array of voices currently active on the channel
* @property {WorkletVoice[]} sustainedVoices - array of voices that are sustained on the channel
*/
Note: this definition is stripped from internal values.
Sequencer
Initialization
const sequencer = new Sequencer(synthesizer);
- synthesizer - a
Synthesizer
instance to play to.
loadNewSongList
Loads a new song list.
sequencer.loadNewSongList(parsedMidis);
- parsedMidis - an array of
MIDI
instances representing the songs to play. If there's only one, the loop will be enabled.
play
Starts playing the sequence. If the sequence was paused, it won't change any controllers, but if it wasn't (ex. the time was changed) then it will go through all the controller changes from the start before playing. This function does NOT modify the current playback time!
sequencer.play(resetTime);
- resetTime - boolean, if set to
true
then the playback will start from 0. Defaults tofalse
;
pause
Pauses the playback of the sequence.
sequencer.pause();
stop
Stops the playback of the sequence. Currently only used internally by the pause
function.
sequencer.stop();
nextSong
Plays the next song in the list.
sequencer.nextSong();
previousSong
Plays the previous song in the list.
sequencer.previousSong();
paused
Read-only boolean, indicating that if the sequencer's playback is paused.
if(sequencer.paused)
{
console.log("Sequencer paused!");
}
else
{
console.log("Sequencer playing or stopped!");
}
loop
Boolean that controls if the sequencer loops.
sequencer.loop = false; // the playback will stop after reaching the end
currentTime
Property used for changing and reading the current playback time.
get
Returns the current playback time in seconds.
console.log("The sequences is playing for"+sequencer.currentTime+" seconds.");
set
Sets the current playback time. Calls stop
and then play
internally.
sequencer.currentTime = 0; // go to the start
duration
Length of the track in seconds. Equivalent of Audio.duration
;
console.log(`The track lasts for ${sequencer.duration} seconds!`);
MIDI
SoundFont2
See SoundFont2 on SpessaSynth wiki
Logging
By default, SpessaSynth prints out a lot of stuff to console. Here's how you can disable it:
// import (or require) here
SpessaSynthLogging(enableInfo, enableWarning, enableGroup, enableTable);
All the input variables are booleans corresponding to the things SpessaSynth logs.
- Info - all general info such as parsing soundfonts, midi files, RPN changes, etc.
- Warnings - all messages unrecognized by the synthesizer, other warnings
- group - the groups for parsing the soundfont and midi files.
- table - the debug table
when enableDebugging
is set totrue
forsynth.noteOn