Make bars navigable and editable with _meta export
Navigation: - SubObjects: bar → voice (with chevron when offsets exist) → offsets - PaneCP shortView handles voice and offset types FO pane: - Bar: editable beats_per_minute, stress_pattern (serialized as groups string), upper/lower stress bounds and tempo shape via ShapeEditor; sets bar.isDirty on change - Voice: read-only name, articles, motifs, offset count - Offset: read-only tick, stem notes and clusters as label strings (note re-serialization deferred) Export: - exporter.patchScore() now accepts optional bars[] parameter - Splits rawScoreText by \n---\n; instruments patched in header, dirty bar _meta blocks regenerated, voice note lines kept verbatim - ast-parser buildBar() adds isDirty:false - PaneCP passes scoreModel.bars to patchScore
This commit is contained in:
@@ -134,11 +134,11 @@ export function exportInstrument(instr) {
|
||||
}
|
||||
|
||||
// ── Score patch ────────────────────────────────────────────────────────────
|
||||
// Replace dirty instrument blocks in rawScoreText with RFC-serialized output.
|
||||
// Non-dirty instruments are left verbatim.
|
||||
// Replace dirty instrument blocks and dirty bar _meta blocks.
|
||||
// Voice note content in bar documents is left verbatim.
|
||||
|
||||
export function patchScore(rawScoreText, instruments) {
|
||||
const lines = rawScoreText.split('\n');
|
||||
function patchInstrumentHeader(text, instruments) {
|
||||
const lines = text.split('\n');
|
||||
const result = [];
|
||||
const instrMap = {};
|
||||
for (const instr of instruments) {
|
||||
@@ -149,7 +149,6 @@ export function patchScore(rawScoreText, instruments) {
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
// RFC §4.4: "instrument NAME:" — strip optional quotes around name
|
||||
const m = line.match(/^instrument\s+(.+?)\s*:/);
|
||||
if (m) {
|
||||
const rawName = m[1].replace(/^'|'$/g, '');
|
||||
@@ -171,3 +170,62 @@ export function patchScore(rawScoreText, instruments) {
|
||||
|
||||
return result.join('\n');
|
||||
}
|
||||
|
||||
function patchBarMeta(doc, bar) {
|
||||
const props = [];
|
||||
if (bar.stressor?.groups?.length)
|
||||
props.push(` stress_pattern: ${bar.stressor.groups.map(g => g.join(',')).join(';')}`);
|
||||
if (bar.tempoLevels != null)
|
||||
props.push(` beats_per_minute: ${bar.tempoLevels}`);
|
||||
const ub = serializeShape(bar.upperStressBound);
|
||||
if (ub) props.push(` upper_stress_bound: ${ub}`);
|
||||
const lb = serializeShape(bar.lowerStressBound);
|
||||
if (lb) props.push(` lower_stress_bound: ${lb}`);
|
||||
if (bar.tempoShape) { const ts = serializeShape(bar.tempoShape); if (ts) props.push(` tempo_shape: "${ts}"`); }
|
||||
|
||||
const lines = doc.split('\n');
|
||||
const out = [];
|
||||
let i = 0;
|
||||
let replaced = false;
|
||||
|
||||
while (i < lines.length) {
|
||||
if (lines[i] === '_meta:') {
|
||||
replaced = true;
|
||||
i++;
|
||||
while (i < lines.length && lines[i].startsWith(' ')) i++;
|
||||
if (props.length) { out.push('_meta:'); out.push(...props); }
|
||||
} else {
|
||||
out.push(lines[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!replaced && props.length) {
|
||||
const idIdx = out.findIndex(l => /^_id:/.test(l));
|
||||
if (idIdx !== -1) out.splice(idIdx + 1, 0, '_meta:', ...props);
|
||||
}
|
||||
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
export function patchScore(rawScoreText, instruments, bars = []) {
|
||||
const SEP = '\n---\n';
|
||||
const [header, ...barDocs] = rawScoreText.split(SEP);
|
||||
|
||||
const patchedHeader = patchInstrumentHeader(header, instruments);
|
||||
|
||||
if (!barDocs.length) return patchedHeader;
|
||||
|
||||
const barMap = {};
|
||||
for (const bar of bars) barMap[bar.id] = bar;
|
||||
|
||||
const patchedBarDocs = barDocs.map(doc => {
|
||||
const m = doc.match(/^_id:\s*(\S+)/m);
|
||||
if (!m) return doc;
|
||||
const bar = barMap[m[1]];
|
||||
if (!bar?.isDirty) return doc;
|
||||
return patchBarMeta(doc, bar);
|
||||
});
|
||||
|
||||
return [patchedHeader, ...patchedBarDocs].join(SEP);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user