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 /* If there's a matching sample step and no phase offset, use a simple copy
791 const ResamplerFunc Resample
{(increment
== MixerFracOne
&& DataPosFrac
== 0)
792 ? ResamplerFunc
{[](const InterpState
*, const float *RESTRICT src
, uint
, const uint
,
793 const al::span
<float> dst
) { std::copy_n(src
, dst
.size(), dst
.begin()); }}
796 /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels
797 * (3rd channel is generated from decoding).
799 const size_t realChannels
{(mFmtChannels
== FmtUHJ2
|| mFmtChannels
== FmtSuperStereo
) ? 2u
800 : MixingSamples
.size()};
801 for(size_t chan
{0};chan
< realChannels
;++chan
)
803 using ResBufType
= decltype(DeviceBase::mResampleData
);
804 static constexpr uint srcSizeMax
{static_cast<uint
>(ResBufType
{}.size()-MaxResamplerEdge
)};
806 const al::span prevSamples
{mPrevSamples
[chan
]};
807 const auto resampleBuffer
= std::copy(prevSamples
.cbegin(), prevSamples
.cend(),
808 Device
->mResampleData
.begin()) - MaxResamplerEdge
;
809 int intPos
{DataPosInt
};
810 uint fracPos
{DataPosFrac
};
812 /* Load samples for this channel from the available buffer(s), with
815 for(uint samplesLoaded
{0};samplesLoaded
< samplesToLoad
;)
817 /* Calculate the number of dst samples that can be loaded this
818 * iteration, given the available resampler buffer size, and the
819 * number of src samples that are needed to load it.
821 auto calc_buffer_sizes
= [fracPos
,increment
](uint dstBufferSize
)
823 /* If ext=true, calculate the last written dst pos from the dst
824 * count, convert to the last read src pos, then add one to get
827 * If ext=false, convert the dst count to src count directly.
829 * Without this, the src count could be short by one when
830 * increment < 1.0, or not have a full src at the end when
833 const bool ext
{increment
<= MixerFracOne
};
834 uint64_t dataSize64
{dstBufferSize
- ext
};
835 dataSize64
= (dataSize64
*increment
+ fracPos
) >> MixerFracBits
;
836 /* Also include resampler padding. */
837 dataSize64
+= ext
+ MaxResamplerEdge
;
839 if(dataSize64
<= srcSizeMax
)
840 return std::make_pair(dstBufferSize
, static_cast<uint
>(dataSize64
));
842 /* If the source size got saturated, we can't fill the desired
843 * dst size. Figure out how many dst samples we can fill.
845 dataSize64
= srcSizeMax
- MaxResamplerEdge
;
846 dataSize64
= ((dataSize64
<<MixerFracBits
) - fracPos
) / increment
;
847 if(dataSize64
< dstBufferSize
)
849 /* Some resamplers require the destination being 16-byte
850 * aligned, so limit to a multiple of 4 samples to maintain
851 * alignment if we need to do another iteration after this.
853 dstBufferSize
= static_cast<uint
>(dataSize64
) & ~3u;
855 return std::make_pair(dstBufferSize
, srcSizeMax
);
857 const auto [dstBufferSize
, srcBufferSize
] = calc_buffer_sizes(
858 samplesToLoad
- samplesLoaded
);
860 /* Load the necessary samples from the given buffer(s). */
863 const uint avail
{std::min(srcBufferSize
, uint
{MaxResamplerEdge
})};
864 const uint tofill
{std::max(srcBufferSize
, uint
{MaxResamplerEdge
})};
866 /* When loading from a voice that ended prematurely, only take
867 * the samples that get closest to 0 amplitude. This helps
868 * certain sounds fade out better.
870 auto abs_lt
= [](const float lhs
, const float rhs
) noexcept
-> bool
871 { return std::abs(lhs
) < std::abs(rhs
); };
872 auto srciter
= std::min_element(resampleBuffer
, resampleBuffer
+avail
, abs_lt
);
874 std::fill(srciter
+1, resampleBuffer
+tofill
, *srciter
);
878 size_t srcSampleDelay
{0};
879 if(intPos
< 0) UNLIKELY
881 /* If the current position is negative, there's that many
882 * silent samples to load before using the buffer.
884 srcSampleDelay
= static_cast<uint
>(-intPos
);
885 if(srcSampleDelay
>= srcBufferSize
)
887 /* If the number of silent source samples exceeds the
888 * number to load, the output will be silent.
890 std::fill_n(MixingSamples
[chan
]+samplesLoaded
, dstBufferSize
, 0.0f
);
891 std::fill_n(resampleBuffer
, srcBufferSize
, 0.0f
);
895 std::fill_n(resampleBuffer
, srcSampleDelay
, 0.0f
);
897 const uint uintPos
{static_cast<uint
>(std::max(intPos
, 0))};
899 if(mFlags
.test(VoiceIsStatic
))
900 LoadBufferStatic(BufferListItem
, BufferLoopItem
, uintPos
, mFmtType
, chan
,
901 mFrameStep
, srcSampleDelay
, srcBufferSize
, al::to_address(resampleBuffer
));
902 else if(mFlags
.test(VoiceIsCallback
))
904 const uint callbackBase
{mCallbackBlockBase
* mSamplesPerBlock
};
905 const size_t bufferOffset
{uintPos
- callbackBase
};
906 const size_t needSamples
{bufferOffset
+ srcBufferSize
- srcSampleDelay
};
907 const size_t needBlocks
{(needSamples
+ mSamplesPerBlock
-1) / mSamplesPerBlock
};
908 if(!mFlags
.test(VoiceCallbackStopped
) && needBlocks
> mNumCallbackBlocks
)
910 const size_t byteOffset
{mNumCallbackBlocks
*size_t{mBytesPerBlock
}};
911 const size_t needBytes
{(needBlocks
-mNumCallbackBlocks
)*size_t{mBytesPerBlock
}};
913 const int gotBytes
{BufferListItem
->mCallback(BufferListItem
->mUserData
,
914 &BufferListItem
->mSamples
[byteOffset
], static_cast<int>(needBytes
))};
916 mFlags
.set(VoiceCallbackStopped
);
917 else if(static_cast<uint
>(gotBytes
) < needBytes
)
919 mFlags
.set(VoiceCallbackStopped
);
920 mNumCallbackBlocks
+= static_cast<uint
>(gotBytes
) / mBytesPerBlock
;
923 mNumCallbackBlocks
= static_cast<uint
>(needBlocks
);
925 const size_t numSamples
{size_t{mNumCallbackBlocks
} * mSamplesPerBlock
};
926 LoadBufferCallback(BufferListItem
, bufferOffset
, numSamples
, mFmtType
, chan
,
927 mFrameStep
, srcSampleDelay
, srcBufferSize
, al::to_address(resampleBuffer
));
930 LoadBufferQueue(BufferListItem
, BufferLoopItem
, uintPos
, mFmtType
, chan
,
931 mFrameStep
, srcSampleDelay
, srcBufferSize
, al::to_address(resampleBuffer
));
934 Resample(&mResampleState
, al::to_address(resampleBuffer
), fracPos
, increment
,
935 {MixingSamples
[chan
]+samplesLoaded
, dstBufferSize
});
937 /* Store the last source samples used for next time. */
938 if(vstate
== Playing
) LIKELY
940 /* Only store samples for the end of the mix, excluding what
941 * gets loaded for decoder padding.
943 const uint loadEnd
{samplesLoaded
+ dstBufferSize
};
944 if(samplesToMix
> samplesLoaded
&& samplesToMix
<= loadEnd
) LIKELY
946 const size_t dstOffset
{samplesToMix
- samplesLoaded
};
947 const size_t srcOffset
{(dstOffset
*increment
+ fracPos
) >> MixerFracBits
};
948 std::copy_n(resampleBuffer
-MaxResamplerEdge
+srcOffset
, prevSamples
.size(),
949 prevSamples
.begin());
954 samplesLoaded
+= dstBufferSize
;
955 if(samplesLoaded
< samplesToLoad
)
957 fracPos
+= dstBufferSize
*increment
;
958 const uint srcOffset
{fracPos
>> MixerFracBits
};
959 fracPos
&= MixerFracMask
;
960 intPos
+= static_cast<int>(srcOffset
);
962 /* If more samples need to be loaded, copy the back of the
963 * resampleBuffer to the front to reuse it. prevSamples isn't
964 * reliable since it's only updated for the end of the mix.
966 std::copy(resampleBuffer
-MaxResamplerEdge
+srcOffset
,
967 resampleBuffer
+MaxResamplerEdge
+srcOffset
, resampleBuffer
-MaxResamplerEdge
);
971 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 ChannelsFromFmt(mFmtChannels
, std::min(mAmbiOrder
, device
->mAmbiOrder
))};
1182 if(num_channels
> device
->mSampleData
.size()) UNLIKELY
1184 ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels
,
1185 device
->mSampleData
.size(), mFmtChannels
, mAmbiOrder
);
1186 num_channels
= static_cast<uint
>(device
->mSampleData
.size());
1188 if(mChans
.capacity() > 2 && num_channels
< mChans
.capacity())
1190 decltype(mChans
){}.swap(mChans
);
1191 decltype(mPrevSamples
){}.swap(mPrevSamples
);
1193 mChans
.reserve(std::max(2u, num_channels
));
1194 mChans
.resize(num_channels
);
1195 mPrevSamples
.reserve(std::max(2u, num_channels
));
1196 mPrevSamples
.resize(num_channels
);
1199 mDecoderPadding
= 0;
1200 if(mFmtChannels
== FmtSuperStereo
)
1202 switch(UhjDecodeQuality
)
1204 case UhjQualityType::IIR
:
1205 mDecoder
= std::make_unique
<UhjStereoDecoderIIR
>();
1206 mDecoderPadding
= UhjStereoDecoderIIR::sInputPadding
;
1208 case UhjQualityType::FIR256
:
1209 mDecoder
= std::make_unique
<UhjStereoDecoder
<UhjLength256
>>();
1210 mDecoderPadding
= UhjStereoDecoder
<UhjLength256
>::sInputPadding
;
1212 case UhjQualityType::FIR512
:
1213 mDecoder
= std::make_unique
<UhjStereoDecoder
<UhjLength512
>>();
1214 mDecoderPadding
= UhjStereoDecoder
<UhjLength512
>::sInputPadding
;
1218 else if(IsUHJ(mFmtChannels
))
1220 switch(UhjDecodeQuality
)
1222 case UhjQualityType::IIR
:
1223 mDecoder
= std::make_unique
<UhjDecoderIIR
>();
1224 mDecoderPadding
= UhjDecoderIIR::sInputPadding
;
1226 case UhjQualityType::FIR256
:
1227 mDecoder
= std::make_unique
<UhjDecoder
<UhjLength256
>>();
1228 mDecoderPadding
= UhjDecoder
<UhjLength256
>::sInputPadding
;
1230 case UhjQualityType::FIR512
:
1231 mDecoder
= std::make_unique
<UhjDecoder
<UhjLength512
>>();
1232 mDecoderPadding
= UhjDecoder
<UhjLength512
>::sInputPadding
;
1237 /* Clear the stepping value explicitly so the mixer knows not to mix this
1238 * until the update gets applied.
1242 /* Make sure the sample history is cleared. */
1243 std::fill(mPrevSamples
.begin(), mPrevSamples
.end(), HistoryLine
{});
1245 if(mFmtChannels
== FmtUHJ2
&& !device
->mUhjEncoder
)
1247 /* 2-channel UHJ needs different shelf filters. However, we can't just
1248 * use different shelf filters after mixing it, given any old speaker
1249 * setup the user has. To make this work, we apply the expected shelf
1250 * filters for decoding UHJ2 to quad (only needs LF scaling), and act
1251 * as if those 4 quad channels are encoded right back into B-Format.
1253 * This isn't perfect, but without an entirely separate and limited
1254 * UHJ2 path, it's better than nothing.
1256 * Note this isn't needed with UHJ output (UHJ2->B-Format->UHJ2 is
1257 * identity, so don't mess with it).
1259 const BandSplitter splitter
{device
->mXOverFreq
/ static_cast<float>(device
->Frequency
)};
1260 for(auto &chandata
: mChans
)
1262 chandata
.mAmbiHFScale
= 1.0f
;
1263 chandata
.mAmbiLFScale
= 1.0f
;
1264 chandata
.mAmbiSplitter
= splitter
;
1265 chandata
.mDryParams
= DirectParams
{};
1266 chandata
.mDryParams
.NFCtrlFilter
= device
->mNFCtrlFilter
;
1267 std::fill_n(chandata
.mWetParams
.begin(), device
->NumAuxSends
, SendParams
{});
1269 mChans
[0].mAmbiLFScale
= DecoderBase::sWLFScale
;
1270 mChans
[1].mAmbiLFScale
= DecoderBase::sXYLFScale
;
1271 mChans
[2].mAmbiLFScale
= DecoderBase::sXYLFScale
;
1272 mFlags
.set(VoiceIsAmbisonic
);
1274 /* Don't need to set the VoiceIsAmbisonic flag if the device is not higher
1275 * order than the voice. No HF scaling is necessary to mix it.
1277 else if(mAmbiOrder
&& device
->mAmbiOrder
> mAmbiOrder
)
1279 const uint8_t *OrderFromChan
{Is2DAmbisonic(mFmtChannels
) ?
1280 AmbiIndex::OrderFrom2DChannel
.data() : AmbiIndex::OrderFromChannel
.data()};
1281 const auto scales
= AmbiScale::GetHFOrderScales(mAmbiOrder
, device
->mAmbiOrder
,
1284 const BandSplitter splitter
{device
->mXOverFreq
/ static_cast<float>(device
->Frequency
)};
1285 for(auto &chandata
: mChans
)
1287 chandata
.mAmbiHFScale
= scales
[*(OrderFromChan
++)];
1288 chandata
.mAmbiLFScale
= 1.0f
;
1289 chandata
.mAmbiSplitter
= splitter
;
1290 chandata
.mDryParams
= DirectParams
{};
1291 chandata
.mDryParams
.NFCtrlFilter
= device
->mNFCtrlFilter
;
1292 std::fill_n(chandata
.mWetParams
.begin(), device
->NumAuxSends
, SendParams
{});
1294 mFlags
.set(VoiceIsAmbisonic
);
1298 for(auto &chandata
: mChans
)
1300 chandata
.mDryParams
= DirectParams
{};
1301 chandata
.mDryParams
.NFCtrlFilter
= device
->mNFCtrlFilter
;
1302 std::fill_n(chandata
.mWetParams
.begin(), device
->NumAuxSends
, SendParams
{});
1304 mFlags
.reset(VoiceIsAmbisonic
);