SuperCollider GUIDES (extension)

SynthDef and NRT examples for ATK
ExtensionExtension

SynthDef and NRT examples for ATK
 

ATK with SynthDef and Synth

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.

Mono Encoder using FoaPanB

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;

Omni Encoder using FoaEncoderMatrix, Transforms using FoaTransform

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;

Route Encoding Synth to a separate Decoding Synth

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

Kernel Decoders and Encoders

B-format Sound File and Binaural Decoder

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

Encode an Ambisonic UHJ Stereo File, Decode to HRTF

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

ATK in Non-Realtime

ATK and Score

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

)

ATK and the Composer's Toolkit (Ctk)

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

[1] - See further discussion here.