]> git.tdb.fi Git - ext/openal.git/blob - core/mixer/mixer_neon.cpp
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / core / mixer / mixer_neon.cpp
1 #include "config.h"
2
3 #include <arm_neon.h>
4
5 #include <cmath>
6 #include <limits>
7
8 #include "alnumeric.h"
9 #include "core/bsinc_defs.h"
10 #include "core/cubic_defs.h"
11 #include "defs.h"
12 #include "hrtfbase.h"
13
14 struct NEONTag;
15 struct LerpTag;
16 struct CubicTag;
17 struct BSincTag;
18 struct FastBSincTag;
19
20
21 #if defined(__GNUC__) && !defined(__clang__) && !defined(__ARM_NEON)
22 #pragma GCC target("fpu=neon")
23 #endif
24
25 namespace {
26
27 constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits};
28 constexpr uint BSincPhaseDiffOne{1 << BSincPhaseDiffBits};
29 constexpr uint BSincPhaseDiffMask{BSincPhaseDiffOne - 1u};
30
31 constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits};
32 constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits};
33 constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u};
34
35 inline float32x4_t set_f4(float l0, float l1, float l2, float l3)
36 {
37     float32x4_t ret{vmovq_n_f32(l0)};
38     ret = vsetq_lane_f32(l1, ret, 1);
39     ret = vsetq_lane_f32(l2, ret, 2);
40     ret = vsetq_lane_f32(l3, ret, 3);
41     return ret;
42 }
43
44 inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs,
45     const float left, const float right)
46 {
47     float32x4_t leftright4;
48     {
49         float32x2_t leftright2{vmov_n_f32(left)};
50         leftright2 = vset_lane_f32(right, leftright2, 1);
51         leftright4 = vcombine_f32(leftright2, leftright2);
52     }
53
54     ASSUME(IrSize >= MinIrLength);
55     for(size_t c{0};c < IrSize;c += 2)
56     {
57         float32x4_t vals = vld1q_f32(&Values[c][0]);
58         float32x4_t coefs = vld1q_f32(&Coeffs[c][0]);
59
60         vals = vmlaq_f32(vals, coefs, leftright4);
61
62         vst1q_f32(&Values[c][0], vals);
63     }
64 }
65
66 force_inline void MixLine(const al::span<const float> InSamples, float *RESTRICT dst,
67     float &CurrentGain, const float TargetGain, const float delta, const size_t min_len,
68     const size_t aligned_len, size_t Counter)
69 {
70     float gain{CurrentGain};
71     const float step{(TargetGain-gain) * delta};
72
73     size_t pos{0};
74     if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
75         gain = TargetGain;
76     else
77     {
78         float step_count{0.0f};
79         /* Mix with applying gain steps in aligned multiples of 4. */
80         if(size_t todo{min_len >> 2})
81         {
82             const float32x4_t four4{vdupq_n_f32(4.0f)};
83             const float32x4_t step4{vdupq_n_f32(step)};
84             const float32x4_t gain4{vdupq_n_f32(gain)};
85             float32x4_t step_count4{vdupq_n_f32(0.0f)};
86             step_count4 = vsetq_lane_f32(1.0f, step_count4, 1);
87             step_count4 = vsetq_lane_f32(2.0f, step_count4, 2);
88             step_count4 = vsetq_lane_f32(3.0f, step_count4, 3);
89
90             do {
91                 const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
92                 float32x4_t dry4 = vld1q_f32(&dst[pos]);
93                 dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
94                 step_count4 = vaddq_f32(step_count4, four4);
95                 vst1q_f32(&dst[pos], dry4);
96                 pos += 4;
97             } while(--todo);
98             /* NOTE: step_count4 now represents the next four counts after the
99              * last four mixed samples, so the lowest element represents the
100              * next step count to apply.
101              */
102             step_count = vgetq_lane_f32(step_count4, 0);
103         }
104         /* Mix with applying left over gain steps that aren't aligned multiples of 4. */
105         for(size_t leftover{min_len&3};leftover;++pos,--leftover)
106         {
107             dst[pos] += InSamples[pos] * (gain + step*step_count);
108             step_count += 1.0f;
109         }
110         if(pos == Counter)
111             gain = TargetGain;
112         else
113             gain += step*step_count;
114
115         /* Mix until pos is aligned with 4 or the mix is done. */
116         for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
117             dst[pos] += InSamples[pos] * gain;
118     }
119     CurrentGain = gain;
120
121     if(!(std::abs(gain) > GainSilenceThreshold))
122         return;
123     if(size_t todo{(InSamples.size()-pos) >> 2})
124     {
125         const float32x4_t gain4 = vdupq_n_f32(gain);
126         do {
127             const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
128             float32x4_t dry4 = vld1q_f32(&dst[pos]);
129             dry4 = vmlaq_f32(dry4, val4, gain4);
130             vst1q_f32(&dst[pos], dry4);
131             pos += 4;
132         } while(--todo);
133     }
134     for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
135         dst[pos] += InSamples[pos] * gain;
136 }
137
138 } // namespace
139
140 template<>
141 void Resample_<LerpTag,NEONTag>(const InterpState*, const float *RESTRICT src, uint frac,
142     const uint increment, const al::span<float> dst)
143 {
144     ASSUME(frac < MixerFracOne);
145
146     const int32x4_t increment4 = vdupq_n_s32(static_cast<int>(increment*4));
147     const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne);
148     const int32x4_t fracMask4 = vdupq_n_s32(MixerFracMask);
149     alignas(16) uint pos_[4], frac_[4];
150     int32x4_t pos4, frac4;
151
152     InitPosArrays(frac, increment, frac_, pos_);
153     frac4 = vld1q_s32(reinterpret_cast<int*>(frac_));
154     pos4 = vld1q_s32(reinterpret_cast<int*>(pos_));
155
156     auto dst_iter = dst.begin();
157     for(size_t todo{dst.size()>>2};todo;--todo)
158     {
159         const int pos0{vgetq_lane_s32(pos4, 0)};
160         const int pos1{vgetq_lane_s32(pos4, 1)};
161         const int pos2{vgetq_lane_s32(pos4, 2)};
162         const int pos3{vgetq_lane_s32(pos4, 3)};
163         const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])};
164         const float32x4_t val2{set_f4(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])};
165
166         /* val1 + (val2-val1)*mu */
167         const float32x4_t r0{vsubq_f32(val2, val1)};
168         const float32x4_t mu{vmulq_f32(vcvtq_f32_s32(frac4), fracOne4)};
169         const float32x4_t out{vmlaq_f32(val1, mu, r0)};
170
171         vst1q_f32(dst_iter, out);
172         dst_iter += 4;
173
174         frac4 = vaddq_s32(frac4, increment4);
175         pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, MixerFracBits));
176         frac4 = vandq_s32(frac4, fracMask4);
177     }
178
179     if(size_t todo{dst.size()&3})
180     {
181         src += static_cast<uint>(vgetq_lane_s32(pos4, 0));
182         frac = static_cast<uint>(vgetq_lane_s32(frac4, 0));
183
184         do {
185             *(dst_iter++) = lerpf(src[0], src[1], static_cast<float>(frac) * (1.0f/MixerFracOne));
186
187             frac += increment;
188             src  += frac>>MixerFracBits;
189             frac &= MixerFracMask;
190         } while(--todo);
191     }
192 }
193
194 template<>
195 void Resample_<CubicTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
196     const uint increment, const al::span<float> dst)
197 {
198     ASSUME(frac < MixerFracOne);
199
200     const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter);
201
202     src -= 1;
203     for(float &out_sample : dst)
204     {
205         const uint pi{frac >> CubicPhaseDiffBits};
206         const float pf{static_cast<float>(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)};
207         const float32x4_t pf4{vdupq_n_f32(pf)};
208
209         /* Apply the phase interpolated filter. */
210
211         /* f = fil + pf*phd */
212         const float32x4_t f4 = vmlaq_f32(vld1q_f32(filter[pi].mCoeffs), pf4,
213             vld1q_f32(filter[pi].mDeltas));
214         /* r = f*src */
215         float32x4_t r4{vmulq_f32(f4, vld1q_f32(src))};
216
217         r4 = vaddq_f32(r4, vrev64q_f32(r4));
218         out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
219
220         frac += increment;
221         src  += frac>>MixerFracBits;
222         frac &= MixerFracMask;
223     }
224 }
225
226 template<>
227 void Resample_<BSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
228     const uint increment, const al::span<float> dst)
229 {
230     const float *const filter{state->bsinc.filter};
231     const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)};
232     const size_t m{state->bsinc.m};
233     ASSUME(m > 0);
234     ASSUME(frac < MixerFracOne);
235
236     src -= state->bsinc.l;
237     for(float &out_sample : dst)
238     {
239         // Calculate the phase index and factor.
240         const uint pi{frac >> BSincPhaseDiffBits};
241         const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
242
243         // Apply the scale and phase interpolated filter.
244         float32x4_t r4{vdupq_n_f32(0.0f)};
245         {
246             const float32x4_t pf4{vdupq_n_f32(pf)};
247             const float *RESTRICT fil{filter + m*pi*2};
248             const float *RESTRICT phd{fil + m};
249             const float *RESTRICT scd{fil + BSincPhaseCount*2*m};
250             const float *RESTRICT spd{scd + m};
251             size_t td{m >> 2};
252             size_t j{0u};
253
254             do {
255                 /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */
256                 const float32x4_t f4 = vmlaq_f32(
257                     vmlaq_f32(vld1q_f32(&fil[j]), sf4, vld1q_f32(&scd[j])),
258                     pf4, vmlaq_f32(vld1q_f32(&phd[j]), sf4, vld1q_f32(&spd[j])));
259                 /* r += f*src */
260                 r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j]));
261                 j += 4;
262             } while(--td);
263         }
264         r4 = vaddq_f32(r4, vrev64q_f32(r4));
265         out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
266
267         frac += increment;
268         src  += frac>>MixerFracBits;
269         frac &= MixerFracMask;
270     }
271 }
272
273 template<>
274 void Resample_<FastBSincTag,NEONTag>(const InterpState *state, const float *RESTRICT src, uint frac,
275     const uint increment, const al::span<float> dst)
276 {
277     const float *const filter{state->bsinc.filter};
278     const size_t m{state->bsinc.m};
279     ASSUME(m > 0);
280     ASSUME(frac < MixerFracOne);
281
282     src -= state->bsinc.l;
283     for(float &out_sample : dst)
284     {
285         // Calculate the phase index and factor.
286         const uint pi{frac >> BSincPhaseDiffBits};
287         const float pf{static_cast<float>(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)};
288
289         // Apply the phase interpolated filter.
290         float32x4_t r4{vdupq_n_f32(0.0f)};
291         {
292             const float32x4_t pf4{vdupq_n_f32(pf)};
293             const float *RESTRICT fil{filter + m*pi*2};
294             const float *RESTRICT phd{fil + m};
295             size_t td{m >> 2};
296             size_t j{0u};
297
298             do {
299                 /* f = fil + pf*phd */
300                 const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j]));
301                 /* r += f*src */
302                 r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j]));
303                 j += 4;
304             } while(--td);
305         }
306         r4 = vaddq_f32(r4, vrev64q_f32(r4));
307         out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
308
309         frac += increment;
310         src  += frac>>MixerFracBits;
311         frac &= MixerFracMask;
312     }
313 }
314
315
316 template<>
317 void MixHrtf_<NEONTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
318     const MixHrtfFilter *hrtfparams, const size_t BufferSize)
319 { MixHrtfBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); }
320
321 template<>
322 void MixHrtfBlend_<NEONTag>(const float *InSamples, float2 *AccumSamples, const uint IrSize,
323     const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize)
324 {
325     MixHrtfBlendBase<ApplyCoeffs>(InSamples, AccumSamples, IrSize, oldparams, newparams,
326         BufferSize);
327 }
328
329 template<>
330 void MixDirectHrtf_<NEONTag>(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
331     const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
332     float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize)
333 {
334     MixDirectHrtfBase<ApplyCoeffs>(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState,
335         IrSize, BufferSize);
336 }
337
338
339 template<>
340 void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
341     float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos)
342 {
343     const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
344     const auto min_len = minz(Counter, InSamples.size());
345     const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
346
347     for(FloatBufferLine &output : OutBuffer)
348         MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++,
349             *TargetGains++, delta, min_len, aligned_len, Counter);
350 }
351
352 template<>
353 void Mix_<NEONTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
354     const float TargetGain, const size_t Counter)
355 {
356     const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
357     const auto min_len = minz(Counter, InSamples.size());
358     const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
359
360     MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len,
361         aligned_len, Counter);
362 }