| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- export type AudioCueKey =
- | 'session_started'
- | 'control_completed:start'
- | 'control_completed:control'
- | 'control_completed:finish'
- | 'punch_feedback:warning'
- | 'guidance:searching'
- | 'guidance:distant'
- | 'guidance:approaching'
- | 'guidance:ready'
- export interface AudioCueConfig {
- src: string
- volume: number
- loop: boolean
- loopGapMs: number
- backgroundMode: 'disabled' | 'guidance'
- }
- export interface GameAudioConfig {
- enabled: boolean
- masterVolume: number
- obeyMuteSwitch: boolean
- backgroundAudioEnabled: boolean
- distantDistanceMeters: number
- approachDistanceMeters: number
- readyDistanceMeters: number
- cues: Record<AudioCueKey, AudioCueConfig>
- }
- export interface PartialAudioCueConfig {
- src?: string
- volume?: number
- loop?: boolean
- loopGapMs?: number
- backgroundMode?: 'disabled' | 'guidance'
- }
- export interface GameAudioConfigOverrides {
- enabled?: boolean
- masterVolume?: number
- obeyMuteSwitch?: boolean
- backgroundAudioEnabled?: boolean
- distantDistanceMeters?: number
- approachDistanceMeters?: number
- readyDistanceMeters?: number
- cues?: Partial<Record<AudioCueKey, PartialAudioCueConfig>>
- }
- export const DEFAULT_GAME_AUDIO_CONFIG: GameAudioConfig = {
- enabled: true,
- masterVolume: 1,
- obeyMuteSwitch: true,
- backgroundAudioEnabled: true,
- distantDistanceMeters: 80,
- approachDistanceMeters: 20,
- readyDistanceMeters: 5,
- cues: {
- session_started: {
- src: '/assets/sounds/session-start.wav',
- volume: 0.78,
- loop: false,
- loopGapMs: 0,
- backgroundMode: 'disabled',
- },
- 'control_completed:start': {
- src: '/assets/sounds/start-complete.wav',
- volume: 0.84,
- loop: false,
- loopGapMs: 0,
- backgroundMode: 'disabled',
- },
- 'control_completed:control': {
- src: '/assets/sounds/control-complete.wav',
- volume: 0.8,
- loop: false,
- loopGapMs: 0,
- backgroundMode: 'disabled',
- },
- 'control_completed:finish': {
- src: '/assets/sounds/finish-complete.wav',
- volume: 0.92,
- loop: false,
- loopGapMs: 0,
- backgroundMode: 'disabled',
- },
- 'punch_feedback:warning': {
- src: '/assets/sounds/warning.wav',
- volume: 0.72,
- loop: false,
- loopGapMs: 0,
- backgroundMode: 'disabled',
- },
- 'guidance:searching': {
- src: '/assets/sounds/guidance-searching.wav',
- volume: 0.48,
- loop: true,
- loopGapMs: 1800,
- backgroundMode: 'guidance',
- },
- 'guidance:distant': {
- src: '/assets/sounds/guidance-searching.wav',
- volume: 0.34,
- loop: true,
- loopGapMs: 4800,
- backgroundMode: 'guidance',
- },
- 'guidance:approaching': {
- src: '/assets/sounds/guidance-approaching.wav',
- volume: 0.58,
- loop: true,
- loopGapMs: 950,
- backgroundMode: 'guidance',
- },
- 'guidance:ready': {
- src: '/assets/sounds/guidance-ready.wav',
- volume: 0.68,
- loop: true,
- loopGapMs: 650,
- backgroundMode: 'guidance',
- },
- },
- }
- function clampVolume(value: number, fallback: number): number {
- return Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : fallback
- }
- function clampDistance(value: number, fallback: number): number {
- return Number.isFinite(value) && value > 0 ? value : fallback
- }
- function clampGap(value: number, fallback: number): number {
- return Number.isFinite(value) && value >= 0 ? value : fallback
- }
- export function mergeGameAudioConfig(overrides?: GameAudioConfigOverrides | null): GameAudioConfig {
- const cues: GameAudioConfig['cues'] = {
- session_started: { ...DEFAULT_GAME_AUDIO_CONFIG.cues.session_started },
- 'control_completed:start': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['control_completed:start'] },
- 'control_completed:control': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['control_completed:control'] },
- 'control_completed:finish': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['control_completed:finish'] },
- 'punch_feedback:warning': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['punch_feedback:warning'] },
- 'guidance:searching': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['guidance:searching'] },
- 'guidance:distant': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['guidance:distant'] },
- 'guidance:approaching': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['guidance:approaching'] },
- 'guidance:ready': { ...DEFAULT_GAME_AUDIO_CONFIG.cues['guidance:ready'] },
- }
- if (overrides && overrides.cues) {
- const keys = Object.keys(overrides.cues) as AudioCueKey[]
- for (const key of keys) {
- const cue = overrides.cues[key]
- if (!cue) {
- continue
- }
- if (typeof cue.src === 'string' && cue.src) {
- cues[key].src = cue.src
- }
- if (cue.volume !== undefined) {
- cues[key].volume = clampVolume(Number(cue.volume), cues[key].volume)
- }
- if (cue.loop !== undefined) {
- cues[key].loop = !!cue.loop
- }
- if (cue.loopGapMs !== undefined) {
- cues[key].loopGapMs = clampGap(Number(cue.loopGapMs), cues[key].loopGapMs)
- }
- if (cue.backgroundMode === 'disabled' || cue.backgroundMode === 'guidance') {
- cues[key].backgroundMode = cue.backgroundMode
- }
- }
- }
- return {
- enabled: overrides && overrides.enabled !== undefined ? !!overrides.enabled : DEFAULT_GAME_AUDIO_CONFIG.enabled,
- masterVolume: clampVolume(Number(overrides && overrides.masterVolume), DEFAULT_GAME_AUDIO_CONFIG.masterVolume),
- obeyMuteSwitch: overrides && overrides.obeyMuteSwitch !== undefined ? !!overrides.obeyMuteSwitch : DEFAULT_GAME_AUDIO_CONFIG.obeyMuteSwitch,
- backgroundAudioEnabled: overrides && overrides.backgroundAudioEnabled !== undefined
- ? !!overrides.backgroundAudioEnabled
- : true,
- distantDistanceMeters: clampDistance(
- Number(overrides && overrides.distantDistanceMeters),
- DEFAULT_GAME_AUDIO_CONFIG.distantDistanceMeters,
- ),
- approachDistanceMeters: clampDistance(Number(overrides && overrides.approachDistanceMeters), DEFAULT_GAME_AUDIO_CONFIG.approachDistanceMeters),
- readyDistanceMeters: clampDistance(
- Number(overrides && overrides.readyDistanceMeters),
- DEFAULT_GAME_AUDIO_CONFIG.readyDistanceMeters,
- ),
- cues,
- }
- }
|