title:: High order ambisonics using IEM Plugins
summary:: Working with High Order Ambisonics in SuperCollider using the IEM Plugins and the SuperCollider VST package by IEM
related:: Classes/VSTPluginController, Classes/VSTPluginGui, Classes/VSTPlugin
categories:: UGens>FX

DESCRIPTION::
In this example we will test out an high order ambisonic workflow using plugins and SuperCollider.

The workflow usually in ambisonics is like this:
Sound source -> [Fx] -> Encoder (panning/conversion to ambisonic domain) -> [Fx] -> [Master FX] -> Decoding (to arbitrary speaker setups)

We will make functions that will allocate encoder "voices" as groups in SuperCollider and put our sound sources into those, then route it all through an fx group and finally through a decoder for listening.

We will then manipulate and modulate the VST plugins using SuperCollider LFOs.

subsection:: Dependencies

LIST::
## IEM Plugins: https://plugins.iem.at/
::

section:: Code

code::
(
// search for plugins
VSTPlugin.search(verbose: false);
)

// SynthDefs
(
~order = 3; // set this to the order you want
~numChannels = ((~order + 1)**2).asInteger;
// binaural decoder (~numChannels -> 2) - reads from 'bus' and sums into 'out'
SynthDef.new(\binauralDecoder, { | bus, out = 0 |
	Out.ar(out, VSTPlugin.ar(In.ar(bus, ~numChannels), 2));
}).add;

// stereo encoder (2 -> ~numChannels) - replaces stereo signal with ambisonics signal
SynthDef.new(\stereoEncoder, { | bus = 0 |
	ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, 2), ~numChannels));
}).add;

// ambisonics insert FX (replaces input with output)
SynthDef.new(\ambiFX, { | bus = 0, bypass |
	ReplaceOut.ar(bus, VSTPlugin.ar(In.ar(bus, 2), ~numChannels, bypass));
}).add;

// helper Synth (throws audio from ambi bus to ambi master bus)
SynthDef.new(\ambiThrow, { | from, to |
	Out.ar(to, In.ar(from, ~numChannels));
}).add;

// load sound file (replace with your test sound file)
~buf = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

// play a sound file (in different rates)
SynthDef(\test, { | out = 0, vol = 1.0 |
    Out.ar(out,
		PlayBuf.ar(2, ~buf, BufRateScale.kr(~buf), rate: Rand(0.5, 2), loop: 1) * vol;
    )
}).add;
)

// create ambisonic master section
(
// bus + group
~ambiMasterBus = Bus.audio(s, ~numChannels);
~ambiMasterGroup = Group.new;
// binaural decoder (writes to master output)
~decoder = VSTPluginController(Synth(\binauralDecoder, [\bus, ~ambiMasterBus, \out, 0],
	target: ~ambiMasterGroup, addAction: \addToTail)).open("BinauralDecoder");
// a group for ambisonic master effects
~ambiMasterFXGroup = Group.before(~decoder.synth);
)

// create ambisonic busses
(
// N ambisonic busses (3rd order) with stereo encoder. add ambi groups *before* master group!
~numBus = 4; // change this if you want
~ambiBus = Array.newClear(~numBus);
~ambiGroup = Array.newClear(~numBus);
~encoder = Array.newClear(~numBus);
~numBus.do { |i|
	~ambiBus[i] = Bus.audio(s, ~numChannels);
	~ambiGroup[i] = Group.before(~ambiMasterGroup);
	// sound source (head)
	Synth.new(\test, [\out, ~ambiBus[i], \vol, 1.0 / ~numBus], ~ambiGroup[i], addAction: \addToHead);
	// encoder (after sound source)
	~encoder[i] = VSTPluginController(Synth(\stereoEncoder, [\bus, ~ambiBus[i]],
		target: ~ambiGroup[i], addAction: \addToTail));
	// open plugin and randomize azimuth
	~encoder[i].open("StereoEncoder", action: { |self| self.set(6, rand(1.0)) }); // 6 -> azimuth
	// throw to master section (after encoder)
	Synth(\ambiThrow, [\from, ~ambiBus[i], \to, ~ambiMasterBus], target: ~ambiGroup[i], addAction: \addToTail);

	// you can add more sound sources to the head of the group, stereo FX *before* the encoder and ambi FX *after* the encoder:
	// sound source, [sound source] ..., [stereo FX], [stereo FX], ..., ENCODER, [ambi FX], [ambi FX], ..., THROW
}
)

// randomize source positions
~encoder.do(_.set(6, rand(1.0))) // 6 -> azimuth

// move around manually with the SC GUI:
~encoder.do(_.gui);
// move around with the VST editor:
~encoder.do(_.editor);

// modulate azimuth values with random LFOs:
(
~lfoGroup = Group.new;
~lfoBus = ~numBus.collect { Bus.control };
~lfo = ~numBus.collect { |i| { | rate = 0.5 | LFNoise1.kr(rate, 0.5, 0.5) }.play(~lfoGroup, ~lfoBus[i]) };
~encoder.do { | enc, i | enc.map(6, ~lfoBus[i]) }; // map azimuth parameter (nr. 6) to control bus
)

~lfoGroup.set(\rate, exprand(0.1, 4.0));  // change LFO rates
~encoder.do(_.unmap(6)); // unmap

// add an ambisonic master FX
(
~ambiReverb = VSTPluginController(Synth(\ambiFX, [\bus, ~ambiMasterBus, \out, 0],
	target: ~ambiMasterFXGroup)).open("FdnReverb");
)
~ambiReverb.set(0, rand(1.0)); // vary room size
~ambiReverb.set(1, rand(1.0)); // vary reverb time
// bypass it:
~ambiReverb.synth.set(\bypass, 1); // bypass
~ambiReverb.synth.set(\bypass, 0); // process

// Recording
(
// When recording ambisonics, we usually don't want the decoded output.
// Instead, we want the signal while it's still in the ambisonic domain
// (so that we can use it with any kind of decoder in the future), i.e. just before the decoder.
var path = "~/Music/ambisonic-piece-%_o%_%chan.wav".format(Date.getDate.stamp, ~order, ~numChannels).standardizePath;
r = Recorder(s);
// Put the recorder after the master FX group (before the decoder)
r.record(path, ~ambiMasterBus, ~numChannels, ~ambiMasterFXGroup);
)
r.stopRecording;
::
