aboutsummaryrefslogtreecommitdiffstats
path: root/core/device.h
blob: 1da087278931d42a4425a6749f3d206d140eea05 (plain)
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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
#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 "flexarray.h"
#include "intrusive_ptr.h"
#include "mixer/hrtfdefs.h"
#include "opthelpers.h"
#include "resampler_limits.h"
#include "uhjfilter.h"
#include "vector.h"

class BFormatDec;
namespace Bs2b {
struct bs2b;
} // namespace Bs2b
class Compressor;
struct ContextBase;
struct DirectHrtfState;
struct HrtfStore;

using uint = unsigned int;


inline constexpr size_t MinOutputRate{8000};
inline constexpr size_t MaxOutputRate{192000};
inline constexpr size_t DefaultOutputRate{48000};

inline constexpr size_t DefaultUpdateSize{960}; /* 20ms */
inline constexpr size_t DefaultNumUpdates{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,MaxOutputChannels> 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 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
};

enum class DeviceState : uint8_t {
    Unprepared,
    Configured,
    Playing
};

struct DeviceBase {
    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{};
    DeviceState mDeviceState{DeviceState::Unprepared};

    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) std::array<float,BufferLineSize> FilteredData{};
    union {
        alignas(16) std::array<float,BufferLineSize+HrtfHistoryLength> HrtfSourceData{};
        alignas(16) std::array<float,BufferLineSize> NfcSampleData;
    };

    /* Persistent storage for HRTF mixing. */
    alignas(16) std::array<float2,BufferLineSize+HrirLength> HrtfAccumData{};

    /* 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;
    std::array<uint,MaxAmbiOrder+1> NumChannelsPerOrder{};

    /* "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> 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> mMixCount{0u};

    // Contexts created on this device
    al::atomic_unique_ptr<al::FlexArray<ContextBase*>> mContexts;


    DeviceBase(DeviceType type);
    DeviceBase(const DeviceBase&) = delete;
    DeviceBase& operator=(const DeviceBase&) = delete;
    ~DeviceBase();

    [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); }
    [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
    [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); }

    struct MixLock {
        std::atomic<uint> &mCount;
        const uint mLastVal;

        MixLock(std::atomic<uint> &count, const uint last_val) noexcept
            : mCount{count}, mLastVal{last_val}
        { }
        /* Increment the mix count when the lock goes out of scope to "release"
         * it (lsb should be 0).
         */
        ~MixLock() { mCount.store(mLastVal+2, std::memory_order_release); }
    };
    auto getWriteMixLock() noexcept
    {
        /* Increment the mix count at the start of mixing and writing clock
         * info (lsb should be 1).
         */
        const auto mixCount = mMixCount.load(std::memory_order_relaxed);
        mMixCount.store(mixCount+1, std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_release);
        return MixLock{mMixCount, mixCount};
    }

    /** Waits for the mixer to not be mixing or updating the clock. */
    [[nodiscard]] auto waitForMix() const noexcept -> uint
    {
        uint refcount;
        while((refcount=mMixCount.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.
     */
    [[nodiscard]] auto getClockTime() const noexcept -> std::chrono::nanoseconds
    {
        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.
     */
    [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> uint8_t
    { return RealOut.ChannelIndex[chan]; }

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 */