How many heights does a dip need? How many bits does a swell deserve? Turn the knobs and
watch the piano's eight envelopes survive — or lose their personality.
Storage and playback are separate. Eight stored heights can describe a
curve that the CPU evaluates thousands of times. A smaller instrument does not require a
slower control clock.
Loading the piano curves…
Listen to the native 16-point, 4-bit rounded player → A fixed
version of these defaults now runs in FANCO at 600 Hz, including the exponential tail.
Complete program: 1,109 bytes. The controls below explore descriptors; they do not change
that published sound.
01 / Three independent budgets
Points. Precision. Playback.
The white line is the current 440-reduced piano envelope. Green is reconstructed from the
actual packed bytes. Orange shows that curve sampled by the CPU and held
by the existing chip between updates.
Try a budget:
—shape bytes · all eight partials
—descriptor bytes · peaks, decay + header included
—CPU vector writes · through final silence
Current curveDecoded compact curveCPU-held gain
All eight envelopes with these settings
02 / Nothing up the sleeve
Count every byte.
The diagram is drawn after encoding and decoding the descriptor. The time grid is
generated from its header; no list of timestamps or large expansion table is stored.
Part
Contents
Bytes
Header
Version, modes, count, duration, time expansion, log range
8
Partial peaks
Eight 12-bit gain peaks
12
Exponential tails
Eight response bytes · mode 11 + six-bit T₉₀
8
Curve heights
—
—
Total
One eight-partial envelope descriptor
—
These are envelope bytes, not the whole instrument. Frequencies, pan, hammer, reverb,
decoder code, and moving CPU state are separate. If a bank shares timing and peak rules,
some overhead can be amortized; it is counted here rather than hidden.
Little-endian fields and least-significant-bit-first packing. Version byte 0 = 2. Byte
1: precision index in bits 0–1; log encoding bit 2; expanding time bit 3; S-curve bit 4;
log interpolation bit 5. Bytes 2–3: destination count. Bytes 4–5: shape-to-decay handoff
in 600 Hz ticks. Byte 6: expansion × 10. Byte 7: log range in dB. Next 12 bytes: eight
packed 12-bit peaks. Next 8 bytes: exponential response bytes, 11qqqqqq, with T₉₀ =
2^(q/5)/600 seconds. Remaining bytes: destination-major heights, eight per row. Version
1 downloads remain decodable with their original final-zero behavior.
The preview shows up to 256 bytes; the download includes the complete descriptor.
Decoded destinations for this partial
Point
Time
Packed code
Gain / peak
dB
Decoded decay settings — included in the download
Partial
Response byte
T₉₀
Handoff gain / peak
Silence by
The last point starts the tail
gain(t) = lastGain · 10^(-(t − handoff) / T₉₀)
The tail starts at the final landmark's level and keeps falling. S-curves shape the
stored body; they do not ease the ending into an artificial zero. T₉₀ is fitted from the
last 1.5 seconds before handoff. A flat or rising ending uses the broader decay trend,
then an 8-second fallback. Response times round to the six-bit time code, whose longest
value is about 10.35 s.
The moving value keeps fractional precision. Once it drops below half a mixer-gain unit,
it becomes exact zero. That prevents a tiny integer gain from getting stuck forever. A
tail already below that threshold is silent immediately. A zero endpoint needs no decay
work.
Why log heights?
With 4-bit linear heights, the smallest nonzero value is 1/15 of peak — about −23.5
dB. Much of a piano's tail falls below that. Log heights distribute codes across the
selected dB range instead, with one reserved silence code.
For 4 bits and 60 dB, the 15 nonzero codes are about 4.29 dB apart. More bits reduce
the height error; more points capture extra bends. Neither substitutes for the other.
Why expanding time?
Uniformly storing eight points over eight seconds misses the whole strike. An
exponential time warp spends points near the beginning and gradually widens the
intervals.
t(k) = T · expm1(a · k/K) / expm1(a) k = 1…K; t(0) = 0
a = 0 gives uniform spacing. Times land on 600 Hz ticks and are kept strictly
increasing. Changing the expansion moves detail between attack and tail.
03 / Store behavior instead of a trail of heights
A tiny envelope program.
A second format can describe changes of motion. Instead of spending a new height
on every segment, keep moving toward the same target and change the rule when it matters.
Tick
Instruction
Meaning
0 · 0 ms
S-curve → 1.0, duration 12 ticks
A rounded 20 ms rise
1 · 1.667 ms
Linear; keep target and deadline
Continue from the current height toward 1.0 at 20 ms
4 · 6.667 ms
Exponential; T₉₀ = 5 ms
Keep heading toward 1.0, now closing 90% of the gap every 5 ms
Green is the continuous path defined by the three instructions. Orange is its 600 Hz
CPU-held version. Switching motion preserves the current height; a change of slope is
allowed.
“Keep target” is not “hold”
Keep target preserves the destination and continues moving.
Hold captures the current height and stops. Mode changes rebase from
the current value, not the old segment start. An exponential mode uses settling time
instead of an arrival deadline.
Compact event encoding
A delta tick and a field mask can say which fields change. A mode-only event need not
repeat target or duration. All partials can share a motion script while keeping their
own heights and decay parameters.
The example is a behavior proposal, separate from the packed-height format above. It
is not included in that descriptor's byte count.
04 / Use the chip we already have
600 Hz is a practical bridge.
A CPU routine can keep a small amount of state per envelope, read the next compact
destination when needed, and calculate intermediate gains at the existing 600 Hz latch
cadence. The SIREN synthesizer still generates audio at 48 kHz.
What gets cheaper
The stored shape can shrink from 30,720 bytes to a small descriptor. There is no need
to expand eight seconds of values in RAM. Linear motion is incremental addition;
exponential following can use a precomputed multiplier; an S-curve can use cubic
forward differences.
Those arithmetic rules can be adapted to fixed point for the guest CPU. Log-domain
ramps need domain conversion, so gain-space linear ramps are the simplest first
player.
What still costs work
At 600 Hz, eight gains require 4,800 lane evaluations per second,
versus 1,920 at the current 240 Hz. Over eight seconds that is 4,800 vector updates
and 38,400 gain writes. A smaller file is not proof of fewer guest instructions.
At 48 kHz, each 600 Hz gain value lasts 80 audio samples. The orange line shows these
held steps and the current 12-bit mixer gain rounding. A later audio-rate smoother
could remove that last staircase if listening finds it matters.
Choose the description first. CPU interpolation lets us audition compact
curves with no chipset changes. Chip-side motion remains an option if reducing CPU traffic
or smoothing the final sample steps earns its place.
The curves here use the existing 240 Hz parameter snapshot, joined linearly as the
reference. This page packs, decodes, and draws envelopes in the browser; it does not alter
the listening programs or run a guest CPU player. No audio autoplay.