Skip to content

Built-in Synthesizer (NativeSynth)

NativeSynth turns MIDI into sound on its own — no samples to download, no SoundFont to ship. It is built into libsonare, so a MIDI track always makes sound out of the box.

For a first pass, you only need three ideas:

  1. choose a named preset such as acoustic-piano, warm-pad, or drum-kit;
  2. route MIDI notes to the destination that uses that preset;
  3. optionally override simple fields such as cutoffHz, ampAttackMs, or stereoSpread.

Under the hood, NativeSynth is one synthesizer with fifteen swappable synthesis engines. Each engine is a different way to create the raw tone. Several acoustic-style engines are still provisional physical models: they are useful for data-free preview and fallback, but their final voicing/calibration is still in progress.

  • a virtual-analog subtractive voice (classic synth leads and pads),
  • FM (electric pianos, bells, and clavinet),
  • Karplus-Strong plucked string (guitars, basses, harp, and harpsichord),
  • modal percussion (marimba, vibraphone),
  • additive drawbar organ,
  • membrane percussion (the drum kit),
  • an extended-waveguide acoustic piano,
  • sustained flue-pipe organ,
  • bowed-string waveguide,
  • reed woodwind waveguide,
  • brass lip-reed waveguide,
  • air-jet flute waveguide,
  • a buzzing-bridge plucked string (koto, sitar, tanpura),
  • a source-filter vocal voice (choir and solo voices),
  • and a free-reed voice (accordion, harmonica, bandoneon).

All fifteen share one common control layer for modulation, envelopes, filters, stereo width, and polyphony, so the same patch fields work across very different sounds. To get a sound, pick a preset by name — or start from a preset and change only the fields you care about with a SynthPatch. You never have to touch the engine internals to start.

Synthesis terms in one place

The engine names below are different ways to generate a tone. You don't need them all to start — pick a preset and play — but here is the one-line version of each:

  • subtractive — start with a bright waveform and carve it with a filter (the classic analog-synth recipe).
  • FM / phase modulation — one oscillator's output is added to another's phase (the DX-family way of implementing FM), producing metallic and bell-like tones.
  • Karplus-Strong — a short delay loop that models a plucked string.
  • modal — a bank of tuned resonators modeling a struck bar or bell.
  • additive / drawbar — sums harmonic sine partials, like the drawbars on a Hammond organ.
  • (extended) waveguide — a delay-line model of a vibrating string or tube.
  • reed / brass / flute waveguide — sustained breath-excited models for woodwinds and brass.
  • buzzing-bridge plucked — a plucked-string loop whose bridge can be made to graze the string and spray energy into the upper partials: cleanly terminated at buzz 0 (harp, koto), shimmering and buzzing as buzz rises (sitar, tanpura).
  • source-filter vocal — a glottal source (sawtooth + tilt) fed through a bank of vowel formant resonators for choir and solo-voice tones.
  • free reed — a driven metal-tongue oscillator (accordion, harmonica, bandoneon), optionally musette-detuned into two beating tongues.

Two terms appear throughout the patch controls: an ADSR envelope (attack/decay/sustain/release — how a level rises and falls over a note) and the mod matrix (a routing table that sends modulation sources such as LFOs or envelopes to targets such as pitch or filter cutoff).

MIDI never renders silent

NativeSynth is also the data-free floor of the SoundFont player. When you bounce a project through an SF2 and a program (or the whole SoundFont) is missing, those notes fall back to the NativeSynth GM fallback bank — all 128 General MIDI programs plus the drum map. You get audio either way.

Where NativeSynth sits

A NativeSynth patch is an instrument: you bind it to a MIDI destination, and the MIDI on tracks routed to that destination plays through it. Offline you bind it in bounceWithSynthInstrument; live you bind it with engine.setSynthInstrument and feed MIDI input. For sampled, multisampled instruments instead, use the SoundFont player.

A single signal path runs through NativeSynth on every note: a MIDI note picks one of the fifteen engines, and the engine's raw tone then flows through the shared control layer before reaching the stereo output.

NativeSynth signal path
1 OF 15 ENGINESSHARED CONTROL LAYERMIDI noteEngine selectFilterAmp & filter ADSRLFOs / mod matrixBody resonanceStereo spreadStereo audio out
Whichever engine is selected, the same shared control layer and patch fields apply afterward.

What You Will Learn

By the end of this page you should be able to:

  • pick the right synthesis engine for a sound, and the right named preset;
  • start from a preset and override individual fields with a SynthPatch;
  • list the real preset and enum names from the runtime instead of guessing;
  • understand the va: routing prefix and the drum-kit GM drum map;
  • render MIDI to audio offline with bounceWithSynthInstrument and live with setSynthInstrument;
  • know when a note plays NativeSynth versus the loaded SoundFont.

The fifteen synthesis engines

Every preset selects one engineMode. The shared sections (filter, envelopes, LFOs, mod matrix, body resonance, polyphony) apply on top of whichever engine is active. Mode-specific deep parameters — FM operator stacks, modal mode tables, drawbar registrations, kit pieces, piano strings, pipe ranks, bowed-string friction, reed/brass bores, and flute jet geometry — live inside the named presets, not in the patch.

subtractive — virtual-analog

The classic oscillator → filter → amp voice. Detuned unison, drift, a pre-filter drive stage, and a choice of four filter models give it everything from fat saw leads to wide pads. Good for leads, basses, pads, and plucks — anything you'd reach for an analog synth to do. Presets: sine, saw, square, triangle, saw-lead, square-lead, sub-bass, warm-pad.

SYNTH · SUBTRACTIVEIDLE
Shaping a note — oscillator, filter, envelope

The built-in synth renders a single A3. The outline is its amplitude envelope: raise the attack and the note fades in; lower the cutoff and the tone darkens. Press play to hear the patch.

Oscillator
Cutoff
2200 Hz
Attack
8 ms

The filter model is the heart of the "character". Four classic models are available via filterModel:

ModelVoicing it emulatesNotes
svfTPT state-variable (SEM family)Clean, the only model with a selectable filterOutput (lowpass / bandpass / highpass)
moog-ladder4-pole transistor ladderZero-delay-feedback, saturating loop, self-oscillates
diode-ladderDiode ladder (VCS3 / TB-303 family)Coupled-stage ZDF, self-oscillates
sallen-keyKorg35 Sallen-Key (MS-10 / early MS-20)Self-oscillates

All four stay stable and zipper-free under per-sample cutoff/resonance modulation, and self-oscillation is deterministic.

SYNTH · SUBTRACTIVEIDLE
Lowpass filter — cutoff, resonance, and model

A sawtooth through a lowpass filter — the single most recognizable synth gesture. Lower the cutoff and the high harmonics roll off, so the tone darkens; raise the resonance and the filter rings at the cutoff, adding the vocal "wah". The model selects the filter circuit being emulated: the SVF stays clean, while the ladder and Sallen-Key models saturate and can self-oscillate at high resonance. The bottom scope is the waveshape; press play to hear it.

Cutoff
2200 Hz
Resonance
3
Filter model

fm — frequency modulation

A phase-modulation operator stack (one oscillator's output is added to another's phase → metallic/bell tones) with a small algorithm table, exponential operator envelopes, a feedback operator, and velocity-to-index (brightness) scaling. Good for electric pianos, bells, mallets, clavinet, and brass — the metallic, bell-like, and inharmonic sounds subtractive struggles with. Presets: e-piano.

karplus-strong — plucked string

A fractional-delay waveguide loop (a short delay loop that models a plucked string) with phase-exact tuning, plus pick-position comb, velocity-driven brightness, decay stretching, and note-off loop damping (finger/palm mute). Guitar, harp, and bass presets add provisional physical details: pickup position, body coupling, steel-string dispersion, sympathetic open strings, tension bend, and dual-polarization decay. Treat the acoustic realism as in calibration, not as a finished instrument model. Good for plucked and strummed strings — guitar, bass, harp, harpsichord, and the plucked ethnic family. Presets: classical-guitar, steel-guitar, electric-guitar, harp, bass-acoustic, bass-fingered, bass-picked, bass-fretless, bass-slap.

A modal resonator bank (a bank of tuned resonators modeling a struck bar or bell) tuned to physical mode ratios (uniform-bar glockenspiel, deep-arch marimba/vibraphone), with mallet-hardness velocity weighting and per-mode decay. Good for tuned mallet instruments — glockenspiel, vibraphone, marimba, xylophone. Presets: marimba, glass, bell.

additive — drawbar organ

The nine Hammond drawbar pitches (summing harmonic sine partials, one drawbar per partial) with stepped stop levels, free-running partial phases, and a key-click contact transient. Good for organs — sustained, harmonic-rich registrations. Preset: organ.

percussion — membrane percussion

Rayleigh circular-membrane modes with a descending strike-pitch envelope under filtered noise. This engine backs the GM drum kit — kick, snare shell + wires, toms, hats, and cymbals with inharmonic ring modes, one-shot and deterministic. Preset: drum-kit.

piano — extended-waveguide acoustic piano

A data-free grand-piano sketch with the four piano-defining elements: stiff-string dispersion (partials stretch sharp up the keyboard), a nonlinear felt hammer (hard strikes are shorter and brighter), 2-3 coupled micro-detuned unison strings, and a soundboard resonator bank. The voicing is register-scaled, so bass notes, middle-register chords, and treble notes do not share one over-simple brightness curve. This is still a provisional model intended for built-in preview, not a sampled-piano replacement. Good for acoustic piano. Preset: acoustic-piano.

pipe-organ — sustained flue pipe

A provisional waveguide flue-pipe model with shared wind behavior, multi-rank registration, reed-pipe color, and mouth/radiation correction. Good for church organ color previews from principals and bourdon stops to flute and trumpet ranks. Presets: church-organ, church-flute, church-bourdon, church-trumpet.

bowed-string — friction-excited string

A sustained bowed-string waveguide with bow speed/force/position control, sympathetic resonance, second-polarization beating, and a violin-family body resonator. The model is provisional and still being tuned against references. Good for violin-family previews. Presets: violin, viola, cello, contrabass.

reed — woodwind reed

A reed-bore waveguide with cylindrical and conical variants, tonehole/growth-cone behavior, register-scaled voicing, and live breath/brightness control. This is a provisional GM fallback/preview voice while calibration continues. Good for single- and double-reed woodwind previews and saxophones. Presets: clarinet, soprano-sax, alto-sax, tenor-sax, baritone-sax, oboe, english-horn, bassoon.

brass — lip-reed brass

A brass waveguide with lip tension, brass-bell body resonance, conical/cylindrical voicing, register scaling, and a bright cuivré edge for loud playing. This is a provisional physical model, so use it as a built-in brass fallback rather than as a final brass simulation. Presets: brass, trumpet, trombone, tuba, french-horn, muted-trumpet, cornet, flugelhorn, euphonium.

flute — air-jet flute

A breath-driven air-jet / open-pipe model with jet/reflection brightness, chiff/noise, overblow behavior, and vibrato control. This is currently a provisional fallback voice for flutes, whistles, and ocarina-like edge-tone instruments. Presets: concert-flute, piccolo, recorder, pan-flute, shakuhachi, tin-whistle, ocarina, blown-bottle.

plucked-string — buzzing-bridge plucked string

A plucked-string waveguide whose bridge model keeps grazing the string, spraying energy back into the upper partials so the note shimmers and buzzes for its whole ring. The buzz control sweeps from a clean harp or koto (no buzz) to the bright, sustaining rattle of a sitar's curved jawari bridge or a shamisen's sawari. Distinct from karplus-strong, which models a clean-terminated pluck (the named harp preset stays there — pluck, bell, and brass are GM-fallback aliases whose engine differs from what the name suggests). Good for the koto / sitar buzzing-bridge plucked family. Presets: pluck, harp-plucked, koto, sitar, tanpura.

vocal — source-filter voice

A two-stage voice: a glottal source (a naive sawtooth shaped by a one-pole spectral tilt, plus aspiration noise) feeding a bank of five resonant bandpass formants tuned to a sung vowel. The source oscillator is not band-limited; because the source-filter path is feed-forward, the raw sawtooth's aliasing is attenuated by the narrow formant bandpasses rather than prevented at the oscillator. The vowel field selects the formant table (/a/, /e/, /i/, /o/, /u/), brightness tilts the source and opens the upper formants, and a per-voice vibrato modulates the pitch. Good for choir and solo-voice previews. Presets: choir-aah, choir-ooh, voice-eeh.

free-reed — driven free reed

A driven metal-tongue oscillator (a phase accumulator shaped by an asymmetric saturator and a body lowpass) that models the free reed of an accordion, harmonica, or bandoneon — the tongue's own pitch sets the note, with no coupled air column. A detune control adds a second tongue a few cents sharp of the first, and the beat between the pair is the shimmering musette sound; detune 0 collapses back to a single tongue. Good for accordion, harmonica, and reed-organ previews. Presets: accordion, harmonica, bandoneon, reed-organ.

The GM fallback bank

The GM fallback is not just a last-resort sine bank. When a SoundFont is absent or incomplete, NativeSynth chooses the closest built-in synthesis voice for the requested GM program. Some of those voices are provisional physical models whose calibration is still underway. The goal is useful, data-free preview and missing-program coverage, not final sampled-instrument realism.

GM areaData-free fallback voice
Programs 0-7, keyboardExtended-waveguide grand piano, FM electric pianos/clavinet, and Karplus-Strong harpsichord bank variants
Programs 8-15, chromatic percussionModal celesta, glockenspiel, music box, vibraphone, marimba, xylophone, and tubular bells, plus a Karplus-Strong dulcimer
Programs 16-23, organAdditive drawbar organs (16-18), the physical church-organ flue pipe (19), and free-reed-engine reed-organ/accordion, harmonica, and bandoneon voices (20-23)
Programs 24-37, guitar and bassKarplus-Strong nylon, steel, electric, muted/overdriven/distorted guitars, and dedicated bass variants
Programs 40-47, strings/orchestraBowed violin family, a tremolo-strings pad, Karplus-Strong pizzicato strings and harp, and a timpani fallback
Programs 52-54, choir/voiceChoir-aahs, voice-oohs, and synth-voice programs voiced on the dedicated source-filter vocal engine
Programs 56-79, brass/reed/fluteProvisional lip-reed brass (56-60) and FM brass (61-63), plus reed woodwinds/saxophones and air-jet flutes
Programs 104-107, ethnic pluckedBuzzing-bridge plucked-string sitar (104), shamisen (106), and koto (107); the banjo (105) stays on Karplus-Strong
Programs 112-119, percussivePercussion-engine tinkle bell, agogo, steel drums, woodblock, taiko drum, melodic tom, synth drum, and reverse cymbal
Drums and GS variantsGM/GS drum-kit variants and GM2/GS bank fallbacks, with GS EFX routed to built-in insert chains where available

Two notes worth knowing up front: named pipe-organ colors like bourdon and trumpet-rank live only in the named preset catalog, not in GM program routing (program 19 is the church-organ flue pipe, and programs 20-23 are the free-reed reed-organ, harmonica, and bandoneon); and program 6 (Harpsichord) is the one GM program whose fallback also reads Bank Select, choosing between plain, octave-mix, wide-stereo, and key-off-noise registrations.

For beginners, the practical rule is simple: use SoundFont when you need exact or production-ready sampled instruments; rely on NativeSynth fallback when you need a small, always-available preview or a missing-program safety net.

The named preset catalog

NativeSynth ships a named preset catalog. Do not hardcode preset names — list them from the runtime with synthPresetNames(), and inspect any one as a SynthPatch with synthPresetPatch(name).

SYNTH · PRESETIDLE
Representative presets — one note, many engines

The same A3, auditioned through representative named presets from the current NativeSynth catalog. Each patch comes from `synthPresetPatch(name)` and keeps its actual engine character: subtractive, FM, buzzing-bridge plucked string, modal, drawbar organ, percussion, piano, pipe organ, bowed string, reed, brass, or flute. The drum kit is the exception: it plays the GM percussion map, where a key selects a kit piece instead of a pitch, so that key strikes a crash cymbal. Change the preset, watch the envelope and waveshape, then press play to compare them.

Preset
typescript
import { init, synthPresetNames, synthPresetPatch } from '@libraz/libsonare';

await init();

synthPresetNames();
// ['sine', 'saw', 'square', 'triangle', 'saw-lead', 'square-lead', 'sub-bass',
//  'warm-pad', 'e-piano', 'bell', 'brass', 'pluck', 'classical-guitar',
//  'steel-guitar', 'electric-guitar', 'harp', 'bass-acoustic', ...,
//  'church-organ', 'violin', 'clarinet', 'trumpet', 'concert-flute', ...,
//  'harp-plucked', 'koto', 'sitar', 'tanpura', ...]

const pad = synthPresetPatch('warm-pad');
// { preset: 'warm-pad', engineMode: 'subtractive', waveform: 'saw',
//   unison: 7, detuneCents: 18, cutoffHz: 2800, ampAttackMs: 400, ... }
python
import libsonare as sonare

sonare.synth_preset_names()
# ['sine', 'saw', 'square', 'triangle', 'saw-lead', 'square-lead', 'sub-bass',
#  'warm-pad', 'e-piano', 'bell', 'brass', 'pluck', 'classical-guitar',
#  'steel-guitar', 'electric-guitar', 'harp', 'bass-acoustic', ...,
#  'church-organ', 'violin', 'clarinet', 'trumpet', 'concert-flute', ...,
#  'harp-plucked', 'koto', 'sitar', 'tanpura', ...]

pad = sonare.synth_preset_patch("warm-pad")
# SynthPatch(preset='warm-pad', engine_mode='subtractive', waveform='saw',
#            unison=7, detune_cents=18.0, cutoff_hz=2800.0, ...)

The catalog maps to the engines like this (one preset per row is enough to feel each engine):

PresetEngineGood for
sine saw square triangle saw-lead square-lead sub-bass warm-padsubtractiveleads, basses, pads
e-pianofmelectric piano, bells, brass
classical-guitar steel-guitar electric-guitar harp bass-acoustic bass-fingered bass-picked bass-fretless bass-slapkarplus-strongplucked strings and basses
marimba glass bellmodaltuned mallets
organadditivedrawbar organ
drum-kitpercussionGM drum map
acoustic-pianopianoacoustic piano
church-organ church-flute church-bourdon church-trumpetpipe-organpipe organ ranks
violin viola cello contrabassbowed-stringbowed strings
clarinet soprano-sax alto-sax tenor-sax baritone-sax oboe english-horn bassoonreedreed woodwinds
brass trumpet trombone tuba french-horn muted-trumpet cornet flugelhorn euphoniumbrassbrass instruments
concert-flute piccolo recorder pan-flute shakuhachi tin-whistle ocarina blown-bottlefluteair-jet flutes and whistles
pluck harp-plucked koto sitar tanpuraplucked-stringbuzzing-bridge plucked strings
choir-aah choir-ooh voice-eehvocalchoir and solo voices
accordion harmonica bandoneon reed-organfree-reedaccordion, harmonica, reed organ

The roll below sequences one three-voice phrase and bounces it through bounceWithSynthInstrument(presetName, …). The instrument selector walks across representative piano, FM, plucked-string, modal, organ, bowed-string, reed, brass, and flute presets, so the same notes audibly take on each engine's character.

MIDI · PIANO ROLLIDLE
A MIDI passage — same notes, any instrument

A three-voice phrase — melody, chords, and bass — drawn as a piano roll. The notes never change; switch the instrument and the engine bounces the exact same MIDI through a different built-in voice. Drag the tempo and the whole sequence speeds up or slows down. Press play to hear the passage; the playhead tracks the audio.

Instrument
Tempo
100 BPM

The va: routing prefix

A preset name may carry a va: prefix (for example va:saw-lead, va:e-piano). The prefix is accepted everywhere a preset name issynthPresetPatch, bounceWithSynthInstrument, and setSynthInstrument — and resolves to the same patch as the bare name. It is a routing convention some hosts use to mark "this destination plays the virtual-analog NativeSynth"; the synth strips it before lookup.

The drum-kit preset and the GM drum map

drum-kit selects the percussion engine and maps incoming MIDI notes to the General MIDI drum map — note 36 is the kick, note 38 the acoustic snare, and so on — rather than treating note number as pitch. Route a drum pattern's notes to a destination bound to drum-kit and each note triggers its mapped piece.

GS / GM drum-kit variants

drum-kit also recognizes GS-style drum-kit selection (GS is Roland's General MIDI extension set; kits are addressed by bank-128 program numbers) and reshapes the Standard kit per variant at note-on — more shell body for Room, bigger/lower shells for Power, and so on. Two naming systems overlap at kit 25: the GS bank-128 name and the GM2 percussion-set name differ, which is a property of the two standards rather than a bug.

Kit no.GS (bank-128) nameGM2 percussion-set nameVoicing change vs Standard
0StandardStandard
8RoomRoommore shell body, longer ambient tail
16PowerPowerbigger/lower/longer shells
24ElectronicElectronicsine-ified, dried-out membranes
25TR-808Analogclassic decaying-sine kick/snare/tom
32JazzJazztighter, higher, softer
40BrushBrushsnare becomes a sustained swish
48OrchestraOrchestralonger membrane/cymbal tails
56SFXSFXrecognized/addressed; per-note SFX sounds not yet modeled (plays Standard voicing)

SFX kit and Sound-Effects programs are addressed, not yet modeled

The GS-style SFX drum kit (kit 56) and the GM Sound-Effects programs (120-127, covered in the GM tone map below) are addressed and named but their per-note effect sounds are not yet individually synthesized in the data-free fallback — the SFX kit plays the Standard kit's voicing, and programs 120-127 share one generic noise voice. A SoundFont that supplies real effect samples for these addresses plays back normally through the SF2 player.

The SynthPatch object

Think of a SynthPatch as "a preset, plus your tweaks". It starts from a base — the named preset (omit it for the default subtractive init patch) — and every field you set overrides that base. Leave a field out and the base value stays.

The most useful beginner workflow is small and reversible: choose a preset, change one or two audible fields, listen, then reset or move on. For example, start from warm-pad, lengthen ampAttackMs for a slower fade-in, lower cutoffHz for a darker tone, or raise stereoSpread for a wider pad. You do not need to fill the whole object.

Absent and zero are different

What decides whether a field overrides the base is presence, not value. Omit a numeric field and the base value stays; set one and it overrides the base, clamped to its audible range. That includes an explicit 0 — writing ampSustain: 0 really does drop the sustain to zero, and stereoSpread: 0 really does collapse the patch to the centre. Enum fields still use 'default' to mean "keep".

The patch carries a per-field "was this set?" record alongside a struct version, which is what keeps "absent" and "zero" apart. Earlier builds could not tell them apart and had to treat a zero as "untouched", so older code sometimes wrote a token value such as ampSustain: 0.001 to approximate a real zero. That workaround is no longer needed — write the 0 you mean.

One more rule: a non-empty modRoutings array replaces the base mod matrix entirely, rather than adding to it. An empty array clears it, while omitting the key keeps the base matrix.

The patch exposes the shared controls every engine uses:

SYNTH · SUBTRACTIVEIDLE
ADSR envelope — how a note rises and falls

The same held A3, shaped by its amplitude envelope. Attack and decay set how the note reaches and leaves its peak; sustain is the level it holds while a key is down; release is the fade after the key lifts. The top outline is that envelope — drag a control and watch the shape, then press play to hear it. (Pull sustain all the way to zero and the note decays to silence while the key is still down: a plucked shape rather than a held one.)

Attack
8 ms
Decay
140 ms
Sustain
0.75
Release
280 ms

Cents, velocity, and key tracking

  • Cent — 1/100 of a semitone; 100 cents = one piano key, 1200 = an octave. Pitch and detune amounts are in cents.
  • Velocity — how hard a note was struck (0–127); presets use it to control brightness or loudness.
  • Key tracking — making a parameter (like filter cutoff) follow the note's pitch up the keyboard.
GroupFields
OscillatorengineMode, waveform, unison (1-7), detuneCents, driftCents, drive (0-1)
FilterfilterModel, filterOutput (SVF only), cutoffHz, resonanceQ, keyTrack (0-1), envToCutoffCents, velToCutoffCents
Amp envelopeampAttackMs, ampDecayMs, ampSustain, ampReleaseMs
Filter envelopefilterAttackMs, filterDecayMs, filterSustain, filterReleaseMs
LFOs & glidelfoRateHz, lfoToPitchCents, lfo2RateHz, glideMs
Body resonancebody (none / guitar / violin / wood-tube / brass-bell / vocal), bodyMix (0-1)
Stereo & outputstereoSpread (0-1), gain (linear), polyphony (1-64), busDrive (0-1)
Mod matrixmodRoutings (up to 8)
Binding (JS only)destinationId (default 0)

(Polyphony is how many notes can sound at once; a voice is one sounding note, and voice stealing cuts the oldest note when you run out.)

LFO 2 needs a routing

The two LFOs behave differently. LFO 1 (lfoRateHz + lfoToPitchCents) is hardwired to pitch and produces vibrato on its own. LFO 2 is matrix-only: setting lfo2RateHz does nothing until a modRoutings entry uses source: 'lfo2' to send it to a destination.

Each mod routing is { source, destination, depth }. The mod matrix lets envelopes, LFOs, velocity, key tracking, the mod wheel, and a seeded per-voice random source modulate pitch, filter cutoff, amplitude, and pan. depth is in destination units at full source deflection.

SYNTH · SUBTRACTIVEIDLE
LFO tremolo — modulation you can see

An LFO (low-frequency oscillator) is too slow to hear as a pitch; instead it moves something else. Here LFO 1 is routed to amplitude — tremolo — so the envelope ripples instead of holding flat. Rate sets how fast it pulses; depth sets how far. Turn depth to zero and the ripple disappears, leaving the plain held note. Press play to hear the pulsing. This is one routing in the mod matrix; the same LFO aimed at pitch would be vibrato, at the cutoff a filter wobble.

Rate
6 Hz
Depth
0.6

The body field is NativeSynth's body/formant resonance layer — the resonant character of an instrument's physical shell or vocal tract. Acoustic guitars, harps, violin-family strings, woodwinds, brass, and choir/voice fallbacks use this layer; solid-body electrics can leave body at none.

Pitch bend, controller reset, and per-channel state

NativeSynth responds to pitch-bend messages, and the bend range follows RPN 0 (the standard pitch-bend-range parameter, set with the CC6 / CC38 Data Entry MSB/LSB fine-byte pair — default ±2 semitones). A MIDI Reset All Controllers message returns the performance controllers (mod wheel, expression, pitch-bend value, the pedals) and the RPN/NRPN selection to their defaults, but it deliberately leaves the bend range where you set it — send RPN 0 again if you want ±2 semitones back. You drive these with ordinary MIDI events: pitch-bend events (e.g. Project.midiPitchBend(...) offline) and the RPN 0 / data-entry / reset CCs in your stream.

This state is tracked per channel, not per note: NativeSynth does not track polyphonic (per-note) or channel pressure at all, and MIDI 2.0 note velocity is quantized down to the ordinary 7-bit range rather than kept at full 16-bit resolution. If you need MPE-style (MIDI Polyphonic Expression) per-note pitch-bend and pressure, or full 16-bit velocity, reach for the simpler built-in waveform synth instead — see setBuiltinInstrument in MIDI Input.

Piano-style pedal controls are decoded as ordinary MIDI CCs. Sustain pedal CC64 supports half-pedal damping only on the piano engine — there, intermediate values 64-126 damp ringing key-up notes proportionally; on every other engine CC64 is a plain on/off sustain switching at the 64 threshold, so 64 and 126 sound the same as 127. CC66 acts as sostenuto, and CC67 applies una-corda / soft-pedal voicing where the active preset uses it.

Enum name tables

Every enum field accepts either a name string or its C ordinal. Read the authoritative tables from the runtime with synthEnumTables() so names and ordinals never drift:

typescript
import { init, synthEnumTables } from '@libraz/libsonare';

await init();
synthEnumTables();
// {
//   engineModes:      ['default', 'subtractive', 'fm', 'karplus-strong',
//                      'modal', 'additive', 'percussion', 'piano',
//                      'pipe-organ', 'bowed-string', 'reed', 'brass', 'flute',
//                      'plucked-string', 'vocal', 'free-reed'],
//   waveforms:        ['default', 'sine', 'saw', 'square', 'triangle', 'noise'],
//   builtinWaveforms: ['sine', 'saw', 'sawtooth', 'square', 'triangle'],
//   filterModels:     ['default', 'svf', 'moog-ladder', 'diode-ladder', 'sallen-key'],
//   filterOutputs:    ['default', 'lowpass', 'bandpass', 'highpass'],
//   bodyTypes:        ['default', 'none', 'guitar', 'violin', 'wood-tube',
//                      'brass-bell', 'vocal'],
//   modSources:       ['none', 'amp-env', 'filter-env', 'lfo1', 'lfo2',
//                      'velocity', 'key-track', 'mod-wheel', 'random'],
//   modDestinations:  ['none', 'pitch-cents', 'cutoff-cents', 'amp-gain', 'pan-units'],
// }

The same arrays are also exported as named constants (SYNTH_ENGINE_MODES, SYNTH_OSC_WAVEFORMS, SYNTH_FILTER_MODELS, SYNTH_FILTER_OUTPUTS, SYNTH_BODY_TYPES, SYNTH_MOD_SOURCES, SYNTH_MOD_DESTINATIONS, plus BUILTIN_SYNTH_WAVEFORMS). Note the index 0 in most tables is 'default' (keep the base value); modSources / modDestinations use 'none' instead.

builtinWaveforms / BUILTIN_SYNTH_WAVEFORMS is a separate list: it belongs to the minimal built-in oscillator synth (setBuiltinInstrument), not to NativeSynth's waveform field. It has no 'default' entry, accepts 'sawtooth' as well as 'saw', and does not accept 'noise'.

Render offline: bounceWithSynthInstrument

To turn a MIDI arrangement into audio, bind a NativeSynth instrument to your MIDI destination and bounce. Pass a preset-name string, a SynthPatch, or an array of either to bind several destinations at once. When you pass an array, each SynthPatch may set destinationId (default 0) to choose which MIDI destination it binds to — for example [{ preset: 'saw-lead', destinationId: 0 }, { preset: 'drum-kit', destinationId: 1 }] renders two destinations from one call. destinationId is a JS binding convenience, not part of the NativeSynth patch itself (Python takes the destination as a separate argument instead). An explicitly empty array [] (or a runtime null) produces zero bindings; omitting the argument — or passing undefined — falls back to {} and still creates one default binding. The render is deterministic for a fixed project, options, and patch.

Following GM programs instead of pinning one patch

A binding normally pins one patch to a destination: every note through it plays that voice, whatever program changes the MIDI carries. For a general MIDI file that is the wrong shape — you want each channel to pick up its own instrument.

Turn on GM program following and the synth resolves melodic voices from the tracked bank and program change, and routes MIDI channel 10 through the GM drum-kit map. The bound patch stays as the fallback for anything the map does not cover, so nothing goes silent. With the mode off, the fixed-patch behaviour above is unchanged.

python
# Python
audio = project.bounce_with_synth_instrument(
    "acoustic-piano",          # fallback for unmapped programs
    auto_select_gm=True,
    sample_rate=48000,
)
typescript
// A SynthPatch object carries the JS binding option; a preset string cannot.
const audio = project.bounceWithSynthInstrument(
  { preset: 'acoustic-piano', useGmPrograms: true },
  { totalFrames: 48000, numChannels: 2 },
);

The flag is use_gm_programs on the C ABI's SonareSynthInstrumentBinding, auto_select_gm in Python, and useGmPrograms on the WASM/Node JavaScript SynthPatch descriptor. All default to false, preserving the fixed-patch fallback. useGmPrograms is a JS binding convenience, not a NativeSynth patch field. On both CLIs it is the bare --synth flag (sonare project bounce --in project.json --synth -o out.wav); passing a preset name instead pins that patch.

typescript
import { init, Project } from '@libraz/libsonare';

await init();

const project = new Project();
project.setSampleRate(48000);

// One MIDI clip: a 2-beat C4 note routed to destination 0.
const { trackId, clipId } = project.addMidiClip(0, 4);
project.setTrackMidiDestination(trackId, 0);
project.setMidiEvents(clipId, [
  Project.midiNoteOn(0, 0, 0, 60, 100),
  Project.midiNoteOff(2, 0, 0, 60, 0),
]);

try {
  // Bind a named preset to destination 0 and render stereo.
  const audio = project.bounceWithSynthInstrument('va:saw-lead', {
    totalFrames: 48000,
    numChannels: 2,
  });
  // audio is interleaved Float32 (frames * channels); non-silent.
} finally {
  project.delete();   // the WASM handle is NOT garbage-collected — always release it
}
python
import libsonare as sonare

project = sonare.Project()
project.set_sample_rate(48000)

track_id, clip_id = project.add_midi_clip(0, 4)
project.set_track_midi_destination(track_id, 0)
project.set_midi_events(clip_id, [
    sonare.Project.midi_note_on(0, 0, 0, 60, 100),
    sonare.Project.midi_note_off(2, 0, 0, 60, 0),
])

# Bind a named preset to destination 0 and render -> (frames, channels) float32.
audio = project.bounce_with_synth_instrument(
    "va:saw-lead", total_frames=48000, num_channels=2,
)
project.close()
bash
# --synth <preset> takes any name from the NativeSynth preset catalog, not just the
# oscillator waveforms — run `sonare project synth-presets` for the full list.
# Bare --synth follows the project's GM program changes instead.
# A custom SynthPatch object (rather than a preset name) is binding-only
# (see Browser / Python above).
sonare project bounce --in song.json -o synth.wav --synth saw
sonare project bounce --in song.json -o pad.wav --synth warm-pad

To customize, pass a SynthPatch instead of a name — start from a preset and override:

typescript
const audio = project.bounceWithSynthInstrument(
  {
    preset: 'warm-pad',
    cutoffHz: 1200,                // darker than the preset's 2800 Hz
    resonanceQ: 3,
    modRoutings: [{ source: 'lfo1', destination: 'cutoff-cents', depth: 600 }],
  },
  { totalFrames: 48000, numChannels: 2 },
);

Leave totalFrames at 0 and the bounce auto-derives the length from the arrangement plus the patch's release tail. Unknown preset names throw. For everything bounceWith* shares — channels, sample rate, latency — see Project Bounce.

Render live: setSynthInstrument + MIDI input

For interactive playback, bind the synth to a destination on a RealtimeEngine and feed it MIDI. The snippet below runs entirely on the control thread (no AudioWorklet needed) and produces non-zero samples.

typescript
import { init, RealtimeEngine } from '@libraz/libsonare';

await init();

const engine = new RealtimeEngine(48000, 128);
try {
  engine.setSynthInstrument('va:saw-lead', 7);   // bind to destination 7
  engine.pushMidiNoteOn(7, 0, 0, 60, 100);       // destination, group, channel, note, velocity

  const out = engine.process([new Float32Array(128), new Float32Array(128)]);
  // out[0] / out[1] are the rendered stereo block; non-silent.

  engine.midiInstrumentCount();                   // 1
} finally {
  engine.destroy();   // release the native handle
}

In a real app you would drive pushMidiNoteOn / pushMidiNoteOff / pushMidiCc from a live keyboard, or enable the engine-owned MIDI input source and push events as they arrive — see MIDI Input. setSynthInstrument resolves a preset name or SynthPatch exactly like bounceWithSynthInstrument, so a sound you dialed in offline plays identically live.

NativeSynth and the SoundFont fallback

NativeSynth is the safety net under the SoundFont player. When you render with bounceWithSf2Instrument (or bind an SF2 live), libsonare resolves each (channel, bank, program) the arrangement actually plays:

  • if the loaded SoundFont covers the program, that note renders from the SF2 (GS variation and drum fallbacks included);
  • otherwise — including when no SoundFont is loaded at all — the note plays through the NativeSynth GM fallback bank (all 128 programs plus the drum map).

Inspect the per-program backend before rendering with soundFontManifest(), which reports 'sf2' or 'synth' for each program in first-use order:

typescript
project.loadSoundFont(sf2Bytes);
const manifest = project.soundFontManifest();
// [{ channel, bank, program, backend: 'sf2' | 'synth', presetName }, ...]

Because the GM fallback bank is always present, MIDI never renders silent for lack of data. See SoundFont Player for loading SF2 data and per-channel/program resolution.

GM fallback program routing

The fallback bank uses the closest NativeSynth engine for each GM program family, with a few program-level overrides where the instrument behavior matters. Acoustic-style rows below are still provisional calibration targets, so read them as routing coverage, not as a claim of final sampled-instrument realism.

GM programInstrumentFallback engineWhy
4-5Electric Piano 1 / 2fmphase-modulated tine/bell brightness
6Harpsichordkarplus-strongquill-plucked string with near velocity-insensitive brightness
7Clavifmstruck string and pickup color, currently approximated by FM
8, 10, 14Celesta, Music Box, Tubular Bellsmodalfelt-struck steel bar, twin-tooth tine shimmer, and a missing-fundamental strike pitch
9, 11-13Glockenspiel, Vibraphone, Marimba, Xylophonemodaltuned-bar resonators
15Dulcimerkarplus-strongprovisional; hammered (struck, not plucked) string
16-23Organ familyadditive / pipe-organ / free-reeddrawbar registrations (16-18), the provisional church-organ flue pipe (19), and free-reed reed-organ, harmonica, and bandoneon voices (20-23)
24-31Guitar familykarplus-strongplucked string waveguide
32-37Acoustic, electric, fretless, and slap basseskarplus-strongbass-string waveguide with program-specific slap/polarization
40-43Violin, Viola, Cello, Contrabassbowed-stringprovisional sustained friction-excited string waveguide
44Tremolo Stringssubtractivedetuned-saw section with an amplitude-tremolo LFO rather than a bowed model
45-46Pizzicato Strings, Orchestral Harpkarplus-strongshort pluck into a violin-body or steel-string corpus
47Timpanipercussionnote-tracked kettledrum fallback voice
48String Ensemble 1subtractivepad-like ensemble fallback rather than solo bow model
52-54Choir Aahs, Voice Oohs, Synth Voicevocalsource-filter voice (glottal source + vowel formant bank), not a subtractive pad
56-60Trumpet, Trombone, Tuba, Muted Trumpet, French Hornbrassprovisional lip-reed brass waveguide
61-63Brass Section, Synth Brass 1 / 2fmFM by design, not the brass waveguide
64-71Saxophones, Oboe, English Horn, Bassoon, Clarinetreedprovisional reed and bore waveguides
72-79Piccolo, Flute, Recorder, Pan Flute, Bottle, Shakuhachi, Whistle, Ocarinafluteprovisional air-jet / open-pipe waveguides
104, 106, 107Sitar, Shamisen, Kotoplucked-stringbuzzing-bridge (jawari / sawari) plucked string; the banjo (105) stays on karplus-strong
112-119Tinkle Bell, Agogo, Steel Drums, Woodblock, Taiko Drum, Melodic Tom, Synth Drum, Reverse Cymbalpercussionnote-tracked percussion-engine voices, distinct from the drum-kit map

Program 6 (Harpsichord) is the one GM program whose fallback also reads Bank Select: bank 0 plays a plain 8′ registration, bank 1 adds an octave (8′+4′) mix, bank 2 widens to a two-choir stereo spread, and bank 3 adds key-off jack noise.

This routing is separate from the named preset catalog: synthPresetNames() still lists the hand-authored presets (e-piano, harp, drum-kit, and so on), while the GM fallback bank chooses the internal patch for each MIDI program number during SF2 fallback.

GM tone map — all 128 programs

Every General MIDI program resolves to one of the fifteen engines. The table below is the data-free fallback voicing NativeSynth uses for each GM program number when no SoundFont covers it; the canonical instrument names are also available at runtime from Project.gmInstrumentName(program). Rows marked provisional use one of the acoustic physical models still being calibrated.

Show the full 128-program tone map

Model statusstable: the subtractive, FM, modal, additive, and percussion cores are settled. provisional: the piano, Karplus-Strong, pipe-organ, bowed-string, reed, brass, flute, plucked-string (buzzing-bridge), vocal, and free-reed physical models are still being calibrated.

Piano (0-7)

ProgInstrumentEngineNotes
0Acoustic Grand Pianopianoprovisional; shared modal soundboard
1Bright Acoustic Pianopianoprovisional
2Electric Grand Pianopianoprovisional (the acoustic waveguide, not FM)
3Honky-tonk Pianopianoprovisional
4Electric Piano 1fmtine/bell FM
5Electric Piano 2fmshares the EP1 voicing
6Harpsichordkarplus-strongquill pluck; bank-aware registrations (see the note above)
7Clavifmbright high-ratio FM

Chromatic Percussion (8-15)

ProgInstrumentEngineNotes
8Celestamodalsoft felt-struck steel bar
9Glockenspielmodaluniform-bar mode ratios
10Music Boxmodaltwin-tooth beating for tine shimmer
11Vibraphonemodalmotor tremolo (LFO → amplitude)
12Marimbamodaldeep-arch bar, wood-tube body
13Xylophonemodalshort, dry deep-arch bar
14Tubular Bellsmodalmissing-fundamental strike pitch, long ring
15Dulcimerkarplus-strongprovisional; hammered (struck) string

Organ (16-23)

ProgInstrumentEngineNotes
16Drawbar Organadditive9-drawbar Hammond
17Percussive Organadditive
18Rock Organadditive
19Church Organpipe-organprovisional; multi-rank plenum
20Reed Organfree-reedprovisional; harmonium — mellow plate, soft tongues
21Accordionfree-reedprovisional; shares the reed-organ voicing
22Harmonicafree-reedprovisional; small, bright, stiff tongues + hand vibrato
23Tango Accordionfree-reedprovisional; bandoneon, musette (wet-beating) detune

Guitar (24-31)

ProgInstrumentEngineNotes
24Acoustic Guitar (nylon)karplus-strongprovisional; softer pluck, no dispersion
25Acoustic Guitar (steel)karplus-strongprovisional; steel-string dispersion + sympathetic
26Electric Guitar (jazz)karplus-strongprovisional; near-bridge pickup, no body
27Electric Guitar (clean)karplus-strongprovisional; shares the jazz voicing
28Electric Guitar (muted)karplus-strongprovisional; choked (palm-mute) decay
29Overdriven Guitarkarplus-strongprovisional; pre-filter drive
30Distortion Guitarkarplus-strongprovisional; harder drive
31Guitar Harmonicskarplus-strongprovisional

Bass (32-39)

ProgInstrumentEngineNotes
32Acoustic Basskarplus-strongprovisional; large resonating body
33Electric Bass (finger)karplus-strongprovisional; pickup + two-polarization beat
34Electric Bass (pick)karplus-strongprovisional; bright near-bridge attack
35Fretless Basskarplus-strongprovisional; rounder, glide-friendly
36Slap Bass 1karplus-strongprovisional; thumb slap + fret-slap buzz
37Slap Bass 2karplus-strongprovisional; sharper pop
38Synth Bass 1subtractivesynth bass by design
39Synth Bass 2subtractivesynth bass by design

Strings (40-47)

ProgInstrumentEngineNotes
40Violinbowed-stringprovisional
41Violabowed-stringprovisional; darker/slower
42Cellobowed-stringprovisional
43Contrabassbowed-stringprovisional; darkest/slowest
44Tremolo Stringssubtractivedetuned-saw section with an amplitude-tremolo LFO
45Pizzicato Stringskarplus-strongprovisional; short pluck into a violin-body corpus
46Orchestral Harpkarplus-strongprovisional; long undamped ring
47Timpanipercussionnote-tracked kettledrum

Ensemble (48-55)

ProgInstrumentEngineNotes
48String Ensemble 1subtractivewide supersaw pad with section vibrato
49String Ensemble 2subtractive
50SynthStrings 1subtractive
51SynthStrings 2subtractive
52Choir Aahsvocalprovisional; open /a/ vowel, glottal source + formants
53Voice Oohsvocalprovisional; darker closed /u/ vowel
54Synth Voicevocalprovisional; brighter, steadier synthetic vowel
55Orchestra Hitsubtractivebright detuned-saw stab

Brass (56-63)

ProgInstrumentEngineNotes
56Trumpetbrassprovisional; lip-reed waveguide
57Trombonebrassprovisional
58Tubabrassprovisional; dark, conical
59Muted Trumpetbrassprovisional; physical mute model
60French Hornbrassprovisional; rounder, conical
61Brass SectionfmFM by design (not the brass waveguide)
62SynthBrass 1fmFM by design
63SynthBrass 2fmFM by design

Reed (64-71)

ProgInstrumentEngineNotes
64Soprano Saxreedprovisional; conical bore
65Alto Saxreedprovisional; conical
66Tenor Saxreedprovisional; conical
67Baritone Saxreedprovisional; conical, darkest sax
68Oboereedprovisional; conical, bright/nasal
69English Hornreedprovisional; conical
70Bassoonreedprovisional; conical, low
71Clarinetreedprovisional; cylindrical bore (odd harmonics)

Pipe (72-79) — air-jet flute engine

ProgInstrumentEngineNotes
72Piccolofluteprovisional; brightest
73Flutefluteprovisional
74Recorderfluteprovisional
75Pan Flutefluteprovisional; breathy vortex
76Blown Bottlefluteprovisional; dark, high damping
77Shakuhachifluteprovisional; breathiest
78Whistlefluteprovisional
79Ocarinafluteprovisional; closed-vessel

Synth Lead (80-87) — all subtractive

ProgInstrumentEngineNotes
80Lead 1 (square)subtractive3-osc detuned lead through a Moog-ladder filter
81Lead 2 (sawtooth)subtractive
82Lead 3 (calliope)subtractive
83Lead 4 (chiff)subtractive
84Lead 5 (charang)subtractive
85Lead 6 (voice)subtractive
86Lead 7 (fifths)subtractive
87Lead 8 (bass + lead)subtractive

Synth Pad (88-95) — all subtractive

ProgInstrumentEngineNotes
88Pad 1 (new age)subtractive7-osc supersaw pad
89Pad 2 (warm)subtractive
90Pad 3 (polysynth)subtractive
91Pad 4 (choir)subtractive
92Pad 5 (bowed)subtractive
93Pad 6 (metallic)subtractive
94Pad 7 (halo)subtractive
95Pad 8 (sweep)subtractive

Synth Effects (96-103) — all subtractive

ProgInstrumentEngineNotes
96FX 1 (rain)subtractivedrifting detuned triangles
97FX 2 (soundtrack)subtractive
98FX 3 (crystal)subtractive
99FX 4 (atmosphere)subtractive
100FX 5 (brightness)subtractive
101FX 6 (goblins)subtractive
102FX 7 (echoes)subtractive
103FX 8 (sci-fi)subtractive

Ethnic (104-111) — buzzing-bridge plucked + karplus-strong

ProgInstrumentEngineNotes
104Sitarplucked-stringprovisional; jawari bridge buzz, long shimmering ring
105Banjokarplus-strongprovisional; shared pluck sketch
106Shamisenplucked-stringprovisional; sawari buzz, drier and harder than the sitar
107Kotoplucked-stringprovisional; bridge-buzz plucked string
108Kalimbakarplus-strongprovisional; shared pluck sketch
109Bag pipekarplus-strongprovisional; shared pluck sketch (no reed drone yet)
110Fiddlekarplus-strongprovisional; shared pluck sketch (not bowed yet)
111Shanaikarplus-strongprovisional; shared pluck sketch (no reed model yet)

Percussive (112-119) — all percussion

ProgInstrumentEngineNotes
112Tinkle Bellpercussionsparse inharmonic modes
113Agogopercussiontwo-tone metal bell
114Steel Drumspercussionnear-harmonic modes
115Woodblockpercussionvery short, with stick click
116Taiko Drumpercussionstrong pitch drop + shell boom
117Melodic Tompercussionnote-tracked, with shell body
118Synth Drumpercussiondecaying-sine electronic drum
119Reverse Cymbalpercussionlong rising swell (simulated reverse)

Sound Effects (120-127) — generic placeholder

GM · FALLBACKIDLE
GM Sound-Effects — the data-free fallback

Audition the built-in fallback for the General MIDI Sound-Effects family (programs 120–127), rendered with no SoundFont loaded. In the current build these eight programs share one generic noise-based placeholder voice — so they sound alike on purpose; per-effect modeling is future work. Load a SoundFont that supplies real effect samples and those addresses play its sounds instead. Press play to hear the current data-free fallback.

Program

The demo above auditions all eight GM Sound-Effects programs directly — a quick way to hear that they currently share one voice instead of eight distinct effects.

ProgInstrumentEngineNotes
120Guitar Fret Noisesubtractivegeneric resonant-noise placeholder (see note below)
121Breath Noisesubtractivegeneric resonant-noise placeholder
122Seashoresubtractivegeneric resonant-noise placeholder
123Bird Tweetsubtractivegeneric resonant-noise placeholder
124Telephone Ringsubtractivegeneric resonant-noise placeholder
125Helicoptersubtractivegeneric resonant-noise placeholder
126Applausesubtractivegeneric resonant-noise placeholder
127Gunshotsubtractivegeneric resonant-noise placeholder

Note on 120-127: in the data-free fallback these eight programs currently share one generic noise-through-a-resonant-bandpass voice, differentiated only by the note played — there is no per-effect procedural model yet. A SoundFont that covers these programs plays its own samples instead.

Current status and limitations

The physical models are provisional and still being calibrated. Ten of the fifteen engines are provisional physical models of acoustic instruments — piano, plucked string (Karplus-Strong), bowed string, reed woodwind, brass, air-jet flute, pipe organ, buzzing-bridge plucked string, source-filter vocal, and free reed. (The modal and membrane-percussion engines are also physical models, but their voicing is already mature — see below.) They are designed for data-free preview and as the GM fallback floor, not as finished sampled-instrument replacements. Their voicing is tuned by a developer-run A/B harness that compares the synth against a reference SoundFont; this is a manual, ongoing loop, not an automatic or verified-against-reference calibration, and the tuning is not finished. Recent work continues to retune the piano, organ, brass, reed, and violin-family voicing.

Some advanced physics is implemented but not yet reachable. The bowed string, reed, brass, and flute engines carry richer nonlinear refinements (elasto-plastic bow friction, tonehole scattering, a brass "cuivré" edge, flute overblow, and more). These exist in the core and default to off — no public binding exposes a switch to turn them on yet — so the sound you get today is the simpler linear model. Expect these to become reachable, and the voicing to keep improving, in future releases.

A couple of self-oscillating models have a small residual intonation error. The air-jet flute and flue pipe-organ lock slightly off the naive tuning and are corrected by a calibrated factor; a small, note-dependent residual remains.

The other five engines are settled. Subtractive (virtual-analog), FM, and additive (drawbar organ) are signal-based (non-physical); modal (mallets/bells) and membrane percussion are physical models whose voicing is already mature. None carry provisional caveats — they are the settled core.

Where the sounds come from. The synthesis engines are original implementations of published synthesis and physical-modelling algorithm families, and the GM/GS behavior follows the openly documented General MIDI / GS addressing — no sampled or captured instrument audio is bundled, and the result is an independent re-creation rather than a copy of any specific device. For the standards and papers behind each engine, see Algorithm References.

Recipes

Audition every engine from one project

Bounce the same MIDI clip through one preset per engine to hear each voice.

typescript
const project = new Project();
project.setSampleRate(48000);
const { trackId, clipId } = project.addMidiClip(0, 4);
project.setTrackMidiDestination(trackId, 0);
project.setMidiEvents(clipId, [
  Project.midiNoteOn(0, 0, 0, 60, 100),
  Project.midiNoteOff(2, 0, 0, 60, 0),
]);
try {
  for (const preset of ['saw-lead', 'e-piano', 'electric-guitar',
                         'marimba', 'organ', 'drum-kit', 'acoustic-piano',
                         'church-organ', 'violin', 'clarinet', 'trumpet',
                         'concert-flute']) {
    const audio = project.bounceWithSynthInstrument(preset, { totalFrames: 48000 });
    // render / inspect each preset's audio
  }
} finally {
  project.delete();
}
Play a drum pattern through the GM drum map

Route drum notes (kick 36, snare 38, hat 42, ...) to a destination bound to drum-kit.

typescript
project.setMidiEvents(clipId, [
  Project.midiNoteOn(0, 0, 9, 36, 110),   // kick
  Project.midiNoteOff(1, 0, 9, 36, 0),
  Project.midiNoteOn(0, 0, 9, 38, 100),   // snare
  Project.midiNoteOff(1, 0, 9, 38, 0),
]);
const audio = project.bounceWithSynthInstrument('drum-kit', { totalFrames: 24000 });

Each note triggers its mapped GM piece rather than playing the note as a pitch.

A custom patch with an LFO wobble

Start from warm-pad, darken the filter, and wobble the cutoff with LFO 1.

typescript
const audio = project.bounceWithSynthInstrument(
  {
    preset: 'warm-pad',
    cutoffHz: 1200,
    resonanceQ: 3,
    lfoRateHz: 6,
    modRoutings: [{ source: 'lfo1', destination: 'cutoff-cents', depth: 600 }],
  },
  { totalFrames: 48000, numChannels: 2 },
);

A non-empty modRoutings replaces the preset's mod matrix entirely.