rig schema 2: measured ships a curve, blend arrives, and the manifest carries what a reader cannot derive - #15
Merged
Merged
Conversation
…d control role MEASURED loses its 1-pole fit. `lp1_hz`, `gain_db` and `residual_db` are removed, not deprecated: measured on real hardware, a Big Muff tone control is a bass-cut/treble-boost blend of ~30 dB across the band, and the best 1-pole fit missed it by 15-65 dB. Everything parsed, nothing complained, and a player written against those numbers would sound like a different pedal. `db[]` — the magnitude table on the stated grid — becomes REQUIRED and is the whole description. No bands, no coefficients, nothing tied to a sample rate. TRUSTED adds the band where that curve was shown to BE a filter, and the drive range it was verified over: `lo_hz`, `hi_hz`, `span_db`, `levels`. Each position is swept at several levels 6 dB apart; a filter comes back the same at all of them, harmonic products do not. On that Big Muff the curves agreed within 0.8 dB to 9 kHz and disagreed by 4-8 dB by 12 kHz, so the +16 dB of treble the control really has is worth shipping and the +24 dB the same measurement claimed at 16 kHz is not. The curve ships whole either way — a measurement thrown away cannot be reconsidered later; the band says where to stop believing it. `span_db` is why the band is a claim with terms attached. Push the same pedal 40 dB below its capture level and it stops distorting at all, at which point its bass really is a different filter — the pedal telling the truth, not the measurement failing. Verified over 24 dB its tone control is a filter from 60 Hz; over 42 dB, only from 700 Hz. Both are correct about different questions. BLEND is new and is neither of the other two roles. Not a filter, so `measured` cannot describe it; and never a captured axis, because the dry signal IS the DI the model is already fed — eleven blend positions as an axis would be eleven copies of one model plus arithmetic a player does for free. It ships as the mix itself: the dry path's own response (measured at `dry_end`, where the output is that path ALONE and therefore linear), the two gains per position, `polarity` (-1 for a box that inverts one path, which decides whether the middle of the knob's travel is full or hollow), and `law` as provenance only — the gains are explicit so a pot with a peculiar taper stays expressible. The reference invariant holds for both roles: every model was captured with the control at `reference` (a blend's is its full-WET end), so a reader that skips these blocks plays the reference and nothing is double-filtered or double-mixed. Conformance: rig-measured carries `trusted`; goldens regenerated; rig + rig-conformance updated. 5/5 green.
…ices, per-position level, labels
Six holes, each of which a player either had to guess at or could not answer at all.
DRY LEVEL. `blend.dry[]` shipped the dry path's SHAPE with its broadband level removed, and the
level itself was computed and then dropped on the floor. A dry path with 9 dB of insertion loss
therefore rendered 9 dB too hot at every position that is not an end, and no reader could correct
it — the pedal is not in their hands. `dry_level_db` is now required and stated as non-derivable.
TRUSTED, HONESTLY. The band was only written when it came out non-empty, so "tested and failed
everywhere" and "never tested" reached the wire identically — and the spec reads the second as
"trust the whole grid", which handed the least reproducible measurement the widest permission. It
is now emitted whenever levels were weighed, and `hi_hz <= lo_hz` means do not apply this curve.
`levels` is the FEWEST any position was weighed at, not the most, so one thorough position cannot
vouch for the rest; `span_db` is the range actually swept rather than the window that was allowed.
TRUSTED AS INDICES. The hold rule is applied on the grid, so every player had to map Hz to an
index; two players rounding differently pick neighbouring points, and on a 30 dB tilt adjacent
1/12-octave points are decibels apart. The producer knows the grid exactly and now says which
points it meant.
PER-POSITION LEVEL. A tone control with 30 dB of tilt changes loudness across its travel. Without
a stated level every player either integrates the table itself — choosing its own band and
weighting — or ships a knob that jumps. One number, defined over the trusted band, defined once.
POSITIONS ASCEND BY NORM, and a default is stated. A knob declared "300, 240, … 0" — a normal way
to write one that reads ten to zero — used to ship descending norms straight into readers that
assume otherwise, ours included: the interpolator then returned the first position's filter at
every setting, silently. Sorting at export removes the search, the sort and the defence from every
player. `default` exists because the reference is usually an END of travel, so a player reaching
for it as a starting position pins the knob at one extreme.
BLEND `norm` IS ROTATION, as it already was for measured. It used to be the mix fraction here and
rotation there — one field name, one pack, two meanings. Which end is wet is said by `reference`,
which end is dry by `dry_end`; norm says where the knob points and nothing else.
LABELS. The token is the pack's identity ("green"); the label is what a human calls it ("Clean").
A player cannot derive one from the other, so without this every player invents its own prettifier
over our tokens.
Conformance regenerated; rig round-trip covers each new field. 5/5 green.
The blend block had no fixture at all — the newest part of the format and the one where a reader's mistakes are silent: a wrong polarity or a dB-interpolated gain still produces audio, just not the pedal's. rig-measured now carries a blend, and the conformance binary builds it, checks every field and holds the manifest byte-exact. Wiring it up found a real defect. The loader has always read `default` on a measured control; the writer never emitted it. So a pack that said where the knob starts lost that fact the first time anything rewrote it, silently, because a missing `default` legally means "start at the reference". Conformance missed it for exactly one reason: no fixture set the field. One does now, and it fails against the old golden before it passes against the new one.
The block exists to say where a curve was shown to BE a filter, which is a statement about two or more sweeps agreeing. A producer that records one sweep per position emitted it anyway, and then it always carried the same six values: the whole grid, span_db 0, levels 1 — "nothing was tested", spelled out six times in every measured control of every pack. Absence says that in none. The empty-band case still ships whenever levels >= 2, because "tested and failed everywhere" is a different statement from "never tested" and the two must not look alike on the wire — which is why the gate was > 0 in the first place.
A device can point two settings at one model. The bottom notches of a gain dial are where the hardware gives hiss and little else, and a model trained on that is a model of hiss — so the capture side may decide those settings play the neighbour above them with less signal going in. Into the model, not out of it: less signal is also less drive, so the dial fades rather than falling into a hole, which is what turning a gain knob down actually does. `input_db` on a file entry, written only when it is not zero, read back as a plain number. A player applies it before the model and needs no other rule; the decision, and the reason for it, live on the capture side.
…e must stop `measured` stopped shipping a fitted 1-pole and started shipping the curve itself; `blend` arrived as a third role; and the manifest began carrying what a reader cannot derive — the dry path's level, the trusted band as grid indices, the level of each position. None of that is additive. A reader built for schema 1 would parse such a manifest, recognise the keys it knows, drop the rest, and be confidently wrong about every measured knob — which is the exact failure `schema` exists to prevent, and the number stayed at 1 through all of it. So it goes to 2, and the writer stops spelling it as a literal: one constant, bumped once, or the two ends drift apart the day someone edits only the one they are looking at. The library's own version was a separate untruth. NAMZ_VERSION has said "1.0.0" since the first release while the tags walked to v1.1.1 — a header that misreports itself is worse than one that says nothing. It reads 2.0.0 now, which is also what this release is: the wire format is untouched, and the rig layer is not what it was.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
.orbitriglayer changes shape. The.namzwire format is untouched.What changed
measuredships a CURVE, not a fitted 1-pole. Measured on a Big Muff tone control the best1-pole fit missed by 15 to 65 dB across a 30 dB tilt, so the fit is gone rather than deprecated.
blend— a third control role. A blend is not a filter andmeasuredcannot describe it.grid indices as well as hertz, the broadband level of each position, position labels.
trustedships only when levels were actually compared — an untested claim now says so bybeing absent instead of by carrying a default.
input_db) — one setting playinganother's model, which is how the bottom of a gain dial fades instead of turning into a model of hiss.
schema 1 → 2
None of the above is additive. A schema-1 reader would parse such a manifest, recognise the keys it
knows, drop the rest and be confidently wrong about every measured knob — the exact failure the
number exists to prevent. It stayed at 1 through all of it; this bumps it, and makes the writer spell
it from the constant so the two ends cannot drift.
Known consequence, deliberate: a schema-1 pack that carried a
measuredcontrol reads back withthat knob silent, because its old shape is no longer parsed. No such pack was ever published — the
factory packs are still unbuilt — so this costs nothing today. Worth revisiting if that changes.
Library version
NAMZ_VERSIONhas read "1.0.0" since the first release while the tags walked to v1.1.1. A headerthat misreports itself is worse than one that says nothing. It reads 2.0.0 now — which is what this
release is: the codec stands, the rig layer is not what it was.
Verification
ctest— 5/5: roundtrip, adversarial, rig, conformance, rig conformance. The golden.orbitrigpacks were regenerated and are compared byte for byte in both directions.