@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/lib/fmt.mjs
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* The one formatting of a data file (spec §3.2): two-space indent, one key per line, keys in schema
|
|
5
5
|
* order, a trailing newline. Names (items, fields, sheet keys) keep the order the author gave them:
|
|
6
|
-
* a travels table's field order is its C block order.
|
|
6
|
+
* a travels table's field order is its C block order. A kernel file's sections come in their order (controls,
|
|
7
|
+
* tables, aggregates, constants), its controls in the author's order, each control's keys in schema order.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
const ORDER = {
|
|
11
|
+
control: ['name', 'kind', 'global', 'table', 'rearms', 'since', 'doc', 'why', 'unit', 'ids', 'default', 'labels', 'travel', 'c'],
|
|
12
|
+
sections: ['controls', 'tables', 'aggregates', 'constants'],
|
|
10
13
|
item: ['kind', 'since', 'doc', 'why', 'unit', 'value', 'values', 'ids', 'default', 'labels', 'fields', 'travel', 'c'],
|
|
11
14
|
travel: ['min', 'max', 'step', 'unit', 'default', 'defaultFrom', 'scale', 'byKind', 'doc', 'why'],
|
|
12
15
|
c: ['alias', 'render'],
|
|
@@ -52,10 +55,27 @@ function item(it) {
|
|
|
52
55
|
return o;
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
function control(c) {
|
|
59
|
+
if (!isObj(c)) return c;
|
|
60
|
+
const o = ordered(c, ORDER.control);
|
|
61
|
+
if (isObj(o.travel)) o.travel = travel(o.travel);
|
|
62
|
+
if (isObj(o.c)) o.c = item({ c: o.c }).c;
|
|
63
|
+
return o;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const items = (m) => (isObj(m) ? Object.fromEntries(Object.entries(m).map(([k, v]) => [k, item(v)])) : m);
|
|
67
|
+
|
|
55
68
|
/** The canonical text of a parsed data file. */
|
|
56
69
|
export function formatDoc(doc) {
|
|
57
70
|
const out = {};
|
|
58
71
|
if ('$schema' in doc) out.$schema = doc.$schema;
|
|
59
|
-
|
|
72
|
+
if (ORDER.sections.some((k) => k in doc)) {
|
|
73
|
+
for (const [k, v] of Object.entries(ordered(doc, ORDER.sections))) {
|
|
74
|
+
if (k === '$schema') continue;
|
|
75
|
+
out[k] = k === 'controls' && Array.isArray(v) ? v.map(control) : ORDER.sections.includes(k) ? items(v) : item(v);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
for (const [k, v] of Object.entries(doc)) if (k !== '$schema') out[k] = item(v);
|
|
79
|
+
}
|
|
60
80
|
return `${JSON.stringify(out, null, 2)}\n`;
|
|
61
81
|
}
|
package/lib/validate.mjs
CHANGED
|
@@ -8,13 +8,51 @@
|
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
9
|
import { posix } from 'node:path';
|
|
10
10
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
11
|
-
import { resolveData } from './data.mjs';
|
|
11
|
+
import { KERNEL_SECTIONS, entriesOf, isKernelFile, resolveData } from './data.mjs';
|
|
12
12
|
import { eqDefaultBands, eqDefaultParams } from './eq-defaults.mjs';
|
|
13
13
|
import { renderC } from '../render/c.mjs';
|
|
14
14
|
import { renderTs } from '../render/ts.mjs';
|
|
15
15
|
|
|
16
16
|
const SCHEMA = JSON.parse(readFileSync(new URL('../schema/omx-contract.schema.json', import.meta.url), 'utf8'));
|
|
17
17
|
let compiled;
|
|
18
|
+
/**
|
|
19
|
+
* The kernel-file shape (2.0.0), which the item schema cannot see: only the four sections at the top; every control
|
|
20
|
+
* named, of kind travel or choice, unique by name, a travel carrying a `travel` and a choice its `ids`; a control's
|
|
21
|
+
* `table` declared under `tables`; `rearms` a boolean, and only on a control; an aggregate's fields naming controls; a constant a scalar, a list or a sheet.
|
|
22
|
+
* A kernel file written as a flat item map (the 1.x shape) is refused as a whole. Any other data file returns [].
|
|
23
|
+
*/
|
|
24
|
+
export function shapeProblems(rel, doc) {
|
|
25
|
+
if (!isKernelFile(rel)) return [];
|
|
26
|
+
const sections = KERNEL_SECTIONS;
|
|
27
|
+
const keys = Object.keys(doc).filter((k) => k !== '$schema');
|
|
28
|
+
if (keys.length && !keys.some((k) => sections.includes(k))) {
|
|
29
|
+
return [`${rel} is a flat item map (the 1.x shape): a kernel file declares its items under ${sections.join(', ')}`];
|
|
30
|
+
}
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const k of keys) if (!sections.includes(k)) out.push(`unknown top-level key ${k}: a kernel file holds ${sections.join(', ')}`);
|
|
33
|
+
const controls = doc.controls ?? [];
|
|
34
|
+
if (!Array.isArray(controls)) return [...out, 'controls must be a list'];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const [i, c] of controls.entries()) {
|
|
37
|
+
if (typeof c.name !== 'string' || !/^[a-z][A-Za-z0-9]*$/.test(c.name)) out.push(`controls[${i}]: name ${JSON.stringify(c.name)} is not a lowerCamel identifier`);
|
|
38
|
+
if (seen.has(c.name)) out.push(`controls[${i}]: name ${c.name} declared twice`);
|
|
39
|
+
seen.add(c.name);
|
|
40
|
+
if (c.kind === 'travel' && !c.travel) out.push(`controls[${i}] ${c.name}: a travel without "travel"`);
|
|
41
|
+
else if (c.kind === 'choice' && !Array.isArray(c.ids)) out.push(`controls[${i}] ${c.name}: a choice without "ids"`);
|
|
42
|
+
else if (c.kind !== 'travel' && c.kind !== 'choice') out.push(`controls[${i}] ${c.name}: kind ${JSON.stringify(c.kind)} is neither travel nor choice`);
|
|
43
|
+
if (c.table && !(doc.tables && c.table in doc.tables)) out.push(`controls[${i}] ${c.name}: table ${c.table} is not declared`);
|
|
44
|
+
if ('rearms' in c && typeof c.rearms !== 'boolean') out.push(`controls[${i}] ${c.name}: rearms ${JSON.stringify(c.rearms)} is not a boolean`);
|
|
45
|
+
}
|
|
46
|
+
for (const s of ['tables', 'aggregates', 'constants']) {
|
|
47
|
+
for (const [n, it] of Object.entries(doc[s] ?? {})) if (it && typeof it === 'object' && 'rearms' in it) out.push(`${s} ${n}: rearms is a control's flag, never a ${s.slice(0, -1)}'s`);
|
|
48
|
+
}
|
|
49
|
+
for (const [n, a] of Object.entries(doc.aggregates ?? {})) for (const f of a.fields ?? []) if (!seen.has(f)) out.push(`aggregate ${n}: field ${f} names no control`);
|
|
50
|
+
for (const [n, it] of Object.entries(doc.constants ?? {})) {
|
|
51
|
+
if (!['scalar', 'list', 'sheet'].includes(it?.kind)) out.push(`constant ${n}: kind ${JSON.stringify(it?.kind)} is not scalar, list or sheet (a travel or a set is a control)`);
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
18
56
|
/** The compiled schema validator. */
|
|
19
57
|
export function schemaValidator() {
|
|
20
58
|
if (!compiled) compiled = new Ajv2020({ allErrors: true, strict: true, strictRequired: false, allowUnionTypes: true }).compile(SCHEMA);
|
|
@@ -88,7 +126,10 @@ export function validateData(data) {
|
|
|
88
126
|
const depth = f.rel.split('/').length - 1;
|
|
89
127
|
const want = posix.join(...Array(depth).fill('..'), 'schema/omx-contract.schema.json');
|
|
90
128
|
if (f.doc.$schema !== want) say(f.rel, '-', 'schema-path', `"$schema" must be "${want}"`);
|
|
91
|
-
|
|
129
|
+
for (const m of shapeProblems(f.rel, f.doc)) say(f.rel, '-', 'shape', m);
|
|
130
|
+
// The schema reads items: a kernel file is checked as the items it expands to, so no rule is lost.
|
|
131
|
+
const items = { $schema: f.doc.$schema, ...Object.fromEntries(entriesOf(f.rel, f.doc)) };
|
|
132
|
+
if (!validate(items)) {
|
|
92
133
|
for (const e of validate.errors) {
|
|
93
134
|
const item = e.instancePath.split('/')[1] || '-';
|
|
94
135
|
say(f.rel, item, 'schema', `${e.instancePath || '/'} ${e.message}${e.params?.allowedValues ? ` (${e.params.allowedValues.join(', ')})` : ''}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmixer/omx-contract",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.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",
|
package/render/json.mjs
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
3
|
/**
|
|
4
4
|
* The `json` target: the resolved data (every reference and derivation evaluated) for readers in
|
|
5
|
-
* other languages, which need no resolver of their own.
|
|
5
|
+
* other languages, which need no resolver of their own. Beside the items, `kernels` lists each
|
|
6
|
+
* kernel's controls in their declared order (name, kind, the global each resolves to, the table of a
|
|
7
|
+
* table field, `rearms` when changing it re-arms the kernel's state), so a reader needs no kernel file.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
/** Every file of the `json` target: `[{ path, text }]`. */
|
|
@@ -12,5 +14,7 @@ export function renderJson(resolved, version) {
|
|
|
12
14
|
const { name, ...rest } = r;
|
|
13
15
|
items[name] = rest;
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
const doc = { name: '@openmixer/omx-contract', version, items };
|
|
18
|
+
if (resolved.kernels) doc.kernels = resolved.kernels;
|
|
19
|
+
return [{ path: 'omx-contract.json', text: `${JSON.stringify(doc, null, 2)}\n` }];
|
|
16
20
|
}
|