1 /*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #ifndef ANDROID_AUDIO_MIXER_OPS_H
18 #define ANDROID_AUDIO_MIXER_OPS_H
19
20 #include <system/audio.h>
21
22 namespace android {
23
24 // Hack to make static_assert work in a constexpr
25 // https://en.cppreference.com/w/cpp/language/if
26 template <int N>
27 inline constexpr bool dependent_false = false;
28
29 /* MixMul is a multiplication operator to scale an audio input signal
30 * by a volume gain, with the formula:
31 *
32 * O(utput) = I(nput) * V(olume)
33 *
34 * The output, input, and volume may have different types.
35 * There are 27 variants, of which 14 are actually defined in an
36 * explicitly templated class.
37 *
38 * The following type variables and the underlying meaning:
39 *
40 * Output type TO: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
41 * Input signal type TI: int32_t (Q4.27) or int16_t (Q.15) or float [-1,1]
42 * Volume type TV: int32_t (U4.28) or int16_t (U4.12) or float [-1,1]
43 *
44 * For high precision audio, only the <TO, TI, TV> = <float, float, float>
45 * needs to be accelerated. This is perhaps the easiest form to do quickly as well.
46 *
47 * A generic version is NOT defined to catch any mistake of using it.
48 */
49
50 template <typename TO, typename TI, typename TV>
51 TO MixMul(TI value, TV volume);
52
53 template <>
54 inline int32_t MixMul<int32_t, int16_t, int16_t>(int16_t value, int16_t volume) {
55 return value * volume;
56 }
57
58 template <>
59 inline int32_t MixMul<int32_t, int32_t, int16_t>(int32_t value, int16_t volume) {
60 return (value >> 12) * volume;
61 }
62
63 template <>
64 inline int32_t MixMul<int32_t, int16_t, int32_t>(int16_t value, int32_t volume) {
65 return value * (volume >> 16);
66 }
67
68 template <>
69 inline int32_t MixMul<int32_t, int32_t, int32_t>(int32_t value, int32_t volume) {
70 return (value >> 12) * (volume >> 16);
71 }
72
73 template <>
74 inline float MixMul<float, float, int16_t>(float value, int16_t volume) {
75 static const float norm = 1. / (1 << 12);
76 return value * volume * norm;
77 }
78
79 template <>
80 inline float MixMul<float, float, int32_t>(float value, int32_t volume) {
81 static const float norm = 1. / (1 << 28);
82 return value * volume * norm;
83 }
84
85 template <>
86 inline int16_t MixMul<int16_t, float, int16_t>(float value, int16_t volume) {
87 return clamp16_from_float(MixMul<float, float, int16_t>(value, volume));
88 }
89
90 template <>
91 inline int16_t MixMul<int16_t, float, int32_t>(float value, int32_t volume) {
92 return clamp16_from_float(MixMul<float, float, int32_t>(value, volume));
93 }
94
95 template <>
96 inline float MixMul<float, int16_t, int16_t>(int16_t value, int16_t volume) {
97 static const float norm = 1. / (1 << (15 + 12));
98 return static_cast<float>(value) * static_cast<float>(volume) * norm;
99 }
100
101 template <>
102 inline float MixMul<float, int16_t, int32_t>(int16_t value, int32_t volume) {
103 static const float norm = 1. / (1ULL << (15 + 28));
104 return static_cast<float>(value) * static_cast<float>(volume) * norm;
105 }
106
107 template <>
108 inline int16_t MixMul<int16_t, int16_t, int16_t>(int16_t value, int16_t volume) {
109 return clamp16(MixMul<int32_t, int16_t, int16_t>(value, volume) >> 12);
110 }
111
112 template <>
113 inline int16_t MixMul<int16_t, int32_t, int16_t>(int32_t value, int16_t volume) {
114 return clamp16(MixMul<int32_t, int32_t, int16_t>(value, volume) >> 12);
115 }
116
117 template <>
118 inline int16_t MixMul<int16_t, int16_t, int32_t>(int16_t value, int32_t volume) {
119 return clamp16(MixMul<int32_t, int16_t, int32_t>(value, volume) >> 12);
120 }
121
122 template <>
123 inline int16_t MixMul<int16_t, int32_t, int32_t>(int32_t value, int32_t volume) {
124 return clamp16(MixMul<int32_t, int32_t, int32_t>(value, volume) >> 12);
125 }
126
127 /* Required for floating point volume. Some are needed for compilation but
128 * are not needed in execution and should be removed from the final build by
129 * an optimizing compiler.
130 */
131 template <>
132 inline float MixMul<float, float, float>(float value, float volume) {
133 return value * volume;
134 }
135
136 template <>
137 inline float MixMul<float, int16_t, float>(int16_t value, float volume) {
138 static const float float_from_q_15 = 1. / (1 << 15);
139 return value * volume * float_from_q_15;
140 }
141
142 template <>
143 inline int32_t MixMul<int32_t, int32_t, float>(int32_t value, float volume) {
144 LOG_ALWAYS_FATAL("MixMul<int32_t, int32_t, float> Runtime Should not be here");
145 return value * volume;
146 }
147
148 template <>
149 inline int32_t MixMul<int32_t, int16_t, float>(int16_t value, float volume) {
150 LOG_ALWAYS_FATAL("MixMul<int32_t, int16_t, float> Runtime Should not be here");
151 static const float u4_12_from_float = (1 << 12);
152 return value * volume * u4_12_from_float;
153 }
154
155 template <>
156 inline int16_t MixMul<int16_t, int16_t, float>(int16_t value, float volume) {
157 LOG_ALWAYS_FATAL("MixMul<int16_t, int16_t, float> Runtime Should not be here");
158 return clamp16_from_float(MixMul<float, int16_t, float>(value, volume));
159 }
160
161 template <>
162 inline int16_t MixMul<int16_t, float, float>(float value, float volume) {
163 return clamp16_from_float(value * volume);
164 }
165
166 /*
167 * MixAccum is used to add into an accumulator register of a possibly different
168 * type. The TO and TI types are the same as MixMul.
169 */
170
171 template <typename TO, typename TI>
MixAccum(TO * auxaccum,TI value)172 inline void MixAccum(TO *auxaccum, TI value) {
173 if (!std::is_same_v<TO, TI>) {
174 LOG_ALWAYS_FATAL("MixAccum type not properly specialized: %zu %zu\n",
175 sizeof(TO), sizeof(TI));
176 }
177 *auxaccum += value;
178 }
179
180 template<>
181 inline void MixAccum<float, int16_t>(float *auxaccum, int16_t value) {
182 static constexpr float norm = 1. / (1 << 15);
183 *auxaccum += norm * value;
184 }
185
186 template<>
187 inline void MixAccum<float, int32_t>(float *auxaccum, int32_t value) {
188 static constexpr float norm = 1. / (1 << 27);
189 *auxaccum += norm * value;
190 }
191
192 template<>
193 inline void MixAccum<int32_t, int16_t>(int32_t *auxaccum, int16_t value) {
194 *auxaccum += value << 12;
195 }
196
197 template<>
198 inline void MixAccum<int32_t, float>(int32_t *auxaccum, float value) {
199 *auxaccum += clampq4_27_from_float(value);
200 }
201
202 /* MixMulAux is just like MixMul except it combines with
203 * an accumulator operation MixAccum.
204 */
205
206 template <typename TO, typename TI, typename TV, typename TA>
MixMulAux(TI value,TV volume,TA * auxaccum)207 inline TO MixMulAux(TI value, TV volume, TA *auxaccum) {
208 MixAccum<TA, TI>(auxaccum, value);
209 return MixMul<TO, TI, TV>(value, volume);
210 }
211
212 /* MIXTYPE is used to determine how the samples in the input frame
213 * are mixed with volume gain into the output frame.
214 * See the volumeRampMulti functions below for more details.
215 */
216 enum {
217 MIXTYPE_MULTI,
218 MIXTYPE_MONOEXPAND,
219 MIXTYPE_MULTI_SAVEONLY,
220 MIXTYPE_MULTI_MONOVOL,
221 MIXTYPE_MULTI_SAVEONLY_MONOVOL,
222 MIXTYPE_MULTI_STEREOVOL,
223 MIXTYPE_MULTI_SAVEONLY_STEREOVOL,
224 MIXTYPE_STEREOEXPAND,
225 };
226
227 /*
228 * TODO: We should work on non-interleaved streams - the
229 * complexity of working on interleaved streams is now getting
230 * too high, and likely limits compiler optimization.
231 */
232 template <int MIXTYPE, int NCHAN,
233 typename TO, typename TI, typename TV,
234 typename F>
stereoVolumeHelper(TO * & out,const TI * & in,const TV * vol,F f)235 void stereoVolumeHelper(TO*& out, const TI*& in, const TV *vol, F f) {
236 static_assert(NCHAN > 0 && NCHAN <= FCC_LIMIT);
237 static_assert(MIXTYPE == MIXTYPE_MULTI_STEREOVOL
238 || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
239 || MIXTYPE == MIXTYPE_STEREOEXPAND
240 || MIXTYPE == MIXTYPE_MONOEXPAND);
241 auto proc = [](auto& a, const auto& b) {
242 if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
243 || MIXTYPE == MIXTYPE_STEREOEXPAND
244 || MIXTYPE == MIXTYPE_MONOEXPAND) {
245 a += b;
246 } else {
247 a = b;
248 }
249 };
250 auto inp = [&in]() -> const TI& {
251 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND
252 || MIXTYPE == MIXTYPE_MONOEXPAND) {
253 return *in;
254 } else {
255 return *in++;
256 }
257 };
258
259 // HALs should only expose the canonical channel masks.
260 proc(*out++, f(inp(), vol[0])); // front left
261 if constexpr (NCHAN == 1) return;
262 proc(*out++, f(inp(), vol[1])); // front right
263 if constexpr (NCHAN == 2) return;
264 if constexpr (NCHAN == 4) {
265 proc(*out++, f(inp(), vol[0])); // back left
266 proc(*out++, f(inp(), vol[1])); // back right
267 return;
268 }
269
270 // TODO: Precompute center volume if not ramping.
271 std::decay_t<TV> center;
272 if constexpr (std::is_floating_point_v<TV>) {
273 center = (vol[0] + vol[1]) * 0.5; // do not use divide
274 } else {
275 center = (vol[0] >> 1) + (vol[1] >> 1); // rounds to 0.
276 }
277 proc(*out++, f(inp(), center)); // center (or 2.1 LFE)
278 if constexpr (NCHAN == 3) return;
279 if constexpr (NCHAN == 5) {
280 proc(*out++, f(inp(), vol[0])); // back left
281 proc(*out++, f(inp(), vol[1])); // back right
282 return;
283 }
284
285 proc(*out++, f(inp(), center)); // lfe
286 proc(*out++, f(inp(), vol[0])); // back left
287 proc(*out++, f(inp(), vol[1])); // back right
288 if constexpr (NCHAN == 6) return;
289 if constexpr (NCHAN == 7) {
290 proc(*out++, f(inp(), center)); // back center
291 return;
292 }
293 // NCHAN == 8
294 proc(*out++, f(inp(), vol[0])); // side left
295 proc(*out++, f(inp(), vol[1])); // side right
296 if constexpr (NCHAN > FCC_8) {
297 // Mutes to zero extended surround channels.
298 // 7.1.4 has the correct behavior.
299 // 22.2 has the behavior that FLC and FRC will be mixed instead
300 // of SL and SR and LFE will be center, not left.
301 for (int i = 8; i < NCHAN; ++i) {
302 // TODO: Consider using android::audio_utils::channels::kSideFromChannelIdx
303 proc(*out++, f(inp(), 0.f));
304 }
305 }
306 }
307
308 /*
309 * The volumeRampMulti and volumeRamp functions take a MIXTYPE
310 * which indicates the per-frame mixing and accumulation strategy.
311 *
312 * MIXTYPE_MULTI:
313 * NCHAN represents number of input and output channels.
314 * TO: int32_t (Q4.27) or float
315 * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
316 * TA: int32_t (Q4.27) or float
317 * TV: int32_t (U4.28) or int16_t (U4.12) or float
318 * vol: represents a volume array.
319 *
320 * This accumulates into the out pointer.
321 *
322 * MIXTYPE_MONOEXPAND:
323 * Single input channel. NCHAN represents number of output channels.
324 * TO: int32_t (Q4.27) or float
325 * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
326 * TA: int32_t (Q4.27) or float
327 * TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float
328 * Input channel count is 1.
329 * vol: represents volume array.
330 * This uses stereo balanced volume vol[0] and vol[1].
331 * Before R, this was a full volume array but was called only for channels <= 2.
332 *
333 * This accumulates into the out pointer.
334 *
335 * MIXTYPE_MULTI_SAVEONLY:
336 * NCHAN represents number of input and output channels.
337 * TO: int16_t (Q.15) or float
338 * TI: int32_t (Q4.27) or int16_t (Q0.15) or float
339 * TA: int32_t (Q4.27) or float
340 * TV/TAV: int32_t (U4.28) or int16_t (U4.12) or float
341 * vol: represents a volume array.
342 *
343 * MIXTYPE_MULTI_SAVEONLY does not accumulate into the out pointer.
344 *
345 * MIXTYPE_MULTI_MONOVOL:
346 * Same as MIXTYPE_MULTI, but uses only volume[0].
347 *
348 * MIXTYPE_MULTI_SAVEONLY_MONOVOL:
349 * Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0].
350 *
351 * MIXTYPE_MULTI_STEREOVOL:
352 * Same as MIXTYPE_MULTI, but uses only volume[0] and volume[1].
353 *
354 * MIXTYPE_MULTI_SAVEONLY_STEREOVOL:
355 * Same as MIXTYPE_MULTI_SAVEONLY, but uses only volume[0] and volume[1].
356 *
357 * MIXTYPE_STEREOEXPAND:
358 * Stereo input channel. NCHAN represents number of output channels.
359 * Expand size 2 array "in" and "vol" to multi-channel output. Note
360 * that the 2 array is assumed to have replicated L+R.
361 *
362 */
363
364 template <int MIXTYPE, int NCHAN,
365 typename TO, typename TI, typename TV, typename TA, typename TAV>
volumeRampMulti(TO * out,size_t frameCount,const TI * in,TA * aux,TV * vol,const TV * volinc,TAV * vola,TAV volainc)366 inline void volumeRampMulti(TO* out, size_t frameCount,
367 const TI* in, TA* aux, TV *vol, const TV *volinc, TAV *vola, TAV volainc)
368 {
369 #ifdef ALOGVV
370 ALOGVV("volumeRampMulti, MIXTYPE:%d\n", MIXTYPE);
371 #endif
372 if (aux != NULL) {
373 do {
374 TA auxaccum = 0;
375 if constexpr (MIXTYPE == MIXTYPE_MULTI) {
376 static_assert(NCHAN <= 2);
377 for (int i = 0; i < NCHAN; ++i) {
378 *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
379 vol[i] += volinc[i];
380 }
381 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
382 static_assert(NCHAN <= 2);
383 for (int i = 0; i < NCHAN; ++i) {
384 *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
385 vol[i] += volinc[i];
386 }
387 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
388 for (int i = 0; i < NCHAN; ++i) {
389 *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
390 }
391 vol[0] += volinc[0];
392 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
393 for (int i = 0; i < NCHAN; ++i) {
394 *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
395 }
396 vol[0] += volinc[0];
397 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
398 || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
399 || MIXTYPE == MIXTYPE_MONOEXPAND
400 || MIXTYPE == MIXTYPE_STEREOEXPAND) {
401 stereoVolumeHelper<MIXTYPE, NCHAN>(
402 out, in, vol, [&auxaccum] (auto &a, const auto &b) {
403 return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
404 });
405 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
406 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
407 vol[0] += volinc[0];
408 vol[1] += volinc[1];
409 } else /* constexpr */ {
410 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
411 }
412 auxaccum /= NCHAN;
413 *aux++ += MixMul<TA, TA, TAV>(auxaccum, *vola);
414 vola[0] += volainc;
415 } while (--frameCount);
416 } else {
417 do {
418 if constexpr (MIXTYPE == MIXTYPE_MULTI) {
419 static_assert(NCHAN <= 2);
420 for (int i = 0; i < NCHAN; ++i) {
421 *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
422 vol[i] += volinc[i];
423 }
424 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
425 static_assert(NCHAN <= 2);
426 for (int i = 0; i < NCHAN; ++i) {
427 *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
428 vol[i] += volinc[i];
429 }
430 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
431 for (int i = 0; i < NCHAN; ++i) {
432 *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
433 }
434 vol[0] += volinc[0];
435 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
436 for (int i = 0; i < NCHAN; ++i) {
437 *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
438 }
439 vol[0] += volinc[0];
440 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
441 || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
442 || MIXTYPE == MIXTYPE_MONOEXPAND
443 || MIXTYPE == MIXTYPE_STEREOEXPAND) {
444 stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
445 return MixMul<TO, TI, TV>(a, b);
446 });
447 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
448 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
449 vol[0] += volinc[0];
450 vol[1] += volinc[1];
451 } else /* constexpr */ {
452 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
453 }
454 } while (--frameCount);
455 }
456 }
457
458 template <int MIXTYPE, int NCHAN,
459 typename TO, typename TI, typename TV, typename TA, typename TAV>
volumeMulti(TO * out,size_t frameCount,const TI * in,TA * aux,const TV * vol,TAV vola)460 inline void volumeMulti(TO* out, size_t frameCount,
461 const TI* in, TA* aux, const TV *vol, TAV vola)
462 {
463 #ifdef ALOGVV
464 ALOGVV("volumeMulti MIXTYPE:%d\n", MIXTYPE);
465 #endif
466 if (aux != NULL) {
467 do {
468 TA auxaccum = 0;
469 if constexpr (MIXTYPE == MIXTYPE_MULTI) {
470 static_assert(NCHAN <= 2);
471 for (int i = 0; i < NCHAN; ++i) {
472 *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
473 }
474 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
475 static_assert(NCHAN <= 2);
476 for (int i = 0; i < NCHAN; ++i) {
477 *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[i], &auxaccum);
478 }
479 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
480 for (int i = 0; i < NCHAN; ++i) {
481 *out++ += MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
482 }
483 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
484 for (int i = 0; i < NCHAN; ++i) {
485 *out++ = MixMulAux<TO, TI, TV, TA>(*in++, vol[0], &auxaccum);
486 }
487 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
488 || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
489 || MIXTYPE == MIXTYPE_MONOEXPAND
490 || MIXTYPE == MIXTYPE_STEREOEXPAND) {
491 stereoVolumeHelper<MIXTYPE, NCHAN>(
492 out, in, vol, [&auxaccum] (auto &a, const auto &b) {
493 return MixMulAux<TO, TI, TV, TA>(a, b, &auxaccum);
494 });
495 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
496 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
497 } else /* constexpr */ {
498 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
499 }
500 auxaccum /= NCHAN;
501 *aux++ += MixMul<TA, TA, TAV>(auxaccum, vola);
502 } while (--frameCount);
503 } else {
504 do {
505 // ALOGD("Mixtype:%d NCHAN:%d", MIXTYPE, NCHAN);
506 if constexpr (MIXTYPE == MIXTYPE_MULTI) {
507 static_assert(NCHAN <= 2);
508 for (int i = 0; i < NCHAN; ++i) {
509 *out++ += MixMul<TO, TI, TV>(*in++, vol[i]);
510 }
511 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY) {
512 static_assert(NCHAN <= 2);
513 for (int i = 0; i < NCHAN; ++i) {
514 *out++ = MixMul<TO, TI, TV>(*in++, vol[i]);
515 }
516 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_MONOVOL) {
517 for (int i = 0; i < NCHAN; ++i) {
518 *out++ += MixMul<TO, TI, TV>(*in++, vol[0]);
519 }
520 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_SAVEONLY_MONOVOL) {
521 for (int i = 0; i < NCHAN; ++i) {
522 *out++ = MixMul<TO, TI, TV>(*in++, vol[0]);
523 }
524 } else if constexpr (MIXTYPE == MIXTYPE_MULTI_STEREOVOL
525 || MIXTYPE == MIXTYPE_MULTI_SAVEONLY_STEREOVOL
526 || MIXTYPE == MIXTYPE_MONOEXPAND
527 || MIXTYPE == MIXTYPE_STEREOEXPAND) {
528 stereoVolumeHelper<MIXTYPE, NCHAN>(out, in, vol, [] (auto &a, const auto &b) {
529 return MixMul<TO, TI, TV>(a, b);
530 });
531 if constexpr (MIXTYPE == MIXTYPE_MONOEXPAND) in += 1;
532 if constexpr (MIXTYPE == MIXTYPE_STEREOEXPAND) in += 2;
533 } else /* constexpr */ {
534 static_assert(dependent_false<MIXTYPE>, "invalid mixtype");
535 }
536 } while (--frameCount);
537 }
538 }
539
540 };
541
542 #endif /* ANDROID_AUDIO_MIXER_OPS_H */
543