Skip to content

GM and GS Fallback Bank ​

This page covers the General MIDI and GS side of the built-in synthesizer: the data-free GM fallback bank with its GS variation tones and drum-kit variants, how a bounce follows GM program changes, the Roland-GS architecture layer and insertion effects the SoundFont player implements, and when a note falls from a SoundFont to the bank. The per-program voicing table is on GM Tone Map.

What lives here is the concrete side of GS — the addresses, the tables, the per-slot detail. Why a browser audio engine speaks GS at all, and how the places it parts company with the specification sort into extension, deliberate divergence and reduction, is on Sound Sources.

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. The piano has been tuned; every other physical fallback model still awaits adjustment and calibration. Further work is planned for future patch releases. 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 the jack-and-plectrum harpsichord with its three registrations
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

One note 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).

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.

GS variation tones ​

A GM program number selects a capital tone. GS and GM2 both let a file reach a variation of that capital — a wide piano, a detuned organ, a 12-string guitar — by sending Bank Select before the program change. The fallback bank voices thirty such variations under seventeen capital programs.

Each variation carries two addresses for one voice: its GS Bank Select MSB number and its GM2 number. A GS-authored file and a GM2-authored file therefore sound the same tone, rather than one of them landing on the capital because it used the other standard's number.

Capital programVariationGS MSBGM2
0 Acoustic Grand PianoWide81
0 Acoustic Grand PianoDark162
1 Bright Acoustic PianoWide81
2 Electric Grand PianoWide81
3 Honky-tonk PianoWide81
4 Electric Piano 1Detuned81
4 Electric Piano 1Velocity-switched162
4 Electric Piano 1Sixties243
5 Electric Piano 2Detuned81
5 Electric Piano 2Velocity-switched162
6 HarpsichordCoupled (8′+4′ octave)81
6 HarpsichordWide (two-choir stereo)162
6 HarpsichordKey-off jack noise243
11 VibraphoneWide81
12 MarimbaWide81
14 Tubular BellsChurch bell81
14 Tubular BellsCarillon92
16 Drawbar OrganDetuned81
16 Drawbar OrganSixties162
16 Drawbar OrganOrgan 4323
17 Percussive OrganDetuned81
17 Percussive OrganOrgan 5322
19 Church OrganFlute registration81
19 Church OrganFull organ162
21 AccordionItalian tuning8—
24 Acoustic Guitar (nylon)Ukulele81
24 Acoustic Guitar (nylon)Key-off noise162
25 Acoustic Guitar (steel)12-string81
25 Acoustic Guitar (steel)Mandolin162
40 ViolinSlow attack81

The Italian accordion is deliberately GS-only. GM2 gives its own variation 1 under program 21 to the French accordion, which is the dry tuning the capital already voices — adopting that address would make the two standards contradict each other.

A bank number that names nothing still sounds

A Bank Select value that reaches no variation this table voices resolves to the capital tone, which is what a hardware module does for a variation it does not have. No file loses a sound because it asked for a tone the bank has not been given yet, and adding a variation later changes only the files that were already asking for it.

Each variation is voiced from its capital's own physical model rather than from a separate recording, so re-voicing a capital carries its variations with it instead of leaving them behind. That is also why the three piano capitals each get their own wide variation instead of sharing the grand's: sharing it would have made each one duller, quieter, or more in tune than the capital it is supposed to be a variation of.

The audition takes one capital — program 16, Drawbar Organ — and offers the three variations the bank voices apart from it. It stops there because a Bank Select MSB with no variation behind it resolves to the capital and renders a bit-identical buffer: MSB 24 on this program is one such number, and an option that cannot sound different is not a comparison.

GS · VARIATIONIDLE
GS variation tones — one organ, four registrations

GS files a capital tone's variations behind Bank Select MSB: the same Program Change with a different MSB plays a sibling voice. Here the capital is program 16, Drawbar Organ, and the three variations the fallback voices apart are offered — Detuned Organ 1 (MSB 8) adds the chorus scanner's slow beating, 60's Organ 1 (MSB 16) pulls only the first three drawbars with a hard key click, Organ 4 (MSB 32) draws every bar for the full tutti. An MSB the fallback does not voice falls back to the capital, as GS specifies, so only the ones that actually change are listed. Each render uses one shared peak gain across both channels; loudness differences remain.

Variation

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 rhythm-part program numbers in bank 128) and reshapes the Standard kit per set at note-on — more shell body for Room, bigger/lower shells for Power, and so on.

Two numbers appear per row and they are not interchangeable. Program is what a file sends; it is the rhythm part's program-change number and the address the standards define. Index is this bank's own slot for the set. Indices are append-only: a set added later takes the next free index, so adding one can never renumber a set already voiced, and nothing that already sounds right starts sounding like something else.

The tone map column is the earliest generation that defines the set, numbered as the wire numbers them — 1 is the oldest and 4 the newest — and Bank Select is what reaches it. The two halves of that message do two different jobs and never the same one: Bank Select MSB selects the variation tone, and Bank Select LSB selects the tone map. Writing a variation number into the LSB therefore pins a generation rather than picking a tone. (GM2 re-uses the LSB for its own variation number under its own two MSBs; the GS architecture layer below has that exception.)

Which generation counts as the current one is fixed by the device being targeted, and that device is the newest-generation module, the one whose own map is map 4 — which includes the map 3 generation rather than trading against it. The two Parameter Address Maps agree on every address they share and part company at ten points, and at nine of the ten the newer module is the superset, so a file written for map 3 selects that map (40 4x 00 = 03) and plays. Two consequences are worth stating because they remove work rather than add it: there is no double-module mode to implement, and the target's sixty-four parts are four ports of sixteen rather than a second address space, so a part is still named by one address nibble.

A tone map is audible exactly where it fails to reach a kit. Sixteen of the twenty-six sets below were introduced by a later map, so pinning an older one drops those to Standard, exactly as a module of that generation does. The melodic side is the opposite case: every variation the bank voices is a map 1 tone that all maps reach, which is why the LSB alone moves nothing in the variation table above and a great deal in the kit table below.

Every set is a re-voicing of the one shared percussion model, not a second copy of it: the kick, snare, tom, hat, and cymbal parameters are reshaped at note-on. Improving the underlying model therefore improves all 26 at once, and a set can only differ in ways the model has a parameter for.

ProgramIndexGS nameTone mapVoicing change vs Standard
00StandardMap 1—
81RoomMap 1more shell body, longer ambient tail
162PowerMap 1bigger, lower, longer shells
243ElectronicMap 1sine-ified, dried-out membranes
254Analog Machine (GM2: Analog)Map 1decaying-sine kick, single-tone snare and toms
325JazzMap 1tighter, higher, softer
406BrushMap 1snare becomes a sustained swish
487OrchestraMap 1longer membrane and cymbal tails
568SFXMap 1one-shot set — plays the Standard voicing
1279Legacy MapMap 1short, thin, bright — the drum map that predates GS
110Standard 2Map 2drier, tighter room; more snare wire
2611DanceMap 2sine kick, clap-lit snare, tight hats
4912EthnicMap 2hand drums — struck near the rim, thin shell
5013Kick & SnareMap 2only the kick and snare move; the rest is Standard
5714Rhythm FXMap 2one-shot set — plays the Standard voicing
215Standard 3Map 3struck off-centre, left more open
916Hip HopMap 3low, short and squashed
1017JungleMap 3everything cut off early and pushed bright
1118TechnoMap 3purely synthetic membranes, hard bright top
2719Rhythm BoxMap 3filtered noise ticks; snare with no wire under it
2820Compact MachineMap 3thin and tinny — the least body of the analog sets
2921Digital MachineMap 3sampled, not analog: crisp, dry and short
3022Hybrid MachineMap 3long decaying-sine kick with a click on top
5223AsiaMap 3gongs and taiko — big, low, long-ringing
5324Cymbal & ClapsMap 3one-shot set — plays the Standard voicing
5825Rhythm FX 2Map 3one-shot set — plays the Standard voicing

One-shot sets and Sound-Effects programs are addressed, not yet modeled

Four rhythm sets are banks of individual one-shot recordings on real GS hardware rather than re-voiced kits: SFX (program 56), Rhythm FX (57), Cymbal & Claps (53), and Rhythm FX 2 (58). There is nothing for a membrane model to reshape, so the player addresses and names them while the fallback map sends all four through to the Standard kit's voicing. The GM Sound-Effects programs (120-127, Guitar Fret Noise through Gunshot, covered in the GM tone map) are in the same position and share one generic noise-based voice. Both gaps are in the data-free fallback only: a SoundFont that supplies real samples for those addresses plays back normally through the SF2 player.

The audition below plays one bar of the same groove through a selection of the sets, choosing the kit the way a GS file does — a Program Change on the rhythm part. It is a selection rather than the whole table because it was built by rendering every set and dropping the ones whose output came back bit-identical to Standard, which is exactly what the four one-shot programs above do by design. A host does not need to render anything to draw that line — the queries below return it.

GS · DRUM KITIDLE
GS drum kits — one groove, eight sets

The same one-bar rock beat on the rhythm part (MIDI channel 10), with the kit chosen by that part's Program Change — exactly how a GS file switches drum sets. No SoundFont is loaded, so each kit is the built-in fallback's own re-voicing of the standard pieces: Power drops and lengthens the shells, Analog Machine and Hybrid Machine swap them for decaying sines with a click on top, Jazz tightens and lifts them, Orchestra rings like concert bass drum and timpani, Asia turns them into gongs and taiko. Sets the fallback leaves unvoiced (the one-shot SFX and Rhythm FX banks) sound identical to Standard and are left out here. Each render uses one shared peak gain across both channels; loudness differences remain.

Kit

Deriving which sets are voiced apart ​

Which slots a Program Change or Bank Select actually moves is a question the engine answers, so a picker's annotations should be computed from three queries rather than copied from the tables above. On WASM/Node they are synthGsDrumKitName, synthGsDrumKitIsVoicedApart, and synthGsVariationIsVoicedApart; the C ABI and Python expose the same three under their own naming conventions.

  • synthGsDrumKitName(program) returns the GS rhythm-set name a rhythm part's Program Change selects ('Standard', 'Room', 'Jazz', ...), or null when the module's own tone map defines no set at that program. Six of the names the engine returns are the model designations of the drum machines those sets voice; the tables on this page call those six by the kind of machine instead, and the program number is the same either way.
  • synthGsDrumKitIsVoicedApart(program) returns true when at least one drum note in the set differs from Standard, false when the set renders exactly as Standard, and null when no set sits at that program. Program 0 is Standard itself, so it answers false too, alongside the four one-shot sets above.
  • synthGsVariationIsVoicedApart(bank, program) is the melodic half: true when the Bank Select variation has a patch of its own, false when it resolves to the capital tone, null when either argument falls outside 0..127. It accepts the GS Bank Select MSB and the GM2 LSB alike, since both address the same variation. Note that 128 is out of range here rather than the drum bank it selects elsewhere in this API. Resolving an unvoiced variation to its capital is what GS specifies, so false is correct behaviour rather than a gap — but only this query separates it from a bank that is genuinely voiced, which otherwise takes rendering both and comparing.

The answers are derived, not listed: the kit predicate applies the set to every drum note's own resolved patch and compares bytes, and the variation predicate asks whether resolution returns the capital tone's own patch. A slot that gains a voicing changes its answer with no list to keep in step, and a case that exists but no-ops against the current patches still reports honestly. The reference is the module's own tone map, which is the newest one and reaches every set this build voices; a file that selects an older tone map reaches fewer sets.

Both predicates return three states, and a truthiness check destroys them: null means no slot is there at all, and 102 programs are in that state, so if (!voicedApart) sweeps those 102 empty programs in as placeholders on top of the few sets that answer false. Compare against false explicitly.

typescript
import { synthGsDrumKitIsVoicedApart, synthGsDrumKitName } from '@libraz/libsonare';

const kits = Array.from({ length: 128 }, (_, program) => ({ program, name: synthGsDrumKitName(program) }))
  .filter((kit): kit is { program: number; name: string } => kit.name !== null)
  .map((kit) => ({ ...kit, placeholder: synthGsDrumKitIsVoicedApart(kit.program) === false }));

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.

The GS architecture layer ​

On top of GM, the SoundFont player implements the Roland-GS extensions a GS-authored arrangement expects. Everything below is reached by address: a GS write is a Roland frame whose three address bytes name a map, a block inside it, and a parameter inside that.

Where a GS write lands, and how far it travels
F041dev4212himidlodata…sumF7Roland · GS · DT1addressdata · checksumhi byte — which map00Systemmode set20–27User · stored21 drums: heard20, 22–27 ignored40Patchsystem · effects · EQ · EFX · partsthe mid byte picks the block41Drum setupmap m, note rr50·51Other groupone port onlymid byte inside 40 — which block00Systemtune · volume · pan01Patch · effectsreverb · chorus · delay02Master EQlow & high shelf03EFXtype · 20 params · sends1xPart xx = part; 0 is part 102xControllers6 sources × 11 targets3uInsertion unit uextension: units 0–154xPart x routingEQ switch · EFX assignlo byte — the parameter inside the block; under 41 it is the drum notelevel — how far a write travelsdecodedheldheardAUDIBLE40 01 33reverb levelchanging the byte changes the renderSTATE40 01 32reverb pre-LPFheld and read back; the bus takes no such controlACCEPT40 01 00patch namedecoded, then dropped: nothing to display it onIGNORE40 4x 21output assigndecoded, then dropped: one output pair, and the row says so
  • how far the byte travels
  • not reached
  • declared absent, reason on the row
Every address in the space carries exactly one of four levels, and an address with no level at all is treated as a defect rather than as silence. STATE is the one worth reading twice: it marks a byte that is received and held faithfully but that nothing downstream reads, because the effect it would drive has no such control.

The four levels are what the player promises per address. AUDIBLE means changing the byte changes the render; STATE means the value is held and readable but no engine asks for it; ACCEPT means it is decoded and dropped; IGNORE means the row deliberately declines it and says why. The 16 parts the player carries follow from receiving a single MIDI port — the 50 ** ** and 51 ** ** blocks are the other group's parts and are declined for that reason, and the target device has no such addresses at all because the port decides which group 40 and 41 mean. It is not a WebAssembly limit; Sound Sources separates the constraints that genuinely are.

  • Variation-bank fallback — a GS variation bank that the SoundFont does not cover falls back to the capital (bank-0) tone, so a missing variation still plays the right family instead of going silent.
  • Bank-128 drum kits on channel 10 — drum programs live in bank 128; channel 10 (index 9) is the drum part by convention.
  • NRPN part edits — TVF cutoff/resonance, TVA envelope, and vibrato can be edited per part via NRPN, plus per-note drum NRPNs for individual drum sounds.
  • GS / GM SysEx — GS Reset, GM System On, and "use for rhythm part" SysEx are recognized — both from the host and from SysEx events embedded inside an arrangement.
  • Send-return system effects — one shared send-return bus behind all 16 parts, with reverb, chorus, and delay units. Each part's send amount is additive from two sources: the channel CC sends (CC91 reverb, CC93 chorus, CC94 delay) and, for reverb and chorus only, the SF2 zone generators reverbEffectsSend/chorusEffectsSend layered on top (GS delay send is CC-only — there is no SF2 zone generator for it). At power-on the parts start with a musically audible default room (reverb send 40, chorus send 8), so a plain SMF that never sends a reset SysEx still has ambience. A separate per-part drive insert (gain-compensated saturation) sits alongside this bus — distinct from the GS insertion effects (EFX) described below.
  • MIDI 2.0 / GM2 — the player decodes MIDI 2.0 banked Program Change, and resolves the Bank Select LSB (CC#32) one of two ways depending on the MSB:
    • GM2 addressing — when the MSB is GM2's melodic bank (0x79) or percussion bank (0x78), the LSB is the variation number (or the percussion set), exactly as GM2 defines it.
    • GS tone-map select — for any other MSB the LSB instead picks which generation's tone set the MSB's variation number reaches: 0 the module's own (newest) map, 1 to 4 the four generations of the tone set in the order they were defined, oldest first — so 4 is the newest, and the same set 0 reaches on this module. Any other value reads as 0, because a module that never saw the message is already playing its own map. A tone or kit that the selected map predates falls back to the capital tone or the Standard kit — the same thing a real module of that generation does.

The LSB means two different things

This is the byte you set as bankLsb in Project.midiBankProgram(...) (see the authoring tip below), and it is the easiest value to get wrong. Under a GM2 MSB it selects a variation; under a GS MSB it selects a tone map, and the variation number lives in the MSB instead. Writing bankLsb: 1 next to a GS variation MSB does not pick variation 1 — it pins the part to tone map 1, the oldest set.

GS insertion effects (EFX) ​

An original DSP re-creation, not bundled hardware data

libsonare's insertion effects are an original DSP re-creation — a combination of libsonare's own algorithms, reconstructed from publicly documented information, mapped onto the GS EFX SysEx and type-numbering model so GS-authored MIDI selects the effect the composer intended. Because the algorithms are independent, they follow the same addressing and effect structure but do not reproduce the exact sound of any hardware module; treat them as a compatible re-creation, not a 1:1 emulation. There are no bundled samples, ROM data, or firmware, and no affiliation with or endorsement by any hardware manufacturer. For the standards and literature behind this compatibility, see Algorithm References.

Separate from the reverb/chorus/delay send-return bus above, GS defines an insertion effect (EFX): an effect placed directly in a part's signal path, like a guitar pedal, rather than a send-return bus. The specification's block lives at 40 03 xx and the hardware runs one such unit for the whole module. libsonare runs sixteen. Unit 0 keeps 40 03 xx unchanged — same semantics, same defaults, same layout — and the other fifteen live at 40 3u xx, where the unit number is the address nibble itself; 40 30 xx is unit 0 again, a second door into the storage 40 03 xx already writes.

Routing does not add an address either. 40 4x 22 PART EFX ASSIGN keeps 00 BYPASS and 01 EFX with their exact specified meanings and widens its own value range: 02–10 select units 1–15. A value outside 00–10 is ignored like any other out-of-range write. Nothing collides, because 40 30–40 3F carries no row in either device's parameter map — that is what makes the extension unreachable from a spec-compliant file and therefore safe without a feature flag. Real hardware ignores an unknown address, so a file that uses the extension still plays there, with one insertion effect.

Part, insert, system effects, master EQ
Part x40 4x 22to unit: 01–10Insertion effecttype + 20 parametersruns once on the sumMaster EQ40 02 00–03bypass: 40 4x 20outdry mix40 4x 22 = 00: bypass, no insertsend at the partCC91 · 93 · 94send after the effect40 03 17–19System effectssend-return, wet onlyin parallelReverb40 01 30–37Chorus40 01 38–40Delay40 01 50–5Areturns sum into the mixchorus → reverb · chorus → delay · delay → reverb: held, never routed (STATE)
  • the assigned part's path
  • sends and returns, wet only
  • bypass: 40 4x 22 = 00
A bypassed part sends to reverb, chorus and delay from the part itself. A part routed into a unit sends after the effect, by the unit's own three send levels, so the wet tail is made from the processed signal instead of the raw one.

Two different things wear the phrase "what the hardware does", and they must not be merged. One unit for the whole module is a resource limit of the machine that was built, not a property of GS, and it is lifted. Parts routed to the same unit sum into it is not a limit at all — it is what an effect is, the way two guitars into one pedal intermodulate — and it is not lifted. A unit runs once, over the sum of every part assigned to it. So the unit count changed and the summing did not, and asking to "restore the summing" never means capping the units. Sound Sources sets out why the one is a limit and the other is behaviour.

Because a unit's output is one signal, what sits downstream of it belongs to the unit rather than to the parts. Its send to the system effects is the unit's own (40 3u 17–19), and the part-level CC91/CC93/CC94 send is suppressed for a routed part so the wet tail is not sent twice. Its master-EQ routing follows the parts feeding it only where those parts agree: a bypass at 40 4x 20 holds when every part on the unit asked for it, and otherwise the unit takes the EQ, which is what every part powers on with.

The GS tone controls and equalizers use first-order low and high shelves. Their slopeDbOct is 6 dB/octave; each shelf's frequency is where it reaches half its gain change in dB, and its q setting does not affect that first-order section. This is also how a host selects a first-order shelf in the parametric EQ band API.

How parameter bytes reach the sound

Every printed EFX parameter drives a control or a stage switch. A translated binding uses a measured conversion table or a step fixed by the printed range. A designed binding uses a chosen law where the archive does not establish one. An enables binding turns stages on and off, or selects between them. These describe how the re-creation responds; a designed law does not establish that the original unit used the same law.

The GS module demo shows each parameter's target and conversion basis. A byte may reach several controls or whichever stage a selector enables.

There is no typed "set EFX" call in any binding. Like real GS hardware, the EFX type and its parameters are programmed exclusively by sending raw SysEx: live, you push those bytes with RealtimeEngine.pushMidiSysex(); offline, SysEx embedded in the arrangement's MIDI is realised inline during the bounce.

EFX type → insertion effect ​

Each EFX type number selects one insertion effect. Type 0 is Thru (no effect).

EFX typeGS EFX namelibsonare insertion effect
0x0100Stereo EQparametric EQ
0x0101Spectrumgraphic EQ
0x0102Enhancerpresence enhancer
0x0103Humanizervowel filter
0x0110Overdriveamp-sim (crunch voicing)
0x0111Distortionamp-sim (high-gain voicing)
0x0120Phaserphaser
0x0121Auto Wahenvelope-following resonant bandpass
0x0122Rotarydual-rotor rotary-speaker model
0x0123Stereo Flangerflanger
0x0124Step Flangerflanger
0x0125Tremoloring modulator driven as amplitude modulation
0x0126Auto Panauto-pan
0x0130Compressorcompressor
0x0131Limiterlimiter
0x0140Hexa Chorussix-voice ensemble
0x0141Tremolo Choruschorus
0x0142Stereo Choruschorus
0x0143Space-Dchorus (unmodulated)
0x01443D Choruschorus (widened)
0x0150Stereo Delaystereo delay
0x0151Modulation Delaystereo delay
0x0152–0x01543-tap / 4-tap / Time-Control Delaystereo delay
0x0155Reverbplate reverb
0x0156Gate Reverbplate reverb (gated tail not yet modelled)
0x01573D Delaystereo delay
0x01602-voice Pitch Shifterpitch shifter
0x0161Feedback Pitch Shifterpitch shifter (feedback loop not modelled)
0x0170 / 0x01713D Auto / Manualbinaural positioning
0x0172 / 0x0173Lo-Fi 1 / 2bit-crusher

Humanizer (0x0103) uses a vowel filter with drive and vowel controls. 3D Auto and 3D Manual (0x0170, 0x0171) use binaural positioning. Their parameters reach the mapped controls through translated or designed laws, with separate switches where the effect has optional stages.

Composite EFX types (multi-stage chains) ​

A composite EFX type realises as an ordered chain of the same DSP inserts running in series, matching the hardware's block structure — a guitar multi-effect, for example, still runs through the individual amp-sim/chorus/delay inserts above, just chained together. The table below is a representative slice; the full map covers the dual-stage 0x0200–0x020C matrix (Overdrive / Distortion / Enhancer feeding Chorus, Flanger, or Delay) and the guitar / bass / Rhodes / keyboard multi presets in the 0x0400–0x0500 range.

EFX typeGS EFX nameChain (signal order)
0x0200OD → Chorusamp-sim → chorus
0x0202OD → Delayamp-sim → stereo delay
0x0400Guitar Multi 1compressor → amp-sim → chorus → delay
0x0405Bass Multicompressor → amp-sim (bass cab) → EQ → chorus
0x0406Rhodes Multienhancer → phaser → chorus → auto-pan
0x0500Keyboard Multiring-mod → EQ → pitch-shifter → phaser → delay

Modern and classic EFX ​

gsEfxRealization selects the EFX sound when binding or bouncing a SoundFont instrument, including its NativeSynth fallback:

  • modern (default) builds chains from the current insert processors. The mapping tables above describe this path.
  • classic uses dedicated models running at 32 kHz internally, with resampling to the project's rate. It receives the same EFX type and parameter bytes; the instrument voices and system reverb, chorus and delay stay separate from this choice.

The GS module demo lets you choose either path before playback. Assign the selected part to EFX and select an effect to hear the comparison; Thru has no insertion effect to compare.

Live vs. offline realisation ​

  • Offline (bounce) — EFX SysEx embedded in the arrangement is applied inline during the render: an EFX change mid-bounce takes effect on the next block.
  • Live — pushMidiSysex() builds the new effect chain off the audio thread and hands it over wait-free, so a live engine hears an EFX change without stopping playback.

The demo below renders one held chord through the GS-compatible player and lets you switch the insertion effect, so you can hear how each one reshapes the tone against the dry reference.

GS · EFXIDLE
GS insertion effects (EFX) — hear each one

The same held chord, rendered through the GS-compatible player with one insertion effect (EFX) switched in. Pick an effect and hear how it reshapes the tone — overdrive adds grit, rotary swirls, a phaser sweeps, and delay repeats. These are libsonare's own DSP algorithms, reconstructed from publicly documented information and mapped to the GS EFX numbering. They follow the same addressing, not the exact sound of any hardware. Each render uses one shared peak gain for both channels; loudness differences remain. Switch to Dry for the untouched reference. The 3D Auto and 3D Manual choices are real stereo binaural renders intended for headphones.

Effect
Realization

Author GS banks with the MIDI helpers

Project.midiBankProgram(ppq, group, channel, bankMsb, bankLsb, program) expands a bank-select-plus-program-change into the MIDI events setMidiEvents accepts — the right way to select a GS variation or a drum kit. Static helpers like Project.gmInstrumentName(program), Project.gmDrumName(note), Project.gm2InstrumentName(bankLsb, program), and Project.midiCcName(controller) name the slots so your authoring code reads clearly. The reverse direction is symmetric: Project.gmProgramForName(name), Project.gmDrumNoteForName(name), and Project.midiCcIndexForName(name) return the number for a canonical name (-1 when unknown), while Project.gmFamilyName(family) and Project.gmFamilyFirstProgram(family) enumerate the 16 GM instrument families. Project.gm2DrumSetName(bankLsb) and Project.gm2DrumName(bankLsb, note) name the GM2 drum-set variations.

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. The piano has been tuned, while every other physical fallback model still awaits adjustment and calibration. Read the acoustic-style rows below as routing coverage, not as a claim of final sampled-instrument realism.

GM programInstrumentFallback engineWhy
4-5Electric Piano 1 / 2fmphase-modulated tine/bell brightness
6Harpsichordharpsichordjack and plectrum; key speed barely changes loudness
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

Bank Select is read on every capital program the fallback voices a variation for — see GS variation tones above for the full map.

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 synthesis engines, and the per-program table of all 128 — instrument name, engine, and voicing notes — is long enough to live on its own page. The canonical instrument names are also available at runtime from Project.gmInstrumentName(program).

See GM Tone Map for the complete table.