[ATK] Decode ambisonics to PanAz format


I have a project entirely made with ATK, which I was monitoring on binaural as well as 2D loudspeakers regular polygon. However, I need to present it again using a loudspeaker 1D array (5 channels on a row, equally spaced).

Is it possible to decode ATK formats into a PanAz behavior? Any alternative to avoid the sound circularly wrapping around the corners (thus behaving like PanAz and not like FoaTransform ‘push’ with azimuth rotation)? Which ambisonic transformation would be more accurate to simulate PanAz’s width?


Hello @fmiramar,

Great to hear you’re finding the Ambisonic Toolkit to be of use for your project.

I’m going to assume you mean you’ve got five loudspeakers that will be arranged as a line array arranged something like this:

[ Far Left, Center Left, Center, Center Right, Far Right ]


How exactly do you want the encoded angles to map?

For instance are you after something like this…

0 deg → Center
45 deg → Center Left
90 deg → Far Left
135 deg → Center Left
180 deg → Center

… where the image wraps back towards the center as the sound material pans around the circle?

Or, are you after something different?

You might like to have a closer look at this example, which makes a direct comparison of Ambisonic decoding with PanAz.

PanAz’s width argument controls the returned panning law loudspeaker “beam shape”. With Ambisonics we control this via decoder k for FOA and beam shape for HOA. (We just renamed k for the HOA implementation.)

Anyway… if you can clarify how you’d like the angles to match, I can think about throwing together a relevant example.


Hi @joslloand thanks a lot for such great software with fully detailed tutorials, it is amazing! :smiley: :clap:

Yes it is exactly this! I cannot realize how I would do it only changing the “beam shape” k parameter, I thought that some kind of soundfield transformation would be necessary. Could you provide an example?

I am aware that doing this is a little bit “against” ambisonics philosophy, but sometimes you have strong restrictions regarding physical space for placing the loudspeakers. Instead of readequating the whole piece to PanAz or VBAP I would like to hear how this ambisonics soundfield distortion would sound like.

Hello @fmiramar, I’ve gone ahead and thrown together three examples of one possible kind of solution.

In principle, a better way to generate a solution is a search / optimization approach of the sort that Arteaga and BLaH take.

Instead I’ve taken an approach inspired by Gerzon & Barton’s “Vienna” decoders. The idea here is to feed a decoder that distorts the imaging (due to non-ideal loudspeaker positioning) with a “pre-warped” Ambisonic signal. The pre-warping compensates or un-does the distortion introduced by the distorting array.

What I’ve done is:

  1. design an 8 channel decoder
  2. discard the three rear loudspeaker
  3. warp the image (via dominance) to undo (reduce, actually) the imaging distortion

Dominance both warps the image and increases the gain in the warped direction. So, we’re attempting to make up for the missing gain and image distortion introduced by discarding the rear loudspeakers.

Since I’ve done this “by hand”, I choose three different possible optimizations:

  1. match energy across front stage ( 0 & pi/2)
  2. match energy at front and back ( 0 & pi )
  3. map pi/2 to max reproduced angle

Here’s a gist with the relevant code (3 examples) for FOA.

And… here’s another gist, which is the “scratch pad” I used to come up with the required distortion parameters.

Some things to keep in mind:

  • I haven’t listened to the results of any of these.

  • From the analysis plots, I would expect 1. to sound the best. But, 2. might also work. While 3. gives the widest returned image for sources at +/- pi/2 (hard left/right), the gain performance isn’t so good.

  • Imaging performance will likely improve if distance / delay of the actual loudspeaker array is taken into account and compensated for.

  • These three arrays perform better (greater maximum imaging angle & separation) than just decoding to a circle and folding the back into the front.

  • An optimization approach will very likely return a better result… but as a quick hack hopefully one of these three will be suitable for your purposes.

  • You can tweak the warping parameter in situ, if you like, to see if you can get an improved result.

Hope this helps!


@joslloand thanks a lot !!! Super helpful explanations!

I made some in situ comparisons, it is a super “problematic” space in terms of simulating soundfield since it is too much reverberant and the noise floor from the traffic is too intense. At the end, I had to introduce some reverb and LPF compensation to simulate distance better, but the localization was not the most sharpest perceptive quality…

I am positively surprise to see that ways of “de-isotrophy” is a research topic!

One last tangential question to this topic: is it possible to render NRT pieces when the ATK decoder is set to FoaDecoderMatrix instead of FoaDecoderKernel? The former does not have and an argument for Score as there is on the latter.

The short answer is… yes. A matrix decoder doesn’t require the use of Buffers. (That’s why we needed to save the decoder kernel to a score.)

You can design the matrix decoder within the SynthDef function, or outside of it. For each of these cases, the decoder needs to be completely defined (instanced) before the SynthDef is instanced. E.g., for the former, no SynthDef args can be referenced. For the later, the matrix decoder must be completely defined (instanced) before the SynthDef is described.

Here’s an example to do the job, derived from this and this example:

var score, bufnum, sndPath, duration, decoder, sampleRate, headerFormat, sampleFormat, numChannels;
var offset = 0.1;

// deinfe our score
score = Score.new;

// get a buffer number from the server
bufnum = Server.default.bufferAllocator.alloc(1);

// the path to our B-Format sound file
sndPath = Atk.userSoundsDir ++ "/b-format/Pampin-On_Space.wav";

// get some info about the soundfile we are decoding for the Score requirements
    {arg soundFile;
        headerFormat = soundFile.headerFormat;
        sampleFormat = soundFile.sampleFormat;
        sampleRate = soundFile.sampleRate;
        numChannels = soundFile.numChannels;
        duration = soundFile.duration;

// define our deocder
decoder = FoaDecoderMatrix.newStereo((131/2).degrad, 0.5);

// define an encoding and decoding synth
SynthDef(\foaStereoDecode, {arg buffer;
    var out, src;

    // play B-format sound file from a buffer
    src = PlayBuf.ar(numChannels, buffer, BufRateScale.kr(buffer));

    // decode our B-format signal
    out = FoaDecode.ar(src, decoder);

    Out.ar(0, out);

    [ 0.0,
        [ 'b_allocRead', bufnum, sndPath, 0, 0 ],

// add commands to free the synth and buffer
score.add([ duration, [ 'n_free', 1001 ] ],);
score.add([ duration + 0.1, [ 'b_free', bufnum ] ],);

// add the needed dummy command to stop NRT
score.add([offset + duration + 0.2, [0]] );

// render our score to a sound file
    sampleRate: sampleRate,
    headerFormat: headerFormat,
    sampleFormat: sampleFormat,
    options: ServerOptions.new.numOutputBusChannels_(decoder.numChannels)

1 Like

Thanks a ton!!! This is great! :grin: