Class Cacophony

Constructors

  • Constructs a new Cacophony instance.

    If the supplied (or auto-constructed) audio context is in suspended state and runtimeOptions.autoUnlock is true (default), one-time touchend / click / keydown listeners are installed on document.body so the first user gesture transparently unlocks audio — matching Howler's autoUnlock behavior. See RuntimeOptions.autoUnlock for the opt-out and the iOS primer rationale.

    Parameters

    • Optional context: BaseContext

      Audio context to use. If omitted, a fresh AudioContext is constructed (which on mobile will be suspended until a user gesture, triggering the auto-unlock path described above).

    • Optional cache: ICache

      Optional cache implementation. Defaults to AudioCache.

    • runtimeOptions: RuntimeOptions = {}

      Optional runtime configuration including the autoUnlock opt-out and a createAudioWorkletNode factory override.

    Returns Cacophony

Properties

_busRegistry: Map<string, Bus> = ...

Named-bus registry. Populated by createBus when a name is supplied; entries are removed by the bus's onDestroy hook.

autoplayUnlockCleanup: (() => void) = ...

Type declaration

    • (): void
    • Returns void

cache: ICache
context: BaseContext
createAudioWorkletNode: ((context, name, options?) => any)

Type declaration

    • (context, name, options?): any
    • Parameters

      • context: BaseContext
      • name: string
      • Optional options: AudioWorkletNodeOptions

      Returns any

eventEmitter: TypedEventEmitter<CacophonyEvents> = ...
finalizationRegistry: FinalizationRegistry<SoundCleanupHoldings>
foaHrirCache: WeakMap<BaseContext, Promise<AudioBuffer>> = ...

Per-context cache of the decoded order-1 SH-HRIR AudioBuffer used by FoaDecoder. Keyed on the context (like loadedAudioWorklets) because decodeAudioData produces a buffer bound to ONE context's sample rate; a buffer decoded on context A must not be reused on context B. Stores the in-flight Promise so concurrent loadFoaHrir calls share a single fetch/decode.

globalGainNode: GainNode
impulseResponseCache: WeakMap<BaseContext, Map<string, Promise<AudioBuffer>>> = ...

Per-context, per-URL decoded impulse-response cache. Stores in-flight promises so concurrent effect builds for the same context/URL share the same fetch/decode. Rejected promises are evicted so the caller can retry.

listener: AudioListener
loadedAudioWorklets: WeakMap<BaseContext, Set<string>> = ...

Per-context cache of "module-name has been loaded on this BaseContext". Keyed on the context itself because AudioWorklet.addModule() registers the module against ONE context — a module loaded on context A is NOT loaded on context B. A previous host-scoped Set<string> short-circuited loadAudioWorkletModule() on name alone, so cross-context ReverbEffect.build would skip addModule on the new context and the second construct would throw with no module registered.

master: Bus

The master bus — master.input IS globalGainNode (literally the same GainNode), so every existing .connect(globalGainNode) call in the codebase remains correct. cacophony.volume and cacophony.mute still read and write master.input.gain.value through that alias.

prevVolume: number = 1
suspendState: "suspended" | "unknown" | "running" = "unknown"

Accessors

  • get isOffline(): boolean
  • Returns true if this instance is backed by an offline audio context (i.e., the context has a startRendering method).

    Returns boolean

  • get locked(): boolean
  • Returns true while the audio context is suspended — i.e. the library cannot produce sound until the context is resumed (typically by a user gesture). After the auto-unlock fires (or resume() is called manually), this becomes false.

    Mirrors Howler's Howler.ctx.state === 'suspended' check via the Howler global's _audioUnlocked. Use this to gate UI that hints to the user that an interaction is required to start audio.

    Returns boolean

Methods

  • The single worklet-effect construction seam (WorkletEffectHost). Idempotently registers worklet's module on context — or this host's own context when omitted, honoring the cross-context contract that effects' build(context) promises — then constructs the AudioWorkletNode with parameterData. Every worklet-backed CacophonyEffect routes through here, as does the phase-vocoder pitch-shift path (Playback.setPitchShift).

    Parameters

    • worklet: WorkletModule
    • parameterData: Record<string, number>
    • Optional context: BaseContext

    Returns Promise<AudioWorkletNode>

  • Creates a TremoloEffect preset as an AUTO-PAN — a tremolo with stereoPhase: 180, so the left and right channel gains modulate in anti-phase (the sound swings hard between the speakers). The 180-deg per-channel LFO offset is Dattorro's stereo-modulation convention (p.776). A convenience wrapper over createTremolo; caller options override the preset.

    Parameters

    Returns TremoloEffect

  • Creates a new Bus. If name is provided, the bus is registered so getBus can look it up by name. Anonymous buses (no name) are not registered and cannot be retrieved by name.

    Parameters

    • Optional name: string

    Returns Bus

    Throws

    if a named bus with the same name already exists, or if the reserved name 'master' is used (the master bus is auto-created).

  • Creates a ModulatedDelayEffect preset as a (white) CHORUS — Dattorro Table 6 white-chorus knobs blend: 0.7071, feedforward: 1, feedback: 0.7071 (blend = feedback, feedforward = 1, the negative-feedback path that minimizes the spectral aberration of a plain dry+wet chorus). A 9 ms tap modulated 4 ms at 0.5 Hz sits in the chorus range (Table 7).

    NOTE: Dattorro's white chorus (p.776) combines this negative-feedback path with ALLPASS fractional-delay interpolation. This implementation uses the white-chorus knob settings but substitutes cubic-Lagrange interpolation (Laakso 1996) — transient-safe under modulation (Laakso p.52), trading some high-frequency trough depth versus a true allpass-interpolated white chorus.

    Parameters

    Returns ModulatedDelayEffect

  • Creates a dynamics CacophonyEffect configured as a COMPRESSOR (ratio > 1 reduces the level of signals above threshold). Implements the feed-forward design of Giannoulis, Massberg & Reiss 2012. Add the returned effect to a Bus via bus.addFilter(effect). The same machinery (gain computer + log-domain smooth-branching detector) backs the limiter and gate presets below.

    Parameters

    Returns DynamicsEffect

  • Creates a ModulatedDelayEffect preset as a DELAY / ECHO — Dattorro's unified modulated-delay circuit (JAES 1997, Fig. 36) with the modulation OFF (depth: 0, rate: 0). Echo knobs (Table 6): full dry blend: 1 plus a unity wet tap feedforward: 1; feedback defaults to 0 (a single tap) but is caller-configurable for a regenerating echo (|feedback| < 1, Table 6). The 250 ms tap sits in the echo range (Table 7). All knobs are exposed.

    Parameters

    Returns ModulatedDelayEffect

  • Creates a Feedback Delay Network (FDN) CacophonyEffect — an algorithmic reverb with a lossless degree-0 paraunitary Hadamard feedback matrix (Schlecht & Habets 2019), per-delay-line absorption filters setting the reverberation time T60 (Jot & Chaigne 1991), and multiplication-free velvet-noise input diffusion (Fagerström et al. 2020). The effect's build lazily registers the worklet module (no-op if already loaded) and constructs the AudioWorkletNode with the supplied FdnReverbOptions as parameterData. Add the returned effect to a Bus via bus.addFilter(effect).

    Parameters

    Returns FdnReverbEffect

  • Creates a FoaDecoder — a standalone first-order ambisonic (FOA) -> binaural FORMAT CONVERTER built from native Web Audio nodes (no worklet). It decodes a 4-channel ACN/SN3D [W, Y, Z, X] signal to headphone stereo via the per-SH-channel HRIR decode of Ahrens 2022 (eq.31), using Omnitone's WY/ZX 2-stereo-ConvolverNode packing and the bundled order-1 SH-HRIR.

    It is 4-channel-in / 2-channel-out; this method returns the explicit endpoint object for custom graph wiring: feed FOA into decoder.input (4-ch) and route decoder.output (2-ch stereo) downstream:

      const decoder = await cacophony.createFoaDecoder();
    foaSource.connect(decoder.input);
    decoder.output.connect(bus.input); // or context.destination

    Build is async (the bundled HRIR is fetched + decoded). Pair it with encodeMonoToFoaSN3D (clean, physically correct) or with createStereoToBFormatNode (the perceptual, approximate stereo-upmix path).

    Parameters

    Returns Promise<FoaDecoder>

  • Creates a dynamics CacophonyEffect preset as a downward EXPANDER / GATE — ratio < 1 so signals BELOW the threshold are pushed further down (Giannoulis 2012 p.403). The default ratio of 0.1 gives gate-like downward expansion; pass a ratio closer to 1 for gentler expansion.

    Parameters

    Returns DynamicsEffect

  • Creates a Group containing Sound instances loaded from multiple URLs.

    Parameters

    • urls: string[]

      Array of URL strings to load as sounds

    • soundType: SoundType = "buffer"

      Type of sound (Buffer, HTML, Streaming)

    • panType: PanType = "HRTF"

      Type of panning (HRTF or stereo)

    • Optional signal: AbortSignal

      Optional AbortSignal to cancel the operation

    Returns Promise<Group>

    Promise that resolves to a Group containing all loaded sounds

  • Creates a native ConvolverNode impulse-response effect. Pass an AudioBuffer for an already-decoded IR or a URL to fetch/decode through the per-context IR cache. The default is wet-only (dry: 0, wet: 1) and returns a single ConvolverNode when added to a bus; setting dry or non-unity wet builds an owned dry/wet endpoint graph exposing dry and wet automation params.

    Parameters

    Returns ImpulseResponseEffect

  • Creates a dynamics CacophonyEffect preset as a LIMITER — a compressor with an effectively infinite ratio so output is clamped at the threshold (Giannoulis 2012 eqs 18-19). The ratio is fixed to the worklet's limiter sentinel; caller-supplied ratio is ignored. Other params (threshold, knee, attack, release, makeup) remain configurable.

    Parameters

    Returns DynamicsEffect

  • Creates an ITU-R BS.1770-5 LoudnessMeter that TAPS the output of a target node without altering the audible path. The meter reports momentary (400 ms), short-term (3 s), and gated integrated loudness in LKFS/LUFS, plus true-peak level in dBTP (Annex 2 4× oversampling).

    The tap is a BRANCH: the target's output is connected to the metering worklet in ADDITION to its existing forward edge, and the worklet's output is routed through an owned zero-gain sink to keep the branch silent and live. By default the target is the master bus output (master.output), measuring everything that reaches the destination — the integrated-loudness target. Pass a Bus to meter one bus's output, or any AudioNode to meter that node.

    Lazily registers the loudness-meter worklet module (no-op if already loaded) on the target's context, then constructs the node and branch-taps.

    Parameters

    • target: AudioNode | Bus = ...

      Node or bus whose output to meter. Defaults to the master bus.

    Returns Promise<LoudnessMeter>

    A LoudnessMeter handle exposing the live readings.

  • Parameters

    • url: string
    • soundType: "html" | "streaming"
    • panType: PanType
    • Optional signal: AbortSignal

    Returns Promise<Sound>

  • Creates a PannerNode with the specified options.

    Parameters

    • options: Partial<PannerOptions>

      An object containing the options to use when creating the PannerNode.

    Returns PannerNode

    A new PannerNode instance with the specified options.

    Example

    const panner = audio.createPanner({
    positionX: 0,
    positionY: 0,
    positionZ: 0,
    orientationX: 0,
    orientationY: 0,
    orientationZ: 0,
    });
  • Creates a PhaserEffect — a classic MXR/Univibe-style phase shifter: a cascade of first-order allpass sections at a common LFO-swept break frequency summed additively with the dry signal to sweep notches through the spectrum (Smith STAN-M-21; PASP §8.9). Two allpass sections per notch (default 4 sections = 2 notches). Add the returned effect to a Bus via bus.addFilter(effect).

    Parameters

    Returns PhaserEffect

  • Creates a Sound instance from an AudioBuffer or URL.

    Parameters

    • buffer: AudioBuffer
    • Optional soundType: SoundType

      Type of sound (Buffer, HTML, Streaming)

    • Optional panType: PanType

      Type of panning (HRTF or stereo)

    • Optional signal: AbortSignal

      Optional AbortSignal to cancel the operation

    Returns Promise<Sound>

    Promise that resolves to a Sound instance

  • Parameters

    • url: string
    • Optional soundType: SoundType
    • Optional panType: PanType
    • Optional signal: AbortSignal

    Returns Promise<Sound>

  • Creates a Sound from the first playable URL in a Howler-style fallback array. The browser's HTMLAudioElement.canPlayType is queried per extension; the first source it reports as supported ('probably' or 'maybe') is fetched and decoded. If decoding rejects for a candidate, the next playable candidate is tried — a 'maybe' can still fail at decode time.

    MIME types are inferred from file extensions: .webmaudio/webm, .mp3audio/mpeg, .oggaudio/ogg, .wavaudio/wav, .flacaudio/flac, .m4aaudio/mp4, .aacaudio/aac, .opusaudio/ogg; codecs=opus.

    Cache and loading events fire only for the URL actually fetched.

    v1 limitation: only the default 'buffer' sound type participates in fallback. Passing an array with any non-'buffer' soundType (e.g. 'html', 'streaming', 'oscillator') rejects with a clear "not yet supported" error.

    Parameters

    • urls: string[]

      Non-empty array of candidate URLs in priority order.

    • Optional soundType: SoundType
    • Optional panType: PanType
    • Optional signal: AbortSignal

    Returns Promise<Sound>

    Throws

    If the array is empty, if soundType is not 'buffer', or if no candidate is playable (codec unsupported or every decode failed). The error message names the URLs that were tried.

  • Creates a streaming Sound instance from a URL.

    Parameters

    • url: string

      URL string to stream audio from

    • Optional signal: AbortSignal

      Optional AbortSignal to cancel the operation

    Returns Promise<Sound>

    Promise that resolves to a Sound instance for streaming

  • Creates a TremoloEffect — LFO-driven amplitude modulation (a VCA swung by a low-frequency oscillator). The gain swings between (1 - depth) and 1, never inverting (a true tremolo, not ring modulation). Anchored to standard AM theory, Dattorro 1997 (p.776) for the quadrature stereo LFO, and Mitcheltree et al. (DAFx23) for the LFO-driven-effect framing. shape selects the LFO waveform (0 = sine, 1 = triangle, 2 = square); stereoPhase offsets the per-channel LFO. Add the returned effect to a Bus via bus.addFilter(effect).

    Parameters

    Returns TremoloEffect

  • Creates a WaveshaperEffect — an antialiased distortion/waveshaper implementing first-order Antiderivative Antialiasing (Parker, Zavalishin & Le Bivic 2016, DAFx-16): y[n] = (F0(x_n) - F0(x_{n-1}))/(x_n - x_{n-1}) (eq.9), with an f(midpoint) fallback at the 0/0 singularity (eq.10). Ships two nonlinearities — shape: 0 hard clip (polynomial F0, eq.25), shape: 1 tanh soft clip (F0 = log cosh, eq.20). Carries an inherent 0.5-sample group delay (eq.17). Add the returned effect to a Bus via bus.addFilter(effect).

    Parameters

    Returns WaveshaperEffect

  • Retrieves a bus by name from the named-bus registry. Returns undefined if no bus with that name exists. The special name 'master' returns the auto-created master bus.

    Parameters

    • name: string

    Returns undefined | Bus

  • Returns Promise<MicrophoneStream>

  • Returns the names of every registered named bus. Anonymous buses are not included. The master bus's name ('master') is included.

    Returns string[]

  • Parameters

    • name: string
    • url: string
    • Optional signal: AbortSignal
    • Optional context: BaseContext

    Returns Promise<void>

  • Fetches and decodes the bundled order-1 SH-HRIR (sh_hrir_order_1.wav, from Omnitone, Apache-2.0 — see src/assets/NOTICE) into an AudioBuffer, memoized per context. The buffer is the 4-channel (ACN rows W,Y,Z,X) SH-domain HRIR consumed by FoaDecoder to drive its WY/ZX stereo ConvolverNodes (Ahrens 2022 eq.31 decode).

    The WAV is 48 kHz; decodeAudioData resamples it to the context's sample rate automatically when they differ (standard Web Audio behavior, which Omnitone relies on).

    Parameters

    • Optional context: BaseContext

      Optional BaseContext to decode on. Defaults to this host's own context. Supplied so a FoaDecoder added to a bus on a different context decodes the HRIR on the right context.

    Returns Promise<AudioBuffer>

  • Fetches and decodes an impulse response URL on the requested audio context, memoized per context and URL. Buffers decoded by one context are not reused on another context, matching Web Audio's context-bound decode behavior.

    Parameters

    • url: string

      Impulse-response audio URL.

    • Optional context: BaseContext

      Optional decode context. Defaults to this Cacophony instance.

    • Optional signal: AbortSignal

      Optional abort signal for the fetch. Decode itself is not abortable in Web Audio, but an already-aborted signal is honored before decode starts.

    Returns Promise<AudioBuffer>

  • Parameters

    • Optional signal: AbortSignal

    Returns Promise<void>

  • Eagerly registers every bundled AudioWorklet module on this context. This is OPTIONAL — effects load their own worklet lazily in build, and the pitch-shift path loads the phase-vocoder on first use. Call this up front to pay the registration cost ahead of time. Idempotent per context (each module short-circuits via the per-context loadedAudioWorklets set).

    Parameters

    • Optional signal: AbortSignal

    Returns Promise<void>

  • Record name as loaded on ctx, lazily allocating the per-context Set.

    Parameters

    Returns void

  • Register event listener.

    Type Parameters

    Parameters

    • event: K
    • listener: ((data) => void)

    Returns (() => void)

    Cleanup function

      • (): void
      • Returns void

  • Suspends the audio context.

    Resolves after the underlying AudioContext transition completes, then emits the suspend event. If the context is already suspended (per this instance's view), resolves immediately as a no-op. If the underlying suspend() call rejects, the rejection is propagated and no event fires.

    Returns Promise<void>

  • Resumes the audio context. This method is required to resume the audio context on mobile devices. On desktop, the audio context will automatically resume when a sound is played.

    Resolves after the underlying AudioContext transition completes, then emits the resume event. If the underlying resume() call rejects, the rejection is propagated and no event fires.

    Returns Promise<void>

  • Wraps a raw AudioNode in a CacophonyEffect so it can be added to a Bus filter chain. By default Bus.addFilter rejects raw AudioNodes — wrapping via shareEffect is the explicit opt-in that signals "yes, I understand this single node will be shared across every bus I add it to."

    Parameters

    Returns CacophonyEffect

  • Renders the offline audio graph to a buffer. Only available when the context has a startRendering method.

    Returns Promise<AudioBuffer>

    Promise that resolves to the rendered AudioBuffer

    Throws

    Error if the context does not support offline rendering

  • OFFLINE independent time-stretch: change an AudioBuffer's tempo WITHOUT changing its pitch, returning a NEW AudioBuffer of length ≈ round(buffer.length · factor) at the same sample rate.

    Algorithm: Phase Gradient Heap Integration (PGHI) per Zdeněk Průša & Nicki Holighaus, "Phase Vocoder Done Right" (EUSIPCO 2017 / arXiv:2202.07382). The signal is STFT'd, the synthesis phase is reconstructed by integrating the analysis-phase time/frequency gradients along the magnitude ridges (max-heap), then overlap-added at the stretched synthesis hop. No peak picking and no transient detection. Each channel is processed independently.

    This is an OFFLINE buffer transform, NOT a real-time worklet: the project's OLA worklet base is unity-rate (analysis hop == synthesis hop, fixed to the 128-sample render quantum) and cannot change the time base in real time. A genuine independent stretch needs analysis hop ≠ synthesis hop, which only the whole-buffer offline path provides.

    Parameters

    • buffer: AudioBuffer

      The source AudioBuffer to time-stretch.

    • factor: number

      Stretch factor (> 0). > 1 lengthens (slower tempo), < 1 shortens (faster tempo); pitch is preserved either way.

    • Optional options: TimeStretchOptions

      Optional PGHI parameters (fftSize, analysisHop, tol, seed).

    Returns AudioBuffer

    A new, time-stretched AudioBuffer.

    Throws

    If the context cannot create buffers, or factor <= 0.

  • Internal

    Parameters

    • unregisterToken: object

    Returns void

  • Creates a Cacophony instance backed by an OfflineAudioContext. Use this for rendering, bouncing, precomputing processed output, or non-realtime scenarios.

    Parameters

    • options: OfflineOptions

      Offline context configuration (channels, length, sampleRate)

    • Optional cache: ICache

      Optional cache implementation

    Returns Cacophony

    A Cacophony instance backed by OfflineAudioContext

  • Returns true if the error came from AudioContext.decodeAudioData.

    Per the Web Audio spec, decode failures throw a DOMException with name EncodingError. We deliberately do NOT treat plain Error instances as decode failures: fetch/cache/network errors must propagate so the caller sees the real cause instead of silently falling through to a different format. See reports/format-fallback-codex-review.md (Major 1).

    Parameters

    • error: unknown

    Returns boolean