@openmixer/omx-contract 1.3.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -2
- package/bin/omx-contract.mjs +14 -3
- package/data/kernels/balance.json +18 -14
- package/data/kernels/band_dyn.json +132 -0
- package/data/kernels/chorus.json +87 -78
- package/data/kernels/comp.json +67 -29
- package/data/kernels/deesser.json +114 -94
- package/data/kernels/delay.json +67 -57
- package/data/kernels/drive.json +118 -107
- package/data/kernels/eq.json +282 -267
- package/data/kernels/fbs.json +144 -142
- package/data/kernels/flanger.json +74 -66
- package/data/kernels/gate.json +76 -29
- package/data/kernels/geq.json +32 -27
- package/data/kernels/hrp.json +62 -56
- package/data/kernels/limiter.json +26 -9
- package/data/kernels/mixmatrix.json +7 -5
- package/data/kernels/phaser.json +110 -106
- package/data/kernels/pitch.json +35 -17
- package/data/kernels/reverb.json +176 -159
- package/data/kernels/rotary.json +189 -186
- package/data/kernels/rta.json +13 -11
- package/data/kernels/transient.json +64 -36
- package/data/kernels/tremolo.json +59 -57
- package/dist/ts/omx-contract.d.ts +157 -67
- package/dist/ts/omx-contract.js +211 -118
- package/include/omxcontract/omx_contract_limits.h +35 -0
- package/lib/data.mjs +134 -3
- package/lib/fmt.mjs +22 -2
- package/lib/validate.mjs +43 -2
- package/package.json +1 -1
- package/render/json.mjs +6 -2
- package/share/omx-contract/omx-contract.json +865 -128
package/dist/ts/omx-contract.js
CHANGED
|
@@ -13,6 +13,98 @@ export const PAN_RANGE = {
|
|
|
13
13
|
"unit": ""
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The side of the threshold the band acts on. Above acts when the band is loud, taming a resonance that only rings when pushed; below acts when it is quiet, lifting a band that drops out. The order is the integer the kernel reads: above is 0, below is 1.
|
|
18
|
+
* @see data/kernels/band_dyn.json
|
|
19
|
+
*/
|
|
20
|
+
export const BAND_DYN_MODES = [
|
|
21
|
+
"above",
|
|
22
|
+
"below"
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Where the band starts to act, in dB of the band's own level and not the strip's. It is the same fact the de-esser's threshold is, so it carries the same travel.
|
|
27
|
+
* @see data/kernels/band_dyn.json
|
|
28
|
+
*/
|
|
29
|
+
export const BAND_DYN_THRESHOLD_RANGE = {
|
|
30
|
+
"min": -60,
|
|
31
|
+
"max": 0,
|
|
32
|
+
"step": 0.5,
|
|
33
|
+
"unit": "dB",
|
|
34
|
+
"default": -30,
|
|
35
|
+
"defaultFrom": "desk"
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* How hard the band is held once it is past its threshold. 2:1 rather than the de-esser's 4:1, because a dynamic band is corrective, not a limiter for a sibilant.
|
|
40
|
+
* @see data/kernels/band_dyn.json
|
|
41
|
+
*/
|
|
42
|
+
export const BAND_DYN_RATIO_RANGE = {
|
|
43
|
+
"min": 1,
|
|
44
|
+
"max": 20,
|
|
45
|
+
"step": 0.1,
|
|
46
|
+
"unit": "",
|
|
47
|
+
"default": 2,
|
|
48
|
+
"defaultFrom": "desk"
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The most the band may add to its static gain, signed: below zero the deepest cut, above zero the largest boost. The ceiling of +12 keeps static plus dynamic inside the EQ's own travel. Zero makes the band inert without switching it off.
|
|
53
|
+
* @see data/kernels/band_dyn.json
|
|
54
|
+
*/
|
|
55
|
+
export const BAND_DYN_RANGE_RANGE = {
|
|
56
|
+
"min": -24,
|
|
57
|
+
"max": 12,
|
|
58
|
+
"step": 0.5,
|
|
59
|
+
"unit": "dB",
|
|
60
|
+
"default": -6,
|
|
61
|
+
"defaultFrom": "desk"
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The detector's attack, in ms. The travel stops at the oversampling floor, below which the band would need the compressor's delayed control path.
|
|
66
|
+
* @see data/kernels/band_dyn.json
|
|
67
|
+
*/
|
|
68
|
+
export const BAND_DYN_ATTACK_RANGE = {
|
|
69
|
+
"min": 0.5,
|
|
70
|
+
"max": 100,
|
|
71
|
+
"step": 0.1,
|
|
72
|
+
"unit": "ms",
|
|
73
|
+
"default": 5,
|
|
74
|
+
"defaultFrom": "desk"
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The detector's release, in ms. 100 ms is long enough not to modulate a 40 Hz band inside four cycles.
|
|
79
|
+
* @see data/kernels/band_dyn.json
|
|
80
|
+
*/
|
|
81
|
+
export const BAND_DYN_RELEASE_RANGE = {
|
|
82
|
+
"min": 5,
|
|
83
|
+
"max": 3000,
|
|
84
|
+
"step": 1,
|
|
85
|
+
"unit": "ms",
|
|
86
|
+
"default": 100,
|
|
87
|
+
"defaultFrom": "desk"
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The soft knee of the band's gain computer, in dB. One knee for the one band-dynamics word, shared by every stage that runs it, the de-esser included: a second spelling of 6 would be a second place where the reduction starts.
|
|
92
|
+
* @see data/kernels/band_dyn.json
|
|
93
|
+
*/
|
|
94
|
+
export const BAND_DYN_KNEE_DB = 6;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* How many dynamic bands a channel may hold. A dynamic band is an operator band, so the capacity is the operator's reserve, read and not typed again.
|
|
98
|
+
* @see data/kernels/band_dyn.json
|
|
99
|
+
*/
|
|
100
|
+
export const BAND_DYN_MAX_BANDS = 9;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The shortest attack, in ms, at which the compressor's detector does not alias. Below it the compressor engages its 4x control path, which adds 72 samples of latency; a zero-latency band has no such path, so its attack travel stops at this floor.
|
|
104
|
+
* @see data/kernels/band_dyn.json
|
|
105
|
+
*/
|
|
106
|
+
export const DETECTOR_OVERSAMPLE_AUTO_MS = 0.5;
|
|
107
|
+
|
|
16
108
|
/**
|
|
17
109
|
* The chorus's right-leg phase offset, in TURNS of its one oscillator (§2, §3a; the native-FX catalogue's M32 Dimension row). The right leg's voice k reads the LFO at k/N + spread: 0 is both legs one phase (the stage without the field, bit for bit), 0.25 quadrature, 0.5 the Dimension's opposed sweep. A fraction of a cycle carries no engineering symbol, so the unit is ''.
|
|
18
110
|
* @see data/kernels/chorus.json
|
|
@@ -261,12 +353,6 @@ export const DEESS_RELEASE_RANGE = {
|
|
|
261
353
|
"defaultFrom": "desk"
|
|
262
354
|
};
|
|
263
355
|
|
|
264
|
-
/**
|
|
265
|
-
* The knee, in dB — a CONSTANT of the stage and not a row field (`2026-09-22-native-deesser-stage.md` §4b): a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.
|
|
266
|
-
* @see data/kernels/deesser.json
|
|
267
|
-
*/
|
|
268
|
-
export const DEESS_KNEE_DB = 6;
|
|
269
|
-
|
|
270
356
|
/**
|
|
271
357
|
* The de-esser's modes, in the order of the integer the kernel reads: split attenuates the detected band only, wideband the whole signal while the sibilance lasts.
|
|
272
358
|
* @see data/kernels/deesser.json
|
|
@@ -276,6 +362,12 @@ export const DEESS_MODES = [
|
|
|
276
362
|
"wideband"
|
|
277
363
|
];
|
|
278
364
|
|
|
365
|
+
/**
|
|
366
|
+
* The knee, in dB — a CONSTANT of the stage and not a row field (`2026-09-22-native-deesser-stage.md` §4b): a de-esser without a soft knee latches audibly on the first sibilant of a phrase, and no other value of this number is a de-esser anyone would dial. The controller pushes it to the node and this is the same number; a second spelling would be a picture that disagrees with the audio about where the reduction starts.
|
|
367
|
+
* @see data/kernels/deesser.json
|
|
368
|
+
*/
|
|
369
|
+
export const DEESS_KNEE_DB = 6;
|
|
370
|
+
|
|
279
371
|
/**
|
|
280
372
|
* The FX delay's time travel. `max` is the NATIVE ring's ceiling — the same number as `OMX_FXDELAY_MAX_MS` in `omx_delay.h`, pinned to it by `output-delay-ceiling.test.ts`. Distinct from the per-route ALIGNMENT delay (`OUTPUT_DELAY_RANGE`, 1 s, `OMX_DELAY_MAX_MS`): one is a musical echo with feedback and mix, the other is a pure latency. Two facts, two rings, two ceilings — sharing the word "delay" is why they keep being confused for each other.
|
|
281
373
|
* @see data/kernels/delay.json
|
|
@@ -337,12 +429,6 @@ export const DELAY_MIX_RANGE = {
|
|
|
337
429
|
*/
|
|
338
430
|
export const FX_DELAY_PINGPONG_DEFAULT = false;
|
|
339
431
|
|
|
340
|
-
/**
|
|
341
|
-
* The bias at `character = +1`, in the SHAPER's own input units — `OMX_DRIVE_BIAS_MAX`. DERIVED, not chosen (§4b). In the saturating limit every knee tends to `sgn`, the output is a rectangular wave of duty `d = ½ + asin(b)/π`, and its `n`th harmonic goes as `sin(nπd)/n` — so `|H2|` is maximal at `d = ¾`, which is `b = sin(π/4) = 1/√2` exactly. Measured too: the H2-maximising bias for a full-scale sine at unity drive is 0.712 (soft), 0.658 (tape), 0.645 (tube), and this one value costs 0.000 / 0.035 / 0.088 dB of H2 against each curve's own optimum. ONE normalised bias for all three knees, not three.
|
|
342
|
-
* @see data/kernels/drive.json
|
|
343
|
-
*/
|
|
344
|
-
export const DRIVE_BIAS_MAX = 0.7071067811865476;
|
|
345
|
-
|
|
346
432
|
/**
|
|
347
433
|
* The PRE-CURVE gain, in dB. 0 dB is unity into the curve, so a stage switched on at its defaults changes almost nothing — which is what an `on` switch should mean. The ceiling is where every curve is saturating hard on any signal a console carries.
|
|
348
434
|
* @see data/kernels/drive.json
|
|
@@ -431,54 +517,10 @@ export const DRIVE_BANDS = [
|
|
|
431
517
|
];
|
|
432
518
|
|
|
433
519
|
/**
|
|
434
|
-
* The
|
|
435
|
-
* @see data/kernels/
|
|
436
|
-
*/
|
|
437
|
-
export const EQ_MAX_BANDS = 24;
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* The standard ISO 266 one-third-octave centre frequencies, 20 Hz–20 kHz (31 bands) — the classic 31-band graphic-EQ layout's centres.
|
|
441
|
-
* @see data/kernels/eq.json
|
|
442
|
-
*/
|
|
443
|
-
export const ISO_THIRD_OCTAVE_CENTRES_HZ = [
|
|
444
|
-
20,
|
|
445
|
-
25,
|
|
446
|
-
31.5,
|
|
447
|
-
40,
|
|
448
|
-
50,
|
|
449
|
-
63,
|
|
450
|
-
80,
|
|
451
|
-
100,
|
|
452
|
-
125,
|
|
453
|
-
160,
|
|
454
|
-
200,
|
|
455
|
-
250,
|
|
456
|
-
315,
|
|
457
|
-
400,
|
|
458
|
-
500,
|
|
459
|
-
630,
|
|
460
|
-
800,
|
|
461
|
-
1000,
|
|
462
|
-
1250,
|
|
463
|
-
1600,
|
|
464
|
-
2000,
|
|
465
|
-
2500,
|
|
466
|
-
3150,
|
|
467
|
-
4000,
|
|
468
|
-
5000,
|
|
469
|
-
6300,
|
|
470
|
-
8000,
|
|
471
|
-
10000,
|
|
472
|
-
12500,
|
|
473
|
-
16000,
|
|
474
|
-
20000
|
|
475
|
-
];
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* The graphic EQ's section count: DERIVED from ISO_THIRD_OCTAVE_CENTRES_HZ, never a second literal. Generated into `omx_contract_limits.h` as `OMX_GEQ_BANDS`, which sizes `mix_geq.h`'s section array — its OWN budget, never a claimant on EQ_MAX_BANDS (graphic-eq-31 spec §3, roster ruling 3).
|
|
479
|
-
* @see data/kernels/eq.json
|
|
520
|
+
* The bias at `character = +1`, in the SHAPER's own input units — `OMX_DRIVE_BIAS_MAX`. DERIVED, not chosen (§4b). In the saturating limit every knee tends to `sgn`, the output is a rectangular wave of duty `d = ½ + asin(b)/π`, and its `n`th harmonic goes as `sin(nπd)/n` — so `|H2|` is maximal at `d = ¾`, which is `b = sin(π/4) = 1/√2` exactly. Measured too: the H2-maximising bias for a full-scale sine at unity drive is 0.712 (soft), 0.658 (tape), 0.645 (tube), and this one value costs 0.000 / 0.035 / 0.088 dB of H2 against each curve's own optimum. ONE normalised bias for all three knees, not three.
|
|
521
|
+
* @see data/kernels/drive.json
|
|
480
522
|
*/
|
|
481
|
-
export const
|
|
523
|
+
export const DRIVE_BIAS_MAX = 0.7071067811865476;
|
|
482
524
|
|
|
483
525
|
/**
|
|
484
526
|
* The frequency band a parametric band can be dragged across.
|
|
@@ -546,12 +588,6 @@ export const LPF_FREQ_RANGE = {
|
|
|
546
588
|
"unit": "Hz"
|
|
547
589
|
};
|
|
548
590
|
|
|
549
|
-
/**
|
|
550
|
-
* The band slots kept for the operator's own hand: the reserve the feedback suppressor and the ring correction may never eat into. Nine is the working set a live EQ actually holds. The budget OPERATOR_EQ_BANDS_RESERVE + FBS_DEFAULT_MAX_AUTO_BANDS + HRP_DEFAULT_MAX_AUTO_BANDS (9 + 6 + 8 = 23) must fit EQ_MAX_BANDS, and `omx-contract validate` refuses data where it does not.
|
|
551
|
-
* @see data/kernels/eq.json
|
|
552
|
-
*/
|
|
553
|
-
export const OPERATOR_EQ_BANDS_RESERVE = 9;
|
|
554
|
-
|
|
555
591
|
/**
|
|
556
592
|
* The generic EQ band shapes, in the order a picker offers them and in the order of the integer a plugin host sees on a band type port. The id is the name; the position is the value. A bell is the shape a band added by the operator starts as.
|
|
557
593
|
* @see data/kernels/eq.json
|
|
@@ -574,6 +610,62 @@ export const FILTER_SLOPES = [
|
|
|
574
610
|
24
|
|
575
611
|
];
|
|
576
612
|
|
|
613
|
+
/**
|
|
614
|
+
* The maximum number of parametric bands a channel EQ may hold — the operator can add up to this many. Generated into `omx_contract_limits.h` as `OMX_EQ_MAX_BANDS`, which `omx_biquad.h` reads (`output-delay-ceiling.test.ts` reads the header): the native lane allocates exactly this many biquad slots per channel and silently clamps above it, so the model, the wire, and the DSP agree by test, not by memory.
|
|
615
|
+
* @see data/kernels/eq.json
|
|
616
|
+
*/
|
|
617
|
+
export const EQ_MAX_BANDS = 24;
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The standard ISO 266 one-third-octave centre frequencies, 20 Hz–20 kHz (31 bands) — the classic 31-band graphic-EQ layout's centres.
|
|
621
|
+
* @see data/kernels/eq.json
|
|
622
|
+
*/
|
|
623
|
+
export const ISO_THIRD_OCTAVE_CENTRES_HZ = [
|
|
624
|
+
20,
|
|
625
|
+
25,
|
|
626
|
+
31.5,
|
|
627
|
+
40,
|
|
628
|
+
50,
|
|
629
|
+
63,
|
|
630
|
+
80,
|
|
631
|
+
100,
|
|
632
|
+
125,
|
|
633
|
+
160,
|
|
634
|
+
200,
|
|
635
|
+
250,
|
|
636
|
+
315,
|
|
637
|
+
400,
|
|
638
|
+
500,
|
|
639
|
+
630,
|
|
640
|
+
800,
|
|
641
|
+
1000,
|
|
642
|
+
1250,
|
|
643
|
+
1600,
|
|
644
|
+
2000,
|
|
645
|
+
2500,
|
|
646
|
+
3150,
|
|
647
|
+
4000,
|
|
648
|
+
5000,
|
|
649
|
+
6300,
|
|
650
|
+
8000,
|
|
651
|
+
10000,
|
|
652
|
+
12500,
|
|
653
|
+
16000,
|
|
654
|
+
20000
|
|
655
|
+
];
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* The graphic EQ's section count: DERIVED from ISO_THIRD_OCTAVE_CENTRES_HZ, never a second literal. Generated into `omx_contract_limits.h` as `OMX_GEQ_BANDS`, which sizes `mix_geq.h`'s section array — its OWN budget, never a claimant on EQ_MAX_BANDS (graphic-eq-31 spec §3, roster ruling 3).
|
|
659
|
+
* @see data/kernels/eq.json
|
|
660
|
+
*/
|
|
661
|
+
export const GEQ_BANDS = 31;
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* The band slots kept for the operator's own hand: the reserve the feedback suppressor and the ring correction may never eat into. Nine is the working set a live EQ actually holds. The budget OPERATOR_EQ_BANDS_RESERVE + FBS_DEFAULT_MAX_AUTO_BANDS + HRP_DEFAULT_MAX_AUTO_BANDS (9 + 6 + 8 = 23) must fit EQ_MAX_BANDS, and `omx-contract validate` refuses data where it does not.
|
|
665
|
+
* @see data/kernels/eq.json
|
|
666
|
+
*/
|
|
667
|
+
export const OPERATOR_EQ_BANDS_RESERVE = 9;
|
|
668
|
+
|
|
577
669
|
/**
|
|
578
670
|
* How many bands each strip type's EQ starts with (`default`) and may hold (`max`). `channel` is the console's channel strip: it starts with four and the operator adds bands up to EQ_MAX_BANDS (a band added takes the frequency it is added at, so no default is declared for it). `strip` is the omx-strip plugin's EQ and `eq8`, `eq16` and `eq32` the omx-eq plugins, whose bands are fixed. A default may never exceed its maximum, and the default centres of any count come from the one default rule, whatever the strip.
|
|
579
671
|
* @see data/kernels/eq.json
|
|
@@ -894,6 +986,15 @@ export const FLANGER_BASE_MS = 0.5;
|
|
|
894
986
|
*/
|
|
895
987
|
export const FLANGER_MAX_MS = 6;
|
|
896
988
|
|
|
989
|
+
/**
|
|
990
|
+
* Where the gate listens: its own input (self), or the key it is handed (sidechain), in the order of the integer the key port carries.
|
|
991
|
+
* @see data/kernels/gate.json
|
|
992
|
+
*/
|
|
993
|
+
export const GATE_KEY_SOURCES = [
|
|
994
|
+
"self",
|
|
995
|
+
"sidechain"
|
|
996
|
+
];
|
|
997
|
+
|
|
897
998
|
/**
|
|
898
999
|
* The legal range of each gate field (engineering units). The widget clamps drags to these, `normalizeGate` enforces them, the row's codec refuses by them and the travel sheet publishes them — one source of truth. `rangeDb` reaches GATE_RANGE_FLOOR_DB, shown as "−∞" in readouts (a gate fully closed is silence).
|
|
899
1000
|
* @see data/kernels/gate.json
|
|
@@ -973,15 +1074,6 @@ export const GATE_LIMITS = {
|
|
|
973
1074
|
}
|
|
974
1075
|
};
|
|
975
1076
|
|
|
976
|
-
/**
|
|
977
|
-
* Where the gate listens: its own input (self), or the key it is handed (sidechain), in the order of the integer the key port carries.
|
|
978
|
-
* @see data/kernels/gate.json
|
|
979
|
-
*/
|
|
980
|
-
export const GATE_KEY_SOURCES = [
|
|
981
|
-
"self",
|
|
982
|
-
"sidechain"
|
|
983
|
-
];
|
|
984
|
-
|
|
985
1077
|
/**
|
|
986
1078
|
* 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).
|
|
987
1079
|
* @see data/kernels/geq.json
|
|
@@ -1013,6 +1105,19 @@ export const GEQ_PROTO_DB = 12;
|
|
|
1013
1105
|
*/
|
|
1014
1106
|
export const GEQ_DESIGN_GAIN_MAX_DB = 30;
|
|
1015
1107
|
|
|
1108
|
+
/**
|
|
1109
|
+
* 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.
|
|
1110
|
+
* @see data/kernels/hrp.json
|
|
1111
|
+
*/
|
|
1112
|
+
export const HRP_AMOUNT_RANGE = {
|
|
1113
|
+
"min": 0,
|
|
1114
|
+
"max": 1,
|
|
1115
|
+
"step": 0,
|
|
1116
|
+
"unit": "",
|
|
1117
|
+
"default": 0.5,
|
|
1118
|
+
"defaultFrom": "desk"
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1016
1121
|
/**
|
|
1017
1122
|
* 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.
|
|
1018
1123
|
* @see data/kernels/hrp.json
|
|
@@ -1061,18 +1166,6 @@ export const HRP_ATTRIBUTION_CENTS = 60;
|
|
|
1061
1166
|
*/
|
|
1062
1167
|
export const HRP_DEFAULT_MAX_AUTO_BANDS = 8;
|
|
1063
1168
|
|
|
1064
|
-
/**
|
|
1065
|
-
* 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.
|
|
1066
|
-
* @see data/kernels/hrp.json
|
|
1067
|
-
*/
|
|
1068
|
-
export const HRP_AMOUNT_RANGE = {
|
|
1069
|
-
"min": 0,
|
|
1070
|
-
"max": 1,
|
|
1071
|
-
"step": 0,
|
|
1072
|
-
"unit": "",
|
|
1073
|
-
"default": 0.5
|
|
1074
|
-
};
|
|
1075
|
-
|
|
1076
1169
|
/**
|
|
1077
1170
|
* 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.
|
|
1078
1171
|
* @see data/kernels/limiter.json
|
|
@@ -1162,19 +1255,6 @@ export const PHASER_STAGES_RANGE = {
|
|
|
1162
1255
|
"defaultFrom": "desk"
|
|
1163
1256
|
};
|
|
1164
1257
|
|
|
1165
|
-
/**
|
|
1166
|
-
* The member set the `stages` field accepts: PHASER_STAGES_RANGE walked by its step.
|
|
1167
|
-
* @see data/kernels/phaser.json
|
|
1168
|
-
*/
|
|
1169
|
-
export const PHASER_STAGE_COUNTS = [
|
|
1170
|
-
2,
|
|
1171
|
-
4,
|
|
1172
|
-
6,
|
|
1173
|
-
8,
|
|
1174
|
-
10,
|
|
1175
|
-
12
|
|
1176
|
-
];
|
|
1177
|
-
|
|
1178
1258
|
/**
|
|
1179
1259
|
* 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.
|
|
1180
1260
|
* @see data/kernels/phaser.json
|
|
@@ -1248,6 +1328,19 @@ export const PHASER_TRAVELS = {
|
|
|
1248
1328
|
}
|
|
1249
1329
|
};
|
|
1250
1330
|
|
|
1331
|
+
/**
|
|
1332
|
+
* The member set the `stages` field accepts: PHASER_STAGES_RANGE walked by its step.
|
|
1333
|
+
* @see data/kernels/phaser.json
|
|
1334
|
+
*/
|
|
1335
|
+
export const PHASER_STAGE_COUNTS = [
|
|
1336
|
+
2,
|
|
1337
|
+
4,
|
|
1338
|
+
6,
|
|
1339
|
+
8,
|
|
1340
|
+
10,
|
|
1341
|
+
12
|
|
1342
|
+
];
|
|
1343
|
+
|
|
1251
1344
|
/**
|
|
1252
1345
|
* The highest frequency the phaser's all-pass sweep reaches, Hz.
|
|
1253
1346
|
* @see data/kernels/phaser.json
|
|
@@ -1554,6 +1647,16 @@ export const ROTARY_MIX_RANGE = {
|
|
|
1554
1647
|
"defaultFrom": "desk"
|
|
1555
1648
|
};
|
|
1556
1649
|
|
|
1650
|
+
/**
|
|
1651
|
+
* The rotary speaker's speed settings, in the order of the integer the kernel reads: the rotors stopped, at their chorale (slow) rate, or at their tremolo (fast) rate.
|
|
1652
|
+
* @see data/kernels/rotary.json
|
|
1653
|
+
*/
|
|
1654
|
+
export const ROTARY_SPEEDS = [
|
|
1655
|
+
"stop",
|
|
1656
|
+
"slow",
|
|
1657
|
+
"fast"
|
|
1658
|
+
];
|
|
1659
|
+
|
|
1557
1660
|
/**
|
|
1558
1661
|
* The rotary's numeric travels, field → declaration: the ONE map the row's state, its bounds, its OPTIONS and its controller read (`speed` is a member set, ROTARY_SPEEDS, beside it).
|
|
1559
1662
|
* @see data/kernels/rotary.json
|
|
@@ -1689,16 +1792,6 @@ export const ROTARY_STOP_EPS = 0.0001;
|
|
|
1689
1792
|
*/
|
|
1690
1793
|
export const ROTARY_RING_FLOATS = 256;
|
|
1691
1794
|
|
|
1692
|
-
/**
|
|
1693
|
-
* The rotary speaker's speed settings, in the order of the integer the kernel reads: the rotors stopped, at their chorale (slow) rate, or at their tremolo (fast) rate.
|
|
1694
|
-
* @see data/kernels/rotary.json
|
|
1695
|
-
*/
|
|
1696
|
-
export const ROTARY_SPEEDS = [
|
|
1697
|
-
"stop",
|
|
1698
|
-
"slow",
|
|
1699
|
-
"fast"
|
|
1700
|
-
];
|
|
1701
|
-
|
|
1702
1795
|
/**
|
|
1703
1796
|
* The RTA's target bin resolution — generated into `omx_contract_limits.h` as `OMX_RTA_TARGET_BIN_HZ`, which `mix_dsp.h` reads; `rta-window-derivation.test.ts` reads it back from the header.
|
|
1704
1797
|
* @see data/kernels/rta.json
|
|
@@ -1821,6 +1914,15 @@ export const TREMOLO_MIX_RANGE = {
|
|
|
1821
1914
|
"defaultFrom": "desk"
|
|
1822
1915
|
};
|
|
1823
1916
|
|
|
1917
|
+
/**
|
|
1918
|
+
* What the tremolo's one oscillator modulates, in the order of the integer the kernel reads: the level of both legs in phase (tremolo), or the balance law's position with the legs in opposition (pan).
|
|
1919
|
+
* @see data/kernels/tremolo.json
|
|
1920
|
+
*/
|
|
1921
|
+
export const TREMOLO_MODES = [
|
|
1922
|
+
"tremolo",
|
|
1923
|
+
"pan"
|
|
1924
|
+
];
|
|
1925
|
+
|
|
1824
1926
|
/**
|
|
1825
1927
|
* The tremolo's numeric travels, field → declaration: the ONE map the row's state, its bounds, its OPTIONS and its controller read (`mode` is a member set, TREMOLO_MODES, beside it).
|
|
1826
1928
|
* @see data/kernels/tremolo.json
|
|
@@ -1852,15 +1954,6 @@ export const TREMOLO_TRAVELS = {
|
|
|
1852
1954
|
}
|
|
1853
1955
|
};
|
|
1854
1956
|
|
|
1855
|
-
/**
|
|
1856
|
-
* What the tremolo's one oscillator modulates, in the order of the integer the kernel reads: the level of both legs in phase (tremolo), or the balance law's position with the legs in opposition (pan).
|
|
1857
|
-
* @see data/kernels/tremolo.json
|
|
1858
|
-
*/
|
|
1859
|
-
export const TREMOLO_MODES = [
|
|
1860
|
-
"tremolo",
|
|
1861
|
-
"pan"
|
|
1862
|
-
];
|
|
1863
|
-
|
|
1864
1957
|
/**
|
|
1865
1958
|
* The Q of a second-order Butterworth section, 1/√2 — maximally flat, the one value every Butterworth biquad in the tree designs at: the LR4 crossover section, the pitch pre-filter and the drive's tilt / band / HF sections, in TS and (as `OMX_BUTTERWORTH_Q`) in C (§7, the scalar door).
|
|
1866
1959
|
* @see data/primitives.json
|
|
@@ -129,6 +129,33 @@
|
|
|
129
129
|
* is an integer literal, any other a float literal AND its OMX_<macro>_DOUBLE spelling. */
|
|
130
130
|
#define OMX_ALLPASS_UNITY_TOLERANCE 0.0001f
|
|
131
131
|
#define OMX_ALLPASS_UNITY_TOLERANCE_DOUBLE 0.0001
|
|
132
|
+
#define OMX_BAND_DYN_ATTACK_RANGE_DEFAULT 5
|
|
133
|
+
#define OMX_BAND_DYN_ATTACK_RANGE_MAX 100
|
|
134
|
+
#define OMX_BAND_DYN_ATTACK_RANGE_MIN 0.5f
|
|
135
|
+
#define OMX_BAND_DYN_ATTACK_RANGE_MIN_DOUBLE 0.5
|
|
136
|
+
#define OMX_BAND_DYN_ATTACK_RANGE_STEP 0.1f
|
|
137
|
+
#define OMX_BAND_DYN_ATTACK_RANGE_STEP_DOUBLE 0.1
|
|
138
|
+
#define OMX_BAND_DYN_KNEE_DB 6
|
|
139
|
+
#define OMX_BAND_DYN_MAX_BANDS 9
|
|
140
|
+
#define OMX_BAND_DYN_RANGE_RANGE_DEFAULT -6
|
|
141
|
+
#define OMX_BAND_DYN_RANGE_RANGE_MAX 12
|
|
142
|
+
#define OMX_BAND_DYN_RANGE_RANGE_MIN -24
|
|
143
|
+
#define OMX_BAND_DYN_RANGE_RANGE_STEP 0.5f
|
|
144
|
+
#define OMX_BAND_DYN_RANGE_RANGE_STEP_DOUBLE 0.5
|
|
145
|
+
#define OMX_BAND_DYN_RATIO_RANGE_DEFAULT 2
|
|
146
|
+
#define OMX_BAND_DYN_RATIO_RANGE_MAX 20
|
|
147
|
+
#define OMX_BAND_DYN_RATIO_RANGE_MIN 1
|
|
148
|
+
#define OMX_BAND_DYN_RATIO_RANGE_STEP 0.1f
|
|
149
|
+
#define OMX_BAND_DYN_RATIO_RANGE_STEP_DOUBLE 0.1
|
|
150
|
+
#define OMX_BAND_DYN_RELEASE_RANGE_DEFAULT 100
|
|
151
|
+
#define OMX_BAND_DYN_RELEASE_RANGE_MAX 3000
|
|
152
|
+
#define OMX_BAND_DYN_RELEASE_RANGE_MIN 5
|
|
153
|
+
#define OMX_BAND_DYN_RELEASE_RANGE_STEP 1
|
|
154
|
+
#define OMX_BAND_DYN_THRESHOLD_RANGE_DEFAULT -30
|
|
155
|
+
#define OMX_BAND_DYN_THRESHOLD_RANGE_MAX 0
|
|
156
|
+
#define OMX_BAND_DYN_THRESHOLD_RANGE_MIN -60
|
|
157
|
+
#define OMX_BAND_DYN_THRESHOLD_RANGE_STEP 0.5f
|
|
158
|
+
#define OMX_BAND_DYN_THRESHOLD_RANGE_STEP_DOUBLE 0.5
|
|
132
159
|
#define OMX_BUS_INPUT_CAP 128
|
|
133
160
|
#define OMX_BUTTERWORTH_Q 0.7071067811865476f
|
|
134
161
|
#define OMX_BUTTERWORTH_Q_DOUBLE 0.7071067811865476
|
|
@@ -223,6 +250,8 @@
|
|
|
223
250
|
#define OMX_DELAY_TONE_RANGE_MAX 1
|
|
224
251
|
#define OMX_DELAY_TONE_RANGE_MIN 0
|
|
225
252
|
#define OMX_DELAY_TONE_RANGE_STEP 0
|
|
253
|
+
#define OMX_DETECTOR_OVERSAMPLE_AUTO_MS 0.5f
|
|
254
|
+
#define OMX_DETECTOR_OVERSAMPLE_AUTO_MS_DOUBLE 0.5
|
|
226
255
|
#define OMX_DRIVE_AMOUNT_RANGE_DEFAULT 0
|
|
227
256
|
#define OMX_DRIVE_AMOUNT_RANGE_MAX 36
|
|
228
257
|
#define OMX_DRIVE_AMOUNT_RANGE_MIN 0
|
|
@@ -587,6 +616,12 @@
|
|
|
587
616
|
|
|
588
617
|
/* Every set rendered to C, sorted by set: an enum of the ids (a named id's position, or a numeric id's number), `OMX_<SET>_COUNT` and,
|
|
589
618
|
* where the set names a default, `OMX_<SET>_DEFAULT`. */
|
|
619
|
+
enum omx_band_dyn_modes {
|
|
620
|
+
OMX_BAND_DYN_MODES_ABOVE = 0,
|
|
621
|
+
OMX_BAND_DYN_MODES_BELOW = 1,
|
|
622
|
+
};
|
|
623
|
+
#define OMX_BAND_DYN_MODES_COUNT 2u
|
|
624
|
+
#define OMX_BAND_DYN_MODES_DEFAULT OMX_BAND_DYN_MODES_ABOVE
|
|
590
625
|
enum omx_deess_modes {
|
|
591
626
|
OMX_DEESS_MODES_SPLIT = 0,
|
|
592
627
|
OMX_DEESS_MODES_WIDEBAND = 1,
|
package/lib/data.mjs
CHANGED
|
@@ -34,8 +34,7 @@ export function loadData(dir, texts) {
|
|
|
34
34
|
let doc;
|
|
35
35
|
try { doc = JSON.parse(text); } catch (e) { throw new Error(`${rel}: not JSON: ${e.message}`); }
|
|
36
36
|
files.push({ rel, text, doc });
|
|
37
|
-
for (const [name, item] of
|
|
38
|
-
if (name === '$schema') continue;
|
|
37
|
+
for (const [name, item] of entriesOf(rel, doc)) {
|
|
39
38
|
if (items.has(name)) throw new Error(`${rel}: ${name}: already declared by ${items.get(name).rel}`);
|
|
40
39
|
items.set(name, { name, rel, item });
|
|
41
40
|
}
|
|
@@ -43,6 +42,136 @@ export function loadData(dir, texts) {
|
|
|
43
42
|
return { files, items };
|
|
44
43
|
}
|
|
45
44
|
|
|
45
|
+
const snakeOf = (n) => n.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
|
|
46
|
+
const camelOf = (u) => u.toLowerCase().replace(/_([a-z0-9])/g, (_, x) => x.toUpperCase());
|
|
47
|
+
|
|
48
|
+
/** The four sections of a kernel file, in their order. */
|
|
49
|
+
export const KERNEL_SECTIONS = ['controls', 'tables', 'aggregates', 'constants'];
|
|
50
|
+
|
|
51
|
+
/** Whether a data file is a kernel file (`kernels/<kernel>.json`), which holds the four sections, never an item map. */
|
|
52
|
+
export const isKernelFile = (rel) => /(^|\/)kernels\/[^/]+\.json$/.test(rel);
|
|
53
|
+
|
|
54
|
+
/** The kernel's upper-case prefix, read off its file name: `kernels/band_dyn.json` is BAND_DYN. */
|
|
55
|
+
export const kernelPrefix = (rel) => rel.replace(/^.*\//, '').replace(/\.json$/, '').toUpperCase().replace(/-/g, '_');
|
|
56
|
+
|
|
57
|
+
/** A control's item name: its `global`, else <KERNEL>_<NAME>_RANGE for a travel and <KERNEL>_<NAME>S for a choice. */
|
|
58
|
+
export const controlGlobal = (rel, c) => c.global ?? `${kernelPrefix(rel)}_${snakeOf(c.name)}${c.kind === 'choice' ? 'S' : '_RANGE'}`;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A data file's items as [name, item] pairs. A kernel file's four sections are expanded into the items the renders
|
|
62
|
+
* read: a travel control is a `travels` item named by controlGlobal, a choice a `set`; the controls of one `table`
|
|
63
|
+
* regroup into that table's `travels` item with `fields`; an aggregate is regenerated as fields that reference its
|
|
64
|
+
* controls; constants pass through. Any other top-level key of a kernel file is not read (validation names it).
|
|
65
|
+
* rates.json and primitives.json are item maps, read as they are.
|
|
66
|
+
*/
|
|
67
|
+
export function entriesOf(rel, doc) {
|
|
68
|
+
if (!isKernelFile(rel)) return Object.entries(doc).filter(([name]) => name !== '$schema');
|
|
69
|
+
const out = [];
|
|
70
|
+
const controls = Array.isArray(doc.controls) ? doc.controls : [];
|
|
71
|
+
const tables = new Map(Object.entries(doc.tables ?? {}).map(([n, meta]) => [n, { kind: 'travels', ...meta, fields: {} }]));
|
|
72
|
+
for (const c of controls) {
|
|
73
|
+
const { name, kind, global, table, rearms, ...rest } = c;
|
|
74
|
+
if (table) {
|
|
75
|
+
if (!tables.has(table)) throw new Error(`${rel}: control ${name} names table ${table}, which is not declared`);
|
|
76
|
+
tables.get(table).fields[name] = rest.travel;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
out.push([controlGlobal(rel, c), kind === 'choice' ? { kind: 'set', ...rest } : { kind: 'travels', ...rest }]);
|
|
80
|
+
}
|
|
81
|
+
for (const [n, t] of tables) out.push([n, t]);
|
|
82
|
+
const byName = new Map(controls.map((c) => [c.name, c]));
|
|
83
|
+
for (const [n, a] of Object.entries(doc.aggregates ?? {})) {
|
|
84
|
+
const { fields, ...meta } = a;
|
|
85
|
+
out.push([n, { kind: 'travels', ...meta, fields: Object.fromEntries((fields ?? []).map((f) => {
|
|
86
|
+
if (!byName.has(f)) throw new Error(`${rel}: aggregate ${n} names control ${f}, which is not declared`);
|
|
87
|
+
return [f, { ref: controlGlobal(rel, byName.get(f)) }];
|
|
88
|
+
})) }]);
|
|
89
|
+
}
|
|
90
|
+
for (const [n, item] of Object.entries(doc.constants ?? {})) out.push([n, item]);
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A kernel's name, read off its file name: `kernels/band_dyn.json` is band_dyn. */
|
|
95
|
+
export const kernelName = (rel) => rel.replace(/^.*\//, '').replace(/\.json$/, '');
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Every kernel's controls, in its file's order: `{ <kernel>: { controls: [{ name, kind, global, table?, rearms? }] } }`,
|
|
99
|
+
* kernels in file order. `global` is the item the control's value lives in: its own travel or set, or, for a field of
|
|
100
|
+
* a table, the table (`table` names it too, and the field is the control's name). `rearms` is carried only when true:
|
|
101
|
+
* changing the control re-arms the kernel's state, it is not a smooth parameter.
|
|
102
|
+
*/
|
|
103
|
+
export function kernelControls(data) {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const f of data.files) {
|
|
106
|
+
if (!isKernelFile(f.rel)) continue;
|
|
107
|
+
const controls = Array.isArray(f.doc.controls) ? f.doc.controls : [];
|
|
108
|
+
out[kernelName(f.rel)] = {
|
|
109
|
+
controls: controls.map((c) => ({
|
|
110
|
+
name: c.name,
|
|
111
|
+
kind: c.kind,
|
|
112
|
+
global: c.table ?? controlGlobal(f.rel, c),
|
|
113
|
+
...(c.table ? { table: c.table } : {}),
|
|
114
|
+
...(c.rearms === true ? { rearms: true } : {}),
|
|
115
|
+
})),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const isWholeRef = (v) => typeof v === 'object' && v !== null && Object.keys(v).length === 1 && typeof v.ref === 'string' && !v.ref.includes('.');
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The kernel file that declares `entries` ([name, item] pairs, the items entriesOf gives back), the inverse of
|
|
125
|
+
* entriesOf: a single travel is a travel control and a set a choice; a fields table whose every field IS a single
|
|
126
|
+
* travel of the kernel (`{ ref }`) is an aggregate, and its field names name those controls; any other fields table
|
|
127
|
+
* is a table whose fields are its controls; every other item is a constant. A control's `global` is written only
|
|
128
|
+
* when its name does not derive it. `kept`, a kernel file already written, is extended: its declarations stay as
|
|
129
|
+
* they are and the entries it lacks are added. Empty sections are left out.
|
|
130
|
+
* @throws naming the item: a fields table that mixes references and travels, an aggregate field whose name is not
|
|
131
|
+
* its control's.
|
|
132
|
+
*/
|
|
133
|
+
export function kernelDocOf(rel, entries, kept) {
|
|
134
|
+
const K = kernelPrefix(rel);
|
|
135
|
+
const doc = kept ? JSON.parse(JSON.stringify(kept)) : {};
|
|
136
|
+
doc.controls ??= [];
|
|
137
|
+
for (const s of KERNEL_SECTIONS.slice(1)) doc[s] ??= {};
|
|
138
|
+
const declared = new Set(entriesOf(rel, kept ?? {}).map(([n]) => n));
|
|
139
|
+
const singles = new Map(entries.filter(([, it]) => (it.kind === 'travels' && it.travel) || it.kind === 'set'));
|
|
140
|
+
for (const c of doc.controls) if (!c.table) singles.set(controlGlobal(rel, c), c);
|
|
141
|
+
const isAggregate = (it) => it.kind === 'travels' && it.fields && Object.values(it.fields).length > 0 && Object.values(it.fields).every((f) => isWholeRef(f) && singles.get(f.ref)?.travel);
|
|
142
|
+
// an aggregate's field names are its controls' names
|
|
143
|
+
const named = new Map();
|
|
144
|
+
for (const [n, it] of entries) if (isAggregate(it)) for (const [f, t] of Object.entries(it.fields)) named.set(t.ref, f);
|
|
145
|
+
for (const [n, it] of entries) {
|
|
146
|
+
if (declared.has(n)) continue;
|
|
147
|
+
if ((it.kind === 'travels' && it.travel) || it.kind === 'set') {
|
|
148
|
+
const { kind, ...rest } = it;
|
|
149
|
+
const ck = kind === 'set' ? 'choice' : 'travel';
|
|
150
|
+
const suffix = ck === 'choice' ? 'S' : '_RANGE';
|
|
151
|
+
const derived = n.startsWith(`${K}_`) && n.endsWith(suffix) && n.length > K.length + 1 + suffix.length ? camelOf(n.slice(K.length + 1, -suffix.length)) : undefined;
|
|
152
|
+
const c = { name: named.get(n) ?? derived ?? camelOf(n.endsWith(suffix) ? n.slice(0, -suffix.length) : n), kind: ck };
|
|
153
|
+
if (controlGlobal(rel, c) !== n) c.global = n;
|
|
154
|
+
doc.controls.push({ ...c, ...rest });
|
|
155
|
+
} else if (it.kind === 'travels' && it.fields && isAggregate(it)) {
|
|
156
|
+
const { kind, fields, ...meta } = it;
|
|
157
|
+
doc.aggregates[n] = { ...meta, fields: Object.entries(fields).map(([f, t]) => {
|
|
158
|
+
const c = doc.controls.find((x) => !x.table && controlGlobal(rel, x) === t.ref) ?? { name: named.get(t.ref) };
|
|
159
|
+
if (c.name !== f) throw new Error(`${rel}: ${n}.${f} refers to ${t.ref}, whose control is named ${c.name}: an aggregate's field is named by its control`);
|
|
160
|
+
return f;
|
|
161
|
+
}) };
|
|
162
|
+
} else if (it.kind === 'travels' && it.fields) {
|
|
163
|
+
const { kind, fields, ...meta } = it;
|
|
164
|
+
if (Object.values(fields).some(isWholeRef)) throw new Error(`${rel}: ${n} mixes fields that refer to single travels with travels of its own: declare it as an aggregate of controls, or as a table`);
|
|
165
|
+
doc.tables[n] = meta;
|
|
166
|
+
for (const [f, travel] of Object.entries(fields)) doc.controls.push({ name: f, kind: 'travel', table: n, travel });
|
|
167
|
+
} else {
|
|
168
|
+
doc.constants[n] = it;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const s of KERNEL_SECTIONS) if (s === 'controls' ? !doc[s].length : !Object.keys(doc[s]).length) delete doc[s];
|
|
172
|
+
return doc;
|
|
173
|
+
}
|
|
174
|
+
|
|
46
175
|
/** An item's own value before resolution, by kind. */
|
|
47
176
|
export function rawValue(item) {
|
|
48
177
|
switch (item.kind) {
|
|
@@ -111,7 +240,8 @@ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
|
111
240
|
|
|
112
241
|
/**
|
|
113
242
|
* Resolve every item: `{ name, rel, kind, doc, why?, unit?, c?, shape?, default?, labels?, value }` (default and labels: a set's), where `value` has
|
|
114
|
-
* every `{ref}` and `{derive}` evaluated.
|
|
243
|
+
* every `{ref}` and `{derive}` evaluated. The returned map also carries `kernels`, every kernel's ordered controls
|
|
244
|
+
* (kernelControls), which the json render publishes beside the items.
|
|
115
245
|
* @throws naming the item: a reference that does not resolve, a cycle, an unknown derivation.
|
|
116
246
|
*/
|
|
117
247
|
export function resolveData(data) {
|
|
@@ -160,6 +290,7 @@ export function resolveData(data) {
|
|
|
160
290
|
}
|
|
161
291
|
const out = new Map();
|
|
162
292
|
for (const name of data.items.keys()) out.set(name, resolveItem(name));
|
|
293
|
+
out.kernels = kernelControls(data);
|
|
163
294
|
return out;
|
|
164
295
|
}
|
|
165
296
|
|