Renders (decodes) a first order ambisonic signal (B-format) to speaker feeds in a variety of configurations. DecodeB2 is the SuperCollider inbuilt equivalent.
in |
The B-format signal, an array: [w, x, y, z] |
decoder |
FoaDecoderMatrix or FoaDecoderKernel instance. |
mul |
Output will be multiplied by this value. |
add |
This value will be added to the output. |
An array of channels, one for each speaker.
The examples below are intended to briefly illustrate some of the first order decoding options made available in the Ambisonic Toolkit. The user is encouraged to carefully review the features of FoaDecoderMatrix and FoaDecoderKernel to gain a deeper understanding of the flexibility of these tools.
As the Ambisonic technique is a hierarchal system, numerous options for playback are possible. These include two channel stereo, two channel binaural, 2D horizontal only surround (pantophonic) and full 3D with height surround (periphonic). A brief introduction is explored below.
Encoded as an omnidirectional soundfield, PinkNoise is used as the example sound source. In a well aligned, dampend studio environment, this usually sounds "in the head". FoaPush is used to "push" the omnidirectional soundfield so that it becomes a planewave (infinite distance, in an anechoic environment) arriving from some direction.
The soundfield is controlled by MouseX and MouseY, where MouseX specifies the incident azimuth angle (pi to -pi; left to right of display) and MouseY the FoaPush angle (0 to pi/2; bottom to top of display). With the mouse at the bottom of the display, the soundfield remains omnidirectional. Placed at the top of the display, the soundfield becomes directional, and varying left/right position will vary the incident azimuth of the resulting planewave.
Before exploring the examples below, it is suggested you confirm your Server has enough output channels to support your chosen decoder. You can query the server:
myServer.options.numOutputBusChannels
An example Function, ~checkMyServerOutputs
, can be found here. ~checkMyServerOutputs
throws a warning if myServer.options.numOutputBusChannels < myDecoder.numOutputs
. If you need to update your Server's number of output bus channels, review the example found here.
The soundfield may be decoded to stereo using a pair of virtual microphones.
// ------------------------------------------------------------ // virtual microphone stereo decoder // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderMatrix.newStereo // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig;// audio signal var angle, azim; // angle and azimuth control // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to stereo) FoaDecode.ar(sig, ~decoder); }.scope; ) // free kernel ~decoder.free // ------------------------------------------------------------
Ambisonic UHJ stereo1 is the 'native' stereo format for Ambisonics. A B-format signal (2D, with some losses) can be recovered from a UHJ decoded signal through the use of FoaEncoderKernel: *newUHJ.
// ------------------------------------------------------------ // UHJ (stereo) decoder // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderKernel.newUHJ // kernel decoders should be freed after use!! // free below... // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig;// audio signal var angle, azim; // angle and azimuth control // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to stereo) FoaDecode.ar(sig, ~decoder); }.scope; ) // free kernel ~decoder.free // ------------------------------------------------------------
The Ambisonic Tookit provides a synthetic spherical head model HRTF decoder.2 Ten subjects with varying head sizes are available. Audition to find one that works best for you.
Additionally, HRTF decoders computed from measured HRIRs are also available: FoaDecoderKernel: *newListen & FoaDecoderKernel: *newCIPIC.
// ------------------------------------------------------------ // Binaural (synthetic) decoder // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderKernel.newSpherical // kernel decoders should be freed after use!! // free below... // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig;// audio signal var angle, azim; // angle and azimuth control // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to binaural) FoaDecode.ar(sig, ~decoder); }.scope; ) // free kernel ~decoder.free // ------------------------------------------------------------
Measured HRTF decoder, with measurements from the University of California Davis' CIPIC HRTF database.3 Forty-five subjects with varying head sizes are available. Audition to find one that works best for you.
// ------------------------------------------------------------ // Binaural (CIPIC) decoder // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderKernel.newCIPIC // kernel decoders should be freed after use!! // free below... // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig;// audio signal var angle, azim; // angle and azimuth control // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to binaural) FoaDecode.ar(sig, ~decoder); }.scope; ) // free kernel ~decoder.free // ------------------------------------------------------------
The Ambisonic Toolkit provides an optimised quadraphonic decoder with variable loudspeaker angle. The below example uses the default settings, which results in a square layout, 'single'
band type ( 'energy'
) decoder. This sort of decoder is suitable for mid-scale playback, though, for best results for an audience, the use of a larger array (5+ loudspeakers) is advised. FoaDecoderMatrix: *newPanto or FoaDecoderMatrix: *newDiametric would be appropriate.
A psychoacoustically optimised (dual-band) near-field compensated decoder, suitable for studio monitoring, is demonstrated below.
// ------------------------------------------------------------ // quad decoder // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderMatrix.newQuad // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig; // audio signal var angle, azim; // angle and azimuth control var fl, bl, br, fr; // quad output channels // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to quad) #fl, bl, br, fr = FoaDecode.ar(sig, ~decoder); [fl, fr, bl, br] // reorder output to match speaker arrangement }.scope; ) // ------------------------------------------------------------
By default, SuperCollider includes a pantophonic (2D) decoder, DecodeB2. This inbuilt decoder provides functionality similar to the Ambisonic Toolkit's FoaDecoderMatrix: *newPanto, with the exceptions of a variable k argument and the documentation features of FoaDecoderMatrix, e.g. FoaDecoderMatrix: -dirChannels.
The inbuilt decoder is a 'controlled'
k decoder. (See this discussion on k.) The below code includes a function, funK
, to add variable k functionality to DecodeB2. So, this example is realised as a 'single'
band type ( 'energy'
) decoder, matching the FoaDecoderMatrix: *newQuad example above.
// ------------------------------------------------------------ // compare to SuperCollider's inbuilt DecodeB2 (as quad decoder) // // mono pink noise source // omni encoder // define encoder matrix and decoder channels ~encoder = FoaEncoderMatrix.newOmni ~numChans = 4 // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~numChans // function to adjust k of DecodeB2.ar ( var funK; funK = { arg k; if ( k.isNumber, { k }, { switch ( k, 'velocity', { [1, 2, 2, 2] }, 'energy', { [1, 2.sqrt, 2.sqrt, 2.sqrt] }, 'controlled', { [1, 1, 1, 1] }, 'single', { [1, 2.sqrt, 2.sqrt, 2.sqrt] } ) } ) }; ~kScale = funK.value('single'); // specify ATK's default, ) // a single band ('energy') decoder ( { var sig; // audio signal var angle, azim; // angle and azimuth control var fl, bl, br, fr; // quad output channels var w, x, y, z; // b-format channels (split) // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format("inbuilt").postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // split to w, x, y, z, and scale k #w, x, y, z = sig * ~kScale; // ------------------------------------------------------------ // decode (to quad), and match gain to ATK decoders #fl, fr, br, bl = DecodeB2.ar(~numChans, w, x, y) * 6.neg.dbamp; [fl, fr, bl, br] // reorder output to match speaker arrangement }.scope; ) // ------------------------------------------------------------
The decoder presented here is an example of a dual-band ( 'dual'
) psychoacoustically optmisied, near-field compensated decoder described by Gerzon.4 This sort of decoder is considered the ideal for first order Ambisonics, meeting all the criteria outlined by Gerzon to qualify as Ambisonic, 5 and is the choice for critical studio listening.
Additionally, this decode is rendered as a 'narrow quadraphonic' layout, with loudspeaker angles at [ 30.0, 150.0, -150.0, -30.0 ]
. For studio based work, this can be convenient, as the front pair is at the correct angle for two channel stereo monitoring. The narrow layout gives increased localisation and stabilised images at front and back, at the expense of reduced stability at the sides.
Near-field compensation, filtering for the near-field effects of loudspeaker placement, is made through the use of FoaNFC.
// ------------------------------------------------------------ // narrow quad decoder, psychocacousticly optimised, & with NFC // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderMatrix.newQuad(pi/6, 'dual') ~distance = 1.2 // louspeaker distance, for NFC, in meters // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ~distance ( { var sig; // audio signal var angle, azim; // angle and azimuth control var fl, bl, br, fr; // quad output channels // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // nfc & decode (to quad) sig = FoaTransform.ar(sig, 'nfc', ~distance); #fl, bl, br, fr = FoaDecode.ar(sig, ~decoder); [fl, fr, bl, br] // reorder output to match speaker arrangement }.scope; ) // ------------------------------------------------------------
The Ambisonic Toolkit includes Bruce Wiggins' optimised ITU 5.0 decoders.6
// ------------------------------------------------------------ // 5.0 decoder // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderMatrix.new5_0 // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig;// audio signal var angle, azim; // angle and azimuth control var fc, fl, bl, br, fr; // 5.0 output channels var lo; // low freq channel place holder // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to 5.0) #fc, fl, bl, br, fr = FoaDecode.ar(sig, ~decoder); lo = Silent.ar; [fl, fr, fc, lo, bl, br] // reorder output to match speaker arrangement }.scope; ) // ------------------------------------------------------------
A full 3D decoder, with eight loudspeakers arranged in upper and lower rings of four. This small eight channel array is not optimal for large scale playback. For public performance, a 10 or 12 channel arrangement (two rings of 5 or 6) is more suitable.
The loudspeaker layout specified by this decoder is more suited to a small-scale situation. See below for a minimal arrangement appropriate for full 3D studio monitoring.
// ------------------------------------------------------------ // periphonic (3D) decoder (8-channels arranged as a cube) // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderMatrix.newPeri // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ( { var sig; // audio signal var angle, azim; // angle and azimuth control var flu, blu, bru, fru; // cube output channels var fld, bld, brd, frd; // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (to cube) #flu, blu, bru, fru, fld, bld, brd, frd = FoaDecode.ar(sig, ~decoder); [flu, fru, blu, bru, fld, frd, bld, brd] // reorder output to match speaker arrangement }.scope; ) // ------------------------------------------------------------
This bi-rectangular decoder has been described by Gerzon as optimal for small-scale, full 3D listening. The decoder presented is an example of a dual-band ( 'dual'
) psychoacoustically optmisied, near-field compensated decoder. Meeting all the criteria outlined by Gerzon to qualify as Ambisonic, this decoder is a good choice for full 3D critical studio listening.
The frontal loudspeaker pair is arranged at [ 30.0, -30.0 ]
degrees. For studio based work, this can be convenient, as the front pair is at the correct angle for two channel stereo monitoring.
Near-field compensation, filtering for the near-field effects of loudspeaker placement, is made through the use of FoaNFC.
// ------------------------------------------------------------ // diametric 3d decoder (8-channels in a bi-rectangle) // psychocacousticly optimised, & with NFC // // mono pink noise source // omni encoder // define encoder / decoder matrices ~encoder = FoaEncoderMatrix.newOmni ~decoder = FoaDecoderMatrix.newDiametric( [[30, 0], [-30, 0], [90, 35.3], [-90, 35.3]].degrad, 'dual' ) ~distance = 1.2 // louspeaker distance, for NFC, in meters // inspect ~encoder.kind ~encoder.numChannels ~encoder.dirChannels ~decoder.kind ~decoder.numChannels ~decoder.dirChannels.raddeg ~distance ( { var sig; // audio signal var angle, azim; // angle and azimuth control var fl, fr, bl, br; // bi-rectangle output channels var slu, sru, sld, srd; // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar; // mono pink noise // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // nfc & decode (to bi-rectangle) sig = FoaTransform.ar(sig, 'nfc', ~distance); #fl, fr, slu, sru, br, bl, srd, sld = FoaDecode.ar(sig, ~decoder); [fl, fr, bl, br, slu, sru, sld, srd] // reorder output to match speaker arrangement }.scope; ) // ------------------------------------------------------------