-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathiem-headrotator.scd
More file actions
166 lines (123 loc) · 4.06 KB
/
iem-headrotator.scd
File metadata and controls
166 lines (123 loc) · 4.06 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
This is an example patch for SuperCollider which uses the IEM plugins as main output fx with the Teensy Head Tracker setup to rotate the sound scene using SceneRotator and decode to headphones using BinauralDecoder.
It expects the SuperCollider user to be using ATK's ambisonics format:
AtkHoa.format;
AtkHoa.refRadius;
---------------------------
DEPENDENCIES:
To make this script work, you need to install the following dependencies:
CC14 for 14 bit midi support:
Quarks.install("CC14");
ATK
Quarks.install("atk-sc3");
And IEM plugins:
"https://plugins.iem.at/".openOS;
And then IEM's VSTPlugin extension for SuperCollider:
"https://git.iem.at/pd/vstplugin/-/releases".openOS;
*/
(
// Set the ambisonic order
~order = 7;
/**********************************************/
s.waitForBoot{
~numChans = ((~order+1)**2).asInteger;
~iemBinDecRefRadius = 3.25;
SynthDef(\headtracker, { |bus|
// HOA input
var sig = In.ar(bus, ~numChans);
// Format exchange from ATK's HOA-format to what IEM expects (ambix) with the binauralDecoder's expected radius.
// (for source, see https://github.com/ambisonictoolkit/atk-sc3/issues/95)
// exchange reference radius
sig = HoaNFCtrl.ar(
in: sig,
encRadius: AtkHoa.refRadius,
decRadius: ~iemBinDecRefRadius,
order: ~order
);
// exchange normalisation
sig = HoaDecodeMatrix.ar(
in: sig,
hoaMatrix: HoaMatrixDecoder.newFormat(\ambix, ~order)
);
/*
the resulting signal can be
fed directly to the IEM BinauralDecoder plugin
and is encoded as:
Ambisonic order: 7
Ambisonic component ordering: ACN
Ambisonic component normalisation: SN3D
Ambisonic reference radius: 3.25
*/
// This will be the SceneRotator
sig = VSTPlugin.ar(sig, ~numChans, id: \sceneRot);
// This will be the BinauralDecoder
sig = VSTPlugin.ar(sig, ~numChans, id: \binauralDec);
ReplaceOut.ar(bus, sig);
}).add;
s.sync;
// This function is called every time the user presses hard stop/CMdPeriod to stop the sound
// It respawns the synths with the VST plugins and remaps the midi controller
~treeFunc = {
"Adding % order ambisonics headtracker to main output".format(~order).postln;
forkIfNeeded{
~headtrackerGroup = Group.after(1);
s.sync;
/*
Open plugins
*/
~headtrackFX = VSTPluginController.collect(Synth(\headtracker, [\bus, 0], ~headtrackerGroup, addAction:\addToTail));
s.sync;
~headtrackFX.sceneRot.open("SceneRotator");
s.sync;
~headtrackFX.binauralDec.open("BinauralDecoder");
/*
Map head tracker to scene rotator
*/
s.sync;
~yawMidi = ~yawMidi ?? {CC14.new(cc1: 16, cc2: 48, chan: 0, fix: true, normalizeValues: true)};
~yawMidi.func_({|val|
if(~headtrackFX.sceneRot.loaded, {
~headtrackFX.sceneRot.set('Yaw Angle', val);
});
});
s.sync;
~pitchMidi = ~pitchMidi ?? {CC14.new(cc1: 17, cc2: 49, chan: 0, fix: true, normalizeValues: true)};
~pitchMidi.func_({|val|
if(~headtrackFX.sceneRot.loaded,{
~headtrackFX.sceneRot.set('Pitch Angle', val);
});
});
s.sync;
~rollMidi = ~rollMidi ?? { CC14.new(cc1: 18, cc2: 50, chan: 0, fix: true, normalizeValues: true)};
~rollMidi.func_({|val|
if(~headtrackFX.sceneRot.loaded,{
~headtrackFX.sceneRot.set('Roll Angle', val)
});
});
}
};
// This will respawn the synth on hardstop/cmd-period. Inspired by SafetyNet
ServerTree.add(~treeFunc, Server.local);
// Connect midi controller
if(MIDIClient.initialized.not, {
"MIDIClient not initialized... initializing now".postln;
MIDIClient.init;
});
MIDIClient.sources.do{|src, srcNum|
if(src.device == "Teensy Head Tracker", {
if(try{MIDIIn.isTeensyHeadTrackerConnected}.isNil, {
if(MIDIClient.sources.any({|e| e.device=="Teensy Head Tracker"}), {
"Connecting Teensy Head Tracker".postln;
MIDIIn.connect(srcNum, src).addUniqueMethod(\isTeensyHeadTrackerConnected, {true});
});
}, {"Teensy Head Tracker is already connected... (device is busy)".postln});
});
};
s.sync;
~treeFunc.value;
s.sync;
~headtrackFX.sceneRot.gui;
// Open guis
// ~headtrackFX.binauralDec.gui;
}
)