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 ─────────────────────────────────────────────────────────────
|
// ── Variation ─────────────────────────────────────────────────────────────
|
||||||
// Returns YAML lines for one variation MAPPING (no leading "- ").
|
// 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) {
|
function variationLines(v) {
|
||||||
const lines = [];
|
const lines = [];
|
||||||
@ -70,25 +71,34 @@ function variationLines(v) {
|
|||||||
for (const ls of (v.labelSpecs ?? [])) lines.push(...labelSpecLines(ls));
|
for (const ls of (v.labelSpecs ?? [])) lines.push(...labelSpecLines(ls));
|
||||||
if (v.spread?.length) lines.push(`SPREAD: [${v.spread.join(', ')}]`);
|
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}"`); }
|
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));
|
for (const sv of (v.subvariations ?? [])) lines.push(...variationLines(sv));
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Instrument character block ─────────────────────────────────────────────
|
// ── Instrument character block ─────────────────────────────────────────────
|
||||||
// VOLUMES / TIMBRE / FM appear at depth 01 (direct instrument children per RFC §3.2.1.3).
|
// VOLUMES, TIMBRE, FM are variation properties (RFC §3.2.1.3). The AST parser
|
||||||
// RAILSBACK_CURVE is depth 02 (inside variation) and is emitted by variationLines().
|
// 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) {
|
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 variations = instr.variations ?? [];
|
||||||
const syntheticRoot = instr.basicProperties
|
const hasRootProps = instr.basicProperties || instr.volumes || instr.timbre ||
|
||||||
? { basicProperties: instr.basicProperties, labelSpecs: [], subvariations: [], spread: null, dependsOn: null }
|
(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;
|
: null;
|
||||||
|
|
||||||
const allVariations = [
|
const allVariations = [
|
||||||
@ -97,21 +107,17 @@ function instrCharacterLines(instr) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (allVariations.length <= 1) {
|
if (allVariations.length <= 1) {
|
||||||
// Single variation — emit as MAPPING directly under character:
|
const vLines = allVariations.length ? variationLines(allVariations[0]) : [];
|
||||||
const vLines = allVariations.length
|
|
||||||
? [...variationLines(allVariations[0]), ...extraLines]
|
|
||||||
: extraLines;
|
|
||||||
return vLines.map(l => ` ${l}`);
|
return vLines.map(l => ` ${l}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple variations — RFC MAYBE_LIST<VARIATION> as YAML sequence.
|
// Multiple variations — RFC MAYBE_LIST<VARIATION> as YAML sequence.
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let i = 0; i < allVariations.length; i++) {
|
for (const v of allVariations) {
|
||||||
const vLines = variationLines(allVariations[i]);
|
const vLines = variationLines(v);
|
||||||
const allLines = i === 0 ? [...vLines, ...extraLines] : vLines;
|
if (!vLines.length) continue;
|
||||||
if (!allLines.length) continue;
|
result.push(` - ${vLines[0]}`);
|
||||||
result.push(` - ${allLines[0]}`);
|
for (const l of vLines.slice(1)) result.push(` ${l}`);
|
||||||
for (const l of allLines.slice(1)) result.push(` ${l}`);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// Fixture-based compliance test for ast-parser.js + exporter.js
|
// 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 { readFileSync } from 'fs';
|
||||||
import { parseAstLog, buildModel } from './static/ast-parser.js';
|
import { parseAstLog, buildModel } from './static/ast-parser.js';
|
||||||
import { exportInstrument, patchScore } from './static/exporter.js';
|
import { exportInstrument, patchScore } from './static/exporter.js';
|
||||||
|
|
||||||
const FIXTURE = new URL(
|
const FIXTURE = new URL('./fixtures/ast.log', import.meta.url);
|
||||||
'../../PLAN/vue3js-app-proposal-for-sdk-claude/fixtures/ast.log',
|
|
||||||
import.meta.url
|
|
||||||
);
|
|
||||||
const text = readFileSync(FIXTURE, 'utf8');
|
const text = readFileSync(FIXTURE, 'utf8');
|
||||||
|
|
||||||
let pass = 0, fail = 0;
|
let pass = 0, fail = 0;
|
||||||
@ -191,10 +188,7 @@ if (alpha) {
|
|||||||
|
|
||||||
// ── patchScore ─────────────────────────────────────────────────────────────
|
// ── patchScore ─────────────────────────────────────────────────────────────
|
||||||
section('patchScore');
|
section('patchScore');
|
||||||
const SCORE_FIXTURE = new URL(
|
const SCORE_FIXTURE = new URL('./fixtures/pathetique.spls', import.meta.url);
|
||||||
'../../PLAN/vue3js-app-proposal-for-sdk-claude/fixtures/pathetique.spls',
|
|
||||||
import.meta.url
|
|
||||||
);
|
|
||||||
const rawScore = readFileSync(SCORE_FIXTURE, 'utf8');
|
const rawScore = readFileSync(SCORE_FIXTURE, 'utf8');
|
||||||
|
|
||||||
// pathetique.spls contains alpha and ki; dev/piano is a linked instrument not embedded.
|
// pathetique.spls contains alpha and ki; dev/piano is a linked instrument not embedded.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user