-
-
Notifications
You must be signed in to change notification settings - Fork 17
Synthetizer Class
This is the main module that generates the sound.
Yes, it's spelled "Synthetizer" and not "Synthesizer" here.
Tip
If you encounter any errors in this documentation, please open an issue!
- Synthetizer Class
- Table of Contents
- Importing
- Initialization
- Destruction
- Methods
- Properties
- Managers And Modes
// normal install
import { Synthetizer } from "./spessasynth_lib/synthetizer/synthetizer.js";
// npm package
import { Synthetizer } from "spessasynth_lib";
Tip
Using the npm package? Make sure you've read this
Caution
Note that you need to add the worklet processor for the synthesizer to work! See Importing the worklet
const synth = new Synthetizer(
tagetNode,
soundFontBuffer,
enableEventSystem(optional),
startRenderingData(optional),
synthConfig(optional)
);
- targetNode - the AudioNode the synth should play to. Usually it is the
AudioContext.destination
property. - soundFontBuffer - the
ArrayBuffer
to your soundFont. - enableEventSystem -
boolean
, disables the event system. Useful when rendering audio to file - startRenderingData -
object
, used for rendering to file. It's formatted as follows:- parsedMIDI: a
MIDI
class instance. The synthesizer will immediately start rendering it if specified - snapshot: a
SynthesizerSnapshot
object, a copy of controllers from another synthesizer instance. If specified, synth will copy this configuration. - oneOutput: a
boolean
- indicates the One output mode - loopCount: the number of loops to play.
It defaults to 0.
Make sure your
OfflineAudioContext
's length accounts for these!
- parsedMIDI: a
- synthConfig → optional, the configuration for audio effects. See below.
Caution
Avoid using multiple synthesizer instances. The SoundFontManager and one instance should be sufficient. See this comment for more info.
-
chorusConfig
-object
- this is the chorus config object:-
nodesAmount
-number
- the number of delay nodes (for each channel) and the corresponding oscillators. -
defaultDelay
-number
- the initial delay, in seconds. -
delayVariation
-number
- the difference between delays in the delay nodes. -
stereoDifference
-number
- the difference of delays between two channels (added to the left channel and subtracted from the right). -
oscillatorFrequency
-number
- the initial delay oscillator frequency, in Hz. -
oscillatorFrequencyVariation
-number
- the difference between frequencies of oscillators, in Hz. -
oscillatorGain
-number
- how much the oscillator will alter the delay in delay nodes, in seconds.
-
-
chorusEnabled
-boolean
- indicates if the chorus effect is enabled. -
chorusConfig
-ChorusConfig
- the configuration for chorus. Passundefined
to use defaults. Described above. -
reverbEnabled
-boolean
- indicates if the reverb effect is enabled. -
reverbImpulseResponse
-AudioBuffer
- the impulse response for the reverb. Passundefined
to use defaults. -
audioNodeCreators
- custom functions for creating Web Audio API wrapper nodes, such asstandardized-audio-context
.-
worklet
- a function that takes three arguments, the exact same as regularAudioWorkletNode
constructor: context, processor's name and worklet processor options. It should return the initializedAudioWorkletNode
object. - Example with regular Web Audio API constructor:
-
worklet: (ctx, name, options) => new AudioWorkletNode(ctx, name, options);
Tip
Pass undefined
to chorusConfig
or reverbImpulseResponse
to use the defaults.
Important
If you're rendering the audio to a file, it is highly recommended to supply the synthesizer with the reverb buffer
(even the stock one) through effects config, because it internally fetch
es the file which will result in reverb
enabling after a second or so.
A promise that gets resolved when the worklet synthesizer gets fully initialized (including sf3 support)
await synth.isReady;
Tip
It is recommended (but not always required) to wait for this promise.
Below is a simple example of creating a new synthesizer with a soundfont coming from a file input.
// create audio context
const context = new AudioContext({
sampleRate: 44100
});
// add worklet
await context.audioWorklet.addModule("worklet_processor.min.js");
// load soundfont
const file = document.getElementById("file_input").files[0];
const soundfont = await file.arrayBuffer()
// set up synthetizer
const synth = new Synthetizer(context.destination, soundfont);
Use the .destroy()
method.
synth.destroy();
Caution
Remember, you MUST call this method after you're done with the synthesizer! Otherwise it will keep processing and the performance will greatly suffer.
Important
The synthesizer internally sends commands to the AudioWorklet
where all the processing happens.
Keep that in mind as not all methods will immediately report values!
(E.g. noteOn
won't instantly increase the voice
count in channelProperties
)
Send a raw MIDI message to the synthesizer. Calls noteOn, noteOff, etc. internally.
synth.sendMessage(message, channelOffset = 0);
- message - an array of bytes (numbers from 0 to 255). The MIDI message to process.
- channelOffset, optional - adds to the channel number of the message. It defaults to 0.
Example:
// send a MIDI note on message for channel 2 and a note 61 (C#) with velocity 120
synth.sendMessage([0x92, 0x3D, 0x78]);
Play the given note.
synth.noteOn(channel, midiNote, velocity, enableDebugging);
- channel - the MIDI channel to use. It 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.
Note that velocity of 0 has
the same effect as using
noteOff
. Ranges from 0 to 127, where 127 is the loudest and 1 is the quietest. - enableDebugging - boolean, used only for debugging.
When
true
, the console will print out tables of the soundfont generator data used to play the note.
Example:
// start the note 64 (E) on channel 0 with velocity of 120
synth.noteOn(0, 64, 120);
Stop the given note.
synth.noteOff(channel, midiNote);
- channel - the MIDI channel to use. It Usually ranges from 0 to 15, but it depends on the channel count.
- midiNote - the note to play. Ranges from 0 to 127.
Note that when highPerformanceMode
is set to true, the note will always have a release time of 50ms.
Example:
// stop the note 78 (F) on channel 15
synt.noteOff(15, 77);
Change the preset for the given channel.
synth.programChange(channel, programNumber);
- channel - the MIDI channel to change. It 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.
Example:
// change the program on channel 1 to 16 (drawbar organ)
synth.programChange(0, 16);
Change the channel's pitch, including the currently playing notes.
synth.pitchWheel(channel, MSB, LSB);
- channel - the MIDI channel to use. It 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 calculated as:
(MSB << 7) | LSB
Example:
// set pitch bend on channel 3 to middle (no change)
synth.pitchWheel(3, 64, 0);
Change the channel's pitch bend range in semitones. It uses Registered Parameter Number internally.
synth.setPitchBendRange(channel, pitchBendRangeSemitones);
- channel - the MIDI channel to use. It usually ranges from 0 to 15, but it depends on the channel count.
- pitchBendRangeSemitones - the pitch bend range, in full semitones.
Tip
The pitch bend range can be decimal, for example, 0.5 means += half a semitone.
Example:
// set the pitch bend range on channel 0 to +-12 semitones (one octave)
synth.setPitchBendRange(0, 12);
Handle a MIDI System Exclusive message.
synth.systemExclusive(messageData, channelOffset = 0);
- messageData - Uint8Array, the message byte data Excluding the 0xF0 byte!
- channelOffset - number, the channel offset for the message as they usually can only address the first 16 channels. For example, to send a system exclusive on channel 16, send a system exclusive for channel 0 and specify the channel offset to be 16.
Tip
Refer to MIDI Implementation for the list of supported System Exclusives.
Example:
// send a GS DT1 Use Drums On Channel 10 (turn channel 10 into a drum channel)
synth.systemExclusive([0x41, 0x10, 0x42, 0x12, 0x40, 0x1A, 0x15, 0x01, 0x10, 0xF7]);
// send a GS DT1 Use Drums On Channel 10 (turn channel 20 into a drum channel)
synth.systemExclusive([0x41, 0x10, 0x42, 0x12, 0x40, 0x1A, 0x15, 0x01, 0x10, 0xF7], 10);
Tunes individual MIDI key numbers on a given program using the MIDI Tuning Standard. Think of it as a pitch wheel but for individual notes.
synth.tuneKeys(program, tunings);
- program - the MIDI program to tune. Ranges from 0 to 127.
- tunings - an array of objects, each containing two properties:
- sourceKey - the MIDI key number to tune.
- targetTuning - the MIDI key number of the target pitch. Note that floating values are allowed and they are specified in cents.
Example:
// tune the program 81 (Saw Lead)
// tune the MIDI note 60 (middle C) an octave and 57.78 cents up, and tune note 78 (F) to note 64 (E) and 12 cents up.
synth.tuneKeys(81, [
{ sourceKey: 60, targetPitch: 72.5778 },
{ sourceKey: 78, targetPitch: 64.12 }
]);
Set a given MIDI controller to a given value.
synth.controllerChange(channel, controllerNumber, controllerValue);
- channel - the MIDI channel to use. It usually ranges from 0 to 15, but it depends on the channel count.
- controllerNumber - the MIDI CC number of the controller to change. 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
Note that theoretically all controllers are supported as it depends on the SoundFont's modulators.
Example:
// set controller 10 (Channel Pan) on channel 2 to 127 (Hard right)
synth.controllerChange(2, 10, 127);
// select bank 1 and program 80 on channel 0 to select Square instead of Square Lead
synth.controllerChange(0, 0, 1);
synth.programChange(0, 80);
Reset all controllers to their default values. (for every channel)
synth.resetControllers();
Cause the given midi channel to ignore controller messages for the given controller number.
synth.lockController(channel, controllerNumber, isLocked);
- channel - the channel to lock. It usually ranges from 0 to 15, but it depends on the channel count.
- controllerNumber - the MIDI CC to lock. Ranges from 0 to 146. See the tip below to see why.
- isLocked - boolean, if true then locked, if false then unlocked.
Tip
To lock other modulator sources add 128 to the Source Enumerator (Soundfont 2.04 Specification section 8.2.1)
For example to lock pitch wheel, use synth.lockController(channel, 142, true)
. (128 + 14 = 142)
Example:
// disable portamento on channel 0
synth.controllerChange(0, 65, 0); // portamento on/off set to off
synth.lockController(0, 65, true); // lock portamento on/off
Apply pressure to the given channel. It usually controls the vibrato amount.
synth.channelPressure(channel, pressure);
- channel - the channel to use. It usually ranges from 0 to 15, but it depends on the channel count.
- pressure - the pressure to apply. Ranges from 0 to 127.
Example:
// set channel 1 pressure to 64 (middle)
synth.channelPressure(1, 64);
Apply pressure to the given note on a given channel. It usually controls the vibrato amount.
synth.polyPressure(channel, midiNote, pressure);
- channel - the channel to use. It usually ranges from 0 to 15, but it depends on the channel count.
- midiNote - the note to apply pressure to. Ranges from 0 to 127.
- pressure - the pressure to apply. Ranges from 0 to 127.
Example:
// set channel 11 pressure on note 60 (C) to 127 (max)
synth.polylPressure(11, 60, 127);
Mute or unmute a given channel.
synth.muteChannel(channel, isMuted);
- channel - number - the channel to mute/unmute. It usually ranges from 0 to 15, but it depends on the channel count.
- isMuted - boolean - if the channel should be muted. boolean.
Example:
// set solo on channel 3
for(const i = 0; i < synth.channelsAmount; i++)
{
if(i === 3)
{
synth.muteChannel(i, false);
}
else
{
synth.muteChannel(i, true);
}
}
Force all the notes in a given channel to have the specified velocity.
synth.velocityOverride(channel, velocity);
- channel - number - the channel to use. It usually ranges from 0 to 15, but it depends on the channel count.
- velocity - number - the velocity to use. 0 disables the override.
Example:
// Force max velocity on drum channel (channel 9)
synth.velocityOverride(9, 127);
Stop all notes. Equivalent of MIDI "panic."
synth.stopAll();
Transpose the synth up or down in semitones. Floating point values can be used for more precise tuning.
synth.transpose(semitones);
- semitones - number - the number of semitones to transpose the synth by. It can be positive or negative or zero. Zero resets the pitch.
Example:
// transpose the synth up by 125 cents
synth.transpose(1.25);
Set the synth's main volume.
synth.setMainVolume(volume);
- volume - number - the synth's volume. Ranges from 0 to anything, but 1 is the recommended maximum.
Note
Raising the gain above 1 can lead to unexpected results.
Example:
// halve synth's volume
synth.setMainVolume(0.5);
Set the synth's interpolation method.
synth.setInterpolationType(type);
-
type - number - the interpolation type. Currently, defined types:
-
0 - linear interpolation. This was previously the default
-
1 - no interpolation (the nearest neighbor). Useful for songs like chip-tunes.
-
2 - cubic (fourth) order interpolation. Default.
Example:
// set nearest neighbor interpolation
synth.setInterpolationType(0);
Add a new channel. Invokes a newchannel
event.
synth.addNewChannel();
Caution
This function is deprecated. It may be removed without further notice. Use soundfontManager instead.
Change the soundfont of a Synthesizer's instance.
await synth.reloadSoundFont(soundFontBuffer);
Important
This function is asynchronous.
- soundFont - the soundfont to change to, an
ArrayBuffer
instance of the file.
Set the new impulse response for the reverb algorithm.
synth.setReverbResponse(buffer);
- buffer -
AudioBuffer
- contains the new impulse response to use.
Set and update the chorus processor with a given config.
synth.setChorusConfig(config);
- config -
ChorusConfig
- the new configuration. Format is described here.
Set the gain value of the built-in effects or disable them.
synth.setEffectsGain(reverbGain, chorusGain);
- reverbGain -
number
- the gain value of the reverb effect. Ranges from 0 to 1. Set to 0 to disable. - chorusGain -
number
- the gain value of the chorus effect. Ranges from 0 to 1. Set to 0 to disable.
Example:
// disable reverb and chorus
synth.setEffectsGain(0, 0);
Get a current snapshot of the Worklet synthesizer.
Important
This function is asynchronous.
const snapshot = await synth.getSynthesizerSnapshot();
The returned value is formatted like this. It is essentially an object of the entire synthesizer instance from the audioWorklet side.
Disables GS NRPN (Non-Registered Parameter Number) messages from being recognized. Such as vibrato or drum key tuning.
synth.disableGSNRPparams();
Connects individual channel outputs to given target nodes.
synth.connectIndividualOutputs(audioNodes);
- audioNodes -
audioNode[]
- an array of exactly 16AudioNodes
to connect each channel to. The first node connects to the first channel and so on.
Example:
// create 16 analyzers and connect them
const analyzers = Array(16).map(() => context.createAnalyser());
synth.connectIndividualOutputs(analyzers);
Print out the synth class instance, both from the main thread and the AudioWorklet thread.
synth.debugMessage();
The synthesizer's event handler. Refer to Event handling for more.
The synthesizer's soundfont manager. Refer to The soundfont manager for more.
The synthesizer's key modifier manager. Refer to Key modifier manager for more.
The current amount of voices (notes) playing or during their release phase.
console.log(`This synthetizer is currently playing ${synth.voicesAmount} notes!`);
The maximum allowed voices at once. If new voices are added, the voices considered unimportant are killed. Default is 350.
synth.voiceCap = 100; // max 100 voices at once
The connected AudioContext
's time.
console.log(`The current AudioContext's time is ${synth.currentTime}!`); // example usage
Indicates the current system the synth is in. Currently, there are: GM, GM2, GS, XG. Default is GS
console.log(synth.system); // "gm"
Boolean, if the high performance mode is enabled. High performance mode currently overrides release time to be almost instant. Intended for "Black MIDIs."
synth.highPerformanceMode = true; // we can now play black MIDIs! >:)
The current channel properties. An array
of objects formatted like this:
/**
* @typedef {Object} ChannelProperty
* @property {number} voicesAmount - the channel's current voice amount
* @property {number} pitchBend - the channel's current pitch bend from -8192 do 8192
* @property {number} pitchBendRangeSemitones - the pitch bend's range, in semitones
* @property {boolean} isMuted - indicates whether the channel is muted
* @property {boolean} isDrum - indicates whether the channel is a drum channel
*/
console.log(synth.channelProperties[0]); // {voicesAmount: 0, pitchBend: 0, pitchBendRangeSemitones: 2, isMuted: false, isDrum: false }
Below are the additional managers for the synthesizer.
This is a special synth mode, which causes the synth to have one output (instead of 18), but 32 channels.
Every midi channel has two audio channels. So it looks like this:
- MIDI channel 0:
- audio output 0
- audio output 1
- MIDI channel 1:
- audio output 2
- audio output 3
- MIDI channel 2:
- audio output 4
- audio output 5
etc.
This allows for many things, such as exporting files of individual channels.
const midi = YOUR_MIDI_HERE;
const soundfont = YOUR_SOUNDFONT_HERE;
const sampleRate = 44100;
const offline = new OfflineAudioContext({
numberOfChannels: 32,
length: sampleRate * midi.duration,
sampleRate: sampleRate
});
const synth = new Synthetizer(
offline.destination,
soundfont,
false, // disable event system for faster rendering
{
parsedMIDI: midi,
oneOutput: true
}
);
// render
const renderedData = await offline.startRendering();
for (let i = 0; i < 16; i++)
{
const audioOut = audioBufferToWav(buf, false, i * 2);
// do whatever you need with the channel buffer
console.log('Rendered channel', i, 'data:', audioOut);
}
Important
Chorus and reverb are disabled when the one output mode is on.
Caution
The OfflineAudioContext must be initialized with 32 channels! Otherwise there will be an error!
Tip
If you encounter any errors in this documentation, please open an issue!
Warning
Make sure you always update worklet_processor.min.js
along with the npm package!