First Commit

This commit is contained in:
2025-02-06 22:24:29 +08:00
parent ed7df4c81e
commit 7539e6a53c
18116 changed files with 6181499 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
name: CI
on: [push]
jobs:
build:
name: ${{matrix.config.name}}
runs-on: ${{matrix.config.os}}
strategy:
fail-fast: false
matrix:
config:
- {
name: "Win32-Release",
os: windows-latest,
cmake_opts: "-A Win32 \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Release"
}
- {
name: "Win32-Debug",
os: windows-latest,
cmake_opts: "-A Win32 \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Debug"
}
- {
name: "Win64-Release",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Release"
}
- {
name: "Win64-Debug",
os: windows-latest,
cmake_opts: "-A x64 \
-DALSOFT_BUILD_ROUTER=ON \
-DALSOFT_REQUIRE_WINMM=ON \
-DALSOFT_REQUIRE_DSOUND=ON \
-DALSOFT_REQUIRE_WASAPI=ON",
build_type: "Debug"
}
- {
name: "macOS-Release",
os: macos-latest,
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON",
build_type: "Release"
}
- {
name: "Linux-Release",
os: ubuntu-latest,
cmake_opts: "-DALSOFT_REQUIRE_RTKIT=ON \
-DALSOFT_REQUIRE_ALSA=ON \
-DALSOFT_REQUIRE_OSS=ON \
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_JACK=ON \
-DALSOFT_REQUIRE_PIPEWIRE=ON",
deps_cmdline: "sudo apt update && sudo apt-get install -qq \
libpulse-dev \
portaudio19-dev \
libasound2-dev \
libjack-dev \
libpipewire-0.3-dev \
qtbase5-dev \
libdbus-1-dev",
build_type: "Release"
}
steps:
- uses: actions/checkout@v1
- name: Install Dependencies
shell: bash
run: |
if [[ ! -z "${{matrix.config.deps_cmdline}}" ]]; then
eval ${{matrix.config.deps_cmdline}}
fi
- name: Configure
shell: bash
run: |
cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} ${{matrix.config.cmake_opts}} .
- name: Build
shell: bash
run: |
cmake --build build --config ${{matrix.config.build_type}}
- name: Create Archive
if: ${{ matrix.config.os == 'windows-latest' }}
shell: bash
run: |
cd build
mkdir archive
mkdir archive/router
cp ${{matrix.config.build_type}}/soft_oal.dll archive
cp ${{matrix.config.build_type}}/OpenAL32.dll archive/router
- name: Upload Archive
# Upload package as an artifact of this workflow.
uses: actions/upload-artifact@v3.1.1
if: ${{ matrix.config.os == 'windows-latest' }}
with:
name: soft_oal-${{matrix.config.name}}
path: build/archive

View File

@@ -0,0 +1,76 @@
name: makemhr
on:
push:
paths:
- 'utils/makemhr/**'
- '.github/workflows/makemhr.yml'
workflow_dispatch:
env:
BUILD_TYPE: Release
jobs:
Win64:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Get current date
run: echo "CurrentDate=$(date +'%Y-%m-%d')" >> $env:GITHUB_ENV
- name: Get commit hash
run: echo "CommitHash=$(git rev-parse --short=7 HEAD)" >> $env:GITHUB_ENV
- name: Clone libmysofa
run: git clone --depth 1 --branch v1.3.1 https://github.com/hoene/libmysofa.git libmysofa
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v1.1.3
- name: Restore libmysofa NuGet packages
working-directory: ${{github.workspace}}/libmysofa
run: nuget restore ${{github.workspace}}/libmysofa/windows/libmysofa.sln
- name: Build libmysofa
working-directory: ${{github.workspace}}/libmysofa
run: msbuild /m /p:Configuration=${{env.BUILD_TYPE}} ${{github.workspace}}/libmysofa/windows/libmysofa.sln
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -D "MYSOFA_LIBRARY=${{github.workspace}}/libmysofa/windows/bin/x64/Release/mysofa.lib" -D "MYSOFA_INCLUDE_DIR=${{github.workspace}}/libmysofa/src/hrtf" -D "ZLIB_LIBRARY=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/lib/zlib.lib" -D "ZLIB_INCLUDE_DIR=${{github.workspace}}/libmysofa/windows/third-party/zlib-1.2.11/include"
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Make Artifacts folder
run: |
mkdir "Artifacts"
mkdir "Release"
- name: Collect artifacts
run: |
copy "build/Release/makemhr.exe" "Artifacts/makemhr.exe"
copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll"
- name: Upload makemhr artifact
uses: actions/upload-artifact@v3.1.1
with:
name: makemhr
path: "Artifacts/"
- name: Compress artifacts
uses: papeloto/action-zip@v1
with:
files: Artifacts/
dest: "Release/makemhr.zip"
- name: GitHub pre-release
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{secrets.GITHUB_TOKEN}}"
automatic_release_tag: "makemhr"
prerelease: true
title: "[${{env.CurrentDate}}] makemhr-${{env.CommitHash}}"
files: "Release/makemhr.zip"

9
externals/openal-soft/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
build*/
winbuild
win64build
## kdevelop
*.kdev4
## qt-creator
CMakeLists.txt.user*

125
externals/openal-soft/.travis.yml vendored Normal file
View File

@@ -0,0 +1,125 @@
language: cpp
matrix:
include:
- os: linux
dist: xenial
- os: linux
dist: trusty
env:
- BUILD_ANDROID=true
- os: freebsd
compiler: clang
- os: osx
- os: osx
osx_image: xcode11
env:
- BUILD_IOS=true
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 \
libdbus-1-dev
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "linux" && "${BUILD_ANDROID}" == "true" ]]; then
curl -o ~/android-ndk.zip https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip
unzip -q ~/android-ndk.zip -d ~ \
'android-ndk-r21/build/cmake/*' \
'android-ndk-r21/build/core/toolchains/arm-linux-androideabi-*/*' \
'android-ndk-r21/platforms/android-16/arch-arm/*' \
'android-ndk-r21/source.properties' \
'android-ndk-r21/sources/android/support/include/*' \
'android-ndk-r21/sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/*' \
'android-ndk-r21/sources/cxx-stl/llvm-libc++/include/*' \
'android-ndk-r21/sysroot/*' \
'android-ndk-r21/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/*' \
'android-ndk-r21/toolchains/llvm/prebuilt/linux-x86_64/*'
export OBOE_LOC=~/oboe
git clone --depth 1 -b 1.3-stable https://github.com/google/oboe "$OBOE_LOC"
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then
# Install Ninja as it's used downstream.
# Install dependencies for all supported backends.
# Install Qt5 dependency for alsoft-config.
# Install ffmpeg for examples.
sudo pkg install -y \
alsa-lib \
ffmpeg \
jackit \
libmysofa \
ninja \
portaudio \
pulseaudio \
qt5-buildtools \
qt5-qmake \
qt5-widgets \
sdl2 \
sndio \
$NULL
fi
script:
- cmake --version
- >
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-r21/build/cmake/android.toolchain.cmake \
-DOBOE_SOURCE="$OBOE_LOC" \
-DALSOFT_REQUIRE_OBOE=ON \
-DALSOFT_REQUIRE_OPENSL=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "freebsd" ]]; then
cmake -GNinja \
-DALSOFT_REQUIRE_ALSA=ON \
-DALSOFT_REQUIRE_JACK=ON \
-DALSOFT_REQUIRE_OSS=ON \
-DALSOFT_REQUIRE_PORTAUDIO=ON \
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
-DALSOFT_REQUIRE_SDL2=ON \
-DALSOFT_REQUIRE_SNDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "osx" && -z "${BUILD_IOS}" ]]; then
cmake \
-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
.
fi
- >
if [[ "${TRAVIS_OS_NAME}" == "osx" && "${BUILD_IOS}" == "true" ]]; then
cmake \
-GXcode \
-DCMAKE_SYSTEM_NAME=iOS \
-DALSOFT_OSX_FRAMEWORK=ON \
-DALSOFT_REQUIRE_COREAUDIO=ON \
-DALSOFT_EMBED_HRTF_DATA=YES \
"-DCMAKE_OSX_ARCHITECTURES=armv7;arm64" \
.
fi
- cmake --build . --clean-first

31
externals/openal-soft/BSD-3Clause vendored Normal file
View File

@@ -0,0 +1,31 @@
Portions of this software are licensed under the BSD 3-Clause license.
Copyright (c) 2015, Archontis Politis
Copyright (c) 2019, Anis A. Hireche
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.

1778
externals/openal-soft/CMakeLists.txt vendored Normal file

File diff suppressed because it is too large Load Diff

437
externals/openal-soft/COPYING vendored Normal file
View File

@@ -0,0 +1,437 @@
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

737
externals/openal-soft/ChangeLog vendored Normal file
View File

@@ -0,0 +1,737 @@
openal-soft-1.23.1:
Implemented the AL_SOFT_UHJ_ex extension.
Implemented the AL_SOFT_buffer_length_query extension.
Implemented the AL_SOFT_source_start_delay extension.
Implemented the AL_EXT_STATIC_BUFFER extension.
Fixed compiling with certain older versions of GCC.
Fixed compiling as a submodule.
Fixed compiling with newer versions of Oboe.
Improved EAX effect version switching.
Improved the quality of the reverb modulator.
Improved performance of the cubic resampler.
Added a compatibility option to restore AL_SOFT_buffer_sub_data. The option
disables AL_EXT_SOURCE_RADIUS due to incompatibility.
Reduced CPU usage when EAX is initialized and FXSlot0 or FXSlot1 are not
used.
Reduced memory usage for ADPCM buffer formats. They're no longer converted
to 16-bit samples on load.
openal-soft-1.23.0:
Fixed CoreAudio capture support.
Fixed handling per-version EAX properties.
Fixed interpolating changes to the Super Stereo width source property.
Fixed detection of the update and buffer size from PipeWire.
Fixed resuming playback devices with OpenSL.
Fixed support for certain OpenAL implementations with the router.
Improved reverb environment transitions.
Improved performance of convolution reverb.
Improved quality and performance of the pitch shifter effect slightly.
Improved sub-sample precision for resampled sources.
Improved blending spatialized multi-channel sources that use the source
radius property.
Improved mixing 2D ambisonic sources for higher-order 3D ambisonic mixing.
Improved quadraphonic and 7.1 surround sound output slightly.
Added config options for UHJ encoding/decoding quality. Including Super
Stereo processing.
Added a config option for specifying the speaker distance.
Added a compatibility config option for specifying the NFC distance
scaling.
Added a config option for mixing on PipeWire's non-real-time thread.
Added support for virtual source nodes with PipeWire capture.
Added the ability for the WASAPI backend to use different playback rates.
Added support for SOFA files that define per-response delays in makemhr.
Changed the default fallback playback sample rate to 48khz. This doesn't
affect most backends, which can detect a default rate from the system.
Changed the default resampler to cubic.
Changed the default HRTF size from 32 to 64 points.
openal-soft-1.22.2:
Fixed PipeWire version check.
Fixed building with PipeWire versions before 0.3.33.
openal-soft-1.22.1:
Fixed CoreAudio capture.
Fixed air absorption strength.
Fixed handling 5.1 devices on Windows that use Rear channels instead of
Side channels.
Fixed some compilation issues on MinGW.
Fixed ALSA not being used on some systems without PipeWire and PulseAudio.
Fixed OpenSL capturing noise.
Fixed Oboe capture failing with some buffer sizes.
Added checks for the runtime PipeWire version. The same or newer version
than is used for building will be needed at runtime for the backend to
work.
Separated 3D7.1 into its own speaker configuration.
openal-soft-1.22.0:
Implemented the ALC_SOFT_reopen_device extension. This allows for moving
devices to different outputs without losing object state.
Implemented the ALC_SOFT_output_mode extension.
Implemented the AL_SOFT_callback_buffer extension.
Implemented the AL_SOFT_UHJ extension. This supports native UHJ buffer
formats and Super Stereo processing.
Implemented the legacy EAX extensions. Enabled by default only on Windows.
Improved sound positioning stability when a source is near the listener.
Improved the default 5.1 output decoder.
Improved the high frequency response for the HRTF second-order ambisonic
decoder.
Improved SoundIO capture behavior.
Fixed UHJ output on NEON-capable CPUs.
Fixed redundant effect updates when setting an effect property to the
current value.
Fixed WASAPI capture using really low sample rates, and sources with very
high pitch shifts when using a bsinc resampler.
Added a PipeWire backend.
Added enumeration for the JACK and CoreAudio backends.
Added optional support for RTKit to get real-time priority. Only used as a
backup when pthread_setschedparam fails.
Added an option for JACK playback to render directly in the real-time
processing callback. For lower playback latency, on by default.
Added an option for custom JACK devices.
Added utilities to encode and decode UHJ audio files. Files are decoded to
the .amb format, and are encoded from libsndfile-compatible formats.
Added an in-progress extension to hold sources in a playing state when a
device disconnects. Allows devices to be reset or reopened and have sources
resume from where they left off.
Lowered the priority of the JACK backend. To avoid it getting picked when
PipeWire is providing JACK compatibility, since the JACK backend is less
robust with auto-configuration.
openal-soft-1.21.1:
Improved alext.h's detection of standard types.
Improved slightly the local source position when the listener and source
are near each other.
Improved click/pop prevention for sounds that stop prematurely.
Fixed compilation for Windows ARM targets with MSVC.
Fixed ARM NEON detection on Windows.
Fixed CoreAudio capture when the requested sample rate doesn't match the
system configuration.
Fixed OpenSL capture desyncing from the internal capture buffer.
Fixed sources missing a batch update when applied after quickly restarting
the source.
Fixed missing source stop events when stopping a paused source.
Added capture support to the experimental Oboe backend.
openal-soft-1.21.0:
Updated library codebase to C++14.
Implemented the AL_SOFT_effect_target extension.
Implemented the AL_SOFT_events extension.
Implemented the ALC_SOFT_loopback_bformat extension.
Improved memory use for mixing voices.
Improved detection of NEON capabilities.
Improved handling of PulseAudio devices that lack manual start control.
Improved mixing performance with PulseAudio.
Improved high-frequency scaling quality for the HRTF B-Format decoder.
Improved makemhr's HRIR delay calculation.
Improved WASAPI capture of mono formats with multichannel input.
Reimplemented the modulation stage for reverb.
Enabled real-time mixing priority by default, for backends that use the
setting. It can still be disabled in the config file.
Enabled dual-band processing for the built-in quad and 7.1 output decoders.
Fixed a potential crash when deleting an effect slot immediately after the
last source using it stops.
Fixed building with the static runtime on MSVC.
Fixed using source stereo angles outside of -pi...+pi.
Fixed the buffer processed event count for sources that start with empty
buffers.
Fixed trying to open an unopenable WASAPI device causing all devices to
stop working.
Fixed stale devices when re-enumerating WASAPI devices.
Fixed using unicode paths with the log file on Windows.
Fixed DirectSound capture reporting bad sample counts or erroring when
reading samples.
Added an in-progress extension for a callback-driven buffer type.
Added an in-progress extension for higher-order B-Format buffers.
Added an in-progress extension for convolution reverb.
Added an experimental Oboe backend for Android playback. This requires the
Oboe sources at build time, so that it's built as a static library included
in libopenal.
Added an option for auto-connecting JACK ports.
Added greater-than-stereo support to the SoundIO backend.
Modified the mixer to be fully asynchronous with the external API, and
should now be real-time safe. Although alcRenderSamplesSOFT is not due to
locking to check the device handle validity.
Modified the UHJ encoder to use an all-pass FIR filter that's less harmful
to non-filtered signal phase.
Converted examples from SDL_sound to libsndfile. To avoid issues when
combining SDL2 and SDL_sound.
Worked around a 32-bit GCC/MinGW bug with TLS destructors. See:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83562
Reduced the maximum number of source sends from 16 to 6.
Removed the QSA backend. It's been broken for who knows how long.
Got rid of the compile-time native-tools targets, using cmake and global
initialization instead. This should make cross-compiling less troublesome.
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.

View File

@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.1)
include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake")
set(OPENAL_FOUND ON)
set(OPENAL_INCLUDE_DIR $<TARGET_PROPERTY:OpenAL::OpenAL,INTERFACE_INCLUDE_DIRECTORIES>)
set(OPENAL_LIBRARY $<LINK_ONLY:OpenAL::OpenAL>)
set(OPENAL_DEFINITIONS $<TARGET_PROPERTY:OpenAL::OpenAL,INTERFACE_COMPILE_DEFINITIONS>)
set(OPENAL_VERSION_STRING @PACKAGE_VERSION@)

78
externals/openal-soft/README.md vendored Normal file
View File

@@ -0,0 +1,78 @@
OpenAL Soft
===========
`master` branch CI status : [![GitHub Actions Status](https://github.com/kcat/openal-soft/actions/workflows/ci.yml/badge.svg)](https://github.com/kcat/openal-soft/actions) [![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)
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.
More information is available on the [official website](http://openal-soft.org/).
Source Install
-------------
To install OpenAL Soft, use your favorite shell to go into the build/
directory, and run:
```bash
cmake ..
```
Alternatively, you can use any available CMake front-end, like cmake-gui,
ccmake, or your preferred IDE's CMake project parser.
Assuming configuration went well, you can then build it. The command
`cmake --build .` will instruct CMake to build the project with the toolchain
chosen during configuration (often GNU Make or NMake, although others are
possible).
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 PipeWire, PulseAudio, and
ALSA were detected (if your target system uses them). For Windows, make sure
WASAPI was detected.
Building openal-soft - Using vcpkg
----------------------------------
You can download and install openal-soft using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
./vcpkg install openal-soft
The openal-soft port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.
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.

View File

@@ -0,0 +1,16 @@
# Cross-compiling for Android is handled by the NDK's own provided toolchain,
# which as of this writing, should be in
# ${ndk_root}/build/cmake/android.toolchain.cmake
#
# Certain older NDK versions may also need to explicitly pick the libc++
# runtime. So for example:
# cmake .. -DANDROID_STL=c++_shared \
# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake
#
# Certain NDK versions may also need to use the lld linker to avoid errors
# about missing liblog.so and libOpenSLES.so. That can be done by:
# cmake .. -DANDROID_LD=lld \
# -DCMAKE_TOOLCHAIN_FILE=${ndk_root}/build/cmake/android.toolchain.cmake
#
MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK")

37
externals/openal-soft/XCompile.txt vendored Normal file
View File

@@ -0,0 +1,37 @@
# 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)

1563
externals/openal-soft/al/auxeffectslot.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

368
externals/openal-soft/al/auxeffectslot.h vendored Normal file
View File

@@ -0,0 +1,368 @@
#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 "alc/device.h"
#include "alc/effects/base.h"
#include "almalloc.h"
#include "atomic.h"
#include "core/effectslot.h"
#include "intrusive_ptr.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include <memory>
#include "eax/call.h"
#include "eax/effect.h"
#include "eax/exception.h"
#include "eax/fx_slot_index.h"
#include "eax/utils.h"
#endif // ALSOFT_EAX
struct ALbuffer;
struct ALeffect;
struct WetBuffer;
#ifdef ALSOFT_EAX
class EaxFxSlotException : public EaxException {
public:
explicit EaxFxSlotException(const char* message)
: EaxException{"EAX_FX_SLOT", message}
{}
};
#endif // ALSOFT_EAX
enum class SlotState : ALenum {
Initial = AL_INITIAL,
Playing = AL_PLAYING,
Stopped = AL_STOPPED,
};
struct ALeffectslot {
float Gain{1.0f};
bool AuxSendAuto{true};
ALeffectslot *Target{nullptr};
ALbuffer *Buffer{nullptr};
struct {
EffectSlotType Type{EffectSlotType::None};
EffectProps Props{};
al::intrusive_ptr<EffectState> State;
} Effect;
bool mPropsDirty{true};
SlotState mState{SlotState::Initial};
RefCount ref{0u};
EffectSlot *mSlot{nullptr};
/* Self ID */
ALuint id{};
ALeffectslot(ALCcontext *context);
ALeffectslot(const ALeffectslot&) = delete;
ALeffectslot& operator=(const ALeffectslot&) = delete;
~ALeffectslot();
ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context);
void updateProps(ALCcontext *context);
/* This can be new'd for the context's default effect slot. */
DEF_NEWDEL(ALeffectslot)
#ifdef ALSOFT_EAX
public:
void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index);
EaxFxSlotIndexValue eax_get_index() const noexcept { return eax_fx_slot_index_; }
const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept
{ return eax_; }
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_dispatch(const EaxCall& call)
{ return call.is_get() ? eax_get(call) : eax_set(call); }
void eax_commit();
private:
static constexpr auto eax_load_effect_dirty_bit = EaxDirtyFlags{1} << 0;
static constexpr auto eax_volume_dirty_bit = EaxDirtyFlags{1} << 1;
static constexpr auto eax_lock_dirty_bit = EaxDirtyFlags{1} << 2;
static constexpr auto eax_flags_dirty_bit = EaxDirtyFlags{1} << 3;
static constexpr auto eax_occlusion_dirty_bit = EaxDirtyFlags{1} << 4;
static constexpr auto eax_occlusion_lf_ratio_dirty_bit = EaxDirtyFlags{1} << 5;
using Exception = EaxFxSlotException;
using Eax4Props = EAX40FXSLOTPROPERTIES;
struct Eax4State {
Eax4Props i; // Immediate.
};
using Eax5Props = EAX50FXSLOTPROPERTIES;
struct Eax5State {
Eax5Props i; // Immediate.
};
struct EaxRangeValidator {
template<typename TValue>
void operator()(
const char* name,
const TValue& value,
const TValue& min_value,
const TValue& max_value) const
{
eax_validate_range<Exception>(name, value, min_value, max_value);
}
};
struct Eax4GuidLoadEffectValidator {
void operator()(const GUID& guidLoadEffect) const
{
if (guidLoadEffect != EAX_NULL_GUID &&
guidLoadEffect != EAX_REVERB_EFFECT &&
guidLoadEffect != EAX_AGCCOMPRESSOR_EFFECT &&
guidLoadEffect != EAX_AUTOWAH_EFFECT &&
guidLoadEffect != EAX_CHORUS_EFFECT &&
guidLoadEffect != EAX_DISTORTION_EFFECT &&
guidLoadEffect != EAX_ECHO_EFFECT &&
guidLoadEffect != EAX_EQUALIZER_EFFECT &&
guidLoadEffect != EAX_FLANGER_EFFECT &&
guidLoadEffect != EAX_FREQUENCYSHIFTER_EFFECT &&
guidLoadEffect != EAX_VOCALMORPHER_EFFECT &&
guidLoadEffect != EAX_PITCHSHIFTER_EFFECT &&
guidLoadEffect != EAX_RINGMODULATOR_EFFECT)
{
eax_fail_unknown_effect_id();
}
}
};
struct Eax4VolumeValidator {
void operator()(long lVolume) const
{
EaxRangeValidator{}(
"Volume",
lVolume,
EAXFXSLOT_MINVOLUME,
EAXFXSLOT_MAXVOLUME);
}
};
struct Eax4LockValidator {
void operator()(long lLock) const
{
EaxRangeValidator{}(
"Lock",
lLock,
EAXFXSLOT_MINLOCK,
EAXFXSLOT_MAXLOCK);
}
};
struct Eax4FlagsValidator {
void operator()(unsigned long ulFlags) const
{
EaxRangeValidator{}(
"Flags",
ulFlags,
0UL,
~EAX40FXSLOTFLAGS_RESERVED);
}
};
struct Eax4AllValidator {
void operator()(const EAX40FXSLOTPROPERTIES& all) const
{
Eax4GuidLoadEffectValidator{}(all.guidLoadEffect);
Eax4VolumeValidator{}(all.lVolume);
Eax4LockValidator{}(all.lLock);
Eax4FlagsValidator{}(all.ulFlags);
}
};
struct Eax5OcclusionValidator {
void operator()(long lOcclusion) const
{
EaxRangeValidator{}(
"Occlusion",
lOcclusion,
EAXFXSLOT_MINOCCLUSION,
EAXFXSLOT_MAXOCCLUSION);
}
};
struct Eax5OcclusionLfRatioValidator {
void operator()(float flOcclusionLFRatio) const
{
EaxRangeValidator{}(
"Occlusion LF Ratio",
flOcclusionLFRatio,
EAXFXSLOT_MINOCCLUSIONLFRATIO,
EAXFXSLOT_MAXOCCLUSIONLFRATIO);
}
};
struct Eax5FlagsValidator {
void operator()(unsigned long ulFlags) const
{
EaxRangeValidator{}(
"Flags",
ulFlags,
0UL,
~EAX50FXSLOTFLAGS_RESERVED);
}
};
struct Eax5AllValidator {
void operator()(const EAX50FXSLOTPROPERTIES& all) const
{
Eax4AllValidator{}(static_cast<const EAX40FXSLOTPROPERTIES&>(all));
Eax5OcclusionValidator{}(all.lOcclusion);
Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio);
}
};
ALCcontext* eax_al_context_{};
EaxFxSlotIndexValue eax_fx_slot_index_{};
int eax_version_{}; // Current EAX version.
EaxDirtyFlags eax_df_{}; // Dirty flags for the current EAX version.
EaxEffectUPtr eax_effect_{};
Eax5State eax123_{}; // EAX1/EAX2/EAX3 state.
Eax4State eax4_{}; // EAX4 state.
Eax5State eax5_{}; // EAX5 state.
Eax5Props eax_{}; // Current EAX state.
[[noreturn]] static void eax_fail(const char* message);
[[noreturn]] static void eax_fail_unknown_effect_id();
[[noreturn]] static void eax_fail_unknown_property_id();
[[noreturn]] static void eax_fail_unknown_version();
// Gets a new value from EAX call,
// validates it,
// sets a dirty flag only if the new value differs form the old one,
// and assigns the new value.
template<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties>
static void eax_fx_slot_set(const EaxCall& call, TProperties& dst, EaxDirtyFlags& dirty_flags)
{
const auto& src = call.get_value<Exception, const TProperties>();
TValidator{}(src);
dirty_flags |= (dst != src ? TDirtyBit : EaxDirtyFlags{});
dst = src;
}
// Gets a new value from EAX call,
// validates it,
// sets a dirty flag without comparing the values,
// and assigns the new value.
template<typename TValidator, EaxDirtyFlags TDirtyBit, typename TProperties>
static void eax_fx_slot_set_dirty(const EaxCall& call, TProperties& dst,
EaxDirtyFlags& dirty_flags)
{
const auto& src = call.get_value<Exception, const TProperties>();
TValidator{}(src);
dirty_flags |= TDirtyBit;
dst = src;
}
constexpr bool eax4_fx_slot_is_legacy() const noexcept
{ return eax_fx_slot_index_ < 2; }
void eax4_fx_slot_ensure_unlocked() const;
static ALenum eax_get_efx_effect_type(const GUID& guid);
const GUID& eax_get_eax_default_effect_guid() const noexcept;
long eax_get_eax_default_lock() const noexcept;
void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept;
void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept;
void eax4_fx_slot_set_current_defaults(const Eax4Props& props) noexcept;
void eax5_fx_slot_set_current_defaults(const Eax5Props& props) noexcept;
void eax_fx_slot_set_current_defaults();
void eax_fx_slot_set_defaults();
void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const;
void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const;
void eax_fx_slot_get(const EaxCall& call) const;
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_get(const EaxCall& call);
void eax_fx_slot_load_effect(int version, ALenum altype);
void eax_fx_slot_set_volume();
void eax_fx_slot_set_environment_flag();
void eax_fx_slot_set_flags();
void eax4_fx_slot_set_all(const EaxCall& call);
void eax5_fx_slot_set_all(const EaxCall& call);
bool eax_fx_slot_should_update_sources() const noexcept;
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax4_fx_slot_set(const EaxCall& call);
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax5_fx_slot_set(const EaxCall& call);
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_fx_slot_set(const EaxCall& call);
// Returns `true` if all sources should be updated, or `false` otherwise.
bool eax_set(const EaxCall& call);
template<
EaxDirtyFlags TDirtyBit,
typename TMemberResult,
typename TProps,
typename TState>
void eax_fx_slot_commit_property(TState& state, EaxDirtyFlags& dst_df,
TMemberResult TProps::*member) noexcept
{
auto& src_i = state.i;
auto& dst_i = eax_;
if((eax_df_ & TDirtyBit) != EaxDirtyFlags{})
{
dst_df |= TDirtyBit;
dst_i.*member = src_i.*member;
}
}
void eax4_fx_slot_commit(EaxDirtyFlags& dst_df);
void eax5_fx_slot_commit(Eax5State& state, EaxDirtyFlags& dst_df);
// `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_EFFECT, effect)`
void eax_set_efx_slot_effect(EaxEffect &effect);
// `alAuxiliaryEffectSloti(effect_slot, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, value)`
void eax_set_efx_slot_send_auto(bool is_send_auto);
// `alAuxiliaryEffectSlotf(effect_slot, AL_EFFECTSLOT_GAIN, gain)`
void eax_set_efx_slot_gain(ALfloat gain);
public:
class EaxDeleter {
public:
void operator()(ALeffectslot *effect_slot);
};
#endif // ALSOFT_EAX
};
void UpdateAllEffectSlotProps(ALCcontext *context);
#ifdef ALSOFT_EAX
using EaxAlEffectSlotUPtr = std::unique_ptr<ALeffectslot, ALeffectslot::EaxDeleter>;
EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context);
void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot);
#endif // ALSOFT_EAX
#endif

1692
externals/openal-soft/al/buffer.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

58
externals/openal-soft/al/buffer.h vendored Normal file
View File

@@ -0,0 +1,58 @@
#ifndef AL_BUFFER_H
#define AL_BUFFER_H
#include <atomic>
#include "AL/al.h"
#include "albyte.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "atomic.h"
#include "core/buffer_storage.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include "eax/x_ram.h"
enum class EaxStorage : uint8_t {
Automatic,
Accessible,
Hardware
};
#endif // ALSOFT_EAX
struct ALbuffer : public BufferStorage {
ALbitfieldSOFT Access{0u};
al::vector<al::byte,16> mDataStorage;
ALuint OriginalSize{0};
ALuint UnpackAlign{0};
ALuint PackAlign{0};
ALuint UnpackAmbiOrder{1};
ALbitfieldSOFT MappedAccess{0u};
ALsizei MappedOffset{0};
ALsizei MappedSize{0};
ALuint mLoopStart{0u};
ALuint mLoopEnd{0u};
/* Number of times buffer was attached to a source (deletion can only occur when 0) */
RefCount ref{0u};
/* Self ID */
ALuint id{0};
DISABLE_ALLOC()
#ifdef ALSOFT_EAX
EaxStorage eax_x_ram_mode{EaxStorage::Automatic};
bool eax_x_ram_is_hardware{};
#endif // ALSOFT_EAX
};
#endif

1621
externals/openal-soft/al/eax/api.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

1493
externals/openal-soft/al/eax/api.h vendored Normal file

File diff suppressed because it is too large Load Diff

219
externals/openal-soft/al/eax/call.cpp vendored Normal file
View File

@@ -0,0 +1,219 @@
#include "config.h"
#include "call.h"
#include "exception.h"
namespace {
constexpr auto deferred_flag = 0x80000000U;
class EaxCallException : public EaxException {
public:
explicit EaxCallException(const char* message)
: EaxException{"EAX_CALL", message}
{}
}; // EaxCallException
} // namespace
EaxCall::EaxCall(
EaxCallType type,
const GUID& property_set_guid,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size)
: mCallType{type}, mVersion{0}, mPropertySetId{EaxCallPropertySetId::none}
, mIsDeferred{(property_id & deferred_flag) != 0}
, mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id}
, mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size}
{
switch(mCallType)
{
case EaxCallType::get:
case EaxCallType::set:
break;
default:
fail("Invalid type.");
}
if (false)
{
}
else if (property_set_guid == EAXPROPERTYID_EAX40_Context)
{
mVersion = 4;
mPropertySetId = EaxCallPropertySetId::context;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_Context)
{
mVersion = 5;
mPropertySetId = EaxCallPropertySetId::context;
}
else if (property_set_guid == DSPROPSETID_EAX20_ListenerProperties)
{
mVersion = 2;
mFxSlotIndex = 0u;
mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
}
else if (property_set_guid == DSPROPSETID_EAX30_ListenerProperties)
{
mVersion = 3;
mFxSlotIndex = 0u;
mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot0)
{
mVersion = 4;
mFxSlotIndex = 0u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot0)
{
mVersion = 5;
mFxSlotIndex = 0u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot1)
{
mVersion = 4;
mFxSlotIndex = 1u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot1)
{
mVersion = 5;
mFxSlotIndex = 1u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot2)
{
mVersion = 4;
mFxSlotIndex = 2u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot2)
{
mVersion = 5;
mFxSlotIndex = 2u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_FXSlot3)
{
mVersion = 4;
mFxSlotIndex = 3u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_FXSlot3)
{
mVersion = 5;
mFxSlotIndex = 3u;
mPropertySetId = EaxCallPropertySetId::fx_slot;
}
else if (property_set_guid == DSPROPSETID_EAX20_BufferProperties)
{
mVersion = 2;
mPropertySetId = EaxCallPropertySetId::source;
}
else if (property_set_guid == DSPROPSETID_EAX30_BufferProperties)
{
mVersion = 3;
mPropertySetId = EaxCallPropertySetId::source;
}
else if (property_set_guid == EAXPROPERTYID_EAX40_Source)
{
mVersion = 4;
mPropertySetId = EaxCallPropertySetId::source;
}
else if (property_set_guid == EAXPROPERTYID_EAX50_Source)
{
mVersion = 5;
mPropertySetId = EaxCallPropertySetId::source;
}
else if (property_set_guid == DSPROPSETID_EAX_ReverbProperties)
{
mVersion = 1;
mFxSlotIndex = 0u;
mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
}
else if (property_set_guid == DSPROPSETID_EAXBUFFER_ReverbProperties)
{
mVersion = 1;
mPropertySetId = EaxCallPropertySetId::source;
}
else
{
fail("Unsupported property set id.");
}
switch(mPropertyId)
{
case EAXCONTEXT_LASTERROR:
case EAXCONTEXT_SPEAKERCONFIG:
case EAXCONTEXT_EAXSESSION:
case EAXFXSLOT_NONE:
case EAXFXSLOT_ALLPARAMETERS:
case EAXFXSLOT_LOADEFFECT:
case EAXFXSLOT_VOLUME:
case EAXFXSLOT_LOCK:
case EAXFXSLOT_FLAGS:
case EAXFXSLOT_OCCLUSION:
case EAXFXSLOT_OCCLUSIONLFRATIO:
// EAX allow to set "defer" flag on immediate-only properties.
// If we don't clear our flag then "applyAllUpdates" in EAX context won't be called.
mIsDeferred = false;
break;
}
if(!mIsDeferred)
{
if(mPropertySetId != EaxCallPropertySetId::fx_slot && mPropertyId != 0)
{
if(mPropertyBuffer == nullptr)
fail("Null property buffer.");
if(mPropertyBufferSize == 0)
fail("Empty property.");
}
}
if(mPropertySetId == EaxCallPropertySetId::source && mPropertySourceId == 0)
fail("Null AL source id.");
if(mPropertySetId == EaxCallPropertySetId::fx_slot)
{
if(mPropertyId < EAXFXSLOT_NONE)
mPropertySetId = EaxCallPropertySetId::fx_slot_effect;
}
}
[[noreturn]] void EaxCall::fail(const char* message)
{
throw EaxCallException{message};
}
[[noreturn]] void EaxCall::fail_too_small()
{
fail("Property buffer too small.");
}
EaxCall create_eax_call(
EaxCallType type,
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size)
{
if(!property_set_id)
throw EaxCallException{"Null property set ID."};
return EaxCall{
type,
*property_set_id,
property_id,
property_source_id,
property_buffer,
property_size
};
}

97
externals/openal-soft/al/eax/call.h vendored Normal file
View File

@@ -0,0 +1,97 @@
#ifndef EAX_EAX_CALL_INCLUDED
#define EAX_EAX_CALL_INCLUDED
#include "AL/al.h"
#include "alnumeric.h"
#include "alspan.h"
#include "api.h"
#include "fx_slot_index.h"
enum class EaxCallType {
none,
get,
set,
}; // EaxCallType
enum class EaxCallPropertySetId {
none,
context,
fx_slot,
source,
fx_slot_effect,
}; // EaxCallPropertySetId
class EaxCall {
public:
EaxCall(
EaxCallType type,
const GUID& property_set_guid,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size);
bool is_get() const noexcept { return mCallType == EaxCallType::get; }
bool is_deferred() const noexcept { return mIsDeferred; }
int get_version() const noexcept { return mVersion; }
EaxCallPropertySetId get_property_set_id() const noexcept { return mPropertySetId; }
ALuint get_property_id() const noexcept { return mPropertyId; }
ALuint get_property_al_name() const noexcept { return mPropertySourceId; }
EaxFxSlotIndex get_fx_slot_index() const noexcept { return mFxSlotIndex; }
template<typename TException, typename TValue>
TValue& get_value() const
{
if(mPropertyBufferSize < sizeof(TValue))
fail_too_small();
return *static_cast<TValue*>(mPropertyBuffer);
}
template<typename TValue>
al::span<TValue> get_values(size_t max_count) const
{
if(max_count == 0 || mPropertyBufferSize < sizeof(TValue))
fail_too_small();
const auto count = minz(mPropertyBufferSize / sizeof(TValue), max_count);
return al::as_span(static_cast<TValue*>(mPropertyBuffer), count);
}
template<typename TValue>
al::span<TValue> get_values() const
{
return get_values<TValue>(~size_t{});
}
template<typename TException, typename TValue>
void set_value(const TValue& value) const
{
get_value<TException, TValue>() = value;
}
private:
const EaxCallType mCallType;
int mVersion;
EaxFxSlotIndex mFxSlotIndex;
EaxCallPropertySetId mPropertySetId;
bool mIsDeferred;
const ALuint mPropertyId;
const ALuint mPropertySourceId;
ALvoid*const mPropertyBuffer;
const ALuint mPropertyBufferSize;
[[noreturn]] static void fail(const char* message);
[[noreturn]] static void fail_too_small();
}; // EaxCall
EaxCall create_eax_call(
EaxCallType type,
const GUID* property_set_id,
ALuint property_id,
ALuint property_source_id,
ALvoid* property_buffer,
ALuint property_size);
#endif // !EAX_EAX_CALL_INCLUDED

418
externals/openal-soft/al/eax/effect.h vendored Normal file
View File

@@ -0,0 +1,418 @@
#ifndef EAX_EFFECT_INCLUDED
#define EAX_EFFECT_INCLUDED
#include <cassert>
#include <memory>
#include "alnumeric.h"
#include "AL/al.h"
#include "core/effects/base.h"
#include "call.h"
struct EaxEffectErrorMessages
{
static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; }
static constexpr auto unknown_version() noexcept { return "Unknown version."; }
}; // EaxEffectErrorMessages
/* TODO: Use std::variant (C++17). */
enum class EaxEffectType {
None, Reverb, Chorus, Autowah, Compressor, Distortion, Echo, Equalizer, Flanger,
FrequencyShifter, Modulator, PitchShifter, VocalMorpher
};
struct EaxEffectProps {
EaxEffectType mType;
union {
EAXREVERBPROPERTIES mReverb;
EAXCHORUSPROPERTIES mChorus;
EAXAUTOWAHPROPERTIES mAutowah;
EAXAGCCOMPRESSORPROPERTIES mCompressor;
EAXDISTORTIONPROPERTIES mDistortion;
EAXECHOPROPERTIES mEcho;
EAXEQUALIZERPROPERTIES mEqualizer;
EAXFLANGERPROPERTIES mFlanger;
EAXFREQUENCYSHIFTERPROPERTIES mFrequencyShifter;
EAXRINGMODULATORPROPERTIES mModulator;
EAXPITCHSHIFTERPROPERTIES mPitchShifter;
EAXVOCALMORPHERPROPERTIES mVocalMorpher;
};
};
constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props)
{
switch(props.mType)
{
case EaxEffectType::None: break;
case EaxEffectType::Reverb: return AL_EFFECT_EAXREVERB;
case EaxEffectType::Chorus: return AL_EFFECT_CHORUS;
case EaxEffectType::Autowah: return AL_EFFECT_AUTOWAH;
case EaxEffectType::Compressor: return AL_EFFECT_COMPRESSOR;
case EaxEffectType::Distortion: return AL_EFFECT_DISTORTION;
case EaxEffectType::Echo: return AL_EFFECT_ECHO;
case EaxEffectType::Equalizer: return AL_EFFECT_EQUALIZER;
case EaxEffectType::Flanger: return AL_EFFECT_FLANGER;
case EaxEffectType::FrequencyShifter: return AL_EFFECT_FREQUENCY_SHIFTER;
case EaxEffectType::Modulator: return AL_EFFECT_RING_MODULATOR;
case EaxEffectType::PitchShifter: return AL_EFFECT_PITCH_SHIFTER;
case EaxEffectType::VocalMorpher: return AL_EFFECT_VOCAL_MORPHER;
}
return AL_EFFECT_NULL;
}
struct EaxReverbCommitter {
struct Exception;
EaxReverbCommitter(EaxEffectProps &eaxprops, EffectProps &alprops)
: mEaxProps{eaxprops}, mAlProps{alprops}
{ }
EaxEffectProps &mEaxProps;
EffectProps &mAlProps;
[[noreturn]] static void fail(const char* message);
[[noreturn]] static void fail_unknown_property_id()
{ fail(EaxEffectErrorMessages::unknown_property_id()); }
template<typename TValidator, typename TProperty>
static void defer(const EaxCall& call, TProperty& property)
{
const auto& value = call.get_value<Exception, const TProperty>();
TValidator{}(value);
property = value;
}
template<typename TValidator, typename TDeferrer, typename TProperties, typename TProperty>
static void defer(const EaxCall& call, TProperties& properties, TProperty&)
{
const auto& value = call.get_value<Exception, const TProperty>();
TValidator{}(value);
TDeferrer{}(properties, value);
}
template<typename TValidator, typename TProperty>
static void defer3(const EaxCall& call, EAXREVERBPROPERTIES& properties, TProperty& property)
{
const auto& value = call.get_value<Exception, const TProperty>();
TValidator{}(value);
if (value == property)
return;
property = value;
properties.ulEnvironment = EAX_ENVIRONMENT_UNDEFINED;
}
bool commit(const EAX_REVERBPROPERTIES &props);
bool commit(const EAX20LISTENERPROPERTIES &props);
bool commit(const EAXREVERBPROPERTIES &props);
bool commit(const EaxEffectProps &props);
static void SetDefaults(EAX_REVERBPROPERTIES &props);
static void SetDefaults(EAX20LISTENERPROPERTIES &props);
static void SetDefaults(EAXREVERBPROPERTIES &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props);
static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props);
static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props);
static void Get(const EaxCall &call, const EaxEffectProps &props);
static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props);
static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props);
static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props);
static void Set(const EaxCall &call, EaxEffectProps &props);
static void translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept;
static void translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept;
static void translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept;
};
template<typename T>
struct EaxCommitter {
struct Exception;
EaxCommitter(EaxEffectProps &eaxprops, EffectProps &alprops)
: mEaxProps{eaxprops}, mAlProps{alprops}
{ }
EaxEffectProps &mEaxProps;
EffectProps &mAlProps;
template<typename TValidator, typename TProperty>
static void defer(const EaxCall& call, TProperty& property)
{
const auto& value = call.get_value<Exception, const TProperty>();
TValidator{}(value);
property = value;
}
[[noreturn]] static void fail(const char *message);
[[noreturn]] static void fail_unknown_property_id()
{ fail(EaxEffectErrorMessages::unknown_property_id()); }
bool commit(const EaxEffectProps &props);
static void SetDefaults(EaxEffectProps &props);
static void Get(const EaxCall &call, const EaxEffectProps &props);
static void Set(const EaxCall &call, EaxEffectProps &props);
};
struct EaxAutowahCommitter : public EaxCommitter<EaxAutowahCommitter> {
using EaxCommitter<EaxAutowahCommitter>::EaxCommitter;
};
struct EaxChorusCommitter : public EaxCommitter<EaxChorusCommitter> {
using EaxCommitter<EaxChorusCommitter>::EaxCommitter;
};
struct EaxCompressorCommitter : public EaxCommitter<EaxCompressorCommitter> {
using EaxCommitter<EaxCompressorCommitter>::EaxCommitter;
};
struct EaxDistortionCommitter : public EaxCommitter<EaxDistortionCommitter> {
using EaxCommitter<EaxDistortionCommitter>::EaxCommitter;
};
struct EaxEchoCommitter : public EaxCommitter<EaxEchoCommitter> {
using EaxCommitter<EaxEchoCommitter>::EaxCommitter;
};
struct EaxEqualizerCommitter : public EaxCommitter<EaxEqualizerCommitter> {
using EaxCommitter<EaxEqualizerCommitter>::EaxCommitter;
};
struct EaxFlangerCommitter : public EaxCommitter<EaxFlangerCommitter> {
using EaxCommitter<EaxFlangerCommitter>::EaxCommitter;
};
struct EaxFrequencyShifterCommitter : public EaxCommitter<EaxFrequencyShifterCommitter> {
using EaxCommitter<EaxFrequencyShifterCommitter>::EaxCommitter;
};
struct EaxModulatorCommitter : public EaxCommitter<EaxModulatorCommitter> {
using EaxCommitter<EaxModulatorCommitter>::EaxCommitter;
};
struct EaxPitchShifterCommitter : public EaxCommitter<EaxPitchShifterCommitter> {
using EaxCommitter<EaxPitchShifterCommitter>::EaxCommitter;
};
struct EaxVocalMorpherCommitter : public EaxCommitter<EaxVocalMorpherCommitter> {
using EaxCommitter<EaxVocalMorpherCommitter>::EaxCommitter;
};
struct EaxNullCommitter : public EaxCommitter<EaxNullCommitter> {
using EaxCommitter<EaxNullCommitter>::EaxCommitter;
};
class EaxEffect {
public:
EaxEffect() noexcept = default;
~EaxEffect() = default;
ALenum al_effect_type_{AL_EFFECT_NULL};
EffectProps al_effect_props_{};
using Props1 = EAX_REVERBPROPERTIES;
using Props2 = EAX20LISTENERPROPERTIES;
using Props3 = EAXREVERBPROPERTIES;
using Props4 = EaxEffectProps;
struct State1 {
Props1 i; // Immediate.
Props1 d; // Deferred.
};
struct State2 {
Props2 i; // Immediate.
Props2 d; // Deferred.
};
struct State3 {
Props3 i; // Immediate.
Props3 d; // Deferred.
};
struct State4 {
Props4 i; // Immediate.
Props4 d; // Deferred.
};
int version_{};
bool changed_{};
Props4 props_{};
State1 state1_{};
State2 state2_{};
State3 state3_{};
State4 state4_{};
State4 state5_{};
template<typename T, typename ...Args>
void call_set_defaults(Args&& ...args)
{ return T::SetDefaults(std::forward<Args>(args)...); }
void call_set_defaults(const ALenum altype, EaxEffectProps &props)
{
if(altype == AL_EFFECT_EAXREVERB)
return call_set_defaults<EaxReverbCommitter>(props);
if(altype == AL_EFFECT_CHORUS)
return call_set_defaults<EaxChorusCommitter>(props);
if(altype == AL_EFFECT_AUTOWAH)
return call_set_defaults<EaxAutowahCommitter>(props);
if(altype == AL_EFFECT_COMPRESSOR)
return call_set_defaults<EaxCompressorCommitter>(props);
if(altype == AL_EFFECT_DISTORTION)
return call_set_defaults<EaxDistortionCommitter>(props);
if(altype == AL_EFFECT_ECHO)
return call_set_defaults<EaxEchoCommitter>(props);
if(altype == AL_EFFECT_EQUALIZER)
return call_set_defaults<EaxEqualizerCommitter>(props);
if(altype == AL_EFFECT_FLANGER)
return call_set_defaults<EaxFlangerCommitter>(props);
if(altype == AL_EFFECT_FREQUENCY_SHIFTER)
return call_set_defaults<EaxFrequencyShifterCommitter>(props);
if(altype == AL_EFFECT_RING_MODULATOR)
return call_set_defaults<EaxModulatorCommitter>(props);
if(altype == AL_EFFECT_PITCH_SHIFTER)
return call_set_defaults<EaxPitchShifterCommitter>(props);
if(altype == AL_EFFECT_VOCAL_MORPHER)
return call_set_defaults<EaxVocalMorpherCommitter>(props);
return call_set_defaults<EaxNullCommitter>(props);
}
template<typename T>
void init()
{
call_set_defaults<EaxReverbCommitter>(state1_.d);
state1_.i = state1_.d;
call_set_defaults<EaxReverbCommitter>(state2_.d);
state2_.i = state2_.d;
call_set_defaults<EaxReverbCommitter>(state3_.d);
state3_.i = state3_.d;
call_set_defaults<T>(state4_.d);
state4_.i = state4_.d;
call_set_defaults<T>(state5_.d);
state5_.i = state5_.d;
}
void set_defaults(int eax_version, ALenum altype)
{
switch(eax_version)
{
case 1: call_set_defaults<EaxReverbCommitter>(state1_.d); break;
case 2: call_set_defaults<EaxReverbCommitter>(state2_.d); break;
case 3: call_set_defaults<EaxReverbCommitter>(state3_.d); break;
case 4: call_set_defaults(altype, state4_.d); break;
case 5: call_set_defaults(altype, state5_.d); break;
}
changed_ = true;
}
#define EAXCALL(T, Callable, ...) \
if(T == EaxEffectType::Reverb) \
return Callable<EaxReverbCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Chorus) \
return Callable<EaxChorusCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Autowah) \
return Callable<EaxAutowahCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Compressor) \
return Callable<EaxCompressorCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Distortion) \
return Callable<EaxDistortionCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Echo) \
return Callable<EaxEchoCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Equalizer) \
return Callable<EaxEqualizerCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Flanger) \
return Callable<EaxFlangerCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::FrequencyShifter) \
return Callable<EaxFrequencyShifterCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Modulator) \
return Callable<EaxModulatorCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::PitchShifter) \
return Callable<EaxPitchShifterCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::VocalMorpher) \
return Callable<EaxVocalMorpherCommitter>(__VA_ARGS__); \
return Callable<EaxNullCommitter>(__VA_ARGS__)
template<typename T, typename ...Args>
static void call_set(Args&& ...args)
{ return T::Set(std::forward<Args>(args)...); }
static void call_set(const EaxCall &call, EaxEffectProps &props)
{ EAXCALL(props.mType, call_set, call, props); }
void set(const EaxCall &call)
{
switch(call.get_version())
{
case 1: call_set<EaxReverbCommitter>(call, state1_.d); break;
case 2: call_set<EaxReverbCommitter>(call, state2_.d); break;
case 3: call_set<EaxReverbCommitter>(call, state3_.d); break;
case 4: call_set(call, state4_.d); break;
case 5: call_set(call, state5_.d); break;
}
changed_ = true;
}
template<typename T, typename ...Args>
static void call_get(Args&& ...args)
{ return T::Get(std::forward<Args>(args)...); }
static void call_get(const EaxCall &call, const EaxEffectProps &props)
{ EAXCALL(props.mType, call_get, call, props); }
void get(const EaxCall &call)
{
switch(call.get_version())
{
case 1: call_get<EaxReverbCommitter>(call, state1_.d); break;
case 2: call_get<EaxReverbCommitter>(call, state2_.d); break;
case 3: call_get<EaxReverbCommitter>(call, state3_.d); break;
case 4: call_get(call, state4_.d); break;
case 5: call_get(call, state5_.d); break;
}
}
template<typename T, typename ...Args>
bool call_commit(Args&& ...args)
{ return T{props_, al_effect_props_}.commit(std::forward<Args>(args)...); }
bool call_commit(const EaxEffectProps &props)
{ EAXCALL(props.mType, call_commit, props); }
bool commit(int eax_version)
{
changed_ |= version_ != eax_version;
if(!changed_) return false;
bool ret{version_ != eax_version};
version_ = eax_version;
changed_ = false;
switch(eax_version)
{
case 1:
state1_.i = state1_.d;
ret |= call_commit<EaxReverbCommitter>(state1_.d);
break;
case 2:
state2_.i = state2_.d;
ret |= call_commit<EaxReverbCommitter>(state2_.d);
break;
case 3:
state3_.i = state3_.d;
ret |= call_commit<EaxReverbCommitter>(state3_.d);
break;
case 4:
state4_.i = state4_.d;
ret |= call_commit(state4_.d);
break;
case 5:
state5_.i = state5_.d;
ret |= call_commit(state5_.d);
break;
}
al_effect_type_ = EnumFromEaxEffectType(props_);
return ret;
}
#undef EAXCALL
}; // EaxEffect
using EaxEffectUPtr = std::unique_ptr<EaxEffect>;
#endif // !EAX_EFFECT_INCLUDED

View File

@@ -0,0 +1,59 @@
#include "config.h"
#include "exception.h"
#include <cassert>
#include <string>
EaxException::EaxException(const char *context, const char *message)
: std::runtime_error{make_message(context, message)}
{
}
EaxException::~EaxException() = default;
std::string EaxException::make_message(const char *context, const char *message)
{
const auto context_size = (context ? std::string::traits_type::length(context) : 0);
const auto has_contex = (context_size > 0);
const auto message_size = (message ? std::string::traits_type::length(message) : 0);
const auto has_message = (message_size > 0);
if (!has_contex && !has_message)
{
return std::string{};
}
static constexpr char left_prefix[] = "[";
const auto left_prefix_size = std::string::traits_type::length(left_prefix);
static constexpr char right_prefix[] = "] ";
const auto right_prefix_size = std::string::traits_type::length(right_prefix);
const auto what_size =
(
has_contex ?
left_prefix_size + context_size + right_prefix_size :
0) +
message_size +
1;
auto what = std::string{};
what.reserve(what_size);
if (has_contex)
{
what.append(left_prefix, left_prefix_size);
what.append(context, context_size);
what.append(right_prefix, right_prefix_size);
}
if (has_message)
{
what.append(message, message_size);
}
return what;
}

View File

@@ -0,0 +1,18 @@
#ifndef EAX_EXCEPTION_INCLUDED
#define EAX_EXCEPTION_INCLUDED
#include <stdexcept>
#include <string>
class EaxException : public std::runtime_error {
static std::string make_message(const char *context, const char *message);
public:
EaxException(const char *context, const char *message);
~EaxException() override;
}; // EaxException
#endif // !EAX_EXCEPTION_INCLUDED

View File

@@ -0,0 +1,71 @@
#include "config.h"
#include "fx_slot_index.h"
#include "exception.h"
namespace
{
class EaxFxSlotIndexException :
public EaxException
{
public:
explicit EaxFxSlotIndexException(
const char* message)
:
EaxException{"EAX_FX_SLOT_INDEX", message}
{
}
}; // EaxFxSlotIndexException
} // namespace
void EaxFxSlotIndex::set(EaxFxSlotIndexValue index)
{
if(index >= EaxFxSlotIndexValue{EAX_MAX_FXSLOTS})
fail("Index out of range.");
emplace(index);
}
void EaxFxSlotIndex::set(const GUID &guid)
{
if (false)
{
}
else if (guid == EAX_NULL_GUID)
{
reset();
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot0 || guid == EAXPROPERTYID_EAX50_FXSlot0)
{
emplace(0u);
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot1 || guid == EAXPROPERTYID_EAX50_FXSlot1)
{
emplace(1u);
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot2 || guid == EAXPROPERTYID_EAX50_FXSlot2)
{
emplace(2u);
}
else if (guid == EAXPROPERTYID_EAX40_FXSlot3 || guid == EAXPROPERTYID_EAX50_FXSlot3)
{
emplace(3u);
}
else
{
fail("Unsupported GUID.");
}
}
[[noreturn]]
void EaxFxSlotIndex::fail(const char* message)
{
throw EaxFxSlotIndexException{message};
}

View File

@@ -0,0 +1,41 @@
#ifndef EAX_FX_SLOT_INDEX_INCLUDED
#define EAX_FX_SLOT_INDEX_INCLUDED
#include <cstddef>
#include "aloptional.h"
#include "api.h"
using EaxFxSlotIndexValue = std::size_t;
class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue>
{
public:
using al::optional<EaxFxSlotIndexValue>::optional;
EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }
void set(EaxFxSlotIndexValue index);
void set(const GUID& guid);
private:
[[noreturn]]
static void fail(const char *message);
}; // EaxFxSlotIndex
inline bool operator==(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept
{
if(lhs.has_value() != rhs.has_value())
return false;
if(lhs.has_value())
return *lhs == *rhs;
return true;
}
inline bool operator!=(const EaxFxSlotIndex& lhs, const EaxFxSlotIndex& rhs) noexcept
{ return !(lhs == rhs); }
#endif // !EAX_FX_SLOT_INDEX_INCLUDED

View File

@@ -0,0 +1,75 @@
#include "config.h"
#include "fx_slots.h"
#include <array>
#include "api.h"
#include "exception.h"
namespace
{
class EaxFxSlotsException :
public EaxException
{
public:
explicit EaxFxSlotsException(
const char* message)
:
EaxException{"EAX_FX_SLOTS", message}
{
}
}; // EaxFxSlotsException
} // namespace
void EaxFxSlots::initialize(ALCcontext& al_context)
{
initialize_fx_slots(al_context);
}
void EaxFxSlots::uninitialize() noexcept
{
for (auto& fx_slot : fx_slots_)
{
fx_slot = nullptr;
}
}
const ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index) const
{
if(!index.has_value())
fail("Empty index.");
return *fx_slots_[index.value()];
}
ALeffectslot& EaxFxSlots::get(EaxFxSlotIndex index)
{
if(!index.has_value())
fail("Empty index.");
return *fx_slots_[index.value()];
}
[[noreturn]]
void EaxFxSlots::fail(
const char* message)
{
throw EaxFxSlotsException{message};
}
void EaxFxSlots::initialize_fx_slots(ALCcontext& al_context)
{
auto fx_slot_index = EaxFxSlotIndexValue{};
for (auto& fx_slot : fx_slots_)
{
fx_slot = eax_create_al_effect_slot(al_context);
fx_slot->eax_initialize(al_context, fx_slot_index);
fx_slot_index += 1;
}
}

49
externals/openal-soft/al/eax/fx_slots.h vendored Normal file
View File

@@ -0,0 +1,49 @@
#ifndef EAX_FX_SLOTS_INCLUDED
#define EAX_FX_SLOTS_INCLUDED
#include <array>
#include "al/auxeffectslot.h"
#include "api.h"
#include "call.h"
#include "fx_slot_index.h"
class EaxFxSlots
{
public:
void initialize(ALCcontext& al_context);
void uninitialize() noexcept;
void commit()
{
for(auto& fx_slot : fx_slots_)
fx_slot->eax_commit();
}
const ALeffectslot& get(
EaxFxSlotIndex index) const;
ALeffectslot& get(
EaxFxSlotIndex index);
private:
using Items = std::array<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
Items fx_slots_{};
[[noreturn]]
static void fail(
const char* message);
void initialize_fx_slots(ALCcontext& al_context);
}; // EaxFxSlots
#endif // !EAX_FX_SLOTS_INCLUDED

View File

@@ -0,0 +1,21 @@
#include "config.h"
#include "globals.h"
bool eax_g_is_enabled = true;
const char eax1_ext_name[] = "EAX";
const char eax2_ext_name[] = "EAX2.0";
const char eax3_ext_name[] = "EAX3.0";
const char eax4_ext_name[] = "EAX4.0";
const char eax5_ext_name[] = "EAX5.0";
const char eax_x_ram_ext_name[] = "EAX-RAM";
const char eax_eax_set_func_name[] = "EAXSet";
const char eax_eax_get_func_name[] = "EAXGet";
const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode";
const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode";

22
externals/openal-soft/al/eax/globals.h vendored Normal file
View File

@@ -0,0 +1,22 @@
#ifndef EAX_GLOBALS_INCLUDED
#define EAX_GLOBALS_INCLUDED
extern bool eax_g_is_enabled;
extern const char eax1_ext_name[];
extern const char eax2_ext_name[];
extern const char eax3_ext_name[];
extern const char eax4_ext_name[];
extern const char eax5_ext_name[];
extern const char eax_x_ram_ext_name[];
extern const char eax_eax_set_func_name[];
extern const char eax_eax_get_func_name[];
extern const char eax_eax_set_buffer_mode_func_name[];
extern const char eax_eax_get_buffer_mode_func_name[];
#endif // !EAX_GLOBALS_INCLUDED

26
externals/openal-soft/al/eax/utils.cpp vendored Normal file
View File

@@ -0,0 +1,26 @@
#include "config.h"
#include "utils.h"
#include <cassert>
#include <exception>
#include "core/logging.h"
void eax_log_exception(const char *message) noexcept
{
const auto exception_ptr = std::current_exception();
assert(exception_ptr);
try {
std::rethrow_exception(exception_ptr);
}
catch(const std::exception& ex) {
const auto ex_message = ex.what();
ERR("%s %s\n", message ? message : "", ex_message);
}
catch(...) {
ERR("%s %s\n", message ? message : "", "Generic exception.");
}
}

95
externals/openal-soft/al/eax/utils.h vendored Normal file
View File

@@ -0,0 +1,95 @@
#ifndef EAX_UTILS_INCLUDED
#define EAX_UTILS_INCLUDED
#include <algorithm>
#include <cstdint>
#include <string>
#include <type_traits>
using EaxDirtyFlags = unsigned int;
struct EaxAlLowPassParam {
float gain;
float gain_hf;
};
void eax_log_exception(const char *message) noexcept;
template<typename TException, typename TValue>
void eax_validate_range(
const char* value_name,
const TValue& value,
const TValue& min_value,
const TValue& max_value)
{
if (value >= min_value && value <= max_value)
return;
const auto message =
std::string{value_name} +
" out of range (value: " +
std::to_string(value) + "; min: " +
std::to_string(min_value) + "; max: " +
std::to_string(max_value) + ").";
throw TException{message.c_str()};
}
namespace detail {
template<typename T>
struct EaxIsBitFieldStruct {
private:
using yes = std::true_type;
using no = std::false_type;
template<typename U>
static auto test(int) -> decltype(std::declval<typename U::EaxIsBitFieldStruct>(), yes{});
template<typename>
static no test(...);
public:
static constexpr auto value = std::is_same<decltype(test<T>(0)), yes>::value;
};
template<typename T, typename TValue>
inline bool eax_bit_fields_are_equal(const T& lhs, const T& rhs) noexcept
{
static_assert(sizeof(T) == sizeof(TValue), "Invalid type size.");
return reinterpret_cast<const TValue&>(lhs) == reinterpret_cast<const TValue&>(rhs);
}
} // namespace detail
template<
typename T,
std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
>
inline bool operator==(const T& lhs, const T& rhs) noexcept
{
using Value = std::conditional_t<
sizeof(T) == 1,
std::uint8_t,
std::conditional_t<
sizeof(T) == 2,
std::uint16_t,
std::conditional_t<
sizeof(T) == 4,
std::uint32_t,
void>>>;
static_assert(!std::is_same<Value, void>::value, "Unsupported type.");
return detail::eax_bit_fields_are_equal<T, Value>(lhs, rhs);
}
template<
typename T,
std::enable_if_t<detail::EaxIsBitFieldStruct<T>::value, int> = 0
>
inline bool operator!=(const T& lhs, const T& rhs) noexcept
{
return !(lhs == rhs);
}
#endif // !EAX_UTILS_INCLUDED

38
externals/openal-soft/al/eax/x_ram.h vendored Normal file
View File

@@ -0,0 +1,38 @@
#ifndef EAX_X_RAM_INCLUDED
#define EAX_X_RAM_INCLUDED
#include "AL/al.h"
constexpr auto eax_x_ram_min_size = ALsizei{};
constexpr auto eax_x_ram_max_size = ALsizei{64 * 1'024 * 1'024};
constexpr auto AL_EAX_RAM_SIZE = ALenum{0x202201};
constexpr auto AL_EAX_RAM_FREE = ALenum{0x202202};
constexpr auto AL_STORAGE_AUTOMATIC = ALenum{0x202203};
constexpr auto AL_STORAGE_HARDWARE = ALenum{0x202204};
constexpr auto AL_STORAGE_ACCESSIBLE = ALenum{0x202205};
constexpr auto AL_EAX_RAM_SIZE_NAME = "AL_EAX_RAM_SIZE";
constexpr auto AL_EAX_RAM_FREE_NAME = "AL_EAX_RAM_FREE";
constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC";
constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE";
constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE";
ALboolean AL_APIENTRY EAXSetBufferMode(
ALsizei n,
const ALuint* buffers,
ALint value);
ALenum AL_APIENTRY EAXGetBufferMode(
ALuint buffer,
ALint* pReserved);
#endif // !EAX_X_RAM_INCLUDED

766
externals/openal-soft/al/effect.cpp vendored Normal file
View File

@@ -0,0 +1,766 @@
/**
* 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 "albit.h"
#include "alc/context.h"
#include "alc/device.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alstring.h"
#include "core/except.h"
#include "core/logging.h"
#include "opthelpers.h"
#include "vector.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "eax/exception.h"
#endif // ALSOFT_EAX
const EffectList gEffectList[16]{
{ "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 },
{ "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT },
};
bool DisabledEffects[MAX_EFFECTS];
effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
effect_exception::~effect_exception() = default;
namespace {
struct EffectPropsItem {
ALenum Type;
const EffectProps &DefaultProps;
const EffectVtable &Vtable;
};
constexpr EffectPropsItem EffectPropsList[] = {
{ AL_EFFECT_NULL, NullEffectProps, NullEffectVtable },
{ AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable },
{ AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable },
{ AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable },
{ AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable },
{ AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable },
{ AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable },
{ AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable },
{ AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable },
{ AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable },
{ AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable },
{ AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable },
{ AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable },
{ AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable },
{ AL_EFFECT_DEDICATED_DIALOGUE, DedicatedEffectProps, DedicatedEffectVtable },
{ AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedEffectProps, DedicatedEffectVtable },
{ AL_EFFECT_CONVOLUTION_REVERB_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable },
};
void ALeffect_setParami(ALeffect *effect, ALenum param, int value)
{ effect->vtab->setParami(&effect->Props, param, value); }
void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values)
{ effect->vtab->setParamiv(&effect->Props, param, values); }
void ALeffect_setParamf(ALeffect *effect, ALenum param, float value)
{ effect->vtab->setParamf(&effect->Props, param, value); }
void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values)
{ effect->vtab->setParamfv(&effect->Props, param, values); }
void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value)
{ effect->vtab->getParami(&effect->Props, param, value); }
void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values)
{ effect->vtab->getParamiv(&effect->Props, param, values); }
void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value)
{ effect->vtab->getParamf(&effect->Props, param, value); }
void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values)
{ effect->vtab->getParamfv(&effect->Props, param, values); }
const EffectPropsItem *getEffectPropsItemByType(ALenum type)
{
auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList),
[type](const EffectPropsItem &item) noexcept -> bool
{ return item.Type == type; });
return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr;
}
void InitEffectParams(ALeffect *effect, ALenum type)
{
const EffectPropsItem *item{getEffectPropsItemByType(type)};
if(item)
{
effect->Props = item->DefaultProps;
effect->vtab = &item->Vtable;
}
else
{
effect->Props = EffectProps{};
effect->vtab = &NullEffectVtable;
}
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>(al::popcount(sublist.FreeMask)); })};
while(needed > count)
{
if(device->EffectList.size() >= 1<<25) UNLIKELY
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(!sublist->Effects) UNLIKELY
{
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>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64);
ALeffect *effect{al::construct_at(sublist->Effects + slidx)};
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(lidx >= device->EffectList.size()) UNLIKELY
return nullptr;
EffectSubList &sublist = device->EffectList[lidx];
if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
return nullptr;
return sublist.Effects + slidx;
}
} // namespace
AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.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(n == 1) LIKELY
{
/* 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 void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.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(inveffect != effects_end) UNLIKELY
{
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(context) LIKELY
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
}
return AL_FALSE;
}
END_API_FUNC
AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else if(param == AL_EFFECT_TYPE)
{
bool isOk{value == AL_EFFECT_NULL};
if(!isOk)
{
for(const EffectList &effectitem : gEffectList)
{
if(value == effectitem.val && !DisabledEffects[effectitem.type])
{
isOk = true;
break;
}
}
}
if(isOk)
InitEffectParams(aleffect, value);
else
context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value);
}
else try
{
/* Call the appropriate handler */
ALeffect_setParami(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void 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(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_setParamiv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_setParamf(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_setParamfv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else if(param == AL_EFFECT_TYPE)
*value = aleffect->type;
else try
{
/* Call the appropriate handler */
ALeffect_getParami(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void 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(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_getParamiv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_getParamf(aleffect, param, value);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->EffectLock};
const ALeffect *aleffect{LookupEffect(device, effect)};
if(!aleffect) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
else try
{
/* Call the appropriate handler */
ALeffect_getParamfv(aleffect, param, values);
}
catch(effect_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
void InitEffect(ALeffect *effect)
{
InitEffectParams(effect, AL_EFFECT_NULL);
}
EffectSubList::~EffectSubList()
{
uint64_t usemask{~FreeMask};
while(usemask)
{
const int idx{al::countr_zero(usemask)};
al::destroy_at(Effects+idx);
usemask &= ~(1_u64 << idx);
}
FreeMask = ~usemask;
al_free(Effects);
Effects = 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);
}
bool IsValidEffectType(ALenum type) noexcept
{
if(type == AL_EFFECT_NULL)
return true;
for(const auto &effect_item : gEffectList)
{
if(type == effect_item.val && !DisabledEffects[effect_item.type])
return true;
}
return false;
}

62
externals/openal-soft/al/effect.h vendored Normal file
View File

@@ -0,0 +1,62 @@
#ifndef AL_EFFECT_H
#define AL_EFFECT_H
#include "AL/al.h"
#include "AL/efx.h"
#include "al/effects/effects.h"
#include "alc/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,
CONVOLUTION_EFFECT,
MAX_EFFECTS
};
extern bool DisabledEffects[MAX_EFFECTS];
extern float ReverbBoost;
struct EffectList {
const char name[16];
int type;
ALenum val;
};
extern const EffectList gEffectList[16];
struct ALeffect {
// Effect type (AL_EFFECT_NULL, ...)
ALenum type{AL_EFFECT_NULL};
EffectProps Props{};
const EffectVtable *vtab{nullptr};
/* Self ID */
ALuint id{0u};
DISABLE_ALLOC()
};
void InitEffect(ALeffect *effect);
void LoadReverbPreset(const char *name, ALeffect *effect);
bool IsValidEffectType(ALenum type) noexcept;
#endif

View File

@@ -0,0 +1,252 @@
#include "config.h"
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
void Autowah_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_AUTOWAH_ATTACK_TIME:
if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"};
props->Autowah.PeakGain = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
}
}
void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Autowah_setParamf(props, param, vals[0]); }
void Autowah_setParami(EffectProps*, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void Autowah_setParamiv(EffectProps*, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
}
void Autowah_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param};
}
}
void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Autowah_getParamf(props, param, vals); }
void Autowah_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; }
void Autowah_getParamiv(const EffectProps*, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x",
param};
}
EffectProps genDefaultProps() 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
DEFINE_ALEFFECT_VTABLE(Autowah);
const EffectProps AutowahEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using AutowahCommitter = EaxCommitter<EaxAutowahCommitter>;
struct AttackTimeValidator {
void operator()(float flAttackTime) const
{
eax_validate_range<AutowahCommitter::Exception>(
"Attack Time",
flAttackTime,
EAXAUTOWAH_MINATTACKTIME,
EAXAUTOWAH_MAXATTACKTIME);
}
}; // AttackTimeValidator
struct ReleaseTimeValidator {
void operator()(float flReleaseTime) const
{
eax_validate_range<AutowahCommitter::Exception>(
"Release Time",
flReleaseTime,
EAXAUTOWAH_MINRELEASETIME,
EAXAUTOWAH_MAXRELEASETIME);
}
}; // ReleaseTimeValidator
struct ResonanceValidator {
void operator()(long lResonance) const
{
eax_validate_range<AutowahCommitter::Exception>(
"Resonance",
lResonance,
EAXAUTOWAH_MINRESONANCE,
EAXAUTOWAH_MAXRESONANCE);
}
}; // ResonanceValidator
struct PeakLevelValidator {
void operator()(long lPeakLevel) const
{
eax_validate_range<AutowahCommitter::Exception>(
"Peak Level",
lPeakLevel,
EAXAUTOWAH_MINPEAKLEVEL,
EAXAUTOWAH_MAXPEAKLEVEL);
}
}; // PeakLevelValidator
struct AllValidator {
void operator()(const EAXAUTOWAHPROPERTIES& all) const
{
AttackTimeValidator{}(all.flAttackTime);
ReleaseTimeValidator{}(all.flReleaseTime);
ResonanceValidator{}(all.lResonance);
PeakLevelValidator{}(all.lPeakLevel);
}
}; // AllValidator
} // namespace
template<>
struct AutowahCommitter::Exception : public EaxException
{
explicit Exception(const char *message) : EaxException{"EAX_AUTOWAH_EFFECT", message}
{ }
};
template<>
[[noreturn]] void AutowahCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool AutowahCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mAutowah.flAttackTime == props.mAutowah.flAttackTime
&& mEaxProps.mAutowah.flReleaseTime == props.mAutowah.flReleaseTime
&& mEaxProps.mAutowah.lResonance == props.mAutowah.lResonance
&& mEaxProps.mAutowah.lPeakLevel == props.mAutowah.lPeakLevel)
return false;
mEaxProps = props;
mAlProps.Autowah.AttackTime = props.mAutowah.flAttackTime;
mAlProps.Autowah.ReleaseTime = props.mAutowah.flReleaseTime;
mAlProps.Autowah.Resonance = level_mb_to_gain(static_cast<float>(props.mAutowah.lResonance));
mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast<float>(props.mAutowah.lPeakLevel));
return true;
}
template<>
void AutowahCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Autowah;
props.mAutowah.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME;
props.mAutowah.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME;
props.mAutowah.lResonance = EAXAUTOWAH_DEFAULTRESONANCE;
props.mAutowah.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL;
}
template<>
void AutowahCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXAUTOWAH_NONE: break;
case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props.mAutowah); break;
case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.mAutowah.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.mAutowah.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.mAutowah.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.mAutowah.lPeakLevel); break;
default: fail_unknown_property_id();
}
}
template<>
void AutowahCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXAUTOWAH_NONE: break;
case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props.mAutowah); break;
case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.mAutowah.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.mAutowah.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.mAutowah.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.mAutowah.lPeakLevel); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,724 @@
#include "config.h"
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "core/logging.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
static_assert(ChorusMaxDelay >= AL_CHORUS_MAX_DELAY, "Chorus max delay too small");
static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too small");
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");
inline al::optional<ChorusWaveform> WaveformFromEnum(ALenum type)
{
switch(type)
{
case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid;
case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle;
}
return al::nullopt;
}
inline ALenum EnumFromWaveform(ChorusWaveform type)
{
switch(type)
{
case ChorusWaveform::Sinusoid: return AL_CHORUS_WAVEFORM_SINUSOID;
case ChorusWaveform::Triangle: return AL_CHORUS_WAVEFORM_TRIANGLE;
}
throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast<int>(type))};
}
void Chorus_setParami(EffectProps *props, ALenum param, int val)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
if(auto formopt = WaveformFromEnum(val))
props->Chorus.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val};
break;
case AL_CHORUS_PHASE:
if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE))
throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val};
props->Chorus.Phase = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
}
}
void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Chorus_setParami(props, param, vals[0]); }
void Chorus_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_CHORUS_RATE:
if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val};
props->Chorus.Rate = val;
break;
case AL_CHORUS_DEPTH:
if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val};
props->Chorus.Depth = val;
break;
case AL_CHORUS_FEEDBACK:
if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val};
props->Chorus.Feedback = val;
break;
case AL_CHORUS_DELAY:
if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val};
props->Chorus.Delay = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
}
}
void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Chorus_setParamf(props, param, vals[0]); }
void Chorus_getParami(const EffectProps *props, ALenum param, int *val)
{
switch(param)
{
case AL_CHORUS_WAVEFORM:
*val = EnumFromWaveform(props->Chorus.Waveform);
break;
case AL_CHORUS_PHASE:
*val = props->Chorus.Phase;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param};
}
}
void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Chorus_getParami(props, param, vals); }
void Chorus_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param};
}
}
void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Chorus_getParamf(props, param, vals); }
EffectProps genDefaultChorusProps() noexcept
{
EffectProps props{};
props.Chorus.Waveform = *WaveformFromEnum(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, ALenum param, int val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
if(auto formopt = WaveformFromEnum(val))
props->Chorus.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val};
break;
case AL_FLANGER_PHASE:
if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE))
throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val};
props->Chorus.Phase = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
}
}
void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Flanger_setParami(props, param, vals[0]); }
void Flanger_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_FLANGER_RATE:
if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val};
props->Chorus.Rate = val;
break;
case AL_FLANGER_DEPTH:
if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH))
throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val};
props->Chorus.Depth = val;
break;
case AL_FLANGER_FEEDBACK:
if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK))
throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val};
props->Chorus.Feedback = val;
break;
case AL_FLANGER_DELAY:
if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY))
throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val};
props->Chorus.Delay = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
}
}
void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Flanger_setParamf(props, param, vals[0]); }
void Flanger_getParami(const EffectProps *props, ALenum param, int *val)
{
switch(param)
{
case AL_FLANGER_WAVEFORM:
*val = EnumFromWaveform(props->Chorus.Waveform);
break;
case AL_FLANGER_PHASE:
*val = props->Chorus.Phase;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param};
}
}
void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Flanger_getParami(props, param, vals); }
void Flanger_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param};
}
}
void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Flanger_getParamf(props, param, vals); }
EffectProps genDefaultFlangerProps() noexcept
{
EffectProps props{};
props.Chorus.Waveform = *WaveformFromEnum(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
DEFINE_ALEFFECT_VTABLE(Chorus);
const EffectProps ChorusEffectProps{genDefaultChorusProps()};
DEFINE_ALEFFECT_VTABLE(Flanger);
const EffectProps FlangerEffectProps{genDefaultFlangerProps()};
#ifdef ALSOFT_EAX
namespace {
struct EaxChorusTraits {
using Props = EAXCHORUSPROPERTIES;
using Committer = EaxChorusCommitter;
static constexpr auto Field = &EaxEffectProps::mChorus;
static constexpr auto eax_effect_type() { return EaxEffectType::Chorus; }
static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; }
static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; }
static constexpr auto eax_allparameters_param_id() { return EAXCHORUS_ALLPARAMETERS; }
static constexpr auto eax_waveform_param_id() { return EAXCHORUS_WAVEFORM; }
static constexpr auto eax_phase_param_id() { return EAXCHORUS_PHASE; }
static constexpr auto eax_rate_param_id() { return EAXCHORUS_RATE; }
static constexpr auto eax_depth_param_id() { return EAXCHORUS_DEPTH; }
static constexpr auto eax_feedback_param_id() { return EAXCHORUS_FEEDBACK; }
static constexpr auto eax_delay_param_id() { return EAXCHORUS_DELAY; }
static constexpr auto eax_min_waveform() { return EAXCHORUS_MINWAVEFORM; }
static constexpr auto eax_min_phase() { return EAXCHORUS_MINPHASE; }
static constexpr auto eax_min_rate() { return EAXCHORUS_MINRATE; }
static constexpr auto eax_min_depth() { return EAXCHORUS_MINDEPTH; }
static constexpr auto eax_min_feedback() { return EAXCHORUS_MINFEEDBACK; }
static constexpr auto eax_min_delay() { return EAXCHORUS_MINDELAY; }
static constexpr auto eax_max_waveform() { return EAXCHORUS_MAXWAVEFORM; }
static constexpr auto eax_max_phase() { return EAXCHORUS_MAXPHASE; }
static constexpr auto eax_max_rate() { return EAXCHORUS_MAXRATE; }
static constexpr auto eax_max_depth() { return EAXCHORUS_MAXDEPTH; }
static constexpr auto eax_max_feedback() { return EAXCHORUS_MAXFEEDBACK; }
static constexpr auto eax_max_delay() { return EAXCHORUS_MAXDELAY; }
static constexpr auto eax_default_waveform() { return EAXCHORUS_DEFAULTWAVEFORM; }
static constexpr auto eax_default_phase() { return EAXCHORUS_DEFAULTPHASE; }
static constexpr auto eax_default_rate() { return EAXCHORUS_DEFAULTRATE; }
static constexpr auto eax_default_depth() { return EAXCHORUS_DEFAULTDEPTH; }
static constexpr auto eax_default_feedback() { return EAXCHORUS_DEFAULTFEEDBACK; }
static constexpr auto eax_default_delay() { return EAXCHORUS_DEFAULTDELAY; }
static constexpr auto efx_min_waveform() { return AL_CHORUS_MIN_WAVEFORM; }
static constexpr auto efx_min_phase() { return AL_CHORUS_MIN_PHASE; }
static constexpr auto efx_min_rate() { return AL_CHORUS_MIN_RATE; }
static constexpr auto efx_min_depth() { return AL_CHORUS_MIN_DEPTH; }
static constexpr auto efx_min_feedback() { return AL_CHORUS_MIN_FEEDBACK; }
static constexpr auto efx_min_delay() { return AL_CHORUS_MIN_DELAY; }
static constexpr auto efx_max_waveform() { return AL_CHORUS_MAX_WAVEFORM; }
static constexpr auto efx_max_phase() { return AL_CHORUS_MAX_PHASE; }
static constexpr auto efx_max_rate() { return AL_CHORUS_MAX_RATE; }
static constexpr auto efx_max_depth() { return AL_CHORUS_MAX_DEPTH; }
static constexpr auto efx_max_feedback() { return AL_CHORUS_MAX_FEEDBACK; }
static constexpr auto efx_max_delay() { return AL_CHORUS_MAX_DELAY; }
static constexpr auto efx_default_waveform() { return AL_CHORUS_DEFAULT_WAVEFORM; }
static constexpr auto efx_default_phase() { return AL_CHORUS_DEFAULT_PHASE; }
static constexpr auto efx_default_rate() { return AL_CHORUS_DEFAULT_RATE; }
static constexpr auto efx_default_depth() { return AL_CHORUS_DEFAULT_DEPTH; }
static constexpr auto efx_default_feedback() { return AL_CHORUS_DEFAULT_FEEDBACK; }
static constexpr auto efx_default_delay() { return AL_CHORUS_DEFAULT_DELAY; }
static ChorusWaveform eax_waveform(unsigned long type)
{
if(type == EAX_CHORUS_SINUSOID) return ChorusWaveform::Sinusoid;
if(type == EAX_CHORUS_TRIANGLE) return ChorusWaveform::Triangle;
return ChorusWaveform::Sinusoid;
}
}; // EaxChorusTraits
struct EaxFlangerTraits {
using Props = EAXFLANGERPROPERTIES;
using Committer = EaxFlangerCommitter;
static constexpr auto Field = &EaxEffectProps::mFlanger;
static constexpr auto eax_effect_type() { return EaxEffectType::Flanger; }
static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; }
static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; }
static constexpr auto eax_allparameters_param_id() { return EAXFLANGER_ALLPARAMETERS; }
static constexpr auto eax_waveform_param_id() { return EAXFLANGER_WAVEFORM; }
static constexpr auto eax_phase_param_id() { return EAXFLANGER_PHASE; }
static constexpr auto eax_rate_param_id() { return EAXFLANGER_RATE; }
static constexpr auto eax_depth_param_id() { return EAXFLANGER_DEPTH; }
static constexpr auto eax_feedback_param_id() { return EAXFLANGER_FEEDBACK; }
static constexpr auto eax_delay_param_id() { return EAXFLANGER_DELAY; }
static constexpr auto eax_min_waveform() { return EAXFLANGER_MINWAVEFORM; }
static constexpr auto eax_min_phase() { return EAXFLANGER_MINPHASE; }
static constexpr auto eax_min_rate() { return EAXFLANGER_MINRATE; }
static constexpr auto eax_min_depth() { return EAXFLANGER_MINDEPTH; }
static constexpr auto eax_min_feedback() { return EAXFLANGER_MINFEEDBACK; }
static constexpr auto eax_min_delay() { return EAXFLANGER_MINDELAY; }
static constexpr auto eax_max_waveform() { return EAXFLANGER_MAXWAVEFORM; }
static constexpr auto eax_max_phase() { return EAXFLANGER_MAXPHASE; }
static constexpr auto eax_max_rate() { return EAXFLANGER_MAXRATE; }
static constexpr auto eax_max_depth() { return EAXFLANGER_MAXDEPTH; }
static constexpr auto eax_max_feedback() { return EAXFLANGER_MAXFEEDBACK; }
static constexpr auto eax_max_delay() { return EAXFLANGER_MAXDELAY; }
static constexpr auto eax_default_waveform() { return EAXFLANGER_DEFAULTWAVEFORM; }
static constexpr auto eax_default_phase() { return EAXFLANGER_DEFAULTPHASE; }
static constexpr auto eax_default_rate() { return EAXFLANGER_DEFAULTRATE; }
static constexpr auto eax_default_depth() { return EAXFLANGER_DEFAULTDEPTH; }
static constexpr auto eax_default_feedback() { return EAXFLANGER_DEFAULTFEEDBACK; }
static constexpr auto eax_default_delay() { return EAXFLANGER_DEFAULTDELAY; }
static constexpr auto efx_min_waveform() { return AL_FLANGER_MIN_WAVEFORM; }
static constexpr auto efx_min_phase() { return AL_FLANGER_MIN_PHASE; }
static constexpr auto efx_min_rate() { return AL_FLANGER_MIN_RATE; }
static constexpr auto efx_min_depth() { return AL_FLANGER_MIN_DEPTH; }
static constexpr auto efx_min_feedback() { return AL_FLANGER_MIN_FEEDBACK; }
static constexpr auto efx_min_delay() { return AL_FLANGER_MIN_DELAY; }
static constexpr auto efx_max_waveform() { return AL_FLANGER_MAX_WAVEFORM; }
static constexpr auto efx_max_phase() { return AL_FLANGER_MAX_PHASE; }
static constexpr auto efx_max_rate() { return AL_FLANGER_MAX_RATE; }
static constexpr auto efx_max_depth() { return AL_FLANGER_MAX_DEPTH; }
static constexpr auto efx_max_feedback() { return AL_FLANGER_MAX_FEEDBACK; }
static constexpr auto efx_max_delay() { return AL_FLANGER_MAX_DELAY; }
static constexpr auto efx_default_waveform() { return AL_FLANGER_DEFAULT_WAVEFORM; }
static constexpr auto efx_default_phase() { return AL_FLANGER_DEFAULT_PHASE; }
static constexpr auto efx_default_rate() { return AL_FLANGER_DEFAULT_RATE; }
static constexpr auto efx_default_depth() { return AL_FLANGER_DEFAULT_DEPTH; }
static constexpr auto efx_default_feedback() { return AL_FLANGER_DEFAULT_FEEDBACK; }
static constexpr auto efx_default_delay() { return AL_FLANGER_DEFAULT_DELAY; }
static ChorusWaveform eax_waveform(unsigned long type)
{
if(type == EAX_FLANGER_SINUSOID) return ChorusWaveform::Sinusoid;
if(type == EAX_FLANGER_TRIANGLE) return ChorusWaveform::Triangle;
return ChorusWaveform::Sinusoid;
}
}; // EaxFlangerTraits
template<typename TTraits>
struct ChorusFlangerEffect {
using Traits = TTraits;
using Committer = typename Traits::Committer;
using Exception = typename Committer::Exception;
static constexpr auto Field = Traits::Field;
struct WaveformValidator {
void operator()(unsigned long ulWaveform) const
{
eax_validate_range<Exception>(
"Waveform",
ulWaveform,
Traits::eax_min_waveform(),
Traits::eax_max_waveform());
}
}; // WaveformValidator
struct PhaseValidator {
void operator()(long lPhase) const
{
eax_validate_range<Exception>(
"Phase",
lPhase,
Traits::eax_min_phase(),
Traits::eax_max_phase());
}
}; // PhaseValidator
struct RateValidator {
void operator()(float flRate) const
{
eax_validate_range<Exception>(
"Rate",
flRate,
Traits::eax_min_rate(),
Traits::eax_max_rate());
}
}; // RateValidator
struct DepthValidator {
void operator()(float flDepth) const
{
eax_validate_range<Exception>(
"Depth",
flDepth,
Traits::eax_min_depth(),
Traits::eax_max_depth());
}
}; // DepthValidator
struct FeedbackValidator {
void operator()(float flFeedback) const
{
eax_validate_range<Exception>(
"Feedback",
flFeedback,
Traits::eax_min_feedback(),
Traits::eax_max_feedback());
}
}; // FeedbackValidator
struct DelayValidator {
void operator()(float flDelay) const
{
eax_validate_range<Exception>(
"Delay",
flDelay,
Traits::eax_min_delay(),
Traits::eax_max_delay());
}
}; // DelayValidator
struct AllValidator {
void operator()(const typename Traits::Props& all) const
{
WaveformValidator{}(all.ulWaveform);
PhaseValidator{}(all.lPhase);
RateValidator{}(all.flRate);
DepthValidator{}(all.flDepth);
FeedbackValidator{}(all.flFeedback);
DelayValidator{}(all.flDelay);
}
}; // AllValidator
public:
static void SetDefaults(EaxEffectProps &props)
{
auto&& all = props.*Field;
props.mType = Traits::eax_effect_type();
all.ulWaveform = Traits::eax_default_waveform();
all.lPhase = Traits::eax_default_phase();
all.flRate = Traits::eax_default_rate();
all.flDepth = Traits::eax_default_depth();
all.flFeedback = Traits::eax_default_feedback();
all.flDelay = Traits::eax_default_delay();
}
static void Get(const EaxCall &call, const EaxEffectProps &props)
{
auto&& all = props.*Field;
switch(call.get_property_id())
{
case Traits::eax_none_param_id():
break;
case Traits::eax_allparameters_param_id():
call.template set_value<Exception>(all);
break;
case Traits::eax_waveform_param_id():
call.template set_value<Exception>(all.ulWaveform);
break;
case Traits::eax_phase_param_id():
call.template set_value<Exception>(all.lPhase);
break;
case Traits::eax_rate_param_id():
call.template set_value<Exception>(all.flRate);
break;
case Traits::eax_depth_param_id():
call.template set_value<Exception>(all.flDepth);
break;
case Traits::eax_feedback_param_id():
call.template set_value<Exception>(all.flFeedback);
break;
case Traits::eax_delay_param_id():
call.template set_value<Exception>(all.flDelay);
break;
default:
Committer::fail_unknown_property_id();
}
}
static void Set(const EaxCall &call, EaxEffectProps &props)
{
auto&& all = props.*Field;
switch(call.get_property_id())
{
case Traits::eax_none_param_id():
break;
case Traits::eax_allparameters_param_id():
Committer::template defer<AllValidator>(call, all);
break;
case Traits::eax_waveform_param_id():
Committer::template defer<WaveformValidator>(call, all.ulWaveform);
break;
case Traits::eax_phase_param_id():
Committer::template defer<PhaseValidator>(call, all.lPhase);
break;
case Traits::eax_rate_param_id():
Committer::template defer<RateValidator>(call, all.flRate);
break;
case Traits::eax_depth_param_id():
Committer::template defer<DepthValidator>(call, all.flDepth);
break;
case Traits::eax_feedback_param_id():
Committer::template defer<FeedbackValidator>(call, all.flFeedback);
break;
case Traits::eax_delay_param_id():
Committer::template defer<DelayValidator>(call, all.flDelay);
break;
default:
Committer::fail_unknown_property_id();
}
}
static bool Commit(const EaxEffectProps &props, EaxEffectProps &props_, EffectProps &al_props_)
{
if(props.mType == props_.mType)
{
auto&& src = props_.*Field;
auto&& dst = props.*Field;
if(dst.ulWaveform == src.ulWaveform && dst.lPhase == src.lPhase
&& dst.flRate == src.flRate && dst.flDepth == src.flDepth
&& dst.flFeedback == src.flFeedback && dst.flDelay == src.flDelay)
return false;
}
props_ = props;
auto&& dst = props.*Field;
al_props_.Chorus.Waveform = Traits::eax_waveform(dst.ulWaveform);
al_props_.Chorus.Phase = static_cast<int>(dst.lPhase);
al_props_.Chorus.Rate = dst.flRate;
al_props_.Chorus.Depth = dst.flDepth;
al_props_.Chorus.Feedback = dst.flFeedback;
al_props_.Chorus.Delay = dst.flDelay;
return true;
}
}; // EaxChorusFlangerEffect
using ChorusCommitter = EaxCommitter<EaxChorusCommitter>;
using FlangerCommitter = EaxCommitter<EaxFlangerCommitter>;
} // namespace
template<>
struct ChorusCommitter::Exception : public EaxException
{
explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message}
{ }
};
template<>
[[noreturn]] void ChorusCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool ChorusCommitter::commit(const EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
return Committer::Commit(props, mEaxProps, mAlProps);
}
template<>
void ChorusCommitter::SetDefaults(EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::SetDefaults(props);
}
template<>
void ChorusCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::Get(call, props);
}
template<>
void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
Committer::Set(call, props);
}
template<>
struct FlangerCommitter::Exception : public EaxException
{
explicit Exception(const char *message) : EaxException{"EAX_FLANGER_EFFECT", message}
{ }
};
template<>
[[noreturn]] void FlangerCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool FlangerCommitter::commit(const EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
return Committer::Commit(props, mEaxProps, mAlProps);
}
template<>
void FlangerCommitter::SetDefaults(EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::SetDefaults(props);
}
template<>
void FlangerCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Get(call, props);
}
template<>
void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Set(call, props);
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,162 @@
#include "config.h"
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
void Compressor_setParami(EffectProps *props, ALenum param, int val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF))
throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"};
props->Compressor.OnOff = (val != AL_FALSE);
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param};
}
}
void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Compressor_setParami(props, param, vals[0]); }
void Compressor_setParamf(EffectProps*, ALenum param, float)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; }
void Compressor_setParamfv(EffectProps*, ALenum param, const float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
param};
}
void Compressor_getParami(const EffectProps *props, ALenum param, int *val)
{
switch(param)
{
case AL_COMPRESSOR_ONOFF:
*val = props->Compressor.OnOff;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x",
param};
}
}
void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Compressor_getParami(props, param, vals); }
void Compressor_getParamf(const EffectProps*, ALenum param, float*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; }
void Compressor_getParamfv(const EffectProps*, ALenum param, float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x",
param};
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Compressor);
const EffectProps CompressorEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using CompressorCommitter = EaxCommitter<EaxCompressorCommitter>;
struct OnOffValidator {
void operator()(unsigned long ulOnOff) const
{
eax_validate_range<CompressorCommitter::Exception>(
"On-Off",
ulOnOff,
EAXAGCCOMPRESSOR_MINONOFF,
EAXAGCCOMPRESSOR_MAXONOFF);
}
}; // OnOffValidator
struct AllValidator {
void operator()(const EAXAGCCOMPRESSORPROPERTIES& all) const
{
OnOffValidator{}(all.ulOnOff);
}
}; // AllValidator
} // namespace
template<>
struct CompressorCommitter::Exception : public EaxException
{
explicit Exception(const char *message) : EaxException{"EAX_CHORUS_EFFECT", message}
{ }
};
template<>
[[noreturn]] void CompressorCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool CompressorCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType
&& props.mCompressor.ulOnOff == mEaxProps.mCompressor.ulOnOff)
return false;
mEaxProps = props;
mAlProps.Compressor.OnOff = (props.mCompressor.ulOnOff != 0);
return true;
}
template<>
void CompressorCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Compressor;
props.mCompressor.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF;
}
template<>
void CompressorCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXAGCCOMPRESSOR_NONE: break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value<Exception>(props.mCompressor); break;
case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(props.mCompressor.ulOnOff); break;
default: fail_unknown_property_id();
}
}
template<>
void CompressorCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXAGCCOMPRESSOR_NONE: break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer<AllValidator>(call, props.mCompressor); break;
case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.mCompressor.ulOnOff); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,93 @@
#include "config.h"
#include "AL/al.h"
#include "alc/inprogext.h"
#include "alc/effects/base.h"
#include "effects.h"
namespace {
void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals)
{
switch(param)
{
default:
Convolution_setParami(props, param, vals[0]);
}
}
void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals)
{
switch(param)
{
default:
Convolution_setParamf(props, param, vals[0]);
}
}
void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals)
{
switch(param)
{
default:
Convolution_getParami(props, param, vals);
}
}
void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals)
{
switch(param)
{
default:
Convolution_getParamf(props, param, vals);
}
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Convolution);
const EffectProps ConvolutionEffectProps{genDefaultProps()};

View File

@@ -0,0 +1,72 @@
#include "config.h"
#include <cmath>
#include "AL/al.h"
#include "AL/alext.h"
#include "alc/effects/base.h"
#include "effects.h"
namespace {
void Dedicated_setParami(EffectProps*, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
void Dedicated_setParamiv(EffectProps*, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
}
void Dedicated_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
if(!(val >= 0.0f && std::isfinite(val)))
throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"};
props->Dedicated.Gain = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
}
}
void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Dedicated_setParamf(props, param, vals[0]); }
void Dedicated_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; }
void Dedicated_getParamiv(const EffectProps*, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x",
param};
}
void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val)
{
switch(param)
{
case AL_DEDICATED_GAIN:
*val = props->Dedicated.Gain;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param};
}
}
void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Dedicated_getParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Dedicated.Gain = 1.0f;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Dedicated);
const EffectProps DedicatedEffectProps{genDefaultProps()};

View File

@@ -0,0 +1,271 @@
#include "config.h"
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
void Distortion_setParami(EffectProps*, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
void Distortion_setParamiv(EffectProps*, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param};
}
void Distortion_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_DISTORTION_EDGE:
if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"};
props->Distortion.EQBandwidth = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
}
}
void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Distortion_setParamf(props, param, vals[0]); }
void Distortion_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; }
void Distortion_getParamiv(const EffectProps*, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x",
param};
}
void Distortion_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param};
}
}
void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Distortion_getParamf(props, param, vals); }
EffectProps genDefaultProps() 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
DEFINE_ALEFFECT_VTABLE(Distortion);
const EffectProps DistortionEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using DistortionCommitter = EaxCommitter<EaxDistortionCommitter>;
struct EdgeValidator {
void operator()(float flEdge) const
{
eax_validate_range<DistortionCommitter::Exception>(
"Edge",
flEdge,
EAXDISTORTION_MINEDGE,
EAXDISTORTION_MAXEDGE);
}
}; // EdgeValidator
struct GainValidator {
void operator()(long lGain) const
{
eax_validate_range<DistortionCommitter::Exception>(
"Gain",
lGain,
EAXDISTORTION_MINGAIN,
EAXDISTORTION_MAXGAIN);
}
}; // GainValidator
struct LowPassCutOffValidator {
void operator()(float flLowPassCutOff) const
{
eax_validate_range<DistortionCommitter::Exception>(
"Low-pass Cut-off",
flLowPassCutOff,
EAXDISTORTION_MINLOWPASSCUTOFF,
EAXDISTORTION_MAXLOWPASSCUTOFF);
}
}; // LowPassCutOffValidator
struct EqCenterValidator {
void operator()(float flEQCenter) const
{
eax_validate_range<DistortionCommitter::Exception>(
"EQ Center",
flEQCenter,
EAXDISTORTION_MINEQCENTER,
EAXDISTORTION_MAXEQCENTER);
}
}; // EqCenterValidator
struct EqBandwidthValidator {
void operator()(float flEQBandwidth) const
{
eax_validate_range<DistortionCommitter::Exception>(
"EQ Bandwidth",
flEQBandwidth,
EAXDISTORTION_MINEQBANDWIDTH,
EAXDISTORTION_MAXEQBANDWIDTH);
}
}; // EqBandwidthValidator
struct AllValidator {
void operator()(const EAXDISTORTIONPROPERTIES& all) const
{
EdgeValidator{}(all.flEdge);
GainValidator{}(all.lGain);
LowPassCutOffValidator{}(all.flLowPassCutOff);
EqCenterValidator{}(all.flEQCenter);
EqBandwidthValidator{}(all.flEQBandwidth);
}
}; // AllValidator
} // namespace
template<>
struct DistortionCommitter::Exception : public EaxException {
explicit Exception(const char *message) : EaxException{"EAX_DISTORTION_EFFECT", message}
{ }
};
template<>
[[noreturn]] void DistortionCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool DistortionCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType && mEaxProps.mDistortion.flEdge == props.mDistortion.flEdge
&& mEaxProps.mDistortion.lGain == props.mDistortion.lGain
&& mEaxProps.mDistortion.flLowPassCutOff == props.mDistortion.flLowPassCutOff
&& mEaxProps.mDistortion.flEQCenter == props.mDistortion.flEQCenter
&& mEaxProps.mDistortion.flEQBandwidth == props.mDistortion.flEQBandwidth)
return false;
mEaxProps = props;
mAlProps.Distortion.Edge = props.mDistortion.flEdge;
mAlProps.Distortion.Gain = level_mb_to_gain(static_cast<float>(props.mDistortion.lGain));
mAlProps.Distortion.LowpassCutoff = props.mDistortion.flLowPassCutOff;
mAlProps.Distortion.EQCenter = props.mDistortion.flEQCenter;
mAlProps.Distortion.EQBandwidth = props.mDistortion.flEdge;
return true;
}
template<>
void DistortionCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Distortion;
props.mDistortion.flEdge = EAXDISTORTION_DEFAULTEDGE;
props.mDistortion.lGain = EAXDISTORTION_DEFAULTGAIN;
props.mDistortion.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF;
props.mDistortion.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER;
props.mDistortion.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH;
}
template<>
void DistortionCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXDISTORTION_NONE: break;
case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props.mDistortion); break;
case EAXDISTORTION_EDGE: call.set_value<Exception>(props.mDistortion.flEdge); break;
case EAXDISTORTION_GAIN: call.set_value<Exception>(props.mDistortion.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.mDistortion.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.mDistortion.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.mDistortion.flEQBandwidth); break;
default: fail_unknown_property_id();
}
}
template<>
void DistortionCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXDISTORTION_NONE: break;
case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props.mDistortion); break;
case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.mDistortion.flEdge); break;
case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.mDistortion.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.mDistortion.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.mDistortion.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.mDistortion.flEQBandwidth); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,268 @@
#include "config.h"
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short");
static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short");
void Echo_setParami(EffectProps*, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
void Echo_setParamiv(EffectProps*, ALenum param, const int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; }
void Echo_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_ECHO_DELAY:
if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"};
props->Echo.Spread = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
}
}
void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Echo_setParamf(props, param, vals[0]); }
void Echo_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; }
void Echo_getParamiv(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; }
void Echo_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param};
}
}
void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Echo_getParamf(props, param, vals); }
EffectProps genDefaultProps() 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
DEFINE_ALEFFECT_VTABLE(Echo);
const EffectProps EchoEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EchoCommitter = EaxCommitter<EaxEchoCommitter>;
struct DelayValidator {
void operator()(float flDelay) const
{
eax_validate_range<EchoCommitter::Exception>(
"Delay",
flDelay,
EAXECHO_MINDELAY,
EAXECHO_MAXDELAY);
}
}; // DelayValidator
struct LrDelayValidator {
void operator()(float flLRDelay) const
{
eax_validate_range<EchoCommitter::Exception>(
"LR Delay",
flLRDelay,
EAXECHO_MINLRDELAY,
EAXECHO_MAXLRDELAY);
}
}; // LrDelayValidator
struct DampingValidator {
void operator()(float flDamping) const
{
eax_validate_range<EchoCommitter::Exception>(
"Damping",
flDamping,
EAXECHO_MINDAMPING,
EAXECHO_MAXDAMPING);
}
}; // DampingValidator
struct FeedbackValidator {
void operator()(float flFeedback) const
{
eax_validate_range<EchoCommitter::Exception>(
"Feedback",
flFeedback,
EAXECHO_MINFEEDBACK,
EAXECHO_MAXFEEDBACK);
}
}; // FeedbackValidator
struct SpreadValidator {
void operator()(float flSpread) const
{
eax_validate_range<EchoCommitter::Exception>(
"Spread",
flSpread,
EAXECHO_MINSPREAD,
EAXECHO_MAXSPREAD);
}
}; // SpreadValidator
struct AllValidator {
void operator()(const EAXECHOPROPERTIES& all) const
{
DelayValidator{}(all.flDelay);
LrDelayValidator{}(all.flLRDelay);
DampingValidator{}(all.flDamping);
FeedbackValidator{}(all.flFeedback);
SpreadValidator{}(all.flSpread);
}
}; // AllValidator
} // namespace
template<>
struct EchoCommitter::Exception : public EaxException {
explicit Exception(const char* message) : EaxException{"EAX_ECHO_EFFECT", message}
{ }
};
template<>
[[noreturn]] void EchoCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool EchoCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType && mEaxProps.mEcho.flDelay == props.mEcho.flDelay
&& mEaxProps.mEcho.flLRDelay == props.mEcho.flLRDelay
&& mEaxProps.mEcho.flDamping == props.mEcho.flDamping
&& mEaxProps.mEcho.flFeedback == props.mEcho.flFeedback
&& mEaxProps.mEcho.flSpread == props.mEcho.flSpread)
return false;
mEaxProps = props;
mAlProps.Echo.Delay = props.mEcho.flDelay;
mAlProps.Echo.LRDelay = props.mEcho.flLRDelay;
mAlProps.Echo.Damping = props.mEcho.flDamping;
mAlProps.Echo.Feedback = props.mEcho.flFeedback;
mAlProps.Echo.Spread = props.mEcho.flSpread;
return true;
}
template<>
void EchoCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Echo;
props.mEcho.flDelay = EAXECHO_DEFAULTDELAY;
props.mEcho.flLRDelay = EAXECHO_DEFAULTLRDELAY;
props.mEcho.flDamping = EAXECHO_DEFAULTDAMPING;
props.mEcho.flFeedback = EAXECHO_DEFAULTFEEDBACK;
props.mEcho.flSpread = EAXECHO_DEFAULTSPREAD;
}
template<>
void EchoCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXECHO_NONE: break;
case EAXECHO_ALLPARAMETERS: call.set_value<Exception>(props.mEcho); break;
case EAXECHO_DELAY: call.set_value<Exception>(props.mEcho.flDelay); break;
case EAXECHO_LRDELAY: call.set_value<Exception>(props.mEcho.flLRDelay); break;
case EAXECHO_DAMPING: call.set_value<Exception>(props.mEcho.flDamping); break;
case EAXECHO_FEEDBACK: call.set_value<Exception>(props.mEcho.flFeedback); break;
case EAXECHO_SPREAD: call.set_value<Exception>(props.mEcho.flSpread); break;
default: fail_unknown_property_id();
}
}
template<>
void EchoCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXECHO_NONE: break;
case EAXECHO_ALLPARAMETERS: defer<AllValidator>(call, props.mEcho); break;
case EAXECHO_DELAY: defer<DelayValidator>(call, props.mEcho.flDelay); break;
case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.mEcho.flLRDelay); break;
case EAXECHO_DAMPING: defer<DampingValidator>(call, props.mEcho.flDamping); break;
case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.mEcho.flFeedback); break;
case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.mEcho.flSpread); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,9 @@
#include "config.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "AL/efx.h"
#include "effects.h"
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,88 @@
#ifndef AL_EFFECTS_EFFECTS_H
#define AL_EFFECTS_EFFECTS_H
#include "AL/al.h"
#include "core/except.h"
#ifdef ALSOFT_EAX
#include "al/eax/effect.h"
#endif // ALSOFT_EAX
union EffectProps;
class effect_exception final : public al::base_exception {
ALenum mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
effect_exception(ALenum code, const char *msg, ...);
~effect_exception() override;
ALenum errorCode() const noexcept { return mErrorCode; }
};
struct EffectVtable {
void (*const setParami)(EffectProps *props, ALenum param, int val);
void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals);
void (*const setParamf)(EffectProps *props, ALenum param, float val);
void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals);
void (*const getParami)(const EffectProps *props, ALenum param, int *val);
void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals);
void (*const getParamf)(const EffectProps *props, ALenum param, float *val);
void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals);
};
#define DEFINE_ALEFFECT_VTABLE(T) \
const EffectVtable T##EffectVtable = { \
T##_setParami, T##_setParamiv, \
T##_setParamf, T##_setParamfv, \
T##_getParami, T##_getParamiv, \
T##_getParamf, T##_getParamfv, \
}
/* Default properties for the given effect types. */
extern const EffectProps NullEffectProps;
extern const EffectProps ReverbEffectProps;
extern const EffectProps StdReverbEffectProps;
extern const EffectProps AutowahEffectProps;
extern const EffectProps ChorusEffectProps;
extern const EffectProps CompressorEffectProps;
extern const EffectProps DistortionEffectProps;
extern const EffectProps EchoEffectProps;
extern const EffectProps EqualizerEffectProps;
extern const EffectProps FlangerEffectProps;
extern const EffectProps FshifterEffectProps;
extern const EffectProps ModulatorEffectProps;
extern const EffectProps PshifterEffectProps;
extern const EffectProps VmorpherEffectProps;
extern const EffectProps DedicatedEffectProps;
extern const EffectProps ConvolutionEffectProps;
/* Vtables to get/set properties for the given effect types. */
extern const EffectVtable NullEffectVtable;
extern const EffectVtable ReverbEffectVtable;
extern const EffectVtable StdReverbEffectVtable;
extern const EffectVtable AutowahEffectVtable;
extern const EffectVtable ChorusEffectVtable;
extern const EffectVtable CompressorEffectVtable;
extern const EffectVtable DistortionEffectVtable;
extern const EffectVtable EchoEffectVtable;
extern const EffectVtable EqualizerEffectVtable;
extern const EffectVtable FlangerEffectVtable;
extern const EffectVtable FshifterEffectVtable;
extern const EffectVtable ModulatorEffectVtable;
extern const EffectVtable PshifterEffectVtable;
extern const EffectVtable VmorpherEffectVtable;
extern const EffectVtable DedicatedEffectVtable;
extern const EffectVtable ConvolutionEffectVtable;
#endif /* AL_EFFECTS_EFFECTS_H */

View File

@@ -0,0 +1,411 @@
#include "config.h"
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
void Equalizer_setParami(EffectProps*, ALenum param, int)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
void Equalizer_setParamiv(EffectProps*, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param};
}
void Equalizer_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_EQUALIZER_LOW_GAIN:
if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{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))
throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"};
props->Equalizer.HighCutoff = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
}
}
void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Equalizer_setParamf(props, param, vals[0]); }
void Equalizer_getParami(const EffectProps*, ALenum param, int*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; }
void Equalizer_getParamiv(const EffectProps*, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x",
param};
}
void Equalizer_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param};
}
}
void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Equalizer_getParamf(props, param, vals); }
EffectProps genDefaultProps() 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
DEFINE_ALEFFECT_VTABLE(Equalizer);
const EffectProps EqualizerEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using EqualizerCommitter = EaxCommitter<EaxEqualizerCommitter>;
struct LowGainValidator {
void operator()(long lLowGain) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Low Gain",
lLowGain,
EAXEQUALIZER_MINLOWGAIN,
EAXEQUALIZER_MAXLOWGAIN);
}
}; // LowGainValidator
struct LowCutOffValidator {
void operator()(float flLowCutOff) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Low Cutoff",
flLowCutOff,
EAXEQUALIZER_MINLOWCUTOFF,
EAXEQUALIZER_MAXLOWCUTOFF);
}
}; // LowCutOffValidator
struct Mid1GainValidator {
void operator()(long lMid1Gain) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Mid1 Gain",
lMid1Gain,
EAXEQUALIZER_MINMID1GAIN,
EAXEQUALIZER_MAXMID1GAIN);
}
}; // Mid1GainValidator
struct Mid1CenterValidator {
void operator()(float flMid1Center) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Mid1 Center",
flMid1Center,
EAXEQUALIZER_MINMID1CENTER,
EAXEQUALIZER_MAXMID1CENTER);
}
}; // Mid1CenterValidator
struct Mid1WidthValidator {
void operator()(float flMid1Width) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Mid1 Width",
flMid1Width,
EAXEQUALIZER_MINMID1WIDTH,
EAXEQUALIZER_MAXMID1WIDTH);
}
}; // Mid1WidthValidator
struct Mid2GainValidator {
void operator()(long lMid2Gain) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Mid2 Gain",
lMid2Gain,
EAXEQUALIZER_MINMID2GAIN,
EAXEQUALIZER_MAXMID2GAIN);
}
}; // Mid2GainValidator
struct Mid2CenterValidator {
void operator()(float flMid2Center) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Mid2 Center",
flMid2Center,
EAXEQUALIZER_MINMID2CENTER,
EAXEQUALIZER_MAXMID2CENTER);
}
}; // Mid2CenterValidator
struct Mid2WidthValidator {
void operator()(float flMid2Width) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"Mid2 Width",
flMid2Width,
EAXEQUALIZER_MINMID2WIDTH,
EAXEQUALIZER_MAXMID2WIDTH);
}
}; // Mid2WidthValidator
struct HighGainValidator {
void operator()(long lHighGain) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"High Gain",
lHighGain,
EAXEQUALIZER_MINHIGHGAIN,
EAXEQUALIZER_MAXHIGHGAIN);
}
}; // HighGainValidator
struct HighCutOffValidator {
void operator()(float flHighCutOff) const
{
eax_validate_range<EqualizerCommitter::Exception>(
"High Cutoff",
flHighCutOff,
EAXEQUALIZER_MINHIGHCUTOFF,
EAXEQUALIZER_MAXHIGHCUTOFF);
}
}; // HighCutOffValidator
struct AllValidator {
void operator()(const EAXEQUALIZERPROPERTIES& all) const
{
LowGainValidator{}(all.lLowGain);
LowCutOffValidator{}(all.flLowCutOff);
Mid1GainValidator{}(all.lMid1Gain);
Mid1CenterValidator{}(all.flMid1Center);
Mid1WidthValidator{}(all.flMid1Width);
Mid2GainValidator{}(all.lMid2Gain);
Mid2CenterValidator{}(all.flMid2Center);
Mid2WidthValidator{}(all.flMid2Width);
HighGainValidator{}(all.lHighGain);
HighCutOffValidator{}(all.flHighCutOff);
}
}; // AllValidator
} // namespace
template<>
struct EqualizerCommitter::Exception : public EaxException {
explicit Exception(const char* message) : EaxException{"EAX_EQUALIZER_EFFECT", message}
{ }
};
template<>
[[noreturn]] void EqualizerCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool EqualizerCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType && mEaxProps.mEqualizer.lLowGain == props.mEqualizer.lLowGain
&& mEaxProps.mEqualizer.flLowCutOff == props.mEqualizer.flLowCutOff
&& mEaxProps.mEqualizer.lMid1Gain == props.mEqualizer.lMid1Gain
&& mEaxProps.mEqualizer.flMid1Center == props.mEqualizer.flMid1Center
&& mEaxProps.mEqualizer.flMid1Width == props.mEqualizer.flMid1Width
&& mEaxProps.mEqualizer.lMid2Gain == props.mEqualizer.lMid2Gain
&& mEaxProps.mEqualizer.flMid2Center == props.mEqualizer.flMid2Center
&& mEaxProps.mEqualizer.flMid2Width == props.mEqualizer.flMid2Width
&& mEaxProps.mEqualizer.lHighGain == props.mEqualizer.lHighGain
&& mEaxProps.mEqualizer.flHighCutOff == props.mEqualizer.flHighCutOff)
return false;
mEaxProps = props;
mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lLowGain));
mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff;
mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid1Gain));
mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center;
mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width;
mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid2Gain));
mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center;
mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width;
mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lHighGain));
mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff;
return true;
}
template<>
void EqualizerCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Equalizer;
props.mEqualizer.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN;
props.mEqualizer.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF;
props.mEqualizer.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN;
props.mEqualizer.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER;
props.mEqualizer.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH;
props.mEqualizer.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN;
props.mEqualizer.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER;
props.mEqualizer.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH;
props.mEqualizer.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN;
props.mEqualizer.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF;
}
template<>
void EqualizerCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXEQUALIZER_NONE: break;
case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props.mEqualizer); break;
case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.mEqualizer.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.mEqualizer.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.mEqualizer.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.mEqualizer.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.mEqualizer.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.mEqualizer.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.mEqualizer.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.mEqualizer.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.mEqualizer.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.mEqualizer.flHighCutOff); break;
default: fail_unknown_property_id();
}
}
template<>
void EqualizerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXEQUALIZER_NONE: break;
case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props.mEqualizer); break;
case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.mEqualizer.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.mEqualizer.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.mEqualizer.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.mEqualizer.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.mEqualizer.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.mEqualizer.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.mEqualizer.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.mEqualizer.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.mEqualizer.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.mEqualizer.flHighCutOff); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,264 @@
#include "config.h"
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
al::optional<FShifterDirection> DirectionFromEmum(ALenum value)
{
switch(value)
{
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return FShifterDirection::Down;
case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up;
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off;
}
return al::nullopt;
}
ALenum EnumFromDirection(FShifterDirection dir)
{
switch(dir)
{
case FShifterDirection::Down: return AL_FREQUENCY_SHIFTER_DIRECTION_DOWN;
case FShifterDirection::Up: return AL_FREQUENCY_SHIFTER_DIRECTION_UP;
case FShifterDirection::Off: return AL_FREQUENCY_SHIFTER_DIRECTION_OFF;
}
throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast<int>(dir))};
}
void Fshifter_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY))
throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"};
props->Fshifter.Frequency = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param};
}
}
void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Fshifter_setParamf(props, param, vals[0]); }
void Fshifter_setParami(EffectProps *props, ALenum param, int val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
if(auto diropt = DirectionFromEmum(val))
props->Fshifter.LeftDirection = *diropt;
else
throw effect_exception{AL_INVALID_VALUE,
"Unsupported frequency shifter left direction: 0x%04x", val};
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
if(auto diropt = DirectionFromEmum(val))
props->Fshifter.RightDirection = *diropt;
else
throw effect_exception{AL_INVALID_VALUE,
"Unsupported frequency shifter right direction: 0x%04x", val};
break;
default:
throw effect_exception{AL_INVALID_ENUM,
"Invalid frequency shifter integer property 0x%04x", param};
}
}
void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Fshifter_setParami(props, param, vals[0]); }
void Fshifter_getParami(const EffectProps *props, ALenum param, int *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION:
*val = EnumFromDirection(props->Fshifter.LeftDirection);
break;
case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION:
*val = EnumFromDirection(props->Fshifter.RightDirection);
break;
default:
throw effect_exception{AL_INVALID_ENUM,
"Invalid frequency shifter integer property 0x%04x", param};
}
}
void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Fshifter_getParami(props, param, vals); }
void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val)
{
switch(param)
{
case AL_FREQUENCY_SHIFTER_FREQUENCY:
*val = props->Fshifter.Frequency;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x",
param};
}
}
void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Fshifter_getParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY;
props.Fshifter.LeftDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION);
props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION);
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Fshifter);
const EffectProps FshifterEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using FrequencyShifterCommitter = EaxCommitter<EaxFrequencyShifterCommitter>;
struct FrequencyValidator {
void operator()(float flFrequency) const
{
eax_validate_range<FrequencyShifterCommitter::Exception>(
"Frequency",
flFrequency,
EAXFREQUENCYSHIFTER_MINFREQUENCY,
EAXFREQUENCYSHIFTER_MAXFREQUENCY);
}
}; // FrequencyValidator
struct LeftDirectionValidator {
void operator()(unsigned long ulLeftDirection) const
{
eax_validate_range<FrequencyShifterCommitter::Exception>(
"Left Direction",
ulLeftDirection,
EAXFREQUENCYSHIFTER_MINLEFTDIRECTION,
EAXFREQUENCYSHIFTER_MAXLEFTDIRECTION);
}
}; // LeftDirectionValidator
struct RightDirectionValidator {
void operator()(unsigned long ulRightDirection) const
{
eax_validate_range<FrequencyShifterCommitter::Exception>(
"Right Direction",
ulRightDirection,
EAXFREQUENCYSHIFTER_MINRIGHTDIRECTION,
EAXFREQUENCYSHIFTER_MAXRIGHTDIRECTION);
}
}; // RightDirectionValidator
struct AllValidator {
void operator()(const EAXFREQUENCYSHIFTERPROPERTIES& all) const
{
FrequencyValidator{}(all.flFrequency);
LeftDirectionValidator{}(all.ulLeftDirection);
RightDirectionValidator{}(all.ulRightDirection);
}
}; // AllValidator
} // namespace
template<>
struct FrequencyShifterCommitter::Exception : public EaxException {
explicit Exception(const char *message) : EaxException{"EAX_FREQUENCY_SHIFTER_EFFECT", message}
{ }
};
template<>
[[noreturn]] void FrequencyShifterCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool FrequencyShifterCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mFrequencyShifter.flFrequency == props.mFrequencyShifter.flFrequency
&& mEaxProps.mFrequencyShifter.ulLeftDirection == props.mFrequencyShifter.ulLeftDirection
&& mEaxProps.mFrequencyShifter.ulRightDirection == props.mFrequencyShifter.ulRightDirection)
return false;
mEaxProps = props;
auto get_direction = [](unsigned long dir) noexcept
{
if(dir == EAX_FREQUENCYSHIFTER_DOWN)
return FShifterDirection::Down;
if(dir == EAX_FREQUENCYSHIFTER_UP)
return FShifterDirection::Up;
return FShifterDirection::Off;
};
mAlProps.Fshifter.Frequency = props.mFrequencyShifter.flFrequency;
mAlProps.Fshifter.LeftDirection = get_direction(props.mFrequencyShifter.ulLeftDirection);
mAlProps.Fshifter.RightDirection = get_direction(props.mFrequencyShifter.ulRightDirection);
return true;
}
template<>
void FrequencyShifterCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::FrequencyShifter;
props.mFrequencyShifter.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY;
props.mFrequencyShifter.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION;
props.mFrequencyShifter.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION;
}
template<>
void FrequencyShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXFREQUENCYSHIFTER_NONE: break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mFrequencyShifter); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.mFrequencyShifter.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulRightDirection); break;
default: fail_unknown_property_id();
}
}
template<>
void FrequencyShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXFREQUENCYSHIFTER_NONE: break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mFrequencyShifter); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.mFrequencyShifter.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.mFrequencyShifter.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.mFrequencyShifter.ulRightDirection); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,272 @@
#include "config.h"
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
al::optional<ModulatorWaveform> WaveformFromEmum(ALenum value)
{
switch(value)
{
case AL_RING_MODULATOR_SINUSOID: return ModulatorWaveform::Sinusoid;
case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth;
case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square;
}
return al::nullopt;
}
ALenum EnumFromWaveform(ModulatorWaveform type)
{
switch(type)
{
case ModulatorWaveform::Sinusoid: return AL_RING_MODULATOR_SINUSOID;
case ModulatorWaveform::Sawtooth: return AL_RING_MODULATOR_SAWTOOTH;
case ModulatorWaveform::Square: return AL_RING_MODULATOR_SQUARE;
}
throw std::runtime_error{"Invalid modulator waveform: " +
std::to_string(static_cast<int>(type))};
}
void Modulator_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY))
throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val};
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))
throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val};
props->Modulator.HighPassCutoff = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
}
}
void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Modulator_setParamf(props, param, vals[0]); }
void Modulator_setParami(EffectProps *props, ALenum param, int val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
Modulator_setParamf(props, param, static_cast<float>(val));
break;
case AL_RING_MODULATOR_WAVEFORM:
if(auto formopt = WaveformFromEmum(val))
props->Modulator.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val};
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x",
param};
}
}
void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Modulator_setParami(props, param, vals[0]); }
void Modulator_getParami(const EffectProps *props, ALenum param, int *val)
{
switch(param)
{
case AL_RING_MODULATOR_FREQUENCY:
*val = static_cast<int>(props->Modulator.Frequency);
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = static_cast<int>(props->Modulator.HighPassCutoff);
break;
case AL_RING_MODULATOR_WAVEFORM:
*val = EnumFromWaveform(props->Modulator.Waveform);
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x",
param};
}
}
void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Modulator_getParami(props, param, vals); }
void Modulator_getParamf(const EffectProps *props, ALenum param, float *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param};
}
}
void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Modulator_getParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY;
props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF;
props.Modulator.Waveform = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM);
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Modulator);
const EffectProps ModulatorEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using ModulatorCommitter = EaxCommitter<EaxModulatorCommitter>;
struct FrequencyValidator {
void operator()(float flFrequency) const
{
eax_validate_range<ModulatorCommitter::Exception>(
"Frequency",
flFrequency,
EAXRINGMODULATOR_MINFREQUENCY,
EAXRINGMODULATOR_MAXFREQUENCY);
}
}; // FrequencyValidator
struct HighPassCutOffValidator {
void operator()(float flHighPassCutOff) const
{
eax_validate_range<ModulatorCommitter::Exception>(
"High-Pass Cutoff",
flHighPassCutOff,
EAXRINGMODULATOR_MINHIGHPASSCUTOFF,
EAXRINGMODULATOR_MAXHIGHPASSCUTOFF);
}
}; // HighPassCutOffValidator
struct WaveformValidator {
void operator()(unsigned long ulWaveform) const
{
eax_validate_range<ModulatorCommitter::Exception>(
"Waveform",
ulWaveform,
EAXRINGMODULATOR_MINWAVEFORM,
EAXRINGMODULATOR_MAXWAVEFORM);
}
}; // WaveformValidator
struct AllValidator {
void operator()(const EAXRINGMODULATORPROPERTIES& all) const
{
FrequencyValidator{}(all.flFrequency);
HighPassCutOffValidator{}(all.flHighPassCutOff);
WaveformValidator{}(all.ulWaveform);
}
}; // AllValidator
} // namespace
template<>
struct ModulatorCommitter::Exception : public EaxException {
explicit Exception(const char *message) : EaxException{"EAX_RING_MODULATOR_EFFECT", message}
{ }
};
template<>
[[noreturn]] void ModulatorCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool ModulatorCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mModulator.flFrequency == props.mModulator.flFrequency
&& mEaxProps.mModulator.flHighPassCutOff == props.mModulator.flHighPassCutOff
&& mEaxProps.mModulator.ulWaveform == props.mModulator.ulWaveform)
return false;
mEaxProps = props;
auto get_waveform = [](unsigned long form)
{
if(form == EAX_RINGMODULATOR_SINUSOID)
return ModulatorWaveform::Sinusoid;
if(form == EAX_RINGMODULATOR_SAWTOOTH)
return ModulatorWaveform::Sawtooth;
if(form == EAX_RINGMODULATOR_SQUARE)
return ModulatorWaveform::Square;
return ModulatorWaveform::Sinusoid;
};
mAlProps.Modulator.Frequency = props.mModulator.flFrequency;
mAlProps.Modulator.HighPassCutoff = props.mModulator.flHighPassCutOff;
mAlProps.Modulator.Waveform = get_waveform(props.mModulator.ulWaveform);
return true;
}
template<>
void ModulatorCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::Modulator;
props.mModulator.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY;
props.mModulator.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF;
props.mModulator.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM;
}
template<>
void ModulatorCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXRINGMODULATOR_NONE: break;
case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value<Exception>(props.mModulator); break;
case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.mModulator.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.mModulator.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(props.mModulator.ulWaveform); break;
default: fail_unknown_property_id();
}
}
template<>
void ModulatorCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch (call.get_property_id())
{
case EAXRINGMODULATOR_NONE: break;
case EAXRINGMODULATOR_ALLPARAMETERS: defer<AllValidator>(call, props.mModulator); break;
case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.mModulator.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.mModulator.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.mModulator.ulWaveform); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,149 @@
#include "config.h"
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "al/eax/exception.h"
#endif // ALSOFT_EAX
namespace {
void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Null_setParamiv(EffectProps *props, ALenum param, const int *vals)
{
switch(param)
{
default:
Null_setParami(props, param, vals[0]);
}
}
void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Null_setParamfv(EffectProps *props, ALenum param, const float *vals)
{
switch(param)
{
default:
Null_setParamf(props, param, vals[0]);
}
}
void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x",
param};
}
}
void Null_getParamiv(const EffectProps *props, ALenum param, int *vals)
{
switch(param)
{
default:
Null_getParami(props, param, vals);
}
}
void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/)
{
switch(param)
{
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x",
param};
}
}
void Null_getParamfv(const EffectProps *props, ALenum param, float *vals)
{
switch(param)
{
default:
Null_getParamf(props, param, vals);
}
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Null);
const EffectProps NullEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using NullCommitter = EaxCommitter<EaxNullCommitter>;
} // namespace
template<>
struct NullCommitter::Exception : public EaxException
{
explicit Exception(const char *message) : EaxException{"EAX_NULL_EFFECT", message}
{ }
};
template<>
[[noreturn]] void NullCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool NullCommitter::commit(const EaxEffectProps &props)
{
const bool ret{props.mType != mEaxProps.mType};
mEaxProps = props;
return ret;
}
template<>
void NullCommitter::SetDefaults(EaxEffectProps &props)
{
props = EaxEffectProps{};
props.mType = EaxEffectType::None;
}
template<>
void NullCommitter::Get(const EaxCall &call, const EaxEffectProps&)
{
if(call.get_property_id() != 0)
fail_unknown_property_id();
}
template<>
void NullCommitter::Set(const EaxCall &call, EaxEffectProps&)
{
if(call.get_property_id() != 0)
fail_unknown_property_id();
}
#endif // ALSOFT_EAX

View File

@@ -0,0 +1,191 @@
#include "config.h"
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
void Pshifter_setParamf(EffectProps*, ALenum param, float)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
void Pshifter_setParamfv(EffectProps*, ALenum param, const float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x",
param};
}
void Pshifter_setParami(EffectProps *props, ALenum param, int val)
{
switch(param)
{
case AL_PITCH_SHIFTER_COARSE_TUNE:
if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE))
throw effect_exception{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))
throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"};
props->Pshifter.FineTune = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param};
}
}
void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals)
{ Pshifter_setParami(props, param, vals[0]); }
void Pshifter_getParami(const EffectProps *props, ALenum param, int *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:
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x",
param};
}
}
void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals)
{ Pshifter_getParami(props, param, vals); }
void Pshifter_getParamf(const EffectProps*, ALenum param, float*)
{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; }
void Pshifter_getParamfv(const EffectProps*, ALenum param, float*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x",
param};
}
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE;
props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE;
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Pshifter);
const EffectProps PshifterEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using PitchShifterCommitter = EaxCommitter<EaxPitchShifterCommitter>;
struct CoarseTuneValidator {
void operator()(long lCoarseTune) const
{
eax_validate_range<PitchShifterCommitter::Exception>(
"Coarse Tune",
lCoarseTune,
EAXPITCHSHIFTER_MINCOARSETUNE,
EAXPITCHSHIFTER_MAXCOARSETUNE);
}
}; // CoarseTuneValidator
struct FineTuneValidator {
void operator()(long lFineTune) const
{
eax_validate_range<PitchShifterCommitter::Exception>(
"Fine Tune",
lFineTune,
EAXPITCHSHIFTER_MINFINETUNE,
EAXPITCHSHIFTER_MAXFINETUNE);
}
}; // FineTuneValidator
struct AllValidator {
void operator()(const EAXPITCHSHIFTERPROPERTIES& all) const
{
CoarseTuneValidator{}(all.lCoarseTune);
FineTuneValidator{}(all.lFineTune);
}
}; // AllValidator
} // namespace
template<>
struct PitchShifterCommitter::Exception : public EaxException {
explicit Exception(const char *message) : EaxException{"EAX_PITCH_SHIFTER_EFFECT", message}
{ }
};
template<>
[[noreturn]] void PitchShifterCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool PitchShifterCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mPitchShifter.lCoarseTune == props.mPitchShifter.lCoarseTune
&& mEaxProps.mPitchShifter.lFineTune == props.mPitchShifter.lFineTune)
return false;
mEaxProps = props;
mAlProps.Pshifter.CoarseTune = static_cast<int>(mEaxProps.mPitchShifter.lCoarseTune);
mAlProps.Pshifter.FineTune = static_cast<int>(mEaxProps.mPitchShifter.lFineTune);
return true;
}
template<>
void PitchShifterCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::PitchShifter;
props.mPitchShifter.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE;
props.mPitchShifter.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE;
}
template<>
void PitchShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXPITCHSHIFTER_NONE: break;
case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props.mPitchShifter); break;
case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.mPitchShifter.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.mPitchShifter.lFineTune); break;
default: fail_unknown_property_id();
}
}
template<>
void PitchShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXPITCHSHIFTER_NONE: break;
case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props.mPitchShifter); break;
case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.mPitchShifter.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.mPitchShifter.lFineTune); break;
default: fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,520 @@
#include "config.h"
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "alnumeric.h"
#include "al/eax/exception.h"
#include "al/eax/utils.h"
#endif // ALSOFT_EAX
namespace {
al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
{
#define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \
return VMorpherPhenome::x
switch(val)
{
HANDLE_PHENOME(A);
HANDLE_PHENOME(E);
HANDLE_PHENOME(I);
HANDLE_PHENOME(O);
HANDLE_PHENOME(U);
HANDLE_PHENOME(AA);
HANDLE_PHENOME(AE);
HANDLE_PHENOME(AH);
HANDLE_PHENOME(AO);
HANDLE_PHENOME(EH);
HANDLE_PHENOME(ER);
HANDLE_PHENOME(IH);
HANDLE_PHENOME(IY);
HANDLE_PHENOME(UH);
HANDLE_PHENOME(UW);
HANDLE_PHENOME(B);
HANDLE_PHENOME(D);
HANDLE_PHENOME(F);
HANDLE_PHENOME(G);
HANDLE_PHENOME(J);
HANDLE_PHENOME(K);
HANDLE_PHENOME(L);
HANDLE_PHENOME(M);
HANDLE_PHENOME(N);
HANDLE_PHENOME(P);
HANDLE_PHENOME(R);
HANDLE_PHENOME(S);
HANDLE_PHENOME(T);
HANDLE_PHENOME(V);
HANDLE_PHENOME(Z);
}
return al::nullopt;
#undef HANDLE_PHENOME
}
ALenum EnumFromPhenome(VMorpherPhenome phenome)
{
#define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x
switch(phenome)
{
HANDLE_PHENOME(A);
HANDLE_PHENOME(E);
HANDLE_PHENOME(I);
HANDLE_PHENOME(O);
HANDLE_PHENOME(U);
HANDLE_PHENOME(AA);
HANDLE_PHENOME(AE);
HANDLE_PHENOME(AH);
HANDLE_PHENOME(AO);
HANDLE_PHENOME(EH);
HANDLE_PHENOME(ER);
HANDLE_PHENOME(IH);
HANDLE_PHENOME(IY);
HANDLE_PHENOME(UH);
HANDLE_PHENOME(UW);
HANDLE_PHENOME(B);
HANDLE_PHENOME(D);
HANDLE_PHENOME(F);
HANDLE_PHENOME(G);
HANDLE_PHENOME(J);
HANDLE_PHENOME(K);
HANDLE_PHENOME(L);
HANDLE_PHENOME(M);
HANDLE_PHENOME(N);
HANDLE_PHENOME(P);
HANDLE_PHENOME(R);
HANDLE_PHENOME(S);
HANDLE_PHENOME(T);
HANDLE_PHENOME(V);
HANDLE_PHENOME(Z);
}
throw std::runtime_error{"Invalid phenome: "+std::to_string(static_cast<int>(phenome))};
#undef HANDLE_PHENOME
}
al::optional<VMorpherWaveform> WaveformFromEmum(ALenum value)
{
switch(value)
{
case AL_VOCAL_MORPHER_WAVEFORM_SINUSOID: return VMorpherWaveform::Sinusoid;
case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle;
case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth;
}
return al::nullopt;
}
ALenum EnumFromWaveform(VMorpherWaveform type)
{
switch(type)
{
case VMorpherWaveform::Sinusoid: return AL_VOCAL_MORPHER_WAVEFORM_SINUSOID;
case VMorpherWaveform::Triangle: return AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE;
case VMorpherWaveform::Sawtooth: return AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH;
}
throw std::runtime_error{"Invalid vocal morpher waveform: " +
std::to_string(static_cast<int>(type))};
}
void Vmorpher_setParami(EffectProps *props, ALenum param, int val)
{
switch(param)
{
case AL_VOCAL_MORPHER_PHONEMEA:
if(auto phenomeopt = PhenomeFromEnum(val))
props->Vmorpher.PhonemeA = *phenomeopt;
else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", 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))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"};
props->Vmorpher.PhonemeACoarseTuning = val;
break;
case AL_VOCAL_MORPHER_PHONEMEB:
if(auto phenomeopt = PhenomeFromEnum(val))
props->Vmorpher.PhonemeB = *phenomeopt;
else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", 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))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"};
props->Vmorpher.PhonemeBCoarseTuning = val;
break;
case AL_VOCAL_MORPHER_WAVEFORM:
if(auto formopt = WaveformFromEmum(val))
props->Vmorpher.Waveform = *formopt;
else
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val};
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param};
}
}
void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
param};
}
void Vmorpher_setParamf(EffectProps *props, ALenum param, float val)
{
switch(param)
{
case AL_VOCAL_MORPHER_RATE:
if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE))
throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"};
props->Vmorpher.Rate = val;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
param};
}
}
void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals)
{ Vmorpher_setParamf(props, param, vals[0]); }
void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val)
{
switch(param)
{
case AL_VOCAL_MORPHER_PHONEMEA:
*val = EnumFromPhenome(props->Vmorpher.PhonemeA);
break;
case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING:
*val = props->Vmorpher.PhonemeACoarseTuning;
break;
case AL_VOCAL_MORPHER_PHONEMEB:
*val = EnumFromPhenome(props->Vmorpher.PhonemeB);
break;
case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING:
*val = props->Vmorpher.PhonemeBCoarseTuning;
break;
case AL_VOCAL_MORPHER_WAVEFORM:
*val = EnumFromWaveform(props->Vmorpher.Waveform);
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x",
param};
}
}
void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*)
{
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x",
param};
}
void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val)
{
switch(param)
{
case AL_VOCAL_MORPHER_RATE:
*val = props->Vmorpher.Rate;
break;
default:
throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher float property 0x%04x",
param};
}
}
void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals)
{ Vmorpher_getParamf(props, param, vals); }
EffectProps genDefaultProps() noexcept
{
EffectProps props{};
props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE;
props.Vmorpher.PhonemeA = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA);
props.Vmorpher.PhonemeB = *PhenomeFromEnum(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 = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM);
return props;
}
} // namespace
DEFINE_ALEFFECT_VTABLE(Vmorpher);
const EffectProps VmorpherEffectProps{genDefaultProps()};
#ifdef ALSOFT_EAX
namespace {
using VocalMorpherCommitter = EaxCommitter<EaxVocalMorpherCommitter>;
struct PhonemeAValidator {
void operator()(unsigned long ulPhonemeA) const
{
eax_validate_range<VocalMorpherCommitter::Exception>(
"Phoneme A",
ulPhonemeA,
EAXVOCALMORPHER_MINPHONEMEA,
EAXVOCALMORPHER_MAXPHONEMEA);
}
}; // PhonemeAValidator
struct PhonemeACoarseTuningValidator {
void operator()(long lPhonemeACoarseTuning) const
{
eax_validate_range<VocalMorpherCommitter::Exception>(
"Phoneme A Coarse Tuning",
lPhonemeACoarseTuning,
EAXVOCALMORPHER_MINPHONEMEACOARSETUNING,
EAXVOCALMORPHER_MAXPHONEMEACOARSETUNING);
}
}; // PhonemeACoarseTuningValidator
struct PhonemeBValidator {
void operator()(unsigned long ulPhonemeB) const
{
eax_validate_range<VocalMorpherCommitter::Exception>(
"Phoneme B",
ulPhonemeB,
EAXVOCALMORPHER_MINPHONEMEB,
EAXVOCALMORPHER_MAXPHONEMEB);
}
}; // PhonemeBValidator
struct PhonemeBCoarseTuningValidator {
void operator()(long lPhonemeBCoarseTuning) const
{
eax_validate_range<VocalMorpherCommitter::Exception>(
"Phoneme B Coarse Tuning",
lPhonemeBCoarseTuning,
EAXVOCALMORPHER_MINPHONEMEBCOARSETUNING,
EAXVOCALMORPHER_MAXPHONEMEBCOARSETUNING);
}
}; // PhonemeBCoarseTuningValidator
struct WaveformValidator {
void operator()(unsigned long ulWaveform) const
{
eax_validate_range<VocalMorpherCommitter::Exception>(
"Waveform",
ulWaveform,
EAXVOCALMORPHER_MINWAVEFORM,
EAXVOCALMORPHER_MAXWAVEFORM);
}
}; // WaveformValidator
struct RateValidator {
void operator()(float flRate) const
{
eax_validate_range<VocalMorpherCommitter::Exception>(
"Rate",
flRate,
EAXVOCALMORPHER_MINRATE,
EAXVOCALMORPHER_MAXRATE);
}
}; // RateValidator
struct AllValidator {
void operator()(const EAXVOCALMORPHERPROPERTIES& all) const
{
PhonemeAValidator{}(all.ulPhonemeA);
PhonemeACoarseTuningValidator{}(all.lPhonemeACoarseTuning);
PhonemeBValidator{}(all.ulPhonemeB);
PhonemeBCoarseTuningValidator{}(all.lPhonemeBCoarseTuning);
WaveformValidator{}(all.ulWaveform);
RateValidator{}(all.flRate);
}
}; // AllValidator
} // namespace
template<>
struct VocalMorpherCommitter::Exception : public EaxException {
explicit Exception(const char *message) : EaxException{"EAX_VOCAL_MORPHER_EFFECT", message}
{ }
};
template<>
[[noreturn]] void VocalMorpherCommitter::fail(const char *message)
{
throw Exception{message};
}
template<>
bool VocalMorpherCommitter::commit(const EaxEffectProps &props)
{
if(props.mType == mEaxProps.mType
&& mEaxProps.mVocalMorpher.ulPhonemeA == props.mVocalMorpher.ulPhonemeA
&& mEaxProps.mVocalMorpher.lPhonemeACoarseTuning == props.mVocalMorpher.lPhonemeACoarseTuning
&& mEaxProps.mVocalMorpher.ulPhonemeB == props.mVocalMorpher.ulPhonemeB
&& mEaxProps.mVocalMorpher.lPhonemeBCoarseTuning == props.mVocalMorpher.lPhonemeBCoarseTuning
&& mEaxProps.mVocalMorpher.ulWaveform == props.mVocalMorpher.ulWaveform
&& mEaxProps.mVocalMorpher.flRate == props.mVocalMorpher.flRate)
return false;
mEaxProps = props;
auto get_phoneme = [](unsigned long phoneme) noexcept
{
#define HANDLE_PHENOME(x) case x: return VMorpherPhenome::x
switch(phoneme)
{
HANDLE_PHENOME(A);
HANDLE_PHENOME(E);
HANDLE_PHENOME(I);
HANDLE_PHENOME(O);
HANDLE_PHENOME(U);
HANDLE_PHENOME(AA);
HANDLE_PHENOME(AE);
HANDLE_PHENOME(AH);
HANDLE_PHENOME(AO);
HANDLE_PHENOME(EH);
HANDLE_PHENOME(ER);
HANDLE_PHENOME(IH);
HANDLE_PHENOME(IY);
HANDLE_PHENOME(UH);
HANDLE_PHENOME(UW);
HANDLE_PHENOME(B);
HANDLE_PHENOME(D);
HANDLE_PHENOME(F);
HANDLE_PHENOME(G);
HANDLE_PHENOME(J);
HANDLE_PHENOME(K);
HANDLE_PHENOME(L);
HANDLE_PHENOME(M);
HANDLE_PHENOME(N);
HANDLE_PHENOME(P);
HANDLE_PHENOME(R);
HANDLE_PHENOME(S);
HANDLE_PHENOME(T);
HANDLE_PHENOME(V);
HANDLE_PHENOME(Z);
}
return VMorpherPhenome::A;
#undef HANDLE_PHENOME
};
auto get_waveform = [](unsigned long form) noexcept
{
if(form == EAX_VOCALMORPHER_SINUSOID) return VMorpherWaveform::Sinusoid;
if(form == EAX_VOCALMORPHER_TRIANGLE) return VMorpherWaveform::Triangle;
if(form == EAX_VOCALMORPHER_SAWTOOTH) return VMorpherWaveform::Sawtooth;
return VMorpherWaveform::Sinusoid;
};
mAlProps.Vmorpher.PhonemeA = get_phoneme(props.mVocalMorpher.ulPhonemeA);
mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeACoarseTuning);
mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB);
mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast<int>(props.mVocalMorpher.lPhonemeBCoarseTuning);
mAlProps.Vmorpher.Waveform = get_waveform(props.mVocalMorpher.ulWaveform);
mAlProps.Vmorpher.Rate = props.mVocalMorpher.flRate;
return true;
}
template<>
void VocalMorpherCommitter::SetDefaults(EaxEffectProps &props)
{
props.mType = EaxEffectType::VocalMorpher;
props.mVocalMorpher.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA;
props.mVocalMorpher.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING;
props.mVocalMorpher.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB;
props.mVocalMorpher.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING;
props.mVocalMorpher.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM;
props.mVocalMorpher.flRate = EAXVOCALMORPHER_DEFAULTRATE;
}
template<>
void VocalMorpherCommitter::Get(const EaxCall &call, const EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXVOCALMORPHER_NONE:
break;
case EAXVOCALMORPHER_ALLPARAMETERS:
call.set_value<Exception>(props.mVocalMorpher);
break;
case EAXVOCALMORPHER_PHONEMEA:
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
call.set_value<Exception>(props.mVocalMorpher.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
call.set_value<Exception>(props.mVocalMorpher.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
call.set_value<Exception>(props.mVocalMorpher.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
call.set_value<Exception>(props.mVocalMorpher.flRate);
break;
default:
fail_unknown_property_id();
}
}
template<>
void VocalMorpherCommitter::Set(const EaxCall &call, EaxEffectProps &props)
{
switch(call.get_property_id())
{
case EAXVOCALMORPHER_NONE:
break;
case EAXVOCALMORPHER_ALLPARAMETERS:
defer<AllValidator>(call, props.mVocalMorpher);
break;
case EAXVOCALMORPHER_PHONEMEA:
defer<PhonemeAValidator>(call, props.mVocalMorpher.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
defer<PhonemeACoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
defer<PhonemeBValidator>(call, props.mVocalMorpher.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
defer<PhonemeBCoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
defer<WaveformValidator>(call, props.mVocalMorpher.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
defer<RateValidator>(call, props.mVocalMorpher.flRate);
break;
default:
fail_unknown_property_id();
}
}
#endif // ALSOFT_EAX

106
externals/openal-soft/al/error.cpp vendored Normal file
View File

@@ -0,0 +1,106 @@
/**
* 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 "alc/context.h"
#include "almalloc.h"
#include "core/except.h"
#include "core/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>";
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);
}
AL_API ALenum AL_APIENTRY alGetError(void)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY
{
static 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

215
externals/openal-soft/al/event.cpp vendored Normal file
View File

@@ -0,0 +1,215 @@
#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 "alc/context.h"
#include "alc/effects/base.h"
#include "alc/inprogext.h"
#include "almalloc.h"
#include "core/async_event.h"
#include "core/except.h"
#include "core/logging.h"
#include "core/voice_change.h"
#include "opthelpers.h"
#include "ringbuffer.h"
#include "threads.h"
static int EventThread(ALCcontext *context)
{
RingBuffer *ring{context->mAsyncEvents.get()};
bool quitnow{false};
while(!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 == AsyncEvent::KillThread;
if(quitnow) UNLIKELY break;
if(evt.EnumType == AsyncEvent::ReleaseEffectState)
{
al::intrusive_ptr<EffectState>{evt.u.mEffectState};
continue;
}
auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire);
if(!context->mEventCb || !enabledevts.test(evt.EnumType))
continue;
if(evt.EnumType == AsyncEvent::SourceStateChange)
{
ALuint state{};
std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)};
msg += " state has changed to ";
switch(evt.u.srcstate.state)
{
case AsyncEvent::SrcState::Reset:
msg += "AL_INITIAL";
state = AL_INITIAL;
break;
case AsyncEvent::SrcState::Stop:
msg += "AL_STOPPED";
state = AL_STOPPED;
break;
case AsyncEvent::SrcState::Play:
msg += "AL_PLAYING";
state = AL_PLAYING;
break;
case AsyncEvent::SrcState::Pause:
msg += "AL_PAUSED";
state = AL_PAUSED;
break;
}
context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id,
state, static_cast<ALsizei>(msg.length()), msg.c_str(), context->mEventParam);
}
else if(evt.EnumType == AsyncEvent::BufferCompleted)
{
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(evt.EnumType == AsyncEvent::Disconnected)
{
context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0,
static_cast<ALsizei>(strlen(evt.u.disconnect.msg)), evt.u.disconnect.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);
}
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), AsyncEvent::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(!context) UNLIKELY return;
if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
if(count <= 0) return;
if(!types) return context->setError(AL_INVALID_VALUE, "NULL pointer");
ContextBase::AsyncEventBitset flags{};
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.set(AsyncEvent::BufferCompleted);
else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
flags.set(AsyncEvent::SourceStateChange);
else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
flags.set(AsyncEvent::Disconnected);
else
return false;
return true;
}
);
if(bad_type != types_end)
return context->setError(AL_INVALID_ENUM, "Invalid event type 0x%04x", *bad_type);
if(enable)
{
auto 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
{
auto 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(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEventCbLock};
context->mEventCb = callback;
context->mEventParam = userParam;
}
END_API_FUNC

9
externals/openal-soft/al/event.h vendored Normal file
View File

@@ -0,0 +1,9 @@
#ifndef AL_EVENT_H
#define AL_EVENT_H
struct ALCcontext;
void StartEventThrd(ALCcontext *ctx);
void StopEventThrd(ALCcontext *ctx);
#endif

82
externals/openal-soft/al/extension.cpp vendored Normal file
View File

@@ -0,0 +1,82 @@
/**
* 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 "alc/context.h"
#include "alstring.h"
#include "core/except.h"
#include "opthelpers.h"
AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return AL_FALSE;
if(!extName) UNLIKELY
{
context->setError(AL_INVALID_VALUE, "NULL pointer");
return AL_FALSE;
}
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

722
externals/openal-soft/al/filter.cpp vendored Normal file
View File

@@ -0,0 +1,722 @@
/**
* 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 <cstdarg>
#include <cstdint>
#include <cstdio>
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <numeric>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "albit.h"
#include "alc/context.h"
#include "alc/device.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "core/except.h"
#include "opthelpers.h"
#include "vector.h"
namespace {
class filter_exception final : public al::base_exception {
ALenum mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
filter_exception(ALenum code, const char *msg, ...);
~filter_exception() override;
ALenum errorCode() const noexcept { return mErrorCode; }
};
filter_exception::filter_exception(ALenum code, const char* msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
filter_exception::~filter_exception() = default;
#define DEFINE_ALFILTER_VTABLE(T) \
const ALfilter::Vtable T##_vtable = { \
T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \
T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \
}
void ALlowpass_setParami(ALfilter*, ALenum param, int)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; }
void ALlowpass_setParamiv(ALfilter*, ALenum param, const int*)
{
throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x",
param};
}
void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val)
{
switch(param)
{
case AL_LOWPASS_GAIN:
if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN))
throw filter_exception{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))
throw filter_exception{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val};
filter->GainHF = val;
break;
default:
throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param};
}
}
void ALlowpass_setParamfv(ALfilter *filter, ALenum param, const float *vals)
{ ALlowpass_setParamf(filter, param, vals[0]); }
void ALlowpass_getParami(const ALfilter*, ALenum param, int*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; }
void ALlowpass_getParamiv(const ALfilter*, ALenum param, int*)
{
throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x",
param};
}
void ALlowpass_getParamf(const ALfilter *filter, ALenum param, float *val)
{
switch(param)
{
case AL_LOWPASS_GAIN:
*val = filter->Gain;
break;
case AL_LOWPASS_GAINHF:
*val = filter->GainHF;
break;
default:
throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param};
}
}
void ALlowpass_getParamfv(const ALfilter *filter, ALenum param, float *vals)
{ ALlowpass_getParamf(filter, param, vals); }
DEFINE_ALFILTER_VTABLE(ALlowpass);
void ALhighpass_setParami(ALfilter*, ALenum param, int)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; }
void ALhighpass_setParamiv(ALfilter*, ALenum param, const int*)
{
throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x",
param};
}
void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val)
{
switch(param)
{
case AL_HIGHPASS_GAIN:
if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN))
throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val};
filter->Gain = val;
break;
case AL_HIGHPASS_GAINLF:
if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF))
throw filter_exception{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val};
filter->GainLF = val;
break;
default:
throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param};
}
}
void ALhighpass_setParamfv(ALfilter *filter, ALenum param, const float *vals)
{ ALhighpass_setParamf(filter, param, vals[0]); }
void ALhighpass_getParami(const ALfilter*, ALenum param, int*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; }
void ALhighpass_getParamiv(const ALfilter*, ALenum param, int*)
{
throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x",
param};
}
void ALhighpass_getParamf(const ALfilter *filter, ALenum param, float *val)
{
switch(param)
{
case AL_HIGHPASS_GAIN:
*val = filter->Gain;
break;
case AL_HIGHPASS_GAINLF:
*val = filter->GainLF;
break;
default:
throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param};
}
}
void ALhighpass_getParamfv(const ALfilter *filter, ALenum param, float *vals)
{ ALhighpass_getParamf(filter, param, vals); }
DEFINE_ALFILTER_VTABLE(ALhighpass);
void ALbandpass_setParami(ALfilter*, ALenum param, int)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; }
void ALbandpass_setParamiv(ALfilter*, ALenum param, const int*)
{
throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x",
param};
}
void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val)
{
switch(param)
{
case AL_BANDPASS_GAIN:
if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN))
throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val};
filter->Gain = val;
break;
case AL_BANDPASS_GAINHF:
if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF))
throw filter_exception{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val};
filter->GainHF = val;
break;
case AL_BANDPASS_GAINLF:
if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF))
throw filter_exception{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val};
filter->GainLF = val;
break;
default:
throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param};
}
}
void ALbandpass_setParamfv(ALfilter *filter, ALenum param, const float *vals)
{ ALbandpass_setParamf(filter, param, vals[0]); }
void ALbandpass_getParami(const ALfilter*, ALenum param, int*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; }
void ALbandpass_getParamiv(const ALfilter*, ALenum param, int*)
{
throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x",
param};
}
void ALbandpass_getParamf(const ALfilter *filter, ALenum param, float *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:
throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param};
}
}
void ALbandpass_getParamfv(const ALfilter *filter, ALenum param, float *vals)
{ ALbandpass_getParamf(filter, param, vals); }
DEFINE_ALFILTER_VTABLE(ALbandpass);
void ALnullfilter_setParami(ALfilter*, ALenum param, int)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_setParamiv(ALfilter*, ALenum param, const int*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_setParamf(ALfilter*, ALenum param, float)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_setParamfv(ALfilter*, ALenum param, const float*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_getParami(const ALfilter*, ALenum param, int*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_getParamiv(const ALfilter*, ALenum param, int*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_getParamf(const ALfilter*, ALenum param, float*)
{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; }
void ALnullfilter_getParamfv(const ALfilter*, ALenum param, float*)
{ throw filter_exception{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>(al::popcount(sublist.FreeMask)); })};
while(needed > count)
{
if(device->FilterList.size() >= 1<<25) UNLIKELY
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(!sublist->Filters) UNLIKELY
{
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>(al::countr_zero(sublist->FreeMask));
ASSUME(slidx < 64);
ALfilter *filter{al::construct_at(sublist->Filters + slidx)};
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(lidx >= device->FilterList.size()) UNLIKELY
return nullptr;
FilterSubList &sublist = device->FilterList[lidx];
if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY
return nullptr;
return sublist.Filters + slidx;
}
} // namespace
AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
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(n == 1) LIKELY
{
/* 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 void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(n < 0) UNLIKELY
context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
if(n <= 0) UNLIKELY return;
ALCdevice *device{context->mALDevice.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(invflt != filters_end) UNLIKELY
{
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(context) LIKELY
{
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
if(!filter || LookupFilter(device, filter))
return AL_TRUE;
}
return AL_FALSE;
}
END_API_FUNC
AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
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 try
{
/* Call the appropriate handler */
alfilt->setParami(param, value);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
}
END_API_FUNC
AL_API void 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(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else try
{
/* Call the appropriate handler */
alfilt->setParamiv(param, values);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else try
{
/* Call the appropriate handler */
alfilt->setParamf(param, value);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else try
{
/* Call the appropriate handler */
alfilt->setParamfv(param, values);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else
{
if(param == AL_FILTER_TYPE)
*value = alfilt->type;
else try
{
/* Call the appropriate handler */
alfilt->getParami(param, value);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
}
END_API_FUNC
AL_API void 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(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else try
{
/* Call the appropriate handler */
alfilt->getParamiv(param, values);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else try
{
/* Call the appropriate handler */
alfilt->getParamf(param, value);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> _{device->FilterLock};
const ALfilter *alfilt{LookupFilter(device, filter)};
if(!alfilt) UNLIKELY
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
else try
{
/* Call the appropriate handler */
alfilt->getParamfv(param, values);
}
catch(filter_exception &e) {
context->setError(e.errorCode(), "%s", e.what());
}
}
END_API_FUNC
FilterSubList::~FilterSubList()
{
uint64_t usemask{~FreeMask};
while(usemask)
{
const int idx{al::countr_zero(usemask)};
al::destroy_at(Filters+idx);
usemask &= ~(1_u64 << idx);
}
FreeMask = ~usemask;
al_free(Filters);
Filters = nullptr;
}

52
externals/openal-soft/al/filter.h vendored Normal file
View File

@@ -0,0 +1,52 @@
#ifndef AL_FILTER_H
#define AL_FILTER_H
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "almalloc.h"
#define LOWPASSFREQREF 5000.0f
#define HIGHPASSFREQREF 250.0f
struct ALfilter {
ALenum type{AL_FILTER_NULL};
float Gain{1.0f};
float GainHF{1.0f};
float HFReference{LOWPASSFREQREF};
float GainLF{1.0f};
float LFReference{HIGHPASSFREQREF};
struct Vtable {
void (*const setParami )(ALfilter *filter, ALenum param, int val);
void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals);
void (*const setParamf )(ALfilter *filter, ALenum param, float val);
void (*const setParamfv)(ALfilter *filter, ALenum param, const float *vals);
void (*const getParami )(const ALfilter *filter, ALenum param, int *val);
void (*const getParamiv)(const ALfilter *filter, ALenum param, int *vals);
void (*const getParamf )(const ALfilter *filter, ALenum param, float *val);
void (*const getParamfv)(const ALfilter *filter, ALenum param, float *vals);
};
const Vtable *vtab{nullptr};
/* Self ID */
ALuint id{0};
void setParami(ALenum param, int value) { vtab->setParami(this, param, value); }
void setParamiv(ALenum param, const int *values) { vtab->setParamiv(this, param, values); }
void setParamf(ALenum param, float value) { vtab->setParamf(this, param, value); }
void setParamfv(ALenum param, const float *values) { vtab->setParamfv(this, param, values); }
void getParami(ALenum param, int *value) const { vtab->getParami(this, param, value); }
void getParamiv(ALenum param, int *values) const { vtab->getParamiv(this, param, values); }
void getParamf(ALenum param, float *value) const { vtab->getParamf(this, param, value); }
void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); }
DISABLE_ALLOC()
};
#endif

444
externals/openal-soft/al/listener.cpp vendored Normal file
View File

@@ -0,0 +1,444 @@
/**
* 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 "alc/context.h"
#include "almalloc.h"
#include "atomic.h"
#include "core/except.h"
#include "opthelpers.h"
namespace {
inline void UpdateProps(ALCcontext *context)
{
if(!context->mDeferUpdates)
{
UpdateContextProps(context);
return;
}
context->mPropsDirty = true;
}
inline void CommitAndUpdateProps(ALCcontext *context)
{
if(!context->mDeferUpdates)
{
#ifdef ALSOFT_EAX
if(context->eaxNeedsCommit())
{
context->mPropsDirty = true;
context->applyAllUpdates();
return;
}
#endif
UpdateContextProps(context);
return;
}
context->mPropsDirty = true;
}
} // namespace
AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
switch(param)
{
case AL_GAIN:
if(!(value >= 0.0f && std::isfinite(value)))
return context->setError(AL_INVALID_VALUE, "Listener gain out of range");
listener.Gain = value;
UpdateProps(context.get());
break;
case AL_METERS_PER_UNIT:
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
return context->setError(AL_INVALID_VALUE, "Listener meters per unit out of range");
listener.mMetersPerUnit = value;
UpdateProps(context.get());
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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)))
return context->setError(AL_INVALID_VALUE, "Listener position out of range");
listener.Position[0] = value1;
listener.Position[1] = value2;
listener.Position[2] = value3;
CommitAndUpdateProps(context.get());
break;
case AL_VELOCITY:
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
return context->setError(AL_INVALID_VALUE, "Listener velocity out of range");
listener.Velocity[0] = value1;
listener.Velocity[1] = value2;
listener.Velocity[2] = value3;
CommitAndUpdateProps(context.get());
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
}
}
END_API_FUNC
AL_API void 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(!context) UNLIKELY return;
if(!values) UNLIKELY
return context->setError(AL_INVALID_VALUE, "NULL pointer");
ALlistener &listener = context->mListener;
std::lock_guard<std::mutex> _{context->mPropLock};
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])))
return context->setError(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];
CommitAndUpdateProps(context.get());
break;
default:
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
}
}
END_API_FUNC
AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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(!context) UNLIKELY 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(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
if(!values) UNLIKELY
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 void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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 void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 void AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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(!context) UNLIKELY 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(!context) UNLIKELY 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

24
externals/openal-soft/al/listener.h vendored Normal file
View File

@@ -0,0 +1,24 @@
#ifndef AL_LISTENER_H
#define AL_LISTENER_H
#include <array>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/efx.h"
#include "almalloc.h"
struct ALlistener {
std::array<float,3> Position{{0.0f, 0.0f, 0.0f}};
std::array<float,3> Velocity{{0.0f, 0.0f, 0.0f}};
std::array<float,3> OrientAt{{0.0f, 0.0f, -1.0f}};
std::array<float,3> OrientUp{{0.0f, 1.0f, 0.0f}};
float Gain{1.0f};
float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT};
DISABLE_ALLOC()
};
#endif

5345
externals/openal-soft/al/source.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

1044
externals/openal-soft/al/source.h vendored Normal file

File diff suppressed because it is too large Load Diff

963
externals/openal-soft/al/state.cpp vendored Normal file
View File

@@ -0,0 +1,963 @@
/**
* 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 <mutex>
#include <stdexcept>
#include <string>
#include "AL/al.h"
#include "AL/alc.h"
#include "AL/alext.h"
#include "alc/alu.h"
#include "alc/context.h"
#include "alc/inprogext.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "atomic.h"
#include "core/context.h"
#include "core/except.h"
#include "core/mixer/defs.h"
#include "core/voice.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "strutils.h"
#ifdef ALSOFT_EAX
#include "alc/device.h"
#include "eax/globals.h"
#include "eax/x_ram.h"
#endif // ALSOFT_EAX
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"};
}
al::optional<DistanceModel> DistanceModelFromALenum(ALenum model)
{
switch(model)
{
case AL_NONE: return DistanceModel::Disable;
case AL_INVERSE_DISTANCE: return DistanceModel::Inverse;
case AL_INVERSE_DISTANCE_CLAMPED: return DistanceModel::InverseClamped;
case AL_LINEAR_DISTANCE: return DistanceModel::Linear;
case AL_LINEAR_DISTANCE_CLAMPED: return DistanceModel::LinearClamped;
case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent;
case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped;
}
return al::nullopt;
}
ALenum ALenumFromDistanceModel(DistanceModel model)
{
switch(model)
{
case DistanceModel::Disable: return AL_NONE;
case DistanceModel::Inverse: return AL_INVERSE_DISTANCE;
case DistanceModel::InverseClamped: return AL_INVERSE_DISTANCE_CLAMPED;
case DistanceModel::Linear: return AL_LINEAR_DISTANCE;
case DistanceModel::LinearClamped: return AL_LINEAR_DISTANCE_CLAMPED;
case DistanceModel::Exponent: return AL_EXPONENT_DISTANCE;
case DistanceModel::ExponentClamped: return AL_EXPONENT_DISTANCE_CLAMPED;
}
throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast<int>(model))};
}
} // namespace
/* WARNING: Non-standard export! Not part of any extension, or exposed in the
* alcFunctions list.
*/
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) \
UpdateContextProps(context.get()); \
else \
context->mPropsDirty = true; \
} while(0)
AL_API void AL_APIENTRY alEnable(ALenum capability)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
switch(capability)
{
case AL_SOURCE_DISTANCE_MODEL:
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mSourceDistanceModel = true;
DO_UPDATEPROPS();
}
break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported");
break;
default:
context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability);
}
}
END_API_FUNC
AL_API void AL_APIENTRY alDisable(ALenum capability)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
switch(capability)
{
case AL_SOURCE_DISTANCE_MODEL:
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mSourceDistanceModel = false;
DO_UPDATEPROPS();
}
break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
context->mStopVoicesOnDisconnect = false;
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(!context) UNLIKELY return AL_FALSE;
std::lock_guard<std::mutex> _{context->mPropLock};
ALboolean value{AL_FALSE};
switch(capability)
{
case AL_SOURCE_DISTANCE_MODEL:
value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE;
break;
case AL_STOP_SOURCES_ON_DISCONNECT_SOFT:
value = context->mStopVoicesOnDisconnect ? AL_TRUE : AL_FALSE;
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(!context) UNLIKELY 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)
value = AL_TRUE;
break;
case AL_GAIN_LIMIT_SOFT:
if(GainMixMax/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(!context) UNLIKELY 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>(ALenumFromDistanceModel(context->mDistanceModel));
break;
case AL_SPEED_OF_SOUND:
value = context->mSpeedOfSound;
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates)
value = static_cast<ALdouble>(AL_TRUE);
break;
case AL_GAIN_LIMIT_SOFT:
value = ALdouble{GainMixMax}/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(!context) UNLIKELY 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>(ALenumFromDistanceModel(context->mDistanceModel));
break;
case AL_SPEED_OF_SOUND:
value = context->mSpeedOfSound;
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates)
value = static_cast<ALfloat>(AL_TRUE);
break;
case AL_GAIN_LIMIT_SOFT:
value = GainMixMax/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(!context) UNLIKELY 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 = ALenumFromDistanceModel(context->mDistanceModel);
break;
case AL_SPEED_OF_SOUND:
value = static_cast<ALint>(context->mSpeedOfSound);
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates)
value = AL_TRUE;
break;
case AL_GAIN_LIMIT_SOFT:
value = static_cast<ALint>(GainMixMax/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;
#ifdef ALSOFT_EAX
#define EAX_ERROR "[alGetInteger] EAX not enabled."
case AL_EAX_RAM_SIZE:
if (eax_g_is_enabled)
{
value = eax_x_ram_max_size;
}
else
{
context->setError(AL_INVALID_VALUE, EAX_ERROR);
}
break;
case AL_EAX_RAM_FREE:
if (eax_g_is_enabled)
{
auto device = context->mALDevice.get();
std::lock_guard<std::mutex> device_lock{device->BufferLock};
value = static_cast<ALint>(device->eax_x_ram_free_size);
}
else
{
context->setError(AL_INVALID_VALUE, EAX_ERROR);
}
break;
#undef EAX_ERROR
#endif // ALSOFT_EAX
default:
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
}
return value;
}
END_API_FUNC
AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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 = ALenumFromDistanceModel(context->mDistanceModel);
break;
case AL_SPEED_OF_SOUND:
value = static_cast<ALint64SOFT>(context->mSpeedOfSound);
break;
case AL_DEFERRED_UPDATES_SOFT:
if(context->mDeferUpdates)
value = AL_TRUE;
break;
case AL_GAIN_LIMIT_SOFT:
value = static_cast<ALint64SOFT>(GainMixMax/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 ALvoid* AL_APIENTRY alGetPointerSOFT(ALenum pname)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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 void 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(!context) UNLIKELY 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
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(!context) UNLIKELY 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, ALvoid **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(!context) UNLIKELY 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(!context) UNLIKELY 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 void AL_APIENTRY alDopplerFactor(ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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 void AL_APIENTRY alDopplerVelocity(ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
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 void AL_APIENTRY alSpeedOfSound(ALfloat value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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 void AL_APIENTRY alDistanceModel(ALenum value)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
if(auto model = DistanceModelFromALenum(value))
{
std::lock_guard<std::mutex> _{context->mPropLock};
context->mDistanceModel = *model;
if(!context->mSourceDistanceModel)
DO_UPDATEPROPS();
}
else
context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value);
}
END_API_FUNC
AL_API void AL_APIENTRY alDeferUpdatesSOFT(void)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
context->deferUpdates();
}
END_API_FUNC
AL_API void AL_APIENTRY alProcessUpdatesSOFT(void)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{context->mPropLock};
context->processUpdates();
}
END_API_FUNC
AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
START_API_FUNC
{
ContextRef context{GetContextRef()};
if(!context) UNLIKELY 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. */
ContextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)};
if(!props)
props = new ContextProps{};
else
{
ContextProps *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. */
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;
props->AirAbsorptionGainHF = context->mAirAbsorptionGainHF;
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->mParams.ContextUpdate.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);
}
}

4125
externals/openal-soft/alc/alc.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

528
externals/openal-soft/alc/alconfig.cpp vendored Normal file
View File

@@ -0,0 +1,528 @@
/**
* 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 "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32
#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 "core/helpers.h"
#include "core/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] == '[')
{
auto line = const_cast<char*>(buffer.data());
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();
}
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName)
{
if(!keyName)
return nullptr;
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 nullptr;
}
if(!devName)
{
TRACE("Key %s not found\n", key.c_str());
return nullptr;
}
return GetConfigValue(nullptr, blockName, keyName);
}
} // 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
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return val;
return al::nullopt;
}
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<int>(std::strtol(val, nullptr, 0));
return al::nullopt;
}
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return static_cast<unsigned int>(std::strtoul(val, nullptr, 0));
return al::nullopt;
}
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return std::strtof(val, nullptr);
return al::nullopt;
}
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true")==0 || atoi(val) != 0;
return al::nullopt;
}
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def)
{
if(const char *val{GetConfigValue(devName, blockName, keyName)})
return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0
|| al::strcasecmp(val, "true") == 0 || atoi(val) != 0);
return def;
}

18
externals/openal-soft/alc/alconfig.h vendored Normal file
View File

@@ -0,0 +1,18 @@
#ifndef ALCONFIG_H
#define ALCONFIG_H
#include <string>
#include "aloptional.h"
void ReadALConfig();
bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool 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 */

2210
externals/openal-soft/alc/alu.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

38
externals/openal-soft/alc/alu.h vendored Normal file
View File

@@ -0,0 +1,38 @@
#ifndef ALU_H
#define ALU_H
#include <bitset>
#include "aloptional.h"
struct ALCcontext;
struct ALCdevice;
struct EffectSlot;
enum class StereoEncoding : unsigned char;
constexpr float GainMixMax{1000.0f}; /* +60dB */
enum CompatFlags : uint8_t {
ReverseX,
ReverseY,
ReverseZ,
Count
};
using CompatFlagBitset = std::bitset<CompatFlags::Count>;
void aluInit(CompatFlagBitset flags, const float nfcscale);
/* aluInitRenderer
*
* Set up the appropriate panning method and mixing method given the device
* properties.
*/
void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional<StereoEncoding> stereomode);
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_ALSA_H
#define BACKENDS_ALSA_H
#include "base.h"
struct AlsaBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_ALSA_H */

View File

@@ -0,0 +1,202 @@
#include "config.h"
#include "base.h"
#include <algorithm>
#include <array>
#include <atomic>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#include "albit.h"
#include "core/logging.h"
#include "aloptional.h"
#endif
#include "atomic.h"
#include "core/devformat.h"
namespace al {
backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code}
{
std::va_list args;
va_start(args, msg);
setMessage(msg, args);
va_end(args);
}
backend_exception::~backend_exception() = default;
} // namespace al
bool BackendBase::reset()
{ throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; }
void BackendBase::captureSamples(al::byte*, uint)
{ }
uint BackendBase::availableSamples()
{ return 0; }
ClockLatency BackendBase::getClockLatency()
{
ClockLatency ret;
uint refcount;
do {
refcount = mDevice->waitForMix();
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;
}
void BackendBase::setDefaultWFXChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
switch(mDevice->FmtChans)
{
case DevFmtMono:
mDevice->RealOut.ChannelIndex[FrontCenter] = 0;
break;
case DevFmtStereo:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
break;
case DevFmtQuad:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
break;
case DevFmtX51:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[SideLeft] = 4;
mDevice->RealOut.ChannelIndex[SideRight] = 5;
break;
case DevFmtX61:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackCenter] = 4;
mDevice->RealOut.ChannelIndex[SideLeft] = 5;
mDevice->RealOut.ChannelIndex[SideRight] = 6;
break;
case DevFmtX71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
break;
case DevFmtX714:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[BackLeft] = 4;
mDevice->RealOut.ChannelIndex[BackRight] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[FrontCenter] = 2;
mDevice->RealOut.ChannelIndex[LFE] = 3;
mDevice->RealOut.ChannelIndex[Aux0] = 4;
mDevice->RealOut.ChannelIndex[Aux1] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
break;
case DevFmtAmbi3D:
break;
}
}
void BackendBase::setDefaultChannelOrder()
{
mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex);
switch(mDevice->FmtChans)
{
case DevFmtX51:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[SideLeft] = 2;
mDevice->RealOut.ChannelIndex[SideRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
return;
case DevFmtX71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
return;
case DevFmtX714:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[BackLeft] = 2;
mDevice->RealOut.ChannelIndex[BackRight] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8;
mDevice->RealOut.ChannelIndex[TopFrontRight] = 9;
mDevice->RealOut.ChannelIndex[TopBackLeft] = 10;
mDevice->RealOut.ChannelIndex[TopBackRight] = 11;
break;
case DevFmtX3D71:
mDevice->RealOut.ChannelIndex[FrontLeft] = 0;
mDevice->RealOut.ChannelIndex[FrontRight] = 1;
mDevice->RealOut.ChannelIndex[Aux0] = 2;
mDevice->RealOut.ChannelIndex[Aux1] = 3;
mDevice->RealOut.ChannelIndex[FrontCenter] = 4;
mDevice->RealOut.ChannelIndex[LFE] = 5;
mDevice->RealOut.ChannelIndex[SideLeft] = 6;
mDevice->RealOut.ChannelIndex[SideRight] = 7;
return;
/* Same as WFX order */
case DevFmtMono:
case DevFmtStereo:
case DevFmtQuad:
case DevFmtX61:
case DevFmtAmbi3D:
setDefaultWFXChannelOrder();
break;
}
}

View File

@@ -0,0 +1,114 @@
#ifndef ALC_BACKENDS_BASE_H
#define ALC_BACKENDS_BASE_H
#include <chrono>
#include <cstdarg>
#include <memory>
#include <ratio>
#include <string>
#include "albyte.h"
#include "core/device.h"
#include "core/except.h"
using uint = unsigned int;
struct ClockLatency {
std::chrono::nanoseconds ClockTime;
std::chrono::nanoseconds Latency;
};
struct BackendBase {
virtual void open(const char *name) = 0;
virtual bool reset();
virtual void start() = 0;
virtual void stop() = 0;
virtual void captureSamples(al::byte *buffer, uint samples);
virtual uint availableSamples();
virtual ClockLatency getClockLatency();
DeviceBase *const mDevice;
BackendBase(DeviceBase *device) noexcept : mDevice{device} { }
virtual ~BackendBase() = default;
protected:
/** Sets the default channel order used by most non-WaveFormatEx-based APIs. */
void setDefaultChannelOrder();
/** Sets the default channel order used by WaveFormatEx. */
void setDefaultWFXChannelOrder();
};
using BackendPtr = std::unique_ptr<BackendBase>;
enum class BackendType {
Playback,
Capture
};
/* Helper to get the current clock time from the device's ClockBase, and
* SamplesDone converted from the sample rate.
*/
inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device)
{
using std::chrono::seconds;
using std::chrono::nanoseconds;
auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency;
return device->ClockBase + ns;
}
/* Helper to get the device latency from the backend, including any fixed
* latency from post-processing.
*/
inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend)
{
ClockLatency ret{backend->getClockLatency()};
ret.Latency += device->FixedLatency;
return ret;
}
struct BackendFactory {
virtual bool init() = 0;
virtual bool querySupport(BackendType type) = 0;
virtual std::string probe(BackendType type) = 0;
virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0;
protected:
virtual ~BackendFactory() = default;
};
namespace al {
enum class backend_error {
NoDevice,
DeviceError,
OutOfMemory
};
class backend_exception final : public base_exception {
backend_error mErrorCode;
public:
#ifdef __USE_MINGW_ANSI_STDIO
[[gnu::format(gnu_printf, 3, 4)]]
#else
[[gnu::format(printf, 3, 4)]]
#endif
backend_exception(backend_error code, const char *msg, ...);
~backend_exception() override;
backend_error errorCode() const noexcept { return mErrorCode; }
};
} // namespace al
#endif /* ALC_BACKENDS_BASE_H */

View File

@@ -0,0 +1,932 @@
/**
* 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 "coreaudio.h"
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cmath>
#include <memory>
#include <string>
#include "alnumeric.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
#if TARGET_OS_IOS || TARGET_OS_TV
#define CAN_ENUMERATE 0
#else
#define CAN_ENUMERATE 1
#endif
constexpr auto OutputElement = 0;
constexpr auto InputElement = 1;
#if CAN_ENUMERATE
struct DeviceEntry {
AudioDeviceID mId;
std::string mName;
};
std::vector<DeviceEntry> PlaybackList;
std::vector<DeviceEntry> CaptureList;
OSStatus GetHwProperty(AudioHardwarePropertyID propId, UInt32 dataSize, void *propData)
{
const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
return AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, 0, nullptr, &dataSize,
propData);
}
OSStatus GetHwPropertySize(AudioHardwarePropertyID propId, UInt32 *outSize)
{
const AudioObjectPropertyAddress addr{propId, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster};
return AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, 0, nullptr, outSize);
}
OSStatus GetDevProperty(AudioDeviceID devId, AudioDevicePropertyID propId, bool isCapture,
UInt32 elem, UInt32 dataSize, void *propData)
{
static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
kAudioDevicePropertyScopeInput};
const AudioObjectPropertyAddress addr{propId, scopes[isCapture], elem};
return AudioObjectGetPropertyData(devId, &addr, 0, nullptr, &dataSize, propData);
}
OSStatus GetDevPropertySize(AudioDeviceID devId, AudioDevicePropertyID inPropertyID,
bool isCapture, UInt32 elem, UInt32 *outSize)
{
static const AudioObjectPropertyScope scopes[2]{kAudioDevicePropertyScopeOutput,
kAudioDevicePropertyScopeInput};
const AudioObjectPropertyAddress addr{inPropertyID, scopes[isCapture], elem};
return AudioObjectGetPropertyDataSize(devId, &addr, 0, nullptr, outSize);
}
std::string GetDeviceName(AudioDeviceID devId)
{
std::string devname;
CFStringRef nameRef;
/* Try to get the device name as a CFString, for Unicode name support. */
OSStatus err{GetDevProperty(devId, kAudioDevicePropertyDeviceNameCFString, false, 0,
sizeof(nameRef), &nameRef)};
if(err == noErr)
{
const CFIndex propSize{CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
kCFStringEncodingUTF8)};
devname.resize(static_cast<size_t>(propSize)+1, '\0');
CFStringGetCString(nameRef, &devname[0], propSize+1, kCFStringEncodingUTF8);
CFRelease(nameRef);
}
else
{
/* If that failed, just get the C string. Hopefully there's nothing bad
* with this.
*/
UInt32 propSize{};
if(GetDevPropertySize(devId, kAudioDevicePropertyDeviceName, false, 0, &propSize))
return devname;
devname.resize(propSize+1, '\0');
if(GetDevProperty(devId, kAudioDevicePropertyDeviceName, false, 0, propSize, &devname[0]))
{
devname.clear();
return devname;
}
}
/* Clear extraneous nul chars that may have been written with the name
* string, and return it.
*/
while(!devname.back())
devname.pop_back();
return devname;
}
UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture)
{
UInt32 propSize{};
auto err = GetDevPropertySize(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0,
&propSize);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err);
return 0;
}
auto buflist_data = std::make_unique<char[]>(propSize);
auto *buflist = reinterpret_cast<AudioBufferList*>(buflist_data.get());
err = GetDevProperty(devId, kAudioDevicePropertyStreamConfiguration, isCapture, 0, propSize,
buflist);
if(err)
{
ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err);
return 0;
}
UInt32 numChannels{0};
for(size_t i{0};i < buflist->mNumberBuffers;++i)
numChannels += buflist->mBuffers[i].mNumberChannels;
return numChannels;
}
void EnumerateDevices(std::vector<DeviceEntry> &list, bool isCapture)
{
UInt32 propSize{};
if(auto err = GetHwPropertySize(kAudioHardwarePropertyDevices, &propSize))
{
ERR("Failed to get device list size: %u\n", err);
return;
}
auto devIds = std::vector<AudioDeviceID>(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown);
if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data()))
{
ERR("Failed to get device list: %u\n", err);
return;
}
std::vector<DeviceEntry> newdevs;
newdevs.reserve(devIds.size());
AudioDeviceID defaultId{kAudioDeviceUnknown};
GetHwProperty(isCapture ? kAudioHardwarePropertyDefaultInputDevice :
kAudioHardwarePropertyDefaultOutputDevice, sizeof(defaultId), &defaultId);
if(defaultId != kAudioDeviceUnknown)
{
newdevs.emplace_back(DeviceEntry{defaultId, GetDeviceName(defaultId)});
const auto &entry = newdevs.back();
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
}
for(const AudioDeviceID devId : devIds)
{
if(devId == kAudioDeviceUnknown)
continue;
auto match_devid = [devId](const DeviceEntry &entry) noexcept -> bool
{ return entry.mId == devId; };
auto match = std::find_if(newdevs.cbegin(), newdevs.cend(), match_devid);
if(match != newdevs.cend()) continue;
auto numChannels = GetDeviceChannelCount(devId, isCapture);
if(numChannels > 0)
{
newdevs.emplace_back(DeviceEntry{devId, GetDeviceName(devId)});
const auto &entry = newdevs.back();
TRACE("Got device: %s = ID %u\n", entry.mName.c_str(), entry.mId);
}
}
if(newdevs.size() > 1)
{
/* Rename entries that have matching names, by appending '#2', '#3',
* etc, as needed.
*/
for(auto curitem = newdevs.begin()+1;curitem != newdevs.end();++curitem)
{
auto check_match = [curitem](const DeviceEntry &entry) -> bool
{ return entry.mName == curitem->mName; };
if(std::find_if(newdevs.begin(), curitem, check_match) != curitem)
{
std::string name{curitem->mName};
size_t count{1};
auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
do {
name = curitem->mName;
name += " #";
name += std::to_string(++count);
} while(std::find_if(newdevs.begin(), curitem, check_name) != curitem);
curitem->mName = std::move(name);
}
}
}
newdevs.shrink_to_fit();
newdevs.swap(list);
}
#else
static constexpr char ca_device[] = "CoreAudio Default";
#endif
struct CoreAudioPlayback final : public BackendBase {
CoreAudioPlayback(DeviceBase *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 char *name) override;
bool reset() override;
void start() override;
void stop() override;
AudioUnit mAudioUnit{};
uint 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
{
for(size_t i{0};i < ioData->mNumberBuffers;++i)
{
auto &buffer = ioData->mBuffers[i];
mDevice->renderSamples(buffer.mData, buffer.mDataByteSize/mFrameSize,
buffer.mNumberChannels);
}
return noErr;
}
void CoreAudioPlayback::open(const char *name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice),
&audioDevice);
else
{
if(PlaybackList.empty())
EnumerateDevices(PlaybackList, false);
auto find_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name);
if(devmatch == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
audioDevice = devmatch->mId;
}
#else
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
#endif
/* open the default output unit */
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
#if CAN_ENUMERATE
desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == nullptr)
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
AudioUnit audioUnit{};
OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, OutputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
err = AudioUnitInitialize(audioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
/* WARNING: I don't know if "valid" audio unit values are guaranteed to be
* non-0. If not, this logic is broken.
*/
if(mAudioUnit)
{
AudioUnitUninitialize(mAudioUnit);
AudioComponentInstanceDispose(mAudioUnit);
}
mAudioUnit = audioUnit;
#if CAN_ENUMERATE
if(name)
mDevice->DeviceName = name;
else
{
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(audioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, OutputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
#else
mDevice->DeviceName = name;
#endif
}
bool CoreAudioPlayback::reset()
{
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
if(err != noErr)
ERR("-- AudioUnitUninitialize failed.\n");
/* retrieve default output unit's properties (output side) */
AudioStreamBasicDescription streamFormat{};
UInt32 size{sizeof(streamFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
OutputElement, &streamFormat, &size);
if(err != noErr || size != sizeof(streamFormat))
{
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
/* Use the sample rate from the output unit's current parameters, but reset
* everything else.
*/
if(mDevice->Frequency != streamFormat.mSampleRate)
{
mDevice->BufferSize = static_cast<uint>(mDevice->BufferSize*streamFormat.mSampleRate/
mDevice->Frequency + 0.5);
mDevice->Frequency = static_cast<uint>(streamFormat.mSampleRate);
}
/* FIXME: How to tell what channels are what in the output device, and how
* to specify what we're giving? e.g. 6.0 vs 5.1
*/
streamFormat.mChannelsPerFrame = mDevice->channelsFromFmt();
streamFormat.mFramesPerPacket = 1;
streamFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked;
streamFormat.mFormatID = kAudioFormatLinearPCM;
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.mFramesPerPacket;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
OutputElement, &streamFormat, sizeof(streamFormat));
if(err != noErr)
{
ERR("AudioUnitSetProperty failed\n");
return false;
}
setDefaultWFXChannelOrder();
/* setup callback */
mFrameSize = mDevice->frameSizeFromFmt();
AURenderCallbackStruct input{};
input.inputProc = CoreAudioPlayback::MixerProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, OutputElement, &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;
}
void CoreAudioPlayback::start()
{
const OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
}
void CoreAudioPlayback::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
}
struct CoreAudioCapture final : public BackendBase {
CoreAudioCapture(DeviceBase *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 char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
AudioUnit mAudioUnit{0};
uint mFrameSize{0u};
AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD
SampleConverterPtr mConverter;
al::vector<char> mCaptureData;
RingBufferPtr mRing{nullptr};
DEF_NEWDEL(CoreAudioCapture)
};
CoreAudioCapture::~CoreAudioCapture()
{
if(mAudioUnit)
AudioComponentInstanceDispose(mAudioUnit);
mAudioUnit = 0;
}
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList*) noexcept
{
union {
al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))];
AudioBufferList list;
} audiobuf{};
audiobuf.list.mNumberBuffers = 1;
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
audiobuf.list.mBuffers[0].mData = mCaptureData.data();
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(mCaptureData.size());
OSStatus err{AudioUnitRender(mAudioUnit, ioActionFlags, inTimeStamp, inBusNumber,
inNumberFrames, &audiobuf.list)};
if(err != noErr)
{
ERR("AudioUnitRender capture error: %d\n", err);
return err;
}
mRing->write(mCaptureData.data(), inNumberFrames);
return noErr;
}
void CoreAudioCapture::open(const char *name)
{
#if CAN_ENUMERATE
AudioDeviceID audioDevice{kAudioDeviceUnknown};
if(!name)
GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice),
&audioDevice);
else
{
if(CaptureList.empty())
EnumerateDevices(CaptureList, true);
auto find_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name);
if(devmatch == CaptureList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
audioDevice = devmatch->mId;
}
#else
if(!name)
name = ca_device;
else if(strcmp(name, ca_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
#endif
AudioComponentDescription desc{};
desc.componentType = kAudioUnitType_Output;
#if CAN_ENUMERATE
desc.componentSubType = (audioDevice == kAudioDeviceUnknown) ?
kAudioUnitSubType_DefaultOutput : kAudioUnitSubType_HALOutput;
#else
desc.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
// Search for component with given description
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
if(comp == NULL)
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
// Open the component
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::NoDevice,
"Could not create component instance: %u", err};
// Turn off AudioUnit output
UInt32 enableIO{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable audio unit output property: %u", err};
// Turn on AudioUnit input
enableIO = 1;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not enable audio unit input property: %u", err};
#if CAN_ENUMERATE
if(audioDevice != kAudioDeviceUnknown)
AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, InputElement, &audioDevice, sizeof(AudioDeviceID));
#endif
// set capture callback
AURenderCallbackStruct input{};
input.inputProc = CoreAudioCapture::RecordProcC;
input.inputProcRefCon = this;
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set capture callback: %u", err};
// Disable buffer allocation for capture
UInt32 flag{0};
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output, InputElement, &flag, sizeof(flag));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not disable buffer allocation property: %u", err};
// Initialize the device
err = AudioUnitInitialize(mAudioUnit);
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not initialize audio unit: %u", err};
// Get the hardware format
AudioStreamBasicDescription hardwareFormat{};
UInt32 propertySize{sizeof(hardwareFormat)};
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
InputElement, &hardwareFormat, &propertySize);
if(err != noErr || propertySize != sizeof(hardwareFormat))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input format: %u", err};
// Set up the requested format description
AudioStreamBasicDescription requestedFormat{};
switch(mDevice->FmtType)
{
case DevFmtByte:
requestedFormat.mBitsPerChannel = 8;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
break;
case DevFmtUByte:
requestedFormat.mBitsPerChannel = 8;
requestedFormat.mFormatFlags = kAudioFormatFlagIsPacked;
break;
case DevFmtShort:
requestedFormat.mBitsPerChannel = 16;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtUShort:
requestedFormat.mBitsPerChannel = 16;
requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtInt:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger
| kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtUInt:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
break;
case DevFmtFloat:
requestedFormat.mBitsPerChannel = 32;
requestedFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagsNativeEndian
| kAudioFormatFlagIsPacked;
break;
}
switch(mDevice->FmtChans)
{
case DevFmtMono:
requestedFormat.mChannelsPerFrame = 1;
break;
case DevFmtStereo:
requestedFormat.mChannelsPerFrame = 2;
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%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
AudioStreamBasicDescription 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,
InputElement, &outputFormat, sizeof(outputFormat));
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"Could not set input format: %u", err};
/* Calculate the minimum AudioUnit output format frame count for the pre-
* conversion ring buffer. Ensure at least 100ms for the total buffer.
*/
double srateScale{outputFormat.mSampleRate / mDevice->Frequency};
auto FrameCount64 = maxu64(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
FrameCount64 += MaxResamplerPadding;
if(FrameCount64 > std::numeric_limits<int32_t>::max())
throw al::backend_exception{al::backend_error::DeviceError,
"Calculated frame count is too large: %" PRIu64, FrameCount64};
UInt32 outputFrameCount{};
propertySize = sizeof(outputFrameCount);
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_MaximumFramesPerSlice,
kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize);
if(err != noErr || propertySize != sizeof(outputFrameCount))
throw al::backend_exception{al::backend_error::DeviceError,
"Could not get input frame count: %u", err};
mCaptureData.resize(outputFrameCount * mFrameSize);
outputFrameCount = static_cast<UInt32>(maxu64(outputFrameCount, FrameCount64));
mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false);
/* Set up sample converter if needed */
if(outputFormat.mSampleRate != mDevice->Frequency)
mConverter = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType,
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
mDevice->Frequency, Resampler::FastBSinc24);
#if CAN_ENUMERATE
if(name)
mDevice->DeviceName = name;
else
{
UInt32 propSize{sizeof(audioDevice)};
audioDevice = kAudioDeviceUnknown;
AudioUnitGetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, InputElement, &audioDevice, &propSize);
std::string devname{GetDeviceName(audioDevice)};
if(!devname.empty()) mDevice->DeviceName = std::move(devname);
else mDevice->DeviceName = "Unknown Device Name";
}
#else
mDevice->DeviceName = name;
#endif
}
void CoreAudioCapture::start()
{
OSStatus err{AudioOutputUnitStart(mAudioUnit)};
if(err != noErr)
throw al::backend_exception{al::backend_error::DeviceError,
"AudioOutputUnitStart failed: %d", err};
}
void CoreAudioCapture::stop()
{
OSStatus err{AudioOutputUnitStop(mAudioUnit)};
if(err != noErr)
ERR("AudioOutputUnitStop failed\n");
}
void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples)
{
if(!mConverter)
{
mRing->read(buffer, samples);
return;
}
auto rec_vec = mRing->getReadVector();
const void *src0{rec_vec.first.buf};
auto src0len = static_cast<uint>(rec_vec.first.len);
uint 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<uint>(rec_vec.second.len);
got += mConverter->convert(&src1, &src1len, buffer + got*mFrameSize, samples-got);
total_read += rec_vec.second.len - src1len;
}
mRing->readAdvance(total_read);
}
uint CoreAudioCapture::availableSamples()
{
if(!mConverter) return static_cast<uint>(mRing->readSpace());
return mConverter->availableOut(static_cast<uint>(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; }
std::string CoreAudioBackendFactory::probe(BackendType type)
{
std::string outnames;
#if CAN_ENUMERATE
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
switch(type)
{
case BackendType::Playback:
EnumerateDevices(PlaybackList, false);
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
EnumerateDevices(CaptureList, true);
std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name);
break;
}
#else
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(ca_device, sizeof(ca_device));
break;
}
#endif
return outnames;
}
BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new CoreAudioPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new CoreAudioCapture{device}};
return nullptr;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_COREAUDIO_H
#define BACKENDS_COREAUDIO_H
#include "base.h"
struct CoreAudioBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_COREAUDIO_H */

View File

@@ -0,0 +1,850 @@
/**
* 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 "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 "alnumeric.h"
#include "comptr.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.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 MONO SPEAKER_FRONT_CENTER
#define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)
#define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X5DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X5DOT1REAR (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT)
#define X6DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_CENTER|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT)
#define X7DOT1DOT4 (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_FRONT_CENTER|SPEAKER_LOW_FREQUENCY|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT|SPEAKER_SIDE_LEFT|SPEAKER_SIDE_RIGHT|SPEAKER_TOP_FRONT_LEFT|SPEAKER_TOP_FRONT_RIGHT|SPEAKER_TOP_BACK_LEFT|SPEAKER_TOP_BACK_RIGHT)
#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(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundPlayback() override;
int mixerProc();
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
ComPtr<IDirectSound> mDS;
ComPtr<IDirectSoundBuffer> mPrimaryBuffer;
ComPtr<IDirectSoundBuffer> mBuffer;
ComPtr<IDirectSoundNotify> mNotifies;
HANDLE mNotifyEvent{nullptr};
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(DSoundPlayback)
};
DSoundPlayback::~DSoundPlayback()
{
mNotifies = nullptr;
mBuffer = nullptr;
mPrimaryBuffer = nullptr;
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);
mDevice->handleDisconnect("Failure retrieving playback buffer info: 0x%lx", err);
return 1;
}
const size_t FrameStep{mDevice->channelsFromFmt()};
uint 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);
mDevice->handleDisconnect("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))
{
mDevice->renderSamples(WritePtr1, WriteCnt1/FrameSize, FrameStep);
if(WriteCnt2 > 0)
mDevice->renderSamples(WritePtr2, WriteCnt2/FrameSize, FrameStep);
mBuffer->Unlock(WritePtr1, WriteCnt1, WritePtr2, WriteCnt2);
}
else
{
ERR("Buffer lock error: %#lx\n", err);
mDevice->handleDisconnect("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 char *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())
{
GUID id{};
hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
if(SUCCEEDED(hr))
iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(),
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == PlaybackDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
}
guid = &iter->guid;
}
hr = DS_OK;
if(!mNotifyEvent)
{
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if(!mNotifyEvent) hr = E_FAIL;
}
//DirectSound Init code
ComPtr<IDirectSound> ds;
if(SUCCEEDED(hr))
hr = DirectSoundCreate(guid, ds.getPtr(), nullptr);
if(SUCCEEDED(hr))
hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
mNotifies = nullptr;
mBuffer = nullptr;
mPrimaryBuffer = nullptr;
mDS = std::move(ds);
mDevice->DeviceName = name;
}
bool DSoundPlayback::reset()
{
mNotifies = nullptr;
mBuffer = nullptr;
mPrimaryBuffer = nullptr;
switch(mDevice->FmtType)
{
case DevFmtByte:
mDevice->FmtType = DevFmtUByte;
break;
case DevFmtFloat:
if(mDevice->Flags.test(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(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get speaker config: 0x%08lx", hr};
speakers = DSSPEAKER_CONFIG(speakers);
if(!mDevice->Flags.test(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 || speakers == DSSPEAKER_5POINT1_BACK)
mDevice->FmtChans = DevFmtX51;
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
mDevice->FmtChans = DevFmtX71;
else
ERR("Unknown system speaker config: 0x%lx\n", speakers);
}
mDevice->Flags.set(DirectEar, (speakers == DSSPEAKER_HEADPHONE));
const bool isRear51{speakers == DSSPEAKER_5POINT1_BACK};
switch(mDevice->FmtChans)
{
case DevFmtMono: OutputType.dwChannelMask = MONO; break;
case DevFmtAmbi3D: mDevice->FmtChans = DevFmtStereo;
/* fall-through */
case DevFmtStereo: OutputType.dwChannelMask = STEREO; break;
case DevFmtQuad: OutputType.dwChannelMask = QUAD; break;
case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break;
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; 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;
mPrimaryBuffer = nullptr;
}
else
{
if(SUCCEEDED(hr) && !mPrimaryBuffer)
{
DSBUFFERDESC DSBDescription{};
DSBDescription.dwSize = sizeof(DSBDescription);
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr);
}
if(SUCCEEDED(hr))
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
}
if(SUCCEEDED(hr))
{
uint 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.getPtr(), 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))
{
mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
for(uint i{0};i < num_updates;++i)
{
nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign;
nots[i].hEventNotify = mNotifyEvent;
}
if(mNotifies->SetNotificationPositions(num_updates, nots.data()) != DS_OK)
hr = E_FAIL;
}
}
if(FAILED(hr))
{
mNotifies = nullptr;
mBuffer = nullptr;
mPrimaryBuffer = nullptr;
return false;
}
ResetEvent(mNotifyEvent);
setDefaultWFXChannelOrder();
return true;
}
void DSoundPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&DSoundPlayback::mixerProc), this};
}
catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
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(DeviceBase *device) noexcept : BackendBase{device} { }
~DSoundCapture() override;
void open(const char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
ComPtr<IDirectSoundCapture> mDSC;
ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer;
DWORD mBufferBytes{0u};
DWORD mCursor{0u};
RingBufferPtr mRing;
DEF_NEWDEL(DSoundCapture)
};
DSoundCapture::~DSoundCapture()
{
if(mDSCbuffer)
{
mDSCbuffer->Stop();
mDSCbuffer = nullptr;
}
mDSC = nullptr;
}
void DSoundCapture::open(const char *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())
{
GUID id{};
hr = CLSIDFromString(utf8_to_wstr(name).c_str(), &id);
if(SUCCEEDED(hr))
iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(),
[&id](const DevMap &entry) -> bool { return entry.guid == id; });
if(iter == CaptureDevices.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"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{al::backend_error::DeviceError,
"%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 = MONO; break;
case DevFmtStereo: InputType.dwChannelMask = STEREO; break;
case DevFmtQuad: InputType.dwChannelMask = QUAD; break;
case DevFmtX51: InputType.dwChannelMask = X5DOT1; break;
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break;
case DevFmtX3D71:
case DevFmtAmbi3D:
WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans));
throw al::backend_exception{al::backend_error::DeviceError, "%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);
}
uint 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.getPtr(), nullptr);
if(SUCCEEDED(hr))
mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr);
if(SUCCEEDED(hr))
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
if(FAILED(hr))
{
mRing = nullptr;
mDSCbuffer = nullptr;
mDSC = nullptr;
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
hr};
}
mBufferBytes = DSCBDescription.dwBufferBytes;
setDefaultWFXChannelOrder();
mDevice->DeviceName = name;
}
void DSoundCapture::start()
{
const HRESULT hr{mDSCbuffer->Start(DSCBSTART_LOOPING)};
if(FAILED(hr))
throw al::backend_exception{al::backend_error::DeviceError,
"Failure starting capture: 0x%lx", hr};
}
void DSoundCapture::stop()
{
HRESULT hr{mDSCbuffer->Stop()};
if(FAILED(hr))
{
ERR("stop failed: 0x%08lx\n", hr);
mDevice->handleDisconnect("Failure stopping capture: 0x%lx", hr);
}
}
void DSoundCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
uint DSoundCapture::availableSamples()
{
if(!mDevice->Connected.load(std::memory_order_acquire))
return static_cast<uint>(mRing->readSpace());
const uint FrameSize{mDevice->frameSizeFromFmt()};
const DWORD BufferBytes{mBufferBytes};
const DWORD LastCursor{mCursor};
DWORD ReadCursor{};
void *ReadPtr1{}, *ReadPtr2{};
DWORD ReadCnt1{}, ReadCnt2{};
HRESULT hr{mDSCbuffer->GetCurrentPosition(nullptr, &ReadCursor)};
if(SUCCEEDED(hr))
{
const DWORD NumBytes{(BufferBytes+ReadCursor-LastCursor) % BufferBytes};
if(!NumBytes) return static_cast<uint>(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 = ReadCursor;
}
if(FAILED(hr))
{
ERR("update failed: 0x%08lx\n", hr);
mDevice->handleDisconnect("Failure retrieving capture data: 0x%lx", hr);
}
return static_cast<uint>(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); }
std::string DSoundBackendFactory::probe(BackendType 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 BackendType::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 BackendType::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();
return outnames;
}
BackendPtr DSoundBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new DSoundPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new DSoundCapture{device}};
return nullptr;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_DSOUND_H
#define BACKENDS_DSOUND_H
#include "base.h"
struct DSoundBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_DSOUND_H */

View File

@@ -0,0 +1,744 @@
/**
* 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 "jack.h"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <array>
#include <thread>
#include <functional>
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include "threads.h"
#include <jack/jack.h>
#include <jack/ringbuffer.h>
namespace {
#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
constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE;
jack_options_t ClientOptions = JackNullOption;
bool jack_load()
{
bool error{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 false;
}
error = false;
#define LOAD_FUNC(f) do { \
p##f = reinterpret_cast<decltype(p##f)>(GetSymbol(jack_handle, #f)); \
if(p##f == nullptr) { \
error = 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 JackDeleter {
void operator()(void *ptr) { jack_free(ptr); }
};
using JackPortsPtr = std::unique_ptr<const char*[],JackDeleter>;
struct DeviceEntry {
std::string mName;
std::string mPattern;
template<typename T, typename U>
DeviceEntry(T&& name, U&& pattern)
: mName{std::forward<T>(name)}, mPattern{std::forward<U>(pattern)}
{ }
};
al::vector<DeviceEntry> PlaybackList;
void EnumerateDevices(jack_client_t *client, al::vector<DeviceEntry> &list)
{
std::remove_reference_t<decltype(list)>{}.swap(list);
if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)})
{
for(size_t i{0};ports[i];++i)
{
const char *sep{std::strchr(ports[i], ':')};
if(!sep || ports[i] == sep) continue;
const al::span<const char> portdev{ports[i], sep};
auto check_name = [portdev](const DeviceEntry &entry) -> bool
{
const size_t len{portdev.size()};
return entry.mName.length() == len
&& entry.mName.compare(0, len, portdev.data(), len) == 0;
};
if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
continue;
std::string name{portdev.data(), portdev.size()};
list.emplace_back(name, name+":");
const auto &entry = list.back();
TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
/* There are ports but couldn't get device names from them. Add a
* generic entry.
*/
if(ports[0] && list.empty())
{
WARN("No device names found in available ports, adding a generic name.\n");
list.emplace_back("JACK", "");
}
}
if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices"))
{
for(size_t strpos{0};strpos < listopt->size();)
{
size_t nextpos{listopt->find(';', strpos)};
size_t seppos{listopt->find('=', strpos)};
if(seppos >= nextpos || seppos == strpos)
{
const std::string entry{listopt->substr(strpos, nextpos-strpos)};
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
continue;
}
const al::span<const char> name{listopt->data()+strpos, seppos-strpos};
const al::span<const char> pattern{listopt->data()+(seppos+1),
std::min(nextpos, listopt->size())-(seppos+1)};
/* Check if this custom pattern already exists in the list. */
auto check_pattern = [pattern](const DeviceEntry &entry) -> bool
{
const size_t len{pattern.size()};
return entry.mPattern.length() == len
&& entry.mPattern.compare(0, len, pattern.data(), len) == 0;
};
auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern);
if(itemmatch != list.end())
{
/* If so, replace the name with this custom one. */
itemmatch->mName.assign(name.data(), name.size());
TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(),
itemmatch->mPattern.c_str());
}
else
{
/* Otherwise, add a new device entry. */
list.emplace_back(std::string{name.data(), name.size()},
std::string{pattern.data(), pattern.size()});
const auto &entry = list.back();
TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str());
}
if(nextpos != std::string::npos) ++nextpos;
strpos = nextpos;
}
}
if(list.size() > 1)
{
/* Rename entries that have matching names, by appending '#2', '#3',
* etc, as needed.
*/
for(auto curitem = list.begin()+1;curitem != list.end();++curitem)
{
auto check_match = [curitem](const DeviceEntry &entry) -> bool
{ return entry.mName == curitem->mName; };
if(std::find_if(list.begin(), curitem, check_match) != curitem)
{
std::string name{curitem->mName};
size_t count{1};
auto check_name = [&name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
do {
name = curitem->mName;
name += " #";
name += std::to_string(++count);
} while(std::find_if(list.begin(), curitem, check_name) != curitem);
curitem->mName = std::move(name);
}
}
}
}
struct JackPlayback final : public BackendBase {
JackPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~JackPlayback() override;
int processRt(jack_nframes_t numframes) noexcept;
static int processRtC(jack_nframes_t numframes, void *arg) noexcept
{ return static_cast<JackPlayback*>(arg)->processRt(numframes); }
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 char *name) override;
bool reset() override;
void start() override;
void stop() override;
ClockLatency getClockLatency() override;
std::string mPortPattern;
jack_client_t *mClient{nullptr};
std::array<jack_port_t*,MAX_OUTPUT_CHANNELS> mPort{};
std::mutex mMutex;
std::atomic<bool> mPlaying{false};
bool mRTMixing{false};
RingBufferPtr mRing;
al::semaphore mSem;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(JackPlayback)
};
JackPlayback::~JackPlayback()
{
if(!mClient)
return;
auto unregister_port = [this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); };
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(nullptr);
jack_client_close(mClient);
mClient = nullptr;
}
int JackPlayback::processRt(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
size_t numchans{0};
for(auto port : mPort)
{
if(!port || numchans == mDevice->RealOut.Buffer.size())
break;
out[numchans++] = static_cast<float*>(jack_port_get_buffer(port, numframes));
}
if(mPlaying.load(std::memory_order_acquire)) LIKELY
mDevice->renderSamples({out.data(), numchans}, static_cast<uint>(numframes));
else
{
auto clear_buf = [numframes](float *outbuf) -> void
{ std::fill_n(outbuf, numframes, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
return 0;
}
int JackPlayback::process(jack_nframes_t numframes) noexcept
{
std::array<jack_default_audio_sample_t*,MAX_OUTPUT_CHANNELS> out;
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(mPlaying.load(std::memory_order_acquire)) LIKELY
{
auto data = mRing->getReadVector();
jack_nframes_t todo{minu(numframes, static_cast<uint>(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.begin(), out.begin()+numchans, out.begin(), write_first);
total += todo;
todo = minu(numframes-total, static_cast<uint>(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.begin(), out.begin()+numchans, out.begin(), write_second);
total += todo;
}
mRing->readAdvance(total);
mSem.post();
}
if(numframes > total)
{
const jack_nframes_t todo{numframes - total};
auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); };
std::for_each(out.begin(), out.begin()+numchans, clear_buf);
}
return 0;
}
int JackPlayback::mixerProc()
{
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
const size_t frame_step{mDevice->channelsFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
if(mRing->writeSpace() < mDevice->UpdateSize)
{
mSem.wait();
continue;
}
auto data = mRing->getWriteVector();
size_t todo{data.first.len + data.second.len};
todo -= todo%mDevice->UpdateSize;
const auto len1 = static_cast<uint>(minz(data.first.len, todo));
const auto len2 = static_cast<uint>(minz(data.second.len, todo-len1));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(data.first.buf, len1, frame_step);
if(len2 > 0)
mDevice->renderSamples(data.second.buf, len2, frame_step);
mRing->writeAdvance(todo);
}
return 0;
}
void JackPlayback::open(const char *name)
{
if(!mClient)
{
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
if(mClient == nullptr)
throw al::backend_exception{al::backend_error::DeviceError,
"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);
}
}
if(PlaybackList.empty())
EnumerateDevices(mClient, PlaybackList);
if(!name && !PlaybackList.empty())
{
name = PlaybackList[0].mName.c_str();
mPortPattern = PlaybackList[0].mPattern;
}
else
{
auto check_name = [name](const DeviceEntry &entry) -> bool
{ return entry.mName == name; };
auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name);
if(iter == PlaybackList.cend())
throw al::backend_exception{al::backend_error::NoDevice,
"Device name \"%s\" not found", name?name:""};
mPortPattern = iter->mPattern;
}
mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true);
jack_set_process_callback(mClient,
mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this);
mDevice->DeviceName = name;
}
bool JackPlayback::reset()
{
auto unregister_port = [this](jack_port_t *port) -> void
{ if(port) jack_port_unregister(mClient, port); };
std::for_each(mPort.begin(), mPort.end(), unregister_port);
mPort.fill(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);
if(mRTMixing)
{
/* Assume only two periods when directly mixing. Should try to query
* the total port latency when connected.
*/
mDevice->BufferSize = mDevice->UpdateSize * 2;
}
else
{
const char *devname{mDevice->DeviceName.c_str()};
uint 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;
int port_num{0};
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
auto bad_port = mPort.begin();
while(bad_port != ports_end)
{
std::string name{"channel_" + std::to_string(++port_num)};
*bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType,
JackPortIsOutput | JackPortIsTerminal, 0);
if(!*bad_port) break;
++bad_port;
}
if(bad_port != ports_end)
{
ERR("Failed to register enough JACK ports for %s output\n",
DevFmtChannelsString(mDevice->FmtChans));
if(bad_port == mPort.begin()) return false;
if(bad_port == mPort.begin()+1)
mDevice->FmtChans = DevFmtMono;
else
{
ports_end = mPort.begin()+2;
while(bad_port != ports_end)
{
jack_port_unregister(mClient, *(--bad_port));
*bad_port = nullptr;
}
mDevice->FmtChans = DevFmtStereo;
}
}
setDefaultChannelOrder();
return true;
}
void JackPlayback::start()
{
if(jack_activate(mClient))
throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"};
const char *devname{mDevice->DeviceName.c_str()};
if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true))
{
JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType,
JackPortIsInput)};
if(!pnames)
{
jack_deactivate(mClient);
throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"};
}
for(size_t i{0};i < al::size(mPort) && mPort[i];++i)
{
if(!pnames[i])
{
ERR("No physical playback port for \"%s\"\n", jack_port_name(mPort[i]));
break;
}
if(jack_connect(mClient, jack_port_name(mPort[i]), pnames[i]))
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(mPort[i]),
pnames[i]);
}
}
/* 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;
mRing = nullptr;
if(mRTMixing)
mPlaying.store(true, std::memory_order_release);
else
{
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
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};
}
catch(std::exception& e) {
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
}
void JackPlayback::stop()
{
if(mPlaying.load(std::memory_order_acquire))
{
mKillNow.store(true, std::memory_order_release);
if(mThread.joinable())
{
mSem.post();
mThread.join();
}
jack_deactivate(mClient);
mPlaying.store(false, std::memory_order_release);
}
}
ClockLatency JackPlayback::getClockLatency()
{
ClockLatency ret;
std::lock_guard<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize};
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", false))
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
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(client_name, 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); }
std::string JackBackendFactory::probe(BackendType type)
{
std::string outnames;
auto append_name = [&outnames](const DeviceEntry &entry) -> void
{
/* Includes null char. */
outnames.append(entry.mName.c_str(), entry.mName.length()+1);
};
const PathNamePair &binname = GetProcBinary();
const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()};
jack_status_t status;
switch(type)
{
case BackendType::Playback:
if(jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)})
{
EnumerateDevices(client, PlaybackList);
jack_client_close(client);
}
else
WARN("jack_client_open() failed, 0x%02x\n", status);
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr JackBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new JackPlayback{device}};
return nullptr;
}
BackendFactory &JackBackendFactory::getFactory()
{
static JackBackendFactory factory{};
return factory;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_JACK_H
#define BACKENDS_JACK_H
#include "base.h"
struct JackBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_JACK_H */

View File

@@ -0,0 +1,78 @@
/**
* 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 "loopback.h"
#include "core/device.h"
namespace {
struct LoopbackBackend final : public BackendBase {
LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { }
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
DEF_NEWDEL(LoopbackBackend)
};
void LoopbackBackend::open(const char *name)
{
mDevice->DeviceName = name;
}
bool LoopbackBackend::reset()
{
setDefaultWFXChannelOrder();
return true;
}
void LoopbackBackend::start()
{ }
void LoopbackBackend::stop()
{ }
} // namespace
bool LoopbackBackendFactory::init()
{ return true; }
bool LoopbackBackendFactory::querySupport(BackendType)
{ return true; }
std::string LoopbackBackendFactory::probe(BackendType)
{ return std::string{}; }
BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType)
{ return BackendPtr{new LoopbackBackend{device}}; }
BackendFactory &LoopbackBackendFactory::getFactory()
{
static LoopbackBackendFactory factory{};
return factory;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_LOOPBACK_H
#define BACKENDS_LOOPBACK_H
#include "base.h"
struct LoopbackBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_LOOPBACK_H */

View File

@@ -0,0 +1,179 @@
/**
* 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 "null.h"
#include <exception>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <functional>
#include <thread>
#include "core/device.h"
#include "almalloc.h"
#include "core/helpers.h"
#include "threads.h"
namespace {
using std::chrono::seconds;
using std::chrono::milliseconds;
using std::chrono::nanoseconds;
constexpr char nullDevice[] = "No Output";
struct NullBackend final : public BackendBase {
NullBackend(DeviceBase *device) noexcept : BackendBase{device} { }
int mixerProc();
void open(const char *name) override;
bool reset() override;
void 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)
{
mDevice->renderSamples(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 char *name)
{
if(!name)
name = nullDevice;
else if(strcmp(name, nullDevice) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mDevice->DeviceName = name;
}
bool NullBackend::reset()
{
setDefaultWFXChannelOrder();
return true;
}
void NullBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this};
}
catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
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); }
std::string NullBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
/* Includes null char. */
outnames.append(nullDevice, sizeof(nullDevice));
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new NullBackend{device}};
return nullptr;
}
BackendFactory &NullBackendFactory::getFactory()
{
static NullBackendFactory factory{};
return factory;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_NULL_H
#define BACKENDS_NULL_H
#include "base.h"
struct NullBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_NULL_H */

View File

@@ -0,0 +1,360 @@
#include "config.h"
#include "oboe.h"
#include <cassert>
#include <cstring>
#include <stdint.h>
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "oboe/Oboe.h"
namespace {
constexpr char device_name[] = "Oboe Default";
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
OboePlayback(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
};
oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames)
{
assert(numFrames > 0);
const int32_t numChannels{oboeStream->getChannelCount()};
mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
static_cast<uint32_t>(numChannels));
return oboe::DataCallbackResult::Continue;
}
void OboePlayback::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
/* Open a basic output stream, just to ensure it can work. */
oboe::ManagedStream stream;
oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->openManagedStream(stream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mDevice->DeviceName = name;
}
bool OboePlayback::reset()
{
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
/* Don't let Oboe convert. We should be able to handle anything it gives
* back.
*/
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
builder.setChannelConversionAllowed(false);
builder.setFormatConversionAllowed(false);
builder.setCallback(this);
if(mDevice->Flags.test(FrequencyRequest))
{
builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High);
builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
}
if(mDevice->Flags.test(ChannelsRequest))
{
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
: (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
: oboe::ChannelCount::Unspecified);
}
if(mDevice->Flags.test(SampleTypeRequest))
{
oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
switch(mDevice->FmtType)
{
case DevFmtByte:
case DevFmtUByte:
case DevFmtShort:
case DevFmtUShort:
format = oboe::AudioFormat::I16;
break;
case DevFmtInt:
case DevFmtUInt:
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
format = oboe::AudioFormat::I32;
break;
#endif
case DevFmtFloat:
format = oboe::AudioFormat::Float;
break;
}
builder.setFormat(format);
}
oboe::Result result{builder.openManagedStream(mStream)};
/* If the format failed, try asking for the defaults. */
while(result == oboe::Result::ErrorInvalidFormat)
{
if(builder.getFormat() != oboe::AudioFormat::Unspecified)
builder.setFormat(oboe::AudioFormat::Unspecified);
else if(builder.getSampleRate() != oboe::kUnspecified)
builder.setSampleRate(oboe::kUnspecified);
else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
builder.setChannelCount(oboe::ChannelCount::Unspecified);
else
break;
result = builder.openManagedStream(mStream);
}
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
mStream->setBufferSizeInFrames(mini(static_cast<int32_t>(mDevice->BufferSize),
mStream->getBufferCapacityInFrames()));
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
if(static_cast<uint>(mStream->getChannelCount()) != mDevice->channelsFromFmt())
{
if(mStream->getChannelCount() >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(mStream->getChannelCount() == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled channel count: %d", mStream->getChannelCount()};
}
setDefaultWFXChannelOrder();
switch(mStream->getFormat())
{
case oboe::AudioFormat::I16:
mDevice->FmtType = DevFmtShort;
break;
case oboe::AudioFormat::Float:
mDevice->FmtType = DevFmtFloat;
break;
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
case oboe::AudioFormat::I32:
mDevice->FmtType = DevFmtInt;
break;
case oboe::AudioFormat::I24:
#endif
case oboe::AudioFormat::Unspecified:
case oboe::AudioFormat::Invalid:
throw al::backend_exception{al::backend_error::DeviceError,
"Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
}
mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
/* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
* indicating variable updates, but OpenAL should have a reasonable minimum update size set.
* FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
* update size.
*/
mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
static_cast<uint32_t>(mStream->getFramesPerBurst()));
mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
return true;
}
void OboePlayback::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboePlayback::stop()
{
oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback {
OboeCapture(DeviceBase *device) : BackendBase{device} { }
oboe::ManagedStream mStream;
RingBufferPtr mRing{nullptr};
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
int32_t numFrames) override;
void open(const char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint availableSamples() override;
};
oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData,
int32_t numFrames)
{
mRing->write(audioData, static_cast<uint32_t>(numFrames));
return oboe::DataCallbackResult::Continue;
}
void OboeCapture::open(const char *name)
{
if(!name)
name = device_name;
else if(std::strcmp(name, device_name) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Input)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::High)
->setChannelConversionAllowed(true)
->setFormatConversionAllowed(true)
->setSampleRate(static_cast<int32_t>(mDevice->Frequency))
->setCallback(this);
/* Only use mono or stereo at user request. There's no telling what
* other counts may be inferred as.
*/
switch(mDevice->FmtChans)
{
case DevFmtMono:
builder.setChannelCount(oboe::ChannelCount::Mono);
break;
case DevFmtStereo:
builder.setChannelCount(oboe::ChannelCount::Stereo);
break;
case DevFmtQuad:
case DevFmtX51:
case DevFmtX61:
case DevFmtX71:
case DevFmtX714:
case DevFmtX3D71:
case DevFmtAmbi3D:
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
DevFmtChannelsString(mDevice->FmtChans)};
}
/* FIXME: This really should support UByte, but Oboe doesn't. We'll need to
* convert.
*/
switch(mDevice->FmtType)
{
case DevFmtShort:
builder.setFormat(oboe::AudioFormat::I16);
break;
case DevFmtFloat:
builder.setFormat(oboe::AudioFormat::Float);
break;
case DevFmtInt:
#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 6)
builder.setFormat(oboe::AudioFormat::I32);
break;
#endif
case DevFmtByte:
case DevFmtUByte:
case DevFmtUShort:
case DevFmtUInt:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
oboe::Result result{builder.openManagedStream(mStream)};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
oboe::convertToText(result)};
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
/* Ensure a minimum ringbuffer size of 100ms. */
mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10),
static_cast<uint32_t>(mStream->getBytesPerFrame()), false);
mDevice->DeviceName = name;
}
void OboeCapture::start()
{
const oboe::Result result{mStream->start()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
oboe::convertToText(result)};
}
void OboeCapture::stop()
{
const oboe::Result result{mStream->stop()};
if(result != oboe::Result::OK)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
oboe::convertToText(result)};
}
uint OboeCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void OboeCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
} // namespace
bool OboeBackendFactory::init() { return true; }
bool OboeBackendFactory::querySupport(BackendType type)
{ return type == BackendType::Playback || type == BackendType::Capture; }
std::string OboeBackendFactory::probe(BackendType type)
{
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
return std::string{device_name, sizeof(device_name)};
}
return std::string{};
}
BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OboePlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new OboeCapture{device}};
return BackendPtr{};
}
BackendFactory &OboeBackendFactory::getFactory()
{
static OboeBackendFactory factory{};
return factory;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_OBOE_H
#define BACKENDS_OBOE_H
#include "base.h"
struct OboeBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_OBOE_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_OSL_H
#define BACKENDS_OSL_H
#include "base.h"
struct OSLBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_OSL_H */

View File

@@ -0,0 +1,690 @@
/**
* 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 "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 "albyte.h"
#include "alc/alconfig.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "aloptional.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/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;
};
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, al::span<const char> handle, al::span<const char> path)
{
#ifdef ALC_OSS_DEVNODE_TRUC
for(size_t i{0};i < path.size();++i)
{
if(path[i] == '.' && handle.size() + i >= path.size())
{
const size_t hoffset{handle.size() + i - path.size()};
if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0)
handle = handle.first(hoffset);
path = path.first(i);
}
}
#endif
if(handle.empty())
handle = path;
std::string basename{handle.data(), handle.size()};
std::string devname{path.data(), path.size()};
auto match_devname = [&devname](const DevMap &entry) -> bool
{ return entry.device_name == devname; };
if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend())
return;
auto checkName = [&list](const std::string &name) -> bool
{
auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; };
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
};
int count{1};
std::string newname{basename};
while(checkName(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;
al::span<const char> handle;
if(ai.handle[0] != '\0')
handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))};
else
handle = {ai.name, strnlen(ai.name, sizeof(ai.name))};
al::span<const char> devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))};
ALCossListAppend(devlist, handle, 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
uint log2i(uint x)
{
uint y{0};
while(x > 1)
{
x >>= 1;
y++;
}
return y;
}
struct OSSPlayback final : public BackendBase {
OSSPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~OSSPlayback() override;
int mixerProc();
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
al::vector<al::byte> 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 size_t frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
int pret{poll(&pollitem, 1, 1000)};
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
{
WARN("poll timeout\n");
continue;
}
al::byte *write_ptr{mMixData.data()};
size_t to_write{mMixData.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(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));
mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno));
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
}
}
return 0;
}
void OSSPlayback::open(const char *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{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
int fd{::open(devname, O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
strerror(errno)};
if(mFd != -1)
::close(mFd);
mFd = fd;
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;
}
uint periods{mDevice->BufferSize / mDevice->UpdateSize};
uint numChannels{mDevice->channelsFromFmt()};
uint ossSpeed{mDevice->Frequency};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
/* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */
uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)};
uint 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<uint>(info.fragsize) / frameSize;
mDevice->BufferSize = static_cast<uint>(info.fragments) * mDevice->UpdateSize;
setDefaultChannelOrder();
mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
return true;
}
void OSSPlayback::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSSPlayback::mixerProc), this};
}
catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
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(DeviceBase *device) noexcept : BackendBase{device} { }
~OSScapture() override;
int recordProc();
void open(const char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint 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 size_t 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));
mDevice->handleDisconnect("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));
mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno));
break;
}
mRing->writeAdvance(static_cast<size_t>(amt)/frame_size);
}
}
return 0;
}
void OSScapture::open(const char *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{al::backend_error::NoDevice,
"Device name \"%s\" not found", name};
devname = iter->device_name.c_str();
}
mFd = ::open(devname, O_RDONLY);
if(mFd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "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{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
uint periods{4};
uint numChannels{mDevice->channelsFromFmt()};
uint frameSize{numChannels * mDevice->bytesFromFmt()};
uint ossSpeed{mDevice->Frequency};
/* according to the OSS spec, 16 bytes are the minimum */
uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)};
uint numFragmentsLogSize{(periods << 16) | log2FragmentSize};
audio_buf_info info{};
#define CHECKERR(func) if((func) < 0) { \
throw al::backend_exception{al::backend_error::DeviceError, #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{al::backend_error::DeviceError,
"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{al::backend_error::DeviceError,
"Failed to set %s samples, got OSS format %#x", DevFmtTypeString(mDevice->FmtType),
ossFormat};
mRing = RingBuffer::Create(mDevice->BufferSize, frameSize, false);
mDevice->DeviceName = name;
}
void OSScapture::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&OSScapture::recordProc), this};
}
catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording thread: %s", e.what()};
}
}
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));
}
void OSScapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
uint OSScapture::availableSamples()
{ return static_cast<uint>(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); }
std::string OSSBackendFactory::probe(BackendType type)
{
std::string outnames;
auto add_device = [&outnames](const DevMap &entry) -> void
{
struct stat buf;
if(stat(entry.device_name.c_str(), &buf) == 0)
{
/* Includes null char. */
outnames.append(entry.name.c_str(), entry.name.length()+1);
}
};
switch(type)
{
case BackendType::Playback:
PlaybackDevices.clear();
ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT);
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
break;
case BackendType::Capture:
CaptureDevices.clear();
ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT);
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
break;
}
return outnames;
}
BackendPtr OSSBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new OSSPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new OSScapture{device}};
return nullptr;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_OSS_H
#define BACKENDS_OSS_H
#include "base.h"
struct OSSBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_OSS_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
#ifndef BACKENDS_PIPEWIRE_H
#define BACKENDS_PIPEWIRE_H
#include <string>
#include "base.h"
struct DeviceBase;
struct PipeWireBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PIPEWIRE_H */

View File

@@ -0,0 +1,447 @@
/**
* 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 "portaudio.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "alc/alconfig.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
#include "dynload.h"
#include "ringbuffer.h"
#include <portaudio.h>
namespace {
constexpr char 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(DeviceBase *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 char *name) override;
bool reset() override;
void start() override;
void stop() override;
PaStream *mStream{nullptr};
PaStreamParameters mParams{};
uint 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
{
mDevice->renderSamples(outputBuffer, static_cast<uint>(framesPerBuffer),
static_cast<uint>(mParams.channelCount));
return 0;
}
void PortPlayback::open(const char *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
PaStreamParameters params{};
auto devidopt = ConfigValueInt(nullptr, "port", "device");
if(devidopt && *devidopt >= 0) params.device = *devidopt;
else params.device = Pa_GetDefaultOutputDevice();
params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
params.hostApiSpecificStreamInfo = nullptr;
params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
switch(mDevice->FmtType)
{
case DevFmtByte:
params.sampleFormat = paInt8;
break;
case DevFmtUByte:
params.sampleFormat = paUInt8;
break;
case DevFmtUShort:
/* fall-through */
case DevFmtShort:
params.sampleFormat = paInt16;
break;
case DevFmtUInt:
/* fall-through */
case DevFmtInt:
params.sampleFormat = paInt32;
break;
case DevFmtFloat:
params.sampleFormat = paFloat32;
break;
}
retry_open:
PaStream *stream{};
PaError err{Pa_OpenStream(&stream, nullptr, &params, mDevice->Frequency, mDevice->UpdateSize,
paNoFlag, &PortPlayback::writeCallbackC, this)};
if(err != paNoError)
{
if(params.sampleFormat == paFloat32)
{
params.sampleFormat = paInt16;
goto retry_open;
}
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
}
Pa_CloseStream(mStream);
mStream = stream;
mParams = params;
mUpdateSize = mDevice->UpdateSize;
mDevice->DeviceName = name;
}
bool PortPlayback::reset()
{
const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)};
mDevice->Frequency = static_cast<uint>(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();
return true;
}
void PortPlayback::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err == paNoError)
throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s",
Pa_GetErrorText(err)};
}
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(DeviceBase *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 char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint 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 char *name)
{
if(!name)
name = pa_device;
else if(strcmp(name, pa_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
uint samples{mDevice->BufferSize};
samples = maxu(samples, 100 * mDevice->Frequency / 1000);
uint 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{al::backend_error::DeviceError, "%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{al::backend_error::NoDevice, "Failed to open stream: %s",
Pa_GetErrorText(err)};
mDevice->DeviceName = name;
}
void PortCapture::start()
{
const PaError err{Pa_StartStream(mStream)};
if(err != paNoError)
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start recording: %s", Pa_GetErrorText(err)};
}
void PortCapture::stop()
{
PaError err{Pa_StopStream(mStream)};
if(err != paNoError)
ERR("Error stopping stream: %s\n", Pa_GetErrorText(err));
}
uint PortCapture::availableSamples()
{ return static_cast<uint>(mRing->readSpace()); }
void PortCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
} // 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); }
std::string PortBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(pa_device, sizeof(pa_device));
break;
}
return outnames;
}
BackendPtr PortBackendFactory::createBackend(DeviceBase *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;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_PORTAUDIO_H
#define BACKENDS_PORTAUDIO_H
#include "base.h"
struct PortBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PORTAUDIO_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_PULSEAUDIO_H
#define BACKENDS_PULSEAUDIO_H
#include "base.h"
class PulseBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_PULSEAUDIO_H */

View File

@@ -0,0 +1,224 @@
/**
* 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 "sdl2.h"
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <string>
#include "almalloc.h"
#include "alnumeric.h"
#include "core/device.h"
#include "core/logging.h"
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wold-style-cast\"")
#include "SDL.h"
_Pragma("GCC diagnostic pop")
namespace {
#ifdef _WIN32
#define DEVNAME_PREFIX "OpenAL Soft on "
#else
#define DEVNAME_PREFIX ""
#endif
constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device";
struct Sdl2Backend final : public BackendBase {
Sdl2Backend(DeviceBase *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 char *name) override;
bool reset() override;
void start() override;
void stop() override;
SDL_AudioDeviceID mDeviceID{0u};
uint mFrameSize{0};
uint mFrequency{0u};
DevFmtChannels mFmtChans{};
DevFmtType mFmtType{};
uint 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);
mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt());
}
void Sdl2Backend::open(const char *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>(minu(mDevice->UpdateSize, 8192));
want.callback = &Sdl2Backend::audioCallbackC;
want.userdata = this;
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
* necessarily the first in the list.
*/
SDL_AudioDeviceID devid;
if(!name || strcmp(name, defaultDeviceName) == 0)
devid = 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)
devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
SDL_AUDIO_ALLOW_ANY_CHANGE);
else
devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE);
}
if(!devid)
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
DevFmtChannels devchans{};
if(have.channels >= 2)
devchans = DevFmtStereo;
else if(have.channels == 1)
devchans = DevFmtMono;
else
{
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled SDL channel count: %d", int{have.channels}};
}
DevFmtType devtype{};
switch(have.format)
{
case AUDIO_U8: devtype = DevFmtUByte; break;
case AUDIO_S8: devtype = DevFmtByte; break;
case AUDIO_U16SYS: devtype = DevFmtUShort; break;
case AUDIO_S16SYS: devtype = DevFmtShort; break;
case AUDIO_S32SYS: devtype = DevFmtInt; break;
case AUDIO_F32SYS: devtype = DevFmtFloat; break;
default:
SDL_CloseAudioDevice(devid);
throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
have.format};
}
if(mDeviceID)
SDL_CloseAudioDevice(mDeviceID);
mDeviceID = devid;
mFrameSize = BytesFromDevFmt(devtype) * have.channels;
mFrequency = static_cast<uint>(have.freq);
mFmtChans = devchans;
mFmtType = devtype;
mUpdateSize = have.samples;
mDevice->DeviceName = name ? name : defaultDeviceName;
}
bool Sdl2Backend::reset()
{
mDevice->Frequency = mFrequency;
mDevice->FmtChans = mFmtChans;
mDevice->FmtType = mFmtType;
mDevice->UpdateSize = mUpdateSize;
mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */
setDefaultWFXChannelOrder();
return true;
}
void Sdl2Backend::start()
{ SDL_PauseAudioDevice(mDeviceID, 0); }
void Sdl2Backend::stop()
{ SDL_PauseAudioDevice(mDeviceID, 1); }
} // 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; }
std::string SDL2BackendFactory::probe(BackendType type)
{
std::string outnames;
if(type != BackendType::Playback)
return outnames;
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);
}
return outnames;
}
BackendPtr SDL2BackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new Sdl2Backend{device}};
return nullptr;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_SDL2_H
#define BACKENDS_SDL2_H
#include "base.h"
struct SDL2BackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SDL2_H */

View File

@@ -0,0 +1,540 @@
/**
* 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 "sndio.h"
#include <functional>
#include <inttypes.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>
#include "alnumeric.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "ringbuffer.h"
#include "threads.h"
#include "vector.h"
#include <sndio.h>
namespace {
static const char sndio_device[] = "SndIO Default";
struct SioPar : public sio_par {
SioPar() { sio_initpar(this); }
void clear() { sio_initpar(this); }
};
struct SndioPlayback final : public BackendBase {
SndioPlayback(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioPlayback() override;
int mixerProc();
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
sio_hdl *mSndHandle{nullptr};
uint mFrameStep{};
al::vector<al::byte> mBuffer;
std::atomic<bool> mKillNow{true};
std::thread mThread;
DEF_NEWDEL(SndioPlayback)
};
SndioPlayback::~SndioPlayback()
{
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = nullptr;
}
int SndioPlayback::mixerProc()
{
const size_t frameStep{mFrameStep};
const size_t frameSize{frameStep * mDevice->bytesFromFmt()};
SetRTPriority();
althrd_setname(MIXER_THREAD_NAME);
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
al::span<al::byte> buffer{mBuffer};
mDevice->renderSamples(buffer.data(), static_cast<uint>(buffer.size() / frameSize),
frameStep);
while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire))
{
size_t wrote{sio_write(mSndHandle, buffer.data(), buffer.size())};
if(wrote > buffer.size() || wrote == 0)
{
ERR("sio_write failed: 0x%" PRIx64 "\n", wrote);
mDevice->handleDisconnect("Failed to write playback samples");
break;
}
buffer = buffer.subspan(wrote);
}
}
return 0;
}
void SndioPlayback::open(const char *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)};
if(!sndHandle)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
if(mSndHandle)
sio_close(mSndHandle);
mSndHandle = sndHandle;
mDevice->DeviceName = name;
}
bool SndioPlayback::reset()
{
SioPar par;
auto tryfmt = mDevice->FmtType;
retry_params:
switch(tryfmt)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
case DevFmtShort:
par.bits = 16;
par.sig = 1;
break;
case DevFmtUShort:
par.bits = 16;
par.sig = 0;
break;
case DevFmtFloat:
case DevFmtInt:
par.bits = 32;
par.sig = 1;
break;
case DevFmtUInt:
par.bits = 32;
par.sig = 0;
break;
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rate = mDevice->Frequency;
par.pchan = mDevice->channelsFromFmt();
par.round = mDevice->UpdateSize;
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
try {
if(!sio_setpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device parameters"};
par.clear();
if(!sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to get device parameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8};
if(par.pchan < 1)
throw al::backend_exception{al::backend_error::DeviceError,
"No playback channels on device"};
}
catch(al::backend_exception &e) {
if(tryfmt == DevFmtShort)
throw;
par.clear();
tryfmt = DevFmtShort;
goto retry_params;
}
if(par.bps == 1)
mDevice->FmtType = (par.sig==1) ? DevFmtByte : DevFmtUByte;
else if(par.bps == 2)
mDevice->FmtType = (par.sig==1) ? DevFmtShort : DevFmtUShort;
else if(par.bps == 4)
mDevice->FmtType = (par.sig==1) ? DevFmtInt : DevFmtUInt;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Unhandled sample format: %s %u-bit", (par.sig?"signed":"unsigned"), par.bps*8};
mFrameStep = par.pchan;
if(par.pchan != mDevice->channelsFromFmt())
{
WARN("Got %u channel%s for %s\n", par.pchan, (par.pchan==1)?"":"s",
DevFmtChannelsString(mDevice->FmtChans));
if(par.pchan < 2) mDevice->FmtChans = DevFmtMono;
else mDevice->FmtChans = DevFmtStereo;
}
mDevice->Frequency = par.rate;
setDefaultChannelOrder();
mDevice->UpdateSize = par.round;
mDevice->BufferSize = par.bufsz + par.round;
mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps);
if(par.sig == 1)
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
else if(par.bits == 8)
std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80));
else if(par.bits == 16)
std::fill_n(reinterpret_cast<uint16_t*>(mBuffer.data()), mBuffer.size()/2, 0x8000);
else if(par.bits == 32)
std::fill_n(reinterpret_cast<uint32_t*>(mBuffer.data()), mBuffer.size()/4, 0x80000000u);
return true;
}
void SndioPlayback::start()
{
if(!sio_start(mSndHandle))
throw al::backend_exception{al::backend_error::DeviceError, "Error starting playback"};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioPlayback::mixerProc), this};
}
catch(std::exception& e) {
sio_stop(mSndHandle);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
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");
}
/* TODO: This could be improved by avoiding the ring buffer and record thread,
* counting the available samples with the sio_onmove callback and reading
* directly from the device. However, this depends on reasonable support for
* capture buffer sizes apps may request.
*/
struct SndioCapture final : public BackendBase {
SndioCapture(DeviceBase *device) noexcept : BackendBase{device} { }
~SndioCapture() override;
int recordProc();
void open(const char *name) override;
void start() override;
void stop() override;
void captureSamples(al::byte *buffer, uint samples) override;
uint 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 uint frameSize{mDevice->frameSizeFromFmt()};
int nfds_pre{sio_nfds(mSndHandle)};
if(nfds_pre <= 0)
{
mDevice->handleDisconnect("Incorrect return value from sio_nfds(): %d", nfds_pre);
return 1;
}
auto fds = std::make_unique<pollfd[]>(static_cast<uint>(nfds_pre));
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
/* Wait until there's some samples to read. */
const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)};
if(nfds <= 0)
{
mDevice->handleDisconnect("Failed to get polling fds: %d", nfds);
break;
}
int pollres{::poll(fds.get(), static_cast<uint>(nfds), 2000)};
if(pollres < 0)
{
if(errno == EINTR) continue;
mDevice->handleDisconnect("Poll error: %s", strerror(errno));
break;
}
if(pollres == 0)
continue;
const int revents{sio_revents(mSndHandle, fds.get())};
if((revents&POLLHUP))
{
mDevice->handleDisconnect("Got POLLHUP from poll events");
break;
}
if(!(revents&POLLIN))
continue;
auto data = mRing->getWriteVector();
al::span<al::byte> buffer{data.first.buf, data.first.len*frameSize};
while(!buffer.empty())
{
size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())};
if(got == 0)
break;
if(got > buffer.size())
{
ERR("sio_read failed: 0x%" PRIx64 "\n", got);
mDevice->handleDisconnect("sio_read failed: 0x%" PRIx64, got);
break;
}
mRing->writeAdvance(got / frameSize);
buffer = buffer.subspan(got);
if(buffer.empty())
{
data = mRing->getWriteVector();
buffer = {data.first.buf, data.first.len*frameSize};
}
}
if(buffer.empty())
{
/* Got samples to read, but no place to store it. Drop it. */
static char junk[4096];
sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize));
}
}
return 0;
}
void SndioCapture::open(const char *name)
{
if(!name)
name = sndio_device;
else if(strcmp(name, sndio_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
mSndHandle = sio_open(nullptr, SIO_REC, true);
if(mSndHandle == nullptr)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
SioPar par;
switch(mDevice->FmtType)
{
case DevFmtByte:
par.bits = 8;
par.sig = 1;
break;
case DevFmtUByte:
par.bits = 8;
par.sig = 0;
break;
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;
case DevFmtFloat:
throw al::backend_exception{al::backend_error::DeviceError,
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
}
par.bps = SIO_BPS(par.bits);
par.le = SIO_LE_NATIVE;
par.msb = 1;
par.rchan = mDevice->channelsFromFmt();
par.rate = mDevice->Frequency;
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
par.round = minu(par.appbufsz/2, mDevice->Frequency/40);
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to set device praameters"};
if(par.bps > 1 && par.le != SIO_LE_NATIVE)
throw al::backend_exception{al::backend_error::DeviceError,
"%s-endian samples not supported", par.le ? "Little" : "Big"};
if(par.bits < par.bps*8 && !par.msb)
throw al::backend_exception{al::backend_error::DeviceError,
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
auto match_fmt = [](DevFmtType fmttype, const sio_par &p) -> bool
{
return (fmttype == DevFmtByte && p.bps == 1 && p.sig != 0)
|| (fmttype == DevFmtUByte && p.bps == 1 && p.sig == 0)
|| (fmttype == DevFmtShort && p.bps == 2 && p.sig != 0)
|| (fmttype == DevFmtUShort && p.bps == 2 && p.sig == 0)
|| (fmttype == DevFmtInt && p.bps == 4 && p.sig != 0)
|| (fmttype == DevFmtUInt && p.bps == 4 && p.sig == 0);
};
if(!match_fmt(mDevice->FmtType, par) || mDevice->channelsFromFmt() != par.rchan
|| mDevice->Frequency != par.rate)
throw al::backend_exception{al::backend_error::DeviceError,
"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.bps*8, par.rchan, par.rate};
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace());
mDevice->UpdateSize = par.round;
setDefaultChannelOrder();
mDevice->DeviceName = name;
}
void SndioCapture::start()
{
if(!sio_start(mSndHandle))
throw al::backend_exception{al::backend_error::DeviceError, "Error starting capture"};
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SndioCapture::recordProc), this};
}
catch(std::exception& e) {
sio_stop(mSndHandle);
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start capture thread: %s", e.what()};
}
}
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");
}
void SndioCapture::captureSamples(al::byte *buffer, uint samples)
{ mRing->read(buffer, samples); }
uint SndioCapture::availableSamples()
{ return static_cast<uint>(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); }
std::string SndIOBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
case BackendType::Capture:
/* Includes null char. */
outnames.append(sndio_device, sizeof(sndio_device));
break;
}
return outnames;
}
BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SndioPlayback{device}};
if(type == BackendType::Capture)
return BackendPtr{new SndioCapture{device}};
return nullptr;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_SNDIO_H
#define BACKENDS_SNDIO_H
#include "base.h"
struct SndIOBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SNDIO_H */

View File

@@ -0,0 +1,303 @@
/**
* 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 "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 <string.h>
#include <thread>
#include <functional>
#include "albyte.h"
#include "alc/alconfig.h"
#include "core/device.h"
#include "core/helpers.h"
#include "core/logging.h"
#include "threads.h"
#include "vector.h"
#include <sys/audioio.h>
namespace {
constexpr char solaris_device[] = "Solaris Default";
std::string solaris_driver{"/dev/audio"};
struct SolarisBackend final : public BackendBase {
SolarisBackend(DeviceBase *device) noexcept : BackendBase{device} { }
~SolarisBackend() override;
int mixerProc();
void open(const char *name) override;
bool reset() override;
void start() override;
void stop() override;
int mFd{-1};
uint mFrameStep{};
al::vector<al::byte> 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 uint frame_size{mDevice->frameSizeFromFmt()};
while(!mKillNow.load(std::memory_order_acquire)
&& mDevice->Connected.load(std::memory_order_acquire))
{
pollfd pollitem{};
pollitem.fd = mFd;
pollitem.events = POLLOUT;
int pret{poll(&pollitem, 1, 1000)};
if(pret < 0)
{
if(errno == EINTR || errno == EAGAIN)
continue;
ERR("poll failed: %s\n", strerror(errno));
mDevice->handleDisconnect("Failed to wait for playback buffer: %s", strerror(errno));
break;
}
else if(pret == 0)
{
WARN("poll timeout\n");
continue;
}
al::byte *write_ptr{mBuffer.data()};
size_t to_write{mBuffer.size()};
mDevice->renderSamples(write_ptr, static_cast<uint>(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));
mDevice->handleDisconnect("Failed to write playback samples: %s", strerror(errno));
break;
}
to_write -= static_cast<size_t>(wrote);
write_ptr += wrote;
}
}
return 0;
}
void SolarisBackend::open(const char *name)
{
if(!name)
name = solaris_device;
else if(strcmp(name, solaris_device) != 0)
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
name};
int fd{::open(solaris_driver.c_str(), O_WRONLY)};
if(fd == -1)
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
solaris_driver.c_str(), strerror(errno)};
if(mFd != -1)
::close(mFd);
mFd = fd;
mDevice->DeviceName = name;
}
bool SolarisBackend::reset()
{
audio_info_t info;
AUDIO_INITINFO(&info);
info.play.sample_rate = mDevice->Frequency;
info.play.channels = mDevice->channelsFromFmt();
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;
}
info.play.buffer_size = mDevice->BufferSize * mDevice->frameSizeFromFmt();
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
{
ERR("ioctl failed: %s\n", strerror(errno));
return false;
}
if(mDevice->channelsFromFmt() != info.play.channels)
{
if(info.play.channels >= 2)
mDevice->FmtChans = DevFmtStereo;
else if(info.play.channels == 1)
mDevice->FmtChans = DevFmtMono;
else
throw al::backend_exception{al::backend_error::DeviceError,
"Got %u device channels", info.play.channels};
}
if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8)
mDevice->FmtType = DevFmtUByte;
else if(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtByte;
else if(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtShort;
else if(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR)
mDevice->FmtType = DevFmtInt;
else
{
ERR("Got unhandled sample type: %d (0x%x)\n", info.play.precision, info.play.encoding);
return false;
}
uint frame_size{mDevice->bytesFromFmt() * info.play.channels};
mFrameStep = info.play.channels;
mDevice->Frequency = info.play.sample_rate;
mDevice->BufferSize = info.play.buffer_size / frame_size;
/* How to get the actual period size/count? */
mDevice->UpdateSize = mDevice->BufferSize / 2;
setDefaultChannelOrder();
mBuffer.resize(mDevice->UpdateSize * size_t{frame_size});
std::fill(mBuffer.begin(), mBuffer.end(), al::byte{});
return true;
}
void SolarisBackend::start()
{
try {
mKillNow.store(false, std::memory_order_release);
mThread = std::thread{std::mem_fn(&SolarisBackend::mixerProc), this};
}
catch(std::exception& e) {
throw al::backend_exception{al::backend_error::DeviceError,
"Failed to start mixing thread: %s", e.what()};
}
}
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; }
std::string SolarisBackendFactory::probe(BackendType type)
{
std::string outnames;
switch(type)
{
case BackendType::Playback:
{
struct stat buf;
if(stat(solaris_driver.c_str(), &buf) == 0)
outnames.append(solaris_device, sizeof(solaris_device));
}
break;
case BackendType::Capture:
break;
}
return outnames;
}
BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type)
{
if(type == BackendType::Playback)
return BackendPtr{new SolarisBackend{device}};
return nullptr;
}

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_SOLARIS_H
#define BACKENDS_SOLARIS_H
#include "base.h"
struct SolarisBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_SOLARIS_H */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
#ifndef BACKENDS_WASAPI_H
#define BACKENDS_WASAPI_H
#include "base.h"
struct WasapiBackendFactory final : public BackendFactory {
public:
bool init() override;
bool querySupport(BackendType type) override;
std::string probe(BackendType type) override;
BackendPtr createBackend(DeviceBase *device, BackendType type) override;
static BackendFactory &getFactory();
};
#endif /* BACKENDS_WASAPI_H */

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