20 #include "alnumeric.h"
24 #include "async_event.h"
25 #include "buffer_storage.h"
28 #include "devformat.h"
30 #include "filters/biquad.h"
31 #include "filters/nfc.h"
32 #include "filters/splitter.h"
33 #include "fmt_traits.h"
36 #include "mixer/defs.h"
37 #include "mixer/hrtfdefs.h"
38 #include "opthelpers.h"
39 #include "resampler_limits.h"
40 #include "ringbuffer.h"
42 #include "voice_change.h"
53 static_assert(!(sizeof(DeviceBase::MixerBufferLine
)&15),
54 "DeviceBase::MixerBufferLine must be a multiple of 16 bytes");
55 static_assert(!(MaxResamplerEdge
&3), "MaxResamplerEdge is not a multiple of 4");
57 static_assert((BufferLineSize
-1)/MaxPitch
> 0, "MaxPitch is too large for BufferLineSize!");
58 static_assert((INT_MAX
>>MixerFracBits
)/MaxPitch
> BufferLineSize
,
59 "MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
61 Resampler ResamplerDefault
{Resampler::Cubic
};
65 using uint
= unsigned int;
66 using namespace std::chrono
;
67 using namespace std::string_view_literals
;
69 using HrtfMixerFunc
= void(*)(const float *InSamples
, float2
*AccumSamples
, const uint IrSize
,
70 const MixHrtfFilter
*hrtfparams
, const size_t BufferSize
);
71 using HrtfMixerBlendFunc
= void(*)(const float *InSamples
, float2
*AccumSamples
,
72 const uint IrSize
, const HrtfFilter
*oldparams
, const MixHrtfFilter
*newparams
,
73 const size_t BufferSize
);
75 HrtfMixerFunc MixHrtfSamples
{MixHrtf_
<CTag
>};
76 HrtfMixerBlendFunc MixHrtfBlendSamples
{MixHrtfBlend_
<CTag
>};
78 inline MixerOutFunc
SelectMixer()
81 if((CPUCapFlags
&CPU_CAP_NEON
))
85 if((CPUCapFlags
&CPU_CAP_SSE
))
91 inline MixerOneFunc
SelectMixerOne()
94 if((CPUCapFlags
&CPU_CAP_NEON
))
98 if((CPUCapFlags
&CPU_CAP_SSE
))
104 inline HrtfMixerFunc
SelectHrtfMixer()
107 if((CPUCapFlags
&CPU_CAP_NEON
))
108 return MixHrtf_
<NEONTag
>;
111 if((CPUCapFlags
&CPU_CAP_SSE
))
112 return MixHrtf_
<SSETag
>;
114 return MixHrtf_
<CTag
>;
117 inline HrtfMixerBlendFunc
SelectHrtfBlendMixer()
120 if((CPUCapFlags
&CPU_CAP_NEON
))
121 return MixHrtfBlend_
<NEONTag
>;
124 if((CPUCapFlags
&CPU_CAP_SSE
))
125 return MixHrtfBlend_
<SSETag
>;
127 return MixHrtfBlend_
<CTag
>;
132 void Voice::InitMixer(std::optional
<std::string
> resopt
)
136 struct ResamplerEntry
{
137 const std::string_view name
;
138 const Resampler resampler
;
140 constexpr std::array ResamplerList
{
141 ResamplerEntry
{"none"sv
, Resampler::Point
},
142 ResamplerEntry
{"point"sv
, Resampler::Point
},
143 ResamplerEntry
{"linear"sv
, Resampler::Linear
},
144 ResamplerEntry
{"cubic"sv
, Resampler::Cubic
},
145 ResamplerEntry
{"bsinc12"sv
, Resampler::BSinc12
},
146 ResamplerEntry
{"fast_bsinc12"sv
, Resampler::FastBSinc12
},
147 ResamplerEntry
{"bsinc24"sv
, Resampler::BSinc24
},
148 ResamplerEntry
{"fast_bsinc24"sv
, Resampler::FastBSinc24
},
151 std::string_view resampler
{*resopt
};
152 if(al::case_compare(resampler
, "bsinc"sv
) == 0)
154 WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt
->c_str());
155 resampler
= "bsinc12"sv
;
157 else if(al::case_compare(resampler
, "sinc4"sv
) == 0
158 || al::case_compare(resampler
, "sinc8"sv
) == 0)
160 WARN("Resampler option \"%s\" is deprecated, using cubic\n", resopt
->c_str());
161 resampler
= "cubic"sv
;
164 auto iter
= std::find_if(ResamplerList
.begin(), ResamplerList
.end(),
165 [resampler
](const ResamplerEntry
&entry
) -> bool
166 { return al::case_compare(resampler
, entry
.name
) == 0; });
167 if(iter
== ResamplerList
.end())
168 ERR("Invalid resampler: %s\n", resopt
->c_str());
170 ResamplerDefault
= iter
->resampler
;
173 MixSamplesOut
= SelectMixer();
174 MixSamplesOne
= SelectMixerOne();
175 MixHrtfBlendSamples
= SelectHrtfBlendMixer();
176 MixHrtfSamples
= SelectHrtfMixer();
182 /* IMA ADPCM Stepsize table */
183 constexpr std::array
<int,89> IMAStep_size
{{
184 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19,
185 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55,
186 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157,
187 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449,
188 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282,
189 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660,
190 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442,
191 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794,
195 /* IMA4 ADPCM Codeword decode table */
196 constexpr std::array
<int,16> IMA4Codeword
{{
197 1, 3, 5, 7, 9, 11, 13, 15,
198 -1,-3,-5,-7,-9,-11,-13,-15,
201 /* IMA4 ADPCM Step index adjust decode table */
202 constexpr std::array
<int,16>IMA4Index_adjust
{{
203 -1,-1,-1,-1, 2, 4, 6, 8,
204 -1,-1,-1,-1, 2, 4, 6, 8
207 /* MSADPCM Adaption table */
208 constexpr std::array
<int,16> MSADPCMAdaption
{{
209 230, 230, 230, 230, 307, 409, 512, 614,
210 768, 614, 512, 409, 307, 230, 230, 230
213 /* MSADPCM Adaption Coefficient tables */
214 constexpr std::array MSADPCMAdaptionCoeff
{
216 std::array
{512, -256},
220 std::array
{460, -208},
221 std::array
{392, -232}
225 void SendSourceStoppedEvent(ContextBase
*context
, uint id
)
227 RingBuffer
*ring
{context
->mAsyncEvents
.get()};
228 auto evt_vec
= ring
->getWriteVector();
229 if(evt_vec
.first
.len
< 1) return;
231 auto &evt
= InitAsyncEvent
<AsyncSourceStateEvent
>(evt_vec
.first
.buf
);
233 evt
.mState
= AsyncSrcState::Stop
;
235 ring
->writeAdvance(1);
239 const float *DoFilters(BiquadFilter
&lpfilter
, BiquadFilter
&hpfilter
, float *dst
,
240 const al::span
<const float> src
, int type
)
250 lpfilter
.process(src
, dst
);
255 hpfilter
.process(src
, dst
);
259 DualBiquad
{lpfilter
, hpfilter
}.process(src
, dst
);
266 template<FmtType Type
>
267 inline void LoadSamples(float *RESTRICT dstSamples
, const std::byte
*src
, const size_t srcChan
,
268 const size_t srcOffset
, const size_t srcStep
, const size_t /*samplesPerBlock*/,
269 const size_t samplesToLoad
) noexcept
271 constexpr size_t sampleSize
{sizeof(typename
al::FmtTypeTraits
<Type
>::Type
)};
272 auto s
= src
+ (srcOffset
*srcStep
+ srcChan
)*sampleSize
;
274 al::LoadSampleArray
<Type
>(dstSamples
, s
, srcStep
, samplesToLoad
);
278 inline void LoadSamples
<FmtIMA4
>(float *RESTRICT dstSamples
, const std::byte
*src
,
279 const size_t srcChan
, const size_t srcOffset
, const size_t srcStep
,
280 const size_t samplesPerBlock
, const size_t samplesToLoad
) noexcept
282 const size_t blockBytes
{((samplesPerBlock
-1)/2 + 4)*srcStep
};
284 /* Skip to the ADPCM block containing the srcOffset sample. */
285 src
+= srcOffset
/samplesPerBlock
*blockBytes
;
286 /* Calculate how many samples need to be skipped in the block. */
287 size_t skip
{srcOffset
% samplesPerBlock
};
289 /* NOTE: This could probably be optimized better. */
292 static constexpr int MaxStepIndex
{static_cast<int>(IMAStep_size
.size()) - 1};
293 /* Each IMA4 block starts with a signed 16-bit sample, and a signed
294 * 16-bit table index. The table index needs to be clamped.
296 int sample
{int(src
[srcChan
*4]) | (int(src
[srcChan
*4 + 1]) << 8)};
297 int index
{int(src
[srcChan
*4 + 2]) | (int(src
[srcChan
*4 + 3]) << 8)};
299 sample
= (sample
^0x8000) - 32768;
300 index
= std::clamp((index
^0x8000) - 32768, 0, MaxStepIndex
);
304 dstSamples
[wrote
++] = static_cast<float>(sample
) / 32768.0f
;
305 if(wrote
== samplesToLoad
) return;
310 auto decode_sample
= [&sample
,&index
](const uint nibble
)
312 sample
+= IMA4Codeword
[nibble
] * IMAStep_size
[static_cast<uint
>(index
)] / 8;
313 sample
= std::clamp(sample
, -32768, 32767);
315 index
+= IMA4Index_adjust
[nibble
];
316 index
= std::clamp(index
, 0, MaxStepIndex
);
321 /* The rest of the block is arranged as a series of nibbles, contained
322 * in 4 *bytes* per channel interleaved. So every 8 nibbles we need to
323 * skip 4 bytes per channel to get the next nibbles for this channel.
325 * First, decode the samples that we need to skip in the block (will
326 * always be less than the block size). They need to be decoded despite
327 * being ignored for proper state on the remaining samples.
329 const std::byte
*nibbleData
{src
+ (srcStep
+srcChan
)*4};
330 size_t nibbleOffset
{0};
331 const size_t startOffset
{skip
+ 1};
334 const size_t byteShift
{(nibbleOffset
&1) * 4};
335 const size_t wordOffset
{(nibbleOffset
>>1) & ~3_uz
};
336 const size_t byteOffset
{wordOffset
*srcStep
+ ((nibbleOffset
>>1)&3u)};
339 std::ignore
= decode_sample(uint(nibbleData
[byteOffset
]>>byteShift
) & 15u);
342 /* Second, decode the rest of the block and write to the output, until
343 * the end of the block or the end of output.
345 const size_t todo
{std::min(samplesPerBlock
-startOffset
, samplesToLoad
-wrote
)};
346 for(size_t i
{0};i
< todo
;++i
)
348 const size_t byteShift
{(nibbleOffset
&1) * 4};
349 const size_t wordOffset
{(nibbleOffset
>>1) & ~3_uz
};
350 const size_t byteOffset
{wordOffset
*srcStep
+ ((nibbleOffset
>>1)&3u)};
353 const int result
{decode_sample(uint(nibbleData
[byteOffset
]>>byteShift
) & 15u)};
354 dstSamples
[wrote
++] = static_cast<float>(result
) / 32768.0f
;
356 if(wrote
== samplesToLoad
)
364 inline void LoadSamples
<FmtMSADPCM
>(float *RESTRICT dstSamples
, const std::byte
*src
,
365 const size_t srcChan
, const size_t srcOffset
, const size_t srcStep
,
366 const size_t samplesPerBlock
, const size_t samplesToLoad
) noexcept
368 const size_t blockBytes
{((samplesPerBlock
-2)/2 + 7)*srcStep
};
370 src
+= srcOffset
/samplesPerBlock
*blockBytes
;
371 size_t skip
{srcOffset
% samplesPerBlock
};
375 /* Each MS ADPCM block starts with an 8-bit block predictor, used to
376 * dictate how the two sample history values are mixed with the decoded
377 * sample, and an initial signed 16-bit delta value which scales the
378 * nibble sample value. This is followed by the two initial 16-bit
379 * sample history values.
381 const std::byte
*input
{src
};
382 const uint8_t blockpred
{std::min(uint8_t(input
[srcChan
]), uint8_t{6})};
384 int delta
{int(input
[2*srcChan
+ 0]) | (int(input
[2*srcChan
+ 1]) << 8)};
387 std::array
<int,2> sampleHistory
{};
388 sampleHistory
[0] = int(input
[2*srcChan
+ 0]) | (int(input
[2*srcChan
+ 1])<<8);
390 sampleHistory
[1] = int(input
[2*srcChan
+ 0]) | (int(input
[2*srcChan
+ 1])<<8);
393 const al::span coeffs
{MSADPCMAdaptionCoeff
[blockpred
]};
394 delta
= (delta
^0x8000) - 32768;
395 sampleHistory
[0] = (sampleHistory
[0]^0x8000) - 32768;
396 sampleHistory
[1] = (sampleHistory
[1]^0x8000) - 32768;
398 /* The second history sample is "older", so it's the first to be
403 dstSamples
[wrote
++] = static_cast<float>(sampleHistory
[1]) / 32768.0f
;
404 if(wrote
== samplesToLoad
) return;
405 dstSamples
[wrote
++] = static_cast<float>(sampleHistory
[0]) / 32768.0f
;
406 if(wrote
== samplesToLoad
) return;
411 dstSamples
[wrote
++] = static_cast<float>(sampleHistory
[0]) / 32768.0f
;
412 if(wrote
== samplesToLoad
) return;
417 auto decode_sample
= [&sampleHistory
,&delta
,coeffs
](const int nibble
)
419 int pred
{(sampleHistory
[0]*coeffs
[0] + sampleHistory
[1]*coeffs
[1]) / 256};
420 pred
+= ((nibble
^0x08) - 0x08) * delta
;
421 pred
= std::clamp(pred
, -32768, 32767);
423 sampleHistory
[1] = sampleHistory
[0];
424 sampleHistory
[0] = pred
;
426 delta
= (MSADPCMAdaption
[static_cast<uint
>(nibble
)] * delta
) / 256;
427 delta
= std::max(16, delta
);
432 /* The rest of the block is a series of nibbles, interleaved per-
433 * channel. First, skip samples.
435 const size_t startOffset
{skip
+ 2};
436 size_t nibbleOffset
{srcChan
};
439 const size_t byteOffset
{nibbleOffset
>>1};
440 const size_t byteShift
{((nibbleOffset
&1)^1) * 4};
441 nibbleOffset
+= srcStep
;
443 std::ignore
= decode_sample(int(input
[byteOffset
]>>byteShift
) & 15);
446 /* Now decode the rest of the block, until the end of the block or the
447 * dst buffer is filled.
449 const size_t todo
{std::min(samplesPerBlock
-startOffset
, samplesToLoad
-wrote
)};
450 for(size_t j
{0};j
< todo
;++j
)
452 const size_t byteOffset
{nibbleOffset
>>1};
453 const size_t byteShift
{((nibbleOffset
&1)^1) * 4};
454 nibbleOffset
+= srcStep
;
456 const int sample
{decode_sample(int(input
[byteOffset
]>>byteShift
) & 15)};
457 dstSamples
[wrote
++] = static_cast<float>(sample
) / 32768.0f
;
459 if(wrote
== samplesToLoad
)
466 void LoadSamples(float *dstSamples
, const std::byte
*src
, const size_t srcChan
,
467 const size_t srcOffset
, const FmtType srcType
, const size_t srcStep
,
468 const size_t samplesPerBlock
, const size_t samplesToLoad
) noexcept
470 #define HANDLE_FMT(T) case T: \
471 LoadSamples<T>(dstSamples, src, srcChan, srcOffset, srcStep, \
472 samplesPerBlock, samplesToLoad); \
477 HANDLE_FMT(FmtUByte
);
478 HANDLE_FMT(FmtShort
);
480 HANDLE_FMT(FmtFloat
);
481 HANDLE_FMT(FmtDouble
);
482 HANDLE_FMT(FmtMulaw
);
485 HANDLE_FMT(FmtMSADPCM
);
490 void LoadBufferStatic(VoiceBufferItem
*buffer
, VoiceBufferItem
*bufferLoopItem
,
491 const size_t dataPosInt
, const FmtType sampleType
, const size_t srcChannel
,
492 const size_t srcStep
, size_t samplesLoaded
, const size_t samplesToLoad
,
497 /* Load what's left to play from the buffer */
498 if(buffer
->mSampleLen
> dataPosInt
) LIKELY
500 const size_t buffer_remaining
{buffer
->mSampleLen
- dataPosInt
};
501 const size_t remaining
{std::min(samplesToLoad
-samplesLoaded
, buffer_remaining
)};
502 LoadSamples(voiceSamples
+samplesLoaded
, buffer
->mSamples
, srcChannel
, dataPosInt
,
503 sampleType
, srcStep
, buffer
->mBlockAlign
, remaining
);
504 samplesLoaded
+= remaining
;
507 if(const size_t toFill
{samplesToLoad
- samplesLoaded
})
509 auto srcsamples
= voiceSamples
+ samplesLoaded
;
510 std::fill_n(srcsamples
, toFill
, *(srcsamples
-1));
515 const size_t loopStart
{buffer
->mLoopStart
};
516 const size_t loopEnd
{buffer
->mLoopEnd
};
517 ASSUME(loopEnd
> loopStart
);
519 const size_t intPos
{(dataPosInt
< loopEnd
) ? dataPosInt
520 : (((dataPosInt
-loopStart
)%(loopEnd
-loopStart
)) + loopStart
)};
522 /* Load what's left of this loop iteration */
523 const size_t remaining
{std::min(samplesToLoad
-samplesLoaded
, loopEnd
-dataPosInt
)};
524 LoadSamples(voiceSamples
+samplesLoaded
, buffer
->mSamples
, srcChannel
, intPos
, sampleType
,
525 srcStep
, buffer
->mBlockAlign
, remaining
);
526 samplesLoaded
+= remaining
;
528 /* Load repeats of the loop to fill the buffer. */
529 const size_t loopSize
{loopEnd
- loopStart
};
530 while(const size_t toFill
{std::min(samplesToLoad
- samplesLoaded
, loopSize
)})
532 LoadSamples(voiceSamples
+samplesLoaded
, buffer
->mSamples
, srcChannel
, loopStart
,
533 sampleType
, srcStep
, buffer
->mBlockAlign
, toFill
);
534 samplesLoaded
+= toFill
;
539 void LoadBufferCallback(VoiceBufferItem
*buffer
, const size_t dataPosInt
,
540 const size_t numCallbackSamples
, const FmtType sampleType
, const size_t srcChannel
,
541 const size_t srcStep
, size_t samplesLoaded
, const size_t samplesToLoad
, float *voiceSamples
)
543 /* Load what's left to play from the buffer */
544 if(numCallbackSamples
> dataPosInt
) LIKELY
546 const size_t remaining
{std::min(samplesToLoad
-samplesLoaded
,
547 numCallbackSamples
-dataPosInt
)};
548 LoadSamples(voiceSamples
+samplesLoaded
, buffer
->mSamples
, srcChannel
, dataPosInt
,
549 sampleType
, srcStep
, buffer
->mBlockAlign
, remaining
);
550 samplesLoaded
+= remaining
;
553 if(const size_t toFill
{samplesToLoad
- samplesLoaded
})
555 auto srcsamples
= voiceSamples
+ samplesLoaded
;
556 std::fill_n(srcsamples
, toFill
, *(srcsamples
-1));
560 void LoadBufferQueue(VoiceBufferItem
*buffer
, VoiceBufferItem
*bufferLoopItem
,
561 size_t dataPosInt
, const FmtType sampleType
, const size_t srcChannel
,
562 const size_t srcStep
, size_t samplesLoaded
, const size_t samplesToLoad
,
565 /* Crawl the buffer queue to fill in the temp buffer */
566 while(buffer
&& samplesLoaded
!= samplesToLoad
)
568 if(dataPosInt
>= buffer
->mSampleLen
)
570 dataPosInt
-= buffer
->mSampleLen
;
571 buffer
= buffer
->mNext
.load(std::memory_order_acquire
);
572 if(!buffer
) buffer
= bufferLoopItem
;
576 const size_t remaining
{std::min(samplesToLoad
-samplesLoaded
,
577 buffer
->mSampleLen
-dataPosInt
)};
578 LoadSamples(voiceSamples
+samplesLoaded
, buffer
->mSamples
, srcChannel
, dataPosInt
,
579 sampleType
, srcStep
, buffer
->mBlockAlign
, remaining
);
581 samplesLoaded
+= remaining
;
582 if(samplesLoaded
== samplesToLoad
)
586 buffer
= buffer
->mNext
.load(std::memory_order_acquire
);
587 if(!buffer
) buffer
= bufferLoopItem
;
589 if(const size_t toFill
{samplesToLoad
- samplesLoaded
})
591 auto srcsamples
= voiceSamples
+ samplesLoaded
;
592 std::fill_n(srcsamples
, toFill
, *(srcsamples
-1));
597 void DoHrtfMix(const float *samples
, const uint DstBufferSize
, DirectParams
&parms
,
598 const float TargetGain
, const uint Counter
, uint OutPos
, const bool IsPlaying
,
601 const uint IrSize
{Device
->mIrSize
};
602 const auto HrtfSamples
= al::span
{Device
->ExtraSampleData
};
603 const auto AccumSamples
= al::span
{Device
->HrtfAccumData
};
605 /* Copy the HRTF history and new input samples into a temp buffer. */
606 auto src_iter
= std::copy(parms
.Hrtf
.History
.begin(), parms
.Hrtf
.History
.end(),
607 HrtfSamples
.begin());
608 std::copy_n(samples
, DstBufferSize
, src_iter
);
609 /* Copy the last used samples back into the history buffer for later. */
611 std::copy_n(HrtfSamples
.begin() + DstBufferSize
, parms
.Hrtf
.History
.size(),
612 parms
.Hrtf
.History
.begin());
614 /* If fading and this is the first mixing pass, fade between the IRs. */
616 if(Counter
&& OutPos
== 0)
618 fademix
= std::min(DstBufferSize
, Counter
);
620 float gain
{TargetGain
};
622 /* The new coefficients need to fade in completely since they're
623 * replacing the old ones. To keep the gain fading consistent,
624 * interpolate between the old and new target gains given how much of
625 * the fade time this mix handles.
627 if(Counter
> fademix
)
629 const float a
{static_cast<float>(fademix
) / static_cast<float>(Counter
)};
630 gain
= lerpf(parms
.Hrtf
.Old
.Gain
, TargetGain
, a
);
633 MixHrtfFilter hrtfparams
{
634 parms
.Hrtf
.Target
.Coeffs
,
635 parms
.Hrtf
.Target
.Delay
,
636 0.0f
, gain
/ static_cast<float>(fademix
)};
637 MixHrtfBlendSamples(HrtfSamples
.data(), AccumSamples
.data()+OutPos
, IrSize
,
638 &parms
.Hrtf
.Old
, &hrtfparams
, fademix
);
640 /* Update the old parameters with the result. */
641 parms
.Hrtf
.Old
= parms
.Hrtf
.Target
;
642 parms
.Hrtf
.Old
.Gain
= gain
;
646 if(fademix
< DstBufferSize
)
648 const uint todo
{DstBufferSize
- fademix
};
649 float gain
{TargetGain
};
651 /* Interpolate the target gain if the gain fading lasts longer than
654 if(Counter
> DstBufferSize
)
656 const float a
{static_cast<float>(todo
) / static_cast<float>(Counter
-fademix
)};
657 gain
= lerpf(parms
.Hrtf
.Old
.Gain
, TargetGain
, a
);
660 MixHrtfFilter hrtfparams
{
661 parms
.Hrtf
.Target
.Coeffs
,
662 parms
.Hrtf
.Target
.Delay
,
664 (gain
- parms
.Hrtf
.Old
.Gain
) / static_cast<float>(todo
)};
665 MixHrtfSamples(HrtfSamples
.data()+fademix
, AccumSamples
.data()+OutPos
, IrSize
, &hrtfparams
,
668 /* Store the now-current gain for next time. */
669 parms
.Hrtf
.Old
.Gain
= gain
;
673 void DoNfcMix(const al::span
<const float> samples
, FloatBufferLine
*OutBuffer
, DirectParams
&parms
,
674 const float *TargetGains
, const uint Counter
, const uint OutPos
, DeviceBase
*Device
)
676 using FilterProc
= void (NfcFilter::*)(const al::span
<const float>, float*);
677 static constexpr std::array
<FilterProc
,MaxAmbiOrder
+1> NfcProcess
{{
678 nullptr, &NfcFilter::process1
, &NfcFilter::process2
, &NfcFilter::process3
}};
680 float *CurrentGains
{parms
.Gains
.Current
.data()};
681 MixSamples(samples
, {OutBuffer
, 1u}, CurrentGains
, TargetGains
, Counter
, OutPos
);
686 const auto nfcsamples
= al::span
{Device
->ExtraSampleData
.begin(), samples
.size()};
688 while(const size_t chancount
{Device
->NumChannelsPerOrder
[order
]})
690 (parms
.NFCtrlFilter
.*NfcProcess
[order
])(samples
, nfcsamples
.data());
691 MixSamples(nfcsamples
, {OutBuffer
, chancount
}, CurrentGains
, TargetGains
, Counter
, OutPos
);
692 OutBuffer
+= chancount
;
693 CurrentGains
+= chancount
;
694 TargetGains
+= chancount
;
695 if(++order
== MaxAmbiOrder
+1)
702 void Voice::mix(const State vstate
, ContextBase
*Context
, const nanoseconds deviceTime
,
703 const uint SamplesToDo
)
705 static constexpr std::array
<float,MaxOutputChannels
> SilentTarget
{};
707 ASSUME(SamplesToDo
> 0);
709 DeviceBase
*Device
{Context
->mDevice
};
710 const uint NumSends
{Device
->NumAuxSends
};
713 int DataPosInt
{mPosition
.load(std::memory_order_relaxed
)};
714 uint DataPosFrac
{mPositionFrac
.load(std::memory_order_relaxed
)};
715 VoiceBufferItem
*BufferListItem
{mCurrentBuffer
.load(std::memory_order_relaxed
)};
716 VoiceBufferItem
*BufferLoopItem
{mLoopBuffer
.load(std::memory_order_relaxed
)};
717 const uint increment
{mStep
};
718 if(increment
< 1) UNLIKELY
720 /* If the voice is supposed to be stopping but can't be mixed, just
721 * stop it before bailing.
723 if(vstate
== Stopping
)
724 mPlayState
.store(Stopped
, std::memory_order_release
);
728 /* If the static voice's current position is beyond the buffer loop end
729 * position, disable looping.
731 if(mFlags
.test(VoiceIsStatic
) && BufferLoopItem
)
733 if(DataPosInt
>= 0 && static_cast<uint
>(DataPosInt
) >= BufferListItem
->mLoopEnd
)
734 BufferLoopItem
= nullptr;
739 /* Check if we're doing a delayed start, and we start in this update. */
740 if(mStartTime
> deviceTime
) UNLIKELY
742 /* If the voice is supposed to be stopping but hasn't actually started
743 * yet, make sure its stopped.
745 if(vstate
== Stopping
)
747 mPlayState
.store(Stopped
, std::memory_order_release
);
751 /* If the start time is too far ahead, don't bother. */
752 auto diff
= mStartTime
- deviceTime
;
753 if(diff
>= seconds
{1})
756 /* Get the number of samples ahead of the current time that output
757 * should start at. Skip this update if it's beyond the output sample
760 * Round the start position to a multiple of 4, which some mixers want.
761 * This makes the start time accurate to 4 samples. This could be made
762 * sample-accurate by forcing non-SIMD functions on the first run.
764 seconds::rep sampleOffset
{duration_cast
<seconds
>(diff
* Device
->Frequency
).count()};
765 sampleOffset
= (sampleOffset
+2) & ~seconds::rep
{3};
766 if(sampleOffset
>= SamplesToDo
)
769 OutPos
= static_cast<uint
>(sampleOffset
);
772 /* Calculate the number of samples to mix, and the number of (resampled)
773 * samples that need to be loaded (mixing samples and decoder padding).
775 const uint samplesToMix
{SamplesToDo
- OutPos
};
776 const uint samplesToLoad
{samplesToMix
+ mDecoderPadding
};
778 /* Get a span of pointers to hold the floating point, deinterlaced,
779 * resampled buffer data to be mixed.
781 std::array
<float*,DeviceBase::MixerChannelsMax
> SamplePointers
;
782 const al::span
<float*> MixingSamples
{SamplePointers
.data(), mChans
.size()};
783 auto get_bufferline
= [](DeviceBase::MixerBufferLine
&bufline
) noexcept
-> float*
784 { return bufline
.data(); };
785 std::transform(Device
->mSampleData
.end() - mChans
.size(), Device
->mSampleData
.end(),
786 MixingSamples
.begin(), get_bufferline
);
788 /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels
789 * (3rd channel is generated from decoding).
791 const size_t realChannels
{(mFmtChannels
== FmtUHJ2
|| mFmtChannels
== FmtSuperStereo
) ? 2u
792 : (mFmtChannels
== FmtMonoDup
) ? 1u : MixingSamples
.size()};
793 for(size_t chan
{0};chan
< realChannels
;++chan
)
795 static constexpr uint ResBufSize
{std::tuple_size_v
<decltype(DeviceBase::mResampleData
)>};
796 static constexpr uint srcSizeMax
{ResBufSize
- MaxResamplerEdge
};
798 const al::span prevSamples
{mPrevSamples
[chan
]};
799 std::copy(prevSamples
.cbegin(), prevSamples
.cend(), Device
->mResampleData
.begin());
800 const auto resampleBuffer
= Device
->mResampleData
.begin() + MaxResamplerEdge
;
801 int intPos
{DataPosInt
};
802 uint fracPos
{DataPosFrac
};
804 /* Load samples for this channel from the available buffer(s), with
807 for(uint samplesLoaded
{0};samplesLoaded
< samplesToLoad
;)
809 /* Calculate the number of dst samples that can be loaded this
810 * iteration, given the available resampler buffer size, and the
811 * number of src samples that are needed to load it.
813 auto calc_buffer_sizes
= [fracPos
,increment
](uint dstBufferSize
)
815 /* If ext=true, calculate the last written dst pos from the dst
816 * count, convert to the last read src pos, then add one to get
819 * If ext=false, convert the dst count to src count directly.
821 * Without this, the src count could be short by one when
822 * increment < 1.0, or not have a full src at the end when
825 const bool ext
{increment
<= MixerFracOne
};
826 uint64_t dataSize64
{dstBufferSize
- ext
};
827 dataSize64
= (dataSize64
*increment
+ fracPos
) >> MixerFracBits
;
828 /* Also include resampler padding. */
829 dataSize64
+= ext
+ MaxResamplerEdge
;
831 if(dataSize64
<= srcSizeMax
)
832 return std::make_pair(dstBufferSize
, static_cast<uint
>(dataSize64
));
834 /* If the source size got saturated, we can't fill the desired
835 * dst size. Figure out how many dst samples we can fill.
837 dataSize64
= srcSizeMax
- MaxResamplerEdge
;
838 dataSize64
= ((dataSize64
<<MixerFracBits
) - fracPos
) / increment
;
839 if(dataSize64
< dstBufferSize
)
841 /* Some resamplers require the destination being 16-byte
842 * aligned, so limit to a multiple of 4 samples to maintain
843 * alignment if we need to do another iteration after this.
845 dstBufferSize
= static_cast<uint
>(dataSize64
) & ~3u;
847 return std::make_pair(dstBufferSize
, srcSizeMax
);
849 const auto [dstBufferSize
, srcBufferSize
] = calc_buffer_sizes(
850 samplesToLoad
- samplesLoaded
);
852 /* Load the necessary samples from the given buffer(s). */
855 const uint avail
{std::min(srcBufferSize
, MaxResamplerEdge
)};
856 const uint tofill
{std::max(srcBufferSize
, MaxResamplerEdge
)};
858 /* When loading from a voice that ended prematurely, only take
859 * the samples that get closest to 0 amplitude. This helps
860 * certain sounds fade out better.
862 auto abs_lt
= [](const float lhs
, const float rhs
) noexcept
-> bool
863 { return std::abs(lhs
) < std::abs(rhs
); };
864 auto srciter
= std::min_element(resampleBuffer
, resampleBuffer
+avail
, abs_lt
);
866 std::fill(srciter
+1, resampleBuffer
+tofill
, *srciter
);
870 size_t srcSampleDelay
{0};
871 if(intPos
< 0) UNLIKELY
873 /* If the current position is negative, there's that many
874 * silent samples to load before using the buffer.
876 srcSampleDelay
= static_cast<uint
>(-intPos
);
877 if(srcSampleDelay
>= srcBufferSize
)
879 /* If the number of silent source samples exceeds the
880 * number to load, the output will be silent.
882 std::fill_n(MixingSamples
[chan
]+samplesLoaded
, dstBufferSize
, 0.0f
);
883 std::fill_n(resampleBuffer
, srcBufferSize
, 0.0f
);
887 std::fill_n(resampleBuffer
, srcSampleDelay
, 0.0f
);
889 const uint uintPos
{static_cast<uint
>(std::max(intPos
, 0))};
891 if(mFlags
.test(VoiceIsStatic
))
892 LoadBufferStatic(BufferListItem
, BufferLoopItem
, uintPos
, mFmtType
, chan
,
893 mFrameStep
, srcSampleDelay
, srcBufferSize
, al::to_address(resampleBuffer
));
894 else if(mFlags
.test(VoiceIsCallback
))
896 const uint callbackBase
{mCallbackBlockBase
* mSamplesPerBlock
};
897 const size_t bufferOffset
{uintPos
- callbackBase
};
898 const size_t needSamples
{bufferOffset
+ srcBufferSize
- srcSampleDelay
};
899 const size_t needBlocks
{(needSamples
+ mSamplesPerBlock
-1) / mSamplesPerBlock
};
900 if(!mFlags
.test(VoiceCallbackStopped
) && needBlocks
> mNumCallbackBlocks
)
902 const size_t byteOffset
{mNumCallbackBlocks
*size_t{mBytesPerBlock
}};
903 const size_t needBytes
{(needBlocks
-mNumCallbackBlocks
)*size_t{mBytesPerBlock
}};
905 const int gotBytes
{BufferListItem
->mCallback(BufferListItem
->mUserData
,
906 &BufferListItem
->mSamples
[byteOffset
], static_cast<int>(needBytes
))};
908 mFlags
.set(VoiceCallbackStopped
);
909 else if(static_cast<uint
>(gotBytes
) < needBytes
)
911 mFlags
.set(VoiceCallbackStopped
);
912 mNumCallbackBlocks
+= static_cast<uint
>(gotBytes
) / mBytesPerBlock
;
915 mNumCallbackBlocks
= static_cast<uint
>(needBlocks
);
917 const size_t numSamples
{size_t{mNumCallbackBlocks
} * mSamplesPerBlock
};
918 LoadBufferCallback(BufferListItem
, bufferOffset
, numSamples
, mFmtType
, chan
,
919 mFrameStep
, srcSampleDelay
, srcBufferSize
, al::to_address(resampleBuffer
));
922 LoadBufferQueue(BufferListItem
, BufferLoopItem
, uintPos
, mFmtType
, chan
,
923 mFrameStep
, srcSampleDelay
, srcBufferSize
, al::to_address(resampleBuffer
));
926 /* If there's a matching sample step and no phase offset, use a
927 * simple copy for resampling.
929 if(increment
== MixerFracOne
&& fracPos
== 0)
930 std::copy_n(resampleBuffer
, dstBufferSize
, MixingSamples
[chan
]+samplesLoaded
);
932 mResampler(&mResampleState
, al::to_address(resampleBuffer
), fracPos
, increment
,
933 {MixingSamples
[chan
]+samplesLoaded
, dstBufferSize
});
935 /* Store the last source samples used for next time. */
936 if(vstate
== Playing
) LIKELY
938 /* Only store samples for the end of the mix, excluding what
939 * gets loaded for decoder padding.
941 const uint loadEnd
{samplesLoaded
+ dstBufferSize
};
942 if(samplesToMix
> samplesLoaded
&& samplesToMix
<= loadEnd
) LIKELY
944 const size_t dstOffset
{samplesToMix
- samplesLoaded
};
945 const size_t srcOffset
{(dstOffset
*increment
+ fracPos
) >> MixerFracBits
};
946 std::copy_n(resampleBuffer
-MaxResamplerEdge
+srcOffset
, prevSamples
.size(),
947 prevSamples
.begin());
952 samplesLoaded
+= dstBufferSize
;
953 if(samplesLoaded
< samplesToLoad
)
955 fracPos
+= dstBufferSize
*increment
;
956 const uint srcOffset
{fracPos
>> MixerFracBits
};
957 fracPos
&= MixerFracMask
;
958 intPos
+= static_cast<int>(srcOffset
);
960 /* If more samples need to be loaded, copy the back of the
961 * resampleBuffer to the front to reuse it. prevSamples isn't
962 * reliable since it's only updated for the end of the mix.
964 std::copy(resampleBuffer
-MaxResamplerEdge
+srcOffset
,
965 resampleBuffer
+MaxResamplerEdge
+srcOffset
, resampleBuffer
-MaxResamplerEdge
);
969 if(mFmtChannels
== FmtMonoDup
)
970 MixingSamples
[1] = MixingSamples
[0];
971 else for(auto &samples
: MixingSamples
.subspan(realChannels
))
972 std::fill_n(samples
, samplesToLoad
, 0.0f
);
975 mDecoder
->decode(MixingSamples
, samplesToMix
, (vstate
==Playing
));
977 if(mFlags
.test(VoiceIsAmbisonic
))
979 auto voiceSamples
= MixingSamples
.begin();
980 for(auto &chandata
: mChans
)
982 chandata
.mAmbiSplitter
.processScale({*voiceSamples
, samplesToMix
},
983 chandata
.mAmbiHFScale
, chandata
.mAmbiLFScale
);
988 const uint Counter
{mFlags
.test(VoiceIsFading
) ? std::min(samplesToMix
, 64u) : 0u};
991 /* No fading, just overwrite the old/current params. */
992 for(auto &chandata
: mChans
)
995 DirectParams
&parms
= chandata
.mDryParams
;
996 if(!mFlags
.test(VoiceHasHrtf
))
997 parms
.Gains
.Current
= parms
.Gains
.Target
;
999 parms
.Hrtf
.Old
= parms
.Hrtf
.Target
;
1001 for(uint send
{0};send
< NumSends
;++send
)
1003 if(mSend
[send
].Buffer
.empty())
1006 SendParams
&parms
= chandata
.mWetParams
[send
];
1007 parms
.Gains
.Current
= parms
.Gains
.Target
;
1012 auto voiceSamples
= MixingSamples
.begin();
1013 for(auto &chandata
: mChans
)
1015 /* Now filter and mix to the appropriate outputs. */
1016 const al::span
<float,BufferLineSize
> FilterBuf
{Device
->FilteredData
};
1018 DirectParams
&parms
= chandata
.mDryParams
;
1019 const float *samples
{DoFilters(parms
.LowPass
, parms
.HighPass
, FilterBuf
.data(),
1020 {*voiceSamples
, samplesToMix
}, mDirect
.FilterType
)};
1022 if(mFlags
.test(VoiceHasHrtf
))
1024 const float TargetGain
{parms
.Hrtf
.Target
.Gain
* float(vstate
== Playing
)};
1025 DoHrtfMix(samples
, samplesToMix
, parms
, TargetGain
, Counter
, OutPos
,
1026 (vstate
== Playing
), Device
);
1030 const float *TargetGains
{(vstate
== Playing
) ? parms
.Gains
.Target
.data()
1031 : SilentTarget
.data()};
1032 if(mFlags
.test(VoiceHasNfc
))
1033 DoNfcMix({samples
, samplesToMix
}, mDirect
.Buffer
.data(), parms
,
1034 TargetGains
, Counter
, OutPos
, Device
);
1036 MixSamples({samples
, samplesToMix
}, mDirect
.Buffer
,
1037 parms
.Gains
.Current
.data(), TargetGains
, Counter
, OutPos
);
1041 for(uint send
{0};send
< NumSends
;++send
)
1043 if(mSend
[send
].Buffer
.empty())
1046 SendParams
&parms
= chandata
.mWetParams
[send
];
1047 const float *samples
{DoFilters(parms
.LowPass
, parms
.HighPass
, FilterBuf
.data(),
1048 {*voiceSamples
, samplesToMix
}, mSend
[send
].FilterType
)};
1050 const float *TargetGains
{(vstate
== Playing
) ? parms
.Gains
.Target
.data()
1051 : SilentTarget
.data()};
1052 MixSamples({samples
, samplesToMix
}, mSend
[send
].Buffer
,
1053 parms
.Gains
.Current
.data(), TargetGains
, Counter
, OutPos
);
1059 mFlags
.set(VoiceIsFading
);
1061 /* Don't update positions and buffers if we were stopping. */
1062 if(vstate
== Stopping
) UNLIKELY
1064 mPlayState
.store(Stopped
, std::memory_order_release
);
1068 /* Update voice positions and buffers as needed. */
1069 DataPosFrac
+= increment
*samplesToMix
;
1070 DataPosInt
+= static_cast<int>(DataPosFrac
>>MixerFracBits
);
1071 DataPosFrac
&= MixerFracMask
;
1073 uint buffers_done
{0u};
1074 if(BufferListItem
&& DataPosInt
>= 0) LIKELY
1076 if(mFlags
.test(VoiceIsStatic
))
1080 /* Handle looping static source */
1081 const uint LoopStart
{BufferListItem
->mLoopStart
};
1082 const uint LoopEnd
{BufferListItem
->mLoopEnd
};
1083 uint DataPosUInt
{static_cast<uint
>(DataPosInt
)};
1084 if(DataPosUInt
>= LoopEnd
)
1086 assert(LoopEnd
> LoopStart
);
1087 DataPosUInt
= ((DataPosUInt
-LoopStart
)%(LoopEnd
-LoopStart
)) + LoopStart
;
1088 DataPosInt
= static_cast<int>(DataPosUInt
);
1093 /* Handle non-looping static source */
1094 if(static_cast<uint
>(DataPosInt
) >= BufferListItem
->mSampleLen
)
1095 BufferListItem
= nullptr;
1098 else if(mFlags
.test(VoiceIsCallback
))
1100 /* Handle callback buffer source */
1101 const uint currentBlock
{static_cast<uint
>(DataPosInt
) / mSamplesPerBlock
};
1102 const uint blocksDone
{currentBlock
- mCallbackBlockBase
};
1103 if(blocksDone
< mNumCallbackBlocks
)
1105 const size_t byteOffset
{blocksDone
*size_t{mBytesPerBlock
}};
1106 const size_t byteEnd
{mNumCallbackBlocks
*size_t{mBytesPerBlock
}};
1107 std::byte
*data
{BufferListItem
->mSamples
};
1108 std::copy(data
+byteOffset
, data
+byteEnd
, data
);
1109 mNumCallbackBlocks
-= blocksDone
;
1110 mCallbackBlockBase
+= blocksDone
;
1114 BufferListItem
= nullptr;
1115 mNumCallbackBlocks
= 0;
1116 mCallbackBlockBase
+= blocksDone
;
1121 /* Handle streaming source */
1123 if(BufferListItem
->mSampleLen
> static_cast<uint
>(DataPosInt
))
1126 DataPosInt
-= static_cast<int>(BufferListItem
->mSampleLen
);
1129 BufferListItem
= BufferListItem
->mNext
.load(std::memory_order_relaxed
);
1130 if(!BufferListItem
) BufferListItem
= BufferLoopItem
;
1131 } while(BufferListItem
);
1135 /* Capture the source ID in case it gets reset for stopping. */
1136 const uint SourceID
{mSourceID
.load(std::memory_order_relaxed
)};
1138 /* Update voice info */
1139 mPosition
.store(DataPosInt
, std::memory_order_relaxed
);
1140 mPositionFrac
.store(DataPosFrac
, std::memory_order_relaxed
);
1141 mCurrentBuffer
.store(BufferListItem
, std::memory_order_relaxed
);
1144 mLoopBuffer
.store(nullptr, std::memory_order_relaxed
);
1145 mSourceID
.store(0u, std::memory_order_relaxed
);
1147 std::atomic_thread_fence(std::memory_order_release
);
1149 /* Send any events now, after the position/buffer info was updated. */
1150 const auto enabledevt
= Context
->mEnabledEvts
.load(std::memory_order_acquire
);
1151 if(buffers_done
> 0 && enabledevt
.test(al::to_underlying(AsyncEnableBits::BufferCompleted
)))
1153 RingBuffer
*ring
{Context
->mAsyncEvents
.get()};
1154 auto evt_vec
= ring
->getWriteVector();
1155 if(evt_vec
.first
.len
> 0)
1157 auto &evt
= InitAsyncEvent
<AsyncBufferCompleteEvent
>(evt_vec
.first
.buf
);
1159 evt
.mCount
= buffers_done
;
1160 ring
->writeAdvance(1);
1166 /* If the voice just ended, set it to Stopping so the next render
1167 * ensures any residual noise fades to 0 amplitude.
1169 mPlayState
.store(Stopping
, std::memory_order_release
);
1170 if(enabledevt
.test(al::to_underlying(AsyncEnableBits::SourceState
)))
1171 SendSourceStoppedEvent(Context
, SourceID
);
1175 void Voice::prepare(DeviceBase
*device
)
1177 /* Even if storing really high order ambisonics, we only mix channels for
1178 * orders up to the device order. The rest are simply dropped.
1180 uint num_channels
{(mFmtChannels
== FmtUHJ2
|| mFmtChannels
== FmtSuperStereo
) ? 3 :
1181 (mFmtChannels
== FmtMonoDup
) ? 2 :
1182 ChannelsFromFmt(mFmtChannels
, std::min(mAmbiOrder
, device
->mAmbiOrder
))};
1183 if(num_channels
> device
->mSampleData
.size()) UNLIKELY
1185 ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels
,
1186 device
->mSampleData
.size(), mFmtChannels
, mAmbiOrder
);
1187 num_channels
= static_cast<uint
>(device
->mSampleData
.size());
1189 if(mChans
.capacity() > 2 && num_channels
< mChans
.capacity())
1191 decltype(mChans
){}.swap(mChans
);
1192 decltype(mPrevSamples
){}.swap(mPrevSamples
);
1194 mChans
.reserve(std::max(2u, num_channels
));
1195 mChans
.resize(num_channels
);
1196 mPrevSamples
.reserve(std::max(2u, num_channels
));
1197 mPrevSamples
.resize(num_channels
);
1200 mDecoderPadding
= 0;
1201 if(mFmtChannels
== FmtSuperStereo
)
1203 switch(UhjDecodeQuality
)
1205 case UhjQualityType::IIR
:
1206 mDecoder
= std::make_unique
<UhjStereoDecoderIIR
>();
1207 mDecoderPadding
= UhjStereoDecoderIIR::sInputPadding
;
1209 case UhjQualityType::FIR256
:
1210 mDecoder
= std::make_unique
<UhjStereoDecoder
<UhjLength256
>>();
1211 mDecoderPadding
= UhjStereoDecoder
<UhjLength256
>::sInputPadding
;
1213 case UhjQualityType::FIR512
:
1214 mDecoder
= std::make_unique
<UhjStereoDecoder
<UhjLength512
>>();
1215 mDecoderPadding
= UhjStereoDecoder
<UhjLength512
>::sInputPadding
;
1219 else if(IsUHJ(mFmtChannels
))
1221 switch(UhjDecodeQuality
)
1223 case UhjQualityType::IIR
:
1224 mDecoder
= std::make_unique
<UhjDecoderIIR
>();
1225 mDecoderPadding
= UhjDecoderIIR::sInputPadding
;
1227 case UhjQualityType::FIR256
:
1228 mDecoder
= std::make_unique
<UhjDecoder
<UhjLength256
>>();
1229 mDecoderPadding
= UhjDecoder
<UhjLength256
>::sInputPadding
;
1231 case UhjQualityType::FIR512
:
1232 mDecoder
= std::make_unique
<UhjDecoder
<UhjLength512
>>();
1233 mDecoderPadding
= UhjDecoder
<UhjLength512
>::sInputPadding
;
1238 /* Clear the stepping value explicitly so the mixer knows not to mix this
1239 * until the update gets applied.
1243 /* Make sure the sample history is cleared. */
1244 std::fill(mPrevSamples
.begin(), mPrevSamples
.end(), HistoryLine
{});
1246 if(mFmtChannels
== FmtUHJ2
&& !device
->mUhjEncoder
)
1248 /* 2-channel UHJ needs different shelf filters. However, we can't just
1249 * use different shelf filters after mixing it, given any old speaker
1250 * setup the user has. To make this work, we apply the expected shelf
1251 * filters for decoding UHJ2 to quad (only needs LF scaling), and act
1252 * as if those 4 quad channels are encoded right back into B-Format.
1254 * This isn't perfect, but without an entirely separate and limited
1255 * UHJ2 path, it's better than nothing.
1257 * Note this isn't needed with UHJ output (UHJ2->B-Format->UHJ2 is
1258 * identity, so don't mess with it).
1260 const BandSplitter splitter
{device
->mXOverFreq
/ static_cast<float>(device
->Frequency
)};
1261 for(auto &chandata
: mChans
)
1263 chandata
.mAmbiHFScale
= 1.0f
;
1264 chandata
.mAmbiLFScale
= 1.0f
;
1265 chandata
.mAmbiSplitter
= splitter
;
1266 chandata
.mDryParams
= DirectParams
{};
1267 chandata
.mDryParams
.NFCtrlFilter
= device
->mNFCtrlFilter
;
1268 std::fill_n(chandata
.mWetParams
.begin(), device
->NumAuxSends
, SendParams
{});
1270 mChans
[0].mAmbiLFScale
= DecoderBase::sWLFScale
;
1271 mChans
[1].mAmbiLFScale
= DecoderBase::sXYLFScale
;
1272 mChans
[2].mAmbiLFScale
= DecoderBase::sXYLFScale
;
1273 mFlags
.set(VoiceIsAmbisonic
);
1275 /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher
1276 * order than the voice. No HF scaling is necessary to mix it.
1278 else if(mAmbiOrder
&& device
->mAmbiOrder
> mAmbiOrder
)
1280 const uint8_t *OrderFromChan
{Is2DAmbisonic(mFmtChannels
) ?
1281 AmbiIndex::OrderFrom2DChannel
.data() : AmbiIndex::OrderFromChannel
.data()};
1282 const auto scales
= AmbiScale::GetHFOrderScales(mAmbiOrder
, device
->mAmbiOrder
,
1285 const BandSplitter splitter
{device
->mXOverFreq
/ static_cast<float>(device
->Frequency
)};
1286 for(auto &chandata
: mChans
)
1288 chandata
.mAmbiHFScale
= scales
[*(OrderFromChan
++)];
1289 chandata
.mAmbiLFScale
= 1.0f
;
1290 chandata
.mAmbiSplitter
= splitter
;
1291 chandata
.mDryParams
= DirectParams
{};
1292 chandata
.mDryParams
.NFCtrlFilter
= device
->mNFCtrlFilter
;
1293 std::fill_n(chandata
.mWetParams
.begin(), device
->NumAuxSends
, SendParams
{});
1295 mFlags
.set(VoiceIsAmbisonic
);
1299 for(auto &chandata
: mChans
)
1301 chandata
.mDryParams
= DirectParams
{};
1302 chandata
.mDryParams
.NFCtrlFilter
= device
->mNFCtrlFilter
;
1303 std::fill_n(chandata
.mWetParams
.begin(), device
->NumAuxSends
, SendParams
{});
1305 mFlags
.reset(VoiceIsAmbisonic
);