Transforms (spatial domain filter) a first order ambisonic signal (B-format). FoaTransform applies dynamic transforms, for static transforms see FoaXform.
in |
The input signal, an array: [in0, in1, ... inN] |
kind |
The kind of transform to apply.
NOTE: Axial transforms - except as noted, all take args of angle = 0, mul = 1, add = 0 as defaults
|
... args |
Arguments (listed above with each 'kind') for the wrapped transformer UGens. Arguments can NOT be passed in through keyword through the FoaTransform wrapper. You can pass values in by keyword if you use the transform UGens directly. |
The examples below are intended to briefly illustrate some of the first order tranform options made available in the Ambisonic Toolkit. The user is encouraged to carefully review the features of the individual transforms to gain a deeper understanding of the flexibility of these tools.
Available transformers include rotations, mirroring, directivity (spatial low pass fitering), dominance (image warping), and a variety of dominance related transforms.
As the Ambisonic technique is a hierarchal system, numerous options for playback are possible. These include two channel stereo, two channel binaural, pantophonic and full 3D periphonic. With the examples below, we'll take advantage of this by first choosing a suitable decoder with with to audition.
Choose a decoder suitable for your system, as illustrated here. You'll end up definining ~decoder
and ~renderDecode
.
Rotation is one of the most used soundfield transforms. In this case we'll it to centre the subject of a field recording.
The soundfield is controlled by MouseX, which specifies the rotation angle (pi to -pi; left to right of display).
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // rotate transformer // b-format soundfile read from disk // choose transformer ~transformer = 'rotate' // read a whole sound into memory // remember to free the buffer later! // (boot the server, if you haven't!) ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Courville-Dialogue.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Hodges-Purcell.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Leonard-Orfeo_Trio.wav") ( { var sig; // audio signal var azim; // azimuth control // display transformer & decoder "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PlayBuf.ar(~sndbuf.numChannels, ~sndbuf, BufRateScale.kr(~sndbuf), doneAction:2); // soundfile // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, azim); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // free buffer ~sndbuf.free // ------------------------------------------------------------
Dominance specified in gain is a classic Ambisonic production technique. Here we apply gain across the X axis. With these example recordings, we adjust both the stage width and the subject to reverb balance.
The soundfield is controlled by MouseY, which specifies the dominance gain (4.5 dB to -4.5 dB; top to bottom of display).
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // dominateX transformer // b-format soundfile read from disk // choose transformer ~transformer = 'dominateX' // read a whole sound into memory // remember to free the buffer later! // (boot the server, if you haven't!) ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Courville-Dialogue.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Hodges-Purcell.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Leonard-Orfeo_Trio.wav") ( { var sig; // audio signal var gain; // gain control // display transformer & decoder "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // gain ---> top = +4.5db for front // bottom = -4.5db for front gain = MouseY.kr(4.5, 4.5.neg); // ------------------------------------------------------------ // test sig sig = PlayBuf.ar(~sndbuf.numChannels, ~sndbuf, BufRateScale.kr(~sndbuf), doneAction:2); // soundfile // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, gain); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // free buffer ~sndbuf.free // ------------------------------------------------------------
Here we encode four channels of decorrelated and comb filtered PinkNoise as a decorrelated soundfield, resulting in a maximally diffuse soundfield. FoaFocus is used to "focus" on various parts of the soundfield. At extremes, it becomes a planewave (infinite distance, in an anechoic environment) arriving from some direction. This technique gives the opportunity to continuously modulate between a directional and a diffuse soundfield.
The soundfield is controlled by MouseX and MouseY, where MouseX specifies the incident azimuth angle (pi to -pi; left to right of display) and MouseY the FoaFocus angle (0 to pi/2; bottom to top of display). With the mouse at the bottom of the display, the soundfield remains decorrelated. Placed at the top of the display, the soundfield becomes directional, and varying left/right position will vary the incident azimuth of the resulting planewave.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // focus transformer // decorrelated, comb filtered pink noise source // define encoder matrix ~encoder = FoaEncoderMatrix.newAtoB // choose transformer ~transformer = 'focus' ( { var sig; // audio signal var angle, azim; // angle and azimuth control var freq; // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // frequencies freq = 220; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PinkNoise.ar([1, 1, 1, 1]); // 4 channels decorrelated pink noise // ------------------------------------------------------------ // comb filter sig = HPF.ar(sig, freq); sig = CombL.ar(sig, freq.reciprocal, freq.reciprocal, mul: 9.neg.dbamp); // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, angle, azim); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // ------------------------------------------------------------
Here we encode four channels of Klank resonated Dust from A-format. FoaPush is used to "push" the soundfield so that it becomes a planewave (infinite distance, in an anechoic environment) arriving from some direction. This technique gives the opportunity to continuously modulate between a directional and a spatially active soundfield. Additionally, FoaRTT is used to continuously reorient the granular stream so that individual A-format directions don't predominate, and the complete soundfield is filled with activity.
The soundfield is controlled by MouseX and MouseY, where MouseX specifies the incident azimuth angle (pi to -pi; left to right of display) and MouseY the FoaPush angle (0 to pi/2; bottom to top of display). With the mouse at the bottom of the display, the soundfield remains decorrelated. Placed at the top of the display, the soundfield becomes directional, and varying left/right position will vary the incident azimuth of the resulting planewave.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // push and rtt transformer // granular klank stream source // define encoder matrix ~encoder = FoaEncoderMatrix.newAtoB // choose transformer ~transformer1 = 'rtt' ~transformer2 = 'push' ( { var sig; // audio signal var angle, azim; // angle and azimuth control var freq; // ir... var gain = -18; var freqs = [50.0, 7000.0], gains = [-24, 0], rtimes = [0.1, 2.0]; var frequencies, amplitudes, ringTimes; var numModes = 20; var density = 20; // grain/klank density var rttFreq = 10 * density; // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic transforming via % transformer".format(~transformer1).postln; "Ambisonic transforming via % transformer".format(~transformer2).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // calculate klank args frequencies = Array.rand(numModes, freqs.at(0), freqs.at(1)).sort; amplitudes = Array.rand(numModes, gains.at(0), gains.at(1)).sort.reverse.dbamp; ringTimes = Array.rand(numModes, rtimes.at(0), rtimes.at(1)).sort.reverse; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = Dust.ar(Array.fill(4, density / 4)); // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform 1 (rtt) sig = FoaTransform.ar( sig, ~transformer1, LFSaw.ar(rttFreq, pi, add: pi), LFSaw.ar(rttFreq**(1/3), pi, add: pi), LFSaw.ar(rttFreq**(2/3), pi, add: pi) ); // ------------------------------------------------------------ // Klank sig = gain.dbamp * Klank.ar( `[ frequencies, amplitudes, ringTimes ], sig ); // ------------------------------------------------------------ // transform 2 (push) sig = FoaTransform.ar(sig, ~transformer2, angle, azim); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // ------------------------------------------------------------
Here we encode the mono component of a stereo soundfile via the FoaEncoderKernel: *newSpread encoder. FoaPush is used to "push" the soundfield so that it becomes a planewave (infinite distance, in an anechoic environment) arriving from some direction. This technique gives the opportunity to continuously modulate between a directional and a spatially spread soundfield. Additionally, FoaRTT is used to continuously reorient the frequency spread soundfield so that individual frequencies are moved throughout the space, and the complete soundfield is constantly in motion.
The soundfield is controlled by MouseX and MouseY, where MouseX specifies the incident azimuth angle (pi to -pi; left to right of display) and MouseY the FoaPush angle (0 to pi/2; bottom to top of display). With the mouse at the bottom of the display, the soundfield remains decorrelated. Placed at the top of the display, the soundfield becomes directional, and varying left/right position will vary the incident azimuth of the resulting planewave.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // push and rtt transformer // spreader encoder // stereo soundfile read from disk // define encoder matrix ~encoder = FoaEncoderKernel.newSpread(0000) ~encoder = FoaEncoderKernel.newSpread(0001) ~encoder = FoaEncoderKernel.newSpread(0006) ~encoder = FoaEncoderKernel.newSpread(0008) ~encoder = FoaEncoderKernel.newSpread(0010) ~encoder = FoaEncoderKernel.newSpread(0012) // free kernel (when you swap encoders!) ~encoder.free // inspect ~encoder ~encoder.kind ~encoder.numChannels ~encoder.dirChannels.raddeg // read a whole sound into memory // remember to free the buffer later! // (boot the server, if you haven't!) ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/stereo/The_City_Waites-The_Downfall.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/stereo/The_City_Waites-An_Old.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/stereo/Aurora_Surgit-Lux_Aeterna.wav") ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/stereo/Aurora_Surgit-Dies_Irae.wav") // free buffer (when you swap buffers!) ~sndbuf.free ( { var sig; // audio signal var angle, azim; // angle and azimuth control // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.kind).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // angle ---> top = push to plane wave // bottom = omni-directional angle = MouseY.kr(pi/2, 0); // azimuth -> hard left = back // centre = centre // hard right = back azim = MouseX.kr(pi, -pi); // ------------------------------------------------------------ // test sig sig = PlayBuf.ar(~sndbuf.numChannels, ~sndbuf, BufRateScale.kr(~sndbuf), doneAction:2); // soundfile sig = 0.5 * sig.sum; // to mono // ------------------------------------------------------------ // encode sig = FoaEncode.ar(sig, ~encoder); // ------------------------------------------------------------ // transform sig = FoaRTT.ar(sig, LFNoise2.kr(1.0/5.0, pi), LFNoise2.kr(1.0/5.0, pi), LFNoise2.kr(1.0/5.0, pi) ); sig = FoaTransform.ar(sig, 'push', angle, azim); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // free buffer ~sndbuf.free // free kernel ~encoder.free // ------------------------------------------------------------
With this example we encode four channels of comb filtered Dust as planewaves arriving from the cardinal directions. FoaFocusX, FoaZoomX and FoaPushX are used to distort the soundfield. At extremes, encoded planewaves are distorted to arrive from the same direction. This example allows one to get compare these transforms on the x-axis.
The soundfield is controlled by MouseY, with angle varying between -pi/2 and pi/2. With the mouse in the centre of the display, the soundfield remains unchanged. Placed at the top or bottom of the display, the soundfield is distorted.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // x-axis transformers // comb filtered dust noise source, at cardinal points // define encoder matricies, for each cardinal point ( ~encoder = [ FoaEncoderMatrix.newDirection, FoaEncoderMatrix.newDirection(pi/2), FoaEncoderMatrix.newDirection(pi), FoaEncoderMatrix.newDirection(pi.neg/2) ] ) // choose transformer ~transformer = 'focusX' ~transformer = 'zoomX' ~transformer = 'pushX' ( { var sig; // audio signal var angle; // angle control var freq; var density = 10; // grain density // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.at(0).kind).postln; "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // frequencies freq = 220 * [ 4, 5, 7, 6 ] / 4; // angle ---> top = pi/2 // bottom = -pi/2 angle = MouseY.kr(pi/2, pi.neg/2); // ------------------------------------------------------------ // test sig sig = Dust.ar(Array.fill(4, density / 4)); // ------------------------------------------------------------ // comb filter sig = BPF.ar(sig, freq, mul: 18.dbamp); sig = CombL.ar(sig, freq.reciprocal, freq.reciprocal, mul: 9.neg.dbamp); // ------------------------------------------------------------ // encode sig = Mix.fill(sig.numChannels, { arg i; FoaEncode.ar(sig.at(i), ~encoder.at(i)) }); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, angle); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // ------------------------------------------------------------
With with the above example we encode four channels of comb filtered Dust as planewaves arriving from the cardinal directions. FoaZoomY and FoaAsymmetry are used to distort the soundfield. At extremes, encoded planewaves are distorted to arrive from the same direction. This example allows one to get compare these transforms on the x-axis.
The soundfield is controlled by MouseX, with angle varying between -pi/2 and pi/2. With the mouse in the centre of the display, the soundfield remains unchanged. Placed at the left or right of the display, the soundfield is distorted.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // y-axis transformers // comb filtered dust noise source, at cardinal points // define encoder matricies, for each cardinal point ( ~encoder = [ FoaEncoderMatrix.newDirection, FoaEncoderMatrix.newDirection(pi/2), FoaEncoderMatrix.newDirection(pi), FoaEncoderMatrix.newDirection(pi.neg/2) ] ) // choose transformer ~transformer = 'balance' ~transformer = 'asymmetry' ( { var sig; // audio signal var angle; // angle control var freq; var density = 10; // grain density // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.at(0).kind).postln; "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // frequencies freq = 220 * [ 4, 5, 7, 6 ] / 4; // angle ---> left = pi/2 // right = -pi/2 angle = MouseX.kr(pi/2, pi.neg/2); // ------------------------------------------------------------ // test sig sig = Dust.ar(Array.fill(4, density / 4)); // ------------------------------------------------------------ // comb filter sig = BPF.ar(sig, freq, mul: 18.dbamp); sig = CombL.ar(sig, freq.reciprocal, freq.reciprocal, mul: 9.neg.dbamp); // ------------------------------------------------------------ // encode sig = Mix.fill(sig.numChannels, { arg i; FoaEncode.ar(sig.at(i), ~encoder.at(i)) }); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, angle); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // ------------------------------------------------------------
FoaProximity facilitates the introduction of the proximity effect to encoded signals. At extremes, the proximity effect introduces a strong bass boost, as well as phase differences. The proximity effect can be an important contributor to perceptions of nearness.
The soundfield is controlled by MouseY, with distance varying between 0.05 and 0.5 meter. With the mouse at the bottom of the display, the soundfield receives the strongest effect, contributing to as sense of nearness.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // proximity transform // comb filtered dust noise source, panned across the front // define encoder matricies ( ~encoder = [ FoaEncoderMatrix.newDirection(pi/6), FoaEncoderMatrix.newDirection(pi/12), FoaEncoderMatrix.newDirection(pi.neg/12), FoaEncoderMatrix.newDirection(pi.neg/6) ] ) // choose transformer ~transformer = 'proximity' ( { var sig; // audio signal var dist; // distance control var freq; var density = 10; // grain density // display encoder and decoder "Ambisonic encoding via % encoder".format(~encoder.at(0).kind).postln; "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // frequencies freq = 220 * [ 4, 5, 7, 6 ] / 4; // dist ---> top = 0.5 // bottom = 0.05 dist = MouseY.kr(0.5, 0.05); // ------------------------------------------------------------ // test sig sig = Dust.ar(Array.fill(4, density / 4)); // ------------------------------------------------------------ // comb filter sig = BPF.ar(sig, freq, mul: 18.dbamp); sig = CombL.ar(sig, freq.reciprocal, freq.reciprocal, mul: 9.neg.dbamp); // ------------------------------------------------------------ // encode sig = Mix.fill(sig.numChannels, { arg i; FoaEncode.ar(sig.at(i), ~encoder.at(i)) }); // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, dist); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // ------------------------------------------------------------
FoaNFC facilitates the reduction or removal of the proximity effect from encoded signals. The proximity effect can be an important contributor to perceptions of nearness.
The soundfield is controlled by MouseY, with distance varying between 0.5 and 0.05 meter. With the mouse at the top of the display, the soundfield receives the strongest effect (removal), contributing to as sense of distance.
If you haven't already choosen a ~decoder
and defined ~renderDecode
, do so now.
// ------------------------------------------------------------ // NFC transform // b-format soundfile read from disk // read a whole sound into memory // remember to free the buffer later! // (boot the server, if you haven't!) ~sndbuf = Buffer.read(s, Atk.userSoundsDir ++ "/b-format/Anderson-Nearfield.wav") // choose transformer ~transformer = 'nfc' ( { var sig; // audio signal var dist; // distance control // display encoder and decoder "Ambisonic transforming via % transformer".format(~transformer).postln; "Ambisonic decoding via % decoder".format(~decoder.kind).postln; // dist ---> top = 0.05 // bottom = 1.0 dist = MouseY.kr(0.05, 1.0); // ------------------------------------------------------------ // test sig sig = PlayBuf.ar(~sndbuf.numChannels, ~sndbuf, BufRateScale.kr(~sndbuf), doneAction:2); // soundfile // ------------------------------------------------------------ // transform sig = FoaTransform.ar(sig, ~transformer, dist); // ------------------------------------------------------------ // decode (via ~renderDecode) ~renderDecode.value(sig, ~decoder) }.scope; ) // free buffer ~sndbuf.free // ------------------------------------------------------------