Add per-node scene music volume control
Author a music track and volume (0-100%) per story node; the runtime scales it to 0-1 and applies it without restarting the track when only the level changes. Adds migration 024, threads music_volume through the repository/runtime, and gives the node inspector a volume slider. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { CUTSCENE_COMPONENT_KEYS, DialoguePreview } from './narrative'
|
||||
|
||||
type StoryNodeType = 'cutscene' | 'dialogue' | 'level' | 'det_gate' | 'llm_gate'
|
||||
type Terminal = { id: string; terminalKey: string; label: string; toNodeId: string | null; sortOrder: number }
|
||||
type StoryNode = { id: string; nodeType: StoryNodeType; label: string; hasUtterances: boolean; xpos: number; ypos: number; levelTemplateVersionId: string | null; componentKey: string | null; musicAssetId: string | null; terminals: Terminal[] }
|
||||
type StoryNode = { id: string; nodeType: StoryNodeType; label: string; hasUtterances: boolean; xpos: number; ypos: number; levelTemplateVersionId: string | null; componentKey: string | null; musicAssetId: string | null; musicVolume: number; terminals: Terminal[] }
|
||||
type AudioAsset = { id: string; originalName: string; mimeType: string }
|
||||
type Graph = { mysteryId: string; entryNodeId: string | null; nodes: StoryNode[] }
|
||||
type LevelTemplate = { versionId: string; slug: string; name: string; version: number }
|
||||
@@ -182,6 +182,7 @@ function NodeInspector({ node, graph, templates, audioAssets, onPatch, onSetEntr
|
||||
}) {
|
||||
const [label, setLabel] = useState(node.label)
|
||||
const [componentKey, setComponentKey] = useState(node.componentKey || '')
|
||||
const [volume, setVolume] = useState(node.musicVolume)
|
||||
const nodeName = (id: string | null) => id ? (graph.nodes.find(n => n.id === id)?.label || '—') : '— unwired —'
|
||||
const usesComponent = node.nodeType === 'cutscene' || node.nodeType === 'det_gate' || node.nodeType === 'llm_gate'
|
||||
return <div className="inspector-body">
|
||||
@@ -204,6 +205,10 @@ function NodeInspector({ node, graph, templates, audioAssets, onPatch, onSetEntr
|
||||
{audioAssets.map(asset => <option key={asset.id} value={asset.id}>{asset.originalName}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{node.musicAssetId && <label className="ins-field"><span>Volume {volume}%</span>
|
||||
<input type="range" min={0} max={100} value={volume} onChange={e => setVolume(Number(e.target.value))}
|
||||
onPointerUp={() => volume !== node.musicVolume && onPatch({ musicVolume: volume })} onBlur={() => volume !== node.musicVolume && onPatch({ musicVolume: volume })} />
|
||||
</label>}
|
||||
|
||||
<div className="ins-terminals-head"><span>Output terminals</span><button onClick={onAddTerminal}>+ Add</button></div>
|
||||
{node.terminals.map(t => <div key={t.id} className="ins-terminal">
|
||||
|
||||
Reference in New Issue
Block a user