@openmixer/omx-contract 1.2.1 → 1.3.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.
@@ -2,6 +2,7 @@
2
2
  // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
3
  #ifndef OMX_CONTRACT_LIMITS_H
4
4
  #define OMX_CONTRACT_LIMITS_H
5
+ #include <math.h>
5
6
  /*
6
7
  * GENERATED — DO NOT EDIT BY HAND.
7
8
  * Produced by `omx-contract render --target c` from FreeMixer/omx-contract's data/, the declaration
@@ -128,13 +129,16 @@
128
129
  * is an integer literal, any other a float literal AND its OMX_<macro>_DOUBLE spelling. */
129
130
  #define OMX_ALLPASS_UNITY_TOLERANCE 0.0001f
130
131
  #define OMX_ALLPASS_UNITY_TOLERANCE_DOUBLE 0.0001
132
+ #define OMX_BUS_INPUT_CAP 128
131
133
  #define OMX_BUTTERWORTH_Q 0.7071067811865476f
132
134
  #define OMX_BUTTERWORTH_Q_DOUBLE 0.7071067811865476
135
+ #define OMX_CHORUS_BASE_MS 10
133
136
  #define OMX_CHORUS_DEPTH_RANGE_DEFAULT 4
134
137
  #define OMX_CHORUS_DEPTH_RANGE_MAX 12
135
138
  #define OMX_CHORUS_DEPTH_RANGE_MIN 0
136
139
  #define OMX_CHORUS_DEPTH_RANGE_STEP 0.1f
137
140
  #define OMX_CHORUS_DEPTH_RANGE_STEP_DOUBLE 0.1
141
+ #define OMX_CHORUS_MAX_MS 22
138
142
  #define OMX_CHORUS_MIX_RANGE_DEFAULT 35
139
143
  #define OMX_CHORUS_MIX_RANGE_MAX 100
140
144
  #define OMX_CHORUS_MIX_RANGE_MIN 0
@@ -157,6 +161,7 @@
157
161
  #define OMX_CHORUS_VOICES_RANGE_MAX 4
158
162
  #define OMX_CHORUS_VOICES_RANGE_MIN 1
159
163
  #define OMX_CHORUS_VOICES_RANGE_STEP 1
164
+ #define OMX_DEEP_PLATEAU_FRAMES 20
160
165
  #define OMX_DEESS_ATTACK_RANGE_DEFAULT 1
161
166
  #define OMX_DEESS_ATTACK_RANGE_MAX 50
162
167
  #define OMX_DEESS_ATTACK_RANGE_MIN 0.1f
@@ -193,6 +198,20 @@
193
198
  #define OMX_DEESS_WIDTH_RANGE_MIN_DOUBLE 0.25
194
199
  #define OMX_DEESS_WIDTH_RANGE_STEP 0.05f
195
200
  #define OMX_DEESS_WIDTH_RANGE_STEP_DOUBLE 0.05
201
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_CONFIRM_FRAMES 2
202
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MAX_DIP_DB 0.5f
203
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MAX_DIP_DB_DOUBLE 0.5
204
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MAX_SINGLE_FRAME_SHARE 0.5f
205
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MAX_SINGLE_FRAME_SHARE_DOUBLE 0.5
206
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MIN_DB_PER_FRAME 0.8f
207
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MIN_DB_PER_FRAME_DOUBLE 0.8
208
+ #define OMX_DEFAULT_THRESHOLDS_GROWTH_MIN_TOTAL_RISE_DB 6
209
+ #define OMX_DEFAULT_THRESHOLDS_PAPR_DB 18
210
+ #define OMX_DEFAULT_THRESHOLDS_PERSIST_FRAMES 5
211
+ #define OMX_DEFAULT_THRESHOLDS_PHPR_DB 9
212
+ #define OMX_DEFAULT_THRESHOLDS_PNPR_DB 12
213
+ #define OMX_DEFAULT_THRESHOLDS_PSHR_MIN_DB 10
214
+ #define OMX_DEFAULT_THRESHOLDS_SUB_CLEAN_FRAMES 5
196
215
  #define OMX_DELAY_MIX_RANGE_BY_KIND_FX_RETURN 1
197
216
  #define OMX_DELAY_MIX_RANGE_DEFAULT 0.3f
198
217
  #define OMX_DELAY_MIX_RANGE_DEFAULT_DOUBLE 0.3
@@ -231,6 +250,18 @@
231
250
  #define OMX_DRIVE_TRIM_RANGE_STEP 0.1f
232
251
  #define OMX_DRIVE_TRIM_RANGE_STEP_DOUBLE 0.1
233
252
  #define OMX_DSP_KNOB_REFERENCE_RATE 96000
253
+ #define OMX_EQ_BAND_COUNTS_CHANNEL_DEFAULT 4
254
+ #define OMX_EQ_BAND_COUNTS_CHANNEL_MAX 24
255
+ #define OMX_EQ_BAND_COUNTS_EQ16_DEFAULT 16
256
+ #define OMX_EQ_BAND_COUNTS_EQ16_MAX 16
257
+ #define OMX_EQ_BAND_COUNTS_EQ32_DEFAULT 32
258
+ #define OMX_EQ_BAND_COUNTS_EQ32_MAX 32
259
+ #define OMX_EQ_BAND_COUNTS_EQ8_DEFAULT 8
260
+ #define OMX_EQ_BAND_COUNTS_EQ8_MAX 8
261
+ #define OMX_EQ_BAND_COUNTS_STRIP_DEFAULT 4
262
+ #define OMX_EQ_BAND_COUNTS_STRIP_MAX 4
263
+ #define OMX_EQ_BAND_DEFAULTS_GAIN_DB 0
264
+ #define OMX_EQ_BAND_DEFAULTS_Q 1
234
265
  #define OMX_EQ_FREQ_RANGE_MAX 20000
235
266
  #define OMX_EQ_FREQ_RANGE_MIN 20
236
267
  #define OMX_EQ_FREQ_RANGE_STEP 10
@@ -243,14 +274,29 @@
243
274
  #define OMX_EQ_NOTCH_Q_RANGE_MIN 0.3f
244
275
  #define OMX_EQ_NOTCH_Q_RANGE_MIN_DOUBLE 0.3
245
276
  #define OMX_EQ_NOTCH_Q_RANGE_STEP 1
277
+ #define OMX_EQ_PASS_FILTER_DEFAULTS_HPF_FREQ_HZ 80
278
+ #define OMX_EQ_PASS_FILTER_DEFAULTS_LPF_FREQ_HZ 18000
246
279
  #define OMX_EQ_Q_RANGE_MAX 8
247
280
  #define OMX_EQ_Q_RANGE_MIN 0.3f
248
281
  #define OMX_EQ_Q_RANGE_MIN_DOUBLE 0.3
249
282
  #define OMX_EQ_Q_RANGE_STEP 0.1f
250
283
  #define OMX_EQ_Q_RANGE_STEP_DOUBLE 0.1
284
+ #define OMX_FBS_DEFAULT_MAX_AUTO_BANDS 6
285
+ #define OMX_FBS_DEFAULT_NOTCH_Q 116
286
+ #define OMX_FBS_FLOOR_LIN 1e-12f
287
+ #define OMX_FBS_FLOOR_LIN_DOUBLE 1e-12
288
+ #define OMX_FBS_MAX_NOTCH_DEPTH_DB 18
289
+ #define OMX_FBS_PAPR_GUARD_BINS 2
290
+ #define OMX_FBS_PHPR_HARMONICS 3
291
+ #define OMX_FBS_PNPR_GUARD_BINS 1
292
+ #define OMX_FBS_PNPR_RADIUS_BINS 3
293
+ #define OMX_FBS_PSHR_SELF_SKIP_BINS 3
294
+ #define OMX_FBS_TRACK_SLOTS 512
251
295
  #define OMX_FDELAY_MOD_READ_ORDER 3
252
296
  #define OMX_FDELAY_READ_L1_NORM 1.25f
253
297
  #define OMX_FDELAY_READ_L1_NORM_DOUBLE 1.25
298
+ #define OMX_FLANGER_BASE_MS 0.5f
299
+ #define OMX_FLANGER_BASE_MS_DOUBLE 0.5
254
300
  #define OMX_FLANGER_DEPTH_RANGE_DEFAULT 2
255
301
  #define OMX_FLANGER_DEPTH_RANGE_MAX 5
256
302
  #define OMX_FLANGER_DEPTH_RANGE_MIN 0
@@ -264,6 +310,7 @@
264
310
  #define OMX_FLANGER_FEEDBACK_RANGE_MIN_DOUBLE -0.95
265
311
  #define OMX_FLANGER_FEEDBACK_RANGE_STEP 0.01f
266
312
  #define OMX_FLANGER_FEEDBACK_RANGE_STEP_DOUBLE 0.01
313
+ #define OMX_FLANGER_MAX_MS 6
267
314
  #define OMX_FLANGER_MIX_RANGE_DEFAULT 50
268
315
  #define OMX_FLANGER_MIX_RANGE_MAX 100
269
316
  #define OMX_FLANGER_MIX_RANGE_MIN 0
@@ -296,12 +343,35 @@
296
343
  #define OMX_GEQ_BAND_RANGE_STEP_DOUBLE 0.5
297
344
  #define OMX_GEQ_DESIGN_GAIN_MAX_DB 30
298
345
  #define OMX_GEQ_PROTO_DB 12
346
+ #define OMX_GROWTH_CONFIRM_FRAMES 2
347
+ #define OMX_GROWTH_MAX_DIP_DB 0.5f
348
+ #define OMX_GROWTH_MAX_DIP_DB_DOUBLE 0.5
349
+ #define OMX_GROWTH_MAX_SINGLE_FRAME_SHARE 0.5f
350
+ #define OMX_GROWTH_MAX_SINGLE_FRAME_SHARE_DOUBLE 0.5
351
+ #define OMX_GROWTH_MIN_DB_PER_FRAME 0.8f
352
+ #define OMX_GROWTH_MIN_DB_PER_FRAME_DOUBLE 0.8
353
+ #define OMX_GROWTH_MIN_TOTAL_RISE_DB 6
354
+ #define OMX_GROWTH_WINDOW_FRAMES 5
299
355
  #define OMX_HPF_FREQ_RANGE_MAX 1000
300
356
  #define OMX_HPF_FREQ_RANGE_MIN 20
301
357
  #define OMX_HPF_FREQ_RANGE_STEP 10
358
+ #define OMX_HRP_AMOUNT_RANGE_DEFAULT 0.5f
359
+ #define OMX_HRP_AMOUNT_RANGE_DEFAULT_DOUBLE 0.5
360
+ #define OMX_HRP_AMOUNT_RANGE_MAX 1
361
+ #define OMX_HRP_AMOUNT_RANGE_MIN 0
362
+ #define OMX_HRP_AMOUNT_RANGE_STEP 0
363
+ #define OMX_HRP_ATTRIBUTION_CENTS 60
364
+ #define OMX_HRP_BAND_Q 5
365
+ #define OMX_HRP_COLLISION_CENTS 100
366
+ #define OMX_HRP_DEFAULT_MAX_AUTO_BANDS 8
367
+ #define OMX_HRP_EXCESS_THRESHOLD_DB 3
368
+ #define OMX_HRP_GAIN_STEP_DB 2
369
+ #define OMX_HRP_MAX_CUT_DB 6
370
+ #define OMX_HRP_MAX_VOICES 8
302
371
  #define OMX_LPF_FREQ_RANGE_MAX 20000
303
372
  #define OMX_LPF_FREQ_RANGE_MIN 1000
304
373
  #define OMX_LPF_FREQ_RANGE_STEP 10
374
+ #define OMX_OPERATOR_EQ_BANDS_RESERVE 9
305
375
  #define OMX_PAN_RANGE_MAX 1
306
376
  #define OMX_PAN_RANGE_MIN -1
307
377
  #define OMX_PAN_RANGE_STEP 0
@@ -322,6 +392,7 @@
322
392
  #define OMX_PHASER_FEEDBACK_RANGE_MIN_DOUBLE -0.9
323
393
  #define OMX_PHASER_FEEDBACK_RANGE_STEP 0.01f
324
394
  #define OMX_PHASER_FEEDBACK_RANGE_STEP_DOUBLE 0.01
395
+ #define OMX_PHASER_F_TOP_HZ 16000
325
396
  #define OMX_PHASER_MIX_RANGE_DEFAULT 50
326
397
  #define OMX_PHASER_MIX_RANGE_MAX 100
327
398
  #define OMX_PHASER_MIX_RANGE_MIN 0
@@ -349,11 +420,14 @@
349
420
  #define OMX_PROGRAM_RELEASE_PROFILES_OPTO_SHARE_DOUBLE 0.5
350
421
  #define OMX_PROGRAM_RELEASE_PROFILES_OPTO_SLOW_MAX_MS 1100
351
422
  #define OMX_PROGRAM_RELEASE_PROFILES_OPTO_SLOW_MIN_MS 110
423
+ #define OMX_PSHR_MIN_DB 10
352
424
  #define OMX_REVERB_DAMPING_RANGE_DEFAULT 0.5f
353
425
  #define OMX_REVERB_DAMPING_RANGE_DEFAULT_DOUBLE 0.5
354
426
  #define OMX_REVERB_DAMPING_RANGE_MAX 1
355
427
  #define OMX_REVERB_DAMPING_RANGE_MIN 0
356
428
  #define OMX_REVERB_DAMPING_RANGE_STEP 0
429
+ #define OMX_REVERB_GATE_ATTACK_MS 1.5f
430
+ #define OMX_REVERB_GATE_ATTACK_MS_DOUBLE 1.5
357
431
  #define OMX_REVERB_GATE_THRESHOLD_RANGE_DEFAULT -40
358
432
  #define OMX_REVERB_GATE_THRESHOLD_RANGE_MAX 0
359
433
  #define OMX_REVERB_GATE_THRESHOLD_RANGE_MIN -80
@@ -460,8 +534,12 @@
460
534
  #define OMX_ROTARY_RING_FLOATS 256
461
535
  #define OMX_ROTARY_STOP_EPS 0.0001f
462
536
  #define OMX_ROTARY_STOP_EPS_DOUBLE 0.0001
537
+ #define OMX_RTA_FFT_SIZE_MAX 32768
538
+ #define OMX_RTA_TARGET_BIN_HZ 5.9f
539
+ #define OMX_RTA_TARGET_BIN_HZ_DOUBLE 5.9
463
540
  #define OMX_RT_HARD_TARGET_QUANTUM 128
464
541
  #define OMX_RT_HARD_TARGET_RATE 192000
542
+ #define OMX_SUBHARMONIC_RADIUS_BINS 2
465
543
  #define OMX_TRANSIENT_FAST_ATTACK_MS 0.5f
466
544
  #define OMX_TRANSIENT_FAST_ATTACK_MS_DOUBLE 0.5
467
545
  #define OMX_TRANSIENT_FAST_RELEASE_MS 20
@@ -492,11 +570,131 @@
492
570
  #define OMX_XOVER_PARTITION_TOLERANCE_DOUBLE 0.00001
493
571
 
494
572
  /* Every list rendered to C, sorted by list: OMX_<LIST>_COUNT and OMX_<LIST>_INIT { a, b, … }. */
573
+ #define OMX_EQ_CENTRE_SERIES_R20_HZ_COUNT 61u
574
+ #define OMX_EQ_CENTRE_SERIES_R20_HZ_INIT { 20, 22.4f, 25, 28, 31.5f, 35.5f, 40, 45, 50, 56, 63, 71, 80, 90, 100, 112, 125, 140, 160, 180, 200, 224, 250, 280, 315, 355, 400, 450, 500, 560, 630, 710, 800, 900, 1000, 1120, 1250, 1400, 1600, 1800, 2000, 2240, 2500, 2800, 3150, 3550, 4000, 4500, 5000, 5600, 6300, 7100, 8000, 9000, 10000, 11200, 12500, 14000, 16000, 18000, 20000 }
575
+ #define OMX_EQ_DEFAULT_CENTRES_FOUR_BAND_HZ_COUNT 4u
576
+ #define OMX_EQ_DEFAULT_CENTRES_FOUR_BAND_HZ_INIT { 100, 400, 2000, 8000 }
577
+ #define OMX_ISO_THIRD_OCTAVE_CENTRES_HZ_COUNT 31u
578
+ #define OMX_ISO_THIRD_OCTAVE_CENTRES_HZ_INIT { 20, 25, 31.5f, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000 }
579
+ #define OMX_ORACLE_FLOOR_RATES_COUNT 4u
580
+ #define OMX_ORACLE_FLOOR_RATES_INIT { 44100, 48000, 96000, 192000 }
495
581
  #define OMX_PHASER_STAGE_COUNTS_COUNT 6u
496
582
  #define OMX_PHASER_STAGE_COUNTS_INIT { 2, 4, 6, 8, 10, 12 }
583
+ #define OMX_RME_RATES_COUNT 9u
584
+ #define OMX_RME_RATES_INIT { 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000 }
497
585
  #define OMX_STANDARD_SAMPLE_RATES_COUNT 6u
498
586
  #define OMX_STANDARD_SAMPLE_RATES_INIT { 44100, 48000, 88200, 96000, 176400, 192000 }
499
587
 
588
+ /* 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
+ * where the set names a default, `OMX_<SET>_DEFAULT`. */
590
+ enum omx_deess_modes {
591
+ OMX_DEESS_MODES_SPLIT = 0,
592
+ OMX_DEESS_MODES_WIDEBAND = 1,
593
+ };
594
+ #define OMX_DEESS_MODES_COUNT 2u
595
+ enum omx_drive_bands {
596
+ OMX_DRIVE_BANDS_FULL = 0,
597
+ OMX_DRIVE_BANDS_LOW = 1,
598
+ OMX_DRIVE_BANDS_HIGH = 2,
599
+ OMX_DRIVE_BANDS_TILT = 3,
600
+ };
601
+ #define OMX_DRIVE_BANDS_COUNT 4u
602
+ enum omx_drive_curves {
603
+ OMX_DRIVE_CURVES_SOFT = 0,
604
+ OMX_DRIVE_CURVES_TAPE = 1,
605
+ OMX_DRIVE_CURVES_TUBE = 2,
606
+ OMX_DRIVE_CURVES_EXCITER = 3,
607
+ };
608
+ #define OMX_DRIVE_CURVES_COUNT 4u
609
+ enum omx_eq_band_types {
610
+ OMX_EQ_BAND_TYPES_BELL = 0,
611
+ OMX_EQ_BAND_TYPES_LOW_SHELF = 1,
612
+ OMX_EQ_BAND_TYPES_HIGH_SHELF = 2,
613
+ OMX_EQ_BAND_TYPES_NOTCH = 3,
614
+ OMX_EQ_BAND_TYPES_ALLPASS1 = 4,
615
+ OMX_EQ_BAND_TYPES_ALLPASS2 = 5,
616
+ };
617
+ #define OMX_EQ_BAND_TYPES_COUNT 6u
618
+ #define OMX_EQ_BAND_TYPES_DEFAULT OMX_EQ_BAND_TYPES_BELL
619
+ enum omx_filter_slopes {
620
+ OMX_FILTER_SLOPES_12 = 12,
621
+ OMX_FILTER_SLOPES_24 = 24,
622
+ };
623
+ #define OMX_FILTER_SLOPES_COUNT 2u
624
+ #define OMX_FILTER_SLOPES_DEFAULT OMX_FILTER_SLOPES_12
625
+ enum omx_gate_key_sources {
626
+ OMX_GATE_KEY_SOURCES_SELF = 0,
627
+ OMX_GATE_KEY_SOURCES_SIDECHAIN = 1,
628
+ };
629
+ #define OMX_GATE_KEY_SOURCES_COUNT 2u
630
+ enum omx_param_scales {
631
+ OMX_PARAM_SCALES_LINEAR = 0,
632
+ OMX_PARAM_SCALES_LOG = 1,
633
+ OMX_PARAM_SCALES_STEPPED = 2,
634
+ };
635
+ #define OMX_PARAM_SCALES_COUNT 3u
636
+ #define OMX_PARAM_SCALES_DEFAULT OMX_PARAM_SCALES_LINEAR
637
+ enum omx_reverb_algorithms {
638
+ OMX_REVERB_ALGORITHMS_ROOM = 0,
639
+ OMX_REVERB_ALGORITHMS_PLATE = 1,
640
+ OMX_REVERB_ALGORITHMS_HALL = 2,
641
+ OMX_REVERB_ALGORITHMS_REVERSE = 3,
642
+ OMX_REVERB_ALGORITHMS_GATED = 4,
643
+ };
644
+ #define OMX_REVERB_ALGORITHMS_COUNT 5u
645
+ enum omx_rotary_speeds {
646
+ OMX_ROTARY_SPEEDS_STOP = 0,
647
+ OMX_ROTARY_SPEEDS_SLOW = 1,
648
+ OMX_ROTARY_SPEEDS_FAST = 2,
649
+ };
650
+ #define OMX_ROTARY_SPEEDS_COUNT 3u
651
+ enum omx_tremolo_modes {
652
+ OMX_TREMOLO_MODES_TREMOLO = 0,
653
+ OMX_TREMOLO_MODES_PAN = 1,
654
+ };
655
+ #define OMX_TREMOLO_MODES_COUNT 2u
656
+ #define OMX_TREMOLO_MODES_DEFAULT OMX_TREMOLO_MODES_TREMOLO
657
+
658
+ /* The default bands of an EQ with `count` bands: THE one rule, for every strip, host and count (omx-contract's
659
+ * lib/eq-defaults.mjs; the ts render exports the same as eqDefaultCentres and eqDefaultTypes). Needs -lm. */
660
+ static inline unsigned omx_eq_default_centres(unsigned count, float *out) {
661
+ static const double r10[] = OMX_ISO_THIRD_OCTAVE_CENTRES_HZ_INIT;
662
+ static const double r20[] = OMX_EQ_CENTRE_SERIES_R20_HZ_INIT;
663
+ static const double four[] = OMX_EQ_DEFAULT_CENTRES_FOUR_BAND_HZ_INIT;
664
+ if (count == 0) return 0;
665
+ if (count == 4) {
666
+ for (unsigned i = 0; i < 4; i++) out[i] = (float)four[i];
667
+ return 4;
668
+ }
669
+ const double lo = log((double)OMX_EQ_FREQ_RANGE_MIN);
670
+ const double span = log((double)OMX_EQ_FREQ_RANGE_MAX / (double)OMX_EQ_FREQ_RANGE_MIN);
671
+ for (unsigned s = 0; s < 2; s++) {
672
+ const double *series = s ? r20 : r10;
673
+ const unsigned n = s ? OMX_EQ_CENTRE_SERIES_R20_HZ_COUNT : OMX_ISO_THIRD_OCTAVE_CENTRES_HZ_COUNT;
674
+ int distinct = count <= n;
675
+ for (unsigned i = 0; distinct && i < count; i++) {
676
+ const double t = lo + span * ((double)i + 0.5) / (double)count;
677
+ unsigned best = 0;
678
+ double best_d = 1e300;
679
+ for (unsigned k = 0; k < n; k++) {
680
+ const double d = fabs(log(series[k]) - t);
681
+ if (d < best_d) { best_d = d; best = k; }
682
+ }
683
+ out[i] = (float)series[best];
684
+ if (i > 0 && out[i] == out[i - 1]) distinct = 0;
685
+ }
686
+ if (distinct) return count;
687
+ }
688
+ return 0;
689
+ }
690
+
691
+ /* The default type of band `index` of an EQ with `count` bands: a low shelf first, a high shelf last, bells
692
+ * between (a single band is a bell). An OMX_EQ_BAND_TYPES_* member. */
693
+ static inline int omx_eq_default_type(unsigned index, unsigned count) {
694
+ if (count < 2) return OMX_EQ_BAND_TYPES_BELL;
695
+ return index == 0 ? OMX_EQ_BAND_TYPES_LOW_SHELF : index == count - 1 ? OMX_EQ_BAND_TYPES_HIGH_SHELF : OMX_EQ_BAND_TYPES_BELL;
696
+ }
697
+
500
698
  #endif /* OMX_CONTRACT_LIMITS_H */
501
699
 
502
700
  /* The sample rates the console declares (core's STANDARD_SAMPLE_RATES, row-codecs.ts): the set a
package/lib/data.mjs CHANGED
@@ -50,6 +50,7 @@ export function rawValue(item) {
50
50
  case 'travels': return item.fields ?? item.travel;
51
51
  case 'sheet': return item.value;
52
52
  case 'list': return item.values;
53
+ case 'set': return item.ids;
53
54
  default: return undefined;
54
55
  }
55
56
  }
@@ -66,6 +67,11 @@ export const DERIVATIONS = {
66
67
  if (typeof v !== 'number' || v < 0) throw new Error(`sqrt: ${JSON.stringify(d.of)} is not a non-negative number`);
67
68
  return Math.sqrt(v);
68
69
  },
70
+ ceilSum: (d, val) => {
71
+ const terms = d.of.map((x) => val(x));
72
+ if (!terms.every((t) => typeof t === 'number' && Number.isFinite(t))) throw new Error(`ceilSum: ${JSON.stringify(d.of)} holds a term that is not a finite number`);
73
+ return Math.ceil(terms.reduce((a, t) => a + t, 0));
74
+ },
69
75
  lagrangeReadL1: (d, val) => {
70
76
  const order = val(d.order);
71
77
  if (!Number.isInteger(order) || order < 1) throw new Error(`lagrangeReadL1: order ${order} is not a positive integer`);
@@ -104,7 +110,7 @@ export function lagrangeReadL1(order) {
104
110
  const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
105
111
 
106
112
  /**
107
- * Resolve every item: `{ name, rel, kind, doc, why?, unit?, c?, shape?, value }`, where `value` has
113
+ * Resolve every item: `{ name, rel, kind, doc, why?, unit?, c?, shape?, default?, labels?, value }` (default and labels: a set's), where `value` has
108
114
  * every `{ref}` and `{derive}` evaluated.
109
115
  * @throws naming the item: a reference that does not resolve, a cycle, an unknown derivation.
110
116
  */
@@ -142,6 +148,10 @@ export function resolveData(data) {
142
148
  if (item.why !== undefined) r.why = item.why;
143
149
  if (item.unit !== undefined) r.unit = item.unit;
144
150
  if (item.c !== undefined) r.c = item.c;
151
+ if (item.kind === 'set') {
152
+ if (item.default !== undefined) r.default = item.default;
153
+ if (item.labels !== undefined) r.labels = item.labels;
154
+ }
145
155
  if (item.kind === 'travels') r.shape = item.fields ? 'table' : 'travel';
146
156
  r.value = val(rawValue(item), name);
147
157
  busy.delete(name);
@@ -0,0 +1,87 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * THE default rule for the bands of a fresh EQ, whatever the strip, the host or the band count: one
5
+ * rule, no override, for the console, omx-plugins and any other host. Per strip type the contract
6
+ * declares only the band counts (EQ_BAND_COUNTS: default and maximum); the bands come from here.
7
+ *
8
+ * Types the first band is a low shelf and the last a high shelf (each switchable to a bell by the
9
+ * operator); every band between is a bell. A single band is a bell.
10
+ * Gain, Q every band is EQ_BAND_DEFAULTS.gainDb and .q.
11
+ * Centres
12
+ * 4 bands the declared four (EQ_DEFAULT_CENTRES_FOUR_BAND_HZ): 100, 400, 2000, 8000 Hz,
13
+ * ISO 266 preferred values.
14
+ * N bands N slots of equal width on a log axis across EQ_FREQ_RANGE; band i sits in the
15
+ * middle of slot i (lo * (hi / lo) ** ((i + 0.5) / N)) and takes the nearest preferred
16
+ * frequency on that axis, from the ISO 266 R10 series (ISO_THIRD_OCTAVE_CENTRES_HZ);
17
+ * when two bands would share a value in R10 the same is done on its R20 refinement
18
+ * (EQ_CENTRE_SERIES_R20_HZ). Never a repeated centre: more bands than R20 holds is an
19
+ * error.
20
+ *
21
+ * The same functions are exported by the ts render (`eqDefaultCentres`, `eqDefaultTypes`,
22
+ * `eqDefaultBands`) and the c render (`omx_eq_default_centres`, `omx_eq_default_type`); test/eq-defaults.test.mjs holds the three equal for every count.
23
+ * This file is the one JavaScript spelling: the ts render embeds its source.
24
+ */
25
+
26
+ /**
27
+ * The default band centres (Hz) of an EQ with `count` bands.
28
+ * @param {number} count how many bands the strip's EQ starts with, a positive integer.
29
+ * @param {{ four: number[], series: number[][], lo: number, hi: number }} p the declared four-band
30
+ * set, the preferred-frequency series from coarsest to finest, and the band frequency travel.
31
+ * @returns {number[]}
32
+ */
33
+ export function eqDefaultCentres(count, p) {
34
+ if (!Number.isInteger(count) || count < 1) throw new RangeError(`eqDefaultCentres: ${count} is not a band count`);
35
+ if (count === 4) return [...p.four];
36
+ const targets = [];
37
+ for (let i = 0; i < count; i++) targets.push(Math.log(p.lo) + (Math.log(p.hi / p.lo) * (i + 0.5)) / count);
38
+ for (const series of p.series) {
39
+ const out = targets.map((t) => {
40
+ let best = series[0];
41
+ let bestD = Infinity;
42
+ for (const f of series) {
43
+ const d = Math.abs(Math.log(f) - t);
44
+ if (d < bestD) { bestD = d; best = f; }
45
+ }
46
+ return best;
47
+ });
48
+ if (new Set(out).size === out.length) return out;
49
+ }
50
+ throw new RangeError(`eqDefaultCentres: ${count} bands have no distinct preferred centres across ${p.lo}..${p.hi} Hz`);
51
+ }
52
+
53
+ /**
54
+ * The band types of an EQ with `count` bands, by EQ_BAND_TYPES id: a low shelf, bells, a high shelf.
55
+ * @param {number} count
56
+ * @returns {string[]}
57
+ */
58
+ export function eqDefaultTypes(count) {
59
+ if (!Number.isInteger(count) || count < 1) throw new RangeError(`eqDefaultTypes: ${count} is not a band count`);
60
+ return Array.from({ length: count }, (_, i) => (count === 1 ? 'bell' : i === 0 ? 'lowShelf' : i === count - 1 ? 'highShelf' : 'bell'));
61
+ }
62
+
63
+ /**
64
+ * Every default band of an EQ with `count` bands.
65
+ * @param {number} count
66
+ * @param {{ four: number[], series: number[][], lo: number, hi: number, gainDb: number, q: number }} p
67
+ * @returns {{ type: string, freqHz: number, gainDb: number, q: number }[]}
68
+ */
69
+ export function eqDefaultBands(count, p) {
70
+ const types = eqDefaultTypes(count);
71
+ return eqDefaultCentres(count, p).map((freqHz, i) => ({ type: types[i], freqHz, gainDb: p.gainDb, q: p.q }));
72
+ }
73
+
74
+ /** The parameters of the rule, read off the resolved items. */
75
+ export function eqDefaultParams(resolved) {
76
+ const v = (name) => {
77
+ const r = resolved.get(name);
78
+ if (!r) throw new Error(`the default centre rule needs ${name}`);
79
+ return r.value;
80
+ };
81
+ const range = v('EQ_FREQ_RANGE');
82
+ const band = v('EQ_BAND_DEFAULTS');
83
+ return {
84
+ four: v('EQ_DEFAULT_CENTRES_FOUR_BAND_HZ'), series: [v('ISO_THIRD_OCTAVE_CENTRES_HZ'), v('EQ_CENTRE_SERIES_R20_HZ')],
85
+ lo: range.min, hi: range.max, gainDb: band.gainDb, q: band.q,
86
+ };
87
+ }
package/lib/fmt.mjs CHANGED
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  const ORDER = {
10
- item: ['kind', 'doc', 'why', 'unit', 'value', 'values', 'fields', 'travel', 'c'],
11
- travel: ['min', 'max', 'step', 'unit', 'default', 'defaultFrom', 'byKind', 'doc', 'why'],
10
+ item: ['kind', 'since', 'doc', 'why', 'unit', 'value', 'values', 'ids', 'default', 'labels', 'fields', 'travel', 'c'],
11
+ travel: ['min', 'max', 'step', 'unit', 'default', 'defaultFrom', 'scale', 'byKind', 'doc', 'why'],
12
12
  c: ['alias', 'render'],
13
13
  alias: ['fact', 'field'],
14
14
  number: ['ref', 'derive', 'of', 'order'],
package/lib/semver.mjs CHANGED
@@ -7,8 +7,8 @@
7
7
  * major a field or alias removed; an item removed (MINOR in the development phase, below); a
8
8
  * unit or kind changed; a travel narrowed; a constant a kernel computes with changed (a
9
9
  * scalar, sheet leaf or derived value that is not a travel bound or default; a list member
10
- * changed or removed)
11
- * minor an item, field, sheet leaf or list member added (appended); a travel widened;
10
+ * or set id changed or removed; a set's default added, removed)
11
+ * minor an item, field, sheet leaf, list member or set id added (appended); a travel widened;
12
12
  * a default moved inside its travel; a step changed
13
13
  * patch doc and why text
14
14
  */
@@ -32,6 +32,7 @@ function travelChanges(at, a, b, say) {
32
32
  if (a.default !== b.default) say(a.default === undefined || b.default === undefined ? 'major' : 'minor', `${at}: default ${a.default} -> ${b.default}`);
33
33
  if (a.step !== b.step) say('minor', `${at}: step ${a.step} -> ${b.step}`);
34
34
  if (a.defaultFrom !== b.defaultFrom) say('minor', `${at}: defaultFrom ${a.defaultFrom} -> ${b.defaultFrom}`);
35
+ if (a.scale !== b.scale) say('minor', `${at}: scale ${a.scale} -> ${b.scale}`);
35
36
  const ka = a.byKind ?? {};
36
37
  const kb = b.byKind ?? {};
37
38
  for (const k of Object.keys(ka)) if (!(k in kb)) say('major', `${at}.byKind.${k} removed`);
@@ -92,6 +93,15 @@ export function classify(prev, next, { developmentPhase = DEVELOPMENT_PHASE } =
92
93
  });
93
94
  if (b.value.length > a.value.length) say('minor', `${name}: ${b.value.length - a.value.length} member(s) appended`);
94
95
  break;
96
+ case 'set':
97
+ a.value.forEach((v, i) => {
98
+ if (i >= b.value.length) say('major', `${name}[${i}] removed`);
99
+ else if (b.value[i] !== v) say('major', `${name}[${i}]: ${v} -> ${b.value[i]}`);
100
+ });
101
+ if (b.value.length > a.value.length) say('minor', `${name}: ${b.value.length - a.value.length} id(s) appended`);
102
+ if (a.default !== b.default) say(a.default === undefined || b.default === undefined ? 'major' : 'minor', `${name}: default ${a.default} -> ${b.default}`);
103
+ if (!same(a.labels, b.labels)) say('patch', `${name}: labels`);
104
+ break;
95
105
  default:
96
106
  break;
97
107
  }
package/lib/validate.mjs CHANGED
@@ -9,6 +9,7 @@ import { readFileSync } from 'node:fs';
9
9
  import { posix } from 'node:path';
10
10
  import Ajv2020 from 'ajv/dist/2020.js';
11
11
  import { resolveData } from './data.mjs';
12
+ import { eqDefaultBands, eqDefaultParams } from './eq-defaults.mjs';
12
13
  import { renderC } from '../render/c.mjs';
13
14
  import { renderTs } from '../render/ts.mjs';
14
15
 
@@ -22,6 +23,62 @@ export function schemaValidator() {
22
23
 
23
24
  const constCase = (s) => s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
24
25
 
26
+ /**
27
+ * The rules about the meaning of the numbers, not their shape (they live with the data they judge):
28
+ * a set's default and labels; a travel's scale is a PARAM_SCALES id; the EQ band counts and budget; the
29
+ * default EQ rule's inputs.
30
+ */
31
+ function semanticRules(resolved, say) {
32
+ const val = (name) => resolved.get(name)?.value;
33
+ for (const r of resolved.values()) {
34
+ if (r.kind === 'set') {
35
+ if (r.default !== undefined && !r.value.includes(r.default)) say(r.rel, r.name, 'set-default', `default ${JSON.stringify(r.default)} is not one of ${JSON.stringify(r.value)}`);
36
+ if (r.labels !== undefined && r.labels.length !== r.value.length) say(r.rel, r.name, 'set-labels', `${r.labels.length} labels for ${r.value.length} ids`);
37
+ }
38
+ const travels = r.kind === 'travels' ? (r.shape === 'table' ? Object.entries(r.value) : [['', r.value]]) : [];
39
+ for (const [field, t] of travels) {
40
+ if (t.scale !== undefined && !(val('PARAM_SCALES') ?? []).includes(t.scale)) say(r.rel, r.name, 'scale', `${field ? `${r.name}.${field}` : r.name}: scale ${JSON.stringify(t.scale)} is not an id of PARAM_SCALES`);
41
+ }
42
+ }
43
+
44
+ // The EQ band budget (ruling 2026-10-08): what the operator keeps, what the feedback suppressor plants
45
+ // and what the ring correction plants all fit in the band maximum.
46
+ const reserve = val('OPERATOR_EQ_BANDS_RESERVE');
47
+ if (reserve !== undefined) {
48
+ const need = reserve + val('FBS_DEFAULT_MAX_AUTO_BANDS') + val('HRP_DEFAULT_MAX_AUTO_BANDS');
49
+ const cap = val('EQ_MAX_BANDS');
50
+ if (!(need <= cap)) say(resolved.get('OPERATOR_EQ_BANDS_RESERVE').rel, 'OPERATOR_EQ_BANDS_RESERVE', 'eq-band-budget',
51
+ `OPERATOR_EQ_BANDS_RESERVE ${reserve} + FBS_DEFAULT_MAX_AUTO_BANDS ${val('FBS_DEFAULT_MAX_AUTO_BANDS')} + HRP_DEFAULT_MAX_AUTO_BANDS ${val('HRP_DEFAULT_MAX_AUTO_BANDS')} = ${need} exceeds EQ_MAX_BANDS ${cap}`);
52
+ }
53
+ const counts = val('EQ_BAND_COUNTS');
54
+ if (counts) {
55
+ const rel = resolved.get('EQ_BAND_COUNTS').rel;
56
+ for (const [strip, c] of Object.entries(counts)) {
57
+ for (const k of ['default', 'max']) if (!Number.isInteger(c[k]) || c[k] < 1) say(rel, 'EQ_BAND_COUNTS', 'eq-band-counts', `${strip}.${k} ${c[k]} is not a positive integer`);
58
+ if (c.default > c.max) say(rel, 'EQ_BAND_COUNTS', 'eq-band-counts', `${strip}: default ${c.default} exceeds max ${c.max}`);
59
+ }
60
+ }
61
+
62
+ // The default EQ rule: its inputs are consistent and it answers for every declared default count.
63
+ if (resolved.has('EQ_DEFAULT_CENTRES_FOUR_BAND_HZ')) {
64
+ const rel = resolved.get('EQ_DEFAULT_CENTRES_FOUR_BAND_HZ').rel;
65
+ const bad = (rule, message) => say(rel, 'EQ_DEFAULT_CENTRES_FOUR_BAND_HZ', rule, message);
66
+ let p;
67
+ try { p = eqDefaultParams(resolved); } catch (e) { bad('eq-default-rule', e.message); return; }
68
+ const [r10, r20] = p.series;
69
+ if (p.four.length !== 4) bad('eq-default-rule', `the four-band set holds ${p.four.length} centres`);
70
+ for (const [name, list] of [['four-band set', p.four], ['R10', r10], ['R20', r20]]) {
71
+ if (!list.every((f, i) => f >= p.lo && f <= p.hi && (i === 0 || f > list[i - 1]))) bad('eq-default-rule', `the ${name} is not ascending inside [${p.lo}, ${p.hi}] Hz`);
72
+ }
73
+ for (const f of [...p.four, ...r10]) if (!r20.includes(f)) bad('eq-default-rule', `${f} Hz is not in the R20 series: R10 and the four-band set are preferred values of it`);
74
+ const types = val('EQ_BAND_TYPES') ?? [];
75
+ for (const id of ['bell', 'lowShelf', 'highShelf']) if (!types.includes(id)) bad('eq-default-rule', `EQ_BAND_TYPES lacks ${id}, which the default rule uses`);
76
+ for (const [strip, c] of Object.entries(counts ?? {})) {
77
+ try { eqDefaultBands(c.default, p); } catch (e) { bad('eq-default-rule', `${strip}: ${e.message}`); }
78
+ }
79
+ }
80
+ }
81
+
25
82
  /** Every problem of the loaded data: `[{ file, item, rule, message }]`; empty is valid. */
26
83
  export function validateData(data) {
27
84
  const problems = [];
@@ -71,6 +128,8 @@ export function validateData(data) {
71
128
  if (typeof r.doc !== 'string' || !r.doc.trim()) say(r.rel, r.name, 'doc', 'every item carries its doc');
72
129
  }
73
130
 
131
+ semanticRules(resolved, say);
132
+
74
133
  for (const [target, files] of [['c', () => renderC(resolved)], ['ts', () => renderTs(resolved)]]) {
75
134
  let out;
76
135
  try {
@@ -82,7 +141,7 @@ export function validateData(data) {
82
141
  if (target !== 'c') continue;
83
142
  for (const f of out) {
84
143
  const seen = new Map();
85
- for (const m of f.text.matchAll(/^#define (\w+)/gm)) {
144
+ for (const m of f.text.matchAll(/^(?:#define | )(OMX_\w+)(?= = |\s)/gm)) {
86
145
  if (seen.has(m[1])) say('-', m[1], 'c-name-unique', `${f.path} defines ${m[1]} twice`);
87
146
  seen.set(m[1], true);
88
147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmixer/omx-contract",
3
- "version": "1.2.1",
3
+ "version": "1.3.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",