@openmixer/omx-dsp 0.30.1 → 0.32.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.
@@ -0,0 +1,85 @@
1
+ /**
2
+ * curvePoints, the twin of omx_curve_points (omx_curve.h): a panel description's display, bound to the current
3
+ * values and a rate, to its points. The same words (the description's display, family, role, unit and shape
4
+ * words) and the same codes (CURVE_CODES), checked in the same order. Pinned by test/agreement.test.mjs at the
5
+ * nine oracle rates: the EQ through the magnitude twins, the transfer curves through the gain computer twins.
6
+ */
7
+ import { CURVE_CODES } from './definitions.js';
8
+ /** The wire words of each set, in value order. */
9
+ export declare const CURVE_WORDS: {
10
+ readonly displays: string[];
11
+ readonly families: string[];
12
+ readonly roles: string[];
13
+ readonly units: string[];
14
+ readonly shapes: string[];
15
+ /** fbs-fixed and fbs-live: the origins are spelt with a hyphen */
16
+ readonly origins: string[];
17
+ };
18
+ /** What curvePoints answers when it draws nothing: a CURVE_CODES id other than `ok`. */
19
+ export type CurveCode = Exclude<(typeof CURVE_CODES)[number], 'ok'>;
20
+ /** One bound term: a role of the description, its value now, in the unit the description gives it. */
21
+ export interface CurveTerm {
22
+ /** The role word: `band_freq`, `threshold`, `knee_start`… */
23
+ readonly role: string;
24
+ /** The band's index for a `band_*` role; 0 (or absent) for any other. */
25
+ readonly index?: number;
26
+ /** The control's plain value. */
27
+ readonly value: number;
28
+ /** The unit token: `hz`, `khz`, `db`, `coef`, `oct`, `none`… */
29
+ readonly unit: string;
30
+ /** For `band_type`, the shape its value selects (from the role's `shapes`); for `band_freq`, the fixed shape. */
31
+ readonly shape?: string;
32
+ /** Where the band comes from: `user` (the default), `fbs-fixed` or `fbs-live`. A band's is its band_freq term's. */
33
+ readonly origin?: string;
34
+ }
35
+ /** A curve request: one display of the description, bound. */
36
+ export interface CurveRequest {
37
+ /** The display's kind: `eq_response` or `transfer_curve`. */
38
+ readonly kind: string;
39
+ /** The family: `eq`, `dynamics`, `limiter`, `gate` or `deesser`. */
40
+ readonly family: string;
41
+ /** The instance's rate, Hz. */
42
+ readonly sampleRate: number;
43
+ /** The grid points, 2 to CURVE_POINTS_MAX (an EQ's anchors on top). */
44
+ readonly points: number;
45
+ /** The x extents; CURVE_EXTENTS when absent. */
46
+ readonly extents?: {
47
+ readonly min: number;
48
+ readonly max: number;
49
+ };
50
+ /** The terms; a later term of one role and index replaces an earlier. */
51
+ readonly terms: readonly CurveTerm[];
52
+ /** Also answer the curve over the `user` bands alone, at the same x. */
53
+ readonly userOnly?: boolean;
54
+ }
55
+ /** A band whose origin is not `user`: its centre (Hz), its own magnitude there (dB), its Q and its origin. */
56
+ export interface CurveMark {
57
+ readonly freq: number;
58
+ readonly depthDb: number;
59
+ readonly q: number;
60
+ readonly origin: string;
61
+ }
62
+ /** One point: Hz and dB for an EQ, input dB and output dB for a transfer curve. */
63
+ export interface CurvePoint {
64
+ readonly x: number;
65
+ readonly y: number;
66
+ }
67
+ /** The answer: the points, ascending in x, or the code of what is missing or refused. */
68
+ export type CurveAnswer = {
69
+ readonly points: readonly CurvePoint[];
70
+ /** One per drawn band whose origin is not `user`, in band order; none for a transfer curve. */
71
+ readonly marks: readonly CurveMark[];
72
+ /** With `userOnly`: the curve over the user bands (and the head filters) alone, at the same x. */
73
+ readonly userPoints?: readonly CurvePoint[];
74
+ } | {
75
+ readonly code: CurveCode;
76
+ };
77
+ /** The Q of each section of a Butterworth head filter (omx_eq_butterworth_qs): 12 dB/oct one at 1/sqrt(2), 24 dB/oct
78
+ * two whose product is maximally flat, the kernel's own digits. */
79
+ export declare function butterworthQs(slope24: boolean): readonly number[];
80
+ /**
81
+ * The points of one display of a panel description: the twin of omx_curve_points, the same checks in the same
82
+ * order, the same codes.
83
+ * @param rq The request.
84
+ */
85
+ export declare function curvePoints(rq: CurveRequest): CurveAnswer;
package/dist/curve.js ADDED
@@ -0,0 +1,216 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * curvePoints, the twin of omx_curve_points (omx_curve.h): a panel description's display, bound to the current
5
+ * values and a rate, to its points. The same words (the description's display, family, role, unit and shape
6
+ * words) and the same codes (CURVE_CODES), checked in the same order. Pinned by test/agreement.test.mjs at the
7
+ * nine oracle rates: the EQ through the magnitude twins, the transfer curves through the gain computer twins.
8
+ */
9
+ import { COMP_KNEE_DB_RANGE, COMP_RATIO_RANGE, CURVE_DISPLAYS, CURVE_EXTENTS, CURVE_FAMILIES, CURVE_ORIGINS, CURVE_POINTS_MAX, CURVE_ROLES, CURVE_SHAPES, CURVE_UNITS, EQ_BAND_DEFAULTS, GATE_RANGE_DB_RANGE, GATE_RATIO_RANGE, LIN_TO_DB_FLOOR, } from './definitions.js';
10
+ import { clampEqQFor, magnitudeSumDb, octavesToQ } from './eq.js';
11
+ import { gaincompDb, gaincompDbShifted, gateKnee } from './gaincomp.js';
12
+ /** A set id as the description spells it: `eqResponse` is `eq_response`; the unshaped band is `-`. */
13
+ const wordOf = (id) => (id === 'unshaped' ? '-' : id.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`));
14
+ /** The wire words of each set, in value order. */
15
+ export const CURVE_WORDS = {
16
+ displays: CURVE_DISPLAYS.map(wordOf),
17
+ families: CURVE_FAMILIES.map(wordOf),
18
+ roles: CURVE_ROLES.map(wordOf),
19
+ units: CURVE_UNITS.map(wordOf),
20
+ shapes: CURVE_SHAPES.map(wordOf),
21
+ /** fbs-fixed and fbs-live: the origins are spelt with a hyphen */
22
+ origins: CURVE_ORIGINS.map((id) => id.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)),
23
+ };
24
+ /** The units each role converts from (omx_curve_unit_accepted). */
25
+ const ACCEPTS = {
26
+ band_freq: ['hz', 'khz'], hpf_freq: ['hz', 'khz'], lpf_freq: ['hz', 'khz'],
27
+ band_gain: ['db', 'coef'],
28
+ band_q: ['none', 'coef', 'oct'],
29
+ band_type: ['none'], band_on: ['none'], hpf_on: ['none'], lpf_on: ['none'],
30
+ hpf_slope: ['none', 'db'], lpf_slope: ['none', 'db'],
31
+ threshold: ['db', 'dbfs', 'dbu', 'lufs'],
32
+ ratio: ['ratio', 'coef'],
33
+ knee: ['db'], makeup: ['db'], range: ['db'], knee_start: ['db'], knee_end: ['db'],
34
+ };
35
+ /** omx_lin_to_db: a linear factor to dB, floored at LIN_TO_DB_FLOOR. */
36
+ const linToDb = (lin) => {
37
+ const v = Math.fround(lin);
38
+ return Math.fround(20 * Math.log10(v < Math.fround(LIN_TO_DB_FLOOR.lin) ? Math.fround(LIN_TO_DB_FLOOR.lin) : v));
39
+ };
40
+ /** A term's value in the model's unit (omx_curve_term_value). */
41
+ const valueOf = (t) => {
42
+ switch (t.unit) {
43
+ case 'khz': return t.value * 1000;
44
+ case 'coef': return t.role === 'band_gain' ? linToDb(t.value) : t.value;
45
+ case 'oct': return octavesToQ(t.value);
46
+ default: return t.value;
47
+ }
48
+ };
49
+ const SHAPE_TYPE = {
50
+ bell: 'bell', lowshelf: 'lowshelf', highshelf: 'highshelf', highpass: 'highpass', lowpass: 'lowpass', notch: 'notch',
51
+ allpass1: 'allpass1', allpass2: 'allpass2',
52
+ };
53
+ /** The Q of each section of a Butterworth head filter (omx_eq_butterworth_qs): 12 dB/oct one at 1/sqrt(2), 24 dB/oct
54
+ * two whose product is maximally flat, the kernel's own digits. */
55
+ export function butterworthQs(slope24) {
56
+ return slope24 ? [0.5411961, 1.306563] : [Math.SQRT1_2];
57
+ }
58
+ /** The frequencies a drawn curve is sampled at (omx_eq_curve_freqs): the log grid and the bands' anchors inside it,
59
+ * ascending. */
60
+ function curveFreqs(bands, fMin, fMax, points) {
61
+ const g = Math.max(points, 2);
62
+ const out = [];
63
+ for (let i = 0; i < g; i++)
64
+ out.push(fMin * Math.pow(fMax / fMin, i / (g - 1)));
65
+ for (const b of bands) {
66
+ const half = b.freq / (2 * (b.q > 0.1 ? b.q : 0.1));
67
+ for (const a of [b.freq - half, b.freq, b.freq + half])
68
+ if (a > fMin && a < fMax)
69
+ out.push(a);
70
+ }
71
+ for (let i = 1; i < out.length; i++) {
72
+ const v = out[i];
73
+ let j = i;
74
+ for (; j > 0 && out[j - 1] > v; j--)
75
+ out[j] = out[j - 1];
76
+ out[j] = v;
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * The points of one display of a panel description: the twin of omx_curve_points, the same checks in the same
82
+ * order, the same codes.
83
+ * @param rq The request.
84
+ */
85
+ export function curvePoints(rq) {
86
+ const eq = rq.kind === 'eq_response';
87
+ if (!(eq ? rq.family === 'eq' : rq.kind === 'transfer_curve' && rq.family !== 'eq' && CURVE_WORDS.families.includes(rq.family)))
88
+ return { code: 'kindUnknown' };
89
+ if (!Number.isInteger(rq.points) || rq.points < 2 || rq.points > CURVE_POINTS_MAX)
90
+ return { code: 'pointsRange' };
91
+ if (!Number.isFinite(rq.sampleRate) || !(rq.sampleRate > 0) || (rq.extents && !(Number.isFinite(rq.extents.min) && Number.isFinite(rq.extents.max))))
92
+ return { code: 'valueNotFinite' };
93
+ if (rq.terms.some((t) => !Number.isFinite(t.value)))
94
+ return { code: 'valueNotFinite' };
95
+ if (rq.terms.some((t) => !ACCEPTS[t.role]?.includes(t.unit) || !CURVE_WORDS.origins.includes(t.origin ?? 'user')))
96
+ return { code: 'unitRefused' };
97
+ let lo, hi;
98
+ if (rq.extents)
99
+ [lo, hi] = [rq.extents.min, rq.extents.max];
100
+ else if (eq)
101
+ [lo, hi] = [CURVE_EXTENTS.freqMinHz, Math.min(CURVE_EXTENTS.freqMaxHz, (CURVE_EXTENTS.nyquistFraction * rq.sampleRate) / 2)];
102
+ else
103
+ [lo, hi] = [CURVE_EXTENTS.levelMinDb, CURVE_EXTENTS.levelMaxDb];
104
+ if (!(hi > lo) || (eq && !(lo > 0)))
105
+ return { code: 'pointsRange' };
106
+ const find = (role, index = 0) => {
107
+ let found;
108
+ for (const t of rq.terms)
109
+ if (t.role === role && (t.index ?? 0) === index)
110
+ found = t;
111
+ return found;
112
+ };
113
+ return eq ? eqPoints(rq, find, lo, hi) : transferPoints(rq, find, lo, hi);
114
+ }
115
+ const BANDS_MAX = Math.floor((CURVE_POINTS_MAX - 2) / 3);
116
+ const BAND_ROLES = ['band_type', 'band_freq', 'band_gain', 'band_q', 'band_on'];
117
+ function eqPoints(rq, find, lo, hi) {
118
+ let nb = 0;
119
+ for (const t of rq.terms) {
120
+ if (!BAND_ROLES.includes(t.role))
121
+ continue;
122
+ const i = t.index ?? 0;
123
+ if (i >= BANDS_MAX)
124
+ return { code: 'pointsRange' };
125
+ nb = Math.max(nb, i + 1);
126
+ }
127
+ const bands = [];
128
+ const origin = [];
129
+ for (let b = 0; b < nb; b++) {
130
+ const type = find('band_type', b), freq = find('band_freq', b), gain = find('band_gain', b), q = find('band_q', b), on = find('band_on', b);
131
+ if (!type && !freq && !gain && !q && !on)
132
+ continue;
133
+ if (!freq)
134
+ return { code: 'termMissing' };
135
+ const shape = type?.shape ?? freq.shape;
136
+ if (shape === undefined)
137
+ return { code: 'termMissing' };
138
+ const kind = SHAPE_TYPE[shape];
139
+ if (!kind)
140
+ continue; // off, or a choice with no shape (`-`): not drawn
141
+ const gains = kind === 'bell' || kind === 'lowshelf' || kind === 'highshelf';
142
+ if (gains && !gain)
143
+ return { code: 'termMissing' };
144
+ if (on && valueOf(on) === 0)
145
+ continue;
146
+ bands.push({ type: kind, freq: valueOf(freq), gainDb: gains && gain ? valueOf(gain) : 0,
147
+ // no Q control: the declared default (EQ_BAND_DEFAULTS), clamped for the shape as the kernel clamps it
148
+ q: q ? valueOf(q) : Math.fround(clampEqQFor(kind === 'notch' ? 'notch' : 'bell', EQ_BAND_DEFAULTS.q)) });
149
+ origin.push(freq.origin ?? 'user');
150
+ }
151
+ const pass = [];
152
+ let present = false;
153
+ for (const [type, pre] of [['highpass', 'hpf'], ['lowpass', 'lpf']]) {
154
+ const fr = find(`${pre}_freq`), sl = find(`${pre}_slope`), on = find(`${pre}_on`);
155
+ if (!fr && !sl && !on)
156
+ continue;
157
+ present = true;
158
+ // the slope in dB per octave, from the role's declared enumeration or a dB control; a bare choice value is none
159
+ if (!fr || !sl || sl.unit !== 'db')
160
+ return { code: 'termMissing' };
161
+ const slope = valueOf(sl);
162
+ if (slope !== 12 && slope !== 24)
163
+ return { code: 'kindUnknown' };
164
+ if (on && valueOf(on) === 0)
165
+ continue;
166
+ const corner = valueOf(fr);
167
+ if (corner > 0)
168
+ for (const q of butterworthQs(slope === 24))
169
+ pass.push({ type, freq: corner, gainDb: 0, q });
170
+ }
171
+ if (nb === 0 && !present)
172
+ return { code: 'termMissing' };
173
+ const drawn = [...bands, ...pass];
174
+ if (rq.points + 3 * drawn.length > CURVE_POINTS_MAX)
175
+ return { code: 'pointsRange' };
176
+ const top = (rq.sampleRate / 2) * 0.999;
177
+ const xs = curveFreqs(drawn, lo, hi, rq.points);
178
+ const sum = (set) => xs.map((x) => ({ x, y: magnitudeSumDb(set, x < top ? x : top, rq.sampleRate, CURVE_EXTENTS.eqFloorDb) }));
179
+ const marks = bands.flatMap((b, i) => origin[i] === 'user' ? [] : [{ freq: b.freq, depthDb: magnitudeSumDb([b], b.freq, rq.sampleRate, CURVE_EXTENTS.eqFloorDb), q: b.q, origin: origin[i] }]);
180
+ const points = sum(drawn);
181
+ if (!rq.userOnly)
182
+ return { points, marks };
183
+ return { points, marks, userPoints: sum([...bands.filter((_, i) => origin[i] === 'user'), ...pass]) };
184
+ }
185
+ function transferPoints(rq, find, lo, hi) {
186
+ const below = rq.family === 'gate';
187
+ const thr = find('threshold'), ratio = find('ratio'), knee = find('knee'), makeup = find('makeup'), range = find('range');
188
+ const ks = find('knee_start'), ke = find('knee_end');
189
+ // a limiter has no ratio role: the dynamics form at ratio infinity, a hard corner at the threshold
190
+ const limiter = rq.family === 'limiter';
191
+ if (!thr || (!ratio && !limiter) || (below && !range) || (below && !ks !== !ke))
192
+ return { code: 'termMissing' };
193
+ const f = Math.fround;
194
+ const r = ratio ? f(valueOf(ratio)) : Infinity, k = knee ? f(valueOf(knee)) : 0;
195
+ const rmin = f(below ? GATE_RATIO_RANGE.min : COMP_RATIO_RANGE.min);
196
+ const thresh = f(valueOf(thr));
197
+ let kneeDb = k < COMP_KNEE_DB_RANGE.min ? COMP_KNEE_DB_RANGE.min : k;
198
+ let shiftDb = 0;
199
+ let rangeDb = 0;
200
+ if (below && range) {
201
+ const rg = f(valueOf(range));
202
+ rangeDb = rg > GATE_RANGE_DB_RANGE.max ? GATE_RANGE_DB_RANGE.max : rg;
203
+ if (ks && ke)
204
+ ({ kneeDb, shiftDb } = gateKnee(thresh, valueOf(ks), valueOf(ke)));
205
+ }
206
+ const p = { mode: below ? 'below' : 'above', threshDb: thresh, ratio: r < rmin ? rmin : r, kneeDb, rangeDb };
207
+ const mk = makeup ? valueOf(makeup) : 0;
208
+ const n = rq.points;
209
+ const points = [];
210
+ for (let i = 0; i < n; i++) {
211
+ const lv = i + 1 === n ? hi : lo + ((hi - lo) * i) / (n - 1);
212
+ const g = below ? gaincompDbShifted(p, shiftDb, lv) : gaincompDb(p, lv);
213
+ points.push({ x: lv, y: lv + g + mk });
214
+ }
215
+ return rq.userOnly ? { points, marks: [], userPoints: points } : { points, marks: [] };
216
+ }
@@ -13,6 +13,95 @@ export declare const LOWPASS_HANDOVER: {
13
13
  readonly hiAtLoQ: 0.9;
14
14
  readonly hiAtPeakQ: 0.8;
15
15
  };
16
+ /** 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. @see data/kernels/eq.json */
17
+ export declare const EQ_BAND_DEFAULTS: {
18
+ readonly gainDb: 0;
19
+ readonly q: 1;
20
+ };
21
+ /** The deepest cut a band of an fbs-* origin may hold, in dB: the feedback corrector's own safety bound, never the operator EQ's ±15 (EQ_GAIN_RANGE), which stays the bound of every other origin. Declared here as one value until the vocabulary owner (omx-contract) carries it as its own word, a new ref beside EQ_GAIN_RANGE and never a widening of it; this item then becomes that ref, and no second copy of the number is ever written. (dB) @see data/kernels/fbs.json */
22
+ export declare const FBS_MAX_CUT_DB = -24;
23
+ /** How much deeper a live band's full depth goes each time regrowth re-clamps a release, in dB, down to FBS_MAX_CUT_DB: regrowth at a depth is the measurement that the depth was not enough. (dB) @see data/kernels/fbs.json */
24
+ export declare const FBS_RECLAMP_DEEPEN_DB = 3;
25
+ /** The most times the clean hold doubles after re-clamps: the hold before the first release stage is the channel's hold times 2^min(re-clamps, this). @see data/kernels/fbs.json */
26
+ export declare const FBS_RECLAMP_BACKOFF_CAP = 3;
27
+ /** How long one release stage's gain ramp takes, in ms, from the depth the band stands at to the stage's depth. (ms) @see data/kernels/fbs.json */
28
+ export declare const FBS_LIFT_RAMP_MS = 3000;
29
+ /** The equal steps one release ramp takes, FBS_LIFT_RAMP_MS / this apart: the last step lands the stage's depth exactly. @see data/kernels/fbs.json */
30
+ export declare const FBS_LIFT_RAMP_STEPS = 10;
31
+ /** Each release stage holds the planted depth times this ratio to the stage's power: every stage releases a third of what remains. @see data/kernels/fbs.json */
32
+ export declare const FBS_RELEASE_RATIO = 0.6666666666666666;
33
+ /** A release stage whose depth is shallower than this, in dB, removes the band instead of holding it: the cut is inaudible. (dB) @see data/kernels/fbs.json */
34
+ export declare const FBS_RELEASE_DONE_DB = 1;
35
+ /** How long a band listens at an eased stage, clean, before the next stage begins, in ms. (ms) @see data/kernels/fbs.json */
36
+ export declare const FBS_RELEASE_DWELL_MS = 8000;
37
+ /** The lowest frequency a confirmed ring is planted at, in Hz; one below it is refused as rejected-edge, never clamped onto the EQ's edge. (Hz) @see data/kernels/fbs.json */
38
+ export declare const FBS_RING_HZ_MIN = 60;
39
+ /** The highest frequency a confirmed ring is planted at, in Hz; one above it is refused as rejected-edge. (Hz) @see data/kernels/fbs.json */
40
+ export declare const FBS_RING_HZ_MAX = 16000;
41
+ /** Two readings are the same ringing frequency within this many Hz, or FBS_SAME_RING_SHARE of the higher one, whichever is wider. (Hz) @see data/kernels/fbs.json */
42
+ export declare const FBS_SAME_RING_HZ = 3;
43
+ /** The share of the higher frequency within which two readings are the same ringing frequency, when it is wider than FBS_SAME_RING_HZ: FFT bin spacing grows with frequency. @see data/kernels/fbs.json */
44
+ export declare const FBS_SAME_RING_SHARE = 0.015;
45
+ /** The clean hold, in seconds, before a live band's first release stage. (s) @see data/kernels/fbs.json */
46
+ export declare const FBS_DEFAULT_HOLD_SECONDS = 5;
47
+ /** How many bands one corrector may plant when nobody set its cap; a full pool plants nothing and says pool-full. @see data/kernels/fbs.json */
48
+ export declare const FBS_DEFAULT_MAX_AUTO_BANDS = 6;
49
+ /** The merge radius, in cents: a confirmed ring this close to a band already planted is that band's find, and plants nothing. (cents) @see data/kernels/fbs.json */
50
+ export declare const FBS_DEFAULT_MERGE_CENTS = 100;
51
+ /** How long a fully released find's re-clamp count is remembered, in seconds, so a chronic ringer re-plants with its doubled holds. (s) @see data/kernels/fbs.json */
52
+ export declare const FBS_DEFAULT_COOLDOWN_SECONDS = 600;
53
+ /** 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. @see data/kernels/fbs.json */
54
+ export declare const FBS_DEFAULT_NOTCH_Q = 116;
55
+ /** Released finds the ring memory holds at once. The console's list is unbounded and expires by FBS_DEFAULT_COOLDOWN_SECONDS; a full memory here forgets its oldest entry first, and counts it. @see data/kernels/fbs.json */
56
+ export declare const FBS_RING_MEMORY_SLOTS = 32;
57
+ /** The displays a panel description draws from a closed form, the kinds omx_curve_points answers: an EQ's magnitude response, x in Hz and y in dB, and a dynamics stage's static transfer curve, input dB to output dB. The wire spells each id in snake case (`eq_response`, `transfer_curve`), the description's own display word. @see data/primitives/curve.json */
58
+ export declare const CURVE_DISPLAYS: readonly ["eqResponse", "transferCurve"];
59
+ /** The panel families omx_curve_points draws, by the description's family word. `eq` draws an eq_response; the other four a transfer_curve, the family choosing the gain computer's mode: `dynamics`, `limiter` and `deesser` act ABOVE the threshold, `gate` BELOW it with the shifted knee of the gate's knee range. Any other family has no closed form here. @see data/primitives/curve.json */
60
+ export declare const CURVE_FAMILIES: readonly ["eq", "dynamics", "limiter", "gate", "deesser"];
61
+ /** The roles of a panel description a curve request reads, by the description's role word (the wire spells each id in snake case: `band_freq`, `knee_start`). The EQ's indexed band roles and head filters, and the gain computer's terms. A term of any other role is no term of the curve. @see data/primitives/curve.json */
62
+ export declare const CURVE_ROLES: readonly ["bandType", "bandFreq", "bandGain", "bandQ", "bandOn", "hpfFreq", "hpfSlope", "hpfOn", "lpfFreq", "lpfSlope", "lpfOn", "threshold", "ratio", "knee", "makeup", "range", "kneeStart", "kneeEnd"];
63
+ /** The units a curve term's value may come in, the param-contract extension's unit tokens the served description gives a role. omx_curve_points converts once: `coef` to dB for a gain, `oct` to Q for a bandwidth, `khz` to Hz; a level in `db`, `dbfs`, `dbu` or `lufs` is read as dB from the threshold's own scale; `none` is a plain number, a switch or a choice; `ratio` a ratio. A unit its role does not accept is refused. @see data/primitives/curve.json */
64
+ export declare const CURVE_UNITS: readonly ["hz", "khz", "db", "dbfs", "dbu", "lufs", "coef", "oct", "none", "ratio"];
65
+ /** The shape of an EQ band, as the caller resolves it from the description: a `band_type` role's `shapes` list indexed by the control's choice value, or the fixed `shape` of a band no control selects. `off` is a band that is not drawn, and so is `unshaped`, spelt `-` on the wire, a choice value with no shape. The others design the band through omx_eq_design: a bell, the two shelves, the pass filters, the notch, and the first- and second-order allpasses, which draw what they run, 0 dB. @see data/primitives/curve.json */
66
+ export declare const CURVE_SHAPES: readonly ["off", "bell", "lowshelf", "highshelf", "highpass", "lowpass", "notch", "unshaped", "allpass1", "allpass2"];
67
+ /** Where an EQ band of a curve request comes from, as the console's strip EQ state says it (its BAND_ORIGINS): `user`, the operator's own band, and the bands a planter put there, `fbsFixed` and `fbsLive` (the feedback corrector's ring-out and show pools), `hrp` (the harmonic resonance processor's cuts) and `roomMode` (a room mode's correction), spelt `user`, `fbs-fixed`, `fbs-live`, `hrp` and `room-mode` on the wire. The sum ignores it: every drawn band is in the curve. A band whose origin is not `user` is answered as a mark (its frequency, depth, Q and origin), and a request that asks for it also gets the curve over the `user` bands alone. A plugin's descriptor carries no origin: a request with none draws no marks. @see data/primitives/curve.json */
68
+ export declare const CURVE_ORIGINS: readonly ["user", "fbsFixed", "fbsLive", "hrp", "roomMode"];
69
+ /** What omx_curve_points answers in its code: `ok`, a curve; `kindUnknown`, a kind, family or head filter slope with no closed form; `termMissing`, a term the closed form has no default for is not in the request (a band's frequency and shape, a bell's or shelf's gain, a head filter's corner, and its slope unless it comes from the role's declared enumeration as dB per octave; threshold and ratio for dynamics and the de-esser, threshold for a limiter (its ratio is infinity, a hard corner), threshold, range and ratio for a gate; both ends of a gate's knee range or neither); `unitRefused`, a term in a unit its role does not accept; `valueNotFinite`, a value, the rate or an extent that is not finite, a rate that is not positive; `pointsRange`, grid points outside 2 to CURVE_POINTS_MAX (for an EQ, with three anchors a drawn band), a band index past what fits, or extents that bound nothing; `room`, a caller's buffers too small. Where omx-dsp declares a default the curve takes it: a band's Q is EQ_BAND_DEFAULTS q, clamped for its shape as clampEqQFor clamps it. No curve is drawn from a guess. @see data/primitives/curve.json */
70
+ export declare const CURVE_CODES: readonly ["ok", "kindUnknown", "termMissing", "unitRefused", "valueNotFinite", "pointsRange", "room"];
71
+ /** The most points one curve carries, an EQ's band anchors included: a reply of x with four significant digits and y to 0.01 dB, about 13 bytes a point, fits the 4096-byte line a host's reply is built in. Every caller reads it here; it moves with that buffer, in this one place. @see data/primitives/curve.json */
72
+ export declare const CURVE_POINTS_MAX = 256;
73
+ /** A curve's default extents, where a request gives none: an eq_response from `freqMinHz` to the lower of `freqMaxHz` and `nyquistFraction` of Nyquist, each band's dB floored at `eqFloorDb` so a notch's null stays finite (the console's EQ draws to -120 dB); a transfer_curve's input from `levelMinDb` to `levelMaxDb`. Hz and dB. @see data/primitives/curve.json */
74
+ export declare const CURVE_EXTENTS: {
75
+ readonly freqMinHz: 20;
76
+ readonly freqMaxHz: 20000;
77
+ readonly nyquistFraction: 0.999;
78
+ readonly eqFloorDb: -120;
79
+ readonly levelMinDb: -80;
80
+ readonly levelMaxDb: 0;
81
+ };
82
+ /** The floor of the linear-to-decibel conversions (`omx_lin_to_db`, `omx_lin_to_db_poly`): an amplitude below `lin` reads as `lin`, so silence is `db` dB, never -inf or NaN. `db` is `20·log10(lin)`. A design constant, not a control. @see data/primitives/lin-to-db-floor.json */
83
+ export declare const LIN_TO_DB_FLOOR: {
84
+ readonly lin: 1e-9;
85
+ readonly db: -180;
86
+ };
87
+ /** A meter's view law, the same on every surface: the fill is linear in dB from `floorDb` (dBFS, an empty display) to 0 dBFS (a full one); a stepped display (an LED ladder) holds its count until the level moves `stepHysteresis` of a step past the count's own rounding edge; the peak marker falls `fallDbPerSecond` dB for each second elapsed at the view's draw tick, whatever the tick's rate, silence included. A host reads a plugin's held meter at `hostReadHz`; the console streams meter values at `streamHz`. dBFS, a fraction of a step, dB per second and Hz. @see data/primitives/meter.json */
88
+ export declare const METER_VIEW: {
89
+ readonly floorDb: -60;
90
+ readonly stepHysteresis: 0.25;
91
+ readonly fallDbPerSecond: 18;
92
+ readonly hostReadHz: 50;
93
+ readonly streamHz: 20;
94
+ };
95
+ /** What a meter measures, as a plugin's descriptor and the panel description spell it: `level`, a signal level (its measurement one of METER_MEASUREMENTS), and `gainReduction`, the gain a dynamics stage takes off, dB at or below 0. The wire spells them `level` and `gain-reduction`. `rms`, `vu` and `ppm` are not kinds. @see data/primitives/meter.json */
96
+ export declare const METER_KINDS: readonly ["level", "gainReduction"];
97
+ /** The measurement of a `level` meter: the sample peak (`peak`, dBFS), the true peak (`truePeak`, dBTP), and the loudness measures of omx_loudness.h: momentary, short-term and integrated loudness (`lufsM`, `lufsS`, `lufsI`, LUFS) and the loudness range (`lra`, LU). The wire spells them `peak`, `true-peak`, `lufs-m`, `lufs-s`, `lufs-i` and `lra`. A foreign meter's measurement is null, never guessed. @see data/primitives/meter.json */
98
+ export declare const METER_MEASUREMENTS: readonly ["peak", "truePeak", "lufsM", "lufsS", "lufsI", "lra"];
99
+ /** The unit a meter's values are in, spelt as its label: dBFS for a sample peak, dBTP for a true peak, LUFS for a loudness, LU for a loudness range, dB for a gain reduction. The scale a surface draws is captioned with it. @see data/primitives/meter.json */
100
+ export declare const METER_UNITS: readonly ["dbfs", "dbtp", "lufs", "lu", "db"];
101
+ /** The labels of METER_UNITS, in its order. @see data/primitives/meter.json */
102
+ export declare const METER_UNITS_LABELS: readonly ["dBFS", "dBTP", "LUFS", "LU", "dB"];
103
+ /** What a subscriber that decimates a meter stream keeps of the frames it drops, one per kind in METER_KINDS order, as the engine's slot keeps between two reads: a `level` keeps the `peak` (the largest value), a `gainReduction` the `deepest` (the smallest dB). Never the newest. @see data/primitives/meter.json */
104
+ export declare const METER_DECIMATION: readonly ["peak", "deepest"];
16
105
  /** The Q (resonance) extent of a parametric band. @see data/kernels/eq.json */
17
106
  export declare const EQ_Q_RANGE: {
18
107
  readonly min: 0.3;
@@ -23,3 +112,53 @@ export declare const EQ_NOTCH_Q_RANGE: {
23
112
  readonly min: 0.3;
24
113
  readonly max: 116;
25
114
  };
115
+ /** 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. @see data/kernels/eq.json */
116
+ export declare const EQ_GAIN_RANGE: {
117
+ readonly min: -15;
118
+ readonly max: 15;
119
+ };
120
+ /** The frequency band a parametric band can be dragged across. @see data/kernels/eq.json */
121
+ export declare const EQ_FREQ_RANGE: {
122
+ readonly min: 20;
123
+ readonly max: 20000;
124
+ };
125
+ /** How many bands this corrector may plant at once (its pool), from one to the EQ's band count. An integer control. @see data/kernels/fbs.json */
126
+ export declare const FBS_BANDS_RANGE: {
127
+ readonly min: 1;
128
+ readonly max: 24;
129
+ };
130
+ /** The clean hold, in seconds, before a live band's first release stage. @see data/kernels/fbs.json */
131
+ export declare const FBS_HOLD_RANGE: {
132
+ readonly min: 1;
133
+ readonly max: 300;
134
+ };
135
+ /** The merge radius, in cents. An integer control. @see data/kernels/fbs.json */
136
+ export declare const FBS_MERGE_RANGE: {
137
+ readonly min: 0;
138
+ readonly max: 600;
139
+ };
140
+ /** How long a released find's escalation is remembered, in seconds. An integer control. @see data/kernels/fbs.json */
141
+ export declare const FBS_COOLDOWN_RANGE: {
142
+ readonly min: 0;
143
+ readonly max: 7200;
144
+ };
145
+ /** The travel of ratio, from its table COMP_LIMITS. @see data/kernels/comp.json */
146
+ export declare const COMP_RATIO_RANGE: {
147
+ readonly min: 1;
148
+ readonly max: 20;
149
+ };
150
+ /** The travel of kneeDb, from its table COMP_LIMITS. @see data/kernels/comp.json */
151
+ export declare const COMP_KNEE_DB_RANGE: {
152
+ readonly min: 0;
153
+ readonly max: 24;
154
+ };
155
+ /** The travel of ratio, from its table GATE_LIMITS. @see data/kernels/gate.json */
156
+ export declare const GATE_RATIO_RANGE: {
157
+ readonly min: 1;
158
+ readonly max: 100;
159
+ };
160
+ /** The travel of rangeDb, from its table GATE_LIMITS. @see data/kernels/gate.json */
161
+ export declare const GATE_RANGE_DB_RANGE: {
162
+ readonly min: -90;
163
+ readonly max: 0;
164
+ };
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
2
  // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
- // GENERATED by ts/tools/definitions.mjs from omx-dsp data/kernels/ - do not edit.
3
+ // GENERATED by ts/tools/definitions.mjs from omx-dsp data/ - do not edit.
4
4
  /** 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. (Hz) @see data/kernels/rta.json */
5
5
  export const RTA_TARGET_BIN_HZ = 5.9;
6
6
  /** The largest RTA FFT the native ring holds — generated into `omx_contract_limits.h` as `OMX_RTA_FFT_SIZE_MAX`. @see data/kernels/rta.json */
@@ -16,7 +16,116 @@ export const LOWPASS_HANDOVER = {
16
16
  hiAtLoQ: 0.9,
17
17
  hiAtPeakQ: 0.8,
18
18
  };
19
+ /** 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. @see data/kernels/eq.json */
20
+ export const EQ_BAND_DEFAULTS = {
21
+ gainDb: 0,
22
+ q: 1,
23
+ };
24
+ /** The deepest cut a band of an fbs-* origin may hold, in dB: the feedback corrector's own safety bound, never the operator EQ's ±15 (EQ_GAIN_RANGE), which stays the bound of every other origin. Declared here as one value until the vocabulary owner (omx-contract) carries it as its own word, a new ref beside EQ_GAIN_RANGE and never a widening of it; this item then becomes that ref, and no second copy of the number is ever written. (dB) @see data/kernels/fbs.json */
25
+ export const FBS_MAX_CUT_DB = -24;
26
+ /** How much deeper a live band's full depth goes each time regrowth re-clamps a release, in dB, down to FBS_MAX_CUT_DB: regrowth at a depth is the measurement that the depth was not enough. (dB) @see data/kernels/fbs.json */
27
+ export const FBS_RECLAMP_DEEPEN_DB = 3;
28
+ /** The most times the clean hold doubles after re-clamps: the hold before the first release stage is the channel's hold times 2^min(re-clamps, this). @see data/kernels/fbs.json */
29
+ export const FBS_RECLAMP_BACKOFF_CAP = 3;
30
+ /** How long one release stage's gain ramp takes, in ms, from the depth the band stands at to the stage's depth. (ms) @see data/kernels/fbs.json */
31
+ export const FBS_LIFT_RAMP_MS = 3000;
32
+ /** The equal steps one release ramp takes, FBS_LIFT_RAMP_MS / this apart: the last step lands the stage's depth exactly. @see data/kernels/fbs.json */
33
+ export const FBS_LIFT_RAMP_STEPS = 10;
34
+ /** Each release stage holds the planted depth times this ratio to the stage's power: every stage releases a third of what remains. @see data/kernels/fbs.json */
35
+ export const FBS_RELEASE_RATIO = 0.6666666666666666;
36
+ /** A release stage whose depth is shallower than this, in dB, removes the band instead of holding it: the cut is inaudible. (dB) @see data/kernels/fbs.json */
37
+ export const FBS_RELEASE_DONE_DB = 1;
38
+ /** How long a band listens at an eased stage, clean, before the next stage begins, in ms. (ms) @see data/kernels/fbs.json */
39
+ export const FBS_RELEASE_DWELL_MS = 8000;
40
+ /** The lowest frequency a confirmed ring is planted at, in Hz; one below it is refused as rejected-edge, never clamped onto the EQ's edge. (Hz) @see data/kernels/fbs.json */
41
+ export const FBS_RING_HZ_MIN = 60;
42
+ /** The highest frequency a confirmed ring is planted at, in Hz; one above it is refused as rejected-edge. (Hz) @see data/kernels/fbs.json */
43
+ export const FBS_RING_HZ_MAX = 16000;
44
+ /** Two readings are the same ringing frequency within this many Hz, or FBS_SAME_RING_SHARE of the higher one, whichever is wider. (Hz) @see data/kernels/fbs.json */
45
+ export const FBS_SAME_RING_HZ = 3;
46
+ /** The share of the higher frequency within which two readings are the same ringing frequency, when it is wider than FBS_SAME_RING_HZ: FFT bin spacing grows with frequency. @see data/kernels/fbs.json */
47
+ export const FBS_SAME_RING_SHARE = 0.015;
48
+ /** The clean hold, in seconds, before a live band's first release stage. (s) @see data/kernels/fbs.json */
49
+ export const FBS_DEFAULT_HOLD_SECONDS = 5;
50
+ /** How many bands one corrector may plant when nobody set its cap; a full pool plants nothing and says pool-full. @see data/kernels/fbs.json */
51
+ export const FBS_DEFAULT_MAX_AUTO_BANDS = 6;
52
+ /** The merge radius, in cents: a confirmed ring this close to a band already planted is that band's find, and plants nothing. (cents) @see data/kernels/fbs.json */
53
+ export const FBS_DEFAULT_MERGE_CENTS = 100;
54
+ /** How long a fully released find's re-clamp count is remembered, in seconds, so a chronic ringer re-plants with its doubled holds. (s) @see data/kernels/fbs.json */
55
+ export const FBS_DEFAULT_COOLDOWN_SECONDS = 600;
56
+ /** 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. @see data/kernels/fbs.json */
57
+ export const FBS_DEFAULT_NOTCH_Q = 116;
58
+ /** Released finds the ring memory holds at once. The console's list is unbounded and expires by FBS_DEFAULT_COOLDOWN_SECONDS; a full memory here forgets its oldest entry first, and counts it. @see data/kernels/fbs.json */
59
+ export const FBS_RING_MEMORY_SLOTS = 32;
60
+ /** The displays a panel description draws from a closed form, the kinds omx_curve_points answers: an EQ's magnitude response, x in Hz and y in dB, and a dynamics stage's static transfer curve, input dB to output dB. The wire spells each id in snake case (`eq_response`, `transfer_curve`), the description's own display word. @see data/primitives/curve.json */
61
+ export const CURVE_DISPLAYS = ['eqResponse', 'transferCurve'];
62
+ /** The panel families omx_curve_points draws, by the description's family word. `eq` draws an eq_response; the other four a transfer_curve, the family choosing the gain computer's mode: `dynamics`, `limiter` and `deesser` act ABOVE the threshold, `gate` BELOW it with the shifted knee of the gate's knee range. Any other family has no closed form here. @see data/primitives/curve.json */
63
+ export const CURVE_FAMILIES = ['eq', 'dynamics', 'limiter', 'gate', 'deesser'];
64
+ /** The roles of a panel description a curve request reads, by the description's role word (the wire spells each id in snake case: `band_freq`, `knee_start`). The EQ's indexed band roles and head filters, and the gain computer's terms. A term of any other role is no term of the curve. @see data/primitives/curve.json */
65
+ export const CURVE_ROLES = ['bandType', 'bandFreq', 'bandGain', 'bandQ', 'bandOn', 'hpfFreq', 'hpfSlope', 'hpfOn', 'lpfFreq', 'lpfSlope', 'lpfOn', 'threshold', 'ratio', 'knee', 'makeup', 'range', 'kneeStart', 'kneeEnd'];
66
+ /** The units a curve term's value may come in, the param-contract extension's unit tokens the served description gives a role. omx_curve_points converts once: `coef` to dB for a gain, `oct` to Q for a bandwidth, `khz` to Hz; a level in `db`, `dbfs`, `dbu` or `lufs` is read as dB from the threshold's own scale; `none` is a plain number, a switch or a choice; `ratio` a ratio. A unit its role does not accept is refused. @see data/primitives/curve.json */
67
+ export const CURVE_UNITS = ['hz', 'khz', 'db', 'dbfs', 'dbu', 'lufs', 'coef', 'oct', 'none', 'ratio'];
68
+ /** The shape of an EQ band, as the caller resolves it from the description: a `band_type` role's `shapes` list indexed by the control's choice value, or the fixed `shape` of a band no control selects. `off` is a band that is not drawn, and so is `unshaped`, spelt `-` on the wire, a choice value with no shape. The others design the band through omx_eq_design: a bell, the two shelves, the pass filters, the notch, and the first- and second-order allpasses, which draw what they run, 0 dB. @see data/primitives/curve.json */
69
+ export const CURVE_SHAPES = ['off', 'bell', 'lowshelf', 'highshelf', 'highpass', 'lowpass', 'notch', 'unshaped', 'allpass1', 'allpass2'];
70
+ /** Where an EQ band of a curve request comes from, as the console's strip EQ state says it (its BAND_ORIGINS): `user`, the operator's own band, and the bands a planter put there, `fbsFixed` and `fbsLive` (the feedback corrector's ring-out and show pools), `hrp` (the harmonic resonance processor's cuts) and `roomMode` (a room mode's correction), spelt `user`, `fbs-fixed`, `fbs-live`, `hrp` and `room-mode` on the wire. The sum ignores it: every drawn band is in the curve. A band whose origin is not `user` is answered as a mark (its frequency, depth, Q and origin), and a request that asks for it also gets the curve over the `user` bands alone. A plugin's descriptor carries no origin: a request with none draws no marks. @see data/primitives/curve.json */
71
+ export const CURVE_ORIGINS = ['user', 'fbsFixed', 'fbsLive', 'hrp', 'roomMode'];
72
+ /** What omx_curve_points answers in its code: `ok`, a curve; `kindUnknown`, a kind, family or head filter slope with no closed form; `termMissing`, a term the closed form has no default for is not in the request (a band's frequency and shape, a bell's or shelf's gain, a head filter's corner, and its slope unless it comes from the role's declared enumeration as dB per octave; threshold and ratio for dynamics and the de-esser, threshold for a limiter (its ratio is infinity, a hard corner), threshold, range and ratio for a gate; both ends of a gate's knee range or neither); `unitRefused`, a term in a unit its role does not accept; `valueNotFinite`, a value, the rate or an extent that is not finite, a rate that is not positive; `pointsRange`, grid points outside 2 to CURVE_POINTS_MAX (for an EQ, with three anchors a drawn band), a band index past what fits, or extents that bound nothing; `room`, a caller's buffers too small. Where omx-dsp declares a default the curve takes it: a band's Q is EQ_BAND_DEFAULTS q, clamped for its shape as clampEqQFor clamps it. No curve is drawn from a guess. @see data/primitives/curve.json */
73
+ export const CURVE_CODES = ['ok', 'kindUnknown', 'termMissing', 'unitRefused', 'valueNotFinite', 'pointsRange', 'room'];
74
+ /** The most points one curve carries, an EQ's band anchors included: a reply of x with four significant digits and y to 0.01 dB, about 13 bytes a point, fits the 4096-byte line a host's reply is built in. Every caller reads it here; it moves with that buffer, in this one place. @see data/primitives/curve.json */
75
+ export const CURVE_POINTS_MAX = 256;
76
+ /** A curve's default extents, where a request gives none: an eq_response from `freqMinHz` to the lower of `freqMaxHz` and `nyquistFraction` of Nyquist, each band's dB floored at `eqFloorDb` so a notch's null stays finite (the console's EQ draws to -120 dB); a transfer_curve's input from `levelMinDb` to `levelMaxDb`. Hz and dB. @see data/primitives/curve.json */
77
+ export const CURVE_EXTENTS = {
78
+ freqMinHz: 20,
79
+ freqMaxHz: 20000,
80
+ nyquistFraction: 0.999,
81
+ eqFloorDb: -120,
82
+ levelMinDb: -80,
83
+ levelMaxDb: 0,
84
+ };
85
+ /** The floor of the linear-to-decibel conversions (`omx_lin_to_db`, `omx_lin_to_db_poly`): an amplitude below `lin` reads as `lin`, so silence is `db` dB, never -inf or NaN. `db` is `20·log10(lin)`. A design constant, not a control. @see data/primitives/lin-to-db-floor.json */
86
+ export const LIN_TO_DB_FLOOR = {
87
+ lin: 1e-9,
88
+ db: -180,
89
+ };
90
+ /** A meter's view law, the same on every surface: the fill is linear in dB from `floorDb` (dBFS, an empty display) to 0 dBFS (a full one); a stepped display (an LED ladder) holds its count until the level moves `stepHysteresis` of a step past the count's own rounding edge; the peak marker falls `fallDbPerSecond` dB for each second elapsed at the view's draw tick, whatever the tick's rate, silence included. A host reads a plugin's held meter at `hostReadHz`; the console streams meter values at `streamHz`. dBFS, a fraction of a step, dB per second and Hz. @see data/primitives/meter.json */
91
+ export const METER_VIEW = {
92
+ floorDb: -60,
93
+ stepHysteresis: 0.25,
94
+ fallDbPerSecond: 18,
95
+ hostReadHz: 50,
96
+ streamHz: 20,
97
+ };
98
+ /** What a meter measures, as a plugin's descriptor and the panel description spell it: `level`, a signal level (its measurement one of METER_MEASUREMENTS), and `gainReduction`, the gain a dynamics stage takes off, dB at or below 0. The wire spells them `level` and `gain-reduction`. `rms`, `vu` and `ppm` are not kinds. @see data/primitives/meter.json */
99
+ export const METER_KINDS = ['level', 'gainReduction'];
100
+ /** The measurement of a `level` meter: the sample peak (`peak`, dBFS), the true peak (`truePeak`, dBTP), and the loudness measures of omx_loudness.h: momentary, short-term and integrated loudness (`lufsM`, `lufsS`, `lufsI`, LUFS) and the loudness range (`lra`, LU). The wire spells them `peak`, `true-peak`, `lufs-m`, `lufs-s`, `lufs-i` and `lra`. A foreign meter's measurement is null, never guessed. @see data/primitives/meter.json */
101
+ export const METER_MEASUREMENTS = ['peak', 'truePeak', 'lufsM', 'lufsS', 'lufsI', 'lra'];
102
+ /** The unit a meter's values are in, spelt as its label: dBFS for a sample peak, dBTP for a true peak, LUFS for a loudness, LU for a loudness range, dB for a gain reduction. The scale a surface draws is captioned with it. @see data/primitives/meter.json */
103
+ export const METER_UNITS = ['dbfs', 'dbtp', 'lufs', 'lu', 'db'];
104
+ /** The labels of METER_UNITS, in its order. @see data/primitives/meter.json */
105
+ export const METER_UNITS_LABELS = ['dBFS', 'dBTP', 'LUFS', 'LU', 'dB'];
106
+ /** What a subscriber that decimates a meter stream keeps of the frames it drops, one per kind in METER_KINDS order, as the engine's slot keeps between two reads: a `level` keeps the `peak` (the largest value), a `gainReduction` the `deepest` (the smallest dB). Never the newest. @see data/primitives/meter.json */
107
+ export const METER_DECIMATION = ['peak', 'deepest'];
19
108
  /** The Q (resonance) extent of a parametric band. @see data/kernels/eq.json */
20
109
  export const EQ_Q_RANGE = { min: 0.3, max: 8 };
21
110
  /** The Q extent of a NOTCH band. Wider than EQ_Q_RANGE on purpose (#116, operator decision): an ultra-narrow surgical cut is the notch shape's whole point — the FBS ring-out pool plants dbx-AFS-style ring killers at Q ≈ 116 (~1/80 octave), inaudible on program yet fatal to a single ringing frequency. Gain-carrying shapes stay bounded at EQ_Q_RANGE — a Q-116 bell is a resonator, not an EQ move. @see data/kernels/eq.json */
22
111
  export const EQ_NOTCH_Q_RANGE = { min: 0.3, max: 116 };
112
+ /** 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. @see data/kernels/eq.json */
113
+ export const EQ_GAIN_RANGE = { min: -15, max: 15 };
114
+ /** The frequency band a parametric band can be dragged across. @see data/kernels/eq.json */
115
+ export const EQ_FREQ_RANGE = { min: 20, max: 20000 };
116
+ /** How many bands this corrector may plant at once (its pool), from one to the EQ's band count. An integer control. @see data/kernels/fbs.json */
117
+ export const FBS_BANDS_RANGE = { min: 1, max: 24 };
118
+ /** The clean hold, in seconds, before a live band's first release stage. @see data/kernels/fbs.json */
119
+ export const FBS_HOLD_RANGE = { min: 1, max: 300 };
120
+ /** The merge radius, in cents. An integer control. @see data/kernels/fbs.json */
121
+ export const FBS_MERGE_RANGE = { min: 0, max: 600 };
122
+ /** How long a released find's escalation is remembered, in seconds. An integer control. @see data/kernels/fbs.json */
123
+ export const FBS_COOLDOWN_RANGE = { min: 0, max: 7200 };
124
+ /** The travel of ratio, from its table COMP_LIMITS. @see data/kernels/comp.json */
125
+ export const COMP_RATIO_RANGE = { min: 1, max: 20 };
126
+ /** The travel of kneeDb, from its table COMP_LIMITS. @see data/kernels/comp.json */
127
+ export const COMP_KNEE_DB_RANGE = { min: 0, max: 24 };
128
+ /** The travel of ratio, from its table GATE_LIMITS. @see data/kernels/gate.json */
129
+ export const GATE_RATIO_RANGE = { min: 1, max: 100 };
130
+ /** The travel of rangeDb, from its table GATE_LIMITS. @see data/kernels/gate.json */
131
+ export const GATE_RANGE_DB_RANGE = { min: -90, max: 0 };