Use fmt instead of to_string and concating strings
[openal-soft.git] / core / device.h
blob344a932e8b63e662d49352233df0d8ef2467aff3
1 #ifndef CORE_DEVICE_H
2 #define CORE_DEVICE_H
4 #include <array>
5 #include <atomic>
6 #include <bitset>
7 #include <chrono>
8 #include <cstddef>
9 #include <cstdint>
10 #include <memory>
11 #include <string>
13 #include "almalloc.h"
14 #include "alspan.h"
15 #include "ambidefs.h"
16 #include "atomic.h"
17 #include "bufferline.h"
18 #include "devformat.h"
19 #include "filters/nfc.h"
20 #include "flexarray.h"
21 #include "intrusive_ptr.h"
22 #include "mixer/hrtfdefs.h"
23 #include "opthelpers.h"
24 #include "resampler_limits.h"
25 #include "uhjfilter.h"
26 #include "vector.h"
28 class BFormatDec;
29 namespace Bs2b {
30 struct bs2b;
31 } // namespace Bs2b
32 class Compressor;
33 struct ContextBase;
34 struct DirectHrtfState;
35 struct HrtfStore;
37 using uint = unsigned int;
40 inline constexpr std::size_t MinOutputRate{8000};
41 inline constexpr std::size_t MaxOutputRate{192000};
42 inline constexpr std::size_t DefaultOutputRate{48000};
44 inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */
45 inline constexpr std::size_t DefaultNumUpdates{3};
48 enum class DeviceType : std::uint8_t {
49 Playback,
50 Capture,
51 Loopback
55 enum class RenderMode : std::uint8_t {
56 Normal,
57 Pairwise,
58 Hrtf
61 enum class StereoEncoding : std::uint8_t {
62 Basic,
63 Uhj,
64 Hrtf,
66 Default = Basic
70 struct InputRemixMap {
71 struct TargetMix { Channel channel; float mix; };
73 Channel channel;
74 al::span<const TargetMix> targets;
78 struct DistanceComp {
79 /* Maximum delay in samples for speaker distance compensation. */
80 static constexpr uint MaxDelay{1024};
82 struct ChanData {
83 al::span<float> Buffer{}; /* Valid size is [0...MaxDelay). */
84 float Gain{1.0f};
87 std::array<ChanData,MaxOutputChannels> mChannels;
88 al::FlexArray<float,16> mSamples;
90 DistanceComp(std::size_t count) : mSamples{count} { }
92 static std::unique_ptr<DistanceComp> Create(std::size_t numsamples)
93 { return std::unique_ptr<DistanceComp>{new(FamCount(numsamples)) DistanceComp{numsamples}}; }
95 DEF_FAM_NEWDEL(DistanceComp, mSamples)
99 constexpr auto InvalidChannelIndex = static_cast<std::uint8_t>(~0u);
101 struct BFChannelConfig {
102 float Scale;
103 uint Index;
106 struct MixParams {
107 /* Coefficient channel mapping for mixing to the buffer. */
108 std::array<BFChannelConfig,MaxAmbiChannels> AmbiMap{};
110 al::span<FloatBufferLine> Buffer;
113 * Helper to set an identity/pass-through panning for ambisonic mixing. The
114 * source is expected to be a 3D ACN/N3D ambisonic buffer, and for each
115 * channel [0...count), the given functor is called with the source channel
116 * index, destination channel index, and the gain for that channel. If the
117 * destination channel is InvalidChannelIndex, the given source channel is
118 * not used for output.
120 template<typename F>
121 void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const
123 const std::size_t numIn{inmix.Buffer.size()};
124 const std::size_t numOut{Buffer.size()};
125 for(std::size_t i{0};i < numIn;++i)
127 std::uint8_t idx{InvalidChannelIndex};
128 float gain{0.0f};
130 for(std::size_t j{0};j < numOut;++j)
132 if(AmbiMap[j].Index == inmix.AmbiMap[i].Index)
134 idx = static_cast<std::uint8_t>(j);
135 gain = AmbiMap[j].Scale * gainbase;
136 break;
139 func(i, idx, gain);
144 struct RealMixParams {
145 al::span<const InputRemixMap> RemixMap;
146 std::array<std::uint8_t,MaxChannels> ChannelIndex{};
148 al::span<FloatBufferLine> Buffer;
151 using AmbiRotateMatrix = std::array<std::array<float,MaxAmbiChannels>,MaxAmbiChannels>;
153 enum {
154 // Frequency was requested by the app or config file
155 FrequencyRequest,
156 // Channel configuration was requested by the app or config file
157 ChannelsRequest,
158 // Sample type was requested by the config file
159 SampleTypeRequest,
161 // Specifies if the DSP is paused at user request
162 DevicePaused,
164 // Specifies if the output plays directly on/in ears (headphones, headset,
165 // ear buds, etc).
166 DirectEar,
168 /* Specifies if output is using speaker virtualization (e.g. Windows
169 * Spatial Audio).
171 Virtualization,
173 DeviceFlagsCount
176 enum class DeviceState : std::uint8_t {
177 Unprepared,
178 Configured,
179 Playing
182 struct SIMDALIGN DeviceBase {
183 std::atomic<bool> Connected{true};
184 const DeviceType Type{};
186 std::string mDeviceName;
188 uint Frequency{};
189 uint UpdateSize{};
190 uint BufferSize{};
192 DevFmtChannels FmtChans{};
193 DevFmtType FmtType{};
194 uint mAmbiOrder{0};
195 float mXOverFreq{400.0f};
196 /* If the main device mix is horizontal/2D only. */
197 bool m2DMixing{false};
198 /* For DevFmtAmbi* output only, specifies the channel order and
199 * normalization.
201 DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default};
202 DevAmbiScaling mAmbiScale{DevAmbiScaling::Default};
204 // Device flags
205 std::bitset<DeviceFlagsCount> Flags{};
206 DeviceState mDeviceState{DeviceState::Unprepared};
208 uint NumAuxSends{};
210 /* Rendering mode. */
211 RenderMode mRenderMode{RenderMode::Normal};
213 /* The average speaker distance as determined by the ambdec configuration,
214 * HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
216 float AvgSpeakerDist{0.0f};
218 /* The default NFC filter. Not used directly, but is pre-initialized with
219 * the control distance from AvgSpeakerDist.
221 NfcFilter mNFCtrlFilter{};
223 std::atomic<uint> mSamplesDone{0u};
224 std::atomic<std::chrono::nanoseconds> mClockBase{std::chrono::nanoseconds{}};
225 std::chrono::nanoseconds FixedLatency{0};
227 AmbiRotateMatrix mAmbiRotateMatrix{};
228 AmbiRotateMatrix mAmbiRotateMatrix2{};
230 /* Temp storage used for mixer processing. */
231 static constexpr std::size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding};
232 static constexpr std::size_t MixerChannelsMax{16};
233 alignas(16) std::array<float,MixerLineSize*MixerChannelsMax> mSampleData{};
234 alignas(16) std::array<float,MixerLineSize+MaxResamplerPadding> mResampleData{};
236 alignas(16) std::array<float,BufferLineSize> FilteredData{};
237 alignas(16) std::array<float,BufferLineSize+HrtfHistoryLength> ExtraSampleData{};
239 /* Persistent storage for HRTF mixing. */
240 alignas(16) std::array<float2,BufferLineSize+HrirLength> HrtfAccumData{};
242 /* Mixing buffer used by the Dry mix and Real output. */
243 al::vector<FloatBufferLine, 16> MixBuffer;
245 /* The "dry" path corresponds to the main output. */
246 MixParams Dry;
247 std::array<uint,MaxAmbiOrder+1> NumChannelsPerOrder{};
249 /* "Real" output, which will be written to the device buffer. May alias the
250 * dry buffer.
252 RealMixParams RealOut;
254 /* HRTF state and info */
255 std::unique_ptr<DirectHrtfState> mHrtfState;
256 al::intrusive_ptr<HrtfStore> mHrtf;
257 uint mIrSize{0};
259 /* Ambisonic-to-UHJ encoder */
260 std::unique_ptr<UhjEncoderBase> mUhjEncoder;
262 /* Ambisonic decoder for speakers */
263 std::unique_ptr<BFormatDec> AmbiDecoder;
265 /* Stereo-to-binaural filter */
266 std::unique_ptr<Bs2b::bs2b> Bs2b;
268 using PostProc = void(DeviceBase::*)(const size_t SamplesToDo);
269 PostProc PostProcess{nullptr};
271 std::unique_ptr<Compressor> Limiter;
273 /* Delay buffers used to compensate for speaker distances. */
274 std::unique_ptr<DistanceComp> ChannelDelays;
276 /* Dithering control. */
277 float DitherDepth{0.0f};
278 uint DitherSeed{0u};
280 /* Running count of the mixer invocations, in 31.1 fixed point. This
281 * actually increments *twice* when mixing, first at the start and then at
282 * the end, so the bottom bit indicates if the device is currently mixing
283 * and the upper bits indicates how many mixes have been done.
285 std::atomic<uint> mMixCount{0u};
287 // Contexts created on this device
288 al::atomic_unique_ptr<al::FlexArray<ContextBase*>> mContexts;
291 DeviceBase(DeviceType type);
292 DeviceBase(const DeviceBase&) = delete;
293 DeviceBase& operator=(const DeviceBase&) = delete;
294 ~DeviceBase();
296 [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); }
297 [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
298 [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); }
300 struct MixLock {
301 DeviceBase *const self;
302 const uint mEndVal;
304 MixLock(DeviceBase *device, const uint endval) noexcept : self{device}, mEndVal{endval} { }
305 MixLock(const MixLock&) = delete;
306 void operator=(const MixLock&) = delete;
307 /* Update the mix count when the lock goes out of scope to "release" it
308 * (lsb should be 0).
310 ~MixLock() { self->mMixCount.store(mEndVal, std::memory_order_release); }
312 auto getWriteMixLock() noexcept -> MixLock
314 /* Increment the mix count at the start of mixing and writing clock
315 * info (lsb should be 1).
317 auto mixCount = mMixCount.load(std::memory_order_relaxed);
318 mMixCount.store(++mixCount, std::memory_order_release);
319 return MixLock{this, ++mixCount};
322 /** Waits for the mixer to not be mixing or updating the clock. */
323 [[nodiscard]] auto waitForMix() const noexcept -> uint
325 uint refcount{mMixCount.load(std::memory_order_acquire)};
326 while((refcount&1)) refcount = mMixCount.load(std::memory_order_acquire);
327 return refcount;
331 * Helper to get the current clock time from the device's ClockBase, and
332 * SamplesDone converted from the sample rate. Should only be called while
333 * watching the MixCount.
335 [[nodiscard]] auto getClockTime() const noexcept -> std::chrono::nanoseconds
337 using std::chrono::seconds;
338 using std::chrono::nanoseconds;
340 auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency;
341 return mClockBase.load(std::memory_order_relaxed) + ns;
344 void ProcessHrtf(const std::size_t SamplesToDo);
345 void ProcessAmbiDec(const std::size_t SamplesToDo);
346 void ProcessAmbiDecStablized(const std::size_t SamplesToDo);
347 void ProcessUhj(const std::size_t SamplesToDo);
348 void ProcessBs2b(const std::size_t SamplesToDo);
350 inline void postProcess(const std::size_t SamplesToDo)
351 { if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); }
353 void renderSamples(const al::span<void*> outBuffers, const uint numSamples);
354 void renderSamples(void *outBuffer, const uint numSamples, const std::size_t frameStep);
356 /* Caller must lock the device state, and the mixer must not be running. */
357 #ifdef __MINGW32__
358 [[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]]
359 #else
360 [[gnu::format(printf,2,3)]]
361 #endif
362 void handleDisconnect(const char *msg, ...);
365 * Returns the index for the given channel name (e.g. FrontCenter), or
366 * InvalidChannelIndex if it doesn't exist.
368 [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t
369 { return RealOut.ChannelIndex[chan]; }
371 private:
372 uint renderSamples(const uint numSamples);
375 /* Must be less than 15 characters (16 including terminating null) for
376 * compatibility with pthread_setname_np limitations. */
377 [[nodiscard]] constexpr
378 auto GetMixerThreadName() noexcept -> const char* { return "alsoft-mixer"; }
380 [[nodiscard]] constexpr
381 auto GetRecordThreadName() noexcept -> const char* { return "alsoft-record"; }
383 #endif /* CORE_DEVICE_H */