Parser: support sompyler 10cad1f preamble (stage.*, articles.<subtype>, tuning.frequency_factors)
- Top-level dispatch switches on fqSlot (parentSlot.slot) so dotted top-level
slots (`stage.cone`, `stage.voice`, `articles.<subtype>`) are matched.
- `stage_voice` -> `stage.cone` (new, one per score) + `stage.voice` (list).
- `article` -> `articles.<subtype>` (subtype stored on the article entry;
first observed subtype is `defaults`).
- New `tuning.frequency_factors` child captured as `{ label, factors }`.
- buildModel un-nests preamble siblings that sompyler emits at depth 01
under `00 tuning` via `with deeper_level("articles")` / `deeper_level("stage")`.
Only `tuning`'s children are flattened; `instrument`'s implicit containers
(`character`, `VOLUMES`, `TIMBRE`, `FM`, `AM`) are left untouched.
- Fixture test extended with 18 preamble assertions (172 total now pass).
This commit is contained in:
parent
330b3788f0
commit
b23e243225
@ -118,24 +118,47 @@ export function buildModel(rawTree) {
|
|||||||
info: null,
|
info: null,
|
||||||
tuning: null,
|
tuning: null,
|
||||||
articles: [],
|
articles: [],
|
||||||
|
stageCone: null,
|
||||||
stageVoices: [],
|
stageVoices: [],
|
||||||
instruments: [],
|
instruments: [],
|
||||||
bars: [],
|
bars: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Upstream uses `with deeper_level("articles"):` / `deeper_level("stage"):`
|
||||||
|
// implicit containers that emit no depth-00 header. Their depth-01 lines get
|
||||||
|
// nested under the preceding `00 tuning` by the depth-stack parser even
|
||||||
|
// though they are conceptually siblings of tuning. Un-nest them here.
|
||||||
|
const topLevel = [];
|
||||||
for (const node of rawTree.children) {
|
for (const node of rawTree.children) {
|
||||||
switch (node.slot) {
|
if (node.parentSlot === null && node.slot === 'tuning') {
|
||||||
|
const trulyTuning = [];
|
||||||
|
const misnested = [];
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (child.parentSlot === 'tuning') trulyTuning.push(child);
|
||||||
|
else misnested.push(child);
|
||||||
|
}
|
||||||
|
topLevel.push({ ...node, children: trulyTuning });
|
||||||
|
topLevel.push(...misnested);
|
||||||
|
} else {
|
||||||
|
topLevel.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const node of topLevel) {
|
||||||
|
const fqSlot = node.parentSlot ? `${node.parentSlot}.${node.slot}` : node.slot;
|
||||||
|
switch (fqSlot) {
|
||||||
case 'info':
|
case 'info':
|
||||||
score.info = { ...node.props };
|
score.info = { ...node.props };
|
||||||
break;
|
break;
|
||||||
case 'tuning':
|
case 'tuning':
|
||||||
score.tuning = buildTuning(node);
|
score.tuning = buildTuning(node);
|
||||||
break;
|
break;
|
||||||
case 'article':
|
case 'stage.cone':
|
||||||
score.articles.push(buildArticle(node));
|
score.stageCone = { type: 'stage_cone', ...node.props };
|
||||||
break;
|
break;
|
||||||
case 'stage_voice':
|
case 'stage.voice':
|
||||||
score.stageVoices.push({
|
score.stageVoices.push({
|
||||||
|
type: 'stage_voice',
|
||||||
name: node.positionals[0],
|
name: node.positionals[0],
|
||||||
direction: node.props.direction,
|
direction: node.props.direction,
|
||||||
distance: node.props.distance,
|
distance: node.props.distance,
|
||||||
@ -148,7 +171,11 @@ export function buildModel(rawTree) {
|
|||||||
score.bars.push(buildBar(node));
|
score.bars.push(buildBar(node));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
score[node.slot] = buildGeneric(node);
|
if (node.parentSlot === 'articles') {
|
||||||
|
score.articles.push(buildArticle(node));
|
||||||
|
} else {
|
||||||
|
score[node.slot] = buildGeneric(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,12 +183,17 @@ export function buildModel(rawTree) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildTuning(node) {
|
function buildTuning(node) {
|
||||||
const t = { base: node.props.base, scales: {}, chords: {} };
|
const t = { type: 'tuning', base: node.props.base, scales: {}, chords: {}, frequencyFactors: null };
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
if (child.slot === 'scales') {
|
if (child.slot === 'scales') {
|
||||||
t.scales[child.positionals[0]] = child.positionals.slice(1);
|
t.scales[child.positionals[0]] = child.positionals.slice(1);
|
||||||
} else if (child.slot === 'chords') {
|
} else if (child.slot === 'chords') {
|
||||||
t.chords[child.positionals[0]] = child.positionals.slice(1);
|
t.chords[child.positionals[0]] = child.positionals.slice(1);
|
||||||
|
} else if (child.slot === 'frequency_factors') {
|
||||||
|
t.frequencyFactors = {
|
||||||
|
label: child.props.label ?? null,
|
||||||
|
factors: child.positionals.slice(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
@ -170,11 +202,9 @@ function buildTuning(node) {
|
|||||||
function buildArticle(node) {
|
function buildArticle(node) {
|
||||||
return {
|
return {
|
||||||
type: 'article',
|
type: 'article',
|
||||||
|
subtype: node.slot,
|
||||||
name: node.positionals[0],
|
name: node.positionals[0],
|
||||||
props: { ...node.props },
|
props: { ...node.props },
|
||||||
properties: node.children
|
|
||||||
.filter(c => c.slot === 'property')
|
|
||||||
.map(c => ({ name: c.positionals[0], ...c.props })),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,27 @@ ok('has bars', model.bars.length > 0);
|
|||||||
ok('432 bars', model.bars.length === 432);
|
ok('432 bars', model.bars.length === 432);
|
||||||
console.log(` bars: ${model.bars.length}, instruments: ${model.instruments.length}`);
|
console.log(` bars: ${model.bars.length}, instruments: ${model.instruments.length}`);
|
||||||
|
|
||||||
|
// ── Preamble (info, tuning, stage, articles) ───────────────────────────────
|
||||||
|
section('Preamble');
|
||||||
|
ok('info parsed', model.info && typeof model.info.title === 'string');
|
||||||
|
ok('info composer', model.info?.composer === 'Ludwig van Beethoven');
|
||||||
|
ok('tuning parsed', model.tuning && model.tuning.base === 'tones_euro_de+en');
|
||||||
|
ok('tuning has scales', Object.keys(model.tuning.scales).length > 0);
|
||||||
|
ok('tuning.scales hm7', Array.isArray(model.tuning.scales.hm7));
|
||||||
|
ok('tuning has chords', Object.keys(model.tuning.chords).length > 0);
|
||||||
|
ok('tuning frequencyFactors object', model.tuning.frequencyFactors && typeof model.tuning.frequencyFactors === 'object');
|
||||||
|
ok('frequencyFactors label', model.tuning.frequencyFactors.label === 'just5lim');
|
||||||
|
ok('frequencyFactors 12 ratios', model.tuning.frequencyFactors.factors.length === 12);
|
||||||
|
ok('stageCone parsed', model.stageCone && model.stageCone.type === 'stage_cone');
|
||||||
|
ok('stageCone has minvol', model.stageCone.minvol !== undefined);
|
||||||
|
ok('stageVoices parsed', model.stageVoices.length === 3);
|
||||||
|
ok('stageVoices[0] is "pi"', model.stageVoices[0].name === 'pi');
|
||||||
|
ok('stageVoices[0].direction', model.stageVoices[0].direction === '1|1');
|
||||||
|
ok('articles array non-empty', model.articles.length > 0);
|
||||||
|
ok('articles[0].subtype defaults', model.articles[0].subtype === 'defaults');
|
||||||
|
ok('articles[0].name "f"', model.articles[0].name === 'f');
|
||||||
|
ok('articles[0].props.add_stress', model.articles[0].props.add_stress === 3);
|
||||||
|
|
||||||
// ── Bar IDs ────────────────────────────────────────────────────────────────
|
// ── Bar IDs ────────────────────────────────────────────────────────────────
|
||||||
// Bar IDs are opaque auto-increment strings; only the raw id string matters.
|
// Bar IDs are opaque auto-increment strings; only the raw id string matters.
|
||||||
section('Bar IDs');
|
section('Bar IDs');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user