Rotation of HOA soundfields

Hi there, I’ve been playing with HOA Ambisonics again, using the SC-HOA UGens and also experimenting with the IEM Plugins (via VSTPlugin). I’ve been using the SC HOA encoder (because I like its spherical mode with distance effects) and the IEM BinauralDecoder (because it uses less CPU than HOADecLebedev26 and sounds better than HOABinaural).

I’m a bit confused with the HOATransRotateXYZ UGen. The documentation doesn’t make it clear the order of rotations (hopefully roll, pitch and then yaw). The axes being used seem to be X-right, Y-forward and Z-up, but if you look at the “Spatial Transformations” paper referenced in the documentation, it seems to show X-forward, Y-left and Z-up (since it refers to roll about the X axis)…

Can anyone shed some light on this? Even better would be if we could use a quaternion or something less ambiguous to specify the rotation, especially if you’re trying to work with a head tracker.

The HOATransRotateXYZ arguments would make sense to me as the other (Y-forward) coordinate system, since it’s named RotateXYZ, and the order of arguments is pitch/roll/yaw (presumably corresponding to X/Y/Z). But in this case, the pitch values seem negated. I don’t know how to validate this – but by listening on headphones, I’ve found a positive yaw rotates things left (as expected), but a positive pitch/elevation seems to rotate below you rather than what I’d expect (above you). Also, once I start to combine non-zero pitches, rolls and yaws, things don’t seem to combine in the way I’d expect.

Is there anyone who’s been using these classes (or who wrote them!) who can help clarify things? I can always do three cumulative calls to HOATransRotateXYZ to break apart the rotation ordering into pieces, but that seems like it might be a bit heavy for a large number of sources.

Thanks for any tips,

I figured out a way to “debug” this further… According to the documentation in “HOA Tutorial Exercise 02”, the 1-2-3 ambisonic channels represent the Y, Z, X axes. So we can inspect this directly:

Ndef(\hoaDebug, {
	var az = 0.poll(1, 'az').degrad;
	var elev = 0.poll(1, 'elev').degrad;
	var sig =,, az, elev);
	sig[1..3].round(1e-3).poll(1, [\Y, \Z, \X]);

With az 0 and elev 0, we get +X axis (sounds ahead)
With az 90 and elev 0, we get +Y (sounds left)
With az -90 and elev 0, we get -Y (sounds right)
With az 180 and elev 0, we get -X axis (sounds behind)
With az 0 and elev 90, we get +Z axis (sounds above)
With az 0 and elev -90, we get -Z axis (sounds below)

So that answers my question about the coordinate system. And from doing something similar as above, to debug HOATransRotateXYZ, I can see that indeed a positive pitch argument makes the sound tilt downwards, which is surprising and the reverse of what I’d expect. The roll and yaw arguments seem to behave as expected.

Note: the docs say SC-HOA uses ACN channel ordering, and I’m assuming IEM plugins do too. Also, I had to set my IEM decoder to use N3D normalization, which is what SC-HOA uses (not the default SN3D).


Duh, never mind. I was assuming the arguments to HOATransRotateXYZ represented X, Y and Z rotations, but now that I have confirmed that in ambisonic land X is forward, Y left and Z up, the downward positive tilt makes sense.

But: I really don’t know why that confusing order of arguments was used – I might have used X, Y and Z rotations, or else roll, pitch, yaw (or even yaw, pitch, roll). But I definitely would not have used what the function expects: pitch, roll, yaw… It doesn’t match the order of rotation, or the order of listing the axes in the method name, or anything.

Finally (and unfortunately), just for the record…here is what I need to do to get the rotation ordering I was wanting/expecting (first roll, then pitch, then yaw):

	hoa =, hoa, roll: roll);
	hoa =, hoa, pitch: pitch);
	hoa =, hoa, az: yaw);

I just tested, and I can confirm that HOATransRotateXYZ actually composes the rotation operations in exactly the reverse of what I’d want: it does yaw/heading/azimuth first, then pitch, then roll, and I want the opposite (as shown in my previous post). So that really looks like a bug…

Hello Glen, I expect you’re aware that the ATK now does HOA. (My reply is more for the record.)

We have compound rotations:

Along with single axis rotations, classic names:

And “modern” names:

1 Like