For 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) ); )