1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
|
#ifndef CORE_DEVICE_H
#define CORE_DEVICE_H
#include <array>
#include <atomic>
#include <bitset>
#include <chrono>
#include <memory>
#include <stddef.h>
#include <stdint.h>
#include <string>
#include "almalloc.h"
#include "alspan.h"
#include "ambidefs.h"
#include "atomic.h"
#include "bufferline.h"
#include "devformat.h"
#include "filters/nfc.h"
#include "intrusive_ptr.h"
#include "mixer/hrtfdefs.h"
#include "opthelpers.h"
#include "resampler_limits.h"
#include "uhjfilter.h"
#include "vector.h"
class BFormatDec;
struct bs2b;
struct Compressor;
struct ContextBase;
struct DirectHrtfState;
struct HrtfStore;
using uint = unsigned int;
#define MIN_OUTPUT_RATE 8000
#define MAX_OUTPUT_RATE 192000
#define DEFAULT_OUTPUT_RATE 48000
#define DEFAULT_UPDATE_SIZE 960 /* 20ms */
#define DEFAULT_NUM_UPDATES 3
enum class DeviceType : uint8_t {
Playback,
Capture,
Loopback
};
enum class RenderMode : uint8_t {
Normal,
Pairwise,
Hrtf
};
enum class StereoEncoding : uint8_t {
Basic,
Uhj,
Hrtf,
Default = Basic
};
struct InputRemixMap {
struct TargetMix { Channel channel; float mix; };
Channel channel;
al::span<const TargetMix> targets;
};
struct DistanceComp {
/* Maximum delay in samples for speaker distance compensation. */
static constexpr uint MaxDelay{1024};
struct ChanData {
float Gain{1.0f};
uint Length{0u}; /* Valid range is [0...MaxDelay). */
float *Buffer{nullptr};
};
std::array<ChanData,MAX_OUTPUT_CHANNELS> mChannels;
al::FlexArray<float,16> mSamples;
DistanceComp(size_t count) : mSamples{count} { }
static std::unique_ptr<DistanceComp> Create(size_t numsamples)
{ return std::unique_ptr<DistanceComp>{new(FamCount(numsamples)) DistanceComp{numsamples}}; }
DEF_FAM_NEWDEL(DistanceComp, mSamples)
};
constexpr uint8_t InvalidChannelIndex{static_cast<uint8_t>(~0u)};
struct BFChannelConfig {
float Scale;
uint Index;
};
struct MixParams {
/* Coefficient channel mapping for mixing to the buffer. */
std::array<BFChannelConfig,MaxAmbiChannels> AmbiMap{};
al::span<FloatBufferLine> Buffer;
/**
* Helper to set an identity/pass-through panning for ambisonic mixing. The
* source is expected to be a 3D ACN/N3D ambisonic buffer, and for each
* channel [0...count), the given functor is called with the source channel
* index, destination channel index, and the gain for that channel. If the
* destination channel is InvalidChannelIndex, the given source channel is
* not used for output.
*/
template<typename F>
void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const
{
const size_t numIn{inmix.Buffer.size()};
const size_t numOut{Buffer.size()};
for(size_t i{0};i < numIn;++i)
{
uint8_t idx{InvalidChannelIndex};
float gain{0.0f};
for(size_t j{0};j < numOut;++j)
{
if(AmbiMap[j].Index == inmix.AmbiMap[i].Index)
{
idx = static_cast<uint8_t>(j);
gain = AmbiMap[j].Scale * gainbase;
break;
}
}
func(i, idx, gain);
}
}
};
struct RealMixParams {
al::span<const InputRemixMap> RemixMap;
std::array<uint8_t,MaxChannels> ChannelIndex{};
al::span<FloatBufferLine> Buffer;
};
using AmbiRotateMatrix = std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels>;
enum {
// Frequency was requested by the app or config file
FrequencyRequest,
// Channel configuration was requested by the app or config file
ChannelsRequest,
// Sample type was requested by the config file
SampleTypeRequest,
// Specifies if the DSP is paused at user request
DevicePaused,
// Specifies if the device is currently running
DeviceRunning,
// Specifies if the output plays directly on/in ears (headphones, headset,
// ear buds, etc).
DirectEar,
/* Specifies if output is using speaker virtualization (e.g. Windows
* Spatial Audio).
*/
Virtualization,
DeviceFlagsCount
};
struct DeviceBase {
/* To avoid extraneous allocations, a 0-sized FlexArray<ContextBase*> is
* defined globally as a sharable object.
*/
static al::FlexArray<ContextBase*> sEmptyContextArray;
std::atomic<bool> Connected{true};
const DeviceType Type{};
uint Frequency{};
uint UpdateSize{};
uint BufferSize{};
DevFmtChannels FmtChans{};
DevFmtType FmtType{};
uint mAmbiOrder{0};
float mXOverFreq{400.0f};
/* If the main device mix is horizontal/2D only. */
bool m2DMixing{false};
/* For DevFmtAmbi* output only, specifies the channel order and
* normalization.
*/
DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default};
DevAmbiScaling mAmbiScale{DevAmbiScaling::Default};
std::string DeviceName;
// Device flags
std::bitset<DeviceFlagsCount> Flags{};
uint NumAuxSends{};
/* Rendering mode. */
RenderMode mRenderMode{RenderMode::Normal};
/* The average speaker distance as determined by the ambdec configuration,
* HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
*/
float AvgSpeakerDist{0.0f};
/* The default NFC filter. Not used directly, but is pre-initialized with
* the control distance from AvgSpeakerDist.
*/
NfcFilter mNFCtrlFilter{};
std::atomic<uint> mSamplesDone{0u};
std::atomic<std::chrono::nanoseconds> mClockBase{std::chrono::nanoseconds{}};
std::chrono::nanoseconds FixedLatency{0};
AmbiRotateMatrix mAmbiRotateMatrix{};
AmbiRotateMatrix mAmbiRotateMatrix2{};
/* Temp storage used for mixer processing. */
static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding};
static constexpr size_t MixerChannelsMax{16};
using MixerBufferLine = std::array<float,MixerLineSize>;
alignas(16) std::array<MixerBufferLine,MixerChannelsMax> mSampleData;
alignas(16) std::array<float,MixerLineSize+MaxResamplerPadding> mResampleData;
alignas(16) float FilteredData[BufferLineSize];
union {
alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength];
alignas(16) float NfcSampleData[BufferLineSize];
};
/* Persistent storage for HRTF mixing. */
alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength];
/* Mixing buffer used by the Dry mix and Real output. */
al::vector<FloatBufferLine, 16> MixBuffer;
/* The "dry" path corresponds to the main output. */
MixParams Dry;
uint NumChannelsPerOrder[MaxAmbiOrder+1]{};
/* "Real" output, which will be written to the device buffer. May alias the
* dry buffer.
*/
RealMixParams RealOut;
/* HRTF state and info */
std::unique_ptr<DirectHrtfState> mHrtfState;
al::intrusive_ptr<HrtfStore> mHrtf;
uint mIrSize{0};
/* Ambisonic-to-UHJ encoder */
std::unique_ptr<UhjEncoderBase> mUhjEncoder;
/* Ambisonic decoder for speakers */
std::unique_ptr<BFormatDec> AmbiDecoder;
/* Stereo-to-binaural filter */
std::unique_ptr<bs2b> Bs2b;
using PostProc = void(DeviceBase::*)(const size_t SamplesToDo);
PostProc PostProcess{nullptr};
std::unique_ptr<Compressor> Limiter;
/* Delay buffers used to compensate for speaker distances. */
std::unique_ptr<DistanceComp> ChannelDelays;
/* Dithering control. */
float DitherDepth{0.0f};
uint DitherSeed{0u};
/* Running count of the mixer invocations, in 31.1 fixed point. This
* actually increments *twice* when mixing, first at the start and then at
* the end, so the bottom bit indicates if the device is currently mixing
* and the upper bits indicates how many mixes have been done.
*/
std::atomic<uint> MixCount{0u};
// Contexts created on this device
std::atomic<al::FlexArray<ContextBase*>*> mContexts{nullptr};
DeviceBase(DeviceType type);
DeviceBase(const DeviceBase&) = delete;
DeviceBase& operator=(const DeviceBase&) = delete;
~DeviceBase();
uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
uint waitForMix() const noexcept
{
uint refcount;
while((refcount=MixCount.load(std::memory_order_acquire))&1) {
}
return refcount;
}
/**
* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate. Should only be called while
* watching the MixCount.
*/
std::chrono::nanoseconds getClockTime() const noexcept
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency;
return mClockBase.load(std::memory_order_relaxed) + ns;
}
void ProcessHrtf(const size_t SamplesToDo);
void ProcessAmbiDec(const size_t SamplesToDo);
void ProcessAmbiDecStablized(const size_t SamplesToDo);
void ProcessUhj(const size_t SamplesToDo);
void ProcessBs2b(const size_t SamplesToDo);
inline void postProcess(const size_t SamplesToDo)
{ if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); }
void renderSamples(const al::span<float*> outBuffers, const uint numSamples);
void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep);
/* Caller must lock the device state, and the mixer must not be running. */
#ifdef __MINGW32__
[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
#else
[[gnu::format(printf,2,3)]]
#endif
void handleDisconnect(const char *msg, ...);
/**
* Returns the index for the given channel name (e.g. FrontCenter), or
* InvalidChannelIndex if it doesn't exist.
*/
uint8_t channelIdxByName(Channel chan) const noexcept
{ return RealOut.ChannelIndex[chan]; }
DISABLE_ALLOC()
private:
uint renderSamples(const uint numSamples);
};
/* Must be less than 15 characters (16 including terminating null) for
* compatibility with pthread_setname_np limitations. */
#define MIXER_THREAD_NAME "alsoft-mixer"
#define RECORD_THREAD_NAME "alsoft-record"
#endif /* CORE_DEVICE_H */
|