// ── Sonic Brief Studio — app.js ─────────────────────────────────────────────── // Taxonomy aligned with: "A Cognitive Approach to In-Vehicle Warning Sound Design" // Tugral (2026) — 6-level urgency x 8 functional units framework const URGENCY_LEVELS = [ { id:'warningHigh', name:'WarningHigh', priority:1, color:'#dc3545', icon:'🔴', desc:'Immediate action — near-zero reaction time allowed', examples:'Drowsiness detection, severe tire-pressure loss, collision imminent', loopInterval:500, loopBehavior:'Continuous loop', perceptualTempo:'Very High' }, { id:'warningMiddle', name:'WarningMiddle', priority:2, color:'#fd7e14', icon:'🟠', desc:'Timely driver response required, moderate danger', examples:'One-pedal driving deactivation, moderate drowsiness advisory', loopInterval:1000, loopBehavior:'Continuous loop', perceptualTempo:'High' }, { id:'warningLow', name:'WarningLow', priority:3, color:'#ffc107', icon:'🟡', desc:'Attentive correction needed, lower severity', examples:'Stability-control notice, attention reminder', loopInterval:2000, loopBehavior:'Continuous loop', perceptualTempo:'Moderate' }, { id:'caution', name:'Caution', priority:4, color:'#0dcaf0', icon:'🔵', desc:'Situational awareness — no direct intervention required', examples:'System-status changes, mild tire-pressure fluctuations', loopInterval:3000, loopBehavior:'Continuous loop', perceptualTempo:'Low' }, { id:'notification', name:'Notification', priority:5, color:'#6c757d', icon:'⚫', desc:'Routine informational updates, no intervention needed', examples:'Attention-monitoring feedback, temperature messages', loopInterval:null, loopBehavior:'Single play', perceptualTempo:'Discrete' }, { id:'feedback', name:'Feedback', priority:6, color:'#198754', icon:'🟢', desc:'Real-time continuous response to driver actions', examples:'Cruise-control availability confirmation, charging progress', loopInterval:null, loopBehavior:'Single play', perceptualTempo:'Discrete' } ]; const FUNCTIONAL_UNITS = [ { id:'awareness', name:'ADAS Awareness', priority:1, icon:'🧠', desc:'Driver internal state monitoring — drowsiness and attention alerts', timbre:'Bright percussive mallet tones', centroid:'3.5 kHz', attack:'<10 ms', rationale:'Driver cognition and attention are prerequisites for safe vehicle operation' }, { id:'primaryControl', name:'ADAS PrimaryControl', priority:2, icon:'🎛️', desc:'Core vehicle control — tire pressure, cruise control, stable operation', timbre:'Warm electric-keyboard timbres, sustained harmonics', centroid:'1.2 kHz', attack:'30–80 ms', rationale:'Maintaining stable primary control is the second essential layer' }, { id:'brakeSystems', name:'BrakeSystems', priority:3, icon:'🛑', desc:'Braking interventions — collision-linked and general system faults', timbre:'Flowing modulated synthesizer with rhythmic movement (spectral flux >0.3)', centroid:'2.0 kHz', attack:'<10 ms', rationale:'Braking capabilities enable decisive intervention' }, { id:'highEmergency', name:'HighEmergency', priority:4, icon:'⚡', desc:'Critical failures — fire detection, high-voltage system faults', timbre:'Resonant instrument tones, dense harmonic spectra (HNR >15 dB)', centroid:'3.0 kHz', attack:'<5 ms', rationale:'Acute hazards commanding immediate attention' }, { id:'lowEmergency', name:'LowEmergency', priority:5, icon:'⚠️', desc:'Routine safety-relevant alerts — door-status, temperature irregularities', timbre:'Clean neutral electronic timbres, minimal inharmonicity', centroid:'2.0 kHz', attack:'20–50 ms', rationale:'Moderate hazards with less immediate urgency' }, { id:'roadTraffic', name:'Road & Traffic Sign Cases', priority:6, icon:'🚦', desc:'Infrastructure recognition — collision risks, lane-keeping deviations (28/122 cases)', timbre:'Directional tonal identifier with spatial audio envelope', centroid:'2.5 kHz', attack:'10–30 ms', rationale:'Largest functional group — spatial positioning matches hazard location' }, { id:'lowSafety', name:'LowSafety', priority:7, icon:'🔔', desc:'Lower-severity situations — parking, seatbelt-related messages (36/122 cases)', timbre:'Soft neutral tones, minimal intrusion', centroid:'1.5 kHz', attack:'40–100 ms', rationale:'Everyday operational scenarios with lower immediate safety risk' }, { id:'media', name:'Media', priority:8, icon:'🎵', desc:'Non-safety functions — connectivity, voice-interaction feedback (10/122 cases)', timbre:'Clean minimal electronic tones', centroid:'2.0 kHz', attack:'30–60 ms', rationale:'Non-safety functions carry lowest priority in the hierarchy' } ]; // Paper Table 7: Loop Rate Specifications const LOOP_RATE_TABLE = { warningHigh: { interval:'500 ms', behavior:'Continuous Loop', tempo:'Very High', attack:'<10 ms', spectral:'3–8 kHz' }, warningMiddle: { interval:'1000 ms', behavior:'Continuous Loop', tempo:'High', attack:'10–30 ms', spectral:'2–6 kHz' }, warningLow: { interval:'2000 ms', behavior:'Continuous Loop', tempo:'Moderate', attack:'20–50 ms', spectral:'1.5–4 kHz' }, caution: { interval:'3000 ms', behavior:'Continuous Loop', tempo:'Low', attack:'30–80 ms', spectral:'1–3 kHz' }, notification: { interval:'N/A', behavior:'Play Once', tempo:'Discrete', attack:'30–80 ms', spectral:'0.8–2.5 kHz' }, feedback: { interval:'N/A', behavior:'Play Once', tempo:'Discrete', attack:'5–30 ms', spectral:'1–3 kHz' } }; // Paper Section 4.2.2: Timbre per Functional Unit const TIMBRE_TABLE = { awareness: { timbre:'Bright percussive mallet tones', centroid:'3.5 kHz', attack:'<10 ms', hnr:'N/A', flux:'N/A', spatial:'Center / directional' }, primaryControl: { timbre:'Warm electric-keyboard timbres, sustained harmonic series', centroid:'1.2 kHz', attack:'30–80 ms', hnr:'N/A', flux:'N/A', spatial:'Center' }, brakeSystems: { timbre:'Flowing modulated synthesizer, rhythmic movement', centroid:'2.0 kHz', attack:'<10 ms', hnr:'N/A', flux:'>0.3', spatial:'Center (max attention)' }, highEmergency: { timbre:'Resonant instrument tones, dense harmonic spectra', centroid:'3.0 kHz', attack:'<5 ms', hnr:'>15 dB', flux:'N/A', spatial:'Center (max attention)' }, lowEmergency: { timbre:'Clean neutral electronic timbres, minimal inharmonicity',centroid:'2.0 kHz', attack:'20–50 ms', hnr:'N/A', flux:'N/A', spatial:'Contextual' }, roadTraffic: { timbre:'Directional tonal identifier with spatial envelope', centroid:'2.5 kHz', attack:'10–30 ms', hnr:'N/A', flux:'N/A', spatial:'Left / Right / Rear — match hazard' }, lowSafety: { timbre:'Soft neutral tones, minimal intrusion', centroid:'1.5 kHz', attack:'40–100 ms',hnr:'N/A', flux:'N/A', spatial:'Center' }, media: { timbre:'Clean minimal electronic tones', centroid:'2.0 kHz', attack:'30–60 ms', hnr:'N/A', flux:'N/A', spatial:'Center' } }; // Paper Table 6: Visual color system const COLOR_MAP = { 'Red — WarningHigh (#dc3545)': { hex:'#dc3545', level:'WarningHigh', rationale:'Maximum salience; immediate danger signal; strong attentional capture' }, 'Orange — WarningMiddle (#fd7e14)':{ hex:'#fd7e14', level:'WarningMiddle', rationale:'High salience; urgent but allows brief assessment time' }, 'Amber — WarningLow (#ffc107)': { hex:'#ffc107', level:'WarningLow', rationale:'Advisory warning; automotive standard for caution conditions' }, 'Cyan — Caution (#0dcaf0)': { hex:'#0dcaf0', level:'Caution', rationale:'Transitional advisory; maintains visibility with reduced urgency' }, 'Gray — Notification (#6c757d)': { hex:'#6c757d', level:'Notification', rationale:'Low salience; informational status updates' }, 'Green — Feedback (#198754)': { hex:'#198754', level:'Feedback', rationale:'Positive confirmation; successful operation indicator' } }; const SPATIAL_MAP = { 'Center — system-level, no direction': 'center', 'Front — forward-facing threat': 'front-center', 'Left — blind spot / lane change': 'left', 'Right — right-side hazard': 'right', 'Rear-left — rear approaching threat': 'rear-left', 'Rear-right — rear approaching threat': 'rear-right', 'Rear-center — reversing / backup': 'rear-center' }; const LOOP_FROM_ANSWER = { 'Very rapid — maximum density (500ms intervals)': { interval:'500 ms', urgency:'WarningHigh' }, 'Fast — high urgency (1000ms intervals)': { interval:'1000 ms', urgency:'WarningMiddle' }, 'Moderate — moderate urgency (2000ms intervals)': { interval:'2000 ms', urgency:'WarningLow' }, 'Slow — advisory (3000ms intervals)': { interval:'3000 ms', urgency:'Caution' }, 'Play once only — single event': { interval:'N/A', urgency:'Notification/Feedback' } }; const COGLOAD_MAP = { 'Maximum — complex traffic, hands on wheel': 'Lower perceptual threshold — auditory dominates visual channel', 'High — monitoring automation, non-driving task': 'Moderate threshold — out-of-loop recovery needed', 'Medium — relaxed highway driving': 'Standard perceptual threshold applies', 'Low — parked or stopped': 'Higher threshold acceptable — driver attention available', 'Medium — focused but not overloaded': 'Standard perceptual threshold applies', 'Low — idle, waiting, or at rest': 'Higher threshold acceptable — user attention available' }; // ─── Industry-agnostic Session Questions ───────────────────────────────────── function getQuestions(urgency, unit, product) { return [ { id:'trigger_scenario', key:'trigger_scenario', text:'Describe the exact moment this sound is triggered.', subtext:'What is the system state? What is the user doing? What event caused the sound to fire? Be as specific as possible.', type:'text', placeholder:'e.g. User submits a form with an invalid field. System detects low battery at 5%. A file upload completes successfully…' }, { id:'required_action', key:'required_action', text:'What should the user do after hearing this?', subtext:'The expected response determines urgency, loop behavior, and salience. Consider the reaction window available to the user.', type:'choices', choices:[ { label:'Act immediately — no time to assess (<1 sec)', emoji:'⚡' }, { label:'Respond within a few seconds', emoji:'🎯' }, { label:'Take action when convenient (>3 sec)', emoji:'⏱️' }, { label:'Simply be aware — no action required', emoji:'👁️' }, { label:'Receive confirmation of their own action', emoji:'✅' } ] }, { id:'simultaneous_conflict', key:'simultaneous_conflict', text:'If multiple sounds fire at the same time, how should this one behave?', subtext:`Priority stacking prevents auditory overload. Define this sound's rank relative to others in the system.`, type:'choices', choices:[ { label:'Always takes priority — interrupts everything else', emoji:'🔴' }, { label:'Yields to higher-priority sounds already playing', emoji:'🟡' }, { label:'Queues — plays after the current sound ends', emoji:'🔵' }, { label:'Suppressed if the user is already overloaded', emoji:'⚪' } ] }, { id:'spatial_channel', key:'spatial_channel', text:`Where should this sound appear in the listener's space?`, subtext:'Spatial positioning can reinforce meaning — a directional sound can indicate where an event is occurring. Center is safe for system-level events.', type:'choices', choices:[ { label:'Center / ambient — no specific direction', emoji:'🎯' }, { label:'Left', emoji:'⬅️' }, { label:'Right', emoji:'➡️' }, { label:'Front / top', emoji:'⬆️' }, { label:'Rear / bottom', emoji:'⬇️' }, { label:'Positional — matches event location', emoji:'📍' }, { label:'Omnidirectional / surround', emoji:'🔊' } ] }, { id:'loop_expectation', key:'loop_expectation', text:'How should this sound repeat if the user has not responded?', subtext:'Repetition rate is the primary pre-attentive urgency cue. Faster loops read as more critical; slower loops or single plays feel informational.', type:'choices', choices:[ { label:'Very rapid — continuous, maximum density (≤500ms)', emoji:'🚨' }, { label:'Fast — repeating, high urgency (≈1000ms)', emoji:'⚠️' }, { label:'Moderate — intermittent (≈2000ms)', emoji:'🔔' }, { label:'Slow — gentle reminder (≈3000ms)', emoji:'🔵' }, { label:'Play once — single non-repeating event', emoji:'1️⃣' } ] }, { id:'timbre_character', key:'timbre_character', text:'What timbral quality should this sound have?', subtext:'Timbre is the primary identifier of which system or function is speaking. It communicates meaning before the user consciously processes the sound.', type:'choices', choices:[ { label:'Sharp & percussive — immediate attention, high onset clarity', emoji:'🥁' }, { label:'Warm & harmonic — familiar, calm, sustained tone', emoji:'🎹' }, { label:'Moving & rhythmic — dynamic, implies process or change', emoji:'〰️' }, { label:'Dense & resonant — full spectrum, commands full attention', emoji:'🔔' }, { label:'Clean & neutral — minimal, purely informational', emoji:'◻️' }, { label:'Soft & ambient — non-intrusive, background awareness', emoji:'🌊' } ] }, { id:'cognitive_load', key:'cognitive_load', text:'How cognitively occupied is the user when this sound fires?', subtext:'Auditory signals are most effective when visual attention is unavailable. Higher load demands lower perceptual thresholds and more distinctive sounds.', type:'choices', choices:[ { label:'Maximum — fully occupied, eyes and hands engaged', emoji:'😰' }, { label:'High — multitasking or under time pressure', emoji:'🤔' }, { label:'Medium — focused but not overloaded', emoji:'😐' }, { label:'Low — idle, waiting, or at rest', emoji:'😌' } ] }, { id:'semantic_reference', key:'semantic_reference', text:'What real-world sound intuitively conveys the same meaning?', subtext:'Auditory icons borrow from learned associations — everyday sounds that already carry meaning. No acoustics vocabulary needed.', type:'text', placeholder:'e.g. A cracking branch → sudden danger. A microwave chime → task done. A coin drop → success. A soft knock → gentle reminder…' }, { id:'startle_level', key:'startle_level', text:'Where on the startle–annoyance spectrum should this sound land?', subtext:'Too quiet: missed entirely. Too loud or abrupt: startle response, stress, or the user turns it off. Find the appropriate perceptual window.', type:'slider', min:1, max:7, defaultVal:4, leftLabel:'Barely perceptible (risk: ignored)', rightLabel:'Maximum startle (risk: disabled)' }, { id:'visual_urgency', key:'visual_urgency', text:'Which visual urgency color would accompany this sound in the interface?', subtext:'Color and sound should communicate the same urgency level. This cross-modal alignment reinforces meaning and reduces cognitive load.', type:'choices', choices:[ { label:'Red — critical, immediate danger', emoji:'🔴' }, { label:'Orange — urgent, timely response needed', emoji:'🟠' }, { label:'Amber / Yellow — caution, advisory', emoji:'🟡' }, { label:'Blue / Cyan — informational, low urgency', emoji:'🔵' }, { label:'Gray — neutral, background status', emoji:'⚫' }, { label:'Green — success, confirmation, positive', emoji:'🟢' } ] }, { id:'multimodal_pairing', key:'multimodal_pairing', text:'What other modalities accompany or reinforce this sound?', subtext:'Multimodal coordination — audio + visual + haptic — significantly improves user response accuracy and perceived confidence.', type:'multi', chips:[ 'Visual notification (banner, toast, icon)', 'Screen flash or color change', 'Haptic / vibration feedback', 'Badge or indicator update', 'Speech / voice message', 'LED or ambient light', 'Animation or motion', 'Audio only — no additional modalities' ] }, { id:'avoid', key:'avoid', text:'What must this sound absolutely avoid?', subtext:'Design constraints define the failure modes. Identify what would make this sound harmful, annoying, or ignored.', type:'multi', chips:[ 'Startle reflex — too abrupt or loud', 'Listener fatigue over repeated exposure', 'Confusion with other system sounds', 'Masking speech, music, or other critical audio', 'Too quiet for the ambient noise environment', 'Causing the user to disable the alert entirely', 'Cultural misinterpretation', 'Inaccessibility — not usable without hearing' ] }, { id:'duration_envelope', key:'duration_envelope', text:'How long should this sound last, and how does it end?', subtext:'Duration and release shape affect perceived urgency and completion. A sound that cuts off abruptly feels different from one that fades.', type:'choices', choices:[ { label:'Very short — under 0.5 sec, sharp transient', emoji:'⚡' }, { label:'Short — 0.5–1 sec, clear onset and decay', emoji:'🔹' }, { label:'Medium — 1–3 sec, sustained with natural release', emoji:'🔷' }, { label:'Long — 3+ sec, evolving or layered texture', emoji:'🌊' }, { label:'Variable — continues until dismissed or resolved', emoji:'🔁' } ] }, { id:'brand_personality', key:'brand_personality', text:'What personality or brand quality should this sound express?', subtext:'Sound is a brand touchpoint. The emotional character of a sound shapes how users perceive the product — even before they consciously process the message.', type:'choices', choices:[ { label:'Trustworthy & reliable — solid, grounded, no surprises', emoji:'🛡️' }, { label:'Friendly & approachable — warm, human, low intimidation', emoji:'🤝' }, { label:'Precise & technical — clean, efficient, exact', emoji:'🔬' }, { label:'Premium & refined — subtle, polished, never cheap', emoji:'✦' }, { label:'Bold & confident — strong, assertive, unmistakable', emoji:'💥' }, { label:'Calm & minimal — restrained, considered, never intrusive', emoji:'🌿' } ] }, { id:'user_expectation', key:'user_expectation', text:'Has the user heard this sound before, or is it new to them?', subtext:'Familiarity changes how a sound is processed. Learned sounds trigger faster responses; novel sounds demand more attention but can carry richer meaning.', type:'choices', choices:[ { label:'Completely new — user has no prior association', emoji:'🆕' }, { label:'Partially familiar — similar to sounds they know', emoji:'🔄' }, { label:'Fully learned — part of an established sound system', emoji:'✅' }, { label:'Culturally universal — leverages global shared meaning', emoji:'🌍' } ] }, { id:'accessibility', key:'accessibility', text:'How should this sound work for users with hearing differences?', subtext:'Inclusive sound design ensures the experience is not exclusively auditory. Consider redundancy and alternatives from the start, not as an afterthought.', type:'multi', chips:[ 'Paired with a visual indicator (color, icon, animation)', 'Paired with haptic / vibration feedback', 'Subtitles or text description available', 'Adjustable volume or pitch range', 'Works at low volume — designed for quiet environments', 'Does not rely solely on frequency extremes (very high or very low)', 'Meets WCAG 1.4.2 audio control requirements' ] }, { id:'context_environment', key:'context_environment', text:'In what physical environment will users most often hear this sound?', subtext:'Ambient noise floor, reverberation, and speaker quality all affect how a sound is perceived. A sound designed for open-plan offices fails in a crowded transit station.', type:'choices', choices:[ { label:'Quiet indoor space — home, private office', emoji:'🏠' }, { label:'Open-plan office — moderate background noise', emoji:'🏢' }, { label:'Public or outdoor — high ambient noise', emoji:'🌆' }, { label:'Mobile — variable, headphones or small speakers', emoji:'📱' }, { label:'Wearable — on-body device, close to skin', emoji:'⌚' }, { label:'Shared space — classrooms, hospitals, retail', emoji:'🏥' } ] }, { id:'failure_mode', key:'failure_mode', text:'What happens if the user misses or ignores this sound?', subtext:'Understanding the consequence of non-response defines how much salience the sound truly needs. Not every missed sound is a crisis.', type:'choices', choices:[ { label:'Safety risk — consequences are physical or irreversible', emoji:'🚨' }, { label:'Data or work loss — significant but recoverable', emoji:'💾' }, { label:'Minor friction — user must redo a step or wait', emoji:'⏳' }, { label:'No real consequence — purely informational', emoji:'ℹ️' }, { label:'Opportunity missed — time-sensitive but not critical', emoji:'⏰' } ] }, { id:'sound_system_fit', key:'sound_system_fit', text:'How does this sound relate to the existing sound system or palette?', subtext:'Sounds do not exist in isolation. A new sound must be discriminable from others while still belonging to the same family — coherent but distinct.', type:'choices', choices:[ { label:'Part of an established system — must follow existing rules', emoji:'📐' }, { label:'Extending an existing system — adjacent, new category', emoji:'➕' }, { label:'Standalone — no existing system to align with', emoji:'🆓' }, { label:'Replacing an old sound — must feel like an upgrade', emoji:'🔄' }, { label:'Cross-platform — must work across multiple products', emoji:'🔗' } ] }, { id:'iteration_notes', key:'iteration_notes', text:'Any additional context, constraints, or references the sound designer should know?', subtext:'This is an open field for anything not captured above — regulatory notes, competitive references, internal debates, gut feelings, or technical constraints.', type:'text', placeholder:'e.g. Must not sound like our competitor. Legal reviewed and approved. PM wants it to feel like a high-end appliance. Engineering has a 48kHz / 16-bit constraint…' } ]; } // ─── Brief builder ──────────────────────────────────────────────────────────── function buildTechnicalBrief(urgency, unit, answers, product) { const loop = LOOP_RATE_TABLE[urgency.id] || LOOP_RATE_TABLE.notification; const timbre = TIMBRE_TABLE[unit.id] || TIMBRE_TABLE.media; const spatialKey= answers.spatial_channel || 'Center — system-level, no direction'; const spatial = SPATIAL_MAP[spatialKey] || 'center'; const colorKey = answers.visual_urgency || ''; const color = COLOR_MAP[colorKey] || { hex: urgency.color, level: urgency.name, rationale: urgency.desc }; const loopAns = answers.loop_expectation || ''; const loopSpec = LOOP_FROM_ANSWER[loopAns] || {}; const cogLoad = answers.cognitive_load || 'Medium — focused but not overloaded'; const cogSpec = COGLOAD_MAP[cogLoad] || COGLOAD_MAP['Medium — focused but not overloaded']; const multimodal= Array.isArray(answers.multimodal_pairing) ? answers.multimodal_pairing.join(', ') : (answers.multimodal_pairing || 'Not specified'); const avoidList = Array.isArray(answers.avoid) ? answers.avoid.map(a => ' – ' + a).join('\n') : (' – ' + (answers.avoid || 'Not specified')); const trigger = answers.trigger_scenario || 'Not specified'; const action = answers.required_action || 'Not specified'; const conflict = answers.simultaneous_conflict || 'Not specified'; const startleRaw= typeof answers.startle_level === 'object' ? (answers.startle_level?.raw || 4) : (answers.startle_level || 4); const semantic = answers.semantic_reference || 'Not specified'; const timbreAns = answers.timbre_character || 'Not specified'; return `SONIC BRIEF Product / Context : ${product.toUpperCase()} Framework : Tugral (2026) — Cognitive Approach to In-Vehicle Warning Sound Design ${'═'.repeat(60)} I. CLASSIFICATION [Table 2 — Data Architecture] ──────────────────────────────────────────────────────────── Urgency Level : ${urgency.name} (Priority ${urgency.priority}/6) Functional Unit : ${unit.name} (Priority ${unit.priority}/8) Priority Logic : Lexicographic — urgency dominates, then unit, then intra-unit rank Visual Telltale : ${color.hex} — ${color.level} Color Rationale : ${color.rationale} II. TRIGGER CONTEXT ──────────────────────────────────────────────────────────── Trigger scenario : ${trigger} Required response : ${action} Conflict behavior : ${conflict} Cognitive load : ${cogLoad} → ${cogSpec} III. AUDITORY DESIGN — METRICAL ACCELERATION [Section 4.2.1 / Table 7] ──────────────────────────────────────────────────────────── Loop interval : ${loopSpec.interval || loop.interval} Loop behavior : ${loop.behavior} Perceptual tempo : ${loop.tempo} Attack envelope : ${loop.attack} Spectral focus : ${loop.spectral} Stakeholder input : ${loopAns || 'Not specified'} Note : Loop rate variation enables pre-attentive urgency discrimination. Drivers perceive threat severity from repetition tempo alone. IV. AUDITORY DESIGN — TIMBRE [Section 4.2.2 — Functional Unit Identifier] ──────────────────────────────────────────────────────────── Unit timbre spec : ${timbre.timbre} Spectral centroid : ${timbre.centroid} Attack : ${timbre.attack} HNR target : ${timbre.hnr} Spectral flux : ${timbre.flux} Stakeholder input : ${timbreAns} Semantic reference: "${semantic}" V. SPATIAL AUDIO [Section 4.2.2 — Channels Field, Table 2] ──────────────────────────────────────────────────────────── Channel : ${spatial} Hazard direction : ${spatialKey} Design rule : Critical (HighEmergency, BrakeSystems) → center-panned for max attention Directional threats (blind-spot, cross-traffic) → match hazard location VI. MULTIMODAL INTEGRATION [Section 2.2 + 4.1] ──────────────────────────────────────────────────────────── Accompanying modalities : ${multimodal} Startle/annoyance balance: ${startleRaw}/7 ${startleRaw <= 2 ? '⚠ Risk: too subtle — may be missed in ambient noise' : startleRaw >= 6 ? '⚠ Risk: startle response — user may disable the sound' : '✓ Within acceptable perceptual range'} VII. DESIGN CONSTRAINTS — AVOID ──────────────────────────────────────────────────────────── ${avoidList} VIII. FUNCTIONAL UNIT DESIGN RATIONALE ──────────────────────────────────────────────────────────── ${unit.rationale} Unit descriptor : ${unit.desc}`; } function buildSunoPrompt(urgency, unit, answers, product) { const loop = LOOP_RATE_TABLE[urgency.id] || LOOP_RATE_TABLE.notification; const timbre = TIMBRE_TABLE[unit.id] || TIMBRE_TABLE.media; const spatial = SPATIAL_MAP[answers.spatial_channel] || 'center'; const semantic = answers.semantic_reference || ''; const loopDesc = loop.behavior === 'Play Once' ? 'single discrete event, play once' : `looping at ${loop.interval} intervals, ${loop.tempo.toLowerCase()} perceptual tempo`; const semPhrase = semantic ? `Sonic reference: "${semantic}". ` : ''; const spatPhrase = spatial !== 'center' ? `Spatially positioned ${spatial} to indicate hazard direction. ` : ''; return `UX interaction sound for ${product}. Sound category: ${unit.name}. Urgency: ${urgency.name}. Timbre: ${timbre.timbre}. Spectral centroid ${timbre.centroid}, attack ${timbre.attack}. ${loopDesc}. ${semPhrase}${spatPhrase}No vocals, no melody, no music. Pure UI/UX sound design — professional grade. Duration: under 2 seconds per loop. ${urgency.desc}.`; } // ─── State ──────────────────────────────────────────────────────────────────── let state = { product:'', selectedUrgency:null, selectedUnit:null, participants:[], currentQuestionIdx:0, currentParticipantIdx:0, allAnswers:{}, answers:{}, questions:[] }; // ─── DOM Refs ───────────────────────────────────────────────────────────────── const viewSetup = document.getElementById('viewSetup'); const viewSession = document.getElementById('viewSession'); const categoryInput = document.getElementById('categoryInput'); const urgencyInput = document.getElementById('urgencyInput'); const productInput = document.getElementById('productInput'); const participantInput = document.getElementById('participantInput'); const addParticipantBtn = document.getElementById('addParticipantBtn'); const participantTags = document.getElementById('participantTags'); const startSessionBtn = document.getElementById('startSessionBtn'); const backToSetupBtn = document.getElementById('backToSetupBtn'); const endSessionBtn = document.getElementById('endSessionBtn'); const sessionCatBadge = document.getElementById('sessionCatBadge'); const sessionProductName = document.getElementById('sessionProductName'); const progressLabel = document.getElementById('progressLabel'); const progressFill = document.getElementById('progressFill'); const questionArea = document.getElementById('questionArea'); const prevQuestionBtn = document.getElementById('prevQuestionBtn'); const nextQuestionBtn = document.getElementById('nextQuestionBtn'); const briefAnswers = document.getElementById('briefAnswers'); const briefStatus = document.getElementById('briefStatus'); const briefActions = document.getElementById('briefActions'); const generatedBrief = document.getElementById('generatedBrief'); const briefGenerating = document.getElementById('briefGenerating'); const generateBriefBtn = document.getElementById('generateBriefBtn'); const gbTechnical = document.getElementById('gbTechnical'); const gbSuno = document.getElementById('gbSuno'); const copyBriefBtn = document.getElementById('copyBriefBtn'); const copySunoBtn = document.getElementById('copySunoBtn'); const sessionLabel = document.getElementById('sessionLabel'); const sessionDot = document.querySelector('.session-dot'); // ─── Wave canvas ────────────────────────────────────────────────────────────── function initWaveCanvas() { const canvas = document.getElementById('waveCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); let W, H, t = 0; const resize = () => { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }; resize(); window.addEventListener('resize', resize); function draw() { ctx.clearRect(0,0,W,H); for (let row=0; row<5; row++) { const y=H*(0.2+row*0.16), amp=20+row*8, freq=0.004+row*0.001, speed=0.4+row*0.15; ctx.strokeStyle='#a78bfa'; ctx.lineWidth=1; ctx.beginPath(); for (let x=0; x<=W; x+=2) { const yp=y+Math.sin(x*freq+t*speed)*amp+Math.sin(x*freq*2.3+t*speed*0.7)*(amp*0.4); x===0 ? ctx.moveTo(x,yp) : ctx.lineTo(x,yp); } ctx.globalAlpha=0.3-row*0.04; ctx.stroke(); ctx.globalAlpha=1; } t+=0.03; requestAnimationFrame(draw); } draw(); } // ─── UI Builder ─────────────────────────────────────────────────────────────── function buildCategoryUI() { // Free-text: match on input against known values, or use raw text as custom function matchUnit(val) { const v = val.trim().toLowerCase(); return FUNCTIONAL_UNITS.find(u => u.id.toLowerCase() === v || u.name.toLowerCase().includes(v) ) || { id: 'custom', name: val.trim(), priority: 9, icon: '🎵', desc: val.trim(), timbre: 'Custom', centroid: '—', attack: '—', hnr: '—', flux: '—', spatial: 'Center', rationale: val.trim() }; } function matchUrgency(val) { const v = val.trim().toLowerCase(); return URGENCY_LEVELS.find(u => u.id.toLowerCase() === v || u.name.toLowerCase().includes(v) ) || { id: 'custom', name: val.trim(), priority: 7, color: '#a78bfa', icon: '🎵', desc: val.trim(), loopInterval: null, loopBehavior: 'Custom', perceptualTempo: 'Custom' }; } categoryInput.addEventListener('input', () => { state.selectedUnit = categoryInput.value.trim() ? matchUnit(categoryInput.value) : null; updateStartBtn(); }); urgencyInput.addEventListener('input', () => { state.selectedUrgency = urgencyInput.value.trim() ? matchUrgency(urgencyInput.value) : null; updateStartBtn(); }); // Participant / Role tags function renderParticipantTags() { participantTags.innerHTML = ''; state.participants.forEach((p, i) => { const tag = document.createElement('span'); tag.className = 'ptag'; tag.innerHTML = p + ''; participantTags.appendChild(tag); }); participantTags.querySelectorAll('.ptag-remove').forEach(btn => { btn.addEventListener('click', () => { state.participants.splice(parseInt(btn.dataset.i), 1); renderParticipantTags(); }); }); } function addParticipant() { const val = participantInput.value.trim(); if (val && !state.participants.includes(val)) { state.participants.push(val); renderParticipantTags(); } participantInput.value = ''; participantInput.focus(); } addParticipantBtn.addEventListener('click', addParticipant); participantInput.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); addParticipant(); } }); } function updateStartBtn() { startSessionBtn.disabled = !(state.selectedUrgency && state.selectedUnit); } updateStartBtn(); productInput.addEventListener('input', () => { state.product = productInput.value; }); // ─── Session ────────────────────────────────────────────────────────────────── startSessionBtn.addEventListener('click', () => { if (!state.selectedUrgency || !state.selectedUnit) return; state.product = productInput.value || 'the vehicle'; state.questions = getQuestions(state.selectedUrgency, state.selectedUnit, state.product); state.answers = {}; state.allAnswers = {}; state.currentQuestionIdx = 0; state.currentParticipantIdx = 0; // Auto-add participant if typed but not yet confirmed with + or Enter const pendingParticipant = participantInput.value.trim(); if (pendingParticipant && !state.participants.includes(pendingParticipant)) { state.participants.push(pendingParticipant); } if (!state.participants || state.participants.length === 0) state.participants = []; const u = state.selectedUrgency, unit = state.selectedUnit; const pNames = state.participants.filter(p => p && p !== 'Participant').join(', '); sessionCatBadge.textContent = u.icon+' '+u.name; sessionCatBadge.style.color = u.color; sessionCatBadge.style.borderColor = u.color; sessionCatBadge.style.background = u.color+'18'; sessionProductName.textContent = (state.product ? '· ' + state.product : '') + (pNames ? (state.product ? ' ' : '') + '· ' + pNames : ''); sessionDot.classList.add('active'); sessionLabel.textContent = u.name + (state.product ? ' · ' + state.product : '') + (pNames ? ' · ' + pNames : ''); // Show participant names in session topbar const participantDots = document.getElementById('participantDots'); if (participantDots) { participantDots.innerHTML = pNames ? `👤 ${pNames}` : ''; participantDots.style.display = pNames ? 'flex' : 'none'; } briefAnswers.innerHTML = '