/* One lifetime owner for the player's realtime AudioContext and media sources. */ (function (root) { 'use strict'; function create(options = {}) { const getConstructor = options.getConstructor || (() => root.AudioContext || root.webkitAudioContext); let context = null; const sources = new Map(); const routes = new Map(); const failed = new Map(); function getContext(contextOptions) { if (context) return context; const Constructor = getConstructor(); if (typeof Constructor !== 'function') throw new Error('Web Audio is unavailable'); context = contextOptions === undefined ? new Constructor() : new Constructor(contextOptions); return context; } function source(element) { if (!element) throw new TypeError('A media element is required'); if (sources.has(element)) return sources.get(element); if (failed.has(element)) throw failed.get(element); try { const created = getContext().createMediaElementSource(element); sources.set(element, created); return created; } catch (error) { // MediaElementSource cannot be undone. Remember failure so a later // route change can never attempt a second source for this element. failed.set(element, error); throw error; } } function connect(element, destination) { const mediaSource = source(element); if (!destination) throw new TypeError('An audio destination is required'); if (routes.get(element) === destination) return mediaSource; if (routes.has(element)) mediaSource.disconnect(); mediaSource.connect(destination); routes.set(element, destination); return mediaSource; } function routeAll(elements, destination) { for (const element of elements || []) connect(element, destination); } function snapshot() { return { contextCreated: !!context, sampleRate: context?.sampleRate || null, sourceCount: sources.size, failedCount: failed.size, routedCount: routes.size, }; } return { context: getContext, source, connect, routeAll, snapshot }; } const singleton = create(); const api = { create, context: (...args) => singleton.context(...args), source: (...args) => singleton.source(...args), connect: (...args) => singleton.connect(...args), routeAll: (...args) => singleton.routeAll(...args), snapshot: () => singleton.snapshot() }; if (typeof module !== 'undefined' && module.exports) module.exports = api; else root.AudioGraph = api; })(typeof window !== 'undefined' ? window : globalThis);