exporter: remove stage voice entries for deleted instruments

This commit is contained in:
c0dev0id
2026-07-13 21:16:02 +02:00
parent f18f07dc22
commit 19c89711be
2 changed files with 107 additions and 0 deletions

View File

@@ -246,6 +246,66 @@ function _patchInstrumentHeader(text, instruments) {
}
// Remove stage voice entries whose referenced instrument is no longer in the model.
// String form: ` voicename: LEFT|RIGHT DIST [Instrument]` — 3rd token is instrument.
// Mapping form: ` voicename:\n instrument: Name` — look for `instrument:` child.
// When no explicit instrument is present, the voice name is the implicit instrument name.
function _patchStageSection(text, instruments) {
const instrNames = new Set(instruments.flatMap(i => {
const names = [i.name];
if (i.name.includes('/')) names.push(i.name.split('/').pop());
return names;
}));
const lines = text.split('\n');
const stageIdx = lines.findIndex(l => /^stage\s*:/.test(l));
if (stageIdx === -1) return text;
let stageEnd = lines.length;
for (let j = stageIdx + 1; j < lines.length; j++) {
if (lines[j] !== '' && !/^\s/.test(lines[j])) { stageEnd = j; break; }
}
const stageLines = lines.slice(stageIdx + 1, stageEnd);
const firstIndented = stageLines.find(l => l !== '' && /^\s/.test(l));
if (!firstIndented) return text;
const voiceIndentLen = firstIndented.match(/^(\s+)/)[1].length;
// Group stageLines into per-voice entries
const entries = [];
let cur = null;
for (const line of stageLines) {
if (line === '') { if (cur) cur.lines.push(line); continue; }
const indentLen = (line.match(/^(\s+)/) ?? ['', ''])[1].length;
if (indentLen === voiceIndentLen) {
if (cur) entries.push(cur);
const m = line.match(/^\s+(\w[\w./]*)\s*:(.*)/);
cur = m ? { lines: [line], name: m[1], rest: m[2].trim() } : { lines: [line], name: null };
} else if (cur) {
cur.lines.push(line);
}
}
if (cur) entries.push(cur);
const keptLines = [];
for (const entry of entries) {
if (!entry.name || entry.name === '_space') { keptLines.push(...entry.lines); continue; }
let instrRef = null;
if (entry.rest) {
const parts = entry.rest.split(/\s+/);
if (parts.length >= 3) instrRef = parts[2];
} else {
for (const l of entry.lines.slice(1)) {
const im = l.match(/^\s+instrument\s*:\s*(\S+)/);
if (im) { instrRef = im[1]; break; }
}
}
if (instrNames.has(instrRef ?? entry.name)) keptLines.push(...entry.lines);
}
return [...lines.slice(0, stageIdx + 1), ...keptLines, ...lines.slice(stageEnd)].join('\n');
}
function _patchBarMeta(doc, bar) {
const props = [];
const sp = stressorToString(bar.stressor);
@@ -304,6 +364,7 @@ export function patchScore(rawScoreText, instruments, bars = [], info = null, ar
}
patchedHeader = _patchInstrumentHeader(patchedHeader, instruments);
patchedHeader = _patchStageSection(patchedHeader, instruments);
instruments.forEach(i => {
if (!(i.isLinked && !i._modified)) log.push({ level: 'changed', path: `instrument / ${i.name}` });
});