cacophony
    Preparing search index...

    Class FoaDecoder

    Standalone 4-channel-in / 2-channel-out FOA → binaural FORMAT CONVERTER, built entirely from native Web Audio nodes (NO worklet).

    This standalone construct exposes the two endpoints explicitly:

    • input — the 4-channel ChannelSplitterNode you feed FOA into.
    • output — the 2-channel binaural GainNode you route downstream.

    Wire it explicitly when building custom FOA graphs:

      const decoder = await cacophony.createFoaDecoder();
    foaSourceNode.connect(decoder.input); // 4-ch FOA in
    decoder.output.connect(bus.input /* or context.destination */); // stereo out

    For the common bus-filter case use FoaDecoderEffect, created via cacophony.createFoaDecoderEffect(), which returns the same endpoint graph through the standard CacophonyEffect.build() contract.

    Under a real (SN3D/ACN) SH basis the binaural decode collapses to a single per-EAR multiply-accumulate over ALL the SH channels: B^{L,R}(w) = sum_{n,m} S_{n,m}(w) * H_{n,m}^{L,R}(w) (Ahrens, "Binaural Audio Rendering in the Spherical Harmonic Domain", 2022, eq. 31 — the real-basis form where the conjugation and m -> -m degree flip vanish). EACH ear sums the contribution of EVERY FOA channel (W, Y, Z, X) convolved with that ear's HRIR — neither ear may drop a channel. With a single stored SH-HRIR the L/R-symmetric channels (W, Z, X) share the same coefficient for both ears while the L/R-ANTISYMMETRIC channel (Y) is the sign-flip for the right ear, so: B^L = WH_W + YH_Y + ZH_Z + XH_X B^R = WH_W - YH_Y + ZH_Z + XH_X (only the Y term differs, by sign)

    Rather than 8 mono convolvers, the four SH channels are grouped into two STEREO ConvolverNodes — W+Y into one, Z+X into the other — each convolved against a 2-row slice of the 4-row SH-HRIR. A 4-channel ConvolverNode would do unwanted cross-channel convolution per the Web Audio spec; the stereo packing is Omnitone's production graph and is mirrored here VERBATIM (the .connect calls below are line-for-line Omnitone's _buildAudioGraph):

    input (ChannelSplitter, 4ch) ch0(W),ch1(Y) -> mergerWY(2ch) -> convolverWY (HRIR rows {W,Y}, stereo) ch2(Z),ch3(X) -> mergerZX(2ch) -> convolverZX (HRIR rows {Z,X}, stereo) convolverWY -> splitterWY(2ch); convolverZX -> splitterZX(2ch) splitterWY.ch0 (W) -> L AND R splitterWY.ch1 (Y) -> L, and (via -1 inverter) -> R splitterZX.ch0 (Z) -> L AND R splitterZX.ch1 (X) -> L AND R mergerBinaural(2ch) -> output (stereo)

    The result is exactly eq.31: BOTH ears receive ALL four SH channels; only the Y channel's right-ear contribution is sign-flipped by a GainNode(-1), because Y is the sole left/right-antisymmetric FOA channel. (This is the fix for the prior broken graph, which sent only W+Z to the left ear and only -Y+X to the right — each ear missing two channels.) convolver.normalize is set false on both: the SH-HRIR is already correctly scaled and Web Audio convolver normalization would corrupt it.

    The decoder, the bundled Omnitone HRIR, and encodeMonoToFoaSN3D are ALL SN3D. The decode is the plain MAC of two SN3D-matched coefficient sets, so NO per-channel sqrt(3) (N3D<->SN3D) rescale is applied — inserting one would double-normalize.

    NOTE on the resurrection path: the dormant StereoToFoaUpmixer (createStereoToBFormatNode) emits ACN [W,Y,Z,X] but is a perceptual, frequency-banded, coherence-gated mix with per-channel non-constant gain — it is NEITHER N3D nor SN3D and no single normalization bridge exists. Its 4-ch output plugs straight into input (ACN ordering already lines up), and the resulting binaural is a documented PERCEPTUAL APPROXIMATION, not a physically-correct sound field. The clean, physically-correct path is encodeMonoToFoaSN3D -> this decoder.

    Index
    input: AudioNode

    The 4-channel FOA [W, Y, Z, X] (ACN) entry node. Feed FOA into this.

    output: AudioNode

    The 2-channel binaural stereo node. Route THIS downstream.

    • Disconnects the decoder's owned native nodes. Safe to call more than once.

      Returns void

    • Builds the live decode graph against context, resolving the SH-HRIR (caller-supplied or bundled Omnitone) and wiring the Omnitone WY/ZX graph. Async because the bundled HRIR is fetched + decodeAudioData-d.

      Parameters

      Returns Promise<FoaDecoder>