ExtensionFor a more in-depth overview of the paradigm of ATK and a complete presentation of its capabilities, see Introducing the Ambisonic Toolkit. These examples show a limited set of the ATK's functionality, but illustrate how to work with the library when using SynthDefs and Synths, and a single decoder that reads the Ambisonic signal through audio bus routing. Additionally, examples for Non-Realtime (NRT) processing are found in this guide.
FoaPanB encodes a monophonic input to a first order ambisonic signal (B-format), as a planewave. PanB is the SuperCollider inbuilt equivalent.
This first example encodes a PinkNoise source as a planewave and decodes to stereo.
Server.default = s = Server.local.boot;
(
var decoder;
// First we will define our decoder
// stereo decoder
decoder = FoaDecoderMatrix.newStereo((131/2).degrad, 0.5);
// next we define a synth using FoaPanB, and decoder using FoaDecode
SynthDef(\foaEncode1, {
var src, theta, phi, foa, out;
// our source: pink noise
src = PinkNoise.ar(-6.dbamp);
// theta is our angle on the X-Y plane and phi is our elevation
// use a MouseX to control theta in real time, from pi to -pi
theta = MouseX.kr(pi, -pi);
phi = 0;
// Encode into our foa signal
foa = FoaPanB.ar(src, theta, phi);
// decode our signal using our decoder defined above
out = FoaDecode.ar(foa, decoder);
Out.ar(0, out);
}).add;
)
// play the synth
a = Synth(\foaEncode1);
//free the synth
a.free;
Encodes a monophonic input as an omnidirectional soundfield, then re-image via two transforms using the FoaTransform UGen wrapper.
(
var decoder, encoder;
// First we will define our decoder and encoder
// stereo decoder
decoder = FoaDecoderMatrix.newStereo((131/2).degrad, 0.5);
// a matrix for an omni image
encoder = FoaEncoderMatrix.newOmni;
// define a synth using FoaEncode and FoaDecode
SynthDef(\foaEncode2, {
var src, angle, azim, foa, out;
// our source: Pink Noise (could be any mono signal)
src = PinkNoise.ar(-6.dbamp);
// for the 'push' transform later
// see FoaPush help for details
// angle ---> top = push to plane wave
// bottom = omni-directional
angle = MouseY.kr(pi/2, 0);
// for 'rotate' transform
// azimuth -> hard left = back
// centre = centre
// hard right = back
azim = MouseX.kr(pi, -pi);
// Encode into our foa signal
foa = FoaEncode.ar(src, encoder);
// push transform using angle
foa = FoaTransform.ar(foa, 'pushX', angle);
// rotate transform using azim
foa = FoaTransform.ar(foa, 'rotate', azim);
// decode our signal
out = FoaDecode.ar(foa, decoder);
Out.ar(0, out);
}).add;
)
// play the synth
a = Synth(\foaEncode2);
// free the synth
a.free;
Encode a planewave, and route to a single decoder:
(
var decoder;
// define our deocder
decoder = FoaDecoderMatrix.newStereo((131/2).degrad, 0.5);
// allocate four channels for routing
a = Bus.audio(s, 4);
// Encoding Synth
SynthDef(\foaEncode3, {arg outBus, duration = 0.05, theta, phi;
var src, foa, env;
// our mono source
src = PinkNoise.ar(-6.dbamp);
// amplitude scaling envelope
env = EnvGen.kr(
Env([0, 1, 0], [0.5, 0.5], \sin),
timeScale: duration,
doneAction: 2);
// Encode into our foa signal
foa = FoaPanB.ar(src, theta, phi, env);
Out.ar(outBus, foa);
}).add;
// Decoding Synth
SynthDef(\foaDecode, {arg inBus;
var foa, out;
// read in 4 channels (B-format) from inBus
foa = In.ar(inBus, 4);
// decode to stereo
out = FoaDecode.ar(foa, decoder);
Out.ar(0, out);
}).add;
)
// start the decoder, reading bus 'a' at the \tail
b = Synth(\foaDecode, [\inBus, a], 1, \addToTail);
// use a Routine to start many encoded signals at random angles
Routine.run({
20.do({
Synth(\foaEncode3, [\outBus, a, \theta, pi.rand2, \phi, 0]);
0.1.wait;
})
});
b.free; // free the decoder
a.free; // free the audio bus
In this example we're working with a B-format sound file. As the source is already encoded, an encoding stage is not needed. For audition, a binaural (HRTF) decoder is used. This decoder takes a subjectID as an argument. It would be wise to experiment with various subjectIDs to discover which suits your own head.
(
var cond, decoder, sndbuf, synth;
// boot the server
s.boot;
// wait for the server to boot
cond = Condition.new;
s.waitForBoot({
Routine.run({
// define a binaural decoder
decoder = FoaDecoderKernel.newListen(1013);
// load sound file into a buffer
sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Pampin-On_Space.wav");
s.sync(cond);
// synth to decode our B-format sound file
SynthDef(\kernelDecode, {arg buffer;
var out, src;
// play B-format sound file
src = PlayBuf.ar(sndbuf.numChannels, buffer, BufRateScale.kr(buffer), loop: 1);
// decode using decoder
out = FoaDecode.ar(src, decoder);
Out.ar(0, out);
}).add;
s.sync(cond);
synth = Synth(\kernelDecode, [\buffer, sndbuf]);
// press command period when done
CmdPeriod.doOnce({
synth.free;
decoder.free;
sndbuf.free
});
})
})
)
Ambisonic UHJ is the stereo compatible Ambisonic format, and a suitable Ambisonic B-format signal can be retrieved from UHJ encoded signals.1
Here we will encode (transcode, actually!) a UHJ Stereo file to B-format. For audition, a binaural (HRTF) decoder is used. This decoder takes a subjectID as an argument. It would be wise to experiment with various subjectIDs to discover which suits your own head. suits your own head.
(
var cond, encoder, decoder, sndbuf, synth;
// boot the server
s.boot;
// wait for the server to boot
cond = Condition.new;
s.waitForBoot({
Routine.run({
// define an UHJ encoder
encoder = FoaEncoderKernel.newUHJ;
// define an HRTF decoder
decoder = FoaDecoderKernel.newListen(1013);
// load a UHJ sound file into a buffer
sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/uhj/Palestrina-O_Bone.wav");
s.sync(cond);
// synth to encode a UHJ file and decode using an HRTF
SynthDef(\kernelEncodeDecode, {arg buffer;
var out, src, encode;
// our stereo source signal
src = PlayBuf.ar(sndbuf.numChannels, buffer, BufRateScale.kr(buffer));
// encode using a UHJ encoder
encode = FoaEncode.ar(src, encoder);
// decode using an HRTF decoder
out = FoaDecode.ar(encode, decoder);
Out.ar(0, out);
}).add;
s.sync(cond);
// play the synth
synth = Synth(\kernelEncodeDecode, [\buffer, sndbuf]);
// press command period when done
CmdPeriod.doOnce({
synth.free;
encoder.free;
decoder.free;
sndbuf.free});
})
})
)
In many cases, examples using Score are often trickier due to the need for the use of bundles. Since the Kernels2 also pass in hardcoded buffer IDs, we need to make sure those are referenced, as well.
The example below decodes a B-format input file to Stereo Ambisonic UHJ using the ATK's FoaDecoderKernel: *newUHJ decoder.
(
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
SoundFile.use(
sndPath,
{arg soundFile;
headerFormat = soundFile.headerFormat;
sampleFormat = soundFile.sampleFormat;
sampleRate = soundFile.sampleRate;
numChannels = soundFile.numChannels;
duration = soundFile.duration;
}
);
// define a decoder of your choosing
// the decoder takes a score argument so that it will add the kernels to the score for you
decoder = FoaDecoderKernel.newUHJ(
sampleRate: sampleRate,
score: score
);
// define an encoding and decoding synth
SynthDef(\kernelDecode, {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);
}).load;
score.add(
[ 0.0,
[ 'b_allocRead', bufnum, sndPath, 0, 0 ],
[ 's_new', 'kernelDecode', 1001, 0, 1, 'buffer', bufnum ]
],
);
// add commands to free the synth and buffer
score.add([ duration, [ 'n_free', 1001 ] ],);
score.add([ duration + 0.1, [ 'b_free', bufnum ] ],);
// free the kernel buffers
decoder.kernel.do({arg bufs;
bufs.do({arg buf;
offset = offset + 0.1;
score.add([ duration + offset, [ 'b_free', buf.bufnum ]])
});
});
// add the needed dummy command to stop NRT
score.add([offset + duration + 0.2, [0]] );
// render our score to a sound file
score.recordNRT(
"/tmp/trashme",
"~/Desktop/myDecode.wav".standardizePath,
sampleRate: sampleRate,
headerFormat: headerFormat,
sampleFormat: sampleFormat,
options: ServerOptions.new.numOutputBusChannels_(decoder.numChannels)
);
)
The Composer's Toolkit (Ctk) quark offers a convenient model for working in a score based paradigm in both Realtime and Non-Realtime.
The example here, as above, decodes a B-format input file to Stereo Ambisonic UHJ using the ATK's FoaDecoderKernel: *newUHJ decoder.
(
var score, sndbuf, sndPath, decoder, synth, duration, sampleRate, headerFormat, sampleFormat, numChannels;
// define our CtkScore
score = CtkScore.new;
// 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
SoundFile.use(
sndPath,
{arg soundFile;
headerFormat = soundFile.headerFormat;
sampleFormat = soundFile.sampleFormat;
sampleRate = soundFile.sampleRate;
numChannels = soundFile.numChannels;
duration = soundFile.duration;
}
);
// define a CtkBuffer and add it to our score
sndbuf = CtkBuffer.playbuf(sndPath).addTo(score);
// define a decoder of your choosing
// the decoder takes a score argument so that it will add the kernels to the score for you
decoder = FoaDecoderKernel.newUHJ(
sampleRate: sampleRate,
score: score
);
// define a CtkSynthDef
synth = CtkSynthDef(\kernelDecode, {arg buffer;
var out, src;
// play a sound file from a buffer
src = PlayBuf.ar(numChannels, buffer, BufRateScale.kr(buffer));
// decode our B-format sound file
out = FoaDecode.ar(src, decoder);
Out.ar(0, out);
});
// create a synth note and add it to the score
score.add(
synth.note(0.0, duration).buffer_(sndbuf)
);
// write our score to disk
score.write("~/Desktop/myDecode.wav".standardizePath,
sampleRate: sampleRate,
headerFormat: headerFormat,
sampleFormat: sampleFormat,
options: ServerOptions.new.numOutputBusChannels_(decoder.numChannels)
);
)
And, this example uses both a Kernel Encoder and Decoder. First an Ambisonic UHJ soundfile is encode to B-format. Then, it is decoded using the ATK's binaural (HRTF) decoder decoder.
(
var score, sndbuf, sndPath, encoder, decoder, synth, duration, sampleRate, headerFormat, sampleFormat, numChannels;
// define our CtkScore
score = CtkScore.new;
// the path to our B-Format sound file
sndPath = Atk.userSoundsDir ++ "/uhj/Palestrina-O_Bone.wav";
// get some info about the soundfile we are decoding for the Score requirements
SoundFile.use(
sndPath,
{arg soundFile;
headerFormat = soundFile.headerFormat;
sampleFormat = soundFile.sampleFormat;
sampleRate = soundFile.sampleRate;
numChannels = soundFile.numChannels;
duration = soundFile.duration;
}
);
// define a CtkBuffer and add it to our score
sndbuf = CtkBuffer.playbuf(sndPath).addTo(score);
// define the UHJ encoder
// the decoder takes a score argument so that it will add the kernels to the score for you
encoder = FoaEncoderKernel.newUHJ(
sampleRate: sampleRate,
score: score
);
// define a decoder of your choosing
// the decoder takes a score argument so that it will add the kernels to the score for you
decoder = FoaDecoderKernel.newListen(
subjectID: 1013,
sampleRate: sampleRate,
score: score
);
// define a CtkSynthDef
synth = CtkSynthDef(\kernelEncodeDecode, {arg buffer;
var out, encoded, src;
// play a sound file from a buffer
src = PlayBuf.ar(numChannels, buffer, BufRateScale.kr(buffer));
// encode our UHJ sound file
encoded = FoaEncode.ar(src, encoder);
// decode our B-format sound file
out = FoaDecode.ar(encoded, decoder);
Out.ar(0, out);
});
// create a synth note and add it to the score
score.add(
synth.note(0.0, duration).buffer_(sndbuf)
);
// write our score to disk
score.write("~/Desktop/myDecode.wav".standardizePath,
sampleRate: sampleRate,
headerFormat: headerFormat,
sampleFormat: sampleFormat,
options: ServerOptions.new.numOutputBusChannels_(decoder.numChannels)
);
)