#ifndef CORE_DEVICE_H #define CORE_DEVICE_H #include #include #include #include #include #include #include #include #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 : unsigned char { Playback, Capture, Loopback }; enum class RenderMode : unsigned char { Normal, Pairwise, Hrtf }; enum class StereoEncoding : unsigned char { Basic, Uhj, Hrtf, Default = Basic }; struct InputRemixMap { struct TargetMix { Channel channel; float mix; }; Channel channel; al::span targets; }; /* Maximum delay in samples for speaker distance compensation. */ #define MAX_DELAY_LENGTH 1024 struct DistanceComp { struct ChanData { float Gain{1.0f}; uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */ float *Buffer{nullptr}; }; std::array mChannels; al::FlexArray mSamples; DistanceComp(size_t count) : mSamples{count} { } static std::unique_ptr Create(size_t numsamples) { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } DEF_FAM_NEWDEL(DistanceComp, mSamples) }; struct BFChannelConfig { float Scale; uint Index; }; struct MixParams { /* Coefficient channel mapping for mixing to the buffer. */ std::array AmbiMap{}; al::span Buffer; }; struct RealMixParams { al::span RemixMap; std::array ChannelIndex{}; al::span Buffer; }; using AmbiRotateMatrix = std::array,MaxAmbiChannels>; enum { // Frequency was requested by the app or config file FrequencyRequest, // Channel configuration was requested by the 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, DeviceFlagsCount }; struct DeviceBase { /* To avoid extraneous allocations, a 0-sized FlexArray is * defined globally as a sharable object. */ static al::FlexArray sEmptyContextArray; std::atomic 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 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{}; uint SamplesDone{0u}; std::chrono::nanoseconds ClockBase{0}; std::chrono::nanoseconds FixedLatency{0}; AmbiRotateMatrix mAmbiRotateMatrix{}; /* Temp storage used for mixer processing. */ static constexpr size_t MixerLineSize{BufferLineSize + MaxResamplerPadding + DecoderBase::sMaxDelay}; static constexpr size_t MixerChannelsMax{16}; using MixerBufferLine = std::array; alignas(16) std::array mSampleData; alignas(16) float ResampledData[BufferLineSize]; 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 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 mHrtfState; al::intrusive_ptr mHrtf; uint mIrSize{0}; /* Ambisonic-to-UHJ encoder */ std::unique_ptr mUhjEncoder; /* Ambisonic decoder for speakers */ std::unique_ptr AmbiDecoder; /* Stereo-to-binaural filter */ std::unique_ptr Bs2b; using PostProc = void(DeviceBase::*)(const size_t SamplesToDo); PostProc PostProcess{nullptr}; std::unique_ptr Limiter; /* Delay buffers used to compensate for speaker distances. */ std::unique_ptr 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. */ RefCount MixCount{0u}; // Contexts created on this device std::atomic*> 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; } 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 LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); } void renderSamples(const al::span 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 __USE_MINGW_ANSI_STDIO [[gnu::format(gnu_printf,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 * INVALID_CHANNEL_INDEX if it doesn't exist. */ uint 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" #define INVALID_CHANNEL_INDEX ~0u #endif /* CORE_DEVICE_H */