aboutsummaryrefslogtreecommitdiffstats
path: root/core/context.h
blob: 980514b3fd4bffb64ade791b361dd49afb2c5d77 (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
#ifndef CORE_CONTEXT_H
#define CORE_CONTEXT_H

#include <array>
#include <atomic>
#include <bitset>
#include <cstddef>
#include <memory>
#include <thread>
#include <vector>

#include "almalloc.h"
#include "alsem.h"
#include "alspan.h"
#include "async_event.h"
#include "atomic.h"
#include "flexarray.h"
#include "opthelpers.h"
#include "vecmat.h"

struct DeviceBase;
struct EffectSlot;
struct EffectSlotProps;
struct RingBuffer;
struct Voice;
struct VoiceChange;
struct VoicePropsItem;


inline constexpr float SpeedOfSoundMetersPerSec{343.3f};

inline constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */

enum class DistanceModel : unsigned char {
    Disable,
    Inverse, InverseClamped,
    Linear, LinearClamped,
    Exponent, ExponentClamped,

    Default = InverseClamped
};


struct ContextProps {
    std::array<float,3> Position;
    std::array<float,3> Velocity;
    std::array<float,3> OrientAt;
    std::array<float,3> OrientUp;
    float Gain;
    float MetersPerUnit;
    float AirAbsorptionGainHF;

    float DopplerFactor;
    float DopplerVelocity;
    float SpeedOfSound;
    bool SourceDistanceModel;
    DistanceModel mDistanceModel;

    std::atomic<ContextProps*> next;
};

struct ContextParams {
    /* Pointer to the most recent property values that are awaiting an update. */
    std::atomic<ContextProps*> ContextUpdate{nullptr};

    alu::Vector Position{};
    alu::Matrix Matrix{alu::Matrix::Identity()};
    alu::Vector Velocity{};

    float Gain{1.0f};
    float MetersPerUnit{1.0f};
    float AirAbsorptionGainHF{AirAbsorbGainHF};

    float DopplerFactor{1.0f};
    float SpeedOfSound{SpeedOfSoundMetersPerSec}; /* in units per sec! */

    bool SourceDistanceModel{false};
    DistanceModel mDistanceModel{};
};

struct ContextBase {
    DeviceBase *const mDevice;

    /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
     * indicates if updates are currently happening).
     */
    std::atomic<unsigned int> mUpdateCount{0u};
    std::atomic<bool> mHoldUpdates{false};
    std::atomic<bool> mStopVoicesOnDisconnect{true};

    float mGainBoost{1.0f};

    /* Linked lists of unused property containers, free to use for future
     * updates.
     */
    std::atomic<ContextProps*> mFreeContextProps{nullptr};
    std::atomic<VoicePropsItem*> mFreeVoiceProps{nullptr};
    std::atomic<EffectSlotProps*> mFreeEffectSlotProps{nullptr};

    /* The voice change tail is the beginning of the "free" elements, up to and
     * *excluding* the current. If tail==current, there's no free elements and
     * new ones need to be allocated. The current voice change is the element
     * last processed, and any after are pending.
     */
    VoiceChange *mVoiceChangeTail{};
    std::atomic<VoiceChange*> mCurrentVoiceChange{};

    void allocVoiceChanges();
    void allocVoiceProps();
    void allocEffectSlotProps();
    void allocContextProps();

    ContextParams mParams;

    using VoiceArray = al::FlexArray<Voice*>;
    al::atomic_unique_ptr<VoiceArray> mVoices{};
    std::atomic<size_t> mActiveVoiceCount{};

    void allocVoices(size_t addcount);
    [[nodiscard]] auto getVoicesSpan() const noexcept -> al::span<Voice*>
    {
        return {mVoices.load(std::memory_order_relaxed)->data(),
            mActiveVoiceCount.load(std::memory_order_relaxed)};
    }
    [[nodiscard]] auto getVoicesSpanAcquired() const noexcept -> al::span<Voice*>
    {
        return {mVoices.load(std::memory_order_acquire)->data(),
            mActiveVoiceCount.load(std::memory_order_acquire)};
    }


    using EffectSlotArray = al::FlexArray<EffectSlot*>;
    std::atomic<EffectSlotArray*> mActiveAuxSlots{nullptr};

    std::thread mEventThread;
    al::semaphore mEventSem;
    std::unique_ptr<RingBuffer> mAsyncEvents;
    using AsyncEventBitset = std::bitset<al::to_underlying(AsyncEnableBits::Count)>;
    std::atomic<AsyncEventBitset> mEnabledEvts{0u};

    /* Asynchronous voice change actions are processed as a linked list of
     * VoiceChange objects by the mixer, which is atomically appended to.
     * However, to avoid allocating each object individually, they're allocated
     * in clusters that are stored in a vector for easy automatic cleanup.
     */
    using VoiceChangeCluster = std::unique_ptr<std::array<VoiceChange,128>>;
    std::vector<VoiceChangeCluster> mVoiceChangeClusters;

    using VoiceCluster = std::unique_ptr<std::array<Voice,32>>;
    std::vector<VoiceCluster> mVoiceClusters;

    using VoicePropsCluster = std::unique_ptr<std::array<VoicePropsItem,32>>;
    std::vector<VoicePropsCluster> mVoicePropClusters;


    EffectSlot *getEffectSlot();

    using EffectSlotCluster = std::unique_ptr<std::array<EffectSlot,4>>;
    std::vector<EffectSlotCluster> mEffectSlotClusters;

    using EffectSlotPropsCluster = std::unique_ptr<std::array<EffectSlotProps,4>>;
    std::vector<EffectSlotPropsCluster> mEffectSlotPropClusters;

    /* This could be greater than 2, but there should be no way there can be
     * more than two context property updates in use simultaneously.
     */
    using ContextPropsCluster = std::unique_ptr<std::array<ContextProps,2>>;
    std::vector<ContextPropsCluster> mContextPropClusters;


    ContextBase(DeviceBase *device);
    ContextBase(const ContextBase&) = delete;
    ContextBase& operator=(const ContextBase&) = delete;
    ~ContextBase();
};

#endif /* CORE_CONTEXT_H */