@openmixer/omx-contract 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,7 @@ pkg-config --cflags omx-contract
49
49
  ```
50
50
 
51
51
  Pin one version and build against its renders instead of keeping a copy: the installed package at exactly
52
- that version (`pkg-config --exact-version=2.6.0 omx-contract`), else the npm tarball, unpacked, and
52
+ that version (`pkg-config --exact-version=2.8.0 omx-contract`), else the npm tarball, unpacked, and
53
53
  `omx-contract render --target c --out build/omx-contract/include`. A kernel's controls and readouts, in their
54
54
  order, are in `share/omx-contract/omx-contract.json`; how a declaration is written is in
55
55
  [docs/kernel-files.md](docs/kernel-files.md), and what a readout is in [docs/readouts.md](docs/readouts.md).
@@ -127,7 +127,7 @@
127
127
  "name": "detectorTopology",
128
128
  "kind": "choice",
129
129
  "global": "DETECTOR_TOPOLOGY",
130
- "doc": "Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, openmixer's ssl-bus-compressor spec §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.",
130
+ "doc": "Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, the SSL bus compressor design §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.",
131
131
  "ids": [
132
132
  "feedforward",
133
133
  "feedback"
@@ -158,7 +158,7 @@
158
158
  "constants": {
159
159
  "PROGRAM_RELEASE": {
160
160
  "kind": "sheet",
161
- "doc": "The program-dependent release's model constants — the LA-2A's opto cell as DATA (`docs/design/specs/2026-09-26-compressor-models.md` §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.",
161
+ "doc": "The program-dependent release's model constants — the LA-2A's opto cell as DATA (compressor models design §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.",
162
162
  "value": {
163
163
  "chargeMs": 1000,
164
164
  "dischargeMs": 5000,
@@ -128,7 +128,7 @@
128
128
  "constants": {
129
129
  "DEESS_KNEE_DB": {
130
130
  "kind": "scalar",
131
- "doc": "The knee, in dB — a CONSTANT of the stage and not a row field (`2026-09-22-native-deesser-stage.md` §4b): a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.",
131
+ "doc": "The knee, in dB — a CONSTANT of the stage and not a row field: a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.",
132
132
  "unit": "dB",
133
133
  "value": 6
134
134
  }
@@ -5,7 +5,7 @@
5
5
  "name": "fxDelayTime",
6
6
  "kind": "travel",
7
7
  "global": "FX_DELAY_TIME_RANGE",
8
- "doc": "The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by `output-delay-ceiling.test.ts`. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word \"delay\" is why they keep being confused for each other.",
8
+ "doc": "The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by the engine's conformance test. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word \"delay\" is why they keep being confused for each other.",
9
9
  "travel": {
10
10
  "min": 0,
11
11
  "max": 2000,
@@ -38,7 +38,7 @@
38
38
  {
39
39
  "name": "tone",
40
40
  "kind": "travel",
41
- "doc": "The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching `DEFAULT_DELAY_STATE.tone`. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.",
41
+ "doc": "The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching the console's delay default tone. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.",
42
42
  "travel": {
43
43
  "min": 0,
44
44
  "max": 1,
@@ -84,7 +84,7 @@
84
84
  "constants": {
85
85
  "FX_DELAY_PINGPONG_DEFAULT": {
86
86
  "kind": "scalar",
87
- "doc": "The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the server's `DEFAULT_DELAY_STATE` (which now reads it) so the DPF plugin generator (`packages/omx-plugins/tools/params-gen.mjs`, `2026-09-25-omx-plugins-dpf.md` §3a) reads the same fact the desk seeds a fresh strip with.",
87
+ "doc": "The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the console's delay default state (which now reads it) so the DPF plugin generator (omx-plugins, DPF plugin design §3a) reads the same fact the desk seeds a fresh strip with.",
88
88
  "unit": "",
89
89
  "value": false
90
90
  }
@@ -161,6 +161,22 @@
161
161
  "12 kHz",
162
162
  "16 kHz"
163
163
  ]
164
+ },
165
+ {
166
+ "name": "oversample",
167
+ "kind": "choice",
168
+ "doc": "The drive's OVERSAMPLING factor: the shaper runs inside the console's one oversampler at 1x, 2x or 4x, and the id is the factor (`omx_drive.h`'s `omx_drive_factor_of` member set, `struct omx_drive.os_factor`). A field of the console's drive row (`DriveState.oversample`, the console's DRIVE_OVERSAMPLE_FACTORS; the set is named for its control), 4 at come-up there and in omx-dsp's drive face (OMX_DRIVE_INSTANCE_FACTOR, the oversampler's largest).",
169
+ "ids": [
170
+ 1,
171
+ 2,
172
+ 4
173
+ ],
174
+ "default": 4,
175
+ "labels": [
176
+ "1x",
177
+ "2x",
178
+ "4x"
179
+ ]
164
180
  }
165
181
  ],
166
182
  "constants": {
@@ -107,7 +107,7 @@
107
107
  {
108
108
  "name": "gain",
109
109
  "kind": "travel",
110
- "doc": "The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (`core/src/eq.ts` derives `EQ_GAIN_LIMIT_DB` from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.",
110
+ "doc": "The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (the engine derives its gain limit from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.",
111
111
  "travel": {
112
112
  "min": -15,
113
113
  "max": 15,
@@ -162,7 +162,7 @@
162
162
  "constants": {
163
163
  "EQ_MAX_BANDS": {
164
164
  "kind": "scalar",
165
- "doc": "The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (`output-delay-ceiling.test.ts` reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.",
165
+ "doc": "The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (the engine's conformance test reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.",
166
166
  "unit": "",
167
167
  "value": 24
168
168
  },
@@ -130,7 +130,7 @@
130
130
  },
131
131
  "FBS_MAX_NOTCH_DEPTH_DB": {
132
132
  "kind": "scalar",
133
- "doc": "THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (2026-09-08-room-analysis.md §4 — \"never more than the FBS notch depth law\"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.",
133
+ "doc": "THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (the room-analysis design §4 — \"never more than the FBS notch depth law\"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.",
134
134
  "unit": "dB",
135
135
  "value": 18
136
136
  },
@@ -126,6 +126,28 @@
126
126
  "default": 16,
127
127
  "defaultFrom": "desk"
128
128
  }
129
+ },
130
+ {
131
+ "name": "hpHz",
132
+ "kind": "travel",
133
+ "table": "GATE_KEY_LIMITS",
134
+ "travel": {
135
+ "min": 0,
136
+ "max": 20000,
137
+ "step": 1,
138
+ "unit": "Hz"
139
+ }
140
+ },
141
+ {
142
+ "name": "lpHz",
143
+ "kind": "travel",
144
+ "table": "GATE_KEY_LIMITS",
145
+ "travel": {
146
+ "min": 0,
147
+ "max": 20000,
148
+ "step": 1,
149
+ "unit": "Hz"
150
+ }
129
151
  }
130
152
  ],
131
153
  "readouts": [
@@ -146,6 +168,9 @@
146
168
  "tables": {
147
169
  "GATE_LIMITS": {
148
170
  "doc": "The legal range of each gate field (engineering units). The widget clamps drags to these, `normalizeGate` enforces them, the row's codec refuses by them and the travel sheet publishes them — one source of truth. `rangeDb` reaches GATE_RANGE_FLOOR_DB, shown as \"−∞\" in readouts (a gate fully closed is silence)."
171
+ },
172
+ "GATE_KEY_LIMITS": {
173
+ "doc": "The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared."
149
174
  }
150
175
  }
151
176
  }
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "HRP_MAX_VOICES": {
26
26
  "kind": "scalar",
27
- "doc": "How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. `core/src/output-delay-ceiling.test.ts` reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.",
27
+ "doc": "How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. the engine's conformance test reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.",
28
28
  "unit": "",
29
29
  "value": 8
30
30
  },
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "HRP_DEFAULT_MAX_AUTO_BANDS": {
62
62
  "kind": "scalar",
63
- "doc": "How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer \"will this fit?\" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. `eq-budget-fits.test.ts` asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.",
63
+ "doc": "How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer \"will this fit?\" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. the engine's conformance test asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.",
64
64
  "unit": "",
65
65
  "value": 8
66
66
  }
@@ -59,7 +59,7 @@
59
59
  ],
60
60
  "tables": {
61
61
  "LIMITER_LIMITS": {
62
- "doc": "The precision limiter's travels (`2026-09-27-precision-limiter.md` §2), rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by `harness/contract-limits-gen.mjs`, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth."
62
+ "doc": "The precision limiter's travels, rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by the engine's limits generator, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth."
63
63
  }
64
64
  }
65
65
  }
@@ -3,7 +3,7 @@
3
3
  "constants": {
4
4
  "BUS_INPUT_CAP": {
5
5
  "kind": "scalar",
6
- "doc": "The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. `native-input-ceiling.test.ts` holds this to the C header; spec `docs/design/specs/2026-09-14-console-allocation.md` §9 carries the ruling and the measured cost of width.",
6
+ "doc": "The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. the engine's conformance test holds this to the C header; the console allocation design §9 carries the ruling and the measured cost of width.",
7
7
  "unit": "",
8
8
  "value": 128
9
9
  }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "plateModDepth",
6
6
  "kind": "travel",
7
- "doc": "PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it (`2026-09-26-native-fx-catalogue.md` §2, Reverb).",
7
+ "doc": "PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it.",
8
8
  "travel": {
9
9
  "min": 0,
10
10
  "max": 400,
@@ -111,7 +111,7 @@
111
111
  {
112
112
  "name": "reverse",
113
113
  "kind": "travel",
114
- "doc": "REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds (`2026-07-15-native-delay-reverb.md`, Amendment 2026-09-17 §B). A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.",
114
+ "doc": "REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds. A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.",
115
115
  "travel": {
116
116
  "min": 50,
117
117
  "max": 500,
@@ -3,7 +3,7 @@
3
3
  "constants": {
4
4
  "RTA_TARGET_BIN_HZ": {
5
5
  "kind": "scalar",
6
- "doc": "The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; `rta-window-derivation.test.ts` reads it back from the header.",
6
+ "doc": "The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; the engine's conformance test reads it back from the header.",
7
7
  "unit": "Hz",
8
8
  "value": 5.9
9
9
  },
@@ -69,7 +69,7 @@
69
69
  ],
70
70
  "tables": {
71
71
  "TRANSIENT_LIMITS": {
72
- "doc": "The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. `harness/contract-limits-gen.mjs` emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921)."
72
+ "doc": "The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. the engine's limits generator emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921)."
73
73
  }
74
74
  },
75
75
  "constants": {
@@ -5,7 +5,7 @@
5
5
  "name": "trimDb",
6
6
  "kind": "travel",
7
7
  "global": "TRIM_RANGE",
8
- "doc": "The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (openmixer's TRIM_RANGE, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.",
8
+ "doc": "The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (the console's trim range, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.",
9
9
  "travel": {
10
10
  "min": -24,
11
11
  "max": 24,
package/data/rates.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "../schema/omx-contract.schema.json",
3
3
  "STANDARD_SAMPLE_RATES": {
4
4
  "kind": "list",
5
- "doc": "The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` (`2026-07-15-clock-rate-honest-force-rate.md`) — this is the ceiling a write is refused by, never a menu.",
5
+ "doc": "The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` — this is the ceiling a write is refused by, never a menu.",
6
6
  "unit": "Hz",
7
7
  "values": [
8
8
  44100,
@@ -255,13 +255,13 @@ export declare const COMP_KINDS: readonly ["comp", "limiter"];
255
255
  export declare const DETECTOR_OVERSAMPLINGS: readonly ["auto", "off", "x4"];
256
256
 
257
257
  /**
258
- * Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, openmixer's ssl-bus-compressor spec §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.
258
+ * Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, the SSL bus compressor design §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.
259
259
  * @see data/kernels/comp.json
260
260
  */
261
261
  export declare const DETECTOR_TOPOLOGY: readonly ["feedforward", "feedback"];
262
262
 
263
263
  /**
264
- * The program-dependent release's model constants — the LA-2A's opto cell as DATA (`docs/design/specs/2026-09-26-compressor-models.md` §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.
264
+ * The program-dependent release's model constants — the LA-2A's opto cell as DATA (compressor models design §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.
265
265
  * @see data/kernels/comp.json
266
266
  */
267
267
  export declare const PROGRAM_RELEASE: {
@@ -381,13 +381,13 @@ export declare const DEESS_RELEASE_RANGE: {
381
381
  export declare const DEESS_MODES: readonly ["split", "wideband"];
382
382
 
383
383
  /**
384
- * The knee, in dB — a CONSTANT of the stage and not a row field (`2026-09-22-native-deesser-stage.md` §4b): a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.
384
+ * The knee, in dB — a CONSTANT of the stage and not a row field: a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.
385
385
  * @see data/kernels/deesser.json
386
386
  */
387
387
  export declare const DEESS_KNEE_DB: 6;
388
388
 
389
389
  /**
390
- * The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by `output-delay-ceiling.test.ts`. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word "delay" is why they keep being confused for each other.
390
+ * The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by the engine's conformance test. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word "delay" is why they keep being confused for each other.
391
391
  * @see data/kernels/delay.json
392
392
  */
393
393
  export declare const FX_DELAY_TIME_RANGE: {
@@ -413,7 +413,7 @@ export declare const FX_DELAY_FEEDBACK_RANGE: {
413
413
  };
414
414
 
415
415
  /**
416
- * The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching `DEFAULT_DELAY_STATE.tone`. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.
416
+ * The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching the console's delay default tone. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.
417
417
  * @see data/kernels/delay.json
418
418
  */
419
419
  export declare const DELAY_TONE_RANGE: {
@@ -448,7 +448,7 @@ export declare const DELAY_MIX_RANGE: {
448
448
  export declare const DELAY_PINGPONGS: readonly ["off", "on"];
449
449
 
450
450
  /**
451
- * The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the server's `DEFAULT_DELAY_STATE` (which now reads it) so the DPF plugin generator (`packages/omx-plugins/tools/params-gen.mjs`, `2026-09-25-omx-plugins-dpf.md` §3a) reads the same fact the desk seeds a fresh strip with.
451
+ * The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the console's delay default state (which now reads it) so the DPF plugin generator (omx-plugins, DPF plugin design §3a) reads the same fact the desk seeds a fresh strip with.
452
452
  * @see data/kernels/delay.json
453
453
  */
454
454
  export declare const FX_DELAY_PINGPONG_DEFAULT: false;
@@ -548,6 +548,12 @@ export declare const DRIVE_STEREO_LINKS: readonly ["off", "on"];
548
548
  */
549
549
  export declare const DRIVE_HF_ROLLOFFS: readonly [0, 12000, 16000];
550
550
 
551
+ /**
552
+ * The drive's OVERSAMPLING factor: the shaper runs inside the console's one oversampler at 1x, 2x or 4x, and the id is the factor (`omx_drive.h`'s `omx_drive_factor_of` member set, `struct omx_drive.os_factor`). A field of the console's drive row (`DriveState.oversample`, the console's DRIVE_OVERSAMPLE_FACTORS; the set is named for its control), 4 at come-up there and in omx-dsp's drive face (OMX_DRIVE_INSTANCE_FACTOR, the oversampler's largest).
553
+ * @see data/kernels/drive.json
554
+ */
555
+ export declare const DRIVE_OVERSAMPLES: readonly [1, 2, 4];
556
+
551
557
  /**
552
558
  * The bias at `character = +1`, in the SHAPER's own input units — `OMX_DRIVE_BIAS_MAX`. DERIVED, not chosen (§4b). In the saturating limit every knee tends to `sgn`, the output is a rectangular wave of duty `d = ½ + asin(b)/π`, and its `n`th harmonic goes as `sin(nπd)/n` — so `|H2|` is maximal at `d = ¾`, which is `b = sin(π/4) = 1/√2` exactly. Measured too: the H2-maximising bias for a full-scale sine at unity drive is 0.712 (soft), 0.658 (tape), 0.645 (tube), and this one value costs 0.000 / 0.035 / 0.088 dB of H2 against each curve's own optimum. ONE normalised bias for all three knees, not three.
553
559
  * @see data/kernels/drive.json
@@ -612,7 +618,7 @@ export declare const EQ_FREQ_RANGE: {
612
618
  };
613
619
 
614
620
  /**
615
- * The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (`core/src/eq.ts` derives `EQ_GAIN_LIMIT_DB` from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.
621
+ * The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (the engine derives its gain limit from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.
616
622
  * @see data/kernels/eq.json
617
623
  */
618
624
  export declare const EQ_GAIN_RANGE: {
@@ -651,7 +657,7 @@ export declare const EQ_NOTCH_Q_RANGE: {
651
657
  export declare const EQ_BAND_ONS: readonly ["off", "on"];
652
658
 
653
659
  /**
654
- * The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (`output-delay-ceiling.test.ts` reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.
660
+ * The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (the engine's conformance test reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.
655
661
  * @see data/kernels/eq.json
656
662
  */
657
663
  export declare const EQ_MAX_BANDS: 24;
@@ -846,7 +852,7 @@ export declare const DEFAULT_THRESHOLDS: {
846
852
  };
847
853
 
848
854
  /**
849
- * THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (2026-09-08-room-analysis.md §4 — "never more than the FBS notch depth law"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.
855
+ * THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (the room-analysis design §4 — "never more than the FBS notch depth law"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.
850
856
  * @see data/kernels/fbs.json
851
857
  */
852
858
  export declare const FBS_MAX_NOTCH_DEPTH_DB: 18;
@@ -1012,6 +1018,25 @@ export declare const GATE_LIMITS: {
1012
1018
  };
1013
1019
  };
1014
1020
 
1021
+ /**
1022
+ * The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared.
1023
+ * @see data/kernels/gate.json
1024
+ */
1025
+ export declare const GATE_KEY_LIMITS: {
1026
+ readonly hpHz: {
1027
+ readonly min: 0;
1028
+ readonly max: 20000;
1029
+ readonly step: 1;
1030
+ readonly unit: "Hz";
1031
+ };
1032
+ readonly lpHz: {
1033
+ readonly min: 0;
1034
+ readonly max: 20000;
1035
+ readonly step: 1;
1036
+ readonly unit: "Hz";
1037
+ };
1038
+ };
1039
+
1015
1040
  /**
1016
1041
  * The generator kinds, in surface order: its position is the integer omx-dsp's omx_gen_fill takes as its kind (OMX_GEN_OSC, OMX_GEN_WHITE, OMX_GEN_PINK).
1017
1042
  * @see data/kernels/generator.json
@@ -1075,7 +1100,7 @@ export declare const HRP_AMOUNT_RANGE: {
1075
1100
  export declare const HRP_MAX_CUT_DB: 6;
1076
1101
 
1077
1102
  /**
1078
- * How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. `core/src/output-delay-ceiling.test.ts` reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.
1103
+ * How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. the engine's conformance test reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.
1079
1104
  * @see data/kernels/hrp.json
1080
1105
  */
1081
1106
  export declare const HRP_MAX_VOICES: 8;
@@ -1111,13 +1136,13 @@ export declare const HRP_COLLISION_CENTS: 100;
1111
1136
  export declare const HRP_ATTRIBUTION_CENTS: 60;
1112
1137
 
1113
1138
  /**
1114
- * How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer "will this fit?" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. `eq-budget-fits.test.ts` asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.
1139
+ * How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer "will this fit?" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. the engine's conformance test asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.
1115
1140
  * @see data/kernels/hrp.json
1116
1141
  */
1117
1142
  export declare const HRP_DEFAULT_MAX_AUTO_BANDS: 8;
1118
1143
 
1119
1144
  /**
1120
- * The precision limiter's travels (`2026-09-27-precision-limiter.md` §2), rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by `harness/contract-limits-gen.mjs`, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth.
1145
+ * The precision limiter's travels, rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by the engine's limits generator, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth.
1121
1146
  * @see data/kernels/limiter.json
1122
1147
  */
1123
1148
  export declare const LIMITER_LIMITS: {
@@ -1148,7 +1173,7 @@ export declare const LIMITER_LIMITS: {
1148
1173
  };
1149
1174
 
1150
1175
  /**
1151
- * The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. `native-input-ceiling.test.ts` holds this to the C header; spec `docs/design/specs/2026-09-14-console-allocation.md` §9 carries the ruling and the measured cost of width.
1176
+ * The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. the engine's conformance test holds this to the C header; the console allocation design §9 carries the ruling and the measured cost of width.
1152
1177
  * @see data/kernels/mixmatrix.json
1153
1178
  */
1154
1179
  export declare const BUS_INPUT_CAP: 128;
@@ -1323,7 +1348,7 @@ export declare const PITCH_KERNEL: {
1323
1348
  };
1324
1349
 
1325
1350
  /**
1326
- * PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it (`2026-09-26-native-fx-catalogue.md` §2, Reverb).
1351
+ * PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it.
1327
1352
  * @see data/kernels/reverb.json
1328
1353
  */
1329
1354
  export declare const REVERB_PLATE_MOD_DEPTH_RANGE: {
@@ -1430,7 +1455,7 @@ export declare const REVERB_HIGHCUT_RANGE: {
1430
1455
  };
1431
1456
 
1432
1457
  /**
1433
- * REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds (`2026-07-15-native-delay-reverb.md`, Amendment 2026-09-17 §B). A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.
1458
+ * REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds. A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.
1434
1459
  * @see data/kernels/reverb.json
1435
1460
  */
1436
1461
  export declare const REVERB_REVERSE_RANGE: {
@@ -1726,7 +1751,7 @@ export declare const ROTARY_STOP_EPS: 0.0001;
1726
1751
  export declare const ROTARY_RING_FLOATS: 256;
1727
1752
 
1728
1753
  /**
1729
- * The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; `rta-window-derivation.test.ts` reads it back from the header.
1754
+ * The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; the engine's conformance test reads it back from the header.
1730
1755
  * @see data/kernels/rta.json
1731
1756
  */
1732
1757
  export declare const RTA_TARGET_BIN_HZ: 5.9;
@@ -1762,7 +1787,7 @@ export declare const SRC_ROLLOFF: 0.92;
1762
1787
  export declare const SRC_MAX_L: 1024;
1763
1788
 
1764
1789
  /**
1765
- * The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. `harness/contract-limits-gen.mjs` emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).
1790
+ * The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. the engine's limits generator emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).
1766
1791
  * @see data/kernels/transient.json
1767
1792
  */
1768
1793
  export declare const TRANSIENT_LIMITS: {
@@ -1909,7 +1934,7 @@ export declare const TREMOLO_TRAVELS: {
1909
1934
  };
1910
1935
 
1911
1936
  /**
1912
- * The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (openmixer's TRIM_RANGE, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.
1937
+ * The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (the console's trim range, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.
1913
1938
  * @see data/kernels/trim.json
1914
1939
  */
1915
1940
  export declare const TRIM_RANGE: {
@@ -2075,7 +2100,7 @@ export declare const EQ_CONTINUOUS_TRAVELS: {
2075
2100
  export declare const READOUT_RATE_HZ: 50;
2076
2101
 
2077
2102
  /**
2078
- * The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` (`2026-07-15-clock-rate-honest-force-rate.md`) — this is the ceiling a write is refused by, never a menu.
2103
+ * The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` — this is the ceiling a write is refused by, never a menu.
2079
2104
  * @see data/rates.json
2080
2105
  */
2081
2106
  export declare const STANDARD_SAMPLE_RATES: readonly [44100, 48000, 88200, 96000, 176400, 192000];
@@ -265,7 +265,7 @@ export const DETECTOR_OVERSAMPLINGS = [
265
265
  ];
266
266
 
267
267
  /**
268
- * Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, openmixer's ssl-bus-compressor spec §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.
268
+ * Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, the SSL bus compressor design §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.
269
269
  * @see data/kernels/comp.json
270
270
  */
271
271
  export const DETECTOR_TOPOLOGY = [
@@ -274,7 +274,7 @@ export const DETECTOR_TOPOLOGY = [
274
274
  ];
275
275
 
276
276
  /**
277
- * The program-dependent release's model constants — the LA-2A's opto cell as DATA (`docs/design/specs/2026-09-26-compressor-models.md` §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.
277
+ * The program-dependent release's model constants — the LA-2A's opto cell as DATA (compressor models design §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.
278
278
  * @see data/kernels/comp.json
279
279
  */
280
280
  export const PROGRAM_RELEASE = {
@@ -397,13 +397,13 @@ export const DEESS_MODES = [
397
397
  ];
398
398
 
399
399
  /**
400
- * The knee, in dB — a CONSTANT of the stage and not a row field (`2026-09-22-native-deesser-stage.md` §4b): a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.
400
+ * The knee, in dB — a CONSTANT of the stage and not a row field: a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.
401
401
  * @see data/kernels/deesser.json
402
402
  */
403
403
  export const DEESS_KNEE_DB = 6;
404
404
 
405
405
  /**
406
- * The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by `output-delay-ceiling.test.ts`. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word "delay" is why they keep being confused for each other.
406
+ * The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by the engine's conformance test. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word "delay" is why they keep being confused for each other.
407
407
  * @see data/kernels/delay.json
408
408
  */
409
409
  export const FX_DELAY_TIME_RANGE = {
@@ -429,7 +429,7 @@ export const FX_DELAY_FEEDBACK_RANGE = {
429
429
  };
430
430
 
431
431
  /**
432
- * The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching `DEFAULT_DELAY_STATE.tone`. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.
432
+ * The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching the console's delay default tone. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.
433
433
  * @see data/kernels/delay.json
434
434
  */
435
435
  export const DELAY_TONE_RANGE = {
@@ -467,7 +467,7 @@ export const DELAY_PINGPONGS = [
467
467
  ];
468
468
 
469
469
  /**
470
- * The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the server's `DEFAULT_DELAY_STATE` (which now reads it) so the DPF plugin generator (`packages/omx-plugins/tools/params-gen.mjs`, `2026-09-25-omx-plugins-dpf.md` §3a) reads the same fact the desk seeds a fresh strip with.
470
+ * The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the console's delay default state (which now reads it) so the DPF plugin generator (omx-plugins, DPF plugin design §3a) reads the same fact the desk seeds a fresh strip with.
471
471
  * @see data/kernels/delay.json
472
472
  */
473
473
  export const FX_DELAY_PINGPONG_DEFAULT = false;
@@ -587,6 +587,16 @@ export const DRIVE_HF_ROLLOFFS = [
587
587
  16000
588
588
  ];
589
589
 
590
+ /**
591
+ * The drive's OVERSAMPLING factor: the shaper runs inside the console's one oversampler at 1x, 2x or 4x, and the id is the factor (`omx_drive.h`'s `omx_drive_factor_of` member set, `struct omx_drive.os_factor`). A field of the console's drive row (`DriveState.oversample`, the console's DRIVE_OVERSAMPLE_FACTORS; the set is named for its control), 4 at come-up there and in omx-dsp's drive face (OMX_DRIVE_INSTANCE_FACTOR, the oversampler's largest).
592
+ * @see data/kernels/drive.json
593
+ */
594
+ export const DRIVE_OVERSAMPLES = [
595
+ 1,
596
+ 2,
597
+ 4
598
+ ];
599
+
590
600
  /**
591
601
  * The bias at `character = +1`, in the SHAPER's own input units — `OMX_DRIVE_BIAS_MAX`. DERIVED, not chosen (§4b). In the saturating limit every knee tends to `sgn`, the output is a rectangular wave of duty `d = ½ + asin(b)/π`, and its `n`th harmonic goes as `sin(nπd)/n` — so `|H2|` is maximal at `d = ¾`, which is `b = sin(π/4) = 1/√2` exactly. Measured too: the H2-maximising bias for a full-scale sine at unity drive is 0.712 (soft), 0.658 (tape), 0.645 (tube), and this one value costs 0.000 / 0.035 / 0.088 dB of H2 against each curve's own optimum. ONE normalised bias for all three knees, not three.
592
602
  * @see data/kernels/drive.json
@@ -667,7 +677,7 @@ export const EQ_FREQ_RANGE = {
667
677
  };
668
678
 
669
679
  /**
670
- * The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (`core/src/eq.ts` derives `EQ_GAIN_LIMIT_DB` from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.
680
+ * The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (the engine derives its gain limit from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.
671
681
  * @see data/kernels/eq.json
672
682
  */
673
683
  export const EQ_GAIN_RANGE = {
@@ -709,7 +719,7 @@ export const EQ_BAND_ONS = [
709
719
  ];
710
720
 
711
721
  /**
712
- * The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (`output-delay-ceiling.test.ts` reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.
722
+ * The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (the engine's conformance test reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.
713
723
  * @see data/kernels/eq.json
714
724
  */
715
725
  export const EQ_MAX_BANDS = 24;
@@ -1003,7 +1013,7 @@ export const DEFAULT_THRESHOLDS = {
1003
1013
  };
1004
1014
 
1005
1015
  /**
1006
- * THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (2026-09-08-room-analysis.md §4 — "never more than the FBS notch depth law"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.
1016
+ * THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (the room-analysis design §4 — "never more than the FBS notch depth law"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.
1007
1017
  * @see data/kernels/fbs.json
1008
1018
  */
1009
1019
  export const FBS_MAX_NOTCH_DEPTH_DB = 18;
@@ -1172,6 +1182,25 @@ export const GATE_LIMITS = {
1172
1182
  }
1173
1183
  };
1174
1184
 
1185
+ /**
1186
+ * The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared.
1187
+ * @see data/kernels/gate.json
1188
+ */
1189
+ export const GATE_KEY_LIMITS = {
1190
+ "hpHz": {
1191
+ "min": 0,
1192
+ "max": 20000,
1193
+ "step": 1,
1194
+ "unit": "Hz"
1195
+ },
1196
+ "lpHz": {
1197
+ "min": 0,
1198
+ "max": 20000,
1199
+ "step": 1,
1200
+ "unit": "Hz"
1201
+ }
1202
+ };
1203
+
1175
1204
  /**
1176
1205
  * The generator kinds, in surface order: its position is the integer omx-dsp's omx_gen_fill takes as its kind (OMX_GEN_OSC, OMX_GEN_WHITE, OMX_GEN_PINK).
1177
1206
  * @see data/kernels/generator.json
@@ -1244,7 +1273,7 @@ export const HRP_AMOUNT_RANGE = {
1244
1273
  export const HRP_MAX_CUT_DB = 6;
1245
1274
 
1246
1275
  /**
1247
- * How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. `core/src/output-delay-ceiling.test.ts` reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.
1276
+ * How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. the engine's conformance test reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.
1248
1277
  * @see data/kernels/hrp.json
1249
1278
  */
1250
1279
  export const HRP_MAX_VOICES = 8;
@@ -1280,13 +1309,13 @@ export const HRP_COLLISION_CENTS = 100;
1280
1309
  export const HRP_ATTRIBUTION_CENTS = 60;
1281
1310
 
1282
1311
  /**
1283
- * How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer "will this fit?" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. `eq-budget-fits.test.ts` asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.
1312
+ * How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer "will this fit?" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. the engine's conformance test asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.
1284
1313
  * @see data/kernels/hrp.json
1285
1314
  */
1286
1315
  export const HRP_DEFAULT_MAX_AUTO_BANDS = 8;
1287
1316
 
1288
1317
  /**
1289
- * The precision limiter's travels (`2026-09-27-precision-limiter.md` §2), rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by `harness/contract-limits-gen.mjs`, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth.
1318
+ * The precision limiter's travels, rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by the engine's limits generator, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth.
1290
1319
  * @see data/kernels/limiter.json
1291
1320
  */
1292
1321
  export const LIMITER_LIMITS = {
@@ -1317,7 +1346,7 @@ export const LIMITER_LIMITS = {
1317
1346
  };
1318
1347
 
1319
1348
  /**
1320
- * The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. `native-input-ceiling.test.ts` holds this to the C header; spec `docs/design/specs/2026-09-14-console-allocation.md` §9 carries the ruling and the measured cost of width.
1349
+ * The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. the engine's conformance test holds this to the C header; the console allocation design §9 carries the ruling and the measured cost of width.
1321
1350
  * @see data/kernels/mixmatrix.json
1322
1351
  */
1323
1352
  export const BUS_INPUT_CAP = 128;
@@ -1499,7 +1528,7 @@ export const PITCH_KERNEL = {
1499
1528
  };
1500
1529
 
1501
1530
  /**
1502
- * PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it (`2026-09-26-native-fx-catalogue.md` §2, Reverb).
1531
+ * PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it.
1503
1532
  * @see data/kernels/reverb.json
1504
1533
  */
1505
1534
  export const REVERB_PLATE_MOD_DEPTH_RANGE = {
@@ -1606,7 +1635,7 @@ export const REVERB_HIGHCUT_RANGE = {
1606
1635
  };
1607
1636
 
1608
1637
  /**
1609
- * REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds (`2026-07-15-native-delay-reverb.md`, Amendment 2026-09-17 §B). A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.
1638
+ * REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds. A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.
1610
1639
  * @see data/kernels/reverb.json
1611
1640
  */
1612
1641
  export const REVERB_REVERSE_RANGE = {
@@ -1912,7 +1941,7 @@ export const ROTARY_STOP_EPS = 0.0001;
1912
1941
  export const ROTARY_RING_FLOATS = 256;
1913
1942
 
1914
1943
  /**
1915
- * The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; `rta-window-derivation.test.ts` reads it back from the header.
1944
+ * The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; the engine's conformance test reads it back from the header.
1916
1945
  * @see data/kernels/rta.json
1917
1946
  */
1918
1947
  export const RTA_TARGET_BIN_HZ = 5.9;
@@ -1948,7 +1977,7 @@ export const SRC_ROLLOFF = 0.92;
1948
1977
  export const SRC_MAX_L = 1024;
1949
1978
 
1950
1979
  /**
1951
- * The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. `harness/contract-limits-gen.mjs` emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).
1980
+ * The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. the engine's limits generator emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).
1952
1981
  * @see data/kernels/transient.json
1953
1982
  */
1954
1983
  export const TRANSIENT_LIMITS = {
@@ -2098,7 +2127,7 @@ export const TREMOLO_TRAVELS = {
2098
2127
  };
2099
2128
 
2100
2129
  /**
2101
- * The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (openmixer's TRIM_RANGE, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.
2130
+ * The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (the console's trim range, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.
2102
2131
  * @see data/kernels/trim.json
2103
2132
  */
2104
2133
  export const TRIM_RANGE = {
@@ -2268,7 +2297,7 @@ export const EQ_CONTINUOUS_TRAVELS = {
2268
2297
  export const READOUT_RATE_HZ = 50;
2269
2298
 
2270
2299
  /**
2271
- * The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` (`2026-07-15-clock-rate-honest-force-rate.md`) — this is the ceiling a write is refused by, never a menu.
2300
+ * The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` — this is the ceiling a write is refused by, never a menu.
2272
2301
  * @see data/rates.json
2273
2302
  */
2274
2303
  export const STANDARD_SAMPLE_RATES = [
@@ -64,6 +64,11 @@
64
64
  #define OMX_GATE_RATIO_MAX 100.0f
65
65
  #define OMX_GATE_RATIO_DEFAULT 16.0f
66
66
 
67
+ #define OMX_GATE_KEY_HP_HZ_MIN 0.0f
68
+ #define OMX_GATE_KEY_HP_HZ_MAX 20000.0f
69
+ #define OMX_GATE_KEY_LP_HZ_MIN 0.0f
70
+ #define OMX_GATE_KEY_LP_HZ_MAX 20000.0f
71
+
67
72
  #define OMX_LIMITER_CEILING_DB_MIN -12.0f
68
73
  #define OMX_LIMITER_CEILING_DB_MAX 0.0f
69
74
  #define OMX_LIMITER_CEILING_DB_DEFAULT -1.0f
@@ -691,6 +696,13 @@ enum omx_drive_hf_rolloffs {
691
696
  };
692
697
  #define OMX_DRIVE_HF_ROLLOFFS_COUNT 3u
693
698
  #define OMX_DRIVE_HF_ROLLOFFS_DEFAULT OMX_DRIVE_HF_ROLLOFFS_0
699
+ enum omx_drive_oversamples {
700
+ OMX_DRIVE_OVERSAMPLES_1 = 1,
701
+ OMX_DRIVE_OVERSAMPLES_2 = 2,
702
+ OMX_DRIVE_OVERSAMPLES_4 = 4,
703
+ };
704
+ #define OMX_DRIVE_OVERSAMPLES_COUNT 3u
705
+ #define OMX_DRIVE_OVERSAMPLES_DEFAULT OMX_DRIVE_OVERSAMPLES_4
694
706
  enum omx_drive_stereo_links {
695
707
  OMX_DRIVE_STEREO_LINKS_OFF = 0,
696
708
  OMX_DRIVE_STEREO_LINKS_ON = 1,
package/lib/semver.mjs CHANGED
@@ -20,7 +20,7 @@
20
20
  const RANK = { none: 0, patch: 1, minor: 2, major: 3 };
21
21
 
22
22
  /**
23
- * The development-phase rule (openmixer spec 2026-10-05-omx-contract §3.6): while every consumer of
23
+ * The development-phase rule (omx-contract spec §3.6): while every consumer of
24
24
  * omx-contract is a FreeMixer repository named here, removing an item is MINOR. The rule ends, and
25
25
  * this becomes null so a removal is MAJOR again, in the release that names the first consumer
26
26
  * outside the list.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmixer/omx-contract",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "The FreeMixer declarations as data: a JSON Schema, the data, and one CLI that renders them into C, TypeScript and JSON",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -1,11 +1,10 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
2
  // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
- // Moved unchanged from openmixer harness/contract-limits-render.mjs at 6c6e709f4429 (omx-contract spec
4
- // §3.4): a frozen export of openmixer's whole sheet renders through it byte for byte (test/round-trip.test.mjs).
3
+ // Moved unchanged from the console engine's contract-limits renderer at 6c6e709f4429 (omx-contract spec
4
+ // §3.4): a frozen export of the engine's whole sheet renders through it byte for byte (test/round-trip.test.mjs).
5
5
  /**
6
6
  * The RENDER half of `contract-limits-gen.mjs`: the header text as a pure function of the
7
- * declaration sheet it is handed. Split out so the declaration-perturbation ratchet (R-094,
8
- * `packages/server/src/declaration-perturbation-conformance.test.ts`) can render the header over
7
+ * declaration sheet it is handed. Split out so the engine's declaration-perturbation test can render the header over
9
8
  * a PERTURBED sheet and see whether every define follows it — the generator itself imports the
10
9
  * built modules and calls this, so there is one renderer and no second copy.
11
10
  */
@@ -41,7 +40,10 @@ function genLimitBlock(prefix, table) {
41
40
  const c = fieldConstName(field);
42
41
  lines.push(`#define OMX_${prefix}_${c}_MIN ${numLiteral(limit.min)}f`);
43
42
  lines.push(`#define OMX_${prefix}_${c}_MAX ${numLiteral(limit.max)}f`);
44
- lines.push(`#define OMX_${prefix}_${c}_DEFAULT ${numLiteral(limit.default)}f`);
43
+ // a field with no come-up value (the gate key filter's edges) has no DEFAULT, as in genCoreLimitBlock
44
+ if (limit.default !== undefined) {
45
+ lines.push(`#define OMX_${prefix}_${c}_DEFAULT ${numLiteral(limit.default)}f`);
46
+ }
45
47
  }
46
48
  return lines.join('\n');
47
49
  }
@@ -217,7 +219,7 @@ function genDeclaredListsBlock(DECLARED_LISTS, definedElsewhere) {
217
219
  * over `<PREFIX>_LIMITS`, `declaredScalars`, `declaredSets`, `declaredLists`), then `extra` — the
218
220
  * names that are not in the core barrel (`OMX_CLAP_HOST_EXTENSIONS`) or a test's override of one
219
221
  * door. The generator and every render test build the sheet here, so a new door is added once,
220
- * never appended to a hand list per caller (2026-09-15-no-stored-derivations.md §9).
222
+ * never appended to a hand list per caller.
221
223
  */
222
224
  export function contractLimitsSheet(coreNs, declarations, extra = {}) {
223
225
  return {
package/render/doors.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
2
  // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
3
  /**
4
- * The resolved data cut into the C renderer's doors, as openmixer's discovery cut its barrels
5
- * (packages/core/src/native-limit-tables.ts, declared-scalars.ts, declared-lists.ts): a `travels`
4
+ * The resolved data cut into the C renderer's doors, as the engine's discovery cut its barrels
5
+ * (its native limit tables, declared scalars and declared lists): a `travels`
6
6
  * table named <PREFIX>_LIMITS is a native table; every scalar, sheet and single travel is scalars
7
7
  * (its finite-number leaves, a travel's `unit`/`defaultFrom` labels skipped); a list is a declared
8
8
  * list unless its item says `"c": { "render": false }`; a set is an enum; a `c.alias` is the core-limit spelling.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmixer/omx-contract",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "items": {
5
5
  "PAN_RANGE": {
6
6
  "rel": "data/kernels/balance.json",
@@ -302,7 +302,7 @@
302
302
  "DETECTOR_TOPOLOGY": {
303
303
  "rel": "data/kernels/comp.json",
304
304
  "kind": "set",
305
- "doc": "Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, openmixer's ssl-bus-compressor spec §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.",
305
+ "doc": "Where the compressor's detector listens: `feedforward` hears the slot's input (or its key), `feedback` hears the slot's own pre-make-up output DYN_FB_LOOP_DELAY_SAMPLES late, through one envelope stage, and a keyed slot stays feed-forward (omx-dsp's OMX_DYN_TOPOLOGY_FEEDFORWARD 0 and _FEEDBACK 1, in that order; the set is named for its control, so C spells them OMX_DETECTOR_TOPOLOGY_*). The console's comp row carries it (`detectorTopology`, the SSL bus compressor design §3a) and comes up `feedforward`, so a zeroed atom is the compressor every slot runs.",
306
306
  "default": "feedforward",
307
307
  "value": [
308
308
  "feedforward",
@@ -312,7 +312,7 @@
312
312
  "PROGRAM_RELEASE": {
313
313
  "rel": "data/kernels/comp.json",
314
314
  "kind": "sheet",
315
- "doc": "The program-dependent release's model constants — the LA-2A's opto cell as DATA (`docs/design/specs/2026-09-26-compressor-models.md` §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.",
315
+ "doc": "The program-dependent release's model constants — the LA-2A's opto cell as DATA (compressor models design §3b). `chargeMs`/`dischargeMs` are the word's memory constants, shared by every profile; each profile's `fastMs`, `slowMinMs`, `slowMaxMs` and `share` fit the unit's published two-stage recovery. Not operator controls: generated into `omx_contract_limits.h` as `OMX_PROGRAM_RELEASE_*`, which libomxdsp's `omx_env_program_release` reads.",
316
316
  "value": {
317
317
  "chargeMs": 1000,
318
318
  "dischargeMs": 5000,
@@ -443,14 +443,14 @@
443
443
  "DEESS_KNEE_DB": {
444
444
  "rel": "data/kernels/deesser.json",
445
445
  "kind": "scalar",
446
- "doc": "The knee, in dB — a CONSTANT of the stage and not a row field (`2026-09-22-native-deesser-stage.md` §4b): a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.",
446
+ "doc": "The knee, in dB — a CONSTANT of the stage and not a row field: a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.",
447
447
  "unit": "dB",
448
448
  "value": 6
449
449
  },
450
450
  "FX_DELAY_TIME_RANGE": {
451
451
  "rel": "data/kernels/delay.json",
452
452
  "kind": "travels",
453
- "doc": "The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by `output-delay-ceiling.test.ts`. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word \"delay\" is why they keep being confused for each other.",
453
+ "doc": "The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by the engine's conformance test. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word \"delay\" is why they keep being confused for each other.",
454
454
  "c": {
455
455
  "alias": {
456
456
  "fact": "delay",
@@ -484,7 +484,7 @@
484
484
  "DELAY_TONE_RANGE": {
485
485
  "rel": "data/kernels/delay.json",
486
486
  "kind": "travels",
487
- "doc": "The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching `DEFAULT_DELAY_STATE.tone`. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.",
487
+ "doc": "The FX delay's TONE — a NORMALIZED_RANGE instance with its own come-up value, matching the console's delay default tone. See REVERB_SIZE_RANGE for why this is a named constant rather than a default on the shared shape.",
488
488
  "shape": "travel",
489
489
  "value": {
490
490
  "min": 0,
@@ -529,7 +529,7 @@
529
529
  "FX_DELAY_PINGPONG_DEFAULT": {
530
530
  "rel": "data/kernels/delay.json",
531
531
  "kind": "scalar",
532
- "doc": "The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the server's `DEFAULT_DELAY_STATE` (which now reads it) so the DPF plugin generator (`packages/omx-plugins/tools/params-gen.mjs`, `2026-09-25-omx-plugins-dpf.md` §3a) reads the same fact the desk seeds a fresh strip with.",
532
+ "doc": "The FX delay's PING-PONG come-up value — a toggle, so it has no travel, only this. Declared here rather than as a literal in the console's delay default state (which now reads it) so the DPF plugin generator (omx-plugins, DPF plugin design §3a) reads the same fact the desk seeds a fresh strip with.",
533
533
  "unit": "",
534
534
  "value": false
535
535
  },
@@ -699,6 +699,22 @@
699
699
  16000
700
700
  ]
701
701
  },
702
+ "DRIVE_OVERSAMPLES": {
703
+ "rel": "data/kernels/drive.json",
704
+ "kind": "set",
705
+ "doc": "The drive's OVERSAMPLING factor: the shaper runs inside the console's one oversampler at 1x, 2x or 4x, and the id is the factor (`omx_drive.h`'s `omx_drive_factor_of` member set, `struct omx_drive.os_factor`). A field of the console's drive row (`DriveState.oversample`, the console's DRIVE_OVERSAMPLE_FACTORS; the set is named for its control), 4 at come-up there and in omx-dsp's drive face (OMX_DRIVE_INSTANCE_FACTOR, the oversampler's largest).",
706
+ "default": 4,
707
+ "labels": [
708
+ "1x",
709
+ "2x",
710
+ "4x"
711
+ ],
712
+ "value": [
713
+ 1,
714
+ 2,
715
+ 4
716
+ ]
717
+ },
702
718
  "DRIVE_BIAS_MAX": {
703
719
  "rel": "data/kernels/drive.json",
704
720
  "kind": "scalar",
@@ -805,7 +821,7 @@
805
821
  "EQ_GAIN_RANGE": {
806
822
  "rel": "data/kernels/eq.json",
807
823
  "kind": "travels",
808
- "doc": "The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (`core/src/eq.ts` derives `EQ_GAIN_LIMIT_DB` from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.",
824
+ "doc": "The parametric-band gain travel, in dB. The ± scalar `clampEqGain` enforces is READ off this (the engine derives its gain limit from `max`), so the bound is stated once and a surface cannot draw a wider knob than the clamp will honour.",
809
825
  "shape": "travel",
810
826
  "value": {
811
827
  "min": -15,
@@ -854,7 +870,7 @@
854
870
  "EQ_MAX_BANDS": {
855
871
  "rel": "data/kernels/eq.json",
856
872
  "kind": "scalar",
857
- "doc": "The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (`output-delay-ceiling.test.ts` reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.",
873
+ "doc": "The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (the engine's conformance test reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.",
858
874
  "unit": "",
859
875
  "value": 24
860
876
  },
@@ -1170,7 +1186,7 @@
1170
1186
  "FBS_MAX_NOTCH_DEPTH_DB": {
1171
1187
  "rel": "data/kernels/fbs.json",
1172
1188
  "kind": "scalar",
1173
- "doc": "THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (2026-09-08-room-analysis.md §4 — \"never more than the FBS notch depth law\"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.",
1189
+ "doc": "THE NOTCH DEPTH LAW: the deepest cut any machine planter may propose from one reading, in dB. A number, not a literal at the call site, because two planters now lean on it. FBS derives a candidate's depth from the peak-to-average ratio around its bin and stops here; Room Analysis derives a mode's depth from its height above the median curve and is held to the SAME ceiling (the room-analysis design §4 — \"never more than the FBS notch depth law\"). Written twice as `18` those two bounds would be equal by coincidence and could drift apart in silence. It is a CEILING ON THE PROPOSAL, not the final gain: a band that lands as a rampable BELL is bounded again by `EQ_GAIN_RANGE`, which is tighter. A planter must honour both and REPORT when either bit, never quietly deliver a shallower cut than it was asked for.",
1174
1190
  "unit": "dB",
1175
1191
  "value": 18
1176
1192
  },
@@ -1347,6 +1363,26 @@
1347
1363
  }
1348
1364
  }
1349
1365
  },
1366
+ "GATE_KEY_LIMITS": {
1367
+ "rel": "data/kernels/gate.json",
1368
+ "kind": "travels",
1369
+ "doc": "The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared.",
1370
+ "shape": "table",
1371
+ "value": {
1372
+ "hpHz": {
1373
+ "min": 0,
1374
+ "max": 20000,
1375
+ "step": 1,
1376
+ "unit": "Hz"
1377
+ },
1378
+ "lpHz": {
1379
+ "min": 0,
1380
+ "max": 20000,
1381
+ "step": 1,
1382
+ "unit": "Hz"
1383
+ }
1384
+ }
1385
+ },
1350
1386
  "GENERATOR_KINDS": {
1351
1387
  "rel": "data/kernels/generator.json",
1352
1388
  "kind": "set",
@@ -1427,7 +1463,7 @@
1427
1463
  "HRP_MAX_VOICES": {
1428
1464
  "rel": "data/kernels/hrp.json",
1429
1465
  "kind": "scalar",
1430
- "doc": "How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. `core/src/output-delay-ceiling.test.ts` reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.",
1466
+ "doc": "How many simultaneous musical voices the detector may report on one channel. PINNED to `OMX_HRP_MAX_VOICES` in `pipewire-native/src/hrp_pitch.h` — the C voice set is a FIXED-SIZE array, so a TS number above the macro promises voices the analyser structurally cannot return, and one below it silently drops notes the C already found. the engine's conformance test reads the header and holds the pair equal. EIGHT (operator sizing, 2026-09-01): a guitar sounds six strings, a piano or string-section voicing sits at eight. It is a SEARCH ceiling, not a cost — the detector abandons a round as soon as no credible candidate remains, so monophonic material costs what it always did.",
1431
1467
  "unit": "",
1432
1468
  "value": 8
1433
1469
  },
@@ -1469,14 +1505,14 @@
1469
1505
  "HRP_DEFAULT_MAX_AUTO_BANDS": {
1470
1506
  "rel": "data/kernels/hrp.json",
1471
1507
  "kind": "scalar",
1472
- "doc": "How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer \"will this fit?\" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. `eq-budget-fits.test.ts` asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.",
1508
+ "doc": "How many bands HRP may plant on ONE channel — its half of the machine budget, and the number that makes the budget FORECASTABLE rather than emergent. Without it the count is whatever the material happens to provoke: the placement law is sparse, so a clean note plants two bands and a resonant room plants nine, and nobody can answer \"will this fit?\" until it does not. With FBS senior (§9.3(b)) that discovery would happen at showtime, to the junior planter, silently. Declared instead, the whole sum is arithmetic an operator can read before a note is played: operator bands + FBS maxAutoBands + HRP maxAutoBands <= EQ_MAX_BANDS EIGHT — ONE CORRECTION PER VOICE at full polyphony (operator sizing, 2026-09-01). The detector's ceiling is HRP_MAX_VOICES voices, a guitar's six strings and a piano/string voicing's eight, and a budget below the voice count would decide by arithmetic which of the notes actually sounding may be corrected — a limit nobody could read off the desk. Scarcity is handled where it belongs instead: the candidates from every voice meet in ONE globally ranked list (spec §29) and the head of it is what gets planted. It is NOT a claim to seniority, and it is larger than FBS's six. Seniority is the ALLOCATION ORDER — `HrpPlanter` takes only `EQ_MAX_BANDS − others.length`, the remainder after the operator and every other machine — so a bigger junior default cannot reach a senior slot; it only means HRP asks for more of what is left. the engine's conformance test asserts both halves. The per-channel DEFAULT; the row's travel is HRP_AUTO_BANDS_RANGE, so a channel that needs more can have more, as an explicit decision with a visible cost.",
1473
1509
  "unit": "",
1474
1510
  "value": 8
1475
1511
  },
1476
1512
  "LIMITER_LIMITS": {
1477
1513
  "rel": "data/kernels/limiter.json",
1478
1514
  "kind": "travels",
1479
- "doc": "The precision limiter's travels (`2026-09-27-precision-limiter.md` §2), rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by `harness/contract-limits-gen.mjs`, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth.",
1515
+ "doc": "The precision limiter's travels, rendered into `omx_contract_limits.h` as `OMX_LIMITER_*` by the engine's limits generator, so `mix_limiter.h` reads them and never restates one (R-094). The ceiling is a TRUE peak in dBFS (the ×4 detector); the look-ahead sets the declared latency; the release is the envelope's time constant on the gain depth.",
1480
1516
  "shape": "table",
1481
1517
  "value": {
1482
1518
  "ceilingDb": {
@@ -1508,7 +1544,7 @@
1508
1544
  "BUS_INPUT_CAP": {
1509
1545
  "rel": "data/kernels/mixmatrix.json",
1510
1546
  "kind": "scalar",
1511
- "doc": "The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. `native-input-ceiling.test.ts` holds this to the C header; spec `docs/design/specs/2026-09-14-console-allocation.md` §9 carries the ruling and the measured cost of width.",
1547
+ "doc": "The input cap a summing bus can be sized to — **the engine's declared input ceiling**, read from `@freemixer/declarations` rather than re-typed here. A gig with more than this many inputs needs more than one engine node; below it a bus is sized to `min(N, BUS_INPUT_CAP)`. It was a literal 64 until 2026-09-15, said to mirror the engine's `MAX_BUS_INPUTS` — and it did, but that 64 was the PipeWire builtin mixer's single-tier fan-in (8 × 8), never the engine's bound: the native node has allocated `OMX_MAX_STRIPS` (128) strips all along. Two literals agreeing by hand is how the console came to refuse a 96-channel desk it could carry. the engine's conformance test holds this to the C header; the console allocation design §9 carries the ruling and the measured cost of width.",
1512
1548
  "unit": "",
1513
1549
  "value": 128
1514
1550
  },
@@ -1701,7 +1737,7 @@
1701
1737
  "REVERB_PLATE_MOD_DEPTH_RANGE": {
1702
1738
  "rel": "data/kernels/reverb.json",
1703
1739
  "kind": "travels",
1704
- "doc": "PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it (`2026-09-26-native-fx-catalogue.md` §2, Reverb).",
1740
+ "doc": "PLATE's tank MODULATION DEPTH, in percent of Dattorro's own excursion (±8 samples at his 29 761 Hz tuning rate, scaled to the live rate the way the tank lengths are). 100 is the depth the plate has always run at, so the come-up is bit-identical to the plate before the field; 0 brings back the unmodulated tank's ring; 400 is the wide, chorused EMT-style swing. The rate stays 1 Hz. Only PLATE reads it.",
1705
1741
  "shape": "travel",
1706
1742
  "value": {
1707
1743
  "min": 0,
@@ -1816,7 +1852,7 @@
1816
1852
  "REVERB_REVERSE_RANGE": {
1817
1853
  "rel": "data/kernels/reverb.json",
1818
1854
  "kind": "travels",
1819
- "doc": "REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds (`2026-07-15-native-delay-reverb.md`, Amendment 2026-09-17 §B). A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.",
1855
+ "doc": "REVERSE's MIRROR WINDOW — how long a slice of the wet is played backward, in milliseconds. A DECLARED travel and never derived from `size`'s RT60: an RT60 of seconds cannot be buffered at 192 kHz, and a window silently clamped to fit is the silent-clamp defect. The ceiling is what `mix_reverb.h`'s pool budgets a ring for (`OMX_REVERB_REVERSE_MAX_MS`), so the console offers exactly the travel the kernel accepts — nothing wider, nothing narrower.",
1820
1856
  "shape": "travel",
1821
1857
  "value": {
1822
1858
  "min": 50,
@@ -2147,7 +2183,7 @@
2147
2183
  "RTA_TARGET_BIN_HZ": {
2148
2184
  "rel": "data/kernels/rta.json",
2149
2185
  "kind": "scalar",
2150
- "doc": "The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; `rta-window-derivation.test.ts` reads it back from the header.",
2186
+ "doc": "The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; the engine's conformance test reads it back from the header.",
2151
2187
  "unit": "Hz",
2152
2188
  "value": 5.9
2153
2189
  },
@@ -2189,7 +2225,7 @@
2189
2225
  "TRANSIENT_LIMITS": {
2190
2226
  "rel": "data/kernels/transient.json",
2191
2227
  "kind": "travels",
2192
- "doc": "The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. `harness/contract-limits-gen.mjs` emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).",
2228
+ "doc": "The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. the engine's limits generator emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).",
2193
2229
  "shape": "table",
2194
2230
  "value": {
2195
2231
  "attackDb": {
@@ -2349,7 +2385,7 @@
2349
2385
  "TRIM_RANGE": {
2350
2386
  "rel": "data/kernels/trim.json",
2351
2387
  "kind": "travels",
2352
- "doc": "The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (openmixer's TRIM_RANGE, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.",
2388
+ "doc": "The digital input trim, in dB: the console's fine head gain, applied first in a channel's lane as a click-free ramp from the last block's gain to this one (the console's trim range, -24..+24 dB in 0.1 dB steps, spelled minDb/maxDb/stepDb there). It comes up at 0 dB, the trim a channel has when none is set. omx-dsp's trim face takes it as `trim_db`.",
2353
2389
  "shape": "travel",
2354
2390
  "value": {
2355
2391
  "min": -24,
@@ -2529,7 +2565,7 @@
2529
2565
  "STANDARD_SAMPLE_RATES": {
2530
2566
  "rel": "data/rates.json",
2531
2567
  "kind": "list",
2532
- "doc": "The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` (`2026-07-15-clock-rate-honest-force-rate.md`) — this is the ceiling a write is refused by, never a menu.",
2568
+ "doc": "The standard sample rates (Hz) the desk recognises — the pro-audio superset. One source of truth for the clock controller's `clock.allowed-rates` seed, the force row's validation ceiling and the device-rate probe fallback. What the operator is OFFERED is always the DEVICE's own `supportedRates` — this is the ceiling a write is refused by, never a menu.",
2533
2569
  "unit": "Hz",
2534
2570
  "value": [
2535
2571
  44100,
@@ -2866,6 +2902,11 @@
2866
2902
  "name": "hfRolloff",
2867
2903
  "kind": "choice",
2868
2904
  "global": "DRIVE_HF_ROLLOFFS"
2905
+ },
2906
+ {
2907
+ "name": "oversample",
2908
+ "kind": "choice",
2909
+ "global": "DRIVE_OVERSAMPLES"
2869
2910
  }
2870
2911
  ]
2871
2912
  },
@@ -3027,6 +3068,18 @@
3027
3068
  "kind": "travel",
3028
3069
  "global": "GATE_LIMITS",
3029
3070
  "table": "GATE_LIMITS"
3071
+ },
3072
+ {
3073
+ "name": "hpHz",
3074
+ "kind": "travel",
3075
+ "global": "GATE_KEY_LIMITS",
3076
+ "table": "GATE_KEY_LIMITS"
3077
+ },
3078
+ {
3079
+ "name": "lpHz",
3080
+ "kind": "travel",
3081
+ "global": "GATE_KEY_LIMITS",
3082
+ "table": "GATE_KEY_LIMITS"
3030
3083
  }
3031
3084
  ],
3032
3085
  "readouts": [