Skip to content

Latest commit

 

History

History
194 lines (175 loc) · 10.7 KB

patch-json.md

File metadata and controls

194 lines (175 loc) · 10.7 KB

A LAB sound as text: sidou-patch/1

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.

Reading

  • format is 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. null is left out.
  • The length of layers is the number of extra oscillators, of table.steps the table's steps. A table with steps is on unless "on": false.
  • name is 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_hz and 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 layers meet sync_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.

Writing

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 fields

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

Banks: sidou-bank/1

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"}]}
  • format is required; name is 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), BANK when left out; patches holds 1 to 16 patches, each read as above, its format optional.
  • 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.

STEP patterns: sidou-step/1

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, STEP when left out), voice 1 to 3 (1), role auto, drums, bass, arp, lead, none, bass+drums, acid (the SCNE chunk; auto), gate staccato (a key off after a note when the next row is empty) or legato (staccato), pages 1 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: step 1 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) and chance 0 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.

The link

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.