1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
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;
::
|