The reference synthesiser gains what a short channel sting needs, all in the wai.det.fixed64 floor and trig-free, every committed score rendering byte for byte as before (the three first-revision vectors keep their mixdown hash, PCM SHA-256 and score hash): - score §2.2, §3.1: waveforms 4 to 6, the saw, square and 25 %-pulse with each step corrected by a polynomial (PolyBLEP); 7, a sine by a refined parabola; 8, noise held from a 32-bit LCG between wraps of the phase. - score §2.1, §3.4: a contract of two channels mixes down to interleaved frames; a reader refuses any count but 1 and 2; a score with no voices centres each note. - score §2.4, §3.3: an optional third section, one 73-byte voice per note: a constant-power pan by the profile's integer square root, curved attack, decay and release (the release from the level at note-off), up to four one-pole low-pass sections that follow the envelope, a linear glide, the noise seed, and up to eight echoes at alternating pans. The 65-byte note record is unchanged; the score hash binds the voices when present. - wai score build|render: channels 2, waveforms to 8, each note's optional voice; render prints frames and channels. Receipt blocks are 4096 frames. - score-conformance: five vectors at 48 kHz (band-limited, two channels, voiced in one, a two-second sting, a voiced scale); multi_verify.py's independent synth agrees with all eight. - packaging §5.8: a score of one or two channels packs beside the lead with its voices, its floor the FLAC of the mixdown in its channels; verify holds the floor to the base's channels and rate. pack-conformance/multi/multi-sting-01 is a voiced two-channel score at 48 kHz. - The composing sink synthesises and plays a score in its contract's channels; its reference package's score is now two channels at 48 kHz, voiced. Co-Authored-By: Claude Opus 5.5 <[email protected]>
17 KiB
WAI Extension: Score (Music as Instructions)
Status: Draft. Phase 6 — the deepest audio lever. Ship the composition, not the recording: a score of note events the sink synthesizes. Conformance is mixdown-equivalence, the music counterpart of spatial audio. Reference impl: the
scorefeature ofwai-rs; corpus:score-conformance/; live sink: wai.transaction.science/score. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.
1. Scope and model
A wai.audio.score object carries no recorded audio — only note
events. Where wai.audio.scene mixes recorded sound objects, a score
is reconstructed from instructions: a deterministic reference synth
turns the notes into a canonical PCM mixdown of one channel or two, and a
sink with a richer instrument (soundfont, neural) renders the same score
for presentation. A note may carry a voice (§2.4): its place between the
two channels, curved envelope segments, a low-pass filter that follows
its envelope, a pitch glide, a noise seed and echoes.
It is lever 3 (instructions-at-the-sink) for music — a minute of music
is kilobytes of notes, not megabytes of samples.
In scope: the container, the note model, the deterministic reference synthesis, the mixdown-equivalence conformance criterion, the receipt profile. Out of scope: a transport; the sink's instrument (soundfonts, neural synths — presentation, not conformance); MIDI-file import (an authoring concern).
Relationship to the core spec: mirrors spatial audio. The reconstruct
runs in the wai.det.fixed64 floor; media takes the value "audio";
wai.audio.score is authoritative.
2. Container (WSCR)
"WSCR" | u16 n_sections | section table (u8 kind, u32 off, u32 len) | sections
| kind | section | required |
|---|---|---|
0x01 |
contract (canonical JSON) | REQUIRED |
0x02 |
note events | REQUIRED |
0x03 |
voices, one per note (§2.4) | OPTIONAL |
off counts from the end of the section table; the reference writer writes the sections
in this order, each padded with zero bytes to a multiple of 8. A score with a voices
section is voiced. A reader skips a section of a kind the table does not name: it does
not enter the mixdown. A score of the first revision's two sections renders as it always
has (§3.2).
2.1 Contract
{ "channels": 1, "duration": 34000, "numeric": "wai.det.fixed64",
"sample_rate": 22050 }
duration is the rendered mixdown length in samples of each channel (sample frames).
channels is 1 or 2; a reader refuses any other count. A mix of two channels is the
left and the right, interleaved (§3.4).
Reader limit. A reader MUST NOT refuse a score for its length while duration × channels is at most 134 217 728 (2²⁷, the audio limit of SPEC §2), and MAY refuse one
past it (duration_too_long), when it reads the contract, before it renders anything.
A conforming encoder writes no score past it.
2.2 Notes
n_notes u32, then per note (65 bytes):
start u64 | dur u64 | phase_inc i64 Fx | gain i64 Fx | waveform u8 | attack u64 | decay u64 | sustain i64 Fx | release u64.
phase_inc is the per-sample phase advance freq / sample_rate,
authored off-line from the pitch — so the floor never evaluates an
exponential to turn a note into a frequency (the same authoring
discipline as the avatar quaternion). waveform selects a reference
timbre (§3.1):
| code | timbre |
|---|---|
0 |
saw, piecewise-linear |
1 |
square, piecewise-linear |
2 |
triangle, piecewise-linear |
3 |
25 %-pulse, piecewise-linear |
4 |
saw, band-limited (each step corrected, PolyBLEP) |
5 |
square, band-limited |
6 |
25 %-pulse, band-limited |
7 |
sine (a refined parabola) |
8 |
noise (a generator's value held between wraps of the phase) |
A note of another code is silent. The band-limited timbres are the piecewise-linear ones with the step at each discontinuity spread over the samples beside it, so a high note does not fold its harmonics back below the Nyquist frequency.
2.3 Authoring form (informative)
wai score build writes a WSCR from a JSON document that holds every
field as an integer, so the committed document is the definition and no
platform's floating-point pow can change a pitch:
{ "sample_rate": 22050, "channels": 1, "duration": 34000,
"numeric": "wai.det.fixed64",
"notes": [ { "start": 0, "dur": 3500, "phase_inc": 50960238,
"gain": 1717986918, "waveform": 0, "attack": 220,
"decay": 600, "sustain": 2791728742, "release": 1500 } ] }
A voiced score of two channels at 48 000 Hz: one band-limited saw (A4,
phase_inc = round(440 / 48 000 × 2³²)), panned half left, its decay and
release curved, through two low-pass sections that open with its envelope, with
three echoes a quarter second apart crossing from right to left:
{ "sample_rate": 48000, "channels": 2, "duration": 72000,
"numeric": "wai.det.fixed64",
"notes": [ { "start": 0, "dur": 24000, "phase_inc": 39370534,
"gain": 1288490189, "waveform": 4, "attack": 480,
"decay": 24000, "sustain": 2147483648, "release": 9600,
"voice": { "pan": -2147483648, "decay_curve": 4,
"release_curve": 4, "lp_poles": 2,
"lp_a": 214748365, "lp_env": 1717986918,
"echo_taps": 3, "echo_delay": 12000,
"echo_gain": 1717986918,
"echo_pan": 3006477107 } } ] }
phase_inc, gain, sustain and a voice's pan, lp_a, lp_env, glide_to,
echo_gain and echo_pan are the raw Q32.32 bits (a pan negative for the left).
numeric may be left out. voice is optional on each note, and each of its members
is optional, a member left out taking the default voice's value (§2.4); a score in
which any note has a voice is voiced, and a note of it with none takes the default
voice. The tool refuses a number with a fraction or an exponent, a member it does not
name, a member given twice (readers differ on which of the two values they keep), a
channel count other than 1 or 2, a duration × channels past 2²⁶, a phase_inc or a
glide_to of a whole cycle or more, a gain or sustain above 1.0, a waveform above
8, a voice member out of the range §2.4 gives, a pan or an echo_pan other than 0
in one channel, a glide with no glide_to, an echo of no delay, and a note whose
release, or last echo, would end after duration, so the mixdown always holds every
note whole. score-conformance/notes/ holds each vector in this form, and wai score build turns it back into the committed WSCR byte for byte.
2.4 Voices
A voices section holds one 73-byte record per note, in the notes' order, and nothing
else: its length is n_notes × 73 (it has no count of its own). Each record:
pan i64 Fx | attack_curve u8 | decay_curve u8 | release_curve u8 | lp_poles u8 |
lp_a i64 Fx | lp_env i64 Fx | glide_to i64 Fx | glide u64 | seed u32 |
echo_taps u8 | echo_delay u64 | echo_gain i64 Fx | echo_pan i64 Fx
| field | meaning | range |
|---|---|---|
pan |
the note's place in a two-channel mix, −1.0 left to 1.0 right | −1.0 to 1.0; 0 in one channel |
attack_curve, decay_curve, release_curve |
the exponent of each envelope segment (§3.3); 1 is a line | 1 to 8 |
lp_poles |
one-pole low-pass sections in series; 0 is none | 0 to 4 |
lp_a |
each section's coefficient at an envelope of 0 | 0 to 1.0 |
lp_env |
what the coefficient gains per unit of envelope | 0 to 1.0 |
glide_to |
the phase increment the note glides to | 0 to below 1.0 |
glide |
the glide's length in samples; 0 is none (glide_to is then not read) |
any |
seed |
the noise generator's seed (waveform 8) | any |
echo_taps |
how many echoes of the note | 0 to 8 |
echo_delay |
samples from one echo to the next | 1 to 2²⁷ when echo_taps > 0 |
echo_gain |
each echo's level against the one before it | 0 to 1.0 |
echo_pan |
the pan of the odd echoes; the even ones sit at its negation | −1.0 to 1.0; 0 in one channel |
A reader refuses a voices section of another length, and a record with a field out of
its range. The default voice is pan 0, the three curves 1, lp_poles 0, lp_a
1.0, lp_env 0, glide_to 0, glide 0, seed 0, echo_taps 0, echo_delay 0,
echo_gain 0 and echo_pan 0. A voiced note sounds from start to its end,
start + dur + release + echo_taps × echo_delay (each sum exact); a note of a score with
no voices to start + dur + release.
3. Reference synthesis (trig-free)
Every value is a wai.det.fixed64 Q32.32 number: + and − saturate, × and ÷ are
the profile's (computed in 128 bits, rounded toward negative infinity, then saturated),
a negation of the least value is the greatest, and √ is the profile's (the floor square
root of the raw bits times 2³², and 0 for a value of 0 or below). An expression is
evaluated as written, × and ÷ before + and − and otherwise left to right, each
operation rounded as it is made; 1 is 1.0, ½ is 2³¹ raw, ¼ is 2³⁰ raw, and an
integer n of samples used as a value is n · 2³² raw. The whole path is + − × ÷ √
in the Fx floor — bit-identical on every machine.
3.1 Oscillators
A note's phase p starts at 0. At each sample the oscillator gives a value from p and
the sample's increment dt (the note's phase_inc, or its glide's, §3.3); then
p ← p + dt, and where p ≥ 1, p ← p − 1 once (the phase wraps).
0saw:2·p − 1.1square:1forp < ½, else−1.2triangle:4·p − 1forp < ½, else3 − 4·p.3pulse:1forp < ¼, else−1.- The step correction
B(t, dt):0fordt ≤ 0; fort < dt, withx = t ÷ dt,x + x − x·x − 1; fort > 1 − dt, withx = (t − 1) ÷ dt,x·x + x + x + 1; otherwise0. WithW(q)=q − 1forq ≥ 1andqotherwise:4saw:2·p − 1 − B(p, dt);5square:s + B(p, dt) − B(W(p + ½), dt),sthe square's value;6pulse:s + B(p, dt) − B(W(p + ½ + ¼), dt),sthe pulse's value. 7sine:u = 2·p − 1,y = 4·u·(1 − |u|),y ← P·(y·|y| − y) + ywithP= 966 367 642 raw (0.225), and the value is−y(sin 2πpto within a thousandth).8noise: the value held, from a 32-bit states, set to the voice'sseed(0 in a score with no voices) when the note starts, and advanced once then and again each time the phase wraps:s ← (1 664 525 · s + 1 013 904 223) mod 2³², the held value then((s >> 8) − 2²³) · 2⁹raw, in[−1, 1). Aphase_incjust below 1 draws a new value every sample after the first.
3.2 A score with no voices
Each note's envelope at t samples into it is the linear ADSR: for attack > 0 and
t < attack, t ÷ attack; else for t < attack + decay, 1 − (1 − sustain) × (t − attack) ÷ decay (or sustain when decay is 0); else for t < dur, sustain;
else for release > 0 and t < dur + release, sustain × (1 − (t − dur) ÷ release);
else 0. The note's sample is osc × env × gain, for t from 0 while start + t is
below both start + dur + release and duration. In one channel it is added to the
mix; in two, each channel adds it times √((1 − 0) / 2) (the centre, §3.3's pan of 0).
3.3 A voiced score
For each note and its voice, with F(t, n) = ⌊t · 2³² / n⌋ raw (exact, for t < n)
and v^k = v multiplied by v k − 1 times in turn:
- Glide. With
glide= 0,dt = phase_inc. Otherwisedt = phase_inc + ⌊(glide_to − phase_inc) · min(t, glide) / glide⌋raw, the difference of the raw values timesmin(t, glide), divided byglidetoward negative infinity: the increment moves in a line toglide_toand holds there. - Envelope. The held level
H(t): forattack > 0andt < attack,1 − (1 − F(t, attack))^attack_curve; else, withu = t − attack, fordecay > 0andu < decay,sustain + (1 − sustain) × (1 − F(u, decay))^decay_curve; elsesustain. The envelopeE(t)isH(t)fort < dur; forrelease > 0andt − dur < release,H(dur) × (1 − F(t − dur, release))^release_curve; else 0. The release starts from the level the note had at note-off, so a note let go during its attack or decay does not jump. - Low-pass. With
lp_poles=k> 0, the coefficient isa = lp_a + lp_env × E(t), held to[0, 1]; each sectioniof1..k, its state 0 when the note starts, makesy_i ← y_i + a × (x − y_i)from its inputx(the oscillator's value for the first, the section before'syfor the rest), and the last section'syis the filtered value. Withlp_poles0 the oscillator's value passes. - Gain.
x = filtered × E(t) × gain. - Pan. The gains of a pan
qare(L, R) = (√((1 − q) ⁄ 2), √((1 + q) ⁄ 2)), each⁄ 2the raw value shifted right one bit (floor):L² + R²is 1 at every pan, to within the rounding, and the centre is 3 037 000 499 raw (√½) on each side. In two channels the note addsx × Lto the left andx × Rto the right at framestart + t, by itspan; in one channel it addsx. - Echoes. Echo
jof1..echo_tapshas the levelg_j = g_{j−1} × echo_gain,g_0 = 1, and the panecho_panfor oddj,−echo_panfor evenj. After the note's own sample, each echo in order addsx × g_jat framestart + t + j × echo_delay, by its pan (in one channel,x × g_j), while that frame is belowduration.
t runs from 0 while start + t is below both start + dur + release and duration;
each sample the oscillator's value is taken, then the phase advances by that sample's
dt (§3.1).
3.4 Mixdown
The mix is duration × channels values, each 0 to start, two channels interleaved left,
right (frame f's left is value 2f, its right 2f + 1). The notes are rendered in
their order; within a note, sample by sample as above, each addition saturating as it
is made. Each value is then quantized to i16 (round(v · 32767), half away from zero,
v first clamped to [−1, 1]) in integer space.
4. Conformance — mixdown-equivalence
Given the same
wai.audio.score, a conforming sink MUST reconstruct the identical canonicali16PCM via the reference synth, hence the identical BLAKE3 — on every machine, no tolerance parameter.
mixdown_hash = BLAKE3("wai:score-mixdown\x01" || i16le PCM)
score_hash = BLAKE3("wai:score\x01" || contract || notes [|| voices]) // identity
The PCM is the mix of §3.4, two channels interleaved. contract, notes and voices
are the sections' bytes; a score with no voices section hashes as the first revision's
did. A conformance vector also states pcm_sha256, the SHA-256 of the same i16le PCM
(the audio buffer of SPEC §7 Digests), so a verifier that holds only SHA-256 checks
the samples. wai score render prints both, with samples the frames and channels
the contract's, and, with --pcm, writes the samples.
A sink MAY play a soundfont/neural instrument instead; that is presentation and does not affect conformance, exactly as the audio-scene HRTF render or the worlds renderer does not.
In a package (packaging §5, Components beside the lead), a score is cut at the lead's piece starts:
each piece a WSCR of the notes that sound in it (to their end, §2.4, echoes included),
its starts relative to the piece and their voices with them, whose mixdown is the
whole's for the piece's frames; its entry states the SHA-256 of that mixdown, and its
floor is the mixdown as FLAC in the score's channels.
5. Receipt (JWP profile)
score_hash is the identity; the group's objects are the mix PCM blocks
(4096 frames each, every channel of them; the last may be short), Merkle-bound; one Ed25519 signature (verified as
SPEC §7 Signatures requires) over score_hash + root + exact work (= duration × notes) + carried
joules_micro; parent_receipt_hash chains — the worlds/audio receipt
shape reused. The figure's acquisition class rides beside it as an optional signed label
(energy-measurement §2.8): absent, the receipt keeps
its legacy bytes and the figure is unlabelled; present, it is inside the
signature. The reference meter labels every figure it measures
OnChipCounter, with its declared uncertainty.
Appendix
Mixdown-equivalence is spatial audio's criterion with the recorded
objects replaced by note events: both render a deterministic i16 mix
in the Fx floor and hash it. The score is the most extreme
instructions-at-the-sink case in audio — there is no recording anywhere
in the payload, only the composition. Sight, hearing, touch, motion,
state, captured-content provenance — and now the composition itself —
are one floor, one verification.