One LAB sound as JSON, with named fields in musical units: a wave by name, the cutoff in Hz, the
envelope as the 0..15 steps LAB's cards show. It is what a language model writes from
the instructions, what the site's Library
reads, checks, previews and sends to the FM-1, and what it shows any of SIDOU's sixteen sounds as.
The JSON Schema is patch.schema.json; the site serves both as
llms.txt (with llms-fields.txt, llms-lab.txt, llms-examples.txt it links, and
llms-full.txt, all of them in one) and patch.schema.json at its root, and the sixteen as sounds/patches/*.json.
{
"format": "sidou-patch/1",
"name": "303 BASS",
"wave": "saw",
"envelope": {"attack": 0, "decay": 8, "sustain": 8, "release": 8},
"filter": {"cutoff_hz": 2077, "resonance": 12, "voices": [1], "envelope": {"depth": 40, "decay": 6, "sustain": 0}},
"vibrato": {"rate_hz": 0}
}A patch holds everything a LAB sound holds (sound-format.md, the 109 bytes of
app/lab_sound.h) and its name; not the chip, its character or the octave, which are the
instrument's. One converter, platform/web/patch_json.c, reads a patch into the 109 bytes and
writes the 109 bytes as a patch: the site runs it as WebAssembly (tools/wasm-patch), the host
tests through sidou-sys. A patch written from a sound reads back as exactly that sound.
formatis required and is"sidou-patch/1". Every other field may be left out.- What is left out is LAB's own starting sound, SAW LEAD as the FM-1 loads it at power-on.
A layer is a copy of the top-level oscillator but what it names; a table step plays its layer's
wave unless it names one; an effect takes its type's settings (the FM-1's own, as its CHIP view
picks a type) but what it names; an LFO is a triangle unless it names a shape.
nullis left out. - The length of
layersis the number of extra oscillators, oftable.stepsthe table's steps. A table with steps is on unless"on": false. nameis upper-cased; it is the instrument's name in the song and LAB's name for the sound.- Text before the first
{and after the patch is skipped, so a model's whole answer can be pasted. - A number takes up to three decimals;
cutoff_hz, the LFOs'rate_hzand the percentages become the nearest value the sound can hold. - A patch is refused, with the field, what is wrong and what is allowed, when a field is unknown,
of the wrong type or out of its range; when
layersmeetsync_lead; when a table is on with no steps or loops past its last step; when a one-oscillator sound filters some voices but not all (each key plays on the next free voice, so the filter takes all three or none; a layered sound routes layers apart, layer n being voice n); and when it is silent. Silent is heard, not estimated: the song is played (platform/web/patch_level.c, the song player on each chip model, C2, C3 and C4 held 0.3 s each, the first as long as the attack, then let go) and a sound is heard on a chip when the eighth loudest 128-sample block, its mean taken out, has an RMS of 512 or more. Neither chip hearing it is a refusal; one chip only, a warning.
A sound is written with format, name, the wave and the envelope, then every field whose
value is not what reading would fill in. Numbers are the shortest decimal that reads back as the
same value.
The ranges are the converter's own (this table is generated by
host/crates/sidou-patch/tests/patch_docs.rs; SIDOU_PATCH_DOC_UPDATE=1 rewrites it).
| Field | Allowed |
|---|---|
format |
"sidou-patch/1" |
name |
up to 12 characters: A to Z, 0 to 9, space and !"#$%&'()*+,-./:;<=>?@[]^_ |
wave |
triangle, saw, pulse and noise, one or several joined with + (like "triangle+saw"), or none |
ring |
true or false |
sync |
true or false |
pulse_width |
0 to 100 (percent of the cycle, 50 is square) |
envelope.attack |
0 to 15 |
envelope.decay |
0 to 15 |
envelope.sustain |
0 to 15 |
envelope.release |
0 to 15 |
transpose |
-24 to 24 (semitones) |
detune |
-99 to 99 (cents) |
fine_tune |
-50 to 50 (cents, the whole sound) |
filter.mode |
low, band and high, one or several joined with + (like "low+band"), or off |
filter.cutoff_hz |
0 to 14298 (Hz, as an 8580 plays it) |
filter.resonance |
0 to 15 |
filter.voices |
a list of chip voices 1 to 3, like [1, 2, 3] (the chip voices through the filter: all three or none on a one-oscillator sound) |
filter.resonance_link |
-8 to 8 (the resonance follows the cutoff knob) |
filter.lfo_depth |
0 to 32 (a cutoff wobble at the PWM rate, 0 off) |
filter.envelope.depth |
-63 to 63 (cutoff steps of 32 at the peak, below 0 closes) |
filter.envelope.attack |
0 to 15 |
filter.envelope.decay |
0 to 15 |
filter.envelope.sustain |
0 to 15 |
filter.envelope.release |
0 to 15 |
pwm.depth |
0 to 50 (percent of the cycle swept) |
pwm.rate_hz |
0 to 12.75 |
pwm.link |
-8 to 8 (the PWM depth follows the pulse width knob) |
sync_lead.on |
true or false (oscillator 1 hard-synced to a silent one; one note at a time) |
sync_lead.ratio |
0 to 15.996 (the synced pitch over the note's, 2.75 is SYNC LEAD's) |
sync_lead.semitones |
-12 to 12 (added to the ratio) |
vibrato.rate_hz |
0 to 12.7 |
vibrato.depth |
0 to 127 (127 is about a semitone each way) |
vibrato.delay |
0 to 127 (hundredths of a second after the note starts) |
arp.on |
true or false |
arp.rate |
1fr, 2fr, 3fr, 4fr, 6fr, 8fr, 1/32, 1/16 or 1/8 (50 Hz frames a note, or a MIDI clock division) |
arp.order |
up, down, up_down or played |
arp.octaves |
1 to 2 |
bend_range |
0 to 24 (semitones) |
velocity |
off, sustain, cutoff or pulse_width (what the key velocity moves) |
mod_wheel |
pulse_width, vibrato or cutoff (what the wheel moves) |
aftertouch |
off, vibrato, pulse_width or cutoff (what pressure moves) |
layers |
a list of up to 2 |
layers[].wave |
triangle, saw, pulse and noise, one or several joined with + (like "triangle+saw"), or none |
layers[].ring |
true or false |
layers[].sync |
true or false |
layers[].pulse_width |
0 to 100 |
layers[].envelope.attack |
0 to 15 |
layers[].envelope.decay |
0 to 15 |
layers[].envelope.sustain |
0 to 15 |
layers[].envelope.release |
0 to 15 |
layers[].transpose |
-24 to 24 |
layers[].detune |
-99 to 99 |
table.on |
true or false |
table.layer |
1 to 3 (the layer it plays on; a one-oscillator sound plays it on every note) |
table.loop |
0 to 16 (0 plays once and holds the last step, n goes back to step n) |
table.frames |
1 to 16 (50 Hz frames a step) |
table.steps |
a list of up to 16 |
table.steps[].wave |
triangle, saw, pulse and noise, one or several joined with + (like "triangle+saw"), or none |
table.steps[].test |
true or false (the SID's test bit: silences and resets the oscillator) |
table.steps[].ring |
true or false |
table.steps[].sync |
true or false |
table.steps[].note |
-32 to 95 semitones from the key played, a fixed note "C#0" to "B7", or "hold" |
lfos |
a list of up to 2 |
lfos[].shape |
off, triangle, saw_up, saw_down, square or sample_hold |
lfos[].to |
pitch, pulse_width, cutoff or volume |
lfos[].rate_hz |
0.05 to 12.75 (snapped to the nearest of 32 rates) |
lfos[].depth |
-63 to 63 (below 0 inverts; 63 on pitch is an octave) |
fx.distortion.type |
off, tape, overdrive, fuzz, crush or lofi |
fx.distortion.amount |
0 to 127 |
fx.distortion.a |
0 to 127 |
fx.distortion.b |
0 to 127 |
fx.distortion.mix |
0 to 127 |
fx.modulation.type |
off, chorus, flanger, phaser, tremolo, autopan or wow |
fx.modulation.amount |
0 to 127 |
fx.modulation.a |
0 to 127 |
fx.modulation.b |
0 to 127 |
fx.modulation.mix |
0 to 127 |
fx.effect.type |
off, delay, pingpong, reverb or lush |
fx.effect.amount |
0 to 127 |
fx.effect.a |
0 to 127 |
fx.effect.b |
0 to 127 |
fx.effect.mix |
0 to 127 |
Up to sixteen patches as one song, an instrument each, the way the site's sixteen sounds go
(the sixteen as a bank, assets/sounds/sidou-sounds-bank.json, make sidou-sounds.sng byte for
byte). Its schema is bank.schema.json.
{"format": "sidou-bank/1", "name": "ACID KIT", "patches": [{"name": "KICK", "...": "..."}, {"name": "BASS"}]}formatis required;nameis the song's name on the FM-1, 1 to 16 characters (a store name: the font's upper case, not starting with a space),BANKwhen left out;patchesholds 1 to 16 patches, each read as above, itsformatoptional.- A refusal names the patch:
patches[1].wave: "sawtooth" is not allowed. .... A bank whose sounds' tables do not fit one song's 255 rows each is refused at the patch that overflows. - Its link is
library.html#bank=, made as a patch's; the sixteen are a 6.8 KB link, inside the page's 8 KB.
One STEP lane as a song: the FM-1's NEW song (app/song_io.c's template, with its drum kit) with
the lane's pattern written as STEP's keys write it (a test plays the keys and compares the
pattern and its locks), so it opens on STEP and plays. Examples: assets/steps/*.json.
format(required),name(the song's, as a bank's,STEPwhen left out),voice1 to 3 (1),roleauto, drums, bass, arp, lead, none, bass+drums, acid (theSCNEchunk; auto),gatestaccato (a key off after a note when the next row is empty) or legato (staccato),pages1 to 4 (1; the pattern is 16 rows a page).instrument: a patch (placed in the first free instrument, kept so ENV pulls it back exactly),"pulse"or"bass"(the NEW song's instruments 1 and 2), or"kit"(its drums, instruments 3 to 14).steps:step1 to 64 (required, once each, inside the pages),note"C0"to"G#7"("C3"),drum(with the kit only, and then required),slide,accent(true; the locks 10 and 11) andchance0 to 100 (lock 9; 100 keeps none).- Writing a song back as a pattern takes the lane's notes, the instrument (one patch, the pulse, the bass, or the kit), staccato when a key off follows a note, the locks and the role.
library.html#patch= followed by the patch's JSON, percent-encoded (what encodeURIComponent
writes), opens the patch on the Library page: it checks and previews it and shows Send to FM-1, and
sends nothing by itself. A fragment never reaches a server. The longest of the sixteen sounds is a
link of under 2 KB.