@openmixer/omx-contract 1.3.0 → 2.0.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.
@@ -1,33 +1,38 @@
1
1
  {
2
2
  "$schema": "../../schema/omx-contract.schema.json",
3
- "GEQ_BAND_RANGE": {
4
- "kind": "travels",
5
- "doc": "One graphic-EQ fader's travel: EQ_GAIN_RANGE by reference (a graphic fader is an EQ gain), with the come-up value it needs as a row field — flat, 0 dB (graphic-eq-31 spec §3).",
6
- "travel": {
7
- "min": -15,
8
- "max": 15,
9
- "step": 0.5,
3
+ "controls": [
4
+ {
5
+ "name": "band",
6
+ "kind": "travel",
7
+ "doc": "One graphic-EQ fader's travel: EQ_GAIN_RANGE by reference (a graphic fader is an EQ gain), with the come-up value it needs as a row field — flat, 0 dB (graphic-eq-31 spec §3).",
8
+ "travel": {
9
+ "min": -15,
10
+ "max": 15,
11
+ "step": 0.5,
12
+ "unit": "dB",
13
+ "default": 0,
14
+ "defaultFrom": "desk"
15
+ }
16
+ }
17
+ ],
18
+ "constants": {
19
+ "GEQ_BAND_Q": {
20
+ "kind": "scalar",
21
+ "doc": "The graphic EQ's band Q: the ISO third-octave bandwidth, `2^(1/6)/(2^(1/3) − 1) ≈ 4.318`, to one decimal as the M32 GEQ carries it. Read by the `geq31` preset and the graphic EQ's section design (graphic-eq-31 spec §3).",
22
+ "unit": "",
23
+ "value": 4.3
24
+ },
25
+ "GEQ_PROTO_DB": {
26
+ "kind": "scalar",
27
+ "doc": "The prototype gain the `true` solve's first interaction matrix is evaluated at (spec §2, §3): the middle of the travel's magnitude, where a matched-Z section's dB response per dB of gain is closest to its average.",
28
+ "unit": "dB",
29
+ "value": 12
30
+ },
31
+ "GEQ_DESIGN_GAIN_MAX_DB": {
32
+ "kind": "scalar",
33
+ "doc": "The ceiling on a section gain the `true` solve may ask for (spec §3): above the travel's worst case (26.0 dB, the alternating ±15 set) with margin. A solve past it clamps and the row says so (`designClamped`), never silently.",
10
34
  "unit": "dB",
11
- "default": 0,
12
- "defaultFrom": "desk"
35
+ "value": 30
13
36
  }
14
- },
15
- "GEQ_BAND_Q": {
16
- "kind": "scalar",
17
- "doc": "The graphic EQ's band Q: the ISO third-octave bandwidth, `2^(1/6)/(2^(1/3) − 1) ≈ 4.318`, to one decimal as the M32 GEQ carries it. Read by the `geq31` preset and the graphic EQ's section design (graphic-eq-31 spec §3).",
18
- "unit": "",
19
- "value": 4.3
20
- },
21
- "GEQ_PROTO_DB": {
22
- "kind": "scalar",
23
- "doc": "The prototype gain the `true` solve's first interaction matrix is evaluated at (spec §2, §3): the middle of the travel's magnitude, where a matched-Z section's dB response per dB of gain is closest to its average.",
24
- "unit": "dB",
25
- "value": 12
26
- },
27
- "GEQ_DESIGN_GAIN_MAX_DB": {
28
- "kind": "scalar",
29
- "doc": "The ceiling on a section gain the `true` solve may ask for (spec §3): above the travel's worst case (26.0 dB, the alternating ±15 set) with margin. A solve past it clamps and the row says so (`designClamped`), never silently.",
30
- "unit": "dB",
31
- "value": 30
32
37
  }
33
38
  }
@@ -1,62 +1,68 @@
1
1
  {
2
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,
3
+ "controls": [
4
+ {
5
+ "name": "amount",
6
+ "kind": "travel",
7
+ "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.",
8
+ "travel": {
9
+ "min": 0,
10
+ "max": 1,
11
+ "step": 0,
12
+ "unit": "",
13
+ "default": 0.5,
14
+ "defaultFrom": "desk"
15
+ }
16
+ }
17
+ ],
18
+ "constants": {
19
+ "HRP_MAX_CUT_DB": {
20
+ "kind": "scalar",
21
+ "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.",
22
+ "unit": "dB",
23
+ "value": 6
24
+ },
25
+ "HRP_MAX_VOICES": {
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.",
28
+ "unit": "",
29
+ "value": 8
30
+ },
31
+ "HRP_BAND_Q": {
32
+ "kind": "scalar",
33
+ "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.",
34
+ "unit": "",
35
+ "value": 5
36
+ },
37
+ "HRP_EXCESS_THRESHOLD_DB": {
38
+ "kind": "scalar",
39
+ "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.)",
40
+ "unit": "dB",
41
+ "value": 3
42
+ },
43
+ "HRP_GAIN_STEP_DB": {
44
+ "kind": "scalar",
45
+ "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.",
46
+ "unit": "dB",
47
+ "value": 2
48
+ },
49
+ "HRP_COLLISION_CENTS": {
50
+ "kind": "scalar",
51
+ "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.",
52
+ "unit": "cents",
53
+ "value": 100
54
+ },
55
+ "HRP_ATTRIBUTION_CENTS": {
56
+ "kind": "scalar",
57
+ "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.",
58
+ "unit": "cents",
59
+ "value": 60
60
+ },
61
+ "HRP_DEFAULT_MAX_AUTO_BANDS": {
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.",
58
64
  "unit": "",
59
- "default": 0.5
65
+ "value": 8
60
66
  }
61
67
  }
62
68
  }
@@ -1,26 +1,37 @@
1
1
  {
2
2
  "$schema": "../../schema/omx-contract.schema.json",
3
- "LIMITER_LIMITS": {
4
- "kind": "travels",
5
- "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.",
6
- "fields": {
7
- "ceilingDb": {
3
+ "controls": [
4
+ {
5
+ "name": "ceilingDb",
6
+ "kind": "travel",
7
+ "table": "LIMITER_LIMITS",
8
+ "travel": {
8
9
  "min": -12,
9
10
  "max": 0,
10
11
  "step": 0.1,
11
12
  "unit": "dBFS",
12
13
  "default": -1,
13
14
  "defaultFrom": "desk"
14
- },
15
- "lookaheadMs": {
15
+ }
16
+ },
17
+ {
18
+ "name": "lookaheadMs",
19
+ "kind": "travel",
20
+ "table": "LIMITER_LIMITS",
21
+ "travel": {
16
22
  "min": 0.5,
17
23
  "max": 5,
18
24
  "step": 0.1,
19
25
  "unit": "ms",
20
26
  "default": 1.5,
21
27
  "defaultFrom": "desk"
22
- },
23
- "releaseMs": {
28
+ }
29
+ },
30
+ {
31
+ "name": "releaseMs",
32
+ "kind": "travel",
33
+ "table": "LIMITER_LIMITS",
34
+ "travel": {
24
35
  "min": 1,
25
36
  "max": 1000,
26
37
  "step": 1,
@@ -29,5 +40,10 @@
29
40
  "defaultFrom": "desk"
30
41
  }
31
42
  }
43
+ ],
44
+ "tables": {
45
+ "LIMITER_LIMITS": {
46
+ "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."
47
+ }
32
48
  }
33
49
  }
@@ -1,9 +1,11 @@
1
1
  {
2
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
3
+ "constants": {
4
+ "BUS_INPUT_CAP": {
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.",
7
+ "unit": "",
8
+ "value": 128
9
+ }
8
10
  }
9
11
  }
@@ -1,115 +1,119 @@
1
1
  {
2
2
  "$schema": "../../schema/omx-contract.schema.json",
3
- "PHASER_RATE_RANGE": {
4
- "kind": "travels",
5
- "doc": "The phaser's sweep speed (§3). `omx_lfo_inc` freezes at Nyquist; this travel stays far below.",
6
- "travel": {
7
- "min": 0.05,
8
- "max": 5,
9
- "step": 0.01,
10
- "unit": "Hz",
11
- "default": 0.5,
12
- "defaultFrom": "desk"
13
- }
14
- },
15
- "PHASER_BASE_RANGE": {
16
- "kind": "travels",
17
- "doc": "The phaser's lowest centre frequency; the sweep runs upward from it (§3).",
18
- "travel": {
19
- "min": 50,
20
- "max": 2000,
21
- "step": 1,
22
- "unit": "Hz",
23
- "default": 200,
24
- "defaultFrom": "desk"
25
- }
26
- },
27
- "PHASER_DEPTH_RANGE": {
28
- "kind": "travels",
29
- "doc": "The sweep's span above `base`, in octaves; 0 is a static phaser at `base` (§3).",
30
- "travel": {
31
- "min": 0,
32
- "max": 6,
33
- "step": 0.1,
34
- "unit": "oct",
35
- "default": 4,
36
- "defaultFrom": "desk"
3
+ "controls": [
4
+ {
5
+ "name": "rateHz",
6
+ "kind": "travel",
7
+ "global": "PHASER_RATE_RANGE",
8
+ "doc": "The phaser's sweep speed (§3). `omx_lfo_inc` freezes at Nyquist; this travel stays far below.",
9
+ "travel": {
10
+ "min": 0.05,
11
+ "max": 5,
12
+ "step": 0.01,
13
+ "unit": "Hz",
14
+ "default": 0.5,
15
+ "defaultFrom": "desk"
16
+ }
17
+ },
18
+ {
19
+ "name": "baseHz",
20
+ "kind": "travel",
21
+ "global": "PHASER_BASE_RANGE",
22
+ "doc": "The phaser's lowest centre frequency; the sweep runs upward from it (§3).",
23
+ "travel": {
24
+ "min": 50,
25
+ "max": 2000,
26
+ "step": 1,
27
+ "unit": "Hz",
28
+ "default": 200,
29
+ "defaultFrom": "desk"
30
+ }
31
+ },
32
+ {
33
+ "name": "depthOct",
34
+ "kind": "travel",
35
+ "global": "PHASER_DEPTH_RANGE",
36
+ "doc": "The sweep's span above `base`, in octaves; 0 is a static phaser at `base` (§3).",
37
+ "travel": {
38
+ "min": 0,
39
+ "max": 6,
40
+ "step": 0.1,
41
+ "unit": "oct",
42
+ "default": 4,
43
+ "defaultFrom": "desk"
44
+ }
45
+ },
46
+ {
47
+ "name": "stages",
48
+ "kind": "travel",
49
+ "doc": "The phaser's section count per leg — EVEN, 2 … 12 (§2: two sections per notch). Its ends are the kernel's state capacity (`OMX_PHASER_MAX_STAGES` reads `OMX_PHASER_STAGES_RANGE_MAX`); the row moves it through the closed member set PHASER_STAGE_COUNTS, which is derived from it.",
50
+ "travel": {
51
+ "min": 2,
52
+ "max": 12,
53
+ "step": 2,
54
+ "unit": "",
55
+ "default": 6,
56
+ "defaultFrom": "desk"
57
+ }
58
+ },
59
+ {
60
+ "name": "feedback",
61
+ "kind": "travel",
62
+ "doc": "The phaser's SIGNED resonance (§3). ±0.9, tighter than the flanger's ±0.95: the loop runs through a time-varying chain. Declared here (the refusal half); the kernel's clamp is the enforcement half.",
63
+ "travel": {
64
+ "min": -0.9,
65
+ "max": 0.9,
66
+ "step": 0.01,
67
+ "unit": "",
68
+ "default": 0.4,
69
+ "defaultFrom": "desk"
70
+ }
71
+ },
72
+ {
73
+ "name": "mix",
74
+ "kind": "travel",
75
+ "doc": "The phaser's wet/dry, as an instance of DRIVE_MIX_RANGE: 50 %, where the notch is complete (§3).",
76
+ "travel": {
77
+ "min": 0,
78
+ "max": 100,
79
+ "step": 0.1,
80
+ "unit": "%",
81
+ "default": 50,
82
+ "defaultFrom": "desk"
83
+ }
37
84
  }
38
- },
39
- "PHASER_STAGES_RANGE": {
40
- "kind": "travels",
41
- "doc": "The phaser's section count per leg — EVEN, 2 … 12 (§2: two sections per notch). Its ends are the kernel's state capacity (`OMX_PHASER_MAX_STAGES` reads `OMX_PHASER_STAGES_RANGE_MAX`); the row moves it through the closed member set PHASER_STAGE_COUNTS, which is derived from it.",
42
- "travel": {
43
- "min": 2,
44
- "max": 12,
45
- "step": 2,
46
- "unit": "",
47
- "default": 6,
48
- "defaultFrom": "desk"
85
+ ],
86
+ "aggregates": {
87
+ "PHASER_TRAVELS": {
88
+ "doc": "The phaser's continuous travels by row field — the ONE map its control bounds and its row travels both read (§3). `stages` is not here: it moves through PHASER_STAGE_COUNTS.",
89
+ "fields": [
90
+ "rateHz",
91
+ "baseHz",
92
+ "depthOct",
93
+ "feedback",
94
+ "mix"
95
+ ]
49
96
  }
50
97
  },
51
- "PHASER_STAGE_COUNTS": {
52
- "kind": "list",
53
- "doc": "The member set the `stages` field accepts: PHASER_STAGES_RANGE walked by its step.",
54
- "unit": "",
55
- "values": [
56
- 2,
57
- 4,
58
- 6,
59
- 8,
60
- 10,
61
- 12
62
- ]
63
- },
64
- "PHASER_FEEDBACK_RANGE": {
65
- "kind": "travels",
66
- "doc": "The phaser's SIGNED resonance (§3). ±0.9, tighter than the flanger's ±0.95: the loop runs through a time-varying chain. Declared here (the refusal half); the kernel's clamp is the enforcement half.",
67
- "travel": {
68
- "min": -0.9,
69
- "max": 0.9,
70
- "step": 0.01,
98
+ "constants": {
99
+ "PHASER_STAGE_COUNTS": {
100
+ "kind": "list",
101
+ "doc": "The member set the `stages` field accepts: PHASER_STAGES_RANGE walked by its step.",
71
102
  "unit": "",
72
- "default": 0.4,
73
- "defaultFrom": "desk"
74
- }
75
- },
76
- "PHASER_MIX_RANGE": {
77
- "kind": "travels",
78
- "doc": "The phaser's wet/dry, as an instance of DRIVE_MIX_RANGE: 50 %, where the notch is complete (§3).",
79
- "travel": {
80
- "min": 0,
81
- "max": 100,
82
- "step": 0.1,
83
- "unit": "%",
84
- "default": 50,
85
- "defaultFrom": "desk"
86
- }
87
- },
88
- "PHASER_TRAVELS": {
89
- "kind": "travels",
90
- "doc": "The phaser's continuous travels by row field — the ONE map its control bounds and its row travels both read (§3). `stages` is not here: it moves through PHASER_STAGE_COUNTS.",
91
- "fields": {
92
- "rateHz": {
93
- "ref": "PHASER_RATE_RANGE"
94
- },
95
- "baseHz": {
96
- "ref": "PHASER_BASE_RANGE"
97
- },
98
- "depthOct": {
99
- "ref": "PHASER_DEPTH_RANGE"
100
- },
101
- "feedback": {
102
- "ref": "PHASER_FEEDBACK_RANGE"
103
- },
104
- "mix": {
105
- "ref": "PHASER_MIX_RANGE"
106
- }
103
+ "values": [
104
+ 2,
105
+ 4,
106
+ 6,
107
+ 8,
108
+ 10,
109
+ 12
110
+ ]
111
+ },
112
+ "PHASER_F_TOP_HZ": {
113
+ "kind": "scalar",
114
+ "doc": "The highest frequency the phaser's all-pass sweep reaches, Hz.",
115
+ "unit": "Hz",
116
+ "value": 16000
107
117
  }
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
114
118
  }
115
119
  }
@@ -1,34 +1,52 @@
1
1
  {
2
2
  "$schema": "../../schema/omx-contract.schema.json",
3
- "PITCH_LIMITS": {
4
- "kind": "travels",
5
- "doc": "PITCH_LIMITS, moved from openmixer.",
6
- "fields": {
7
- "semitones": {
3
+ "controls": [
4
+ {
5
+ "name": "semitones",
6
+ "kind": "travel",
7
+ "table": "PITCH_LIMITS",
8
+ "travel": {
8
9
  "min": -12,
9
10
  "max": 12,
10
11
  "default": 0
11
- },
12
- "cents": {
12
+ }
13
+ },
14
+ {
15
+ "name": "cents",
16
+ "kind": "travel",
17
+ "table": "PITCH_LIMITS",
18
+ "travel": {
13
19
  "min": -50,
14
20
  "max": 50,
15
21
  "default": 0
16
- },
17
- "mix": {
22
+ }
23
+ },
24
+ {
25
+ "name": "mix",
26
+ "kind": "travel",
27
+ "table": "PITCH_LIMITS",
28
+ "travel": {
18
29
  "min": 0,
19
30
  "max": 100,
20
31
  "default": 100
21
32
  }
22
33
  }
34
+ ],
35
+ "tables": {
36
+ "PITCH_LIMITS": {
37
+ "doc": "PITCH_LIMITS, moved from openmixer."
38
+ }
23
39
  },
24
- "PITCH_KERNEL": {
25
- "kind": "sheet",
26
- "doc": "PITCH_KERNEL, moved from openmixer.",
27
- "value": {
28
- "windowMs": 40,
29
- "prefilterCeilingHz": 20000,
30
- "prefilterQ": {
31
- "ref": "BUTTERWORTH_Q"
40
+ "constants": {
41
+ "PITCH_KERNEL": {
42
+ "kind": "sheet",
43
+ "doc": "PITCH_KERNEL, moved from openmixer.",
44
+ "value": {
45
+ "windowMs": 40,
46
+ "prefilterCeilingHz": 20000,
47
+ "prefilterQ": {
48
+ "ref": "BUTTERWORTH_Q"
49
+ }
32
50
  }
33
51
  }
34
52
  }