@openmixer/omx-contract 1.2.0 → 1.3.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
@@ -7,12 +7,17 @@ list), as JSON validated by a JSON Schema, rendered by one CLI into
7
7
  every language that reads it. Governed by openmixer's
8
8
  `docs/design/specs/2026-10-05-omx-contract.md`; 1.0.0 carries the phase-1 items of its §4.1, what
9
9
  omx-dsp needs, 1.1.0 adds the flanger, de-esser, phaser, rotary, tremolo and graphic EQ kernels, and
10
- 1.2.0 the chorus, drive, EQ, reverb and pan travels omx-dsp and omx-plugins still kept locally.
10
+ 1.2.0 the chorus, drive, EQ, reverb and pan travels omx-dsp and omx-plugins still kept locally, and
11
+ 1.3.0 everything else more than one repository reads: the feedback detector, ring correction and
12
+ RTA constants, the EQ band budget, the choice enums (as the `set` kind), the EQ band counts and the
13
+ one rule that gives a fresh EQ its bands, the measurement rate sets and the bus input cap. What is
14
+ declared here is declared nowhere else: the EQ band budget that 1.2.0 left in openmixer is here too.
11
15
 
12
16
  ```
13
17
  data/ the declaration, one file per group, edited by hand
14
18
  rates.json primitives.json kernels/<kernel>.json
15
- schema/omx-contract.schema.json JSON Schema 2020-12: five kinds, closed units, refs, closed derivations
19
+ schema/omx-contract.schema.json JSON Schema 2020-12: five kinds (scalar, travels, sheet, list, set), closed units, refs, closed derivations
20
+ lib/eq-defaults.mjs the one default rule for a fresh EQ's bands; the ts and c renders carry it
16
21
  bin/omx-contract.mjs the CLI
17
22
  render/ c.mjs (openmixer's renderer, moved unchanged, under c/), ts.mjs, json.mjs
18
23
  include/omxcontract/ the committed c render (what omx-contract-devel installs)
@@ -41,9 +46,9 @@ share/omx-contract` and `render --target ts --out test/golden/ts`; CI holds all
41
46
 
42
47
  ## Consuming it
43
48
 
44
- Pin one version (`omx-contract 1.2.0` in `.github/pins.txt`) and build against its renders, committing
49
+ Pin one version (`omx-contract 1.3.0` in `.github/pins.txt`) and build against its renders, committing
45
50
  none: the installed `omx-contract-devel` / `libomx-contract-dev` at exactly that version
46
- (`pkg-config --exact-version=1.2.0 omx-contract`), else the release's npm tarball, unpacked, and
51
+ (`pkg-config --exact-version=1.3.0 omx-contract`), else the release's npm tarball, unpacked, and
47
52
  `omx-contract render --target c --out build/omx-contract/include`. Include
48
53
  `<omxcontract/omx_contract_limits.h>`. A plugin's parameter header is rendered by omx-plugins from its
49
54
  declaration, by reference to the kernel travels here (since 1.1.0).
@@ -59,5 +59,27 @@
59
59
  "default": 35,
60
60
  "defaultFrom": "desk"
61
61
  }
62
+ },
63
+ "CHORUS_BASE_MS": {
64
+ "kind": "scalar",
65
+ "doc": "The chorus's base delay, ms: the voices swing around it by up to the depth.",
66
+ "unit": "ms",
67
+ "value": 10
68
+ },
69
+ "CHORUS_MAX_MS": {
70
+ "kind": "scalar",
71
+ "doc": "The longest delay a chorus voice reaches, rounded up to whole ms: the base delay plus the depth maximum. The delay line is sized from it.",
72
+ "unit": "ms",
73
+ "value": {
74
+ "derive": "ceilSum",
75
+ "of": [
76
+ {
77
+ "ref": "CHORUS_BASE_MS"
78
+ },
79
+ {
80
+ "ref": "CHORUS_DEPTH_RANGE.max"
81
+ }
82
+ ]
83
+ }
62
84
  }
63
85
  }
@@ -89,5 +89,13 @@
89
89
  "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.",
90
90
  "unit": "dB",
91
91
  "value": 6
92
+ },
93
+ "DEESS_MODES": {
94
+ "kind": "set",
95
+ "doc": "The de-esser's modes, in the order of the integer the kernel reads: split attenuates the detected band only, wideband the whole signal while the sibilance lasts.",
96
+ "ids": [
97
+ "split",
98
+ "wideband"
99
+ ]
92
100
  }
93
101
  }
@@ -98,5 +98,25 @@
98
98
  "field": "trimDb"
99
99
  }
100
100
  }
101
+ },
102
+ "DRIVE_CURVES": {
103
+ "kind": "set",
104
+ "doc": "The drive's shaper curves, in the order of the integer the kernel reads. Exciter runs the soft shape in a parallel-sum topology.",
105
+ "ids": [
106
+ "soft",
107
+ "tape",
108
+ "tube",
109
+ "exciter"
110
+ ]
111
+ },
112
+ "DRIVE_BANDS": {
113
+ "kind": "set",
114
+ "doc": "Which part of the spectrum the drive's curve is fed, in the order of the integer the kernel reads. The split is complementary, so mix 0 stays bit-identical to the dry signal.",
115
+ "ids": [
116
+ "full",
117
+ "low",
118
+ "high",
119
+ "tilt"
120
+ ]
101
121
  }
102
122
  }
@@ -42,10 +42,7 @@
42
42
  12500,
43
43
  16000,
44
44
  20000
45
- ],
46
- "c": {
47
- "render": false
48
- }
45
+ ]
49
46
  },
50
47
  "GEQ_BANDS": {
51
48
  "kind": "scalar",
@@ -115,5 +112,164 @@
115
112
  "step": 10,
116
113
  "unit": "Hz"
117
114
  }
115
+ },
116
+ "OPERATOR_EQ_BANDS_RESERVE": {
117
+ "kind": "scalar",
118
+ "doc": "The band slots kept for the operator's own hand: the reserve the feedback suppressor and the ring correction may never eat into. Nine is the working set a live EQ actually holds. The budget OPERATOR_EQ_BANDS_RESERVE + FBS_DEFAULT_MAX_AUTO_BANDS + HRP_DEFAULT_MAX_AUTO_BANDS (9 + 6 + 8 = 23) must fit EQ_MAX_BANDS, and `omx-contract validate` refuses data where it does not.",
119
+ "unit": "",
120
+ "value": 9
121
+ },
122
+ "EQ_BAND_TYPES": {
123
+ "kind": "set",
124
+ "doc": "The generic EQ band shapes, in the order a picker offers them and in the order of the integer a plugin host sees on a band type port. The id is the name; the position is the value. A bell is the shape a band added by the operator starts as.",
125
+ "ids": [
126
+ "bell",
127
+ "lowShelf",
128
+ "highShelf",
129
+ "notch",
130
+ "allpass1",
131
+ "allpass2"
132
+ ],
133
+ "default": "bell",
134
+ "labels": [
135
+ "Bell",
136
+ "Low Shelf",
137
+ "High Shelf",
138
+ "Notch",
139
+ "All-pass 1st",
140
+ "All-pass 2nd"
141
+ ]
142
+ },
143
+ "FILTER_SLOPES": {
144
+ "kind": "set",
145
+ "doc": "The pass-filter slopes the EQ offers, in dB per octave. The id is the slope: 12 is one maximally flat section, 24 the two-section Butterworth.",
146
+ "ids": [
147
+ 12,
148
+ 24
149
+ ],
150
+ "default": 12
151
+ },
152
+ "EQ_BAND_COUNTS": {
153
+ "kind": "sheet",
154
+ "doc": "How many bands each strip type's EQ starts with (`default`) and may hold (`max`). `channel` is the console's channel strip: it starts with four and the operator adds bands up to EQ_MAX_BANDS (a band added takes the frequency it is added at, so no default is declared for it). `strip` is the omx-strip plugin's EQ and `eq8`, `eq16` and `eq32` the omx-eq plugins, whose bands are fixed. A default may never exceed its maximum, and the default centres of any count come from the one default rule, whatever the strip.",
155
+ "value": {
156
+ "channel": {
157
+ "default": 4,
158
+ "max": {
159
+ "ref": "EQ_MAX_BANDS"
160
+ }
161
+ },
162
+ "strip": {
163
+ "default": 4,
164
+ "max": 4
165
+ },
166
+ "eq8": {
167
+ "default": 8,
168
+ "max": 8
169
+ },
170
+ "eq16": {
171
+ "default": 16,
172
+ "max": 16
173
+ },
174
+ "eq32": {
175
+ "default": 32,
176
+ "max": 32
177
+ }
178
+ }
179
+ },
180
+ "EQ_BAND_DEFAULTS": {
181
+ "kind": "sheet",
182
+ "doc": "What every band of a fresh EQ comes up at, whatever its type and its strip: flat (0 dB) and Q 1. The band frequencies and types come from the default rule.",
183
+ "value": {
184
+ "gainDb": 0,
185
+ "q": 1
186
+ }
187
+ },
188
+ "EQ_PASS_FILTER_DEFAULTS": {
189
+ "kind": "sheet",
190
+ "doc": "The corners the high-pass and low-pass filters of a fresh EQ are parked at, whatever the strip. Both come up off.",
191
+ "value": {
192
+ "hpfFreqHz": 80,
193
+ "lpfFreqHz": 18000
194
+ }
195
+ },
196
+ "EQ_DEFAULT_CENTRES_FOUR_BAND_HZ": {
197
+ "kind": "list",
198
+ "doc": "The default band centres of a four-band EQ, Hz: ISO 266 preferred values, two to two and a half octaves apart. The default rule gives every other band count its centres from the preferred series; four bands start here.",
199
+ "unit": "Hz",
200
+ "values": [
201
+ 100,
202
+ 400,
203
+ 2000,
204
+ 8000
205
+ ]
206
+ },
207
+ "EQ_CENTRE_SERIES_R20_HZ": {
208
+ "kind": "list",
209
+ "doc": "The ISO 266 R20 preferred frequencies from 20 Hz to 20 kHz (61 values, sixth-octave spacing): the finer series the default band rule falls back to where R10 (ISO_THIRD_OCTAVE_CENTRES_HZ, 31 values) has too few to give every band its own centre. R10 is a subset of it.",
210
+ "unit": "Hz",
211
+ "values": [
212
+ 20,
213
+ 22.4,
214
+ 25,
215
+ 28,
216
+ 31.5,
217
+ 35.5,
218
+ 40,
219
+ 45,
220
+ 50,
221
+ 56,
222
+ 63,
223
+ 71,
224
+ 80,
225
+ 90,
226
+ 100,
227
+ 112,
228
+ 125,
229
+ 140,
230
+ 160,
231
+ 180,
232
+ 200,
233
+ 224,
234
+ 250,
235
+ 280,
236
+ 315,
237
+ 355,
238
+ 400,
239
+ 450,
240
+ 500,
241
+ 560,
242
+ 630,
243
+ 710,
244
+ 800,
245
+ 900,
246
+ 1000,
247
+ 1120,
248
+ 1250,
249
+ 1400,
250
+ 1600,
251
+ 1800,
252
+ 2000,
253
+ 2240,
254
+ 2500,
255
+ 2800,
256
+ 3150,
257
+ 3550,
258
+ 4000,
259
+ 4500,
260
+ 5000,
261
+ 5600,
262
+ 6300,
263
+ 7100,
264
+ 8000,
265
+ 9000,
266
+ 10000,
267
+ 11200,
268
+ 12500,
269
+ 14000,
270
+ 16000,
271
+ 18000,
272
+ 20000
273
+ ]
118
274
  }
119
275
  }
@@ -0,0 +1,148 @@
1
+ {
2
+ "$schema": "../../schema/omx-contract.schema.json",
3
+ "SUBHARMONIC_RADIUS_BINS": {
4
+ "kind": "scalar",
5
+ "doc": "How far (bins) around bin/2 and bin/3 PSHR (`omx_fbs_pshr`) scans for a sub-harmonic line. A true half/third partial lands mid-bin in the worst case (bin/2 and bin/3 are rarely integers) and spectral leakage smears it one bin further; ±2 covers both. Measured on the #116 live-music corpus: every false positive's sub-line sat inside this window. Deliberately NOT wider — at low bins the window is already a large fraction of an octave, and widening it would start reading unrelated program lines as \"sub-harmonics\" of real feedback.",
6
+ "unit": "",
7
+ "value": 2
8
+ },
9
+ "FBS_PAPR_GUARD_BINS": {
10
+ "kind": "scalar",
11
+ "doc": "PAPR's guard band: bins either side of the peak left out of the floor average (its own skirt).",
12
+ "unit": "",
13
+ "value": 2
14
+ },
15
+ "FBS_PNPR_RADIUS_BINS": {
16
+ "kind": "scalar",
17
+ "doc": "PNPR's neighbourhood radius in bins, and the guard inside it that belongs to the peak's own skirt: a pure tone is compared with bins 2 and 3 away on each side.",
18
+ "unit": "",
19
+ "value": 3
20
+ },
21
+ "FBS_PNPR_GUARD_BINS": {
22
+ "kind": "scalar",
23
+ "doc": "The guard inside PNPR's neighbourhood radius that belongs to the peak's own skirt, in bins.",
24
+ "unit": "",
25
+ "value": 1
26
+ },
27
+ "FBS_PHPR_HARMONICS": {
28
+ "kind": "scalar",
29
+ "doc": "How many harmonics PHPR reads above the candidate: 2f, 3f and 4f.",
30
+ "unit": "",
31
+ "value": 3
32
+ },
33
+ "FBS_PSHR_SELF_SKIP_BINS": {
34
+ "kind": "scalar",
35
+ "doc": "PSHR skips sub-window bins within this many of the candidate, so a low bin never measures its own skirt as a sub-harmonic.",
36
+ "unit": "",
37
+ "value": 3
38
+ },
39
+ "FBS_FLOOR_LIN": {
40
+ "kind": "scalar",
41
+ "doc": "The magnitude floor of every detector dB reading (−240 dB): a zero bin stays finite.",
42
+ "unit": "",
43
+ "value": 1e-12
44
+ },
45
+ "FBS_TRACK_SLOTS": {
46
+ "kind": "scalar",
47
+ "doc": "Tracked candidate bins per armed channel. A candidate that finds no free slot is COUNTED and reported, never dropped silently; the identity corpus peaks at 34.",
48
+ "unit": "",
49
+ "value": 512
50
+ },
51
+ "PSHR_MIN_DB": {
52
+ "kind": "scalar",
53
+ "doc": "Minimum PSHR (`omx_fbs_pshr`) (dB) for a candidate frame to count as sub-harmonic-CLEAN. Evidence-set on the #116 corpus, margin on both sides: the four live false positives (musical overtones at 1 869.1 / 832.0 / 263.7 / 228.5 Hz) read PSHR between −10.2 and +8.8 dB across their windows — never a 5-frame sub-clean stretch, and the single closest approach (+8.8) is isolated between frames at ≈ −1 and −10 — while the committed #106 injected ramp reads ≥ +12.2 dB from its second frame on (its frame 0 sits at +9.4, costing exactly one frame of confirm latency, pinned in the corpus test). A static program line at f/2 of REAL feedback also clears this fast: the runaway gains several dB per hop on anything static, so the deferral is transient.",
54
+ "unit": "dB",
55
+ "value": 10
56
+ },
57
+ "GROWTH_WINDOW_FRAMES": {
58
+ "kind": "scalar",
59
+ "doc": "How many of a candidate bin's most recent dB readings feed the growth-slope estimate (the engine's least-squares slope) — set equal to DEFAULT_THRESHOLDS's `persistFrames` so growth is judged over the SAME window persistence already demands (the full run since the bin became a candidate, not a shorter or longer slice of it). Long enough that a couple of noisy/vibrato frames can't fake a trend by themselves.",
60
+ "unit": "",
61
+ "value": 5
62
+ },
63
+ "DEEP_PLATEAU_FRAMES": {
64
+ "kind": "scalar",
65
+ "doc": "The DEEP plateau tier's dwell: ~2 s at the watcher's 10 Hz — the TIME that buys the deeper floor its music safety (see DetectThresholds.deepPlateauFloorDb).",
66
+ "unit": "",
67
+ "value": 20
68
+ },
69
+ "GROWTH_MIN_DB_PER_FRAME": {
70
+ "kind": "scalar",
71
+ "doc": "Minimum least-squares slope, in dB PER FRAME, over GROWTH_WINDOW_FRAMES for a bin to count as \"running away\" rather than merely holding level. Set with real margin on BOTH sides — comfortably above measurement jitter on a held tone or a quiet/broadband mix (well under 0.2 dB/frame in practice) and comfortably below a genuine feedback ramp, which climbs several dB across a handful of ~20 ms hops as it heads toward clipping. Issue #91's acceptance bar is \"arming must never burst-alarm on non-growing content\", so this errs conservative (under-fire a marginal ramp) rather than tight (risk a false alarm) — the ramp gate (`omx_fbs_is_growing`) also requires the window's end to sit above its start, a cheap belt-and-braces check against a fit dragged positive by one mid-window wobble.",
72
+ "unit": "dB",
73
+ "value": 0.8
74
+ },
75
+ "GROWTH_MAX_DIP_DB": {
76
+ "kind": "scalar",
77
+ "doc": "Largest single-frame DIP (dB) a run-away window may contain. Real feedback climbs every hop; a small allowance covers measurement jitter and the −0.3 dB-ish wobble frames seen on live captures, while any real drop (a note ending, tremolo) breaks the run.",
78
+ "unit": "dB",
79
+ "value": 0.5
80
+ },
81
+ "GROWTH_MIN_TOTAL_RISE_DB": {
82
+ "kind": "scalar",
83
+ "doc": "Minimum END-TO-END rise (dB) across the window. The live-music corpus (issue #106) showed sustained melody notes drifting up ~4–5 dB over a window without ever being feedback; a loop past unity gains several dB per hop and clears 6 dB in a window trivially. Below this the tone is holding level, not running away.",
84
+ "unit": "dB",
85
+ "value": 6
86
+ },
87
+ "GROWTH_MAX_SINGLE_FRAME_SHARE": {
88
+ "kind": "scalar",
89
+ "doc": "Cap on the share of the window's total rise a SINGLE frame may carry. Feedback is exponential in amplitude ⇒ linear in dB ⇒ its rise is distributed roughly evenly across the window; a musical attack / re-articulation puts almost the whole rise in one hop (the exact shape behind every one of issue #106's 72 live false positives). One half is generous to real ramps (an even N-frame ramp puts 1/(N−1) per hop) and fatal to steps. A side effect worth knowing: a 2-sample window can never pass (one hop IS the whole rise), so confirmation always rests on at least three frames of actual ramp evidence.",
90
+ "unit": "",
91
+ "value": 0.5
92
+ },
93
+ "GROWTH_CONFIRM_FRAMES": {
94
+ "kind": "scalar",
95
+ "doc": "How many CONSECUTIVE frames the ramp gate (`omx_fbs_is_growing`) must hold before a bin confirms — the time-domain tiebreak for the one shape a single window cannot judge (issue #106): a smooth musical crescendo. Within one window a +6 dB swell (live corpus: a C6 at 1 049 Hz, `[-53.7, -51.4, -50.4, -48, -47.3]`) is indistinguishable from slow feedback — the difference is only visible on the NEXT hop, where a crescendo peaks and plateaus (the window's rise collapses) while feedback keeps climbing. Requiring the gate to hold twice in a row costs a clean ramp nothing — its gate is already true while persistence is still arming — and blocks the swell that peaks the very frame it would have confirmed.",
96
+ "unit": "",
97
+ "value": 2
98
+ },
99
+ "DEFAULT_THRESHOLDS": {
100
+ "kind": "sheet",
101
+ "doc": "The corpus-tuned SAFE DEFAULT — today's exact gates, assembled byte-for-byte from the exported consts above (which stay the authoritative literal source, so the corpus test's import type { MagnitudeSeries } from './spectrum-bands.js'; imports remain the single truth). This is the ANCHOR the whole sensitivity mapping pins to: thresholdsFor`(0, 'live')` and `(0, 'fixed')` both deep-equal this object, so an untouched channel runs the identical detector it does now. `plateauFloorDb` is omitted ⇒ the #140 plateau route is OFF at the default.",
102
+ "value": {
103
+ "paprDb": 18,
104
+ "pnprDb": 12,
105
+ "phprDb": 9,
106
+ "persistFrames": 5,
107
+ "growthConfirmFrames": {
108
+ "ref": "GROWTH_CONFIRM_FRAMES"
109
+ },
110
+ "subCleanFrames": {
111
+ "ref": "GROWTH_WINDOW_FRAMES"
112
+ },
113
+ "growthMinTotalRiseDb": {
114
+ "ref": "GROWTH_MIN_TOTAL_RISE_DB"
115
+ },
116
+ "growthMaxSingleFrameShare": {
117
+ "ref": "GROWTH_MAX_SINGLE_FRAME_SHARE"
118
+ },
119
+ "growthMinDbPerFrame": {
120
+ "ref": "GROWTH_MIN_DB_PER_FRAME"
121
+ },
122
+ "growthMaxDipDb": {
123
+ "ref": "GROWTH_MAX_DIP_DB"
124
+ },
125
+ "pshrMinDb": {
126
+ "ref": "PSHR_MIN_DB"
127
+ }
128
+ }
129
+ },
130
+ "FBS_MAX_NOTCH_DEPTH_DB": {
131
+ "kind": "scalar",
132
+ "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
+ "unit": "dB",
134
+ "value": 18
135
+ },
136
+ "FBS_DEFAULT_NOTCH_Q": {
137
+ "kind": "scalar",
138
+ "doc": "Default planted-notch width (Q) — the dbx-AFS-style ultra-narrow notch the corrector plants. `addBand`'s per-shape clamp holds it at 116 for a fixed notch and narrows it to 8 for a live bell, so this is the declared starting point rather than the width every notch ends up with.",
139
+ "unit": "",
140
+ "value": 116
141
+ },
142
+ "FBS_DEFAULT_MAX_AUTO_BANDS": {
143
+ "kind": "scalar",
144
+ "doc": "Default per-channel cap on auto-planted bands — a full pool plants nothing and raises a pool-full alert instead (predictability over silent eviction).",
145
+ "unit": "",
146
+ "value": 6
147
+ }
148
+ }
@@ -47,5 +47,27 @@
47
47
  "default": 50,
48
48
  "defaultFrom": "desk"
49
49
  }
50
+ },
51
+ "FLANGER_BASE_MS": {
52
+ "kind": "scalar",
53
+ "doc": "The flanger's base delay, ms: the sweep rides on it up to the depth.",
54
+ "unit": "ms",
55
+ "value": 0.5
56
+ },
57
+ "FLANGER_MAX_MS": {
58
+ "kind": "scalar",
59
+ "doc": "The longest delay a flanger sweep reaches, rounded up to whole ms: the base delay plus the depth maximum. The delay line is sized from it.",
60
+ "unit": "ms",
61
+ "value": {
62
+ "derive": "ceilSum",
63
+ "of": [
64
+ {
65
+ "ref": "FLANGER_BASE_MS"
66
+ },
67
+ {
68
+ "ref": "FLANGER_DEPTH_RANGE.max"
69
+ }
70
+ ]
71
+ }
50
72
  }
51
73
  }
@@ -77,5 +77,13 @@
77
77
  "defaultFrom": "desk"
78
78
  }
79
79
  }
80
+ },
81
+ "GATE_KEY_SOURCES": {
82
+ "kind": "set",
83
+ "doc": "Where the gate listens: its own input (self), or the key it is handed (sidechain), in the order of the integer the key port carries.",
84
+ "ids": [
85
+ "self",
86
+ "sidechain"
87
+ ]
80
88
  }
81
89
  }
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "../../schema/omx-contract.schema.json",
3
+ "HRP_MAX_CUT_DB": {
4
+ "kind": "scalar",
5
+ "doc": "The largest automatic cut HRP may place on one harmonic, as a POSITIVE dB magnitude. Well inside the ±15 dB an operator band may hold (core's `EQ_GAIN_LIMIT_DB`): an automatic correction the operator did not ask for gets far less authority than one they dialled themselves.",
6
+ "unit": "dB",
7
+ "value": 6
8
+ },
9
+ "HRP_MAX_VOICES": {
10
+ "kind": "scalar",
11
+ "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.",
12
+ "unit": "",
13
+ "value": 8
14
+ },
15
+ "HRP_BAND_Q": {
16
+ "kind": "scalar",
17
+ "doc": "The Q of an HRP correction band — one declared number, operator-adjustable later. A musical cut, not a ring killer: FBS's default notch Q is ~116 because a howl is a single frequency, whereas a partial that is too loud is a note an instrument is playing and a surgical null on it would be heard as a hole. 5 sits mid-band in the 4…6 range a musical subtractive cut occupies, and inside EQ_Q_RANGE — the gain-carrying shapes' extent — so it survives the store's clamp unchanged. Deliberately NOT derived from the harmonic index. A derivation invented with no measurement behind it is a second declaration wearing a formula; this is revisited when the analyse-only surface shows what real corrections look like.",
18
+ "unit": "",
19
+ "value": 5
20
+ },
21
+ "HRP_EXCESS_THRESHOLD_DB": {
22
+ "kind": "scalar",
23
+ "doc": "How far a partial must stand above its LEARNED BASELINE before HRP calls it a resonance (increment 8, §23–§24: `deviation = measured − baseline[note][harmonic]`). A partial level tells you nothing on its own; what marks a resonance is a partial louder than THIS instrument normally measures at THIS note. Three decibels is the same order as the feedback detector's peak-to-neighbour gate and for the same reason: below it, an ordinary performance's variation — a bow dug in, a verse sung harder — would be flattened, and flattening expression is not correcting a room. (Before the learned model existed this same threshold gated the placeholder neighbour rule; the number and its reasoning carried over.)",
24
+ "unit": "dB",
25
+ "value": 3
26
+ },
27
+ "HRP_GAIN_STEP_DB": {
28
+ "kind": "scalar",
29
+ "doc": "How far ONE reconcile pass may move an HRP band's gain, in dB — §48's gain smoothing, done at the control level because the native cascade applies pushed coefficients verbatim and §48 forbids interpolating there. An unbounded gain step is a waveform discontinuity — the click heard when a correction entered or left at full depth (found live, 2026-08-20, the evening increment 9 raised the enter/leave rate). Bounded, a correction ENTERS by deepening from silence and LEAVES by walking to exactly 0 dB — the provable parked identity (`eqBandIsIdentity`) — from which removal is bit-identical. Two decibels per pass at the corrector's 10 Hz cadence walks the full HRP_MAX_CUT_DB travel in ~300 ms: the same order as the tracker's stability hold and FBS's lift steps, while each individual move stays below audibility on a Q-5 bell.",
30
+ "unit": "dB",
31
+ "value": 2
32
+ },
33
+ "HRP_COLLISION_CENTS": {
34
+ "kind": "scalar",
35
+ "doc": "How close two correction candidates must sit, in cents, to count as the SAME spectral region (§30): within it, the stronger candidate is selected and the weaker discarded rather than two near-identical filters being planted. One semitone, and the number follows from HRP_BAND_Q: a Q-5 bell's −3 dB half-width is ~170 cents, so two cuts a semitone apart overlap over most of their travel — the second one deepens the first far more than it shapes anything of its own. The spec orders the threshold configurable and tested (§30's last line): declared once here, a parameter everywhere below.",
36
+ "unit": "cents",
37
+ "value": 100
38
+ },
39
+ "HRP_ATTRIBUTION_CENTS": {
40
+ "kind": "scalar",
41
+ "doc": "The ATTRIBUTION window (§11): two partials closer than this are one spectral event as far as the desk can tell — the same ±1 bin `omx_hrp_bin_peak` reads over, expressed musically so it holds across the range. A different law from HRP_COLLISION_CENTS (§30's SELECTION window, which stays above it): this one is the analyser's resolution, that one the corrector's. Generated into `omx_contract_limits.h` as `OMX_HRP_ATTRIBUTION_CENTS`; `hrp_attribute.h` reads it.",
42
+ "unit": "cents",
43
+ "value": 60
44
+ },
45
+ "HRP_DEFAULT_MAX_AUTO_BANDS": {
46
+ "kind": "scalar",
47
+ "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.",
48
+ "unit": "",
49
+ "value": 8
50
+ },
51
+ "HRP_AMOUNT_RANGE": {
52
+ "kind": "travels",
53
+ "doc": "How much of the computed correction HRP applies: 0 = analyse only, 1 = the full cut. It comes up at 0.5, half the computed cut.",
54
+ "travel": {
55
+ "min": 0,
56
+ "max": 1,
57
+ "step": 0,
58
+ "unit": "",
59
+ "default": 0.5
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "../../schema/omx-contract.schema.json",
3
+ "BUS_INPUT_CAP": {
4
+ "kind": "scalar",
5
+ "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
+ "unit": "",
7
+ "value": 128
8
+ }
9
+ }
@@ -105,5 +105,11 @@
105
105
  "ref": "PHASER_MIX_RANGE"
106
106
  }
107
107
  }
108
+ },
109
+ "PHASER_F_TOP_HZ": {
110
+ "kind": "scalar",
111
+ "doc": "The highest frequency the phaser's all-pass sweep reaches, Hz.",
112
+ "unit": "Hz",
113
+ "value": 16000
108
114
  }
109
115
  }
@@ -146,5 +146,22 @@
146
146
  "default": -40,
147
147
  "defaultFrom": "desk"
148
148
  }
149
+ },
150
+ "REVERB_ALGORITHMS": {
151
+ "kind": "set",
152
+ "doc": "The reverb's algorithms, in the order of the integer the kernel reads. Reverse and gated are the room network handed a window and a gate.",
153
+ "ids": [
154
+ "room",
155
+ "plate",
156
+ "hall",
157
+ "reverse",
158
+ "gated"
159
+ ]
160
+ },
161
+ "REVERB_GATE_ATTACK_MS": {
162
+ "kind": "scalar",
163
+ "doc": "The gated reverb's gate attack, ms: the time the gate takes to open on a hit.",
164
+ "unit": "ms",
165
+ "value": 1.5
149
166
  }
150
167
  }
@@ -182,5 +182,14 @@
182
182
  "doc": "One inline ring per rotor per leg, floats — at least the 192 kHz need of the horn's sweep.",
183
183
  "unit": "",
184
184
  "value": 256
185
+ },
186
+ "ROTARY_SPEEDS": {
187
+ "kind": "set",
188
+ "doc": "The rotary speaker's speed settings, in the order of the integer the kernel reads: the rotors stopped, at their chorale (slow) rate, or at their tremolo (fast) rate.",
189
+ "ids": [
190
+ "stop",
191
+ "slow",
192
+ "fast"
193
+ ]
185
194
  }
186
195
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "../../schema/omx-contract.schema.json",
3
+ "RTA_TARGET_BIN_HZ": {
4
+ "kind": "scalar",
5
+ "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
+ "unit": "Hz",
7
+ "value": 5.9
8
+ },
9
+ "RTA_FFT_SIZE_MAX": {
10
+ "kind": "scalar",
11
+ "doc": "The largest RTA FFT the native ring holds — generated into `omx_contract_limits.h` as `OMX_RTA_FFT_SIZE_MAX`.",
12
+ "unit": "",
13
+ "value": 32768
14
+ }
15
+ }
@@ -50,5 +50,14 @@
50
50
  "ref": "TREMOLO_MIX_RANGE"
51
51
  }
52
52
  }
53
+ },
54
+ "TREMOLO_MODES": {
55
+ "kind": "set",
56
+ "doc": "What the tremolo's one oscillator modulates, in the order of the integer the kernel reads: the level of both legs in phase (tremolo), or the balance law's position with the legs in opposition (pan).",
57
+ "ids": [
58
+ "tremolo",
59
+ "pan"
60
+ ],
61
+ "default": "tremolo"
53
62
  }
54
63
  }