Using arduino Headtracker via HID to control a transform in Ambisonic Toolkit?

Hi all, I’m looking for some help with trying to connect an arduino headtracker to rotate the soundfield using ‘Foatransform’ in ATK (atk-sc3).

In this example (created from the tutorials), the Ugen to directly pass a variable into the transform.

SynthDef(\kernelEncodeDecode, {arg buffer;
            var out, src, trans, encode;

            src =, buffer,;
            encode =, encoder);
			trans =, 'rotate',, pi));
            out =, decoder);

  , out);


        // play the synth
        synth = Synth(\kernelEncodeDecode, [\buffer, sndbuf]);

My question is if there is there a way of replacing, with an expression which will pass values from a USB headtracker, the outputs of which are directly recognised by SC3 as HID elements.

I have searched the forum and found some other examples on Github (i.e. notam’s TeensyHeadTracker, nvsonic-head-tracker, etc.) which appear to either convert to MIDI or use an OSC bridge, but ideally I’d like to bypass any conversion or middleware, as intend to run on a RPi in headless mode, just running Supercollider.

I’ve checked the headtracker’s output (it’s an EdTracker DIY - Arduino Nano / MPU-9250) and it is giving out continuous data on all three axis and can use it to modify the synth the connect to a simple SinOsc synth to change pitch and amplitude (as per tutorial example):

Ndef( \sinewave, { |freq=500, amp=0.1| freq, 0, amp * 0.2 ) } );
// Ndef( \sinewave ).play;
~freqRange = [500, 5000, \exponential].asSpec; // create a frequency range
HIDdef.usage( \freq, { |value| Ndef( \sinewave ).set( \freq, value ) ).poll; }, \X );
HIDdef.usage( \amp, { |value| Ndef( \sinewave ).set( \amp, value ); }, \Z );

Can the same function be used/rewritten to take the place of MouseX in the first example?

Sorry if I’ve not explained that too well but if anyone has any success in using a headtracker this way (or any HID - gamepad, Wii remote), I’d be very interested to find out how - or if I am trying to do the impossible?

Many thanks!

You’re closer than you think :slight_smile:

You would replace your MouseX control with an new argument to your SynthDef that controls the amount of rotation. Something like:

SynthDef(\kernelEncodeDecode, {arg buffer, rotation=0;
	var out, src, trans, encode;
	src =, buffer,;
	encode =, encoder);
	trans =, 'rotate', rotation);
	out =, decoder);, out);


// play the synth, note it's a global variable ~synth
~synth = Synth(\kernelEncodeDecode, [\buffer, sndbuf]);

// set the rotation (units are RADIANS)
~synth.set(\rotation, 30.degrad); // rotate 30 degrees counter-clockwise (left)

Now that you have your synth argument controlling the rotation, you modify the HIDdef to set that synth argument.

// map [0->1] messages to [-pi->pi] radians, adjust to your HID input range
~rotateRange = [-pi, pi, \linear].asSpec; 

HIDdef.usage( \hidRotator, 
	{ |value| 
	\X );

How you map your ranges will depend on what range and units your headtracker is giving.
I assumed your headtracker is giving you ranges from [0->1], but if it’s instead giving you, for example [-1->1], you could use something like linlin to map your values:

// in your HIDdef
~synth.set(\rotation, value.linlin(-1, 1, -pi, pi));

If you prefer, ATK has an equivalent UGen for rotation, FoaRotate (“yaw”). For rotating the other two axes you can use FoaTilt (“roll”) and FoaTumble (“pitch”), or one that combines all three: FoaRTT.

You’ll have use care when mapping your ranges/units and directions (positive or negative), and which order you perform those rotations. With 3 axes of rotation it can get quite confusing, so I’d recommend working with one axis at a time, then combining them.

Hi Mike,

Thank you very much - that’s got things working! I think I tried a few of those things before but never in the right order or syntax. Now I’ve got it working, I’ve realised that the headtracker itself isn’t that good and is drifting back into the centre, so looks like I might need to force a recalibration or replace it.

One quick query is this line …

// set the rotation (units are RADIANS)
~synth.set(\rotation, 30.degrad); // rotate 30 degrees counter-clockwise (left)

…just used to centre the x-axis?

Thanks again for pointing me in the right direction, it was beginning to drive me crazy!

I’m glad you have it working :slight_smile:

No, this was jut to demonstrate how to set the rotation argument.

The drift is a common problem with accelerometers/magnetometers, and requires calibration. The mpu-9250 has auto-calibration built in, but you may need to take additional steps. This page has some info about calibrating the magnetometer, although if you google ‘mpu-9250 calibration’ there should be lots of resources online (though just takes some digging to sort through).

If you’re using this with headphones with large tranducers (like over-the-ear style), you’ll likely also need to calibrate with the IMU mounted to them, since the drivers in the headphones will distort the magnetic field (“hard iron calibration”). Here are a couple related links:

Hi Mike,
Just wanted to let you know that the recalibration did the trick (I ended up finding the old Windows GUI utility and an old PC to run it one). Unfortunately, the headtracker broke soon after (the USB socket came away from the Arduino - I guess I’ve plugged it in about 100 times this week…). I think it will be easy enough to replace but now I have to change the headtracker, I thought I might as well try a bluetooth version! Have you had any expereince with any bluetooth varieties?


It’s been a while since playing with these, and at the time I only used old fashioned bluetooth (iirc because it was easier to interface with serial), but BluetoothLE is how most are done these days. I heard some good things about this one:
And it looks like they have dev kits for a bit lower price than the out-of-the-box version.

If you’re still OK with a wired connection, I’ve also heard good things about this one:
which is more affordable than most.

(I’d be surprised if any of them have really solved the drift issue :laughing:)