import { createEngineSound } from '$sound'; import type { EngineSound, SoundDom, SoundTimers } from '$sound'; import type { AppServiceFactory } from '../services.ts'; /** * `defineEngineSound()` produces the service factory for the `sound` slot — the * shared Web Audio runtime extracted from sema's `SoundChannel` (2026-07-30). * * It is a SINGLE engine on purpose: the browser caps concurrent * `AudioContext`s and the autoplay unlock gesture is per-context, so a second * one would leave either the UI earcons or the app's own content mute. Every * consumer — sema's sound channel, a media player, a waveform, a visualiser — * meets here and hangs its graph off `sound.context`. * * Needs the DOM service (the global unlock listener) via * `serviceDependencies: ['dom']`, and the core `timers` for the earcon's * completion. Both degrade: without `dom` a suspended context is resumed on the * next `prime()` instead of on the next gesture; without `timers` the engine * falls back to `setTimeout`. */ export function defineEngineSound(): AppServiceFactory< 'sound', readonly ['timers'], readonly ['dom'], EngineSound > { return { name: 'sound', coreDependencies: ['timers'], serviceDependencies: ['dom'], initMode: 'lazy', create({ core, services }): EngineSound { return createEngineSound({ dom: services.dom as SoundDom | undefined, timers: core.timers as SoundTimers | undefined }); }, dispose(instance) { instance.dispose(); } }; }