The Ambisonic Toolkit (ATK) brings together a number of classic and novel tools and transforms for the artist working with Ambisonic surround sound and makes these available to the SuperCollider3 user. The toolset in intended to be both ergonomic and comprehensive, and is framed so that the user is encouraged to ‘think Ambisonically’. By this, it is meant the ATK addresses the holistic problem of creatively controlling a complete soundfield, allowing and encouraging the artist to think beyond the placement of sounds in a sound-space (sound-scene paradigm). Instead the artist is encouraged to attend to the impression and imaging of a soundfield, therefore taking advantage of the native soundfield-kernel paradigm the Ambisonic technique presents.
The ATK's production model is illustrated below:

Here you'll see that the ATK breaks down the task of working with Ambisonics into three separate elements:
The below sections go into more detail as to the specifics of each task. For examples that show more concise examples for usage in SynthDef and NRT, see SynthDef and NRT examples for ATK
Most users approaching Ambisonics are usually presented with two avenues to author an Ambisonic soundfield: capture a natural soundfield directly with a Soundfield microphone,1 or author a planewave from a monophonic signal.2 SuperCollider's inbuilt PanB provides the latter solution.
The ATK provides a much wider palate of authoring tools via FoaEncode. These include:
The pseudoinverse encoding technique provides the greatest flexibility, and can be used with both microphone arrays and synthetic signals. In the absence of a Soundfield microphone, this feature gives the opportunity to deploy real-world microphone arrays (omni, cardioid, etc.) to capture natural soundfields. With synthetic signals, pseudoinverse encoding is usually regarded as the method of choice to generate spatially complex synthetic Ambisonic images. In combination with the ATK's imaging tools these can then be compositionally controlled as required.
See FoaEncode, FoaEncoderMatrix and FoaEncoderKernel for more details about encoding.
For the artist, the real power of the ATK is found in the imaging transforms. These are spatial domain filters which reorient, reshape or otherwise spatially filter an input soundfield. Many users will be familiar with the soundfield rotation transform, as SuperCollider provides the inbuilt Rotate2.
The ATK provides a much wider and comprehensive toolset, including:
The imaging tools are provided in two forms: static and dynamic implementations. While most transforms are provided in both categories, a number are found in only one guise.3
See FoaTransform, FoaXform and FoaXformerMatrix for more details about imaging.
As artists working with sound, we should expect most users to be very capable expert listeners, and able to audition the results of spatial filtering of an input soundfield. However, it is often convenient to view a visual representation of the effect of a transform. The ATK illustrates a number of its included transforms in the form shown below.
On the left hand side of the figures a plot of the unchanged soundfield is illustrated. The X-Y axis is shown, viewed from above with the top of the plot corresponding to the front of the soundfield. Eight planewaves are encoded arriving from cardinal directions:
Front, Left, Back-Left and Back are noted with an associated gain, in dB. Individual planewaves are coloured with respect to the gain scale at the far left of the figures.
With changing angle, we see the encoded planewaves both move towards the front of the image (to a single planewave at the extreme) and change gain in ZoomX. When angle is 90 degrees, the planewave at Front has been increased to 6dB, while Back has disappeared.4

PushX also distorts the incident angles of the encoded planewaves. However, here we see two differences with ZoomX. Firstly, the gains of the individual elements don't vary as considerably, and the encoded planewaves are illustrated as moving off the perimeter of the plot.
Moving from the edge of the diagram towards the centre does not imply the sound moves from the edge of the loudspeakers towards the centre, as one might expect. Instead, what we are seeing is the loss of directivity. Moving towards the centre means a planewave moves toward becoming omnidirectional, or without direction. (This becomes clearer when we look at DirectO.)
With PushX, when angle is 90 degrees, all encoded planewaves have been pushed to the front of the image, and gain is retained at 0 dB for all elements.

DirectO adjusts the directivity of the soundfield across the origin. Here we see all elements collapsing towards the centre of the plot. At this point the soundfield is omnidirectional, or directionless.

See FoaZoomX, FoaPushX and FoaDirectO for more details about these illustrated transforms.
Perhaps one of the most celebrated aspects of the Ambisonic sound technique has been its design as a hierarchal reproduction system, able to target a number of varying loudspeaker arrays. Users may be familiar with SuperCollider's inbuilt regular polygon decoder, DecodeB2.
The ATK provides a much wider palate of optimised monitoring tools via FoaDecode. These include:
While the regular decoders will be suitable for many users, diametric decoding enables the greatest flexibility, and allows the user to design substantially varying semi-regular arrays suitable for a wide variety of playback situations.
See FoaDecode, FoaDecoderMatrix and FoaDecoderKernel for more details about decoding.
The examples below are intended to briefly illustrate playback and imaging of soundfields with the ATK. The user is encouraged to explore the ATK's documentation 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, pantophonic and full 3D periphonic. With the examples below, we'll take advantage of this by first choosing a suitable decoder with with to audition.
A number of decoders are defined below. Please choose one suitable for your system. Also, don't forget to define ~renderDecode !
// ------------------------------------------------------------
// switch to internal server, and boot
(
s = Server.internal;
Server.default = s;
s.boot;
)
// ------------------------------------------------------------
// choose a decoder
// stereophonic / binaural
~decoder = FoaDecoderMatrix.newStereo(131/2 * pi/180, 0.5) // Cardioids at 131 deg
~decoder = FoaDecoderKernel.newUHJ // UHJ (kernel)
~decoder = FoaDecoderKernel.newSpherical // synthetic binaural (kernel)
~decoder = FoaDecoderKernel.newCIPIC // KEMAR binaural (kernel)
// pantophonic (2D)
~decoder = FoaDecoderMatrix.newQuad(k: 'dual') // psycho optimised quad
~decoder = FoaDecoderMatrix.newQuad(pi/6, 'dual') // psycho optimised narrow quad
~decoder = FoaDecoderMatrix.new5_0 // 5.0
~decoder = FoaDecoderMatrix.newPanto(6, k: 'dual') // psycho optimised hex
// periphonic (3D)
~decoder = FoaDecoderMatrix.newPeri(k: 'dual') // psycho optimised cube
~decoder = FoaDecoderMatrix.newDiametric( // psycho optimised bi-rectangle
pi/180 * [[30, 0], [-30, 0], [90, 35.3], [-90, 35.3]],
'dual'
)
// inspect
~decoder.kind
// ------------------------------------------------------------
// define ~renderDecode
(
~renderDecode = { arg in, decoder;
var kind;
var fl, bl, br, fr;
var fc, lo;
var sl, sr;
var flu, blu, bru, fru;
var fld, bld, brd, frd;
var slu, sru, sld, srd;
kind = decoder.kind;
case
{ decoder.numChannels == 2 }
{
// decode to stereo (or binaural)
FoaDecode.ar(in, ~decoder)
}
{ kind == 'quad' }
{
// decode (to quad)
#fl, bl, br, fr = FoaDecode.ar(in, ~decoder);
// reorder output to match speaker arrangement
[fl, fr, bl, br]
}
{ kind == '5.0' }
{
// decode (to 5.0)
#fc, fl, bl, br, fr = FoaDecode.ar(in, ~decoder);
lo = Silent.ar;
// reorder output to match speaker arrangement
[fl, fr, fc, lo, bl, br]
}
{ kind == 'panto' }
{
// decode (to hex)
#fl, sl, bl, br, sr, fr = FoaDecode.ar(in, ~decoder);
// reorder output to match speaker arrangement
[fl, fr, sl, sr, bl, br]
}
{ kind == 'peri' }
{
// decode (to cube)
#flu, blu, bru, fru, fld, bld, brd, frd = FoaDecode.ar(in, ~decoder);
// reorder output to match speaker arrangement
[flu, fru, blu, bru, fld, frd, bld, brd]
}
{ kind == 'diametric' }
{
// decode (to bi-rectangle)
#fl, fr, slu, sru, br, bl, srd, sld = FoaDecode.ar(in, ~decoder);
// reorder output to match speaker arrangement
[fl, fr, bl, br, slu, sru, sld, srd]
};
}
)
// ------------------------------------------------------------
// now we're ready to try the examples below!
// ------------------------------------------------------------
The following three sound examples are excerpts from recordings produced via the ATK.
If you haven't already choosen a ~decoder and defined ~renderDecode , do so now.
// ------------------------------------------------------------ // B-format examples, produced via the ATK // B-format soundfile read from disk // read a whole sound into memory // remember to free the buffer later! // (boot the server, if you haven't!) ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Anderson-Pacific_Slope.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Howle-Calling_Tunes.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Pampin-On_Space.wav") ( { var sig; // audio signal // display encoder and decoder "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // ------------------------------------------------------------ // test sig sig = PlayBuf.ar(~sndbuf.numChannels, ~sndbuf, doneAction:2); // soundfile // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // free buffer ~sndbuf.free // ------------------------------------------------------------
This example illustrates the application of various spatial filtering transforms to natural soundfields, recorded via the Soundfield microphone.
The soundfield is controlled by MouseY, which specifies the transform angle argument (90 deg to 0 deg; top to bottom of display). With the mouse at the bottom of the display, the recorded soundfield is unchanged. At the top, the transform is at its most extreme.
If you haven't already choosen a ~decoder and defined ~renderDecode , do so now.
// ------------------------------------------------------------ // B-format examples, natural soundfield with imaging transform // B-format soundfile read from disk // choose transformer ~transformer = 'zoomX' ~transformer = 'pushX' ~transformer = 'directO' // read a whole sound into memory // remember to free the buffer later! // (boot the server, if you haven't!) ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Hodges-Purcell.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Leonard-Orfeo_Trio.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Courville-Dialogue.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Leonard-Chinook.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Leonard-Fireworks.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Anderson-Nearfield.wav") ( { var sig; // audio signal var angle; // angle control // display transformer & decoder "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // gain ---> top = 90 deg // bottom = 0 deg angle = MouseY.kr(pi/2, 0); // ------------------------------------------------------------ // test sig sig = PlayBuf.ar(~sndbuf.numChannels, ~sndbuf, doneAction:2); // soundfile // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, angle); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // free buffer ~sndbuf.free // ------------------------------------------------------------