@openmixer/omx-dsp 0.31.0 → 0.32.1
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/dist/curve.d.ts +17 -0
- package/dist/curve.js +26 -18
- package/dist/definitions.d.ts +94 -3
- package/dist/definitions.js +76 -3
- package/dist/fbs-place.d.ts +125 -0
- package/dist/fbs-place.js +280 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/meter.d.ts +77 -0
- package/dist/meter.js +114 -0
- package/oracle/ts-twin-oracle.c +137 -1
- package/package.json +3 -3
- package/src/curve.ts +50 -16
- package/src/definitions.ts +108 -3
- package/src/fbs-place.ts +367 -0
- package/src/index.ts +48 -0
- package/src/meter.ts +147 -0
package/oracle/ts-twin-oracle.c
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
#include <omxdsp/analysis/omx_room.h>
|
|
32
32
|
#include <omxdsp/analysis/omx_rta.h>
|
|
33
33
|
#include <omxdsp/fx/omx_eq_instance.h>
|
|
34
|
+
#include <omxdsp/mix/omx_meter_view.h>
|
|
34
35
|
#include <omxdsp/mix/omx_pan_law.h>
|
|
35
36
|
#include <omxdsp/omx_balance_law.h>
|
|
36
37
|
#include <omxdsp/omx_contract.h>
|
|
@@ -151,9 +152,10 @@ static int h_curve_points(const double *in, int n, double *out) {
|
|
|
151
152
|
terms[i].value = t[2];
|
|
152
153
|
terms[i].unit = (enum omx_curve_units)(int)t[3];
|
|
153
154
|
terms[i].shape = (int)t[4];
|
|
155
|
+
terms[i].origin = OMX_CURVE_ORIGINS_USER;
|
|
154
156
|
}
|
|
155
157
|
const struct omx_curve_request rq = {(enum omx_curve_displays)(int)in[0], (enum omx_curve_families)(int)in[1], in[2],
|
|
156
|
-
(uint32_t)in[3], in[4] != 0.0, in[5], in[6], terms, (uint32_t)nt};
|
|
158
|
+
(uint32_t)in[3], in[4] != 0.0, in[5], in[6], terms, (uint32_t)nt, 0};
|
|
157
159
|
double x[OMX_CURVE_POINTS_MAX], y[OMX_CURVE_POINTS_MAX];
|
|
158
160
|
int code = -1;
|
|
159
161
|
const uint32_t cnt = omx_curve_points(&rq, x, y, OMX_CURVE_POINTS_MAX, &code);
|
|
@@ -166,6 +168,49 @@ static int h_curve_points(const double *in, int n, double *out) {
|
|
|
166
168
|
return 2 + 2 * (int)cnt;
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
/* curve_points_marked: kind family rate points has_extents x_min x_max user_only nterms
|
|
172
|
+
* { role index value unit shape origin }*nterms
|
|
173
|
+
* -> code count { x y }*count nmarks { freq depth_db q origin }*nmarks [{ y_user }*count] */
|
|
174
|
+
static int h_curve_points_marked(const double *in, int n, double *out) {
|
|
175
|
+
if (n < 9) return -1;
|
|
176
|
+
const int nt = (int)in[8];
|
|
177
|
+
if (nt < 0 || nt > 512 || n != 9 + 6 * nt) return -1;
|
|
178
|
+
static struct omx_curve_term terms[512];
|
|
179
|
+
for (int i = 0; i < nt; i++) {
|
|
180
|
+
const double *t = in + 9 + 6 * i;
|
|
181
|
+
terms[i].role = (enum omx_curve_roles)(int)t[0];
|
|
182
|
+
terms[i].index = (uint32_t)t[1];
|
|
183
|
+
terms[i].value = t[2];
|
|
184
|
+
terms[i].unit = (enum omx_curve_units)(int)t[3];
|
|
185
|
+
terms[i].shape = (int)t[4];
|
|
186
|
+
terms[i].origin = (enum omx_curve_origins)(int)t[5];
|
|
187
|
+
}
|
|
188
|
+
const struct omx_curve_request rq = {(enum omx_curve_displays)(int)in[0], (enum omx_curve_families)(int)in[1], in[2],
|
|
189
|
+
(uint32_t)in[3], in[4] != 0.0, in[5], in[6], terms, (uint32_t)nt, in[7] != 0.0};
|
|
190
|
+
double x[OMX_CURVE_POINTS_MAX], y[OMX_CURVE_POINTS_MAX], yu[OMX_CURVE_POINTS_MAX];
|
|
191
|
+
struct omx_curve_mark marks[OMX_CURVE_BANDS_MAX];
|
|
192
|
+
struct omx_curve_extra ex = {yu, marks, OMX_CURVE_BANDS_MAX, 0u};
|
|
193
|
+
int code = -1;
|
|
194
|
+
const uint32_t cnt = omx_curve_points_marked(&rq, x, y, OMX_CURVE_POINTS_MAX, &ex, &code);
|
|
195
|
+
int k = 0;
|
|
196
|
+
out[k++] = code;
|
|
197
|
+
out[k++] = cnt;
|
|
198
|
+
for (uint32_t i = 0; i < cnt; i++) {
|
|
199
|
+
out[k++] = x[i];
|
|
200
|
+
out[k++] = y[i];
|
|
201
|
+
}
|
|
202
|
+
out[k++] = cnt ? ex.nmarks : 0;
|
|
203
|
+
for (uint32_t i = 0; cnt && i < ex.nmarks; i++) {
|
|
204
|
+
out[k++] = marks[i].freq_hz;
|
|
205
|
+
out[k++] = marks[i].depth_db;
|
|
206
|
+
out[k++] = marks[i].q;
|
|
207
|
+
out[k++] = marks[i].origin;
|
|
208
|
+
}
|
|
209
|
+
if (cnt && rq.user_only)
|
|
210
|
+
for (uint32_t i = 0; i < cnt; i++) out[k++] = yu[i];
|
|
211
|
+
return k;
|
|
212
|
+
}
|
|
213
|
+
|
|
169
214
|
/* gaincomp_db: mode thresh_db ratio knee_db range_db level_db -> dB (float, the per-sample form) */
|
|
170
215
|
static int h_gaincomp_db(const double *in, int n, double *out) {
|
|
171
216
|
if (n != 6) return -1;
|
|
@@ -359,6 +404,86 @@ static int h_room_mode_set_stable(const double *in, int n, double *out) {
|
|
|
359
404
|
return 1;
|
|
360
405
|
}
|
|
361
406
|
|
|
407
|
+
/* meter_view_fill_db: db floor_db -> fill */
|
|
408
|
+
static int h_meter_view_fill_db(const double *in, int n, double *out) {
|
|
409
|
+
if (n != 2) return -1;
|
|
410
|
+
out[0] = omx_meter_view_fill_db(in[0], in[1]);
|
|
411
|
+
return 1;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* meter_view_fill: level floor_db -> fill */
|
|
415
|
+
static int h_meter_view_fill(const double *in, int n, double *out) {
|
|
416
|
+
if (n != 2) return -1;
|
|
417
|
+
out[0] = omx_meter_view_fill(in[0], in[1]);
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* meter_view_step: fall_db_per_s floor_db { fill dt_s }*k -> the marker at each tick, from a fresh view */
|
|
422
|
+
static int h_meter_view_step(const double *in, int n, double *out) {
|
|
423
|
+
if (n < 2 || (n - 2) % 2 != 0) return -1;
|
|
424
|
+
struct omx_meter_view v = {0};
|
|
425
|
+
const int k = (n - 2) / 2;
|
|
426
|
+
for (int i = 0; i < k; i++) out[i] = omx_meter_view_step(&v, in[2 + 2 * i], in[3 + 2 * i], in[0], in[1]);
|
|
427
|
+
return k;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* meter_view_clip: { clip clear }*k -> the clip word at each tick (a clear is taken before the tick's clip) */
|
|
431
|
+
static int h_meter_view_clip(const double *in, int n, double *out) {
|
|
432
|
+
if (n % 2 != 0) return -1;
|
|
433
|
+
struct omx_meter_view v = {0};
|
|
434
|
+
for (int i = 0; i < n / 2; i++) {
|
|
435
|
+
if (in[2 * i + 1] != 0.0) omx_meter_view_clip_clear(&v);
|
|
436
|
+
out[i] = omx_meter_view_clip(&v, in[2 * i] != 0.0);
|
|
437
|
+
}
|
|
438
|
+
return n / 2;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* meter_view_ladder: steps hysteresis prev fill -> count (prev negative: none) */
|
|
442
|
+
static int h_meter_view_ladder(const double *in, int n, double *out) {
|
|
443
|
+
if (n != 4) return -1;
|
|
444
|
+
out[0] = omx_meter_view_ladder((int32_t)in[2], in[3], (uint32_t)in[0], in[1]);
|
|
445
|
+
return 1;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* meter_view_bar_step: steps prev level -> count */
|
|
449
|
+
static int h_meter_view_bar_step(const double *in, int n, double *out) {
|
|
450
|
+
if (n != 3) return -1;
|
|
451
|
+
out[0] = omx_meter_view_bar_step((int32_t)in[1], in[2], (uint32_t)in[0]);
|
|
452
|
+
return 1;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/* meter_view_decimate: kind kept value -> kept */
|
|
456
|
+
static int h_meter_view_decimate(const double *in, int n, double *out) {
|
|
457
|
+
if (n != 3) return -1;
|
|
458
|
+
out[0] = omx_meter_view_decimate((enum omx_meter_kinds)(int)in[0], in[1], in[2]);
|
|
459
|
+
return 1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/* meter_view_gr: rate pole { deepest_db dt_s }*k -> the reading at each tick, from no reduction */
|
|
463
|
+
static int h_meter_view_gr(const double *in, int n, double *out) {
|
|
464
|
+
if (n < 2 || (n - 2) % 2 != 0) return -1;
|
|
465
|
+
struct omx_meter_gr m = {0.0f, 0.0f};
|
|
466
|
+
const int k = (n - 2) / 2;
|
|
467
|
+
for (int i = 0; i < k; i++)
|
|
468
|
+
out[i] = (double)omx_meter_view_gr(&m, (float)in[2 + 2 * i], in[3 + 2 * i], (float)in[0], (float)in[1]);
|
|
469
|
+
return k;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* meter_view_unit_of: kind measurement -> unit (METER_UNITS position; the count for none) */
|
|
473
|
+
static int h_meter_view_unit_of(const double *in, int n, double *out) {
|
|
474
|
+
if (n != 2) return -1;
|
|
475
|
+
out[0] = (double)omx_meter_view_unit_of((enum omx_meter_kinds)(int)in[0], (enum omx_meter_measurements)(int)in[1]);
|
|
476
|
+
return 1;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* meter_gr_hold_ms: -> OMX_METER_GR_HOLD_MS */
|
|
480
|
+
static int h_meter_gr_hold_ms(const double *in, int n, double *out) {
|
|
481
|
+
(void)in;
|
|
482
|
+
if (n != 0) return -1;
|
|
483
|
+
out[0] = (double)OMX_METER_GR_HOLD_MS;
|
|
484
|
+
return 1;
|
|
485
|
+
}
|
|
486
|
+
|
|
362
487
|
static const struct {
|
|
363
488
|
const char *name;
|
|
364
489
|
handler fn;
|
|
@@ -372,6 +497,7 @@ static const struct {
|
|
|
372
497
|
{"eq_band_magnitude_db", h_eq_band_magnitude_db},
|
|
373
498
|
{"eq_magnitude_sum_db", h_eq_magnitude_sum_db},
|
|
374
499
|
{"curve_points", h_curve_points},
|
|
500
|
+
{"curve_points_marked", h_curve_points_marked},
|
|
375
501
|
{"gaincomp_db", h_gaincomp_db},
|
|
376
502
|
{"gaincomp_db_shifted", h_gaincomp_db_shifted},
|
|
377
503
|
{"gate_knee", h_gate_knee},
|
|
@@ -390,6 +516,16 @@ static const struct {
|
|
|
390
516
|
{"room_mode_eq_proposals", h_room_mode_eq_proposals},
|
|
391
517
|
{"room_mode_residual_db", h_room_mode_residual_db},
|
|
392
518
|
{"room_mode_set_stable", h_room_mode_set_stable},
|
|
519
|
+
{"meter_view_fill_db", h_meter_view_fill_db},
|
|
520
|
+
{"meter_view_fill", h_meter_view_fill},
|
|
521
|
+
{"meter_view_step", h_meter_view_step},
|
|
522
|
+
{"meter_view_clip", h_meter_view_clip},
|
|
523
|
+
{"meter_view_ladder", h_meter_view_ladder},
|
|
524
|
+
{"meter_view_bar_step", h_meter_view_bar_step},
|
|
525
|
+
{"meter_view_decimate", h_meter_view_decimate},
|
|
526
|
+
{"meter_view_gr", h_meter_view_gr},
|
|
527
|
+
{"meter_view_unit_of", h_meter_view_unit_of},
|
|
528
|
+
{"meter_gr_hold_ms", h_meter_gr_hold_ms},
|
|
393
529
|
};
|
|
394
530
|
|
|
395
531
|
/* The kernels the table answers, for a test that asks "is the oracle really here". */
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmixer/omx-dsp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.1",
|
|
4
4
|
"description": "The TypeScript twins of omx-dsp kernels, each pinned to its C kernel by an agreement test, and the oracle that pins them",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
7
7
|
"author": "Pau Aliagas <linuxnow@gmail.com>",
|
|
8
|
-
"homepage": "https://github.com/
|
|
8
|
+
"homepage": "https://github.com/Open-Mixer/omx-dsp",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
11
|
+
"url": "git+https://github.com/Open-Mixer/omx-dsp.git",
|
|
12
12
|
"directory": "ts"
|
|
13
13
|
},
|
|
14
14
|
"exports": {
|
package/src/curve.ts
CHANGED
|
@@ -13,15 +13,17 @@ import {
|
|
|
13
13
|
CURVE_DISPLAYS,
|
|
14
14
|
CURVE_EXTENTS,
|
|
15
15
|
CURVE_FAMILIES,
|
|
16
|
+
CURVE_ORIGINS,
|
|
16
17
|
CURVE_POINTS_MAX,
|
|
17
18
|
CURVE_ROLES,
|
|
18
19
|
CURVE_SHAPES,
|
|
19
20
|
CURVE_UNITS,
|
|
21
|
+
EQ_BAND_DEFAULTS,
|
|
20
22
|
GATE_RANGE_DB_RANGE,
|
|
21
23
|
GATE_RATIO_RANGE,
|
|
22
24
|
LIN_TO_DB_FLOOR,
|
|
23
25
|
} from './definitions.js';
|
|
24
|
-
import { magnitudeSumDb, octavesToQ, type BiquadBand, type BiquadType } from './eq.js';
|
|
26
|
+
import { clampEqQFor, magnitudeSumDb, octavesToQ, type BiquadBand, type BiquadType } from './eq.js';
|
|
25
27
|
import { gaincompDb, gaincompDbShifted, gateKnee } from './gaincomp.js';
|
|
26
28
|
|
|
27
29
|
/** A set id as the description spells it: `eqResponse` is `eq_response`; the unshaped band is `-`. */
|
|
@@ -34,6 +36,8 @@ export const CURVE_WORDS = {
|
|
|
34
36
|
roles: CURVE_ROLES.map(wordOf),
|
|
35
37
|
units: CURVE_UNITS.map(wordOf),
|
|
36
38
|
shapes: CURVE_SHAPES.map(wordOf),
|
|
39
|
+
/** fbs-fixed and fbs-live: the origins are spelt with a hyphen */
|
|
40
|
+
origins: CURVE_ORIGINS.map((id) => id.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)),
|
|
37
41
|
} as const;
|
|
38
42
|
|
|
39
43
|
/** What curvePoints answers when it draws nothing: a CURVE_CODES id other than `ok`. */
|
|
@@ -51,6 +55,8 @@ export interface CurveTerm {
|
|
|
51
55
|
readonly unit: string;
|
|
52
56
|
/** For `band_type`, the shape its value selects (from the role's `shapes`); for `band_freq`, the fixed shape. */
|
|
53
57
|
readonly shape?: string;
|
|
58
|
+
/** Where the band comes from: `user` (the default), `fbs-fixed` or `fbs-live`. A band's is its band_freq term's. */
|
|
59
|
+
readonly origin?: string;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/** A curve request: one display of the description, bound. */
|
|
@@ -67,6 +73,16 @@ export interface CurveRequest {
|
|
|
67
73
|
readonly extents?: { readonly min: number; readonly max: number };
|
|
68
74
|
/** The terms; a later term of one role and index replaces an earlier. */
|
|
69
75
|
readonly terms: readonly CurveTerm[];
|
|
76
|
+
/** Also answer the curve over the `user` bands alone, at the same x. */
|
|
77
|
+
readonly userOnly?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A band whose origin is not `user`: its centre (Hz), its own magnitude there (dB), its Q and its origin. */
|
|
81
|
+
export interface CurveMark {
|
|
82
|
+
readonly freq: number;
|
|
83
|
+
readonly depthDb: number;
|
|
84
|
+
readonly q: number;
|
|
85
|
+
readonly origin: string;
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
/** One point: Hz and dB for an EQ, input dB and output dB for a transfer curve. */
|
|
@@ -76,7 +92,15 @@ export interface CurvePoint {
|
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
/** The answer: the points, ascending in x, or the code of what is missing or refused. */
|
|
79
|
-
export type CurveAnswer =
|
|
95
|
+
export type CurveAnswer =
|
|
96
|
+
| {
|
|
97
|
+
readonly points: readonly CurvePoint[];
|
|
98
|
+
/** One per drawn band whose origin is not `user`, in band order; none for a transfer curve. */
|
|
99
|
+
readonly marks: readonly CurveMark[];
|
|
100
|
+
/** With `userOnly`: the curve over the user bands (and the head filters) alone, at the same x. */
|
|
101
|
+
readonly userPoints?: readonly CurvePoint[];
|
|
102
|
+
}
|
|
103
|
+
| { readonly code: CurveCode };
|
|
80
104
|
|
|
81
105
|
/** The units each role converts from (omx_curve_unit_accepted). */
|
|
82
106
|
const ACCEPTS: Readonly<Record<string, readonly string[]>> = {
|
|
@@ -108,6 +132,7 @@ const valueOf = (t: CurveTerm): number => {
|
|
|
108
132
|
|
|
109
133
|
const SHAPE_TYPE: Readonly<Record<string, BiquadType>> = {
|
|
110
134
|
bell: 'bell', lowshelf: 'lowshelf', highshelf: 'highshelf', highpass: 'highpass', lowpass: 'lowpass', notch: 'notch',
|
|
135
|
+
allpass1: 'allpass1', allpass2: 'allpass2',
|
|
111
136
|
};
|
|
112
137
|
|
|
113
138
|
/** 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
|
|
@@ -148,7 +173,8 @@ export function curvePoints(rq: CurveRequest): CurveAnswer {
|
|
|
148
173
|
if (!Number.isFinite(rq.sampleRate) || !(rq.sampleRate > 0) || (rq.extents && !(Number.isFinite(rq.extents.min) && Number.isFinite(rq.extents.max))))
|
|
149
174
|
return { code: 'valueNotFinite' };
|
|
150
175
|
if (rq.terms.some((t) => !Number.isFinite(t.value))) return { code: 'valueNotFinite' };
|
|
151
|
-
if (rq.terms.some((t) => !ACCEPTS[t.role]?.includes(t.unit)
|
|
176
|
+
if (rq.terms.some((t) => !ACCEPTS[t.role]?.includes(t.unit) || !CURVE_WORDS.origins.includes(t.origin ?? 'user')))
|
|
177
|
+
return { code: 'unitRefused' };
|
|
152
178
|
let lo: number, hi: number;
|
|
153
179
|
if (rq.extents) [lo, hi] = [rq.extents.min, rq.extents.max];
|
|
154
180
|
else if (eq) [lo, hi] = [CURVE_EXTENTS.freqMinHz, Math.min(CURVE_EXTENTS.freqMaxHz, (CURVE_EXTENTS.nyquistFraction * rq.sampleRate) / 2)];
|
|
@@ -176,20 +202,22 @@ function eqPoints(rq: CurveRequest, find: Find, lo: number, hi: number): CurveAn
|
|
|
176
202
|
nb = Math.max(nb, i + 1);
|
|
177
203
|
}
|
|
178
204
|
const bands: BiquadBand[] = [];
|
|
205
|
+
const origin: string[] = [];
|
|
179
206
|
for (let b = 0; b < nb; b++) {
|
|
180
207
|
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);
|
|
181
208
|
if (!type && !freq && !gain && !q && !on) continue;
|
|
182
209
|
if (!freq) return { code: 'termMissing' };
|
|
183
210
|
const shape = type?.shape ?? freq.shape;
|
|
184
211
|
if (shape === undefined) return { code: 'termMissing' };
|
|
185
|
-
if (shape === 'off') continue;
|
|
186
212
|
const kind = SHAPE_TYPE[shape];
|
|
187
|
-
if (!kind)
|
|
188
|
-
if (!q) return { code: 'termMissing' };
|
|
213
|
+
if (!kind) continue; // off, or a choice with no shape (`-`): not drawn
|
|
189
214
|
const gains = kind === 'bell' || kind === 'lowshelf' || kind === 'highshelf';
|
|
190
215
|
if (gains && !gain) return { code: 'termMissing' };
|
|
191
216
|
if (on && valueOf(on) === 0) continue;
|
|
192
|
-
bands.push({ type: kind, freq: valueOf(freq), gainDb: gains && gain ? valueOf(gain) : 0,
|
|
217
|
+
bands.push({ type: kind, freq: valueOf(freq), gainDb: gains && gain ? valueOf(gain) : 0,
|
|
218
|
+
// no Q control: the declared default (EQ_BAND_DEFAULTS), clamped for the shape as the kernel clamps it
|
|
219
|
+
q: q ? valueOf(q) : Math.fround(clampEqQFor(kind === 'notch' ? 'notch' : 'bell', EQ_BAND_DEFAULTS.q)) });
|
|
220
|
+
origin.push(freq.origin ?? 'user');
|
|
193
221
|
}
|
|
194
222
|
const pass: BiquadBand[] = [];
|
|
195
223
|
let present = false;
|
|
@@ -197,7 +225,8 @@ function eqPoints(rq: CurveRequest, find: Find, lo: number, hi: number): CurveAn
|
|
|
197
225
|
const fr = find(`${pre}_freq`), sl = find(`${pre}_slope`), on = find(`${pre}_on`);
|
|
198
226
|
if (!fr && !sl && !on) continue;
|
|
199
227
|
present = true;
|
|
200
|
-
|
|
228
|
+
// the slope in dB per octave, from the role's declared enumeration or a dB control; a bare choice value is none
|
|
229
|
+
if (!fr || !sl || sl.unit !== 'db') return { code: 'termMissing' };
|
|
201
230
|
const slope = valueOf(sl);
|
|
202
231
|
if (slope !== 12 && slope !== 24) return { code: 'kindUnknown' };
|
|
203
232
|
if (on && valueOf(on) === 0) continue;
|
|
@@ -208,20 +237,25 @@ function eqPoints(rq: CurveRequest, find: Find, lo: number, hi: number): CurveAn
|
|
|
208
237
|
const drawn = [...bands, ...pass];
|
|
209
238
|
if (rq.points + 3 * drawn.length > CURVE_POINTS_MAX) return { code: 'pointsRange' };
|
|
210
239
|
const top = (rq.sampleRate / 2) * 0.999;
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
y: magnitudeSumDb(
|
|
214
|
-
|
|
215
|
-
|
|
240
|
+
const xs = curveFreqs(drawn, lo, hi, rq.points);
|
|
241
|
+
const sum = (set: readonly BiquadBand[]) =>
|
|
242
|
+
xs.map((x) => ({ x, y: magnitudeSumDb(set, x < top ? x : top, rq.sampleRate, CURVE_EXTENTS.eqFloorDb) }));
|
|
243
|
+
const marks = bands.flatMap((b, i) =>
|
|
244
|
+
origin[i] === 'user' ? [] : [{ freq: b.freq, depthDb: magnitudeSumDb([b], b.freq, rq.sampleRate, CURVE_EXTENTS.eqFloorDb), q: b.q, origin: origin[i] }]);
|
|
245
|
+
const points = sum(drawn);
|
|
246
|
+
if (!rq.userOnly) return { points, marks };
|
|
247
|
+
return { points, marks, userPoints: sum([...bands.filter((_, i) => origin[i] === 'user'), ...pass]) };
|
|
216
248
|
}
|
|
217
249
|
|
|
218
250
|
function transferPoints(rq: CurveRequest, find: Find, lo: number, hi: number): CurveAnswer {
|
|
219
251
|
const below = rq.family === 'gate';
|
|
220
252
|
const thr = find('threshold'), ratio = find('ratio'), knee = find('knee'), makeup = find('makeup'), range = find('range');
|
|
221
253
|
const ks = find('knee_start'), ke = find('knee_end');
|
|
222
|
-
|
|
254
|
+
// a limiter has no ratio role: the dynamics form at ratio infinity, a hard corner at the threshold
|
|
255
|
+
const limiter = rq.family === 'limiter';
|
|
256
|
+
if (!thr || (!ratio && !limiter) || (below && !range) || (below && !ks !== !ke)) return { code: 'termMissing' };
|
|
223
257
|
const f = Math.fround;
|
|
224
|
-
const r = f(valueOf(ratio)), k = knee ? f(valueOf(knee)) : 0;
|
|
258
|
+
const r = ratio ? f(valueOf(ratio)) : Infinity, k = knee ? f(valueOf(knee)) : 0;
|
|
225
259
|
const rmin = f(below ? GATE_RATIO_RANGE.min : COMP_RATIO_RANGE.min);
|
|
226
260
|
const thresh = f(valueOf(thr));
|
|
227
261
|
let kneeDb = k < COMP_KNEE_DB_RANGE.min ? COMP_KNEE_DB_RANGE.min : k;
|
|
@@ -241,5 +275,5 @@ function transferPoints(rq: CurveRequest, find: Find, lo: number, hi: number): C
|
|
|
241
275
|
const g = below ? gaincompDbShifted(p, shiftDb, lv) : gaincompDb(p, lv);
|
|
242
276
|
points.push({ x: lv, y: lv + g + mk });
|
|
243
277
|
}
|
|
244
|
-
return { points };
|
|
278
|
+
return rq.userOnly ? { points, marks: [], userPoints: points } : { points, marks: [] };
|
|
245
279
|
}
|
package/src/definitions.ts
CHANGED
|
@@ -20,6 +20,66 @@ export const LOWPASS_HANDOVER = {
|
|
|
20
20
|
hiAtPeakQ: 0.8,
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
|
+
/** 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 */
|
|
24
|
+
export const EQ_BAND_DEFAULTS = {
|
|
25
|
+
gainDb: 0,
|
|
26
|
+
q: 1,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
/** 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 */
|
|
30
|
+
export const FBS_MAX_CUT_DB = -24;
|
|
31
|
+
|
|
32
|
+
/** 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 */
|
|
33
|
+
export const FBS_RECLAMP_DEEPEN_DB = 3;
|
|
34
|
+
|
|
35
|
+
/** 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 */
|
|
36
|
+
export const FBS_RECLAMP_BACKOFF_CAP = 3;
|
|
37
|
+
|
|
38
|
+
/** 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 */
|
|
39
|
+
export const FBS_LIFT_RAMP_MS = 3000;
|
|
40
|
+
|
|
41
|
+
/** 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 */
|
|
42
|
+
export const FBS_LIFT_RAMP_STEPS = 10;
|
|
43
|
+
|
|
44
|
+
/** 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 */
|
|
45
|
+
export const FBS_RELEASE_RATIO = 0.6666666666666666;
|
|
46
|
+
|
|
47
|
+
/** 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 */
|
|
48
|
+
export const FBS_RELEASE_DONE_DB = 1;
|
|
49
|
+
|
|
50
|
+
/** How long a band listens at an eased stage, clean, before the next stage begins, in ms. (ms) @see data/kernels/fbs.json */
|
|
51
|
+
export const FBS_RELEASE_DWELL_MS = 8000;
|
|
52
|
+
|
|
53
|
+
/** 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 */
|
|
54
|
+
export const FBS_RING_HZ_MIN = 60;
|
|
55
|
+
|
|
56
|
+
/** 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 */
|
|
57
|
+
export const FBS_RING_HZ_MAX = 16000;
|
|
58
|
+
|
|
59
|
+
/** 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 */
|
|
60
|
+
export const FBS_SAME_RING_HZ = 3;
|
|
61
|
+
|
|
62
|
+
/** 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 */
|
|
63
|
+
export const FBS_SAME_RING_SHARE = 0.015;
|
|
64
|
+
|
|
65
|
+
/** The clean hold, in seconds, before a live band's first release stage. (s) @see data/kernels/fbs.json */
|
|
66
|
+
export const FBS_DEFAULT_HOLD_SECONDS = 5;
|
|
67
|
+
|
|
68
|
+
/** 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 */
|
|
69
|
+
export const FBS_DEFAULT_MAX_AUTO_BANDS = 6;
|
|
70
|
+
|
|
71
|
+
/** 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 */
|
|
72
|
+
export const FBS_DEFAULT_MERGE_CENTS = 100;
|
|
73
|
+
|
|
74
|
+
/** 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 */
|
|
75
|
+
export const FBS_DEFAULT_COOLDOWN_SECONDS = 600;
|
|
76
|
+
|
|
77
|
+
/** 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 */
|
|
78
|
+
export const FBS_DEFAULT_NOTCH_Q = 116;
|
|
79
|
+
|
|
80
|
+
/** 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 */
|
|
81
|
+
export const FBS_RING_MEMORY_SLOTS = 32;
|
|
82
|
+
|
|
23
83
|
/** 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 */
|
|
24
84
|
export const CURVE_DISPLAYS = ['eqResponse', 'transferCurve'] as const;
|
|
25
85
|
|
|
@@ -32,10 +92,13 @@ export const CURVE_ROLES = ['bandType', 'bandFreq', 'bandGain', 'bandQ', 'bandOn
|
|
|
32
92
|
/** 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 */
|
|
33
93
|
export const CURVE_UNITS = ['hz', 'khz', 'db', 'dbfs', 'dbu', 'lufs', 'coef', 'oct', 'none', 'ratio'] as const;
|
|
34
94
|
|
|
35
|
-
/** 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
|
|
36
|
-
export const CURVE_SHAPES = ['off', 'bell', 'lowshelf', 'highshelf', 'highpass', 'lowpass', 'notch', 'unshaped'] as const;
|
|
95
|
+
/** 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 */
|
|
96
|
+
export const CURVE_SHAPES = ['off', 'bell', 'lowshelf', 'highshelf', 'highpass', 'lowpass', 'notch', 'unshaped', 'allpass1', 'allpass2'] as const;
|
|
37
97
|
|
|
38
|
-
/**
|
|
98
|
+
/** 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 */
|
|
99
|
+
export const CURVE_ORIGINS = ['user', 'fbsFixed', 'fbsLive', 'hrp', 'roomMode'] as const;
|
|
100
|
+
|
|
101
|
+
/** 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 */
|
|
39
102
|
export const CURVE_CODES = ['ok', 'kindUnknown', 'termMissing', 'unitRefused', 'valueNotFinite', 'pointsRange', 'room'] as const;
|
|
40
103
|
|
|
41
104
|
/** 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 */
|
|
@@ -57,12 +120,54 @@ export const LIN_TO_DB_FLOOR = {
|
|
|
57
120
|
db: -180,
|
|
58
121
|
} as const;
|
|
59
122
|
|
|
123
|
+
/** 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 */
|
|
124
|
+
export const METER_VIEW = {
|
|
125
|
+
floorDb: -60,
|
|
126
|
+
stepHysteresis: 0.25,
|
|
127
|
+
fallDbPerSecond: 18,
|
|
128
|
+
hostReadHz: 50,
|
|
129
|
+
streamHz: 20,
|
|
130
|
+
} as const;
|
|
131
|
+
|
|
132
|
+
/** 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 */
|
|
133
|
+
export const METER_KINDS = ['level', 'gainReduction'] as const;
|
|
134
|
+
|
|
135
|
+
/** 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 */
|
|
136
|
+
export const METER_MEASUREMENTS = ['peak', 'truePeak', 'lufsM', 'lufsS', 'lufsI', 'lra'] as const;
|
|
137
|
+
|
|
138
|
+
/** 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 */
|
|
139
|
+
export const METER_UNITS = ['dbfs', 'dbtp', 'lufs', 'lu', 'db'] as const;
|
|
140
|
+
|
|
141
|
+
/** The labels of METER_UNITS, in its order. @see data/primitives/meter.json */
|
|
142
|
+
export const METER_UNITS_LABELS = ['dBFS', 'dBTP', 'LUFS', 'LU', 'dB'] as const;
|
|
143
|
+
|
|
144
|
+
/** 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 */
|
|
145
|
+
export const METER_DECIMATION = ['peak', 'deepest'] as const;
|
|
146
|
+
|
|
60
147
|
/** The Q (resonance) extent of a parametric band. @see data/kernels/eq.json */
|
|
61
148
|
export const EQ_Q_RANGE = { min: 0.3, max: 8 } as const;
|
|
62
149
|
|
|
63
150
|
/** 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 */
|
|
64
151
|
export const EQ_NOTCH_Q_RANGE = { min: 0.3, max: 116 } as const;
|
|
65
152
|
|
|
153
|
+
/** 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 */
|
|
154
|
+
export const EQ_GAIN_RANGE = { min: -15, max: 15 } as const;
|
|
155
|
+
|
|
156
|
+
/** The frequency band a parametric band can be dragged across. @see data/kernels/eq.json */
|
|
157
|
+
export const EQ_FREQ_RANGE = { min: 20, max: 20000 } as const;
|
|
158
|
+
|
|
159
|
+
/** 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 */
|
|
160
|
+
export const FBS_BANDS_RANGE = { min: 1, max: 24 } as const;
|
|
161
|
+
|
|
162
|
+
/** The clean hold, in seconds, before a live band's first release stage. @see data/kernels/fbs.json */
|
|
163
|
+
export const FBS_HOLD_RANGE = { min: 1, max: 300 } as const;
|
|
164
|
+
|
|
165
|
+
/** The merge radius, in cents. An integer control. @see data/kernels/fbs.json */
|
|
166
|
+
export const FBS_MERGE_RANGE = { min: 0, max: 600 } as const;
|
|
167
|
+
|
|
168
|
+
/** How long a released find's escalation is remembered, in seconds. An integer control. @see data/kernels/fbs.json */
|
|
169
|
+
export const FBS_COOLDOWN_RANGE = { min: 0, max: 7200 } as const;
|
|
170
|
+
|
|
66
171
|
/** The travel of ratio, from its table COMP_LIMITS. @see data/kernels/comp.json */
|
|
67
172
|
export const COMP_RATIO_RANGE = { min: 1, max: 20 } as const;
|
|
68
173
|
|