-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPlugin_Spatializer.cpp
288 lines (247 loc) · 11.4 KB
/
Plugin_Spatializer.cpp
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Please note that this will only work on Unity 5.2 or higher.
#include "AudioPluginUtil.h"
extern float hrtfSrcData[];
extern float reverbmixbuffer[];
namespace Spatializer
{
enum
{
P_AUDIOSRCATTN,
P_FIXEDVOLUME,
P_CUSTOMFALLOFF,
P_NUM
};
const int HRTFLEN = 512;
const float GAINCORRECTION = 2.0f;
class HRTFData
{
struct CircleCoeffs
{
int numangles;
float* hrtf;
float* angles;
void GetHRTF(UnityComplexNumber* h, float angle, float mix)
{
int index1 = 0;
while (index1 < numangles && angles[index1] < angle)
index1++;
if (index1 > 0)
index1--;
int index2 = (index1 + 1) % numangles;
float* hrtf1 = hrtf + HRTFLEN * 4 * index1;
float* hrtf2 = hrtf + HRTFLEN * 4 * index2;
float f = (angle - angles[index1]) / (angles[index2] - angles[index1]);
for (int n = 0; n < HRTFLEN * 2; n++)
{
h[n].re += (hrtf1[0] + (hrtf2[0] - hrtf1[0]) * f - h[n].re) * mix;
h[n].im += (hrtf1[1] + (hrtf2[1] - hrtf1[1]) * f - h[n].im) * mix;
hrtf1 += 2;
hrtf2 += 2;
}
}
};
public:
CircleCoeffs hrtfChannel[2][14];
public:
HRTFData()
{
float* p = hrtfSrcData;
for (int c = 0; c < 2; c++)
{
for (int e = 0; e < 14; e++)
{
CircleCoeffs& coeffs = hrtfChannel[c][e];
coeffs.numangles = (int)(*p++);
coeffs.angles = p;
p += coeffs.numangles;
coeffs.hrtf = new float[coeffs.numangles * HRTFLEN * 4];
float* dst = coeffs.hrtf;
UnityComplexNumber h[HRTFLEN * 2];
for (int a = 0; a < coeffs.numangles; a++)
{
memset(h, 0, sizeof(h));
for (int n = 0; n < HRTFLEN; n++)
h[n + HRTFLEN].re = p[n];
p += HRTFLEN;
FFT::Forward(h, HRTFLEN * 2, false);
for (int n = 0; n < HRTFLEN * 2; n++)
{
*dst++ = h[n].re;
*dst++ = h[n].im;
}
}
}
}
}
};
static HRTFData sharedData;
struct InstanceChannel
{
UnityComplexNumber h[HRTFLEN * 2];
UnityComplexNumber x[HRTFLEN * 2];
UnityComplexNumber y[HRTFLEN * 2];
float buffer[HRTFLEN * 2];
};
struct EffectData
{
float p[P_NUM];
InstanceChannel ch[2];
};
inline bool IsHostCompatible(UnityAudioEffectState* state)
{
// Somewhat convoluted error checking here because hostapiversion is only supported from SDK version 1.03 (i.e. Unity 5.2) and onwards.
// Since we are only checking for version 0x010300 here, we can't use newer fields in the UnityAudioSpatializerData struct, such as minDistance and maxDistance.
return
state->structsize >= sizeof(UnityAudioEffectState) &&
state->hostapiversion >= 0x010300;
}
int InternalRegisterEffectDefinition(UnityAudioEffectDefinition& definition)
{
int numparams = P_NUM;
definition.paramdefs = new UnityAudioParameterDefinition[numparams];
RegisterParameter(definition, "AudioSrc Attn", "", 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, P_AUDIOSRCATTN, "AudioSource distance attenuation");
RegisterParameter(definition, "Fixed Volume", "", 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, P_FIXEDVOLUME, "Fixed volume amount");
RegisterParameter(definition, "Custom Falloff", "", 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, P_CUSTOMFALLOFF, "Custom volume falloff amount (logarithmic)");
definition.flags |= UnityAudioEffectDefinitionFlags_IsSpatializer;
return numparams;
}
static UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK DistanceAttenuationCallback(UnityAudioEffectState* state, float distanceIn, float attenuationIn, float* attenuationOut)
{
EffectData* data = state->GetEffectData<EffectData>();
*attenuationOut =
data->p[P_AUDIOSRCATTN] * attenuationIn +
data->p[P_FIXEDVOLUME] +
data->p[P_CUSTOMFALLOFF] * (1.0f / FastMax(1.0f, distanceIn));
return UNITY_AUDIODSP_OK;
}
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK CreateCallback(UnityAudioEffectState* state)
{
EffectData* effectdata = new EffectData;
memset(effectdata, 0, sizeof(EffectData));
state->effectdata = effectdata;
if (IsHostCompatible(state))
state->spatializerdata->distanceattenuationcallback = DistanceAttenuationCallback;
InitParametersFromDefinitions(InternalRegisterEffectDefinition, effectdata->p);
return UNITY_AUDIODSP_OK;
}
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK ReleaseCallback(UnityAudioEffectState* state)
{
EffectData* data = state->GetEffectData<EffectData>();
delete data;
return UNITY_AUDIODSP_OK;
}
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK SetFloatParameterCallback(UnityAudioEffectState* state, int index, float value)
{
EffectData* data = state->GetEffectData<EffectData>();
if (index >= P_NUM)
return UNITY_AUDIODSP_ERR_UNSUPPORTED;
data->p[index] = value;
return UNITY_AUDIODSP_OK;
}
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK GetFloatParameterCallback(UnityAudioEffectState* state, int index, float* value, char *valuestr)
{
EffectData* data = state->GetEffectData<EffectData>();
if (index >= P_NUM)
return UNITY_AUDIODSP_ERR_UNSUPPORTED;
if (value != NULL)
*value = data->p[index];
if (valuestr != NULL)
valuestr[0] = 0;
return UNITY_AUDIODSP_OK;
}
int UNITY_AUDIODSP_CALLBACK GetFloatBufferCallback(UnityAudioEffectState* state, const char* name, float* buffer, int numsamples)
{
return UNITY_AUDIODSP_OK;
}
static void GetHRTF(int channel, UnityComplexNumber* h, float azimuth, float elevation)
{
float e = FastClip(elevation * 0.1f + 4, 0, 12);
float f = floorf(e);
int index1 = (int)f;
if (index1 < 0)
index1 = 0;
else if (index1 > 12)
index1 = 12;
int index2 = index1 + 1;
if (index2 > 12)
index2 = 12;
sharedData.hrtfChannel[channel][index1].GetHRTF(h, azimuth, 1.0f);
sharedData.hrtfChannel[channel][index2].GetHRTF(h, azimuth, e - f);
}
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK ProcessCallback(UnityAudioEffectState* state, float* inbuffer, float* outbuffer, unsigned int length, int inchannels, int outchannels)
{
// Check that I/O formats are right and that the host API supports this feature
if (inchannels != 2 || outchannels != 2 ||
!IsHostCompatible(state) || state->spatializerdata == NULL)
{
memcpy(outbuffer, inbuffer, length * outchannels * sizeof(float));
return UNITY_AUDIODSP_OK;
}
EffectData* data = state->GetEffectData<EffectData>();
static const float kRad2Deg = 180.0f / kPI;
float* m = state->spatializerdata->listenermatrix;
float* s = state->spatializerdata->sourcematrix;
// Currently we ignore source orientation and only use the position
float px = s[12];
float py = s[13];
float pz = s[14];
float dir_x = m[0] * px + m[4] * py + m[8] * pz + m[12];
float dir_y = m[1] * px + m[5] * py + m[9] * pz + m[13];
float dir_z = m[2] * px + m[6] * py + m[10] * pz + m[14];
float azimuth = (fabsf(dir_z) < 0.001f) ? 0.0f : atan2f(dir_x, dir_z);
if (azimuth < 0.0f)
azimuth += 2.0f * kPI;
azimuth = FastClip(azimuth * kRad2Deg, 0.0f, 360.0f);
float elevation = atan2f(dir_y, sqrtf(dir_x * dir_x + dir_z * dir_z) + 0.001f) * kRad2Deg;
float spatialblend = state->spatializerdata->spatialblend;
float reverbmix = state->spatializerdata->reverbzonemix;
GetHRTF(0, data->ch[0].h, azimuth, elevation);
GetHRTF(1, data->ch[1].h, azimuth, elevation);
// From the FMOD documentation:
// A spread angle of 0 makes the stereo sound mono at the point of the 3D emitter.
// A spread angle of 90 makes the left part of the stereo sound place itself at 45 degrees to the left and the right part 45 degrees to the right.
// A spread angle of 180 makes the left part of the stero sound place itself at 90 degrees to the left and the right part 90 degrees to the right.
// A spread angle of 360 makes the stereo sound mono at the opposite speaker location to where the 3D emitter should be located (by moving the left part 180 degrees left and the right part 180 degrees right). So in this case, behind you when the sound should be in front of you!
// Note that FMOD performs the spreading and panning in one go. We can't do this here due to the way that impulse-based spatialization works, so we perform the spread calculations on the left/right source signals before they enter the convolution processing.
// That way we can still use it to control how the source signal downmixing takes place.
float spread = cosf(state->spatializerdata->spread * kPI / 360.0f);
float spreadmatrix[2] = { 2.0f - spread, spread };
float* reverb = reverbmixbuffer;
for (int sampleOffset = 0; sampleOffset < length; sampleOffset += HRTFLEN)
{
for (int c = 0; c < 2; c++)
{
// stereopan is in the [-1; 1] range, this acts the way fmod does it for stereo
float stereopan = 1.0f - ((c == 0) ? FastMax(0.0f, state->spatializerdata->stereopan) : FastMax(0.0f, -state->spatializerdata->stereopan));
InstanceChannel& ch = data->ch[c];
for (int n = 0; n < HRTFLEN; n++)
{
float left = inbuffer[n * 2];
float right = inbuffer[n * 2 + 1];
ch.buffer[n] = ch.buffer[n + HRTFLEN];
ch.buffer[n + HRTFLEN] = left * spreadmatrix[c] + right * spreadmatrix[1 - c];
}
for (int n = 0; n < HRTFLEN * 2; n++)
{
ch.x[n].re = ch.buffer[n];
ch.x[n].im = 0.0f;
}
FFT::Forward(ch.x, HRTFLEN * 2, false);
for (int n = 0; n < HRTFLEN * 2; n++)
UnityComplexNumber::Mul<float, float, float>(ch.x[n], ch.h[n], ch.y[n]);
FFT::Backward(ch.y, HRTFLEN * 2, false);
for (int n = 0; n < HRTFLEN; n++)
{
float s = inbuffer[n * 2 + c] * stereopan;
float y = s + (ch.y[n].re * GAINCORRECTION - s) * spatialblend;
outbuffer[n * 2 + c] = y;
reverb[n * 2 + c] += y * reverbmix;
}
}
inbuffer += HRTFLEN * 2;
outbuffer += HRTFLEN * 2;
reverb += HRTFLEN * 2;
}
return UNITY_AUDIODSP_OK;
}
}