@forgeax/engine-audio-webaudio 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +134 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/audio-bus-name-owner.test-d.d.ts +2 -0
- package/dist/__tests__/audio-bus-name-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/audio-loader-contract.test.d.ts +2 -0
- package/dist/__tests__/audio-loader-contract.test.d.ts.map +1 -0
- package/dist/__tests__/audio-local-artifacts.test.d.ts +2 -0
- package/dist/__tests__/audio-local-artifacts.test.d.ts.map +1 -0
- package/dist/__tests__/audio-webaudio.browser.test.d.ts +2 -0
- package/dist/__tests__/audio-webaudio.browser.test.d.ts.map +1 -0
- package/dist/__tests__/audio-webaudio.unit.test.d.ts +2 -0
- package/dist/__tests__/audio-webaudio.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gain-automation.unit.test.d.ts +2 -0
- package/dist/__tests__/gain-automation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/host-audio-consumer.unit.test.d.ts +2 -0
- package/dist/__tests__/host-audio-consumer.unit.test.d.ts.map +1 -0
- package/dist/__tests__/root-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/root-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/spatial-cleanup.test.d.ts +2 -0
- package/dist/__tests__/spatial-cleanup.test.d.ts.map +1 -0
- package/dist/__tests__/web-audio-engine-decode-recovery.browser.test.d.ts +2 -0
- package/dist/__tests__/web-audio-engine-decode-recovery.browser.test.d.ts.map +1 -0
- package/dist/__tests__/web-audio-engine-decode-recovery.unit.test.d.ts +2 -0
- package/dist/__tests__/web-audio-engine-decode-recovery.unit.test.d.ts.map +1 -0
- package/dist/audio-importer.d.ts +17 -0
- package/dist/audio-importer.d.ts.map +1 -0
- package/dist/audio-importer.mjs +90 -0
- package/dist/audio-importer.mjs.map +1 -0
- package/dist/audio-listener-sync-system.d.ts +42 -0
- package/dist/audio-listener-sync-system.d.ts.map +1 -0
- package/dist/audio-loader.d.ts +4 -0
- package/dist/audio-loader.d.ts.map +1 -0
- package/dist/clip-loader.d.ts +5 -0
- package/dist/clip-loader.d.ts.map +1 -0
- package/dist/host-audio-consumer.d.ts +11 -0
- package/dist/host-audio-consumer.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +595 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/web-audio-engine.d.ts +43 -0
- package/dist/web-audio-engine.d.ts.map +1 -0
- package/package.json +67 -0
- package/src/__tests__/audio-bus-name-owner.test-d.ts +21 -0
- package/src/__tests__/audio-loader-contract.test.ts +103 -0
- package/src/__tests__/audio-local-artifacts.test.ts +34 -0
- package/src/__tests__/audio-webaudio.browser.test.ts +311 -0
- package/src/__tests__/audio-webaudio.unit.test.ts +3031 -0
- package/src/__tests__/gain-automation.unit.test.ts +173 -0
- package/src/__tests__/host-audio-consumer.unit.test.ts +195 -0
- package/src/__tests__/root-surface.unit.test.ts +8 -0
- package/src/__tests__/spatial-cleanup.test.ts +85 -0
- package/src/__tests__/web-audio-engine-decode-recovery.browser.test.ts +125 -0
- package/src/__tests__/web-audio-engine-decode-recovery.unit.test.ts +93 -0
- package/src/audio-importer.ts +149 -0
- package/src/audio-listener-sync-system.ts +98 -0
- package/src/audio-loader.ts +55 -0
- package/src/clip-loader.ts +111 -0
- package/src/host-audio-consumer.ts +168 -0
- package/src/index.ts +32 -0
- package/src/plugin.ts +15 -0
- package/src/web-audio-engine.ts +411 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/audio-listener-sync-system.ts","../src/clip-loader.ts","../src/audio-loader.ts","../src/web-audio-engine.ts","../src/host-audio-consumer.ts","../src/plugin.ts"],"names":["AudioError"],"mappings":"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","file":"index.mjs","sourcesContent":["// audio-listener-sync-system.ts -- AudioListener world-matrix sync.\n//\n// Syncs the first AudioListener entity's resolved world transform to the Web\n// Audio listener (position + forward/up orientation).\n//\n// feat-20260601 D-6: the sync consumes the single `Transform.world` mat4 (16\n// column-major floats, written by propagateTransforms) instead of a decomposed\n// GlobalTransform TRS. forward / up / position are extracted via the\n// `@forgeax/engine-math` mat4 helpers (getForward = -col2 normalized; getUp =\n// col1 normalized; getTranslation = col3, NOT normalized) -- direction\n// normalization eliminates non-uniform-scale pollution that a bare-column read\n// would leak. The old quaternion-rotate-vector formula is gone.\n//\n// Decision anchors:\n// - plan-strategy D-6 (rename + world-mat4 reshape + mat4 helper reuse)\n// - requirements AC-08 (AudioListener world-matrix sync; quadrant 3 falsify)\n// - requirements E-3 (multiple AudioListeners -> first only)\n//\n// Architecture note: the audio-webaudio package has no dependency on\n// engine-runtime (where Transform lives). The sync function is a pure helper\n// exported for host assembly. The host (engine-runtime or app layer) queries\n// the first AudioListener entity, reads its `Transform.world` mat4 (a 16-float\n// Float32Array), obtains the AudioContext listener from the AudioBackend, and\n// calls `syncListenerFromWorldMatrix(listener, worldMatrix)`.\n//\n// charter awareness:\n// - P3 explicit failure: no-op contracts, no throw for missing listener\n// - P4 consistent abstraction: reuse the math mat4 extract helpers (no\n// hand-rolled quaternion rotation); pure function exports for unit testing\n// - P5 producer/consumer: sync function is the producer; host is the consumer\n\nimport { mat4, vec3 } from '@forgeax/engine-math';\n\n/**\n * Resolved world transform shape (feat-20260601 D-6): a single column-major\n * mat4 carried as 16 contiguous floats -- the `Transform.world` column array\n * view written by propagateTransforms.\n */\nexport interface WorldMatrixData {\n readonly worldMatrix: Float32Array;\n}\n\n/**\n * Pure function: write a world mat4's position/orientation to the Web Audio\n * listener's AudioParams.\n *\n * Exported for unit testing (listener-sync.test.ts) and for host assembly (the\n * host reads `Transform.world` + AudioListener from the World and calls this\n * each frame).\n *\n * - position = `mat4.getTranslation(world)` (col3, copied directly, not normalized)\n * - forward = `mat4.getForward(world)` (-col2, normalized -- removes scale)\n * - up = `mat4.getUp(world)` (col1, normalized -- removes scale)\n *\n * @param listener The Web Audio API AudioListener (from AudioContext.listener)\n * @param worldMatrix The resolved world mat4 (16 column-major floats) of the\n * entity carrying AudioListener.\n */\nexport function syncListenerFromWorldMatrix(\n listener: AudioListener,\n worldMatrix: Float32Array,\n): void {\n const m = worldMatrix as unknown as mat4.Mat4Like;\n\n const position = mat4.getTranslation(vec3.create(), m);\n listener.positionX.value = position[0] as number;\n listener.positionY.value = position[1] as number;\n listener.positionZ.value = position[2] as number;\n\n const forward = mat4.getForward(vec3.create(), m);\n listener.forwardX.value = forward[0] as number;\n listener.forwardY.value = forward[1] as number;\n listener.forwardZ.value = forward[2] as number;\n\n const up = mat4.getUp(vec3.create(), m);\n listener.upX.value = up[0] as number;\n listener.upY.value = up[1] as number;\n listener.upZ.value = up[2] as number;\n}\n\n/**\n * Run the audio listener sync system for a host-owned audio context.\n *\n * The host is responsible for:\n * 1. Querying the World for entities with AudioListener\n * 2. Taking the first AudioListener entity (E-3)\n * 3. Reading its `Transform.world` mat4 (16-float Float32Array)\n * 4. Calling this function with the AudioContext's listener\n *\n * This function is a convenience wrapper that calls\n * `syncListenerFromWorldMatrix(ctx.listener, worldMatrix)`.\n *\n * @param ctx The Web Audio AudioContext (whose .listener receives position/orientation)\n * @param worldMatrix The `Transform.world` mat4 from the AudioListener entity\n */\nexport function audioListenerSyncSystem(ctx: AudioContext, worldMatrix: Float32Array): void {\n syncListenerFromWorldMatrix(ctx.listener, worldMatrix);\n}\n","// clip-loader.ts -- M3 (w27) AudioClipAsset register/load path\n//\n// Load AudioClipAsset from pack-index catalog via fetch + decodeAudioData.\n//\n// Decision anchors:\n// - plan-strategy D-6 (load path: fetch ArrayBuffer -> decodeAudioData -> register)\n// - requirements AC-03 (AudioClipAsset via typed AssetRegistry.load)\n// - requirements constraint 5 (format decided by browser decodeAudioData)\n// - requirements E-2 (decodeAudioData failure returns Err with code: 'decode-failed')\n// - requirements E-9 (nonexistent GUID returns Err with code 'asset-not-found')\n// - research Finding 'decodeAudioData error semantics'\n//\n// charter awareness:\n// - P3 explicit failure: returns Result<AudioClipAsset, AudioError> with structured errors\n// - P4 consistent abstraction: parallel to image/gltf loader patterns\n\nimport { AudioError } from '@forgeax/engine-audio';\nimport type {\n AssetDecoderContribution,\n AssetKind,\n AudioClipAsset,\n Result,\n} from '@forgeax/engine-types';\nimport { err, ok } from '@forgeax/engine-types';\n\nexport async function decodeAudioClipBytes(\n guid: string,\n bytes: Uint8Array,\n mediaType: `audio/${string}`,\n): Promise<Result<AudioClipAsset, AudioError>> {\n if (mediaType.length <= 'audio/'.length || bytes.byteLength === 0) {\n return err(\n new AudioError({\n code: 'decode-failed',\n expected: `non-empty audio mediaType and source bytes for GUID ${guid}`,\n hint: 'verify the audio artifact mediaType and recook the source bytes',\n detail: {\n code: 'decode-failed' as const,\n reason: 'audio mediaType or source bytes are empty',\n },\n }),\n );\n }\n try {\n return ok({ kind: 'audio', sourceKey: guid, mediaType, bytes: bytes.slice() });\n } catch (e) {\n return err(\n new AudioError({\n code: 'decode-failed',\n expected: `decodable audio artifact bytes for GUID ${guid}`,\n hint: 'verify the audio artifact mediaType and browser-supported codec',\n detail: {\n code: 'decode-failed' as const,\n reason: e instanceof Error ? e.message : 'audio artifact decode failed',\n },\n }),\n );\n }\n}\n\nexport const audioClipContribution: AssetDecoderContribution<AudioClipAsset, 'audio'> = {\n kind: { kind: 'audio' } as AssetKind<AudioClipAsset, 'audio'>,\n consumer: 'WebAudioEngine',\n decoder: {\n async decode({ envelope, artifacts }) {\n const source = Object.values(envelope.artifacts).find((descriptor) =>\n descriptor.path.endsWith('.audio'),\n );\n if (source === undefined) {\n return err({\n code: 'asset-package-invalid',\n expected: 'an asset-local audio source artifact',\n hint: 'recook the audio asset with its source artifact',\n detail: { guid: envelope.guid, reason: 'source artifact is missing' },\n });\n }\n const bytes = await artifacts.read(source);\n if (!bytes.ok) {\n return err({\n code: 'asset-package-invalid',\n expected: 'a readable audio source artifact',\n hint: 'repair the Pack artifact and retry the audio decoder',\n detail: { guid: envelope.guid, reason: bytes.error.code },\n });\n }\n const mediaType = source.mediaType;\n if (!mediaType.startsWith('audio/')) {\n return err({\n code: 'asset-package-invalid',\n expected: 'an audio/* source artifact',\n hint: 'publish the audio artifact with its actual media type',\n detail: { guid: envelope.guid, reason: `unsupported media type ${mediaType}` },\n });\n }\n if (bytes.value.byteLength === 0) {\n return err({\n code: 'asset-package-invalid',\n expected: 'non-empty audio source bytes',\n hint: 'recook the audio source and retry the decoder',\n detail: { guid: envelope.guid, reason: 'empty audio artifact' },\n });\n }\n return ok({\n kind: 'audio',\n sourceKey: envelope.guid,\n mediaType: mediaType as `audio/${string}`,\n bytes: bytes.value.slice(),\n });\n },\n },\n};\n","import { AssetError, type Loader, type LoaderAsyncResult } from '@forgeax/engine-types';\nimport { decodeAudioClipBytes } from './clip-loader';\n\n/** Loads an audio catalog row through the browser's native decoder. */\nexport const audioLoader: Loader = {\n kind: 'audio',\n loadPack(input): Promise<LoaderAsyncResult> {\n if (input.kind !== 'audio') {\n return Promise.resolve({\n ok: false,\n error: new AssetError({\n code: 'asset-parse-failed',\n expected: \"Pack v2 loader input with kind 'audio'\",\n hint: 'pass the asset-local audio envelope to the audio loader',\n detail: { sourcePath: input.guid },\n }),\n });\n }\n const source = input.artifacts.source;\n const payloadMediaType =\n typeof input.payload.mediaType === 'string' ? input.payload.mediaType : undefined;\n if (\n source === undefined ||\n !source.descriptor.mediaType.startsWith('audio/') ||\n payloadMediaType !== source.descriptor.mediaType\n ) {\n return Promise.resolve({\n ok: false,\n error: {\n code: 'asset-artifact-media-unsupported',\n expected: 'an asset-local source artifact with an audio/* mediaType',\n hint: 'declare the audio source artifact with a supported mediaType and re-cook',\n detail: {\n guid: input.guid,\n artifactKey: 'source',\n observed: payloadMediaType ?? source?.descriptor.mediaType ?? 'missing',\n expected: 'payload.mediaType matching the audio/* source artifact',\n },\n },\n });\n }\n return decodeAudioClipBytes(input.guid, source.bytes, payloadMediaType as `audio/${string}`);\n },\n async load(): Promise<LoaderAsyncResult> {\n return {\n ok: false,\n error: new AssetError({\n code: 'asset-parse-failed',\n expected: 'Pack v2 audio input with an asset-local source artifact',\n hint: 're-cook the audio source into the Pack v2 asset envelope',\n detail: { sourcePath: 'audio-loader-input' },\n }),\n };\n },\n};\n","// web-audio-engine.ts -- M2 (w16) WebAudioEngine AudioBackend implementation\n//\n// Implements AudioBackend for Web Audio API:\n// 1. Lazy AudioContext creation (D-3) -- ensureContext() on first play()\n// 2. Gesture listener resume (D-3) -- register bounded 'click'/'keydown'/'touchstart'\n// listeners that call ctx.resume(), re-arming them after a refusal\n// 3. Fixed two-bus topology (D-5): masterGain <= sfxGain + musicGain\n// 4. Per-source GainNode for individual volume control\n// 5. Active source Map<entityId, { node, sourceGain, bus }>\n// 6. Health check: getState() / getActiveSourceCount()\n// 7. destroy(): stop all, disconnect, close ctx\n//\n// Decision anchors:\n// - plan-strategy D-3 (lazy-create + one-shot gesture listener resume)\n// - plan-strategy D-5 (fixed two-bus topology: SFX + Music -> Master)\n// - plan-strategy section 3.1 (WebAudioEngine owner of AudioContext + bus GainNodes)\n// - requirements S-1 (AudioContext lifecycle), S-5 (dual bus), S-9 (World Resource)\n// - requirements AC-01 (lazy creation), AC-02 (auto resume), AC-10 (bus volume/mute)\n//\n// charter awareness:\n// - P3 explicit failure: getState() returns real AudioContext.state, never a stale cache\n// - P4 consistent abstraction: implements AudioBackend interface, parallel to InputBackend\n\nimport {\n AUDIO_ERROR_HINTS,\n AudioError,\n type AudioListenerPose,\n type AudioPlayOptions,\n type AudioState,\n type BusName,\n} from '@forgeax/engine-audio';\nimport type { AudioClipAsset } from '@forgeax/engine-types';\n\ninterface ActiveSource {\n node: AudioBufferSourceNode;\n sourceGain: GainNode;\n panner: PannerNode | undefined;\n bus: BusName;\n}\n\nconst GESTURE_EVENTS = ['click', 'keydown', 'touchstart'] as const;\nconst GAIN_TRANSITION_SECONDS = 0.01;\n\nexport class WebAudioEngine {\n private ctx: AudioContext | undefined;\n private closed = false;\n private masterGain: GainNode | undefined;\n private sfxGain: GainNode | undefined;\n private musicGain: GainNode | undefined;\n\n private readonly sources = new Map<number, ActiveSource>();\n\n private gestureListening = false;\n private resumeInFlight: Promise<void> | undefined;\n private readonly gestureResumeHandler: () => void;\n private lastError: AudioError | null = null;\n\n // Per-bus previous-volume cache for mute/unmute restore (D-5).\n private readonly busVolumes = new Map<BusName, number>([\n ['sfx', 1],\n ['music', 1],\n ]);\n private readonly busMuted = new Map<BusName, boolean>([\n ['sfx', false],\n ['music', false],\n ]);\n\n constructor() {\n // Lazy: AudioContext is NOT created here (D-3 / AC-01).\n // The gesture resume handler is a bound arrow so we can pass it\n // to addEventListener/removeEventListener with the same identity.\n this.gestureResumeHandler = () => {\n void this.tryResume();\n };\n }\n\n /**\n * Returns the Web Audio AudioListener for spatialization (D-2).\n * Triggers lazy ensureContext() on first access.\n * Returns undefined if the context could not be created or is closed.\n */\n get listener(): AudioListener | undefined {\n return this.ensureContext().listener;\n }\n\n setListenerPose(pose: AudioListenerPose): void {\n const listener = this.ensureContext().listener;\n listener.positionX.value = pose.positionX;\n listener.positionY.value = pose.positionY;\n listener.positionZ.value = pose.positionZ;\n listener.forwardX.value = pose.forwardX;\n listener.forwardY.value = pose.forwardY;\n listener.forwardZ.value = pose.forwardZ;\n listener.upX.value = pose.upX;\n listener.upY.value = pose.upY;\n listener.upZ.value = pose.upZ;\n }\n\n // -----------------------------------------------------------------------\n // ensureContext -- lazy AudioContext + bus topology creation\n // -----------------------------------------------------------------------\n\n private ensureContext(): AudioContext {\n if (this.ctx) {\n this.registerGestureListener(this.ctx);\n return this.ctx;\n }\n\n const ctx = new AudioContext();\n\n // Build bus topology: masterGain <= sfxGain + musicGain\n const master = ctx.createGain();\n master.gain.value = 1;\n master.connect(ctx.destination);\n\n const sfx = ctx.createGain();\n sfx.gain.value = 1;\n sfx.connect(master);\n\n const music = ctx.createGain();\n music.gain.value = 1;\n music.connect(master);\n\n this.ctx = ctx;\n this.masterGain = master;\n this.sfxGain = sfx;\n this.musicGain = music;\n\n // Register the bounded gesture listener set if ctx is suspended (autoplay gate).\n this.registerGestureListener(ctx);\n\n return ctx;\n }\n\n // -----------------------------------------------------------------------\n // Gesture listener -- D-3 bounded resume retry on user gesture\n // -----------------------------------------------------------------------\n\n private registerGestureListener(ctx: AudioContext): void {\n if (ctx.state !== 'suspended') {\n return;\n }\n if (this.gestureListening) {\n return;\n }\n\n this.gestureListening = true;\n for (const event of GESTURE_EVENTS) {\n document.addEventListener(event, this.gestureResumeHandler, { once: true });\n }\n }\n\n private removeGestureListener(): void {\n if (!this.gestureListening) {\n return;\n }\n this.gestureListening = false;\n for (const event of GESTURE_EVENTS) {\n document.removeEventListener(event, this.gestureResumeHandler);\n }\n }\n\n private async tryResume(): Promise<void> {\n const ctx = this.ctx;\n if (!ctx || this.closed || ctx.state !== 'suspended') return;\n if (this.resumeInFlight !== undefined) return this.resumeInFlight;\n\n const attempt = (async () => {\n try {\n await ctx.resume();\n } catch {\n // Inspect the real context state below so refusal remains recoverable.\n } finally {\n if (!this.closed && this.ctx === ctx) {\n if (ctx.state === 'running') {\n this.lastError = null;\n this.removeGestureListener();\n } else if (ctx.state === 'suspended') {\n this.recordResumeFailure();\n this.rearmGestureListener(ctx);\n } else {\n this.removeGestureListener();\n }\n }\n this.resumeInFlight = undefined;\n }\n })();\n this.resumeInFlight = attempt;\n return attempt;\n }\n\n private recordResumeFailure(): void {\n this.lastError = new AudioError({\n code: 'context-suspended',\n expected: 'AudioContext.resume() to make the existing context running',\n hint: AUDIO_ERROR_HINTS['context-suspended'],\n detail: { code: 'context-suspended' },\n });\n }\n\n private recordDecodeFailure(sourceKey: string, cause: unknown): void {\n this.lastError = new AudioError({\n code: 'decode-failed',\n expected: `browser-decodable audio bytes for sourceKey ${sourceKey}`,\n hint: AUDIO_ERROR_HINTS['decode-failed'],\n detail: {\n code: 'decode-failed',\n reason: cause instanceof Error ? cause.message : String(cause),\n },\n });\n }\n\n private rearmGestureListener(ctx: AudioContext): void {\n this.removeGestureListener();\n this.registerGestureListener(ctx);\n }\n\n // -----------------------------------------------------------------------\n // AudioBackend implementation\n // -----------------------------------------------------------------------\n\n decode(bytes: Uint8Array): Promise<AudioBuffer> {\n return this.ensureContext().decodeAudioData(bytes.slice().buffer as ArrayBuffer);\n }\n\n play(entityId: number, clip: AudioBuffer | AudioClipAsset, opts: AudioPlayOptions): void {\n if ('kind' in clip) {\n void this.decode(clip.bytes).then(\n (buffer) => {\n if (this.lastError?.code === 'decode-failed') {\n this.lastError = null;\n }\n this.play(entityId, buffer, opts);\n },\n (cause) => this.recordDecodeFailure(clip.sourceKey, cause),\n );\n return;\n }\n const clipBuffer = clip;\n // If this entity is already playing, stop it first (replace).\n if (this.sources.has(entityId)) {\n this.stop(entityId);\n }\n\n const ctx = this.ensureContext();\n\n // Per-source GainNode for volume control\n const sourceGain = ctx.createGain();\n sourceGain.gain.value = opts.volume;\n\n // PannerNode for 3D spatialization (D-2 equalpower default)\n let panner: PannerNode | undefined;\n if (opts.spatialBlend > 0) {\n panner = ctx.createPanner();\n panner.panningModel = 'equalpower';\n }\n\n // Route to the appropriate bus (gain nodes guaranteed by ensureContext above)\n const busGain = this.busGainFor(opts.bus);\n if (!busGain) return;\n\n if (panner) {\n sourceGain.connect(panner);\n panner.connect(busGain);\n } else {\n sourceGain.connect(busGain);\n }\n\n // Create AudioBufferSourceNode for one-shot playback\n const node = ctx.createBufferSource();\n node.buffer = clipBuffer;\n node.loop = opts.loop;\n node.connect(sourceGain);\n node.start();\n\n // Bookkeeping\n this.sources.set(entityId, { node, sourceGain, panner, bus: opts.bus });\n\n // F24: attach onended for non-loop sources with identity guard (D-5).\n // Loop sources never naturally end — no onended needed.\n if (!opts.loop) {\n node.onended = () => {\n const current = this.sources.get(entityId);\n if (current?.node === node) {\n this.stop(entityId);\n }\n };\n }\n }\n\n stop(entityId: number): void {\n const source = this.sources.get(entityId);\n if (!source) return;\n\n try {\n source.node.stop();\n } catch {\n // Already stopped -- ignore InvalidStateError from doubly-stopped nodes.\n }\n source.node.disconnect();\n source.sourceGain.disconnect();\n source.panner?.disconnect();\n this.sources.delete(entityId);\n }\n\n setVolume(entityId: number, volume: number): void {\n const source = this.sources.get(entityId);\n if (!source) return;\n this.scheduleGainTransition(source.sourceGain, volume);\n }\n\n setBusVolume(busName: BusName, volume: number): void {\n const gain = this.busGainFor(busName);\n if (!gain) return;\n\n if (!this.scheduleGainTransition(gain, volume)) return;\n this.busVolumes.set(busName, volume);\n\n // If we were muted, un-mute (setting volume is an explicit un-mute signal).\n if (this.busMuted.get(busName)) {\n this.busMuted.set(busName, false);\n }\n }\n\n setBusMute(busName: BusName, muted: boolean): void {\n const gain = this.busGainFor(busName);\n if (!gain) return;\n\n const target = muted ? 0 : (this.busVolumes.get(busName) ?? 1);\n if (!this.scheduleGainTransition(gain, target)) return;\n this.busMuted.set(busName, muted);\n }\n\n getState(): AudioState {\n if (this.closed) {\n return { contextState: 'closed', activeSourceCount: 0, lastError: null };\n }\n const contextState: 'running' | 'suspended' | 'closed' =\n this.ctx?.state === 'closed'\n ? 'closed'\n : this.ctx?.state === 'running'\n ? 'running'\n : 'suspended';\n return {\n contextState,\n activeSourceCount: this.sources.size,\n lastError: this.lastError,\n };\n }\n\n getActiveSourceCount(): number {\n return this.sources.size;\n }\n\n destroy(): void {\n if (this.closed) return;\n this.closed = true;\n\n // Stop all active sources\n for (const entityId of this.sources.keys()) {\n this.stop(entityId);\n }\n\n // Disconnect bus topology\n if (this.sfxGain) {\n this.sfxGain.disconnect();\n this.sfxGain = undefined;\n }\n if (this.musicGain) {\n this.musicGain.disconnect();\n this.musicGain = undefined;\n }\n if (this.masterGain) {\n this.masterGain.disconnect();\n this.masterGain = undefined;\n }\n\n // Remove gesture listener\n this.removeGestureListener();\n\n // Close AudioContext (irreversible per R-4)\n if (this.ctx) {\n void this.ctx.close();\n this.ctx = undefined;\n }\n }\n\n // -----------------------------------------------------------------------\n // Private helpers\n // -----------------------------------------------------------------------\n\n private scheduleGainTransition(gain: GainNode, target: number): boolean {\n if (!this.ctx || !Number.isFinite(target) || target < 0) return false;\n\n const now = this.ctx.currentTime;\n const param = gain.gain;\n param.cancelScheduledValues(now);\n param.setValueAtTime(param.value, now);\n param.linearRampToValueAtTime(target, now + GAIN_TRANSITION_SECONDS);\n return true;\n }\n\n private busGainFor(busName: BusName): GainNode | undefined {\n switch (busName) {\n case 'sfx':\n return this.sfxGain;\n case 'music':\n return this.musicGain;\n }\n }\n}\n","import {\n type AudioBackend,\n type AudioIntent,\n type AudioPlayOptions,\n type AudioState,\n createAudioIntentBackend,\n} from '@forgeax/engine-audio';\nimport { AudioError } from '@forgeax/engine-types';\nimport { WebAudioEngine } from './web-audio-engine';\n\ninterface ActiveSource {\n readonly entityId: number;\n readonly sourceKey: string;\n readonly bytes?: Uint8Array;\n readonly options: AudioPlayOptions;\n}\n\nexport interface HostAudioConsumer {\n consume(intent: AudioIntent): void;\n state(): AudioState;\n dispose(): void;\n readonly engine: WebAudioEngine;\n}\n\nfunction decodeError(sourceKey: string, cause: unknown): AudioError {\n return new AudioError({\n code: 'decode-failed',\n expected: `browser-decodable audio bytes for sourceKey ${sourceKey}`,\n hint: 'verify the audio media type and source bytes',\n detail: {\n code: 'decode-failed',\n reason: cause instanceof Error ? cause.message : String(cause),\n },\n });\n}\n\nfunction sameBytes(left: Uint8Array, right: Uint8Array): boolean {\n if (left.byteLength !== right.byteLength) return false;\n for (let index = 0; index < left.byteLength; index += 1) {\n if (left[index] !== right[index]) return false;\n }\n return true;\n}\n\ninterface DecodeEntry {\n readonly promise: Promise<AudioBuffer>;\n}\n\nexport function createHostAudioConsumer(engine = new WebAudioEngine()): HostAudioConsumer {\n const sources = new Map<string, DecodeEntry>();\n const sourceBytes = new Map<string, Uint8Array>();\n const activeSources = new Map<number, ActiveSource>();\n const entityEpoch = new Map<number, number>();\n const bus = {\n sfx: { volume: 1, muted: false },\n music: { volume: 1, muted: false },\n };\n const cleanup: number[] = [];\n let lastError: AudioError | null = null;\n let disposed = false;\n const nextEpoch = (entityId: number): number => {\n const epoch = (entityEpoch.get(entityId) ?? 0) + 1;\n entityEpoch.set(entityId, epoch);\n return epoch;\n };\n const consumer: HostAudioConsumer = {\n engine,\n consume(intent): void {\n if (disposed && intent.kind !== 'destroy') return;\n if (intent.kind === 'play') {\n const epoch = nextEpoch(intent.entityId);\n if (intent.bytes !== undefined) {\n const incomingBytes = intent.bytes.slice();\n const publishedBytes = sourceBytes.get(intent.sourceKey);\n if (publishedBytes === undefined || !sameBytes(publishedBytes, incomingBytes)) {\n sourceBytes.set(intent.sourceKey, incomingBytes);\n sources.delete(intent.sourceKey);\n }\n }\n const bytes = sourceBytes.get(intent.sourceKey);\n activeSources.set(intent.entityId, {\n entityId: intent.entityId,\n sourceKey: intent.sourceKey,\n ...(bytes === undefined ? {} : { bytes: bytes.slice() }),\n options: intent.options,\n });\n let decoded = sources.get(intent.sourceKey);\n if (decoded === undefined && bytes !== undefined) {\n const entry: DecodeEntry = {\n promise: engine.decode(bytes),\n };\n decoded = entry;\n sources.set(intent.sourceKey, entry);\n void entry.promise.then(\n () => {\n if (sources.get(intent.sourceKey) === entry && lastError?.code === 'decode-failed') {\n lastError = null;\n }\n },\n (cause) => {\n if (sources.get(intent.sourceKey) !== entry) return;\n sources.delete(intent.sourceKey);\n lastError = decodeError(intent.sourceKey, cause);\n },\n );\n }\n if (decoded === undefined) {\n lastError = decodeError(intent.sourceKey, new Error('sourceKey was not published'));\n return;\n }\n const currentDecode = decoded;\n void currentDecode.promise\n .then((buffer) => {\n if (\n !disposed &&\n sources.get(intent.sourceKey) === currentDecode &&\n entityEpoch.get(intent.entityId) === epoch\n ) {\n engine.play(intent.entityId, buffer, intent.options);\n }\n })\n .catch(() => {});\n } else if (intent.kind === 'stop') {\n nextEpoch(intent.entityId);\n if (activeSources.delete(intent.entityId)) {\n cleanup.push(intent.entityId);\n engine.stop(intent.entityId);\n }\n } else if (intent.kind === 'set-volume') {\n engine.setVolume(intent.entityId, intent.volume);\n } else if (intent.kind === 'set-bus-volume') {\n bus[intent.bus].volume = intent.volume;\n bus[intent.bus].muted = false;\n engine.setBusVolume(intent.bus, intent.volume);\n } else if (intent.kind === 'set-bus-mute') {\n bus[intent.bus].muted = intent.muted;\n engine.setBusMute(intent.bus, intent.muted);\n } else if (intent.kind === 'set-listener-pose') {\n engine.setListenerPose(intent.pose);\n } else {\n consumer.dispose();\n }\n },\n state(): AudioState {\n return { ...engine.getState(), lastError };\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n activeSources.clear();\n entityEpoch.clear();\n sources.clear();\n sourceBytes.clear();\n cleanup.length = 0;\n engine.destroy();\n },\n };\n return consumer;\n}\n\nexport function createWebAudioBackend(): AudioBackend {\n const consumer = createHostAudioConsumer();\n const backend = createAudioIntentBackend({\n emit: (intent) => consumer.consume(intent),\n state: () => consumer.state(),\n });\n return backend;\n}\n","import type { Plugin } from '@forgeax/engine-plugin';\n\nimport { createWebAudioBackend } from './host-audio-consumer';\n\nexport function webAudioPlugin(): Plugin {\n return {\n name: 'web-audio',\n provide: 'audio',\n apply(ctx) {\n const backend = createWebAudioBackend();\n ctx.effect(() => () => backend.destroy(), 'audio/destroy-webaudio');\n ctx.provide('audio', backend);\n },\n };\n}\n"]}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAIrD,wBAAgB,cAAc,IAAI,MAAM,CAUvC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type AudioListenerPose, type AudioPlayOptions, type AudioState, type BusName } from '@forgeax/engine-audio';
|
|
2
|
+
import type { AudioClipAsset } from '@forgeax/engine-types';
|
|
3
|
+
export declare class WebAudioEngine {
|
|
4
|
+
private ctx;
|
|
5
|
+
private closed;
|
|
6
|
+
private masterGain;
|
|
7
|
+
private sfxGain;
|
|
8
|
+
private musicGain;
|
|
9
|
+
private readonly sources;
|
|
10
|
+
private gestureListening;
|
|
11
|
+
private resumeInFlight;
|
|
12
|
+
private readonly gestureResumeHandler;
|
|
13
|
+
private lastError;
|
|
14
|
+
private readonly busVolumes;
|
|
15
|
+
private readonly busMuted;
|
|
16
|
+
constructor();
|
|
17
|
+
/**
|
|
18
|
+
* Returns the Web Audio AudioListener for spatialization (D-2).
|
|
19
|
+
* Triggers lazy ensureContext() on first access.
|
|
20
|
+
* Returns undefined if the context could not be created or is closed.
|
|
21
|
+
*/
|
|
22
|
+
get listener(): AudioListener | undefined;
|
|
23
|
+
setListenerPose(pose: AudioListenerPose): void;
|
|
24
|
+
private ensureContext;
|
|
25
|
+
private registerGestureListener;
|
|
26
|
+
private removeGestureListener;
|
|
27
|
+
private tryResume;
|
|
28
|
+
private recordResumeFailure;
|
|
29
|
+
private recordDecodeFailure;
|
|
30
|
+
private rearmGestureListener;
|
|
31
|
+
decode(bytes: Uint8Array): Promise<AudioBuffer>;
|
|
32
|
+
play(entityId: number, clip: AudioBuffer | AudioClipAsset, opts: AudioPlayOptions): void;
|
|
33
|
+
stop(entityId: number): void;
|
|
34
|
+
setVolume(entityId: number, volume: number): void;
|
|
35
|
+
setBusVolume(busName: BusName, volume: number): void;
|
|
36
|
+
setBusMute(busName: BusName, muted: boolean): void;
|
|
37
|
+
getState(): AudioState;
|
|
38
|
+
getActiveSourceCount(): number;
|
|
39
|
+
destroy(): void;
|
|
40
|
+
private scheduleGainTransition;
|
|
41
|
+
private busGainFor;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=web-audio-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-audio-engine.d.ts","sourceRoot":"","sources":["../src/web-audio-engine.ts"],"names":[],"mappings":"AAuBA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,OAAO,EACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAY5D,qBAAa,cAAc;IACzB,OAAO,CAAC,GAAG,CAA2B;IACtC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,SAAS,CAAuB;IAExC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAE3D,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAa;IAClD,OAAO,CAAC,SAAS,CAA2B;IAG5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAGxB;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAGtB;;IAWH;;;;OAIG;IACH,IAAI,QAAQ,IAAI,aAAa,GAAG,SAAS,CAExC;IAED,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI;IAiB9C,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,qBAAqB;YAUf,SAAS;IA6BvB,OAAO,CAAC,mBAAmB;IAS3B,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,oBAAoB;IAS5B,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC;IAI/C,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,cAAc,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAiExF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAe5B,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAMjD,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAapD,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IASlD,QAAQ,IAAI,UAAU;IAiBtB,oBAAoB,IAAI,MAAM;IAI9B,OAAO,IAAI,IAAI;IAqCf,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,UAAU;CAQnB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeax/engine-audio-webaudio",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"description": "Host-owned ForgeaX Web Audio consumer: decode cache, entity epochs, AudioContext, buses, source nodes, and cleanup.",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./audio-importer": {
|
|
15
|
+
"types": "./dist/audio-importer.d.ts",
|
|
16
|
+
"import": "./dist/audio-importer.mjs"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.mjs",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@forgeax/engine-audio": "0.1.2",
|
|
30
|
+
"@forgeax/engine-ecs": "0.1.2",
|
|
31
|
+
"@forgeax/engine-math": "0.1.2",
|
|
32
|
+
"@forgeax/engine-plugin": "0.1.2",
|
|
33
|
+
"@forgeax/engine-types": "0.1.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@forgeax/engine-import": "0.1.2"
|
|
37
|
+
},
|
|
38
|
+
"forgeax": {
|
|
39
|
+
"metrics": {
|
|
40
|
+
"bundle-size": {
|
|
41
|
+
"enabled": false,
|
|
42
|
+
"reason": "Host Web Audio consumer is covered by package tests; no standalone player bundle is emitted"
|
|
43
|
+
},
|
|
44
|
+
"fps": {
|
|
45
|
+
"enabled": false,
|
|
46
|
+
"reason": "library package; fps is reported by apps that consume the audio tick system in their render loop"
|
|
47
|
+
},
|
|
48
|
+
"bench": {
|
|
49
|
+
"enabled": false,
|
|
50
|
+
"reason": "Web Audio API backend hot-path profiling deferred to a future feat after the tick system implementation matures"
|
|
51
|
+
},
|
|
52
|
+
"gate": {
|
|
53
|
+
"enabled": false,
|
|
54
|
+
"reason": "no package-level binary gate; test surface coverage runs as part of pnpm test:unit + tsc -b"
|
|
55
|
+
},
|
|
56
|
+
"spike-report": {
|
|
57
|
+
"enabled": false,
|
|
58
|
+
"reason": "not a spike package; this is a production MVP per plan-strategy D-1 (dual-package split)"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"typecheck": "tsc -b"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AudioState, BusName } from '@forgeax/engine-audio';
|
|
2
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('HostAudioState bus name owner', () => {
|
|
5
|
+
it('keeps the host state focused on lifecycle and error evidence', () => {
|
|
6
|
+
expectTypeOf<AudioState>().toMatchTypeOf<{
|
|
7
|
+
readonly contextState: 'running' | 'suspended' | 'closed';
|
|
8
|
+
readonly activeSourceCount: number;
|
|
9
|
+
readonly lastError: unknown;
|
|
10
|
+
}>();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('preserves the exact closed bus vocabulary', () => {
|
|
14
|
+
expectTypeOf<BusName>().toEqualTypeOf<'sfx' | 'music'>();
|
|
15
|
+
const acceptBusName = (busName: BusName): BusName => busName;
|
|
16
|
+
expect(acceptBusName('sfx')).toBe('sfx');
|
|
17
|
+
expect(acceptBusName('music')).toBe('music');
|
|
18
|
+
// @ts-expect-error BusName intentionally excludes additional buses.
|
|
19
|
+
acceptBusName('voice');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { audioLoader } from '../audio-loader';
|
|
3
|
+
|
|
4
|
+
describe('audio Pack v2 loader contract', () => {
|
|
5
|
+
it('returns the persisted media type with the complete source bytes', async () => {
|
|
6
|
+
const loadPack = audioLoader.loadPack;
|
|
7
|
+
if (loadPack === undefined) throw new Error('audio loader must expose Pack v2 loading');
|
|
8
|
+
const result = (await loadPack(
|
|
9
|
+
{
|
|
10
|
+
guid: 'audio-guid',
|
|
11
|
+
kind: 'audio',
|
|
12
|
+
payload: { kind: 'audio', mediaType: 'audio/ogg' },
|
|
13
|
+
refs: [],
|
|
14
|
+
artifacts: {
|
|
15
|
+
source: {
|
|
16
|
+
descriptor: { path: 'audio.ogg', mediaType: 'audio/ogg' },
|
|
17
|
+
bytes: Uint8Array.of(1, 2),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
} as never,
|
|
21
|
+
{} as never,
|
|
22
|
+
)) as { readonly ok: boolean; readonly value?: Record<string, unknown> };
|
|
23
|
+
|
|
24
|
+
expect(result.ok).toBe(true);
|
|
25
|
+
if (result.ok) {
|
|
26
|
+
expect(result.value).toMatchObject({ kind: 'audio', mediaType: 'audio/ogg' });
|
|
27
|
+
expect(result.value?.bytes).toEqual(Uint8Array.of(1, 2));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('requires asset-local source input instead of a catalog URL row', async () => {
|
|
32
|
+
const result = (await audioLoader.load(
|
|
33
|
+
{
|
|
34
|
+
guid: 'audio-guid',
|
|
35
|
+
kind: 'audio',
|
|
36
|
+
payload: { kind: 'audio' },
|
|
37
|
+
refs: [],
|
|
38
|
+
artifacts: {
|
|
39
|
+
source: {
|
|
40
|
+
descriptor: { path: 'audio.ogg', mediaType: 'audio/ogg' },
|
|
41
|
+
bytes: Uint8Array.of(1, 2),
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} as never,
|
|
45
|
+
{} as never,
|
|
46
|
+
{} as never,
|
|
47
|
+
)) as {
|
|
48
|
+
readonly ok: boolean;
|
|
49
|
+
readonly error?: unknown;
|
|
50
|
+
};
|
|
51
|
+
expect(result.ok).toBe(false);
|
|
52
|
+
if (!result.ok) expect(result.error).toBeTruthy();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('rejects an audio payload without its persisted media type', async () => {
|
|
56
|
+
const loadPack = audioLoader.loadPack;
|
|
57
|
+
if (loadPack === undefined) throw new Error('audio loader must expose Pack v2 loading');
|
|
58
|
+
const result = (await loadPack(
|
|
59
|
+
{
|
|
60
|
+
guid: 'audio-guid',
|
|
61
|
+
kind: 'audio',
|
|
62
|
+
payload: { kind: 'audio' },
|
|
63
|
+
refs: [],
|
|
64
|
+
artifacts: {
|
|
65
|
+
source: {
|
|
66
|
+
descriptor: { path: 'audio.ogg', mediaType: 'audio/ogg' },
|
|
67
|
+
bytes: Uint8Array.of(1, 2),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
} as never,
|
|
71
|
+
{} as never,
|
|
72
|
+
)) as { readonly ok: boolean };
|
|
73
|
+
expect(result.ok).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('rejects a payload whose media type disagrees with the source artifact', async () => {
|
|
77
|
+
const loadPack = audioLoader.loadPack;
|
|
78
|
+
if (loadPack === undefined) throw new Error('audio loader must expose Pack v2 loading');
|
|
79
|
+
const result = (await loadPack(
|
|
80
|
+
{
|
|
81
|
+
guid: 'audio-guid',
|
|
82
|
+
kind: 'audio',
|
|
83
|
+
payload: { kind: 'audio', mediaType: 'audio/ogg' },
|
|
84
|
+
refs: [],
|
|
85
|
+
artifacts: {
|
|
86
|
+
source: {
|
|
87
|
+
descriptor: { path: 'audio.wav', mediaType: 'audio/wav' },
|
|
88
|
+
bytes: Uint8Array.of(1, 2),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
} as never,
|
|
92
|
+
{} as never,
|
|
93
|
+
)) as { readonly ok: boolean; readonly error?: unknown };
|
|
94
|
+
|
|
95
|
+
expect(result.ok).toBe(false);
|
|
96
|
+
if (!result.ok) {
|
|
97
|
+
expect(result.error).toMatchObject({
|
|
98
|
+
code: 'asset-artifact-media-unsupported',
|
|
99
|
+
detail: { artifactKey: 'source', observed: 'audio/ogg' },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ImportContext } from '@forgeax/engine-types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { audioImporter } from '../audio-importer.js';
|
|
4
|
+
|
|
5
|
+
describe('audio asset-local artifacts', () => {
|
|
6
|
+
it('emits a source artifact body for the declared audio GUID', async () => {
|
|
7
|
+
const context: ImportContext = {
|
|
8
|
+
source: 'audio/intro.ogg',
|
|
9
|
+
readSource: async () => ({ ok: true as const, value: new Uint8Array([1, 2, 3]) }),
|
|
10
|
+
readSibling: async () => ({ ok: true as const, value: new Uint8Array() }),
|
|
11
|
+
decodeImage: async () => {
|
|
12
|
+
throw new Error('audio import must not use image decoding');
|
|
13
|
+
},
|
|
14
|
+
subAssets: [{ guid: 'audio-guid', sourceIndex: 0, kind: 'audio' }],
|
|
15
|
+
importSettings: {},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const result = await audioImporter.import(context);
|
|
19
|
+
expect(result).toMatchObject({ ok: true });
|
|
20
|
+
if (!('ok' in (result as object)) || !(result as { ok: boolean }).ok) return;
|
|
21
|
+
const asset = (result as { value: { assets: readonly Record<string, unknown>[] } }).value
|
|
22
|
+
.assets[0];
|
|
23
|
+
expect(asset?.payload).toMatchObject({ kind: 'audio', mediaType: 'audio/ogg' });
|
|
24
|
+
expect(asset?.payload).toHaveProperty('bytes');
|
|
25
|
+
const body = (asset?.artifacts as Record<string, Record<string, unknown>>).source;
|
|
26
|
+
expect(body).toBeDefined();
|
|
27
|
+
if (body === undefined) return;
|
|
28
|
+
expect(body.mediaType).toBe('audio/ogg');
|
|
29
|
+
expect(body.assetCodec).toEqual({ name: 'browser-audio' });
|
|
30
|
+
expect(body.bytes).toBeInstanceOf(Uint8Array);
|
|
31
|
+
expect(body).not.toHaveProperty('path');
|
|
32
|
+
expect(body).not.toHaveProperty('integrity');
|
|
33
|
+
});
|
|
34
|
+
});
|