Compare commits

...

675 Commits

Author SHA1 Message Date
Ivan Epifanov 27239f7844 Bump cmake version 2026-01-28 19:45:50 +03:00
Ivan Epifanov 75e5b00ef7 Allow setting thread affinity/priority and bump cmake version 2026-01-28 15:46:16 +03:00
Ivan Epifanov 430467664c Update openal 2021-02-28 23:04:23 +03:00
Ivan Epifanov ef45405eed Samplerate fixes 2021-01-28 23:36:19 +03:00
Ivan Epifanov 425e5b5615 Fixes 2021-01-27 18:48:35 +03:00
Ivan Epifanov cd9f8bd49d Building 2021-01-27 18:18:42 +03:00
Ivan Epifanov d9f5668968 Master is for patchsets 2021-01-27 18:16:01 +03:00
Chris Robinson 5ff5fd8ecc Use a standard bitset for bitfield flags 2021-01-22 00:00:10 -08:00
Chris Robinson 5729e1004d Make the endian test more C++-like 2021-01-21 23:21:18 -08:00
Chris Robinson 87dbeed853 Update apt before installing dependencies 2021-01-21 09:15:18 -08:00
Chris Robinson fb6585cf18 Fix handling paused devices with the wave writer backend 2021-01-21 06:51:29 -08:00
Chris Robinson 19adb622ef Simplify a division
The division opcode typically also generates the remainder, making the result
of x%y free with x/y.
2021-01-21 06:27:11 -08:00
Chris Robinson db4de2b56a Avoid declaring some variables multiple times 2021-01-21 06:25:50 -08:00
Chris Robinson 20f5e7c1fa Avoid global constexpr arrays 2021-01-21 04:03:30 -08:00
Chris Robinson 92c653e5f0 Update ChangeLog 2021-01-20 17:47:36 -08:00
Chris Robinson 1c649943f7 Revert "Print the build results after CI"
This reverts commit e1e0809253.
2021-01-20 06:33:11 -08:00
Chris Robinson e1e0809253 Print the build results after CI 2021-01-20 04:16:06 -08:00
Chris Robinson 32e0d92c51 Avoid extra copies/work when reading HRTF data 2021-01-20 01:26:55 -08:00
Chris Robinson 13698362f1 Avoid explicitly searching for the WindowsSDK
It's causing problems with various setups. So instead we'll have to assume some
things for Windows (namely that winmm exists, and if dsound isn't in DXSDK_DIR,
it needs to be in the compiler's default paths to be usable).
2021-01-19 17:19:44 -08:00
Chris Robinson c3380d4201 Remove constexpr from a function
Various versions of GCC have a problem with it, and it doesn't really add much
benefit anyway.
2021-01-17 23:39:33 -08:00
Chris Robinson 8ebf146c72 First try at using GitHub Actions 2021-01-17 23:09:42 -08:00
Chris Robinson 229bf45e24 Avoid calling readAdvance in a loop 2021-01-17 03:17:46 -08:00
Chris Robinson 855a8c0cd9 Don't assume the ring buffer's read vector is the next writable space
This is untrue since the ring buffer leaves one element unwritten, so there's
one extra element to be written once a readable element is read.
2021-01-15 18:18:07 -08:00
Chris Robinson 8c4adfd752 Use a span instead of passing a vector by reference 2021-01-11 17:39:57 -08:00
Chris Robinson 1f01311ee3 Make sure proper names are used for querying PulseAudio info 2021-01-10 13:34:05 -08:00
kcat 3348d3ce15 Merge pull request #515 from ilya-fedin/pulse-nullptr-default
Pass nullptr to pulse for default device
2021-01-10 11:25:05 -08:00
Ilya Fedin a30d3ad803 Pass nullptr to pulse for default device 2021-01-10 16:23:08 +04:00
Chris Robinson b99adb623d Remove the invalid noexcept specification 2021-01-08 19:23:17 -08:00
Chris Robinson 89906453ea Quickly fade out samples of a stopping voice
Rather than linearly fading out the mix over the mixing update, logarithmically
fade out the post-resampled samples by approx -60dB per millisecond.
2021-01-08 18:54:46 -08:00
Chris Robinson 537fd6d0a1 Avoid dereferencing the end iterator 2021-01-08 18:51:25 -08:00
Chris Robinson 1f24f5caa1 Ensure the endian test is constexpr 2021-01-06 18:04:04 -08:00
Chris Robinson 948ae3bc7e Avoid duplicating an inline template function 2021-01-06 17:52:50 -08:00
Chris Robinson a9ac165c39 Properly trace the new HRTF delay for the B-Format decoder 2021-01-06 17:19:10 -08:00
Chris Robinson 292de73c75 Use a template to read integers with endian awareness 2021-01-06 17:10:03 -08:00
Chris Robinson 04cd0bc576 Return non-const from the resampler function 2021-01-02 17:37:09 -08:00
Chris Robinson 1f51de9983 Make optional trivially destructible if the stored type is 2021-01-02 14:24:53 -08:00
Chris Robinson d6495b35df Fix comment typo 2021-01-01 15:35:46 -08:00
Chris Robinson 1327aca9d7 Don't use templates unnecessarily 2021-01-01 15:35:14 -08:00
Chris Robinson 87a862199d Make FPUCtl methods noexcept 2020-12-31 18:23:59 -08:00
Chris Robinson 20ef8bf390 Move cpu_caps and fpu_ctrl to core 2020-12-31 16:47:12 -08:00
Chris Robinson 002c506296 Remove an unnecessary include 2020-12-31 13:15:38 -08:00
Chris Robinson f2b7a063ef Add NEON-enhanced FIR loops for convolution and UHJ 2020-12-31 13:01:17 -08:00
Chris Robinson 9d354f721c Assume run-time NEON support if detected by cmake 2020-12-31 11:42:02 -08:00
Chris Robinson 78f3d8fcd8 Check for SSE and NEON earlier 2020-12-31 11:16:44 -08:00
Chris Robinson ec7e7c9174 Avoid ugly reinterpret_casts with offsetof 2020-12-29 09:57:08 -08:00
Chris Robinson 96942d7968 Combine multiple functions into one 2020-12-28 08:37:49 -08:00
Chris Robinson aae115c35a Return an intrusive_ptr from EffectStateFactory::create 2020-12-28 08:04:23 -08:00
Chris Robinson 15e05fcef8 Workaround a GCC 5 constexpr function bug 2020-12-28 03:36:26 -08:00
Chris Robinson 7fe9d8d20c Remove extraneous semicolor 2020-12-28 02:50:15 -08:00
Chris Robinson 62fa6a2277 Avoid including voice.h in alcontext.h 2020-12-27 16:14:11 -08:00
Chris Robinson ffa7d1b1a7 Reduce a few lines 2020-12-27 14:37:43 -08:00
Chris Robinson 43d053cb32 Reorder some device fields 2020-12-27 12:23:35 -08:00
Chris Robinson 04358890d9 Use a unique_ptr to hold DistanceComp data 2020-12-27 11:30:45 -08:00
Chris Robinson adb5467720 Use smaller types for enums 2020-12-27 10:28:59 -08:00
Chris Robinson 4c0f77c13e Remove some unnecessary constructors 2020-12-27 06:43:59 -08:00
Chris Robinson 7833cfa8de Make FlexArray trivially destructible when possible 2020-12-27 04:29:54 -08:00
Chris Robinson cf7f201c92 Reorder some context properties 2020-12-27 03:11:01 -08:00
Chris Robinson cd2ebb9c3a Use a uint for a bitfield 2020-12-27 01:05:16 -08:00
Chris Robinson e20143fcc4 Move the WetBuffer reference to EffectSlot 2020-12-27 00:14:58 -08:00
Chris Robinson 507cbfa027 Combine EffectState::deviceUpdate with setBuffer 2020-12-26 12:33:00 -08:00
Chris Robinson 54719de132 Avoid AL enums for the EffectSlot type 2020-12-26 09:21:30 -08:00
Chris Robinson 8850a3f730 Combine variable terms separate from the scaling weights 2020-12-26 06:27:49 -08:00
Chris Robinson 8a86ec8ac5 Add a crossover frequency field for the device
Used when upsampling low-order ambisonic signals to higher order. Rather than a
hardcoded 400hz, it ensures a consistent crossover point when an ambdec
configuration is used. It can also allow for an alsoft config option.
2020-12-26 05:23:08 -08:00
Chris Robinson 5f233a2c24 Use more constexpr variables instead of macros 2020-12-25 20:52:18 -08:00
Chris Robinson 223f93e13d Increase direct HRTF delay to 256
This should improve the error from the all-pass on the partial input.
2020-12-25 13:02:22 -08:00
Chris Robinson f6e29ce5bb Return an optional for an error 2020-12-25 09:26:24 -08:00
Chris Robinson 78f73a4277 Avoid dynamic vectors in AmbDecConf
Also add a bit more sanity checking
2020-12-25 08:31:02 -08:00
Chris Robinson eaf209d2f1 Update a comment and type used in the convolution effect 2020-12-25 07:00:57 -08:00
Chris Robinson 204f7d8811 Rename From2D to FromACN2D 2020-12-25 06:40:13 -08:00
Chris Robinson 20820fd01b Move the ambdec loader to core 2020-12-25 06:30:47 -08:00
Chris Robinson fe9ec157fd Use an import target for linking OpenSL 2020-12-24 22:49:55 -08:00
Chris Robinson 4bc686d89b Remove unnecessary includes 2020-12-24 22:49:55 -08:00
Chris Robinson 0d3b041aa2 Avoid AL types and enums in the effect processors 2020-12-24 22:49:55 -08:00
Chris Robinson eedc42890f Move alexcpt to core 2020-12-24 22:49:55 -08:00
Chris Robinson 63ea62bea1 Avoid some more AL types 2020-12-24 22:49:55 -08:00
David CARLIER 8c4c14ea73 fast_roundf implementations in asm for ARM64. (#511) 2020-12-24 22:48:57 -08:00
Chris Robinson 6ae0115bf7 Avoid AL/ALC types in the backends 2020-12-17 23:21:45 -08:00
Chris Robinson 5edd5a11fc Don't use ALC error enums for the backend error code 2020-12-17 21:07:53 -08:00
Chris Robinson 7d2e21334c Move the AL error enum out of base_exception 2020-12-17 16:46:21 -08:00
Chris Robinson 4d1ac95ae2 Don't return an enum from captureSamples
It's always no_error
2020-12-17 03:06:52 -08:00
Chris Robinson d578bc6cb1 Move logging to core 2020-12-17 02:47:03 -08:00
Chris Robinson f0fe57dc5a Don't link DistanceModel enums to AL values 2020-12-16 14:58:55 -08:00
Chris Robinson efc9c146c3 Move AsyncEvent to a separate header 2020-12-16 13:58:51 -08:00
Chris Robinson 9710a859fa Add missing include 2020-12-16 03:02:49 -08:00
Chris Robinson 41594a07bc Update changelog 2020-12-16 02:17:18 -08:00
Chris Robinson 02be0149f1 Send stop events when stopping paused voices 2020-12-16 02:12:18 -08:00
Chris Robinson c96b50fb65 Use a separate enum for the VoiceChange state 2020-12-16 01:40:15 -08:00
Chris Robinson 5ad28f8cba Move VoiceChange to a separate header 2020-12-16 00:50:50 -08:00
Chris Robinson f38919eecc Avoid using a hidden unnamed template parameter 2020-12-15 23:47:47 -08:00
Chris Robinson 5b51a58ff2 Fix some includes 2020-12-15 23:39:17 -08:00
Chris Robinson 304ab8d890 Avoid a generic event struct 2020-12-15 21:32:01 -08:00
Chris Robinson a08f68f213 Avoid some AL types 2020-12-15 20:48:21 -08:00
Chris Robinson daf9d46478 Use a separate structure for the context/listener params 2020-12-15 18:41:50 -08:00
Chris Robinson 03358a0d80 Avoid changing the global CPU flags in FillCPUCaps 2020-12-15 12:33:57 -08:00
Chris Robinson 9435641c0d Remove unneeded includes 2020-12-15 01:31:09 -08:00
Chris Robinson 56af137ba0 Move fmt_traits to core 2020-12-13 21:12:03 -08:00
Chris Robinson 783904e414 Avoid compiling different sources for different targets
Simplifies configuration and fixes a potential problem with inline functions.
Inline functions that fail to inline will have a callable body generated. If
such a body is generated with the SSE4 source, for example, it can generate
SSE4 instructions. Calls for that function in other sources can then end up
calling the SSE4-generated body outside of any CPU capability check.
2020-12-13 16:49:46 -08:00
Chris Robinson 225d42538d Be more consistent with size_t 2020-12-12 23:41:38 -08:00
Chris Robinson bb597546e1 Make sure to stop ALSA playback when the backend stops 2020-12-12 15:10:25 -08:00
Chris Robinson e179bf0a12 Move the mixer functions to core 2020-12-12 14:58:09 -08:00
Chris Robinson 14df534114 Use a separate list for core objects 2020-12-12 14:45:41 -08:00
Chris Robinson f57801102a Remove an unnecessary include and forward declaration 2020-12-12 14:21:45 -08:00
Chris Robinson 1ad944555c Move some HRTF definitions to a separate header 2020-12-12 14:15:17 -08:00
Chris Robinson 176bc8a835 Avoid passing DirectHrtfState to MixDirectHrtf 2020-12-12 13:52:14 -08:00
Chris Robinson 191fe888b4 Move ambidefs.h to core 2020-12-12 10:38:24 -08:00
Chris Robinson 1bb7f575f0 Don't modify the IR size of loaded HRTFs 2020-12-11 15:08:50 -08:00
Chris Robinson bd08b9a5b5 Use constexpr variables instead of macros 2020-12-06 08:49:20 -08:00
Chris Robinson 4ee95dc296 Use a smaller FFT for convolution 2020-12-05 14:18:19 -08:00
Chris Robinson 2fc10a2bc0 Avoid returning an array for initialization 2020-12-05 07:27:54 -08:00
Chris Robinson 1679ab937e Reorder definitions to keep them near each other 2020-12-05 06:40:06 -08:00
Chris Robinson 16c7d1816d Remove some unnecessary inlines and includes 2020-12-05 06:30:13 -08:00
Chris Robinson 7485e402c1 Avoid a REQUIRES macro for SFINAE 2020-12-05 05:57:18 -08:00
Chris Robinson 8a352d25f9 Move the ringbuffer to common 2020-12-05 03:28:19 -08:00
Chris Robinson 2b919eac78 Use an alias for the DevFmtType type 2020-12-05 02:44:19 -08:00
Chris Robinson 16ec07275f Remove an unneeded include 2020-12-04 14:45:55 -08:00
Chris Robinson 50e33ce8f4 Change some macros into constexpr variables 2020-12-04 13:53:56 -08:00
Chris Robinson c4132b80ed Move a couple more things to core 2020-12-04 13:13:52 -08:00
Chris Robinson 3a3094c887 Fix some tabs and missing cast 2020-12-04 11:19:59 -08:00
Chris Robinson 69d55d7e03 Move the filters to core 2020-12-04 11:15:50 -08:00
Chris Robinson 84d47f7d4c Move the bsinc tables to core 2020-12-04 11:15:50 -08:00
Chris Robinson 36c1589c11 Move mastering.cpp/h to core 2020-12-04 11:15:50 -08:00
Chris Robinson aa05feec4a Avoid a global MAX_PITCH macro 2020-12-04 11:15:50 -08:00
Chris Robinson b1c2671e9d Move the resampler declarations to a more appropriate header 2020-12-04 11:15:50 -08:00
Chris Robinson 75f469fa71 Remove some unnecessary includes 2020-12-04 11:15:50 -08:00
Chris Robinson 630a3b8cbd Move lerp/cubic to alnumeric.h 2020-12-04 11:15:50 -08:00
Eliot Miranda e3179aa058 Add a gain argument to altonegen (#502)
* Fix a speeling rorre in an error message.
Add a gain command-line argument to altonegen.c

* Fix some formatting issues and a copy/paste error.
2020-12-04 11:15:27 -08:00
Chris Robinson 8750810f5c Change a couple macros into constexpr variables 2020-11-28 03:38:20 -08:00
Chris Robinson eb9b9fb4e5 Avoid AL types in mastering.cpp/h 2020-11-28 02:06:56 -08:00
Chris Robinson dbfdd35545 Move some declarations to more appropriate headers 2020-11-27 22:27:45 -08:00
Chris Robinson 7e798df7b8 Avoid AL types in the converter 2020-11-27 21:40:02 -08:00
Chris Robinson b0919240ab Move some sources to a separate directory
To begin separating the ALC interfaces from internal ones.
2020-11-27 19:18:17 -08:00
Chris Robinson d86046d522 Update ChangeLog 2020-11-25 16:31:52 -08:00
Chris Robinson 32b9a46b39 Move AL EffectProp handling to separate sources 2020-11-25 13:55:29 -08:00
Chris Robinson 3970252da9 Don't limit the available converted sample count to BUFFERSIZE 2020-11-25 04:20:16 -08:00
Chris Robinson 28278a9c15 Fix a UInt32 variable type 2020-11-24 16:43:38 -08:00
Chris Robinson f16692ecf8 Fill all buffers for CoreAudio playback 2020-11-24 14:47:04 -08:00
Chris Robinson ba39c622ef Disable buffer allocation with CoreAudio capture 2020-11-24 14:44:13 -08:00
Chris Robinson a913aa6ba8 Make sure batched source updates are with the proper voice 2020-11-24 10:49:53 -08:00
Chris Robinson 7632dfb51a Use a type alias instead of decltype 2020-11-23 12:20:33 -08:00
Chris Robinson d08d6b18c4 Ceil the scaled buffer size instead of round
The result has to be large enough to get the original requested size from it.
2020-11-23 10:55:13 -08:00
Chris Robinson d062c16629 Fix buffer offset for the second conversion 2020-11-23 10:43:20 -08:00
Chris Robinson 31c7eb5c55 Fix capture buffer size scaling in CoreAudio 2020-11-23 10:40:20 -08:00
Chris Robinson 5d82058da7 Use kAudioUnitScope_Global to get the maximum frame size 2020-11-23 10:11:28 -08:00
Chris Robinson 534c487f14 Avoid AL types in hrtf.cpp/h 2020-11-22 17:27:22 -08:00
Chris Robinson 822224312d Use optionals for some attribute parameters 2020-11-22 04:31:02 -08:00
Chris Robinson 21174047df Make ALbuffer inherit from BufferStorage 2020-11-21 00:54:25 -08:00
Chris Robinson 54afcbe113 Use clearer variable names 2020-11-20 22:02:20 -08:00
Chris Robinson 2cb2ffef72 Disassociate internal buffer enums from AL enum values 2020-11-20 04:27:10 -08:00
Chris Robinson c3cb09aa0c Avoid AL types in the mixer 2020-11-20 03:59:14 -08:00
Chris Robinson f85e76285f Disassociate internal voice enums from AL enums 2020-11-20 01:37:19 -08:00
Chris Robinson bee9917598 Disassociate internal format enums from ALC enums 2020-11-20 00:55:51 -08:00
Chris Robinson 0024ea229b Don't try to override the CoreAudio maximum capture slice 2020-11-19 17:49:25 -08:00
Chris Robinson 03f9700071 Use BufferStorage for the buffer queue 2020-11-19 04:18:25 -08:00
Chris Robinson 537510afc8 Put the loop start/end in BufferStorage 2020-11-19 03:23:46 -08:00
Chris Robinson ee9f0fe306 Recognize ARM64 as a VS platform 2020-11-18 04:20:10 -08:00
Chris Robinson 645d354ffa Add a close method to the win32 ifstream 2020-11-16 14:51:50 -08:00
Chris Robinson 88cb398a83 Avoid direct initialization of float32x4_t 2020-11-16 13:42:20 -08:00
Chris Robinson 1869dc14d4 Use WinAPI functions to check for NEON support 2020-11-16 12:42:33 -08:00
Chris Robinson a494a89c5a Add some missing custom device config descriptions 2020-11-15 15:54:09 -08:00
Chris Robinson e64c52ce8c Use doubles to calculate the listener matrix translation
To help stablize sources near the listener, when away from origin. Also clean
up some related methods and move them to more appropriate places.
2020-11-13 23:33:40 -08:00
Chris Robinson 417d8da6f4 Avoid a deprecated function 2020-11-13 21:47:48 -08:00
Chris Robinson 1a05dda8db Avoid testing a bool against AL_FALSE 2020-11-12 12:46:15 -08:00
Chris Robinson 7e76770278 Avoid an unnecessary multiplier 2020-11-09 12:33:07 -08:00
Chris Robinson 3a58bac7bd Fix pitch shifter bin count 2020-11-09 08:10:43 -08:00
Chris Robinson 54e4346f50 Simplify some pitch shifter scaling calculations 2020-11-08 07:59:14 -08:00
Chris Robinson db6d6d68b4 Avoid bitshifts for loop counting 2020-11-08 07:46:10 -08:00
Chris Robinson 0498ca200d Use an array of auxslot pointers instead of IDs to lookup 2020-11-07 12:58:19 -08:00
Chris Robinson 4e760bbecc Use a separate structure for the active effect slot properties 2020-11-07 08:36:49 -08:00
Chris Robinson 120776bec5 Use a range-for loop instead of for_each 2020-11-06 15:44:49 -08:00
Chris Robinson 26b03f534c Use more efficient sorting for effect slots 2020-11-05 15:35:09 -08:00
Chris Robinson aeb7170a8b Add missing include for the CoreAudio backend 2020-11-05 05:42:02 -08:00
Chris Robinson 4e50ce9d30 Avoid declaring standard types 2020-11-05 04:57:50 -08:00
Chris Robinson 40c92c4643 Simplify the [u]int64_t typedef header check
And include the correct header
2020-11-05 04:41:17 -08:00
Chris Robinson c0cbe602ce Release 1.21.0 2020-11-04 02:00:35 -08:00
Chris Robinson ce52ecaa87 Update changelog 2020-11-02 18:06:10 -08:00
Chris Robinson 03389f390c Avoid a full wipe and reallocation of wet buffers 2020-11-02 05:19:17 -08:00
Chris Robinson 52d58a4023 Store the wet buffers in the context
This is rather ugly, but we need the wet buffers to remain allocated after its
effect slot is deleted, because a voice can still use it for its final fade-out
mix.
2020-11-02 04:24:36 -08:00
Chris Robinson 6e05adf955 Fix a typo in the changelog 2020-10-27 00:26:16 -07:00
Chris Robinson 267d9caac4 Check for the right extension string in alffplay 2020-10-26 12:21:42 -07:00
Chris Robinson dfb6d70902 Update Changelog 2020-10-26 11:18:08 -07:00
Chris Robinson be0798b56d Finalize AL_SOFT_events 2020-10-26 10:57:48 -07:00
Chris Robinson 9e7c816498 Fix copy-paste error 2020-10-26 10:47:31 -07:00
Chris Robinson 36ecea458a Clear /W3 on MSVC since we use /W4 2020-10-25 14:53:44 -07:00
Chris Robinson 0311f6431c Add a comment about building for static linking 2020-10-25 00:08:54 -07:00
Chris Robinson 88b9eb1560 Silence a couple more MSVC warnings 2020-10-23 21:03:13 -07:00
Chris Robinson 6de861a75c Remove a constexpr that needs C++17 2020-10-23 20:00:09 -07:00
Chris Robinson 966c4498d4 Don't use a template parameter as a variable
And try to silence an MSVC warning about a constant overflow.
2020-10-23 19:53:07 -07:00
Chris Robinson 556321f18d Some cleanup in alspan.h 2020-10-23 19:03:37 -07:00
Chris Robinson 7cb1cc8ab5 Add some constexpr 2020-10-23 18:40:54 -07:00
Chris Robinson 7bb37deb51 Replace the mixer fraction macros with constexpr variables 2020-10-21 17:16:27 -07:00
Chris Robinson 3e62600ecc Replace some more macros with constexpr variables 2020-10-21 16:39:21 -07:00
Chris Robinson b8f5e1f424 Change some more macros into constexpr variables 2020-10-21 10:39:00 -07:00
Chris Robinson a3ea46f773 Add some missing constexpr attributes 2020-10-20 11:40:27 -07:00
kcat 1acfd11635 Merge pull request #483 from jhasse/silence-nodiscard
Silence warning about unused return values
2020-10-20 11:31:32 -07:00
Jan Niklas Hasse bd9d5d7bf0 Silence warning about unused return values
warning C4834: discarding return value of function with 'nodiscard'
attribute
2020-10-20 18:33:53 +02:00
Chris Robinson 95ea5951b1 Try again to work around a GCC 5 issue 2020-10-19 08:29:50 -07:00
Chris Robinson a3903b84f9 Revert "Workaround a GCC 5 issue"
This reverts commit ada92b058a.

It didn't fix the problem.
2020-10-19 08:24:19 -07:00
Chris Robinson ada92b058a Workaround a GCC 5 issue 2020-10-19 08:01:38 -07:00
Chris Robinson cdc15a4783 Use constexpr variables in place of some macros 2020-10-19 07:55:25 -07:00
Chris Robinson d6686bceb0 Avoid an extra function to generate BSincHeaders 2020-10-18 16:49:36 -07:00
Chris Robinson 20cc3882b4 Do in-place initialization for the bsinc filters
Requires less dynamic allocations and less troublesome return values of large
arrays. Also reduces repetition with specified parameters.
2020-10-18 05:34:27 -07:00
Chris Robinson 24c7ddbac2 Update Changelog 2020-10-18 05:32:48 -07:00
Chris Robinson 3728f74e10 Utilize C++11's [u]int64_t in alext.h 2020-10-16 11:13:20 -07:00
Chris Robinson f8af992b2b Quick update to ALC_BFORMAT3D_SOFT's value 2020-10-16 07:49:59 -07:00
Chris Robinson 17e88bdef3 Add a missing enum to the enum list 2020-10-16 06:39:42 -07:00
Chris Robinson 8e09ae8ee6 Finalize AL_SOFT_effect_target 2020-10-16 06:36:43 -07:00
Chris Robinson 2beb095eb1 Finalize ALC_SOFT_loopback_bformat 2020-10-16 06:35:23 -07:00
Chris Robinson 5f8fe0e5bb Enable standard stdio methods with MinGW
This unfortunately doesn't fix the %z warnings for whatever reason, but it
should help guarantee correct function behavior by not relying on msvcrt's
stdio functions.
2020-10-13 05:59:35 -07:00
Chris Robinson 7b43cb8266 Declare variables closer to where they're used 2020-10-13 05:54:05 -07:00
Chris Robinson b0e025946e Don't check for the app path if one was already retrieved 2020-10-13 01:57:53 -07:00
Chris Robinson 303ca3af72 Use inline functions for popcnt and ctz instead of macros 2020-10-13 01:21:44 -07:00
Chris Robinson 45ff10fb4d Use a span instead of a pointer and count parameters 2020-10-12 23:58:54 -07:00
Chris Robinson 0dc9ae0998 Update event enum values
To keep them less spread out from other OpenAL Soft extensions.
2020-10-07 20:13:52 -07:00
Chris Robinson af9c990277 Print the number of used measurements in sofa-info 2020-10-07 01:04:42 -07:00
Chris Robinson 5fbd02e60e Update ChangeLog 2020-10-06 00:38:37 -07:00
Chris Robinson 7fb6d64ca8 Be more robust with to-mono channel conversions 2020-10-05 22:30:23 -07:00
Chris Robinson e7a44d3b70 Cast the ringbuffer size to the correct type 2020-10-05 00:43:06 -07:00
Chris Robinson 7361e108a3 Fix a variable name 2020-10-05 00:17:13 -07:00
Chris Robinson e0097c18b8 Simplify some DSound capture math 2020-10-03 16:17:34 -07:00
Chris Robinson b5eccf424b Remove the unused Ref logging level 2020-09-28 16:14:20 -07:00
Chris Robinson f02bc1354d Rework logging a little
Use OutputDebugStringW on Windows in addition to the log file

Avoid duplicate formatter parsing with Android
2020-09-28 11:34:24 -07:00
Chris Robinson da29489ead Use an enum class for the log level 2020-09-28 00:40:30 -07:00
Chris Robinson 39f4ea61c5 Add missing linear resampler to the option setting list 2020-09-24 14:21:57 -07:00
Chris Robinson cef82153b6 Use a smaller buffer for the phase-shift filter 2020-09-24 13:26:01 -07:00
Chris Robinson 04fd50bcdf Support B-Format sounds in the examples 2020-09-22 11:36:43 -07:00
Chris Robinson d2970443dd Recognize B-Format format names in the examples 2020-09-22 10:43:27 -07:00
Chris Robinson c993ade9bf Support B-Format amb file IRs in alconvolve
Be aware this requires proper header data (a WAVE_FORMAT_EXTENSIBLE format with
the proper integer or float B-Format sub-format GUID). A normal 4-channel wave
file will not be recognized, since it's indistinguishable from quadrophonic.
2020-09-22 10:18:43 -07:00
Chris Robinson 844e6c881b Add an alconvolve option to silence the dry signal 2020-09-22 08:30:04 -07:00
Chris Robinson d912b92a60 Support all buffer layouts for convolution 2020-09-22 08:09:42 -07:00
Chris Robinson 191150d9a8 Remove deprecated, performance, and error event types
These would be better served with a proper debug API, rather than a general
audio event API.
2020-09-20 04:11:52 -07:00
Chris Robinson 8ef242bce0 Don't use direct channels with convolution
It may come back as an effect property, but it's probably better to not try
forcing it by default.
2020-09-18 10:12:58 -07:00
Chris Robinson 07989a34dc Allow higher-order B-Format buffers for convolution
Only the first-order channels will be used, but higher-order buffers will be
allowed.
2020-09-18 00:06:10 -07:00
Chris Robinson e7d8e7eb3f Avoid extraneous buffers and buffer clears 2020-09-17 21:34:26 -07:00
Chris Robinson d86d882d8b Fix an invalid unicode character in a comment 2020-09-14 02:52:16 -07:00
Chris Robinson 0974b6b47c Use inline wrappers to clarify forward/inverse FFTs 2020-09-13 04:18:40 -07:00
Chris Robinson 9a883f5046 Partly simplify FFT bit-reversal
This can almost certainly be improved further, as less than half of the indices
really need their reversed bit-pattern calculated and elements swapped (any
symetrical bit pattern would just swap with itself, and indices whose reversed
bit-pattern has already been traversed is already swapped).

It may also prove beneficial to provide the base-2 log of the fft buffer size
(number of bits to represent the indices), as that could help make the reversal
more efficient with a known bit/loop count.
2020-09-13 03:33:52 -07:00
Chris Robinson 1d4b355622 Use standard sin and sqrt 2020-09-12 19:06:06 -07:00
Chris Robinson bb90bfd9b3 Accumulate delays as samples before calculating nanoseconds 2020-09-12 01:52:45 -07:00
Chris Robinson cf298075b5 Round (and clamp) the reftime instead of ceiling it 2020-09-12 00:47:04 -07:00
Chris Robinson 33d91ceeec Avoid repeating string literals 2020-09-11 01:40:36 -07:00
Chris Robinson a4bfba4cb5 Recognize GUID name strings with the DSound backend 2020-09-10 22:05:25 -07:00
Chris Robinson 21162cf5fc Allow JACK to enumerate custom devices
Each device being associated with a port name pattern it'll try connecting to.
2020-09-10 21:09:06 -07:00
Chris Robinson bb63d5aab3 Simplify clearing an array 2020-09-09 23:21:46 -07:00
Chris Robinson ce0a45cbc7 Don't assume iterators are pointers 2020-09-09 14:02:02 -07:00
Chris Robinson 68a099ba35 Apply the first convolution segment in the time domain
This avoids an inherent delay from the effect, at the cost of higher CPU use.
Having a customizable user-specified delay (with said user ensuring a properly
trimmed impulse response) could help alleviate the cost since once the delay
exceeds the segment size, the initial FIR filter could be skipped.
2020-09-09 02:23:38 -07:00
Chris Robinson f4a55cc8c2 Don't leave the negative frequencies as 0 for inverse FFT 2020-09-08 23:20:06 -07:00
Chris Robinson 29566b995c Avoid an extraneous ConvolutionFilter sub-object 2020-09-06 00:16:50 -07:00
Chris Robinson 7eb5dfb3f8 Allow querying the new enums and functions 2020-09-05 23:53:53 -07:00
Chris Robinson f5163ca988 Print the name and format for alconvole's impulse response 2020-09-05 23:48:19 -07:00
Chris Robinson 800e2b6125 Cleanup and fix alAuxiliaryEffectSlotPlayv/StopvSOFT 2020-09-05 21:39:31 -07:00
Chris Robinson 13710b474e Ensure effect slot properties are updated when playing 2020-09-05 21:04:41 -07:00
Chris Robinson c52bf8c401 Rework effect slot buffer setting
Rather than creating an effect-specific buffer that gets passed along as a
property, the buffer is set the effect state when the effect state is created,
the device is updated, or the buffer is changed. The buffer can only be set
while the effect slot isn't playing, so it won't be changed or updated while
the mixer is processing the effect state.
2020-09-05 20:48:56 -07:00
Chris Robinson 9975aeb37f Add methods to start and stop effect slot processing
A newly-created effect slot is in an AL_INITIAL state, in which processing is
stopped but will automatically become AL_PLAYING after successfully setting an
AL_EFFECTSLOT_EFFECT value (including AL_EFFECT_NULL or 0). Calling Play[v] or
Stop[v] will set the effect slot to AL_PLAYING or AL_STOPPED respectively.
While stopped, the effect won't produce audio and will not be processed.
2020-09-05 19:11:57 -07:00
Chris Robinson 7851f7d4ce Don't add effect slots to the active list until an effect is set 2020-09-05 18:13:08 -07:00
Chris Robinson c1ad5d6055 Avoid confusing */*...*/ syntax
MSVC warns about */ being outside of a comment.
2020-09-05 14:28:08 -07:00
Chris Robinson 7e3fed42ed Use a recursive_mutex for the router enumeration lock 2020-09-05 12:41:28 -07:00
Chris Robinson 73f5331305 Use an RAII wrapper to manage COM object references 2020-09-05 12:32:41 -07:00
Chris Robinson 88f7617807 Avoid duplicate WASAPI devices by matching GUIDs 2020-09-04 18:32:06 -07:00
Chris Robinson afdd1c67ad Update changelog 2020-09-03 13:25:51 -07:00
Chris Robinson 9e862900fe Update some UHJ comments 2020-09-02 15:50:11 -07:00
Chris Robinson fe329c2af2 Avoid class templates for the POPCNT64/CTZ64 macros 2020-09-01 15:55:48 -07:00
Chris Robinson 1968136c98 Add an in-progress extension string for convolution reverb 2020-09-01 07:37:07 -07:00
Chris Robinson eeba1a385c Don't use config.h to define RESTRICT 2020-09-01 06:22:50 -07:00
Chris Robinson a6aefeccd8 Use enum class for a couple enums 2020-09-01 05:46:19 -07:00
Chris Robinson 02e51c0525 Avoid using a deprecated function 2020-08-31 17:48:26 -07:00
Chris Robinson af97a92952 Move more processing to the ConvolutionFilter struct 2020-08-31 06:43:42 -07:00
Chris Robinson 88425becb2 Remove a couple unnecessary pointers 2020-08-30 04:41:19 -07:00
Chris Robinson 82873486b7 Dynamically allocate the convolution channel mixing data 2020-08-30 04:28:01 -07:00
Chris Robinson ebe0765ce3 Reorganize some convolution fields 2020-08-30 03:42:44 -07:00
Chris Robinson 8724c1ce4b Remove extraneous parenthesis 2020-08-28 23:28:20 -07:00
Chris Robinson 3fdee6c814 Calculate the correct number of convolution segments 2020-08-28 23:08:44 -07:00
Chris Robinson ecfb0d4f5b Limit convolution processing to the output ambisonic order 2020-08-28 06:47:21 -07:00
Chris Robinson 7266cd8b6c Don't dereference a null convolution filter 2020-08-28 06:44:05 -07:00
Chris Robinson 52531d8b72 Support B-Format impulse responses for convolution 2020-08-28 05:48:26 -07:00
Chris Robinson 986a58d5b4 Pass a BufferStorage to EffectState::createBuffer 2020-08-28 00:44:55 -07:00
Chris Robinson 1f486f820e Use a separate structure for buffer storage 2020-08-28 00:09:46 -07:00
Chris Robinson ecf30de36f Rename buffer_formats to buffer_storage 2020-08-27 23:02:17 -07:00
Chris Robinson a9ebcdcb7f Handle 0-length convolution buffers 2020-08-27 21:32:25 -07:00
Chris Robinson 0ad7bb577a Don't allow callback buffers for effect slots 2020-08-27 21:10:00 -07:00
Chris Robinson 3c09f17421 Blend panned B-Format source directivity given its radius 2020-08-27 08:44:29 -07:00
Chris Robinson 2a01940041 De-duplicate LoadSampleArray and FmtTypeTraits 2020-08-26 21:29:16 -07:00
Chris Robinson 97ecf5810f Base the convolution example on the simpler stream example 2020-08-26 17:23:50 -07:00
Chris Robinson 577a8234f2 Recognize float32 format names for the example healpers 2020-08-26 17:15:30 -07:00
Chris Robinson 9dab2db33b Avoid copying the convolution filter history to shift it
Instead, the current/head/newest segment is tracked, and decremented with each
process to overwrite the oldest.
2020-08-25 15:43:48 -07:00
Chris Robinson 309be1c6f6 Add an example using convolution reverb 2020-08-25 04:59:04 -07:00
Chris Robinson 801c7a9226 Initial implementation of the convolution effect
Currently limited to mono and stereo impulse responses, and stereo IRs try to
use direct/real output rather than panning.
2020-08-25 04:21:10 -07:00
Chris Robinson e98a058595 Fix handling doubles 2020-08-25 02:38:44 -07:00
Chris Robinson a6bd53c4e1 Store a reference to the effect buffer as an active property 2020-08-24 20:40:24 -07:00
Chris Robinson 1a9fbc1b2f Stub out a convolution effect state 2020-08-24 20:04:16 -07:00
Chris Robinson b955479e18 Recognize a convolution reverb effect type 2020-08-24 20:00:58 -07:00
Chris Robinson 9e5a388dfe Add a method for effects to create persistent buffer data 2020-08-24 19:13:46 -07:00
Chris Robinson 9d61484e4b Move storable buffer format info to a separate source 2020-08-24 17:59:07 -07:00
Chris Robinson 73ab9d46c8 Use an intrusive_ptr to hold the unapplied effect state 2020-08-24 16:34:53 -07:00
Chris Robinson f9d6aa2f48 Allow setting a buffer on an effect slot 2020-08-24 16:08:09 -07:00
Chris Robinson 54a0972bfe Clean up some comments 2020-08-24 14:26:47 -07:00
Chris Robinson ff5c9d1c15 Use an intrusive_ptr for ALeffectslotProps::State 2020-08-24 14:09:26 -07:00
Chris Robinson 01f76f2b67 Allow intrusive_ptr::reset to take a new pointer 2020-08-24 14:09:26 -07:00
HALX99 de060ce09a macOS osx/ios dynamic framework support (#466)
* OSX bundle support

* Disable framework by default, and fix domain name typo

* Remove info.plist, add efx.h for framework public header

* Fix osx/ios framework PUBLIC_HEADER doesn't work

* Refine comment message

* Auto set CFBundleShortVersionString by var LIB_VERSION

* Set CFBundleVersion from git commit count

* Use space to separate elements in a list

* Specific framework name to variable 'IMPL_TARGET'

* Solve cmake try_compile failed with code sign, and disable framework code sign

* Make ios travis to build dynamic framework bundle by default

* Update ios.toolchain.cmake

Since we solve code sign issue for cmake to generate dynamic framework xcode project, enable strict try_compile by default

* Remove MAKE_CXX_EXTENSIONS from travis-ci

* Combined flat lib armv7;arm64 support

* Remvoe ios.toolchain.cmake since we don't need
[skip appveyor] [skip travis]

* Sets framework name to soft_oal,
avoid ambiguous with system OpenAL.framework

* Fix missing BUNDLE, FRAMEWORK's DESTINATION
Build osx/ios dynamic framework required them.

* Use @rpath instead fullPath to mac local disk
see also:
https://github.com/libjpeg-turbo/libjpeg-turbo/commit/c80ddef7a4ce21ace9e3ca0fd190d320cc8cdaeb

* CMake, use TRUE for bool value

* Don't disable examples, utils, install

* Make ALSOFT_OSX_FRAMEWORK for APPLE spec

* Remove unused flag and more clearly comment

* More clearly comment for solve armv7 target issue

Co-authored-by: deal <deal@dealdeMac.local>
Co-authored-by: bel <bel@beldeMac.local>
2020-08-24 14:09:02 -07:00
Chris Robinson 4eba5c34e9 Use CMAKE_DLLTOOL instead of looking for dlltool manually 2020-08-20 16:46:51 -07:00
Chris Robinson 34b29a48a4 Ensure enough space is allocated for structs with a FlexArray 2020-08-15 23:53:43 -07:00
Chris Robinson 23259b48dd Avoid a zero-length array for the FlexArray definition
Newer GCCs are warning about FlexArray being used at the end of another struct.
Though not the most ideal to always allocate space for at least 1 element,
actual 0-sized uses are rare and for smaller element types.
2020-08-15 23:22:10 -07:00
Chris Robinson 2fa842a04c Avoid allocating for each HRIR reconstruction 2020-08-14 18:22:20 -07:00
Chris Robinson 3796e407b5 Rename ALSOFT_INSTALL_HRTF_DEFS to ALSOFT_INSTALL_HRTF_DATA 2020-08-13 18:04:09 -07:00
Chris Robinson aafcb1f6fc Simplify StrSubst in makemhr 2020-08-13 16:02:13 -07:00
Chris Robinson 8e7199cbb6 Avoid a cmake check for determining the size of long 2020-08-13 14:04:29 -07:00
Chris Robinson fd52c828a9 Improve handling main() with UTF-8 args on Windows 2020-08-12 17:40:00 -07:00
Chris Robinson 476e9d7522 Cleanup some warnings with ffmpeg/alffplay 2020-08-12 17:39:08 -07:00
Chris Robinson b12cd77d32 Use a wait predicate instead of a while loop 2020-08-12 10:00:43 -07:00
Chris Robinson ed1135f7ae Fix configure-time ffmpeg version detection 2020-08-12 09:48:17 -07:00
Chris Robinson 57d6df29b4 Update .gitignore for QtCreator files 2020-08-11 10:11:20 -07:00
Chris Robinson 529049dc59 Remove an unused variable 2020-08-11 10:06:12 -07:00
Chris Robinson 475ed0f039 Use a FlexArray instead of a vector for distance compensation 2020-08-11 05:59:20 -07:00
Chris Robinson c549797af0 Make osme things constexpr 2020-08-10 15:11:52 -07:00
Chris Robinson e8b3e82f96 Change a couple functions into member functions 2020-08-07 06:22:39 -07:00
Chris Robinson 84354c792e Cleanup OSS enumeration and fix duplicate names 2020-08-07 00:11:29 -07:00
Chris Robinson 02f8419663 Fix DIAGNOSTIC_POP for GCC and Clang 2020-08-06 23:20:20 -07:00
Chris Robinson 58a2a5e2e3 Add a comment about 32-bit GCC stack aligning with SSE codegen 2020-08-05 17:43:51 -07:00
Chris Robinson 816510fee7 Update changelog 2020-08-05 07:18:13 -07:00
Luis Cáceres 064710cdf7 Force stack alignment on 32-bit gcc for proper SSE use (#462)
Fixes crashes due to misaligned stack variables in SSE instructions (#460).
2020-08-05 07:16:56 -07:00
Chris Robinson 7ddd49e16a Add the jack/connect-ports option to alsoft-config 2020-08-05 00:43:08 -07:00
Chris Robinson f138369808 Add an option to auto-connect JACK ports 2020-08-05 00:27:12 -07:00
Chris Robinson 516de157d8 Use std::array instead of some C-style arrays 2020-08-05 00:10:10 -07:00
Chris Robinson 2e1645f32a Print the error from pthread_setschedparam 2020-08-04 12:46:27 -07:00
HALX99 7cac5304c8 Add ios build for travis (#452)
* Add ios build for travis

* fix cmake command and set osx_image: xcode11

* print cmake version

* print cmake version before config

* [iOS] By default use iphoneos for sysroot

* Use ios.toolchain.cmake to build ios

* By default, don't require gnu extension

* Update CMakeLists.txt

Co-authored-by: deal <deal@dealdeMac.local>
2020-08-01 02:52:36 -07:00
Chris Robinson 76c57990be Cleanup and fix a typo in the changelog 2020-07-23 19:20:55 -07:00
Chris Robinson 39a2f0626a Use a predicate instead of a while loop 2020-07-23 09:03:47 -07:00
Chris Robinson ac1fc1b60a Don't include headers in a namespace definition 2020-07-23 09:03:47 -07:00
Chris Robinson 91df03f7eb Simplify mixer buffer size saturation handling 2020-07-23 09:03:47 -07:00
kcat 4817d82185 Merge pull request #453 from devnexen/haiku_support_completion
GetProcBinary implementation for Haiku.
2020-07-21 10:27:43 -07:00
David Carlier d3ffaeb8d7 GetProcBinary implementation for Haiku. 2020-07-21 17:22:38 +00:00
Chris Robinson f409cb4039 Revert "Don't call find_package(WindowsSDK) if the platform ver is already set"
This reverts commit c1383e3a48.
2020-07-09 00:07:33 -07:00
Chris Robinson c1383e3a48 Don't call find_package(WindowsSDK) if the platform ver is already set 2020-07-08 23:32:24 -07:00
Chris Robinson da25557ed6 Remove dead code 2020-06-30 16:44:14 -07:00
Chris Robinson b0e72266b6 Don't round up the dst size when the src size was saturated 2020-06-29 19:41:08 -07:00
Chris Robinson 775187788a Fix SrcBufferSize calculation when downsampling 2020-06-29 12:34:12 -07:00
Chris Robinson 88ad84edf6 Reduce the MAX_PITCH to 10
255 is insanely overkill. The bsinc resamplers fade to silence after a few
octaves, and the others would be a mess.
2020-06-29 11:32:15 -07:00
Chris Robinson f81558c948 Avoid including windows.h in threads.h 2020-06-28 12:55:55 -07:00
Chris Robinson 31791c9997 Workaround a MinGW quirk
Sometimes MinGW defines strncasecmp to _strnicmp in string.h, which can cause
problems with al::strncasecmp if it's not consistently replaced.
2020-06-26 05:39:26 -07:00
Chris Robinson c4cf84c549 Update default HRTF 2020-06-21 19:41:07 -07:00
Chris Robinson a0eb532100 Apply simulated HRIR occlusion in the frequency domain 2020-06-21 19:28:37 -07:00
Chris Robinson 967ea42359 Update the default HRTF 2020-06-21 05:01:35 -07:00
Chris Robinson 24393ab192 Synthesize missing elevations in the frequency domain
This should help avoid destructive phase interference. The occlusion low-pass
filter is still applied in the time domain due to no clear topology (cutoff
frequency, slope, bandwidth, etc).
2020-06-21 00:29:57 -07:00
Chris Robinson a01dbeb09f Use multiple threads for magnitude calculations
Also allow user-declarable thread count.
2020-06-19 16:43:09 -07:00
Chris Robinson bc91e4d302 Update the default HRTF 2020-06-19 15:44:20 -07:00
Chris Robinson 8780976979 Avoid blending multiple IRs for the -90 elevation IR 2020-06-19 15:28:22 -07:00
Chris Robinson 4cd0e333c8 Calculate the HRIR onsets and magnitudes separately from loading
This should help improve memory use a bit since the SOFA file can be unloaded
before allocating some temp buffers for onset detection and FFT calculation.
2020-06-17 17:15:26 -07:00
Chris Robinson 3f26ce4a82 Add a makemhr option to use the farthest field only
All fields are used for equalization, but only the farthest field is resampled,
reconstructed, normalized, and written to the mhr.
2020-06-17 16:25:40 -07:00
Chris Robinson beba71a6f9 Resample HRIRs after equalization 2020-06-17 13:03:26 -07:00
Chris Robinson 4cfd63a1c7 Avoid reinitializing the resampler with each HRIR 2020-06-17 01:50:11 -07:00
Chris Robinson cd6bb65d49 Resample HRTFs in the frequency domain in makemhr
This should produce far better results given it works directly on the frequency
response magnitudes prior to phase reconstruction, as it doesn't deal with a
linear phase filter on a truncated time-domain response (with the result also
getting truncated in both direction).

The in-library on-load HRTF resampler still uses the linear filter due to its
relative performance and simplicity benefits. It's good enough as a backup,
though users with custom HRTFs would benefit from resampling when creating the
mhr (adjusting its window size as appropriate/desired).
2020-06-17 01:15:01 -07:00
Chris Robinson 8ea7d5183b Print the synthesized elevations' azimuth counts 2020-06-16 17:33:26 -07:00
Chris Robinson b0b720c609 Avoid an unnecessary local variable 2020-06-16 14:01:39 -07:00
Chris Robinson bd0144065a Set channel labels from WFX channel masks 2020-06-15 20:21:54 -07:00
Chris Robinson c142b49981 Print the name of unhandled PulseAudio channels 2020-06-15 14:07:21 -07:00
Chris Robinson 6db6d1510a Remove the Lower channel labels and rename the Upper labels 2020-06-15 13:49:45 -07:00
Chris Robinson 401a9bacd1 Don't bother with otherwise unused Aux channels 2020-06-15 02:27:29 -07:00
Chris Robinson 540315b07c Handle the front stablizer with the B-Format decoder 2020-06-15 02:13:14 -07:00
Chris Robinson c951190d3a Fix a typo 2020-06-12 12:54:05 -07:00
Chris Robinson 6ce9bf6c94 Move a couple related functions to the backend base 2020-06-12 12:53:47 -07:00
Chris Robinson 26eccd159f Link with the CoreFoundation framework on iOS 2020-06-11 08:55:52 -07:00
Chris Robinson ad9fc31bfd More capitalization fixes 2020-06-11 08:55:24 -07:00
Chris Robinson df1bdc7bc4 Fix some capitalization 2020-06-11 07:56:45 -07:00
kcat 8fee1da426 Merge pull request #439 from halx99/patch-1
Enable to detect Windows SDK v2004
2020-06-11 07:55:53 -07:00
HALX99 3702dcb1d3 Enable to detect Windows SDK v2004 2020-06-11 22:39:47 +08:00
Chris Robinson 15437e3140 Don't export functions when static linking 2020-06-10 21:03:04 -07:00
Chris Robinson c50250c978 Use a range-for loop instead of for_each 2020-06-09 21:28:09 -07:00
Chris Robinson b2b3ad570b Use better types for array lengths 2020-06-09 11:52:48 -07:00
Chris Robinson 46eb353b84 Update changelog 2020-06-07 18:59:49 -07:00
Chris Robinson b7b52ec28b Simplify the aligned over-allocation strategy 2020-06-07 15:40:04 -07:00
Chris Robinson 9322c86e2f Avoid explicit checks for _BitScanForward[64] 2020-06-07 14:15:48 -07:00
Chris Robinson ab3ccb513a Check the returned sample endianess with sndio 2020-06-07 11:50:16 -07:00
Chris Robinson 2a324d34be Fix a return value 2020-06-07 11:47:39 -07:00
Chris Robinson b4a52321c4 Use a safer layout if sndio changes the channel count 2020-06-07 11:45:06 -07:00
Chris Robinson c8f67cffc5 Use a more appropriate size value for copying 2020-06-07 11:00:19 -07:00
Chris Robinson 03eb6a01b9 Use a better type for array access 2020-06-07 11:00:19 -07:00
kcat 4acfbfc37a Merge pull request #435 from DanielGibson/sndio-more-channels
sndio: Support more than 2 channels
2020-06-07 10:59:54 -07:00
Daniel Gibson 5cffe7e50a sndio: Support more than 2 channels
So far the sndio backend only supports Mono and Stereo, but sndio
supports up to 16 channels (64 in their current git code).
This adds support for all the openal-soft formats (DevFmt*).
I tested this on Linux with 5.1 surround speakers.
2020-06-07 17:22:51 +02:00
Chris Robinson 70d345bbf2 Avoid convolving an always-0 sample 2020-05-30 16:29:23 -07:00
Chris Robinson 7a95a870be Clarify some changelog entries 2020-05-27 15:46:25 -07:00
Chris Robinson f1612ce228 Use dual-biquad processing for the equalizer effect 2020-05-26 18:08:57 -07:00
Chris Robinson 9b63e2460c Update the Changelog 2020-05-26 14:13:02 -07:00
Chris Robinson 7b642330c5 Simplify UHJ delay handling 2020-05-26 12:19:19 -07:00
Chris Robinson 8d77c59da4 Fade over more of the update with HRTF 2020-05-25 16:57:38 -07:00
Chris Robinson 4e82feffef Only apply non-silent inputs for blended HRIR 2020-05-25 16:37:31 -07:00
Chris Robinson 5eb1ae5a14 Simplify the HRTF blending fade out gain scaling 2020-05-25 13:18:10 -07:00
Chris Robinson 4d9d406966 Clarify an error message 2020-05-25 02:02:50 -07:00
kcat 070ed0d5ac Merge pull request #429 from jbeich/ci
Add FreeBSD to CI
2020-05-24 11:31:06 -07:00
Jan Beich c262d0146e CI: add FreeBSD job 2020-05-24 13:12:18 +00:00
Chris Robinson 1a287f434a Round the pitch shifter frequency bin targets 2020-05-22 16:36:41 -07:00
Chris Robinson 978f32acf7 Round the scaled HRTF delays 2020-05-21 17:59:15 -07:00
Chris Robinson 109875942c Slightly increase the HRTF direct delay
To help improve the phase-corrected HF scaler.
2020-05-21 15:25:03 -07:00
Chris Robinson 94105409f8 Print why a sofa file couldn't be opened 2020-05-21 13:51:48 -07:00
Chris Robinson da3dfa2fb0 Simplify handling the stablizer side channel delay 2020-05-21 13:48:50 -07:00
Chris Robinson e98017b475 Avoid a log10 constant call 2020-05-21 09:46:39 -07:00
Chris Robinson 5bbbe8341e Use an enum class for SpatializeMode 2020-05-21 09:10:32 -07:00
Chris Robinson e880c04f32 Clarify a comment about the band-splitter 2020-05-20 11:22:53 -07:00
Chris Robinson 200c164d78 Improve HRTF delay handling
Ensures source-level HRTF, the dry mix, and direct output all align properly,
and simplifies adding the delay in the direct mix output.
2020-05-20 11:13:06 -07:00
Chris Robinson f38b813ba8 Fix some comments 2020-05-19 18:22:38 -07:00
Chris Robinson 19baf646d2 Rename applyHfScale to processHfScale 2020-05-19 13:58:55 -07:00
Chris Robinson c5e90ed6c7 Use an enum for FamCount 2020-05-19 12:15:57 -07:00
Chris Robinson 825206bfa2 Apply the ambisonic HF scaling in real-time with HRTF
Rather than applying the HF scale to the IRs necessitating them to be truncated
along with increasing the IR size, it can be applied to the input signal for
the same results. Consequently, the IR size can be notably shortened while
avoiding the extra truncation. In its place, the delayed reversed all-pass
technique can still be used on the input for maintaining phase when applying
the bandsplit/hfscalar filter to the input signal.
2020-05-19 10:27:52 -07:00
Chris Robinson a512eae7bb Move BUFFERSIZE and FloatBufferLine to a separate header 2020-05-19 10:21:19 -07:00
Chris Robinson 463591663c Check that aligned_alloc is available with cmake
Some compilers support C++17 even on targets that lack required functions.
Projects that want to force C++17 will then run into a problem with
std::aligned_alloc not existing on those targets, so it needs to be explicitly
checked for. The alternative is to simply never use it even when it would be
available.
2020-05-19 08:13:13 -07:00
Chris Robinson 400a108ead Also search for asimd for Neon 2020-05-17 16:23:24 -07:00
Chris Robinson c75c96ce2d Assume Neon support if cpu_caps.cpp is compiled with it 2020-05-17 14:14:29 -07:00
Chris Robinson 36aebbb6ae Include the existing direct signal in the UHJ delay
So any direct_channels sources and normal panned sources remain aligned, and
the reported latency is accurate.
2020-05-16 11:35:55 -07:00
Chris Robinson 0f81fafc62 Fix some standard calls 2020-05-16 01:21:09 -07:00
Chris Robinson d086e78a38 Fix the all-pass's shuffle order 2020-05-14 21:16:21 -07:00
Chris Robinson 91b8387c0a Give names to parameter value constants 2020-05-13 00:57:03 -07:00
Chris Robinson f5bf7dc76e Do two samples at once with the SSE UHJ all-pass 2020-05-12 05:51:48 -07:00
Chris Robinson ad95cb6312 Clear the whole buffer 2020-05-12 04:46:15 -07:00
Chris Robinson 2f8f829772 Properly count the last number of samples to mix 2020-05-12 04:45:25 -07:00
Chris Robinson 3ce8a9914c Fix applying the UHJ phase-shift filter 2020-05-11 23:49:39 -07:00
Chris Robinson 2574b98f8a Move the bsinc tables out of common 2020-05-11 02:24:08 -07:00
Chris Robinson 0406e3bef7 Change a couple functions into member functions 2020-05-10 20:16:43 -07:00
Chris Robinson 2fb4ac1621 Revert "Fix the FFT half point for the shifter effects"
This reverts commit 12bb5a47cd.
2020-05-09 16:44:29 -07:00
Chris Robinson 52a5dc23a3 Fix the UHJ filter generator
The half point needs to be included in the frequency response.
2020-05-09 16:42:43 -07:00
Chris Robinson 73569dfeca Avoid a reference on a constexpr variable 2020-05-08 20:11:30 -07:00
Chris Robinson 1c320c3532 Use a FIR filter for the UHJ all-pass 2020-05-08 15:38:14 -07:00
Chris Robinson 12bb5a47cd Fix the FFT half point for the shifter effects 2020-05-08 01:39:48 -07:00
Chris Robinson 48fbad9836 Slightly improve the Hann windows
There's no need to include the 0 terms on the ends since they'll never
contribute a sample. So extend the width to have the 0 terms just outside the
window where it wouldn't contribute anyway.
2020-05-08 01:25:32 -07:00
Chris Robinson 301f8f5db5 Use std::arrays for the UHJ filters 2020-05-08 01:24:14 -07:00
Chris Robinson 0cfb8fee44 Combine multiple UHJ encoder all-pass loops 2020-05-07 21:01:37 -07:00
Chris Robinson 7ed37a8282 Use dual-band for the internal quad and 7.1 decoders 2020-05-07 03:15:46 -07:00
Chris Robinson faec0c78eb Allow declaring internal dual-band decoders 2020-05-07 02:13:11 -07:00
Chris Robinson 4630a535b2 Re-add an option for installing the main lib 2020-05-07 00:02:23 -07:00
Chris Robinson ca851298ad Update the quad and 7.1 decoders 2020-05-06 22:36:29 -07:00
Chris Robinson bef25361f1 Make the built-in decoders more flexible 2020-05-06 19:31:31 -07:00
Chris Robinson f82fca2c6a Fix the dual-band decoder 2020-05-05 16:56:25 -07:00
Chris Robinson ee82db3966 Use a flexible array for the front stablizer delay buffers 2020-05-05 01:34:14 -07:00
Chris Robinson deac36a1eb Improve the front stablizer
Apply the all-pass+band-split only once, after generating the mid and side
signals separately.
2020-05-05 01:05:38 -07:00
Chris Robinson fcec76663f Move some sin/cos constants out of a loop 2020-05-04 16:16:55 -07:00
Chris Robinson 58d953f6aa Clear buffers right before use 2020-05-03 19:13:20 -07:00
Chris Robinson 15fd3da870 Don't put the LFO on the stack 2020-05-03 19:04:43 -07:00
Chris Robinson fe7a74b09a Use a helper to set an identity ambisonic pan 2020-05-03 18:47:49 -07:00
Chris Robinson b52fde7c0e Simplify SIMD linear resampler loop count 2020-05-02 20:27:05 -07:00
Chris Robinson b051f2e33d Use a matching type given the initializer 2020-05-02 19:24:21 -07:00
Chris Robinson 6823fa2de2 Improve the leftover and realignment mixing loops
Using a mask helps the compiler recognize that the leftover (any remaining non-
multiple-of-4) and realignment loops will only have 3 iterations at most, which
it can unroll or otherwise make more meaningful optimizations for. Previously
it would try to vectorize and partially unroll the loops, which is wasteful
when there would never be enough to vectorize.
2020-05-02 16:42:24 -07:00
Chris Robinson 3898b77dd6 Avoid iterators for the main mixer 2020-05-01 21:14:41 -07:00
Chris Robinson 8acf16fc82 Make BuildBFormatHrtf a member of DirectHrtfState 2020-05-01 10:28:16 -07:00
Chris Robinson ae09e1f60c Don't truncate odd IR sizes with SSE 2020-04-30 20:33:35 -07:00
Chris Robinson 6bc3ae178e Use a more efficient type for holding the IrSize 2020-04-30 17:03:56 -07:00
Chris Robinson 972869f76f Dump the ffmpeg file info on the main thread 2020-04-29 21:32:09 -07:00
Chris Robinson 054071998c Correctly handle unicode paths on Windows for ALSOFT_LOGFILE 2020-04-29 15:41:11 -07:00
Chris Robinson a25dea6cd9 Rename effect_chain to effect_target 2020-04-29 14:39:28 -07:00
Chris Robinson 047f1666d5 Fix subspan extent calculation 2020-04-28 19:58:17 -07:00
Chris Robinson 4094135ed7 Don't return a bool from the backend start method 2020-04-28 19:25:58 -07:00
Chris Robinson 065775d814 Limit the maximum settable sample rate 2020-04-28 16:30:11 -07:00
Chris Robinson 45cb3e4956 Remove a couple redundant comments 2020-04-28 14:59:45 -07:00
Chris Robinson cf64dc1103 Fix up some more uses of [AL[C]]void 2020-04-28 14:48:12 -07:00
Chris Robinson 2520d492a5 Clean up al.h a bit 2020-04-28 14:27:52 -07:00
Chris Robinson c47f4822dc Move standard ALC function's annotations to the header 2020-04-28 13:36:43 -07:00
Chris Robinson 1120f52556 Clean up some function comments 2020-04-28 08:29:42 -07:00
Chris Robinson b828cf81a9 Shorten the name of format types 2020-04-28 07:56:36 -07:00
Chris Robinson 5441fba419 Don't maintain a global backend list end 2020-04-28 07:28:22 -07:00
Chris Robinson 187477db03 Properly return on error 2020-04-28 07:01:30 -07:00
Chris Robinson b4435cd053 Avoid using some extraneous ALC types 2020-04-28 06:51:21 -07:00
Chris Robinson 0a69adf845 Avoid unnecessary PATH_SUFFIXES 2020-04-26 07:59:08 -07:00
Chris Robinson 950e4f3cc4 Avoid duplicate env var declarations 2020-04-26 07:36:50 -07:00
Chris Robinson ff1562c916 Don't delete non-existent files in AppVeyor 2020-04-26 07:19:27 -07:00
Chris Robinson e3f6afafde Remove some hopefully unneeded macros 2020-04-25 02:12:43 -07:00
Chris Robinson be476c7935 Use global placement new for AsyncEvent 2020-04-24 07:04:32 -07:00
Chris Robinson f49238c792 Minor cleanup of reverb code 2020-04-24 02:51:23 -07:00
Chris Robinson 8739833770 Add a method to process two biquads at once 2020-04-23 22:13:52 -07:00
Chris Robinson e89978195f Clean up some scaling math 2020-04-23 06:18:54 -07:00
Chris Robinson aff410fa6d Avoid using some more AL types 2020-04-23 04:57:04 -07:00
Chris Robinson 2deb5e47d1 Avoid an unnecessary out parameter 2020-04-21 23:58:53 -07:00
Chris Robinson a97e6e6a12 Fix the source ID for a source voice stopped from deletion 2020-04-21 01:01:18 -07:00
Chris Robinson 552d2b1918 Set the device name in the Oboe backend 2020-04-20 04:39:36 -07:00
Chris Robinson 947826af1c Get rid of an unnecessary variable 2020-04-20 04:30:14 -07:00
Chris Robinson b692440725 Trace the opened Oboe stream properties 2020-04-19 15:19:28 -07:00
Chris Robinson 3ef6f22760 More gracefully relax the Oboe format request 2020-04-19 15:10:22 -07:00
Chris Robinson 61cf6d3bb6 Simplify some install statements 2020-04-19 04:38:44 -07:00
Chris Robinson 9b9a65a0bf Use an import target for libsndfile 2020-04-19 04:00:01 -07:00
Chris Robinson 3902337e01 Fix some message formatting 2020-04-19 03:37:32 -07:00
Chris Robinson 07726ad63a Make sure HAVE_OBOE is cleared before checking 2020-04-19 03:35:31 -07:00
Chris Robinson 1ca7f22145 Correctly test for SSE switches 2020-04-19 02:47:11 -07:00
Chris Robinson 0a0478f670 Rename install options for consistency 2020-04-19 00:58:01 -07:00
Chris Robinson 12d71a0ad1 Don't explicitly link libpthread
Compiling and linking with -pthread is apparently enough.
2020-04-19 00:37:03 -07:00
Chris Robinson 253773b316 Build Oboe with hidden visibility 2020-04-18 23:03:50 -07:00
Chris Robinson 643b192fd5 Try to be more robust with the Oboe format 2020-04-18 22:43:20 -07:00
Chris Robinson a387a610f8 Add missing backslash 2020-04-18 20:46:20 -07:00
Chris Robinson 0390828de0 Initial implementation for Oboe-based playback 2020-04-18 19:21:13 -07:00
Chris Robinson bd9eae8edd Attempt to build with Oboe on Android with Travis 2020-04-18 19:20:38 -07:00
Chris Robinson 67e54a2669 Add an Oboe backend stub 2020-04-18 15:17:53 -07:00
Chris Robinson a1e5f4eb83 Use cmake --build with Travis to build
Instead of assuming makefiles
2020-04-17 23:33:09 -07:00
Chris Robinson cd87b9c66b Update Travis to use Android NDK r21, API 16 2020-04-17 23:21:04 -07:00
Chris Robinson 93e677edb3 Precalculate some values
MSVC isn't as willing to precompute sqrt and log10 results from known input
values.
2020-04-16 23:17:07 -07:00
Chris Robinson 3824b92704 Simplify some reverb update code 2020-04-16 20:52:20 -07:00
Chris Robinson 27ac637a66 Remove another unnecessary return value 2020-04-16 17:29:32 -07:00
Chris Robinson cf4a848fd0 Remove an always-true return value 2020-04-16 16:00:26 -07:00
Chris Robinson 3cb12da765 Move a type to where it's used 2020-04-16 02:05:07 -07:00
Chris Robinson dc41f276d8 Get rid of the specialized MixRow_ methods 2020-04-16 01:47:33 -07:00
Chris Robinson 5214a7210a Combine multiple target installs 2020-04-15 00:21:36 -07:00
Chris Robinson 3a5a9e90d0 Fix installing alsoft-config 2020-04-15 00:09:45 -07:00
Chris Robinson 8ef8b5e215 Combine utility target installs 2020-04-14 23:37:48 -07:00
Chris Robinson cd6908fd19 Always define install targets for the main library
Examples and utilities now have separate install options.
2020-04-14 23:25:47 -07:00
Chris Robinson 930cda39cd Simplify some indentation 2020-04-14 22:02:59 -07:00
Chris Robinson ac54ab8a3e Remove some unnecessary cmake checks 2020-04-14 18:25:22 -07:00
Chris Robinson 78e789bf7b Silence a type conversion warning with GCC 2020-04-14 12:05:54 -07:00
Chris Robinson 6ac5816354 Avoid inlining some potentially costly calls 2020-04-14 11:50:59 -07:00
Chris Robinson 7f81eec1a6 Avoid a function call to get the frame step 2020-04-13 23:36:05 -07:00
Chris Robinson 02d7fbfa0c Use standard attribute declarations 2020-04-13 23:27:56 -07:00
Chris Robinson 1d8ac4d61f Throw exceptions for errors in the effect getters/setters 2020-04-10 20:23:20 -07:00
Chris Robinson 13153bab60 Add an effect_exception for handling effect properties 2020-04-10 18:26:08 -07:00
Chris Robinson f7380a44d4 Use a common base for a couple exceptions 2020-04-10 15:12:57 -07:00
Chris Robinson 611a0155cd Ignore "attribute '...' is not recognized" MSVC warning 2020-04-10 15:12:57 -07:00
kcat fc140db781 Merge pull request #414 from ArthurSonzogni/master
Add missing <cstdarg>
2020-04-10 15:01:38 -07:00
ArthurSonzogni 7548c235bd Add missing <cstdarg>
OpenAL-Soft fails to compile on some GCC version:
https://travis-ci.com/github/ArthurSonzogni/smk/jobs/318304162

It has regressed likely after:
https://github.com/kcat/openal-soft/commit/c83609277bed4be4ef40ed306bf2c57fefa19519

va_start is defined in:
- <stdarg.h>
- <cstdarg>

The repository is using it from:
- al/error.cpp
- al/filter.cpp
- alc/alu.cpp
- alc/helpers.cpp
- common/alexcpt.cpp
- utils/makemhr/loaddef.cpp
- utils/openal-info.c

This patch is adding its definition in the files missing it:
- common/alexcpt.cpp
- utils/makemhr/loaddef.cpp
- utils/openal-info.c

Bug: https://travis-ci.com/github/ArthurSonzogni/smk/jobs/318304162
2020-04-10 23:36:19 +02:00
Chris Robinson e123fa0ec3 Apply some alloc and alignment attributes 2020-04-10 11:05:25 -07:00
Chris Robinson 8bddf3e6ab Report invalid filter values 2020-04-10 09:16:08 -07:00
Chris Robinson 8713bb8afb Add missing include 2020-04-10 09:04:40 -07:00
Chris Robinson ae173ba863 Remove an unused cmake variable 2020-04-10 08:59:22 -07:00
Chris Robinson 0dc9b0392d Apply static-link flags directly to the target 2020-04-10 08:43:59 -07:00
Chris Robinson 35a2b0e5f8 Simplify generating chorus delays a bit 2020-04-09 21:00:37 -07:00
Chris Robinson c83609277b Use exceptions for filter errors 2020-04-09 19:36:37 -07:00
Chris Robinson 97c11577cd Make a default/empty constructor noexcept 2020-04-09 19:32:02 -07:00
Chris Robinson d67cba99bd Clean up some more unnecessary uses of AL types 2020-04-08 10:15:43 -07:00
Chris Robinson 8a5153ce0d Avoid ALfloat and ALint in the effects 2020-04-08 07:28:07 -07:00
Chris Robinson 55539787fb Avoid ALfloat in some places 2020-04-08 06:17:04 -07:00
Chris Robinson 024112a53a Use acquire-release semantics for changing deferred updates 2020-04-07 12:46:35 -07:00
Chris Robinson 0077a01667 Don't yield the CPU when waiting for updates to finish 2020-04-07 12:36:44 -07:00
Chris Robinson f628ab2151 Use better values for a couple in-progress enums 2020-04-07 12:24:20 -07:00
Chris Robinson efed431237 Use a unique_ptr for constructing the bsinc table coefficients 2020-04-07 08:30:37 -07:00
Chris Robinson 2fcc3f3887 Dynamically allocate the temporary bsinc filter table 2020-04-06 17:12:39 -07:00
Chris Robinson 7a324231a3 Combine the late reverb feedback and modulator delay lines 2020-04-06 16:14:13 -07:00
Chris Robinson 6ff5339112 Include the average modulation delay for the late reverb feedback 2020-04-06 15:46:14 -07:00
Chris Robinson baf14aad2a Pass the BSincHeader by reference instead of value 2020-04-05 23:08:02 -07:00
Chris Robinson ab6afd6fa0 Reimplement the modulation stage for reverb
This seems to be quite close recordings from real hardware, so it's probably
good enough.
2020-04-05 12:00:45 -07:00
Chris Robinson 192b1a1f65 Require Qt5 for alsoft-config 2020-04-05 01:41:36 -07:00
kcat de2599b70b Merge pull request #167 from aaronmjacobs/build-version-option
Add option to disable "build_version" cmake target
2020-04-05 00:02:54 -07:00
Chris Robinson 25464b2210 Avoid storing the bsinc coefficient deltas before output
This cuts the majority of stack use when generating the coefficients.
2020-04-04 22:04:59 -07:00
Chris Robinson 8084d08cf9 Get rid of the Android cross-compile toolchain
It has problems, and the Android NDK provides one that works fine.
2020-04-04 10:58:43 -07:00
Chris Robinson 9b11029af9 Remove the now-unused native tools 2020-04-04 08:58:06 -07:00
Chris Robinson 431d01cc7f Use a cmake script to convert a binary file to a header file 2020-04-04 08:46:18 -07:00
Chris Robinson a0b7638d63 Add an extension to change a buffer's unpack ambisonic order 2020-04-04 03:10:01 -07:00
Chris Robinson fc906c97f5 Track a buffer's ambisonic order 2020-04-04 01:52:29 -07:00
Chris Robinson 902f5a0dd2 Add an ambisonic order parameter to the buffer channel count 2020-04-03 21:43:57 -07:00
Chris Robinson bd50601f15 Remove a couple outdated comments 2020-04-03 10:07:43 -07:00
Chris Robinson 8f47013e43 Some more ALfloat->float cleanup 2020-04-03 08:49:15 -07:00
Chris Robinson 1130597562 Use structs for the mixer and resampler tags 2020-04-03 03:52:23 -07:00
Chris Robinson d9225083b4 Avoid using ALfloat in the mixer functions 2020-04-03 03:11:32 -07:00
Chris Robinson 236564b54a Another small cleanup 2020-04-03 02:57:51 -07:00
Chris Robinson 8adbde90f5 Jump to the target gain if the fade amount is small 2020-04-03 02:48:35 -07:00
Chris Robinson 367d4af07c Avoid unnecessary duplication in the resamplers 2020-04-03 02:39:23 -07:00
Chris Robinson ebe30fb0bd Remove the now-unused bsincgen 2020-04-02 04:34:28 -07:00
Chris Robinson ee89ca1bdd Fix bsinc filter alignment and offsets 2020-04-02 02:13:18 -07:00
Chris Robinson 57109902cd Avoid std::abs in a constexpr function 2020-04-02 01:52:07 -07:00
Chris Robinson 8853519d89 Generate the bsinc tables using constexpr methods
All the methods used should be compliant with C++14 constexpr rules. However,
the number of scales and phases cause GenerateBSincCoeffs to reach the allowed
step limit, preventing full compile-time generation. It's not a terribly big
deal, it'll generate them very quickly when loading, but it does prevent using
shared read-only memory pages.
2020-04-02 00:49:19 -07:00
Chris Robinson 6fb59f1182 Avoid using M_PI 2020-04-01 22:22:50 -07:00
Chris Robinson bf48dcd375 Report better latency when PulseAudio has no timing info 2020-03-30 20:06:24 -07:00
Chris Robinson b9f19c6ca2 Inline a small helper method 2020-03-30 16:06:34 -07:00
Chris Robinson 662d77159b Get rid of a redundant enum 2020-03-30 16:00:02 -07:00
Chris Robinson f2ddf971df Return the enumerated device names from the backend
Rather than using an out parameter.
2020-03-30 15:37:41 -07:00
Chris Robinson 167bdce48d Hold the ListLock while opening a device
Since it may rely on the enumerated device list that could be updated
asynchronously.
2020-03-30 13:50:50 -07:00
Chris Robinson 75479b58e3 Remove use of unnecessary type aliases 2020-03-30 13:43:49 -07:00
Chris Robinson cc009b8aa0 Move the FrontStablizer definition to its own header 2020-03-30 01:16:15 -07:00
Chris Robinson f8a5ea2d74 Inline a couple BackendBase methods 2020-03-30 00:07:35 -07:00
Chris Robinson 9b237790b2 Remove the mutex from the backend base 2020-03-29 23:57:37 -07:00
Chris Robinson d70912c034 Remove the QSA backend
It's been broken for who knows how long, and could really do with a rewrite for
the new interface anyway.
2020-03-29 20:37:58 -07:00
Chris Robinson fed80e0c10 Reset real-time priority on fork 2020-03-29 19:39:00 -07:00
Chris Robinson fb433cd918 Protect the call to pa_mainloop_quit with the mutex 2020-03-29 03:44:34 -07:00
Chris Robinson 5dfa24d5e2 Stop updating effects when one fails 2020-03-29 03:10:35 -07:00
Chris Robinson a5b8167280 Make some functions private methods 2020-03-28 18:15:05 -07:00
Chris Robinson 963d9b761b Rename ALvoice and related structs to Voice 2020-03-28 18:15:05 -07:00
Chris Robinson fb07612545 Get rid of MAX_INPUT_CHANNELS 2020-03-28 18:15:05 -07:00
Chris Robinson f1f9a14172 Avoid AL[C]boolean for internal use 2020-03-28 18:15:05 -07:00
Chris Robinson 504745abec Use a standard bool instead of ALboolean 2020-03-28 18:15:05 -07:00
Chris Robinson e78cb9b77f Avoid using more unnecessary type aliases 2020-03-28 18:15:04 -07:00
kcat 3fce1c4217 Merge pull request #405 from aaronmjacobs/cmake-fixes
Fix find_package_handle_standard_args name mismatch warnings
2020-03-28 18:14:34 -07:00
Aaron Jacobs a3a2305d7a Fix find_package_handle_standard_args name mismatch warnings 2020-03-28 17:43:17 -07:00
kcat 6cfb4ec462 Merge pull request #404 from jhasse/cmake-no-cxx-compiler
Don't require a C++ compiler for native-tools
2020-03-27 12:35:23 -07:00
Jan Niklas Hasse 2a3c7ccd81 Don't require a C++ compiler for native-tools
"By default C and CXX are enabled if no language options are given."
https://cmake.org/cmake/help/v3.17/command/project.html
2020-03-27 20:14:25 +01:00
Chris Robinson 8731accd3f Make sure prebuf is filled when starting pulseaudio playback 2020-03-27 01:03:01 -07:00
Chris Robinson e73c0979a1 Use "zero-copy" writes with pulseaudio playback 2020-03-26 22:50:54 -07:00
Chris Robinson d344c5a228 Avoid some extraneous uses of AL type aliases 2020-03-25 22:39:45 -07:00
Chris Robinson e8149ec509 Move some setup to a more logical place 2020-03-25 22:24:09 -07:00
Chris Robinson a27096dd63 Dynamically allocate voice channel data
Rather than allocating for a full 8 channels for each voice, when the vast
majority will only need 1 or 2. The voice channel data is relatively big since
it needs to hold HRTF coefficients and history, and this will allow increasing
the maximum number of buffer channels without an obscene memory increase.
2020-03-25 21:06:24 -07:00
Chris Robinson cae78e79e8 Convert the examples from SDL_sound to libsndfile 2020-03-24 15:46:47 -07:00
Chris Robinson 586bc94d51 Use libsndfile for the alplay example 2020-03-24 13:36:49 -07:00
Chris Robinson c24d127229 Avoid non-namespace template specialization 2020-03-24 12:03:07 -07:00
Chris Robinson cc27bbb680 Clear the WASAPI device list when enumerating 2020-03-24 11:48:25 -07:00
Chris Robinson 62f17d8763 Don't send close messages for unopened WASAPI devices 2020-03-24 11:40:34 -07:00
Chris Robinson eed8407599 Remove some unused functions 2020-03-23 17:18:11 -07:00
Chris Robinson 7effad45c2 Clean up ALfilter some
Use inline methods instead of macros
Fix indentation
Add some consts
2020-03-23 16:31:08 -07:00
Chris Robinson 6a3b3b180b Add a macro to disable class-specific new/delete 2020-03-23 16:00:50 -07:00
Chris Robinson b53294e291 Use inline initialization more for sources 2020-03-23 15:04:26 -07:00
Chris Robinson 5d0f90fed0 Avoid ## in a macro
Cppcheck thinks it's an invalid use of it
2020-03-23 14:26:00 -07:00
Chris Robinson 41b4c06697 Simplify clamping the voice mix size 2020-03-23 11:35:08 -07:00
Chris Robinson 132a69c03d Use more appropriate types for bitfields 2020-03-22 21:34:50 -07:00
Chris Robinson 46234171d1 Clean up some C++11-isms 2020-03-22 21:15:12 -07:00
Chris Robinson dc8ccc06ce More cleanup for the pitch shifter 2020-03-22 20:48:02 -07:00
Chris Robinson 813d4ed566 Use make_unique instead of new'ing into a unique_ptr 2020-03-22 18:48:33 -07:00
Chris Robinson 49b2d703e7 Properly define the new[] and delete[] operators 2020-03-22 13:28:45 -07:00
Chris Robinson 3c631149e3 Update AppVeyor to VS 2017 for 32-bit 2020-03-22 11:41:57 -07:00
Chris Robinson be1584ab47 Avoid assigning in a conditional expression 2020-03-22 11:35:08 -07:00
Chris Robinson ef663f13c0 Properly allocate voices 2020-03-22 11:34:37 -07:00
Chris Robinson 3d09898d40 Use the correct include for al::fstream 2020-03-22 11:09:19 -07:00
Chris Robinson 2acdcba8d1 Silence a potential type truncation warning 2020-03-22 10:41:23 -07:00
Chris Robinson f3ff28fb2a Some type cleanup 2020-03-22 10:41:02 -07:00
Chris Robinson d30d9a2c9f Clean up the pitch and frequency shifter some 2020-03-22 10:06:23 -07:00
Chris Robinson 002d0eb6a0 Fix the frequency shifter channel panning 2020-03-22 08:51:59 -07:00
Chris Robinson 9ce182228d Avoid some pre-C++14 workarounds 2020-03-22 08:51:06 -07:00
Chris Robinson 040309b9f4 Add missing semicolon 2020-03-22 08:22:25 -07:00
Chris Robinson f6a0b004e0 Update to C++14 2020-03-22 08:05:22 -07:00
Chris Robinson adf28d87aa Remove the check for stat()
It's POSIX-2001 standard
2020-03-21 08:39:45 -07:00
Chris Robinson 12775513ae Cleanup some helper code 2020-03-20 15:48:27 -07:00
Chris Robinson 0d11de3e8d Move CPUCapFlags and FillCPUCaps to their own source 2020-03-20 15:30:41 -07:00
Chris Robinson f56ef433d8 Move the FPUCtl methods to its own source 2020-03-20 15:01:45 -07:00
Chris Robinson ad98895876 Avoid a function call to get the channel count 2020-03-18 04:08:26 -07:00
Chris Robinson 7805179313 Rename a method to be clearer 2020-03-18 03:17:14 -07:00
Chris Robinson 54e2eafcc9 Make the pulseaudio enumeration functions class methods 2020-03-18 03:07:54 -07:00
Chris Robinson 9f077d5f41 Remove a couple unused methods 2020-03-18 02:35:31 -07:00
Chris Robinson bf50f227b9 Use a busy-wait when synchronizing against the mixer
The mixer should have higher priority than any thread that can make AL calls,
so even on single-core systems, it shouldn't stall the mix. It will, however,
return back to the caller as soon as it can, while yielding will give up the
timeslice if there's any other thread waiting to process even if the mix is
almost done.
2020-03-16 01:05:14 -07:00
Chris Robinson 78251fd7e6 Fix voices being forced to update without valid properties
When starting a voice, the source ID was set before its first update struct was
provided, creating a small window where a listener or effect slot update could
force a voice to update without it having any valid properties to update with.
Supplying the update struct first would create a different race, where the
mixer could see a voice without a source but with an update struct, causing the
update struct to be 'freed' without being applied.

The fix here is to provide the update struct before setting the source ID, and
change the mixer to ignore update structs for voices without a source ID. This
can pseudo-orphan the updates that get set on a voice just as it stops, leaving
the struct unusable until the voice is used again, or the voice gets deleted
which will clear it. But it allows the update struct to stay in place and get
applied once the voice gets a source ID.
2020-03-04 23:18:05 -08:00
Chris Robinson 48b9b541ec Avoid redundantly setting voice members 2020-03-04 13:40:21 -08:00
Chris Robinson 971862a80c Don't change flags after InitVoice 2020-03-04 11:01:35 -08:00
Chris Robinson e7405f7801 Use a new voice state to indicate a pending source offset change 2020-03-04 10:16:59 -08:00
Chris Robinson 08dc831e0e Avoid unnecessarily reclearing some variables 2020-03-03 20:44:19 -08:00
Chris Robinson accc1ec1c8 Add a helper to wait for the device mix 2020-03-03 20:32:44 -08:00
Chris Robinson 3e1a2c0f77 Use an intrusive_ptr for the device's HrtfStore 2020-03-01 17:16:09 -08:00
Chris Robinson a01617904c Simplify getting a voice for a new source offset 2020-02-28 20:07:43 -08:00
Chris Robinson b42d241da5 Use real-time priority by default 2020-02-26 09:53:06 -08:00
Chris Robinson eb49290dab Remove unnecessary locks now that the mixer doesn't require one 2020-02-26 04:58:02 -08:00
Chris Robinson 4555b74bd7 Use a VoiceChange object to change the offset of a playing source 2020-02-26 03:52:40 -08:00
Chris Robinson 872096958b Rename the voice's PendingStop to PendingChange 2020-02-26 01:48:59 -08:00
Chris Robinson 814f80ec59 Move voice initialization to a separate function 2020-02-26 01:39:30 -08:00
Chris Robinson 8618571866 Avoid storing the source offset when it's just read and reset 2020-02-26 01:03:23 -08:00
Chris Robinson 795c4fcecc Make the source's send array static instead of dynamic 2020-02-25 06:39:03 -08:00
Chris Robinson 9ddfcd6a1b Update an comment about the default HRTF filters 2020-02-25 05:00:26 -08:00
Chris Robinson af6584b8fc Reduce the maximum number of source sends to 6 2020-02-25 04:52:39 -08:00
Chris Robinson 7144cf405a Handle playing voices after disconnection 2020-02-24 22:21:44 -08:00
Chris Robinson fd0ed9f33f Improve searching for the next voice when playing multiple sources
Instead of searching from the beginning of the voice list for each source, just
continue searching from the last source's voice.
2020-02-24 21:40:54 -08:00
Chris Robinson 52d86ad51f Ignore VoiceChange objects while disconnected
And try to improve ALvoice/VoiceChange handling when attempting to recover a
lost device.
2020-02-24 11:01:45 -08:00
Chris Robinson 90d45984dc Simplify replaying a source 2020-02-23 21:40:54 -08:00
Chris Robinson 8554e6cde2 Remove AL_SOFT_map_buffer from alffplay and add AL_SOFT_callback_buffer
The former doesn't really help too much since buffers still need to be
(re)filled and (de)queued individually. A callback buffer, on the other hand,
allows for greater efficiency since it just needs to write into a ring buffer
that the mixer will directly read from.
2020-02-23 06:28:39 -08:00
Chris Robinson a37932a0d0 Fix voice allocation increase amount 2020-02-22 18:01:34 -08:00
Chris Robinson 6044e0d7ca Remove a couple unnecessary type aliases 2020-02-22 00:08:55 -08:00
Chris Robinson 4660819f6a Avoid a single function call in a loop 2020-02-21 22:17:29 -08:00
Chris Robinson a8162a77c2 Use an array of ALvoice pointers for the active voices
This allows growing the array atomically with the mixer since the ALvoice
objects themselves don't move, and a new larger array of them can be swapped in
without blocking the mixer.
2020-02-21 20:14:28 -08:00
Chris Robinson 6e6a30679e Set the voice's property update after the source ID
Otherwise the mixer has a chance to throw it out before realizing it's needed.
2020-02-21 04:55:04 -08:00
Chris Robinson 0694df9014 Handle playing and pausing with VoiceChanges 2020-02-21 04:29:32 -08:00
Chris Robinson 9887c6971d Prepare VoiceChange objects one at a time 2020-02-21 03:23:22 -08:00
Chris Robinson 9b801227d5 Don't unnecessarily count all voices when playing sources 2020-02-21 00:54:05 -08:00
Chris Robinson 9b43327f56 Asynchronously stop and rewind voices 2020-02-20 23:53:56 -08:00
Chris Robinson bdb8ef22b0 Asynchronously stop voices if its source is being deleted 2020-02-20 22:50:37 -08:00
Chris Robinson acf7f6f74e Add a voice flag to indicate a voice has a pending stop
Pending/asynchronous stops to be implemented soon.
2020-02-20 19:18:07 -08:00
Chris Robinson 642ef4edc9 Add a streaming example using a callback buffer 2020-02-20 17:53:09 -08:00
Chris Robinson 3aad01d3ba Add AL_SOFTX_callback_buffer to the extension list 2020-02-19 17:32:32 -08:00
Chris Robinson 4277c05b0c Workaround a 32-bit GCC/MinGW TLS bug 2020-02-18 18:14:59 -08:00
Chris Robinson d4e01a1818 Flag the voice as callback-based when given a callback buffer 2020-02-17 17:34:17 -08:00
Chris Robinson 04e5b23286 Don't bother looping the calculated source offset
The offset shouldn't be in a non-wrapped state when playing, and when not
playing it's always 0 anyway.
2020-02-17 17:30:47 -08:00
Chris Robinson d6e49b503b Don't allow setting a source offset for a callback 2020-02-17 17:20:01 -08:00
Chris Robinson 3122c3b4a1 Handle running the buffer callback in the voice 2020-02-17 17:13:25 -08:00
Chris Robinson c8dfd24818 Add a callback flag for voices 2020-02-17 04:50:00 -08:00
Chris Robinson a221f8671e Prevent queueing a callback buffer
Also prevent setting it on multiple sources
2020-02-17 00:32:22 -08:00
Chris Robinson ccc6da0a5b Set and retrieve a buffer's callback 2020-02-17 00:22:51 -08:00
Chris Robinson 3156b17057 Stub out an interface for a callback-driven buffer 2020-02-16 23:49:29 -08:00
Chris Robinson 8665f404a5 Always start streaming sources from the start of the queue
For sources that don't start with an offset, at least. This is necessary so the
completed buffer event will give the correct count if it starts with null or
empty buffers that become processed.
2020-02-15 22:37:19 -08:00
Chris Robinson be89684f3c Update the makemhr utility and default HRTF 2020-02-11 01:01:10 -08:00
Chris Robinson 1d91e282c8 Update the MHR format
This update removes the 16/24-bit sample type enum, now always being 24-bit
(other than a very small size saving, there's no practical benefit to storing
16-bit samples). This also reverses the field storage, so no on-load fixup is
needed, and stores the IR delays with 2 bits of sub-sample precision, allowing
for slightly better timing (after resampling, blending, etc).
2020-02-11 00:37:21 -08:00
Chris Robinson 3acc667c28 Scale delays when exceeding the max for generated mhr files 2020-02-10 22:25:07 -08:00
Chris Robinson 6ebdf3ab62 Use a triplet struct array instead of separate arrays 2020-02-09 14:25:20 -08:00
Chris Robinson f2bc24f761 Consistently use distant wavefronts for 0 distance 2020-02-08 22:39:32 -08:00
Chris Robinson 85395b23a4 Clear voice target gains earlier 2020-02-08 18:10:59 -08:00
Chris Robinson 3ee0906c81 Use std::array and span for the HRTF delays 2020-02-08 15:00:58 -08:00
kcat e72a02c5e2 Merge pull request #393 from Etwus/patch-1
Fix unreachable code warning
2020-02-08 15:00:26 -08:00
Aleš Gajdacz 7e319ad99b Fix unreachable code warning 2020-02-08 23:34:09 +01:00
Chris Robinson ebb1aaa06d Make sure stereo angles are kept in-range. 2020-01-31 11:11:06 -08:00
Chris Robinson e960276a47 Try harder to find the closest HRIR for the B-Format decoder 2020-01-26 01:23:39 -08:00
Chris Robinson bd7937e5b5 Update the default HRTF 2020-01-25 16:59:07 -08:00
Chris Robinson f63880c2ff Use the peak IR magnitude to get the onset
While maybe not technically correct, we actually only care about the difference
between onsets (any base constant is removed). This should work better since it
determines when the IR is most audible, whereas previously it used a variable
threshold of when it reached 15% of the max amplitude.

An even better method may be to check where the IR amplitude exceeds a fixed
threshold (i.e. the same threshold applied to all IRs), but that would need
tweaking to find a level that doesn't catch random noise and doesn't
potentially miss the more occluded IRs.
2020-01-25 16:59:07 -08:00
kcat 3cd4af1b8e Merge pull request #385 from sergeyext/fix_msvc_runtime
Set static MSVC runtime for cpp files when FORCE_STATIC_VCRT is ON.
2020-01-25 15:44:30 -08:00
Sergey Karchevsky 3001d56d4b Set static MSVC runtim for cpp files when FORCE_STATIC_VCRT is ON.
Fixes #384
2020-01-26 05:38:37 +07:00
Aaron Jacobs 79bce84900 Add option to disable "build_version" cmake target 2018-01-28 00:03:02 -08:00
246 changed files with 1570 additions and 78725 deletions
+8 -4
View File
@@ -1,5 +1,9 @@
build*/
winbuild/
win64build/
openal-soft.kdev4
.kdev4/
winbuild
win64build
## kdevelop
*.kdev4
## qt-creator
CMakeLists.txt.user*
-68
View File
@@ -1,68 +0,0 @@
language: cpp
matrix:
include:
- os: linux
dist: xenial
- os: linux
dist: trusty
env:
- BUILD_ANDROID=true
- os: osx
sudo: required
install:
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
# Install pulseaudio, portaudio, ALSA, JACK dependencies for
# corresponding backends.
# Install Qt5 dependency for alsoft-config.
sudo apt-get install -qq \
libpulse-dev \
portaudio19-dev \
libasound2-dev \
libjack-dev \
qtbase5-dev
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip
unzip -q ~/android-ndk.zip -d ~ \
'android-ndk-r16b/build/cmake/*' \
'android-ndk-r16b/build/core/toolchains/arm-linux-androideabi-*/*' \
'android-ndk-r16b/platforms/android-14/arch-arm/*' \
'android-ndk-r16b/source.properties' \
'android-ndk-r16b/sources/android/support/include/*' \
'android-ndk-r16b/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
'android-ndk-r16b/sources/cxx-stl/llvm-libc++/include/*' \
'android-ndk-r16b/sysroot/*' \
'android-ndk-r16b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
'android-ndk-r16b/toolchains/llvm/prebuilt/linux-x86_64/*'
fi
script:
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && -z "${BUILD_ANDROID}" ]]; then
cmake \
-DALSOFT_REQUIRE_ALSA=ON \
-DALSOFT_REQUIRE_OSS=ON \
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_JACK=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
cmake \
-DANDROID_STL=c++_shared \
-DCMAKE_TOOLCHAIN_FILE=~/android-ndk-r16b/build/cmake/android.toolchain.cmake \
-DALSOFT_REQUIRE_OPENSL=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
cmake \
-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- make -j2
-30
View File
@@ -1,30 +0,0 @@
Portions of this software are licensed under the BSD 3-Clause license.
Copyright (c) 2015, Archontis Politis
Copyright (c) 2019, Christopher Robinson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Spherical-Harmonic-Transform nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-1575
View File
File diff suppressed because it is too large Load Diff
-437
View File
@@ -1,437 +0,0 @@
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the library GPL. It is
numbered 2 because it goes with version 2 of the ordinary GPL.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Library General Public License, applies to some
specially designated Free Software Foundation software, and to any
other libraries whose authors decide to use it. You can use it for
your libraries, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if
you distribute copies of the library, or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link a program with the library, you must provide
complete object files to the recipients so that they can relink them
with the library, after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
Our method of protecting your rights has two steps: (1) copyright
the library, and (2) offer you this license which gives you legal
permission to copy, distribute and/or modify the library.
Also, for each distributor's protection, we want to make certain
that everyone understands that there is no warranty for this free
library. If the library is modified by someone else and passed on, we
want its recipients to know that what they have is not the original
version, so that any problems introduced by others will not reflect on
the original authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that companies distributing free
software will individually obtain patent licenses, thus in effect
transforming the program into proprietary software. To prevent this,
we have made it clear that any patent must be licensed for everyone's
free use or not licensed at all.
Most GNU software, including some libraries, is covered by the ordinary
GNU General Public License, which was designed for utility programs. This
license, the GNU Library General Public License, applies to certain
designated libraries. This license is quite different from the ordinary
one; be sure to read it in full, and don't assume that anything in it is
the same as in the ordinary license.
The reason we have a separate public license for some libraries is that
they blur the distinction we usually make between modifying or adding to a
program and simply using it. Linking a program with a library, without
changing the library, is in some sense simply using the library, and is
analogous to running a utility program or application program. However, in
a textual and legal sense, the linked executable is a combined work, a
derivative of the original library, and the ordinary General Public License
treats it as such.
Because of this blurred distinction, using the ordinary General
Public License for libraries did not effectively promote software
sharing, because most developers did not use the libraries. We
concluded that weaker conditions might promote sharing better.
However, unrestricted linking of non-free programs would deprive the
users of those programs of all benefit from the free status of the
libraries themselves. This Library General Public License is intended to
permit developers of non-free programs to use free libraries, while
preserving your freedom as a user of such programs to change the free
libraries that are incorporated in them. (We have not seen how to achieve
this as regards changes in header files, but we have achieved it as regards
changes in the actual functions of the Library.) The hope is that this
will lead to faster development of free libraries.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, while the latter only
works together with the library.
Note that it is possible for a library to be covered by the ordinary
General Public License rather than by this special one.
GNU LIBRARY GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library which
contains a notice placed by the copyright holder or other authorized
party saying it may be distributed under the terms of this Library
General Public License (also called "this License"). Each licensee is
addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also compile or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
c) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
d) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the source code distributed need not include anything that is normally
distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Library General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
-461
View File
@@ -1,461 +0,0 @@
openal-soft-1.20.1:
Implemented the AL_SOFT_direct_channels_remix extension. This extends
AL_DIRECT_CHANNELS_SOFT to optionally remix input channels that don't have
a matching output channel.
Implemented the AL_SOFT_bformat_ex extension. This extends B-Format buffer
support for N3D or SN3D scaling, or ACN channel ordering.
Fixed a potential voice leak when a source is started and stopped or
restarted in quick succession.
Fixed a potential device reset failure with JACK.
Improved handling of unsupported channel configurations with WASAPI. Such
setups will now try to output at least a stereo mix.
Improved clarity a bit for the HRTF second-order ambisonic decoder.
Improved detection of compatible layouts for SOFA files in makemhr and
sofa-info.
Added the ability to resample HRTFs on load. MHR files no longer need to
match the device sample rate to be usable.
Added an option to limit the HRTF's filter length.
openal-soft-1.20.0:
Converted the library codebase to C++11. A lot of hacks and custom
structures have been replaced with standard or cleaner implementations.
Partially implemented the Vocal Morpher effect.
Fixed the bsinc SSE resamplers on non-GCC compilers.
Fixed OpenSL capture.
Fixed support for extended capture formats with OpenSL.
Fixed handling of WASAPI not reporting a default device.
Fixed performance problems relating to semaphores on macOS.
Modified the bsinc12 resampler's transition band to better avoid aliasing
noise.
Modified alcResetDeviceSOFT to attempt recovery of disconnected devices.
Modified the virtual speaker layout for HRTF B-Format decoding.
Modified the PulseAudio backend to use a custom processing loop.
Renamed the makehrtf utility to makemhr.
Improved the efficiency of the bsinc resamplers when up-sampling.
Improved the quality of the bsinc resamplers slightly.
Improved the efficiency of the HRTF filters.
Improved the HRTF B-Format decoder coefficient generation.
Improved reverb feedback fading to be more consistent with pan fading.
Improved handling of sources that end prematurely, avoiding loud clicks.
Improved the performance of some reverb processing loops.
Added fast_bsinc12 and 24 resamplers that improve efficiency at the cost of
some quality. Notably, down-sampling has less smooth pitch ramping.
Added support for SOFA input files with makemhr.
Added a build option to use pre-built native tools. For cross-compiling,
use with caution and ensure the native tools' binaries are kept up-to-date.
Added an adjust-latency config option for the PulseAudio backend.
Added basic support for multi-field HRTFs.
Added an option for mixing first- or second-order B-Format with HRTF
output. This can improve HRTF performance given a number of sources.
Added an RC file for proper DLL version information.
Disabled some old KDE workarounds by default. Specifically, PulseAudio
streams can now be moved (KDE may try to move them after opening).
openal-soft-1.19.1:
Implemented capture support for the SoundIO backend.
Fixed source buffer queues potentially not playing properly when a queue
entry completes.
Fixed possible unexpected failures when generating auxiliary effect slots.
Fixed a crash with certain reverb or device settings.
Fixed OpenSL capture.
Improved output limiter response, better ensuring the sample amplitude is
clamped for output.
openal-soft-1.19.0:
Implemented the ALC_SOFT_device_clock extension.
Implemented the Pitch Shifter, Frequency Shifter, and Autowah effects.
Fixed compiling on FreeBSD systems that use freebsd-lib 9.1.
Fixed compiling on NetBSD.
Fixed the reverb effect's density scale and panning parameters.
Fixed use of the WASAPI backend with certain games, which caused odd COM
initialization errors.
Increased the number of virtual channels for decoding Ambisonics to HRTF
output.
Changed 32-bit x86 builds to use SSE2 math by default for performance.
Build-time options are available to use just SSE1 or x87 instead.
Replaced the 4-point Sinc resampler with a more efficient cubic resampler.
Renamed the MMDevAPI backend to WASAPI.
Added support for 24-bit, dual-ear HRTF data sets. The built-in data set
has been updated to 24-bit.
Added a 24- to 48-point band-limited Sinc resampler.
Added an SDL2 playback backend. Disabled by default to avoid a dependency
on SDL2.
Improved the performance and quality of the Chorus and Flanger effects.
Improved the efficiency of the band-limited Sinc resampler.
Improved the Sinc resampler's transition band to avoid over-attenuating
higher frequencies.
Improved the performance of some filter operations.
Improved the efficiency of object ID lookups.
Improved the efficienty of internal voice/source synchronization.
Improved AL call error logging with contextualized messages.
Removed the reverb effect's modulation stage. Due to the lack of reference
for its intended behavior and strength.
openal-soft-1.18.2:
Fixed resetting the FPU rounding mode after certain function calls on
Windows.
Fixed use of SSE intrinsics when building with Clang on Windows.
Fixed a crash with the JACK backend when using JACK1.
Fixed use of pthread_setnane_np on NetBSD.
Fixed building on FreeBSD with an older freebsd-lib.
OSS now links with libossaudio if found at build time (for NetBSD).
openal-soft-1.18.1:
Fixed an issue where resuming a source might not restart playing it.
Fixed PulseAudio playback when the configured stream length is much less
than the requested length.
Fixed MMDevAPI capture with sample rates not matching the backing device.
Fixed int32 output for the Wave Writer.
Fixed enumeration of OSS devices that are missing device files.
Added correct retrieval of the executable's path on FreeBSD.
Added a config option to specify the dithering depth.
Added a 5.1 decoder preset that excludes front-center output.
openal-soft-1.18.0:
Implemented the AL_EXT_STEREO_ANGLES and AL_EXT_SOURCE_RADIUS extensions.
Implemented the AL_SOFT_gain_clamp_ex, AL_SOFT_source_resampler,
AL_SOFT_source_spatialize, and ALC_SOFT_output_limiter extensions.
Implemented 3D processing for some effects. Currently implemented for
Reverb, Compressor, Equalizer, and Ring Modulator.
Implemented 2-channel UHJ output encoding. This needs to be enabled with a
config option to be used.
Implemented dual-band processing for high-quality ambisonic decoding.
Implemented distance-compensation for surround sound output.
Implemented near-field emulation and compensation with ambisonic rendering.
Currently only applies when using the high-quality ambisonic decoder or
ambisonic output, with appropriate config options.
Implemented an output limiter to reduce the amount of distortion from
clipping.
Implemented dithering for 8-bit and 16-bit output.
Implemented a config option to select a preferred HRTF.
Implemented a run-time check for NEON extensions using /proc/cpuinfo.
Implemented experimental capture support for the OpenSL backend.
Fixed building on compilers with NEON support but don't default to having
NEON enabled.
Fixed support for JACK on Windows.
Fixed starting a source while alcSuspendContext is in effect.
Fixed detection of headsets as headphones, with MMDevAPI.
Added support for AmbDec config files, for custom ambisonic decoder
configurations. Version 3 files only.
Added backend-specific options to alsoft-config.
Added first-, second-, and third-order ambisonic output formats. Currently
only works with backends that don't rely on channel labels, like JACK,
ALSA, and OSS.
Added a build option to embed the default HRTFs into the lib.
Added AmbDec presets to enable high-quality ambisonic decoding.
Added an AmbDec preset for 3D7.1 speaker setups.
Added documentation regarding Ambisonics, 3D7.1, AmbDec config files, and
the provided ambdec presets.
Added the ability for MMDevAPI to open devices given a Device ID or GUID
string.
Added an option to the example apps to open a specific device.
Increased the maximum auxiliary send limit to 16 (up from 4). Requires
requesting them with the ALC_MAX_AUXILIARY_SENDS context creation
attribute.
Increased the default auxiliary effect slot count to 64 (up from 4).
Reduced the default period count to 3 (down from 4).
Slightly improved automatic naming for enumerated HRTFs.
Improved B-Format decoding with HRTF output.
Improved internal property handling for better batching behavior.
Improved performance of certain filter uses.
Removed support for the AL_SOFT_buffer_samples and AL_SOFT_buffer_sub_data
extensions. Due to conflicts with AL_EXT_SOURCE_RADIUS.
openal-soft-1.17.2:
Implemented device enumeration for OSSv4.
Fixed building on OSX.
Fixed building on non-Windows systems without POSIX-2008.
Fixed Dedicated Dialog and Dedicated LFE effect output.
Added a build option to override the share install dir.
Added a build option to static-link libgcc for MinGW.
openal-soft-1.17.1:
Fixed building with JACK and without PulseAudio.
Fixed building on FreeBSD.
Fixed the ALSA backend's allow-resampler option.
Fixed handling of inexact ALSA period counts.
Altered device naming scheme on Windows backends to better match other
drivers.
Updated the CoreAudio backend to use the AudioComponent API. This clears up
deprecation warnings for OSX 10.11, although requires OSX 10.6 or newer.
openal-soft-1.17.0:
Implemented a JACK playback backend.
Implemented the AL_EXT_BFORMAT and AL_EXT_MULAW_BFORMAT extensions.
Implemented the ALC_SOFT_HRTF extension.
Implemented C, SSE3, and SSE4.1 based 4- and 8-point Sinc resamplers.
Implemented a C and SSE based band-limited Sinc resampler. This does 12- to
24-point Sinc resampling, and performs anti-aliasing.
Implemented B-Format output support for the wave file writer. This creates
FuMa-style first-order Ambisonics wave files (AMB format).
Implemented a stereo-mode config option for treating stereo modes as either
speakers or headphones.
Implemented per-device configuration options.
Fixed handling of PulseAudio and MMDevAPI devices that have identical
descriptions.
Fixed a potential lockup when stopping playback of suspended PulseAudio devices.
Fixed logging of Unicode characters on Windows.
Fixed 5.1 surround sound channels. By default it will now use the side
channels for the surround output. A configuration using rear channels is
still available.
Fixed the QSA backend potentially altering the capture format.
Fixed detecting MMDevAPI's default device.
Fixed returning the default capture device name.
Fixed mixing property calculations when deferring context updates.
Altered the behavior of alcSuspendContext and alcProcessContext to better
match certain Windows drivers.
Altered the panning algorithm, utilizing Ambisonics for better side and
back positioning cues with surround sound output.
Improved support for certain older Windows apps.
Improved the alffplay example to support surround sound streams.
Improved support for building as a sub-project.
Added an HRTF playback example.
Added a tone generator output test.
Added a toolchain to help with cross-compiling to Android.
openal-soft-1.16.0:
Implemented EFX Chorus, Flanger, Distortion, Equalizer, and Compressor
effects.
Implemented high-pass and band-pass EFX filters.
Implemented the high-pass filter for the EAXReverb effect.
Implemented SSE2 and SSE4.1 linear resamplers.
Implemented Neon-enhanced non-HRTF mixers.
Implemented a QSA backend, for QNX.
Implemented the ALC_SOFT_pause_device, AL_SOFT_deferred_updates,
AL_SOFT_block_alignment, AL_SOFT_MSADPCM, and AL_SOFT_source_length
extensions.
Fixed resetting mmdevapi backend devices.
Fixed clamping when converting 32-bit float samples to integer.
Fixed modulation range in the Modulator effect.
Several fixes for the OpenSL playback backend.
Fixed device specifier names that have Unicode characters on Windows.
Added support for filenames and paths with Unicode (UTF-8) characters on
Windows.
Added support for alsoft.conf config files found in XDG Base Directory
Specification locations (XDG_CONFIG_DIRS and XDG_CONFIG_HOME, or their
defaults) on non-Windows systems.
Added a GUI configuration utility (requires Qt 4.8).
Added support for environment variable expansion in config options (not
keys or section names).
Added an example that uses SDL2 and ffmpeg.
Modified examples to use SDL_sound.
Modified CMake config option names for better sorting.
HRTF data sets specified in the hrtf_tables config option may now be
relative or absolute filenames.
Made the default HRTF data set an external file, and added a data set for
48khz playback in addition to 44.1khz.
Added support for C11 atomic methods.
Improved support for some non-GNU build systems.
openal-soft-1.15.1:
Fixed a regression with retrieving the source's AL_GAIN property.
openal-soft-1.15:
Fixed device enumeration with the OSS backend.
Reorganized internal mixing logic, so unneeded steps can potentially be
skipped for better performance.
Removed the lookup table for calculating the mixing pans. The panning is
now calculated directly for better precision.
Improved the panning of stereo source channels when using stereo output.
Improved source filter quality on send paths.
Added a config option to allow PulseAudio to move streams between devices.
The PulseAudio backend will now attempt to spawn a server by default.
Added a workaround for a DirectSound bug relating to float32 output.
Added SSE-based mixers, for HRTF and non-HRTF mixing.
Added support for the new AL_SOFT_source_latency extension.
Improved ALSA capture by avoiding an extra buffer when using sizes
supported by the underlying device.
Improved the makehrtf utility to support new options and input formats.
Modified the CFLAGS declared in the pkg-config file so the "AL/" portion of
the header includes can optionally be omitted.
Added a couple example code programs to show how to apply reverb, and
retrieve latency.
The configuration sample is now installed into the share/openal/ directory
instead of /etc/openal.
The configuration sample now gets installed by default.
+10 -55
View File
@@ -1,61 +1,16 @@
OpenAL soft
===========
# openal-soft-vita
`master` branch CI status : [![Build Status](https://travis-ci.org/kcat/openal-soft.svg?branch=master)](https://travis-ci.org/kcat/openal-soft) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)
This repo contains patchsets for vita [openal-soft](https://openal-soft.org/) port.
OpenAL Soft is an LGPL-licensed, cross-platform, software implementation of the OpenAL 3D audio API. It's forked from the open-sourced Windows version available originally from openal.org's SVN repository (now defunct).
OpenAL provides capabilities for playing audio in a virtual 3D environment. Distance attenuation, doppler shift, and directional sound emitters are among the features handled by the API. More advanced effects, including air absorption, occlusion, and environmental reverb, are available through the EFX extension. It also facilitates streaming audio, multi-channel buffers, and audio capture.
Format is openal-soft-<openal_version>-vita-<patchset_version>.patch
More information is available on the [official website](http://openal-soft.org/)
For full source see vita-* branches
Source Install
-------------
To install OpenAL Soft, use your favorite shell to go into the build/
directory, and run:
```bash
cmake ..
# building
```
mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release ..
make install
```
Assuming configuration went well, you can then build it, typically using GNU
Make (KDevelop, MSVC, and others are possible depending on your system setup
and CMake configuration).
Please Note: Double check that the appropriate backends were detected. Often,
complaints of no sound, crashing, and missing devices can be solved by making
sure the correct backends are being used. CMake's output will identify which
backends were enabled.
For most systems, you will likely want to make sure ALSA, OSS, and PulseAudio
were detected (if your target system uses them). For Windows, make sure
DirectSound was detected.
Utilities
---------
The source package comes with an informational utility, openal-info, and is
built by default. It prints out information provided by the ALC and AL sub-
systems, including discovered devices, version information, and extensions.
Configuration
-------------
OpenAL Soft can be configured on a per-user and per-system basis. This allows
users and sysadmins to control information provided to applications, as well
as application-agnostic behavior of the library. See alsoftrc.sample for
available settings.
Acknowledgements
----------------
Special thanks go to:
- Creative Labs for the original source code this is based off of.
- Christopher Fitzgerald for the current reverb effect implementation, and
helping with the low-pass and HRTF filters.
- Christian Borss for the 3D panning code previous versions used as a base.
- Ben Davis for the idea behind a previous version of the click-removal code.
- Richard Furse for helping with my understanding of Ambisonics that is used by
the various parts of the library.
-39
View File
@@ -1,39 +0,0 @@
# Cross-compiling requires CMake 2.6 or newer. Example:
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt -DHOST=arm-linux-androideabi
# Where 'arm-linux-androideabi' is the host prefix for the cross-compiler. If
# you already have a toolchain file setup, you may use that instead of this
# file. Make sure to set CMAKE_FIND_ROOT_PATH to where the NDK toolchain was
# installed (e.g. "$ENV{HOME}/toolchains/arm-linux-androideabi-r10c-21").
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Linux)
# which compilers to use for C and C++
SET(CMAKE_C_COMPILER "${HOST}-gcc")
SET(CMAKE_CXX_COMPILER "${HOST}-g++")
SET(CMAKE_RC_COMPILER "${HOST}-windres")
# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH "SET THIS TO THE NDK TOOLCHAIN'S INSTALL PATH")
# here is where stuff gets installed to
SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# set env vars so that pkg-config will look in the appropriate directory for
# .pc files (as there seems to be no way to force using ${HOST}-pkg-config)
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "")
# Qt4 tools
SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake)
SET(QT_MOC_EXECUTABLE ${HOST}-moc)
SET(QT_RCC_EXECUTABLE ${HOST}-rcc)
SET(QT_UIC_EXECUTABLE ${HOST}-uic)
SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease)
-37
View File
@@ -1,37 +0,0 @@
# Cross-compiling requires CMake 2.6 or newer. Example:
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile.txt -DHOST=i686-w64-mingw32
# Where 'i686-w64-mingw32' is the host prefix for your cross-compiler. If you
# already have a toolchain file setup, you may use that instead of this file.
# the name of the target operating system
SET(CMAKE_SYSTEM_NAME Windows)
# which compilers to use for C and C++
SET(CMAKE_C_COMPILER "${HOST}-gcc")
SET(CMAKE_CXX_COMPILER "${HOST}-g++")
SET(CMAKE_RC_COMPILER "${HOST}-windres")
# here is the target environment located
SET(CMAKE_FIND_ROOT_PATH "/usr/${HOST}")
# here is where stuff gets installed to
SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
# adjust the default behaviour of the FIND_XXX() commands:
# search headers and libraries in the target environment, search
# programs in the host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# set env vars so that pkg-config will look in the appropriate directory for
# .pc files (as there seems to be no way to force using ${HOST}-pkg-config)
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
set(ENV{PKG_CONFIG_PATH} "")
# Qt4 tools
SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake)
SET(QT_MOC_EXECUTABLE ${HOST}-moc)
SET(QT_RCC_EXECUTABLE ${HOST}-rcc)
SET(QT_UIC_EXECUTABLE ${HOST}-uic)
SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease)
-796
View File
@@ -1,796 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "auxeffectslot.h"
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <memory>
#include <mutex>
#include <numeric>
#include <thread>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alu.h"
#include "effect.h"
#include "fpu_modes.h"
#include "inprogext.h"
#include "logging.h"
#include "opthelpers.h"
namespace {
inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept
{
const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f};
if UNLIKELY(lidx >= context->mEffectSlotList.size())
return nullptr;
EffectSlotSubList &sublist{context->mEffectSlotList[lidx]};
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
return nullptr;
return sublist.EffectSlots + slidx;
}
inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept
{
const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f};
if UNLIKELY(lidx >= device->EffectList.size())
return nullptr;
EffectSubList &sublist = device->EffectList[lidx];
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
return nullptr;
return sublist.Effects + slidx;
}
void AddActiveEffectSlots(const ALuint *slotids, size_t count, ALCcontext *context)
{
if(count < 1) return;
ALeffectslotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
size_t newcount{curarray->size() + count};
/* Insert the new effect slots into the head of the array, followed by the
* existing ones.
*/
ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(newcount);
auto slotiter = std::transform(slotids, slotids+count, newarray->begin(),
[context](ALuint id) noexcept -> ALeffectslot*
{ return LookupEffectSlot(context, id); }
);
std::copy(curarray->begin(), curarray->end(), slotiter);
/* Remove any duplicates (first instance of each will be kept). */
auto last = newarray->end();
for(auto start=newarray->begin()+1;;)
{
last = std::remove(start, last, *(start-1));
if(start == last) break;
++start;
}
newcount = static_cast<size_t>(std::distance(newarray->begin(), last));
/* Reallocate newarray if the new size ended up smaller from duplicate
* removal.
*/
if UNLIKELY(newcount < newarray->size())
{
curarray = newarray;
newarray = ALeffectslot::CreatePtrArray(newcount);
std::copy_n(curarray->begin(), newcount, newarray->begin());
delete curarray;
curarray = nullptr;
}
std::uninitialized_fill_n(newarray->end(), newcount, nullptr);
curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel);
ALCdevice *device{context->mDevice.get()};
while((device->MixCount.load(std::memory_order_acquire)&1))
std::this_thread::yield();
al::destroy_n(curarray->end(), curarray->size());
delete curarray;
}
void RemoveActiveEffectSlots(const ALuint *slotids, size_t count, ALCcontext *context)
{
if(count < 1) return;
ALeffectslotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)};
/* Don't shrink the allocated array size since we don't know how many (if
* any) of the effect slots to remove are in the array.
*/
ALeffectslotArray *newarray = ALeffectslot::CreatePtrArray(curarray->size());
/* Copy each element in curarray to newarray whose ID is not in slotids. */
const ALuint *slotids_end{slotids + count};
auto slotiter = std::copy_if(curarray->begin(), curarray->end(), newarray->begin(),
[slotids, slotids_end](const ALeffectslot *slot) -> bool
{ return std::find(slotids, slotids_end, slot->id) == slotids_end; }
);
/* Reallocate with the new size. */
auto newsize = static_cast<size_t>(std::distance(newarray->begin(), slotiter));
if LIKELY(newsize != newarray->size())
{
curarray = newarray;
newarray = ALeffectslot::CreatePtrArray(newsize);
std::copy_n(curarray->begin(), newsize, newarray->begin());
delete curarray;
curarray = nullptr;
}
std::uninitialized_fill_n(newarray->end(), newsize, nullptr);
curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel);
ALCdevice *device{context->mDevice.get()};
while((device->MixCount.load(std::memory_order_acquire)&1))
std::this_thread::yield();
al::destroy_n(curarray->end(), curarray->size());
delete curarray;
}
bool EnsureEffectSlots(ALCcontext *context, size_t needed)
{
size_t count{std::accumulate(context->mEffectSlotList.cbegin(),
context->mEffectSlotList.cend(), size_t{0},
[](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t
{ return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
)};
while(needed > count)
{
if UNLIKELY(context->mEffectSlotList.size() >= 1<<25)
return false;
context->mEffectSlotList.emplace_back();
auto sublist = context->mEffectSlotList.end() - 1;
sublist->FreeMask = ~0_u64;
sublist->EffectSlots = static_cast<ALeffectslot*>(
al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64));
if UNLIKELY(!sublist->EffectSlots)
{
context->mEffectSlotList.pop_back();
return false;
}
count += 64;
}
return true;
}
ALeffectslot *AllocEffectSlot(ALCcontext *context)
{
auto sublist = std::find_if(context->mEffectSlotList.begin(), context->mEffectSlotList.end(),
[](const EffectSlotSubList &entry) noexcept -> bool
{ return entry.FreeMask != 0; }
);
auto lidx = static_cast<ALuint>(std::distance(context->mEffectSlotList.begin(), sublist));
auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
ALeffectslot *slot{::new (sublist->EffectSlots + slidx) ALeffectslot{}};
if(ALenum err{InitEffectSlot(slot)})
{
al::destroy_at(slot);
context->setError(err, "Effect slot object initialization failed");
return nullptr;
}
aluInitEffectPanning(slot, context->mDevice.get());
/* Add 1 to avoid source ID 0. */
slot->id = ((lidx<<6) | slidx) + 1;
context->mNumEffectSlots += 1;
sublist->FreeMask &= ~(1_u64 << slidx);
return slot;
}
void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot)
{
const ALuint id{slot->id - 1};
const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f};
al::destroy_at(slot);
context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx;
context->mNumEffectSlots--;
}
#define DO_UPDATEPROPS() do { \
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
UpdateEffectSlotProps(slot, context.get()); \
else \
slot->PropsClean.clear(std::memory_order_release); \
} while(0)
} // namespace
ALeffectslotArray *ALeffectslot::CreatePtrArray(size_t count) noexcept
{
/* Allocate space for twice as many pointers, so the mixer has scratch
* space to store a sorted list during mixing.
*/
void *ptr{al_calloc(alignof(ALeffectslotArray), ALeffectslotArray::Sizeof(count*2))};
return new (ptr) ALeffectslotArray{count};
}
AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if UNLIKELY(n < 0)
context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n);
if UNLIKELY(n <= 0) return;
std::unique_lock<std::mutex> slotlock{context->mEffectSlotLock};
ALCdevice *device{context->mDevice.get()};
if(static_cast<ALuint>(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots)
{
context->setError(AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)",
device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n);
return;
}
if(!EnsureEffectSlots(context.get(), static_cast<ALuint>(n)))
{
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n,
(n==1) ? "" : "s");
return;
}
if(n == 1)
{
ALeffectslot *slot{AllocEffectSlot(context.get())};
if(!slot) return;
effectslots[0] = slot->id;
}
else
{
al::vector<ALuint> ids;
ALsizei count{n};
ids.reserve(static_cast<ALuint>(count));
do {
ALeffectslot *slot{AllocEffectSlot(context.get())};
if(!slot)
{
slotlock.unlock();
alDeleteAuxiliaryEffectSlots(static_cast<ALsizei>(ids.size()), ids.data());
return;
}
ids.emplace_back(slot->id);
} while(--count);
std::copy(ids.cbegin(), ids.cend(), effectslots);
}
AddActiveEffectSlots(effectslots, static_cast<ALuint>(n), context.get());
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if UNLIKELY(n < 0)
context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n);
if UNLIKELY(n <= 0) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
auto validate_slot = [&context](const ALuint id) -> bool
{
ALeffectslot *slot{LookupEffectSlot(context.get(), id)};
if UNLIKELY(!slot)
{
context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", id);
return false;
}
if UNLIKELY(ReadRef(slot->ref) != 0)
{
context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", id);
return false;
}
return true;
};
auto effectslots_end = effectslots + n;
auto bad_slot = std::find_if_not(effectslots, effectslots_end, validate_slot);
if UNLIKELY(bad_slot != effectslots_end) return;
// All effectslots are valid, remove and delete them
RemoveActiveEffectSlots(effectslots, static_cast<ALuint>(n), context.get());
auto delete_slot = [&context](const ALuint sid) -> void
{
ALeffectslot *slot{LookupEffectSlot(context.get(), sid)};
if(slot) FreeEffectSlot(context.get(), slot);
};
std::for_each(effectslots, effectslots_end, delete_slot);
}
END_API_FUNC
AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if LIKELY(context)
{
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
if(LookupEffectSlot(context.get(), effectslot) != nullptr)
return AL_TRUE;
}
return AL_FALSE;
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
ALeffectslot *target{};
ALCdevice *device{};
ALenum err{};
switch(param)
{
case AL_EFFECTSLOT_EFFECT:
device = context->mDevice.get();
{ std::lock_guard<std::mutex> ___{device->EffectLock};
ALeffect *effect{value ? LookupEffect(device, static_cast<ALuint>(value)) : nullptr};
if(!(value == 0 || effect != nullptr))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect ID %u", value);
err = InitializeEffect(context.get(), slot, effect);
}
if(err != AL_NO_ERROR)
{
context->setError(err, "Effect initialization failed");
return;
}
break;
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
if(!(value == AL_TRUE || value == AL_FALSE))
SETERR_RETURN(context, AL_INVALID_VALUE,,
"Effect slot auxiliary send auto out of range");
slot->AuxSendAuto = static_cast<ALboolean>(value);
break;
case AL_EFFECTSLOT_TARGET_SOFT:
target = LookupEffectSlot(context.get(), static_cast<ALuint>(value));
if(value && !target)
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid effect slot target ID");
if(target)
{
ALeffectslot *checker{target};
while(checker && checker != slot)
checker = checker->Target;
if(checker)
SETERR_RETURN(context, AL_INVALID_OPERATION,,
"Setting target of effect slot ID %u to %u creates circular chain", slot->id,
target->id);
}
if(ALeffectslot *oldtarget{slot->Target})
{
/* We must force an update if there was an existing effect slot
* target, in case it's about to be deleted.
*/
if(target) IncrementRef(target->ref);
DecrementRef(oldtarget->ref);
slot->Target = target;
UpdateEffectSlotProps(slot, context.get());
return;
}
if(target) IncrementRef(target->ref);
slot->Target = target;
break;
default:
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot integer property 0x%04x",
param);
}
DO_UPDATEPROPS();
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECTSLOT_EFFECT:
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
case AL_EFFECTSLOT_TARGET_SOFT:
alAuxiliaryEffectSloti(effectslot, param, values[0]);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
default:
SETERR_RETURN(context, AL_INVALID_ENUM,,
"Invalid effect slot integer-vector property 0x%04x", param);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
case AL_EFFECTSLOT_GAIN:
if(!(value >= 0.0f && value <= 1.0f))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Effect slot gain out of range");
slot->Gain = value;
break;
default:
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid effect slot float property 0x%04x",
param);
}
DO_UPDATEPROPS();
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECTSLOT_GAIN:
alAuxiliaryEffectSlotf(effectslot, param, values[0]);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
default:
SETERR_RETURN(context, AL_INVALID_ENUM,,
"Invalid effect slot float-vector property 0x%04x", param);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
*value = slot->AuxSendAuto;
break;
case AL_EFFECTSLOT_TARGET_SOFT:
if(auto *target = slot->Target)
*value = static_cast<ALint>(target->id);
else
*value = 0;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECTSLOT_EFFECT:
case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO:
case AL_EFFECTSLOT_TARGET_SOFT:
alGetAuxiliaryEffectSloti(effectslot, param, values);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x",
param);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
case AL_EFFECTSLOT_GAIN:
*value = slot->Gain;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECTSLOT_GAIN:
alGetAuxiliaryEffectSlotf(effectslot, param, values);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot);
if UNLIKELY(!slot)
SETERR_RETURN(context, AL_INVALID_NAME,, "Invalid effect slot ID %u", effectslot);
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x",
param);
}
}
END_API_FUNC
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect)
{
ALenum newtype{effect ? effect->type : AL_EFFECT_NULL};
if(newtype != EffectSlot->Effect.Type)
{
EffectStateFactory *factory{getFactoryByType(newtype)};
if(!factory)
{
ERR("Failed to find factory for effect type 0x%04x\n", newtype);
return AL_INVALID_ENUM;
}
EffectState *State{factory->create()};
if(!State) return AL_OUT_OF_MEMORY;
FPUCtl mixer_mode{};
ALCdevice *Device{Context->mDevice.get()};
std::unique_lock<std::mutex> statelock{Device->StateLock};
State->mOutTarget = Device->Dry.Buffer;
if(State->deviceUpdate(Device) == AL_FALSE)
{
statelock.unlock();
mixer_mode.leave();
State->release();
return AL_OUT_OF_MEMORY;
}
mixer_mode.leave();
if(!effect)
{
EffectSlot->Effect.Type = AL_EFFECT_NULL;
EffectSlot->Effect.Props = EffectProps {};
}
else
{
EffectSlot->Effect.Type = effect->type;
EffectSlot->Effect.Props = effect->Props;
}
EffectSlot->Effect.State->release();
EffectSlot->Effect.State = State;
}
else if(effect)
EffectSlot->Effect.Props = effect->Props;
/* Remove state references from old effect slot property updates. */
ALeffectslotProps *props{Context->mFreeEffectslotProps.load()};
while(props)
{
if(props->State)
props->State->release();
props->State = nullptr;
props = props->next.load(std::memory_order_relaxed);
}
return AL_NO_ERROR;
}
ALenum InitEffectSlot(ALeffectslot *slot)
{
EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)};
if(!factory) return AL_INVALID_VALUE;
slot->Effect.State = factory->create();
if(!slot->Effect.State) return AL_OUT_OF_MEMORY;
slot->Effect.State->add_ref();
slot->Params.mEffectState = slot->Effect.State;
return AL_NO_ERROR;
}
ALeffectslot::~ALeffectslot()
{
if(Target)
DecrementRef(Target->ref);
Target = nullptr;
ALeffectslotProps *props{Params.Update.load()};
if(props)
{
if(props->State) props->State->release();
TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n",
decltype(std::declval<void*>()){props});
delete props;
}
if(Effect.State)
Effect.State->release();
if(Params.mEffectState)
Params.mEffectState->release();
}
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context)
{
/* Get an unused property container, or allocate a new one as needed. */
ALeffectslotProps *props{context->mFreeEffectslotProps.load(std::memory_order_relaxed)};
if(!props)
props = new ALeffectslotProps{};
else
{
ALeffectslotProps *next;
do {
next = props->next.load(std::memory_order_relaxed);
} while(context->mFreeEffectslotProps.compare_exchange_weak(props, next,
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
}
/* Copy in current property values. */
props->Gain = slot->Gain;
props->AuxSendAuto = slot->AuxSendAuto;
props->Target = slot->Target;
props->Type = slot->Effect.Type;
props->Props = slot->Effect.Props;
/* Swap out any stale effect state object there may be in the container, to
* delete it.
*/
EffectState *oldstate{props->State};
slot->Effect.State->add_ref();
props->State = slot->Effect.State;
/* Set the new container for updating internal parameters. */
props = slot->Params.Update.exchange(props, std::memory_order_acq_rel);
if(props)
{
/* If there was an unused update container, put it back in the
* freelist.
*/
if(props->State)
props->State->release();
props->State = nullptr;
AtomicReplaceHead(context->mFreeEffectslotProps, props);
}
if(oldstate)
oldstate->release();
}
void UpdateAllEffectSlotProps(ALCcontext *context)
{
std::lock_guard<std::mutex> _{context->mEffectSlotLock};
ALeffectslotArray *auxslots{context->mActiveAuxSlots.load(std::memory_order_acquire)};
for(ALeffectslot *slot : *auxslots)
{
if(!slot->PropsClean.test_and_set(std::memory_order_acq_rel))
UpdateEffectSlotProps(slot, context);
}
}
EffectSlotSubList::~EffectSlotSubList()
{
uint64_t usemask{~FreeMask};
while(usemask)
{
ALsizei idx{CTZ64(usemask)};
al::destroy_at(EffectSlots+idx);
usemask &= ~(1_u64 << idx);
}
FreeMask = ~usemask;
al_free(EffectSlots);
EffectSlots = nullptr;
}
-105
View File
@@ -1,105 +0,0 @@
#ifndef AL_AUXEFFECTSLOT_H
#define AL_AUXEFFECTSLOT_H
#include <atomic>
#include <cstddef>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "alcmain.h"
#include "almalloc.h"
#include "atomic.h"
#include "effects/base.h"
#include "vector.h"
struct ALeffect;
struct ALeffectslot;
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
struct ALeffectslotProps {
ALfloat Gain;
ALboolean AuxSendAuto;
ALeffectslot *Target;
ALenum Type;
EffectProps Props;
EffectState *State;
std::atomic<ALeffectslotProps*> next;
DEF_NEWDEL(ALeffectslotProps)
};
struct ALeffectslot {
ALfloat Gain{1.0f};
ALboolean AuxSendAuto{AL_TRUE};
ALeffectslot *Target{nullptr};
struct {
ALenum Type{AL_EFFECT_NULL};
EffectProps Props{};
EffectState *State{nullptr};
} Effect;
std::atomic_flag PropsClean;
RefCount ref{0u};
struct {
std::atomic<ALeffectslotProps*> Update{nullptr};
ALfloat Gain{1.0f};
ALboolean AuxSendAuto{AL_TRUE};
ALeffectslot *Target{nullptr};
ALenum EffectType{AL_EFFECT_NULL};
EffectProps mEffectProps{};
EffectState *mEffectState{nullptr};
ALfloat RoomRolloff{0.0f}; /* Added to the source's room rolloff, not multiplied. */
ALfloat DecayTime{0.0f};
ALfloat DecayLFRatio{0.0f};
ALfloat DecayHFRatio{0.0f};
ALboolean DecayHFLimit{AL_FALSE};
ALfloat AirAbsorptionGainHF{1.0f};
} Params;
/* Self ID */
ALuint id{};
/* Mixing buffer used by the Wet mix. */
al::vector<FloatBufferLine, 16> MixBuffer;
/* Wet buffer configuration is ACN channel order with N3D scaling.
* Consequently, effects that only want to work with mono input can use
* channel 0 by itself. Effects that want multichannel can process the
* ambisonics signal and make a B-Format source pan.
*/
MixParams Wet;
ALeffectslot() { PropsClean.test_and_set(std::memory_order_relaxed); }
ALeffectslot(const ALeffectslot&) = delete;
ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot();
static ALeffectslotArray *CreatePtrArray(size_t count) noexcept;
DEF_NEWDEL(ALeffectslot)
};
ALenum InitEffectSlot(ALeffectslot *slot);
void UpdateEffectSlotProps(ALeffectslot *slot, ALCcontext *context);
void UpdateAllEffectSlotProps(ALCcontext *context);
ALenum InitializeEffect(ALCcontext *Context, ALeffectslot *EffectSlot, ALeffect *effect);
#endif
-1422
View File
File diff suppressed because it is too large Load Diff
-104
View File
@@ -1,104 +0,0 @@
#ifndef AL_BUFFER_H
#define AL_BUFFER_H
#include <atomic>
#include "AL/al.h"
#include "albyte.h"
#include "almalloc.h"
#include "atomic.h"
#include "inprogext.h"
#include "vector.h"
/* User formats */
enum UserFmtType : unsigned char {
UserFmtUByte,
UserFmtShort,
UserFmtFloat,
UserFmtDouble,
UserFmtMulaw,
UserFmtAlaw,
UserFmtIMA4,
UserFmtMSADPCM,
};
enum UserFmtChannels : unsigned char {
UserFmtMono,
UserFmtStereo,
UserFmtRear,
UserFmtQuad,
UserFmtX51, /* (WFX order) */
UserFmtX61, /* (WFX order) */
UserFmtX71, /* (WFX order) */
UserFmtBFormat2D, /* WXY */
UserFmtBFormat3D, /* WXYZ */
};
/* Storable formats */
enum FmtType : unsigned char {
FmtUByte = UserFmtUByte,
FmtShort = UserFmtShort,
FmtFloat = UserFmtFloat,
FmtDouble = UserFmtDouble,
FmtMulaw = UserFmtMulaw,
FmtAlaw = UserFmtAlaw,
};
enum FmtChannels : unsigned char {
FmtMono = UserFmtMono,
FmtStereo = UserFmtStereo,
FmtRear = UserFmtRear,
FmtQuad = UserFmtQuad,
FmtX51 = UserFmtX51,
FmtX61 = UserFmtX61,
FmtX71 = UserFmtX71,
FmtBFormat2D = UserFmtBFormat2D,
FmtBFormat3D = UserFmtBFormat3D,
};
#define MAX_INPUT_CHANNELS (8)
ALuint BytesFromFmt(FmtType type) noexcept;
ALuint ChannelsFromFmt(FmtChannels chans) noexcept;
struct ALbuffer {
al::vector<al::byte,16> mData;
ALuint Frequency{0u};
ALbitfieldSOFT Access{0u};
ALuint SampleLen{0u};
FmtChannels mFmtChannels{};
FmtType mFmtType{};
UserFmtType OriginalType{};
ALuint OriginalSize{0};
ALuint OriginalAlign{0};
ALenum AmbiLayout{AL_FUMA_SOFT};
ALenum AmbiScaling{AL_FUMA_SOFT};
ALuint LoopStart{0u};
ALuint LoopEnd{0u};
ALuint UnpackAlign{0};
ALuint PackAlign{0};
ALbitfieldSOFT MappedAccess{0u};
ALsizei MappedOffset{0};
ALsizei MappedSize{0};
/* Number of times buffer was attached to a source (deletion can only occur when 0) */
RefCount ref{0u};
/* Self ID */
ALuint id{0};
inline ALuint bytesFromFmt() const noexcept { return BytesFromFmt(mFmtType); }
inline ALuint channelsFromFmt() const noexcept { return ChannelsFromFmt(mFmtChannels); }
inline ALuint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
};
#endif
-725
View File
@@ -1,725 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "effect.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <numeric>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "AL/efx-presets.h"
#include "AL/efx.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alstring.h"
#include "effects/base.h"
#include "logging.h"
#include "opthelpers.h"
#include "vector.h"
const EffectList gEffectList[15]{
{ "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB },
{ "reverb", REVERB_EFFECT, AL_EFFECT_REVERB },
{ "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH },
{ "chorus", CHORUS_EFFECT, AL_EFFECT_CHORUS },
{ "compressor", COMPRESSOR_EFFECT, AL_EFFECT_COMPRESSOR },
{ "distortion", DISTORTION_EFFECT, AL_EFFECT_DISTORTION },
{ "echo", ECHO_EFFECT, AL_EFFECT_ECHO },
{ "equalizer", EQUALIZER_EFFECT, AL_EFFECT_EQUALIZER },
{ "flanger", FLANGER_EFFECT, AL_EFFECT_FLANGER },
{ "fshifter", FSHIFTER_EFFECT, AL_EFFECT_FREQUENCY_SHIFTER },
{ "modulator", MODULATOR_EFFECT, AL_EFFECT_RING_MODULATOR },
{ "pshifter", PSHIFTER_EFFECT, AL_EFFECT_PITCH_SHIFTER },
{ "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER },
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT },
{ "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE },
};
ALboolean DisabledEffects[MAX_EFFECTS];
namespace {
constexpr struct FactoryItem {
ALenum Type;
EffectStateFactory* (&GetFactory)(void);
} FactoryList[] = {
{ AL_EFFECT_NULL, NullStateFactory_getFactory },
{ AL_EFFECT_EAXREVERB, ReverbStateFactory_getFactory },
{ AL_EFFECT_REVERB, StdReverbStateFactory_getFactory },
{ AL_EFFECT_AUTOWAH, AutowahStateFactory_getFactory },
{ AL_EFFECT_CHORUS, ChorusStateFactory_getFactory },
{ AL_EFFECT_COMPRESSOR, CompressorStateFactory_getFactory },
{ AL_EFFECT_DISTORTION, DistortionStateFactory_getFactory },
{ AL_EFFECT_ECHO, EchoStateFactory_getFactory },
{ AL_EFFECT_EQUALIZER, EqualizerStateFactory_getFactory },
{ AL_EFFECT_FLANGER, FlangerStateFactory_getFactory },
{ AL_EFFECT_FREQUENCY_SHIFTER, FshifterStateFactory_getFactory },
{ AL_EFFECT_RING_MODULATOR, ModulatorStateFactory_getFactory },
{ AL_EFFECT_PITCH_SHIFTER, PshifterStateFactory_getFactory},
{ AL_EFFECT_VOCAL_MORPHER, VmorpherStateFactory_getFactory},
{ AL_EFFECT_DEDICATED_DIALOGUE, DedicatedStateFactory_getFactory },
{ AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedStateFactory_getFactory }
};
template<typename... T>
void ALeffect_setParami(ALeffect *effect, T&& ...args)
{ effect->vtab->setParami(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_setParamiv(ALeffect *effect, T&& ...args)
{ effect->vtab->setParamiv(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_setParamf(ALeffect *effect, T&& ...args)
{ effect->vtab->setParamf(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_setParamfv(ALeffect *effect, T&& ...args)
{ effect->vtab->setParamfv(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_getParami(const ALeffect *effect, T&& ...args)
{ effect->vtab->getParami(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_getParamiv(const ALeffect *effect, T&& ...args)
{ effect->vtab->getParamiv(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_getParamf(const ALeffect *effect, T&& ...args)
{ effect->vtab->getParamf(&effect->Props, std::forward<T>(args)...); }
template<typename... T>
void ALeffect_getParamfv(const ALeffect *effect, T&& ...args)
{ effect->vtab->getParamfv(&effect->Props, std::forward<T>(args)...); }
void InitEffectParams(ALeffect *effect, ALenum type)
{
EffectStateFactory *factory = getFactoryByType(type);
if(factory)
{
effect->Props = factory->getDefaultProps();
effect->vtab = factory->getEffectVtable();
}
else
{
effect->Props = EffectProps{};
effect->vtab = nullptr;
}
effect->type = type;
}
bool EnsureEffects(ALCdevice *device, size_t needed)
{
size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0},
[](size_t cur, const EffectSubList &sublist) noexcept -> size_t
{ return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
)};
while(needed > count)
{
if UNLIKELY(device->EffectList.size() >= 1<<25)
return false;
device->EffectList.emplace_back();
auto sublist = device->EffectList.end() - 1;
sublist->FreeMask = ~0_u64;
sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64));
if UNLIKELY(!sublist->Effects)
{
device->EffectList.pop_back();
return false;
}
count += 64;
}
return true;
}
ALeffect *AllocEffect(ALCdevice *device)
{
auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
[](const EffectSubList &entry) noexcept -> bool
{ return entry.FreeMask != 0; }
);
auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist));
auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}};
InitEffectParams(effect, AL_EFFECT_NULL);
/* Add 1 to avoid effect ID 0. */
effect->id = ((lidx<<6) | slidx) + 1;
sublist->FreeMask &= ~(1_u64 << slidx);
return effect;
}
void FreeEffect(ALCdevice *device, ALeffect *effect)
{
const ALuint id{effect->id - 1};
const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f};
al::destroy_at(effect);
device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
}
inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
{
const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f};
if UNLIKELY(lidx >= device->EffectList.size())
return nullptr;
EffectSubList &sublist = device->EffectList[lidx];
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
return nullptr;
return sublist.Effects + slidx;
}
} // namespace
AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if UNLIKELY(n < 0)
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!EnsureEffects(device, static_cast<ALuint>(n)))
{
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
return;
}
if LIKELY(n == 1)
{
/* Special handling for the easy and normal case. */
ALeffect *effect{AllocEffect(device)};
effects[0] = effect->id;
}
else
{
/* Store the allocated buffer IDs in a separate local list, to avoid
* modifying the user storage in case of failure.
*/
al::vector<ALuint> ids;
ids.reserve(static_cast<ALuint>(n));
do {
ALeffect *effect{AllocEffect(device)};
ids.emplace_back(effect->id);
} while(--n);
std::copy(ids.cbegin(), ids.cend(), effects);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if UNLIKELY(n < 0)
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
/* First try to find any effects that are invalid. */
auto validate_effect = [device](const ALuint eid) -> bool
{ return !eid || LookupEffect(device, eid) != nullptr; };
const ALuint *effects_end = effects + n;
auto inveffect = std::find_if_not(effects, effects_end, validate_effect);
if UNLIKELY(inveffect != effects_end)
{
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
return;
}
/* All good. Delete non-0 effect IDs. */
auto delete_effect = [device](ALuint eid) -> void
{
ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr};
if(effect) FreeEffect(device, effect);
};
std::for_each(effects, effects_end, delete_effect);
}
END_API_FUNC
AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if LIKELY(context)
{
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
}
return AL_FALSE;
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
if(param == AL_EFFECT_TYPE)
{
ALboolean isOk{value == AL_EFFECT_NULL};
if(!isOk)
{
for(const EffectList &effectitem : gEffectList)
{
if(value == effectitem.val && !DisabledEffects[effectitem.type])
{
isOk = AL_TRUE;
break;
}
}
}
if(isOk)
InitEffectParams(aleffect, value);
else
context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
}
else
{
/* Call the appropriate handler */
ALeffect_setParami(aleffect, context.get(), param, value);
}
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECT_TYPE:
alEffecti(effect, param, values[0]);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
/* Call the appropriate handler */
ALeffect_setParamiv(aleffect, context.get(), param, values);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
/* Call the appropriate handler */
ALeffect_setParamf(aleffect, context.get(), param, value);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
/* Call the appropriate handler */
ALeffect_setParamfv(aleffect, context.get(), param, values);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
if(param == AL_EFFECT_TYPE)
*value = aleffect->type;
else
{
/* Call the appropriate handler */
ALeffect_getParami(aleffect, context.get(), param, value);
}
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_EFFECT_TYPE:
alGetEffecti(effect, param, values);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
/* Call the appropriate handler */
ALeffect_getParamiv(aleffect, context.get(), param, values);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
/* Call the appropriate handler */
ALeffect_getParamf(aleffect, context.get(), param, value);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if UNLIKELY(!aleffect)
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else
{
/* Call the appropriate handler */
ALeffect_getParamfv(aleffect, context.get(), param, values);
}
}
END_API_FUNC
void InitEffect(ALeffect *effect)
{
InitEffectParams(effect, AL_EFFECT_NULL);
}
EffectSubList::~EffectSubList()
{
uint64_t usemask{~FreeMask};
while(usemask)
{
ALsizei idx = CTZ64(usemask);
al::destroy_at(Effects+idx);
usemask &= ~(1_u64 << idx);
}
FreeMask = ~usemask;
al_free(Effects);
Effects = nullptr;
}
EffectStateFactory *getFactoryByType(ALenum type)
{
auto iter = std::find_if(std::begin(FactoryList), std::end(FactoryList),
[type](const FactoryItem &item) noexcept -> bool
{ return item.Type == type; }
);
return (iter != std::end(FactoryList)) ? iter->GetFactory() : nullptr;
}
#define DECL(x) { #x, EFX_REVERB_PRESET_##x }
static const struct {
const char name[32];
EFXEAXREVERBPROPERTIES props;
} reverblist[] = {
DECL(GENERIC),
DECL(PADDEDCELL),
DECL(ROOM),
DECL(BATHROOM),
DECL(LIVINGROOM),
DECL(STONEROOM),
DECL(AUDITORIUM),
DECL(CONCERTHALL),
DECL(CAVE),
DECL(ARENA),
DECL(HANGAR),
DECL(CARPETEDHALLWAY),
DECL(HALLWAY),
DECL(STONECORRIDOR),
DECL(ALLEY),
DECL(FOREST),
DECL(CITY),
DECL(MOUNTAINS),
DECL(QUARRY),
DECL(PLAIN),
DECL(PARKINGLOT),
DECL(SEWERPIPE),
DECL(UNDERWATER),
DECL(DRUGGED),
DECL(DIZZY),
DECL(PSYCHOTIC),
DECL(CASTLE_SMALLROOM),
DECL(CASTLE_SHORTPASSAGE),
DECL(CASTLE_MEDIUMROOM),
DECL(CASTLE_LARGEROOM),
DECL(CASTLE_LONGPASSAGE),
DECL(CASTLE_HALL),
DECL(CASTLE_CUPBOARD),
DECL(CASTLE_COURTYARD),
DECL(CASTLE_ALCOVE),
DECL(FACTORY_SMALLROOM),
DECL(FACTORY_SHORTPASSAGE),
DECL(FACTORY_MEDIUMROOM),
DECL(FACTORY_LARGEROOM),
DECL(FACTORY_LONGPASSAGE),
DECL(FACTORY_HALL),
DECL(FACTORY_CUPBOARD),
DECL(FACTORY_COURTYARD),
DECL(FACTORY_ALCOVE),
DECL(ICEPALACE_SMALLROOM),
DECL(ICEPALACE_SHORTPASSAGE),
DECL(ICEPALACE_MEDIUMROOM),
DECL(ICEPALACE_LARGEROOM),
DECL(ICEPALACE_LONGPASSAGE),
DECL(ICEPALACE_HALL),
DECL(ICEPALACE_CUPBOARD),
DECL(ICEPALACE_COURTYARD),
DECL(ICEPALACE_ALCOVE),
DECL(SPACESTATION_SMALLROOM),
DECL(SPACESTATION_SHORTPASSAGE),
DECL(SPACESTATION_MEDIUMROOM),
DECL(SPACESTATION_LARGEROOM),
DECL(SPACESTATION_LONGPASSAGE),
DECL(SPACESTATION_HALL),
DECL(SPACESTATION_CUPBOARD),
DECL(SPACESTATION_ALCOVE),
DECL(WOODEN_SMALLROOM),
DECL(WOODEN_SHORTPASSAGE),
DECL(WOODEN_MEDIUMROOM),
DECL(WOODEN_LARGEROOM),
DECL(WOODEN_LONGPASSAGE),
DECL(WOODEN_HALL),
DECL(WOODEN_CUPBOARD),
DECL(WOODEN_COURTYARD),
DECL(WOODEN_ALCOVE),
DECL(SPORT_EMPTYSTADIUM),
DECL(SPORT_SQUASHCOURT),
DECL(SPORT_SMALLSWIMMINGPOOL),
DECL(SPORT_LARGESWIMMINGPOOL),
DECL(SPORT_GYMNASIUM),
DECL(SPORT_FULLSTADIUM),
DECL(SPORT_STADIUMTANNOY),
DECL(PREFAB_WORKSHOP),
DECL(PREFAB_SCHOOLROOM),
DECL(PREFAB_PRACTISEROOM),
DECL(PREFAB_OUTHOUSE),
DECL(PREFAB_CARAVAN),
DECL(DOME_TOMB),
DECL(PIPE_SMALL),
DECL(DOME_SAINTPAULS),
DECL(PIPE_LONGTHIN),
DECL(PIPE_LARGE),
DECL(PIPE_RESONANT),
DECL(OUTDOORS_BACKYARD),
DECL(OUTDOORS_ROLLINGPLAINS),
DECL(OUTDOORS_DEEPCANYON),
DECL(OUTDOORS_CREEK),
DECL(OUTDOORS_VALLEY),
DECL(MOOD_HEAVEN),
DECL(MOOD_HELL),
DECL(MOOD_MEMORY),
DECL(DRIVING_COMMENTATOR),
DECL(DRIVING_PITGARAGE),
DECL(DRIVING_INCAR_RACER),
DECL(DRIVING_INCAR_SPORTS),
DECL(DRIVING_INCAR_LUXURY),
DECL(DRIVING_FULLGRANDSTAND),
DECL(DRIVING_EMPTYGRANDSTAND),
DECL(DRIVING_TUNNEL),
DECL(CITY_STREETS),
DECL(CITY_SUBWAY),
DECL(CITY_MUSEUM),
DECL(CITY_LIBRARY),
DECL(CITY_UNDERPASS),
DECL(CITY_ABANDONED),
DECL(DUSTYROOM),
DECL(CHAPEL),
DECL(SMALLWATERROOM),
};
#undef DECL
void LoadReverbPreset(const char *name, ALeffect *effect)
{
if(al::strcasecmp(name, "NONE") == 0)
{
InitEffectParams(effect, AL_EFFECT_NULL);
TRACE("Loading reverb '%s'\n", "NONE");
return;
}
if(!DisabledEffects[EAXREVERB_EFFECT])
InitEffectParams(effect, AL_EFFECT_EAXREVERB);
else if(!DisabledEffects[REVERB_EFFECT])
InitEffectParams(effect, AL_EFFECT_REVERB);
else
InitEffectParams(effect, AL_EFFECT_NULL);
for(const auto &reverbitem : reverblist)
{
const EFXEAXREVERBPROPERTIES *props;
if(al::strcasecmp(name, reverbitem.name) != 0)
continue;
TRACE("Loading reverb '%s'\n", reverbitem.name);
props = &reverbitem.props;
effect->Props.Reverb.Density = props->flDensity;
effect->Props.Reverb.Diffusion = props->flDiffusion;
effect->Props.Reverb.Gain = props->flGain;
effect->Props.Reverb.GainHF = props->flGainHF;
effect->Props.Reverb.GainLF = props->flGainLF;
effect->Props.Reverb.DecayTime = props->flDecayTime;
effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio;
effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio;
effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain;
effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay;
effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0];
effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1];
effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2];
effect->Props.Reverb.LateReverbGain = props->flLateReverbGain;
effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay;
effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0];
effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1];
effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2];
effect->Props.Reverb.EchoTime = props->flEchoTime;
effect->Props.Reverb.EchoDepth = props->flEchoDepth;
effect->Props.Reverb.ModulationTime = props->flModulationTime;
effect->Props.Reverb.ModulationDepth = props->flModulationDepth;
effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF;
effect->Props.Reverb.HFReference = props->flHFReference;
effect->Props.Reverb.LFReference = props->flLFReference;
effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor;
effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE;
return;
}
WARN("Reverb preset '%s' not found\n", name);
}
-61
View File
@@ -1,61 +0,0 @@
#ifndef AL_EFFECT_H
#define AL_EFFECT_H
#include "AL/al.h"
#include "AL/efx.h"
#include "effects/base.h"
enum {
EAXREVERB_EFFECT = 0,
REVERB_EFFECT,
AUTOWAH_EFFECT,
CHORUS_EFFECT,
COMPRESSOR_EFFECT,
DISTORTION_EFFECT,
ECHO_EFFECT,
EQUALIZER_EFFECT,
FLANGER_EFFECT,
FSHIFTER_EFFECT,
MODULATOR_EFFECT,
PSHIFTER_EFFECT,
VMORPHER_EFFECT,
DEDICATED_EFFECT,
MAX_EFFECTS
};
extern ALboolean DisabledEffects[MAX_EFFECTS];
extern ALfloat ReverbBoost;
struct EffectList {
const char name[16];
int type;
ALenum val;
};
extern const EffectList gEffectList[15];
struct ALeffect {
// Effect type (AL_EFFECT_NULL, ...)
ALenum type{AL_EFFECT_NULL};
EffectProps Props{};
const EffectVtable *vtab{nullptr};
/* Self ID */
ALuint id{0u};
};
inline ALboolean IsReverbEffect(ALenum type)
{ return type == AL_EFFECT_REVERB || type == AL_EFFECT_EAXREVERB; }
EffectStateFactory *getFactoryByType(ALenum type);
void InitEffect(ALeffect *effect);
void LoadReverbPreset(const char *name, ALeffect *effect);
#endif
-117
View File
@@ -1,117 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2000 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <atomic>
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <mutex>
#include "AL/al.h"
#include "AL/alc.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "event.h"
#include "inprogext.h"
#include "logging.h"
#include "opthelpers.h"
#include "vector.h"
bool TrapALError{false};
void ALCcontext::setError(ALenum errorCode, const char *msg, ...)
{
auto message = al::vector<char>(256);
va_list args, args2;
va_start(args, msg);
va_copy(args2, args);
int msglen{std::vsnprintf(message.data(), message.size(), msg, args)};
if(msglen >= 0 && static_cast<size_t>(msglen) >= message.size())
{
message.resize(static_cast<size_t>(msglen) + 1u);
msglen = std::vsnprintf(message.data(), message.size(), msg, args2);
}
va_end(args2);
va_end(args);
if(msglen >= 0) msg = message.data();
else msg = "<internal error constructing message>";
msglen = static_cast<int>(strlen(msg));
WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
decltype(std::declval<void*>()){this}, errorCode, msg);
if(TrapALError)
{
#ifdef _WIN32
/* DebugBreak will cause an exception if there is no debugger */
if(IsDebuggerPresent())
DebugBreak();
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
ALenum curerr{AL_NO_ERROR};
mLastError.compare_exchange_strong(curerr, errorCode);
if((mEnabledEvts.load(std::memory_order_relaxed)&EventType_Error))
{
std::lock_guard<std::mutex> _{mEventCbLock};
ALbitfieldSOFT enabledevts{mEnabledEvts.load(std::memory_order_relaxed)};
if((enabledevts&EventType_Error) && mEventCb)
(*mEventCb)(AL_EVENT_TYPE_ERROR_SOFT, 0, static_cast<ALuint>(errorCode), msglen, msg,
mEventParam);
}
}
AL_API ALenum AL_APIENTRY alGetError(void)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context)
{
constexpr ALenum deferror{AL_INVALID_OPERATION};
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
if(TrapALError)
{
#ifdef _WIN32
if(IsDebuggerPresent())
DebugBreak();
#elif defined(SIGTRAP)
raise(SIGTRAP);
#endif
}
return deferror;
}
return context->mLastError.exchange(AL_NO_ERROR);
}
END_API_FUNC
-205
View File
@@ -1,205 +0,0 @@
#include "config.h"
#include "event.h"
#include <algorithm>
#include <atomic>
#include <cstring>
#include <exception>
#include <memory>
#include <mutex>
#include <new>
#include <string>
#include <thread>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "albyte.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "effects/base.h"
#include "inprogext.h"
#include "logging.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
static int EventThread(ALCcontext *context)
{
RingBuffer *ring{context->mAsyncEvents.get()};
bool quitnow{false};
while LIKELY(!quitnow)
{
auto evt_data = ring->getReadVector().first;
if(evt_data.len == 0)
{
context->mEventSem.wait();
continue;
}
std::lock_guard<std::mutex> _{context->mEventCbLock};
do {
auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf);
evt_data.buf += sizeof(AsyncEvent);
evt_data.len -= 1;
AsyncEvent evt{*evt_ptr};
al::destroy_at(evt_ptr);
ring->readAdvance(1);
quitnow = evt.EnumType == EventType_KillThread;
if UNLIKELY(quitnow) break;
if(evt.EnumType == EventType_ReleaseEffectState)
{
evt.u.mEffectState->release();
continue;
}
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)};
if(!context->mEventCb) continue;
if(evt.EnumType == EventType_SourceStateChange)
{
if(!(enabledevts&EventType_SourceStateChange))
continue;
std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
msg += " state has changed to ";
msg += (evt.u.srcstate.state==AL_INITIAL) ? "AL_INITIAL" :
(evt.u.srcstate.state==AL_PLAYING) ? "AL_PLAYING" :
(evt.u.srcstate.state==AL_PAUSED) ? "AL_PAUSED" :
(evt.u.srcstate.state==AL_STOPPED) ? "AL_STOPPED" : "<unknown>";
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
static_cast<ALuint>(evt.u.srcstate.state), static_cast<ALsizei>(msg.length()),
msg.c_str(), context->mEventParam);
}
else if(evt.EnumType == EventType_BufferCompleted)
{
if(!(enabledevts&EventType_BufferCompleted))
continue;
std::string msg{std::to_string(evt.u.bufcomp.count)};
if(evt.u.bufcomp.count == 1) msg += " buffer completed";
else msg += " buffers completed";
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id,
evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
context->mEventParam);
}
else if((enabledevts&evt.EnumType) == evt.EnumType)
context->mEventCb(evt.u.user.type, evt.u.user.id, evt.u.user.param,
static_cast<ALsizei>(strlen(evt.u.user.msg)), evt.u.user.msg,
context->mEventParam);
} while(evt_data.len != 0);
}
return 0;
}
void StartEventThrd(ALCcontext *ctx)
{
try {
ctx->mEventThread = std::thread{EventThread, ctx};
}
catch(std::exception& e) {
ERR("Failed to start event thread: %s\n", e.what());
}
catch(...) {
ERR("Failed to start event thread! Expect problems.\n");
}
}
void StopEventThrd(ALCcontext *ctx)
{
RingBuffer *ring{ctx->mAsyncEvents.get()};
auto evt_data = ring->getWriteVector().first;
if(evt_data.len == 0)
{
do {
std::this_thread::yield();
evt_data = ring->getWriteVector().first;
} while(evt_data.len == 0);
}
new (evt_data.buf) AsyncEvent{EventType_KillThread};
ring->writeAdvance(1);
ctx->mEventSem.post();
if(ctx->mEventThread.joinable())
ctx->mEventThread.join();
}
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
if(count <= 0) return;
if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
ALbitfieldSOFT flags{0};
const ALenum *types_end = types+count;
auto bad_type = std::find_if_not(types, types_end,
[&flags](ALenum type) noexcept -> bool
{
if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
flags |= EventType_BufferCompleted;
else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
flags |= EventType_SourceStateChange;
else if(type == AL_EVENT_TYPE_ERROR_SOFT)
flags |= EventType_Error;
else if(type == AL_EVENT_TYPE_PERFORMANCE_SOFT)
flags |= EventType_Performance;
else if(type == AL_EVENT_TYPE_DEPRECATED_SOFT)
flags |= EventType_Deprecated;
else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
flags |= EventType_Disconnected;
else
return false;
return true;
}
);
if(bad_type != types_end)
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type);
if(enable)
{
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts|flags,
std::memory_order_acq_rel, std::memory_order_acquire) == 0)
{
/* enabledevts is (re-)filled with the current value on failure, so
* just try again.
*/
}
}
else
{
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags,
std::memory_order_acq_rel, std::memory_order_acquire) == 0)
{
}
/* Wait to ensure the event handler sees the changed flags before
* returning.
*/
std::lock_guard<std::mutex>{context->mEventCbLock};
}
}
END_API_FUNC
AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEventCbLock};
context->mEventCb = callback;
context->mEventParam = userParam;
}
END_API_FUNC
-55
View File
@@ -1,55 +0,0 @@
#ifndef AL_EVENT_H
#define AL_EVENT_H
#include "AL/al.h"
#include "AL/alc.h"
struct EffectState;
enum {
/* End event thread processing. */
EventType_KillThread = 0,
/* User event types. */
EventType_SourceStateChange = 1<<0,
EventType_BufferCompleted = 1<<1,
EventType_Error = 1<<2,
EventType_Performance = 1<<3,
EventType_Deprecated = 1<<4,
EventType_Disconnected = 1<<5,
/* Internal events. */
EventType_ReleaseEffectState = 65536,
};
struct AsyncEvent {
unsigned int EnumType{0u};
union {
char dummy;
struct {
ALuint id;
ALenum state;
} srcstate;
struct {
ALuint id;
ALuint count;
} bufcomp;
struct {
ALenum type;
ALuint id;
ALuint param;
ALchar msg[232];
} user;
EffectState *mEffectState;
} u{};
AsyncEvent() noexcept = default;
constexpr AsyncEvent(unsigned int type) noexcept : EnumType{type} { }
};
void StartEventThrd(ALCcontext *ctx);
void StopEventThrd(ALCcontext *ctx);
#endif
-79
View File
@@ -1,79 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include "AL/al.h"
#include "AL/alc.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "alstring.h"
#include "opthelpers.h"
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return AL_FALSE;
if(!extName)
SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
size_t len{strlen(extName)};
const char *ptr{context->mExtensionList};
while(ptr && *ptr)
{
if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
return AL_TRUE;
if((ptr=strchr(ptr, ' ')) != nullptr)
{
do {
++ptr;
} while(isspace(*ptr));
}
}
return AL_FALSE;
}
END_API_FUNC
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
START_API_FUNC
{
if(!funcName) return nullptr;
return alcGetProcAddress(nullptr, funcName);
}
END_API_FUNC
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
START_API_FUNC
{
if(!enumName) return static_cast<ALenum>(0);
return alcGetEnumValue(nullptr, enumName);
}
END_API_FUNC
-650
View File
@@ -1,650 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "filter.h"
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <numeric>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "opthelpers.h"
#include "vector.h"
namespace {
#define FILTER_MIN_GAIN 0.0f
#define FILTER_MAX_GAIN 4.0f /* +12dB */
void ALlowpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
void ALlowpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
void ALlowpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_LOWPASS_GAIN:
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gain %f out of range", val);
filter->Gain = val;
break;
case AL_LOWPASS_GAINHF:
if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Low-pass gainhf %f out of range", val);
filter->GainHF = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
}
}
void ALlowpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
{ ALlowpass_setParamf(filter, context, param, vals[0]); }
void ALlowpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param); }
void ALlowpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", param); }
void ALlowpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_LOWPASS_GAIN:
*val = filter->Gain;
break;
case AL_LOWPASS_GAINHF:
*val = filter->GainHF;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param);
}
}
void ALlowpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
{ ALlowpass_getParamf(filter, context, param, vals); }
DEFINE_ALFILTER_VTABLE(ALlowpass);
void ALhighpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
void ALhighpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
void ALhighpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_HIGHPASS_GAIN:
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gain out of range");
filter->Gain = val;
break;
case AL_HIGHPASS_GAINLF:
if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "High-pass gainlf out of range");
filter->GainLF = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
}
}
void ALhighpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
{ ALhighpass_setParamf(filter, context, param, vals[0]); }
void ALhighpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param); }
void ALhighpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", param); }
void ALhighpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_HIGHPASS_GAIN:
*val = filter->Gain;
break;
case AL_HIGHPASS_GAINLF:
*val = filter->GainLF;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param);
}
}
void ALhighpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
{ ALhighpass_getParamf(filter, context, param, vals); }
DEFINE_ALFILTER_VTABLE(ALhighpass);
void ALbandpass_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
void ALbandpass_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
void ALbandpass_setParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_BANDPASS_GAIN:
if(!(val >= FILTER_MIN_GAIN && val <= FILTER_MAX_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gain out of range");
filter->Gain = val;
break;
case AL_BANDPASS_GAINHF:
if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainhf out of range");
filter->GainHF = val;
break;
case AL_BANDPASS_GAINLF:
if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Band-pass gainlf out of range");
filter->GainLF = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
}
}
void ALbandpass_setParamfv(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals)
{ ALbandpass_setParamf(filter, context, param, vals[0]); }
void ALbandpass_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param); }
void ALbandpass_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", param); }
void ALbandpass_getParamf(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_BANDPASS_GAIN:
*val = filter->Gain;
break;
case AL_BANDPASS_GAINHF:
*val = filter->GainHF;
break;
case AL_BANDPASS_GAINLF:
*val = filter->GainLF;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param);
}
}
void ALbandpass_getParamfv(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals)
{ ALbandpass_getParamf(filter, context, param, vals); }
DEFINE_ALFILTER_VTABLE(ALbandpass);
void ALnullfilter_setParami(ALfilter*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_setParamiv(ALfilter*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_setParamf(ALfilter*, ALCcontext *context, ALenum param, ALfloat)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_setParamfv(ALfilter*, ALCcontext *context, ALenum param, const ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_getParami(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_getParamiv(ALfilter*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_getParamf(ALfilter*, ALCcontext *context, ALenum param, ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
void ALnullfilter_getParamfv(ALfilter*, ALCcontext *context, ALenum param, ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param); }
DEFINE_ALFILTER_VTABLE(ALnullfilter);
void InitFilterParams(ALfilter *filter, ALenum type)
{
if(type == AL_FILTER_LOWPASS)
{
filter->Gain = AL_LOWPASS_DEFAULT_GAIN;
filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF;
filter->HFReference = LOWPASSFREQREF;
filter->GainLF = 1.0f;
filter->LFReference = HIGHPASSFREQREF;
filter->vtab = &ALlowpass_vtable;
}
else if(type == AL_FILTER_HIGHPASS)
{
filter->Gain = AL_HIGHPASS_DEFAULT_GAIN;
filter->GainHF = 1.0f;
filter->HFReference = LOWPASSFREQREF;
filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF;
filter->LFReference = HIGHPASSFREQREF;
filter->vtab = &ALhighpass_vtable;
}
else if(type == AL_FILTER_BANDPASS)
{
filter->Gain = AL_BANDPASS_DEFAULT_GAIN;
filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF;
filter->HFReference = LOWPASSFREQREF;
filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF;
filter->LFReference = HIGHPASSFREQREF;
filter->vtab = &ALbandpass_vtable;
}
else
{
filter->Gain = 1.0f;
filter->GainHF = 1.0f;
filter->HFReference = LOWPASSFREQREF;
filter->GainLF = 1.0f;
filter->LFReference = HIGHPASSFREQREF;
filter->vtab = &ALnullfilter_vtable;
}
filter->type = type;
}
bool EnsureFilters(ALCdevice *device, size_t needed)
{
size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0},
[](size_t cur, const FilterSubList &sublist) noexcept -> size_t
{ return cur + static_cast<ALuint>(POPCNT64(sublist.FreeMask)); }
)};
while(needed > count)
{
if UNLIKELY(device->FilterList.size() >= 1<<25)
return false;
device->FilterList.emplace_back();
auto sublist = device->FilterList.end() - 1;
sublist->FreeMask = ~0_u64;
sublist->Filters = static_cast<ALfilter*>(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64));
if UNLIKELY(!sublist->Filters)
{
device->FilterList.pop_back();
return false;
}
count += 64;
}
return true;
}
ALfilter *AllocFilter(ALCdevice *device)
{
auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
[](const FilterSubList &entry) noexcept -> bool
{ return entry.FreeMask != 0; }
);
auto lidx = static_cast<ALuint>(std::distance(device->FilterList.begin(), sublist));
auto slidx = static_cast<ALuint>(CTZ64(sublist->FreeMask));
ALfilter *filter{::new (sublist->Filters + slidx) ALfilter{}};
InitFilterParams(filter, AL_FILTER_NULL);
/* Add 1 to avoid filter ID 0. */
filter->id = ((lidx<<6) | slidx) + 1;
sublist->FreeMask &= ~(1_u64 << slidx);
return filter;
}
void FreeFilter(ALCdevice *device, ALfilter *filter)
{
const ALuint id{filter->id - 1};
const size_t lidx{id >> 6};
const ALuint slidx{id & 0x3f};
al::destroy_at(filter);
device->FilterList[lidx].FreeMask |= 1_u64 << slidx;
}
inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
{
const size_t lidx{(id-1) >> 6};
const ALuint slidx{(id-1) & 0x3f};
if UNLIKELY(lidx >= device->FilterList.size())
return nullptr;
FilterSubList &sublist = device->FilterList[lidx];
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
return nullptr;
return sublist.Filters + slidx;
}
} // namespace
AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if UNLIKELY(n < 0)
context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!EnsureFilters(device, static_cast<ALuint>(n)))
{
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s");
return;
}
if LIKELY(n == 1)
{
/* Special handling for the easy and normal case. */
ALfilter *filter{AllocFilter(device)};
if(filter) filters[0] = filter->id;
}
else
{
/* Store the allocated buffer IDs in a separate local list, to avoid
* modifying the user storage in case of failure.
*/
al::vector<ALuint> ids;
ids.reserve(static_cast<ALuint>(n));
do {
ALfilter *filter{AllocFilter(device)};
ids.emplace_back(filter->id);
} while(--n);
std::copy(ids.begin(), ids.end(), filters);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if UNLIKELY(n < 0)
context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
if UNLIKELY(n <= 0) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
/* First try to find any filters that are invalid. */
auto validate_filter = [device](const ALuint fid) -> bool
{ return !fid || LookupFilter(device, fid) != nullptr; };
const ALuint *filters_end = filters + n;
auto invflt = std::find_if_not(filters, filters_end, validate_filter);
if UNLIKELY(invflt != filters_end)
{
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt);
return;
}
/* All good. Delete non-0 filter IDs. */
auto delete_filter = [device](const ALuint fid) -> void
{
ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr};
if(filter) FreeFilter(device, filter);
};
std::for_each(filters, filters_end, delete_filter);
}
END_API_FUNC
AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if LIKELY(context)
{
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
if(!filter || LookupFilter(device, filter))
return AL_TRUE;
}
return AL_FALSE;
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
if(param == AL_FILTER_TYPE)
{
if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS ||
value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
InitFilterParams(alfilt, value);
else
context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
}
else
{
/* Call the appropriate handler */
ALfilter_setParami(alfilt, context.get(), param, value);
}
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_FILTER_TYPE:
alFilteri(filter, param, values[0]);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
/* Call the appropriate handler */
ALfilter_setParamiv(alfilt, context.get(), param, values);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
/* Call the appropriate handler */
ALfilter_setParamf(alfilt, context.get(), param, value);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
/* Call the appropriate handler */
ALfilter_setParamfv(alfilt, context.get(), param, values);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
if(param == AL_FILTER_TYPE)
*value = alfilt->type;
else
{
/* Call the appropriate handler */
ALfilter_getParami(alfilt, context.get(), param, value);
}
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values)
START_API_FUNC
{
switch(param)
{
case AL_FILTER_TYPE:
alGetFilteri(filter, param, values);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
/* Call the appropriate handler */
ALfilter_getParamiv(alfilt, context.get(), param, values);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
/* Call the appropriate handler */
ALfilter_getParamf(alfilt, context.get(), param, value);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALCdevice *device{context->mDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if UNLIKELY(!alfilt)
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
/* Call the appropriate handler */
ALfilter_getParamfv(alfilt, context.get(), param, values);
}
}
END_API_FUNC
FilterSubList::~FilterSubList()
{
uint64_t usemask{~FreeMask};
while(usemask)
{
ALsizei idx = CTZ64(usemask);
al::destroy_at(Filters+idx);
usemask &= ~(1_u64 << idx);
}
FreeMask = ~usemask;
al_free(Filters);
Filters = nullptr;
}
-56
View File
@@ -1,56 +0,0 @@
#ifndef AL_FILTER_H
#define AL_FILTER_H
#include "AL/al.h"
#include "AL/alc.h"
#define LOWPASSFREQREF (5000.0f)
#define HIGHPASSFREQREF (250.0f)
struct ALfilter;
struct ALfilterVtable {
void (*const setParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint val);
void (*const setParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALint *vals);
void (*const setParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat val);
void (*const setParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, const ALfloat *vals);
void (*const getParami)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *val);
void (*const getParamiv)(ALfilter *filter, ALCcontext *context, ALenum param, ALint *vals);
void (*const getParamf)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *val);
void (*const getParamfv)(ALfilter *filter, ALCcontext *context, ALenum param, ALfloat *vals);
};
#define DEFINE_ALFILTER_VTABLE(T) \
const ALfilterVtable T##_vtable = { \
T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \
T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \
}
struct ALfilter {
// Filter type (AL_FILTER_NULL, ...)
ALenum type;
ALfloat Gain;
ALfloat GainHF;
ALfloat HFReference;
ALfloat GainLF;
ALfloat LFReference;
const ALfilterVtable *vtab;
/* Self ID */
ALuint id;
};
#define ALfilter_setParami(o, c, p, v) ((o)->vtab->setParami(o, c, p, v))
#define ALfilter_setParamf(o, c, p, v) ((o)->vtab->setParamf(o, c, p, v))
#define ALfilter_setParamiv(o, c, p, v) ((o)->vtab->setParamiv(o, c, p, v))
#define ALfilter_setParamfv(o, c, p, v) ((o)->vtab->setParamfv(o, c, p, v))
#define ALfilter_getParami(o, c, p, v) ((o)->vtab->getParami(o, c, p, v))
#define ALfilter_getParamf(o, c, p, v) ((o)->vtab->getParamf(o, c, p, v))
#define ALfilter_getParamiv(o, c, p, v) ((o)->vtab->getParamiv(o, c, p, v))
#define ALfilter_getParamfv(o, c, p, v) ((o)->vtab->getParamfv(o, c, p, v))
#endif
-452
View File
@@ -1,452 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2000 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "listener.h"
#include <cmath>
#include <mutex>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "atomic.h"
#include "opthelpers.h"
#define DO_UPDATEPROPS() do { \
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
UpdateListenerProps(context.get()); \
else \
listener.PropsClean.clear(std::memory_order_release); \
} while(0)
AL_API ALvoid AL_APIENTRY alListenerf(ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(param)
{
case AL_GAIN:
if(!(value >= 0.0f && std::isfinite(value)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
listener.Gain = value;
DO_UPDATEPROPS();
break;
case AL_METERS_PER_UNIT:
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
listener.mMetersPerUnit = value;
DO_UPDATEPROPS();
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(param)
{
case AL_POSITION:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range");
listener.Position[0] = value1;
listener.Position[1] = value2;
listener.Position[2] = value3;
DO_UPDATEPROPS();
break;
case AL_VELOCITY:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range");
listener.Velocity[0] = value1;
listener.Velocity[1] = value2;
listener.Velocity[2] = value3;
DO_UPDATEPROPS();
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values)
START_API_FUNC
{
if(values)
{
switch(param)
{
case AL_GAIN:
case AL_METERS_PER_UNIT:
alListenerf(param, values[0]);
return;
case AL_POSITION:
case AL_VELOCITY:
alListener3f(param, values[0], values[1], values[2]);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
switch(param)
{
case AL_ORIENTATION:
if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) &&
std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range");
/* AT then UP */
listener.OrientAt[0] = values[0];
listener.OrientAt[1] = values[1];
listener.OrientAt[2] = values[2];
listener.OrientUp[0] = values[3];
listener.OrientUp[1] = values[4];
listener.OrientUp[2] = values[5];
DO_UPDATEPROPS();
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
START_API_FUNC
{
switch(param)
{
case AL_POSITION:
case AL_VELOCITY:
alListener3f(param, static_cast<ALfloat>(value1), static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
START_API_FUNC
{
if(values)
{
ALfloat fvals[6];
switch(param)
{
case AL_POSITION:
case AL_VELOCITY:
alListener3f(param, static_cast<ALfloat>(values[0]), static_cast<ALfloat>(values[1]), static_cast<ALfloat>(values[2]));
return;
case AL_ORIENTATION:
fvals[0] = static_cast<ALfloat>(values[0]);
fvals[1] = static_cast<ALfloat>(values[1]);
fvals[2] = static_cast<ALfloat>(values[2]);
fvals[3] = static_cast<ALfloat>(values[3]);
fvals[4] = static_cast<ALfloat>(values[4]);
fvals[5] = static_cast<ALfloat>(values[5]);
alListenerfv(param, fvals);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!value)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
case AL_GAIN:
*value = listener.Gain;
break;
case AL_METERS_PER_UNIT:
*value = listener.mMetersPerUnit;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!value1 || !value2 || !value3)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
case AL_POSITION:
*value1 = listener.Position[0];
*value2 = listener.Position[1];
*value3 = listener.Position[2];
break;
case AL_VELOCITY:
*value1 = listener.Velocity[0];
*value2 = listener.Velocity[1];
*value3 = listener.Velocity[2];
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values)
START_API_FUNC
{
switch(param)
{
case AL_GAIN:
case AL_METERS_PER_UNIT:
alGetListenerf(param, values);
return;
case AL_POSITION:
case AL_VELOCITY:
alGetListener3f(param, values+0, values+1, values+2);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
case AL_ORIENTATION:
// AT then UP
values[0] = listener.OrientAt[0];
values[1] = listener.OrientAt[1];
values[2] = listener.OrientAt[2];
values[3] = listener.OrientUp[0];
values[4] = listener.OrientUp[1];
values[5] = listener.OrientUp[2];
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!value)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!value1 || !value2 || !value3)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
case AL_POSITION:
*value1 = static_cast<ALint>(listener.Position[0]);
*value2 = static_cast<ALint>(listener.Position[1]);
*value3 = static_cast<ALint>(listener.Position[2]);
break;
case AL_VELOCITY:
*value1 = static_cast<ALint>(listener.Velocity[0]);
*value2 = static_cast<ALint>(listener.Velocity[1]);
*value3 = static_cast<ALint>(listener.Velocity[2]);
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
START_API_FUNC
{
switch(param)
{
case AL_POSITION:
case AL_VELOCITY:
alGetListener3i(param, values+0, values+1, values+2);
return;
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
{
case AL_ORIENTATION:
// AT then UP
values[0] = static_cast<ALint>(listener.OrientAt[0]);
values[1] = static_cast<ALint>(listener.OrientAt[1]);
values[2] = static_cast<ALint>(listener.OrientAt[2]);
values[3] = static_cast<ALint>(listener.OrientUp[0]);
values[4] = static_cast<ALint>(listener.OrientUp[1]);
values[5] = static_cast<ALint>(listener.OrientUp[2]);
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
}
}
END_API_FUNC
void UpdateListenerProps(ALCcontext *context)
{
/* Get an unused proprty container, or allocate a new one as needed. */
ALlistenerProps *props{context->mFreeListenerProps.load(std::memory_order_acquire)};
if(!props)
props = new ALlistenerProps{};
else
{
ALlistenerProps *next;
do {
next = props->next.load(std::memory_order_relaxed);
} while(context->mFreeListenerProps.compare_exchange_weak(props, next,
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
}
/* Copy in current property values. */
ALlistener &listener = context->mListener;
props->Position = listener.Position;
props->Velocity = listener.Velocity;
props->OrientAt = listener.OrientAt;
props->OrientUp = listener.OrientUp;
props->Gain = listener.Gain;
props->MetersPerUnit = listener.mMetersPerUnit;
/* Set the new container for updating internal parameters. */
props = listener.Params.Update.exchange(props, std::memory_order_acq_rel);
if(props)
{
/* If there was an unused update container, put it back in the
* freelist.
*/
AtomicReplaceHead(context->mFreeListenerProps, props);
}
}
-64
View File
@@ -1,64 +0,0 @@
#ifndef AL_LISTENER_H
#define AL_LISTENER_H
#include <array>
#include <atomic>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "almalloc.h"
#include "vecmat.h"
enum class DistanceModel;
struct ALlistenerProps {
std::array<ALfloat,3> Position;
std::array<ALfloat,3> Velocity;
std::array<ALfloat,3> OrientAt;
std::array<ALfloat,3> OrientUp;
ALfloat Gain;
ALfloat MetersPerUnit;
std::atomic<ALlistenerProps*> next;
DEF_NEWDEL(ALlistenerProps)
};
struct ALlistener {
std::array<ALfloat,3> Position{{0.0f, 0.0f, 0.0f}};
std::array<ALfloat,3> Velocity{{0.0f, 0.0f, 0.0f}};
std::array<ALfloat,3> OrientAt{{0.0f, 0.0f, -1.0f}};
std::array<ALfloat,3> OrientUp{{0.0f, 1.0f, 0.0f}};
ALfloat Gain{1.0f};
ALfloat mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
std::atomic_flag PropsClean;
struct {
/* Pointer to the most recent property values that are awaiting an
* update.
*/
std::atomic<ALlistenerProps*> Update{nullptr};
alu::Matrix Matrix;
alu::Vector Velocity;
ALfloat Gain;
ALfloat MetersPerUnit;
ALfloat DopplerFactor;
ALfloat SpeedOfSound; /* in units per sec! */
ALboolean SourceDistanceModel;
DistanceModel mDistanceModel;
} Params;
ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); }
};
void UpdateListenerProps(ALCcontext *context);
#endif
-3345
View File
File diff suppressed because it is too large Load Diff
-129
View File
@@ -1,129 +0,0 @@
#ifndef AL_SOURCE_H
#define AL_SOURCE_H
#include <array>
#include <atomic>
#include <cstddef>
#include <iterator>
#include "AL/al.h"
#include "AL/alc.h"
#include "alcontext.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alu.h"
#include "vector.h"
struct ALbuffer;
struct ALeffectslot;
#define DEFAULT_SENDS 2
#define INVALID_VOICE_IDX static_cast<ALuint>(-1)
struct ALbufferlistitem {
std::atomic<ALbufferlistitem*> mNext{nullptr};
ALuint mSampleLen{0u};
ALbuffer *mBuffer{nullptr};
DEF_NEWDEL(ALbufferlistitem)
};
struct ALsource {
/** Source properties. */
ALfloat Pitch;
ALfloat Gain;
ALfloat OuterGain;
ALfloat MinGain;
ALfloat MaxGain;
ALfloat InnerAngle;
ALfloat OuterAngle;
ALfloat RefDistance;
ALfloat MaxDistance;
ALfloat RolloffFactor;
std::array<ALfloat,3> Position;
std::array<ALfloat,3> Velocity;
std::array<ALfloat,3> Direction;
std::array<ALfloat,3> OrientAt;
std::array<ALfloat,3> OrientUp;
bool HeadRelative;
bool Looping;
DistanceModel mDistanceModel;
Resampler mResampler;
DirectMode DirectChannels;
SpatializeMode mSpatialize;
bool DryGainHFAuto;
bool WetGainAuto;
bool WetGainHFAuto;
ALfloat OuterGainHF;
ALfloat AirAbsorptionFactor;
ALfloat RoomRolloffFactor;
ALfloat DopplerFactor;
/* NOTE: Stereo pan angles are specified in radians, counter-clockwise
* rather than clockwise.
*/
std::array<ALfloat,2> StereoPan;
ALfloat Radius;
/** Direct filter and auxiliary send info. */
struct {
ALfloat Gain;
ALfloat GainHF;
ALfloat HFReference;
ALfloat GainLF;
ALfloat LFReference;
} Direct;
struct SendData {
ALeffectslot *Slot;
ALfloat Gain;
ALfloat GainHF;
ALfloat HFReference;
ALfloat GainLF;
ALfloat LFReference;
};
al::vector<SendData> Send;
/**
* Last user-specified offset, and the offset type (bytes, samples, or
* seconds).
*/
ALdouble Offset{0.0};
ALenum OffsetType{AL_NONE};
/** Source type (static, streaming, or undetermined) */
ALenum SourceType{AL_UNDETERMINED};
/** Source state (initial, playing, paused, or stopped) */
ALenum state{AL_INITIAL};
/** Source Buffer Queue head. */
ALbufferlistitem *queue{nullptr};
std::atomic_flag PropsClean;
/* Index into the context's Voices array. Lazily updated, only checked and
* reset when looking up the voice.
*/
ALuint VoiceIdx{INVALID_VOICE_IDX};
/** Self ID */
ALuint id{0};
ALsource(ALuint num_sends);
~ALsource();
ALsource(const ALsource&) = delete;
ALsource& operator=(const ALsource&) = delete;
};
void UpdateAllSourceProps(ALCcontext *context);
#endif
-880
View File
@@ -1,880 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2000 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "version.h"
#include <atomic>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alcontext.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alu.h"
#include "atomic.h"
#include "event.h"
#include "inprogext.h"
#include "opthelpers.h"
#include "strutils.h"
namespace {
constexpr ALchar alVendor[] = "OpenAL Community";
constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION;
constexpr ALchar alRenderer[] = "OpenAL Soft";
// Error Messages
constexpr ALchar alNoError[] = "No Error";
constexpr ALchar alErrInvalidName[] = "Invalid Name";
constexpr ALchar alErrInvalidEnum[] = "Invalid Enum";
constexpr ALchar alErrInvalidValue[] = "Invalid Value";
constexpr ALchar alErrInvalidOp[] = "Invalid Operation";
constexpr ALchar alErrOutOfMemory[] = "Out of Memory";
/* Resampler strings */
template<Resampler rtype> struct ResamplerName { };
template<> struct ResamplerName<Resampler::Point>
{ static constexpr const ALchar *Get() noexcept { return "Nearest"; } };
template<> struct ResamplerName<Resampler::Linear>
{ static constexpr const ALchar *Get() noexcept { return "Linear"; } };
template<> struct ResamplerName<Resampler::Cubic>
{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } };
template<> struct ResamplerName<Resampler::FastBSinc12>
{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } };
template<> struct ResamplerName<Resampler::BSinc12>
{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc"; } };
template<> struct ResamplerName<Resampler::FastBSinc24>
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } };
template<> struct ResamplerName<Resampler::BSinc24>
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } };
const ALchar *GetResamplerName(const Resampler rtype)
{
#define HANDLE_RESAMPLER(r) case r: return ResamplerName<r>::Get()
switch(rtype)
{
HANDLE_RESAMPLER(Resampler::Point);
HANDLE_RESAMPLER(Resampler::Linear);
HANDLE_RESAMPLER(Resampler::Cubic);
HANDLE_RESAMPLER(Resampler::FastBSinc12);
HANDLE_RESAMPLER(Resampler::BSinc12);
HANDLE_RESAMPLER(Resampler::FastBSinc24);
HANDLE_RESAMPLER(Resampler::BSinc24);
}
#undef HANDLE_RESAMPLER
/* Should never get here. */
throw std::runtime_error{"Unexpected resampler index"};
}
} // namespace
/* WARNING: Non-standard export! Not part of any extension, or exposed in the
* alcFunctions list.
*/
extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
START_API_FUNC
{
static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION");
if(spoof) return spoof->c_str();
return ALSOFT_VERSION;
}
END_API_FUNC
#define DO_UPDATEPROPS() do { \
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
UpdateContextProps(context.get()); \
else \
context->mPropsClean.clear(std::memory_order_release); \
} while(0)
AL_API ALvoid AL_APIENTRY alEnable(ALenum capability)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(capability)
{
case AL_SOURCE_DISTANCE_MODEL:
context->mSourceDistanceModel = AL_TRUE;
DO_UPDATEPROPS();
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDisable(ALenum capability)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(capability)
{
case AL_SOURCE_DISTANCE_MODEL:
context->mSourceDistanceModel = AL_FALSE;
DO_UPDATEPROPS();
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability);
}
}
END_API_FUNC
AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return AL_FALSE;
std::lock_guard<std::mutex> _{context->mPropLock};
ALboolean value{AL_FALSE};
switch(capability)
{
case AL_SOURCE_DISTANCE_MODEL:
value = context->mSourceDistanceModel;
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability);
}
return value;
}
END_API_FUNC
AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return AL_FALSE;
std::lock_guard<std::mutex> _{context->mPropLock};
ALboolean value{AL_FALSE};
switch(pname)
{
case AL_DOPPLER_FACTOR:
if(context->mDopplerFactor != 0.0f)
value = AL_TRUE;
break;
case AL_DOPPLER_VELOCITY:
if(context->mDopplerVelocity != 0.0f)
value = AL_TRUE;
break;
case AL_DISTANCE_MODEL:
if(context->mDistanceModel == DistanceModel::Default)
value = AL_TRUE;
break;
case AL_SPEED_OF_SOUND:
if(context->mSpeedOfSound != 0.0f)
value = AL_TRUE;
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
value = AL_TRUE;
break;
case AL_GAIN_LIMIT_SOFT:
if(GAIN_MIX_MAX/context->mGainBoost != 0.0f)
value = AL_TRUE;
break;
case AL_NUM_RESAMPLERS_SOFT:
/* Always non-0. */
value = AL_TRUE;
break;
case AL_DEFAULT_RESAMPLER_SOFT:
value = static_cast<int>(ResamplerDefault) ? AL_TRUE : AL_FALSE;
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return 0.0;
std::lock_guard<std::mutex> _{context->mPropLock};
ALdouble value{0.0};
switch(pname)
{
case AL_DOPPLER_FACTOR:
value = context->mDopplerFactor;
break;
case AL_DOPPLER_VELOCITY:
value = context->mDopplerVelocity;
break;
case AL_DISTANCE_MODEL:
value = static_cast<ALdouble>(context->mDistanceModel);
break;
case AL_SPEED_OF_SOUND:
value = context->mSpeedOfSound;
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
value = static_cast<ALdouble>(AL_TRUE);
break;
case AL_GAIN_LIMIT_SOFT:
value = ALdouble{GAIN_MIX_MAX}/context->mGainBoost;
break;
case AL_NUM_RESAMPLERS_SOFT:
value = static_cast<ALdouble>(Resampler::Max) + 1.0;
break;
case AL_DEFAULT_RESAMPLER_SOFT:
value = static_cast<ALdouble>(ResamplerDefault);
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid double property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return 0.0f;
std::lock_guard<std::mutex> _{context->mPropLock};
ALfloat value{0.0f};
switch(pname)
{
case AL_DOPPLER_FACTOR:
value = context->mDopplerFactor;
break;
case AL_DOPPLER_VELOCITY:
value = context->mDopplerVelocity;
break;
case AL_DISTANCE_MODEL:
value = static_cast<ALfloat>(context->mDistanceModel);
break;
case AL_SPEED_OF_SOUND:
value = context->mSpeedOfSound;
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
value = static_cast<ALfloat>(AL_TRUE);
break;
case AL_GAIN_LIMIT_SOFT:
value = GAIN_MIX_MAX/context->mGainBoost;
break;
case AL_NUM_RESAMPLERS_SOFT:
value = static_cast<ALfloat>(Resampler::Max) + 1.0f;
break;
case AL_DEFAULT_RESAMPLER_SOFT:
value = static_cast<ALfloat>(ResamplerDefault);
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid float property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API ALint AL_APIENTRY alGetInteger(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return 0;
std::lock_guard<std::mutex> _{context->mPropLock};
ALint value{0};
switch(pname)
{
case AL_DOPPLER_FACTOR:
value = static_cast<ALint>(context->mDopplerFactor);
break;
case AL_DOPPLER_VELOCITY:
value = static_cast<ALint>(context->mDopplerVelocity);
break;
case AL_DISTANCE_MODEL:
value = static_cast<ALint>(context->mDistanceModel);
break;
case AL_SPEED_OF_SOUND:
value = static_cast<ALint>(context->mSpeedOfSound);
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
value = AL_TRUE;
break;
case AL_GAIN_LIMIT_SOFT:
value = static_cast<ALint>(GAIN_MIX_MAX/context->mGainBoost);
break;
case AL_NUM_RESAMPLERS_SOFT:
value = static_cast<int>(Resampler::Max) + 1;
break;
case AL_DEFAULT_RESAMPLER_SOFT:
value = static_cast<int>(ResamplerDefault);
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
}
return value;
}
END_API_FUNC
extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return 0_i64;
std::lock_guard<std::mutex> _{context->mPropLock};
ALint64SOFT value{0};
switch(pname)
{
case AL_DOPPLER_FACTOR:
value = static_cast<ALint64SOFT>(context->mDopplerFactor);
break;
case AL_DOPPLER_VELOCITY:
value = static_cast<ALint64SOFT>(context->mDopplerVelocity);
break;
case AL_DISTANCE_MODEL:
value = static_cast<ALint64SOFT>(context->mDistanceModel);
break;
case AL_SPEED_OF_SOUND:
value = static_cast<ALint64SOFT>(context->mSpeedOfSound);
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates.load(std::memory_order_acquire))
value = AL_TRUE;
break;
case AL_GAIN_LIMIT_SOFT:
value = static_cast<ALint64SOFT>(GAIN_MIX_MAX/context->mGainBoost);
break;
case AL_NUM_RESAMPLERS_SOFT:
value = static_cast<ALint64SOFT>(Resampler::Max) + 1;
break;
case AL_DEFAULT_RESAMPLER_SOFT:
value = static_cast<ALint64SOFT>(ResamplerDefault);
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return nullptr;
std::lock_guard<std::mutex> _{context->mPropLock};
void *value{nullptr};
switch(pname)
{
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
value = reinterpret_cast<void*>(context->mEventCb);
break;
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
value = context->mEventParam;
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values)
START_API_FUNC
{
if(values)
{
switch(pname)
{
case AL_DOPPLER_FACTOR:
case AL_DOPPLER_VELOCITY:
case AL_DISTANCE_MODEL:
case AL_SPEED_OF_SOUND:
case AL_DEFERRED_UPDATES_SOFT:
case AL_GAIN_LIMIT_SOFT:
case AL_NUM_RESAMPLERS_SOFT:
case AL_DEFAULT_RESAMPLER_SOFT:
values[0] = alGetBoolean(pname);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(pname)
{
default:
context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values)
START_API_FUNC
{
if(values)
{
switch(pname)
{
case AL_DOPPLER_FACTOR:
case AL_DOPPLER_VELOCITY:
case AL_DISTANCE_MODEL:
case AL_SPEED_OF_SOUND:
case AL_DEFERRED_UPDATES_SOFT:
case AL_GAIN_LIMIT_SOFT:
case AL_NUM_RESAMPLERS_SOFT:
case AL_DEFAULT_RESAMPLER_SOFT:
values[0] = alGetDouble(pname);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(pname)
{
default:
context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values)
START_API_FUNC
{
if(values)
{
switch(pname)
{
case AL_DOPPLER_FACTOR:
case AL_DOPPLER_VELOCITY:
case AL_DISTANCE_MODEL:
case AL_SPEED_OF_SOUND:
case AL_DEFERRED_UPDATES_SOFT:
case AL_GAIN_LIMIT_SOFT:
case AL_NUM_RESAMPLERS_SOFT:
case AL_DEFAULT_RESAMPLER_SOFT:
values[0] = alGetFloat(pname);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(pname)
{
default:
context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname);
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values)
START_API_FUNC
{
if(values)
{
switch(pname)
{
case AL_DOPPLER_FACTOR:
case AL_DOPPLER_VELOCITY:
case AL_DISTANCE_MODEL:
case AL_SPEED_OF_SOUND:
case AL_DEFERRED_UPDATES_SOFT:
case AL_GAIN_LIMIT_SOFT:
case AL_NUM_RESAMPLERS_SOFT:
case AL_DEFAULT_RESAMPLER_SOFT:
values[0] = alGetInteger(pname);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(pname)
{
default:
context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname);
}
}
END_API_FUNC
extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
START_API_FUNC
{
if(values)
{
switch(pname)
{
case AL_DOPPLER_FACTOR:
case AL_DOPPLER_VELOCITY:
case AL_DISTANCE_MODEL:
case AL_SPEED_OF_SOUND:
case AL_DEFERRED_UPDATES_SOFT:
case AL_GAIN_LIMIT_SOFT:
case AL_NUM_RESAMPLERS_SOFT:
case AL_DEFAULT_RESAMPLER_SOFT:
values[0] = alGetInteger64SOFT(pname);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(pname)
{
default:
context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname);
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values)
START_API_FUNC
{
if(values)
{
switch(pname)
{
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
values[0] = alGetPointerSOFT(pname);
return;
}
}
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(pname)
{
default:
context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname);
}
}
END_API_FUNC
AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return nullptr;
const ALchar *value{nullptr};
switch(pname)
{
case AL_VENDOR:
value = alVendor;
break;
case AL_VERSION:
value = alVersion;
break;
case AL_RENDERER:
value = alRenderer;
break;
case AL_EXTENSIONS:
value = context->mExtensionList;
break;
case AL_NO_ERROR:
value = alNoError;
break;
case AL_INVALID_NAME:
value = alErrInvalidName;
break;
case AL_INVALID_ENUM:
value = alErrInvalidEnum;
break;
case AL_INVALID_VALUE:
value = alErrInvalidValue;
break;
case AL_INVALID_OPERATION:
value = alErrInvalidOp;
break;
case AL_OUT_OF_MEMORY:
value = alErrOutOfMemory;
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDopplerFactor(ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!(value >= 0.0f && std::isfinite(value)))
context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value);
else
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mDopplerFactor = value;
DO_UPDATEPROPS();
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDopplerVelocity(ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if((context->mEnabledEvts.load(std::memory_order_relaxed)&EventType_Deprecated))
{
std::lock_guard<std::mutex> _{context->mEventCbLock};
ALbitfieldSOFT enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
if((enabledevts&EventType_Deprecated) && context->mEventCb)
{
static const char msg[] =
"alDopplerVelocity is deprecated in AL1.1, use alSpeedOfSound";
const ALsizei msglen{sizeof(msg)-1};
(*context->mEventCb)(AL_EVENT_TYPE_DEPRECATED_SOFT, 0, 0, msglen, msg,
context->mEventParam);
}
}
if(!(value >= 0.0f && std::isfinite(value)))
context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value);
else
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mDopplerVelocity = value;
DO_UPDATEPROPS();
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alSpeedOfSound(ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!(value > 0.0f && std::isfinite(value)))
context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value);
else
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mSpeedOfSound = value;
DO_UPDATEPROPS();
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDistanceModel(ALenum value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
if(!(value == AL_INVERSE_DISTANCE || value == AL_INVERSE_DISTANCE_CLAMPED ||
value == AL_LINEAR_DISTANCE || value == AL_LINEAR_DISTANCE_CLAMPED ||
value == AL_EXPONENT_DISTANCE || value == AL_EXPONENT_DISTANCE_CLAMPED ||
value == AL_NONE))
context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value);
else
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mDistanceModel = static_cast<DistanceModel>(value);
if(!context->mSourceDistanceModel)
DO_UPDATEPROPS();
}
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alDeferUpdatesSOFT(void)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
context->deferUpdates();
}
END_API_FUNC
AL_API ALvoid AL_APIENTRY alProcessUpdatesSOFT(void)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return;
context->processUpdates();
}
END_API_FUNC
AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if UNLIKELY(!context) return nullptr;
const ALchar *value{nullptr};
switch(pname)
{
case AL_RESAMPLER_NAME_SOFT:
if(index < 0 || index > static_cast<ALint>(Resampler::Max))
context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index);
else
value = GetResamplerName(static_cast<Resampler>(index));
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid string indexed property");
}
return value;
}
END_API_FUNC
void UpdateContextProps(ALCcontext *context)
{
/* Get an unused proprty container, or allocate a new one as needed. */
ALcontextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)};
if(!props)
props = new ALcontextProps{};
else
{
ALcontextProps *next;
do {
next = props->next.load(std::memory_order_relaxed);
} while(context->mFreeContextProps.compare_exchange_weak(props, next,
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
}
/* Copy in current property values. */
props->DopplerFactor = context->mDopplerFactor;
props->DopplerVelocity = context->mDopplerVelocity;
props->SpeedOfSound = context->mSpeedOfSound;
props->SourceDistanceModel = context->mSourceDistanceModel;
props->mDistanceModel = context->mDistanceModel;
/* Set the new container for updating internal parameters. */
props = context->mUpdate.exchange(props, std::memory_order_acq_rel);
if(props)
{
/* If there was an unused update container, put it back in the
* freelist.
*/
AtomicReplaceHead(context->mFreeContextProps, props);
}
}
-4300
View File
File diff suppressed because it is too large Load Diff
-399
View File
@@ -1,399 +0,0 @@
#ifndef ALC_MAIN_H
#define ALC_MAIN_H
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstddef>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "albyte.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "ambidefs.h"
#include "atomic.h"
#include "devformat.h"
#include "filters/splitter.h"
#include "hrtf.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#include "vector.h"
class BFormatDec;
struct ALbuffer;
struct ALeffect;
struct ALfilter;
struct BackendBase;
struct Compressor;
struct EffectState;
struct Uhj2Encoder;
struct bs2b;
#define MIN_OUTPUT_RATE 8000
#define DEFAULT_OUTPUT_RATE 44100
#define DEFAULT_UPDATE_SIZE 882 /* 20ms */
#define DEFAULT_NUM_UPDATES 3
enum DeviceType {
Playback,
Capture,
Loopback
};
enum RenderMode {
NormalRender,
StereoPair,
HrtfRender
};
struct InputRemixMap {
struct TargetMix { Channel channel; float mix; };
Channel channel;
std::array<TargetMix,2> targets;
};
struct BufferSubList {
uint64_t FreeMask{~0_u64};
ALbuffer *Buffers{nullptr}; /* 64 */
BufferSubList() noexcept = default;
BufferSubList(const BufferSubList&) = delete;
BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers}
{ rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; }
~BufferSubList();
BufferSubList& operator=(const BufferSubList&) = delete;
BufferSubList& operator=(BufferSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; }
};
struct EffectSubList {
uint64_t FreeMask{~0_u64};
ALeffect *Effects{nullptr}; /* 64 */
EffectSubList() noexcept = default;
EffectSubList(const EffectSubList&) = delete;
EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects}
{ rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; }
~EffectSubList();
EffectSubList& operator=(const EffectSubList&) = delete;
EffectSubList& operator=(EffectSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; }
};
struct FilterSubList {
uint64_t FreeMask{~0_u64};
ALfilter *Filters{nullptr}; /* 64 */
FilterSubList() noexcept = default;
FilterSubList(const FilterSubList&) = delete;
FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
{ rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
~FilterSubList();
FilterSubList& operator=(const FilterSubList&) = delete;
FilterSubList& operator=(FilterSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
};
/* Maximum delay in samples for speaker distance compensation. */
#define MAX_DELAY_LENGTH 1024
class DistanceComp {
public:
struct DistData {
ALfloat Gain{1.0f};
ALuint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
ALfloat *Buffer{nullptr};
};
private:
std::array<DistData,MAX_OUTPUT_CHANNELS> mChannels;
al::vector<ALfloat,16> mSamples;
public:
void setSampleCount(size_t new_size) { mSamples.resize(new_size); }
void clear() noexcept
{
for(auto &chan : mChannels)
{
chan.Gain = 1.0f;
chan.Length = 0;
chan.Buffer = nullptr;
}
using SampleVecT = decltype(mSamples);
SampleVecT{}.swap(mSamples);
}
ALfloat *getSamples() noexcept { return mSamples.data(); }
al::span<DistData,MAX_OUTPUT_CHANNELS> as_span() { return mChannels; }
};
struct BFChannelConfig {
ALfloat Scale;
ALuint Index;
};
/* Size for temporary storage of buffer data, in ALfloats. Larger values need
* more memory, while smaller values may need more iterations. The value needs
* to be a sensible size, however, as it constrains the max stepping value used
* for mixing, as well as the maximum number of samples per mixing iteration.
*/
#define BUFFERSIZE 1024
using FloatBufferLine = std::array<float,BUFFERSIZE>;
/* Maximum number of samples to pad on the ends of a buffer for resampling.
* Note that the padding is symmetric (half at the beginning and half at the
* end)!
*/
#define MAX_RESAMPLER_PADDING 48
struct FrontStablizer {
static constexpr size_t DelayLength{256u};
alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength];
BandSplitter LFilter, RFilter;
alignas(16) float LSplit[2][BUFFERSIZE];
alignas(16) float RSplit[2][BUFFERSIZE];
alignas(16) float TempBuf[BUFFERSIZE + DelayLength];
DEF_NEWDEL(FrontStablizer)
};
struct MixParams {
/* Coefficient channel mapping for mixing to the buffer. */
std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
al::span<FloatBufferLine> Buffer;
};
struct RealMixParams {
al::span<const InputRemixMap> RemixMap;
std::array<ALuint,MaxChannels> ChannelIndex{};
al::span<FloatBufferLine> Buffer;
};
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,
DeviceFlagsCount
};
struct ALCdevice : public al::intrusive_ref<ALCdevice> {
std::atomic<bool> Connected{true};
const DeviceType Type{};
ALuint Frequency{};
ALuint UpdateSize{};
ALuint BufferSize{};
DevFmtChannels FmtChans{};
DevFmtType FmtType{};
ALboolean IsHeadphones{AL_FALSE};
ALuint mAmbiOrder{0};
/* For DevFmtAmbi* output only, specifies the channel order and
* normalization.
*/
AmbiLayout mAmbiLayout{AmbiLayout::Default};
AmbiNorm mAmbiScale{AmbiNorm::Default};
ALCenum LimiterState{ALC_DONT_CARE_SOFT};
std::string DeviceName;
// Device flags
al::bitfield<DeviceFlagsCount> Flags{};
std::string HrtfName;
al::vector<std::string> HrtfList;
ALCenum HrtfStatus{ALC_FALSE};
std::atomic<ALCenum> LastError{ALC_NO_ERROR};
// Maximum number of sources that can be created
ALuint SourcesMax{};
// Maximum number of slots that can be created
ALuint AuxiliaryEffectSlotMax{};
ALCuint NumMonoSources{};
ALCuint NumStereoSources{};
ALCuint NumAuxSends{};
// Map of Buffers for this device
std::mutex BufferLock;
al::vector<BufferSubList> BufferList;
// Map of Effects for this device
std::mutex EffectLock;
al::vector<EffectSubList> EffectList;
// Map of Filters for this device
std::mutex FilterLock;
al::vector<FilterSubList> FilterList;
/* Rendering mode. */
RenderMode mRenderMode{NormalRender};
/* The average speaker distance as determined by the ambdec configuration,
* HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
*/
ALfloat AvgSpeakerDist{0.0f};
ALuint SamplesDone{0u};
std::chrono::nanoseconds ClockBase{0};
std::chrono::nanoseconds FixedLatency{0};
/* Temp storage used for mixer processing. */
alignas(16) ALfloat SourceData[BUFFERSIZE + MAX_RESAMPLER_PADDING];
alignas(16) ALfloat ResampledData[BUFFERSIZE];
alignas(16) ALfloat FilteredData[BUFFERSIZE];
union {
alignas(16) ALfloat HrtfSourceData[BUFFERSIZE + HRTF_HISTORY_LENGTH];
alignas(16) ALfloat NfcSampleData[BUFFERSIZE];
};
/* Persistent storage for HRTF mixing. */
alignas(16) float2 HrtfAccumData[BUFFERSIZE + HRIR_LENGTH];
/* 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;
ALuint NumChannelsPerOrder[MAX_AMBI_ORDER+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;
HrtfStore *mHrtf{nullptr};
/* Ambisonic-to-UHJ encoder */
std::unique_ptr<Uhj2Encoder> Uhj_Encoder;
/* Ambisonic decoder for speakers */
std::unique_ptr<BFormatDec> AmbiDecoder;
/* Stereo-to-binaural filter */
std::unique_ptr<bs2b> Bs2b;
using PostProc = void(ALCdevice::*)(const size_t SamplesToDo);
PostProc PostProcess{nullptr};
std::unique_ptr<FrontStablizer> Stablizer;
std::unique_ptr<Compressor> Limiter;
/* Delay buffers used to compensate for speaker distances. */
DistanceComp ChannelDelay;
/* Dithering control. */
ALfloat DitherDepth{0.0f};
ALuint 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<al::FlexArray<ALCcontext*>*> mContexts{nullptr};
/* This lock protects the device state (format, update size, etc) from
* being from being changed in multiple threads, or being accessed while
* being changed. It's also used to serialize calls to the backend.
*/
std::mutex StateLock;
std::unique_ptr<BackendBase> Backend;
ALCdevice(DeviceType type);
ALCdevice(const ALCdevice&) = delete;
ALCdevice& operator=(const ALCdevice&) = delete;
~ALCdevice();
ALuint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
ALuint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
ALuint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
void ProcessHrtf(const size_t SamplesToDo);
void ProcessAmbiDec(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); }
DEF_NEWDEL(ALCdevice)
};
/* 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"
extern ALint RTPrioLevel;
void SetRTPriority(void);
void SetDefaultChannelOrder(ALCdevice *device);
void SetDefaultWFXChannelOrder(ALCdevice *device);
const ALCchar *DevFmtTypeString(DevFmtType type) noexcept;
const ALCchar *DevFmtChannelsString(DevFmtChannels chans) noexcept;
/**
* GetChannelIdxByName
*
* Returns the index for the given channel name (e.g. FrontCenter), or
* INVALID_CHANNEL_INDEX if it doesn't exist.
*/
inline ALuint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept
{ return real.ChannelIndex[chan]; }
#define INVALID_CHANNEL_INDEX ~0u
al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
#endif
-550
View File
@@ -1,550 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#ifdef _WIN32
#ifdef __MINGW32__
#define _WIN32_IE 0x501
#else
#define _WIN32_IE 0x400
#endif
#endif
#include "config.h"
#include "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32_IE
#include <windows.h>
#include <shlobj.h>
#endif
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif
#include <algorithm>
#include <cstdio>
#include <string>
#include <utility>
#include "alfstream.h"
#include "alstring.h"
#include "compat.h"
#include "logging.h"
#include "strutils.h"
#include "vector.h"
namespace {
struct ConfigEntry {
std::string key;
std::string value;
};
al::vector<ConfigEntry> ConfOpts;
std::string &lstrip(std::string &line)
{
size_t pos{0};
while(pos < line.length() && std::isspace(line[pos]))
++pos;
line.erase(0, pos);
return line;
}
bool readline(std::istream &f, std::string &output)
{
while(f.good() && f.peek() == '\n')
f.ignore();
return std::getline(f, output) && !output.empty();
}
std::string expdup(const char *str)
{
std::string output;
std::string envval;
while(*str != '\0')
{
const char *addstr;
size_t addstrlen;
if(str[0] != '$')
{
const char *next = std::strchr(str, '$');
addstr = str;
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
str += addstrlen;
}
else
{
str++;
if(*str == '$')
{
const char *next = std::strchr(str+1, '$');
addstr = str;
addstrlen = next ? static_cast<size_t>(next-str) : std::strlen(str);
str += addstrlen;
}
else
{
const bool hasbraces{(*str == '{')};
if(hasbraces) str++;
const char *envstart = str;
while(std::isalnum(*str) || *str == '_')
++str;
if(hasbraces && *str != '}')
continue;
const std::string envname{envstart, str};
if(hasbraces) str++;
envval = al::getenv(envname.c_str()).value_or(std::string{});
addstr = envval.data();
addstrlen = envval.length();
}
}
if(addstrlen == 0)
continue;
output.append(addstr, addstrlen);
}
return output;
}
void LoadConfigFromFile(std::istream &f)
{
std::string curSection;
std::string buffer;
while(readline(f, buffer))
{
if(lstrip(buffer).empty())
continue;
if(buffer[0] == '[')
{
char *line{&buffer[0]};
char *section = line+1;
char *endsection;
endsection = std::strchr(section, ']');
if(!endsection || section == endsection)
{
ERR(" config parse error: bad line \"%s\"\n", line);
continue;
}
if(endsection[1] != 0)
{
char *end = endsection+1;
while(std::isspace(*end))
++end;
if(*end != 0 && *end != '#')
{
ERR(" config parse error: bad line \"%s\"\n", line);
continue;
}
}
*endsection = 0;
curSection.clear();
if(al::strcasecmp(section, "general") != 0)
{
do {
char *nextp = std::strchr(section, '%');
if(!nextp)
{
curSection += section;
break;
}
curSection.append(section, nextp);
section = nextp;
if(((section[1] >= '0' && section[1] <= '9') ||
(section[1] >= 'a' && section[1] <= 'f') ||
(section[1] >= 'A' && section[1] <= 'F')) &&
((section[2] >= '0' && section[2] <= '9') ||
(section[2] >= 'a' && section[2] <= 'f') ||
(section[2] >= 'A' && section[2] <= 'F')))
{
int b{0};
if(section[1] >= '0' && section[1] <= '9')
b = (section[1]-'0') << 4;
else if(section[1] >= 'a' && section[1] <= 'f')
b = (section[1]-'a'+0xa) << 4;
else if(section[1] >= 'A' && section[1] <= 'F')
b = (section[1]-'A'+0x0a) << 4;
if(section[2] >= '0' && section[2] <= '9')
b |= (section[2]-'0');
else if(section[2] >= 'a' && section[2] <= 'f')
b |= (section[2]-'a'+0xa);
else if(section[2] >= 'A' && section[2] <= 'F')
b |= (section[2]-'A'+0x0a);
curSection += static_cast<char>(b);
section += 3;
}
else if(section[1] == '%')
{
curSection += '%';
section += 2;
}
else
{
curSection += '%';
section += 1;
}
} while(*section != 0);
}
continue;
}
auto cmtpos = std::min(buffer.find('#'), buffer.size());
while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
--cmtpos;
if(!cmtpos) continue;
buffer.erase(cmtpos);
auto sep = buffer.find('=');
if(sep == std::string::npos)
{
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
auto keyend = sep++;
while(keyend > 0 && std::isspace(buffer[keyend-1]))
--keyend;
if(!keyend)
{
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
continue;
}
while(sep < buffer.size() && std::isspace(buffer[sep]))
sep++;
std::string fullKey;
if(!curSection.empty())
{
fullKey += curSection;
fullKey += '/';
}
fullKey += buffer.substr(0u, keyend);
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
if(value.size() > 1)
{
if((value.front() == '"' && value.back() == '"')
|| (value.front() == '\'' && value.back() == '\''))
{
value.pop_back();
value.erase(value.begin());
}
}
TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
/* Check if we already have this option set */
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
{ return entry.key == fullKey; };
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
if(ent != ConfOpts.end())
{
if(!value.empty())
ent->value = expdup(value.c_str());
else
ConfOpts.erase(ent);
}
else if(!value.empty())
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
}
ConfOpts.shrink_to_fit();
}
} // namespace
#ifdef _WIN32
void ReadALConfig()
{
WCHAR buffer[MAX_PATH];
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
{
std::string filepath{wstr_to_utf8(buffer)};
filepath += "\\alsoft.ini";
TRACE("Loading config %s...\n", filepath.c_str());
al::ifstream f{filepath};
if(f.is_open())
LoadConfigFromFile(f);
}
std::string ppath{GetProcBinary().path};
if(!ppath.empty())
{
ppath += "\\alsoft.ini";
TRACE("Loading config %s...\n", ppath.c_str());
al::ifstream f{ppath};
if(f.is_open())
LoadConfigFromFile(f);
}
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
al::ifstream f{*confpath};
if(f.is_open())
LoadConfigFromFile(f);
}
}
#else
void ReadALConfig()
{
const char *str{"/etc/openal/alsoft.conf"};
TRACE("Loading config %s...\n", str);
al::ifstream f{str};
if(f.is_open())
LoadConfigFromFile(f);
f.close();
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
/* Go through the list in reverse, since "the order of base directories
* denotes their importance; the first directory listed is the most
* important". Ergo, we need to load the settings from the later dirs
* first so that the settings in the earlier dirs override them.
*/
std::string fname;
while(!confpaths.empty())
{
auto next = confpaths.find_last_of(':');
if(next < confpaths.length())
{
fname = confpaths.substr(next+1);
confpaths.erase(next);
}
else
{
fname = confpaths;
confpaths.clear();
}
if(fname.empty() || fname.front() != '/')
WARN("Ignoring XDG config dir: %s\n", fname.c_str());
else
{
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
LoadConfigFromFile(f);
}
fname.clear();
}
#ifdef __APPLE__
CFBundleRef mainBundle = CFBundleGetMainBundle();
if(mainBundle)
{
unsigned char fileName[PATH_MAX];
CFURLRef configURL;
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
{
f = al::ifstream{reinterpret_cast<char*>(fileName)};
if(f.is_open())
LoadConfigFromFile(f);
}
}
#endif
if(auto homedir = al::getenv("HOME"))
{
fname = *homedir;
if(fname.back() != '/') fname += "/.alsoftrc";
else fname += ".alsoftrc";
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
LoadConfigFromFile(f);
}
if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
{
fname = *configdir;
if(fname.back() != '/') fname += "/alsoft.conf";
else fname += "alsoft.conf";
}
else
{
fname.clear();
if(auto homedir = al::getenv("HOME"))
{
fname = *homedir;
if(fname.back() != '/') fname += "/.config/alsoft.conf";
else fname += ".config/alsoft.conf";
}
}
if(!fname.empty())
{
TRACE("Loading config %s...\n", fname.c_str());
f = al::ifstream{fname};
if(f.is_open())
LoadConfigFromFile(f);
}
std::string ppath{GetProcBinary().path};
if(!ppath.empty())
{
if(ppath.back() != '/') ppath += "/alsoft.conf";
else ppath += "alsoft.conf";
TRACE("Loading config %s...\n", ppath.c_str());
f = al::ifstream{ppath};
if(f.is_open())
LoadConfigFromFile(f);
}
if(auto confname = al::getenv("ALSOFT_CONF"))
{
TRACE("Loading config %s...\n", confname->c_str());
f = al::ifstream{*confname};
if(f.is_open())
LoadConfigFromFile(f);
}
}
#endif
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
{
if(!keyName)
return def;
std::string key;
if(blockName && al::strcasecmp(blockName, "general") != 0)
{
key = blockName;
if(devName)
{
key += '/';
key += devName;
}
key += '/';
key += keyName;
}
else
{
if(devName)
{
key = devName;
key += '/';
}
key += keyName;
}
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
[&key](const ConfigEntry &entry) -> bool
{ return entry.key == key; }
);
if(iter != ConfOpts.cend())
{
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
if(!iter->value.empty())
return iter->value.c_str();
return def;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
return def;
}
return GetConfigValue(nullptr, blockName, keyName, def);
}
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
return val[0] != 0;
}
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional<std::string>(val);
}
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
}
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
}
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional(std::strtof(val, nullptr));
}
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return al::nullopt;
return al::make_optional(
al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
}
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
{
const char *val = GetConfigValue(devName, blockName, keyName, "");
if(!val[0]) return def != 0;
return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
}
-20
View File
@@ -1,20 +0,0 @@
#ifndef ALCONFIG_H
#define ALCONFIG_H
#include <string>
#include "aloptional.h"
void ReadALConfig();
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName);
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def);
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def);
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
#endif /* ALCONFIG_H */
-196
View File
@@ -1,196 +0,0 @@
#ifndef ALCONTEXT_H
#define ALCONTEXT_H
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <thread>
#include <utility>
#include "AL/al.h"
#include "AL/alc.h"
#include "al/listener.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alu.h"
#include "atomic.h"
#include "inprogext.h"
#include "intrusive_ptr.h"
#include "logging.h"
#include "threads.h"
#include "vector.h"
#include "voice.h"
struct ALeffectslot;
struct ALeffectslotProps;
struct ALsource;
struct RingBuffer;
enum class DistanceModel {
InverseClamped = AL_INVERSE_DISTANCE_CLAMPED,
LinearClamped = AL_LINEAR_DISTANCE_CLAMPED,
ExponentClamped = AL_EXPONENT_DISTANCE_CLAMPED,
Inverse = AL_INVERSE_DISTANCE,
Linear = AL_LINEAR_DISTANCE,
Exponent = AL_EXPONENT_DISTANCE,
Disable = AL_NONE,
Default = InverseClamped
};
struct ALcontextProps {
ALfloat DopplerFactor;
ALfloat DopplerVelocity;
ALfloat SpeedOfSound;
ALboolean SourceDistanceModel;
DistanceModel mDistanceModel;
std::atomic<ALcontextProps*> next;
DEF_NEWDEL(ALcontextProps)
};
struct SourceSubList {
uint64_t FreeMask{~0_u64};
ALsource *Sources{nullptr}; /* 64 */
SourceSubList() noexcept = default;
SourceSubList(const SourceSubList&) = delete;
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
~SourceSubList();
SourceSubList& operator=(const SourceSubList&) = delete;
SourceSubList& operator=(SourceSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
};
struct EffectSlotSubList {
uint64_t FreeMask{~0_u64};
ALeffectslot *EffectSlots{nullptr}; /* 64 */
EffectSlotSubList() noexcept = default;
EffectSlotSubList(const EffectSlotSubList&) = delete;
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
~EffectSlotSubList();
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
};
struct ALCcontext : public al::intrusive_ref<ALCcontext> {
al::vector<SourceSubList> mSourceList;
ALuint mNumSources{0};
std::mutex mSourceLock;
al::vector<EffectSlotSubList> mEffectSlotList;
ALuint mNumEffectSlots{0u};
std::mutex mEffectSlotLock;
std::atomic<ALenum> mLastError{AL_NO_ERROR};
DistanceModel mDistanceModel{DistanceModel::Default};
ALboolean mSourceDistanceModel{AL_FALSE};
ALfloat mDopplerFactor{1.0f};
ALfloat mDopplerVelocity{1.0f};
ALfloat mSpeedOfSound{SPEEDOFSOUNDMETRESPERSEC};
std::atomic_flag mPropsClean;
std::atomic<bool> mDeferUpdates{false};
std::mutex mPropLock;
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
* indicates if updates are currently happening).
*/
RefCount mUpdateCount{0u};
std::atomic<bool> mHoldUpdates{false};
ALfloat mGainBoost{1.0f};
std::atomic<ALcontextProps*> mUpdate{nullptr};
/* Linked lists of unused property containers, free to use for future
* updates.
*/
std::atomic<ALcontextProps*> mFreeContextProps{nullptr};
std::atomic<ALlistenerProps*> mFreeListenerProps{nullptr};
std::atomic<ALvoiceProps*> mFreeVoiceProps{nullptr};
std::atomic<ALeffectslotProps*> mFreeEffectslotProps{nullptr};
al::vector<ALvoice> mVoices;
using ALeffectslotArray = al::FlexArray<ALeffectslot*>;
std::atomic<ALeffectslotArray*> mActiveAuxSlots{nullptr};
std::thread mEventThread;
al::semaphore mEventSem;
std::unique_ptr<RingBuffer> mAsyncEvents;
std::atomic<ALbitfieldSOFT> mEnabledEvts{0u};
std::mutex mEventCbLock;
ALEVENTPROCSOFT mEventCb{};
void *mEventParam{nullptr};
/* Default effect slot */
std::unique_ptr<ALeffectslot> mDefaultSlot;
const al::intrusive_ptr<ALCdevice> mDevice;
const ALCchar *mExtensionList{nullptr};
ALlistener mListener{};
ALCcontext(al::intrusive_ptr<ALCdevice> device);
ALCcontext(const ALCcontext&) = delete;
ALCcontext& operator=(const ALCcontext&) = delete;
~ALCcontext();
void init();
/**
* Removes the context from its device and removes it from being current on
* the running thread or globally. Returns true if other contexts still
* exist on the device.
*/
bool deinit();
/**
* Defers/suspends updates for the given context's listener and sources.
* This does *NOT* stop mixing, but rather prevents certain property
* changes from taking effect.
*/
void deferUpdates() noexcept { mDeferUpdates.store(true); }
/** Resumes update processing after being deferred. */
void processUpdates();
void setError(ALenum errorCode, const char *msg, ...) DECL_FORMAT(printf, 3, 4);
DEF_NEWDEL(ALCcontext)
};
#define SETERR_RETURN(ctx, err, retval, ...) do { \
(ctx)->setError((err), __VA_ARGS__); \
return retval; \
} while(0)
using ContextRef = al::intrusive_ptr<ALCcontext>;
ContextRef GetContextRef(void);
void UpdateContextProps(ALCcontext *context);
extern bool TrapALError;
#endif /* ALCONTEXT_H */
-2042
View File
File diff suppressed because it is too large Load Diff
-159
View File
@@ -1,159 +0,0 @@
#ifndef ALU_H
#define ALU_H
#include <array>
#include <cmath>
#include <cstddef>
#include "AL/al.h"
#include "alcmain.h"
#include "alspan.h"
#include "logging.h"
struct ALbufferlistitem;
struct ALeffectslot;
#define MAX_PITCH 255
#define MAX_SENDS 16
using MixerFunc = void(*)(const al::span<const float> InSamples,
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
const size_t Counter, const size_t OutPos);
using RowMixerFunc = void(*)(const al::span<float> OutBuffer, const al::span<const float> Gains,
const float *InSamples, const size_t InStride);
using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut,
const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State,
const size_t BufferSize);
extern MixerFunc MixSamples;
extern RowMixerFunc MixRowSamples;
#define GAIN_MIX_MAX (1000.0f) /* +60dB */
#define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */
#define SPEEDOFSOUNDMETRESPERSEC (343.3f)
#define AIRABSORBGAINHF (0.99426f) /* -0.05dB */
/* Target gain for the reverb decay feedback reaching the decay time. */
#define REVERB_DECAY_GAIN (0.001f) /* -60 dB */
#define FRACTIONBITS (12)
#define FRACTIONONE (1<<FRACTIONBITS)
#define FRACTIONMASK (FRACTIONONE-1)
inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu) noexcept
{ return val1 + (val2-val1)*mu; }
inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu) noexcept
{
ALfloat mu2 = mu*mu, mu3 = mu2*mu;
ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu;
ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f;
ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu;
ALfloat a3 = 0.5f*mu3 + -0.5f*mu2;
return val1*a0 + val2*a1 + val3*a2 + val4*a3;
}
enum HrtfRequestMode {
Hrtf_Default = 0,
Hrtf_Enable = 1,
Hrtf_Disable = 2,
};
void aluInit(void);
void aluInitMixer(void);
/* aluInitRenderer
*
* Set up the appropriate panning method and mixing method given the device
* properties.
*/
void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq);
void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device);
/**
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
* components, which must represent a normalized (unit length) vector, and the
* spread is the angular width of the sound (0...tau).
*
* NOTE: The components use ambisonic coordinates. As a result:
*
* Ambisonic Y = OpenAL -X
* Ambisonic Z = OpenAL Y
* Ambisonic X = OpenAL -Z
*
* The components are ordered such that OpenAL's X, Y, and Z are the first,
* second, and third parameters respectively -- simply negate X and Z.
*/
void CalcAmbiCoeffs(const float y, const float z, const float x, const float spread,
const al::span<float,MAX_AMBI_CHANNELS> coeffs);
/**
* CalcDirectionCoeffs
*
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
* vector must be normalized (unit length), and the spread is the angular width
* of the sound (0...tau).
*/
inline void CalcDirectionCoeffs(const float (&dir)[3], const float spread,
const al::span<float,MAX_AMBI_CHANNELS> coeffs)
{
/* Convert from OpenAL coords to Ambisonics. */
CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs);
}
/**
* CalcAngleCoeffs
*
* Calculates ambisonic coefficients based on azimuth and elevation. The
* azimuth and elevation parameters are in radians, going right and up
* respectively.
*/
inline void CalcAngleCoeffs(const float azimuth, const float elevation, const float spread,
const al::span<float,MAX_AMBI_CHANNELS> coeffs)
{
const float x{-std::sin(azimuth) * std::cos(elevation)};
const float y{ std::sin(elevation)};
const float z{ std::cos(azimuth) * std::cos(elevation)};
CalcAmbiCoeffs(x, y, z, spread, coeffs);
}
/**
* ComputePanGains
*
* Computes panning gains using the given channel decoder coefficients and the
* pre-calculated direction or angle coefficients. For B-Format sources, the
* coeffs are a 'slice' of a transform matrix for the input channel, used to
* scale and orient the sound samples.
*/
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
const al::span<float,MAX_OUTPUT_CHANNELS> gains);
inline std::array<ALfloat,MAX_AMBI_CHANNELS> GetAmbiIdentityRow(size_t i) noexcept
{
std::array<ALfloat,MAX_AMBI_CHANNELS> ret{};
ret[i] = 1.0f;
return ret;
}
void aluMixData(ALCdevice *device, void *OutBuffer, const ALuint NumSamples,
const size_t FrameStep);
/* Caller must lock the device state, and the mixer must not be running. */
void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3);
extern const ALfloat ConeScale;
extern const ALfloat ZScale;
#endif
-434
View File
@@ -1,434 +0,0 @@
#include "config.h"
#include "ambdec.h"
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <iterator>
#include <sstream>
#include <string>
#include "alfstream.h"
#include "logging.h"
namespace {
template<typename T, std::size_t N>
constexpr inline std::size_t size(const T(&)[N]) noexcept
{ return N; }
int readline(std::istream &f, std::string &output)
{
while(f.good() && f.peek() == '\n')
f.ignore();
return std::getline(f, output) && !output.empty();
}
bool read_clipped_line(std::istream &f, std::string &buffer)
{
while(readline(f, buffer))
{
std::size_t pos{0};
while(pos < buffer.length() && std::isspace(buffer[pos]))
pos++;
buffer.erase(0, pos);
std::size_t cmtpos{buffer.find_first_of('#')};
if(cmtpos < buffer.length())
buffer.resize(cmtpos);
while(!buffer.empty() && std::isspace(buffer.back()))
buffer.pop_back();
if(!buffer.empty())
return true;
}
return false;
}
std::string read_word(std::istream &f)
{
std::string ret;
f >> ret;
return ret;
}
bool is_at_end(const std::string &buffer, std::size_t endpos)
{
while(endpos < buffer.length() && std::isspace(buffer[endpos]))
++endpos;
return !(endpos < buffer.length());
}
bool load_ambdec_speakers(al::vector<AmbDecConf::SpeakerConf> &spkrs, const std::size_t num_speakers, std::istream &f, std::string &buffer)
{
while(spkrs.size() < num_speakers)
{
std::istringstream istr{buffer};
std::string cmd{read_word(istr)};
if(cmd.empty())
{
if(!read_clipped_line(f, buffer))
{
ERR("Unexpected end of file\n");
return false;
}
continue;
}
if(cmd == "add_spkr")
{
spkrs.emplace_back();
AmbDecConf::SpeakerConf &spkr = spkrs.back();
const size_t spkr_num{spkrs.size()};
istr >> spkr.Name;
if(istr.fail()) WARN("Name not specified for speaker %zu\n", spkr_num);
istr >> spkr.Distance;
if(istr.fail()) WARN("Distance not specified for speaker %zu\n", spkr_num);
istr >> spkr.Azimuth;
if(istr.fail()) WARN("Azimuth not specified for speaker %zu\n", spkr_num);
istr >> spkr.Elevation;
if(istr.fail()) WARN("Elevation not specified for speaker %zu\n", spkr_num);
istr >> spkr.Connection;
if(istr.fail()) TRACE("Connection not specified for speaker %zu\n", spkr_num);
}
else
{
ERR("Unexpected speakers command: %s\n", cmd.c_str());
return false;
}
istr.clear();
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
{
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
return false;
}
buffer.clear();
}
return true;
}
bool load_ambdec_matrix(float (&gains)[MAX_AMBI_ORDER+1], al::vector<AmbDecConf::CoeffArray> &matrix, const std::size_t maxrow, std::istream &f, std::string &buffer)
{
bool gotgains{false};
std::size_t cur{0u};
while(cur < maxrow)
{
std::istringstream istr{buffer};
std::string cmd{read_word(istr)};
if(cmd.empty())
{
if(!read_clipped_line(f, buffer))
{
ERR("Unexpected end of file\n");
return false;
}
continue;
}
if(cmd == "order_gain")
{
std::size_t curgain{0u};
float value;
while(istr.good())
{
istr >> value;
if(istr.fail()) break;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk on gain %zu: %s\n", curgain+1,
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return false;
}
if(curgain < size(gains))
gains[curgain++] = value;
}
std::fill(std::begin(gains)+curgain, std::end(gains), 0.0f);
gotgains = true;
}
else if(cmd == "add_row")
{
matrix.emplace_back();
AmbDecConf::CoeffArray &mtxrow = matrix.back();
std::size_t curidx{0u};
float value{};
while(istr.good())
{
istr >> value;
if(istr.fail()) break;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk on matrix element %zux%zu: %s\n", curidx,
matrix.size(), buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
matrix.pop_back();
return false;
}
if(curidx < mtxrow.size())
mtxrow[curidx++] = value;
}
std::fill(mtxrow.begin()+curidx, mtxrow.end(), 0.0f);
cur++;
}
else
{
ERR("Unexpected matrix command: %s\n", cmd.c_str());
return false;
}
istr.clear();
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
{
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
return false;
}
buffer.clear();
}
if(!gotgains)
{
ERR("Matrix order_gain not specified\n");
return false;
}
return true;
}
} // namespace
int AmbDecConf::load(const char *fname) noexcept
{
al::ifstream f{fname};
if(!f.is_open())
{
ERR("Failed to open: %s\n", fname);
return 0;
}
std::size_t num_speakers{0u};
std::string buffer;
while(read_clipped_line(f, buffer))
{
std::istringstream istr{buffer};
std::string command{read_word(istr)};
if(command.empty())
{
ERR("Malformed line: %s\n", buffer.c_str());
return 0;
}
if(command == "/description")
istr >> Description;
else if(command == "/version")
{
istr >> Version;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk after version: %s\n",
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return 0;
}
if(Version != 3)
{
ERR("Unsupported version: %u\n", Version);
return 0;
}
}
else if(command == "/dec/chan_mask")
{
istr >> std::hex >> ChanMask >> std::dec;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk after mask: %s\n",
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return 0;
}
}
else if(command == "/dec/freq_bands")
{
istr >> FreqBands;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk after freq_bands: %s\n",
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return 0;
}
if(FreqBands != 1 && FreqBands != 2)
{
ERR("Invalid freq_bands value: %u\n", FreqBands);
return 0;
}
}
else if(command == "/dec/speakers")
{
istr >> num_speakers;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk after speakers: %s\n",
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return 0;
}
Speakers.reserve(num_speakers);
LFMatrix.reserve(num_speakers);
HFMatrix.reserve(num_speakers);
}
else if(command == "/dec/coeff_scale")
{
std::string scale = read_word(istr);
if(scale == "n3d") CoeffScale = AmbDecScale::N3D;
else if(scale == "sn3d") CoeffScale = AmbDecScale::SN3D;
else if(scale == "fuma") CoeffScale = AmbDecScale::FuMa;
else
{
ERR("Unsupported coeff scale: %s\n", scale.c_str());
return 0;
}
}
else if(command == "/opt/xover_freq")
{
istr >> XOverFreq;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk after xover_freq: %s\n",
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return 0;
}
}
else if(command == "/opt/xover_ratio")
{
istr >> XOverRatio;
if(!istr.eof() && !std::isspace(istr.peek()))
{
ERR("Extra junk after xover_ratio: %s\n",
buffer.c_str()+static_cast<std::size_t>(istr.tellg()));
return 0;
}
}
else if(command == "/opt/input_scale" || command == "/opt/nfeff_comp" ||
command == "/opt/delay_comp" || command == "/opt/level_comp")
{
/* Unused */
read_word(istr);
}
else if(command == "/speakers/{")
{
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
{
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
return 0;
}
buffer.clear();
if(!load_ambdec_speakers(Speakers, num_speakers, f, buffer))
return 0;
if(!read_clipped_line(f, buffer))
{
ERR("Unexpected end of file\n");
return 0;
}
std::istringstream istr2{buffer};
std::string endmark{read_word(istr2)};
if(endmark != "/}")
{
ERR("Expected /} after speaker definitions, got %s\n", endmark.c_str());
return 0;
}
istr.swap(istr2);
}
else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{")
{
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
{
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
return 0;
}
buffer.clear();
if(FreqBands == 1)
{
if(command != "/matrix/{")
{
ERR("Unexpected \"%s\" type for a single-band decoder\n", command.c_str());
return 0;
}
if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer))
return 0;
}
else
{
if(command == "/lfmatrix/{")
{
if(!load_ambdec_matrix(LFOrderGain, LFMatrix, num_speakers, f, buffer))
return 0;
}
else if(command == "/hfmatrix/{")
{
if(!load_ambdec_matrix(HFOrderGain, HFMatrix, num_speakers, f, buffer))
return 0;
}
else
{
ERR("Unexpected \"%s\" type for a dual-band decoder\n", command.c_str());
return 0;
}
}
if(!read_clipped_line(f, buffer))
{
ERR("Unexpected end of file\n");
return 0;
}
std::istringstream istr2{buffer};
std::string endmark{read_word(istr2)};
if(endmark != "/}")
{
ERR("Expected /} after matrix definitions, got %s\n", endmark.c_str());
return 0;
}
istr.swap(istr2);
}
else if(command == "/end")
{
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
{
ERR("Unexpected junk on end: %s\n", buffer.c_str()+endpos);
return 0;
}
return 1;
}
else
{
ERR("Unexpected command: %s\n", command.c_str());
return 0;
}
istr.clear();
const auto endpos = static_cast<std::size_t>(istr.tellg());
if(!is_at_end(buffer, endpos))
{
ERR("Unexpected junk on line: %s\n", buffer.c_str()+endpos);
return 0;
}
buffer.clear();
}
ERR("Unexpected end of file\n");
return 0;
}
-48
View File
@@ -1,48 +0,0 @@
#ifndef AMBDEC_H
#define AMBDEC_H
#include <array>
#include <string>
#include "ambidefs.h"
#include "vector.h"
/* Helpers to read .ambdec configuration files. */
enum class AmbDecScale {
N3D,
SN3D,
FuMa,
};
struct AmbDecConf {
std::string Description;
int Version{0}; /* Must be 3 */
unsigned int ChanMask{0u};
unsigned int FreqBands{0u}; /* Must be 1 or 2 */
AmbDecScale CoeffScale{};
float XOverFreq{0.0f};
float XOverRatio{0.0f};
struct SpeakerConf {
std::string Name;
float Distance{0.0f};
float Azimuth{0.0f};
float Elevation{0.0f};
std::string Connection;
};
al::vector<SpeakerConf> Speakers;
using CoeffArray = std::array<float,MAX_AMBI_CHANNELS>;
/* Unused when FreqBands == 1 */
float LFOrderGain[MAX_AMBI_ORDER+1]{};
al::vector<CoeffArray> LFMatrix;
float HFOrderGain[MAX_AMBI_ORDER+1]{};
al::vector<CoeffArray> HFMatrix;
int load(const char *fname) noexcept;
};
#endif /* AMBDEC_H */
-132
View File
@@ -1,132 +0,0 @@
#ifndef AMBIDEFS_H
#define AMBIDEFS_H
#include <array>
#include <cstdint>
/* The maximum number of Ambisonics channels. For a given order (o), the size
* needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second-
* order has 9, third-order has 16, and fourth-order has 25.
*/
#define MAX_AMBI_ORDER 3
constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept
{ return (order+1) * (order+1); }
#define MAX_AMBI_CHANNELS AmbiChannelsFromOrder(MAX_AMBI_ORDER)
/* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up
* to 4th order, which is the highest order a 32-bit mask value can specify (a
* 64-bit mask could handle up to 7th order).
*/
#define AMBI_0ORDER_MASK 0x00000001
#define AMBI_1ORDER_MASK 0x0000000f
#define AMBI_2ORDER_MASK 0x000001ff
#define AMBI_3ORDER_MASK 0x0000ffff
#define AMBI_4ORDER_MASK 0x01ffffff
/* A bitmask of ambisonic channels with height information. If none of these
* channels are used/needed, there's no height (e.g. with most surround sound
* speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc.
*/
#define AMBI_PERIPHONIC_MASK (0xfe7ce4)
/* The maximum number of ambisonic channels for 2D (non-periphonic)
* representation. This is 2 per each order above zero-order, plus 1 for zero-
* order. Or simply, o*2 + 1.
*/
constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept
{ return order*2 + 1; }
#define MAX_AMBI2D_CHANNELS Ambi2DChannelsFromOrder(MAX_AMBI_ORDER)
/* NOTE: These are scale factors as applied to Ambisonics content. Decoder
* coefficients should be divided by these values to get proper scalings.
*/
struct AmbiScale {
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromN3D{{
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
}};
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromSN3D{{
1.000000000f, /* ACN 0, sqrt(1) */
1.732050808f, /* ACN 1, sqrt(3) */
1.732050808f, /* ACN 2, sqrt(3) */
1.732050808f, /* ACN 3, sqrt(3) */
2.236067978f, /* ACN 4, sqrt(5) */
2.236067978f, /* ACN 5, sqrt(5) */
2.236067978f, /* ACN 6, sqrt(5) */
2.236067978f, /* ACN 7, sqrt(5) */
2.236067978f, /* ACN 8, sqrt(5) */
2.645751311f, /* ACN 9, sqrt(7) */
2.645751311f, /* ACN 10, sqrt(7) */
2.645751311f, /* ACN 11, sqrt(7) */
2.645751311f, /* ACN 12, sqrt(7) */
2.645751311f, /* ACN 13, sqrt(7) */
2.645751311f, /* ACN 14, sqrt(7) */
2.645751311f, /* ACN 15, sqrt(7) */
}};
static constexpr std::array<float,MAX_AMBI_CHANNELS> FromFuMa{{
1.414213562f, /* ACN 0 (W), sqrt(2) */
1.732050808f, /* ACN 1 (Y), sqrt(3) */
1.732050808f, /* ACN 2 (Z), sqrt(3) */
1.732050808f, /* ACN 3 (X), sqrt(3) */
1.936491673f, /* ACN 4 (V), sqrt(15)/2 */
1.936491673f, /* ACN 5 (T), sqrt(15)/2 */
2.236067978f, /* ACN 6 (R), sqrt(5) */
1.936491673f, /* ACN 7 (S), sqrt(15)/2 */
1.936491673f, /* ACN 8 (U), sqrt(15)/2 */
2.091650066f, /* ACN 9 (Q), sqrt(35/8) */
1.972026594f, /* ACN 10 (O), sqrt(35)/3 */
2.231093404f, /* ACN 11 (M), sqrt(224/45) */
2.645751311f, /* ACN 12 (K), sqrt(7) */
2.231093404f, /* ACN 13 (L), sqrt(224/45) */
1.972026594f, /* ACN 14 (N), sqrt(35)/3 */
2.091650066f, /* ACN 15 (P), sqrt(35/8) */
}};
};
struct AmbiIndex {
static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromFuMa{{
0, /* W */
3, /* X */
1, /* Y */
2, /* Z */
6, /* R */
7, /* S */
5, /* T */
8, /* U */
4, /* V */
12, /* K */
13, /* L */
11, /* M */
14, /* N */
10, /* O */
15, /* P */
9, /* Q */
}};
static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> FromFuMa2D{{
0, /* W */
3, /* X */
1, /* Y */
8, /* U */
4, /* V */
15, /* P */
9, /* Q */
}};
static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> FromACN{{
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15
}};
static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> From2D{{
0, 1,3, 4,8, 9,15
}};
static constexpr std::array<uint8_t,MAX_AMBI_CHANNELS> OrderFromChannel{{
0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3,
}};
static constexpr std::array<uint8_t,MAX_AMBI2D_CHANNELS> OrderFrom2DChannel{{
0, 1,1, 2,2, 3,3,
}};
};
#endif /* AMBIDEFS_H */
File diff suppressed because it is too large Load Diff
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_ALSA_H
#define BACKENDS_ALSA_H
#include "backends/base.h"
struct AlsaBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_ALSA_H */
-62
View File
@@ -1,62 +0,0 @@
#include "config.h"
#include "base.h"
#include <atomic>
#include <thread>
#include "AL/al.h"
#include "alcmain.h"
#include "alexcpt.h"
#include "alnumeric.h"
#include "atomic.h"
ClockLatency GetClockLatency(ALCdevice *device)
{
BackendBase *backend{device->Backend.get()};
ClockLatency ret{backend->getClockLatency()};
ret.Latency += device->FixedLatency;
return ret;
}
/* BackendBase method implementations. */
BackendBase::BackendBase(ALCdevice *device) noexcept : mDevice{device}
{ }
BackendBase::~BackendBase() = default;
bool BackendBase::reset()
{ throw al::backend_exception{ALC_INVALID_DEVICE, "Invalid BackendBase call"}; }
ALCenum BackendBase::captureSamples(al::byte*, ALCuint)
{ return ALC_INVALID_DEVICE; }
ALCuint BackendBase::availableSamples()
{ return 0; }
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
ALuint refcount;
do {
while(((refcount=ReadRef(mDevice->MixCount))&1) != 0)
std::this_thread::yield();
ret.ClockTime = GetDeviceClockTime(mDevice);
std::atomic_thread_fence(std::memory_order_acquire);
} while(refcount != ReadRef(mDevice->MixCount));
/* NOTE: The device will generally have about all but one periods filled at
* any given time during playback. Without a more accurate measurement from
* the output, this is an okay approximation.
*/
ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize},
std::chrono::seconds::zero());
ret.Latency /= mDevice->Frequency;
return ret;
}
-84
View File
@@ -1,84 +0,0 @@
#ifndef ALC_BACKENDS_BASE_H
#define ALC_BACKENDS_BASE_H
#include <chrono>
#include <memory>
#include <mutex>
#include <string>
#include "AL/alc.h"
#include "alcmain.h"
#include "albyte.h"
struct ClockLatency {
std::chrono::nanoseconds ClockTime;
std::chrono::nanoseconds Latency;
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(ALCdevice *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
ClockLatency GetClockLatency(ALCdevice *device);
struct BackendBase {
virtual void open(const ALCchar *name) = 0;
virtual bool reset();
virtual bool start() = 0;
virtual void stop() = 0;
virtual ALCenum captureSamples(al::byte *buffer, ALCuint samples);
virtual ALCuint availableSamples();
virtual ClockLatency getClockLatency();
virtual void lock() { mMutex.lock(); }
virtual void unlock() { mMutex.unlock(); }
ALCdevice *mDevice;
std::recursive_mutex mMutex;
BackendBase(ALCdevice *device) noexcept;
virtual ~BackendBase();
};
using BackendPtr = std::unique_ptr<BackendBase>;
using BackendUniqueLock = std::unique_lock<BackendBase>;
using BackendLockGuard = std::lock_guard<BackendBase>;
enum class BackendType {
Playback,
Capture
};
enum class DevProbe {
Playback,
Capture
};
struct BackendFactory {
virtual bool init() = 0;
virtual bool querySupport(BackendType type) = 0;
virtual void probe(DevProbe type, std::string *outnames) = 0;
virtual BackendPtr createBackend(ALCdevice *device, BackendType type) = 0;
protected:
virtual ~BackendFactory() = default;
};
#endif /* ALC_BACKENDS_BASE_H */
-660
View File
@@ -1,660 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/coreaudio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "ringbuffer.h"
#include "converter.h"
#include "backends/base.h"
#include <unistd.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
static const ALCchar ca_device[] = "CoreAudio Default";
struct CoreAudioPlayback final : public BackendBase {
CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~CoreAudioPlayback() override;
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept;
static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
AudioUnit mAudioUnit{};
ALuint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
DEF_NEWDEL(CoreAudioPlayback)
};
CoreAudioPlayback::~CoreAudioPlayback()
{
AudioUnitUninitialize(mAudioUnit);
AudioComponentInstanceDispose(mAudioUnit);
}
OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTimeStamp*, UInt32,
UInt32, AudioBufferList *ioData) noexcept
{
std::lock_guard<CoreAudioPlayback> _{*this};
aluMixData(mDevice, ioData->mBuffers[0].mData, ioData->mBuffers[0].mDataByteSize/mFrameSize,
ioData->mBuffers[0].mNumberChannels);
return noErr;
}
void CoreAudioPlayback::open(const ALCchar *name)
{
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
/* open the default output unit */
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IOS
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == nullptr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
err};
/* init and start the default audio unit... */
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
mDevice->DeviceName = name;
}
bool CoreAudioPlayback::reset()
{
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
if(err != noErr)
ERR("-- AudioUnitUninitialize failed.\n");
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
0, &streamFormat, &size);
if(err != noErr || size != sizeof(AudioStreamBasicDescription))
{
ERR("AudioUnitGetProperty failed\n");
return false;
}
#if 0
TRACE("Output streamFormat of default output unit -\n");
TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
#endif
/* set default output unit's input side to match output side */
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, size);
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
if(mDevice->Frequency != streamFormat.mSampleRate)
{
mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
streamFormat.mSampleRate / mDevice->Frequency);
mDevice->Frequency = static_cast<ALuint>(streamFormat.mSampleRate);
}
/* FIXME: How to tell what channels are what in the output device, and how
* to specify what we're giving? eg, 6.0 vs 5.1 */
switch(streamFormat.mChannelsPerFrame)
{
case 1:
mDevice->FmtChans = DevFmtMono;
break;
case 2:
mDevice->FmtChans = DevFmtStereo;
break;
case 4:
mDevice->FmtChans = DevFmtQuad;
break;
case 6:
mDevice->FmtChans = DevFmtX51;
break;
case 7:
mDevice->FmtChans = DevFmtX61;
break;
case 8:
mDevice->FmtChans = DevFmtX71;
break;
default:
ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
mDevice->FmtChans = DevFmtStereo;
streamFormat.mChannelsPerFrame = 2;
break;
}
SetDefaultWFXChannelOrder(mDevice);
/* use channel count and sample rate from the default output unit's current
* parameters, but reset everything else */
streamFormat.mFramesPerPacket = 1;
streamFormat.mFormatFlags = 0;
switch(mDevice->FmtType)
{
case DevFmtUByte:
mDevice->FmtType = DevFmtByte;
/* fall-through */
case DevFmtByte:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 8;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 16;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
/* fall-through */
case DevFmtInt:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
streamFormat.mBitsPerChannel = 32;
break;
case DevFmtFloat:
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
streamFormat.mBitsPerChannel = 32;
break;
}
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
streamFormat.mBitsPerChannel / 8;
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
streamFormat.mFormatID = kAudioFormatLinearPCM;
streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
kLinearPCMFormatFlagIsPacked;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
0, &streamFormat, sizeof(AudioStreamBasicDescription));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
input.inputProc = CoreAudioPlayback::MixerProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
/* init the default audio unit... */
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
{
ERR("AudioUnitInitialize failed\n");
return false;
}
return true;
}
bool CoreAudioPlayback::start()
{
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
{
ERR("AudioOutputUnitStart failed\n");
return false;
}
return true;
}
void CoreAudioPlayback::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
}
struct CoreAudioCapture final : public BackendBase {
CoreAudioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
~CoreAudioCapture() override;
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData) noexcept
{
return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
inBusNumber, inNumberFrames, ioData);
}
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
AudioUnit mAudioUnit{0};
ALuint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
SampleConverterPtr mConverter;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
};
CoreAudioCapture::~CoreAudioCapture()
{
if(mAudioUnit)
AudioComponentInstanceDispose(mAudioUnit);
mAudioUnit = 0;
}
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
AudioBufferList*) noexcept
{
AudioUnitRenderActionFlags flags = 0;
union {
al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
AudioBufferList list;
} audiobuf{};
auto rec_vec = mRing->getWriteVector();
inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
rec_vec.first.len+rec_vec.second.len));
// Fill the ringbuffer's two segments with data from the input device
if(rec_vec.first.len >= inNumberFrames)
{
audiobuf.list.mNumberBuffers = 1;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
}
else
{
const auto remaining = static_cast<ALuint>(inNumberFrames - rec_vec.first.len);
audiobuf.list.mNumberBuffers = 2;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
mFormat.mBytesPerFrame;
audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
}
OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender error: %d\n", err);
return err;
}
mRing->writeAdvance(inNumberFrames);
return noErr;
}
void CoreAudioCapture::open(const ALCchar *name)
{
AudioStreamBasicDescription requestedFormat; // The application requested format
AudioStreamBasicDescription hardwareFormat; // The hardware format
AudioStreamBasicDescription outputFormat; // The AudioUnit output format
AURenderCallbackStruct input;
AudioComponentDescription desc;
UInt32 outputFrameCount;
UInt32 propertySize;
#if !TARGET_OS_IOS
AudioObjectPropertyAddress propertyAddress;
#endif
UInt32 enableIO;
AudioComponent comp;
OSStatus err;
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
desc.componentType = kAudioUnitType_Output;
#if TARGET_OS_IOS
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#else
desc.componentSubType = kAudioUnitSubType_HALOutput;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
// Search for component with given description
comp = AudioComponentFindNext(NULL, &desc);
if(comp == NULL)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not find audio component"};
// Open the component
err = AudioComponentInstanceNew(comp, &mAudioUnit);
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not create component instance: %u",
err};
// Turn off AudioUnit output
enableIO = 0;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, 0, &enableIO, sizeof(ALuint));
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE,
"Could not disable audio unit output property: %u", err};
// Turn on AudioUnit input
enableIO = 1;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, 1, &enableIO, sizeof(ALuint));
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE,
"Could not enable audio unit input property: %u", err};
#if !TARGET_OS_IOS
{
// Get the default input device
AudioDeviceID inputDevice = kAudioDeviceUnknown;
propertySize = sizeof(AudioDeviceID);
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
&propertySize, &inputDevice);
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input device: %u", err};
if(inputDevice == kAudioDeviceUnknown)
throw al::backend_exception{ALC_INVALID_VALUE, "Unknown input device"};
// Track the input device
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input device: %u", err};
}
#endif
// set capture callback
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not set capture callback: %u", err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not initialize audio unit: %u", err};
// Get the hardware format
propertySize = sizeof(AudioStreamBasicDescription);
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
throw al::backend_exception{ALC_INVALID_VALUE, "Could not get input format: %u", err};
// Set up the requested format description
switch(mDevice->FmtType)
{
case DevFmtUByte:
requestedFormat.mBitsPerChannel = 8;
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
break;
case DevFmtShort:
requestedFormat.mBitsPerChannel = 16;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtInt:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtFloat:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not suppoted",
DevFmtTypeString(mDevice->FmtType)};
}
switch(mDevice->FmtChans)
{
case DevFmtMono:
requestedFormat.mChannelsPerFrame = 1;
break;
case DevFmtStereo:
requestedFormat.mChannelsPerFrame = 2;
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
throw al::backend_exception{ALC_INVALID_VALUE, "%s not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
requestedFormat.mBytesPerFrame = requestedFormat.mChannelsPerFrame * requestedFormat.mBitsPerChannel / 8;
requestedFormat.mBytesPerPacket = requestedFormat.mBytesPerFrame;
requestedFormat.mSampleRate = mDevice->Frequency;
requestedFormat.mFormatID = kAudioFormatLinearPCM;
requestedFormat.mReserved = 0;
requestedFormat.mFramesPerPacket = 1;
// save requested format description for later use
mFormat = requestedFormat;
mFrameSize = mDevice->frameSizeFromFmt();
// Use intermediate format for sample rate conversion (outputFormat)
// Set sample rate to the same as hardware for resampling later
outputFormat = requestedFormat;
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
// The output format should be the requested format, but using the hardware sample rate
// This is because the AudioUnit will automatically scale other properties, except for sample rate
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
1, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not set input format: %u", err};
// Set the AudioUnit output format frame count
uint64_t FrameCount64{mDevice->UpdateSize};
FrameCount64 = static_cast<uint64_t>(FrameCount64*outputFormat.mSampleRate + mDevice->Frequency-1) /
mDevice->Frequency;
FrameCount64 += MAX_RESAMPLER_PADDING;
if(FrameCount64 > std::numeric_limits<uint32_t>::max()/2)
throw al::backend_exception{ALC_INVALID_VALUE,
"Calculated frame count is too large: %" PRIu64, FrameCount64};
outputFrameCount = static_cast<uint32_t>(FrameCount64);
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Output, 0, &outputFrameCount, sizeof(outputFrameCount));
if(err != noErr)
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set capture frame count: %u",
err};
// Set up sample converter if needed
if(outputFormat.mSampleRate != mDevice->Frequency)
mConverter = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
mFormat.mChannelsPerFrame, static_cast<ALuint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24);
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
mDevice->DeviceName = name;
}
bool CoreAudioCapture::start()
{
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
{
ERR("AudioOutputUnitStart failed\n");
return false;
}
return true;
}
void CoreAudioCapture::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
}
ALCenum CoreAudioCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
auto rec_vec = mRing->getReadVector();
const void *src0{rec_vec.first.buf};
auto src0len = static_cast<ALuint>(rec_vec.first.len);
ALuint got{mConverter->convert(&src0, &src0len, buffer, samples)};
size_t total_read{rec_vec.first.len - src0len};
if(got < samples && !src0len && rec_vec.second.len > 0)
{
const void *src1{rec_vec.second.buf};
auto src1len = static_cast<ALuint>(rec_vec.second.len);
got += mConverter->convert(&src1, &src1len, buffer+got, samples-got);
total_read += rec_vec.second.len - src1len;
}
mRing->readAdvance(total_read);
return ALC_NO_ERROR;
}
ALCuint CoreAudioCapture::availableSamples()
{
if(!mConverter) return static_cast<ALCuint>(mRing->readSpace());
return mConverter->availableOut(static_cast<ALCuint>(mRing->readSpace()));
}
} // namespace
BackendFactory &CoreAudioBackendFactory::getFactory()
{
static CoreAudioBackendFactory factory{};
return factory;
}
bool CoreAudioBackendFactory::init() { return true; }
bool CoreAudioBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
void CoreAudioBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(ca_device, sizeof(ca_device));
break;
}
}
BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new CoreAudioPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new CoreAudioCapture{device}};
return nullptr;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_COREAUDIO_H
#define BACKENDS_COREAUDIO_H
#include "backends/base.h"
struct CoreAudioBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_COREAUDIO_H */
-917
View File
@@ -1,917 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/dsound.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <cguid.h>
#include <mmreg.h>
#ifndef _WAVEFORMATEXTENSIBLE_
#include <ks.h>
#include <ksmedia.h>
#endif
#include <atomic>
#include <cassert>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "ringbuffer.h"
#include "compat.h"
#include "dynload.h"
#include "strutils.h"
#include "threads.h"
/* MinGW-w64 needs this for some unknown reason now. */
using LPCWAVEFORMATEX = const WAVEFORMATEX*;
#include <dsound.h>
#ifndef DSSPEAKER_5POINT1
# define DSSPEAKER_5POINT1 0x00000006
#endif
#ifndef DSSPEAKER_5POINT1_BACK
# define DSSPEAKER_5POINT1_BACK 0x00000006
#endif
#ifndef DSSPEAKER_7POINT1
# define DSSPEAKER_7POINT1 0x00000007
#endif
#ifndef DSSPEAKER_7POINT1_SURROUND
# define DSSPEAKER_7POINT1_SURROUND 0x00000008
#endif
#ifndef DSSPEAKER_5POINT1_SURROUND
# define DSSPEAKER_5POINT1_SURROUND 0x00000009
#endif
/* Some headers seem to define these as macros for __uuidof, which is annoying
* since some headers don't declare them at all. Hopefully the ifdef is enough
* to tell if they need to be declared.
*/
#ifndef KSDATAFORMAT_SUBTYPE_PCM
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
#endif
#ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
#endif
namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
#ifdef HAVE_DYNLOAD
void *ds_handle;
HRESULT (WINAPI *pDirectSoundCreate)(const GUID *pcGuidDevice, IDirectSound **ppDS, IUnknown *pUnkOuter);
HRESULT (WINAPI *pDirectSoundEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
HRESULT (WINAPI *pDirectSoundCaptureCreate)(const GUID *pcGuidDevice, IDirectSoundCapture **ppDSC, IUnknown *pUnkOuter);
HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallback, void *pContext);
#ifndef IN_IDE_PARSER
#define DirectSoundCreate pDirectSoundCreate
#define DirectSoundEnumerateW pDirectSoundEnumerateW
#define DirectSoundCaptureCreate pDirectSoundCaptureCreate
#define DirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW
#endif
#endif
#define MAX_UPDATES 128
struct DevMap {
std::string name;
GUID guid;
template<typename T0, typename T1>
DevMap(T0&& name_, T1&& guid_)
: name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
{ }
};
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
bool checkName(const al::vector<DevMap> &list, const std::string &name)
{
auto match_name = [&name](const DevMap &entry) -> bool
{ return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
}
BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
{
if(!guid)
return TRUE;
auto& devices = *static_cast<al::vector<DevMap>*>(data);
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
int count{1};
std::string newname{basename};
while(checkName(devices, newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
devices.emplace_back(std::move(newname), *guid);
const DevMap &newentry = devices.back();
OLECHAR *guidstr{nullptr};
HRESULT hr{StringFromCLSID(*guid, &guidstr)};
if(SUCCEEDED(hr))
{
TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
CoTaskMemFree(guidstr);
}
return TRUE;
}
struct DSoundPlayback final : public BackendBase {
DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~DSoundPlayback() override;
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
IDirectSound *mDS{nullptr};
IDirectSoundBuffer *mPrimaryBuffer{nullptr};
IDirectSoundBuffer *mBuffer{nullptr};
IDirectSoundNotify *mNotifies{nullptr};
HANDLE mNotifyEvent{nullptr};
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(DSoundPlayback)
};
DSoundPlayback::~DSoundPlayback()
{
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
if(mDS)
mDS->Release();
mDS = nullptr;
if(mNotifyEvent)
CloseHandle(mNotifyEvent);
mNotifyEvent = nullptr;
}
FORCE_ALIGN int DSoundPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
DSBCAPS DSBCaps{};
DSBCaps.dwSize = sizeof(DSBCaps);
HRESULT err{mBuffer->GetCaps(&DSBCaps)};
if(FAILED(err))
{
ERR("Failed to get buffer caps: 0x%lx\n", err);
aluHandleDisconnect(mDevice, "Failure retrieving playback buffer info: 0x%lx", err);
return 1;
}
const size_t FrameStep{mDevice->channelsFromFmt()};
ALuint FrameSize{mDevice->frameSizeFromFmt()};
DWORD FragSize{mDevice->UpdateSize * FrameSize};
bool Playing{false};
DWORD LastCursor{0u};
mBuffer->GetCurrentPosition(&LastCursor, nullptr);
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
// Get current play cursor
DWORD PlayCursor;
mBuffer->GetCurrentPosition(&PlayCursor, nullptr);
DWORD avail = (PlayCursor-LastCursor+DSBCaps.dwBufferBytes) % DSBCaps.dwBufferBytes;
if(avail < FragSize)
{
if(!Playing)
{
err = mBuffer->Play(0, 0, DSBPLAY_LOOPING);
if(FAILED(err))
{
ERR("Failed to play buffer: 0x%lx\n", err);
aluHandleDisconnect(mDevice, "Failure starting playback: 0x%lx", err);
return 1;
}
Playing = true;
}
avail = WaitForSingleObjectEx(mNotifyEvent, 2000, FALSE);
if(avail != WAIT_OBJECT_0)
ERR("WaitForSingleObjectEx error: 0x%lx\n", avail);
continue;
}
avail -= avail%FragSize;
// Lock output buffer
void *WritePtr1, *WritePtr2;
DWORD WriteCnt1{0u}, WriteCnt2{0u};
err = mBuffer->Lock(LastCursor, avail, &WritePtr1, &WriteCnt1, &WritePtr2, &WriteCnt2, 0);
// If the buffer is lost, restore it and lock
if(err == DSERR_BUFFERLOST)
{
WARN("Buffer lost, restoring...\n");
err = mBuffer->Restore();
if(SUCCEEDED(err))
{
Playing = false;
LastCursor = 0;
err = mBuffer->Lock(0, DSBCaps.dwBufferBytes, &WritePtr1, &WriteCnt1,
&WritePtr2, &WriteCnt2, 0);
}
}
if(SUCCEEDED(err))
{
std::unique_lock<DSoundPlayback> dlock{*this};
aluMixData(mDevice, WritePtr1, WriteCnt1/FrameSize, FrameStep);
if(WriteCnt2 > 0)
aluMixData(mDevice, WritePtr2, WriteCnt2/FrameSize, FrameStep);
dlock.unlock();
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
}
else
{
ERR("Buffer lock error: %#lx\n", err);
std::lock_guard<DSoundPlayback> _{*this};
aluHandleDisconnect(mDevice, "Failed to lock output buffer: 0x%lx", err);
return 1;
}
// Update old write cursor location
LastCursor += WriteCnt1+WriteCnt2;
LastCursor %= DSBCaps.dwBufferBytes;
}
return 0;
}
void DSoundPlayback::open(const ALCchar *name)
{
HRESULT hr;
if(PlaybackDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !PlaybackDevices.empty())
{
name = PlaybackDevices[0].name.c_str();
guid = &PlaybackDevices[0].guid;
}
else
{
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == PlaybackDevices.cend())
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
guid = &iter->guid;
}
hr = DS_OK;
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(!mNotifyEvent) hr = E_FAIL;
//DirectSound Init code
if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, &mDS, nullptr);
if(SUCCEEDED(hr))
hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
mDevice->DeviceName = name;
}
bool DSoundPlayback::reset()
{
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
switch(mDevice->FmtType)
{
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtFloat:
if(mDevice->Flags.get<SampleTypeRequest>())
break;
/* fall-through */
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
break;
}
WAVEFORMATEXTENSIBLE OutputType{};
DWORD speakers;
HRESULT hr{mDS->GetSpeakerConfig(&speakers)};
if(SUCCEEDED(hr))
{
speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.get<ChannelsRequest>())
{
if(speakers == DSSPEAKER_MONO)
mDevice->FmtChans = DevFmtMono;
else if(speakers == DSSPEAKER_STEREO || speakers == DSSPEAKER_HEADPHONE)
mDevice->FmtChans = DevFmtStereo;
else if(speakers == DSSPEAKER_QUAD)
mDevice->FmtChans = DevFmtQuad;
else if(speakers == DSSPEAKER_5POINT1_SURROUND)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51Rear;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo &&
speakers == DSSPEAKER_HEADPHONE);
switch(mDevice->FmtChans)
{
case DevFmtMono:
OutputType.dwChannelMask = SPEAKER_FRONT_CENTER;
break;
case DevFmtAmbi3D:
mDevice->FmtChans = DevFmtStereo;
/*fall-through*/
case DevFmtStereo:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT;
break;
case DevFmtQuad:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX51:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtX51Rear:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX61:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_CENTER |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtX71:
OutputType.dwChannelMask = SPEAKER_FRONT_LEFT |
SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
}
retry_open:
hr = S_OK;
OutputType.Format.wFormatTag = WAVE_FORMAT_PCM;
OutputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(OutputType.Format.nChannels *
OutputType.Format.wBitsPerSample / 8);
OutputType.Format.nSamplesPerSec = mDevice->Frequency;
OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec *
OutputType.Format.nBlockAlign;
OutputType.Format.cbSize = 0;
}
if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample;
OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
if(mDevice->FmtType == DevFmtFloat)
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
if(SUCCEEDED(hr))
{
ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
if(num_updates > MAX_UPDATES)
num_updates = MAX_UPDATES;
mDevice->BufferSize = mDevice->UpdateSize * num_updates;
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 |
DSBCAPS_GLOBALFOCUS;
DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign;
DSBDescription.lpwfxFormat = &OutputType.Format;
hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr);
if(FAILED(hr) && mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
goto retry_open;
}
}
if(SUCCEEDED(hr))
{
void *ptr;
hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr);
if(SUCCEEDED(hr))
{
auto Notifies = static_cast<IDirectSoundNotify*>(ptr);
mNotifies = Notifies;
ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
for(ALuint i{0};i < num_updates;++i)
{
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
nots[i].hEventNotify = mNotifyEvent;
}
if(Notifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
hr = E_FAIL;
}
}
if(FAILED(hr))
{
if(mNotifies)
mNotifies->Release();
mNotifies = nullptr;
if(mBuffer)
mBuffer->Release();
mBuffer = nullptr;
if(mPrimaryBuffer)
mPrimaryBuffer->Release();
mPrimaryBuffer = nullptr;
return false;
}
ResetEvent(mNotifyEvent);
SetDefaultWFXChannelOrder(mDevice);
return true;
}
bool DSoundPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void DSoundPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
mBuffer->Stop();
}
struct DSoundCapture final : public BackendBase {
DSoundCapture(ALCdevice *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
IDirectSoundCapture *mDSC{nullptr};
IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
DWORD mBufferBytes{0u};
DWORD mCursor{0u};
RingBufferPtr mRing;
DEF_NEWDEL(DSoundCapture)
};
DSoundCapture::~DSoundCapture()
{
if(mDSCbuffer)
{
mDSCbuffer->Stop();
mDSCbuffer->Release();
mDSCbuffer = nullptr;
}
if(mDSC)
mDSC->Release();
mDSC = nullptr;
}
void DSoundCapture::open(const ALCchar *name)
{
HRESULT hr;
if(CaptureDevices.empty())
{
/* Initialize COM to prevent name truncation */
HRESULT hrcom{CoInitialize(nullptr)};
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr);
if(SUCCEEDED(hrcom))
CoUninitialize();
}
const GUID *guid{nullptr};
if(!name && !CaptureDevices.empty())
{
name = CaptureDevices[0].name.c_str();
guid = &CaptureDevices[0].guid;
}
else
{
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == CaptureDevices.cend())
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
guid = &iter->guid;
}
switch(mDevice->FmtType)
{
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
WARN("%s capture samples not supported\n", DevFmtTypeString(mDevice->FmtType));
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
DevFmtTypeString(mDevice->FmtType)};
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
}
WAVEFORMATEXTENSIBLE InputType{};
switch(mDevice->FmtChans)
{
case DevFmtMono:
InputType.dwChannelMask = SPEAKER_FRONT_CENTER;
break;
case DevFmtStereo:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
break;
case DevFmtQuad:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT |
SPEAKER_BACK_RIGHT;
break;
case DevFmtX51:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
break;
case DevFmtX51Rear:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
case DevFmtX61:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
break;
case DevFmtX71:
InputType.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT |
SPEAKER_SIDE_RIGHT;
break;
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
InputType.Format.wFormatTag = WAVE_FORMAT_PCM;
InputType.Format.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
InputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
InputType.Format.nBlockAlign = static_cast<WORD>(InputType.Format.nChannels *
InputType.Format.wBitsPerSample / 8);
InputType.Format.nSamplesPerSec = mDevice->Frequency;
InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec *
InputType.Format.nBlockAlign;
InputType.Format.cbSize = 0;
InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample;
if(mDevice->FmtType == DevFmtFloat)
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
else
InputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
if(InputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat)
{
InputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
}
ALuint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
DSCBUFFERDESC DSCBDescription{};
DSCBDescription.dwSize = sizeof(DSCBDescription);
DSCBDescription.dwFlags = 0;
DSCBDescription.dwBufferBytes = samples * InputType.Format.nBlockAlign;
DSCBDescription.lpwfxFormat = &InputType.Format;
//DirectSoundCapture Init code
hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr);
if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
if(FAILED(hr))
{
mRing = nullptr;
if(mDSCbuffer)
mDSCbuffer->Release();
mDSCbuffer = nullptr;
if(mDSC)
mDSC->Release();
mDSC = nullptr;
throw al::backend_exception{ALC_INVALID_VALUE, "Device init failed: 0x%08lx", hr};
}
mBufferBytes = DSCBDescription.dwBufferBytes;
SetDefaultWFXChannelOrder(mDevice);
mDevice->DeviceName = name;
}
bool DSoundCapture::start()
{
HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
if(FAILED(hr))
{
ERR("start failed: 0x%08lx\n", hr);
aluHandleDisconnect(mDevice, "Failure starting capture: 0x%lx", hr);
return false;
}
return true;
}
void DSoundCapture::stop()
{
HRESULT hr{mDSCbuffer->Stop()};
if(FAILED(hr))
{
ERR("stop failed: 0x%08lx\n", hr);
aluHandleDisconnect(mDevice, "Failure stopping capture: 0x%lx", hr);
}
}
ALCenum DSoundCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
ALCuint DSoundCapture::availableSamples()
{
if(!mDevice->Connected.load(std::memory_order_acquire))
return static_cast<ALCuint>(mRing->readSpace());
ALuint FrameSize{mDevice->frameSizeFromFmt()};
DWORD BufferBytes{mBufferBytes};
DWORD LastCursor{mCursor};
DWORD ReadCursor{};
void *ReadPtr1{}, *ReadPtr2{};
DWORD ReadCnt1{}, ReadCnt2{};
HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
if(SUCCEEDED(hr))
{
DWORD NumBytes{(ReadCursor-LastCursor + BufferBytes) % BufferBytes};
if(!NumBytes) return static_cast<ALCubyte>(mRing->readSpace());
hr = mDSCbuffer->Lock(LastCursor, NumBytes, &ReadPtr1, &ReadCnt1, &ReadPtr2, &ReadCnt2, 0);
}
if(SUCCEEDED(hr))
{
mRing->write(ReadPtr1, ReadCnt1/FrameSize);
if(ReadPtr2 != nullptr && ReadCnt2 > 0)
mRing->write(ReadPtr2, ReadCnt2/FrameSize);
hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2);
mCursor = (LastCursor+ReadCnt1+ReadCnt2) % BufferBytes;
}
if(FAILED(hr))
{
ERR("update failed: 0x%08lx\n", hr);
aluHandleDisconnect(mDevice, "Failure retrieving capture data: 0x%lx", hr);
}
return static_cast<ALCuint>(mRing->readSpace());
}
} // namespace
BackendFactory &DSoundBackendFactory::getFactory()
{
static DSoundBackendFactory factory{};
return factory;
}
bool DSoundBackendFactory::init()
{
#ifdef HAVE_DYNLOAD
if(!ds_handle)
{
ds_handle = LoadLib("dsound.dll");
if(!ds_handle)
{
ERR("Failed to load dsound.dll\n");
return false;
}
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(ds_handle, #f)); \
if(!p##f) \
{ \
CloseLib(ds_handle); \
ds_handle = nullptr; \
return false; \
} \
} while(0)
LOAD_FUNC(DirectSoundCreate);
LOAD_FUNC(DirectSoundEnumerateW);
LOAD_FUNC(DirectSoundCaptureCreate);
LOAD_FUNC(DirectSoundCaptureEnumerateW);
#undef LOAD_FUNC
}
#endif
return true;
}
bool DSoundBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void DSoundBackendFactory::probe(DevProbe type, std::string *outnames)
{
auto add_device = [outnames](const DevMap &entry) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
outnames->append(entry.name.c_str(), entry.name.length()+1);
};
/* Initialize COM to prevent name truncation */
HRESULT hr;
HRESULT hrcom{CoInitialize(nullptr)};
switch(type)
{
case DevProbe::Playback:
PlaybackDevices.clear();
hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case DevProbe::Capture:
CaptureDevices.clear();
hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices);
if(FAILED(hr))
ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
if(SUCCEEDED(hrcom))
CoUninitialize();
}
BackendPtr DSoundBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new DSoundPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new DSoundCapture{device}};
return nullptr;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_DSOUND_H
#define BACKENDS_DSOUND_H
#include "backends/base.h"
struct DSoundBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_DSOUND_H */
-532
View File
@@ -1,532 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/jack.h"
#include <cstdlib>
#include <cstdio>
#include <memory.h>
#include <thread>
#include <functional>
#include "alcmain.h"
#include "alu.h"
#include "alconfig.h"
#include "alexcpt.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
namespace {
constexpr ALCchar jackDevice[] = "JACK Default";
#ifdef HAVE_DYNLOAD
#define JACK_FUNCS(MAGIC) \
MAGIC(jack_client_open); \
MAGIC(jack_client_close); \
MAGIC(jack_client_name_size); \
MAGIC(jack_get_client_name); \
MAGIC(jack_connect); \
MAGIC(jack_activate); \
MAGIC(jack_deactivate); \
MAGIC(jack_port_register); \
MAGIC(jack_port_unregister); \
MAGIC(jack_port_get_buffer); \
MAGIC(jack_port_name); \
MAGIC(jack_get_ports); \
MAGIC(jack_free); \
MAGIC(jack_get_sample_rate); \
MAGIC(jack_set_error_function); \
MAGIC(jack_set_process_callback); \
MAGIC(jack_set_buffer_size_callback); \
MAGIC(jack_set_buffer_size); \
MAGIC(jack_get_buffer_size);
void *jack_handle;
#define MAKE_FUNC(f) decltype(f) * p##f
JACK_FUNCS(MAKE_FUNC);
decltype(jack_error_callback) * pjack_error_callback;
#undef MAKE_FUNC
#ifndef IN_IDE_PARSER
#define jack_client_open pjack_client_open
#define jack_client_close pjack_client_close
#define jack_client_name_size pjack_client_name_size
#define jack_get_client_name pjack_get_client_name
#define jack_connect pjack_connect
#define jack_activate pjack_activate
#define jack_deactivate pjack_deactivate
#define jack_port_register pjack_port_register
#define jack_port_unregister pjack_port_unregister
#define jack_port_get_buffer pjack_port_get_buffer
#define jack_port_name pjack_port_name
#define jack_get_ports pjack_get_ports
#define jack_free pjack_free
#define jack_get_sample_rate pjack_get_sample_rate
#define jack_set_error_function pjack_set_error_function
#define jack_set_process_callback pjack_set_process_callback
#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
#define jack_set_buffer_size pjack_set_buffer_size
#define jack_get_buffer_size pjack_get_buffer_size
#define jack_error_callback (*pjack_error_callback)
#endif
#endif
jack_options_t ClientOptions = JackNullOption;
ALCboolean jack_load()
{
ALCboolean error = ALC_FALSE;
#ifdef HAVE_DYNLOAD
if(!jack_handle)
{
std::string missing_funcs;
#ifdef _WIN32
#define JACKLIB "libjack.dll"
#else
#define JACKLIB "libjack.so.0"
#endif
jack_handle = LoadLib(JACKLIB);
if(!jack_handle)
{
WARN("Failed to load %s\n", JACKLIB);
return ALC_FALSE;
}
error = ALC_FALSE;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = ALC_TRUE; \
missing_funcs += "\n" #f; \
} \
} while(0)
JACK_FUNCS(LOAD_FUNC);
#undef LOAD_FUNC
/* Optional symbols. These don't exist in all versions of JACK. */
#define LOAD_SYM(f) p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f))
LOAD_SYM(jack_error_callback);
#undef LOAD_SYM
if(error)
{
WARN("Missing expected functions:%s\n", missing_funcs.c_str());
CloseLib(jack_handle);
jack_handle = nullptr;
}
}
#endif
return !error;
}
struct JackPlayback final : public BackendBase {
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~JackPlayback() override;
int process(jack_nframes_t numframes) noexcept;
static int processC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
ClockLatency getClockLatency() override;
jack_client_t *mClient{nullptr};
jack_port_t *mPort[MAX_OUTPUT_CHANNELS]{};
std::atomic<bool> mPlaying{false};
RingBufferPtr mRing;
al::semaphore mSem;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(JackPlayback)
};
JackPlayback::~JackPlayback()
{
if(!mClient)
return;
std::for_each(std::begin(mPort), std::end(mPort),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
std::fill(std::begin(mPort), std::end(mPort), nullptr);
jack_client_close(mClient);
mClient = nullptr;
}
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
jack_default_audio_sample_t *out[MAX_OUTPUT_CHANNELS];
size_t numchans{0};
for(auto port : mPort)
{
if(!port) break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
jack_nframes_t total{0};
if LIKELY(mPlaying.load(std::memory_order_acquire))
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<ALuint>(data.first.len))};
auto write_first = [&data,numchans,todo](float *outbuf) -> float*
{
const float *RESTRICT in = reinterpret_cast<float*>(data.first.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.first.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out, out+numchans, out, write_first);
total += todo;
todo = minu(numframes-total, static_cast<ALuint>(data.second.len));
if(todo > 0)
{
auto write_second = [&data,numchans,todo](float *outbuf) -> float*
{
const float *RESTRICT in = reinterpret_cast<float*>(data.second.buf);
auto deinterlace_input = [&in,numchans]() noexcept -> float
{
float ret{*in};
in += numchans;
return ret;
};
std::generate_n(outbuf, todo, deinterlace_input);
data.second.buf += sizeof(float);
return outbuf + todo;
};
std::transform(out, out+numchans, out, write_second);
total += todo;
}
mRing->readAdvance(total);
mSem.post();
}
if(numframes > total)
{
jack_nframes_t todo{numframes - total};
auto clear_buf = [todo](ALfloat *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
std::for_each(out, out+numchans, clear_buf);
}
return 0;
}
int JackPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const size_t frame_step{mDevice->channelsFromFmt()};
std::unique_lock<JackPlayback> dlock{*this};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
{
dlock.unlock();
mSem.wait();
dlock.lock();
continue;
}
auto data = mRing->getWriteVector();
auto todo = static_cast<ALuint>(data.first.len + data.second.len);
todo -= todo%mDevice->UpdateSize;
ALuint len1{minu(static_cast<ALuint>(data.first.len), todo)};
ALuint len2{minu(static_cast<ALuint>(data.second.len), todo-len1)};
aluMixData(mDevice, data.first.buf, len1, frame_step);
if(len2 > 0)
aluMixData(mDevice, data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
}
return 0;
}
void JackPlayback::open(const ALCchar *name)
{
if(!name)
name = jackDevice;
else if(strcmp(name, jackDevice) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
const char *client_name{"alsoft"};
jack_status_t status;
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open client connection: 0x%02x",
status};
if((status&JackServerStarted))
TRACE("JACK server started\n");
if((status&JackNameNotUnique))
{
client_name = jack_get_client_name(mClient);
TRACE("Client name not unique, got '%s' instead\n", client_name);
}
jack_set_process_callback(mClient, &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
bool JackPlayback::reset()
{
std::for_each(std::begin(mPort), std::end(mPort),
[this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); }
);
std::fill(std::begin(mPort), std::end(mPort), nullptr);
/* Ignore the requested buffer metrics and just keep one JACK-sized buffer
* ready for when requested.
*/
mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
const char *devname{mDevice->DeviceName.c_str()};
ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
/* Force 32-bit float output. */
mDevice->FmtType = DevFmtFloat;
auto ports_end = std::begin(mPort) + mDevice->channelsFromFmt();
auto bad_port = std::find_if_not(std::begin(mPort), ports_end,
[this](jack_port_t *&port) -> bool
{
std::string name{"channel_" + std::to_string(&port - mPort + 1)};
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
return port != nullptr;
}
);
if(bad_port != ports_end)
{
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == std::begin(mPort)) return false;
if(bad_port == std::begin(mPort)+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
*bad_port = nullptr;
}
mDevice->FmtChans = DevFmtStereo;
}
}
SetDefaultChannelOrder(mDevice);
return true;
}
bool JackPlayback::start()
{
if(jack_activate(mClient))
{
ERR("Failed to activate client\n");
return false;
}
const char **ports{jack_get_ports(mClient, nullptr, nullptr,
JackPortIsPhysical|JackPortIsInput)};
if(ports == nullptr)
{
ERR("No physical playback ports found\n");
jack_deactivate(mClient);
return false;
}
std::mismatch(std::begin(mPort), std::end(mPort), ports,
[this](const jack_port_t *port, const char *pname) -> bool
{
if(!port) return false;
if(!pname)
{
ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
return false;
}
if(jack_connect(mClient, jack_port_name(port), pname))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
pname);
return true;
});
jack_free(ports);
/* Reconfigure buffer metrics in case the server changed it since the reset
* (it won't change again after jack_activate), then allocate the ring
* buffer with the appropriate size.
*/
mDevice->Frequency = jack_get_sample_rate(mClient);
mDevice->UpdateSize = jack_get_buffer_size(mClient);
mDevice->BufferSize = mDevice->UpdateSize * 2;
const char *devname{mDevice->DeviceName.c_str()};
ALuint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
mRing = nullptr;
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
try {
mPlaying.store(true, std::memory_order_release);
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
return false;
}
void JackPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mSem.post();
mThread.join();
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
}
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<JackPlayback> _{*this};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace()};
ret.Latency /= mDevice->Frequency;
return ret;
}
void jack_msg_handler(const char *message)
{
WARN("%s\n", message);
}
} // namespace
bool JackBackendFactory::init()
{
if(!jack_load())
return false;
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
jack_set_error_function(jack_msg_handler);
jack_status_t status;
jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
jack_set_error_function(old_error_cb);
if(!client)
{
WARN("jack_client_open() failed, 0x%02x\n", status);
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
ERR("Unable to connect to JACK server\n");
return false;
}
jack_client_close(client);
return true;
}
bool JackBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
void JackBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(jackDevice, sizeof(jackDevice));
break;
case DevProbe::Capture:
break;
}
}
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}};
return nullptr;
}
BackendFactory &JackBackendFactory::getFactory()
{
static JackBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_JACK_H
#define BACKENDS_JACK_H
#include "backends/base.h"
struct JackBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_JACK_H */
-79
View File
@@ -1,79 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2011 by Chris Robinson
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/loopback.h"
#include "alcmain.h"
#include "alu.h"
namespace {
struct LoopbackBackend final : public BackendBase {
LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
void LoopbackBackend::open(const ALCchar *name)
{
mDevice->DeviceName = name;
}
bool LoopbackBackend::reset()
{
SetDefaultWFXChannelOrder(mDevice);
return true;
}
bool LoopbackBackend::start()
{ return true; }
void LoopbackBackend::stop()
{ }
} // namespace
bool LoopbackBackendFactory::init()
{ return true; }
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
void LoopbackBackendFactory::probe(DevProbe, std::string*)
{ }
BackendPtr LoopbackBackendFactory::createBackend(ALCdevice *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }
BackendFactory &LoopbackBackendFactory::getFactory()
{
static LoopbackBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_LOOPBACK_H
#define BACKENDS_LOOPBACK_H
#include "backends/base.h"
struct LoopbackBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_LOOPBACK_H */
-182
View File
@@ -1,182 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2010 by Chris Robinson
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/null.h"
#include <exception>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <functional>
#include <thread>
#include "alcmain.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alu.h"
#include "logging.h"
#include "threads.h"
namespace {
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
constexpr ALCchar nullDevice[] = "No Output";
struct NullBackend final : public BackendBase {
NullBackend(ALCdevice *device) noexcept : BackendBase{device} { }
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(NullBackend)
};
int NullBackend::mixerProc()
{
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
int64_t done{0};
auto start = std::chrono::steady_clock::now();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
/* This converts from nanoseconds to nanosamples, then to samples. */
int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()};
if(avail-done < mDevice->UpdateSize)
{
std::this_thread::sleep_for(restTime);
continue;
}
while(avail-done >= mDevice->UpdateSize)
{
std::lock_guard<NullBackend> _{*this};
aluMixData(mDevice, nullptr, mDevice->UpdateSize, 0u);
done += mDevice->UpdateSize;
}
/* For every completed second, increment the start time and reduce the
* samples done. This prevents the difference between the start time
* and current time from growing too large, while maintaining the
* correct number of samples to render.
*/
if(done >= mDevice->Frequency)
{
seconds s{done/mDevice->Frequency};
start += s;
done -= mDevice->Frequency*s.count();
}
}
return 0;
}
void NullBackend::open(const ALCchar *name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
mDevice->DeviceName = name;
}
bool NullBackend::reset()
{
SetDefaultWFXChannelOrder(mDevice);
return true;
}
bool NullBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void NullBackend::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
}
} // namespace
bool NullBackendFactory::init()
{ return true; }
bool NullBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback); }
void NullBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(nullDevice, sizeof(nullDevice));
break;
case DevProbe::Capture:
break;
}
}
BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new NullBackend{device}};
return nullptr;
}
BackendFactory &NullBackendFactory::getFactory()
{
static NullBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_NULL_H
#define BACKENDS_NULL_H
#include "backends/base.h"
struct NullBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_NULL_H */
-966
View File
@@ -1,966 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* This is an OpenAL backend for Android using the native audio APIs based on
* OpenSL ES 1.0.1. It is based on source code for the native-audio sample app
* bundled with NDK.
*/
#include "config.h"
#include "backends/opensl.h"
#include <stdlib.h>
#include <jni.h>
#include <new>
#include <array>
#include <cstring>
#include <thread>
#include <functional>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "compat.h"
#include "endiantest.h"
#include "ringbuffer.h"
#include "threads.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
namespace {
/* Helper macros */
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
constexpr ALCchar opensl_device[] = "OpenSL";
SLuint32 GetChannelMask(DevFmtChannels chans)
{
switch(chans)
{
case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT;
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
SL_SPEAKER_SIDE_RIGHT;
case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT;
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
case DevFmtAmbi3D:
break;
}
return 0;
}
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLuint32 GetTypeRepresentation(DevFmtType type)
{
switch(type)
{
case DevFmtUByte:
case DevFmtUShort:
case DevFmtUInt:
return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
case DevFmtByte:
case DevFmtShort:
case DevFmtInt:
return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
case DevFmtFloat:
return SL_ANDROID_PCM_REPRESENTATION_FLOAT;
}
return 0;
}
#endif
const char *res_str(SLresult result)
{
switch(result)
{
case SL_RESULT_SUCCESS: return "Success";
case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
case SL_RESULT_RESOURCE_ERROR: return "Resource error";
case SL_RESULT_RESOURCE_LOST: return "Resource lost";
case SL_RESULT_IO_ERROR: return "I/O error";
case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
case SL_RESULT_INTERNAL_ERROR: return "Internal error";
case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
case SL_RESULT_CONTROL_LOST: return "Control lost";
#ifdef SL_RESULT_READONLY
case SL_RESULT_READONLY: return "ReadOnly";
#endif
#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
#endif
#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
#endif
}
return "Unknown error code";
}
#define PRINTERR(x, s) do { \
if UNLIKELY((x) != SL_RESULT_SUCCESS) \
ERR("%s: %s\n", (s), res_str((x))); \
} while(0)
struct OpenSLPlayback final : public BackendBase {
OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~OpenSLPlayback() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
ClockLatency getClockLatency() override;
/* engine interfaces */
SLObjectItf mEngineObj{nullptr};
SLEngineItf mEngine{nullptr};
/* output mix interfaces */
SLObjectItf mOutputMix{nullptr};
/* buffer queue player interfaces */
SLObjectItf mBufferQueueObj{nullptr};
RingBufferPtr mRing{nullptr};
al::semaphore mSem;
ALuint mFrameSize{0};
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OpenSLPlayback)
};
OpenSLPlayback::~OpenSLPlayback()
{
if(mBufferQueueObj)
VCALL0(mBufferQueueObj,Destroy)();
mBufferQueueObj = nullptr;
if(mOutputMix)
VCALL0(mOutputMix,Destroy)();
mOutputMix = nullptr;
if(mEngineObj)
VCALL0(mEngineObj,Destroy)();
mEngineObj = nullptr;
mEngine = nullptr;
}
/* this callback handler is called every time a buffer finishes playing */
void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
{
/* A note on the ringbuffer usage: The buffer queue seems to hold on to the
* pointer passed to the Enqueue method, rather than copying the audio.
* Consequently, the ringbuffer contains the audio that is currently queued
* and waiting to play. This process() callback is called when a buffer is
* finished, so we simply move the read pointer up to indicate the space is
* available for writing again, and wake up the mixer thread to mix and
* queue more audio.
*/
mRing->readAdvance(1);
mSem.post();
}
int OpenSLPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
SLPlayItf player;
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
}
const size_t frame_step{mDevice->channelsFromFmt()};
std::unique_lock<OpenSLPlayback> dlock{*this};
if(SL_RESULT_SUCCESS != result)
aluHandleDisconnect(mDevice, "Failed to get playback buffer: 0x%08x", result);
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() == 0)
{
SLuint32 state{0};
result = VCALL(player,GetPlayState)(&state);
PRINTERR(result, "player->GetPlayState");
if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
PRINTERR(result, "player->SetPlayState");
}
if(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to start platback: 0x%08x", result);
break;
}
if(mRing->writeSpace() == 0)
{
dlock.unlock();
mSem.wait();
dlock.lock();
continue;
}
}
auto data = mRing->getWriteVector();
aluMixData(mDevice, data.first.buf,
static_cast<ALuint>(data.first.len*mDevice->UpdateSize), frame_step);
if(data.second.len > 0)
aluMixData(mDevice, data.second.buf,
static_cast<ALuint>(data.second.len*mDevice->UpdateSize), frame_step);
size_t todo{data.first.len + data.second.len};
mRing->writeAdvance(todo);
for(size_t i{0};i < todo;i++)
{
if(!data.first.len)
{
data.first = data.second;
data.second.buf = nullptr;
data.second.len = 0;
}
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
PRINTERR(result, "bufferQueue->Enqueue");
if(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to queue audio: 0x%08x", result);
break;
}
data.first.len--;
data.first.buf += mDevice->UpdateSize*mFrameSize;
}
}
return 0;
}
void OpenSLPlayback::open(const ALCchar *name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
// create engine
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
PRINTERR(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
PRINTERR(result, "engine->CreateOutputMix");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "outputMix->Realize");
}
if(SL_RESULT_SUCCESS != result)
{
if(mOutputMix)
VCALL0(mOutputMix,Destroy)();
mOutputMix = nullptr;
if(mEngineObj)
VCALL0(mEngineObj,Destroy)();
mEngineObj = nullptr;
mEngine = nullptr;
throw al::backend_exception{ALC_INVALID_VALUE,
"Failed to initialize OpenSL device: 0x%08x", result};
}
mDevice->DeviceName = name;
}
bool OpenSLPlayback::reset()
{
SLresult result;
if(mBufferQueueObj)
VCALL0(mBufferQueueObj,Destroy)();
mBufferQueueObj = nullptr;
mRing = nullptr;
#if 0
if(!mDevice->Flags.get<FrequencyRequest>())
{
/* FIXME: Disabled until I figure out how to get the Context needed for
* the getSystemService call.
*/
JNIEnv *env = Android_GetJNIEnv();
jobject jctx = Android_GetContext();
/* Get necessary stuff for using java.lang.Integer,
* android.content.Context, and android.media.AudioManager.
*/
jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
"parseInt", "(Ljava/lang/String;)I"
);
TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
"AUDIO_SERVICE", "Ljava/lang/String;"
);
jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
"getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
);
TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
ctx_cls, ctx_audsvc, ctx_getSysSvc);
jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
"PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
);
jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
);
TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
const char *strchars;
jstring strobj;
/* Now make the calls. */
//AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
//int sampleRate = Integer.parseInt(srateStr);
sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
if(!sampleRate) sampleRate = device->Frequency;
else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
}
#endif
mDevice->FmtChans = DevFmtStereo;
mDevice->FmtType = DevFmtShort;
SetDefaultWFXChannelOrder(mDevice);
mFrameSize = mDevice->frameSizeFromFmt();
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
SLDataLocator_OutputMix loc_outmix{};
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
loc_outmix.outputMix = mOutputMix;
SLDataSink audioSnk{};
audioSnk.pLocator = &loc_outmix;
audioSnk.pFormat = nullptr;
SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
SLDataSource audioSrc{};
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN;
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
audioSrc.pLocator = &loc_bufq;
audioSrc.pFormat = &format_pcm_ex;
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
if(SL_RESULT_SUCCESS != result)
#endif
{
/* Alter sample type according to what SLDataFormat_PCM can support. */
switch(mDevice->FmtType)
{
case DevFmtByte: mDevice->FmtType = DevFmtUByte; break;
case DevFmtUInt: mDevice->FmtType = DevFmtInt; break;
case DevFmtFloat:
case DevFmtUShort: mDevice->FmtType = DevFmtShort; break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
break;
}
SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
SL_BYTEORDER_BIGENDIAN;
audioSrc.pLocator = &loc_bufq;
audioSrc.pFormat = &format_pcm;
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
ids.data(), reqs.data());
PRINTERR(result, "engine->CreateAudioPlayer");
}
if(SL_RESULT_SUCCESS == result)
{
/* Set the stream type to "media" (games, music, etc), if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
sizeof(streamType));
PRINTERR(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
result = SL_RESULT_SUCCESS;
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "bufferQueue->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
const ALuint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
}
if(SL_RESULT_SUCCESS != result)
{
if(mBufferQueueObj)
VCALL0(mBufferQueueObj,Destroy)();
mBufferQueueObj = nullptr;
return false;
}
return true;
}
bool OpenSLPlayback::start()
{
mRing->reset();
SLAndroidSimpleBufferQueueItf bufferQueue;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS != result)
return false;
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
if(SL_RESULT_SUCCESS != result) return false;
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
return true;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void OpenSLPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mSem.post();
mThread.join();
SLPlayItf player;
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
PRINTERR(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
PRINTERR(result, "player->SetPlayState");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "bufferQueue->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL0(bufferQueue,Clear)();
PRINTERR(result, "bufferQueue->Clear");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
PRINTERR(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
SLAndroidSimpleBufferQueueState state;
do {
std::this_thread::yield();
result = VCALL(bufferQueue,GetState)(&state);
} while(SL_RESULT_SUCCESS == result && state.count > 0);
PRINTERR(result, "bufferQueue->GetState");
}
}
ClockLatency OpenSLPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<OpenSLPlayback> _{*this};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
ret.Latency /= mDevice->Frequency;
return ret;
}
struct OpenSLCapture final : public BackendBase {
OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
~OpenSLCapture() override;
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
{ static_cast<OpenSLCapture*>(context)->process(bq); }
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
/* engine interfaces */
SLObjectItf mEngineObj{nullptr};
SLEngineItf mEngine;
/* recording interfaces */
SLObjectItf mRecordObj{nullptr};
RingBufferPtr mRing{nullptr};
ALCuint mSplOffset{0u};
ALuint mFrameSize{0};
DEF_NEWDEL(OpenSLCapture)
};
OpenSLCapture::~OpenSLCapture()
{
if(mRecordObj)
VCALL0(mRecordObj,Destroy)();
mRecordObj = nullptr;
if(mEngineObj)
VCALL0(mEngineObj,Destroy)();
mEngineObj = nullptr;
mEngine = nullptr;
}
void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
{
/* A new chunk has been written into the ring buffer, advance it. */
mRing->writeAdvance(1);
}
void OpenSLCapture::open(const ALCchar* name)
{
if(!name)
name = opensl_device;
else if(strcmp(name, opensl_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
PRINTERR(result, "slCreateEngine");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "engine->Realize");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
PRINTERR(result, "engine->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
mFrameSize = mDevice->frameSizeFromFmt();
/* Ensure the total length is at least 100ms */
ALuint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
ALuint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
mDevice->Frequency/100*5)};
ALuint num_updates{(length+update_len-1) / update_len};
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
mDevice->UpdateSize = update_len;
mDevice->BufferSize = static_cast<ALuint>(mRing->writeSpace() * update_len);
}
if(SL_RESULT_SUCCESS == result)
{
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
SLDataLocator_IODevice loc_dev{};
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
loc_dev.device = nullptr;
SLDataSource audioSrc{};
audioSrc.pLocator = &loc_dev;
audioSrc.pFormat = nullptr;
SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
SLDataSink audioSnk{};
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm_ex.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
SL_BYTEORDER_BIGENDIAN;
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
audioSnk.pLocator = &loc_bq;
audioSnk.pFormat = &format_pcm_ex;
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
ids.size(), ids.data(), reqs.data());
if(SL_RESULT_SUCCESS != result)
#endif
{
/* Fallback to SLDataFormat_PCM only if it supports the desired
* sample type.
*/
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort
|| mDevice->FmtType == DevFmtInt)
{
SLDataFormat_PCM format_pcm{};
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = mDevice->channelsFromFmt();
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
format_pcm.containerSize = format_pcm.bitsPerSample;
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
format_pcm.endianness = IS_LITTLE_ENDIAN ? SL_BYTEORDER_LITTLEENDIAN :
SL_BYTEORDER_BIGENDIAN;
audioSnk.pLocator = &loc_bq;
audioSnk.pFormat = &format_pcm;
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
ids.size(), ids.data(), reqs.data());
}
PRINTERR(result, "engine->CreateAudioRecorder");
}
}
if(SL_RESULT_SUCCESS == result)
{
/* Set the record preset to "generic", if possible. */
SLAndroidConfigurationItf config;
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
if(SL_RESULT_SUCCESS == result)
{
SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
sizeof(preset));
PRINTERR(result, "config->SetConfiguration");
}
/* Clear any error since this was optional. */
result = SL_RESULT_SUCCESS;
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
PRINTERR(result, "recordObj->Realize");
}
SLAndroidSimpleBufferQueueItf bufferQueue;
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
PRINTERR(result, "recordObj->GetInterface");
}
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
PRINTERR(result, "bufferQueue->RegisterCallback");
}
if(SL_RESULT_SUCCESS == result)
{
const ALuint chunk_size{mDevice->UpdateSize * mFrameSize};
auto data = mRing->getWriteVector();
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
}
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
{
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
PRINTERR(result, "bufferQueue->Enqueue");
}
}
if(SL_RESULT_SUCCESS != result)
{
if(mRecordObj)
VCALL0(mRecordObj,Destroy)();
mRecordObj = nullptr;
if(mEngineObj)
VCALL0(mEngineObj,Destroy)();
mEngineObj = nullptr;
mEngine = nullptr;
throw al::backend_exception{ALC_INVALID_VALUE,
"Failed to initialize OpenSL device: 0x%08x", result};
}
mDevice->DeviceName = name;
}
bool OpenSLCapture::start()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
PRINTERR(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
PRINTERR(result, "record->SetRecordState");
}
if(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to start capture: 0x%08x", result);
return false;
}
return true;
}
void OpenSLCapture::stop()
{
SLRecordItf record;
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
PRINTERR(result, "recordObj->GetInterface");
if(SL_RESULT_SUCCESS == result)
{
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
PRINTERR(result, "record->SetRecordState");
}
}
ALCenum OpenSLCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
SLAndroidSimpleBufferQueueItf bufferQueue{};
if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
{
const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&bufferQueue)};
PRINTERR(result, "recordObj->GetInterface");
if UNLIKELY(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to get capture buffer queue: 0x%08x", result);
bufferQueue = nullptr;
}
}
const ALuint update_size{mDevice->UpdateSize};
const ALuint chunk_size{update_size * mFrameSize};
/* Read the desired samples from the ring buffer then advance its read
* pointer.
*/
auto data = mRing->getReadVector();
for(ALCuint i{0};i < samples;)
{
const ALCuint rem{minu(samples - i, update_size - mSplOffset)};
std::copy_n(data.first.buf + mSplOffset*mFrameSize, rem*mFrameSize, buffer + i*mFrameSize);
mSplOffset += rem;
if(mSplOffset == update_size)
{
/* Finished a chunk, reset the offset and advance the read pointer. */
mSplOffset = 0;
mRing->readAdvance(1);
if LIKELY(bufferQueue)
{
const SLresult result{VCALL(bufferQueue,Enqueue)(data.first.buf, chunk_size)};
PRINTERR(result, "bufferQueue->Enqueue");
if UNLIKELY(SL_RESULT_SUCCESS != result)
{
aluHandleDisconnect(mDevice, "Failed to update capture buffer: 0x%08x",
result);
bufferQueue = nullptr;
}
}
data.first.len--;
if(!data.first.len)
data.first = data.second;
else
data.first.buf += chunk_size;
}
i += rem;
}
return ALC_NO_ERROR;
}
ALCuint OpenSLCapture::availableSamples()
{ return static_cast<ALuint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
} // namespace
bool OSLBackendFactory::init() { return true; }
bool OSLBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void OSLBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(opensl_device, sizeof(opensl_device));
break;
}
}
BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OpenSLPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new OpenSLCapture{device}};
return nullptr;
}
BackendFactory &OSLBackendFactory::getFactory()
{
static OSLBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_OSL_H
#define BACKENDS_OSL_H
#include "backends/base.h"
struct OSLBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_OSL_H */
-713
View File
@@ -1,713 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/oss.h"
#include <fcntl.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include <memory>
#include <new>
#include <string>
#include <thread>
#include <utility>
#include "AL/al.h"
#include "alcmain.h"
#include "alconfig.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "alu.h"
#include "logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sys/soundcard.h>
/*
* The OSS documentation talks about SOUND_MIXER_READ, but the header
* only contains MIXER_READ. Play safe. Same for WRITE.
*/
#ifndef SOUND_MIXER_READ
#define SOUND_MIXER_READ MIXER_READ
#endif
#ifndef SOUND_MIXER_WRITE
#define SOUND_MIXER_WRITE MIXER_WRITE
#endif
#if defined(SOUND_VERSION) && (SOUND_VERSION < 0x040000)
#define ALC_OSS_COMPAT
#endif
#ifndef SNDCTL_AUDIOINFO
#define ALC_OSS_COMPAT
#endif
/*
* FreeBSD strongly discourages the use of specific devices,
* such as those returned in oss_audioinfo.devnode
*/
#ifdef __FreeBSD__
#define ALC_OSS_DEVNODE_TRUC
#endif
namespace {
constexpr char DefaultName[] = "OSS Default";
std::string DefaultPlayback{"/dev/dsp"};
std::string DefaultCapture{"/dev/dsp"};
struct DevMap {
std::string name;
std::string device_name;
};
bool checkName(const al::vector<DevMap> &list, const std::string &name)
{
return std::find_if(list.cbegin(), list.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
) != list.cend();
}
al::vector<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
#ifdef ALC_OSS_COMPAT
#define DSP_CAP_OUTPUT 0x00020000
#define DSP_CAP_INPUT 0x00010000
void ALCossListPopulate(al::vector<DevMap> *devlist, int type)
{
devlist->emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback});
}
#else
void ALCossListAppend(al::vector<DevMap> *list, const char *handle, size_t hlen, const char *path, size_t plen)
{
#ifdef ALC_OSS_DEVNODE_TRUC
for(size_t i{0};i < plen;i++)
{
if(path[i] == '.')
{
if(strncmp(path + i, handle + hlen + i - plen, plen - i) == 0)
hlen = hlen + i - plen;
plen = i;
}
}
#endif
if(handle[0] == '\0')
{
handle = path;
hlen = plen;
}
std::string basename{handle, hlen};
basename.erase(std::find(basename.begin(), basename.end(), '\0'), basename.end());
std::string devname{path, plen};
devname.erase(std::find(devname.begin(), devname.end(), '\0'), devname.end());
auto iter = std::find_if(list->cbegin(), list->cend(),
[&devname](const DevMap &entry) -> bool
{ return entry.device_name == devname; }
);
if(iter != list->cend())
return;
int count{1};
std::string newname{basename};
while(checkName(PlaybackDevices, newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
list->emplace_back(DevMap{std::move(newname), std::move(devname)});
const DevMap &entry = list->back();
TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str());
}
void ALCossListPopulate(al::vector<DevMap> *devlist, int type_flag)
{
int fd{open("/dev/mixer", O_RDONLY)};
if(fd < 0)
{
TRACE("Could not open /dev/mixer: %s\n", strerror(errno));
goto done;
}
oss_sysinfo si;
if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1)
{
TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno));
goto done;
}
for(int i{0};i < si.numaudios;i++)
{
oss_audioinfo ai;
ai.dev = i;
if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1)
{
ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno));
continue;
}
if(!(ai.caps&type_flag) || ai.devnode[0] == '\0')
continue;
const char *handle;
size_t len;
if(ai.handle[0] != '\0')
{
len = strnlen(ai.handle, sizeof(ai.handle));
handle = ai.handle;
}
else
{
len = strnlen(ai.name, sizeof(ai.name));
handle = ai.name;
}
ALCossListAppend(devlist, handle, len, ai.devnode,
strnlen(ai.devnode, sizeof(ai.devnode)));
}
done:
if(fd >= 0)
close(fd);
fd = -1;
const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()};
auto iter = std::find_if(devlist->cbegin(), devlist->cend(),
[defdev](const DevMap &entry) -> bool
{ return entry.device_name == defdev; }
);
if(iter == devlist->cend())
devlist->insert(devlist->begin(), DevMap{DefaultName, defdev});
else
{
DevMap entry{std::move(*iter)};
devlist->erase(iter);
devlist->insert(devlist->begin(), std::move(entry));
}
devlist->shrink_to_fit();
}
#endif
ALCuint log2i(ALCuint x)
{
ALCuint y{0};
while(x > 1)
{
x >>= 1;
y++;
}
return y;
}
struct OSSPlayback final : public BackendBase {
OSSPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~OSSPlayback() override;
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
int mFd{-1};
al::vector<ALubyte> mMixData;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSSPlayback)
};
OSSPlayback::~OSSPlayback()
{
if(mFd != -1)
close(mFd);
mFd = -1;
}
int OSSPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const size_t frame_step{mDevice->channelsFromFmt()};
const ALuint frame_size{mDevice->frameSizeFromFmt()};
std::unique_lock<OSSPlayback> dlock{*this};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
dlock.unlock();
int pret{poll(&pollitem, 1, 1000)};
dlock.lock();
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed waiting for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
{
WARN("poll timeout\n");
continue;
}
ALubyte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
aluMixData(mDevice, write_ptr, static_cast<ALuint>(to_write/frame_size), frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed writing playback samples: %s",
strerror(errno));
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
}
}
return 0;
}
void OSSPlayback::open(const ALCchar *name)
{
const char *devname{DefaultPlayback.c_str()};
if(!name)
name = DefaultName;
else
{
if(PlaybackDevices.empty())
ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == PlaybackDevices.cend())
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_WRONLY);
if(mFd == -1)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
strerror(errno)};
mDevice->DeviceName = name;
}
bool OSSPlayback::reset()
{
int ossFormat{};
switch(mDevice->FmtType)
{
case DevFmtByte:
ossFormat = AFMT_S8;
break;
case DevFmtUByte:
ossFormat = AFMT_U8;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
ossFormat = AFMT_S16_NE;
break;
}
ALuint periods{mDevice->BufferSize / mDevice->UpdateSize};
ALuint numChannels{mDevice->channelsFromFmt()};
ALuint ossSpeed{mDevice->Frequency};
ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
ALuint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
const char *err;
#define CHECKERR(func) if((func) < 0) { \
err = #func; \
goto err; \
}
/* Don't fail if SETFRAGMENT fails. We can handle just about anything
* that's reported back via GETOSPACE */
ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize);
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info));
if(0)
{
err:
ERR("%s failed: %s\n", err, strerror(errno));
return false;
}
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
{
ERR("Failed to set %s, got %d channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
numChannels);
return false;
}
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte) ||
(ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte) ||
(ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
{
ERR("Failed to set %s samples, got OSS format %#x\n", DevFmtTypeString(mDevice->FmtType),
ossFormat);
return false;
}
mDevice->Frequency = ossSpeed;
mDevice->UpdateSize = static_cast<ALuint>(info.fragsize) / frameSize;
mDevice->BufferSize = static_cast<ALuint>(info.fragments) * mDevice->UpdateSize;
SetDefaultChannelOrder(mDevice);
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
return true;
}
bool OSSPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void OSSPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
}
struct OSScapture final : public BackendBase {
OSScapture(ALCdevice *device) noexcept : BackendBase{device} { }
~OSScapture() override;
int recordProc();
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
int mFd{-1};
RingBufferPtr mRing{nullptr};
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(OSScapture)
};
OSScapture::~OSScapture()
{
if(mFd != -1)
close(mFd);
mFd = -1;
}
int OSScapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
const ALuint frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLIN;
int sret{poll(&pollitem, 1, 1000)};
if(sret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed to check capture samples: %s", strerror(errno));
break;
}
else if(sret == 0)
{
WARN("poll timeout\n");
continue;
}
auto vec = mRing->getWriteVector();
if(vec.first.len > 0)
{
ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)};
if(amt < 0)
{
ERR("read failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed reading capture samples: %s",
strerror(errno));
break;
}
mRing->writeAdvance(static_cast<ALuint>(amt)/frame_size);
}
}
return 0;
}
void OSScapture::open(const ALCchar *name)
{
const char *devname{DefaultCapture.c_str()};
if(!name)
name = DefaultName;
else
{
if(CaptureDevices.empty())
ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[&name](const DevMap &entry) -> bool
{ return entry.name == name; }
);
if(iter == CaptureDevices.cend())
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s", devname,
strerror(errno)};
int ossFormat{};
switch(mDevice->FmtType)
{
case DevFmtByte:
ossFormat = AFMT_S8;
break;
case DevFmtUByte:
ossFormat = AFMT_U8;
break;
case DevFmtShort:
ossFormat = AFMT_S16_NE;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
ALuint periods{4};
ALuint numChannels{mDevice->channelsFromFmt()};
ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
ALuint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
ALuint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
ALuint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
throw al::backend_exception{ALC_INVALID_VALUE, #func " failed: %s", strerror(errno)}; \
}
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat));
CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels));
CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed));
CHECKERR(ioctl(mFd, SNDCTL_DSP_GETISPACE, &info));
#undef CHECKERR
if(mDevice->channelsFromFmt() != numChannels)
throw al::backend_exception{ALC_INVALID_VALUE,
"Failed to set %s, got %d channels instead", DevFmtChannelsString(mDevice->FmtChans),
numChannels};
if(!((ossFormat == AFMT_S8 && mDevice->FmtType == DevFmtByte)
|| (ossFormat == AFMT_U8 && mDevice->FmtType == DevFmtUByte)
|| (ossFormat == AFMT_S16_NE && mDevice->FmtType == DevFmtShort)))
throw al::backend_exception{ALC_INVALID_VALUE,
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
ossFormat};
mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
mDevice->DeviceName = name;
}
bool OSScapture::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
return true;
}
catch(std::exception& e) {
ERR("Could not create record thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void OSScapture::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
if(ioctl(mFd, SNDCTL_DSP_RESET) != 0)
ERR("Error resetting device: %s\n", strerror(errno));
}
ALCenum OSScapture::captureSamples(al::byte *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
ALCuint OSScapture::availableSamples()
{ return static_cast<ALCuint>(mRing->readSpace()); }
} // namespace
BackendFactory &OSSBackendFactory::getFactory()
{
static OSSBackendFactory factory{};
return factory;
}
bool OSSBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "oss", "device"))
DefaultPlayback = std::move(*devopt);
if(auto capopt = ConfigValueStr(nullptr, "oss", "capture"))
DefaultCapture = std::move(*capopt);
return true;
}
bool OSSBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void OSSBackendFactory::probe(DevProbe type, std::string *outnames)
{
auto add_device = [outnames](const DevMap &entry) -> void
{
#ifdef HAVE_STAT
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
#endif
{
/* Includes null char. */
outnames->append(entry.name.c_str(), entry.name.length()+1);
}
};
switch(type)
{
case DevProbe::Playback:
PlaybackDevices.clear();
ALCossListPopulate(&PlaybackDevices, DSP_CAP_OUTPUT);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case DevProbe::Capture:
CaptureDevices.clear();
ALCossListPopulate(&CaptureDevices, DSP_CAP_INPUT);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
}
BackendPtr OSSBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OSSPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new OSScapture{device}};
return nullptr;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_OSS_H
#define BACKENDS_OSS_H
#include "backends/base.h"
struct OSSBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_OSS_H */
-448
View File
@@ -1,448 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/portaudio.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "alconfig.h"
#include "dynload.h"
#include "ringbuffer.h"
#include <portaudio.h>
namespace {
constexpr ALCchar pa_device[] = "PortAudio Default";
#ifdef HAVE_DYNLOAD
void *pa_handle;
#define MAKE_FUNC(x) decltype(x) * p##x
MAKE_FUNC(Pa_Initialize);
MAKE_FUNC(Pa_Terminate);
MAKE_FUNC(Pa_GetErrorText);
MAKE_FUNC(Pa_StartStream);
MAKE_FUNC(Pa_StopStream);
MAKE_FUNC(Pa_OpenStream);
MAKE_FUNC(Pa_CloseStream);
MAKE_FUNC(Pa_GetDefaultOutputDevice);
MAKE_FUNC(Pa_GetDefaultInputDevice);
MAKE_FUNC(Pa_GetStreamInfo);
#undef MAKE_FUNC
#ifndef IN_IDE_PARSER
#define Pa_Initialize pPa_Initialize
#define Pa_Terminate pPa_Terminate
#define Pa_GetErrorText pPa_GetErrorText
#define Pa_StartStream pPa_StartStream
#define Pa_StopStream pPa_StopStream
#define Pa_OpenStream pPa_OpenStream
#define Pa_CloseStream pPa_CloseStream
#define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice
#define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice
#define Pa_GetStreamInfo pPa_GetStreamInfo
#endif
#endif
struct PortPlayback final : public BackendBase {
PortPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~PortPlayback() override;
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
ALuint mUpdateSize{0u};
DEF_NEWDEL(PortPlayback)
};
PortPlayback::~PortPlayback()
{
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
if(err != paNoError)
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
mStream = nullptr;
}
int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
{
std::lock_guard<PortPlayback> _{*this};
aluMixData(mDevice, outputBuffer, static_cast<ALuint>(framesPerBuffer),
mDevice->channelsFromFmt());
return 0;
}
void PortPlayback::open(const ALCchar *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
mUpdateSize = mDevice->UpdateSize;
auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
mParams.hostApiSpecificStreamInfo = nullptr;
mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
switch(mDevice->FmtType)
{
case DevFmtByte:
mParams.sampleFormat = paInt8;
break;
case DevFmtUByte:
mParams.sampleFormat = paUInt8;
break;
case DevFmtUShort:
/* fall-through */
case DevFmtShort:
mParams.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
mParams.sampleFormat = paInt32;
break;
case DevFmtFloat:
mParams.sampleFormat = paFloat32;
break;
}
retry_open:
PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
{
if(mParams.sampleFormat == paFloat32)
{
mParams.sampleFormat = paInt16;
goto retry_open;
}
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
Pa_GetErrorText(err)};
}
mDevice->DeviceName = name;
}
bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = static_cast<ALuint>(streamInfo->sampleRate);
mDevice->UpdateSize = mUpdateSize;
if(mParams.sampleFormat == paInt8)
mDevice->FmtType = DevFmtByte;
else if(mParams.sampleFormat == paUInt8)
mDevice->FmtType = DevFmtUByte;
else if(mParams.sampleFormat == paInt16)
mDevice->FmtType = DevFmtShort;
else if(mParams.sampleFormat == paInt32)
mDevice->FmtType = DevFmtInt;
else if(mParams.sampleFormat == paFloat32)
mDevice->FmtType = DevFmtFloat;
else
{
ERR("Unexpected sample format: 0x%lx\n", mParams.sampleFormat);
return false;
}
if(mParams.channelCount == 2)
mDevice->FmtChans = DevFmtStereo;
else if(mParams.channelCount == 1)
mDevice->FmtChans = DevFmtMono;
else
{
ERR("Unexpected channel count: %u\n", mParams.channelCount);
return false;
}
SetDefaultChannelOrder(mDevice);
return true;
}
bool PortPlayback::start()
{
PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
{
ERR("Pa_StartStream() returned an error: %s\n", Pa_GetErrorText(err));
return false;
}
return true;
}
void PortPlayback::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
struct PortCapture final : public BackendBase {
PortCapture(ALCdevice *device) noexcept : BackendBase{device} { }
~PortCapture() override;
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
{
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
framesPerBuffer, timeInfo, statusFlags);
}
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(PortCapture)
};
PortCapture::~PortCapture()
{
PaError err{mStream ? Pa_CloseStream(mStream) : paNoError};
if(err != paNoError)
ERR("Error closing stream: %s\n", Pa_GetErrorText(err));
mStream = nullptr;
}
int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept
{
mRing->write(inputBuffer, framesPerBuffer);
return 0;
}
void PortCapture::open(const ALCchar *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
ALuint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
ALuint frame_size{mDevice->frameSizeFromFmt()};
mRing = RingBuffer::Create(samples, frame_size, false);
auto devidopt = ConfigValueInt(nullptr, "port", "capture");
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
else mParams.device = Pa_GetDefaultOutputDevice();
mParams.suggestedLatency = 0.0f;
mParams.hostApiSpecificStreamInfo = nullptr;
switch(mDevice->FmtType)
{
case DevFmtByte:
mParams.sampleFormat = paInt8;
break;
case DevFmtUByte:
mParams.sampleFormat = paUInt8;
break;
case DevFmtShort:
mParams.sampleFormat = paInt16;
break;
case DevFmtInt:
mParams.sampleFormat = paInt32;
break;
case DevFmtFloat:
mParams.sampleFormat = paFloat32;
break;
case DevFmtUInt:
case DevFmtUShort:
throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
mParams.channelCount = static_cast<int>(mDevice->channelsFromFmt());
PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency,
paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)};
if(err != paNoError)
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to open stream: %s",
Pa_GetErrorText(err)};
mDevice->DeviceName = name;
}
bool PortCapture::start()
{
PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
{
ERR("Error starting stream: %s\n", Pa_GetErrorText(err));
return false;
}
return true;
}
void PortCapture::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
ALCuint PortCapture::availableSamples()
{ return static_cast<ALCuint>(mRing->readSpace()); }
ALCenum PortCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
} // namespace
bool PortBackendFactory::init()
{
PaError err;
#ifdef HAVE_DYNLOAD
if(!pa_handle)
{
#ifdef _WIN32
# define PALIB "portaudio.dll"
#elif defined(__APPLE__) && defined(__MACH__)
# define PALIB "libportaudio.2.dylib"
#elif defined(__OpenBSD__)
# define PALIB "libportaudio.so"
#else
# define PALIB "libportaudio.so.2"
#endif
pa_handle = LoadLib(PALIB);
if(!pa_handle)
return false;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(pa_handle, #f)); \
if(p##f == nullptr) \
{ \
CloseLib(pa_handle); \
pa_handle = nullptr; \
return false; \
} \
} while(0)
LOAD_FUNC(Pa_Initialize);
LOAD_FUNC(Pa_Terminate);
LOAD_FUNC(Pa_GetErrorText);
LOAD_FUNC(Pa_StartStream);
LOAD_FUNC(Pa_StopStream);
LOAD_FUNC(Pa_OpenStream);
LOAD_FUNC(Pa_CloseStream);
LOAD_FUNC(Pa_GetDefaultOutputDevice);
LOAD_FUNC(Pa_GetDefaultInputDevice);
LOAD_FUNC(Pa_GetStreamInfo);
#undef LOAD_FUNC
if((err=Pa_Initialize()) != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
CloseLib(pa_handle);
pa_handle = nullptr;
return false;
}
}
#else
if((err=Pa_Initialize()) != paNoError)
{
ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err));
return false;
}
#endif
return true;
}
bool PortBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void PortBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(pa_device, sizeof(pa_device));
break;
}
}
BackendPtr PortBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PortPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new PortCapture{device}};
return nullptr;
}
BackendFactory &PortBackendFactory::getFactory()
{
static PortBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_PORTAUDIO_H
#define BACKENDS_PORTAUDIO_H
#include "backends/base.h"
struct PortBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PORTAUDIO_H */
File diff suppressed because it is too large Load Diff
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
#include "backends/base.h"
class PulseBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PULSEAUDIO_H */
-963
View File
@@ -1,963 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2011-2013 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/qsa.h"
#include <stdlib.h>
#include <stdio.h>
#include <sched.h>
#include <errno.h>
#include <memory.h>
#include <poll.h>
#include <thread>
#include <memory>
#include <algorithm>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "threads.h"
#include <sys/asoundlib.h>
#include <sys/neutrino.h>
namespace {
struct qsa_data {
snd_pcm_t* pcmHandle{nullptr};
int audio_fd{-1};
snd_pcm_channel_setup_t csetup{};
snd_pcm_channel_params_t cparams{};
ALvoid* buffer{nullptr};
ALsizei size{0};
std::atomic<ALenum> mKillNow{AL_TRUE};
std::thread mThread;
};
struct DevMap {
ALCchar* name;
int card;
int dev;
};
al::vector<DevMap> DeviceNameMap;
al::vector<DevMap> CaptureNameMap;
constexpr ALCchar qsaDevice[] = "QSA Default";
constexpr struct {
int32_t format;
} formatlist[] = {
{SND_PCM_SFMT_FLOAT_LE},
{SND_PCM_SFMT_S32_LE},
{SND_PCM_SFMT_U32_LE},
{SND_PCM_SFMT_S16_LE},
{SND_PCM_SFMT_U16_LE},
{SND_PCM_SFMT_S8},
{SND_PCM_SFMT_U8},
{0},
};
constexpr struct {
int32_t rate;
} ratelist[] = {
{192000},
{176400},
{96000},
{88200},
{48000},
{44100},
{32000},
{24000},
{22050},
{16000},
{12000},
{11025},
{8000},
{0},
};
constexpr struct {
int32_t channels;
} channellist[] = {
{8},
{7},
{6},
{4},
{2},
{1},
{0},
};
void deviceList(int type, al::vector<DevMap> *devmap)
{
snd_ctl_t* handle;
snd_pcm_info_t pcminfo;
int max_cards, card, err, dev;
DevMap entry;
char name[1024];
snd_ctl_hw_info info;
max_cards = snd_cards();
if(max_cards < 0)
return;
std::for_each(devmap->begin(), devmap->end(),
[](const DevMap &entry) -> void
{ free(entry.name); }
);
devmap->clear();
entry.name = strdup(qsaDevice);
entry.card = 0;
entry.dev = 0;
devmap->push_back(entry);
for(card = 0;card < max_cards;card++)
{
if((err=snd_ctl_open(&handle, card)) < 0)
continue;
if((err=snd_ctl_hw_info(handle, &info)) < 0)
{
snd_ctl_close(handle);
continue;
}
for(dev = 0;dev < (int)info.pcmdevs;dev++)
{
if((err=snd_ctl_pcm_info(handle, dev, &pcminfo)) < 0)
continue;
if((type==SND_PCM_CHANNEL_PLAYBACK && (pcminfo.flags&SND_PCM_INFO_PLAYBACK)) ||
(type==SND_PCM_CHANNEL_CAPTURE && (pcminfo.flags&SND_PCM_INFO_CAPTURE)))
{
snprintf(name, sizeof(name), "%s [%s] (hw:%d,%d)", info.name, pcminfo.name, card, dev);
entry.name = strdup(name);
entry.card = card;
entry.dev = dev;
devmap->push_back(entry);
TRACE("Got device \"%s\", card %d, dev %d\n", name, card, dev);
}
}
snd_ctl_close(handle);
}
}
/* Wrappers to use an old-style backend with the new interface. */
struct PlaybackWrapper final : public BackendBase {
PlaybackWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
~PlaybackWrapper() override;
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
std::unique_ptr<qsa_data> mExtraData;
DEF_NEWDEL(PlaybackWrapper)
};
FORCE_ALIGN static int qsa_proc_playback(void *ptr)
{
PlaybackWrapper *self = static_cast<PlaybackWrapper*>(ptr);
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
snd_pcm_channel_status_t status;
sched_param param;
char* write_ptr;
ALint len;
int sret;
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
/* Increase default 10 priority to 11 to avoid jerky sound */
SchedGet(0, 0, &param);
param.sched_priority=param.sched_curpriority+1;
SchedSet(0, 0, SCHED_NOCHANGE, &param);
const ALint frame_size = device->frameSizeFromFmt();
std::unique_lock<PlaybackWrapper> dlock{*self};
while(!data->mKillNow.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = data->audio_fd;
pollitem.events = POLLOUT;
/* Select also works like time slice to OS */
dlock.unlock();
sret = poll(&pollitem, 1, 2000);
dlock.lock();
if(sret == -1)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll error: %s\n", strerror(errno));
aluHandleDisconnect(device, "Failed waiting for playback buffer: %s", strerror(errno));
break;
}
if(sret == 0)
{
ERR("poll timeout\n");
continue;
}
len = data->size;
write_ptr = static_cast<char*>(data->buffer);
aluMixData(device, write_ptr, len/frame_size);
while(len>0 && !data->mKillNow.load(std::memory_order_acquire))
{
int wrote = snd_pcm_plugin_write(data->pcmHandle, write_ptr, len);
if(wrote <= 0)
{
if(errno==EAGAIN || errno==EWOULDBLOCK)
continue;
memset(&status, 0, sizeof(status));
status.channel = SND_PCM_CHANNEL_PLAYBACK;
snd_pcm_plugin_status(data->pcmHandle, &status);
/* we need to reinitialize the sound channel if we've underrun the buffer */
if(status.status == SND_PCM_STATUS_UNDERRUN ||
status.status == SND_PCM_STATUS_READY)
{
if(snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK) < 0)
{
aluHandleDisconnect(device, "Playback recovery failed");
break;
}
}
}
else
{
write_ptr += wrote;
len -= wrote;
}
}
}
return 0;
}
/************/
/* Playback */
/************/
static ALCenum qsa_open_playback(PlaybackWrapper *self, const ALCchar* deviceName)
{
ALCdevice *device = self->mDevice;
int card, dev;
int status;
std::unique_ptr<qsa_data> data{new qsa_data{}};
data->mKillNow.store(AL_TRUE, std::memory_order_relaxed);
if(!deviceName)
deviceName = qsaDevice;
if(strcmp(deviceName, qsaDevice) == 0)
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_PLAYBACK);
else
{
if(DeviceNameMap.empty())
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
auto iter = std::find_if(DeviceNameMap.begin(), DeviceNameMap.end(),
[deviceName](const DevMap &entry) -> bool
{ return entry.name && strcmp(deviceName, entry.name) == 0; }
);
if(iter == DeviceNameMap.cend())
return ALC_INVALID_DEVICE;
status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_PLAYBACK);
}
if(status < 0)
return ALC_INVALID_DEVICE;
data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK);
if(data->audio_fd < 0)
{
snd_pcm_close(data->pcmHandle);
return ALC_INVALID_DEVICE;
}
device->DeviceName = deviceName;
self->mExtraData = std::move(data);
return ALC_NO_ERROR;
}
static void qsa_close_playback(PlaybackWrapper *self)
{
qsa_data *data = self->mExtraData.get();
if (data->buffer!=NULL)
{
free(data->buffer);
data->buffer=NULL;
}
snd_pcm_close(data->pcmHandle);
self->mExtraData = nullptr;
}
static ALCboolean qsa_reset_playback(PlaybackWrapper *self)
{
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
int32_t format=-1;
switch(device->FmtType)
{
case DevFmtByte:
format=SND_PCM_SFMT_S8;
break;
case DevFmtUByte:
format=SND_PCM_SFMT_U8;
break;
case DevFmtShort:
format=SND_PCM_SFMT_S16_LE;
break;
case DevFmtUShort:
format=SND_PCM_SFMT_U16_LE;
break;
case DevFmtInt:
format=SND_PCM_SFMT_S32_LE;
break;
case DevFmtUInt:
format=SND_PCM_SFMT_U32_LE;
break;
case DevFmtFloat:
format=SND_PCM_SFMT_FLOAT_LE;
break;
}
/* we actually don't want to block on writes */
snd_pcm_nonblock_mode(data->pcmHandle, 1);
/* Disable mmap to control data transfer to the audio device */
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_BUFFER_PARTIAL_BLOCKS);
// configure a sound channel
memset(&data->cparams, 0, sizeof(data->cparams));
data->cparams.channel=SND_PCM_CHANNEL_PLAYBACK;
data->cparams.mode=SND_PCM_MODE_BLOCK;
data->cparams.start_mode=SND_PCM_START_FULL;
data->cparams.stop_mode=SND_PCM_STOP_STOP;
data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
data->cparams.buf.block.frags_max=device->BufferSize / device->UpdateSize;
data->cparams.buf.block.frags_min=data->cparams.buf.block.frags_max;
data->cparams.format.interleave=1;
data->cparams.format.rate=device->Frequency;
data->cparams.format.voices=device->channelsFromFmt();
data->cparams.format.format=format;
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
{
int original_rate=data->cparams.format.rate;
int original_voices=data->cparams.format.voices;
int original_format=data->cparams.format.format;
int it;
int jt;
for (it=0; it<1; it++)
{
/* Check for second pass */
if (it==1)
{
original_rate=ratelist[0].rate;
original_voices=channellist[0].channels;
original_format=formatlist[0].format;
}
do {
/* At first downgrade sample format */
jt=0;
do {
if (formatlist[jt].format==data->cparams.format.format)
{
data->cparams.format.format=formatlist[jt+1].format;
break;
}
if (formatlist[jt].format==0)
{
data->cparams.format.format=0;
break;
}
jt++;
} while(1);
if (data->cparams.format.format==0)
{
data->cparams.format.format=original_format;
/* At secod downgrade sample rate */
jt=0;
do {
if (ratelist[jt].rate==data->cparams.format.rate)
{
data->cparams.format.rate=ratelist[jt+1].rate;
break;
}
if (ratelist[jt].rate==0)
{
data->cparams.format.rate=0;
break;
}
jt++;
} while(1);
if (data->cparams.format.rate==0)
{
data->cparams.format.rate=original_rate;
data->cparams.format.format=original_format;
/* At third downgrade channels number */
jt=0;
do {
if(channellist[jt].channels==data->cparams.format.voices)
{
data->cparams.format.voices=channellist[jt+1].channels;
break;
}
if (channellist[jt].channels==0)
{
data->cparams.format.voices=0;
break;
}
jt++;
} while(1);
}
if (data->cparams.format.voices==0)
{
break;
}
}
data->cparams.buf.block.frag_size=device->UpdateSize*
data->cparams.format.voices*
snd_pcm_format_width(data->cparams.format.format)/8;
data->cparams.buf.block.frags_max=device->NumUpdates;
data->cparams.buf.block.frags_min=device->NumUpdates;
if ((snd_pcm_plugin_params(data->pcmHandle, &data->cparams))<0)
{
continue;
}
else
{
break;
}
} while(1);
if (data->cparams.format.voices!=0)
{
break;
}
}
if (data->cparams.format.voices==0)
{
return ALC_FALSE;
}
}
if ((snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_PLAYBACK))<0)
{
return ALC_FALSE;
}
memset(&data->csetup, 0, sizeof(data->csetup));
data->csetup.channel=SND_PCM_CHANNEL_PLAYBACK;
if (snd_pcm_plugin_setup(data->pcmHandle, &data->csetup)<0)
{
return ALC_FALSE;
}
/* now fill back to the our AL device */
device->Frequency=data->cparams.format.rate;
switch (data->cparams.format.voices)
{
case 1:
device->FmtChans=DevFmtMono;
break;
case 2:
device->FmtChans=DevFmtStereo;
break;
case 4:
device->FmtChans=DevFmtQuad;
break;
case 6:
device->FmtChans=DevFmtX51;
break;
case 7:
device->FmtChans=DevFmtX61;
break;
case 8:
device->FmtChans=DevFmtX71;
break;
default:
device->FmtChans=DevFmtMono;
break;
}
switch (data->cparams.format.format)
{
case SND_PCM_SFMT_S8:
device->FmtType=DevFmtByte;
break;
case SND_PCM_SFMT_U8:
device->FmtType=DevFmtUByte;
break;
case SND_PCM_SFMT_S16_LE:
device->FmtType=DevFmtShort;
break;
case SND_PCM_SFMT_U16_LE:
device->FmtType=DevFmtUShort;
break;
case SND_PCM_SFMT_S32_LE:
device->FmtType=DevFmtInt;
break;
case SND_PCM_SFMT_U32_LE:
device->FmtType=DevFmtUInt;
break;
case SND_PCM_SFMT_FLOAT_LE:
device->FmtType=DevFmtFloat;
break;
default:
device->FmtType=DevFmtShort;
break;
}
SetDefaultChannelOrder(device);
device->UpdateSize=data->csetup.buf.block.frag_size / device->frameSizeFromFmt();
device->NumUpdates=data->csetup.buf.block.frags;
data->size=data->csetup.buf.block.frag_size;
data->buffer=malloc(data->size);
if (!data->buffer)
{
return ALC_FALSE;
}
return ALC_TRUE;
}
static ALCboolean qsa_start_playback(PlaybackWrapper *self)
{
qsa_data *data = self->mExtraData.get();
try {
data->mKillNow.store(AL_FALSE, std::memory_order_release);
data->mThread = std::thread(qsa_proc_playback, self);
return ALC_TRUE;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
return ALC_FALSE;
}
static void qsa_stop_playback(PlaybackWrapper *self)
{
qsa_data *data = self->mExtraData.get();
if(data->mKillNow.exchange(AL_TRUE, std::memory_order_acq_rel) || !data->mThread.joinable())
return;
data->mThread.join();
}
PlaybackWrapper::~PlaybackWrapper()
{
if(mExtraData)
qsa_close_playback(this);
}
void PlaybackWrapper::open(const ALCchar *name)
{
if(auto err = qsa_open_playback(this, name))
throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
}
bool PlaybackWrapper::reset()
{
if(!qsa_reset_playback(this))
throw al::backend_exception{ALC_INVALID_VALUE, ""};
return true;
}
bool PlaybackWrapper::start()
{ return qsa_start_playback(this); }
void PlaybackWrapper::stop()
{ qsa_stop_playback(this); }
/***********/
/* Capture */
/***********/
struct CaptureWrapper final : public BackendBase {
CaptureWrapper(ALCdevice *device) noexcept : BackendBase{device} { }
~CaptureWrapper() override;
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
std::unique_ptr<qsa_data> mExtraData;
DEF_NEWDEL(CaptureWrapper)
};
static ALCenum qsa_open_capture(CaptureWrapper *self, const ALCchar *deviceName)
{
ALCdevice *device = self->mDevice;
int card, dev;
int format=-1;
int status;
std::unique_ptr<qsa_data> data{new qsa_data{}};
if(!deviceName)
deviceName = qsaDevice;
if(strcmp(deviceName, qsaDevice) == 0)
status = snd_pcm_open_preferred(&data->pcmHandle, &card, &dev, SND_PCM_OPEN_CAPTURE);
else
{
if(CaptureNameMap.empty())
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
auto iter = std::find_if(CaptureNameMap.cbegin(), CaptureNameMap.cend(),
[deviceName](const DevMap &entry) -> bool
{ return entry.name && strcmp(deviceName, entry.name) == 0; }
);
if(iter == CaptureNameMap.cend())
return ALC_INVALID_DEVICE;
status = snd_pcm_open(&data->pcmHandle, iter->card, iter->dev, SND_PCM_OPEN_CAPTURE);
}
if(status < 0)
return ALC_INVALID_DEVICE;
data->audio_fd = snd_pcm_file_descriptor(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE);
if(data->audio_fd < 0)
{
snd_pcm_close(data->pcmHandle);
return ALC_INVALID_DEVICE;
}
device->DeviceName = deviceName;
switch (device->FmtType)
{
case DevFmtByte:
format=SND_PCM_SFMT_S8;
break;
case DevFmtUByte:
format=SND_PCM_SFMT_U8;
break;
case DevFmtShort:
format=SND_PCM_SFMT_S16_LE;
break;
case DevFmtUShort:
format=SND_PCM_SFMT_U16_LE;
break;
case DevFmtInt:
format=SND_PCM_SFMT_S32_LE;
break;
case DevFmtUInt:
format=SND_PCM_SFMT_U32_LE;
break;
case DevFmtFloat:
format=SND_PCM_SFMT_FLOAT_LE;
break;
}
/* we actually don't want to block on reads */
snd_pcm_nonblock_mode(data->pcmHandle, 1);
/* Disable mmap to control data transfer to the audio device */
snd_pcm_plugin_set_disable(data->pcmHandle, PLUGIN_DISABLE_MMAP);
/* configure a sound channel */
memset(&data->cparams, 0, sizeof(data->cparams));
data->cparams.mode=SND_PCM_MODE_BLOCK;
data->cparams.channel=SND_PCM_CHANNEL_CAPTURE;
data->cparams.start_mode=SND_PCM_START_GO;
data->cparams.stop_mode=SND_PCM_STOP_STOP;
data->cparams.buf.block.frag_size=device->UpdateSize * device->frameSizeFromFmt();
data->cparams.buf.block.frags_max=device->NumUpdates;
data->cparams.buf.block.frags_min=device->NumUpdates;
data->cparams.format.interleave=1;
data->cparams.format.rate=device->Frequency;
data->cparams.format.voices=device->channelsFromFmt();
data->cparams.format.format=format;
if(snd_pcm_plugin_params(data->pcmHandle, &data->cparams) < 0)
{
snd_pcm_close(data->pcmHandle);
return ALC_INVALID_VALUE;
}
self->mExtraData = std::move(data);
return ALC_NO_ERROR;
}
static void qsa_close_capture(CaptureWrapper *self)
{
qsa_data *data = self->mExtraData.get();
if (data->pcmHandle!=nullptr)
snd_pcm_close(data->pcmHandle);
data->pcmHandle = nullptr;
self->mExtraData = nullptr;
}
static void qsa_start_capture(CaptureWrapper *self)
{
qsa_data *data = self->mExtraData.get();
int rstatus;
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
{
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
return;
}
memset(&data->csetup, 0, sizeof(data->csetup));
data->csetup.channel=SND_PCM_CHANNEL_CAPTURE;
if ((rstatus=snd_pcm_plugin_setup(data->pcmHandle, &data->csetup))<0)
{
ERR("capture setup failed: %s\n", snd_strerror(rstatus));
return;
}
snd_pcm_capture_go(data->pcmHandle);
}
static void qsa_stop_capture(CaptureWrapper *self)
{
qsa_data *data = self->mExtraData.get();
snd_pcm_capture_flush(data->pcmHandle);
}
static ALCuint qsa_available_samples(CaptureWrapper *self)
{
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
snd_pcm_channel_status_t status;
ALint frame_size = device->frameSizeFromFmt();
ALint free_size;
int rstatus;
memset(&status, 0, sizeof (status));
status.channel=SND_PCM_CHANNEL_CAPTURE;
snd_pcm_plugin_status(data->pcmHandle, &status);
if ((status.status==SND_PCM_STATUS_OVERRUN) ||
(status.status==SND_PCM_STATUS_READY))
{
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
{
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
aluHandleDisconnect(device, "Failed capture recovery: %s", snd_strerror(rstatus));
return 0;
}
snd_pcm_capture_go(data->pcmHandle);
return 0;
}
free_size=data->csetup.buf.block.frag_size*data->csetup.buf.block.frags;
free_size-=status.free;
return free_size/frame_size;
}
static ALCenum qsa_capture_samples(CaptureWrapper *self, ALCvoid *buffer, ALCuint samples)
{
ALCdevice *device = self->mDevice;
qsa_data *data = self->mExtraData.get();
char* read_ptr;
snd_pcm_channel_status_t status;
int selectret;
int bytes_read;
ALint frame_size=device->frameSizeFromFmt();
ALint len=samples*frame_size;
int rstatus;
read_ptr = static_cast<char*>(buffer);
while (len>0)
{
pollfd pollitem{};
pollitem.fd = data->audio_fd;
pollitem.events = POLLOUT;
/* Select also works like time slice to OS */
bytes_read=0;
selectret = poll(&pollitem, 1, 2000);
switch (selectret)
{
case -1:
aluHandleDisconnect(device, "Failed to check capture samples");
return ALC_INVALID_DEVICE;
case 0:
break;
default:
bytes_read=snd_pcm_plugin_read(data->pcmHandle, read_ptr, len);
break;
}
if (bytes_read<=0)
{
if ((errno==EAGAIN) || (errno==EWOULDBLOCK))
{
continue;
}
memset(&status, 0, sizeof (status));
status.channel=SND_PCM_CHANNEL_CAPTURE;
snd_pcm_plugin_status(data->pcmHandle, &status);
/* we need to reinitialize the sound channel if we've overrun the buffer */
if ((status.status==SND_PCM_STATUS_OVERRUN) ||
(status.status==SND_PCM_STATUS_READY))
{
if ((rstatus=snd_pcm_plugin_prepare(data->pcmHandle, SND_PCM_CHANNEL_CAPTURE))<0)
{
ERR("capture prepare failed: %s\n", snd_strerror(rstatus));
aluHandleDisconnect(device, "Failed capture recovery: %s",
snd_strerror(rstatus));
return ALC_INVALID_DEVICE;
}
snd_pcm_capture_go(data->pcmHandle);
}
}
else
{
read_ptr+=bytes_read;
len-=bytes_read;
}
}
return ALC_NO_ERROR;
}
CaptureWrapper::~CaptureWrapper()
{
if(mExtraData)
qsa_close_capture(this);
}
void CaptureWrapper::open(const ALCchar *name)
{
if(auto err = qsa_open_capture(this, name))
throw al::backend_exception{ALC_INVALID_VALUE, "%d", err};
}
bool CaptureWrapper::start()
{ qsa_start_capture(this); return true; }
void CaptureWrapper::stop()
{ qsa_stop_capture(this); }
ALCenum CaptureWrapper::captureSamples(al::byte *buffer, ALCuint samples)
{ return qsa_capture_samples(this, buffer, samples); }
ALCuint CaptureWrapper::availableSamples()
{ return qsa_available_samples(this); }
} // namespace
bool QSABackendFactory::init()
{ return true; }
bool QSABackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void QSABackendFactory::probe(DevProbe type, std::string *outnames)
{
auto add_device = [outnames](const DevMap &entry) -> void
{
const char *n = entry.name;
if(n && n[0])
outnames->append(n, strlen(n)+1);
};
switch (type)
{
case DevProbe::Playback:
deviceList(SND_PCM_CHANNEL_PLAYBACK, &DeviceNameMap);
std::for_each(DeviceNameMap.cbegin(), DeviceNameMap.cend(), add_device);
break;
case DevProbe::Capture:
deviceList(SND_PCM_CHANNEL_CAPTURE, &CaptureNameMap);
std::for_each(CaptureNameMap.cbegin(), CaptureNameMap.cend(), add_device);
break;
}
}
BackendPtr QSABackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new PlaybackWrapper{device}};
if(type == BackendType::Capture)
return BackendPtr{new CaptureWrapper{device}};
return nullptr;
}
BackendFactory &QSABackendFactory::getFactory()
{
static QSABackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_QSA_H
#define BACKENDS_QSA_H
#include "backends/base.h"
struct QSABackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_QSA_H */
-227
View File
@@ -1,227 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2018 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/sdl2.h"
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <string>
#include "AL/al.h"
#include "alcmain.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alu.h"
#include "logging.h"
#include <SDL2/SDL.h>
namespace {
#ifdef _WIN32
#define DEVNAME_PREFIX "OpenAL Soft on "
#else
#define DEVNAME_PREFIX ""
#endif
constexpr ALCchar defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
struct Sdl2Backend final : public BackendBase {
Sdl2Backend(ALCdevice *device) noexcept : BackendBase{device} { }
~Sdl2Backend() override;
void audioCallback(Uint8 *stream, int len) noexcept;
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
void lock() override;
void unlock() override;
SDL_AudioDeviceID mDeviceID{0u};
ALuint mFrameSize{0};
ALuint mFrequency{0u};
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
ALuint mUpdateSize{0u};
DEF_NEWDEL(Sdl2Backend)
};
Sdl2Backend::~Sdl2Backend()
{
if(mDeviceID)
SDL_CloseAudioDevice(mDeviceID);
mDeviceID = 0;
}
void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept
{
const auto ulen = static_cast<unsigned int>(len);
assert((ulen % mFrameSize) == 0);
aluMixData(mDevice, stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
void Sdl2Backend::open(const ALCchar *name)
{
SDL_AudioSpec want{}, have{};
want.freq = static_cast<int>(mDevice->Frequency);
switch(mDevice->FmtType)
{
case DevFmtUByte: want.format = AUDIO_U8; break;
case DevFmtByte: want.format = AUDIO_S8; break;
case DevFmtUShort: want.format = AUDIO_U16SYS; break;
case DevFmtShort: want.format = AUDIO_S16SYS; break;
case DevFmtUInt: /* fall-through */
case DevFmtInt: want.format = AUDIO_S32SYS; break;
case DevFmtFloat: want.format = AUDIO_F32; break;
}
want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2;
want.samples = static_cast<Uint16>(mDevice->UpdateSize);
want.callback = &Sdl2Backend::audioCallbackC;
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
if(!name || strcmp(name, defaultDeviceName) == 0)
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
else
{
const size_t prefix_len = strlen(DEVNAME_PREFIX);
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
else
mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
}
if(mDeviceID == 0)
throw al::backend_exception{ALC_INVALID_VALUE, "%s", SDL_GetError()};
mDevice->Frequency = static_cast<ALuint>(have.freq);
if(have.channels == 1)
mDevice->FmtChans = DevFmtMono;
else if(have.channels == 2)
mDevice->FmtChans = DevFmtStereo;
else
throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL channel count: %d",
int{have.channels}};
switch(have.format)
{
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break;
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break;
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break;
default:
throw al::backend_exception{ALC_INVALID_VALUE, "Unhandled SDL format: 0x%04x",
have.format};
}
mDevice->UpdateSize = have.samples;
mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
mFrameSize = mDevice->frameSizeFromFmt();
mFrequency = mDevice->Frequency;
mFmtChans = mDevice->FmtChans;
mFmtType = mDevice->FmtType;
mUpdateSize = mDevice->UpdateSize;
mDevice->DeviceName = name ? name : defaultDeviceName;
}
bool Sdl2Backend::reset()
{
mDevice->Frequency = mFrequency;
mDevice->FmtChans = mFmtChans;
mDevice->FmtType = mFmtType;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2;
SetDefaultWFXChannelOrder(mDevice);
return true;
}
bool Sdl2Backend::start()
{
SDL_PauseAudioDevice(mDeviceID, 0);
return true;
}
void Sdl2Backend::stop()
{ SDL_PauseAudioDevice(mDeviceID, 1); }
void Sdl2Backend::lock()
{ SDL_LockAudioDevice(mDeviceID); }
void Sdl2Backend::unlock()
{ SDL_UnlockAudioDevice(mDeviceID); }
} // namespace
BackendFactory &SDL2BackendFactory::getFactory()
{
static SDL2BackendFactory factory{};
return factory;
}
bool SDL2BackendFactory::init()
{ return (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0); }
bool SDL2BackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
void SDL2BackendFactory::probe(DevProbe type, std::string *outnames)
{
if(type != DevProbe::Playback)
return;
int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)};
/* Includes null char. */
outnames->append(defaultDeviceName, sizeof(defaultDeviceName));
for(int i{0};i < num_devices;++i)
{
std::string name{DEVNAME_PREFIX};
name += SDL_GetAudioDeviceName(i, SDL_FALSE);
if(!name.empty())
outnames->append(name.c_str(), name.length()+1);
}
}
BackendPtr SDL2BackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new Sdl2Backend{device}};
return nullptr;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_SDL2_H
#define BACKENDS_SDL2_H
#include "backends/base.h"
struct SDL2BackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SDL2_H */
-477
View File
@@ -1,477 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/sndio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include <functional>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "threads.h"
#include "vector.h"
#include "ringbuffer.h"
#include <sndio.h>
namespace {
static const ALCchar sndio_device[] = "SndIO Default";
struct SndioPlayback final : public BackendBase {
SndioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~SndioPlayback() override;
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
sio_hdl *mSndHandle{nullptr};
al::vector<ALubyte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioPlayback)
};
SndioPlayback::~SndioPlayback()
{
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = nullptr;
}
int SndioPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const size_t frameStep{mDevice->channelsFromFmt()};
const ALuint frameSize{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
ALubyte *WritePtr{mBuffer.data()};
size_t len{mBuffer.size()};
{
std::lock_guard<SndioPlayback> _{*this};
aluMixData(mDevice, WritePtr, static_cast<ALuint>(len/frameSize), frameStep);
}
while(len > 0 && !mKillNow.load(std::memory_order_acquire))
{
size_t wrote{sio_write(mSndHandle, WritePtr, len)};
if(wrote == 0)
{
ERR("sio_write failed\n");
aluHandleDisconnect(mDevice, "Failed to write playback samples");
break;
}
len -= wrote;
WritePtr += wrote;
}
}
return 0;
}
void SndioPlayback::open(const ALCchar *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
if(mSndHandle == nullptr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
mDevice->DeviceName = name;
}
bool SndioPlayback::reset()
{
sio_par par;
sio_initpar(&par);
par.rate = mDevice->Frequency;
par.pchan = ((mDevice->FmtChans != DevFmtMono) ? 2 : 1);
switch(mDevice->FmtType)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.le = SIO_LE_NATIVE;
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
{
ERR("Failed to set device parameters\n");
return false;
}
if(par.bits != par.bps*8)
{
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
return true;
}
mDevice->Frequency = par.rate;
mDevice->FmtChans = ((par.pchan==1) ? DevFmtMono : DevFmtStereo);
if(par.bits == 8 && par.sig == 1)
mDevice->FmtType = DevFmtByte;
else if(par.bits == 8 && par.sig == 0)
mDevice->FmtType = DevFmtUByte;
else if(par.bits == 16 && par.sig == 1)
mDevice->FmtType = DevFmtShort;
else if(par.bits == 16 && par.sig == 0)
mDevice->FmtType = DevFmtUShort;
else if(par.bits == 32 && par.sig == 1)
mDevice->FmtType = DevFmtInt;
else if(par.bits == 32 && par.sig == 0)
mDevice->FmtType = DevFmtUInt;
else
{
ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
return false;
}
SetDefaultChannelOrder(mDevice);
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
std::fill(mBuffer.begin(), mBuffer.end(), 0);
return true;
}
bool SndioPlayback::start()
{
if(!sio_start(mSndHandle))
{
ERR("Error starting playback\n");
return false;
}
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
sio_stop(mSndHandle);
return false;
}
void SndioPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
if(!sio_stop(mSndHandle))
ERR("Error stopping device\n");
}
struct SndioCapture final : public BackendBase {
SndioCapture(ALCdevice *device) noexcept : BackendBase{device} { }
~SndioCapture() override;
int recordProc();
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
sio_hdl *mSndHandle{nullptr};
RingBufferPtr mRing;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioCapture)
};
SndioCapture::~SndioCapture()
{
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = nullptr;
}
int SndioCapture::recordProc()
{
SetRTPriority();
althrd_setname(RECORD_THREAD_NAME);
const ALuint frameSize{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
if(todo == 0)
{
static char junk[4096];
sio_read(mSndHandle, junk,
minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
continue;
}
size_t total{0u};
data.first.len *= frameSize;
data.second.len *= frameSize;
todo = minz(todo, mDevice->UpdateSize) * frameSize;
while(total < todo)
{
if(!data.first.len)
data.first = data.second;
size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
if(!got)
{
aluHandleDisconnect(mDevice, "Failed to read capture samples");
break;
}
data.first.buf += got;
data.first.len -= got;
total += got;
}
mRing->writeAdvance(total / frameSize);
}
return 0;
}
void SndioCapture::open(const ALCchar *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
mSndHandle = sio_open(nullptr, SIO_REC, 0);
if(mSndHandle == nullptr)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open backend device"};
sio_par par;
sio_initpar(&par);
switch(mDevice->FmtType)
{
case DevFmtByte:
par.bps = 1;
par.sig = 1;
break;
case DevFmtUByte:
par.bps = 1;
par.sig = 0;
break;
case DevFmtShort:
par.bps = 2;
par.sig = 1;
break;
case DevFmtUShort:
par.bps = 2;
par.sig = 0;
break;
case DevFmtInt:
par.bps = 4;
par.sig = 1;
break;
case DevFmtUInt:
par.bps = 4;
par.sig = 0;
break;
case DevFmtFloat:
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
par.bits = par.bps * 8;
par.le = SIO_LE_NATIVE;
par.msb = SIO_LE_NATIVE ? 0 : 1;
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz, mDevice->Frequency/40);
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.appbufsz;
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{ALC_INVALID_VALUE, "Failed to set device praameters"};
if(par.bits != par.bps*8)
throw al::backend_exception{ALC_INVALID_VALUE,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0)
|| (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0)
|| (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0)
|| (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0)
|| (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0)
|| (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0))
|| mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate)
throw al::backend_exception{ALC_INVALID_VALUE,
"Failed to set format %s %s %uhz, got %c%u %u-channel %uhz instead",
DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans),
mDevice->Frequency, par.sig?'s':'u', par.bits, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
SetDefaultChannelOrder(mDevice);
mDevice->DeviceName = name;
}
bool SndioCapture::start()
{
if(!sio_start(mSndHandle))
{
ERR("Error starting playback\n");
return false;
}
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
return true;
}
catch(std::exception& e) {
ERR("Could not create record thread: %s\n", e.what());
}
catch(...) {
}
sio_stop(mSndHandle);
return false;
}
void SndioCapture::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
if(!sio_stop(mSndHandle))
ERR("Error stopping device\n");
}
ALCenum SndioCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
ALCuint SndioCapture::availableSamples()
{ return static_cast<ALCuint>(mRing->readSpace()); }
} // namespace
BackendFactory &SndIOBackendFactory::getFactory()
{
static SndIOBackendFactory factory{};
return factory;
}
bool SndIOBackendFactory::init()
{ return true; }
bool SndIOBackendFactory::querySupport(BackendType type)
{ return (type == BackendType::Playback || type == BackendType::Capture); }
void SndIOBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
case DevProbe::Capture:
/* Includes null char. */
outnames->append(sndio_device, sizeof(sndio_device));
break;
}
}
BackendPtr SndIOBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SndioPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new SndioCapture{device}};
return nullptr;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_SNDIO_H
#define BACKENDS_SNDIO_H
#include "backends/base.h"
struct SndIOBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SNDIO_H */
-300
View File
@@ -1,300 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/solaris.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <math.h>
#include <thread>
#include <functional>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "alconfig.h"
#include "threads.h"
#include "vector.h"
#include "compat.h"
#include <sys/audioio.h>
namespace {
constexpr ALCchar solaris_device[] = "Solaris Default";
std::string solaris_driver{"/dev/audio"};
struct SolarisBackend final : public BackendBase {
SolarisBackend(ALCdevice *device) noexcept : BackendBase{device} { }
~SolarisBackend() override;
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
int mFd{-1};
al::vector<ALubyte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SolarisBackend)
};
SolarisBackend::~SolarisBackend()
{
if(mFd != -1)
close(mFd);
mFd = -1;
}
int SolarisBackend::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const size_t frame_step{mDevice->channelsFromFmt()};
const ALuint frame_size{mDevice->frameSizeFromFmt()};
std::unique_lock<SolarisBackend> dlock{*this};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
dlock.unlock();
int pret{poll(&pollitem, 1, 1000)};
dlock.lock();
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed to wait for playback buffer: %s",
strerror(errno));
break;
}
else if(pret == 0)
{
WARN("poll timeout\n");
continue;
}
ALubyte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
aluMixData(mDevice, write_ptr, to_write/frame_size, frame_step);
while(to_write > 0 && !mKillNow.load(std::memory_order_acquire))
{
ssize_t wrote{write(mFd, write_ptr, to_write)};
if(wrote < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
continue;
ERR("write failed: %s\n", strerror(errno));
aluHandleDisconnect(mDevice, "Failed to write playback samples: %s",
strerror(errno));
break;
}
to_write -= wrote;
write_ptr += wrote;
}
}
return 0;
}
void SolarisBackend::open(const ALCchar *name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
mFd = ::open(solaris_driver.c_str(), O_WRONLY);
if(mFd == -1)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open %s: %s",
solaris_driver.c_str(), strerror(errno)};
mDevice->DeviceName = name;
}
bool SolarisBackend::reset()
{
audio_info_t info;
AUDIO_INITINFO(&info);
info.play.sample_rate = mDevice->Frequency;
if(mDevice->FmtChans != DevFmtMono)
mDevice->FmtChans = DevFmtStereo;
ALuint numChannels{mDevice->channelsFromFmt()};
info.play.channels = numChannels;
switch(mDevice->FmtType)
{
case DevFmtByte:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
case DevFmtUByte:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR8;
break;
case DevFmtUShort:
case DevFmtInt:
case DevFmtUInt:
case DevFmtFloat:
mDevice->FmtType = DevFmtShort;
/* fall-through */
case DevFmtShort:
info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
}
ALuint frameSize{numChannels * mDevice->bytesFromFmt()};
info.play.buffer_size = mDevice->BufferSize * frameSize;
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
{
ERR("ioctl failed: %s\n", strerror(errno));
return false;
}
if(mDevice->channelsFromFmt() != info.play.channels)
{
ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
info.play.channels);
return false;
}
if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
{
ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
info.play.precision, info.play.encoding);
return false;
}
mDevice->Frequency = info.play.sample_rate;
mDevice->BufferSize = info.play.buffer_size / frameSize;
mDevice->UpdateSize = mDevice->BufferSize / 2;
SetDefaultChannelOrder(mDevice);
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
std::fill(mBuffer.begin(), mBuffer.end(), 0);
return true;
}
bool SolarisBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Could not create playback thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void SolarisBackend::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
if(ioctl(mFd, AUDIO_DRAIN) < 0)
ERR("Error draining device: %s\n", strerror(errno));
}
} // namespace
BackendFactory &SolarisBackendFactory::getFactory()
{
static SolarisBackendFactory factory{};
return factory;
}
bool SolarisBackendFactory::init()
{
if(auto devopt = ConfigValueStr(nullptr, "solaris", "device"))
solaris_driver = std::move(*devopt);
return true;
}
bool SolarisBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
void SolarisBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
{
#ifdef HAVE_STAT
struct stat buf;
if(stat(solaris_driver.c_str(), &buf) == 0)
#endif
outnames->append(solaris_device, sizeof(solaris_device));
}
break;
case DevProbe::Capture:
break;
}
}
BackendPtr SolarisBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SolarisBackend{device}};
return nullptr;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_SOLARIS_H
#define BACKENDS_SOLARIS_H
#include "backends/base.h"
struct SolarisBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SOLARIS_H */
File diff suppressed because it is too large Load Diff
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_WASAPI_H
#define BACKENDS_WASAPI_H
#include "backends/base.h"
struct WasapiBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_WASAPI_H */
-403
View File
@@ -1,403 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/wave.h"
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <exception>
#include <functional>
#include <thread>
#include "AL/al.h"
#include "albyte.h"
#include "alcmain.h"
#include "alconfig.h"
#include "alexcpt.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alu.h"
#include "compat.h"
#include "endiantest.h"
#include "logging.h"
#include "strutils.h"
#include "threads.h"
#include "vector.h"
namespace {
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
constexpr ALCchar waveDevice[] = "Wave File Writer";
constexpr ALubyte SUBTYPE_PCM[]{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ALubyte SUBTYPE_FLOAT[]{
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
0x00, 0x38, 0x9b, 0x71
};
constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
0xca, 0x00, 0x00, 0x00
};
void fwrite16le(ALushort val, FILE *f)
{
ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) };
fwrite(data, 1, 2, f);
}
void fwrite32le(ALuint val, FILE *f)
{
ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff),
static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) };
fwrite(data, 1, 4, f);
}
struct WaveBackend final : public BackendBase {
WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
~WaveBackend() override;
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
FILE *mFile{nullptr};
long mDataStart{-1};
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WaveBackend)
};
WaveBackend::~WaveBackend()
{
if(mFile)
fclose(mFile);
mFile = nullptr;
}
int WaveBackend::mixerProc()
{
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
althrd_setname(MIXER_THREAD_NAME);
const size_t frameStep{mDevice->channelsFromFmt()};
const ALuint frameSize{mDevice->frameSizeFromFmt()};
int64_t done{0};
auto start = std::chrono::steady_clock::now();
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
auto now = std::chrono::steady_clock::now();
/* This converts from nanoseconds to nanosamples, then to samples. */
int64_t avail{std::chrono::duration_cast<seconds>((now-start) *
mDevice->Frequency).count()};
if(avail-done < mDevice->UpdateSize)
{
std::this_thread::sleep_for(restTime);
continue;
}
while(avail-done >= mDevice->UpdateSize)
{
{
std::lock_guard<WaveBackend> _{*this};
aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize, frameStep);
}
done += mDevice->UpdateSize;
if(!IS_LITTLE_ENDIAN)
{
const ALuint bytesize{mDevice->bytesFromFmt()};
if(bytesize == 2)
{
ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data());
const size_t len{mBuffer.size() / 2};
for(size_t i{0};i < len;i++)
{
const ALushort samp{samples[i]};
samples[i] = static_cast<ALushort>((samp>>8) | (samp<<8));
}
}
else if(bytesize == 4)
{
ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data());
const size_t len{mBuffer.size() / 4};
for(size_t i{0};i < len;i++)
{
const ALuint samp{samples[i]};
samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
((samp<<8)&0x00ff0000) | (samp<<24);
}
}
}
size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
(void)fs;
if(ferror(mFile))
{
ERR("Error writing to file\n");
aluHandleDisconnect(mDevice, "Failed to write playback samples");
break;
}
}
/* For every completed second, increment the start time and reduce the
* samples done. This prevents the difference between the start time
* and current time from growing too large, while maintaining the
* correct number of samples to render.
*/
if(done >= mDevice->Frequency)
{
seconds s{done/mDevice->Frequency};
start += s;
done -= mDevice->Frequency*s.count();
}
}
return 0;
}
void WaveBackend::open(const ALCchar *name)
{
const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
if(!fname[0]) throw al::backend_exception{ALC_INVALID_VALUE, "No wave output filename"};
if(!name)
name = waveDevice;
else if(strcmp(name, waveDevice) != 0)
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
#ifdef _WIN32
{
std::wstring wname = utf8_to_wstr(fname);
mFile = _wfopen(wname.c_str(), L"wb");
}
#else
mFile = fopen(fname, "wb");
#endif
if(!mFile)
throw al::backend_exception{ALC_INVALID_VALUE, "Could not open file '%s': %s", fname,
strerror(errno)};
mDevice->DeviceName = name;
}
bool WaveBackend::reset()
{
ALuint channels=0, bytes=0, chanmask=0;
int isbformat = 0;
size_t val;
fseek(mFile, 0, SEEK_SET);
clearerr(mFile);
if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
{
mDevice->FmtChans = DevFmtAmbi3D;
mDevice->mAmbiOrder = 1;
}
switch(mDevice->FmtType)
{
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtUShort:
mDevice->FmtType = DevFmtShort;
break;
case DevFmtUInt:
mDevice->FmtType = DevFmtInt;
break;
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
}
switch(mDevice->FmtChans)
{
case DevFmtMono: chanmask = 0x04; break;
case DevFmtStereo: chanmask = 0x01 | 0x02; break;
case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break;
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
case DevFmtAmbi3D:
/* .amb output requires FuMa */
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
mDevice->mAmbiLayout = AmbiLayout::FuMa;
mDevice->mAmbiScale = AmbiNorm::FuMa;
isbformat = 1;
chanmask = 0;
break;
}
bytes = mDevice->bytesFromFmt();
channels = mDevice->channelsFromFmt();
rewind(mFile);
fputs("RIFF", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
fputs("WAVE", mFile);
fputs("fmt ", mFile);
fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
// 16-bit val, format type id (extensible: 0xFFFE)
fwrite16le(0xFFFE, mFile);
// 16-bit val, channel count
fwrite16le(static_cast<ALushort>(channels), mFile);
// 32-bit val, frequency
fwrite32le(mDevice->Frequency, mFile);
// 32-bit val, bytes per second
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
// 16-bit val, frame size
fwrite16le(static_cast<ALushort>(channels * bytes), mFile);
// 16-bit val, bits per sample
fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
// 16-bit val, extra byte count
fwrite16le(22, mFile);
// 16-bit val, valid bits per sample
fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
// 32-bit val, channel mask
fwrite32le(chanmask, mFile);
// 16 byte GUID, sub-type format
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
(void)val;
fputs("data", mFile);
fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
if(ferror(mFile))
{
ERR("Error writing header: %s\n", strerror(errno));
return false;
}
mDataStart = ftell(mFile);
SetDefaultWFXChannelOrder(mDevice);
const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
mBuffer.resize(bufsize);
return true;
}
bool WaveBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void WaveBackend::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
long size{ftell(mFile)};
if(size > 0)
{
long dataLen{size - mDataStart};
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
fwrite32le(static_cast<ALuint>(dataLen), mFile); // 'data' header len
if(fseek(mFile, 4, SEEK_SET) == 0)
fwrite32le(static_cast<ALuint>(size-8), mFile); // 'WAVE' header len
}
}
} // namespace
bool WaveBackendFactory::init()
{ return true; }
bool WaveBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback; }
void WaveBackendFactory::probe(DevProbe type, std::string *outnames)
{
switch(type)
{
case DevProbe::Playback:
/* Includes null char. */
outnames->append(waveDevice, sizeof(waveDevice));
break;
case DevProbe::Capture:
break;
}
}
BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WaveBackend{device}};
return nullptr;
}
BackendFactory &WaveBackendFactory::getFactory()
{
static WaveBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_WAVE_H
#define BACKENDS_WAVE_H
#include "backends/base.h"
struct WaveBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_WAVE_H */
-636
View File
@@ -1,636 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 1999-2007 by authors.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include "backends/winmm.h"
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <windows.h>
#include <mmsystem.h>
#include <array>
#include <atomic>
#include <thread>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include "alcmain.h"
#include "alexcpt.h"
#include "alu.h"
#include "ringbuffer.h"
#include "strutils.h"
#include "threads.h"
#include "compat.h"
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
#endif
namespace {
#define DEVNAME_HEAD "OpenAL Soft on "
al::vector<std::string> PlaybackDevices;
al::vector<std::string> CaptureDevices;
bool checkName(const al::vector<std::string> &list, const std::string &name)
{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
void ProbePlaybackDevices(void)
{
PlaybackDevices.clear();
ALuint numdevs{waveOutGetNumDevs()};
PlaybackDevices.reserve(numdevs);
for(ALuint i{0};i < numdevs;i++)
{
std::string dname;
WAVEOUTCAPSW WaveCaps{};
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
int count{1};
std::string newname{basename};
while(checkName(PlaybackDevices, newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
dname = std::move(newname);
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
}
PlaybackDevices.emplace_back(std::move(dname));
}
}
void ProbeCaptureDevices(void)
{
CaptureDevices.clear();
ALuint numdevs{waveInGetNumDevs()};
CaptureDevices.reserve(numdevs);
for(ALuint i{0};i < numdevs;i++)
{
std::string dname;
WAVEINCAPSW WaveCaps{};
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
{
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
int count{1};
std::string newname{basename};
while(checkName(CaptureDevices, newname))
{
newname = basename;
newname += " #";
newname += std::to_string(++count);
}
dname = std::move(newname);
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
}
CaptureDevices.emplace_back(std::move(dname));
}
}
struct WinMMPlayback final : public BackendBase {
WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
~WinMMPlayback() override;
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
int mixerProc();
void open(const ALCchar *name) override;
bool reset() override;
bool start() override;
void stop() override;
std::atomic<ALuint> mWritable{0u};
al::semaphore mSem;
ALuint mIdx{0u};
std::array<WAVEHDR,4> mWaveBuffer{};
HWAVEOUT mOutHdl{nullptr};
WAVEFORMATEX mFormat{};
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMPlayback)
};
WinMMPlayback::~WinMMPlayback()
{
if(mOutHdl)
waveOutClose(mOutHdl);
mOutHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMPlayback::waveOutProc
*
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
* completed and returns to the application (for more data)
*/
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
{
if(msg != WOM_DONE) return;
mWritable.fetch_add(1, std::memory_order_acq_rel);
mSem.post();
}
FORCE_ALIGN int WinMMPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const size_t frame_step{mDevice->channelsFromFmt()};
std::unique_lock<WinMMPlayback> dlock{*this};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
ALsizei todo = mWritable.load(std::memory_order_acquire);
if(todo < 1)
{
dlock.unlock();
mSem.wait();
dlock.lock();
continue;
}
size_t widx{mIdx};
do {
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
aluMixData(mDevice, waveHdr.lpData, mDevice->UpdateSize, frame_step);
mWritable.fetch_sub(1, std::memory_order_acq_rel);
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
mIdx = static_cast<ALuint>(widx);
}
return 0;
}
void WinMMPlayback::open(const ALCchar *name)
{
if(PlaybackDevices.empty())
ProbePlaybackDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
PlaybackDevices.cbegin();
if(iter == PlaybackDevices.cend())
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
retry_open:
mFormat = WAVEFORMATEX{};
if(mDevice->FmtType == DevFmtFloat)
{
mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
mFormat.wBitsPerSample = 32;
}
else
{
mFormat.wFormatTag = WAVE_FORMAT_PCM;
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
mFormat.wBitsPerSample = 8;
else
mFormat.wBitsPerSample = 16;
}
mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
mFormat.nSamplesPerSec = mDevice->Frequency;
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
mFormat.cbSize = 0;
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
{
if(mDevice->FmtType == DevFmtFloat)
{
mDevice->FmtType = DevFmtShort;
goto retry_open;
}
throw al::backend_exception{ALC_INVALID_VALUE, "waveOutOpen failed: %u", res};
}
mDevice->DeviceName = PlaybackDevices[DeviceID];
}
bool WinMMPlayback::reset()
{
mDevice->BufferSize = static_cast<ALuint>(uint64_t{mDevice->BufferSize} *
mFormat.nSamplesPerSec / mDevice->Frequency);
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3;
mDevice->UpdateSize = mDevice->BufferSize / 4;
mDevice->Frequency = mFormat.nSamplesPerSec;
if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
{
if(mFormat.wBitsPerSample == 32)
mDevice->FmtType = DevFmtFloat;
else
{
ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
return false;
}
}
else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
{
if(mFormat.wBitsPerSample == 16)
mDevice->FmtType = DevFmtShort;
else if(mFormat.wBitsPerSample == 8)
mDevice->FmtType = DevFmtUByte;
else
{
ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
return false;
}
}
else
{
ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
return false;
}
if(mFormat.nChannels == 2)
mDevice->FmtChans = DevFmtStereo;
else if(mFormat.nChannels == 1)
mDevice->FmtChans = DevFmtMono;
else
{
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
return false;
}
SetDefaultWFXChannelOrder(mDevice);
ALuint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
al_free(mWaveBuffer[0].lpData);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();i++)
{
mWaveBuffer[i] = WAVEHDR{};
mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
mWaveBuffer[i].dwBufferLength = BufferSize;
}
mIdx = 0;
return true;
}
bool WinMMPlayback::start()
{
try {
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
[this](WAVEHDR &waveHdr) -> void
{ waveOutPrepareHeader(mOutHdl, &waveHdr, static_cast<UINT>(sizeof(WAVEHDR))); }
);
mWritable.store(static_cast<ALuint>(mWaveBuffer.size()), std::memory_order_release);
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
return true;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void WinMMPlayback::stop()
{
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
return;
mThread.join();
while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
mSem.wait();
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
[this](WAVEHDR &waveHdr) -> void
{ waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); }
);
mWritable.store(0, std::memory_order_release);
}
struct WinMMCapture final : public BackendBase {
WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
~WinMMCapture() override;
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
int captureProc();
void open(const ALCchar *name) override;
bool start() override;
void stop() override;
ALCenum captureSamples(al::byte *buffer, ALCuint samples) override;
ALCuint availableSamples() override;
std::atomic<ALuint> mReadable{0u};
al::semaphore mSem;
ALuint mIdx{0};
std::array<WAVEHDR,4> mWaveBuffer{};
HWAVEIN mInHdl{nullptr};
RingBufferPtr mRing{nullptr};
WAVEFORMATEX mFormat{};
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(WinMMCapture)
};
WinMMCapture::~WinMMCapture()
{
// Close the Wave device
if(mInHdl)
waveInClose(mInHdl);
mInHdl = nullptr;
al_free(mWaveBuffer[0].lpData);
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
}
/* WinMMCapture::waveInProc
*
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
* completed and returns to the application (with more data).
*/
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
{
if(msg != WIM_DATA) return;
mReadable.fetch_add(1, std::memory_order_acq_rel);
mSem.post();
}
int WinMMCapture::captureProc()
{
althrd_setname(RECORD_THREAD_NAME);
std::unique_lock<WinMMCapture> dlock{*this};
while(!mKillNow.load(std::memory_order_acquire) &&
mDevice->Connected.load(std::memory_order_acquire))
{
ALuint todo{mReadable.load(std::memory_order_acquire)};
if(todo < 1)
{
dlock.unlock();
mSem.wait();
dlock.lock();
continue;
}
size_t widx{mIdx};
do {
WAVEHDR &waveHdr = mWaveBuffer[widx];
widx = (widx+1) % mWaveBuffer.size();
mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
mReadable.fetch_sub(1, std::memory_order_acq_rel);
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
} while(--todo);
mIdx = static_cast<ALuint>(widx);
}
return 0;
}
void WinMMCapture::open(const ALCchar *name)
{
if(CaptureDevices.empty())
ProbeCaptureDevices();
// Find the Device ID matching the deviceName if valid
auto iter = name ?
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
CaptureDevices.cbegin();
if(iter == CaptureDevices.cend())
throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
switch(mDevice->FmtChans)
{
case DevFmtMono:
case DevFmtStereo:
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX51Rear:
case DevFmtX61:
case DevFmtX71:
case DevFmtAmbi3D:
throw al::backend_exception{ALC_INVALID_VALUE, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
switch(mDevice->FmtType)
{
case DevFmtUByte:
case DevFmtShort:
case DevFmtInt:
case DevFmtFloat:
break;
case DevFmtByte:
case DevFmtUShort:
case DevFmtUInt:
throw al::backend_exception{ALC_INVALID_VALUE, "%s samples not supported",
DevFmtTypeString(mDevice->FmtType)};
}
mFormat = WAVEFORMATEX{};
mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
mFormat.nSamplesPerSec = mDevice->Frequency;
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
mFormat.cbSize = 0;
MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
if(res != MMSYSERR_NOERROR)
throw al::backend_exception{ALC_INVALID_VALUE, "waveInOpen failed: %u", res};
// Ensure each buffer is 50ms each
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
BufferSize -= (BufferSize % mFormat.nBlockAlign);
// Allocate circular memory buffer for the captured audio
// Make sure circular buffer is at least 100ms in size
ALuint CapturedDataSize{mDevice->BufferSize};
CapturedDataSize = static_cast<ALuint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
al_free(mWaveBuffer[0].lpData);
mWaveBuffer[0] = WAVEHDR{};
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
mWaveBuffer[0].dwBufferLength = BufferSize;
for(size_t i{1};i < mWaveBuffer.size();++i)
{
mWaveBuffer[i] = WAVEHDR{};
mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
}
mDevice->DeviceName = CaptureDevices[DeviceID];
}
bool WinMMCapture::start()
{
try {
for(size_t i{0};i < mWaveBuffer.size();++i)
{
waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
}
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
waveInStart(mInHdl);
return true;
}
catch(std::exception& e) {
ERR("Failed to start mixing thread: %s\n", e.what());
}
catch(...) {
}
return false;
}
void WinMMCapture::stop()
{
waveInStop(mInHdl);
mKillNow.store(true, std::memory_order_release);
if(mThread.joinable())
{
mSem.post();
mThread.join();
}
waveInReset(mInHdl);
for(size_t i{0};i < mWaveBuffer.size();++i)
waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
mReadable.store(0, std::memory_order_release);
mIdx = 0;
}
ALCenum WinMMCapture::captureSamples(al::byte *buffer, ALCuint samples)
{
mRing->read(buffer, samples);
return ALC_NO_ERROR;
}
ALCuint WinMMCapture::availableSamples()
{ return static_cast<ALCuint>(mRing->readSpace()); }
} // namespace
bool WinMMBackendFactory::init()
{ return true; }
bool WinMMBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
void WinMMBackendFactory::probe(DevProbe type, std::string *outnames)
{
auto add_device = [outnames](const std::string &dname) -> void
{
/* +1 to also append the null char (to ensure a null-separated list and
* double-null terminated list).
*/
if(!dname.empty())
outnames->append(dname.c_str(), dname.length()+1);
};
switch(type)
{
case DevProbe::Playback:
ProbePlaybackDevices();
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case DevProbe::Capture:
ProbeCaptureDevices();
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
}
BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new WinMMPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new WinMMCapture{device}};
return nullptr;
}
BackendFactory &WinMMBackendFactory::getFactory()
{
static WinMMBackendFactory factory{};
return factory;
}
-19
View File
@@ -1,19 +0,0 @@
#ifndef BACKENDS_WINMM_H
#define BACKENDS_WINMM_H
#include "backends/base.h"
struct WinMMBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
void probe(DevProbe type, std::string *outnames) override;
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_WINMM_H */
-159
View File
@@ -1,159 +0,0 @@
#include "config.h"
#include "bformatdec.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <iterator>
#include <numeric>
#include "AL/al.h"
#include "almalloc.h"
#include "alu.h"
#include "ambdec.h"
#include "filters/splitter.h"
#include "opthelpers.h"
namespace {
constexpr std::array<float,MAX_AMBI_ORDER+1> Ambi3DDecoderHFScale{{
1.00000000e+00f, 1.00000000e+00f
}};
constexpr std::array<float,MAX_AMBI_ORDER+1> Ambi3DDecoderHFScale2O{{
7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f
}};
constexpr std::array<float,MAX_AMBI_ORDER+1> Ambi3DDecoderHFScale3O{{
5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f
}};
inline auto GetDecoderHFScales(ALuint order) noexcept -> const std::array<float,MAX_AMBI_ORDER+1>&
{
if(order >= 3) return Ambi3DDecoderHFScale3O;
if(order == 2) return Ambi3DDecoderHFScale2O;
return Ambi3DDecoderHFScale;
}
inline auto GetAmbiScales(AmbDecScale scaletype) noexcept
-> const std::array<float,MAX_AMBI_CHANNELS>&
{
if(scaletype == AmbDecScale::FuMa) return AmbiScale::FromFuMa;
if(scaletype == AmbDecScale::SN3D) return AmbiScale::FromSN3D;
return AmbiScale::FromN3D;
}
} // namespace
BFormatDec::BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans,
const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]) : mChannelDec{inchans}
{
mDualBand = allow_2band && (conf->FreqBands == 2);
const bool periphonic{(conf->ChanMask&AMBI_PERIPHONIC_MASK) != 0};
const std::array<float,MAX_AMBI_CHANNELS> &coeff_scale = GetAmbiScales(conf->CoeffScale);
if(!mDualBand)
{
for(size_t j{0},k{0};j < mChannelDec.size();++j)
{
const size_t acn{periphonic ? j : AmbiIndex::From2D[j]};
if(!(conf->ChanMask&(1u<<acn))) continue;
const size_t order{AmbiIndex::OrderFromChannel[acn]};
const float gain{conf->HFOrderGain[order] / coeff_scale[acn]};
for(size_t i{0u};i < conf->Speakers.size();++i)
{
const size_t chanidx{chanmap[i]};
mChannelDec[j].mGains.Single[chanidx] = conf->HFMatrix[i][k] * gain;
}
++k;
}
}
else
{
mChannelDec[0].mXOver.init(conf->XOverFreq / static_cast<float>(srate));
for(size_t j{1};j < mChannelDec.size();++j)
mChannelDec[j].mXOver = mChannelDec[0].mXOver;
const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)};
for(size_t j{0},k{0};j < mChannelDec.size();++j)
{
const size_t acn{periphonic ? j : AmbiIndex::From2D[j]};
if(!(conf->ChanMask&(1u<<acn))) continue;
const size_t order{AmbiIndex::OrderFromChannel[acn]};
const float hfGain{conf->HFOrderGain[order] * ratio / coeff_scale[acn]};
const float lfGain{conf->LFOrderGain[order] / ratio / coeff_scale[acn]};
for(size_t i{0u};i < conf->Speakers.size();++i)
{
const size_t chanidx{chanmap[i]};
mChannelDec[j].mGains.Dual[sHFBand][chanidx] = conf->HFMatrix[i][k] * hfGain;
mChannelDec[j].mGains.Dual[sLFBand][chanidx] = conf->LFMatrix[i][k] * lfGain;
}
++k;
}
}
}
BFormatDec::BFormatDec(const ALuint inchans, const al::span<const ChannelDec> chancoeffs)
: mChannelDec{inchans}
{
for(size_t j{0};j < mChannelDec.size();++j)
{
float *outcoeffs{mChannelDec[j].mGains.Single};
for(const ChannelDec &incoeffs : chancoeffs)
*(outcoeffs++) = incoeffs[j];
}
}
void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
const FloatBufferLine *InSamples, const size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);
if(mDualBand)
{
const al::span<const float> hfSamples{mSamples[sHFBand].data(), SamplesToDo};
const al::span<const float> lfSamples{mSamples[sLFBand].data(), SamplesToDo};
for(auto &chandec : mChannelDec)
{
chandec.mXOver.process({InSamples->data(), SamplesToDo}, mSamples[sHFBand].data(),
mSamples[sLFBand].data());
MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand],
chandec.mGains.Dual[sHFBand], 0, 0);
MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sLFBand],
chandec.mGains.Dual[sLFBand], 0, 0);
++InSamples;
}
}
else
{
for(auto &chandec : mChannelDec)
{
MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single,
chandec.mGains.Single, 0, 0);
++InSamples;
}
}
}
auto BFormatDec::GetHFOrderScales(const ALuint in_order, const ALuint out_order) noexcept
-> std::array<float,MAX_AMBI_ORDER+1>
{
std::array<float,MAX_AMBI_ORDER+1> ret{};
assert(out_order >= in_order);
const auto &target = GetDecoderHFScales(out_order);
const auto &input = GetDecoderHFScales(in_order);
for(size_t i{0};i < in_order+1;++i)
ret[i] = input[i] / target[i];
return ret;
}
-71
View File
@@ -1,71 +0,0 @@
#ifndef BFORMATDEC_H
#define BFORMATDEC_H
#include <array>
#include <cstddef>
#include <memory>
#include "AL/al.h"
#include "alcmain.h"
#include "almalloc.h"
#include "alspan.h"
#include "ambidefs.h"
#include "devformat.h"
#include "filters/splitter.h"
struct AmbDecConf;
using ChannelDec = float[MAX_AMBI_CHANNELS];
class BFormatDec {
static constexpr size_t sHFBand{0};
static constexpr size_t sLFBand{1};
static constexpr size_t sNumBands{2};
struct ChannelDecoder {
union MatrixU {
float Dual[sNumBands][MAX_OUTPUT_CHANNELS];
float Single[MAX_OUTPUT_CHANNELS];
} mGains{};
/* NOTE: BandSplitter filter is unused with single-band decoding. */
BandSplitter mXOver;
};
alignas(16) std::array<FloatBufferLine,2> mSamples;
bool mDualBand{false};
al::FlexArray<ChannelDecoder> mChannelDec;
public:
BFormatDec(const AmbDecConf *conf, const bool allow_2band, const ALuint inchans,
const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS]);
BFormatDec(const ALuint inchans, const al::span<const ChannelDec> chancoeffs);
/* Decodes the ambisonic input to the given output channels. */
void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
const size_t SamplesToDo);
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
static std::array<float,MAX_AMBI_ORDER+1> GetHFOrderScales(const ALuint in_order,
const ALuint out_order) noexcept;
static std::unique_ptr<BFormatDec> Create(const AmbDecConf *conf, const bool allow_2band,
const ALuint inchans, const ALuint srate, const ALuint (&chanmap)[MAX_OUTPUT_CHANNELS])
{
return std::unique_ptr<BFormatDec>{new(FamCount{inchans})
BFormatDec{conf, allow_2band, inchans, srate, chanmap}};
}
static std::unique_ptr<BFormatDec> Create(const ALuint inchans,
const al::span<const ChannelDec> chancoeffs)
{
return std::unique_ptr<BFormatDec>{new(FamCount{inchans}) BFormatDec{inchans, chancoeffs}};
}
DEF_FAM_NEWDEL(BFormatDec, mChannelDec)
};
#endif /* BFORMATDEC_H */
-183
View File
@@ -1,183 +0,0 @@
/*-
* Copyright (c) 2005 Boris Mikhaylov
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include <algorithm>
#include <cmath>
#include <iterator>
#include "bs2b.h"
#include "math_defs.h"
/* Set up all data. */
static void init(struct bs2b *bs2b)
{
float Fc_lo, Fc_hi;
float G_lo, G_hi;
float x, g;
switch(bs2b->level)
{
case BS2B_LOW_CLEVEL: /* Low crossfeed level */
Fc_lo = 360.0f;
Fc_hi = 501.0f;
G_lo = 0.398107170553497f;
G_hi = 0.205671765275719f;
break;
case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */
Fc_lo = 500.0f;
Fc_hi = 711.0f;
G_lo = 0.459726988530872f;
G_hi = 0.228208484414988f;
break;
case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */
Fc_lo = 700.0f;
Fc_hi = 1021.0f;
G_lo = 0.530884444230988f;
G_hi = 0.250105790667544f;
break;
case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */
Fc_lo = 360.0f;
Fc_hi = 494.0f;
G_lo = 0.316227766016838f;
G_hi = 0.168236228897329f;
break;
case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */
Fc_lo = 500.0f;
Fc_hi = 689.0f;
G_lo = 0.354813389233575f;
G_hi = 0.187169483835901f;
break;
default: /* High easy crossfeed level */
bs2b->level = BS2B_HIGH_ECLEVEL;
Fc_lo = 700.0f;
Fc_hi = 975.0f;
G_lo = 0.398107170553497f;
G_hi = 0.205671765275719f;
break;
} /* switch */
g = 1.0f / (1.0f - G_hi + G_lo);
/* $fc = $Fc / $s;
* $d = 1 / 2 / pi / $fc;
* $x = exp(-1 / $d);
*/
x = std::exp(-al::MathDefs<float>::Tau() * Fc_lo / static_cast<float>(bs2b->srate));
bs2b->b1_lo = x;
bs2b->a0_lo = G_lo * (1.0f - x) * g;
x = std::exp(-al::MathDefs<float>::Tau() * Fc_hi / static_cast<float>(bs2b->srate));
bs2b->b1_hi = x;
bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g;
bs2b->a1_hi = -x * g;
} /* init */
/* Exported functions.
* See descriptions in "bs2b.h"
*/
void bs2b_set_params(struct bs2b *bs2b, int level, int srate)
{
if(srate <= 0) srate = 1;
bs2b->level = level;
bs2b->srate = srate;
init(bs2b);
} /* bs2b_set_params */
int bs2b_get_level(struct bs2b *bs2b)
{
return bs2b->level;
} /* bs2b_get_level */
int bs2b_get_srate(struct bs2b *bs2b)
{
return bs2b->srate;
} /* bs2b_get_srate */
void bs2b_clear(struct bs2b *bs2b)
{
std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{});
} /* bs2b_clear */
void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo)
{
const float a0_lo{bs2b->a0_lo};
const float b1_lo{bs2b->b1_lo};
const float a0_hi{bs2b->a0_hi};
const float a1_hi{bs2b->a1_hi};
const float b1_hi{bs2b->b1_hi};
float lsamples[128][2];
float rsamples[128][2];
for(size_t base{0};base < SamplesToDo;)
{
const size_t todo{std::min<size_t>(128, SamplesToDo-base)};
/* Process left input */
float z_lo{bs2b->history[0].lo};
float z_hi{bs2b->history[0].hi};
for(size_t i{0};i < todo;i++)
{
lsamples[i][0] = a0_lo*Left[i] + z_lo;
z_lo = b1_lo*lsamples[i][0];
lsamples[i][1] = a0_hi*Left[i] + z_hi;
z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1];
}
bs2b->history[0].lo = z_lo;
bs2b->history[0].hi = z_hi;
/* Process right input */
z_lo = bs2b->history[1].lo;
z_hi = bs2b->history[1].hi;
for(size_t i{0};i < todo;i++)
{
rsamples[i][0] = a0_lo*Right[i] + z_lo;
z_lo = b1_lo*rsamples[i][0];
rsamples[i][1] = a0_hi*Right[i] + z_hi;
z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1];
}
bs2b->history[1].lo = z_lo;
bs2b->history[1].hi = z_hi;
/* Crossfeed */
for(size_t i{0};i < todo;i++)
*(Left++) = lsamples[i][1] + rsamples[i][0];
for(size_t i{0};i < todo;i++)
*(Right++) = rsamples[i][1] + lsamples[i][0];
base += todo;
}
} /* bs2b_cross_feed */
-89
View File
@@ -1,89 +0,0 @@
/*-
* Copyright (c) 2005 Boris Mikhaylov
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef BS2B_H
#define BS2B_H
#include "almalloc.h"
/* Number of crossfeed levels */
#define BS2B_CLEVELS 3
/* Normal crossfeed levels */
#define BS2B_HIGH_CLEVEL 3
#define BS2B_MIDDLE_CLEVEL 2
#define BS2B_LOW_CLEVEL 1
/* Easy crossfeed levels */
#define BS2B_HIGH_ECLEVEL BS2B_HIGH_CLEVEL + BS2B_CLEVELS
#define BS2B_MIDDLE_ECLEVEL BS2B_MIDDLE_CLEVEL + BS2B_CLEVELS
#define BS2B_LOW_ECLEVEL BS2B_LOW_CLEVEL + BS2B_CLEVELS
/* Default crossfeed levels */
#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL
/* Default sample rate (Hz) */
#define BS2B_DEFAULT_SRATE 44100
struct bs2b {
int level; /* Crossfeed level */
int srate; /* Sample rate (Hz) */
/* Lowpass IIR filter coefficients */
float a0_lo;
float b1_lo;
/* Highboost IIR filter coefficients */
float a0_hi;
float a1_hi;
float b1_hi;
/* Buffer of filter history
* [0] - first channel, [1] - second channel
*/
struct t_last_sample {
float lo;
float hi;
} history[2];
DEF_NEWDEL(bs2b)
};
/* Clear buffers and set new coefficients with new crossfeed level and sample
* rate values.
* level - crossfeed level of *LEVEL values.
* srate - sample rate by Hz.
*/
void bs2b_set_params(bs2b *bs2b, int level, int srate);
/* Return current crossfeed level value */
int bs2b_get_level(bs2b *bs2b);
/* Return current sample rate value */
int bs2b_get_srate(bs2b *bs2b);
/* Clear buffer */
void bs2b_clear(bs2b *bs2b);
void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo);
#endif /* BS2B_H */
-9
View File
@@ -1,9 +0,0 @@
#ifndef AL_COMPAT_H
#define AL_COMPAT_H
#include <string>
struct PathNamePair { std::string path, fname; };
const PathNamePair &GetProcBinary(void);
#endif /* AL_COMPAT_H */
-360
View File
@@ -1,360 +0,0 @@
#include "config.h"
#include "converter.h"
#include <algorithm>
#include <cstdint>
#include <iterator>
#include "AL/al.h"
#include "albyte.h"
#include "alu.h"
#include "fpu_modes.h"
#include "mixer/defs.h"
namespace {
/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
* chokes on that given the inline specializations.
*/
template<DevFmtType T>
inline ALfloat LoadSample(typename DevFmtTypeTraits<T>::Type val) noexcept;
template<> inline ALfloat LoadSample<DevFmtByte>(DevFmtTypeTraits<DevFmtByte>::Type val) noexcept
{ return val * (1.0f/128.0f); }
template<> inline ALfloat LoadSample<DevFmtShort>(DevFmtTypeTraits<DevFmtShort>::Type val) noexcept
{ return val * (1.0f/32768.0f); }
template<> inline ALfloat LoadSample<DevFmtInt>(DevFmtTypeTraits<DevFmtInt>::Type val) noexcept
{ return static_cast<float>(val) * (1.0f/2147483648.0f); }
template<> inline ALfloat LoadSample<DevFmtFloat>(DevFmtTypeTraits<DevFmtFloat>::Type val) noexcept
{ return val; }
template<> inline ALfloat LoadSample<DevFmtUByte>(DevFmtTypeTraits<DevFmtUByte>::Type val) noexcept
{ return LoadSample<DevFmtByte>(static_cast<ALbyte>(val - 128)); }
template<> inline ALfloat LoadSample<DevFmtUShort>(DevFmtTypeTraits<DevFmtUShort>::Type val) noexcept
{ return LoadSample<DevFmtShort>(static_cast<ALshort>(val - 32768)); }
template<> inline ALfloat LoadSample<DevFmtUInt>(DevFmtTypeTraits<DevFmtUInt>::Type val) noexcept
{ return LoadSample<DevFmtInt>(static_cast<ALint>(val - 2147483648u)); }
template<DevFmtType T>
inline void LoadSampleArray(ALfloat *RESTRICT dst, const void *src, const size_t srcstep,
const size_t samples) noexcept
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(size_t i{0u};i < samples;i++)
dst[i] = LoadSample<T>(ssrc[i*srcstep]);
}
void LoadSamples(ALfloat *dst, const ALvoid *src, const size_t srcstep, const DevFmtType srctype,
const size_t samples) noexcept
{
#define HANDLE_FMT(T) \
case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
switch(srctype)
{
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
}
#undef HANDLE_FMT
}
template<DevFmtType T>
inline typename DevFmtTypeTraits<T>::Type StoreSample(ALfloat) noexcept;
template<> inline ALfloat StoreSample<DevFmtFloat>(ALfloat val) noexcept
{ return val; }
template<> inline ALint StoreSample<DevFmtInt>(ALfloat val) noexcept
{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
template<> inline ALshort StoreSample<DevFmtShort>(ALfloat val) noexcept
{ return static_cast<ALshort>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
template<> inline ALbyte StoreSample<DevFmtByte>(ALfloat val) noexcept
{ return static_cast<ALbyte>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
/* Define unsigned output variations. */
template<> inline ALuint StoreSample<DevFmtUInt>(ALfloat val) noexcept
{ return static_cast<ALuint>(StoreSample<DevFmtInt>(val)) + 2147483648u; }
template<> inline ALushort StoreSample<DevFmtUShort>(ALfloat val) noexcept
{ return static_cast<ALushort>(StoreSample<DevFmtShort>(val) + 32768); }
template<> inline ALubyte StoreSample<DevFmtUByte>(ALfloat val) noexcept
{ return static_cast<ALubyte>(StoreSample<DevFmtByte>(val) + 128); }
template<DevFmtType T>
inline void StoreSampleArray(void *dst, const ALfloat *RESTRICT src, const size_t dststep,
const size_t samples) noexcept
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
SampleType *sdst = static_cast<SampleType*>(dst);
for(size_t i{0u};i < samples;i++)
sdst[i*dststep] = StoreSample<T>(src[i]);
}
void StoreSamples(ALvoid *dst, const ALfloat *src, const size_t dststep, const DevFmtType dsttype,
const size_t samples) noexcept
{
#define HANDLE_FMT(T) \
case T: StoreSampleArray<T>(dst, src, dststep, samples); break
switch(dsttype)
{
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
}
#undef HANDLE_FMT
}
template<DevFmtType T>
void Mono2Stereo(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(size_t i{0u};i < frames;i++)
dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f;
}
template<DevFmtType T>
void Stereo2Mono(ALfloat *RESTRICT dst, const void *src, const size_t frames) noexcept
{
using SampleType = typename DevFmtTypeTraits<T>::Type;
const SampleType *ssrc = static_cast<const SampleType*>(src);
for(size_t i{0u};i < frames;i++)
dst[i] = (LoadSample<T>(ssrc[i*2 + 0])+LoadSample<T>(ssrc[i*2 + 1])) *
0.707106781187f;
}
} // namespace
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
ALuint srcRate, ALuint dstRate, Resampler resampler)
{
if(numchans < 1 || srcRate < 1 || dstRate < 1)
return nullptr;
SampleConverterPtr converter{new (FamCount{numchans}) SampleConverter{numchans}};
converter->mSrcType = srcType;
converter->mDstType = dstType;
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
converter->mDstTypeSize = BytesFromDevFmt(dstType);
converter->mSrcPrepCount = 0;
converter->mFracOffset = 0;
/* Have to set the mixer FPU mode since that's what the resampler code expects. */
FPUCtl mixer_mode{};
auto step = static_cast<ALuint>(
mind(srcRate*double{FRACTIONONE}/dstRate + 0.5, MAX_PITCH*FRACTIONONE));
converter->mIncrement = maxu(step, 1);
if(converter->mIncrement == FRACTIONONE)
converter->mResample = Resample_<CopyTag,CTag>;
else
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
&converter->mState);
return converter;
}
ALuint SampleConverter::availableOut(ALuint srcframes) const
{
ALint prepcount{mSrcPrepCount};
if(prepcount < 0)
{
/* Negative prepcount means we need to skip that many input samples. */
if(static_cast<ALuint>(-prepcount) >= srcframes)
return 0;
srcframes -= static_cast<ALuint>(-prepcount);
prepcount = 0;
}
if(srcframes < 1)
{
/* No output samples if there's no input samples. */
return 0;
}
if(prepcount < MAX_RESAMPLER_PADDING
&& static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= srcframes)
{
/* Not enough input samples to generate an output sample. */
return 0;
}
auto DataSize64 = static_cast<uint64_t>(prepcount);
DataSize64 += srcframes;
DataSize64 -= MAX_RESAMPLER_PADDING;
DataSize64 <<= FRACTIONBITS;
DataSize64 -= mFracOffset;
/* If we have a full prep, we can generate at least one sample. */
return static_cast<ALuint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, BUFFERSIZE));
}
ALuint SampleConverter::convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes)
{
const ALuint SrcFrameSize{static_cast<ALuint>(mChan.size()) * mSrcTypeSize};
const ALuint DstFrameSize{static_cast<ALuint>(mChan.size()) * mDstTypeSize};
const ALuint increment{mIncrement};
auto SamplesIn = static_cast<const al::byte*>(*src);
ALuint NumSrcSamples{*srcframes};
FPUCtl mixer_mode{};
ALuint pos{0};
while(pos < dstframes && NumSrcSamples > 0)
{
ALint prepcount{mSrcPrepCount};
if(prepcount < 0)
{
/* Negative prepcount means we need to skip that many input samples. */
if(static_cast<ALuint>(-prepcount) >= NumSrcSamples)
{
mSrcPrepCount = static_cast<ALint>(NumSrcSamples) + prepcount;
NumSrcSamples = 0;
break;
}
SamplesIn += SrcFrameSize*static_cast<ALuint>(-prepcount);
NumSrcSamples -= static_cast<ALuint>(-prepcount);
mSrcPrepCount = 0;
continue;
}
ALuint toread{minu(NumSrcSamples, BUFFERSIZE - MAX_RESAMPLER_PADDING)};
if(prepcount < MAX_RESAMPLER_PADDING
&& static_cast<ALuint>(MAX_RESAMPLER_PADDING - prepcount) >= toread)
{
/* Not enough input samples to generate an output sample. Store
* what we're given for later.
*/
for(size_t chan{0u};chan < mChan.size();chan++)
LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
mChan.size(), mSrcType, toread);
mSrcPrepCount = prepcount + static_cast<ALint>(toread);
NumSrcSamples = 0;
break;
}
ALfloat *RESTRICT SrcData{mSrcSamples};
ALfloat *RESTRICT DstData{mDstSamples};
ALuint DataPosFrac{mFracOffset};
auto DataSize64 = static_cast<uint64_t>(prepcount);
DataSize64 += toread;
DataSize64 -= MAX_RESAMPLER_PADDING;
DataSize64 <<= FRACTIONBITS;
DataSize64 -= DataPosFrac;
/* If we have a full prep, we can generate at least one sample. */
auto DstSize = static_cast<ALuint>(
clampu64((DataSize64 + increment-1)/increment, 1, BUFFERSIZE));
DstSize = minu(DstSize, dstframes-pos);
for(size_t chan{0u};chan < mChan.size();chan++)
{
const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan};
al::byte *DstSamples = static_cast<al::byte*>(dst) + mDstTypeSize*chan;
/* Load the previous samples into the source data first, then the
* new samples from the input buffer.
*/
std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
/* Store as many prep samples for next time as possible, given the
* number of output samples being generated.
*/
ALuint SrcDataEnd{(DstSize*increment + DataPosFrac)>>FRACTIONBITS};
if(SrcDataEnd >= static_cast<ALuint>(prepcount)+toread)
std::fill(std::begin(mChan[chan].PrevSamples),
std::end(mChan[chan].PrevSamples), 0.0f);
else
{
const size_t len{minz(al::size(mChan[chan].PrevSamples),
static_cast<ALuint>(prepcount)+toread-SrcDataEnd)};
std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples);
std::fill(std::begin(mChan[chan].PrevSamples)+len,
std::end(mChan[chan].PrevSamples), 0.0f);
}
/* Now resample, and store the result in the output buffer. */
const ALfloat *ResampledData{mResample(&mState, SrcData+(MAX_RESAMPLER_PADDING>>1),
DataPosFrac, increment, {DstData, DstSize})};
StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
}
/* Update the number of prep samples still available, as well as the
* fractional offset.
*/
DataPosFrac += increment*DstSize;
mSrcPrepCount = mini(prepcount + static_cast<ALint>(toread - (DataPosFrac>>FRACTIONBITS)),
MAX_RESAMPLER_PADDING);
mFracOffset = DataPosFrac & FRACTIONMASK;
/* Update the src and dst pointers in case there's still more to do. */
SamplesIn += SrcFrameSize*(DataPosFrac>>FRACTIONBITS);
NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>FRACTIONBITS));
dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
pos += DstSize;
}
*src = SamplesIn;
*srcframes = NumSrcSamples;
return pos;
}
void ChannelConverter::convert(const ALvoid *src, ALfloat *dst, ALuint frames) const
{
if(mSrcChans == DevFmtStereo && mDstChans == DevFmtMono)
{
switch(mSrcType)
{
#define HANDLE_FMT(T) case T: Stereo2Mono<T>(dst, src, frames); break
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
#undef HANDLE_FMT
}
}
else if(mSrcChans == DevFmtMono && mDstChans == DevFmtStereo)
{
switch(mSrcType)
{
#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break
HANDLE_FMT(DevFmtByte);
HANDLE_FMT(DevFmtUByte);
HANDLE_FMT(DevFmtShort);
HANDLE_FMT(DevFmtUShort);
HANDLE_FMT(DevFmtInt);
HANDLE_FMT(DevFmtUInt);
HANDLE_FMT(DevFmtFloat);
#undef HANDLE_FMT
}
}
else
LoadSamples(dst, src, 1u, mSrcType, frames * ChannelsFromDevFmt(mSrcChans, 0));
}
-61
View File
@@ -1,61 +0,0 @@
#ifndef CONVERTER_H
#define CONVERTER_H
#include <cstddef>
#include <memory>
#include "AL/al.h"
#include "alcmain.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alu.h"
#include "devformat.h"
#include "voice.h"
struct SampleConverter {
DevFmtType mSrcType{};
DevFmtType mDstType{};
ALuint mSrcTypeSize{};
ALuint mDstTypeSize{};
ALint mSrcPrepCount{};
ALuint mFracOffset{};
ALuint mIncrement{};
InterpState mState{};
ResamplerFunc mResample{};
alignas(16) ALfloat mSrcSamples[BUFFERSIZE]{};
alignas(16) ALfloat mDstSamples[BUFFERSIZE]{};
struct ChanSamples {
alignas(16) ALfloat PrevSamples[MAX_RESAMPLER_PADDING];
};
al::FlexArray<ChanSamples> mChan;
SampleConverter(size_t numchans) : mChan{numchans} { }
ALuint convert(const ALvoid **src, ALuint *srcframes, ALvoid *dst, ALuint dstframes);
ALuint availableOut(ALuint srcframes) const;
DEF_FAM_NEWDEL(SampleConverter, mChan)
};
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
ALuint srcRate, ALuint dstRate, Resampler resampler);
struct ChannelConverter {
DevFmtType mSrcType;
DevFmtChannels mSrcChans;
DevFmtChannels mDstChans;
bool is_active() const noexcept { return mSrcChans != mDstChans; }
void convert(const ALvoid *src, ALfloat *dst, ALuint frames) const;
};
#endif /* CONVERTER_H */
-16
View File
@@ -1,16 +0,0 @@
#ifndef CPU_CAPS_H
#define CPU_CAPS_H
extern int CPUCapFlags;
enum {
CPU_CAP_SSE = 1<<0,
CPU_CAP_SSE2 = 1<<1,
CPU_CAP_SSE3 = 1<<2,
CPU_CAP_SSE4_1 = 1<<3,
CPU_CAP_NEON = 1<<4,
};
void FillCPUCaps(int capfilter);
#endif /* CPU_CAPS_H */
-121
View File
@@ -1,121 +0,0 @@
#ifndef ALC_DEVFORMAT_H
#define ALC_DEVFORMAT_H
#include <cstdint>
#include "AL/al.h"
#include "AL/alext.h"
#include "inprogext.h"
enum Channel {
FrontLeft = 0,
FrontRight,
FrontCenter,
LFE,
BackLeft,
BackRight,
BackCenter,
SideLeft,
SideRight,
UpperFrontLeft,
UpperFrontRight,
UpperBackLeft,
UpperBackRight,
LowerFrontLeft,
LowerFrontRight,
LowerBackLeft,
LowerBackRight,
Aux0,
Aux1,
Aux2,
Aux3,
Aux4,
Aux5,
Aux6,
Aux7,
Aux8,
Aux9,
Aux10,
Aux11,
Aux12,
Aux13,
Aux14,
Aux15,
MaxChannels
};
/* Device formats */
enum DevFmtType : ALenum {
DevFmtByte = ALC_BYTE_SOFT,
DevFmtUByte = ALC_UNSIGNED_BYTE_SOFT,
DevFmtShort = ALC_SHORT_SOFT,
DevFmtUShort = ALC_UNSIGNED_SHORT_SOFT,
DevFmtInt = ALC_INT_SOFT,
DevFmtUInt = ALC_UNSIGNED_INT_SOFT,
DevFmtFloat = ALC_FLOAT_SOFT,
DevFmtTypeDefault = DevFmtFloat
};
enum DevFmtChannels : ALenum {
DevFmtMono = ALC_MONO_SOFT,
DevFmtStereo = ALC_STEREO_SOFT,
DevFmtQuad = ALC_QUAD_SOFT,
DevFmtX51 = ALC_5POINT1_SOFT,
DevFmtX61 = ALC_6POINT1_SOFT,
DevFmtX71 = ALC_7POINT1_SOFT,
DevFmtAmbi3D = ALC_BFORMAT3D_SOFT,
/* Similar to 5.1, except using rear channels instead of sides */
DevFmtX51Rear = 0x70000000,
DevFmtChannelsDefault = DevFmtStereo
};
#define MAX_OUTPUT_CHANNELS (16)
/* DevFmtType traits, providing the type, etc given a DevFmtType. */
template<DevFmtType T>
struct DevFmtTypeTraits { };
template<>
struct DevFmtTypeTraits<DevFmtByte> { using Type = int8_t; };
template<>
struct DevFmtTypeTraits<DevFmtUByte> { using Type = uint8_t; };
template<>
struct DevFmtTypeTraits<DevFmtShort> { using Type = int16_t; };
template<>
struct DevFmtTypeTraits<DevFmtUShort> { using Type = uint16_t; };
template<>
struct DevFmtTypeTraits<DevFmtInt> { using Type = int32_t; };
template<>
struct DevFmtTypeTraits<DevFmtUInt> { using Type = uint32_t; };
template<>
struct DevFmtTypeTraits<DevFmtFloat> { using Type = float; };
ALuint BytesFromDevFmt(DevFmtType type) noexcept;
ALuint ChannelsFromDevFmt(DevFmtChannels chans, ALuint ambiorder) noexcept;
inline ALuint FrameSizeFromDevFmt(DevFmtChannels chans, DevFmtType type, ALuint ambiorder) noexcept
{ return ChannelsFromDevFmt(chans, ambiorder) * BytesFromDevFmt(type); }
enum class AmbiLayout {
FuMa = ALC_FUMA_SOFT, /* FuMa channel order */
ACN = ALC_ACN_SOFT, /* ACN channel order */
Default = ACN
};
enum class AmbiNorm {
FuMa = ALC_FUMA_SOFT, /* FuMa normalization */
SN3D = ALC_SN3D_SOFT, /* SN3D normalization */
N3D = ALC_N3D_SOFT, /* N3D normalization */
Default = SN3D
};
#endif /* ALC_DEVFORMAT_H */
-299
View File
@@ -1,299 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2018 by Raul Herraiz.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vecmat.h"
namespace {
#define MIN_FREQ 20.0f
#define MAX_FREQ 2500.0f
#define Q_FACTOR 5.0f
struct AutowahState final : public EffectState {
/* Effect parameters */
ALfloat mAttackRate;
ALfloat mReleaseRate;
ALfloat mResonanceGain;
ALfloat mPeakGain;
ALfloat mFreqMinNorm;
ALfloat mBandwidthNorm;
ALfloat mEnvDelay;
/* Filter components derived from the envelope. */
struct {
ALfloat cos_w0;
ALfloat alpha;
} mEnv[BUFFERSIZE];
struct {
/* Effect filters' history. */
struct {
ALfloat z1, z2;
} Filter;
/* Effect gains for each output channel */
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS];
ALfloat TargetGains[MAX_OUTPUT_CHANNELS];
} mChans[MAX_AMBI_CHANNELS];
/* Effects buffers */
alignas(16) ALfloat mBufferOut[BUFFERSIZE];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(AutowahState)
};
ALboolean AutowahState::deviceUpdate(const ALCdevice*)
{
/* (Re-)initializing parameters and clear the buffers. */
mAttackRate = 1.0f;
mReleaseRate = 1.0f;
mResonanceGain = 10.0f;
mPeakGain = 4.5f;
mFreqMinNorm = 4.5e-4f;
mBandwidthNorm = 0.05f;
mEnvDelay = 0.0f;
for(auto &e : mEnv)
{
e.cos_w0 = 0.0f;
e.alpha = 0.0f;
}
for(auto &chan : mChans)
{
std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
chan.Filter.z1 = 0.0f;
chan.Filter.z2 = 0.0f;
}
return AL_TRUE;
}
void AutowahState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
const auto frequency = static_cast<float>(device->Frequency);
const ALfloat ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
/* 0-20dB Resonance Peak gain */
mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain/AL_AUTOWAH_MAX_PEAK_GAIN);
mFreqMinNorm = MIN_FREQ / frequency;
mBandwidthNorm = (MAX_FREQ-MIN_FREQ) / frequency;
mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
}
void AutowahState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const ALfloat attack_rate = mAttackRate;
const ALfloat release_rate = mReleaseRate;
const ALfloat res_gain = mResonanceGain;
const ALfloat peak_gain = mPeakGain;
const ALfloat freq_min = mFreqMinNorm;
const ALfloat bandwidth = mBandwidthNorm;
ALfloat env_delay{mEnvDelay};
for(size_t i{0u};i < samplesToDo;i++)
{
ALfloat w0, sample, a;
/* Envelope follower described on the book: Audio Effects, Theory,
* Implementation and Application.
*/
sample = peak_gain * std::fabs(samplesIn[0][i]);
a = (sample > env_delay) ? attack_rate : release_rate;
env_delay = lerp(sample, env_delay, a);
/* Calculate the cos and alpha components for this sample's filter. */
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
mEnv[i].cos_w0 = cosf(w0);
mEnv[i].alpha = sinf(w0)/(2.0f * Q_FACTOR);
}
mEnvDelay = env_delay;
auto chandata = std::addressof(mChans[0]);
for(const auto &insamples : samplesIn)
{
/* This effectively inlines BiquadFilter_setParams for a peaking
* filter and BiquadFilter_processC. The alpha and cosine components
* for the filter coefficients were previously calculated with the
* envelope. Because the filter changes for each sample, the
* coefficients are transient and don't need to be held.
*/
ALfloat z1{chandata->Filter.z1};
ALfloat z2{chandata->Filter.z2};
for(size_t i{0u};i < samplesToDo;i++)
{
const ALfloat alpha = mEnv[i].alpha;
const ALfloat cos_w0 = mEnv[i].cos_w0;
ALfloat input, output;
ALfloat a[3], b[3];
b[0] = 1.0f + alpha*res_gain;
b[1] = -2.0f * cos_w0;
b[2] = 1.0f - alpha*res_gain;
a[0] = 1.0f + alpha/res_gain;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha/res_gain;
input = insamples[i];
output = input*(b[0]/a[0]) + z1;
z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
mBufferOut[i] = output;
}
chandata->Filter.z1 = z1;
chandata->Filter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
chandata->TargetGains, samplesToDo, 0);
++chandata;
}
}
void Autowah_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_AUTOWAH_ATTACK_TIME:
if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah attack time out of range");
props->Autowah.AttackTime = val;
break;
case AL_AUTOWAH_RELEASE_TIME:
if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah release time out of range");
props->Autowah.ReleaseTime = val;
break;
case AL_AUTOWAH_RESONANCE:
if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah resonance out of range");
props->Autowah.Resonance = val;
break;
case AL_AUTOWAH_PEAK_GAIN:
if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Autowah peak gain out of range");
props->Autowah.PeakGain = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
}
}
void Autowah_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Autowah_setParamf(props, context, param, vals[0]); }
void Autowah_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
void Autowah_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
void Autowah_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_AUTOWAH_ATTACK_TIME:
*val = props->Autowah.AttackTime;
break;
case AL_AUTOWAH_RELEASE_TIME:
*val = props->Autowah.ReleaseTime;
break;
case AL_AUTOWAH_RESONANCE:
*val = props->Autowah.Resonance;
break;
case AL_AUTOWAH_PEAK_GAIN:
*val = props->Autowah.PeakGain;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param);
}
}
void Autowah_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Autowah_getParamf(props, context, param, vals); }
void Autowah_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param); }
void Autowah_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param); }
DEFINE_ALEFFECT_VTABLE(Autowah);
struct AutowahStateFactory final : public EffectStateFactory {
EffectState *create() override { return new AutowahState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Autowah_vtable; }
};
EffectProps AutowahStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME;
props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME;
props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE;
props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN;
return props;
}
} // namespace
EffectStateFactory *AutowahStateFactory_getFactory()
{
static AutowahStateFactory AutowahFactory{};
return &AutowahFactory;
}
-193
View File
@@ -1,193 +0,0 @@
#ifndef EFFECTS_BASE_H
#define EFFECTS_BASE_H
#include <cstddef>
#include "alcmain.h"
#include "almalloc.h"
#include "alspan.h"
#include "atomic.h"
#include "intrusive_ptr.h"
struct ALeffectslot;
union EffectProps {
struct {
// Shared Reverb Properties
ALfloat Density;
ALfloat Diffusion;
ALfloat Gain;
ALfloat GainHF;
ALfloat DecayTime;
ALfloat DecayHFRatio;
ALfloat ReflectionsGain;
ALfloat ReflectionsDelay;
ALfloat LateReverbGain;
ALfloat LateReverbDelay;
ALfloat AirAbsorptionGainHF;
ALfloat RoomRolloffFactor;
bool DecayHFLimit;
// Additional EAX Reverb Properties
ALfloat GainLF;
ALfloat DecayLFRatio;
ALfloat ReflectionsPan[3];
ALfloat LateReverbPan[3];
ALfloat EchoTime;
ALfloat EchoDepth;
ALfloat ModulationTime;
ALfloat ModulationDepth;
ALfloat HFReference;
ALfloat LFReference;
} Reverb;
struct {
ALfloat AttackTime;
ALfloat ReleaseTime;
ALfloat Resonance;
ALfloat PeakGain;
} Autowah;
struct {
ALint Waveform;
ALint Phase;
ALfloat Rate;
ALfloat Depth;
ALfloat Feedback;
ALfloat Delay;
} Chorus; /* Also Flanger */
struct {
bool OnOff;
} Compressor;
struct {
ALfloat Edge;
ALfloat Gain;
ALfloat LowpassCutoff;
ALfloat EQCenter;
ALfloat EQBandwidth;
} Distortion;
struct {
ALfloat Delay;
ALfloat LRDelay;
ALfloat Damping;
ALfloat Feedback;
ALfloat Spread;
} Echo;
struct {
ALfloat LowCutoff;
ALfloat LowGain;
ALfloat Mid1Center;
ALfloat Mid1Gain;
ALfloat Mid1Width;
ALfloat Mid2Center;
ALfloat Mid2Gain;
ALfloat Mid2Width;
ALfloat HighCutoff;
ALfloat HighGain;
} Equalizer;
struct {
ALfloat Frequency;
ALint LeftDirection;
ALint RightDirection;
} Fshifter;
struct {
ALfloat Frequency;
ALfloat HighPassCutoff;
ALint Waveform;
} Modulator;
struct {
ALint CoarseTune;
ALint FineTune;
} Pshifter;
struct {
ALfloat Rate;
ALint PhonemeA;
ALint PhonemeB;
ALint PhonemeACoarseTuning;
ALint PhonemeBCoarseTuning;
ALint Waveform;
} Vmorpher;
struct {
ALfloat Gain;
} Dedicated;
};
struct EffectVtable {
void (*const setParami)(EffectProps *props, ALCcontext *context, ALenum param, ALint val);
void (*const setParamiv)(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals);
void (*const setParamf)(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val);
void (*const setParamfv)(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals);
void (*const getParami)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val);
void (*const getParamiv)(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals);
void (*const getParamf)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val);
void (*const getParamfv)(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals);
};
#define DEFINE_ALEFFECT_VTABLE(T) \
const EffectVtable T##_vtable = { \
T##_setParami, T##_setParamiv, \
T##_setParamf, T##_setParamfv, \
T##_getParami, T##_getParamiv, \
T##_getParamf, T##_getParamfv, \
}
struct EffectTarget {
MixParams *Main;
RealMixParams *RealOut;
};
struct EffectState : public al::intrusive_ref<EffectState> {
al::span<FloatBufferLine> mOutTarget;
virtual ~EffectState() = default;
virtual ALboolean deviceUpdate(const ALCdevice *device) = 0;
virtual void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) = 0;
virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) = 0;
};
struct EffectStateFactory {
virtual ~EffectStateFactory() { }
virtual EffectState *create() = 0;
virtual EffectProps getDefaultProps() const noexcept = 0;
virtual const EffectVtable *getEffectVtable() const noexcept = 0;
};
EffectStateFactory *NullStateFactory_getFactory(void);
EffectStateFactory *ReverbStateFactory_getFactory(void);
EffectStateFactory *StdReverbStateFactory_getFactory(void);
EffectStateFactory *AutowahStateFactory_getFactory(void);
EffectStateFactory *ChorusStateFactory_getFactory(void);
EffectStateFactory *CompressorStateFactory_getFactory(void);
EffectStateFactory *DistortionStateFactory_getFactory(void);
EffectStateFactory *EchoStateFactory_getFactory(void);
EffectStateFactory *EqualizerStateFactory_getFactory(void);
EffectStateFactory *FlangerStateFactory_getFactory(void);
EffectStateFactory *FshifterStateFactory_getFactory(void);
EffectStateFactory *ModulatorStateFactory_getFactory(void);
EffectStateFactory *PshifterStateFactory_getFactory(void);
EffectStateFactory* VmorpherStateFactory_getFactory(void);
EffectStateFactory *DedicatedStateFactory_getFactory(void);
#endif /* EFFECTS_BASE_H */
-534
View File
@@ -1,534 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2013 by Mike Gorchak
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "alu.h"
#include "ambidefs.h"
#include "effects/base.h"
#include "math_defs.h"
#include "opthelpers.h"
#include "vector.h"
namespace {
static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
enum class WaveForm {
Sinusoid,
Triangle
};
void GetTriangleDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
{
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
ALuint offset{start_offset};
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
{
offset = (offset+1)%lfo_range;
const float offset_norm{static_cast<float>(offset) * lfo_scale};
return static_cast<ALuint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
};
std::generate_n(delays, todo, gen_lfo);
}
void GetSinusoidDelays(ALuint *delays, const ALuint start_offset, const ALuint lfo_range,
const ALfloat lfo_scale, const ALfloat depth, const ALsizei delay, const size_t todo)
{
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
ALuint offset{start_offset};
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> ALuint
{
offset = (offset+1)%lfo_range;
const float offset_norm{static_cast<float>(offset) * lfo_scale};
return static_cast<ALuint>(fastf2i(std::sin(offset_norm)*depth) + delay);
};
std::generate_n(delays, todo, gen_lfo);
}
struct ChorusState final : public EffectState {
al::vector<ALfloat,16> mSampleBuffer;
ALuint mOffset{0};
ALuint mLfoOffset{0};
ALuint mLfoRange{1};
ALfloat mLfoScale{0.0f};
ALuint mLfoDisp{0};
/* Gains for left and right sides */
struct {
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
} mGains[2];
/* effect parameters */
WaveForm mWaveform{};
ALint mDelay{0};
ALfloat mDepth{0.0f};
ALfloat mFeedback{0.0f};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ChorusState)
};
ALboolean ChorusState::deviceUpdate(const ALCdevice *Device)
{
constexpr ALfloat max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)};
const auto frequency = static_cast<float>(Device->Frequency);
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
if(maxlen != mSampleBuffer.size())
{
mSampleBuffer.resize(maxlen);
mSampleBuffer.shrink_to_fit();
}
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
}
return AL_TRUE;
}
void ChorusState::update(const ALCcontext *Context, const ALeffectslot *Slot, const EffectProps *props, const EffectTarget target)
{
constexpr ALsizei mindelay{(MAX_RESAMPLER_PADDING>>1) << FRACTIONBITS};
switch(props->Chorus.Waveform)
{
case AL_CHORUS_WAVEFORM_TRIANGLE:
mWaveform = WaveForm::Triangle;
break;
case AL_CHORUS_WAVEFORM_SINUSOID:
mWaveform = WaveForm::Sinusoid;
break;
}
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
* delay and depth to allow enough padding for resampling.
*/
const ALCdevice *device{Context->mDevice.get()};
const auto frequency = static_cast<float>(device->Frequency);
mDelay = maxi(float2int(props->Chorus.Delay*frequency*FRACTIONONE + 0.5f), mindelay);
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
static_cast<float>(mDelay - mindelay));
mFeedback = props->Chorus.Feedback;
/* Gains for left and right sides */
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f, coeffs[0]);
CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f, coeffs[1]);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs[0], Slot->Params.Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs[1], Slot->Params.Gain, mGains[1].Target);
ALfloat rate{props->Chorus.Rate};
if(!(rate > 0.0f))
{
mLfoOffset = 0;
mLfoRange = 1;
mLfoScale = 0.0f;
mLfoDisp = 0;
}
else
{
/* Calculate LFO coefficient (number of samples per cycle). Limit the
* max range to avoid overflow when calculating the displacement.
*/
ALuint lfo_range{float2uint(minf(frequency/rate + 0.5f, ALfloat{INT_MAX/360 - 180}))};
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
mLfoRange = lfo_range;
switch(mWaveform)
{
case WaveForm::Triangle:
mLfoScale = 4.0f / static_cast<float>(mLfoRange);
break;
case WaveForm::Sinusoid:
mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
break;
}
/* Calculate lfo phase displacement */
ALint phase{props->Chorus.Phase};
if(phase < 0) phase = 360 + phase;
mLfoDisp = (mLfoRange*static_cast<ALuint>(phase) + 180) / 360;
}
}
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t bufmask{mSampleBuffer.size()-1};
const ALfloat feedback{mFeedback};
const ALuint avgdelay{(static_cast<ALuint>(mDelay) + (FRACTIONONE>>1)) >> FRACTIONBITS};
ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
ALuint offset{mOffset};
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(256, samplesToDo-base)};
ALuint moddelays[2][256];
if(mWaveform == WaveForm::Sinusoid)
{
GetSinusoidDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
todo);
GetSinusoidDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
mDepth, mDelay, todo);
}
else /*if(mWaveform == WaveForm::Triangle)*/
{
GetTriangleDelays(moddelays[0], mLfoOffset, mLfoRange, mLfoScale, mDepth, mDelay,
todo);
GetTriangleDelays(moddelays[1], (mLfoOffset+mLfoDisp)%mLfoRange, mLfoRange, mLfoScale,
mDepth, mDelay, todo);
}
mLfoOffset = (mLfoOffset+static_cast<ALuint>(todo)) % mLfoRange;
alignas(16) ALfloat temps[2][256];
for(size_t i{0u};i < todo;i++)
{
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][base+i];
// Tap for the left output.
ALuint delay{offset - (moddelays[0][i]>>FRACTIONBITS)};
ALfloat mu{static_cast<float>(moddelays[0][i]&FRACTIONMASK) * (1.0f/FRACTIONONE)};
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
// Tap for the right output.
delay = offset - (moddelays[1][i]>>FRACTIONBITS);
mu = static_cast<float>(moddelays[1][i]&FRACTIONMASK) * (1.0f/FRACTIONONE);
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
// Accumulate feedback from the average delay of the taps.
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
++offset;
}
for(ALsizei c{0};c < 2;c++)
MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo-base, base);
base += todo;
}
mOffset = offset;
}
void Chorus_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
if(!(val >= AL_CHORUS_MIN_WAVEFORM && val <= AL_CHORUS_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid chorus waveform");
props->Chorus.Waveform = val;
break;
case AL_CHORUS_PHASE:
if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus phase out of range");
props->Chorus.Phase = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
}
}
void Chorus_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Chorus_setParami(props, context, param, vals[0]); }
void Chorus_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_CHORUS_RATE:
if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus rate out of range");
props->Chorus.Rate = val;
break;
case AL_CHORUS_DEPTH:
if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus depth out of range");
props->Chorus.Depth = val;
break;
case AL_CHORUS_FEEDBACK:
if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus feedback out of range");
props->Chorus.Feedback = val;
break;
case AL_CHORUS_DELAY:
if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Chorus delay out of range");
props->Chorus.Delay = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
}
}
void Chorus_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Chorus_setParamf(props, context, param, vals[0]); }
void Chorus_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
*val = props->Chorus.Waveform;
break;
case AL_CHORUS_PHASE:
*val = props->Chorus.Phase;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param);
}
}
void Chorus_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Chorus_getParami(props, context, param, vals); }
void Chorus_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_CHORUS_RATE:
*val = props->Chorus.Rate;
break;
case AL_CHORUS_DEPTH:
*val = props->Chorus.Depth;
break;
case AL_CHORUS_FEEDBACK:
*val = props->Chorus.Feedback;
break;
case AL_CHORUS_DELAY:
*val = props->Chorus.Delay;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param);
}
}
void Chorus_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Chorus_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Chorus);
struct ChorusStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ChorusState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Chorus_vtable; }
};
EffectProps ChorusStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Chorus.Waveform = AL_CHORUS_DEFAULT_WAVEFORM;
props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE;
props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE;
props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH;
props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK;
props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY;
return props;
}
void Flanger_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
if(!(val >= AL_FLANGER_MIN_WAVEFORM && val <= AL_FLANGER_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid flanger waveform");
props->Chorus.Waveform = val;
break;
case AL_FLANGER_PHASE:
if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger phase out of range");
props->Chorus.Phase = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
}
}
void Flanger_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Flanger_setParami(props, context, param, vals[0]); }
void Flanger_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_FLANGER_RATE:
if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger rate out of range");
props->Chorus.Rate = val;
break;
case AL_FLANGER_DEPTH:
if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger depth out of range");
props->Chorus.Depth = val;
break;
case AL_FLANGER_FEEDBACK:
if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger feedback out of range");
props->Chorus.Feedback = val;
break;
case AL_FLANGER_DELAY:
if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Flanger delay out of range");
props->Chorus.Delay = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
}
}
void Flanger_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Flanger_setParamf(props, context, param, vals[0]); }
void Flanger_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
*val = props->Chorus.Waveform;
break;
case AL_FLANGER_PHASE:
*val = props->Chorus.Phase;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param);
}
}
void Flanger_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Flanger_getParami(props, context, param, vals); }
void Flanger_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_FLANGER_RATE:
*val = props->Chorus.Rate;
break;
case AL_FLANGER_DEPTH:
*val = props->Chorus.Depth;
break;
case AL_FLANGER_FEEDBACK:
*val = props->Chorus.Feedback;
break;
case AL_FLANGER_DELAY:
*val = props->Chorus.Delay;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param);
}
}
void Flanger_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Flanger_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Flanger);
/* Flanger is basically a chorus with a really short delay. They can both use
* the same processing functions, so piggyback flanger on the chorus functions.
*/
struct FlangerStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ChorusState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Flanger_vtable; }
};
EffectProps FlangerStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Chorus.Waveform = AL_FLANGER_DEFAULT_WAVEFORM;
props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE;
props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE;
props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH;
props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK;
props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY;
return props;
}
} // namespace
EffectStateFactory *ChorusStateFactory_getFactory()
{
static ChorusStateFactory ChorusFactory{};
return &ChorusFactory;
}
EffectStateFactory *FlangerStateFactory_getFactory()
{
static FlangerStateFactory FlangerFactory{};
return &FlangerFactory;
}
-221
View File
@@ -1,221 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2013 by Anis A. Hireche
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cstdlib>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "vecmat.h"
namespace {
#define AMP_ENVELOPE_MIN 0.5f
#define AMP_ENVELOPE_MAX 2.0f
#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
struct CompressorState final : public EffectState {
/* Effect gains for each channel */
ALfloat mGain[MAX_AMBI_CHANNELS][MAX_OUTPUT_CHANNELS]{};
/* Effect parameters */
ALboolean mEnabled{AL_TRUE};
ALfloat mAttackMult{1.0f};
ALfloat mReleaseMult{1.0f};
ALfloat mEnvFollower{1.0f};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(CompressorState)
};
ALboolean CompressorState::deviceUpdate(const ALCdevice *device)
{
/* Number of samples to do a full attack and release (non-integer sample
* counts are okay).
*/
const ALfloat attackCount = static_cast<ALfloat>(device->Frequency) * ATTACK_TIME;
const ALfloat releaseCount = static_cast<ALfloat>(device->Frequency) * RELEASE_TIME;
/* Calculate per-sample multipliers to attack and release at the desired
* rates.
*/
mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
return AL_TRUE;
}
void CompressorState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
mEnabled = props->Compressor.OnOff;
mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mGain[i]);
}
}
void CompressorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
{
ALfloat gains[256];
const size_t td{minz(256, samplesToDo-base)};
/* Generate the per-sample gains from the signal envelope. */
ALfloat env{mEnvFollower};
if(mEnabled)
{
for(size_t i{0u};i < td;++i)
{
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
const ALfloat amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
AMP_ENVELOPE_MAX)};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
env = maxf(env*mReleaseMult, amplitude);
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
gains[i] = 1.0f / env;
}
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and
* off.
*/
for(size_t i{0u};i < td;++i)
{
const ALfloat amplitude{1.0f};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
env = maxf(env*mReleaseMult, amplitude);
gains[i] = 1.0f / env;
}
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto changains = std::addressof(mGain[0]);
for(const auto &input : samplesIn)
{
const ALfloat *outgains{*(changains++)};
for(FloatBufferLine &output : samplesOut)
{
const ALfloat gain{*(outgains++)};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
for(size_t i{0u};i < td;i++)
output[base+i] += input[base+i] * gains[i] * gain;
}
}
base += td;
}
}
void Compressor_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Compressor state out of range");
props->Compressor.OnOff = val != AL_FALSE;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param);
}
}
void Compressor_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Compressor_setParami(props, context, param, vals[0]); }
void Compressor_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
void Compressor_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
void Compressor_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
*val = props->Compressor.OnOff;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param);
}
}
void Compressor_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Compressor_getParami(props, context, param, vals); }
void Compressor_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param); }
void Compressor_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param); }
DEFINE_ALEFFECT_VTABLE(Compressor);
struct CompressorStateFactory final : public EffectStateFactory {
EffectState *create() override { return new CompressorState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Compressor_vtable; }
};
EffectProps CompressorStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
return props;
}
} // namespace
EffectStateFactory *CompressorStateFactory_getFactory()
{
static CompressorStateFactory CompressorFactory{};
return &CompressorFactory;
}
-160
View File
@@ -1,160 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2011 by Chris Robinson.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
namespace {
struct DedicatedState final : public EffectState {
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DedicatedState)
};
ALboolean DedicatedState::deviceUpdate(const ALCdevice*)
{
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
return AL_TRUE;
}
void DedicatedState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
const ALfloat Gain{slot->Params.Gain * props->Dedicated.Gain};
if(slot->Params.EffectType == AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT)
{
const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
GetChannelIdxByName(*target.RealOut, LFE)};
if(idx != INVALID_CHANNEL_INDEX)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
else if(slot->Params.EffectType == AL_EFFECT_DEDICATED_DIALOGUE)
{
/* Dialog goes to the front-center speaker if it exists, otherwise it
* plays from the front-center location. */
const ALuint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
GetChannelIdxByName(*target.RealOut, FrontCenter)};
if(idx != INVALID_CHANNEL_INDEX)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
else
{
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
}
}
}
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
samplesToDo, 0);
}
void Dedicated_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
void Dedicated_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
void Dedicated_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val)))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Dedicated gain out of range");
props->Dedicated.Gain = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
}
}
void Dedicated_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Dedicated_setParamf(props, context, param, vals[0]); }
void Dedicated_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param); }
void Dedicated_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param); }
void Dedicated_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
*val = props->Dedicated.Gain;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param);
}
}
void Dedicated_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Dedicated_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Dedicated);
struct DedicatedStateFactory final : public EffectStateFactory {
EffectState *create() override { return new DedicatedState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Dedicated_vtable; }
};
EffectProps DedicatedStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Dedicated.Gain = 1.0f;
return props;
}
} // namespace
EffectStateFactory *DedicatedStateFactory_getFactory()
{
static DedicatedStateFactory DedicatedFactory{};
return &DedicatedFactory;
}
-262
View File
@@ -1,262 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2013 by Mike Gorchak
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "filters/biquad.h"
namespace {
struct DistortionState final : public EffectState {
/* Effect gains for each channel */
ALfloat mGain[MAX_OUTPUT_CHANNELS]{};
/* Effect parameters */
BiquadFilter mLowpass;
BiquadFilter mBandpass;
ALfloat mAttenuation{};
ALfloat mEdgeCoeff{};
ALfloat mBuffer[2][BUFFERSIZE]{};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DistortionState)
};
ALboolean DistortionState::deviceUpdate(const ALCdevice*)
{
mLowpass.clear();
mBandpass.clear();
return AL_TRUE;
}
void DistortionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
/* Store waveshaper edge settings. */
const ALfloat edge{
minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge), 0.99f)};
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
ALfloat cutoff{props->Distortion.LowpassCutoff};
/* Bandwidth value is constant in octaves. */
ALfloat bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
/* Divide normalized frequency by the amount of oversampling done during
* processing.
*/
auto frequency = static_cast<ALfloat>(device->Frequency);
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
cutoff = props->Distortion.EQCenter;
/* Convert bandwidth in Hz to octaves. */
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs, slot->Params.Gain*props->Distortion.Gain, mGain);
}
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const ALfloat fc{mEdgeCoeff};
for(size_t base{0u};base < samplesToDo;)
{
/* Perform 4x oversampling to avoid aliasing. Oversampling greatly
* improves distortion quality and allows to implement lowpass and
* bandpass filters using high frequencies, at which classic IIR
* filters became unstable.
*/
size_t todo{minz(BUFFERSIZE, (samplesToDo-base) * 4)};
/* Fill oversample buffer using zero stuffing. Multiply the sample by
* the amount of oversampling to maintain the signal's power.
*/
for(size_t i{0u};i < todo;i++)
mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
/* First step, do lowpass filtering of original signal. Additionally
* perform buffer interpolation and lowpass cutoff for oversampling
* (which is fortunately first step of distortion). So combine three
* operations into the one.
*/
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
/* Second step, do distortion using waveshaper function to emulate
* signal processing during tube overdriving. Three steps of
* waveshaping are intended to modify waveform without boost/clipping/
* attenuation process.
*/
auto proc_sample = [fc](float smp) -> float
{
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
return smp;
};
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
proc_sample);
/* Third step, do bandpass filtering of distorted signal. */
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
todo >>= 2;
const ALfloat *outgains{mGain};
for(FloatBufferLine &output : samplesOut)
{
/* Fourth step, final, do attenuation and perform decimation,
* storing only one sample out of four.
*/
const ALfloat gain{*(outgains++)};
if(!(std::fabs(gain) > GAIN_SILENCE_THRESHOLD))
continue;
for(size_t i{0u};i < todo;i++)
output[base+i] += gain * mBuffer[1][i*4];
}
base += todo;
}
}
void Distortion_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
void Distortion_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
void Distortion_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion edge out of range");
props->Distortion.Edge = val;
break;
case AL_DISTORTION_GAIN:
if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion gain out of range");
props->Distortion.Gain = val;
break;
case AL_DISTORTION_LOWPASS_CUTOFF:
if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion low-pass cutoff out of range");
props->Distortion.LowpassCutoff = val;
break;
case AL_DISTORTION_EQCENTER:
if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ center out of range");
props->Distortion.EQCenter = val;
break;
case AL_DISTORTION_EQBANDWIDTH:
if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Distortion EQ bandwidth out of range");
props->Distortion.EQBandwidth = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param);
}
}
void Distortion_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Distortion_setParamf(props, context, param, vals[0]); }
void Distortion_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param); }
void Distortion_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param); }
void Distortion_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
*val = props->Distortion.Edge;
break;
case AL_DISTORTION_GAIN:
*val = props->Distortion.Gain;
break;
case AL_DISTORTION_LOWPASS_CUTOFF:
*val = props->Distortion.LowpassCutoff;
break;
case AL_DISTORTION_EQCENTER:
*val = props->Distortion.EQCenter;
break;
case AL_DISTORTION_EQBANDWIDTH:
*val = props->Distortion.EQBandwidth;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param);
}
}
void Distortion_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Distortion_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Distortion);
struct DistortionStateFactory final : public EffectStateFactory {
EffectState *create() override { return new DistortionState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Distortion_vtable; }
};
EffectProps DistortionStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE;
props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN;
props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF;
props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER;
props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH;
return props;
}
} // namespace
EffectStateFactory *DistortionStateFactory_getFactory()
{
static DistortionStateFactory DistortionFactory{};
return &DistortionFactory;
}
-266
View File
@@ -1,266 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2009 by Chris Robinson.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include "al/auxeffectslot.h"
#include "al/filter.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vector.h"
namespace {
struct EchoState final : public EffectState {
al::vector<ALfloat,16> mSampleBuffer;
// The echo is two tap. The delay is the number of samples from before the
// current offset
struct {
size_t delay{0u};
} mTap[2];
size_t mOffset{0u};
/* The panning gains for the two taps */
struct {
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
} mGains[2];
BiquadFilter mFilter;
ALfloat mFeedGain{0.0f};
alignas(16) ALfloat mTempBuffer[2][BUFFERSIZE];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EchoState)
};
ALboolean EchoState::deviceUpdate(const ALCdevice *Device)
{
const auto frequency = static_cast<float>(Device->Frequency);
// Use the next power of 2 for the buffer length, so the tap offsets can be
// wrapped using a mask instead of a modulo
const ALuint maxlen{NextPowerOf2(float2uint(AL_ECHO_MAX_DELAY*frequency + 0.5f) +
float2uint(AL_ECHO_MAX_LRDELAY*frequency + 0.5f))};
if(maxlen != mSampleBuffer.size())
{
mSampleBuffer.resize(maxlen);
mSampleBuffer.shrink_to_fit();
}
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
}
return AL_TRUE;
}
void EchoState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
const auto frequency = static_cast<ALfloat>(device->Frequency);
mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
const ALfloat gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
mFilter.setParamsFromSlope(BiquadType::HighShelf, LOWPASSFREQREF/frequency, gainhf, 1.0f);
mFeedGain = props->Echo.Feedback;
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
const ALfloat angle{std::asin(props->Echo.Spread)};
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
CalcAngleCoeffs(-angle, 0.0f, 0.0f, coeffs[0]);
CalcAngleCoeffs( angle, 0.0f, 0.0f, coeffs[1]);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
}
void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t mask{mSampleBuffer.size()-1};
ALfloat *RESTRICT delaybuf{mSampleBuffer.data()};
size_t offset{mOffset};
size_t tap1{offset - mTap[0].delay};
size_t tap2{offset - mTap[1].delay};
ALfloat z1, z2;
ASSUME(samplesToDo > 0);
const BiquadFilter filter{mFilter};
std::tie(z1, z2) = mFilter.getComponents();
for(size_t i{0u};i < samplesToDo;)
{
offset &= mask;
tap1 &= mask;
tap2 &= mask;
size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
do {
/* Feed the delay buffer's input first. */
delaybuf[offset] = samplesIn[0][i];
/* Get delayed output from the first and second taps. Use the
* second tap for feedback.
*/
mTempBuffer[0][i] = delaybuf[tap1++];
mTempBuffer[1][i] = delaybuf[tap2++];
const float feedb{mTempBuffer[1][i++]};
/* Add feedback to the delay buffer with damping and attenuation. */
delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
} while(--td);
}
mFilter.setComponents(z1, z2);
mOffset = offset;
for(ALsizei c{0};c < 2;c++)
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo, 0);
}
void Echo_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
void Echo_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
void Echo_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_ECHO_DELAY:
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo delay out of range");
props->Echo.Delay = val;
break;
case AL_ECHO_LRDELAY:
if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo LR delay out of range");
props->Echo.LRDelay = val;
break;
case AL_ECHO_DAMPING:
if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo damping out of range");
props->Echo.Damping = val;
break;
case AL_ECHO_FEEDBACK:
if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo feedback out of range");
props->Echo.Feedback = val;
break;
case AL_ECHO_SPREAD:
if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Echo spread out of range");
props->Echo.Spread = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
}
}
void Echo_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Echo_setParamf(props, context, param, vals[0]); }
void Echo_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param); }
void Echo_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param); }
void Echo_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_ECHO_DELAY:
*val = props->Echo.Delay;
break;
case AL_ECHO_LRDELAY:
*val = props->Echo.LRDelay;
break;
case AL_ECHO_DAMPING:
*val = props->Echo.Damping;
break;
case AL_ECHO_FEEDBACK:
*val = props->Echo.Feedback;
break;
case AL_ECHO_SPREAD:
*val = props->Echo.Spread;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param);
}
}
void Echo_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Echo_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Echo);
struct EchoStateFactory final : public EffectStateFactory {
EffectState *create() override { return new EchoState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Echo_vtable; }
};
EffectProps EchoStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Echo.Delay = AL_ECHO_DEFAULT_DELAY;
props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY;
props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING;
props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK;
props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD;
return props;
}
} // namespace
EffectStateFactory *EchoStateFactory_getFactory()
{
static EchoStateFactory EchoFactory{};
return &EchoFactory;
}
-338
View File
@@ -1,338 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2013 by Mike Gorchak
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <functional>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vecmat.h"
namespace {
/* The document "Effects Extension Guide.pdf" says that low and high *
* frequencies are cutoff frequencies. This is not fully correct, they *
* are corner frequencies for low and high shelf filters. If they were *
* just cutoff frequencies, there would be no need in cutoff frequency *
* gains, which are present. Documentation for "Creative Proteus X2" *
* software describes 4-band equalizer functionality in a much better *
* way. This equalizer seems to be a predecessor of OpenAL 4-band *
* equalizer. With low and high shelf filters we are able to cutoff *
* frequencies below and/or above corner frequencies using attenuation *
* gains (below 1.0) and amplify all low and/or high frequencies using *
* gains above 1.0. *
* *
* Low-shelf Low Mid Band High Mid Band High-shelf *
* corner center center corner *
* frequency frequency frequency frequency *
* 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
* *
* | | | | *
* | | | | *
* B -----+ /--+--\ /--+--\ +----- *
* O |\ | | | | | | /| *
* O | \ - | - - | - / | *
* S + | \ | | | | | | / | *
* T | | | | | | | | | | *
* ---------+---------------+------------------+---------------+-------- *
* C | | | | | | | | | | *
* U - | / | | | | | | \ | *
* T | / - | - - | - \ | *
* O |/ | | | | | | \| *
* F -----+ \--+--/ \--+--/ +----- *
* F | | | | *
* | | | | *
* *
* Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
* up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
* octaves for two mid bands. *
* *
* Implementation is based on the "Cookbook formulae for audio EQ biquad *
* filter coefficients" by Robert Bristow-Johnson *
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
struct EqualizerState final : public EffectState {
struct {
/* Effect parameters */
BiquadFilter filter[4];
/* Effect gains for each channel */
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MAX_AMBI_CHANNELS];
ALfloat mSampleBuffer[BUFFERSIZE]{};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EqualizerState)
};
ALboolean EqualizerState::deviceUpdate(const ALCdevice*)
{
for(auto &e : mChans)
{
std::for_each(std::begin(e.filter), std::end(e.filter),
std::mem_fn(&BiquadFilter::clear));
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
}
return AL_TRUE;
}
void EqualizerState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
auto frequency = static_cast<ALfloat>(device->Frequency);
ALfloat gain, f0norm;
/* Calculate coefficients for the each type of filter. Note that the shelf
* and peaking filters' gain is for the centerpoint of the transition band,
* while the effect property gains are for the shelf/peak itself. So the
* property gains need their dB halved (sqrt of linear gain) for the
* shelf/peak to reach the provided gain.
*/
gain = std::sqrt(props->Equalizer.LowGain);
f0norm = props->Equalizer.LowCutoff / frequency;
mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
gain = std::sqrt(props->Equalizer.Mid1Gain);
f0norm = props->Equalizer.Mid1Center / frequency;
mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid1Width);
gain = std::sqrt(props->Equalizer.Mid2Gain);
f0norm = props->Equalizer.Mid2Center / frequency;
mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid2Width);
gain = std::sqrt(props->Equalizer.HighGain);
f0norm = props->Equalizer.HighCutoff / frequency;
mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
/* Copy the filter coefficients for the other input channels. */
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
{
mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]);
mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
}
mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
}
void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const al::span<float> buffer{mSampleBuffer, samplesToDo};
auto chandata = std::addressof(mChans[0]);
for(const auto &input : samplesIn)
{
chandata->filter[0].process({input.data(), samplesToDo}, buffer.begin());
chandata->filter[1].process(buffer, buffer.begin());
chandata->filter[2].process(buffer, buffer.begin());
chandata->filter[3].process(buffer, buffer.begin());
MixSamples(buffer, samplesOut, chandata->CurrentGains, chandata->TargetGains, samplesToDo,
0u);
++chandata;
}
}
void Equalizer_setParami(EffectProps*, ALCcontext *context, ALenum param, ALint)
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
void Equalizer_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
void Equalizer_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band gain out of range");
props->Equalizer.LowGain = val;
break;
case AL_EQUALIZER_LOW_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer low-band cutoff out of range");
props->Equalizer.LowCutoff = val;
break;
case AL_EQUALIZER_MID1_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band gain out of range");
props->Equalizer.Mid1Gain = val;
break;
case AL_EQUALIZER_MID1_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band center out of range");
props->Equalizer.Mid1Center = val;
break;
case AL_EQUALIZER_MID1_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid1-band width out of range");
props->Equalizer.Mid1Width = val;
break;
case AL_EQUALIZER_MID2_GAIN:
if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band gain out of range");
props->Equalizer.Mid2Gain = val;
break;
case AL_EQUALIZER_MID2_CENTER:
if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band center out of range");
props->Equalizer.Mid2Center = val;
break;
case AL_EQUALIZER_MID2_WIDTH:
if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer mid2-band width out of range");
props->Equalizer.Mid2Width = val;
break;
case AL_EQUALIZER_HIGH_GAIN:
if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band gain out of range");
props->Equalizer.HighGain = val;
break;
case AL_EQUALIZER_HIGH_CUTOFF:
if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Equalizer high-band cutoff out of range");
props->Equalizer.HighCutoff = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
}
}
void Equalizer_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Equalizer_setParamf(props, context, param, vals[0]); }
void Equalizer_getParami(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param); }
void Equalizer_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param); }
void Equalizer_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
*val = props->Equalizer.LowGain;
break;
case AL_EQUALIZER_LOW_CUTOFF:
*val = props->Equalizer.LowCutoff;
break;
case AL_EQUALIZER_MID1_GAIN:
*val = props->Equalizer.Mid1Gain;
break;
case AL_EQUALIZER_MID1_CENTER:
*val = props->Equalizer.Mid1Center;
break;
case AL_EQUALIZER_MID1_WIDTH:
*val = props->Equalizer.Mid1Width;
break;
case AL_EQUALIZER_MID2_GAIN:
*val = props->Equalizer.Mid2Gain;
break;
case AL_EQUALIZER_MID2_CENTER:
*val = props->Equalizer.Mid2Center;
break;
case AL_EQUALIZER_MID2_WIDTH:
*val = props->Equalizer.Mid2Width;
break;
case AL_EQUALIZER_HIGH_GAIN:
*val = props->Equalizer.HighGain;
break;
case AL_EQUALIZER_HIGH_CUTOFF:
*val = props->Equalizer.HighCutoff;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param);
}
}
void Equalizer_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Equalizer_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Equalizer);
struct EqualizerStateFactory final : public EffectStateFactory {
EffectState *create() override { return new EqualizerState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Equalizer_vtable; }
};
EffectProps EqualizerStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF;
props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN;
props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER;
props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN;
props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH;
props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER;
props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN;
props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH;
props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF;
props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN;
return props;
}
} // namespace
EffectStateFactory *EqualizerStateFactory_getFactory()
{
static EqualizerStateFactory EqualizerFactory{};
return &EqualizerFactory;
}
-332
View File
@@ -1,332 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2018 by Raul Herraiz.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <array>
#include <complex>
#include <algorithm>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "alcomplex.h"
namespace {
using complex_d = std::complex<double>;
#define HIL_SIZE 1024
#define OVERSAMP (1<<2)
#define HIL_STEP (HIL_SIZE / OVERSAMP)
#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
/* Define a Hann window, used to filter the HIL input and output. */
/* Making this constexpr seems to require C++14. */
std::array<ALdouble,HIL_SIZE> InitHannWindow()
{
std::array<ALdouble,HIL_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
for(size_t i{0};i < HIL_SIZE>>1;i++)
{
constexpr double scale{al::MathDefs<double>::Pi() / double{HIL_SIZE-1}};
const double val{std::sin(static_cast<double>(i) * scale)};
ret[i] = ret[HIL_SIZE-1-i] = val * val;
}
return ret;
}
alignas(16) const std::array<ALdouble,HIL_SIZE> HannWindow = InitHannWindow();
struct FshifterState final : public EffectState {
/* Effect parameters */
size_t mCount{};
ALsizei mPhaseStep[2]{};
ALsizei mPhase[2]{};
ALdouble mSign[2]{};
/*Effects buffers*/
ALfloat mInFIFO[HIL_SIZE]{};
complex_d mOutFIFO[HIL_SIZE]{};
complex_d mOutputAccum[HIL_SIZE]{};
complex_d mAnalytic[HIL_SIZE]{};
complex_d mOutdata[BUFFERSIZE]{};
alignas(16) ALfloat mBufferOut[BUFFERSIZE]{};
/* Effect gains for each output channel */
struct {
ALfloat Current[MAX_OUTPUT_CHANNELS]{};
ALfloat Target[MAX_OUTPUT_CHANNELS]{};
} mGains[2];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(FshifterState)
};
ALboolean FshifterState::deviceUpdate(const ALCdevice*)
{
/* (Re-)initializing parameters and clear the buffers. */
mCount = FIFO_LATENCY;
std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0);
std::fill(std::begin(mPhase), std::end(mPhase), 0);
std::fill(std::begin(mSign), std::end(mSign), 1.0);
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{});
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{});
for(auto &gain : mGains)
{
std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
}
return AL_TRUE;
}
void FshifterState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
ALfloat step{props->Fshifter.Frequency / static_cast<ALfloat>(device->Frequency)};
mPhaseStep[0] = mPhaseStep[1] = fastf2i(minf(step, 0.5f) * FRACTIONONE);
switch(props->Fshifter.LeftDirection)
{
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
mSign[0] = -1.0;
break;
case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
mSign[0] = 1.0;
break;
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
mPhase[0] = 0;
mPhaseStep[0] = 0;
break;
}
switch (props->Fshifter.RightDirection)
{
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN:
mSign[1] = -1.0;
break;
case AL_FREQUENCY_SHIFTER_DIRECTION_UP:
mSign[1] = 1.0;
break;
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF:
mPhase[1] = 0;
mPhaseStep[1] = 0;
break;
}
ALfloat coeffs[2][MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({-1.0f, 0.0f, -1.0f}, 0.0f, coeffs[0]);
CalcDirectionCoeffs({ 1.0f, 0.0f, -1.0f}, 0.0f, coeffs[1]);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs[0], slot->Params.Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs[1], slot->Params.Gain, mGains[1].Target);
}
void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
static constexpr complex_d complex_zero{0.0, 0.0};
ALfloat *RESTRICT BufferOut = mBufferOut;
size_t j, k;
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(HIL_SIZE-mCount, samplesToDo-base)};
ASSUME(todo > 0);
/* Fill FIFO buffer with samples data */
k = mCount;
for(j = 0;j < todo;j++,k++)
{
mInFIFO[k] = samplesIn[0][base+j];
mOutdata[base+j] = mOutFIFO[k-FIFO_LATENCY];
}
mCount += todo;
base += todo;
/* Check whether FIFO buffer is filled */
if(mCount < HIL_SIZE) continue;
mCount = FIFO_LATENCY;
/* Real signal windowing and store in Analytic buffer */
for(k = 0;k < HIL_SIZE;k++)
{
mAnalytic[k].real(mInFIFO[k] * HannWindow[k]);
mAnalytic[k].imag(0.0);
}
/* Processing signal by Discrete Hilbert Transform (analytical signal). */
complex_hilbert(mAnalytic);
/* Windowing and add to output accumulator */
for(k = 0;k < HIL_SIZE;k++)
mOutputAccum[k] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
/* Shift accumulator, input & output FIFO */
for(k = 0;k < HIL_STEP;k++) mOutFIFO[k] = mOutputAccum[k];
for(j = 0;k < HIL_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
for(;j < HIL_SIZE;j++) mOutputAccum[j] = complex_zero;
for(k = 0;k < FIFO_LATENCY;k++)
mInFIFO[k] = mInFIFO[k+HIL_STEP];
}
/* Process frequency shifter using the analytic signal obtained. */
for(ALsizei c{0};c < 2;++c)
{
for(k = 0;k < samplesToDo;++k)
{
double phase = mPhase[c] * ((1.0 / FRACTIONONE) * al::MathDefs<double>::Tau());
BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
mOutdata[k].imag()*std::sin(phase)*mSign[c]);
mPhase[c] += mPhaseStep[c];
mPhase[c] &= FRACTIONMASK;
}
/* Now, mix the processed sound data to the output. */
MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
maxz(samplesToDo, 512), 0);
}
}
void Fshifter_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter frequency out of range");
props->Fshifter.Frequency = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param);
}
}
void Fshifter_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Fshifter_setParamf(props, context, param, vals[0]); }
void Fshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter left direction out of range");
props->Fshifter.LeftDirection = val;
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION && val <= AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Frequency shifter right direction out of range");
props->Fshifter.RightDirection = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x",
param);
}
}
void Fshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Fshifter_setParami(props, context, param, vals[0]); }
void Fshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
*val = props->Fshifter.LeftDirection;
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
*val = props->Fshifter.RightDirection;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter integer property 0x%04x",
param);
}
}
void Fshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Fshifter_getParami(props, context, param, vals); }
void Fshifter_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
*val = props->Fshifter.Frequency;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param);
}
}
void Fshifter_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Fshifter_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Fshifter);
struct FshifterStateFactory final : public EffectStateFactory {
EffectState *create() override { return new FshifterState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Fshifter_vtable; }
};
EffectProps FshifterStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
props.Fshifter.LeftDirection = AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION;
props.Fshifter.RightDirection = AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION;
return props;
}
} // namespace
EffectStateFactory *FshifterStateFactory_getFactory()
{
static FshifterStateFactory FshifterFactory{};
return &FshifterFactory;
}
-269
View File
@@ -1,269 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2009 by Chris Robinson.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
#include "filters/biquad.h"
#include "vecmat.h"
namespace {
#define MAX_UPDATE_SAMPLES 128
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
inline float Sin(ALuint index)
{
constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
return std::sin(static_cast<float>(index) * scale);
}
inline float Saw(ALuint index)
{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
inline float Square(ALuint index)
{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
inline float One(ALuint) { return 1.0f; }
template<float (&func)(ALuint)>
void Modulate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo)
{
for(size_t i{0u};i < todo;i++)
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
}
struct ModulatorState final : public EffectState {
void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){};
ALuint mIndex{0};
ALuint mStep{1};
struct {
BiquadFilter Filter;
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MAX_AMBI_CHANNELS];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ModulatorState)
};
ALboolean ModulatorState::deviceUpdate(const ALCdevice*)
{
for(auto &e : mChans)
{
e.Filter.clear();
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
}
return AL_TRUE;
}
void ModulatorState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
const float step{props->Modulator.Frequency / static_cast<ALfloat>(device->Frequency)};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
if(mStep == 0)
mGetSamples = Modulate<One>;
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SINUSOID)
mGetSamples = Modulate<Sin>;
else if(props->Modulator.Waveform == AL_RING_MODULATOR_SAWTOOTH)
mGetSamples = Modulate<Saw>;
else /*if(props->Modulator.Waveform == AL_RING_MODULATOR_SQUARE)*/
mGetSamples = Modulate<Square>;
float f0norm{props->Modulator.HighPassCutoff / static_cast<ALfloat>(device->Frequency)};
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */
mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
}
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
{
alignas(16) ALfloat modsamples[MAX_UPDATE_SAMPLES];
size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
mGetSamples(modsamples, mIndex, mStep, td);
mIndex += static_cast<ALuint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
auto chandata = std::addressof(mChans[0]);
for(const auto &input : samplesIn)
{
alignas(16) ALfloat temps[MAX_UPDATE_SAMPLES];
chandata->Filter.process({&input[base], td}, temps);
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
samplesToDo-base, base);
++chandata;
}
base += td;
}
}
void Modulator_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator frequency out of range");
props->Modulator.Frequency = val;
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Modulator high-pass cutoff out of range");
props->Modulator.HighPassCutoff = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
}
}
void Modulator_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Modulator_setParamf(props, context, param, vals[0]); }
void Modulator_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
Modulator_setParamf(props, context, param, static_cast<ALfloat>(val));
break;
case AL_RING_MODULATOR_WAVEFORM:
if(!(val >= AL_RING_MODULATOR_MIN_WAVEFORM && val <= AL_RING_MODULATOR_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Invalid modulator waveform");
props->Modulator.Waveform = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
}
}
void Modulator_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Modulator_setParami(props, context, param, vals[0]); }
void Modulator_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = static_cast<ALint>(props->Modulator.Frequency);
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = static_cast<ALint>(props->Modulator.HighPassCutoff);
break;
case AL_RING_MODULATOR_WAVEFORM:
*val = props->Modulator.Waveform;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", param);
}
}
void Modulator_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Modulator_getParami(props, context, param, vals); }
void Modulator_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = props->Modulator.Frequency;
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = props->Modulator.HighPassCutoff;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param);
}
}
void Modulator_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Modulator_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Modulator);
struct ModulatorStateFactory final : public EffectStateFactory {
EffectState *create() override { return new ModulatorState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Modulator_vtable; }
};
EffectProps ModulatorStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
props.Modulator.Waveform = AL_RING_MODULATOR_DEFAULT_WAVEFORM;
return props;
}
} // namespace
EffectStateFactory *ModulatorStateFactory_getFactory()
{
static ModulatorStateFactory ModulatorFactory{};
return &ModulatorFactory;
}
-165
View File
@@ -1,165 +0,0 @@
#include "config.h"
#include "AL/al.h"
#include "AL/alc.h"
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "almalloc.h"
#include "alspan.h"
#include "effects/base.h"
namespace {
struct NullState final : public EffectState {
NullState();
~NullState() override;
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(NullState)
};
/* This constructs the effect state. It's called when the object is first
* created.
*/
NullState::NullState() = default;
/* This destructs the effect state. It's called only when the effect instance
* is no longer used.
*/
NullState::~NullState() = default;
/* This updates the device-dependant effect state. This is called on state
* initialization and any time the device parameters (e.g. playback frequency,
* format) have been changed. Will always be followed by a call to the update
* method, if successful.
*/
ALboolean NullState::deviceUpdate(const ALCdevice* /*device*/)
{
return AL_TRUE;
}
/* This updates the effect state with new properties. This is called any time
* the effect is (re)loaded into a slot.
*/
void NullState::update(const ALCcontext* /*context*/, const ALeffectslot* /*slot*/,
const EffectProps* /*props*/, const EffectTarget /*target*/)
{
}
/* This processes the effect state, for the given number of samples from the
* input to the output buffer. The result should be added to the output buffer,
* not replace it.
*/
void NullState::process(const size_t/*samplesToDo*/,
const al::span<const FloatBufferLine> /*samplesIn*/,
const al::span<FloatBufferLine> /*samplesOut*/)
{
}
void NullEffect_setParami(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint /*val*/)
{
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
}
}
void NullEffect_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{
switch(param)
{
default:
NullEffect_setParami(props, context, param, vals[0]);
}
}
void NullEffect_setParamf(EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat /*val*/)
{
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
}
}
void NullEffect_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{
switch(param)
{
default:
NullEffect_setParamf(props, context, param, vals[0]);
}
}
void NullEffect_getParami(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALint* /*val*/)
{
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", param);
}
}
void NullEffect_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{
switch(param)
{
default:
NullEffect_getParami(props, context, param, vals);
}
}
void NullEffect_getParamf(const EffectProps* /*props*/, ALCcontext *context, ALenum param, ALfloat* /*val*/)
{
switch(param)
{
default:
context->setError(AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", param);
}
}
void NullEffect_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{
switch(param)
{
default:
NullEffect_getParamf(props, context, param, vals);
}
}
DEFINE_ALEFFECT_VTABLE(NullEffect);
struct NullStateFactory final : public EffectStateFactory {
EffectState *create() override;
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override;
};
/* Creates EffectState objects of the appropriate type. */
EffectState *NullStateFactory::create()
{ return new NullState{}; }
/* Returns an ALeffectProps initialized with this effect type's default
* property values.
*/
EffectProps NullStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
return props;
}
/* Returns a pointer to this effect type's global set/get vtable. */
const EffectVtable *NullStateFactory::getEffectVtable() const noexcept
{ return &NullEffect_vtable; }
} // namespace
EffectStateFactory *NullStateFactory_getFactory()
{
static NullStateFactory NullFactory{};
return &NullFactory;
}
-373
View File
@@ -1,373 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2018 by Raul Herraiz.
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#ifdef HAVE_SSE_INTRINSICS
#include <emmintrin.h>
#endif
#include <cmath>
#include <cstdlib>
#include <array>
#include <complex>
#include <algorithm>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcomplex.h"
#include "alcontext.h"
#include "alnumeric.h"
#include "alu.h"
namespace {
using complex_d = std::complex<double>;
#define STFT_SIZE 1024
#define STFT_HALF_SIZE (STFT_SIZE>>1)
#define OVERSAMP (1<<2)
#define STFT_STEP (STFT_SIZE / OVERSAMP)
#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
/* Define a Hann window, used to filter the STFT input and output. */
/* Making this constexpr seems to require C++14. */
std::array<ALdouble,STFT_SIZE> InitHannWindow()
{
std::array<ALdouble,STFT_SIZE> ret;
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
for(size_t i{0};i < STFT_SIZE>>1;i++)
{
constexpr double scale{al::MathDefs<double>::Pi() / double{STFT_SIZE-1}};
const double val{std::sin(static_cast<double>(i) * scale)};
ret[i] = ret[STFT_SIZE-1-i] = val * val;
}
return ret;
}
alignas(16) const std::array<ALdouble,STFT_SIZE> HannWindow = InitHannWindow();
struct ALphasor {
ALdouble Amplitude;
ALdouble Phase;
};
struct ALfrequencyDomain {
ALdouble Amplitude;
ALdouble Frequency;
};
/* Converts complex to ALphasor */
inline ALphasor rect2polar(const complex_d &number)
{
ALphasor polar;
polar.Amplitude = std::abs(number);
polar.Phase = std::arg(number);
return polar;
}
/* Converts ALphasor to complex */
inline complex_d polar2rect(const ALphasor &number)
{ return std::polar<double>(number.Amplitude, number.Phase); }
struct PshifterState final : public EffectState {
/* Effect parameters */
size_t mCount;
ALuint mPitchShiftI;
ALfloat mPitchShift;
ALfloat mFreqPerBin;
/* Effects buffers */
ALfloat mInFIFO[STFT_SIZE];
ALfloat mOutFIFO[STFT_STEP];
ALdouble mLastPhase[STFT_HALF_SIZE+1];
ALdouble mSumPhase[STFT_HALF_SIZE+1];
ALdouble mOutputAccum[STFT_SIZE];
complex_d mFFTbuffer[STFT_SIZE];
ALfrequencyDomain mAnalysis_buffer[STFT_HALF_SIZE+1];
ALfrequencyDomain mSyntesis_buffer[STFT_HALF_SIZE+1];
alignas(16) ALfloat mBufferOut[BUFFERSIZE];
/* Effect gains for each output channel */
ALfloat mCurrentGains[MAX_OUTPUT_CHANNELS];
ALfloat mTargetGains[MAX_OUTPUT_CHANNELS];
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(PshifterState)
};
ALboolean PshifterState::deviceUpdate(const ALCdevice *device)
{
/* (Re-)initializing parameters and clear the buffers. */
mCount = FIFO_LATENCY;
mPitchShiftI = FRACTIONONE;
mPitchShift = 1.0f;
mFreqPerBin = static_cast<float>(device->Frequency) / float{STFT_SIZE};
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0f);
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), 0.0f);
std::fill(std::begin(mLastPhase), std::end(mLastPhase), 0.0);
std::fill(std::begin(mSumPhase), std::end(mSumPhase), 0.0);
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), 0.0);
std::fill(std::begin(mFFTbuffer), std::end(mFFTbuffer), complex_d{});
std::fill(std::begin(mAnalysis_buffer), std::end(mAnalysis_buffer), ALfrequencyDomain{});
std::fill(std::begin(mSyntesis_buffer), std::end(mSyntesis_buffer), ALfrequencyDomain{});
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
return AL_TRUE;
}
void PshifterState::update(const ALCcontext*, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const float pitch{std::pow(2.0f,
static_cast<ALfloat>(props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune) / 1200.0f
)};
mPitchShiftI = fastf2u(pitch*FRACTIONONE);
mPitchShift = static_cast<float>(mPitchShiftI) * (1.0f/FRACTIONONE);
ALfloat coeffs[MAX_AMBI_CHANNELS];
CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f, coeffs);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs, slot->Params.Gain, mTargetGains);
}
void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
/* Pitch shifter engine based on the work of Stephan Bernsee.
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
*/
static constexpr ALdouble expected{al::MathDefs<double>::Tau() / OVERSAMP};
const ALdouble freq_per_bin{mFreqPerBin};
ALfloat *RESTRICT bufferOut{mBufferOut};
size_t count{mCount};
for(size_t i{0u};i < samplesToDo;)
{
do {
/* Fill FIFO buffer with samples data */
mInFIFO[count] = samplesIn[0][i];
bufferOut[i] = mOutFIFO[count - FIFO_LATENCY];
count++;
} while(++i < samplesToDo && count < STFT_SIZE);
/* Check whether FIFO buffer is filled */
if(count < STFT_SIZE) break;
count = FIFO_LATENCY;
/* Real signal windowing and store in FFTbuffer */
for(ALuint k{0u};k < STFT_SIZE;k++)
{
mFFTbuffer[k].real(mInFIFO[k] * HannWindow[k]);
mFFTbuffer[k].imag(0.0);
}
/* ANALYSIS */
/* Apply FFT to FFTbuffer data */
complex_fft(mFFTbuffer, -1.0);
/* Analyze the obtained data. Since the real FFT is symmetric, only
* STFT_HALF_SIZE+1 samples are needed.
*/
for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
{
/* Compute amplitude and phase */
ALphasor component{rect2polar(mFFTbuffer[k])};
/* Compute phase difference and subtract expected phase difference */
double tmp{(component.Phase - mLastPhase[k]) - k*expected};
/* Map delta phase into +/- Pi interval */
int qpd{double2int(tmp / al::MathDefs<double>::Pi())};
tmp -= al::MathDefs<double>::Pi() * (qpd + (qpd%2));
/* Get deviation from bin frequency from the +/- Pi interval */
tmp /= expected;
/* Compute the k-th partials' true frequency, twice the amplitude
* for maintain the gain (because half of bins are used) and store
* amplitude and true frequency in analysis buffer.
*/
mAnalysis_buffer[k].Amplitude = 2.0 * component.Amplitude;
mAnalysis_buffer[k].Frequency = (k + tmp) * freq_per_bin;
/* Store actual phase[k] for the calculations in the next frame*/
mLastPhase[k] = component.Phase;
}
/* PROCESSING */
/* pitch shifting */
for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
{
mSyntesis_buffer[k].Amplitude = 0.0;
mSyntesis_buffer[k].Frequency = 0.0;
}
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
{
size_t j{(k*mPitchShiftI) >> FRACTIONBITS};
if(j >= STFT_HALF_SIZE+1) break;
mSyntesis_buffer[j].Amplitude += mAnalysis_buffer[k].Amplitude;
mSyntesis_buffer[j].Frequency = mAnalysis_buffer[k].Frequency * mPitchShift;
}
/* SYNTHESIS */
/* Synthesis the processing data */
for(ALuint k{0u};k < STFT_HALF_SIZE+1;k++)
{
ALphasor component;
ALdouble tmp;
/* Compute bin deviation from scaled freq */
tmp = mSyntesis_buffer[k].Frequency/freq_per_bin - k;
/* Calculate actual delta phase and accumulate it to get bin phase */
mSumPhase[k] += (k + tmp) * expected;
component.Amplitude = mSyntesis_buffer[k].Amplitude;
component.Phase = mSumPhase[k];
/* Compute phasor component to cartesian complex number and storage it into FFTbuffer*/
mFFTbuffer[k] = polar2rect(component);
}
/* zero negative frequencies for recontruct a real signal */
for(ALuint k{STFT_HALF_SIZE+1};k < STFT_SIZE;k++)
mFFTbuffer[k] = complex_d{};
/* Apply iFFT to buffer data */
complex_fft(mFFTbuffer, 1.0);
/* Windowing and add to output */
for(ALuint k{0u};k < STFT_SIZE;k++)
mOutputAccum[k] += HannWindow[k] * mFFTbuffer[k].real() /
(0.5 * STFT_HALF_SIZE * OVERSAMP);
/* Shift accumulator, input & output FIFO */
size_t j, k;
for(k = 0;k < STFT_STEP;k++) mOutFIFO[k] = static_cast<ALfloat>(mOutputAccum[k]);
for(j = 0;k < STFT_SIZE;k++,j++) mOutputAccum[j] = mOutputAccum[k];
for(;j < STFT_SIZE;j++) mOutputAccum[j] = 0.0;
for(k = 0;k < FIFO_LATENCY;k++)
mInFIFO[k] = mInFIFO[k+STFT_STEP];
}
mCount = count;
/* Now, mix the processed sound data to the output. */
MixSamples({bufferOut, samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
maxz(samplesToDo, 512), 0);
}
void Pshifter_setParamf(EffectProps*, ALCcontext *context, ALenum param, ALfloat)
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
void Pshifter_setParamfv(EffectProps*, ALCcontext *context, ALenum param, const ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", param); }
void Pshifter_setParami(EffectProps *props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_PITCH_SHIFTER_COARSE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter coarse tune out of range");
props->Pshifter.CoarseTune = val;
break;
case AL_PITCH_SHIFTER_FINE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE))
SETERR_RETURN(context, AL_INVALID_VALUE,,"Pitch shifter fine tune out of range");
props->Pshifter.FineTune = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param);
}
}
void Pshifter_setParamiv(EffectProps *props, ALCcontext *context, ALenum param, const ALint *vals)
{ Pshifter_setParami(props, context, param, vals[0]); }
void Pshifter_getParami(const EffectProps *props, ALCcontext *context, ALenum param, ALint *val)
{
switch(param)
{
case AL_PITCH_SHIFTER_COARSE_TUNE:
*val = props->Pshifter.CoarseTune;
break;
case AL_PITCH_SHIFTER_FINE_TUNE:
*val = props->Pshifter.FineTune;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param);
}
}
void Pshifter_getParamiv(const EffectProps *props, ALCcontext *context, ALenum param, ALint *vals)
{ Pshifter_getParami(props, context, param, vals); }
void Pshifter_getParamf(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param); }
void Pshifter_getParamfv(const EffectProps*, ALCcontext *context, ALenum param, ALfloat*)
{ context->setError(AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param); }
DEFINE_ALEFFECT_VTABLE(Pshifter);
struct PshifterStateFactory final : public EffectStateFactory {
EffectState *create() override;
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Pshifter_vtable; }
};
EffectState *PshifterStateFactory::create()
{ return new PshifterState{}; }
EffectProps PshifterStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
return props;
}
} // namespace
EffectStateFactory *PshifterStateFactory_getFactory()
{
static PshifterStateFactory PshifterFactory{};
return &PshifterFactory;
}
File diff suppressed because it is too large Load Diff
-432
View File
@@ -1,432 +0,0 @@
/**
* OpenAL cross platform audio library
* Copyright (C) 2019 by Anis A. Hireche
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* Or go to http://www.gnu.org/copyleft/lgpl.html
*/
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <functional>
#include "al/auxeffectslot.h"
#include "alcmain.h"
#include "alcontext.h"
#include "alu.h"
namespace {
#define MAX_UPDATE_SAMPLES 128
#define NUM_FORMANTS 4
#define NUM_FILTERS 2
#define Q_FACTOR 5.0f
#define VOWEL_A_INDEX 0
#define VOWEL_B_INDEX 1
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
inline float Sin(ALuint index)
{
constexpr float scale{al::MathDefs<float>::Tau() / WAVEFORM_FRACONE};
return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
}
inline float Saw(ALuint index)
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
inline float Triangle(ALuint index)
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); }
inline float Half(ALuint) { return 0.5f; }
template<float (&func)(ALuint)>
void Oscillate(float *RESTRICT dst, ALuint index, const ALuint step, size_t todo)
{
for(size_t i{0u};i < todo;i++)
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
}
struct FormantFilter
{
ALfloat mCoeff{0.0f};
ALfloat mGain{1.0f};
ALfloat mS1{0.0f};
ALfloat mS2{0.0f};
FormantFilter() = default;
FormantFilter(ALfloat f0norm, ALfloat gain)
: mCoeff{std::tan(al::MathDefs<float>::Pi() * f0norm)}, mGain{gain}
{ }
inline void process(const ALfloat *samplesIn, ALfloat *samplesOut, const size_t numInput)
{
/* A state variable filter from a topology-preserving transform.
* Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
*/
const ALfloat g{mCoeff};
const ALfloat gain{mGain};
const ALfloat h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))};
ALfloat s1{mS1};
ALfloat s2{mS2};
for(size_t i{0u};i < numInput;i++)
{
const ALfloat H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h};
const ALfloat B{g*H + s1};
const ALfloat L{g*B + s2};
s1 = g*H + B;
s2 = g*B + L;
// Apply peak and accumulate samples.
samplesOut[i] += B * gain;
}
mS1 = s1;
mS2 = s2;
}
inline void clear()
{
mS1 = 0.0f;
mS2 = 0.0f;
}
};
struct VmorpherState final : public EffectState {
struct {
/* Effect parameters */
FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
/* Effect gains for each channel */
ALfloat CurrentGains[MAX_OUTPUT_CHANNELS]{};
ALfloat TargetGains[MAX_OUTPUT_CHANNELS]{};
} mChans[MAX_AMBI_CHANNELS];
void (*mGetSamples)(float*RESTRICT, ALuint, const ALuint, size_t){};
ALuint mIndex{0};
ALuint mStep{1};
/* Effects buffers */
ALfloat mSampleBufferA[MAX_UPDATE_SAMPLES]{};
ALfloat mSampleBufferB[MAX_UPDATE_SAMPLES]{};
ALboolean deviceUpdate(const ALCdevice *device) override;
void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) override;
static std::array<FormantFilter,4> getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch);
DEF_NEWDEL(VmorpherState)
};
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(ALenum phoneme, ALfloat frequency, ALfloat pitch)
{
/* Using soprano formant set of values to
* better match mid-range frequency space.
*
* See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
*/
switch(phoneme)
{
case AL_VOCAL_MORPHER_PHONEME_A:
return {{
{( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
{(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
{(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
}};
case AL_VOCAL_MORPHER_PHONEME_E:
return {{
{( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
{(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
{(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
}};
case AL_VOCAL_MORPHER_PHONEME_I:
return {{
{( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
{(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
{(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
}};
case AL_VOCAL_MORPHER_PHONEME_O:
return {{
{( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
{(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
{(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
}};
case AL_VOCAL_MORPHER_PHONEME_U:
return {{
{( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
{( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
{(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
{(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
}};
}
return {};
}
ALboolean VmorpherState::deviceUpdate(const ALCdevice* /*device*/)
{
for(auto &e : mChans)
{
std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]),
std::mem_fn(&FormantFilter::clear));
std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]),
std::mem_fn(&FormantFilter::clear));
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
}
return AL_TRUE;
}
void VmorpherState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target)
{
const ALCdevice *device{context->mDevice.get()};
const ALfloat frequency{static_cast<ALfloat>(device->Frequency)};
const ALfloat step{props->Vmorpher.Rate / frequency};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, ALfloat{WAVEFORM_FRACONE-1}));
if(mStep == 0)
mGetSamples = Oscillate<Half>;
else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SINUSOID)
mGetSamples = Oscillate<Sin>;
else if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH)
mGetSamples = Oscillate<Saw>;
else /*if(props->Vmorpher.Waveform == AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE)*/
mGetSamples = Oscillate<Triangle>;
const ALfloat pitchA{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)};
const ALfloat pitchB{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)};
auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
/* Copy the filter coefficients to the input channels. */
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].Formants[VOWEL_A_INDEX]));
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX]));
}
mOutTarget = target.Main->Buffer;
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
auto coeffs = GetAmbiIdentityRow(i);
ComputePanGains(target.Main, coeffs.data(), slot->Params.Gain, mChans[i].TargetGains);
}
}
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
/* Following the EFX specification for a conformant implementation which describes
* the effect as a pair of 4-band formant filters blended together using an LFO.
*/
for(size_t base{0u};base < samplesToDo;)
{
alignas(16) ALfloat lfo[MAX_UPDATE_SAMPLES];
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
mGetSamples(lfo, mIndex, mStep, td);
mIndex += static_cast<ALuint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
auto chandata = std::addressof(mChans[0]);
for(const auto &input : samplesIn)
{
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
std::fill_n(std::begin(mSampleBufferB), td, 0.0f);
auto& vowelA = chandata->Formants[VOWEL_A_INDEX];
auto& vowelB = chandata->Formants[VOWEL_B_INDEX];
/* Process first vowel. */
vowelA[0].process(&input[base], mSampleBufferA, td);
vowelA[1].process(&input[base], mSampleBufferA, td);
vowelA[2].process(&input[base], mSampleBufferA, td);
vowelA[3].process(&input[base], mSampleBufferA, td);
/* Process second vowel. */
vowelB[0].process(&input[base], mSampleBufferB, td);
vowelB[1].process(&input[base], mSampleBufferB, td);
vowelB[2].process(&input[base], mSampleBufferB, td);
vowelB[3].process(&input[base], mSampleBufferB, td);
alignas(16) ALfloat blended[MAX_UPDATE_SAMPLES];
for(size_t i{0u};i < td;i++)
blended[i] = lerp(mSampleBufferA[i], mSampleBufferB[i], lfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
samplesToDo-base, base);
++chandata;
}
base += td;
}
}
void Vmorpher_setParami(EffectProps* props, ALCcontext *context, ALenum param, ALint val)
{
switch(param)
{
case AL_VOCAL_MORPHER_WAVEFORM:
if(!(val >= AL_VOCAL_MORPHER_MIN_WAVEFORM && val <= AL_VOCAL_MORPHER_MAX_WAVEFORM))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher waveform out of range");
props->Vmorpher.Waveform = val;
break;
case AL_VOCAL_MORPHER_PHONEMEA:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a out of range");
props->Vmorpher.PhonemeA = val;
break;
case AL_VOCAL_MORPHER_PHONEMEB:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b out of range");
props->Vmorpher.PhonemeB = val;
break;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-a coarse tuning out of range");
props->Vmorpher.PhonemeACoarseTuning = val;
break;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher phoneme-b coarse tuning out of range");
props->Vmorpher.PhonemeBCoarseTuning = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param);
}
}
void Vmorpher_setParamiv(EffectProps*, ALCcontext *context, ALenum param, const ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
void Vmorpher_setParamf(EffectProps *props, ALCcontext *context, ALenum param, ALfloat val)
{
switch(param)
{
case AL_VOCAL_MORPHER_RATE:
if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
SETERR_RETURN(context, AL_INVALID_VALUE,, "Vocal morpher rate out of range");
props->Vmorpher.Rate = val;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
param);
}
}
void Vmorpher_setParamfv(EffectProps *props, ALCcontext *context, ALenum param, const ALfloat *vals)
{ Vmorpher_setParamf(props, context, param, vals[0]); }
void Vmorpher_getParami(const EffectProps* props, ALCcontext *context, ALenum param, ALint* val)
{
switch(param)
{
case AL_VOCAL_MORPHER_PHONEMEA:
*val = props->Vmorpher.PhonemeA;
break;
case AL_VOCAL_MORPHER_PHONEMEB:
*val = props->Vmorpher.PhonemeB;
break;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
*val = props->Vmorpher.PhonemeACoarseTuning;
break;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
*val = props->Vmorpher.PhonemeBCoarseTuning;
break;
case AL_VOCAL_MORPHER_WAVEFORM:
*val = props->Vmorpher.Waveform;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param);
}
}
void Vmorpher_getParamiv(const EffectProps*, ALCcontext *context, ALenum param, ALint*)
{ context->setError(AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param); }
void Vmorpher_getParamf(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *val)
{
switch(param)
{
case AL_VOCAL_MORPHER_RATE:
*val = props->Vmorpher.Rate;
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
param);
}
}
void Vmorpher_getParamfv(const EffectProps *props, ALCcontext *context, ALenum param, ALfloat *vals)
{ Vmorpher_getParamf(props, context, param, vals); }
DEFINE_ALEFFECT_VTABLE(Vmorpher);
struct VmorpherStateFactory final : public EffectStateFactory {
EffectState *create() override { return new VmorpherState{}; }
EffectProps getDefaultProps() const noexcept override;
const EffectVtable *getEffectVtable() const noexcept override { return &Vmorpher_vtable; }
};
EffectProps VmorpherStateFactory::getDefaultProps() const noexcept
{
EffectProps props{};
props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
props.Vmorpher.PhonemeA = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA;
props.Vmorpher.PhonemeB = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB;
props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING;
props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING;
props.Vmorpher.Waveform = AL_VOCAL_MORPHER_DEFAULT_WAVEFORM;
return props;
}
} // namespace
EffectStateFactory *VmorpherStateFactory_getFactory()
{
static VmorpherStateFactory VmorpherFactory{};
return &VmorpherFactory;
}
-124
View File
@@ -1,124 +0,0 @@
#include "config.h"
#include "biquad.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include "opthelpers.h"
template<typename Real>
void BiquadFilterR<Real>::setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ)
{
// Limit gain to -100dB
assert(gain > 0.00001f);
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
const Real sin_w0{std::sin(w0)};
const Real cos_w0{std::cos(w0)};
const Real alpha{sin_w0/2.0f * rcpQ};
Real sqrtgain_alpha_2;
Real a[3]{ 1.0f, 0.0f, 0.0f };
Real b[3]{ 1.0f, 0.0f, 0.0f };
/* Calculate filter coefficients depending on filter type */
switch(type)
{
case BiquadType::HighShelf:
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
b[0] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
b[1] = -2.0f*gain*((gain-1.0f) + (gain+1.0f)*cos_w0 );
b[2] = gain*((gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
a[0] = (gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
a[1] = 2.0f* ((gain-1.0f) - (gain+1.0f)*cos_w0 );
a[2] = (gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
break;
case BiquadType::LowShelf:
sqrtgain_alpha_2 = 2.0f * std::sqrt(gain) * alpha;
b[0] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 + sqrtgain_alpha_2);
b[1] = 2.0f*gain*((gain-1.0f) - (gain+1.0f)*cos_w0 );
b[2] = gain*((gain+1.0f) - (gain-1.0f)*cos_w0 - sqrtgain_alpha_2);
a[0] = (gain+1.0f) + (gain-1.0f)*cos_w0 + sqrtgain_alpha_2;
a[1] = -2.0f* ((gain-1.0f) + (gain+1.0f)*cos_w0 );
a[2] = (gain+1.0f) + (gain-1.0f)*cos_w0 - sqrtgain_alpha_2;
break;
case BiquadType::Peaking:
b[0] = 1.0f + alpha * gain;
b[1] = -2.0f * cos_w0;
b[2] = 1.0f - alpha * gain;
a[0] = 1.0f + alpha / gain;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha / gain;
break;
case BiquadType::LowPass:
b[0] = (1.0f - cos_w0) / 2.0f;
b[1] = 1.0f - cos_w0;
b[2] = (1.0f - cos_w0) / 2.0f;
a[0] = 1.0f + alpha;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha;
break;
case BiquadType::HighPass:
b[0] = (1.0f + cos_w0) / 2.0f;
b[1] = -(1.0f + cos_w0);
b[2] = (1.0f + cos_w0) / 2.0f;
a[0] = 1.0f + alpha;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha;
break;
case BiquadType::BandPass:
b[0] = alpha;
b[1] = 0.0f;
b[2] = -alpha;
a[0] = 1.0f + alpha;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha;
break;
}
mA1 = a[1] / a[0];
mA2 = a[2] / a[0];
mB0 = b[0] / a[0];
mB1 = b[1] / a[0];
mB2 = b[2] / a[0];
}
template<typename Real>
void BiquadFilterR<Real>::process(const al::span<const Real> src, Real *dst)
{
const Real b0{mB0};
const Real b1{mB1};
const Real b2{mB2};
const Real a1{mA1};
const Real a2{mA2};
Real z1{mZ1};
Real z2{mZ2};
/* Processing loop is Transposed Direct Form II. This requires less storage
* compared to Direct Form I (only two delay components, instead of a four-
* sample history; the last two inputs and outputs), and works better for
* floating-point which favors summing similarly-sized values while being
* less bothered by overflow.
*
* See: http://www.earlevel.com/main/2003/02/28/biquads/
*/
auto proc_sample = [b0,b1,b2,a1,a2,&z1,&z2](Real input) noexcept -> Real
{
const Real output{input*b0 + z1};
z1 = input*b1 - output*a1 + z2;
z2 = input*b2 - output*a2;
return output;
};
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
mZ1 = z1;
mZ2 = z2;
}
template class BiquadFilterR<float>;
template class BiquadFilterR<double>;
-134
View File
@@ -1,134 +0,0 @@
#ifndef FILTERS_BIQUAD_H
#define FILTERS_BIQUAD_H
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <utility>
#include "alspan.h"
#include "math_defs.h"
/* Filters implementation is based on the "Cookbook formulae for audio
* EQ biquad filter coefficients" by Robert Bristow-Johnson
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
*/
/* Implementation note: For the shelf and peaking filters, the specified gain
* is for the centerpoint of the transition band. This better fits EFX filter
* behavior, which expects the shelf's reference frequency to reach the given
* gain. To set the gain for the shelf or peak itself, use the square root of
* the desired linear gain (or halve the dB gain).
*/
enum class BiquadType {
/** EFX-style low-pass filter, specifying a gain and reference frequency. */
HighShelf,
/** EFX-style high-pass filter, specifying a gain and reference frequency. */
LowShelf,
/** Peaking filter, specifying a gain and reference frequency. */
Peaking,
/** Low-pass cut-off filter, specifying a cut-off frequency. */
LowPass,
/** High-pass cut-off filter, specifying a cut-off frequency. */
HighPass,
/** Band-pass filter, specifying a center frequency. */
BandPass,
};
template<typename Real>
class BiquadFilterR {
/* Last two delayed components for direct form II. */
Real mZ1{0.0f}, mZ2{0.0f};
/* Transfer function coefficients "b" (numerator) */
Real mB0{1.0f}, mB1{0.0f}, mB2{0.0f};
/* Transfer function coefficients "a" (denominator; a0 is pre-applied). */
Real mA1{0.0f}, mA2{0.0f};
void setParams(BiquadType type, Real f0norm, Real gain, Real rcpQ);
/**
* Calculates the rcpQ (i.e. 1/Q) coefficient for shelving filters, using
* the reference gain and shelf slope parameter.
* \param gain 0 < gain
* \param slope 0 < slope <= 1
*/
static Real rcpQFromSlope(Real gain, Real slope)
{ return std::sqrt((gain + 1.0f/gain)*(1.0f/slope - 1.0f) + 2.0f); }
/**
* Calculates the rcpQ (i.e. 1/Q) coefficient for filters, using the
* normalized reference frequency and bandwidth.
* \param f0norm 0 < f0norm < 0.5.
* \param bandwidth 0 < bandwidth
*/
static Real rcpQFromBandwidth(Real f0norm, Real bandwidth)
{
const Real w0{al::MathDefs<Real>::Tau() * f0norm};
return 2.0f*std::sinh(std::log(Real{2.0f})/2.0f*bandwidth*w0/std::sin(w0));
}
public:
void clear() noexcept { mZ1 = mZ2 = 0.0f; }
/**
* Sets the filter state for the specified filter type and its parameters.
*
* \param type The type of filter to apply.
* \param f0norm The normalized reference frequency (ref / sample_rate).
* This is the center point for the Shelf, Peaking, and BandPass filter
* types, or the cutoff frequency for the LowPass and HighPass filter
* types.
* \param gain The gain for the reference frequency response. Only used by
* the Shelf and Peaking filter types.
* \param slope Slope steepness of the transition band.
*/
void setParamsFromSlope(BiquadType type, Real f0norm, Real gain, Real slope)
{
gain = std::max<Real>(gain, 0.001f); /* Limit -60dB */
setParams(type, f0norm, gain, rcpQFromSlope(gain, slope));
}
/**
* Sets the filter state for the specified filter type and its parameters.
*
* \param type The type of filter to apply.
* \param f0norm The normalized reference frequency (ref / sample_rate).
* This is the center point for the Shelf, Peaking, and BandPass filter
* types, or the cutoff frequency for the LowPass and HighPass filter
* types.
* \param gain The gain for the reference frequency response. Only used by
* the Shelf and Peaking filter types.
* \param bandwidth Normalized bandwidth of the transition band.
*/
void setParamsFromBandwidth(BiquadType type, Real f0norm, Real gain, Real bandwidth)
{ setParams(type, f0norm, gain, rcpQFromBandwidth(f0norm, bandwidth)); }
void copyParamsFrom(const BiquadFilterR &other)
{
mB0 = other.mB0;
mB1 = other.mB1;
mB2 = other.mB2;
mA1 = other.mA1;
mA2 = other.mA2;
}
void process(const al::span<const Real> src, Real *dst);
/* Rather hacky. It's just here to support "manual" processing. */
std::pair<Real,Real> getComponents() const noexcept { return {mZ1, mZ2}; }
void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; }
Real processOne(const Real in, Real &z1, Real &z2) const noexcept
{
const Real out{in*mB0 + z1};
z1 = in*mB1 - out*mA1 + z2;
z2 = in*mB2 - out*mA2;
return out;
}
};
using BiquadFilter = BiquadFilterR<float>;
#endif /* FILTERS_BIQUAD_H */
-383
View File
@@ -1,383 +0,0 @@
#include "config.h"
#include "nfc.h"
#include <algorithm>
#include "opthelpers.h"
/* Near-field control filters are the basis for handling the near-field effect.
* The near-field effect is a bass-boost present in the directional components
* of a recorded signal, created as a result of the wavefront curvature (itself
* a function of sound distance). Proper reproduction dictates this be
* compensated for using a bass-cut given the playback speaker distance, to
* avoid excessive bass in the playback.
*
* For real-time rendered audio, emulating the near-field effect based on the
* sound source's distance, and subsequently compensating for it at output
* based on the speaker distances, can create a more realistic perception of
* sound distance beyond a simple 1/r attenuation.
*
* These filters do just that. Each one applies a low-shelf filter, created as
* the combination of a bass-boost for a given sound source distance (near-
* field emulation) along with a bass-cut for a given control/speaker distance
* (near-field compensation).
*
* Note that it is necessary to apply a cut along with the boost, since the
* boost alone is unstable in higher-order ambisonics as it causes an infinite
* DC gain (even first-order ambisonics requires there to be no DC offset for
* the boost to work). Consequently, ambisonics requires a control parameter to
* be used to avoid an unstable boost-only filter. NFC-HOA defines this control
* as a reference delay, calculated with:
*
* reference_delay = control_distance / speed_of_sound
*
* This means w0 (for input) or w1 (for output) should be set to:
*
* wN = 1 / (reference_delay * sample_rate)
*
* when dealing with NFC-HOA content. For FOA input content, which does not
* specify a reference_delay variable, w0 should be set to 0 to apply only
* near-field compensation for output. It's important that w1 be a finite,
* positive, non-0 value or else the bass-boost will become unstable again.
* Also, w0 should not be too large compared to w1, to avoid excessively loud
* low frequencies.
*/
namespace {
constexpr float B[5][4] = {
{ 0.0f },
{ 1.0f },
{ 3.0f, 3.0f },
{ 3.6778f, 6.4595f, 2.3222f },
{ 4.2076f, 11.4877f, 5.7924f, 9.1401f }
};
NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept
{
NfcFilter1 nfc{};
float b_00, g_0;
float r;
nfc.base_gain = 1.0f;
nfc.gain = 1.0f;
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_00 = B[1][0] * r;
g_0 = 1.0f + b_00;
nfc.gain *= g_0;
nfc.b1 = 2.0f * b_00 / g_0;
/* Calculate bass-cut coefficients. */
r = 0.5f * w1;
b_00 = B[1][0] * r;
g_0 = 1.0f + b_00;
nfc.base_gain /= g_0;
nfc.gain /= g_0;
nfc.a1 = 2.0f * b_00 / g_0;
return nfc;
}
void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_00{B[1][0] * r};
const float g_0{1.0f + b_00};
nfc->gain = nfc->base_gain * g_0;
nfc->b1 = 2.0f * b_00 / g_0;
}
NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept
{
NfcFilter2 nfc{};
float b_10, b_11, g_1;
float r;
nfc.base_gain = 1.0f;
nfc.gain = 1.0f;
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_10 = B[2][0] * r;
b_11 = B[2][1] * r * r;
g_1 = 1.0f + b_10 + b_11;
nfc.gain *= g_1;
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc.b2 = 4.0f * b_11 / g_1;
/* Calculate bass-cut coefficients. */
r = 0.5f * w1;
b_10 = B[2][0] * r;
b_11 = B[2][1] * r * r;
g_1 = 1.0f + b_10 + b_11;
nfc.base_gain /= g_1;
nfc.gain /= g_1;
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc.a2 = 4.0f * b_11 / g_1;
return nfc;
}
void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_10{B[2][0] * r};
const float b_11{B[2][1] * r * r};
const float g_1{1.0f + b_10 + b_11};
nfc->gain = nfc->base_gain * g_1;
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc->b2 = 4.0f * b_11 / g_1;
}
NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept
{
NfcFilter3 nfc{};
float b_10, b_11, g_1;
float b_00, g_0;
float r;
nfc.base_gain = 1.0f;
nfc.gain = 1.0f;
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_10 = B[3][0] * r;
b_11 = B[3][1] * r * r;
b_00 = B[3][2] * r;
g_1 = 1.0f + b_10 + b_11;
g_0 = 1.0f + b_00;
nfc.gain *= g_1 * g_0;
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc.b2 = 4.0f * b_11 / g_1;
nfc.b3 = 2.0f * b_00 / g_0;
/* Calculate bass-cut coefficients. */
r = 0.5f * w1;
b_10 = B[3][0] * r;
b_11 = B[3][1] * r * r;
b_00 = B[3][2] * r;
g_1 = 1.0f + b_10 + b_11;
g_0 = 1.0f + b_00;
nfc.base_gain /= g_1 * g_0;
nfc.gain /= g_1 * g_0;
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc.a2 = 4.0f * b_11 / g_1;
nfc.a3 = 2.0f * b_00 / g_0;
return nfc;
}
void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_10{B[3][0] * r};
const float b_11{B[3][1] * r * r};
const float b_00{B[3][2] * r};
const float g_1{1.0f + b_10 + b_11};
const float g_0{1.0f + b_00};
nfc->gain = nfc->base_gain * g_1 * g_0;
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc->b2 = 4.0f * b_11 / g_1;
nfc->b3 = 2.0f * b_00 / g_0;
}
NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept
{
NfcFilter4 nfc{};
float b_10, b_11, g_1;
float b_00, b_01, g_0;
float r;
nfc.base_gain = 1.0f;
nfc.gain = 1.0f;
/* Calculate bass-boost coefficients. */
r = 0.5f * w0;
b_10 = B[4][0] * r;
b_11 = B[4][1] * r * r;
b_00 = B[4][2] * r;
b_01 = B[4][3] * r * r;
g_1 = 1.0f + b_10 + b_11;
g_0 = 1.0f + b_00 + b_01;
nfc.gain *= g_1 * g_0;
nfc.b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc.b2 = 4.0f * b_11 / g_1;
nfc.b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
nfc.b4 = 4.0f * b_01 / g_0;
/* Calculate bass-cut coefficients. */
r = 0.5f * w1;
b_10 = B[4][0] * r;
b_11 = B[4][1] * r * r;
b_00 = B[4][2] * r;
b_01 = B[4][3] * r * r;
g_1 = 1.0f + b_10 + b_11;
g_0 = 1.0f + b_00 + b_01;
nfc.base_gain /= g_1 * g_0;
nfc.gain /= g_1 * g_0;
nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc.a2 = 4.0f * b_11 / g_1;
nfc.a3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
nfc.a4 = 4.0f * b_01 / g_0;
return nfc;
}
void NfcFilterAdjust4(NfcFilter4 *nfc, const float w0) noexcept
{
const float r{0.5f * w0};
const float b_10{B[4][0] * r};
const float b_11{B[4][1] * r * r};
const float b_00{B[4][2] * r};
const float b_01{B[4][3] * r * r};
const float g_1{1.0f + b_10 + b_11};
const float g_0{1.0f + b_00 + b_01};
nfc->gain = nfc->base_gain * g_1 * g_0;
nfc->b1 = (2.0f*b_10 + 4.0f*b_11) / g_1;
nfc->b2 = 4.0f * b_11 / g_1;
nfc->b3 = (2.0f*b_00 + 4.0f*b_01) / g_0;
nfc->b4 = 4.0f * b_01 / g_0;
}
} // namespace
void NfcFilter::init(const float w1) noexcept
{
first = NfcFilterCreate1(0.0f, w1);
second = NfcFilterCreate2(0.0f, w1);
third = NfcFilterCreate3(0.0f, w1);
fourth = NfcFilterCreate4(0.0f, w1);
}
void NfcFilter::adjust(const float w0) noexcept
{
NfcFilterAdjust1(&first, w0);
NfcFilterAdjust2(&second, w0);
NfcFilterAdjust3(&third, w0);
NfcFilterAdjust4(&fourth, w0);
}
void NfcFilter::process1(const al::span<const float> src, float *RESTRICT dst)
{
const float gain{first.gain};
const float b1{first.b1};
const float a1{first.a1};
float z1{first.z[0]};
auto proc_sample = [gain,b1,a1,&z1](const float in) noexcept -> float
{
const float y{in*gain - a1*z1};
const float out{y + b1*z1};
z1 += y;
return out;
};
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
first.z[0] = z1;
}
void NfcFilter::process2(const al::span<const float> src, float *RESTRICT dst)
{
const float gain{second.gain};
const float b1{second.b1};
const float b2{second.b2};
const float a1{second.a1};
const float a2{second.a2};
float z1{second.z[0]};
float z2{second.z[1]};
auto proc_sample = [gain,b1,b2,a1,a2,&z1,&z2](const float in) noexcept -> float
{
const float y{in*gain - a1*z1 - a2*z2};
const float out{y + b1*z1 + b2*z2};
z2 += z1;
z1 += y;
return out;
};
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
second.z[0] = z1;
second.z[1] = z2;
}
void NfcFilter::process3(const al::span<const float> src, float *RESTRICT dst)
{
const float gain{third.gain};
const float b1{third.b1};
const float b2{third.b2};
const float b3{third.b3};
const float a1{third.a1};
const float a2{third.a2};
const float a3{third.a3};
float z1{third.z[0]};
float z2{third.z[1]};
float z3{third.z[2]};
auto proc_sample = [gain,b1,b2,b3,a1,a2,a3,&z1,&z2,&z3](const float in) noexcept -> float
{
float y{in*gain - a1*z1 - a2*z2};
float out{y + b1*z1 + b2*z2};
z2 += z1;
z1 += y;
y = out - a3*z3;
out = y + b3*z3;
z3 += y;
return out;
};
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
third.z[0] = z1;
third.z[1] = z2;
third.z[2] = z3;
}
void NfcFilter::process4(const al::span<const float> src, float *RESTRICT dst)
{
const float gain{fourth.gain};
const float b1{fourth.b1};
const float b2{fourth.b2};
const float b3{fourth.b3};
const float b4{fourth.b4};
const float a1{fourth.a1};
const float a2{fourth.a2};
const float a3{fourth.a3};
const float a4{fourth.a4};
float z1{fourth.z[0]};
float z2{fourth.z[1]};
float z3{fourth.z[2]};
float z4{fourth.z[3]};
auto proc_sample = [gain,b1,b2,b3,b4,a1,a2,a3,a4,&z1,&z2,&z3,&z4](const float in) noexcept -> float
{
float y{in*gain - a1*z1 - a2*z2};
float out{y + b1*z1 + b2*z2};
z2 += z1;
z1 += y;
y = out - a3*z3 - a4*z4;
out = y + b3*z3 + b4*z4;
z4 += z3;
z3 += y;
return out;
};
std::transform(src.cbegin(), src.cend(), dst, proc_sample);
fourth.z[0] = z1;
fourth.z[1] = z2;
fourth.z[2] = z3;
fourth.z[3] = z4;
}
-63
View File
@@ -1,63 +0,0 @@
#ifndef FILTER_NFC_H
#define FILTER_NFC_H
#include <cstddef>
#include "alspan.h"
struct NfcFilter1 {
float base_gain, gain;
float b1, a1;
float z[1];
};
struct NfcFilter2 {
float base_gain, gain;
float b1, b2, a1, a2;
float z[2];
};
struct NfcFilter3 {
float base_gain, gain;
float b1, b2, b3, a1, a2, a3;
float z[3];
};
struct NfcFilter4 {
float base_gain, gain;
float b1, b2, b3, b4, a1, a2, a3, a4;
float z[4];
};
class NfcFilter {
NfcFilter1 first;
NfcFilter2 second;
NfcFilter3 third;
NfcFilter4 fourth;
public:
/* NOTE:
* w0 = speed_of_sound / (source_distance * sample_rate);
* w1 = speed_of_sound / (control_distance * sample_rate);
*
* Generally speaking, the control distance should be approximately the
* average speaker distance, or based on the reference delay if outputing
* NFC-HOA. It must not be negative, 0, or infinite. The source distance
* should not be too small relative to the control distance.
*/
void init(const float w1) noexcept;
void adjust(const float w0) noexcept;
/* Near-field control filter for first-order ambisonic channels (1-3). */
void process1(const al::span<const float> src, float *RESTRICT dst);
/* Near-field control filter for second-order ambisonic channels (4-8). */
void process2(const al::span<const float> src, float *RESTRICT dst);
/* Near-field control filter for third-order ambisonic channels (9-15). */
void process3(const al::span<const float> src, float *RESTRICT dst);
/* Near-field control filter for fourth-order ambisonic channels (16-24). */
void process4(const al::span<const float> src, float *RESTRICT dst);
};
#endif /* FILTER_NFC_H */
-113
View File
@@ -1,113 +0,0 @@
#include "config.h"
#include "splitter.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "math_defs.h"
#include "opthelpers.h"
template<typename Real>
void BandSplitterR<Real>::init(Real f0norm)
{
const Real w{f0norm * al::MathDefs<Real>::Tau()};
const Real cw{std::cos(w)};
if(cw > std::numeric_limits<float>::epsilon())
mCoeff = (std::sin(w) - 1.0f) / cw;
else
mCoeff = cw * -0.5f;
mLpZ1 = 0.0f;
mLpZ2 = 0.0f;
mApZ1 = 0.0f;
}
template<typename Real>
void BandSplitterR<Real>::process(const al::span<const Real> input, Real *hpout, Real *lpout)
{
const Real ap_coeff{mCoeff};
const Real lp_coeff{mCoeff*0.5f + 0.5f};
Real lp_z1{mLpZ1};
Real lp_z2{mLpZ2};
Real ap_z1{mApZ1};
auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real
{
/* Low-pass sample processing. */
Real d{(in - lp_z1) * lp_coeff};
Real lp_y{lp_z1 + d};
lp_z1 = lp_y + d;
d = (lp_y - lp_z2) * lp_coeff;
lp_y = lp_z2 + d;
lp_z2 = lp_y + d;
*(lpout++) = lp_y;
/* All-pass sample processing. */
Real ap_y{in*ap_coeff + ap_z1};
ap_z1 = in - ap_y*ap_coeff;
/* High-pass generated from removing low-passed output. */
return ap_y - lp_y;
};
std::transform(input.cbegin(), input.cend(), hpout, proc_sample);
mLpZ1 = lp_z1;
mLpZ2 = lp_z2;
mApZ1 = ap_z1;
}
template<typename Real>
void BandSplitterR<Real>::applyHfScale(const al::span<Real> samples, const Real hfscale)
{
const Real ap_coeff{mCoeff};
const Real lp_coeff{mCoeff*0.5f + 0.5f};
Real lp_z1{mLpZ1};
Real lp_z2{mLpZ2};
Real ap_z1{mApZ1};
auto proc_sample = [hfscale,ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1](const Real in) noexcept -> Real
{
/* Low-pass sample processing. */
Real d{(in - lp_z1) * lp_coeff};
Real lp_y{lp_z1 + d};
lp_z1 = lp_y + d;
d = (lp_y - lp_z2) * lp_coeff;
lp_y = lp_z2 + d;
lp_z2 = lp_y + d;
/* All-pass sample processing. */
Real ap_y{in*ap_coeff + ap_z1};
ap_z1 = in - ap_y*ap_coeff;
/* High-pass generated by removing the low-passed signal, which is then
* scaled and added back to the low-passed signal.
*/
return (ap_y-lp_y)*hfscale + lp_y;
};
std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample);
mLpZ1 = lp_z1;
mLpZ2 = lp_z2;
mApZ1 = ap_z1;
}
template<typename Real>
void BandSplitterR<Real>::applyAllpass(const al::span<Real> samples) const
{
const Real coeff{mCoeff};
Real z1{0.0f};
auto proc_sample = [coeff,&z1](const Real in) noexcept -> Real
{
const Real out{in*coeff + z1};
z1 = in - out*coeff;
return out;
};
std::transform(samples.begin(), samples.end(), samples.begin(), proc_sample);
}
template class BandSplitterR<float>;
template class BandSplitterR<double>;

Some files were not shown because too many files have changed in this diff Show More