@ng-web-apis/audio
Version:
This is a library for declarative use of Web Audio API with Angular
1 lines • 132 kB
Source Map (JSON)
{"version":3,"file":"ng-web-apis-audio.mjs","sources":["../../../libs/audio/src/tokens/audio-context.ts","../../../libs/audio/src/tokens/constructor-support.ts","../../../libs/audio/src/constants/fallback.ts","../../../libs/audio/src/constants/polling-time.ts","../../../libs/audio/src/utils/process-audio-param.ts","../../../libs/audio/src/decorators/audio-param.ts","../../../libs/audio/src/utils/latency-hint-factory.ts","../../../libs/audio/src/directives/audio-context.ts","../../../libs/audio/src/directives/channel.ts","../../../libs/audio/src/tokens/audio-node.ts","../../../libs/audio/src/utils/connect.ts","../../../libs/audio/src/directives/destination.ts","../../../libs/audio/src/utils/fallback-audio-param.ts","../../../libs/audio/src/directives/listener.ts","../../../libs/audio/src/directives/offline-audio-context.ts","../../../libs/audio/src/directives/output.ts","../../../libs/audio/src/directives/stream-destination.ts","../../../libs/audio/src/utils/parse.ts","../../../libs/audio/src/nodes/analyser.ts","../../../libs/audio/src/nodes/biquad-filter.ts","../../../libs/audio/src/nodes/channel-merger.ts","../../../libs/audio/src/nodes/channel-splitter.ts","../../../libs/audio/src/services/audio-buffer.service.ts","../../../libs/audio/src/nodes/convolver.ts","../../../libs/audio/src/nodes/delay.ts","../../../libs/audio/src/nodes/dynamics-compressor.ts","../../../libs/audio/src/nodes/gain.ts","../../../libs/audio/src/tokens/feedback-coefficients.ts","../../../libs/audio/src/tokens/feedforward-coefficients.ts","../../../libs/audio/src/nodes/iir-filter.ts","../../../libs/audio/src/nodes/panner.ts","../../../libs/audio/src/nodes/script-processor.ts","../../../libs/audio/src/nodes/stereo-panner.ts","../../../libs/audio/src/nodes/wave-shaper.ts","../../../libs/audio/src/nodes/worklet.ts","../../../libs/audio/src/pipes/audio-param.pipe.ts","../../../libs/audio/src/pipes/periodic-wave.pipe.ts","../../../libs/audio/src/sources/buffer-source.ts","../../../libs/audio/src/sources/constant-source.ts","../../../libs/audio/src/sources/media-source.ts","../../../libs/audio/src/tokens/media-stream.ts","../../../libs/audio/src/sources/media-stream-source.ts","../../../libs/audio/src/sources/oscillator.ts","../../../libs/audio/src/module.ts","../../../libs/audio/src/tokens/audio-worklet-processors.ts","../../../libs/audio/src/tokens/audio-worklet-support.ts","../../../libs/audio/src/tokens/audio-worklet-processors-ready.ts","../../../libs/audio/src/tokens/support.ts","../../../libs/audio/src/ng-web-apis-audio.ts"],"sourcesContent":["import {InjectionToken} from '@angular/core';\n\nexport const WA_AUDIO_CONTEXT = new InjectionToken<BaseAudioContext>(\n '[WA_AUDIO_CONTEXT]',\n {\n providedIn: 'root',\n factory: () => new AudioContext(),\n },\n);\n\n/**\n * @deprecated: drop in v5.0, use {@link WA_AUDIO_CONTEXT}\n */\nexport const AUDIO_CONTEXT = WA_AUDIO_CONTEXT;\n","import {inject, InjectionToken} from '@angular/core';\n\nimport {WA_AUDIO_CONTEXT} from './audio-context';\n\n/**\n * This is mostly for internal use only\n */\nexport const WA_CONSTRUCTOR_SUPPORT = new InjectionToken<boolean>(\n '[WA_CONSTRUCTOR_SUPPORT]',\n {\n providedIn: 'root',\n factory: () => {\n try {\n return !!new GainNode(inject(WA_AUDIO_CONTEXT));\n } catch {\n return false;\n }\n },\n },\n);\n\n/**\n * @deprecated: drop in v5.0, use {@link WA_CONSTRUCTOR_SUPPORT}\n */\nexport const CONSTRUCTOR_SUPPORT = WA_CONSTRUCTOR_SUPPORT;\n","import type {Provider} from '@angular/core';\n\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\n\n/**\n * Just for unit tests\n */\nexport const providers: Provider[] = [\n {\n provide: CONSTRUCTOR_SUPPORT,\n useValue: false,\n },\n];\n","export const POLLING_TIME = 100;\n","import type {AudioParamAutomation} from '../types/audio-param-automation';\nimport type {AudioParamCurve} from '../types/audio-param-curve';\nimport type {AudioParamInput} from '../types/audio-param-input';\n\nexport function processAudioParam(\n param: AudioParam,\n value: AudioParamInput,\n currentTime = 0,\n): void {\n if (param.cancelAndHoldAtTime) {\n param.cancelAndHoldAtTime(currentTime);\n } else {\n param.cancelScheduledValues(currentTime);\n param.setValueAtTime(guard(param.value), currentTime);\n }\n\n if (typeof value === 'number') {\n param.setValueAtTime(guard(value), currentTime);\n\n return;\n }\n\n if (value instanceof Array) {\n processSchedule(param, value, currentTime);\n\n return;\n }\n\n if (!('mode' in value)) {\n param.setValueCurveAtTime(value.value, currentTime, value.duration);\n\n return;\n }\n\n param.setValueAtTime(guard(param.value), currentTime);\n processAutomation(param, value, currentTime);\n}\n\nfunction processSchedule(\n param: AudioParam,\n value: Array<AudioParamAutomation | AudioParamCurve>,\n currentTime: number,\n): void {\n value.forEach((automation) => {\n if ('mode' in automation) {\n processAutomation(param, automation, currentTime);\n } else {\n param.setValueCurveAtTime(automation.value, currentTime, automation.duration);\n }\n\n currentTime += automation.duration;\n });\n}\n\nfunction processAutomation(\n param: AudioParam,\n {value, mode = 'instant', duration}: AudioParamAutomation,\n currentTime: number,\n): void {\n switch (mode) {\n case 'exponential':\n if (value < 0 || value * param.value < 0) {\n param.linearRampToValueAtTime(guard(value), currentTime + duration);\n } else {\n param.exponentialRampToValueAtTime(guard(value), currentTime + duration);\n }\n\n param.setValueAtTime(guard(value), currentTime + duration);\n break;\n case 'instant':\n param.setValueAtTime(guard(value), currentTime);\n param.setValueAtTime(guard(value), currentTime + duration);\n break;\n case 'linear':\n param.linearRampToValueAtTime(guard(value), currentTime + duration);\n break;\n }\n}\n\nfunction guard(v: number): number {\n return v || 0.00000001;\n}\n","import type {AudioNodeWithParams} from '../types/audio-node-with-params';\nimport type {\n AudioParamDecorator,\n AudioParamWorkletDecorator,\n} from '../types/audio-param-decorator';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {processAudioParam} from '../utils/process-audio-param';\n\nexport function audioParam(): AudioParamWorkletDecorator;\nexport function audioParam<K extends string>(param: K): AudioParamDecorator<K>;\nexport function audioParam<K extends string>(\n param = '',\n): AudioParamDecorator<K> | AudioParamWorkletDecorator {\n const decorator: AudioParamDecorator<K> = (target, propertyKey) => {\n Object.defineProperty(target, propertyKey, {\n configurable: true,\n set(this: AudioNodeWithParams<K> | AudioWorkletNode, value: AudioParamInput) {\n value = typeof value === 'string' ? Number.parseFloat(value) : value;\n\n const audioParam =\n this instanceof AudioWorkletNode\n ? (this.parameters as Map<string, AudioParam | undefined>).get(\n propertyKey,\n )\n : this[param as K];\n\n if (audioParam instanceof AudioParam) {\n processAudioParam(audioParam, value, this.context.currentTime);\n } else {\n // Fallback for older browsers\n Object.defineProperty(target, propertyKey, {\n value,\n configurable: true,\n });\n }\n },\n });\n };\n\n return decorator;\n}\n","export function latencyHintFactory(\n latencyHint: AudioContextLatencyCategory | null,\n): AudioContextLatencyCategory | number | undefined {\n return latencyHint === null\n ? undefined\n : Number.parseFloat(latencyHint) || latencyHint;\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, forwardRef} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {latencyHintFactory} from '../utils/latency-hint-factory';\n\n@Directive({\n standalone: true,\n selector: '[waAudioContext]',\n providers: [\n {\n provide: AUDIO_CONTEXT,\n useExisting: forwardRef(() => WebAudioContext),\n },\n ],\n})\nexport class WebAudioContext extends AudioContext implements OnDestroy {\n constructor(\n @Attribute('latencyHint') latencyHint: AudioContextLatencyCategory | null,\n @Attribute('sampleRate') sampleRate: string | null,\n ) {\n super({\n latencyHint: latencyHintFactory(latencyHint),\n sampleRate: parseInt(sampleRate ?? '', 10) || undefined,\n });\n }\n\n public ngOnDestroy(): void {\n void this.close();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Directive, inject} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\n\n@Directive({\n standalone: true,\n selector: '[waChannel]',\n exportAs: 'AudioNode',\n})\nexport class WebAudioChannel extends GainNode implements OnDestroy {\n constructor() {\n const context = inject(AUDIO_CONTEXT);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n } else {\n const result = context.createGain();\n\n Object.setPrototypeOf(result, WebAudioChannel.prototype);\n\n return result as WebAudioChannel;\n }\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {ExistingProvider, Type} from '@angular/core';\nimport {InjectionToken} from '@angular/core';\n\nexport const WA_AUDIO_NODE = new InjectionToken<AudioNode | null>('[WA_AUDIO_NODE]', {\n factory: () => null,\n});\n\nexport function asAudioNode(useExisting: Type<AudioNode>): ExistingProvider {\n return {\n provide: WA_AUDIO_NODE,\n useExisting,\n };\n}\n\n/**\n * @deprecated: drop in v5.0, use {@link WA_AUDIO_NODE}\n */\nexport const AUDIO_NODE = WA_AUDIO_NODE;\n","export function connect(\n source?: AudioNode | null,\n destination?: AudioNode | AudioParam | null,\n): void {\n if (source && destination) {\n // @ts-ignore TS does not have a union override for connect method\n source.connect(destination);\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Directive, inject, Output} from '@angular/core';\nimport type {Observable} from 'rxjs';\nimport {\n debounceTime,\n distinctUntilChanged,\n filter,\n interval,\n map,\n skipWhile,\n tap,\n} from 'rxjs';\n\nimport {POLLING_TIME} from '../constants/polling-time';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waAudioDestinationNode]',\n exportAs: 'AudioNode',\n})\nexport class WebAudioDestination extends AnalyserNode implements OnDestroy {\n @Output()\n public quiet!: Observable<unknown>;\n\n constructor() {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n WebAudioDestination.init(this, node);\n } else {\n const result = context.createAnalyser() as WebAudioDestination;\n\n Object.setPrototypeOf(result, WebAudioDestination.prototype);\n WebAudioDestination.init(result, node);\n\n return result;\n }\n }\n\n protected static init(that: WebAudioDestination, node: AudioNode | null): void {\n connect(node, that);\n that.fftSize = 256;\n that.connect(that.context.destination);\n that.quiet = interval(POLLING_TIME).pipe(\n map(() => new Float32Array(that.fftSize)),\n tap((array) => that.getFloatTimeDomainData(array)),\n map((array) => that.isSilent(array)),\n distinctUntilChanged(),\n skipWhile((isSilent) => isSilent),\n debounceTime(5000),\n filter((isSilent) => isSilent),\n );\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n\n private isSilent(array: Float32Array): boolean {\n return Math.abs(array.reduce((acc, cur) => acc + cur, 0)) < 0.001;\n }\n}\n","import type {AudioParamInput} from '../types/audio-param-input';\n\nexport function fallbackAudioParam(value?: AudioParamInput): number {\n if (!value) {\n return 0;\n }\n\n if (typeof value === 'number') {\n return value;\n }\n\n if (value instanceof Array) {\n const last = value[value.length - 1]?.value;\n\n return typeof last === 'number' ? last : (last?.[last.length - 1] ?? 0);\n }\n\n if (value.value instanceof Array) {\n return value.value?.[value.value.length - 1] ?? 0;\n }\n\n return value.value;\n}\n","import type {OnChanges} from '@angular/core';\nimport {Directive, inject, Input} from '@angular/core';\n\nimport {audioParam} from '../decorators/audio-param';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {fallbackAudioParam} from '../utils/fallback-audio-param';\n\n@Directive({\n standalone: true,\n selector: '[waAudioContext],[waOfflineAudioContext][length][sampleRate]',\n})\nexport class WebAudioListener extends GainNode implements OnChanges {\n @Input('forwardX')\n @audioParam('forwardX')\n public forwardXParam?: AudioParamInput;\n\n @Input('forwardY')\n @audioParam('forwardY')\n public forwardYParam?: AudioParamInput;\n\n @Input('forwardZ')\n @audioParam('forwardZ')\n public forwardZParam?: AudioParamInput;\n\n @Input('positionX')\n @audioParam('positionX')\n public positionXParam?: AudioParamInput;\n\n @Input('positionY')\n @audioParam('positionY')\n public positionYParam?: AudioParamInput;\n\n @Input('positionZ')\n @audioParam('positionZ')\n public positionZParam?: AudioParamInput;\n\n @Input('upX')\n @audioParam('upX')\n public upXParam?: AudioParamInput;\n\n @Input('upY')\n @audioParam('upY')\n public upYParam?: AudioParamInput;\n\n @Input('upZ')\n @audioParam('upZ')\n public upZParam?: AudioParamInput;\n\n constructor() {\n const context = inject(AUDIO_CONTEXT, {self: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n } else {\n const result = context.createGain();\n\n Object.setPrototypeOf(result, WebAudioListener.prototype);\n\n return result as WebAudioListener;\n }\n }\n\n public get forwardX(): AudioParam {\n return this.context.listener.forwardX;\n }\n\n public get forwardY(): AudioParam {\n return this.context.listener.forwardY;\n }\n\n public get forwardZ(): AudioParam {\n return this.context.listener.forwardZ;\n }\n\n public get positionX(): AudioParam {\n return this.context.listener.positionX;\n }\n\n public get positionY(): AudioParam {\n return this.context.listener.positionY;\n }\n\n public get positionZ(): AudioParam {\n return this.context.listener.positionZ;\n }\n\n public get upX(): AudioParam {\n return this.context.listener.upX;\n }\n\n public get upY(): AudioParam {\n return this.context.listener.upY;\n }\n\n public get upZ(): AudioParam {\n return this.context.listener.upZ;\n }\n\n public ngOnChanges(): void {\n if (this.context.listener.positionX instanceof AudioParam) {\n return;\n }\n\n // Fallback for older browsers\n this.context.listener.setOrientation(\n fallbackAudioParam(this.forwardXParam),\n fallbackAudioParam(this.forwardYParam),\n fallbackAudioParam(this.forwardZParam),\n fallbackAudioParam(this.upXParam),\n fallbackAudioParam(this.upYParam),\n fallbackAudioParam(this.upZParam),\n );\n this.context.listener.setPosition(\n fallbackAudioParam(this.positionXParam),\n fallbackAudioParam(this.positionYParam),\n fallbackAudioParam(this.positionZParam),\n );\n }\n}\n","import {Attribute, Directive, EventEmitter, forwardRef, Output} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\n\n@Directive({\n standalone: true,\n selector: '[waOfflineAudioContext][length][sampleRate]',\n providers: [\n {\n provide: AUDIO_CONTEXT,\n useExisting: forwardRef(() => WebAudioOfflineContext),\n },\n ],\n})\nexport class WebAudioOfflineContext extends OfflineAudioContext {\n @Output()\n public readonly complete = new EventEmitter<AudioBuffer>();\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n @Attribute('length') length: string,\n @Attribute('sampleRate') sampleRate: string,\n @Attribute('numberOfChannels') numberOfChannels: string | null,\n @Attribute('autoplay') autoplay: string | null,\n ) {\n super(\n parseInt(numberOfChannels ?? '', 10) || 1,\n parseInt(length, 10),\n parseInt(sampleRate, 10),\n );\n\n if (autoplay !== null) {\n void this.startRendering();\n }\n }\n\n public override readonly oncomplete = ({\n renderedBuffer,\n }: OfflineAudioCompletionEvent): void => this.complete.emit(renderedBuffer);\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Directive, inject, Input} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waOutput]',\n})\nexport class WebAudioOutput extends GainNode implements OnDestroy {\n constructor() {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n connect(node, this);\n } else {\n const result = context.createGain();\n\n Object.setPrototypeOf(result, WebAudioOutput.prototype);\n connect(node, result);\n\n return result as WebAudioOutput;\n }\n }\n\n protected static init(that: WebAudioOutput, node: AudioNode | null): void {\n connect(node, that);\n }\n\n @Input()\n public set waOutput(destination: AudioNode | AudioParam | undefined) {\n this.disconnect();\n connect(this, destination);\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import {Directive, inject} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waMediaStreamAudioDestinationNode]',\n exportAs: 'AudioNode',\n})\nexport class WebAudioMediaStreamDestination extends MediaStreamAudioDestinationNode {\n constructor() {\n const context = inject<AudioContext>(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n connect(node, this);\n } else {\n const result = context.createMediaStreamDestination();\n\n Object.setPrototypeOf(result, WebAudioMediaStreamDestination.prototype);\n connect(node, result);\n\n return result as WebAudioMediaStreamDestination;\n }\n }\n}\n","import type {AudioParamInput} from '../types/audio-param-input';\n\nexport function parse(value: AudioParamInput | string | null, fallback: number): number {\n const parsed = parseFloat((value as string) || '');\n\n return Number.isNaN(parsed) ? fallback : parsed;\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, inject, Output} from '@angular/core';\nimport type {Observable} from 'rxjs';\nimport {animationFrameScheduler, interval, map, share} from 'rxjs';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {connect} from '../utils/connect';\nimport {parse} from '../utils/parse';\n\n@Directive({\n standalone: true,\n selector: '[waAnalyserNode]',\n inputs: [\n 'fftSize',\n 'minDecibels',\n 'maxDecibels',\n 'smoothingTimeConstant',\n 'channelCount',\n 'channelCountMode',\n 'channelInterpretation',\n ],\n providers: [asAudioNode(WebAudioAnalyser)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioAnalyser extends AnalyserNode implements OnDestroy {\n // '!' because it is actually set in constructor\n @Output()\n public frequencyByte$!: Observable<Uint8Array>;\n\n @Output()\n public frequencyFloat$!: Observable<Float32Array>;\n\n @Output()\n public timeByte$!: Observable<Uint8Array>;\n\n @Output()\n public timeFloat$!: Observable<Float32Array>;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n @Attribute('fftSize') fftSizeArg: string | null,\n @Attribute('maxDecibels') maxDecibelsArg: string | null,\n @Attribute('minDecibels') minDecibelsArg: string | null,\n @Attribute('smoothingTimeConstant') smoothingTimeConstantArg: string | null,\n ) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const fftSize = parse(fftSizeArg, 2048);\n const maxDecibels = parse(maxDecibelsArg, -30);\n const minDecibels = parse(minDecibelsArg, -100);\n const smoothingTimeConstant = parse(smoothingTimeConstantArg, 0.8);\n\n if (modern) {\n super(context, {fftSize, maxDecibels, minDecibels, smoothingTimeConstant});\n WebAudioAnalyser.init(this, node);\n } else {\n const result = context.createAnalyser() as WebAudioAnalyser;\n\n Object.setPrototypeOf(result, WebAudioAnalyser.prototype);\n WebAudioAnalyser.init(result, node);\n\n result.fftSize = fftSize;\n result.maxDecibels = maxDecibels;\n result.minDecibels = minDecibels;\n result.smoothingTimeConstant = smoothingTimeConstant;\n\n return result;\n }\n }\n\n protected static init(that: WebAudioAnalyser, node: AudioNode | null): void {\n connect(node, that);\n\n let freqByte = new Uint8Array(that.frequencyBinCount);\n let freqFloat = new Float32Array(that.frequencyBinCount);\n let timeByte = new Uint8Array(that.fftSize);\n let timeFloat = new Float32Array(that.fftSize);\n\n that.frequencyByte$ = interval(0, animationFrameScheduler).pipe(\n map(() => {\n if (freqByte.length !== that.frequencyBinCount) {\n freqByte = new Uint8Array(that.frequencyBinCount);\n }\n\n that.getByteFrequencyData(freqByte);\n\n return freqByte;\n }),\n share(),\n );\n\n that.frequencyFloat$ = interval(0, animationFrameScheduler).pipe(\n map(() => {\n if (freqFloat.length !== that.frequencyBinCount) {\n freqFloat = new Float32Array(that.frequencyBinCount);\n }\n\n that.getFloatFrequencyData(freqFloat);\n\n return freqFloat;\n }),\n share(),\n );\n\n that.timeByte$ = interval(0, animationFrameScheduler).pipe(\n map(() => {\n if (timeByte.length !== that.fftSize) {\n timeByte = new Uint8Array(that.frequencyBinCount);\n }\n\n that.getByteTimeDomainData(timeByte);\n\n return timeByte;\n }),\n share(),\n );\n\n that.timeFloat$ = interval(0, animationFrameScheduler).pipe(\n map(() => {\n if (timeFloat.length !== that.fftSize) {\n timeFloat = new Float32Array(that.frequencyBinCount);\n }\n\n that.getFloatTimeDomainData(timeFloat);\n\n return timeFloat;\n }),\n share(),\n );\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, inject, Input} from '@angular/core';\n\nimport {audioParam} from '../decorators/audio-param';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {connect} from '../utils/connect';\nimport {parse} from '../utils/parse';\n\n@Directive({\n standalone: true,\n selector: '[waBiquadFilterNode]',\n inputs: ['type', 'channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioBiquadFilter)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioBiquadFilter extends BiquadFilterNode implements OnDestroy {\n @Input('detune')\n @audioParam('detune')\n public detuneParam?: AudioParamInput;\n\n @Input('frequency')\n @audioParam('frequency')\n public frequencyParam?: AudioParamInput;\n\n @Input('gain')\n @audioParam('gain')\n public gainParam?: AudioParamInput;\n\n @Input('Q')\n @audioParam('Q')\n public qParam?: AudioParamInput;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n @Attribute('detune') detuneArg: string | null,\n @Attribute('frequency') frequencyArg: string | null,\n @Attribute('gain') gainArg: string | null,\n @Attribute('Q') QArg: string | null,\n ) {\n const context = inject(AUDIO_CONTEXT);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const detune = parse(detuneArg, 0);\n const frequency = parse(frequencyArg, 350);\n const gain = parse(gainArg, 0);\n const Q = parse(QArg, 1);\n\n if (modern) {\n super(context, {detune, frequency, gain, Q});\n connect(node, this);\n } else {\n const result = context.createBiquadFilter() as WebAudioBiquadFilter;\n\n Object.setPrototypeOf(result, WebAudioBiquadFilter.prototype);\n\n result.detune.value = detune;\n result.frequency.value = frequency;\n result.gain.value = gain;\n result.Q.value = Q;\n\n connect(node, result);\n\n return result;\n }\n }\n\n protected static init(that: WebAudioBiquadFilter, node: AudioNode | null): void {\n connect(node, that);\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnDestroy, QueryList} from '@angular/core';\nimport {Attribute, ContentChildren, Directive, inject} from '@angular/core';\n\nimport {WebAudioChannel} from '../directives/channel';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\n\n@Directive({\n standalone: true,\n selector: '[waChannelMergerNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioChannelMerger)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioChannelMerger extends ChannelMergerNode implements OnDestroy {\n constructor(@Attribute('numberOfInputs') inputs: string | null) {\n const context = inject(AUDIO_CONTEXT);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const numberOfInputs = parseInt(inputs ?? '', 10) || 6;\n\n if (modern) {\n super(context, {numberOfInputs});\n } else {\n const result = context.createChannelMerger(numberOfInputs);\n\n Object.setPrototypeOf(result, WebAudioChannelMerger.prototype);\n\n return result as WebAudioChannelMerger;\n }\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n\n @ContentChildren(WebAudioChannel, {descendants: false})\n protected set channels(channels: QueryList<AudioNode>) {\n channels.forEach((node, index) => {\n node.connect(this, 0, index);\n });\n }\n}\n","import type {OnDestroy, QueryList} from '@angular/core';\nimport {Attribute, ContentChildren, Directive, inject} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waChannelSplitterNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [\n {\n provide: AUDIO_NODE,\n useValue: null,\n },\n ],\n exportAs: 'AudioNode',\n})\nexport class WebAudioChannelSplitter extends ChannelSplitterNode implements OnDestroy {\n constructor(@Attribute('numberOfOutputs') outputs: string | null) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const numberOfOutputs = parseInt(outputs ?? '', 10) || 6;\n\n if (modern) {\n super(context, {numberOfOutputs});\n connect(node, this);\n } else {\n const result = context.createChannelSplitter(numberOfOutputs);\n\n Object.setPrototypeOf(result, WebAudioChannelSplitter.prototype);\n connect(node, result);\n\n return result as WebAudioChannelSplitter;\n }\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n\n @ContentChildren(AUDIO_NODE, {descendants: false})\n protected set channels(channels: QueryList<AudioNode | null>) {\n this.disconnect();\n channels\n .filter((node) => !!node)\n .forEach((node, index) => {\n this.connect(node!, index);\n });\n }\n}\n","import {inject, Injectable} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class AudioBufferService {\n private readonly context = inject(AUDIO_CONTEXT);\n private readonly cache = new Map<string, AudioBuffer>();\n\n public async fetch(url: string): Promise<AudioBuffer> {\n return new Promise<AudioBuffer>((resolve, reject) => {\n if (this.cache.has(url)) {\n resolve(this.cache.get(url) as AudioBuffer);\n\n return;\n }\n\n const request = new XMLHttpRequest();\n\n request.open('GET', url, true);\n request.responseType = 'arraybuffer';\n request.onerror = reject;\n request.onabort = reject;\n\n request.onload = () => {\n void this.context.decodeAudioData(\n request.response,\n (buffer) => {\n this.cache.set(url, buffer);\n resolve(buffer);\n },\n reject,\n );\n };\n\n request.send();\n });\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Directive, inject, Input} from '@angular/core';\nimport {of, Subject, switchMap} from 'rxjs';\n\nimport {AudioBufferService} from '../services/audio-buffer.service';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waConvolverNode]',\n inputs: ['normalize', 'channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioConvolver)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioConvolver extends ConvolverNode implements OnDestroy {\n protected buffer$!: Subject<AudioBuffer | string | null>;\n\n constructor() {\n const audioBufferService = inject(AudioBufferService);\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n WebAudioConvolver.init(this, node, audioBufferService);\n } else {\n const result = context.createConvolver() as WebAudioConvolver;\n\n Object.setPrototypeOf(result, WebAudioConvolver.prototype);\n WebAudioConvolver.init(result, node, audioBufferService);\n\n return result;\n }\n }\n\n protected static init(\n that: WebAudioConvolver,\n node: AudioNode | null,\n audioBufferService: AudioBufferService,\n ): void {\n connect(node, that);\n that.buffer$ = new Subject<AudioBuffer | string | null>();\n that.buffer$\n .pipe(\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n switchMap((source) =>\n typeof source === 'string'\n ? audioBufferService.fetch(source)\n : of(source),\n ),\n )\n .subscribe((buffer) => {\n that.buffer = buffer;\n });\n }\n\n @Input('buffer')\n public set bufferSetter(source: AudioBuffer | string | null) {\n this.buffer$.next(source);\n }\n\n public ngOnDestroy(): void {\n this.buffer$.complete();\n this.disconnect();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, inject, Input} from '@angular/core';\n\nimport {audioParam} from '../decorators/audio-param';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {connect} from '../utils/connect';\nimport {parse} from '../utils/parse';\n\n@Directive({\n standalone: true,\n selector: '[waDelayNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioDelay)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioDelay extends DelayNode implements OnDestroy {\n @Input('delayTime')\n @audioParam('delayTime')\n public delayTimeParam?: AudioParamInput;\n\n constructor(\n @Attribute('delayTime') delayTimeArg: string | null,\n @Attribute('maxDelayTime') maxDelayTimeArg: string | null,\n ) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const delayTime = parse(delayTimeArg, 0);\n const maxDelayTime = parse(maxDelayTimeArg, 1);\n\n if (modern) {\n super(context, {delayTime, maxDelayTime});\n connect(node, this);\n } else {\n const result = context.createDelay(maxDelayTime) as WebAudioDelay;\n\n Object.setPrototypeOf(result, WebAudioDelay.prototype);\n connect(node, result);\n result.delayTime.value = delayTime;\n\n return result;\n }\n }\n\n protected static init(that: WebAudioDelay, node: AudioNode | null): void {\n connect(node, that);\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, inject, Input} from '@angular/core';\n\nimport {audioParam} from '../decorators/audio-param';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {connect} from '../utils/connect';\nimport {parse} from '../utils/parse';\n\n@Directive({\n standalone: true,\n selector: '[waDynamicsCompressorNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioDynamicsCompressor)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioDynamicsCompressor\n extends DynamicsCompressorNode\n implements OnDestroy\n{\n @Input('attack')\n @audioParam('attack')\n public attackParam?: AudioParamInput;\n\n @Input('knee')\n @audioParam('knee')\n public kneeParam?: AudioParamInput;\n\n @Input('ratio')\n @audioParam('ratio')\n public ratioParam?: AudioParamInput;\n\n @Input('release')\n @audioParam('release')\n public releaseParam?: AudioParamInput;\n\n @Input('threshold')\n @audioParam('threshold')\n public thresholdParam?: AudioParamInput;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n @Attribute('attack') attackArg: string | null,\n @Attribute('knee') kneeArg: string | null,\n @Attribute('ratio') ratioArg: string | null,\n @Attribute('release') releaseArg: string | null,\n @Attribute('threshold') thresholdArg: string | null,\n ) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const attack = parse(attackArg, 0.003);\n const knee = parse(kneeArg, 30);\n const ratio = parse(ratioArg, 12);\n const release = parse(releaseArg, 0.25);\n const threshold = parse(thresholdArg, -24);\n\n if (modern) {\n super(context, {attack, knee, ratio, release, threshold});\n connect(node, this);\n } else {\n const result =\n context.createDynamicsCompressor() as WebAudioDynamicsCompressor;\n\n Object.setPrototypeOf(result, WebAudioDynamicsCompressor.prototype);\n connect(node, result);\n result.attack.value = attack;\n result.knee.value = knee;\n result.ratio.value = ratio;\n result.release.value = release;\n result.threshold.value = threshold;\n\n return result;\n }\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, inject, Input} from '@angular/core';\n\nimport {audioParam} from '../decorators/audio-param';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {connect} from '../utils/connect';\nimport {parse} from '../utils/parse';\n\n@Directive({\n standalone: true,\n selector: '[waGainNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioGain)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioGain extends GainNode implements OnDestroy {\n @Input('gain')\n @audioParam('gain')\n public gainParam?: AudioParamInput | string;\n\n constructor(@Attribute('gain') gainArg: AudioParamInput | string | null) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const gain = parse(gainArg, 1);\n\n if (modern) {\n super(context, {gain});\n connect(node, this);\n } else {\n const result = context.createGain() as WebAudioGain;\n\n Object.setPrototypeOf(result, WebAudioGain.prototype);\n connect(node, result);\n result.gain.value = gain;\n\n return result;\n }\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import {InjectionToken} from '@angular/core';\n\nexport const WA_FEEDBACK_COEFFICIENTS = new InjectionToken<number[]>(\n '[WA_FEEDBACK_COEFFICIENTS]',\n);\n\n/**\n * @deprecated: drop in v5.0, use {@link WA_FEEDBACK_COEFFICIENTS}\n */\nexport const FEEDBACK_COEFFICIENTS = WA_FEEDBACK_COEFFICIENTS;\n","import {InjectionToken} from '@angular/core';\n\nexport const WA_FEEDFORWARD_COEFFICIENTS = new InjectionToken<number[]>(\n '[WA_FEEDFORWARD_COEFFICIENTS]',\n);\n\n/**\n * @deprecated: drop in v5.0, use {@link WA_FEEDFORWARD_COEFFICIENTS}\n */\nexport const FEEDFORWARD_COEFFICIENTS = WA_FEEDFORWARD_COEFFICIENTS;\n","import type {OnDestroy} from '@angular/core';\nimport {Directive, inject} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport {FEEDBACK_COEFFICIENTS} from '../tokens/feedback-coefficients';\nimport {FEEDFORWARD_COEFFICIENTS} from '../tokens/feedforward-coefficients';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waIIRFilterNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioIIRFilter)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioIIRFilter extends IIRFilterNode implements OnDestroy {\n constructor() {\n const feedback = inject(FEEDBACK_COEFFICIENTS);\n const feedforward = inject(FEEDFORWARD_COEFFICIENTS);\n const context = inject(AUDIO_CONTEXT);\n const modern = inject(CONSTRUCTOR_SUPPORT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n\n if (modern) {\n super(context, {feedback, feedforward});\n connect(node, this);\n } else {\n const result = context.createIIRFilter(\n feedforward,\n feedback,\n ) as WebAudioIIRFilter;\n\n Object.setPrototypeOf(result, WebAudioIIRFilter.prototype);\n connect(node, result);\n\n return result;\n }\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnChanges, OnDestroy} from '@angular/core';\nimport {Directive, inject, Input} from '@angular/core';\n\nimport {audioParam} from '../decorators/audio-param';\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {CONSTRUCTOR_SUPPORT} from '../tokens/constructor-support';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {connect} from '../utils/connect';\nimport {fallbackAudioParam} from '../utils/fallback-audio-param';\n\n@Directive({\n standalone: true,\n selector: '[waPannerNode]',\n inputs: [\n 'coneInnerAngle',\n 'coneOuterAngle',\n 'coneOuterGain',\n 'distanceModel',\n 'maxDistance',\n 'panningModel',\n 'refDistance',\n 'rolloffFactor',\n 'channelCount',\n 'channelCountMode',\n 'channelInterpretation',\n ],\n providers: [asAudioNode(WebAudioPanner)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioPanner extends PannerNode implements OnDestroy, OnChanges {\n @Input()\n @audioParam('orientationX')\n public orientationXParam?: AudioParamInput;\n\n @Input()\n @audioParam('orientationY')\n public orientationYParam?: AudioParamInput;\n\n @Input()\n @audioParam('orientationZ')\n public orientationZParam?: AudioParamInput;\n\n @Input()\n @audioParam('positionX')\n public positionXParam?: AudioParamInput;\n\n @Input()\n @audioParam('positionY')\n public positionYParam?: AudioParamInput;\n\n @Input()\n @audioParam('positionZ')\n public positionZParam?: AudioParamInput;\n\n constructor() {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const modern = inject(CONSTRUCTOR_SUPPORT);\n\n if (modern) {\n super(context);\n connect(node, this);\n } else {\n const result = context.createPanner() as WebAudioPanner;\n\n Object.setPrototypeOf(result, WebAudioPanner.prototype);\n connect(node, result);\n\n return result;\n }\n }\n\n public ngOnChanges(): void {\n if (this.positionX instanceof AudioParam) {\n return;\n }\n\n // Fallback for older browsers\n this.setOrientation(\n fallbackAudioParam(this.orientationXParam),\n fallbackAudioParam(this.orientationYParam),\n fallbackAudioParam(this.orientationZParam),\n );\n this.setPosition(\n fallbackAudioParam(this.positionXParam),\n fallbackAudioParam(this.positionYParam),\n fallbackAudioParam(this.positionZParam),\n );\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, EventEmitter, inject, Output} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport {connect} from '../utils/connect';\n\n@Directive({\n standalone: true,\n selector: '[waScriptProcessorNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioScriptProcessor)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioScriptProcessor extends ScriptProcessorNode implements OnDestroy {\n @Output()\n public audioprocess?: EventEmitter<AudioProcessingEvent>;\n\n constructor(\n @Attribute('bufferSize') bufferSize: string | null,\n @Attribute('numberOfInputChannels') numberOfInputChannels: string | null,\n @Attribute('numberOfOutputChannels') numberOfOutputChannels: string | null,\n ) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n\n try {\n const result = context.createScriptProcessor(\n parseInt(bufferSize ?? '', 10) || 0,\n parseInt(numberOfInputChannels ?? '', 10) || 2,\n parseInt(numberOfOutputChannels ?? '', 10) || 2,\n ) as WebAudioScriptProcessor;\n\n Object.setPrototypeOf(result, WebAudioScriptProcessor.prototype);\n connect(node, result);\n\n const audioprocess = new EventEmitter<AudioProcessingEvent>();\n\n result.audioprocess = audioprocess;\n result.onaudioprocess = (e) => audioprocess.emit(e);\n\n return result;\n } catch {}\n\n super();\n }\n\n public ngOnDestroy(): void {\n this.disconnect();\n }\n}\n","import type {OnDestroy} from '@angular/core';\nimport {Attribute, Directive, inject, Input} from '@angular/core';\n\nimport {AUDIO_CONTEXT} from '../tokens/audio-context';\nimport {asAudioNode, AUDIO_NODE} from '../tokens/audio-node';\nimport type {AudioParamInput} from '../types/audio-param-input';\nimport {connect} from '../utils/connect';\nimport {fallbackAudioParam} from '../utils/fallback-audio-param';\nimport {parse} from '../utils/parse';\nimport {processAudioParam} from '../utils/process-audio-param';\n\n@Directive({\n standalone: true,\n selector: '[waStereoPannerNode]',\n inputs: ['channelCount', 'channelCountMode', 'channelInterpretation'],\n providers: [asAudioNode(WebAudioStereoPanner)],\n exportAs: 'AudioNode',\n})\nexport class WebAudioStereoPanner extends StereoPannerNode implements OnDestroy {\n constructor(@Attribute('pan') panArg: string | null) {\n const context = inject(AUDIO_CONTEXT);\n const node = inject(AUDIO_NODE, {skipSelf: true});\n const pan = parse(panArg, 0);\n\n try {\n // eslint-disable-next-line\n new StereoPannerNode(context);\n } catch {\n const result = context.createPanner() as unknown as WebAudioStereo