forked from flow/vue3js-app-proposal-for-sdk-claude
Add missing tests: AM, patchBarMeta, stressorToString, multi-variation, for_value, DEBUG skipping, serializeModulation edge cases
This commit is contained in:
parent
89198ec37e
commit
720a0b6b25
160
test-parser.mjs
160
test-parser.mjs
@ -4,7 +4,7 @@
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { parseAstLog, buildModel } from './static/ast-parser.js';
|
||||
import { exportInstrument, patchScore } from './static/exporter.js';
|
||||
import { exportInstrument, patchScore, stressorToString } from './static/exporter.js';
|
||||
|
||||
const FIXTURE = new URL('./fixtures/ast.log', import.meta.url);
|
||||
const text = readFileSync(FIXTURE, 'utf8');
|
||||
@ -245,6 +245,164 @@ const fmOut = exportInstrument(fmInstr);
|
||||
ok('FM exported with [shape]', /FM:.*\[.*\]/.test(fmOut));
|
||||
ok('FM exported with mod:base', /FM:.*;\d+:\d+/.test(fmOut));
|
||||
|
||||
// ── DEBUG line skipping ────────────────────────────────────────────────────
|
||||
section('DEBUG line skipping');
|
||||
const DEBUG_FIXTURE = `00 instrument 'dbg'
|
||||
01 # DEBUG missing level: [('character', [])]
|
||||
01 character.basic_properties
|
||||
02 A.shape length=1
|
||||
03 shape.coords x='1' y='10' z=1 is_sharp=False
|
||||
03 shape.coords x='2' y='0' z=1 is_sharp=False
|
||||
`;
|
||||
const dbgModel = buildModel(parseAstLog(DEBUG_FIXTURE));
|
||||
const dbgInstr = dbgModel.instruments[0];
|
||||
ok('DEBUG line skipped — instrument parsed', !!dbgInstr);
|
||||
ok('DEBUG line skipped — A shape present', !!dbgInstr?.basicProperties?.A);
|
||||
ok('DEBUG line skipped — A shape has 2 coords', dbgInstr?.basicProperties?.A?.coords?.length === 2);
|
||||
|
||||
// ── parseRest edge cases ───────────────────────────────────────────────────
|
||||
section('parseRest edge cases');
|
||||
const PARSE_FIXTURE = `00 instrument 'pi'
|
||||
01 character.variation depends_on='pitch'
|
||||
02 variation.label_spec 'myLabel' foo=True bar='hello world'
|
||||
`;
|
||||
const parseModel = buildModel(parseAstLog(PARSE_FIXTURE));
|
||||
const parseInstr = parseModel.instruments[0];
|
||||
const parseLs = parseInstr?.variations[0]?.labelSpecs[0];
|
||||
ok('quoted positional parsed', parseLs?.label === 'myLabel');
|
||||
ok('bool prop coerced', parseInstr?.variations[0]?.props?.depends_on === 'pitch' || parseInstr?.variations[0]?.dependsOn === 'pitch');
|
||||
|
||||
// ── Sub-variation for_value ────────────────────────────────────────────────
|
||||
section('Sub-variation for_value');
|
||||
const SV_FIXTURE = `00 instrument 'piano'
|
||||
01 character.variation depends_on='pitch'
|
||||
02 variation.subvariation for_value='440.0'
|
||||
02 variation.subvariation for_value='880.0'
|
||||
`;
|
||||
const svModel = buildModel(parseAstLog(SV_FIXTURE));
|
||||
const svInstr = svModel.instruments[0];
|
||||
const svs = svInstr?.variations[0]?.subvariations;
|
||||
ok('two subvariations parsed', svs?.length === 2);
|
||||
ok('first subvariation dependsOn=440', svs?.[0]?.dependsOn == 440);
|
||||
ok('second subvariation dependsOn=880', svs?.[1]?.dependsOn == 880);
|
||||
|
||||
// ── AM modulation ─────────────────────────────────────────────────────────
|
||||
section('AM modulation (synthetic)');
|
||||
const AM_FIXTURE = `00 instrument 'test'
|
||||
01 character.basic_properties
|
||||
02 AM.modulation frequency='3' mod_share='2' base_share='5' overdrive=True oscillator='sawtooth'
|
||||
03 envelope.shape length=2 start='0' z=1
|
||||
04 shape.coords x='1' y='1' z=1 is_sharp=False
|
||||
04 shape.coords x='2' y='0' z=1 is_sharp=False
|
||||
`;
|
||||
const amRaw = parseAstLog(AM_FIXTURE);
|
||||
const amModel = buildModel(amRaw);
|
||||
const amInstr = amModel.instruments[0];
|
||||
ok('synthetic AM instrument parsed', !!amInstr);
|
||||
const amBp = amInstr?.basicProperties;
|
||||
ok('AM in basicProperties', amBp?.amModulations?.length === 1);
|
||||
const am = amBp?.amModulations?.[0];
|
||||
ok('AM frequency', am?.frequency == 3);
|
||||
ok('AM oscillator', am?.oscillator === 'sawtooth');
|
||||
ok('AM mod_share', am?.mod_share == 2);
|
||||
ok('AM base_share', am?.base_share == 5);
|
||||
ok('AM has shape', !!am?.shape);
|
||||
ok('AM shape coords', am?.shape?.coords?.length === 2);
|
||||
amInstr.isDirty = true;
|
||||
const amOut = exportInstrument(amInstr);
|
||||
ok('AM exported', amOut.includes('AM:'));
|
||||
ok('AM exported with @sawtooth', /AM:.*@sawtooth/.test(amOut));
|
||||
ok('AM exported with mod:base', /AM:.*;\d+:\d+/.test(amOut));
|
||||
ok('AM exported with [shape]', /AM:.*\[.*\]/.test(amOut));
|
||||
|
||||
// ── serializeModulation edge cases ─────────────────────────────────────────
|
||||
section('serializeModulation edge cases');
|
||||
const MOD_INIT_FIXTURE = `00 instrument 'test'
|
||||
01 character.basic_properties
|
||||
02 FM.modulation frequency='5' mod_share='1' base_share='2' overdrive=True oscillator='square' init_phase='+3'
|
||||
`;
|
||||
const modModel = buildModel(parseAstLog(MOD_INIT_FIXTURE));
|
||||
const modInstr = modModel.instruments[0];
|
||||
modInstr.isDirty = true;
|
||||
const modOut = exportInstrument(modInstr);
|
||||
ok('FM with non-sine oscillator exported', /FM:.*@square/.test(modOut));
|
||||
ok('FM with init_phase exported', /FM:.*;.*[+-]\d+/.test(modOut));
|
||||
|
||||
// ── stressorToString ───────────────────────────────────────────────────────
|
||||
section('stressorToString');
|
||||
ok('null stressor → empty string', stressorToString(null) === '');
|
||||
ok('empty groups → empty string', stressorToString({ groups: [] }) === '');
|
||||
ok('single group', stressorToString({ groups: [[1, 2, 3]] }) === '1,2,3');
|
||||
ok('multiple groups', stressorToString({ groups: [[1, 2], [3]] }) === '1,2;3');
|
||||
ok('single-element groups', stressorToString({ groups: [[4], [2], [1]] }) === '4;2;1');
|
||||
|
||||
// ── multiple-variation export (YAML sequence) ──────────────────────────────
|
||||
section('Multiple-variation export');
|
||||
const MV_FIXTURE = `00 instrument 'mv'
|
||||
01 character.variation
|
||||
02 A.shape length=1
|
||||
03 shape.coords x='1' y='10' z=1 is_sharp=False
|
||||
03 shape.coords x='2' y='0' z=1 is_sharp=False
|
||||
01 character.variation depends_on='stress'
|
||||
02 A.shape length=2
|
||||
03 shape.coords x='1' y='5' z=1 is_sharp=False
|
||||
03 shape.coords x='2' y='0' z=1 is_sharp=False
|
||||
`;
|
||||
const mvModel = buildModel(parseAstLog(MV_FIXTURE));
|
||||
const mvInstr = mvModel.instruments[0];
|
||||
ok('two variations parsed', mvInstr?.variations?.length === 2);
|
||||
mvInstr.isDirty = true;
|
||||
const mvOut = exportInstrument(mvInstr);
|
||||
ok('multi-variation: has character:', mvOut.includes('character:'));
|
||||
ok('multi-variation: YAML sequence (- )', /^\s+- /m.test(mvOut));
|
||||
ok('multi-variation: second variation has ATTR:', mvOut.includes('ATTR: stress'));
|
||||
|
||||
// ── patchBarMeta ───────────────────────────────────────────────────────────
|
||||
section('patchBarMeta (via patchScore)');
|
||||
const RAW_SCORE_WITH_BARS = `instrument alpha:
|
||||
character:
|
||||
A: "1:0,10;1,0"
|
||||
|
||||
---
|
||||
_id: 001P1L1M1
|
||||
_meta:
|
||||
stress_pattern: 1,2;3
|
||||
beats_per_minute: 120
|
||||
voice soprano:
|
||||
- C4 4
|
||||
|
||||
---
|
||||
_id: 001P1L1M2
|
||||
_meta:
|
||||
beats_per_minute: 100
|
||||
voice soprano:
|
||||
- D4 4
|
||||
`;
|
||||
|
||||
const dirtyBar = {
|
||||
id: '001P1L1M1',
|
||||
isDirty: true,
|
||||
stressor: { groups: [[2, 3], [1]] },
|
||||
tempoLevels: 140,
|
||||
upperStressBound: null,
|
||||
lowerStressBound: null,
|
||||
tempoShape: null,
|
||||
};
|
||||
const cleanBar = {
|
||||
id: '001P1L1M2',
|
||||
isDirty: false,
|
||||
stressor: null, tempoLevels: null,
|
||||
upperStressBound: null, lowerStressBound: null, tempoShape: null,
|
||||
};
|
||||
const barPatched = patchScore(RAW_SCORE_WITH_BARS, [], [dirtyBar, cleanBar]);
|
||||
const barPatchedLines = barPatched.split('\n');
|
||||
ok('dirty bar _meta updated with new BPM', barPatched.includes('beats_per_minute: 140'));
|
||||
ok('dirty bar stress_pattern updated', barPatched.includes('stress_pattern: 2,3;1'));
|
||||
ok('dirty bar voice content preserved', barPatched.includes('- C4 4'));
|
||||
ok('clean bar unchanged', barPatched.includes('beats_per_minute: 100'));
|
||||
ok('clean bar voice preserved', barPatched.includes('- D4 4'));
|
||||
ok('document separators preserved', (barPatched.match(/\n---\n/g) ?? []).length === 2);
|
||||
|
||||
// ── Summary ────────────────────────────────────────────────────────────────
|
||||
console.log(`\n══ ${pass} passed, ${fail} failed ══\n`);
|
||||
process.exit(fail > 0 ? 1 : 0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user