Fix VOLUMES/TIMBRE/FM placement: variation properties, not instrument-level
Per RFC §3.2.1.3, VOLUMES, TIMBRE and FM are variation properties. The previous exporter incorrectly appended them to the first ATTR variation; they now go into a synthetic root variation (no ATTR) as required. variationLines() handles these fields directly. Fix test-parser.mjs fixture paths for standalone execution from repo root.
This commit is contained in:
parent
17f4529658
commit
f318b155cc
@ -62,6 +62,7 @@ function labelSpecLines(ls) {
|
||||
|
||||
// ── Variation ─────────────────────────────────────────────────────────────
|
||||
// Returns YAML lines for one variation MAPPING (no leading "- ").
|
||||
// RFC §3.2.1.3: VOLUMES, TIMBRE are variation properties, not instrument-level.
|
||||
|
||||
function variationLines(v) {
|
||||
const lines = [];
|
||||
@ -70,25 +71,34 @@ function variationLines(v) {
|
||||
for (const ls of (v.labelSpecs ?? [])) lines.push(...labelSpecLines(ls));
|
||||
if (v.spread?.length) lines.push(`SPREAD: [${v.spread.join(', ')}]`);
|
||||
if (v.railsbackCurve) { const rc = serializeShape(v.railsbackCurve); if (rc) lines.push(`RAILSBACK_CURVE: "${rc}"`); }
|
||||
const vol = serializeShape(v.volumes);
|
||||
if (vol) lines.push(`VOLUMES: "${vol}"`);
|
||||
const timbre = serializeShape(v.timbre);
|
||||
if (timbre) lines.push(`TIMBRE: "${timbre}"`);
|
||||
for (const fm of (v.fmModulations ?? [])) lines.push(`FM: "${serializeFm(fm)}"`);
|
||||
for (const sv of (v.subvariations ?? [])) lines.push(...variationLines(sv));
|
||||
return lines;
|
||||
}
|
||||
|
||||
// ── Instrument character block ─────────────────────────────────────────────
|
||||
// VOLUMES / TIMBRE / FM appear at depth 01 (direct instrument children per RFC §3.2.1.3).
|
||||
// RAILSBACK_CURVE is depth 02 (inside variation) and is emitted by variationLines().
|
||||
// VOLUMES, TIMBRE, FM are variation properties (RFC §3.2.1.3). The AST parser
|
||||
// stores them on the instrument because they appear at depth 01 (root variation
|
||||
// is implicit when no character: wrapper exists). Promote them into a synthetic
|
||||
// root variation here so the export structure is RFC-correct.
|
||||
|
||||
function instrCharacterLines(instr) {
|
||||
const extraLines = [];
|
||||
const vol = serializeShape(instr.volumes);
|
||||
if (vol) extraLines.push(`VOLUMES: "${vol}"`);
|
||||
const timbre = serializeShape(instr.timbre);
|
||||
if (timbre) extraLines.push(`TIMBRE: "${timbre}"`);
|
||||
for (const fm of (instr.fmModulations ?? [])) extraLines.push(`FM: "${serializeFm(fm)}"`);
|
||||
|
||||
const variations = instr.variations ?? [];
|
||||
const syntheticRoot = instr.basicProperties
|
||||
? { basicProperties: instr.basicProperties, labelSpecs: [], subvariations: [], spread: null, dependsOn: null }
|
||||
const hasRootProps = instr.basicProperties || instr.volumes || instr.timbre ||
|
||||
(instr.fmModulations ?? []).length > 0;
|
||||
const syntheticRoot = hasRootProps
|
||||
? {
|
||||
basicProperties: instr.basicProperties,
|
||||
labelSpecs: [], subvariations: [], spread: null,
|
||||
dependsOn: null, railsbackCurve: null,
|
||||
volumes: instr.volumes,
|
||||
timbre: instr.timbre,
|
||||
fmModulations: instr.fmModulations ?? [],
|
||||
}
|
||||
: null;
|
||||
|
||||
const allVariations = [
|
||||
@ -97,21 +107,17 @@ function instrCharacterLines(instr) {
|
||||
];
|
||||
|
||||
if (allVariations.length <= 1) {
|
||||
// Single variation — emit as MAPPING directly under character:
|
||||
const vLines = allVariations.length
|
||||
? [...variationLines(allVariations[0]), ...extraLines]
|
||||
: extraLines;
|
||||
const vLines = allVariations.length ? variationLines(allVariations[0]) : [];
|
||||
return vLines.map(l => ` ${l}`);
|
||||
}
|
||||
|
||||
// Multiple variations — RFC MAYBE_LIST<VARIATION> as YAML sequence.
|
||||
const result = [];
|
||||
for (let i = 0; i < allVariations.length; i++) {
|
||||
const vLines = variationLines(allVariations[i]);
|
||||
const allLines = i === 0 ? [...vLines, ...extraLines] : vLines;
|
||||
if (!allLines.length) continue;
|
||||
result.push(` - ${allLines[0]}`);
|
||||
for (const l of allLines.slice(1)) result.push(` ${l}`);
|
||||
for (const v of allVariations) {
|
||||
const vLines = variationLines(v);
|
||||
if (!vLines.length) continue;
|
||||
result.push(` - ${vLines[0]}`);
|
||||
for (const l of vLines.slice(1)) result.push(` ${l}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
// Fixture-based compliance test for ast-parser.js + exporter.js
|
||||
// Run: node score_editors/vue3_neusik/test-parser.mjs
|
||||
// Run: node test-parser.mjs
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { parseAstLog, buildModel } from './static/ast-parser.js';
|
||||
import { exportInstrument, patchScore } from './static/exporter.js';
|
||||
|
||||
const FIXTURE = new URL(
|
||||
'../../PLAN/vue3js-app-proposal-for-sdk-claude/fixtures/ast.log',
|
||||
import.meta.url
|
||||
);
|
||||
const FIXTURE = new URL('./fixtures/ast.log', import.meta.url);
|
||||
const text = readFileSync(FIXTURE, 'utf8');
|
||||
|
||||
let pass = 0, fail = 0;
|
||||
@ -191,10 +188,7 @@ if (alpha) {
|
||||
|
||||
// ── patchScore ─────────────────────────────────────────────────────────────
|
||||
section('patchScore');
|
||||
const SCORE_FIXTURE = new URL(
|
||||
'../../PLAN/vue3js-app-proposal-for-sdk-claude/fixtures/pathetique.spls',
|
||||
import.meta.url
|
||||
);
|
||||
const SCORE_FIXTURE = new URL('./fixtures/pathetique.spls', import.meta.url);
|
||||
const rawScore = readFileSync(SCORE_FIXTURE, 'utf8');
|
||||
|
||||
// pathetique.spls contains alpha and ki; dev/piano is a linked instrument not embedded.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user