From bf35441b774b78ec1ba77b0fb7451ed4a90e301f Mon Sep 17 00:00:00 2001 From: c0dev0id Date: Mon, 6 Jul 2026 21:20:32 +0200 Subject: [PATCH] refactor: functional style in exporter.js and subobject-kinds.js --- static/exporter.js | 135 +++++++++++++++++--------------------- static/subobject-kinds.js | 14 ++-- 2 files changed, 66 insertions(+), 83 deletions(-) diff --git a/static/exporter.js b/static/exporter.js index 2c73e9a..ebb2e26 100644 --- a/static/exporter.js +++ b/static/exporter.js @@ -20,12 +20,12 @@ function _serializeShape(shape) { // RFC §3.2.1.1.6-7: FM = FREQUENCY ["f"/"F"] ["@" OSC] ["[" SHAPE "]"] ";" MOD ":" BASE -function _serializeModulation(m) { - let s = String(m.frequency ?? ''); - if (m.oscillator) s += `@${m.oscillator}`; - if (m.shape) s += `[${_serializeShape(m.shape)}]`; - s += `;${m.mod_share ?? ''}:${m.base_share ?? ''}`; - if (m.init_phase != null) s += m.init_phase >= 0 ? `+${m.init_phase}` : String(m.init_phase); +function _serializeModulation(modulation) { + let s = String(modulation.frequency ?? ''); + if (modulation.oscillator) s += `@${modulation.oscillator}`; + if (modulation.shape) s += `[${_serializeShape(modulation.shape)}]`; + s += `;${modulation.mod_share ?? ''}:${modulation.base_share ?? ''}`; + if (modulation.init_phase != null) s += modulation.init_phase >= 0 ? `+${modulation.init_phase}` : String(modulation.init_phase); return s; } @@ -33,17 +33,15 @@ function _serializeModulation(m) { function _basicPropLines(bp) { if (!bp) return []; - const lines = []; - if (bp.oscillator) lines.push(`O: ${bp.oscillator}`); - const a = _serializeShape(bp.A); - if (a) lines.push(`A: "${a}"`); - const s = _serializeShape(bp.S); - if (s) lines.push(`S: "${s}"`); - const r = _serializeShape(bp.R); - if (r) lines.push(`R: "${r}"`); - for (const fm of (bp.fmModulations ?? [])) lines.push(`FM: "${_serializeModulation(fm)}"`); - for (const am of (bp.amModulations ?? [])) lines.push(`AM: "${_serializeModulation(am)}"`); - return lines; + const a = _serializeShape(bp.A), s = _serializeShape(bp.S), r = _serializeShape(bp.R); + return [ + bp.oscillator ? `O: ${bp.oscillator}` : null, + a ? `A: "${a}"` : null, + s ? `S: "${s}"` : null, + r ? `R: "${r}"` : null, + ...(bp.fmModulations ?? []).map(fm => `FM: "${_serializeModulation(fm)}"`), + ...(bp.amModulations ?? []).map(am => `AM: "${_serializeModulation(am)}"`), + ].filter(Boolean); } // RFC §3.2.1.2: label name (3+ lowercase chars) is the MAPPING KEY directly. @@ -56,21 +54,22 @@ function _labelSpecLines(ls) { // RFC §3.2.1.3: VOLUMES, TIMBRE are variation properties, not instrument-level. -function _variationLines(v) { - const lines = []; - if (v.dependsOn) lines.push(`ATTR: ${v.dependsOn}`); - lines.push(..._basicPropLines(v.basicProperties)); - 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: "${_serializeModulation(fm)}"`); - for (const am of (v.amModulations ?? [])) lines.push(`AM: "${_serializeModulation(am)}"`); - for (const sv of (v.subvariations ?? [])) lines.push(..._variationLines(sv)); - return lines; +function _variationLines(variation) { + const rc = _serializeShape(variation.railsbackCurve); + const vol = _serializeShape(variation.volumes); + const timbre = _serializeShape(variation.timbre); + return [ + variation.dependsOn ? `ATTR: ${variation.dependsOn}` : null, + ..._basicPropLines(variation.basicProperties), + ...(variation.labelSpecs ?? []).flatMap(_labelSpecLines), + variation.spread?.length ? `SPREAD: [${variation.spread.join(', ')}]` : null, + rc ? `RAILSBACK_CURVE: "${rc}"` : null, + vol ? `VOLUMES: "${vol}"` : null, + timbre ? `TIMBRE: "${timbre}"` : null, + ...(variation.fmModulations ?? []).map(fm => `FM: "${_serializeModulation(fm)}"`), + ...(variation.amModulations ?? []).map(am => `AM: "${_serializeModulation(am)}"`), + ...(variation.subvariations ?? []).flatMap(_variationLines), + ].filter(Boolean); } // VOLUMES, TIMBRE, FM are variation properties (RFC §3.2.1.3). The AST parser @@ -106,14 +105,11 @@ function _instrCharacterLines(instr) { } // Multiple variations — RFC MAYBE_LIST as YAML sequence. - const result = []; - for (const v of allVariations) { + return allVariations.flatMap(v => { const vLines = _variationLines(v); - if (!vLines.length) continue; - result.push(` - ${vLines[0]}`); - for (const l of vLines.slice(1)) result.push(` ${l}`); - } - return result; + if (!vLines.length) return []; + return [` - ${vLines[0]}`, ...vLines.slice(1).map(l => ` ${l}`)]; + }); } // RFC §4.4: embedded instrument key is "instrument NAME:" not "instrument: 'NAME'" @@ -136,14 +132,12 @@ function _serializeArticleValue(v) { } function _buildArticlesBlock(articles) { - const lines = ['articles:']; - for (const art of articles) { + const body = articles.flatMap(art => { const props = (art.properties ?? []).filter(p => p.name); - if (!props.length) continue; - lines.push(` ${art.name}:`); - for (const p of props) lines.push(` ${p.name}: ${_serializeArticleValue(p.value)}`); - } - return lines.length > 1 ? lines.join('\n') : null; + if (!props.length) return []; + return [` ${art.name}:`, ...props.map(p => ` ${p.name}: ${_serializeArticleValue(p.value)}`)]; + }); + return body.length ? ['articles:', ...body].join('\n') : null; } function _patchArticles(text, articles) { @@ -173,12 +167,8 @@ function _patchMetadata(text, info) { const replaced = new Set(); const out = lines.map(line => { - for (const key of META_KEYS) { - if (line.startsWith(key + ':') && info[key] != null && info[key] !== '') { - replaced.add(key); - return `${key}: ${info[key]}`; - } - } + const key = META_KEYS.find(k => line.startsWith(k + ':') && info[k] != null && info[k] !== ''); + if (key) { replaced.add(key); return `${key}: ${info[key]}`; } return line; }); @@ -193,11 +183,11 @@ function _patchMetadata(text, info) { function _patchInstrumentHeader(text, instruments) { const lines = text.split('\n'); const result = []; - const instrMap = {}; - for (const instr of instruments) { - instrMap[instr.name] = instr; - if (instr.name.includes('/')) instrMap[instr.name.split('/').pop()] = instr; - } + const instrMap = Object.fromEntries(instruments.flatMap(instr => { + const entries = [[instr.name, instr]]; + if (instr.name.includes('/')) entries.push([instr.name.split('/').pop(), instr]); + return entries; + })); let i = 0; while (i < lines.length) { @@ -283,41 +273,40 @@ export function patchScore(rawScoreText, instruments, bars = [], info = null, ar const dirtyArticles = articles.filter(a => a.isDirty); if (dirtyArticles.length || flags.articlesModified) { patchedHeader = _patchArticles(patchedHeader, articles); - for (const a of dirtyArticles) log.push({ level: 'changed', path: `articles / ${a.name}` }); + dirtyArticles.forEach(a => log.push({ level: 'changed', path: `articles / ${a.name}` })); } patchedHeader = _patchInstrumentHeader(patchedHeader, instruments); - for (const i of instruments) { + instruments.forEach(i => { if (i.deleted) log.push({ level: 'changed', path: `instrument / ${i.name} (deleted)` }); else if (i.isDirty) log.push({ level: 'changed', path: `instrument / ${i.name}` }); - } + }); - const barMap = {}; - for (const bar of bars) barMap[bar.id] = bar; + const barMap = Object.fromEntries(bars.map(b => [b.id, b])); + const newBars = bars.filter(b => b.isNew && !b.deleted); if (!barDocs.length) { - const newBarDocs = bars.filter(b => b.isNew && !b.deleted).map(b => _buildNewBarDoc(b)); - for (const b of bars.filter(b => b.isNew && !b.deleted)) log.push({ level: 'changed', path: `bar / ${b.id}` }); - return { text: [patchedHeader, ...newBarDocs].join(SEP), log }; + newBars.forEach(b => log.push({ level: 'changed', path: `bar / ${b.id}` })); + return { text: [patchedHeader, ...newBars.map(_buildNewBarDoc)].join(SEP), log }; } let passedThrough = 0; const patchedBarDocs = []; - for (const doc of barDocs) { + barDocs.forEach(doc => { const m = doc.match(/^_id:\s*(\S+)/m); - if (!m) { passedThrough++; patchedBarDocs.push(doc); continue; } + if (!m) { passedThrough++; patchedBarDocs.push(doc); return; } const bar = barMap[m[1]]; - if (!bar) { passedThrough++; patchedBarDocs.push(doc); continue; } - if (bar.deleted) continue; - if (!bar.isDirty) { passedThrough++; patchedBarDocs.push(doc); continue; } + if (!bar) { passedThrough++; patchedBarDocs.push(doc); return; } + if (bar.deleted) return; + if (!bar.isDirty) { passedThrough++; patchedBarDocs.push(doc); return; } log.push({ level: 'changed', path: `bar / ${bar.id}` }); patchedBarDocs.push(_patchBarMeta(doc, bar)); - } + }); - for (const bar of bars.filter(b => b.isNew && !b.deleted)) { + newBars.forEach(bar => { patchedBarDocs.push(_buildNewBarDoc(bar)); log.push({ level: 'changed', path: `bar / ${bar.id}` }); - } + }); if (passedThrough > 0) { log.push({ level: 'info', message: `${passedThrough} bar document${passedThrough !== 1 ? 's' : ''} passed through unchanged` }); diff --git a/static/subobject-kinds.js b/static/subobject-kinds.js index 0e3a69c..78590b6 100644 --- a/static/subobject-kinds.js +++ b/static/subobject-kinds.js @@ -1,9 +1,3 @@ -// Group a node's sub-objects by KIND (the SLOT side of SLOT.SUBTYPE in the AST). -// Per the editor design: items sharing a kind share one pane; different kinds -// produce separate panes whose handles render in the AppShell bottom bar. -// Each group is { kind, items: [{ kind, node, label, hasChildren, readOnly? }] }. -// The returned order is the display order for the handle bar. - export const KIND_LABEL = { tuning: 'TU', stage: 'ST', @@ -30,10 +24,10 @@ export function getKindGroups(node) { ]}); } - const stage = []; - if (node.stageCone) stage.push({ kind: 'stage', node: node.stageCone, label: 'cone (orchestra)', hasChildren: false }); - for (const sv of (node.stageVoices ?? [])) - stage.push({ kind: 'stage', node: sv, label: sv.name, hasChildren: false }); + const stage = [ + ...(node.stageCone ? [{ kind: 'stage', node: node.stageCone, label: 'cone (orchestra)', hasChildren: false }] : []), + ...(node.stageVoices ?? []).map(sv => ({ kind: 'stage', node: sv, label: sv.name, hasChildren: false })), + ]; if (stage.length) groups.push({ kind: 'stage', items: stage }); if (node.instruments.length) {