@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.
- package/README.md +9 -4
- package/data/kernels/chorus.json +22 -0
- package/data/kernels/deesser.json +8 -0
- package/data/kernels/drive.json +20 -0
- package/data/kernels/eq.json +160 -4
- package/data/kernels/fbs.json +148 -0
- package/data/kernels/flanger.json +22 -0
- package/data/kernels/gate.json +8 -0
- package/data/kernels/hrp.json +62 -0
- package/data/kernels/mixmatrix.json +9 -0
- package/data/kernels/phaser.json +6 -0
- package/data/kernels/reverb.json +17 -0
- package/data/kernels/rotary.json +9 -0
- package/data/kernels/rta.json +15 -0
- package/data/kernels/tremolo.json +9 -0
- package/data/primitives.json +51 -0
- package/data/rates.json +29 -0
- package/dist/ts/omx-contract.d.ts +416 -0
- package/dist/ts/omx-contract.js +682 -0
- package/include/omxcontract/omx_contract_limits.h +198 -0
- package/lib/data.mjs +11 -1
- package/lib/eq-defaults.mjs +87 -0
- package/lib/fmt.mjs +2 -2
- package/lib/semver.mjs +12 -2
- package/lib/validate.mjs +60 -1
- package/package.json +1 -1
- package/render/c.mjs +76 -10
- package/render/doors.mjs +6 -3
- package/render/ts.mjs +40 -2
- package/schema/omx-contract.schema.json +39 -3
- package/share/omx-contract/omx-contract.json +590 -4
- package/render/c/omx_plugin_param.h +0 -25
- package/render/c/params-generate.mjs +0 -83
package/render/c.mjs
CHANGED
|
@@ -4,17 +4,13 @@
|
|
|
4
4
|
* The `c` target: `omxcontract/omx_contract_limits.h`. Every define is spelled by openmixer's
|
|
5
5
|
* renderer, moved unchanged (c/contract-limits-render.mjs); only the frame (banner, section
|
|
6
6
|
* comments) is this repository's. A plugin's parameter header is rendered by omx-plugins from its
|
|
7
|
-
* declaration
|
|
8
|
-
* round-trip proof.
|
|
7
|
+
* declaration, by reference to the items here.
|
|
9
8
|
*/
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
11
9
|
import {
|
|
12
10
|
genCoreLimitBlock, genDeclaredListsBlock, genDeclaredRates, genDeclaredScalarsBlock, genLimitBlock, renderContractLimits,
|
|
13
11
|
} from './c/contract-limits-render.mjs';
|
|
14
|
-
import { generate } from './c/params-generate.mjs';
|
|
15
12
|
import { doors } from './doors.mjs';
|
|
16
13
|
|
|
17
|
-
const PLUGIN_PARAM_H = readFileSync(new URL('./c/omx_plugin_param.h', import.meta.url), 'utf8');
|
|
18
14
|
const defined = (text) => new Set([...text.matchAll(/^#define (OMX_\w+)/gm)].map((m) => m[1]));
|
|
19
15
|
|
|
20
16
|
/** The rate tail of openmixer's header, outside the include guard, byte for byte. */
|
|
@@ -39,6 +35,71 @@ static inline int omx_declared_rates_multiple_of(unsigned base, int *out, int ca
|
|
|
39
35
|
`;
|
|
40
36
|
}
|
|
41
37
|
|
|
38
|
+
const constCase = (s) => String(s).replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
|
|
39
|
+
|
|
40
|
+
/** The enumerator names of one set, in order: `OMX_<SET>_<ID>`. */
|
|
41
|
+
export const setEnumerators = (set) => set.ids.map((id) => `OMX_${set.name}_${constCase(id)}`);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Every set as a C enum: the id's position is its value for named ids, the number itself for numeric
|
|
45
|
+
* ids; `OMX_<SET>_COUNT`, and `OMX_<SET>_DEFAULT` when the set names a default.
|
|
46
|
+
*/
|
|
47
|
+
function genDeclaredSetsEnums(sets) {
|
|
48
|
+
return sets.map((set) => {
|
|
49
|
+
const names = setEnumerators(set);
|
|
50
|
+
const numeric = typeof set.ids[0] === 'number';
|
|
51
|
+
const members = set.ids.map((id, i) => ` ${names[i]} = ${numeric ? id : i},`);
|
|
52
|
+
const lines = [`enum omx_${set.name.toLowerCase()} {`, ...members, '};', `#define OMX_${set.name}_COUNT ${set.ids.length}u`];
|
|
53
|
+
if (set.default !== undefined) lines.push(`#define OMX_${set.name}_DEFAULT ${names[set.ids.indexOf(set.default)]}`);
|
|
54
|
+
return lines.join('\n');
|
|
55
|
+
}).join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The default band centres of an EQ (lib/eq-defaults.mjs is the rule; this is its C spelling, held
|
|
60
|
+
* equal to it for every count by test/eq-defaults.test.mjs). Writes \`count\` centres to \`out\` and returns
|
|
61
|
+
* \`count\`, or returns 0 for a count that is 0 or has no distinct preferred centres.
|
|
62
|
+
*/
|
|
63
|
+
function eqCentresFunction() {
|
|
64
|
+
return `static inline unsigned omx_eq_default_centres(unsigned count, float *out) {
|
|
65
|
+
static const double r10[] = OMX_ISO_THIRD_OCTAVE_CENTRES_HZ_INIT;
|
|
66
|
+
static const double r20[] = OMX_EQ_CENTRE_SERIES_R20_HZ_INIT;
|
|
67
|
+
static const double four[] = OMX_EQ_DEFAULT_CENTRES_FOUR_BAND_HZ_INIT;
|
|
68
|
+
if (count == 0) return 0;
|
|
69
|
+
if (count == 4) {
|
|
70
|
+
for (unsigned i = 0; i < 4; i++) out[i] = (float)four[i];
|
|
71
|
+
return 4;
|
|
72
|
+
}
|
|
73
|
+
const double lo = log((double)OMX_EQ_FREQ_RANGE_MIN);
|
|
74
|
+
const double span = log((double)OMX_EQ_FREQ_RANGE_MAX / (double)OMX_EQ_FREQ_RANGE_MIN);
|
|
75
|
+
for (unsigned s = 0; s < 2; s++) {
|
|
76
|
+
const double *series = s ? r20 : r10;
|
|
77
|
+
const unsigned n = s ? OMX_EQ_CENTRE_SERIES_R20_HZ_COUNT : OMX_ISO_THIRD_OCTAVE_CENTRES_HZ_COUNT;
|
|
78
|
+
int distinct = count <= n;
|
|
79
|
+
for (unsigned i = 0; distinct && i < count; i++) {
|
|
80
|
+
const double t = lo + span * ((double)i + 0.5) / (double)count;
|
|
81
|
+
unsigned best = 0;
|
|
82
|
+
double best_d = 1e300;
|
|
83
|
+
for (unsigned k = 0; k < n; k++) {
|
|
84
|
+
const double d = fabs(log(series[k]) - t);
|
|
85
|
+
if (d < best_d) { best_d = d; best = k; }
|
|
86
|
+
}
|
|
87
|
+
out[i] = (float)series[best];
|
|
88
|
+
if (i > 0 && out[i] == out[i - 1]) distinct = 0;
|
|
89
|
+
}
|
|
90
|
+
if (distinct) return count;
|
|
91
|
+
}
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* The default type of band \`index\` of an EQ with \`count\` bands: a low shelf first, a high shelf last, bells
|
|
96
|
+
* between (a single band is a bell). An OMX_EQ_BAND_TYPES_* member. */
|
|
97
|
+
static inline int omx_eq_default_type(unsigned index, unsigned count) {
|
|
98
|
+
if (count < 2) return OMX_EQ_BAND_TYPES_BELL;
|
|
99
|
+
return index == 0 ? OMX_EQ_BAND_TYPES_LOW_SHELF : index == count - 1 ? OMX_EQ_BAND_TYPES_HIGH_SHELF : OMX_EQ_BAND_TYPES_BELL;
|
|
100
|
+
}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
42
103
|
/** The limits header of the resolved data. */
|
|
43
104
|
export function renderLimitsHeader(resolved) {
|
|
44
105
|
const d = doors(resolved);
|
|
@@ -48,6 +109,8 @@ export function renderLimitsHeader(resolved) {
|
|
|
48
109
|
const above = defined([tables, aliases].join('\n'));
|
|
49
110
|
const scalars = d.DECLARED_SCALARS.length ? genDeclaredScalarsBlock(d.DECLARED_SCALARS, above) : '';
|
|
50
111
|
const lists = d.DECLARED_LISTS.length ? genDeclaredListsBlock(d.DECLARED_LISTS, defined([tables, aliases, scalars].join('\n'))) : '';
|
|
112
|
+
const sets = d.DECLARED_SET_ITEMS.length ? genDeclaredSetsEnums(d.DECLARED_SET_ITEMS) : '';
|
|
113
|
+
const centres = resolved.has('EQ_DEFAULT_CENTRES_FOUR_BAND_HZ') ? eqCentresFunction() : '';
|
|
51
114
|
const sections = [
|
|
52
115
|
[tables, `/* Per-parameter numeric travel, one block per \`travels\` table named <PREFIX>_LIMITS:
|
|
53
116
|
* OMX_<PREFIX>_<FIELD>_MIN/_MAX/_DEFAULT, float literals. */`],
|
|
@@ -56,11 +119,16 @@ export function renderLimitsHeader(resolved) {
|
|
|
56
119
|
[scalars, `/* Every scalar, sheet leaf and single travel's numbers, one block sorted by macro. An integer value
|
|
57
120
|
* is an integer literal, any other a float literal AND its OMX_<macro>_DOUBLE spelling. */`],
|
|
58
121
|
[lists, `/* Every list rendered to C, sorted by list: OMX_<LIST>_COUNT and OMX_<LIST>_INIT { a, b, … }. */`],
|
|
122
|
+
[sets, `/* 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,
|
|
123
|
+
* where the set names a default, \`OMX_<SET>_DEFAULT\`. */`],
|
|
124
|
+
[centres, `/* The default bands of an EQ with \`count\` bands: THE one rule, for every strip, host and count (omx-contract's
|
|
125
|
+
* lib/eq-defaults.mjs; the ts render exports the same as eqDefaultCentres and eqDefaultTypes). Needs -lm. */`],
|
|
59
126
|
].filter(([body]) => body);
|
|
60
127
|
return `// SPDX-License-Identifier: GPL-3.0-or-later
|
|
61
128
|
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
62
129
|
#ifndef OMX_CONTRACT_LIMITS_H
|
|
63
130
|
#define OMX_CONTRACT_LIMITS_H
|
|
131
|
+
#include <math.h>
|
|
64
132
|
/*
|
|
65
133
|
* GENERATED — DO NOT EDIT BY HAND.
|
|
66
134
|
* Produced by \`omx-contract render --target c\` from FreeMixer/omx-contract's data/, the declaration
|
|
@@ -82,13 +150,11 @@ export function renderC(resolved) {
|
|
|
82
150
|
}
|
|
83
151
|
|
|
84
152
|
/**
|
|
85
|
-
* The `c` target of a frozen openmixer sheet (test/fixtures/): `{ sheet
|
|
86
|
-
*
|
|
153
|
+
* The `c` target of a frozen openmixer sheet (test/fixtures/): `{ sheet }` rendered by openmixer's own
|
|
154
|
+
* renderer, for the round-trip proof (spec §4.4 step 1).
|
|
87
155
|
*/
|
|
88
156
|
export function renderSheetC(fixture) {
|
|
89
157
|
const s = fixture.sheet;
|
|
90
158
|
const sheet = { ...s, CORE_LIMITS: new Map(Object.entries(s.CORE_LIMITS)) };
|
|
91
|
-
|
|
92
|
-
for (const p of fixture.plugins) files.push({ path: `omxcontract/params/${p.header}`, text: generate(p, p.params) });
|
|
93
|
-
return files;
|
|
159
|
+
return [{ path: 'omxcontract/omx_contract_limits.h', text: renderContractLimits(sheet) }];
|
|
94
160
|
}
|
package/render/doors.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* (packages/core/src/native-limit-tables.ts, declared-scalars.ts, declared-lists.ts): a `travels`
|
|
6
6
|
* table named <PREFIX>_LIMITS is a native table; every scalar, sheet and single travel is scalars
|
|
7
7
|
* (its finite-number leaves, a travel's `unit`/`defaultFrom` labels skipped); a list is a declared
|
|
8
|
-
* list unless its item says `"c": { "render": false }`; a `c.alias` is the core-limit spelling.
|
|
8
|
+
* list unless its item says `"c": { "render": false }`; a set is an enum; a `c.alias` is the core-limit spelling.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const constantCase = (key) => key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
|
|
@@ -38,7 +38,7 @@ export function doors(resolved) {
|
|
|
38
38
|
.sort(([a], [b]) => a.localeCompare(b));
|
|
39
39
|
const byMacro = new Map();
|
|
40
40
|
for (const r of items) {
|
|
41
|
-
if (isNativeTable(r) || r.kind === 'list') continue;
|
|
41
|
+
if (isNativeTable(r) || r.kind === 'list' || r.kind === 'set') continue;
|
|
42
42
|
const leaves = numberLeaves(r.value, r.name, r.name.replace(/_(KERNEL|LIMITS)$/, ''));
|
|
43
43
|
if (leaves === undefined) continue;
|
|
44
44
|
for (const s of leaves) {
|
|
@@ -51,6 +51,9 @@ export function doors(resolved) {
|
|
|
51
51
|
const DECLARED_LISTS = items.filter((r) => r.kind === 'list' && r.c?.render !== false)
|
|
52
52
|
.map((r) => ({ name: r.name, values: r.value }))
|
|
53
53
|
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
54
|
+
const DECLARED_SET_ITEMS = items.filter((r) => r.kind === 'set')
|
|
55
|
+
.map((r) => ({ name: r.name, ids: r.value, default: r.default }))
|
|
56
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
54
57
|
const ALIASES = new Map();
|
|
55
58
|
for (const r of items) {
|
|
56
59
|
if (!r.c?.alias) continue;
|
|
@@ -59,5 +62,5 @@ export function doors(resolved) {
|
|
|
59
62
|
ALIASES.get(fact)[field] = r.value;
|
|
60
63
|
}
|
|
61
64
|
const STANDARD_SAMPLE_RATES = resolved.get('STANDARD_SAMPLE_RATES')?.value;
|
|
62
|
-
return { NATIVE_LIMIT_TABLES, DECLARED_SCALARS, DECLARED_LISTS, ALIASES, STANDARD_SAMPLE_RATES };
|
|
65
|
+
return { NATIVE_LIMIT_TABLES, DECLARED_SCALARS, DECLARED_LISTS, DECLARED_SET_ITEMS, ALIASES, STANDARD_SAMPLE_RATES };
|
|
63
66
|
}
|
package/render/ts.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
2
|
// Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
|
|
3
|
+
import { eqDefaultBands, eqDefaultCentres, eqDefaultParams, eqDefaultTypes } from '../lib/eq-defaults.mjs';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* The `ts` target: one ESM module and its type declarations, every item exported under its own
|
|
5
7
|
* name with its literal type (`as const`), so a re-export in openmixer keeps names, values and types.
|
|
@@ -32,10 +34,46 @@ const BANNER = `// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
32
34
|
// GENERATED by \`omx-contract render --target ts\` from FreeMixer/omx-contract's data/ — do not edit.
|
|
33
35
|
`;
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The default EQ rule (lib/eq-defaults.mjs) as functions of the module: the library's own source,
|
|
39
|
+
* with the declared parameters bound, so the ts reader runs the code the tests hold equal to the c one.
|
|
40
|
+
*/
|
|
41
|
+
function eqRule(resolved) {
|
|
42
|
+
if (!resolved.has('EQ_DEFAULT_CENTRES_FOUR_BAND_HZ')) return { js: '', dts: '' };
|
|
43
|
+
const bound = JSON.stringify(eqDefaultParams(resolved), null, 2);
|
|
44
|
+
const fn = (f, as) => f.toString().replace(`function ${f.name}(`, `function ${as}(`);
|
|
45
|
+
const js = `
|
|
46
|
+
/** The declared parameters of the default EQ rule: the four-band set, the preferred series, the band frequency travel, the band gain and Q. */
|
|
47
|
+
const EQ_DEFAULT_RULE = ${bound};
|
|
48
|
+
${fn(eqDefaultCentres, 'centres')}
|
|
49
|
+
${fn(eqDefaultTypes, 'types')}
|
|
50
|
+
${fn(eqDefaultBands, 'bands')}
|
|
51
|
+
|
|
52
|
+
/** The default band centres (Hz) of an EQ with \`count\` bands: THE one rule, for every strip, host and count. */
|
|
53
|
+
export function eqDefaultCentres(count) { return centres(count, EQ_DEFAULT_RULE); }
|
|
54
|
+
|
|
55
|
+
/** The default band types, by EQ_BAND_TYPES id: a low shelf, bells, a high shelf (a single band is a bell). */
|
|
56
|
+
export function eqDefaultTypes(count) { return types(count); }
|
|
57
|
+
|
|
58
|
+
/** Every default band of an EQ with \`count\` bands: type, centre, gain and Q. */
|
|
59
|
+
export function eqDefaultBands(count) { return bands(count, EQ_DEFAULT_RULE); }
|
|
60
|
+
`;
|
|
61
|
+
const dts = `
|
|
62
|
+
/** The default band centres (Hz) of an EQ with \`count\` bands: THE one rule, for every strip, host and count. */
|
|
63
|
+
export declare function eqDefaultCentres(count: number): number[];
|
|
64
|
+
/** The default band types, by EQ_BAND_TYPES id: a low shelf, bells, a high shelf (a single band is a bell). */
|
|
65
|
+
export declare function eqDefaultTypes(count: number): string[];
|
|
66
|
+
/** Every default band of an EQ with \`count\` bands: type, centre, gain and Q. */
|
|
67
|
+
export declare function eqDefaultBands(count: number): { type: string; freqHz: number; gainDb: number; q: number }[];
|
|
68
|
+
`;
|
|
69
|
+
return { js, dts };
|
|
70
|
+
}
|
|
71
|
+
|
|
35
72
|
/** Every file of the `ts` target: `[{ path, text }]`. */
|
|
36
73
|
export function renderTs(resolved) {
|
|
37
74
|
const items = [...resolved.values()];
|
|
38
|
-
const
|
|
39
|
-
const
|
|
75
|
+
const rule = eqRule(resolved);
|
|
76
|
+
const js = BANNER + items.map((r) => `\n${docComment(r)}\nexport const ${r.name} = ${JSON.stringify(tsValue(r), null, 2)};\n`).join('') + rule.js;
|
|
77
|
+
const dts = BANNER + items.map((r) => `\n${docComment(r)}\nexport declare const ${r.name}: ${tsType(tsValue(r))};\n`).join('') + rule.dts;
|
|
40
78
|
return [{ path: 'omx-contract.js', text: js }, { path: 'omx-contract.d.ts', text: dts }];
|
|
41
79
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/FreeMixer/omx-contract/raw/main/schema/omx-contract.schema.json",
|
|
4
4
|
"title": "omx-contract data file",
|
|
5
|
-
"description": "A map from an export name to one item: a scalar, a travels table or travel, a sheet or a
|
|
5
|
+
"description": "A map from an export name to one item: a scalar, a travels table or travel, a sheet, a list or a set. GPL-3.0-or-later.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"$schema": { "type": "string" }
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"enum": ["dB", "dBFS", "ms", "s", "min", "Hz", "oct", "cents", "%", "x", ""]
|
|
16
16
|
},
|
|
17
17
|
"text": { "type": "string", "minLength": 1 },
|
|
18
|
+
"since": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$", "description": "The release that first carried an item declared outside the kernels (rates.json, primitives.json); a kernel's items record it in recipes/answers/." },
|
|
18
19
|
"name": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
|
|
19
20
|
"ref": {
|
|
20
21
|
"type": "object",
|
|
@@ -37,6 +38,15 @@
|
|
|
37
38
|
"required": ["derive", "of"],
|
|
38
39
|
"additionalProperties": false
|
|
39
40
|
},
|
|
41
|
+
{
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"derive": { "const": "ceilSum" },
|
|
45
|
+
"of": { "type": "array", "items": { "$ref": "#/$defs/number" }, "minItems": 2 }
|
|
46
|
+
},
|
|
47
|
+
"required": ["derive", "of"],
|
|
48
|
+
"additionalProperties": false
|
|
49
|
+
},
|
|
40
50
|
{
|
|
41
51
|
"type": "object",
|
|
42
52
|
"properties": {
|
|
@@ -58,6 +68,7 @@
|
|
|
58
68
|
"unit": { "$ref": "#/$defs/unit" },
|
|
59
69
|
"default": { "$ref": "#/$defs/number" },
|
|
60
70
|
"defaultFrom": { "enum": ["desk"] },
|
|
71
|
+
"scale": { "type": "string", "pattern": "^[a-z]+$", "description": "How a host draws the travel: an id of PARAM_SCALES." },
|
|
61
72
|
"byKind": {
|
|
62
73
|
"type": "object",
|
|
63
74
|
"propertyNames": { "pattern": "^[a-z][A-Za-z0-9]*$" },
|
|
@@ -80,6 +91,7 @@
|
|
|
80
91
|
"type": "object",
|
|
81
92
|
"properties": {
|
|
82
93
|
"kind": { "const": "scalar" },
|
|
94
|
+
"since": { "$ref": "#/$defs/since" },
|
|
83
95
|
"doc": { "$ref": "#/$defs/text" },
|
|
84
96
|
"why": { "$ref": "#/$defs/text" },
|
|
85
97
|
"unit": { "$ref": "#/$defs/unit" },
|
|
@@ -92,6 +104,7 @@
|
|
|
92
104
|
"type": "object",
|
|
93
105
|
"properties": {
|
|
94
106
|
"kind": { "const": "travels" },
|
|
107
|
+
"since": { "$ref": "#/$defs/since" },
|
|
95
108
|
"doc": { "$ref": "#/$defs/text" },
|
|
96
109
|
"why": { "$ref": "#/$defs/text" },
|
|
97
110
|
"fields": {
|
|
@@ -125,6 +138,7 @@
|
|
|
125
138
|
"type": "object",
|
|
126
139
|
"properties": {
|
|
127
140
|
"kind": { "const": "sheet" },
|
|
141
|
+
"since": { "$ref": "#/$defs/since" },
|
|
128
142
|
"doc": { "$ref": "#/$defs/text" },
|
|
129
143
|
"why": { "$ref": "#/$defs/text" },
|
|
130
144
|
"value": { "$ref": "#/$defs/sheetValue" }
|
|
@@ -136,6 +150,7 @@
|
|
|
136
150
|
"type": "object",
|
|
137
151
|
"properties": {
|
|
138
152
|
"kind": { "const": "list" },
|
|
153
|
+
"since": { "$ref": "#/$defs/since" },
|
|
139
154
|
"doc": { "$ref": "#/$defs/text" },
|
|
140
155
|
"why": { "$ref": "#/$defs/text" },
|
|
141
156
|
"unit": { "$ref": "#/$defs/unit" },
|
|
@@ -149,15 +164,36 @@
|
|
|
149
164
|
"required": ["kind", "doc", "unit", "values"],
|
|
150
165
|
"additionalProperties": false
|
|
151
166
|
},
|
|
167
|
+
"set": {
|
|
168
|
+
"type": "object",
|
|
169
|
+
"description": "Ordered ids with an optional default and labels. The ids and their order are the contract: all strings (the id is the name, its position the value) or all non-negative integers (the id is the value). Labels are face text and optional.",
|
|
170
|
+
"properties": {
|
|
171
|
+
"kind": { "const": "set" },
|
|
172
|
+
"since": { "$ref": "#/$defs/since" },
|
|
173
|
+
"doc": { "$ref": "#/$defs/text" },
|
|
174
|
+
"why": { "$ref": "#/$defs/text" },
|
|
175
|
+
"ids": {
|
|
176
|
+
"oneOf": [
|
|
177
|
+
{ "type": "array", "items": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9]*$" }, "minItems": 1, "uniqueItems": true },
|
|
178
|
+
{ "type": "array", "items": { "type": "integer", "minimum": 0 }, "minItems": 1, "uniqueItems": true }
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"default": { "oneOf": [{ "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9]*$" }, { "type": "integer", "minimum": 0 }] },
|
|
182
|
+
"labels": { "type": "array", "items": { "$ref": "#/$defs/text" }, "minItems": 1 }
|
|
183
|
+
},
|
|
184
|
+
"required": ["kind", "doc", "ids"],
|
|
185
|
+
"additionalProperties": false
|
|
186
|
+
},
|
|
152
187
|
"item": {
|
|
153
188
|
"type": "object",
|
|
154
189
|
"required": ["kind"],
|
|
155
|
-
"properties": { "kind": { "enum": ["scalar", "travels", "sheet", "list"] } },
|
|
190
|
+
"properties": { "kind": { "enum": ["scalar", "travels", "sheet", "list", "set"] } },
|
|
156
191
|
"allOf": [
|
|
157
192
|
{ "if": { "properties": { "kind": { "const": "scalar" } } }, "then": { "$ref": "#/$defs/scalar" } },
|
|
158
193
|
{ "if": { "properties": { "kind": { "const": "travels" } } }, "then": { "$ref": "#/$defs/travels" } },
|
|
159
194
|
{ "if": { "properties": { "kind": { "const": "sheet" } } }, "then": { "$ref": "#/$defs/sheet" } },
|
|
160
|
-
{ "if": { "properties": { "kind": { "const": "list" } } }, "then": { "$ref": "#/$defs/list" } }
|
|
195
|
+
{ "if": { "properties": { "kind": { "const": "list" } } }, "then": { "$ref": "#/$defs/list" } },
|
|
196
|
+
{ "if": { "properties": { "kind": { "const": "set" } } }, "then": { "$ref": "#/$defs/set" } }
|
|
161
197
|
]
|
|
162
198
|
}
|
|
163
199
|
}
|