@openmixer/omx-contract 2.5.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,7 @@ pkg-config --cflags omx-contract
49
49
  ```
50
50
 
51
51
  Pin one version and build against its renders instead of keeping a copy: the installed package at exactly
52
- that version (`pkg-config --exact-version=2.5.0 omx-contract`), else the npm tarball, unpacked, and
52
+ that version (`pkg-config --exact-version=2.7.0 omx-contract`), else the npm tarball, unpacked, and
53
53
  `omx-contract render --target c --out build/omx-contract/include`. A kernel's controls and readouts, in their
54
54
  order, are in `share/omx-contract/omx-contract.json`; how a declaration is written is in
55
55
  [docs/kernel-files.md](docs/kernel-files.md), and what a readout is in [docs/readouts.md](docs/readouts.md).
@@ -126,6 +126,28 @@
126
126
  "default": 16,
127
127
  "defaultFrom": "desk"
128
128
  }
129
+ },
130
+ {
131
+ "name": "hpHz",
132
+ "kind": "travel",
133
+ "table": "GATE_KEY_LIMITS",
134
+ "travel": {
135
+ "min": 0,
136
+ "max": 20000,
137
+ "step": 1,
138
+ "unit": "Hz"
139
+ }
140
+ },
141
+ {
142
+ "name": "lpHz",
143
+ "kind": "travel",
144
+ "table": "GATE_KEY_LIMITS",
145
+ "travel": {
146
+ "min": 0,
147
+ "max": 20000,
148
+ "step": 1,
149
+ "unit": "Hz"
150
+ }
129
151
  }
130
152
  ],
131
153
  "readouts": [
@@ -146,6 +168,9 @@
146
168
  "tables": {
147
169
  "GATE_LIMITS": {
148
170
  "doc": "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)."
171
+ },
172
+ "GATE_KEY_LIMITS": {
173
+ "doc": "The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared."
149
174
  }
150
175
  }
151
176
  }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "../../schema/omx-contract.schema.json",
3
+ "controls": [
4
+ {
5
+ "name": "kind",
6
+ "kind": "choice",
7
+ "doc": "The generator kinds, in surface order: its position is the integer omx-dsp's omx_gen_fill takes as its kind (OMX_GEN_OSC, OMX_GEN_WHITE, OMX_GEN_PINK).",
8
+ "ids": [
9
+ "oscillator",
10
+ "white",
11
+ "pink"
12
+ ]
13
+ },
14
+ {
15
+ "name": "waveform",
16
+ "kind": "choice",
17
+ "global": "WAVEFORMS",
18
+ "doc": "The selectable oscillator waveforms, in surface order: its position is the integer omx-dsp's omx_gen_osc takes (OMX_WAVE_SINE, OMX_WAVE_SQUARE, OMX_WAVE_SAW, OMX_WAVE_TRIANGLE).",
19
+ "ids": [
20
+ "sine",
21
+ "square",
22
+ "saw",
23
+ "triangle"
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "../../schema/omx-contract.schema.json",
3
+ "constants": {
4
+ "SRC_TAPS": {
5
+ "kind": "scalar",
6
+ "doc": "Taps per phase. 64 puts the design's transition band comfortably inside what a Kaiser of this beta can hold at every standard rate pair, and costs 64 multiply-adds per output sample — about 12 M flops per track-minute at 48 kHz, which is nothing offline.",
7
+ "unit": "",
8
+ "value": 64
9
+ },
10
+ "SRC_STOPBAND_DB": {
11
+ "kind": "scalar",
12
+ "doc": "Stopband attenuation the window is designed for, in dB. 96 dB is below the noise floor of any take this console can capture (float capture of a 24-bit converter), so the conversion is not what limits the file.",
13
+ "unit": "dB",
14
+ "value": 96
15
+ },
16
+ "SRC_ROLLOFF": {
17
+ "kind": "scalar",
18
+ "doc": "How much of the shared Nyquist the passband keeps. The remaining 8 % is the transition band the window needs to reach its stopband: at 48 kHz that puts the corner at 22.08 kHz and the stopband in place by 24 kHz.",
19
+ "unit": "",
20
+ "value": 0.92
21
+ },
22
+ "SRC_MAX_L": {
23
+ "kind": "scalar",
24
+ "doc": "The largest interpolation factor a plan may carry. 44.1 kHz -> 192 kHz is 640/147, the widest pair the console's standard rates produce; the cap refuses an exotic pair before it allocates a table nobody budgeted for, rather than discovering it as a bad_alloc.",
25
+ "unit": "",
26
+ "value": 1024
27
+ }
28
+ }
29
+ }
@@ -1012,6 +1012,37 @@ export declare const GATE_LIMITS: {
1012
1012
  };
1013
1013
  };
1014
1014
 
1015
+ /**
1016
+ * The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared.
1017
+ * @see data/kernels/gate.json
1018
+ */
1019
+ export declare const GATE_KEY_LIMITS: {
1020
+ readonly hpHz: {
1021
+ readonly min: 0;
1022
+ readonly max: 20000;
1023
+ readonly step: 1;
1024
+ readonly unit: "Hz";
1025
+ };
1026
+ readonly lpHz: {
1027
+ readonly min: 0;
1028
+ readonly max: 20000;
1029
+ readonly step: 1;
1030
+ readonly unit: "Hz";
1031
+ };
1032
+ };
1033
+
1034
+ /**
1035
+ * The generator kinds, in surface order: its position is the integer omx-dsp's omx_gen_fill takes as its kind (OMX_GEN_OSC, OMX_GEN_WHITE, OMX_GEN_PINK).
1036
+ * @see data/kernels/generator.json
1037
+ */
1038
+ export declare const GENERATOR_KINDS: readonly ["oscillator", "white", "pink"];
1039
+
1040
+ /**
1041
+ * The selectable oscillator waveforms, in surface order: its position is the integer omx-dsp's omx_gen_osc takes (OMX_WAVE_SINE, OMX_WAVE_SQUARE, OMX_WAVE_SAW, OMX_WAVE_TRIANGLE).
1042
+ * @see data/kernels/generator.json
1043
+ */
1044
+ export declare const WAVEFORMS: readonly ["sine", "square", "saw", "triangle"];
1045
+
1015
1046
  /**
1016
1047
  * 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).
1017
1048
  * @see data/kernels/geq.json
@@ -1725,6 +1756,30 @@ export declare const RTA_TARGET_BIN_HZ: 5.9;
1725
1756
  */
1726
1757
  export declare const RTA_FFT_SIZE_MAX: 32768;
1727
1758
 
1759
+ /**
1760
+ * Taps per phase. 64 puts the design's transition band comfortably inside what a Kaiser of this beta can hold at every standard rate pair, and costs 64 multiply-adds per output sample — about 12 M flops per track-minute at 48 kHz, which is nothing offline.
1761
+ * @see data/kernels/src.json
1762
+ */
1763
+ export declare const SRC_TAPS: 64;
1764
+
1765
+ /**
1766
+ * Stopband attenuation the window is designed for, in dB. 96 dB is below the noise floor of any take this console can capture (float capture of a 24-bit converter), so the conversion is not what limits the file.
1767
+ * @see data/kernels/src.json
1768
+ */
1769
+ export declare const SRC_STOPBAND_DB: 96;
1770
+
1771
+ /**
1772
+ * How much of the shared Nyquist the passband keeps. The remaining 8 % is the transition band the window needs to reach its stopband: at 48 kHz that puts the corner at 22.08 kHz and the stopband in place by 24 kHz.
1773
+ * @see data/kernels/src.json
1774
+ */
1775
+ export declare const SRC_ROLLOFF: 0.92;
1776
+
1777
+ /**
1778
+ * The largest interpolation factor a plan may carry. 44.1 kHz -> 192 kHz is 640/147, the widest pair the console's standard rates produce; the cap refuses an exotic pair before it allocates a table nobody budgeted for, rather than discovering it as a bad_alloc.
1779
+ * @see data/kernels/src.json
1780
+ */
1781
+ export declare const SRC_MAX_L: 1024;
1782
+
1728
1783
  /**
1729
1784
  * The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. `harness/contract-limits-gen.mjs` emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).
1730
1785
  * @see data/kernels/transient.json
@@ -1172,6 +1172,46 @@ export const GATE_LIMITS = {
1172
1172
  }
1173
1173
  };
1174
1174
 
1175
+ /**
1176
+ * The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared.
1177
+ * @see data/kernels/gate.json
1178
+ */
1179
+ export const GATE_KEY_LIMITS = {
1180
+ "hpHz": {
1181
+ "min": 0,
1182
+ "max": 20000,
1183
+ "step": 1,
1184
+ "unit": "Hz"
1185
+ },
1186
+ "lpHz": {
1187
+ "min": 0,
1188
+ "max": 20000,
1189
+ "step": 1,
1190
+ "unit": "Hz"
1191
+ }
1192
+ };
1193
+
1194
+ /**
1195
+ * The generator kinds, in surface order: its position is the integer omx-dsp's omx_gen_fill takes as its kind (OMX_GEN_OSC, OMX_GEN_WHITE, OMX_GEN_PINK).
1196
+ * @see data/kernels/generator.json
1197
+ */
1198
+ export const GENERATOR_KINDS = [
1199
+ "oscillator",
1200
+ "white",
1201
+ "pink"
1202
+ ];
1203
+
1204
+ /**
1205
+ * The selectable oscillator waveforms, in surface order: its position is the integer omx-dsp's omx_gen_osc takes (OMX_WAVE_SINE, OMX_WAVE_SQUARE, OMX_WAVE_SAW, OMX_WAVE_TRIANGLE).
1206
+ * @see data/kernels/generator.json
1207
+ */
1208
+ export const WAVEFORMS = [
1209
+ "sine",
1210
+ "square",
1211
+ "saw",
1212
+ "triangle"
1213
+ ];
1214
+
1175
1215
  /**
1176
1216
  * 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).
1177
1217
  * @see data/kernels/geq.json
@@ -1902,6 +1942,30 @@ export const RTA_TARGET_BIN_HZ = 5.9;
1902
1942
  */
1903
1943
  export const RTA_FFT_SIZE_MAX = 32768;
1904
1944
 
1945
+ /**
1946
+ * Taps per phase. 64 puts the design's transition band comfortably inside what a Kaiser of this beta can hold at every standard rate pair, and costs 64 multiply-adds per output sample — about 12 M flops per track-minute at 48 kHz, which is nothing offline.
1947
+ * @see data/kernels/src.json
1948
+ */
1949
+ export const SRC_TAPS = 64;
1950
+
1951
+ /**
1952
+ * Stopband attenuation the window is designed for, in dB. 96 dB is below the noise floor of any take this console can capture (float capture of a 24-bit converter), so the conversion is not what limits the file.
1953
+ * @see data/kernels/src.json
1954
+ */
1955
+ export const SRC_STOPBAND_DB = 96;
1956
+
1957
+ /**
1958
+ * How much of the shared Nyquist the passband keeps. The remaining 8 % is the transition band the window needs to reach its stopband: at 48 kHz that puts the corner at 22.08 kHz and the stopband in place by 24 kHz.
1959
+ * @see data/kernels/src.json
1960
+ */
1961
+ export const SRC_ROLLOFF = 0.92;
1962
+
1963
+ /**
1964
+ * The largest interpolation factor a plan may carry. 44.1 kHz -> 192 kHz is 640/147, the widest pair the console's standard rates produce; the cap refuses an exotic pair before it allocates a table nobody budgeted for, rather than discovering it as a bad_alloc.
1965
+ * @see data/kernels/src.json
1966
+ */
1967
+ export const SRC_MAX_L = 1024;
1968
+
1905
1969
  /**
1906
1970
  * The legal range of each transient-designer field (engineering units), transient spec §3. Every kind comes up flat: no per-kind default differs. `harness/contract-limits-gen.mjs` emits each as `OMX_TRANSIENT_<FIELD>_{MIN,MAX,DEFAULT}` for `mix_transient.h`'s resolve; the row publishes them on OPTIONS when it lands (issue #921).
1907
1971
  * @see data/kernels/transient.json
@@ -64,6 +64,11 @@
64
64
  #define OMX_GATE_RATIO_MAX 100.0f
65
65
  #define OMX_GATE_RATIO_DEFAULT 16.0f
66
66
 
67
+ #define OMX_GATE_KEY_HP_HZ_MIN 0.0f
68
+ #define OMX_GATE_KEY_HP_HZ_MAX 20000.0f
69
+ #define OMX_GATE_KEY_LP_HZ_MIN 0.0f
70
+ #define OMX_GATE_KEY_LP_HZ_MAX 20000.0f
71
+
67
72
  #define OMX_LIMITER_CEILING_DB_MIN -12.0f
68
73
  #define OMX_LIMITER_CEILING_DB_MAX 0.0f
69
74
  #define OMX_LIMITER_CEILING_DB_DEFAULT -1.0f
@@ -570,6 +575,11 @@
570
575
  #define OMX_RTA_TARGET_BIN_HZ_DOUBLE 5.9
571
576
  #define OMX_RT_HARD_TARGET_QUANTUM 128
572
577
  #define OMX_RT_HARD_TARGET_RATE 192000
578
+ #define OMX_SRC_MAX_L 1024
579
+ #define OMX_SRC_ROLLOFF 0.92f
580
+ #define OMX_SRC_ROLLOFF_DOUBLE 0.92
581
+ #define OMX_SRC_STOPBAND_DB 96
582
+ #define OMX_SRC_TAPS 64
573
583
  #define OMX_SUBHARMONIC_RADIUS_BINS 2
574
584
  #define OMX_TRANSIENT_FAST_ATTACK_MS 0.5f
575
585
  #define OMX_TRANSIENT_FAST_ATTACK_MS_DOUBLE 0.5
@@ -730,6 +740,12 @@ enum omx_gate_key_sources {
730
740
  OMX_GATE_KEY_SOURCES_SIDECHAIN = 1,
731
741
  };
732
742
  #define OMX_GATE_KEY_SOURCES_COUNT 2u
743
+ enum omx_generator_kinds {
744
+ OMX_GENERATOR_KINDS_OSCILLATOR = 0,
745
+ OMX_GENERATOR_KINDS_WHITE = 1,
746
+ OMX_GENERATOR_KINDS_PINK = 2,
747
+ };
748
+ #define OMX_GENERATOR_KINDS_COUNT 3u
733
749
  enum omx_param_scales {
734
750
  OMX_PARAM_SCALES_LINEAR = 0,
735
751
  OMX_PARAM_SCALES_LOG = 1,
@@ -757,6 +773,13 @@ enum omx_tremolo_modes {
757
773
  };
758
774
  #define OMX_TREMOLO_MODES_COUNT 2u
759
775
  #define OMX_TREMOLO_MODES_DEFAULT OMX_TREMOLO_MODES_TREMOLO
776
+ enum omx_waveforms {
777
+ OMX_WAVEFORMS_SINE = 0,
778
+ OMX_WAVEFORMS_SQUARE = 1,
779
+ OMX_WAVEFORMS_SAW = 2,
780
+ OMX_WAVEFORMS_TRIANGLE = 3,
781
+ };
782
+ #define OMX_WAVEFORMS_COUNT 4u
760
783
 
761
784
  /* Every kernel's readouts, the meters a host draws (docs/readouts.md): an enum of the readouts at their position,
762
785
  * `OMX_<KERNEL>_READOUT_COUNT`, and per readout its display range (_MIN, _MAX), _RATE_HZ, _SCALE (an OMX_PARAM_SCALES_*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmixer/omx-contract",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "The FreeMixer declarations as data: a JSON Schema, the data, and one CLI that renders them into C, TypeScript and JSON",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -41,7 +41,10 @@ function genLimitBlock(prefix, table) {
41
41
  const c = fieldConstName(field);
42
42
  lines.push(`#define OMX_${prefix}_${c}_MIN ${numLiteral(limit.min)}f`);
43
43
  lines.push(`#define OMX_${prefix}_${c}_MAX ${numLiteral(limit.max)}f`);
44
- lines.push(`#define OMX_${prefix}_${c}_DEFAULT ${numLiteral(limit.default)}f`);
44
+ // a field with no come-up value (the gate key filter's edges) has no DEFAULT, as in genCoreLimitBlock
45
+ if (limit.default !== undefined) {
46
+ lines.push(`#define OMX_${prefix}_${c}_DEFAULT ${numLiteral(limit.default)}f`);
47
+ }
45
48
  }
46
49
  return lines.join('\n');
47
50
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmixer/omx-contract",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "items": {
5
5
  "PAN_RANGE": {
6
6
  "rel": "data/kernels/balance.json",
@@ -1347,6 +1347,47 @@
1347
1347
  }
1348
1348
  }
1349
1349
  },
1350
+ "GATE_KEY_LIMITS": {
1351
+ "rel": "data/kernels/gate.json",
1352
+ "kind": "travels",
1353
+ "doc": "The travel of each edge of the keyed gate's detector filter, 0 (off) up to 20 kHz in whole Hz, as the console's `/channel/{kind}/{index}/gate/key` row serves them (the console's GATE_KEY_LIMITS). A property of the detector filter, not of any desk: no come-up value is declared.",
1354
+ "shape": "table",
1355
+ "value": {
1356
+ "hpHz": {
1357
+ "min": 0,
1358
+ "max": 20000,
1359
+ "step": 1,
1360
+ "unit": "Hz"
1361
+ },
1362
+ "lpHz": {
1363
+ "min": 0,
1364
+ "max": 20000,
1365
+ "step": 1,
1366
+ "unit": "Hz"
1367
+ }
1368
+ }
1369
+ },
1370
+ "GENERATOR_KINDS": {
1371
+ "rel": "data/kernels/generator.json",
1372
+ "kind": "set",
1373
+ "doc": "The generator kinds, in surface order: its position is the integer omx-dsp's omx_gen_fill takes as its kind (OMX_GEN_OSC, OMX_GEN_WHITE, OMX_GEN_PINK).",
1374
+ "value": [
1375
+ "oscillator",
1376
+ "white",
1377
+ "pink"
1378
+ ]
1379
+ },
1380
+ "WAVEFORMS": {
1381
+ "rel": "data/kernels/generator.json",
1382
+ "kind": "set",
1383
+ "doc": "The selectable oscillator waveforms, in surface order: its position is the integer omx-dsp's omx_gen_osc takes (OMX_WAVE_SINE, OMX_WAVE_SQUARE, OMX_WAVE_SAW, OMX_WAVE_TRIANGLE).",
1384
+ "value": [
1385
+ "sine",
1386
+ "square",
1387
+ "saw",
1388
+ "triangle"
1389
+ ]
1390
+ },
1350
1391
  "GEQ_BAND_RANGE": {
1351
1392
  "rel": "data/kernels/geq.json",
1352
1393
  "kind": "travels",
@@ -2137,6 +2178,34 @@
2137
2178
  "unit": "",
2138
2179
  "value": 32768
2139
2180
  },
2181
+ "SRC_TAPS": {
2182
+ "rel": "data/kernels/src.json",
2183
+ "kind": "scalar",
2184
+ "doc": "Taps per phase. 64 puts the design's transition band comfortably inside what a Kaiser of this beta can hold at every standard rate pair, and costs 64 multiply-adds per output sample — about 12 M flops per track-minute at 48 kHz, which is nothing offline.",
2185
+ "unit": "",
2186
+ "value": 64
2187
+ },
2188
+ "SRC_STOPBAND_DB": {
2189
+ "rel": "data/kernels/src.json",
2190
+ "kind": "scalar",
2191
+ "doc": "Stopband attenuation the window is designed for, in dB. 96 dB is below the noise floor of any take this console can capture (float capture of a 24-bit converter), so the conversion is not what limits the file.",
2192
+ "unit": "dB",
2193
+ "value": 96
2194
+ },
2195
+ "SRC_ROLLOFF": {
2196
+ "rel": "data/kernels/src.json",
2197
+ "kind": "scalar",
2198
+ "doc": "How much of the shared Nyquist the passband keeps. The remaining 8 % is the transition band the window needs to reach its stopband: at 48 kHz that puts the corner at 22.08 kHz and the stopband in place by 24 kHz.",
2199
+ "unit": "",
2200
+ "value": 0.92
2201
+ },
2202
+ "SRC_MAX_L": {
2203
+ "rel": "data/kernels/src.json",
2204
+ "kind": "scalar",
2205
+ "doc": "The largest interpolation factor a plan may carry. 44.1 kHz -> 192 kHz is 640/147, the widest pair the console's standard rates produce; the cap refuses an exotic pair before it allocates a table nobody budgeted for, rather than discovering it as a bad_alloc.",
2206
+ "unit": "",
2207
+ "value": 1024
2208
+ },
2140
2209
  "TRANSIENT_LIMITS": {
2141
2210
  "rel": "data/kernels/transient.json",
2142
2211
  "kind": "travels",
@@ -2978,6 +3047,18 @@
2978
3047
  "kind": "travel",
2979
3048
  "global": "GATE_LIMITS",
2980
3049
  "table": "GATE_LIMITS"
3050
+ },
3051
+ {
3052
+ "name": "hpHz",
3053
+ "kind": "travel",
3054
+ "global": "GATE_KEY_LIMITS",
3055
+ "table": "GATE_KEY_LIMITS"
3056
+ },
3057
+ {
3058
+ "name": "lpHz",
3059
+ "kind": "travel",
3060
+ "global": "GATE_KEY_LIMITS",
3061
+ "table": "GATE_KEY_LIMITS"
2981
3062
  }
2982
3063
  ],
2983
3064
  "readouts": [
@@ -2992,6 +3073,20 @@
2992
3073
  }
2993
3074
  ]
2994
3075
  },
3076
+ "generator": {
3077
+ "controls": [
3078
+ {
3079
+ "name": "kind",
3080
+ "kind": "choice",
3081
+ "global": "GENERATOR_KINDS"
3082
+ },
3083
+ {
3084
+ "name": "waveform",
3085
+ "kind": "choice",
3086
+ "global": "WAVEFORMS"
3087
+ }
3088
+ ]
3089
+ },
2995
3090
  "geq": {
2996
3091
  "controls": [
2997
3092
  {
@@ -3220,6 +3315,9 @@
3220
3315
  "rta": {
3221
3316
  "controls": []
3222
3317
  },
3318
+ "src": {
3319
+ "controls": []
3320
+ },
3223
3321
  "transient": {
3224
3322
  "controls": [
3225
3323
  {