Master is for patchsets
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{matrix.config.name}}
|
||||
runs-on: ${{matrix.config.os}}
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {
|
||||
name: "Visual Studio 64-bit",
|
||||
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: "macOS",
|
||||
os: macos-latest,
|
||||
cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON",
|
||||
build_type: "Release"
|
||||
}
|
||||
- {
|
||||
name: "Linux",
|
||||
os: ubuntu-latest,
|
||||
cmake_opts: "-DALSOFT_REQUIRE_ALSA=ON \
|
||||
-DALSOFT_REQUIRE_OSS=ON \
|
||||
-DALSOFT_REQUIRE_PORTAUDIO=ON \
|
||||
-DALSOFT_REQUIRE_PULSEAUDIO=ON \
|
||||
-DALSOFT_REQUIRE_JACK=ON",
|
||||
deps_cmdline: "sudo apt update && sudo apt-get install -qq \
|
||||
libpulse-dev \
|
||||
portaudio19-dev \
|
||||
libasound2-dev \
|
||||
libjack-dev \
|
||||
qtbase5-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}}
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
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
|
||||
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
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
Portions of this software are licensed under the BSD 3-Clause license.
|
||||
|
||||
Copyright (c) 2015, Archontis Politis
|
||||
Copyright (c) 2019, Christopher Robinson
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of Spherical-Harmonic-Transform nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-1606
File diff suppressed because it is too large
Load Diff
@@ -1,437 +0,0 @@
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the library GPL. It is
|
||||
numbered 2 because it goes with version 2 of the ordinary GPL.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Library General Public License, applies to some
|
||||
specially designated Free Software Foundation software, and to any
|
||||
other libraries whose authors decide to use it. You can use it for
|
||||
your libraries, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if
|
||||
you distribute copies of the library, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link a program with the library, you must provide
|
||||
complete object files to the recipients so that they can relink them
|
||||
with the library, after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
Our method of protecting your rights has two steps: (1) copyright
|
||||
the library, and (2) offer you this license which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
Also, for each distributor's protection, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
library. If the library is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original
|
||||
version, so that any problems introduced by others will not reflect on
|
||||
the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that companies distributing free
|
||||
software will individually obtain patent licenses, thus in effect
|
||||
transforming the program into proprietary software. To prevent this,
|
||||
we have made it clear that any patent must be licensed for everyone's
|
||||
free use or not licensed at all.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the ordinary
|
||||
GNU General Public License, which was designed for utility programs. This
|
||||
license, the GNU Library General Public License, applies to certain
|
||||
designated libraries. This license is quite different from the ordinary
|
||||
one; be sure to read it in full, and don't assume that anything in it is
|
||||
the same as in the ordinary license.
|
||||
|
||||
The reason we have a separate public license for some libraries is that
|
||||
they blur the distinction we usually make between modifying or adding to a
|
||||
program and simply using it. Linking a program with a library, without
|
||||
changing the library, is in some sense simply using the library, and is
|
||||
analogous to running a utility program or application program. However, in
|
||||
a textual and legal sense, the linked executable is a combined work, a
|
||||
derivative of the original library, and the ordinary General Public License
|
||||
treats it as such.
|
||||
|
||||
Because of this blurred distinction, using the ordinary General
|
||||
Public License for libraries did not effectively promote software
|
||||
sharing, because most developers did not use the libraries. We
|
||||
concluded that weaker conditions might promote sharing better.
|
||||
|
||||
However, unrestricted linking of non-free programs would deprive the
|
||||
users of those programs of all benefit from the free status of the
|
||||
libraries themselves. This Library General Public License is intended to
|
||||
permit developers of non-free programs to use free libraries, while
|
||||
preserving your freedom as a user of such programs to change the free
|
||||
libraries that are incorporated in them. (We have not seen how to achieve
|
||||
this as regards changes in header files, but we have achieved it as regards
|
||||
changes in the actual functions of the Library.) The hope is that this
|
||||
will lead to faster development of free libraries.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, while the latter only
|
||||
works together with the library.
|
||||
|
||||
Note that it is possible for a library to be covered by the ordinary
|
||||
General Public License rather than by this special one.
|
||||
|
||||
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library which
|
||||
contains a notice placed by the copyright holder or other authorized
|
||||
party saying it may be distributed under the terms of this Library
|
||||
General Public License (also called "this License"). Each licensee is
|
||||
addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also compile or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
c) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
d) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the source code distributed need not include anything that is normally
|
||||
distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Library General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,567 +0,0 @@
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
@@ -1,61 +1,7 @@
|
||||
OpenAL soft
|
||||
===========
|
||||
# openal-soft-vita
|
||||
|
||||
`master` branch CI status : [](https://travis-ci.org/kcat/openal-soft) [](https://ci.appveyor.com/api/projects/status/github/kcat/openal-soft?branch=master&svg=true)
|
||||
This repo contains patchsets for vita [openal-soft](https://openal-soft.org/) port.
|
||||
|
||||
OpenAL Soft is an LGPL-licensed, cross-platform, software implementation of the OpenAL 3D audio API. It's forked from the open-sourced Windows version available originally from openal.org's SVN repository (now defunct).
|
||||
OpenAL provides capabilities for playing audio in a virtual 3D environment. Distance attenuation, doppler shift, and directional sound emitters are among the features handled by the API. More advanced effects, including air absorption, occlusion, and environmental reverb, are available through the EFX extension. It also facilitates streaming audio, multi-channel buffers, and audio capture.
|
||||
Format is openal-soft-<openal_version>-vita-<patchset_version>.patch
|
||||
|
||||
More information is available on the [official website](http://openal-soft.org/)
|
||||
|
||||
Source Install
|
||||
-------------
|
||||
To install OpenAL Soft, use your favorite shell to go into the build/
|
||||
directory, and run:
|
||||
|
||||
```bash
|
||||
cmake ..
|
||||
```
|
||||
|
||||
Assuming configuration went well, you can then build it, typically using GNU
|
||||
Make (KDevelop, MSVC, and others are possible depending on your system setup
|
||||
and CMake configuration).
|
||||
|
||||
Please Note: Double check that the appropriate backends were detected. Often,
|
||||
complaints of no sound, crashing, and missing devices can be solved by making
|
||||
sure the correct backends are being used. CMake's output will identify which
|
||||
backends were enabled.
|
||||
|
||||
For most systems, you will likely want to make sure ALSA, OSS, and PulseAudio
|
||||
were detected (if your target system uses them). For Windows, make sure
|
||||
DirectSound was detected.
|
||||
|
||||
|
||||
Utilities
|
||||
---------
|
||||
The source package comes with an informational utility, openal-info, and is
|
||||
built by default. It prints out information provided by the ALC and AL sub-
|
||||
systems, including discovered devices, version information, and extensions.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
OpenAL Soft can be configured on a per-user and per-system basis. This allows
|
||||
users and sysadmins to control information provided to applications, as well
|
||||
as application-agnostic behavior of the library. See alsoftrc.sample for
|
||||
available settings.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
----------------
|
||||
|
||||
Special thanks go to:
|
||||
|
||||
- Creative Labs for the original source code this is based off of.
|
||||
- Christopher Fitzgerald for the current reverb effect implementation, and
|
||||
helping with the low-pass and HRTF filters.
|
||||
- Christian Borss for the 3D panning code previous versions used as a base.
|
||||
- Ben Davis for the idea behind a previous version of the click-removal code.
|
||||
- Richard Furse for helping with my understanding of Ambisonics that is used by
|
||||
the various parts of the library.
|
||||
For full source see vita-* branches
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
#
|
||||
|
||||
MESSAGE(FATAL_ERROR "Use the toolchain provided by the Android NDK")
|
||||
@@ -1,37 +0,0 @@
|
||||
# Cross-compiling requires CMake 2.6 or newer. Example:
|
||||
# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile.txt -DHOST=i686-w64-mingw32
|
||||
# Where 'i686-w64-mingw32' is the host prefix for your cross-compiler. If you
|
||||
# already have a toolchain file setup, you may use that instead of this file.
|
||||
|
||||
# the name of the target operating system
|
||||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
# which compilers to use for C and C++
|
||||
SET(CMAKE_C_COMPILER "${HOST}-gcc")
|
||||
SET(CMAKE_CXX_COMPILER "${HOST}-g++")
|
||||
SET(CMAKE_RC_COMPILER "${HOST}-windres")
|
||||
|
||||
# here is the target environment located
|
||||
SET(CMAKE_FIND_ROOT_PATH "/usr/${HOST}")
|
||||
|
||||
# here is where stuff gets installed to
|
||||
SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
|
||||
|
||||
# adjust the default behaviour of the FIND_XXX() commands:
|
||||
# search headers and libraries in the target environment, search
|
||||
# programs in the host environment
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
||||
# set env vars so that pkg-config will look in the appropriate directory for
|
||||
# .pc files (as there seems to be no way to force using ${HOST}-pkg-config)
|
||||
set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig")
|
||||
set(ENV{PKG_CONFIG_PATH} "")
|
||||
|
||||
# Qt4 tools
|
||||
SET(QT_QMAKE_EXECUTABLE ${HOST}-qmake)
|
||||
SET(QT_MOC_EXECUTABLE ${HOST}-moc)
|
||||
SET(QT_RCC_EXECUTABLE ${HOST}-rcc)
|
||||
SET(QT_UIC_EXECUTABLE ${HOST}-uic)
|
||||
SET(QT_LRELEASE_EXECUTABLE ${HOST}-lrelease)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,68 +0,0 @@
|
||||
#ifndef AL_AUXEFFECTSLOT_H
|
||||
#define AL_AUXEFFECTSLOT_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "atomic.h"
|
||||
#include "effectslot.h"
|
||||
#include "effects/base.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct ALbuffer;
|
||||
struct ALeffect;
|
||||
struct WetBuffer;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
std::atomic_flag PropsClean;
|
||||
|
||||
SlotState mState{SlotState::Initial};
|
||||
|
||||
RefCount ref{0u};
|
||||
|
||||
EffectSlot mSlot;
|
||||
|
||||
/* Self ID */
|
||||
ALuint id{};
|
||||
|
||||
ALeffectslot();
|
||||
ALeffectslot(const ALeffectslot&) = delete;
|
||||
ALeffectslot& operator=(const ALeffectslot&) = delete;
|
||||
~ALeffectslot();
|
||||
|
||||
ALenum initEffect(ALeffect *effect, ALCcontext *context);
|
||||
void updateProps(ALCcontext *context);
|
||||
|
||||
/* This can be new'd for the context's default effect slot. */
|
||||
DEF_NEWDEL(ALeffectslot)
|
||||
};
|
||||
|
||||
void UpdateAllEffectSlotProps(ALCcontext *context);
|
||||
|
||||
#endif
|
||||
-1631
File diff suppressed because it is too large
Load Diff
-65
@@ -1,65 +0,0 @@
|
||||
#ifndef AL_BUFFER_H
|
||||
#define AL_BUFFER_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "atomic.h"
|
||||
#include "buffer_storage.h"
|
||||
#include "inprogext.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
/* User formats */
|
||||
enum UserFmtType : unsigned char {
|
||||
UserFmtUByte = FmtUByte,
|
||||
UserFmtShort = FmtShort,
|
||||
UserFmtFloat = FmtFloat,
|
||||
UserFmtMulaw = FmtMulaw,
|
||||
UserFmtAlaw = FmtAlaw,
|
||||
UserFmtDouble = FmtDouble,
|
||||
|
||||
UserFmtIMA4 = 128,
|
||||
UserFmtMSADPCM,
|
||||
};
|
||||
enum UserFmtChannels : unsigned char {
|
||||
UserFmtMono = FmtMono,
|
||||
UserFmtStereo = FmtStereo,
|
||||
UserFmtRear = FmtRear,
|
||||
UserFmtQuad = FmtQuad,
|
||||
UserFmtX51 = FmtX51,
|
||||
UserFmtX61 = FmtX61,
|
||||
UserFmtX71 = FmtX71,
|
||||
UserFmtBFormat2D = FmtBFormat2D,
|
||||
UserFmtBFormat3D = FmtBFormat3D,
|
||||
};
|
||||
|
||||
|
||||
struct ALbuffer : public BufferStorage {
|
||||
ALbitfieldSOFT Access{0u};
|
||||
|
||||
UserFmtType OriginalType{};
|
||||
ALuint OriginalSize{0};
|
||||
ALuint OriginalAlign{0};
|
||||
|
||||
ALuint UnpackAlign{0};
|
||||
ALuint PackAlign{0};
|
||||
ALuint UnpackAmbiOrder{1};
|
||||
|
||||
ALbitfieldSOFT MappedAccess{0u};
|
||||
ALsizei MappedOffset{0};
|
||||
ALsizei MappedSize{0};
|
||||
|
||||
/* Number of times buffer was attached to a source (deletion can only occur when 0) */
|
||||
RefCount ref{0u};
|
||||
|
||||
/* Self ID */
|
||||
ALuint id{0};
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
#endif
|
||||
-745
@@ -1,745 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "effect.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
#include "AL/efx-presets.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alstring.h"
|
||||
#include "core/except.h"
|
||||
#include "core/logging.h"
|
||||
#include "effects/base.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)) ? std::addressof(*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>(PopCount(sublist.FreeMask)); })};
|
||||
|
||||
while(needed > count)
|
||||
{
|
||||
if UNLIKELY(device->EffectList.size() >= 1<<25)
|
||||
return false;
|
||||
|
||||
device->EffectList.emplace_back();
|
||||
auto sublist = device->EffectList.end() - 1;
|
||||
sublist->FreeMask = ~0_u64;
|
||||
sublist->Effects = static_cast<ALeffect*>(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64));
|
||||
if UNLIKELY(!sublist->Effects)
|
||||
{
|
||||
device->EffectList.pop_back();
|
||||
return false;
|
||||
}
|
||||
count += 64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ALeffect *AllocEffect(ALCdevice *device)
|
||||
{
|
||||
auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(),
|
||||
[](const EffectSubList &entry) noexcept -> bool
|
||||
{ return entry.FreeMask != 0; }
|
||||
);
|
||||
auto lidx = static_cast<ALuint>(std::distance(device->EffectList.begin(), sublist));
|
||||
auto slidx = static_cast<ALuint>(CountTrailingZeros(sublist->FreeMask));
|
||||
|
||||
ALeffect *effect{::new (sublist->Effects + slidx) ALeffect{}};
|
||||
InitEffectParams(effect, AL_EFFECT_NULL);
|
||||
|
||||
/* Add 1 to avoid effect ID 0. */
|
||||
effect->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
sublist->FreeMask &= ~(1_u64 << slidx);
|
||||
|
||||
return effect;
|
||||
}
|
||||
|
||||
void FreeEffect(ALCdevice *device, ALeffect *effect)
|
||||
{
|
||||
const ALuint id{effect->id - 1};
|
||||
const size_t lidx{id >> 6};
|
||||
const ALuint slidx{id & 0x3f};
|
||||
|
||||
al::destroy_at(effect);
|
||||
|
||||
device->EffectList[lidx].FreeMask |= 1_u64 << slidx;
|
||||
}
|
||||
|
||||
inline ALeffect *LookupEffect(ALCdevice *device, ALuint id)
|
||||
{
|
||||
const size_t lidx{(id-1) >> 6};
|
||||
const ALuint slidx{(id-1) & 0x3f};
|
||||
|
||||
if UNLIKELY(lidx >= device->EffectList.size())
|
||||
return nullptr;
|
||||
EffectSubList &sublist = device->EffectList[lidx];
|
||||
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
|
||||
return nullptr;
|
||||
return sublist.Effects + slidx;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Generating %d effects", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
if(!EnsureEffects(device, static_cast<ALuint>(n)))
|
||||
{
|
||||
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
|
||||
return;
|
||||
}
|
||||
|
||||
if LIKELY(n == 1)
|
||||
{
|
||||
/* Special handling for the easy and normal case. */
|
||||
ALeffect *effect{AllocEffect(device)};
|
||||
effects[0] = effect->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Store the allocated buffer IDs in a separate local list, to avoid
|
||||
* modifying the user storage in case of failure.
|
||||
*/
|
||||
al::vector<ALuint> ids;
|
||||
ids.reserve(static_cast<ALuint>(n));
|
||||
do {
|
||||
ALeffect *effect{AllocEffect(device)};
|
||||
ids.emplace_back(effect->id);
|
||||
} while(--n);
|
||||
std::copy(ids.cbegin(), ids.cend(), effects);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Deleting %d effects", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
/* First try to find any effects that are invalid. */
|
||||
auto validate_effect = [device](const ALuint eid) -> bool
|
||||
{ return !eid || LookupEffect(device, eid) != nullptr; };
|
||||
|
||||
const ALuint *effects_end = effects + n;
|
||||
auto inveffect = std::find_if_not(effects, effects_end, validate_effect);
|
||||
if UNLIKELY(inveffect != effects_end)
|
||||
{
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect);
|
||||
return;
|
||||
}
|
||||
|
||||
/* All good. Delete non-0 effect IDs. */
|
||||
auto delete_effect = [device](ALuint eid) -> void
|
||||
{
|
||||
ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr};
|
||||
if(effect) FreeEffect(device, effect);
|
||||
};
|
||||
std::for_each(effects, effects_end, delete_effect);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if LIKELY(context)
|
||||
{
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
if(!effect || LookupEffect(device, effect))
|
||||
return AL_TRUE;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else if(param == AL_EFFECT_TYPE)
|
||||
{
|
||||
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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else if(param == AL_EFFECT_TYPE)
|
||||
*value = aleffect->type;
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
|
||||
const ALeffect *aleffect{LookupEffect(device, effect)};
|
||||
if UNLIKELY(!aleffect)
|
||||
context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect);
|
||||
else 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 ALsizei idx{CountTrailingZeros(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);
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
#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);
|
||||
|
||||
#endif
|
||||
@@ -1,109 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects/base.h"
|
||||
#include "effects.h"
|
||||
|
||||
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()};
|
||||
@@ -1,276 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "aloptional.h"
|
||||
#include "core/logging.h"
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch");
|
||||
static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch");
|
||||
|
||||
inline al::optional<ChorusWaveform> WaveformFromEnum(ALenum type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case AL_CHORUS_WAVEFORM_SINUSOID: return al::make_optional(ChorusWaveform::Sinusoid);
|
||||
case AL_CHORUS_WAVEFORM_TRIANGLE: return al::make_optional(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); }
|
||||
|
||||
const 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()};
|
||||
@@ -1,72 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
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()};
|
||||
@@ -1,93 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "inprogext.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.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()};
|
||||
@@ -1,72 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.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()};
|
||||
@@ -1,114 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
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()};
|
||||
@@ -1,111 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
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()};
|
||||
@@ -1,79 +0,0 @@
|
||||
#ifndef AL_EFFECTS_EFFECTS_H
|
||||
#define AL_EFFECTS_EFFECTS_H
|
||||
|
||||
#include "AL/al.h"
|
||||
|
||||
#include "core/except.h"
|
||||
|
||||
union EffectProps;
|
||||
|
||||
|
||||
class effect_exception final : public al::base_exception {
|
||||
ALenum mErrorCode;
|
||||
|
||||
public:
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
effect_exception(ALenum code, const char *msg, ...);
|
||||
|
||||
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 */
|
||||
@@ -1,169 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
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()};
|
||||
@@ -1,128 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "aloptional.h"
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
al::optional<FShifterDirection> DirectionFromEmum(ALenum value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_DOWN: return al::make_optional(FShifterDirection::Down);
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return al::make_optional(FShifterDirection::Up);
|
||||
case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return al::make_optional(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()};
|
||||
@@ -1,134 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "aloptional.h"
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
al::optional<ModulatorWaveform> WaveformFromEmum(ALenum value)
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case AL_RING_MODULATOR_SINUSOID: return al::make_optional(ModulatorWaveform::Sinusoid);
|
||||
case AL_RING_MODULATOR_SAWTOOTH: return al::make_optional(ModulatorWaveform::Sawtooth);
|
||||
case AL_RING_MODULATOR_SQUARE: return al::make_optional(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()};
|
||||
@@ -1,93 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
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()};
|
||||
@@ -1,84 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
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()};
|
||||
@@ -1,556 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
void Reverb_setParami(EffectProps *props, ALenum param, int val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EAXREVERB_DECAY_HFLIMIT:
|
||||
if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"};
|
||||
props->Reverb.DecayHFLimit = val != AL_FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
|
||||
param};
|
||||
}
|
||||
}
|
||||
void Reverb_setParamiv(EffectProps *props, ALenum param, const int *vals)
|
||||
{ Reverb_setParami(props, param, vals[0]); }
|
||||
void Reverb_setParamf(EffectProps *props, ALenum param, float val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EAXREVERB_DENSITY:
|
||||
if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"};
|
||||
props->Reverb.Density = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DIFFUSION:
|
||||
if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"};
|
||||
props->Reverb.Diffusion = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_GAIN:
|
||||
if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"};
|
||||
props->Reverb.Gain = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_GAINHF:
|
||||
if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"};
|
||||
props->Reverb.GainHF = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_GAINLF:
|
||||
if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"};
|
||||
props->Reverb.GainLF = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DECAY_TIME:
|
||||
if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"};
|
||||
props->Reverb.DecayTime = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DECAY_HFRATIO:
|
||||
if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"};
|
||||
props->Reverb.DecayHFRatio = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DECAY_LFRATIO:
|
||||
if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"};
|
||||
props->Reverb.DecayLFRatio = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_REFLECTIONS_GAIN:
|
||||
if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"};
|
||||
props->Reverb.ReflectionsGain = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_REFLECTIONS_DELAY:
|
||||
if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"};
|
||||
props->Reverb.ReflectionsDelay = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_LATE_REVERB_GAIN:
|
||||
if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"};
|
||||
props->Reverb.LateReverbGain = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_LATE_REVERB_DELAY:
|
||||
if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"};
|
||||
props->Reverb.LateReverbDelay = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
|
||||
if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"};
|
||||
props->Reverb.AirAbsorptionGainHF = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_ECHO_TIME:
|
||||
if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"};
|
||||
props->Reverb.EchoTime = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_ECHO_DEPTH:
|
||||
if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"};
|
||||
props->Reverb.EchoDepth = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_MODULATION_TIME:
|
||||
if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"};
|
||||
props->Reverb.ModulationTime = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_MODULATION_DEPTH:
|
||||
if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"};
|
||||
props->Reverb.ModulationDepth = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_HFREFERENCE:
|
||||
if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"};
|
||||
props->Reverb.HFReference = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_LFREFERENCE:
|
||||
if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"};
|
||||
props->Reverb.LFReference = val;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
|
||||
if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"};
|
||||
props->Reverb.RoomRolloffFactor = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
|
||||
}
|
||||
}
|
||||
void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EAXREVERB_REFLECTIONS_PAN:
|
||||
if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"};
|
||||
props->Reverb.ReflectionsPan[0] = vals[0];
|
||||
props->Reverb.ReflectionsPan[1] = vals[1];
|
||||
props->Reverb.ReflectionsPan[2] = vals[2];
|
||||
break;
|
||||
case AL_EAXREVERB_LATE_REVERB_PAN:
|
||||
if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2])))
|
||||
throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"};
|
||||
props->Reverb.LateReverbPan[0] = vals[0];
|
||||
props->Reverb.LateReverbPan[1] = vals[1];
|
||||
props->Reverb.LateReverbPan[2] = vals[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
Reverb_setParamf(props, param, vals[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Reverb_getParami(const EffectProps *props, ALenum param, int *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EAXREVERB_DECAY_HFLIMIT:
|
||||
*val = props->Reverb.DecayHFLimit;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x",
|
||||
param};
|
||||
}
|
||||
}
|
||||
void Reverb_getParamiv(const EffectProps *props, ALenum param, int *vals)
|
||||
{ Reverb_getParami(props, param, vals); }
|
||||
void Reverb_getParamf(const EffectProps *props, ALenum param, float *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EAXREVERB_DENSITY:
|
||||
*val = props->Reverb.Density;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DIFFUSION:
|
||||
*val = props->Reverb.Diffusion;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_GAIN:
|
||||
*val = props->Reverb.Gain;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_GAINHF:
|
||||
*val = props->Reverb.GainHF;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_GAINLF:
|
||||
*val = props->Reverb.GainLF;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DECAY_TIME:
|
||||
*val = props->Reverb.DecayTime;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DECAY_HFRATIO:
|
||||
*val = props->Reverb.DecayHFRatio;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_DECAY_LFRATIO:
|
||||
*val = props->Reverb.DecayLFRatio;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_REFLECTIONS_GAIN:
|
||||
*val = props->Reverb.ReflectionsGain;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_REFLECTIONS_DELAY:
|
||||
*val = props->Reverb.ReflectionsDelay;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_LATE_REVERB_GAIN:
|
||||
*val = props->Reverb.LateReverbGain;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_LATE_REVERB_DELAY:
|
||||
*val = props->Reverb.LateReverbDelay;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_AIR_ABSORPTION_GAINHF:
|
||||
*val = props->Reverb.AirAbsorptionGainHF;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_ECHO_TIME:
|
||||
*val = props->Reverb.EchoTime;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_ECHO_DEPTH:
|
||||
*val = props->Reverb.EchoDepth;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_MODULATION_TIME:
|
||||
*val = props->Reverb.ModulationTime;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_MODULATION_DEPTH:
|
||||
*val = props->Reverb.ModulationDepth;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_HFREFERENCE:
|
||||
*val = props->Reverb.HFReference;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_LFREFERENCE:
|
||||
*val = props->Reverb.LFReference;
|
||||
break;
|
||||
|
||||
case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR:
|
||||
*val = props->Reverb.RoomRolloffFactor;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param};
|
||||
}
|
||||
}
|
||||
void Reverb_getParamfv(const EffectProps *props, ALenum param, float *vals)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_EAXREVERB_REFLECTIONS_PAN:
|
||||
vals[0] = props->Reverb.ReflectionsPan[0];
|
||||
vals[1] = props->Reverb.ReflectionsPan[1];
|
||||
vals[2] = props->Reverb.ReflectionsPan[2];
|
||||
break;
|
||||
case AL_EAXREVERB_LATE_REVERB_PAN:
|
||||
vals[0] = props->Reverb.LateReverbPan[0];
|
||||
vals[1] = props->Reverb.LateReverbPan[1];
|
||||
vals[2] = props->Reverb.LateReverbPan[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
Reverb_getParamf(props, param, vals);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EffectProps genDefaultProps() noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY;
|
||||
props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION;
|
||||
props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN;
|
||||
props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF;
|
||||
props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF;
|
||||
props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME;
|
||||
props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO;
|
||||
props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO;
|
||||
props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN;
|
||||
props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY;
|
||||
props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
|
||||
props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
|
||||
props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ;
|
||||
props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN;
|
||||
props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY;
|
||||
props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
|
||||
props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
|
||||
props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ;
|
||||
props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME;
|
||||
props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH;
|
||||
props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME;
|
||||
props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH;
|
||||
props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
|
||||
props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE;
|
||||
props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE;
|
||||
props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
|
||||
props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT;
|
||||
return props;
|
||||
}
|
||||
|
||||
|
||||
void StdReverb_setParami(EffectProps *props, ALenum param, int val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_REVERB_DECAY_HFLIMIT:
|
||||
if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb decay hflimit out of range"};
|
||||
props->Reverb.DecayHFLimit = val != AL_FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param};
|
||||
}
|
||||
}
|
||||
void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals)
|
||||
{ StdReverb_setParami(props, param, vals[0]); }
|
||||
void StdReverb_setParamf(EffectProps *props, ALenum param, float val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_REVERB_DENSITY:
|
||||
if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb density out of range"};
|
||||
props->Reverb.Density = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_DIFFUSION:
|
||||
if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb diffusion out of range"};
|
||||
props->Reverb.Diffusion = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_GAIN:
|
||||
if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb gain out of range"};
|
||||
props->Reverb.Gain = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_GAINHF:
|
||||
if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb gainhf out of range"};
|
||||
props->Reverb.GainHF = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_DECAY_TIME:
|
||||
if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb decay time out of range"};
|
||||
props->Reverb.DecayTime = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_DECAY_HFRATIO:
|
||||
if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb decay hfratio out of range"};
|
||||
props->Reverb.DecayHFRatio = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_REFLECTIONS_GAIN:
|
||||
if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb reflections gain out of range"};
|
||||
props->Reverb.ReflectionsGain = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_REFLECTIONS_DELAY:
|
||||
if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb reflections delay out of range"};
|
||||
props->Reverb.ReflectionsDelay = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_LATE_REVERB_GAIN:
|
||||
if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb gain out of range"};
|
||||
props->Reverb.LateReverbGain = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_LATE_REVERB_DELAY:
|
||||
if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb delay out of range"};
|
||||
props->Reverb.LateReverbDelay = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_AIR_ABSORPTION_GAINHF:
|
||||
if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb air absorption gainhf out of range"};
|
||||
props->Reverb.AirAbsorptionGainHF = val;
|
||||
break;
|
||||
|
||||
case AL_REVERB_ROOM_ROLLOFF_FACTOR:
|
||||
if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR))
|
||||
throw effect_exception{AL_INVALID_VALUE, "Reverb room rolloff factor out of range"};
|
||||
props->Reverb.RoomRolloffFactor = val;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param};
|
||||
}
|
||||
}
|
||||
void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals)
|
||||
{ StdReverb_setParamf(props, param, vals[0]); }
|
||||
|
||||
void StdReverb_getParami(const EffectProps *props, ALenum param, int *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_REVERB_DECAY_HFLIMIT:
|
||||
*val = props->Reverb.DecayHFLimit;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param};
|
||||
}
|
||||
}
|
||||
void StdReverb_getParamiv(const EffectProps *props, ALenum param, int *vals)
|
||||
{ StdReverb_getParami(props, param, vals); }
|
||||
void StdReverb_getParamf(const EffectProps *props, ALenum param, float *val)
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_REVERB_DENSITY:
|
||||
*val = props->Reverb.Density;
|
||||
break;
|
||||
|
||||
case AL_REVERB_DIFFUSION:
|
||||
*val = props->Reverb.Diffusion;
|
||||
break;
|
||||
|
||||
case AL_REVERB_GAIN:
|
||||
*val = props->Reverb.Gain;
|
||||
break;
|
||||
|
||||
case AL_REVERB_GAINHF:
|
||||
*val = props->Reverb.GainHF;
|
||||
break;
|
||||
|
||||
case AL_REVERB_DECAY_TIME:
|
||||
*val = props->Reverb.DecayTime;
|
||||
break;
|
||||
|
||||
case AL_REVERB_DECAY_HFRATIO:
|
||||
*val = props->Reverb.DecayHFRatio;
|
||||
break;
|
||||
|
||||
case AL_REVERB_REFLECTIONS_GAIN:
|
||||
*val = props->Reverb.ReflectionsGain;
|
||||
break;
|
||||
|
||||
case AL_REVERB_REFLECTIONS_DELAY:
|
||||
*val = props->Reverb.ReflectionsDelay;
|
||||
break;
|
||||
|
||||
case AL_REVERB_LATE_REVERB_GAIN:
|
||||
*val = props->Reverb.LateReverbGain;
|
||||
break;
|
||||
|
||||
case AL_REVERB_LATE_REVERB_DELAY:
|
||||
*val = props->Reverb.LateReverbDelay;
|
||||
break;
|
||||
|
||||
case AL_REVERB_AIR_ABSORPTION_GAINHF:
|
||||
*val = props->Reverb.AirAbsorptionGainHF;
|
||||
break;
|
||||
|
||||
case AL_REVERB_ROOM_ROLLOFF_FACTOR:
|
||||
*val = props->Reverb.RoomRolloffFactor;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param};
|
||||
}
|
||||
}
|
||||
void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals)
|
||||
{ StdReverb_getParamf(props, param, vals); }
|
||||
|
||||
EffectProps genDefaultStdProps() noexcept
|
||||
{
|
||||
EffectProps props{};
|
||||
props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY;
|
||||
props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION;
|
||||
props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN;
|
||||
props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF;
|
||||
props.Reverb.GainLF = 1.0f;
|
||||
props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME;
|
||||
props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO;
|
||||
props.Reverb.DecayLFRatio = 1.0f;
|
||||
props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN;
|
||||
props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY;
|
||||
props.Reverb.ReflectionsPan[0] = 0.0f;
|
||||
props.Reverb.ReflectionsPan[1] = 0.0f;
|
||||
props.Reverb.ReflectionsPan[2] = 0.0f;
|
||||
props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN;
|
||||
props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY;
|
||||
props.Reverb.LateReverbPan[0] = 0.0f;
|
||||
props.Reverb.LateReverbPan[1] = 0.0f;
|
||||
props.Reverb.LateReverbPan[2] = 0.0f;
|
||||
props.Reverb.EchoTime = 0.25f;
|
||||
props.Reverb.EchoDepth = 0.0f;
|
||||
props.Reverb.ModulationTime = 0.25f;
|
||||
props.Reverb.ModulationDepth = 0.0f;
|
||||
props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF;
|
||||
props.Reverb.HFReference = 5000.0f;
|
||||
props.Reverb.LFReference = 250.0f;
|
||||
props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR;
|
||||
props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT;
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(Reverb);
|
||||
|
||||
const EffectProps ReverbEffectProps{genDefaultProps()};
|
||||
|
||||
DEFINE_ALEFFECT_VTABLE(StdReverb);
|
||||
|
||||
const EffectProps StdReverbEffectProps{genDefaultStdProps()};
|
||||
@@ -1,247 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "aloptional.h"
|
||||
#include "effects.h"
|
||||
#include "effects/base.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
al::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val)
|
||||
{
|
||||
#define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \
|
||||
return al::make_optional(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 al::make_optional(VMorpherWaveform::Sinusoid);
|
||||
case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return al::make_optional(VMorpherWaveform::Triangle);
|
||||
case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return al::make_optional(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()};
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "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 UNLIKELY(!context)
|
||||
{
|
||||
constexpr ALenum deferror{AL_INVALID_OPERATION};
|
||||
WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror);
|
||||
if(TrapALError)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if(IsDebuggerPresent())
|
||||
DebugBreak();
|
||||
#elif defined(SIGTRAP)
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
return deferror;
|
||||
}
|
||||
|
||||
return context->mLastError.exchange(AL_NO_ERROR);
|
||||
}
|
||||
END_API_FUNC
|
||||
-223
@@ -1,223 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "event.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "async_event.h"
|
||||
#include "core/except.h"
|
||||
#include "core/logging.h"
|
||||
#include "effects/base.h"
|
||||
#include "inprogext.h"
|
||||
#include "opthelpers.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
#include "voice_change.h"
|
||||
|
||||
|
||||
static int EventThread(ALCcontext *context)
|
||||
{
|
||||
RingBuffer *ring{context->mAsyncEvents.get()};
|
||||
bool quitnow{false};
|
||||
while LIKELY(!quitnow)
|
||||
{
|
||||
auto evt_data = ring->getReadVector().first;
|
||||
if(evt_data.len == 0)
|
||||
{
|
||||
context->mEventSem.wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mEventCbLock};
|
||||
do {
|
||||
auto *evt_ptr = reinterpret_cast<AsyncEvent*>(evt_data.buf);
|
||||
evt_data.buf += sizeof(AsyncEvent);
|
||||
evt_data.len -= 1;
|
||||
|
||||
AsyncEvent evt{*evt_ptr};
|
||||
al::destroy_at(evt_ptr);
|
||||
ring->readAdvance(1);
|
||||
|
||||
quitnow = evt.EnumType == EventType_KillThread;
|
||||
if UNLIKELY(quitnow) break;
|
||||
|
||||
if(evt.EnumType == EventType_ReleaseEffectState)
|
||||
{
|
||||
evt.u.mEffectState->release();
|
||||
continue;
|
||||
}
|
||||
|
||||
uint enabledevts{context->mEnabledEvts.load(std::memory_order_acquire)};
|
||||
if(!context->mEventCb) continue;
|
||||
|
||||
if(evt.EnumType == EventType_SourceStateChange)
|
||||
{
|
||||
if(!(enabledevts&EventType_SourceStateChange))
|
||||
continue;
|
||||
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 VChangeState::Reset:
|
||||
msg += "AL_INITIAL";
|
||||
state = AL_INITIAL;
|
||||
break;
|
||||
case VChangeState::Stop:
|
||||
msg += "AL_STOPPED";
|
||||
state = AL_STOPPED;
|
||||
break;
|
||||
case VChangeState::Play:
|
||||
msg += "AL_PLAYING";
|
||||
state = AL_PLAYING;
|
||||
break;
|
||||
case VChangeState::Pause:
|
||||
msg += "AL_PAUSED";
|
||||
state = AL_PAUSED;
|
||||
break;
|
||||
/* Shouldn't happen */
|
||||
case VChangeState::Restart:
|
||||
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 == EventType_BufferCompleted)
|
||||
{
|
||||
if(!(enabledevts&EventType_BufferCompleted))
|
||||
continue;
|
||||
std::string msg{std::to_string(evt.u.bufcomp.count)};
|
||||
if(evt.u.bufcomp.count == 1) msg += " buffer completed";
|
||||
else msg += " buffers completed";
|
||||
context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id,
|
||||
evt.u.bufcomp.count, static_cast<ALsizei>(msg.length()), msg.c_str(),
|
||||
context->mEventParam);
|
||||
}
|
||||
else if(evt.EnumType == EventType_Disconnected)
|
||||
{
|
||||
if(!(enabledevts&EventType_Disconnected))
|
||||
continue;
|
||||
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);
|
||||
}
|
||||
::new(evt_data.buf) AsyncEvent{EventType_KillThread};
|
||||
ring->writeAdvance(1);
|
||||
|
||||
ctx->mEventSem.post();
|
||||
if(ctx->mEventThread.joinable())
|
||||
ctx->mEventThread.join();
|
||||
}
|
||||
|
||||
AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count);
|
||||
if(count <= 0) return;
|
||||
if(!types) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
|
||||
|
||||
uint flags{0};
|
||||
const ALenum *types_end = types+count;
|
||||
auto bad_type = std::find_if_not(types, types_end,
|
||||
[&flags](ALenum type) noexcept -> bool
|
||||
{
|
||||
if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT)
|
||||
flags |= EventType_BufferCompleted;
|
||||
else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT)
|
||||
flags |= EventType_SourceStateChange;
|
||||
else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT)
|
||||
flags |= EventType_Disconnected;
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
);
|
||||
if(bad_type != types_end)
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,, "Invalid event type 0x%04x", *bad_type);
|
||||
|
||||
if(enable)
|
||||
{
|
||||
uint 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
|
||||
{
|
||||
uint enabledevts{context->mEnabledEvts.load(std::memory_order_relaxed)};
|
||||
while(context->mEnabledEvts.compare_exchange_weak(enabledevts, enabledevts&~flags,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire) == 0)
|
||||
{
|
||||
}
|
||||
/* Wait to ensure the event handler sees the changed flags before
|
||||
* returning.
|
||||
*/
|
||||
std::lock_guard<std::mutex>{context->mEventCbLock};
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
std::lock_guard<std::mutex> __{context->mEventCbLock};
|
||||
context->mEventCb = callback;
|
||||
context->mEventParam = userParam;
|
||||
}
|
||||
END_API_FUNC
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef AL_EVENT_H
|
||||
#define AL_EVENT_H
|
||||
|
||||
struct ALCcontext;
|
||||
|
||||
void StartEventThrd(ALCcontext *ctx);
|
||||
void StopEventThrd(ALCcontext *ctx);
|
||||
|
||||
#endif
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "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 UNLIKELY(!context) return AL_FALSE;
|
||||
|
||||
if(!extName)
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE, AL_FALSE, "NULL pointer");
|
||||
|
||||
size_t len{strlen(extName)};
|
||||
const char *ptr{context->mExtensionList};
|
||||
while(ptr && *ptr)
|
||||
{
|
||||
if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len])))
|
||||
return AL_TRUE;
|
||||
|
||||
if((ptr=strchr(ptr, ' ')) != nullptr)
|
||||
{
|
||||
do {
|
||||
++ptr;
|
||||
} while(isspace(*ptr));
|
||||
}
|
||||
}
|
||||
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(!funcName) return nullptr;
|
||||
return alcGetProcAddress(nullptr, funcName);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(!enumName) return static_cast<ALenum>(0);
|
||||
return alcGetEnumValue(nullptr, enumName);
|
||||
}
|
||||
END_API_FUNC
|
||||
-714
@@ -1,714 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "filter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <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 "alcmain.h"
|
||||
#include "alcontext.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:
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
filter_exception(ALenum code, const char *msg, ...) : mErrorCode{code}
|
||||
{
|
||||
std::va_list args;
|
||||
va_start(args, msg);
|
||||
setMessage(msg, args);
|
||||
va_end(args);
|
||||
}
|
||||
ALenum errorCode() const noexcept { return mErrorCode; }
|
||||
};
|
||||
|
||||
#define FILTER_MIN_GAIN 0.0f
|
||||
#define FILTER_MAX_GAIN 4.0f /* +12dB */
|
||||
|
||||
#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 >= FILTER_MIN_GAIN && val <= FILTER_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 >= FILTER_MIN_GAIN && val <= FILTER_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 >= FILTER_MIN_GAIN && val <= FILTER_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>(PopCount(sublist.FreeMask)); })};
|
||||
|
||||
while(needed > count)
|
||||
{
|
||||
if UNLIKELY(device->FilterList.size() >= 1<<25)
|
||||
return false;
|
||||
|
||||
device->FilterList.emplace_back();
|
||||
auto sublist = device->FilterList.end() - 1;
|
||||
sublist->FreeMask = ~0_u64;
|
||||
sublist->Filters = static_cast<ALfilter*>(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64));
|
||||
if UNLIKELY(!sublist->Filters)
|
||||
{
|
||||
device->FilterList.pop_back();
|
||||
return false;
|
||||
}
|
||||
count += 64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
ALfilter *AllocFilter(ALCdevice *device)
|
||||
{
|
||||
auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(),
|
||||
[](const FilterSubList &entry) noexcept -> bool
|
||||
{ return entry.FreeMask != 0; }
|
||||
);
|
||||
auto lidx = static_cast<ALuint>(std::distance(device->FilterList.begin(), sublist));
|
||||
auto slidx = static_cast<ALuint>(CountTrailingZeros(sublist->FreeMask));
|
||||
|
||||
ALfilter *filter{::new(sublist->Filters + slidx) ALfilter{}};
|
||||
InitFilterParams(filter, AL_FILTER_NULL);
|
||||
|
||||
/* Add 1 to avoid filter ID 0. */
|
||||
filter->id = ((lidx<<6) | slidx) + 1;
|
||||
|
||||
sublist->FreeMask &= ~(1_u64 << slidx);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
void FreeFilter(ALCdevice *device, ALfilter *filter)
|
||||
{
|
||||
const ALuint id{filter->id - 1};
|
||||
const size_t lidx{id >> 6};
|
||||
const ALuint slidx{id & 0x3f};
|
||||
|
||||
al::destroy_at(filter);
|
||||
|
||||
device->FilterList[lidx].FreeMask |= 1_u64 << slidx;
|
||||
}
|
||||
|
||||
|
||||
inline ALfilter *LookupFilter(ALCdevice *device, ALuint id)
|
||||
{
|
||||
const size_t lidx{(id-1) >> 6};
|
||||
const ALuint slidx{(id-1) & 0x3f};
|
||||
|
||||
if UNLIKELY(lidx >= device->FilterList.size())
|
||||
return nullptr;
|
||||
FilterSubList &sublist = device->FilterList[lidx];
|
||||
if UNLIKELY(sublist.FreeMask & (1_u64 << slidx))
|
||||
return nullptr;
|
||||
return sublist.Filters + slidx;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Generating %d filters", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->EffectLock};
|
||||
if(!EnsureFilters(device, static_cast<ALuint>(n)))
|
||||
{
|
||||
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s");
|
||||
return;
|
||||
}
|
||||
|
||||
if LIKELY(n == 1)
|
||||
{
|
||||
/* Special handling for the easy and normal case. */
|
||||
ALfilter *filter{AllocFilter(device)};
|
||||
if(filter) filters[0] = filter->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Store the allocated buffer IDs in a separate local list, to avoid
|
||||
* modifying the user storage in case of failure.
|
||||
*/
|
||||
al::vector<ALuint> ids;
|
||||
ids.reserve(static_cast<ALuint>(n));
|
||||
do {
|
||||
ALfilter *filter{AllocFilter(device)};
|
||||
ids.emplace_back(filter->id);
|
||||
} while(--n);
|
||||
std::copy(ids.begin(), ids.end(), filters);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if UNLIKELY(n < 0)
|
||||
context->setError(AL_INVALID_VALUE, "Deleting %d filters", n);
|
||||
if UNLIKELY(n <= 0) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
/* First try to find any filters that are invalid. */
|
||||
auto validate_filter = [device](const ALuint fid) -> bool
|
||||
{ return !fid || LookupFilter(device, fid) != nullptr; };
|
||||
|
||||
const ALuint *filters_end = filters + n;
|
||||
auto invflt = std::find_if_not(filters, filters_end, validate_filter);
|
||||
if UNLIKELY(invflt != filters_end)
|
||||
{
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt);
|
||||
return;
|
||||
}
|
||||
|
||||
/* All good. Delete non-0 filter IDs. */
|
||||
auto delete_filter = [device](const ALuint fid) -> void
|
||||
{
|
||||
ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr};
|
||||
if(filter) FreeFilter(device, filter);
|
||||
};
|
||||
std::for_each(filters, filters_end, delete_filter);
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if LIKELY(context)
|
||||
{
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
if(!filter || LookupFilter(device, filter))
|
||||
return AL_TRUE;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
if(param == AL_FILTER_TYPE)
|
||||
{
|
||||
if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS
|
||||
|| value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)
|
||||
InitFilterParams(alfilt, value);
|
||||
else
|
||||
context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value);
|
||||
}
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
const ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter);
|
||||
else
|
||||
{
|
||||
if(param == AL_FILTER_TYPE)
|
||||
*value = alfilt->type;
|
||||
else 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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
const ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
const ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
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 UNLIKELY(!context) return;
|
||||
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
std::lock_guard<std::mutex> _{device->FilterLock};
|
||||
|
||||
const ALfilter *alfilt{LookupFilter(device, filter)};
|
||||
if UNLIKELY(!alfilt)
|
||||
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 ALsizei idx{CountTrailingZeros(usemask)};
|
||||
al::destroy_at(Filters+idx);
|
||||
usemask &= ~(1_u64 << idx);
|
||||
}
|
||||
FreeMask = ~usemask;
|
||||
al_free(Filters);
|
||||
Filters = nullptr;
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
#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
|
||||
-452
@@ -1,452 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "listener.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "atomic.h"
|
||||
#include "core/except.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
|
||||
UpdateListenerProps(context.get()); \
|
||||
else \
|
||||
listener.PropsClean.clear(std::memory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
if(!(value >= 0.0f && std::isfinite(value)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener gain out of range");
|
||||
listener.Gain = value;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
case AL_METERS_PER_UNIT:
|
||||
if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener meters per unit out of range");
|
||||
listener.mMetersPerUnit = value;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener position out of range");
|
||||
listener.Position[0] = value1;
|
||||
listener.Position[1] = value2;
|
||||
listener.Position[2] = value3;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3)))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener velocity out of range");
|
||||
listener.Velocity[0] = value1;
|
||||
listener.Velocity[1] = value2;
|
||||
listener.Velocity[2] = value3;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API 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 UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values) SETERR_RETURN(context, AL_INVALID_VALUE,, "NULL pointer");
|
||||
switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) &&
|
||||
std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])))
|
||||
SETERR_RETURN(context, AL_INVALID_VALUE,, "Listener orientation out of range");
|
||||
/* AT then UP */
|
||||
listener.OrientAt[0] = values[0];
|
||||
listener.OrientAt[1] = values[1];
|
||||
listener.OrientAt[2] = values[2];
|
||||
listener.OrientUp[0] = values[3];
|
||||
listener.OrientUp[1] = values[4];
|
||||
listener.OrientUp[2] = values[5];
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, static_cast<ALfloat>(value1), static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
ALfloat fvals[6];
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alListener3f(param, static_cast<ALfloat>(values[0]), static_cast<ALfloat>(values[1]), static_cast<ALfloat>(values[2]));
|
||||
return;
|
||||
|
||||
case AL_ORIENTATION:
|
||||
fvals[0] = static_cast<ALfloat>(values[0]);
|
||||
fvals[1] = static_cast<ALfloat>(values[1]);
|
||||
fvals[2] = static_cast<ALfloat>(values[2]);
|
||||
fvals[3] = static_cast<ALfloat>(values[3]);
|
||||
fvals[4] = static_cast<ALfloat>(values[4]);
|
||||
fvals[5] = static_cast<ALfloat>(values[5]);
|
||||
alListenerfv(param, fvals);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_GAIN:
|
||||
*value = listener.Gain;
|
||||
break;
|
||||
|
||||
case AL_METERS_PER_UNIT:
|
||||
*value = listener.mMetersPerUnit;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value1 || !value2 || !value3)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
*value1 = listener.Position[0];
|
||||
*value2 = listener.Position[1];
|
||||
*value3 = listener.Position[2];
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
*value1 = listener.Velocity[0];
|
||||
*value2 = listener.Velocity[1];
|
||||
*value3 = listener.Velocity[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API 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 UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
// AT then UP
|
||||
values[0] = listener.OrientAt[0];
|
||||
values[1] = listener.OrientAt[1];
|
||||
values[2] = listener.OrientAt[2];
|
||||
values[3] = listener.OrientUp[0];
|
||||
values[4] = listener.OrientUp[1];
|
||||
values[5] = listener.OrientUp[2];
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!value1 || !value2 || !value3)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
*value1 = static_cast<ALint>(listener.Position[0]);
|
||||
*value2 = static_cast<ALint>(listener.Position[1]);
|
||||
*value3 = static_cast<ALint>(listener.Position[2]);
|
||||
break;
|
||||
|
||||
case AL_VELOCITY:
|
||||
*value1 = static_cast<ALint>(listener.Velocity[0]);
|
||||
*value2 = static_cast<ALint>(listener.Velocity[1]);
|
||||
*value3 = static_cast<ALint>(listener.Velocity[2]);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values)
|
||||
START_API_FUNC
|
||||
{
|
||||
switch(param)
|
||||
{
|
||||
case AL_POSITION:
|
||||
case AL_VELOCITY:
|
||||
alGetListener3i(param, values+0, values+1, values+2);
|
||||
return;
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
ALlistener &listener = context->mListener;
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(param)
|
||||
{
|
||||
case AL_ORIENTATION:
|
||||
// AT then UP
|
||||
values[0] = static_cast<ALint>(listener.OrientAt[0]);
|
||||
values[1] = static_cast<ALint>(listener.OrientAt[1]);
|
||||
values[2] = static_cast<ALint>(listener.OrientAt[2]);
|
||||
values[3] = static_cast<ALint>(listener.OrientUp[0]);
|
||||
values[4] = static_cast<ALint>(listener.OrientUp[1]);
|
||||
values[5] = static_cast<ALint>(listener.OrientUp[2]);
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property");
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
void UpdateListenerProps(ALCcontext *context)
|
||||
{
|
||||
/* Get an unused proprty container, or allocate a new one as needed. */
|
||||
ListenerProps *props{context->mFreeListenerProps.load(std::memory_order_acquire)};
|
||||
if(!props)
|
||||
props = new ListenerProps{};
|
||||
else
|
||||
{
|
||||
ListenerProps *next;
|
||||
do {
|
||||
next = props->next.load(std::memory_order_relaxed);
|
||||
} while(context->mFreeListenerProps.compare_exchange_weak(props, next,
|
||||
std::memory_order_seq_cst, std::memory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
/* Copy in current property values. */
|
||||
ALlistener &listener = context->mListener;
|
||||
props->Position = listener.Position;
|
||||
props->Velocity = listener.Velocity;
|
||||
props->OrientAt = listener.OrientAt;
|
||||
props->OrientUp = listener.OrientUp;
|
||||
props->Gain = listener.Gain;
|
||||
props->MetersPerUnit = listener.mMetersPerUnit;
|
||||
|
||||
/* Set the new container for updating internal parameters. */
|
||||
props = context->mParams.ListenerUpdate.exchange(props, std::memory_order_acq_rel);
|
||||
if(props)
|
||||
{
|
||||
/* If there was an unused update container, put it back in the
|
||||
* freelist.
|
||||
*/
|
||||
AtomicReplaceHead(context->mFreeListenerProps, props);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#ifndef AL_LISTENER_H
|
||||
#define AL_LISTENER_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/efx.h"
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
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};
|
||||
|
||||
std::atomic_flag PropsClean;
|
||||
|
||||
ALlistener() { PropsClean.test_and_set(std::memory_order_relaxed); }
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
void UpdateListenerProps(ALCcontext *context);
|
||||
|
||||
#endif
|
||||
-3494
File diff suppressed because it is too large
Load Diff
-127
@@ -1,127 +0,0 @@
|
||||
#ifndef AL_SOURCE_H
|
||||
#define AL_SOURCE_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "math_defs.h"
|
||||
#include "vector.h"
|
||||
#include "voice.h"
|
||||
|
||||
struct ALbuffer;
|
||||
struct ALeffectslot;
|
||||
struct BufferlistItem;
|
||||
|
||||
|
||||
#define DEFAULT_SENDS 2
|
||||
|
||||
#define INVALID_VOICE_IDX static_cast<ALuint>(-1)
|
||||
|
||||
|
||||
struct ALsource {
|
||||
/** Source properties. */
|
||||
float Pitch{1.0f};
|
||||
float Gain{1.0f};
|
||||
float OuterGain{0.0f};
|
||||
float MinGain{0.0f};
|
||||
float MaxGain{1.0f};
|
||||
float InnerAngle{360.0f};
|
||||
float OuterAngle{360.0f};
|
||||
float RefDistance{1.0f};
|
||||
float MaxDistance{std::numeric_limits<float>::max()};
|
||||
float RolloffFactor{1.0f};
|
||||
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> Direction{{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}};
|
||||
bool HeadRelative{false};
|
||||
bool Looping{false};
|
||||
DistanceModel mDistanceModel{DistanceModel::Default};
|
||||
Resampler mResampler{ResamplerDefault};
|
||||
DirectMode DirectChannels{DirectMode::Off};
|
||||
SpatializeMode mSpatialize{SpatializeMode::Auto};
|
||||
|
||||
bool DryGainHFAuto{true};
|
||||
bool WetGainAuto{true};
|
||||
bool WetGainHFAuto{true};
|
||||
float OuterGainHF{1.0f};
|
||||
|
||||
float AirAbsorptionFactor{0.0f};
|
||||
float RoomRolloffFactor{0.0f};
|
||||
float DopplerFactor{1.0f};
|
||||
|
||||
/* NOTE: Stereo pan angles are specified in radians, counter-clockwise
|
||||
* rather than clockwise.
|
||||
*/
|
||||
std::array<float,2> StereoPan{{Deg2Rad( 30.0f), Deg2Rad(-30.0f)}};
|
||||
|
||||
float Radius{0.0f};
|
||||
|
||||
/** Direct filter and auxiliary send info. */
|
||||
struct {
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float HFReference;
|
||||
float GainLF;
|
||||
float LFReference;
|
||||
} Direct;
|
||||
struct SendData {
|
||||
ALeffectslot *Slot;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float HFReference;
|
||||
float GainLF;
|
||||
float LFReference;
|
||||
};
|
||||
std::array<SendData,MAX_SENDS> Send;
|
||||
|
||||
/**
|
||||
* Last user-specified offset, and the offset type (bytes, samples, or
|
||||
* seconds).
|
||||
*/
|
||||
double Offset{0.0};
|
||||
ALenum OffsetType{AL_NONE};
|
||||
|
||||
/** Source type (static, streaming, or undetermined) */
|
||||
ALenum SourceType{AL_UNDETERMINED};
|
||||
|
||||
/** Source state (initial, playing, paused, or stopped) */
|
||||
ALenum state{AL_INITIAL};
|
||||
|
||||
/** Source Buffer Queue head. */
|
||||
BufferlistItem *queue{nullptr};
|
||||
|
||||
std::atomic_flag PropsClean;
|
||||
|
||||
/* Index into the context's Voices array. Lazily updated, only checked and
|
||||
* reset when looking up the voice.
|
||||
*/
|
||||
ALuint VoiceIdx{INVALID_VOICE_IDX};
|
||||
|
||||
/** Self ID */
|
||||
ALuint id{0};
|
||||
|
||||
|
||||
ALsource();
|
||||
~ALsource();
|
||||
|
||||
ALsource(const ALsource&) = delete;
|
||||
ALsource& operator=(const ALsource&) = delete;
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
void UpdateAllSourceProps(ALCcontext *context);
|
||||
|
||||
#endif
|
||||
-893
@@ -1,893 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2000 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "alu.h"
|
||||
#include "atomic.h"
|
||||
#include "core/except.h"
|
||||
#include "event.h"
|
||||
#include "inprogext.h"
|
||||
#include "opthelpers.h"
|
||||
#include "strutils.h"
|
||||
#include "voice.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ALchar alVendor[] = "OpenAL Community";
|
||||
constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION;
|
||||
constexpr ALchar alRenderer[] = "OpenAL Soft";
|
||||
|
||||
// Error Messages
|
||||
constexpr ALchar alNoError[] = "No Error";
|
||||
constexpr ALchar alErrInvalidName[] = "Invalid Name";
|
||||
constexpr ALchar alErrInvalidEnum[] = "Invalid Enum";
|
||||
constexpr ALchar alErrInvalidValue[] = "Invalid Value";
|
||||
constexpr ALchar alErrInvalidOp[] = "Invalid Operation";
|
||||
constexpr ALchar alErrOutOfMemory[] = "Out of Memory";
|
||||
|
||||
/* Resampler strings */
|
||||
template<Resampler rtype> struct ResamplerName { };
|
||||
template<> struct ResamplerName<Resampler::Point>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "Nearest"; } };
|
||||
template<> struct ResamplerName<Resampler::Linear>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "Linear"; } };
|
||||
template<> struct ResamplerName<Resampler::Cubic>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } };
|
||||
template<> struct ResamplerName<Resampler::FastBSinc12>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } };
|
||||
template<> struct ResamplerName<Resampler::BSinc12>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "11th order Sinc"; } };
|
||||
template<> struct ResamplerName<Resampler::FastBSinc24>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc (fast)"; } };
|
||||
template<> struct ResamplerName<Resampler::BSinc24>
|
||||
{ static constexpr const ALchar *Get() noexcept { return "23rd order Sinc"; } };
|
||||
|
||||
const ALchar *GetResamplerName(const Resampler rtype)
|
||||
{
|
||||
#define HANDLE_RESAMPLER(r) case r: return ResamplerName<r>::Get()
|
||||
switch(rtype)
|
||||
{
|
||||
HANDLE_RESAMPLER(Resampler::Point);
|
||||
HANDLE_RESAMPLER(Resampler::Linear);
|
||||
HANDLE_RESAMPLER(Resampler::Cubic);
|
||||
HANDLE_RESAMPLER(Resampler::FastBSinc12);
|
||||
HANDLE_RESAMPLER(Resampler::BSinc12);
|
||||
HANDLE_RESAMPLER(Resampler::FastBSinc24);
|
||||
HANDLE_RESAMPLER(Resampler::BSinc24);
|
||||
}
|
||||
#undef HANDLE_RESAMPLER
|
||||
/* Should never get here. */
|
||||
throw std::runtime_error{"Unexpected resampler index"};
|
||||
}
|
||||
|
||||
al::optional<DistanceModel> DistanceModelFromALenum(ALenum model)
|
||||
{
|
||||
switch(model)
|
||||
{
|
||||
case AL_NONE: return al::make_optional(DistanceModel::Disable);
|
||||
case AL_INVERSE_DISTANCE: return al::make_optional(DistanceModel::Inverse);
|
||||
case AL_INVERSE_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::InverseClamped);
|
||||
case AL_LINEAR_DISTANCE: return al::make_optional(DistanceModel::Linear);
|
||||
case AL_LINEAR_DISTANCE_CLAMPED: return al::make_optional(DistanceModel::LinearClamped);
|
||||
case AL_EXPONENT_DISTANCE: return al::make_optional(DistanceModel::Exponent);
|
||||
case AL_EXPONENT_DISTANCE_CLAMPED: return al::make_optional(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.
|
||||
*/
|
||||
extern "C" AL_API const ALchar* AL_APIENTRY alsoft_get_version(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION");
|
||||
if(spoof) return spoof->c_str();
|
||||
return ALSOFT_VERSION;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
#define DO_UPDATEPROPS() do { \
|
||||
if(!context->mDeferUpdates.load(std::memory_order_acquire)) \
|
||||
UpdateContextProps(context.get()); \
|
||||
else \
|
||||
context->mPropsClean.clear(std::memory_order_release); \
|
||||
} while(0)
|
||||
|
||||
|
||||
AL_API void AL_APIENTRY alEnable(ALenum capability)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
context->mSourceDistanceModel = true;
|
||||
DO_UPDATEPROPS();
|
||||
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 UNLIKELY(!context) return;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
context->mSourceDistanceModel = false;
|
||||
DO_UPDATEPROPS();
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return AL_FALSE;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALboolean value{AL_FALSE};
|
||||
switch(capability)
|
||||
{
|
||||
case AL_SOURCE_DISTANCE_MODEL:
|
||||
value = context->mSourceDistanceModel ? 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 UNLIKELY(!context) return AL_FALSE;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALboolean value{AL_FALSE};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
if(context->mDopplerFactor != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
if(context->mDopplerVelocity != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
if(context->mDistanceModel == DistanceModel::Default)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
if(context->mSpeedOfSound != 0.0f)
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
if(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 UNLIKELY(!context) return 0.0;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALdouble value{0.0};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = context->mDopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = context->mDopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = static_cast<ALdouble>(ALenumFromDistanceModel(context->mDistanceModel));
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = context->mSpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = static_cast<ALdouble>(AL_TRUE);
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = ALdouble{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 UNLIKELY(!context) return 0.0f;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALfloat value{0.0f};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = context->mDopplerFactor;
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = context->mDopplerVelocity;
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = static_cast<ALfloat>(ALenumFromDistanceModel(context->mDistanceModel));
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = context->mSpeedOfSound;
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = static_cast<ALfloat>(AL_TRUE);
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = 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 UNLIKELY(!context) return 0;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALint value{0};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = static_cast<ALint>(context->mDopplerFactor);
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = static_cast<ALint>(context->mDopplerVelocity);
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = ALenumFromDistanceModel(context->mDistanceModel);
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = static_cast<ALint>(context->mSpeedOfSound);
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = static_cast<ALint>(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;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
extern "C" AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return 0_i64;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
ALint64SOFT value{0};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
value = static_cast<ALint64SOFT>(context->mDopplerFactor);
|
||||
break;
|
||||
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
value = static_cast<ALint64SOFT>(context->mDopplerVelocity);
|
||||
break;
|
||||
|
||||
case AL_DISTANCE_MODEL:
|
||||
value = ALenumFromDistanceModel(context->mDistanceModel);
|
||||
break;
|
||||
|
||||
case AL_SPEED_OF_SOUND:
|
||||
value = static_cast<ALint64SOFT>(context->mSpeedOfSound);
|
||||
break;
|
||||
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
if(context->mDeferUpdates.load(std::memory_order_acquire))
|
||||
value = AL_TRUE;
|
||||
break;
|
||||
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
value = static_cast<ALint64SOFT>(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 UNLIKELY(!context) return nullptr;
|
||||
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
void *value{nullptr};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_EVENT_CALLBACK_FUNCTION_SOFT:
|
||||
value = reinterpret_cast<void*>(context->mEventCb);
|
||||
break;
|
||||
|
||||
case AL_EVENT_CALLBACK_USER_PARAM_SOFT:
|
||||
value = context->mEventParam;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API 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 UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API 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 UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API 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 UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API 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 UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
extern "C" AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values)
|
||||
START_API_FUNC
|
||||
{
|
||||
if(values)
|
||||
{
|
||||
switch(pname)
|
||||
{
|
||||
case AL_DOPPLER_FACTOR:
|
||||
case AL_DOPPLER_VELOCITY:
|
||||
case AL_DISTANCE_MODEL:
|
||||
case AL_SPEED_OF_SOUND:
|
||||
case AL_DEFERRED_UPDATES_SOFT:
|
||||
case AL_GAIN_LIMIT_SOFT:
|
||||
case AL_NUM_RESAMPLERS_SOFT:
|
||||
case AL_DEFAULT_RESAMPLER_SOFT:
|
||||
values[0] = alGetInteger64SOFT(pname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, 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 UNLIKELY(!context) return;
|
||||
|
||||
if(!values)
|
||||
context->setError(AL_INVALID_VALUE, "NULL pointer");
|
||||
else switch(pname)
|
||||
{
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname);
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return nullptr;
|
||||
|
||||
const ALchar *value{nullptr};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_VENDOR:
|
||||
value = alVendor;
|
||||
break;
|
||||
|
||||
case AL_VERSION:
|
||||
value = alVersion;
|
||||
break;
|
||||
|
||||
case AL_RENDERER:
|
||||
value = alRenderer;
|
||||
break;
|
||||
|
||||
case AL_EXTENSIONS:
|
||||
value = context->mExtensionList;
|
||||
break;
|
||||
|
||||
case AL_NO_ERROR:
|
||||
value = alNoError;
|
||||
break;
|
||||
|
||||
case AL_INVALID_NAME:
|
||||
value = alErrInvalidName;
|
||||
break;
|
||||
|
||||
case AL_INVALID_ENUM:
|
||||
value = alErrInvalidEnum;
|
||||
break;
|
||||
|
||||
case AL_INVALID_VALUE:
|
||||
value = alErrInvalidValue;
|
||||
break;
|
||||
|
||||
case AL_INVALID_OPERATION:
|
||||
value = alErrInvalidOp;
|
||||
break;
|
||||
|
||||
case AL_OUT_OF_MEMORY:
|
||||
value = alErrOutOfMemory;
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alDopplerFactor(ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!(value >= 0.0f && std::isfinite(value)))
|
||||
context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value);
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
context->mDopplerFactor = value;
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
if(!(value >= 0.0f && std::isfinite(value)))
|
||||
context->setError(AL_INVALID_VALUE, "Doppler 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 UNLIKELY(!context) return;
|
||||
|
||||
if(!(value > 0.0f && std::isfinite(value)))
|
||||
context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value);
|
||||
else
|
||||
{
|
||||
std::lock_guard<std::mutex> _{context->mPropLock};
|
||||
context->mSpeedOfSound = value;
|
||||
DO_UPDATEPROPS();
|
||||
}
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alDistanceModel(ALenum value)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) 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 UNLIKELY(!context) return;
|
||||
|
||||
context->deferUpdates();
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
AL_API void AL_APIENTRY alProcessUpdatesSOFT(void)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return;
|
||||
|
||||
context->processUpdates();
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index)
|
||||
START_API_FUNC
|
||||
{
|
||||
ContextRef context{GetContextRef()};
|
||||
if UNLIKELY(!context) return nullptr;
|
||||
|
||||
const ALchar *value{nullptr};
|
||||
switch(pname)
|
||||
{
|
||||
case AL_RESAMPLER_NAME_SOFT:
|
||||
if(index < 0 || index > static_cast<ALint>(Resampler::Max))
|
||||
context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index);
|
||||
else
|
||||
value = GetResamplerName(static_cast<Resampler>(index));
|
||||
break;
|
||||
|
||||
default:
|
||||
context->setError(AL_INVALID_VALUE, "Invalid string indexed property");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
END_API_FUNC
|
||||
|
||||
|
||||
void UpdateContextProps(ALCcontext *context)
|
||||
{
|
||||
/* Get an unused proprty container, or allocate a new one as needed. */
|
||||
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. */
|
||||
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);
|
||||
}
|
||||
}
|
||||
-4130
File diff suppressed because it is too large
Load Diff
-371
@@ -1,371 +0,0 @@
|
||||
#ifndef ALC_MAIN_H
|
||||
#define ALC_MAIN_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
#include "AL/alext.h"
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/filters/splitter.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "hrtf.h"
|
||||
#include "inprogext.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "vector.h"
|
||||
|
||||
class BFormatDec;
|
||||
struct ALbuffer;
|
||||
struct ALeffect;
|
||||
struct ALfilter;
|
||||
struct BackendBase;
|
||||
struct Compressor;
|
||||
struct EffectState;
|
||||
struct Uhj2Encoder;
|
||||
struct bs2b;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
#define MIN_OUTPUT_RATE 8000
|
||||
#define MAX_OUTPUT_RATE 192000
|
||||
#define DEFAULT_OUTPUT_RATE 44100
|
||||
|
||||
#define DEFAULT_UPDATE_SIZE 882 /* 20ms */
|
||||
#define DEFAULT_NUM_UPDATES 3
|
||||
|
||||
|
||||
enum class DeviceType : unsigned char {
|
||||
Playback,
|
||||
Capture,
|
||||
Loopback
|
||||
};
|
||||
|
||||
|
||||
enum class RenderMode : unsigned char {
|
||||
Normal,
|
||||
Pairwise,
|
||||
Hrtf
|
||||
};
|
||||
|
||||
|
||||
struct InputRemixMap {
|
||||
struct TargetMix { Channel channel; float mix; };
|
||||
|
||||
Channel channel;
|
||||
std::array<TargetMix,2> targets;
|
||||
};
|
||||
|
||||
|
||||
struct BufferSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALbuffer *Buffers{nullptr}; /* 64 */
|
||||
|
||||
BufferSubList() noexcept = default;
|
||||
BufferSubList(const BufferSubList&) = delete;
|
||||
BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; }
|
||||
~BufferSubList();
|
||||
|
||||
BufferSubList& operator=(const BufferSubList&) = delete;
|
||||
BufferSubList& operator=(BufferSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; }
|
||||
};
|
||||
|
||||
struct EffectSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALeffect *Effects{nullptr}; /* 64 */
|
||||
|
||||
EffectSubList() noexcept = default;
|
||||
EffectSubList(const EffectSubList&) = delete;
|
||||
EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; }
|
||||
~EffectSubList();
|
||||
|
||||
EffectSubList& operator=(const EffectSubList&) = delete;
|
||||
EffectSubList& operator=(EffectSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; }
|
||||
};
|
||||
|
||||
struct FilterSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALfilter *Filters{nullptr}; /* 64 */
|
||||
|
||||
FilterSubList() noexcept = default;
|
||||
FilterSubList(const FilterSubList&) = delete;
|
||||
FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; }
|
||||
~FilterSubList();
|
||||
|
||||
FilterSubList& operator=(const FilterSubList&) = delete;
|
||||
FilterSubList& operator=(FilterSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; }
|
||||
};
|
||||
|
||||
|
||||
/* Maximum delay in samples for speaker distance compensation. */
|
||||
#define MAX_DELAY_LENGTH 1024
|
||||
|
||||
struct DistanceComp {
|
||||
struct ChanData {
|
||||
float Gain{1.0f};
|
||||
uint Length{0u}; /* Valid range is [0...MAX_DELAY_LENGTH). */
|
||||
float *Buffer{nullptr};
|
||||
};
|
||||
|
||||
std::array<ChanData,MAX_OUTPUT_CHANNELS> mChannels;
|
||||
al::FlexArray<float,16> mSamples;
|
||||
|
||||
DistanceComp(size_t count) : mSamples{count} { }
|
||||
|
||||
static std::unique_ptr<DistanceComp> Create(size_t numsamples)
|
||||
{ return std::unique_ptr<DistanceComp>{new(FamCount(numsamples)) DistanceComp{numsamples}}; }
|
||||
|
||||
DEF_FAM_NEWDEL(DistanceComp, mSamples)
|
||||
};
|
||||
|
||||
|
||||
struct BFChannelConfig {
|
||||
float Scale;
|
||||
uint Index;
|
||||
};
|
||||
|
||||
|
||||
struct MixParams {
|
||||
/* Coefficient channel mapping for mixing to the buffer. */
|
||||
std::array<BFChannelConfig,MAX_OUTPUT_CHANNELS> AmbiMap{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
struct RealMixParams {
|
||||
al::span<const InputRemixMap> RemixMap;
|
||||
std::array<uint,MaxChannels> ChannelIndex{};
|
||||
|
||||
al::span<FloatBufferLine> Buffer;
|
||||
};
|
||||
|
||||
enum {
|
||||
// Frequency was requested by the app or config file
|
||||
FrequencyRequest,
|
||||
// Channel configuration was requested by the config file
|
||||
ChannelsRequest,
|
||||
// Sample type was requested by the config file
|
||||
SampleTypeRequest,
|
||||
|
||||
// Specifies if the DSP is paused at user request
|
||||
DevicePaused,
|
||||
// Specifies if the device is currently running
|
||||
DeviceRunning,
|
||||
|
||||
DeviceFlagsCount
|
||||
};
|
||||
|
||||
struct ALCdevice : public al::intrusive_ref<ALCdevice> {
|
||||
std::atomic<bool> Connected{true};
|
||||
const DeviceType Type{};
|
||||
|
||||
uint Frequency{};
|
||||
uint UpdateSize{};
|
||||
uint BufferSize{};
|
||||
|
||||
DevFmtChannels FmtChans{};
|
||||
DevFmtType FmtType{};
|
||||
bool IsHeadphones{false};
|
||||
uint mAmbiOrder{0};
|
||||
float mXOverFreq{400.0f};
|
||||
/* For DevFmtAmbi* output only, specifies the channel order and
|
||||
* normalization.
|
||||
*/
|
||||
DevAmbiLayout mAmbiLayout{DevAmbiLayout::Default};
|
||||
DevAmbiScaling mAmbiScale{DevAmbiScaling::Default};
|
||||
|
||||
std::string DeviceName;
|
||||
|
||||
// Device flags
|
||||
std::bitset<DeviceFlagsCount> Flags{};
|
||||
|
||||
// Maximum number of sources that can be created
|
||||
uint SourcesMax{};
|
||||
// Maximum number of slots that can be created
|
||||
uint AuxiliaryEffectSlotMax{};
|
||||
|
||||
/* Rendering mode. */
|
||||
RenderMode mRenderMode{RenderMode::Normal};
|
||||
|
||||
/* The average speaker distance as determined by the ambdec configuration,
|
||||
* HRTF data set, or the NFC-HOA reference delay. Only used for NFC.
|
||||
*/
|
||||
float AvgSpeakerDist{0.0f};
|
||||
|
||||
uint SamplesDone{0u};
|
||||
std::chrono::nanoseconds ClockBase{0};
|
||||
std::chrono::nanoseconds FixedLatency{0};
|
||||
|
||||
/* Temp storage used for mixer processing. */
|
||||
alignas(16) float SourceData[BufferLineSize + MaxResamplerPadding];
|
||||
alignas(16) float ResampledData[BufferLineSize];
|
||||
alignas(16) float FilteredData[BufferLineSize];
|
||||
union {
|
||||
alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength];
|
||||
alignas(16) float NfcSampleData[BufferLineSize];
|
||||
};
|
||||
|
||||
/* Persistent storage for HRTF mixing. */
|
||||
alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength + HrtfDirectDelay];
|
||||
|
||||
/* Mixing buffer used by the Dry mix and Real output. */
|
||||
al::vector<FloatBufferLine, 16> MixBuffer;
|
||||
|
||||
/* The "dry" path corresponds to the main output. */
|
||||
MixParams Dry;
|
||||
uint NumChannelsPerOrder[MaxAmbiOrder+1]{};
|
||||
|
||||
/* "Real" output, which will be written to the device buffer. May alias the
|
||||
* dry buffer.
|
||||
*/
|
||||
RealMixParams RealOut;
|
||||
|
||||
/* HRTF state and info */
|
||||
std::unique_ptr<DirectHrtfState> mHrtfState;
|
||||
al::intrusive_ptr<HrtfStore> mHrtf;
|
||||
uint mIrSize{0};
|
||||
|
||||
/* Ambisonic-to-UHJ encoder */
|
||||
std::unique_ptr<Uhj2Encoder> Uhj_Encoder;
|
||||
|
||||
/* Ambisonic decoder for speakers */
|
||||
std::unique_ptr<BFormatDec> AmbiDecoder;
|
||||
|
||||
/* Stereo-to-binaural filter */
|
||||
std::unique_ptr<bs2b> Bs2b;
|
||||
|
||||
using PostProc = void(ALCdevice::*)(const size_t SamplesToDo);
|
||||
PostProc PostProcess{nullptr};
|
||||
|
||||
std::unique_ptr<Compressor> Limiter;
|
||||
|
||||
/* Delay buffers used to compensate for speaker distances. */
|
||||
std::unique_ptr<DistanceComp> ChannelDelays;
|
||||
|
||||
/* Dithering control. */
|
||||
float DitherDepth{0.0f};
|
||||
uint DitherSeed{0u};
|
||||
|
||||
/* Running count of the mixer invocations, in 31.1 fixed point. This
|
||||
* actually increments *twice* when mixing, first at the start and then at
|
||||
* the end, so the bottom bit indicates if the device is currently mixing
|
||||
* and the upper bits indicates how many mixes have been done.
|
||||
*/
|
||||
RefCount MixCount{0u};
|
||||
|
||||
// Contexts created on this device
|
||||
std::atomic<al::FlexArray<ALCcontext*>*> mContexts{nullptr};
|
||||
|
||||
/* This lock protects the device state (format, update size, etc) from
|
||||
* being from being changed in multiple threads, or being accessed while
|
||||
* being changed. It's also used to serialize calls to the backend.
|
||||
*/
|
||||
std::mutex StateLock;
|
||||
std::unique_ptr<BackendBase> Backend;
|
||||
|
||||
|
||||
ALCuint NumMonoSources{};
|
||||
ALCuint NumStereoSources{};
|
||||
ALCuint NumAuxSends{};
|
||||
|
||||
std::string HrtfName;
|
||||
al::vector<std::string> HrtfList;
|
||||
ALCenum HrtfStatus{ALC_FALSE};
|
||||
|
||||
ALCenum LimiterState{ALC_DONT_CARE_SOFT};
|
||||
|
||||
std::atomic<ALCenum> LastError{ALC_NO_ERROR};
|
||||
|
||||
// Map of Buffers for this device
|
||||
std::mutex BufferLock;
|
||||
al::vector<BufferSubList> BufferList;
|
||||
|
||||
// Map of Effects for this device
|
||||
std::mutex EffectLock;
|
||||
al::vector<EffectSubList> EffectList;
|
||||
|
||||
// Map of Filters for this device
|
||||
std::mutex FilterLock;
|
||||
al::vector<FilterSubList> FilterList;
|
||||
|
||||
|
||||
ALCdevice(DeviceType type);
|
||||
ALCdevice(const ALCdevice&) = delete;
|
||||
ALCdevice& operator=(const ALCdevice&) = delete;
|
||||
~ALCdevice();
|
||||
|
||||
uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); }
|
||||
uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); }
|
||||
uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); }
|
||||
|
||||
uint waitForMix() const noexcept
|
||||
{
|
||||
uint refcount;
|
||||
while((refcount=MixCount.load(std::memory_order_acquire))&1) {
|
||||
}
|
||||
return refcount;
|
||||
}
|
||||
|
||||
void ProcessHrtf(const size_t SamplesToDo);
|
||||
void ProcessAmbiDec(const size_t SamplesToDo);
|
||||
void ProcessAmbiDecStablized(const size_t SamplesToDo);
|
||||
void ProcessUhj(const size_t SamplesToDo);
|
||||
void ProcessBs2b(const size_t SamplesToDo);
|
||||
|
||||
inline void postProcess(const size_t SamplesToDo)
|
||||
{ if LIKELY(PostProcess) (this->*PostProcess)(SamplesToDo); }
|
||||
|
||||
void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep);
|
||||
|
||||
/* Caller must lock the device state, and the mixer must not be running. */
|
||||
[[gnu::format(printf,2,3)]] void handleDisconnect(const char *msg, ...);
|
||||
|
||||
DEF_NEWDEL(ALCdevice)
|
||||
};
|
||||
|
||||
/* Must be less than 15 characters (16 including terminating null) for
|
||||
* compatibility with pthread_setname_np limitations. */
|
||||
#define MIXER_THREAD_NAME "alsoft-mixer"
|
||||
|
||||
#define RECORD_THREAD_NAME "alsoft-record"
|
||||
|
||||
|
||||
extern int RTPrioLevel;
|
||||
void SetRTPriority(void);
|
||||
|
||||
/**
|
||||
* Returns the index for the given channel name (e.g. FrontCenter), or
|
||||
* INVALID_CHANNEL_INDEX if it doesn't exist.
|
||||
*/
|
||||
inline uint GetChannelIdxByName(const RealMixParams &real, Channel chan) noexcept
|
||||
{ return real.ChannelIndex[chan]; }
|
||||
#define INVALID_CHANNEL_INDEX ~0u
|
||||
|
||||
|
||||
al::vector<std::string> SearchDataFiles(const char *match, const char *subdir);
|
||||
|
||||
#endif
|
||||
@@ -1,542 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "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 "compat.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] == '[')
|
||||
{
|
||||
char *line{&buffer[0]};
|
||||
char *section = line+1;
|
||||
char *endsection;
|
||||
|
||||
endsection = std::strchr(section, ']');
|
||||
if(!endsection || section == endsection)
|
||||
{
|
||||
ERR(" config parse error: bad line \"%s\"\n", line);
|
||||
continue;
|
||||
}
|
||||
if(endsection[1] != 0)
|
||||
{
|
||||
char *end = endsection+1;
|
||||
while(std::isspace(*end))
|
||||
++end;
|
||||
if(*end != 0 && *end != '#')
|
||||
{
|
||||
ERR(" config parse error: bad line \"%s\"\n", line);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*endsection = 0;
|
||||
|
||||
curSection.clear();
|
||||
if(al::strcasecmp(section, "general") != 0)
|
||||
{
|
||||
do {
|
||||
char *nextp = std::strchr(section, '%');
|
||||
if(!nextp)
|
||||
{
|
||||
curSection += section;
|
||||
break;
|
||||
}
|
||||
|
||||
curSection.append(section, nextp);
|
||||
section = nextp;
|
||||
|
||||
if(((section[1] >= '0' && section[1] <= '9') ||
|
||||
(section[1] >= 'a' && section[1] <= 'f') ||
|
||||
(section[1] >= 'A' && section[1] <= 'F')) &&
|
||||
((section[2] >= '0' && section[2] <= '9') ||
|
||||
(section[2] >= 'a' && section[2] <= 'f') ||
|
||||
(section[2] >= 'A' && section[2] <= 'F')))
|
||||
{
|
||||
int b{0};
|
||||
if(section[1] >= '0' && section[1] <= '9')
|
||||
b = (section[1]-'0') << 4;
|
||||
else if(section[1] >= 'a' && section[1] <= 'f')
|
||||
b = (section[1]-'a'+0xa) << 4;
|
||||
else if(section[1] >= 'A' && section[1] <= 'F')
|
||||
b = (section[1]-'A'+0x0a) << 4;
|
||||
if(section[2] >= '0' && section[2] <= '9')
|
||||
b |= (section[2]-'0');
|
||||
else if(section[2] >= 'a' && section[2] <= 'f')
|
||||
b |= (section[2]-'a'+0xa);
|
||||
else if(section[2] >= 'A' && section[2] <= 'F')
|
||||
b |= (section[2]-'A'+0x0a);
|
||||
curSection += static_cast<char>(b);
|
||||
section += 3;
|
||||
}
|
||||
else if(section[1] == '%')
|
||||
{
|
||||
curSection += '%';
|
||||
section += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
curSection += '%';
|
||||
section += 1;
|
||||
}
|
||||
} while(*section != 0);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto cmtpos = std::min(buffer.find('#'), buffer.size());
|
||||
while(cmtpos > 0 && std::isspace(buffer[cmtpos-1]))
|
||||
--cmtpos;
|
||||
if(!cmtpos) continue;
|
||||
buffer.erase(cmtpos);
|
||||
|
||||
auto sep = buffer.find('=');
|
||||
if(sep == std::string::npos)
|
||||
{
|
||||
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
|
||||
continue;
|
||||
}
|
||||
auto keyend = sep++;
|
||||
while(keyend > 0 && std::isspace(buffer[keyend-1]))
|
||||
--keyend;
|
||||
if(!keyend)
|
||||
{
|
||||
ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str());
|
||||
continue;
|
||||
}
|
||||
while(sep < buffer.size() && std::isspace(buffer[sep]))
|
||||
sep++;
|
||||
|
||||
std::string fullKey;
|
||||
if(!curSection.empty())
|
||||
{
|
||||
fullKey += curSection;
|
||||
fullKey += '/';
|
||||
}
|
||||
fullKey += buffer.substr(0u, keyend);
|
||||
|
||||
std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}};
|
||||
if(value.size() > 1)
|
||||
{
|
||||
if((value.front() == '"' && value.back() == '"')
|
||||
|| (value.front() == '\'' && value.back() == '\''))
|
||||
{
|
||||
value.pop_back();
|
||||
value.erase(value.begin());
|
||||
}
|
||||
}
|
||||
|
||||
TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str());
|
||||
|
||||
/* Check if we already have this option set */
|
||||
auto find_key = [&fullKey](const ConfigEntry &entry) -> bool
|
||||
{ return entry.key == fullKey; };
|
||||
auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key);
|
||||
if(ent != ConfOpts.end())
|
||||
{
|
||||
if(!value.empty())
|
||||
ent->value = expdup(value.c_str());
|
||||
else
|
||||
ConfOpts.erase(ent);
|
||||
}
|
||||
else if(!value.empty())
|
||||
ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())});
|
||||
}
|
||||
ConfOpts.shrink_to_fit();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
void ReadALConfig()
|
||||
{
|
||||
WCHAR buffer[MAX_PATH];
|
||||
if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE)
|
||||
{
|
||||
std::string filepath{wstr_to_utf8(buffer)};
|
||||
filepath += "\\alsoft.ini";
|
||||
|
||||
TRACE("Loading config %s...\n", filepath.c_str());
|
||||
al::ifstream f{filepath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
std::string ppath{GetProcBinary().path};
|
||||
if(!ppath.empty())
|
||||
{
|
||||
ppath += "\\alsoft.ini";
|
||||
TRACE("Loading config %s...\n", ppath.c_str());
|
||||
al::ifstream f{ppath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
if(auto confpath = al::getenv(L"ALSOFT_CONF"))
|
||||
{
|
||||
TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str());
|
||||
al::ifstream f{*confpath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void ReadALConfig()
|
||||
{
|
||||
const char *str{"/etc/openal/alsoft.conf"};
|
||||
|
||||
TRACE("Loading config %s...\n", str);
|
||||
al::ifstream f{str};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
f.close();
|
||||
|
||||
std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")};
|
||||
/* Go through the list in reverse, since "the order of base directories
|
||||
* denotes their importance; the first directory listed is the most
|
||||
* important". Ergo, we need to load the settings from the later dirs
|
||||
* first so that the settings in the earlier dirs override them.
|
||||
*/
|
||||
std::string fname;
|
||||
while(!confpaths.empty())
|
||||
{
|
||||
auto next = confpaths.find_last_of(':');
|
||||
if(next < confpaths.length())
|
||||
{
|
||||
fname = confpaths.substr(next+1);
|
||||
confpaths.erase(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
fname = confpaths;
|
||||
confpaths.clear();
|
||||
}
|
||||
|
||||
if(fname.empty() || fname.front() != '/')
|
||||
WARN("Ignoring XDG config dir: %s\n", fname.c_str());
|
||||
else
|
||||
{
|
||||
if(fname.back() != '/') fname += "/alsoft.conf";
|
||||
else fname += "alsoft.conf";
|
||||
|
||||
TRACE("Loading config %s...\n", fname.c_str());
|
||||
f = al::ifstream{fname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
fname.clear();
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
if(mainBundle)
|
||||
{
|
||||
unsigned char fileName[PATH_MAX];
|
||||
CFURLRef configURL;
|
||||
|
||||
if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) &&
|
||||
CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName)))
|
||||
{
|
||||
f = al::ifstream{reinterpret_cast<char*>(fileName)};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if(auto homedir = al::getenv("HOME"))
|
||||
{
|
||||
fname = *homedir;
|
||||
if(fname.back() != '/') fname += "/.alsoftrc";
|
||||
else fname += ".alsoftrc";
|
||||
|
||||
TRACE("Loading config %s...\n", fname.c_str());
|
||||
f = al::ifstream{fname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
if(auto configdir = al::getenv("XDG_CONFIG_HOME"))
|
||||
{
|
||||
fname = *configdir;
|
||||
if(fname.back() != '/') fname += "/alsoft.conf";
|
||||
else fname += "alsoft.conf";
|
||||
}
|
||||
else
|
||||
{
|
||||
fname.clear();
|
||||
if(auto homedir = al::getenv("HOME"))
|
||||
{
|
||||
fname = *homedir;
|
||||
if(fname.back() != '/') fname += "/.config/alsoft.conf";
|
||||
else fname += ".config/alsoft.conf";
|
||||
}
|
||||
}
|
||||
if(!fname.empty())
|
||||
{
|
||||
TRACE("Loading config %s...\n", fname.c_str());
|
||||
f = al::ifstream{fname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
std::string ppath{GetProcBinary().path};
|
||||
if(!ppath.empty())
|
||||
{
|
||||
if(ppath.back() != '/') ppath += "/alsoft.conf";
|
||||
else ppath += "alsoft.conf";
|
||||
|
||||
TRACE("Loading config %s...\n", ppath.c_str());
|
||||
f = al::ifstream{ppath};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
|
||||
if(auto confname = al::getenv("ALSOFT_CONF"))
|
||||
{
|
||||
TRACE("Loading config %s...\n", confname->c_str());
|
||||
f = al::ifstream{*confname};
|
||||
if(f.is_open())
|
||||
LoadConfigFromFile(f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def)
|
||||
{
|
||||
if(!keyName)
|
||||
return def;
|
||||
|
||||
std::string key;
|
||||
if(blockName && al::strcasecmp(blockName, "general") != 0)
|
||||
{
|
||||
key = blockName;
|
||||
if(devName)
|
||||
{
|
||||
key += '/';
|
||||
key += devName;
|
||||
}
|
||||
key += '/';
|
||||
key += keyName;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(devName)
|
||||
{
|
||||
key = devName;
|
||||
key += '/';
|
||||
}
|
||||
key += keyName;
|
||||
}
|
||||
|
||||
auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(),
|
||||
[&key](const ConfigEntry &entry) -> bool
|
||||
{ return entry.key == key; }
|
||||
);
|
||||
if(iter != ConfOpts.cend())
|
||||
{
|
||||
TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str());
|
||||
if(!iter->value.empty())
|
||||
return iter->value.c_str();
|
||||
return def;
|
||||
}
|
||||
|
||||
if(!devName)
|
||||
{
|
||||
TRACE("Key %s not found\n", key.c_str());
|
||||
return def;
|
||||
}
|
||||
return GetConfigValue(nullptr, blockName, keyName, def);
|
||||
}
|
||||
|
||||
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
return val[0] != 0;
|
||||
}
|
||||
|
||||
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional<std::string>(val);
|
||||
}
|
||||
|
||||
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(static_cast<int>(std::strtol(val, nullptr, 0)));
|
||||
}
|
||||
|
||||
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(static_cast<unsigned int>(std::strtoul(val, nullptr, 0)));
|
||||
}
|
||||
|
||||
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(std::strtof(val, nullptr));
|
||||
}
|
||||
|
||||
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
if(!val[0]) return al::nullopt;
|
||||
|
||||
return al::make_optional(
|
||||
al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
|
||||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
|
||||
}
|
||||
|
||||
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def)
|
||||
{
|
||||
const char *val = GetConfigValue(devName, blockName, keyName, "");
|
||||
|
||||
if(!val[0]) return def != 0;
|
||||
return (al::strcasecmp(val, "true") == 0 || al::strcasecmp(val, "yes") == 0 ||
|
||||
al::strcasecmp(val, "on") == 0 || atoi(val) != 0);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef ALCONFIG_H
|
||||
#define ALCONFIG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
|
||||
void ReadALConfig();
|
||||
|
||||
int ConfigValueExists(const char *devName, const char *blockName, const char *keyName);
|
||||
const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName, const char *def);
|
||||
int GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, int def);
|
||||
|
||||
al::optional<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
|
||||
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
|
||||
|
||||
#endif /* ALCONFIG_H */
|
||||
-282
@@ -1,282 +0,0 @@
|
||||
#ifndef ALCONTEXT_H
|
||||
#define ALCONTEXT_H
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "AL/al.h"
|
||||
#include "AL/alc.h"
|
||||
|
||||
#include "al/listener.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "atomic.h"
|
||||
#include "inprogext.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "threads.h"
|
||||
#include "vecmat.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct ALeffectslot;
|
||||
struct ALsource;
|
||||
struct EffectSlot;
|
||||
struct EffectSlotProps;
|
||||
struct RingBuffer;
|
||||
struct Voice;
|
||||
struct VoiceChange;
|
||||
struct VoicePropsItem;
|
||||
|
||||
|
||||
enum class DistanceModel : unsigned char {
|
||||
Disable,
|
||||
Inverse, InverseClamped,
|
||||
Linear, LinearClamped,
|
||||
Exponent, ExponentClamped,
|
||||
|
||||
Default = InverseClamped
|
||||
};
|
||||
|
||||
|
||||
struct WetBuffer {
|
||||
bool mInUse;
|
||||
al::FlexArray<FloatBufferLine, 16> mBuffer;
|
||||
|
||||
WetBuffer(size_t count) : mBuffer{count} { }
|
||||
|
||||
DEF_FAM_NEWDEL(WetBuffer, mBuffer)
|
||||
};
|
||||
using WetBufferPtr = std::unique_ptr<WetBuffer>;
|
||||
|
||||
|
||||
struct ContextProps {
|
||||
float DopplerFactor;
|
||||
float DopplerVelocity;
|
||||
float SpeedOfSound;
|
||||
bool SourceDistanceModel;
|
||||
DistanceModel mDistanceModel;
|
||||
|
||||
std::atomic<ContextProps*> next;
|
||||
|
||||
DEF_NEWDEL(ContextProps)
|
||||
};
|
||||
|
||||
struct ListenerProps {
|
||||
std::array<float,3> Position;
|
||||
std::array<float,3> Velocity;
|
||||
std::array<float,3> OrientAt;
|
||||
std::array<float,3> OrientUp;
|
||||
float Gain;
|
||||
float MetersPerUnit;
|
||||
|
||||
std::atomic<ListenerProps*> next;
|
||||
|
||||
DEF_NEWDEL(ListenerProps)
|
||||
};
|
||||
|
||||
struct ContextParams {
|
||||
/* Pointer to the most recent property values that are awaiting an update. */
|
||||
std::atomic<ContextProps*> ContextUpdate{nullptr};
|
||||
std::atomic<ListenerProps*> ListenerUpdate{nullptr};
|
||||
|
||||
alu::Matrix Matrix{alu::Matrix::Identity()};
|
||||
alu::Vector Velocity{};
|
||||
|
||||
float Gain{1.0f};
|
||||
float MetersPerUnit{1.0f};
|
||||
|
||||
float DopplerFactor{1.0f};
|
||||
float SpeedOfSound{343.3f}; /* in units per sec! */
|
||||
|
||||
bool SourceDistanceModel{false};
|
||||
DistanceModel mDistanceModel{};
|
||||
};
|
||||
|
||||
|
||||
struct SourceSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALsource *Sources{nullptr}; /* 64 */
|
||||
|
||||
SourceSubList() noexcept = default;
|
||||
SourceSubList(const SourceSubList&) = delete;
|
||||
SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; }
|
||||
~SourceSubList();
|
||||
|
||||
SourceSubList& operator=(const SourceSubList&) = delete;
|
||||
SourceSubList& operator=(SourceSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; }
|
||||
};
|
||||
|
||||
struct EffectSlotSubList {
|
||||
uint64_t FreeMask{~0_u64};
|
||||
ALeffectslot *EffectSlots{nullptr}; /* 64 */
|
||||
|
||||
EffectSlotSubList() noexcept = default;
|
||||
EffectSlotSubList(const EffectSlotSubList&) = delete;
|
||||
EffectSlotSubList(EffectSlotSubList&& rhs) noexcept
|
||||
: FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots}
|
||||
{ rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; }
|
||||
~EffectSlotSubList();
|
||||
|
||||
EffectSlotSubList& operator=(const EffectSlotSubList&) = delete;
|
||||
EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept
|
||||
{ std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; }
|
||||
};
|
||||
|
||||
struct ALCcontext : public al::intrusive_ref<ALCcontext> {
|
||||
const al::intrusive_ptr<ALCdevice> mDevice;
|
||||
|
||||
/* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit
|
||||
* indicates if updates are currently happening).
|
||||
*/
|
||||
RefCount mUpdateCount{0u};
|
||||
std::atomic<bool> mHoldUpdates{false};
|
||||
|
||||
float mGainBoost{1.0f};
|
||||
|
||||
/* Linked lists of unused property containers, free to use for future
|
||||
* updates.
|
||||
*/
|
||||
std::atomic<ContextProps*> mFreeContextProps{nullptr};
|
||||
std::atomic<ListenerProps*> mFreeListenerProps{nullptr};
|
||||
std::atomic<VoicePropsItem*> mFreeVoiceProps{nullptr};
|
||||
std::atomic<EffectSlotProps*> mFreeEffectslotProps{nullptr};
|
||||
|
||||
/* The voice change tail is the beginning of the "free" elements, up to and
|
||||
* *excluding* the current. If tail==current, there's no free elements and
|
||||
* new ones need to be allocated. The current voice change is the element
|
||||
* last processed, and any after are pending.
|
||||
*/
|
||||
VoiceChange *mVoiceChangeTail{};
|
||||
std::atomic<VoiceChange*> mCurrentVoiceChange{};
|
||||
|
||||
void allocVoiceChanges(size_t addcount);
|
||||
|
||||
|
||||
ContextParams mParams;
|
||||
|
||||
using VoiceArray = al::FlexArray<Voice*>;
|
||||
std::atomic<VoiceArray*> mVoices{};
|
||||
std::atomic<size_t> mActiveVoiceCount{};
|
||||
|
||||
void allocVoices(size_t addcount);
|
||||
al::span<Voice*> getVoicesSpan() const noexcept
|
||||
{
|
||||
return {mVoices.load(std::memory_order_relaxed)->data(),
|
||||
mActiveVoiceCount.load(std::memory_order_relaxed)};
|
||||
}
|
||||
al::span<Voice*> getVoicesSpanAcquired() const noexcept
|
||||
{
|
||||
return {mVoices.load(std::memory_order_acquire)->data(),
|
||||
mActiveVoiceCount.load(std::memory_order_acquire)};
|
||||
}
|
||||
|
||||
|
||||
using EffectSlotArray = al::FlexArray<EffectSlot*>;
|
||||
std::atomic<EffectSlotArray*> mActiveAuxSlots{nullptr};
|
||||
|
||||
std::thread mEventThread;
|
||||
al::semaphore mEventSem;
|
||||
std::unique_ptr<RingBuffer> mAsyncEvents;
|
||||
std::atomic<uint> mEnabledEvts{0u};
|
||||
|
||||
/* Asynchronous voice change actions are processed as a linked list of
|
||||
* VoiceChange objects by the mixer, which is atomically appended to.
|
||||
* However, to avoid allocating each object individually, they're allocated
|
||||
* in clusters that are stored in a vector for easy automatic cleanup.
|
||||
*/
|
||||
using VoiceChangeCluster = std::unique_ptr<VoiceChange[]>;
|
||||
al::vector<VoiceChangeCluster> mVoiceChangeClusters;
|
||||
|
||||
using VoiceCluster = std::unique_ptr<Voice[]>;
|
||||
al::vector<VoiceCluster> mVoiceClusters;
|
||||
|
||||
/* Wet buffers used by effect slots. */
|
||||
al::vector<WetBufferPtr> mWetBuffers;
|
||||
|
||||
|
||||
std::atomic_flag mPropsClean;
|
||||
std::atomic<bool> mDeferUpdates{false};
|
||||
|
||||
std::mutex mPropLock;
|
||||
|
||||
std::atomic<ALenum> mLastError{AL_NO_ERROR};
|
||||
|
||||
DistanceModel mDistanceModel{DistanceModel::Default};
|
||||
bool mSourceDistanceModel{false};
|
||||
|
||||
float mDopplerFactor{1.0f};
|
||||
float mDopplerVelocity{1.0f};
|
||||
float mSpeedOfSound{SpeedOfSoundMetersPerSec};
|
||||
|
||||
std::mutex mEventCbLock;
|
||||
ALEVENTPROCSOFT mEventCb{};
|
||||
void *mEventParam{nullptr};
|
||||
|
||||
ALlistener mListener{};
|
||||
|
||||
al::vector<SourceSubList> mSourceList;
|
||||
ALuint mNumSources{0};
|
||||
std::mutex mSourceLock;
|
||||
|
||||
al::vector<EffectSlotSubList> mEffectSlotList;
|
||||
ALuint mNumEffectSlots{0u};
|
||||
std::mutex mEffectSlotLock;
|
||||
|
||||
/* Default effect slot */
|
||||
std::unique_ptr<ALeffectslot> mDefaultSlot;
|
||||
|
||||
const char *mExtensionList{nullptr};
|
||||
|
||||
|
||||
ALCcontext(al::intrusive_ptr<ALCdevice> device);
|
||||
ALCcontext(const ALCcontext&) = delete;
|
||||
ALCcontext& operator=(const ALCcontext&) = delete;
|
||||
~ALCcontext();
|
||||
|
||||
void init();
|
||||
/**
|
||||
* Removes the context from its device and removes it from being current on
|
||||
* the running thread or globally. Returns true if other contexts still
|
||||
* exist on the device.
|
||||
*/
|
||||
bool deinit();
|
||||
|
||||
/**
|
||||
* Defers/suspends updates for the given context's listener and sources.
|
||||
* This does *NOT* stop mixing, but rather prevents certain property
|
||||
* changes from taking effect.
|
||||
*/
|
||||
void deferUpdates() noexcept { mDeferUpdates.exchange(true, std::memory_order_acq_rel); }
|
||||
|
||||
/** Resumes update processing after being deferred. */
|
||||
void processUpdates();
|
||||
|
||||
[[gnu::format(printf,3,4)]] void setError(ALenum errorCode, const char *msg, ...);
|
||||
|
||||
DEF_NEWDEL(ALCcontext)
|
||||
};
|
||||
|
||||
#define SETERR_RETURN(ctx, err, retval, ...) do { \
|
||||
(ctx)->setError((err), __VA_ARGS__); \
|
||||
return retval; \
|
||||
} while(0)
|
||||
|
||||
|
||||
using ContextRef = al::intrusive_ptr<ALCcontext>;
|
||||
|
||||
ContextRef GetContextRef(void);
|
||||
|
||||
void UpdateContextProps(ALCcontext *context);
|
||||
|
||||
|
||||
extern bool TrapALError;
|
||||
|
||||
#endif /* ALCONTEXT_H */
|
||||
-2018
File diff suppressed because it is too large
Load Diff
@@ -1,141 +0,0 @@
|
||||
#ifndef ALU_H
|
||||
#define ALU_H
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
|
||||
#include "alspan.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/devformat.h"
|
||||
|
||||
struct ALCcontext;
|
||||
struct ALCdevice;
|
||||
struct EffectSlot;
|
||||
struct MixParams;
|
||||
|
||||
|
||||
#define MAX_SENDS 6
|
||||
|
||||
|
||||
using MixerFunc = void(*)(const al::span<const float> InSamples,
|
||||
const al::span<FloatBufferLine> OutBuffer, float *CurrentGains, const float *TargetGains,
|
||||
const size_t Counter, const size_t OutPos);
|
||||
|
||||
extern MixerFunc MixSamples;
|
||||
|
||||
|
||||
constexpr float GainMixMax{1000.0f}; /* +60dB */
|
||||
|
||||
constexpr float SpeedOfSoundMetersPerSec{343.3f};
|
||||
constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */
|
||||
|
||||
/** Target gain for the reverb decay feedback reaching the decay time. */
|
||||
constexpr float ReverbDecayGain{0.001f}; /* -60 dB */
|
||||
|
||||
|
||||
enum HrtfRequestMode {
|
||||
Hrtf_Default = 0,
|
||||
Hrtf_Enable = 1,
|
||||
Hrtf_Disable = 2,
|
||||
};
|
||||
|
||||
void aluInit(void);
|
||||
|
||||
void aluInitMixer(void);
|
||||
|
||||
/* aluInitRenderer
|
||||
*
|
||||
* Set up the appropriate panning method and mixing method given the device
|
||||
* properties.
|
||||
*/
|
||||
void aluInitRenderer(ALCdevice *device, int hrtf_id, HrtfRequestMode hrtf_appreq,
|
||||
HrtfRequestMode hrtf_userreq);
|
||||
|
||||
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);
|
||||
|
||||
/**
|
||||
* Calculates ambisonic encoder coefficients using the X, Y, and Z direction
|
||||
* components, which must represent a normalized (unit length) vector, and the
|
||||
* spread is the angular width of the sound (0...tau).
|
||||
*
|
||||
* NOTE: The components use ambisonic coordinates. As a result:
|
||||
*
|
||||
* Ambisonic Y = OpenAL -X
|
||||
* Ambisonic Z = OpenAL Y
|
||||
* Ambisonic X = OpenAL -Z
|
||||
*
|
||||
* The components are ordered such that OpenAL's X, Y, and Z are the first,
|
||||
* second, and third parameters respectively -- simply negate X and Z.
|
||||
*/
|
||||
std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,
|
||||
const float spread);
|
||||
|
||||
/**
|
||||
* CalcDirectionCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on an OpenAL direction vector. The
|
||||
* vector must be normalized (unit length), and the spread is the angular width
|
||||
* of the sound (0...tau).
|
||||
*/
|
||||
inline std::array<float,MaxAmbiChannels> CalcDirectionCoeffs(const float (&dir)[3],
|
||||
const float spread)
|
||||
{
|
||||
/* Convert from OpenAL coords to Ambisonics. */
|
||||
return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread);
|
||||
}
|
||||
|
||||
/**
|
||||
* CalcAngleCoeffs
|
||||
*
|
||||
* Calculates ambisonic coefficients based on azimuth and elevation. The
|
||||
* azimuth and elevation parameters are in radians, going right and up
|
||||
* respectively.
|
||||
*/
|
||||
inline std::array<float,MaxAmbiChannels> CalcAngleCoeffs(const float azimuth,
|
||||
const float elevation, const float spread)
|
||||
{
|
||||
const float x{-std::sin(azimuth) * std::cos(elevation)};
|
||||
const float y{ std::sin(elevation)};
|
||||
const float z{ std::cos(azimuth) * std::cos(elevation)};
|
||||
|
||||
return CalcAmbiCoeffs(x, y, z, spread);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ComputePanGains
|
||||
*
|
||||
* Computes panning gains using the given channel decoder coefficients and the
|
||||
* pre-calculated direction or angle coefficients. For B-Format sources, the
|
||||
* coeffs are a 'slice' of a transform matrix for the input channel, used to
|
||||
* scale and orient the sound samples.
|
||||
*/
|
||||
void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain,
|
||||
const al::span<float,MAX_OUTPUT_CHANNELS> gains);
|
||||
|
||||
|
||||
/** Helper to set an identity/pass-through panning for ambisonic mixing (3D input). */
|
||||
template<typename T, typename I, typename F>
|
||||
auto SetAmbiPanIdentity(T iter, I count, F func) -> std::enable_if_t<std::is_integral<I>::value>
|
||||
{
|
||||
if(count < 1) return;
|
||||
|
||||
std::array<float,MaxAmbiChannels> coeffs{{1.0f}};
|
||||
func(*iter, coeffs);
|
||||
++iter;
|
||||
for(I i{1};i < count;++i,++iter)
|
||||
{
|
||||
coeffs[i-1] = 0.0f;
|
||||
coeffs[i ] = 1.0f;
|
||||
func(*iter, coeffs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern const float ConeScale;
|
||||
extern const float ZScale;
|
||||
|
||||
#endif
|
||||
@@ -1,49 +0,0 @@
|
||||
#ifndef ALC_EVENT_H
|
||||
#define ALC_EVENT_H
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
struct EffectState;
|
||||
enum class VChangeState;
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
enum {
|
||||
/* End event thread processing. */
|
||||
EventType_KillThread = 0,
|
||||
|
||||
/* User event types. */
|
||||
EventType_SourceStateChange = 1<<0,
|
||||
EventType_BufferCompleted = 1<<1,
|
||||
EventType_Disconnected = 1<<2,
|
||||
|
||||
/* Internal events. */
|
||||
EventType_ReleaseEffectState = 65536,
|
||||
};
|
||||
|
||||
struct AsyncEvent {
|
||||
uint EnumType{0u};
|
||||
union {
|
||||
char dummy;
|
||||
struct {
|
||||
uint id;
|
||||
VChangeState state;
|
||||
} srcstate;
|
||||
struct {
|
||||
uint id;
|
||||
uint count;
|
||||
} bufcomp;
|
||||
struct {
|
||||
char msg[244];
|
||||
} disconnect;
|
||||
EffectState *mEffectState;
|
||||
} u{};
|
||||
|
||||
AsyncEvent() noexcept = default;
|
||||
constexpr AsyncEvent(uint type) noexcept : EnumType{type} { }
|
||||
|
||||
DISABLE_ALLOC()
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_ALSA_H
|
||||
#define BACKENDS_ALSA_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct AlsaBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_ALSA_H */
|
||||
@@ -1,194 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <mmreg.h>
|
||||
#endif
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alnumeric.h"
|
||||
#include "aloptional.h"
|
||||
#include "atomic.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
|
||||
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(INVALID_CHANNEL_INDEX);
|
||||
|
||||
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 DevFmtX51Rear:
|
||||
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;
|
||||
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 DevFmtAmbi3D:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BackendBase::setDefaultChannelOrder()
|
||||
{
|
||||
mDevice->RealOut.ChannelIndex.fill(INVALID_CHANNEL_INDEX);
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtX51Rear:
|
||||
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;
|
||||
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;
|
||||
|
||||
/* Same as WFX order */
|
||||
case DevFmtMono:
|
||||
case DevFmtStereo:
|
||||
case DevFmtQuad:
|
||||
case DevFmtX51:
|
||||
case DevFmtX61:
|
||||
case DevFmtAmbi3D:
|
||||
setDefaultWFXChannelOrder();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
void BackendBase::setChannelOrderFromWFXMask(uint chanmask)
|
||||
{
|
||||
auto get_channel = [](const DWORD chanbit) noexcept -> al::optional<Channel>
|
||||
{
|
||||
switch(chanbit)
|
||||
{
|
||||
case SPEAKER_FRONT_LEFT: return al::make_optional(FrontLeft);
|
||||
case SPEAKER_FRONT_RIGHT: return al::make_optional(FrontRight);
|
||||
case SPEAKER_FRONT_CENTER: return al::make_optional(FrontCenter);
|
||||
case SPEAKER_LOW_FREQUENCY: return al::make_optional(LFE);
|
||||
case SPEAKER_BACK_LEFT: return al::make_optional(BackLeft);
|
||||
case SPEAKER_BACK_RIGHT: return al::make_optional(BackRight);
|
||||
case SPEAKER_FRONT_LEFT_OF_CENTER: break;
|
||||
case SPEAKER_FRONT_RIGHT_OF_CENTER: break;
|
||||
case SPEAKER_BACK_CENTER: return al::make_optional(BackCenter);
|
||||
case SPEAKER_SIDE_LEFT: return al::make_optional(SideLeft);
|
||||
case SPEAKER_SIDE_RIGHT: return al::make_optional(SideRight);
|
||||
case SPEAKER_TOP_CENTER: return al::make_optional(TopCenter);
|
||||
case SPEAKER_TOP_FRONT_LEFT: return al::make_optional(TopFrontLeft);
|
||||
case SPEAKER_TOP_FRONT_CENTER: return al::make_optional(TopFrontCenter);
|
||||
case SPEAKER_TOP_FRONT_RIGHT: return al::make_optional(TopFrontRight);
|
||||
case SPEAKER_TOP_BACK_LEFT: return al::make_optional(TopBackLeft);
|
||||
case SPEAKER_TOP_BACK_CENTER: return al::make_optional(TopBackCenter);
|
||||
case SPEAKER_TOP_BACK_RIGHT: return al::make_optional(TopBackRight);
|
||||
}
|
||||
WARN("Unhandled WFX channel bit 0x%lx\n", chanbit);
|
||||
return al::nullopt;
|
||||
};
|
||||
|
||||
const uint numchans{mDevice->channelsFromFmt()};
|
||||
uint idx{0};
|
||||
while(chanmask)
|
||||
{
|
||||
const int bit{CountTrailingZeros(chanmask)};
|
||||
const uint mask{1u << bit};
|
||||
chanmask &= ~mask;
|
||||
|
||||
if(auto label = get_channel(mask))
|
||||
{
|
||||
mDevice->RealOut.ChannelIndex[*label] = idx;
|
||||
if(++idx == numchans) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,119 +0,0 @@
|
||||
#ifndef ALC_BACKENDS_BASE_H
|
||||
#define ALC_BACKENDS_BASE_H
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alcmain.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();
|
||||
|
||||
ALCdevice *const mDevice;
|
||||
|
||||
BackendBase(ALCdevice *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();
|
||||
|
||||
#ifdef _WIN32
|
||||
/** Sets the channel order given the WaveFormatEx mask. */
|
||||
void setChannelOrderFromWFXMask(uint chanmask);
|
||||
#endif
|
||||
};
|
||||
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(ALCdevice *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(ALCdevice *device)
|
||||
{
|
||||
BackendBase *backend{device->Backend.get()};
|
||||
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(ALCdevice *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:
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
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_error errorCode() const noexcept { return mErrorCode; }
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* ALC_BACKENDS_BASE_H */
|
||||
@@ -1,686 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/coreaudio.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "converter.h"
|
||||
#include "core/logging.h"
|
||||
#include "backends/base.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
static const char ca_device[] = "CoreAudio Default";
|
||||
|
||||
|
||||
struct CoreAudioPlayback final : public BackendBase {
|
||||
CoreAudioPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~CoreAudioPlayback() override;
|
||||
|
||||
OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) noexcept;
|
||||
static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) noexcept
|
||||
{
|
||||
return static_cast<CoreAudioPlayback*>(inRefCon)->MixerProc(ioActionFlags, inTimeStamp,
|
||||
inBusNumber, inNumberFrames, ioData);
|
||||
}
|
||||
|
||||
void open(const 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(!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};
|
||||
|
||||
/* open the default output unit */
|
||||
AudioComponentDescription desc{};
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IOS
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
#endif
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
AudioComponent comp{AudioComponentFindNext(NULL, &desc)};
|
||||
if(comp == nullptr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
|
||||
|
||||
OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)};
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Could not create component instance: %u", err};
|
||||
|
||||
/* init and start the default audio unit... */
|
||||
err = AudioUnitInitialize(mAudioUnit);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not initialize audio unit: %u", err};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool CoreAudioPlayback::reset()
|
||||
{
|
||||
OSStatus err{AudioUnitUninitialize(mAudioUnit)};
|
||||
if(err != noErr)
|
||||
ERR("-- AudioUnitUninitialize failed.\n");
|
||||
|
||||
/* retrieve default output unit's properties (output side) */
|
||||
AudioStreamBasicDescription streamFormat{};
|
||||
auto size = static_cast<UInt32>(sizeof(AudioStreamBasicDescription));
|
||||
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
|
||||
0, &streamFormat, &size);
|
||||
if(err != noErr || size != sizeof(AudioStreamBasicDescription))
|
||||
{
|
||||
ERR("AudioUnitGetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
TRACE("Output streamFormat of default output unit -\n");
|
||||
TRACE(" streamFormat.mFramesPerPacket = %d\n", streamFormat.mFramesPerPacket);
|
||||
TRACE(" streamFormat.mChannelsPerFrame = %d\n", streamFormat.mChannelsPerFrame);
|
||||
TRACE(" streamFormat.mBitsPerChannel = %d\n", streamFormat.mBitsPerChannel);
|
||||
TRACE(" streamFormat.mBytesPerPacket = %d\n", streamFormat.mBytesPerPacket);
|
||||
TRACE(" streamFormat.mBytesPerFrame = %d\n", streamFormat.mBytesPerFrame);
|
||||
TRACE(" streamFormat.mSampleRate = %5.0f\n", streamFormat.mSampleRate);
|
||||
#endif
|
||||
|
||||
/* set default output unit's input side to match output side */
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
|
||||
0, &streamFormat, size);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mDevice->Frequency != streamFormat.mSampleRate)
|
||||
{
|
||||
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
|
||||
streamFormat.mSampleRate / mDevice->Frequency);
|
||||
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? eg, 6.0 vs 5.1 */
|
||||
switch(streamFormat.mChannelsPerFrame)
|
||||
{
|
||||
case 1:
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
break;
|
||||
case 2:
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
break;
|
||||
case 4:
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
break;
|
||||
case 6:
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
break;
|
||||
case 7:
|
||||
mDevice->FmtChans = DevFmtX61;
|
||||
break;
|
||||
case 8:
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
break;
|
||||
default:
|
||||
ERR("Unhandled channel count (%d), using Stereo\n", streamFormat.mChannelsPerFrame);
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
break;
|
||||
}
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
/* use channel count and sample rate from the default output unit's current
|
||||
* parameters, but reset everything else */
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mFormatFlags = 0;
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
mDevice->FmtType = DevFmtByte;
|
||||
/* fall-through */
|
||||
case DevFmtByte:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
streamFormat.mBitsPerChannel = 32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
streamFormat.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
||||
streamFormat.mBitsPerChannel = 32;
|
||||
break;
|
||||
}
|
||||
streamFormat.mBytesPerFrame = streamFormat.mChannelsPerFrame *
|
||||
streamFormat.mBitsPerChannel / 8;
|
||||
streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags |= kAudioFormatFlagsNativeEndian |
|
||||
kLinearPCMFormatFlagIsPacked;
|
||||
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
|
||||
0, &streamFormat, sizeof(AudioStreamBasicDescription));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* setup callback */
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
AURenderCallbackStruct input{};
|
||||
input.inputProc = CoreAudioPlayback::MixerProcC;
|
||||
input.inputProcRefCon = this;
|
||||
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitSetProperty failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* init the default audio unit... */
|
||||
err = AudioUnitInitialize(mAudioUnit);
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitInitialize failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~CoreAudioCapture() override;
|
||||
|
||||
OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames, AudioBufferList *ioData) noexcept;
|
||||
static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) noexcept
|
||||
{
|
||||
return static_cast<CoreAudioCapture*>(inRefCon)->RecordProc(ioActionFlags, inTimeStamp,
|
||||
inBusNumber, inNumberFrames, ioData);
|
||||
}
|
||||
|
||||
void open(const 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;
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
|
||||
DEF_NEWDEL(CoreAudioCapture)
|
||||
};
|
||||
|
||||
CoreAudioCapture::~CoreAudioCapture()
|
||||
{
|
||||
if(mAudioUnit)
|
||||
AudioComponentInstanceDispose(mAudioUnit);
|
||||
mAudioUnit = 0;
|
||||
}
|
||||
|
||||
|
||||
OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags*,
|
||||
const AudioTimeStamp *inTimeStamp, UInt32, UInt32 inNumberFrames,
|
||||
AudioBufferList*) noexcept
|
||||
{
|
||||
AudioUnitRenderActionFlags flags = 0;
|
||||
union {
|
||||
al::byte _[sizeof(AudioBufferList) + sizeof(AudioBuffer)*2];
|
||||
AudioBufferList list;
|
||||
} audiobuf{};
|
||||
|
||||
auto rec_vec = mRing->getWriteVector();
|
||||
inNumberFrames = static_cast<UInt32>(minz(inNumberFrames,
|
||||
rec_vec.first.len+rec_vec.second.len));
|
||||
|
||||
// Fill the ringbuffer's two segments with data from the input device
|
||||
if(rec_vec.first.len >= inNumberFrames)
|
||||
{
|
||||
audiobuf.list.mNumberBuffers = 1;
|
||||
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
|
||||
audiobuf.list.mBuffers[0].mDataByteSize = inNumberFrames * mFormat.mBytesPerFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto remaining = static_cast<uint>(inNumberFrames - rec_vec.first.len);
|
||||
audiobuf.list.mNumberBuffers = 2;
|
||||
audiobuf.list.mBuffers[0].mNumberChannels = mFormat.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[0].mData = rec_vec.first.buf;
|
||||
audiobuf.list.mBuffers[0].mDataByteSize = static_cast<UInt32>(rec_vec.first.len) *
|
||||
mFormat.mBytesPerFrame;
|
||||
audiobuf.list.mBuffers[1].mNumberChannels = mFormat.mChannelsPerFrame;
|
||||
audiobuf.list.mBuffers[1].mData = rec_vec.second.buf;
|
||||
audiobuf.list.mBuffers[1].mDataByteSize = remaining * mFormat.mBytesPerFrame;
|
||||
}
|
||||
OSStatus err{AudioUnitRender(mAudioUnit, &flags, inTimeStamp, audiobuf.list.mNumberBuffers,
|
||||
inNumberFrames, &audiobuf.list)};
|
||||
if(err != noErr)
|
||||
{
|
||||
ERR("AudioUnitRender error: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
mRing->writeAdvance(inNumberFrames);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
void CoreAudioCapture::open(const char *name)
|
||||
{
|
||||
AudioStreamBasicDescription requestedFormat; // The application requested format
|
||||
AudioStreamBasicDescription hardwareFormat; // The hardware format
|
||||
AudioStreamBasicDescription outputFormat; // The AudioUnit output format
|
||||
AURenderCallbackStruct input;
|
||||
AudioComponentDescription desc;
|
||||
UInt32 propertySize;
|
||||
UInt32 enableIO;
|
||||
AudioComponent comp;
|
||||
OSStatus err;
|
||||
|
||||
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};
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
#if TARGET_OS_IOS
|
||||
desc.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
#else
|
||||
desc.componentSubType = kAudioUnitSubType_HALOutput;
|
||||
#endif
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
// Search for component with given description
|
||||
comp = AudioComponentFindNext(NULL, &desc);
|
||||
if(comp == NULL)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"};
|
||||
|
||||
// Open the component
|
||||
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
|
||||
enableIO = 0;
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_EnableIO,
|
||||
kAudioUnitScope_Output, 0, &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, 1, &enableIO, sizeof(enableIO));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not enable audio unit input property: %u", err};
|
||||
|
||||
#if !TARGET_OS_IOS
|
||||
{
|
||||
// Get the default input device
|
||||
AudioDeviceID inputDevice = kAudioDeviceUnknown;
|
||||
|
||||
propertySize = sizeof(AudioDeviceID);
|
||||
AudioObjectPropertyAddress propertyAddress{};
|
||||
propertyAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
|
||||
propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
|
||||
propertyAddress.mElement = kAudioObjectPropertyElementMaster;
|
||||
|
||||
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, nullptr,
|
||||
&propertySize, &inputDevice);
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Could not get input device: %u", err};
|
||||
if(inputDevice == kAudioDeviceUnknown)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Unknown input device"};
|
||||
|
||||
// Track the input device
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &inputDevice, sizeof(AudioDeviceID));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"Could not set input device: %u", err};
|
||||
}
|
||||
#endif
|
||||
|
||||
// set capture callback
|
||||
input.inputProc = CoreAudioCapture::RecordProcC;
|
||||
input.inputProcRefCon = this;
|
||||
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback,
|
||||
kAudioUnitScope_Global, 0, &input, sizeof(AURenderCallbackStruct));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{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, 1, &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
|
||||
propertySize = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
|
||||
1, &hardwareFormat, &propertySize);
|
||||
if(err != noErr || propertySize != sizeof(AudioStreamBasicDescription))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not get input format: %u", err};
|
||||
|
||||
// Set up the requested format description
|
||||
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 DevFmtX51Rear:
|
||||
case DevFmtX61:
|
||||
case DevFmtX71:
|
||||
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
|
||||
outputFormat = requestedFormat;
|
||||
outputFormat.mSampleRate = hardwareFormat.mSampleRate;
|
||||
|
||||
// The output format should be the requested format, but using the hardware sample rate
|
||||
// This is because the AudioUnit will automatically scale other properties, except for sample rate
|
||||
err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
|
||||
1, &outputFormat, sizeof(outputFormat));
|
||||
if(err != noErr)
|
||||
throw al::backend_exception{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{double{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, 0, &outputFrameCount, &propertySize);
|
||||
if(err != noErr || propertySize != sizeof(outputFrameCount))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Could not get input frame count: %u", err};
|
||||
|
||||
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 = CreateSampleConverter(mDevice->FmtType, mDevice->FmtType,
|
||||
mFormat.mChannelsPerFrame, static_cast<uint>(hardwareFormat.mSampleRate),
|
||||
mDevice->Frequency, Resampler::FastBSinc24);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
case BackendType::Capture:
|
||||
/* Includes null char. */
|
||||
outnames.append(ca_device, sizeof(ca_device));
|
||||
break;
|
||||
}
|
||||
return outnames;
|
||||
}
|
||||
|
||||
BackendPtr CoreAudioBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new CoreAudioPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new CoreAudioCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_COREAUDIO_H
|
||||
#define BACKENDS_COREAUDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct CoreAudioBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_COREAUDIO_H */
|
||||
@@ -1,868 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/dsound.h"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include <cguid.h>
|
||||
#include <mmreg.h>
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "compat.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 MAX_UPDATES 128
|
||||
|
||||
struct DevMap {
|
||||
std::string name;
|
||||
GUID guid;
|
||||
|
||||
template<typename T0, typename T1>
|
||||
DevMap(T0&& name_, T1&& guid_)
|
||||
: name{std::forward<T0>(name_)}, guid{std::forward<T1>(guid_)}
|
||||
{ }
|
||||
};
|
||||
|
||||
al::vector<DevMap> PlaybackDevices;
|
||||
al::vector<DevMap> CaptureDevices;
|
||||
|
||||
bool checkName(const al::vector<DevMap> &list, const std::string &name)
|
||||
{
|
||||
auto match_name = [&name](const DevMap &entry) -> bool
|
||||
{ return entry.name == name; };
|
||||
return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend();
|
||||
}
|
||||
|
||||
BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, void *data) noexcept
|
||||
{
|
||||
if(!guid)
|
||||
return TRUE;
|
||||
|
||||
auto& devices = *static_cast<al::vector<DevMap>*>(data);
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)};
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(devices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
devices.emplace_back(std::move(newname), *guid);
|
||||
const DevMap &newentry = devices.back();
|
||||
|
||||
OLECHAR *guidstr{nullptr};
|
||||
HRESULT hr{StringFromCLSID(*guid, &guidstr)};
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
TRACE("Got device \"%s\", GUID \"%ls\"\n", newentry.name.c_str(), guidstr);
|
||||
CoTaskMemFree(guidstr);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
struct DSoundPlayback final : public BackendBase {
|
||||
DSoundPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~DSoundPlayback() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
IDirectSound *mDS{nullptr};
|
||||
IDirectSoundBuffer *mPrimaryBuffer{nullptr};
|
||||
IDirectSoundBuffer *mBuffer{nullptr};
|
||||
IDirectSoundNotify *mNotifies{nullptr};
|
||||
HANDLE mNotifyEvent{nullptr};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(DSoundPlayback)
|
||||
};
|
||||
|
||||
DSoundPlayback::~DSoundPlayback()
|
||||
{
|
||||
if(mNotifies)
|
||||
mNotifies->Release();
|
||||
mNotifies = nullptr;
|
||||
if(mBuffer)
|
||||
mBuffer->Release();
|
||||
mBuffer = nullptr;
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
|
||||
if(mDS)
|
||||
mDS->Release();
|
||||
mDS = nullptr;
|
||||
if(mNotifyEvent)
|
||||
CloseHandle(mNotifyEvent);
|
||||
mNotifyEvent = nullptr;
|
||||
}
|
||||
|
||||
|
||||
FORCE_ALIGN int DSoundPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
DSBCAPS DSBCaps{};
|
||||
DSBCaps.dwSize = sizeof(DSBCaps);
|
||||
HRESULT err{mBuffer->GetCaps(&DSBCaps)};
|
||||
if(FAILED(err))
|
||||
{
|
||||
ERR("Failed to get buffer caps: 0x%lx\n", err);
|
||||
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;
|
||||
mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if(!mNotifyEvent) hr = E_FAIL;
|
||||
|
||||
//DirectSound Init code
|
||||
if(SUCCEEDED(hr))
|
||||
hr = DirectSoundCreate(guid, &mDS, nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY);
|
||||
if(FAILED(hr))
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
|
||||
hr};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool DSoundPlayback::reset()
|
||||
{
|
||||
if(mNotifies)
|
||||
mNotifies->Release();
|
||||
mNotifies = nullptr;
|
||||
if(mBuffer)
|
||||
mBuffer->Release();
|
||||
mBuffer = nullptr;
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
if(mDevice->Flags.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(SUCCEEDED(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)
|
||||
mDevice->FmtChans = DevFmtX51;
|
||||
else if(speakers == DSSPEAKER_5POINT1_BACK)
|
||||
mDevice->FmtChans = DevFmtX51Rear;
|
||||
else if(speakers == DSSPEAKER_7POINT1 || speakers == DSSPEAKER_7POINT1_SURROUND)
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
else
|
||||
ERR("Unknown system speaker config: 0x%lx\n", speakers);
|
||||
}
|
||||
mDevice->IsHeadphones = mDevice->FmtChans == DevFmtStereo
|
||||
&& speakers == DSSPEAKER_HEADPHONE;
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono: OutputType.dwChannelMask = 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 = X5DOT1; break;
|
||||
case DevFmtX51Rear: OutputType.dwChannelMask = X5DOT1REAR; break;
|
||||
case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break;
|
||||
case DevFmtX71: 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;
|
||||
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(SUCCEEDED(hr) && !mPrimaryBuffer)
|
||||
{
|
||||
DSBUFFERDESC DSBDescription{};
|
||||
DSBDescription.dwSize = sizeof(DSBDescription);
|
||||
DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr);
|
||||
}
|
||||
if(SUCCEEDED(hr))
|
||||
hr = mPrimaryBuffer->SetFormat(&OutputType.Format);
|
||||
}
|
||||
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
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, 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 = 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))
|
||||
{
|
||||
if(mNotifies)
|
||||
mNotifies->Release();
|
||||
mNotifies = nullptr;
|
||||
if(mBuffer)
|
||||
mBuffer->Release();
|
||||
mBuffer = nullptr;
|
||||
if(mPrimaryBuffer)
|
||||
mPrimaryBuffer->Release();
|
||||
mPrimaryBuffer = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetEvent(mNotifyEvent);
|
||||
setChannelOrderFromWFXMask(OutputType.dwChannelMask);
|
||||
|
||||
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(ALCdevice *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;
|
||||
|
||||
IDirectSoundCapture *mDSC{nullptr};
|
||||
IDirectSoundCaptureBuffer *mDSCbuffer{nullptr};
|
||||
DWORD mBufferBytes{0u};
|
||||
DWORD mCursor{0u};
|
||||
|
||||
RingBufferPtr mRing;
|
||||
|
||||
DEF_NEWDEL(DSoundCapture)
|
||||
};
|
||||
|
||||
DSoundCapture::~DSoundCapture()
|
||||
{
|
||||
if(mDSCbuffer)
|
||||
{
|
||||
mDSCbuffer->Stop();
|
||||
mDSCbuffer->Release();
|
||||
mDSCbuffer = nullptr;
|
||||
}
|
||||
|
||||
if(mDSC)
|
||||
mDSC->Release();
|
||||
mDSC = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void DSoundCapture::open(const 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 DevFmtX51Rear: InputType.dwChannelMask = X5DOT1REAR; break;
|
||||
case DevFmtX61: InputType.dwChannelMask = X6DOT1; break;
|
||||
case DevFmtX71: InputType.dwChannelMask = X7DOT1; break;
|
||||
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, nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr);
|
||||
if(SUCCEEDED(hr))
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
mRing = nullptr;
|
||||
if(mDSCbuffer)
|
||||
mDSCbuffer->Release();
|
||||
mDSCbuffer = nullptr;
|
||||
if(mDSC)
|
||||
mDSC->Release();
|
||||
mDSC = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx",
|
||||
hr};
|
||||
}
|
||||
|
||||
mBufferBytes = DSCBDescription.dwBufferBytes;
|
||||
setChannelOrderFromWFXMask(InputType.dwChannelMask);
|
||||
|
||||
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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new DSoundPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new DSoundCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_DSOUND_H
|
||||
#define BACKENDS_DSOUND_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct DSoundBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_DSOUND_H */
|
||||
@@ -1,601 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/jack.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory.h>
|
||||
|
||||
#include <array>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "core/logging.h"
|
||||
#include "dynload.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char jackDevice[] = "JACK Default";
|
||||
|
||||
|
||||
#ifdef HAVE_DYNLOAD
|
||||
#define JACK_FUNCS(MAGIC) \
|
||||
MAGIC(jack_client_open); \
|
||||
MAGIC(jack_client_close); \
|
||||
MAGIC(jack_client_name_size); \
|
||||
MAGIC(jack_get_client_name); \
|
||||
MAGIC(jack_connect); \
|
||||
MAGIC(jack_activate); \
|
||||
MAGIC(jack_deactivate); \
|
||||
MAGIC(jack_port_register); \
|
||||
MAGIC(jack_port_unregister); \
|
||||
MAGIC(jack_port_get_buffer); \
|
||||
MAGIC(jack_port_name); \
|
||||
MAGIC(jack_get_ports); \
|
||||
MAGIC(jack_free); \
|
||||
MAGIC(jack_get_sample_rate); \
|
||||
MAGIC(jack_set_error_function); \
|
||||
MAGIC(jack_set_process_callback); \
|
||||
MAGIC(jack_set_buffer_size_callback); \
|
||||
MAGIC(jack_set_buffer_size); \
|
||||
MAGIC(jack_get_buffer_size);
|
||||
|
||||
void *jack_handle;
|
||||
#define MAKE_FUNC(f) decltype(f) * p##f
|
||||
JACK_FUNCS(MAKE_FUNC);
|
||||
decltype(jack_error_callback) * pjack_error_callback;
|
||||
#undef MAKE_FUNC
|
||||
|
||||
#ifndef IN_IDE_PARSER
|
||||
#define jack_client_open pjack_client_open
|
||||
#define jack_client_close pjack_client_close
|
||||
#define jack_client_name_size pjack_client_name_size
|
||||
#define jack_get_client_name pjack_get_client_name
|
||||
#define jack_connect pjack_connect
|
||||
#define jack_activate pjack_activate
|
||||
#define jack_deactivate pjack_deactivate
|
||||
#define jack_port_register pjack_port_register
|
||||
#define jack_port_unregister pjack_port_unregister
|
||||
#define jack_port_get_buffer pjack_port_get_buffer
|
||||
#define jack_port_name pjack_port_name
|
||||
#define jack_get_ports pjack_get_ports
|
||||
#define jack_free pjack_free
|
||||
#define jack_get_sample_rate pjack_get_sample_rate
|
||||
#define jack_set_error_function pjack_set_error_function
|
||||
#define jack_set_process_callback pjack_set_process_callback
|
||||
#define jack_set_buffer_size_callback pjack_set_buffer_size_callback
|
||||
#define jack_set_buffer_size pjack_set_buffer_size
|
||||
#define jack_get_buffer_size pjack_get_buffer_size
|
||||
#define jack_error_callback (*pjack_error_callback)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
jack_options_t ClientOptions = JackNullOption;
|
||||
|
||||
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 DeviceEntry {
|
||||
std::string mName;
|
||||
std::string mPattern;
|
||||
};
|
||||
|
||||
al::vector<DeviceEntry> PlaybackList;
|
||||
|
||||
|
||||
void EnumerateDevices(al::vector<DeviceEntry> &list)
|
||||
{
|
||||
al::vector<DeviceEntry>{}.swap(list);
|
||||
|
||||
list.emplace_back(DeviceEntry{jackDevice, ""});
|
||||
|
||||
std::string customList{ConfigValueStr(nullptr, "jack", "custom-devices").value_or("")};
|
||||
size_t strpos{0};
|
||||
while(strpos < customList.size())
|
||||
{
|
||||
size_t nextpos{customList.find(';', strpos)};
|
||||
size_t seppos{customList.find('=', strpos)};
|
||||
if(seppos >= nextpos || seppos == strpos)
|
||||
{
|
||||
const std::string entry{customList.substr(strpos, nextpos-strpos)};
|
||||
ERR("Invalid device entry: \"%s\"\n", entry.c_str());
|
||||
if(nextpos != std::string::npos) ++nextpos;
|
||||
strpos = nextpos;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t count{1};
|
||||
std::string name{customList.substr(strpos, seppos-strpos)};
|
||||
auto check_name = [&name](const DeviceEntry &entry) -> bool
|
||||
{ return entry.mName == name; };
|
||||
while(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend())
|
||||
{
|
||||
name = customList.substr(strpos, seppos-strpos);
|
||||
name += " #";
|
||||
name += std::to_string(++count);
|
||||
}
|
||||
|
||||
++seppos;
|
||||
list.emplace_back(DeviceEntry{std::move(name), customList.substr(seppos, nextpos-seppos)});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct JackPlayback final : public BackendBase {
|
||||
JackPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~JackPlayback() override;
|
||||
|
||||
int process(jack_nframes_t numframes) noexcept;
|
||||
static int processC(jack_nframes_t numframes, void *arg) noexcept
|
||||
{ return static_cast<JackPlayback*>(arg)->process(numframes); }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const 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};
|
||||
RingBufferPtr mRing;
|
||||
al::semaphore mSem;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(JackPlayback)
|
||||
};
|
||||
|
||||
JackPlayback::~JackPlayback()
|
||||
{
|
||||
if(!mClient)
|
||||
return;
|
||||
|
||||
std::for_each(mPort.begin(), mPort.end(),
|
||||
[this](jack_port_t *port) -> void
|
||||
{ if(port) jack_port_unregister(mClient, port); }
|
||||
);
|
||||
mPort.fill(nullptr);
|
||||
jack_client_close(mClient);
|
||||
mClient = nullptr;
|
||||
}
|
||||
|
||||
|
||||
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 LIKELY(mPlaying.load(std::memory_order_acquire))
|
||||
{
|
||||
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)
|
||||
{
|
||||
mPortPattern.clear();
|
||||
|
||||
if(!name)
|
||||
name = jackDevice;
|
||||
else if(strcmp(name, jackDevice) != 0)
|
||||
{
|
||||
if(PlaybackList.empty())
|
||||
EnumerateDevices(PlaybackList);
|
||||
|
||||
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};
|
||||
mPortPattern = iter->mPattern;
|
||||
}
|
||||
|
||||
const char *client_name{"alsoft"};
|
||||
jack_status_t status;
|
||||
mClient = jack_client_open(client_name, ClientOptions, &status, nullptr);
|
||||
if(mClient == nullptr)
|
||||
throw al::backend_exception{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);
|
||||
}
|
||||
|
||||
jack_set_process_callback(mClient, &JackPlayback::processC, this);
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool JackPlayback::reset()
|
||||
{
|
||||
std::for_each(mPort.begin(), mPort.end(),
|
||||
[this](jack_port_t *port) -> void
|
||||
{ if(port) jack_port_unregister(mClient, 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);
|
||||
mDevice->BufferSize = mDevice->UpdateSize * 2;
|
||||
|
||||
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;
|
||||
|
||||
auto ports_end = mPort.begin() + mDevice->channelsFromFmt();
|
||||
auto bad_port = std::find_if_not(mPort.begin(), ports_end,
|
||||
[this](jack_port_t *&port) -> bool
|
||||
{
|
||||
std::string name{"channel_" + std::to_string(&port - &mPort[0] + 1)};
|
||||
port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
return port != nullptr;
|
||||
});
|
||||
if(bad_port != ports_end)
|
||||
{
|
||||
ERR("Not enough JACK ports available for %s output\n", DevFmtChannelsString(mDevice->FmtChans));
|
||||
if(bad_port == 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))
|
||||
{
|
||||
const char **ports{jack_get_ports(mClient, mPortPattern.c_str(), nullptr,
|
||||
JackPortIsPhysical|JackPortIsInput)};
|
||||
if(ports == nullptr)
|
||||
{
|
||||
jack_deactivate(mClient);
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"No physical playback ports found"};
|
||||
}
|
||||
auto connect_port = [this](const jack_port_t *port, const char *pname) -> bool
|
||||
{
|
||||
if(!port) return false;
|
||||
if(!pname)
|
||||
{
|
||||
ERR("No physical playback port for \"%s\"\n", jack_port_name(port));
|
||||
return false;
|
||||
}
|
||||
if(jack_connect(mClient, jack_port_name(port), pname))
|
||||
ERR("Failed to connect output port \"%s\" to \"%s\"\n", jack_port_name(port),
|
||||
pname);
|
||||
return true;
|
||||
};
|
||||
std::mismatch(mPort.begin(), mPort.end(), ports, connect_port);
|
||||
jack_free(ports);
|
||||
}
|
||||
|
||||
/* Reconfigure buffer metrics in case the server changed it since the reset
|
||||
* (it won't change again after jack_activate), then allocate the ring
|
||||
* buffer with the appropriate size.
|
||||
*/
|
||||
mDevice->Frequency = jack_get_sample_rate(mClient);
|
||||
mDevice->UpdateSize = jack_get_buffer_size(mClient);
|
||||
mDevice->BufferSize = mDevice->UpdateSize * 2;
|
||||
|
||||
uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)};
|
||||
bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize);
|
||||
mDevice->BufferSize = bufsize + mDevice->UpdateSize;
|
||||
|
||||
mRing = nullptr;
|
||||
mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true);
|
||||
|
||||
try {
|
||||
mPlaying.store(true, std::memory_order_release);
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&JackPlayback::mixerProc), this};
|
||||
}
|
||||
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(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
|
||||
mSem.post();
|
||||
mThread.join();
|
||||
|
||||
jack_deactivate(mClient);
|
||||
mPlaying.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
ClockLatency JackPlayback::getClockLatency()
|
||||
{
|
||||
ClockLatency ret;
|
||||
|
||||
std::lock_guard<std::mutex> _{mMutex};
|
||||
ret.ClockTime = GetDeviceClockTime(mDevice);
|
||||
ret.Latency = std::chrono::seconds{mRing->readSpace()};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void jack_msg_handler(const char *message)
|
||||
{
|
||||
WARN("%s\n", message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool JackBackendFactory::init()
|
||||
{
|
||||
if(!jack_load())
|
||||
return false;
|
||||
|
||||
if(!GetConfigValueBool(nullptr, "jack", "spawn-server", 0))
|
||||
ClientOptions = static_cast<jack_options_t>(ClientOptions | JackNoStartServer);
|
||||
|
||||
void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr};
|
||||
jack_set_error_function(jack_msg_handler);
|
||||
jack_status_t status;
|
||||
jack_client_t *client{jack_client_open("alsoft", ClientOptions, &status, nullptr)};
|
||||
jack_set_error_function(old_error_cb);
|
||||
if(!client)
|
||||
{
|
||||
WARN("jack_client_open() failed, 0x%02x\n", status);
|
||||
if((status&JackServerFailed) && !(ClientOptions&JackNoStartServer))
|
||||
ERR("Unable to connect to JACK server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
jack_client_close(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JackBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback); }
|
||||
|
||||
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);
|
||||
};
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
EnumerateDevices(PlaybackList);
|
||||
std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name);
|
||||
break;
|
||||
case BackendType::Capture:
|
||||
break;
|
||||
}
|
||||
return outnames;
|
||||
}
|
||||
|
||||
BackendPtr JackBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new JackPlayback{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &JackBackendFactory::getFactory()
|
||||
{
|
||||
static JackBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_JACK_H
|
||||
#define BACKENDS_JACK_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct JackBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_JACK_H */
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/loopback.h"
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct LoopbackBackend final : public BackendBase {
|
||||
LoopbackBackend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
|
||||
void open(const ALCchar *name) override;
|
||||
bool reset() override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
DEF_NEWDEL(LoopbackBackend)
|
||||
};
|
||||
|
||||
|
||||
void LoopbackBackend::open(const ALCchar *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(ALCdevice *device, BackendType)
|
||||
{ return BackendPtr{new LoopbackBackend{device}}; }
|
||||
|
||||
BackendFactory &LoopbackBackendFactory::getFactory()
|
||||
{
|
||||
static LoopbackBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_LOOPBACK_H
|
||||
#define BACKENDS_LOOPBACK_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct LoopbackBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_LOOPBACK_H */
|
||||
@@ -1,179 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2010 by Chris Robinson
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/null.h"
|
||||
|
||||
#include <exception>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "alu.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(ALCdevice *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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new NullBackend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &NullBackendFactory::getFactory()
|
||||
{
|
||||
static NullBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_NULL_H
|
||||
#define BACKENDS_NULL_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct NullBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_NULL_H */
|
||||
@@ -1,250 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "oboe.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "alu.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include "oboe/Oboe.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char device_name[] = "Oboe Default";
|
||||
|
||||
|
||||
struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
|
||||
OboePlayback(ALCdevice *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()};
|
||||
|
||||
if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo)
|
||||
{
|
||||
/* If the device is only mixing stereo but there's more than two
|
||||
* output channels, there are unused channels that need to be silenced.
|
||||
*/
|
||||
if(mStream->getFormat() == oboe::AudioFormat::Float)
|
||||
memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(float));
|
||||
else
|
||||
memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(int16_t));
|
||||
}
|
||||
|
||||
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::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
|
||||
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
|
||||
->openManagedStream(mStream)};
|
||||
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.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:
|
||||
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)};
|
||||
TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
|
||||
|
||||
switch(mStream->getChannelCount())
|
||||
{
|
||||
case oboe::ChannelCount::Mono:
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
break;
|
||||
case oboe::ChannelCount::Stereo:
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
break;
|
||||
/* Other potential configurations. Could be wrong, but better than failing.
|
||||
* Assume WFX channel order.
|
||||
*/
|
||||
case 4:
|
||||
mDevice->FmtChans = DevFmtQuad;
|
||||
break;
|
||||
case 6:
|
||||
mDevice->FmtChans = DevFmtX51Rear;
|
||||
break;
|
||||
case 7:
|
||||
mDevice->FmtChans = DevFmtX61;
|
||||
break;
|
||||
case 8:
|
||||
mDevice->FmtChans = DevFmtX71;
|
||||
break;
|
||||
default:
|
||||
if(mStream->getChannelCount() < 1)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Got unhandled channel count: %d", mStream->getChannelCount()};
|
||||
/* Assume first two channels are front left/right. We can do a stereo
|
||||
* mix and keep the other channels silent.
|
||||
*/
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
break;
|
||||
}
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
switch(mStream->getFormat())
|
||||
{
|
||||
case oboe::AudioFormat::I16:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
break;
|
||||
case oboe::AudioFormat::Float:
|
||||
mDevice->FmtType = DevFmtFloat;
|
||||
break;
|
||||
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)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool OboeBackendFactory::init() { return true; }
|
||||
|
||||
bool OboeBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
std::string OboeBackendFactory::probe(BackendType type)
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
/* Includes null char. */
|
||||
return std::string{device_name, sizeof(device_name)};
|
||||
case BackendType::Capture:
|
||||
break;
|
||||
}
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new OboePlayback{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &OboeBackendFactory::getFactory()
|
||||
{
|
||||
static OboeBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_OBOE_H
|
||||
#define BACKENDS_OBOE_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct OboeBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_OBOE_H */
|
||||
@@ -1,974 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* This is an OpenAL backend for Android using the native audio APIs based on
|
||||
* OpenSL ES 1.0.1. It is based on source code for the native-audio sample app
|
||||
* bundled with NDK.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/opensl.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include <new>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "albit.h"
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "compat.h"
|
||||
#include "core/logging.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "threads.h"
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
#include <SLES/OpenSLES_AndroidConfiguration.h>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* Helper macros */
|
||||
#define EXTRACT_VCALL_ARGS(...) __VA_ARGS__))
|
||||
#define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS
|
||||
#define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS
|
||||
|
||||
|
||||
constexpr char opensl_device[] = "OpenSL";
|
||||
|
||||
|
||||
constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case DevFmtMono: return SL_SPEAKER_FRONT_CENTER;
|
||||
case DevFmtStereo: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
case DevFmtQuad: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT;
|
||||
case DevFmtX51: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_SIDE_LEFT |
|
||||
SL_SPEAKER_SIDE_RIGHT;
|
||||
case DevFmtX51Rear: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
|
||||
SL_SPEAKER_BACK_RIGHT;
|
||||
case DevFmtX61: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_CENTER |
|
||||
SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
|
||||
case DevFmtX71: return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT |
|
||||
SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY | SL_SPEAKER_BACK_LEFT |
|
||||
SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT;
|
||||
case DevFmtAmbi3D:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
constexpr SLuint32 GetTypeRepresentation(DevFmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
return SL_ANDROID_PCM_REPRESENTATION_UNSIGNED_INT;
|
||||
case DevFmtByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
return SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
|
||||
case DevFmtFloat:
|
||||
return SL_ANDROID_PCM_REPRESENTATION_FLOAT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr SLuint32 GetByteOrderEndianness() noexcept
|
||||
{
|
||||
if /*constexpr*/(al::endian::native == al::endian::little)
|
||||
return SL_BYTEORDER_LITTLEENDIAN;
|
||||
return SL_BYTEORDER_BIGENDIAN;
|
||||
}
|
||||
|
||||
const char *res_str(SLresult result) noexcept
|
||||
{
|
||||
switch(result)
|
||||
{
|
||||
case SL_RESULT_SUCCESS: return "Success";
|
||||
case SL_RESULT_PRECONDITIONS_VIOLATED: return "Preconditions violated";
|
||||
case SL_RESULT_PARAMETER_INVALID: return "Parameter invalid";
|
||||
case SL_RESULT_MEMORY_FAILURE: return "Memory failure";
|
||||
case SL_RESULT_RESOURCE_ERROR: return "Resource error";
|
||||
case SL_RESULT_RESOURCE_LOST: return "Resource lost";
|
||||
case SL_RESULT_IO_ERROR: return "I/O error";
|
||||
case SL_RESULT_BUFFER_INSUFFICIENT: return "Buffer insufficient";
|
||||
case SL_RESULT_CONTENT_CORRUPTED: return "Content corrupted";
|
||||
case SL_RESULT_CONTENT_UNSUPPORTED: return "Content unsupported";
|
||||
case SL_RESULT_CONTENT_NOT_FOUND: return "Content not found";
|
||||
case SL_RESULT_PERMISSION_DENIED: return "Permission denied";
|
||||
case SL_RESULT_FEATURE_UNSUPPORTED: return "Feature unsupported";
|
||||
case SL_RESULT_INTERNAL_ERROR: return "Internal error";
|
||||
case SL_RESULT_UNKNOWN_ERROR: return "Unknown error";
|
||||
case SL_RESULT_OPERATION_ABORTED: return "Operation aborted";
|
||||
case SL_RESULT_CONTROL_LOST: return "Control lost";
|
||||
#ifdef SL_RESULT_READONLY
|
||||
case SL_RESULT_READONLY: return "ReadOnly";
|
||||
#endif
|
||||
#ifdef SL_RESULT_ENGINEOPTION_UNSUPPORTED
|
||||
case SL_RESULT_ENGINEOPTION_UNSUPPORTED: return "Engine option unsupported";
|
||||
#endif
|
||||
#ifdef SL_RESULT_SOURCE_SINK_INCOMPATIBLE
|
||||
case SL_RESULT_SOURCE_SINK_INCOMPATIBLE: return "Source/Sink incompatible";
|
||||
#endif
|
||||
}
|
||||
return "Unknown error code";
|
||||
}
|
||||
|
||||
#define PRINTERR(x, s) do { \
|
||||
if UNLIKELY((x) != SL_RESULT_SUCCESS) \
|
||||
ERR("%s: %s\n", (s), res_str((x))); \
|
||||
} while(0)
|
||||
|
||||
|
||||
struct OpenSLPlayback final : public BackendBase {
|
||||
OpenSLPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~OpenSLPlayback() override;
|
||||
|
||||
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
|
||||
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
|
||||
{ static_cast<OpenSLPlayback*>(context)->process(bq); }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const char *name) override;
|
||||
bool reset() override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
ClockLatency getClockLatency() override;
|
||||
|
||||
/* engine interfaces */
|
||||
SLObjectItf mEngineObj{nullptr};
|
||||
SLEngineItf mEngine{nullptr};
|
||||
|
||||
/* output mix interfaces */
|
||||
SLObjectItf mOutputMix{nullptr};
|
||||
|
||||
/* buffer queue player interfaces */
|
||||
SLObjectItf mBufferQueueObj{nullptr};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
al::semaphore mSem;
|
||||
|
||||
std::mutex mMutex;
|
||||
|
||||
uint mFrameSize{0};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(OpenSLPlayback)
|
||||
};
|
||||
|
||||
OpenSLPlayback::~OpenSLPlayback()
|
||||
{
|
||||
if(mBufferQueueObj)
|
||||
VCALL0(mBufferQueueObj,Destroy)();
|
||||
mBufferQueueObj = nullptr;
|
||||
|
||||
if(mOutputMix)
|
||||
VCALL0(mOutputMix,Destroy)();
|
||||
mOutputMix = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
}
|
||||
|
||||
|
||||
/* this callback handler is called every time a buffer finishes playing */
|
||||
void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept
|
||||
{
|
||||
/* A note on the ringbuffer usage: The buffer queue seems to hold on to the
|
||||
* pointer passed to the Enqueue method, rather than copying the audio.
|
||||
* Consequently, the ringbuffer contains the audio that is currently queued
|
||||
* and waiting to play. This process() callback is called when a buffer is
|
||||
* finished, so we simply move the read pointer up to indicate the space is
|
||||
* available for writing again, and wake up the mixer thread to mix and
|
||||
* queue more audio.
|
||||
*/
|
||||
mRing->readAdvance(1);
|
||||
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
int OpenSLPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
SLPlayItf player;
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&bufferQueue)};
|
||||
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player);
|
||||
PRINTERR(result, "bufferQueue->GetInterface SL_IID_PLAY");
|
||||
}
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
mDevice->handleDisconnect("Failed to get playback buffer: 0x%08x", result);
|
||||
|
||||
while(SL_RESULT_SUCCESS == result && !mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
if(mRing->writeSpace() == 0)
|
||||
{
|
||||
SLuint32 state{0};
|
||||
|
||||
result = VCALL(player,GetPlayState)(&state);
|
||||
PRINTERR(result, "player->GetPlayState");
|
||||
if(SL_RESULT_SUCCESS == result && state != SL_PLAYSTATE_PLAYING)
|
||||
{
|
||||
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_PLAYING);
|
||||
PRINTERR(result, "player->SetPlayState");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to start playback: 0x%08x", result);
|
||||
break;
|
||||
}
|
||||
|
||||
if(mRing->writeSpace() == 0)
|
||||
{
|
||||
mSem.wait();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> dlock{mMutex};
|
||||
auto data = mRing->getWriteVector();
|
||||
mDevice->renderSamples(data.first.buf,
|
||||
static_cast<uint>(data.first.len)*mDevice->UpdateSize, frame_step);
|
||||
if(data.second.len > 0)
|
||||
mDevice->renderSamples(data.second.buf,
|
||||
static_cast<uint>(data.second.len)*mDevice->UpdateSize, frame_step);
|
||||
|
||||
size_t todo{data.first.len + data.second.len};
|
||||
mRing->writeAdvance(todo);
|
||||
dlock.unlock();
|
||||
|
||||
for(size_t i{0};i < todo;i++)
|
||||
{
|
||||
if(!data.first.len)
|
||||
{
|
||||
data.first = data.second;
|
||||
data.second.buf = nullptr;
|
||||
data.second.len = 0;
|
||||
}
|
||||
|
||||
result = VCALL(bufferQueue,Enqueue)(data.first.buf, mDevice->UpdateSize*mFrameSize);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to queue audio: 0x%08x", result);
|
||||
break;
|
||||
}
|
||||
|
||||
data.first.len--;
|
||||
data.first.buf += mDevice->UpdateSize*mFrameSize;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void OpenSLPlayback::open(const char *name)
|
||||
{
|
||||
if(!name)
|
||||
name = opensl_device;
|
||||
else if(strcmp(name, opensl_device) != 0)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
|
||||
name};
|
||||
|
||||
// create engine
|
||||
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
|
||||
PRINTERR(result, "slCreateEngine");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "engine->Realize");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
|
||||
PRINTERR(result, "engine->GetInterface");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngine,CreateOutputMix)(&mOutputMix, 0, nullptr, nullptr);
|
||||
PRINTERR(result, "engine->CreateOutputMix");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mOutputMix,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "outputMix->Realize");
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
if(mOutputMix)
|
||||
VCALL0(mOutputMix,Destroy)();
|
||||
mOutputMix = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to initialize OpenSL device: 0x%08x", result};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool OpenSLPlayback::reset()
|
||||
{
|
||||
SLresult result;
|
||||
|
||||
if(mBufferQueueObj)
|
||||
VCALL0(mBufferQueueObj,Destroy)();
|
||||
mBufferQueueObj = nullptr;
|
||||
|
||||
mRing = nullptr;
|
||||
|
||||
#if 0
|
||||
if(!mDevice->Flags.get<FrequencyRequest>())
|
||||
{
|
||||
/* FIXME: Disabled until I figure out how to get the Context needed for
|
||||
* the getSystemService call.
|
||||
*/
|
||||
JNIEnv *env = Android_GetJNIEnv();
|
||||
jobject jctx = Android_GetContext();
|
||||
|
||||
/* Get necessary stuff for using java.lang.Integer,
|
||||
* android.content.Context, and android.media.AudioManager.
|
||||
*/
|
||||
jclass int_cls = JCALL(env,FindClass)("java/lang/Integer");
|
||||
jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls,
|
||||
"parseInt", "(Ljava/lang/String;)I"
|
||||
);
|
||||
TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint);
|
||||
|
||||
jclass ctx_cls = JCALL(env,FindClass)("android/content/Context");
|
||||
jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls,
|
||||
"AUDIO_SERVICE", "Ljava/lang/String;"
|
||||
);
|
||||
jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls,
|
||||
"getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"
|
||||
);
|
||||
TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n",
|
||||
ctx_cls, ctx_audsvc, ctx_getSysSvc);
|
||||
|
||||
jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager");
|
||||
jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls,
|
||||
"PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;"
|
||||
);
|
||||
jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls,
|
||||
"getProperty", "(Ljava/lang/String;)Ljava/lang/String;"
|
||||
);
|
||||
TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n",
|
||||
audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty);
|
||||
|
||||
const char *strchars;
|
||||
jstring strobj;
|
||||
|
||||
/* Now make the calls. */
|
||||
//AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
|
||||
strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc);
|
||||
jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj);
|
||||
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
|
||||
TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr);
|
||||
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
|
||||
|
||||
//String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
|
||||
strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate);
|
||||
jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj);
|
||||
strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr);
|
||||
TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr);
|
||||
JCALL(env,ReleaseStringUTFChars)(strobj, strchars);
|
||||
|
||||
//int sampleRate = Integer.parseInt(srateStr);
|
||||
sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr);
|
||||
|
||||
strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr);
|
||||
TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars);
|
||||
JCALL(env,ReleaseStringUTFChars)(srateStr, strchars);
|
||||
|
||||
if(!sampleRate) sampleRate = device->Frequency;
|
||||
else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE);
|
||||
}
|
||||
#endif
|
||||
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
|
||||
setDefaultWFXChannelOrder();
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
|
||||
|
||||
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
|
||||
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
|
||||
|
||||
SLDataLocator_OutputMix loc_outmix{};
|
||||
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
||||
loc_outmix.outputMix = mOutputMix;
|
||||
|
||||
SLDataSink audioSnk{};
|
||||
audioSnk.pLocator = &loc_outmix;
|
||||
audioSnk.pFormat = nullptr;
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq{};
|
||||
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bufq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
|
||||
|
||||
SLDataSource audioSrc{};
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
|
||||
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
|
||||
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm_ex.endianness = GetByteOrderEndianness();
|
||||
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
audioSrc.pFormat = &format_pcm_ex;
|
||||
|
||||
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
|
||||
ids.data(), reqs.data());
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
#endif
|
||||
{
|
||||
/* Alter sample type according to what SLDataFormat_PCM can support. */
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte: mDevice->FmtType = DevFmtUByte; break;
|
||||
case DevFmtUInt: mDevice->FmtType = DevFmtInt; break;
|
||||
case DevFmtFloat:
|
||||
case DevFmtUShort: mDevice->FmtType = DevFmtShort; break;
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
break;
|
||||
}
|
||||
|
||||
SLDataFormat_PCM format_pcm{};
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
|
||||
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm.endianness = GetByteOrderEndianness();
|
||||
|
||||
audioSrc.pLocator = &loc_bufq;
|
||||
audioSrc.pFormat = &format_pcm;
|
||||
|
||||
result = VCALL(mEngine,CreateAudioPlayer)(&mBufferQueueObj, &audioSrc, &audioSnk, ids.size(),
|
||||
ids.data(), reqs.data());
|
||||
PRINTERR(result, "engine->CreateAudioPlayer");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
/* Set the stream type to "media" (games, music, etc), if possible. */
|
||||
SLAndroidConfigurationItf config;
|
||||
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
|
||||
PRINTERR(result, "bufferQueue->GetInterface SL_IID_ANDROIDCONFIGURATION");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
|
||||
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_STREAM_TYPE, &streamType,
|
||||
sizeof(streamType));
|
||||
PRINTERR(result, "config->SetConfiguration");
|
||||
}
|
||||
|
||||
/* Clear any error since this was optional. */
|
||||
result = SL_RESULT_SUCCESS;
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mBufferQueueObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "bufferQueue->Realize");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
|
||||
mRing = RingBuffer::Create(num_updates, mFrameSize*mDevice->UpdateSize, true);
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
if(mBufferQueueObj)
|
||||
VCALL0(mBufferQueueObj,Destroy)();
|
||||
mBufferQueueObj = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenSLPlayback::start()
|
||||
{
|
||||
mRing->reset();
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&bufferQueue)};
|
||||
PRINTERR(result, "bufferQueue->GetInterface");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this);
|
||||
PRINTERR(result, "bufferQueue->RegisterCallback");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to register callback: 0x%08x", result};
|
||||
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread(std::mem_fn(&OpenSLPlayback::mixerProc), this);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
void OpenSLPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
|
||||
mSem.post();
|
||||
mThread.join();
|
||||
|
||||
SLPlayItf player;
|
||||
SLresult result{VCALL(mBufferQueueObj,GetInterface)(SL_IID_PLAY, &player)};
|
||||
PRINTERR(result, "bufferQueue->GetInterface");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(player,SetPlayState)(SL_PLAYSTATE_STOPPED);
|
||||
PRINTERR(result, "player->SetPlayState");
|
||||
}
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
result = VCALL(mBufferQueueObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
|
||||
PRINTERR(result, "bufferQueue->GetInterface");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL0(bufferQueue,Clear)();
|
||||
PRINTERR(result, "bufferQueue->Clear");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(bufferQueue,RegisterCallback)(nullptr, nullptr);
|
||||
PRINTERR(result, "bufferQueue->RegisterCallback");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
SLAndroidSimpleBufferQueueState state;
|
||||
do {
|
||||
std::this_thread::yield();
|
||||
result = VCALL(bufferQueue,GetState)(&state);
|
||||
} while(SL_RESULT_SUCCESS == result && state.count > 0);
|
||||
PRINTERR(result, "bufferQueue->GetState");
|
||||
}
|
||||
}
|
||||
|
||||
ClockLatency OpenSLPlayback::getClockLatency()
|
||||
{
|
||||
ClockLatency ret;
|
||||
|
||||
std::lock_guard<std::mutex> _{mMutex};
|
||||
ret.ClockTime = GetDeviceClockTime(mDevice);
|
||||
ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize};
|
||||
ret.Latency /= mDevice->Frequency;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
struct OpenSLCapture final : public BackendBase {
|
||||
OpenSLCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~OpenSLCapture() override;
|
||||
|
||||
void process(SLAndroidSimpleBufferQueueItf bq) noexcept;
|
||||
static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept
|
||||
{ static_cast<OpenSLCapture*>(context)->process(bq); }
|
||||
|
||||
void open(const char *name) override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void captureSamples(al::byte *buffer, uint samples) override;
|
||||
uint availableSamples() override;
|
||||
|
||||
/* engine interfaces */
|
||||
SLObjectItf mEngineObj{nullptr};
|
||||
SLEngineItf mEngine;
|
||||
|
||||
/* recording interfaces */
|
||||
SLObjectItf mRecordObj{nullptr};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
uint mSplOffset{0u};
|
||||
|
||||
uint mFrameSize{0};
|
||||
|
||||
DEF_NEWDEL(OpenSLCapture)
|
||||
};
|
||||
|
||||
OpenSLCapture::~OpenSLCapture()
|
||||
{
|
||||
if(mRecordObj)
|
||||
VCALL0(mRecordObj,Destroy)();
|
||||
mRecordObj = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept
|
||||
{
|
||||
/* A new chunk has been written into the ring buffer, advance it. */
|
||||
mRing->writeAdvance(1);
|
||||
}
|
||||
|
||||
|
||||
void OpenSLCapture::open(const char* name)
|
||||
{
|
||||
if(!name)
|
||||
name = opensl_device;
|
||||
else if(strcmp(name, opensl_device) != 0)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
|
||||
name};
|
||||
|
||||
SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)};
|
||||
PRINTERR(result, "slCreateEngine");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "engine->Realize");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mEngineObj,GetInterface)(SL_IID_ENGINE, &mEngine);
|
||||
PRINTERR(result, "engine->GetInterface");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
/* Ensure the total length is at least 100ms */
|
||||
uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)};
|
||||
/* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */
|
||||
uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100,
|
||||
mDevice->Frequency/100*5)};
|
||||
uint num_updates{(length+update_len-1) / update_len};
|
||||
|
||||
mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false);
|
||||
|
||||
mDevice->UpdateSize = update_len;
|
||||
mDevice->BufferSize = static_cast<uint>(mRing->writeSpace() * update_len);
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const std::array<SLInterfaceID,2> ids{{ SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION }};
|
||||
const std::array<SLboolean,2> reqs{{ SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }};
|
||||
|
||||
SLDataLocator_IODevice loc_dev{};
|
||||
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
|
||||
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
|
||||
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
|
||||
loc_dev.device = nullptr;
|
||||
|
||||
SLDataSource audioSrc{};
|
||||
audioSrc.pLocator = &loc_dev;
|
||||
audioSrc.pFormat = nullptr;
|
||||
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bq{};
|
||||
loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
|
||||
loc_bq.numBuffers = mDevice->BufferSize / mDevice->UpdateSize;
|
||||
|
||||
SLDataSink audioSnk{};
|
||||
#ifdef SL_ANDROID_DATAFORMAT_PCM_EX
|
||||
SLAndroidDataFormat_PCM_EX format_pcm_ex{};
|
||||
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
|
||||
format_pcm_ex.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm_ex.sampleRate = mDevice->Frequency * 1000;
|
||||
format_pcm_ex.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm_ex.containerSize = format_pcm_ex.bitsPerSample;
|
||||
format_pcm_ex.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm_ex.endianness = GetByteOrderEndianness();
|
||||
format_pcm_ex.representation = GetTypeRepresentation(mDevice->FmtType);
|
||||
|
||||
audioSnk.pLocator = &loc_bq;
|
||||
audioSnk.pFormat = &format_pcm_ex;
|
||||
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
|
||||
ids.size(), ids.data(), reqs.data());
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
#endif
|
||||
{
|
||||
/* Fallback to SLDataFormat_PCM only if it supports the desired
|
||||
* sample type.
|
||||
*/
|
||||
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtShort
|
||||
|| mDevice->FmtType == DevFmtInt)
|
||||
{
|
||||
SLDataFormat_PCM format_pcm{};
|
||||
format_pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
format_pcm.numChannels = mDevice->channelsFromFmt();
|
||||
format_pcm.samplesPerSec = mDevice->Frequency * 1000;
|
||||
format_pcm.bitsPerSample = mDevice->bytesFromFmt() * 8;
|
||||
format_pcm.containerSize = format_pcm.bitsPerSample;
|
||||
format_pcm.channelMask = GetChannelMask(mDevice->FmtChans);
|
||||
format_pcm.endianness = GetByteOrderEndianness();
|
||||
|
||||
audioSnk.pLocator = &loc_bq;
|
||||
audioSnk.pFormat = &format_pcm;
|
||||
result = VCALL(mEngine,CreateAudioRecorder)(&mRecordObj, &audioSrc, &audioSnk,
|
||||
ids.size(), ids.data(), reqs.data());
|
||||
}
|
||||
PRINTERR(result, "engine->CreateAudioRecorder");
|
||||
}
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
/* Set the record preset to "generic", if possible. */
|
||||
SLAndroidConfigurationItf config;
|
||||
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDCONFIGURATION, &config);
|
||||
PRINTERR(result, "recordObj->GetInterface SL_IID_ANDROIDCONFIGURATION");
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
SLuint32 preset = SL_ANDROID_RECORDING_PRESET_GENERIC;
|
||||
result = VCALL(config,SetConfiguration)(SL_ANDROID_KEY_RECORDING_PRESET, &preset,
|
||||
sizeof(preset));
|
||||
PRINTERR(result, "config->SetConfiguration");
|
||||
}
|
||||
|
||||
/* Clear any error since this was optional. */
|
||||
result = SL_RESULT_SUCCESS;
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mRecordObj,Realize)(SL_BOOLEAN_FALSE);
|
||||
PRINTERR(result, "recordObj->Realize");
|
||||
}
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue;
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue);
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this);
|
||||
PRINTERR(result, "bufferQueue->RegisterCallback");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
const uint chunk_size{mDevice->UpdateSize * mFrameSize};
|
||||
|
||||
auto data = mRing->getWriteVector();
|
||||
for(size_t i{0u};i < data.first.len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(data.first.buf + chunk_size*i, chunk_size);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
for(size_t i{0u};i < data.second.len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(data.second.buf + chunk_size*i, chunk_size);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
}
|
||||
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
if(mRecordObj)
|
||||
VCALL0(mRecordObj,Destroy)();
|
||||
mRecordObj = nullptr;
|
||||
|
||||
if(mEngineObj)
|
||||
VCALL0(mEngineObj,Destroy)();
|
||||
mEngineObj = nullptr;
|
||||
mEngine = nullptr;
|
||||
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to initialize OpenSL device: 0x%08x", result};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
void OpenSLCapture::start()
|
||||
{
|
||||
SLRecordItf record;
|
||||
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_RECORDING);
|
||||
PRINTERR(result, "record->SetRecordState");
|
||||
}
|
||||
if(SL_RESULT_SUCCESS != result)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start capture: 0x%08x", result};
|
||||
}
|
||||
|
||||
void OpenSLCapture::stop()
|
||||
{
|
||||
SLRecordItf record;
|
||||
SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_RECORD, &record)};
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
|
||||
if(SL_RESULT_SUCCESS == result)
|
||||
{
|
||||
result = VCALL(record,SetRecordState)(SL_RECORDSTATE_PAUSED);
|
||||
PRINTERR(result, "record->SetRecordState");
|
||||
}
|
||||
}
|
||||
|
||||
void OpenSLCapture::captureSamples(al::byte *buffer, uint samples)
|
||||
{
|
||||
const uint update_size{mDevice->UpdateSize};
|
||||
const uint chunk_size{update_size * mFrameSize};
|
||||
|
||||
/* Read the desired samples from the ring buffer then advance its read
|
||||
* pointer.
|
||||
*/
|
||||
size_t adv_count{0};
|
||||
auto rdata = mRing->getReadVector();
|
||||
for(uint i{0};i < samples;)
|
||||
{
|
||||
const uint rem{minu(samples - i, update_size - mSplOffset)};
|
||||
std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize},
|
||||
buffer + i*size_t{mFrameSize});
|
||||
|
||||
mSplOffset += rem;
|
||||
if(mSplOffset == update_size)
|
||||
{
|
||||
/* Finished a chunk, reset the offset and advance the read pointer. */
|
||||
mSplOffset = 0;
|
||||
|
||||
++adv_count;
|
||||
rdata.first.len -= 1;
|
||||
if(!rdata.first.len)
|
||||
rdata.first = rdata.second;
|
||||
else
|
||||
rdata.first.buf += chunk_size;
|
||||
}
|
||||
|
||||
i += rem;
|
||||
}
|
||||
mRing->readAdvance(adv_count);
|
||||
|
||||
SLAndroidSimpleBufferQueueItf bufferQueue{};
|
||||
if LIKELY(mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
const SLresult result{VCALL(mRecordObj,GetInterface)(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
&bufferQueue)};
|
||||
PRINTERR(result, "recordObj->GetInterface");
|
||||
if UNLIKELY(SL_RESULT_SUCCESS != result)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to get capture buffer queue: 0x%08x", result);
|
||||
bufferQueue = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if LIKELY(bufferQueue)
|
||||
{
|
||||
SLresult result{SL_RESULT_SUCCESS};
|
||||
auto wdata = mRing->getWriteVector();
|
||||
for(size_t i{0u};i < wdata.first.len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(wdata.first.buf + chunk_size*i, chunk_size);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
for(size_t i{0u};i < wdata.second.len && SL_RESULT_SUCCESS == result;i++)
|
||||
{
|
||||
result = VCALL(bufferQueue,Enqueue)(wdata.second.buf + chunk_size*i, chunk_size);
|
||||
PRINTERR(result, "bufferQueue->Enqueue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint OpenSLCapture::availableSamples()
|
||||
{ return static_cast<uint>(mRing->readSpace()*mDevice->UpdateSize - mSplOffset); }
|
||||
|
||||
} // namespace
|
||||
|
||||
bool OSLBackendFactory::init() { return true; }
|
||||
|
||||
bool OSLBackendFactory::querySupport(BackendType type)
|
||||
{ return (type == BackendType::Playback || type == BackendType::Capture); }
|
||||
|
||||
std::string OSLBackendFactory::probe(BackendType type)
|
||||
{
|
||||
std::string outnames;
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
case BackendType::Capture:
|
||||
/* Includes null char. */
|
||||
outnames.append(opensl_device, sizeof(opensl_device));
|
||||
break;
|
||||
}
|
||||
return outnames;
|
||||
}
|
||||
|
||||
BackendPtr OSLBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new OpenSLPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new OpenSLCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &OSLBackendFactory::getFactory()
|
||||
{
|
||||
static OSLBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_OSL_H
|
||||
#define BACKENDS_OSL_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct OSLBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_OSL_H */
|
||||
@@ -1,686 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/oss.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alconfig.h"
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "aloptional.h"
|
||||
#include "alu.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(ALCdevice *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();
|
||||
}
|
||||
|
||||
mFd = ::open(devname, O_WRONLY);
|
||||
if(mFd == -1)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname,
|
||||
strerror(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool OSSPlayback::reset()
|
||||
{
|
||||
int ossFormat{};
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
ossFormat = AFMT_S8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
ossFormat = AFMT_U8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
ossFormat = AFMT_S16_NE;
|
||||
break;
|
||||
}
|
||||
|
||||
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(ALCdevice *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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new OSSPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new OSScapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_OSS_H
|
||||
#define BACKENDS_OSS_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct OSSBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_OSS_H */
|
||||
@@ -1,442 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/portaudio.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.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(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~PortPlayback() override;
|
||||
|
||||
int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
|
||||
static int writeCallbackC(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
|
||||
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
|
||||
{
|
||||
return static_cast<PortPlayback*>(userData)->writeCallback(inputBuffer, outputBuffer,
|
||||
framesPerBuffer, timeInfo, statusFlags);
|
||||
}
|
||||
|
||||
void open(const 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};
|
||||
|
||||
mUpdateSize = mDevice->UpdateSize;
|
||||
|
||||
auto devidopt = ConfigValueInt(nullptr, "port", "device");
|
||||
if(devidopt && *devidopt >= 0) mParams.device = *devidopt;
|
||||
else mParams.device = Pa_GetDefaultOutputDevice();
|
||||
mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency);
|
||||
mParams.hostApiSpecificStreamInfo = nullptr;
|
||||
|
||||
mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mParams.sampleFormat = paInt8;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
mParams.sampleFormat = paUInt8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
mParams.sampleFormat = paInt16;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
/* fall-through */
|
||||
case DevFmtInt:
|
||||
mParams.sampleFormat = paInt32;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
mParams.sampleFormat = paFloat32;
|
||||
break;
|
||||
}
|
||||
|
||||
retry_open:
|
||||
PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize,
|
||||
paNoFlag, &PortPlayback::writeCallbackC, this)};
|
||||
if(err != paNoError)
|
||||
{
|
||||
if(mParams.sampleFormat == paFloat32)
|
||||
{
|
||||
mParams.sampleFormat = paInt16;
|
||||
goto retry_open;
|
||||
}
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s",
|
||||
Pa_GetErrorText(err)};
|
||||
}
|
||||
|
||||
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(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~PortCapture() override;
|
||||
|
||||
int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept;
|
||||
static int readCallbackC(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
|
||||
const PaStreamCallbackFlags statusFlags, void *userData) noexcept
|
||||
{
|
||||
return static_cast<PortCapture*>(userData)->readCallback(inputBuffer, outputBuffer,
|
||||
framesPerBuffer, timeInfo, statusFlags);
|
||||
}
|
||||
|
||||
void open(const 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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new PortPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new PortCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &PortBackendFactory::getFactory()
|
||||
{
|
||||
static PortBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_PORTAUDIO_H
|
||||
#define BACKENDS_PORTAUDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct PortBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_PORTAUDIO_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_PULSEAUDIO_H
|
||||
#define BACKENDS_PULSEAUDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
class PulseBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_PULSEAUDIO_H */
|
||||
@@ -1,216 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/sdl2.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "alu.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
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(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~Sdl2Backend() override;
|
||||
|
||||
void audioCallback(Uint8 *stream, int len) noexcept;
|
||||
static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept
|
||||
{ static_cast<Sdl2Backend*>(ptr)->audioCallback(stream, len); }
|
||||
|
||||
void open(const 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>(mDevice->UpdateSize);
|
||||
want.callback = &Sdl2Backend::audioCallbackC;
|
||||
want.userdata = this;
|
||||
|
||||
/* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't
|
||||
* necessarily the first in the list.
|
||||
*/
|
||||
if(!name || strcmp(name, defaultDeviceName) == 0)
|
||||
mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
else
|
||||
{
|
||||
const size_t prefix_len = strlen(DEVNAME_PREFIX);
|
||||
if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0)
|
||||
mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
else
|
||||
mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have,
|
||||
SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||
}
|
||||
if(mDeviceID == 0)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()};
|
||||
|
||||
mDevice->Frequency = static_cast<uint>(have.freq);
|
||||
|
||||
if(have.channels == 1)
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
else if(have.channels == 2)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
else
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Unhandled SDL channel count: %d", int{have.channels}};
|
||||
|
||||
switch(have.format)
|
||||
{
|
||||
case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break;
|
||||
case AUDIO_S8: mDevice->FmtType = DevFmtByte; break;
|
||||
case AUDIO_U16SYS: mDevice->FmtType = DevFmtUShort; break;
|
||||
case AUDIO_S16SYS: mDevice->FmtType = DevFmtShort; break;
|
||||
case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break;
|
||||
case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break;
|
||||
default:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x",
|
||||
have.format};
|
||||
}
|
||||
mDevice->UpdateSize = have.samples;
|
||||
mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */
|
||||
|
||||
mFrameSize = mDevice->frameSizeFromFmt();
|
||||
mFrequency = mDevice->Frequency;
|
||||
mFmtChans = mDevice->FmtChans;
|
||||
mFmtType = mDevice->FmtType;
|
||||
mUpdateSize = mDevice->UpdateSize;
|
||||
|
||||
mDevice->DeviceName = name ? name : defaultDeviceName;
|
||||
}
|
||||
|
||||
bool Sdl2Backend::reset()
|
||||
{
|
||||
mDevice->Frequency = mFrequency;
|
||||
mDevice->FmtChans = mFmtChans;
|
||||
mDevice->FmtType = mFmtType;
|
||||
mDevice->UpdateSize = mUpdateSize;
|
||||
mDevice->BufferSize = mUpdateSize * 2;
|
||||
setDefaultWFXChannelOrder();
|
||||
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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new Sdl2Backend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_SDL2_H
|
||||
#define BACKENDS_SDL2_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct SDL2BackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SDL2_H */
|
||||
@@ -1,517 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/sndio.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.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 SndioPlayback final : public BackendBase {
|
||||
SndioPlayback(ALCdevice *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};
|
||||
|
||||
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()
|
||||
{
|
||||
sio_par par;
|
||||
sio_initpar(&par);
|
||||
if(!sio_getpar(mSndHandle, &par))
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to get device parameters");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const size_t frameStep{par.pchan};
|
||||
const size_t frameSize{frameStep * par.bps};
|
||||
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
al::byte *WritePtr{mBuffer.data()};
|
||||
size_t len{mBuffer.size()};
|
||||
|
||||
mDevice->renderSamples(WritePtr, static_cast<uint>(len/frameSize), frameStep);
|
||||
while(len > 0 && !mKillNow.load(std::memory_order_acquire))
|
||||
{
|
||||
size_t wrote{sio_write(mSndHandle, WritePtr, len)};
|
||||
if(wrote == 0)
|
||||
{
|
||||
ERR("sio_write failed\n");
|
||||
mDevice->handleDisconnect("Failed to write playback samples");
|
||||
break;
|
||||
}
|
||||
|
||||
len -= wrote;
|
||||
WritePtr += 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};
|
||||
|
||||
mSndHandle = sio_open(nullptr, SIO_PLAY, 0);
|
||||
if(mSndHandle == nullptr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool SndioPlayback::reset()
|
||||
{
|
||||
sio_par par;
|
||||
sio_initpar(&par);
|
||||
|
||||
par.rate = mDevice->Frequency;
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono : par.pchan = 1; break;
|
||||
case DevFmtQuad : par.pchan = 4; break;
|
||||
case DevFmtX51Rear: // fall-through - "Similar to 5.1, except using rear channels instead of sides"
|
||||
case DevFmtX51 : par.pchan = 6; break;
|
||||
case DevFmtX61 : par.pchan = 7; break;
|
||||
case DevFmtX71 : par.pchan = 8; break;
|
||||
|
||||
// fall back to stereo for Ambi3D
|
||||
case DevFmtAmbi3D : // fall-through
|
||||
case DevFmtStereo : par.pchan = 2; break;
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
par.bits = 8;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
par.bits = 8;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
case DevFmtShort:
|
||||
par.bits = 16;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
par.bits = 16;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
par.bits = 32;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
par.bits = 32;
|
||||
par.sig = 0;
|
||||
break;
|
||||
}
|
||||
par.le = SIO_LE_NATIVE;
|
||||
|
||||
par.round = mDevice->UpdateSize;
|
||||
par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize;
|
||||
if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize;
|
||||
|
||||
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
|
||||
{
|
||||
ERR("Failed to set device parameters\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(par.bits != par.bps*8)
|
||||
{
|
||||
ERR("Padded samples not supported (%u of %u bits)\n", par.bits, par.bps*8);
|
||||
return false;
|
||||
}
|
||||
if(par.le != SIO_LE_NATIVE)
|
||||
{
|
||||
ERR("Non-native-endian samples not supported (got %s-endian)\n",
|
||||
par.le ? "little" : "big");
|
||||
return false;
|
||||
}
|
||||
|
||||
mDevice->Frequency = par.rate;
|
||||
|
||||
if(par.pchan < 2)
|
||||
{
|
||||
if(mDevice->FmtChans != DevFmtMono)
|
||||
{
|
||||
WARN("Got %u channel for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans));
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
}
|
||||
}
|
||||
else if((par.pchan == 2 && mDevice->FmtChans != DevFmtStereo)
|
||||
|| par.pchan == 3
|
||||
|| (par.pchan == 4 && mDevice->FmtChans != DevFmtQuad)
|
||||
|| par.pchan == 5
|
||||
|| (par.pchan == 6 && mDevice->FmtChans != DevFmtX51 && mDevice->FmtChans != DevFmtX51Rear)
|
||||
|| (par.pchan == 7 && mDevice->FmtChans != DevFmtX61)
|
||||
|| (par.pchan == 8 && mDevice->FmtChans != DevFmtX71)
|
||||
|| par.pchan > 8)
|
||||
{
|
||||
WARN("Got %u channels for %s\n", par.pchan, DevFmtChannelsString(mDevice->FmtChans));
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
}
|
||||
|
||||
if(par.bits == 8 && par.sig == 1)
|
||||
mDevice->FmtType = DevFmtByte;
|
||||
else if(par.bits == 8 && par.sig == 0)
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
else if(par.bits == 16 && par.sig == 1)
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
else if(par.bits == 16 && par.sig == 0)
|
||||
mDevice->FmtType = DevFmtUShort;
|
||||
else if(par.bits == 32 && par.sig == 1)
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
else if(par.bits == 32 && par.sig == 0)
|
||||
mDevice->FmtType = DevFmtUInt;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled sample format: %s %u-bit\n", (par.sig?"signed":"unsigned"), par.bits);
|
||||
return false;
|
||||
}
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
mDevice->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");
|
||||
}
|
||||
|
||||
|
||||
struct SndioCapture final : public BackendBase {
|
||||
SndioCapture(ALCdevice *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()};
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
auto data = mRing->getWriteVector();
|
||||
size_t todo{data.first.len + data.second.len};
|
||||
if(todo == 0)
|
||||
{
|
||||
static char junk[4096];
|
||||
sio_read(mSndHandle, junk,
|
||||
minz(sizeof(junk)/frameSize, mDevice->UpdateSize)*frameSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t total{0u};
|
||||
data.first.len *= frameSize;
|
||||
data.second.len *= frameSize;
|
||||
todo = minz(todo, mDevice->UpdateSize) * frameSize;
|
||||
while(total < todo)
|
||||
{
|
||||
if(!data.first.len)
|
||||
data.first = data.second;
|
||||
|
||||
size_t got{sio_read(mSndHandle, data.first.buf, minz(todo-total, data.first.len))};
|
||||
if(!got)
|
||||
{
|
||||
mDevice->handleDisconnect("Failed to read capture samples");
|
||||
break;
|
||||
}
|
||||
|
||||
data.first.buf += got;
|
||||
data.first.len -= got;
|
||||
total += got;
|
||||
}
|
||||
mRing->writeAdvance(total / frameSize);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void SndioCapture::open(const 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, 0);
|
||||
if(mSndHandle == nullptr)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"};
|
||||
|
||||
sio_par par;
|
||||
sio_initpar(&par);
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
par.bps = 1;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
par.bps = 1;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtShort:
|
||||
par.bps = 2;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
par.bps = 2;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtInt:
|
||||
par.bps = 4;
|
||||
par.sig = 1;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
par.bps = 4;
|
||||
par.sig = 0;
|
||||
break;
|
||||
case DevFmtFloat:
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"%s capture samples not supported", DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
par.bits = par.bps * 8;
|
||||
par.le = SIO_LE_NATIVE;
|
||||
par.msb = SIO_LE_NATIVE ? 0 : 1;
|
||||
par.rchan = mDevice->channelsFromFmt();
|
||||
par.rate = mDevice->Frequency;
|
||||
|
||||
par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10);
|
||||
par.round = minu(par.appbufsz, mDevice->Frequency/40);
|
||||
|
||||
mDevice->UpdateSize = par.round;
|
||||
mDevice->BufferSize = par.appbufsz;
|
||||
|
||||
if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par))
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to set device praameters"};
|
||||
|
||||
if(par.bits != par.bps*8)
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Padded samples not supported (got %u of %u bits)", par.bits, par.bps*8};
|
||||
|
||||
if(!((mDevice->FmtType == DevFmtByte && par.bits == 8 && par.sig != 0)
|
||||
|| (mDevice->FmtType == DevFmtUByte && par.bits == 8 && par.sig == 0)
|
||||
|| (mDevice->FmtType == DevFmtShort && par.bits == 16 && par.sig != 0)
|
||||
|| (mDevice->FmtType == DevFmtUShort && par.bits == 16 && par.sig == 0)
|
||||
|| (mDevice->FmtType == DevFmtInt && par.bits == 32 && par.sig != 0)
|
||||
|| (mDevice->FmtType == DevFmtUInt && par.bits == 32 && par.sig == 0))
|
||||
|| mDevice->channelsFromFmt() != par.rchan || mDevice->Frequency != par.rate)
|
||||
throw al::backend_exception{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.bits, par.rchan, par.rate};
|
||||
|
||||
mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false);
|
||||
|
||||
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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new SndioPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new SndioCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_SNDIO_H
|
||||
#define BACKENDS_SNDIO_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct SndIOBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SNDIO_H */
|
||||
@@ -1,294 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/solaris.h"
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "albyte.h"
|
||||
#include "alu.h"
|
||||
#include "alconfig.h"
|
||||
#include "compat.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(ALCdevice *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};
|
||||
|
||||
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};
|
||||
|
||||
mFd = ::open(solaris_driver.c_str(), O_WRONLY);
|
||||
if(mFd == -1)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s",
|
||||
solaris_driver.c_str(), strerror(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool SolarisBackend::reset()
|
||||
{
|
||||
audio_info_t info;
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.sample_rate = mDevice->Frequency;
|
||||
|
||||
if(mDevice->FmtChans != DevFmtMono)
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
uint numChannels{mDevice->channelsFromFmt()};
|
||||
info.play.channels = numChannels;
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
info.play.precision = 8;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
info.play.precision = 8;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR8;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtUInt:
|
||||
case DevFmtFloat:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
/* fall-through */
|
||||
case DevFmtShort:
|
||||
info.play.precision = 16;
|
||||
info.play.encoding = AUDIO_ENCODING_LINEAR;
|
||||
break;
|
||||
}
|
||||
|
||||
uint frameSize{numChannels * mDevice->bytesFromFmt()};
|
||||
info.play.buffer_size = mDevice->BufferSize * frameSize;
|
||||
|
||||
if(ioctl(mFd, AUDIO_SETINFO, &info) < 0)
|
||||
{
|
||||
ERR("ioctl failed: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mDevice->channelsFromFmt() != info.play.channels)
|
||||
{
|
||||
ERR("Failed to set %s, got %u channels instead\n", DevFmtChannelsString(mDevice->FmtChans),
|
||||
info.play.channels);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!((info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR8 && mDevice->FmtType == DevFmtUByte) ||
|
||||
(info.play.precision == 8 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtByte) ||
|
||||
(info.play.precision == 16 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtShort) ||
|
||||
(info.play.precision == 32 && info.play.encoding == AUDIO_ENCODING_LINEAR && mDevice->FmtType == DevFmtInt)))
|
||||
{
|
||||
ERR("Could not set %s samples, got %d (0x%x)\n", DevFmtTypeString(mDevice->FmtType),
|
||||
info.play.precision, info.play.encoding);
|
||||
return false;
|
||||
}
|
||||
|
||||
mDevice->Frequency = info.play.sample_rate;
|
||||
mDevice->BufferSize = info.play.buffer_size / frameSize;
|
||||
mDevice->UpdateSize = mDevice->BufferSize / 2;
|
||||
|
||||
setDefaultChannelOrder();
|
||||
|
||||
mBuffer.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt());
|
||||
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(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new SolarisBackend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_SOLARIS_H
|
||||
#define BACKENDS_SOLARIS_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct SolarisBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_SOLARIS_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_WASAPI_H
|
||||
#define BACKENDS_WASAPI_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct WasapiBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_WASAPI_H */
|
||||
@@ -1,405 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/wave.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
#include "albit.h"
|
||||
#include "albyte.h"
|
||||
#include "alcmain.h"
|
||||
#include "alconfig.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alu.h"
|
||||
#include "compat.h"
|
||||
#include "core/logging.h"
|
||||
#include "strutils.h"
|
||||
#include "threads.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using std::chrono::seconds;
|
||||
using std::chrono::milliseconds;
|
||||
using std::chrono::nanoseconds;
|
||||
|
||||
using ubyte = unsigned char;
|
||||
using ushort = unsigned short;
|
||||
|
||||
constexpr char waveDevice[] = "Wave File Writer";
|
||||
|
||||
constexpr ubyte SUBTYPE_PCM[]{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
|
||||
0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
constexpr ubyte SUBTYPE_FLOAT[]{
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
|
||||
0x00, 0x38, 0x9b, 0x71
|
||||
};
|
||||
|
||||
constexpr ubyte SUBTYPE_BFORMAT_PCM[]{
|
||||
0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
|
||||
0xca, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{
|
||||
0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
|
||||
0xca, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
void fwrite16le(ushort val, FILE *f)
|
||||
{
|
||||
ubyte data[2]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff) };
|
||||
fwrite(data, 1, 2, f);
|
||||
}
|
||||
|
||||
void fwrite32le(uint val, FILE *f)
|
||||
{
|
||||
ubyte data[4]{ static_cast<ubyte>(val&0xff), static_cast<ubyte>((val>>8)&0xff),
|
||||
static_cast<ubyte>((val>>16)&0xff), static_cast<ubyte>((val>>24)&0xff) };
|
||||
fwrite(data, 1, 4, f);
|
||||
}
|
||||
|
||||
|
||||
struct WaveBackend final : public BackendBase {
|
||||
WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~WaveBackend() override;
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const char *name) override;
|
||||
bool reset() override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
FILE *mFile{nullptr};
|
||||
long mDataStart{-1};
|
||||
|
||||
al::vector<al::byte> mBuffer;
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(WaveBackend)
|
||||
};
|
||||
|
||||
WaveBackend::~WaveBackend()
|
||||
{
|
||||
if(mFile)
|
||||
fclose(mFile);
|
||||
mFile = nullptr;
|
||||
}
|
||||
|
||||
int WaveBackend::mixerProc()
|
||||
{
|
||||
const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
|
||||
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frameStep{mDevice->channelsFromFmt()};
|
||||
const size_t frameSize{mDevice->frameSizeFromFmt()};
|
||||
|
||||
int64_t done{0};
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
/* This converts from nanoseconds to nanosamples, then to samples. */
|
||||
int64_t avail{std::chrono::duration_cast<seconds>((now-start) *
|
||||
mDevice->Frequency).count()};
|
||||
if(avail-done < mDevice->UpdateSize)
|
||||
{
|
||||
std::this_thread::sleep_for(restTime);
|
||||
continue;
|
||||
}
|
||||
while(avail-done >= mDevice->UpdateSize)
|
||||
{
|
||||
mDevice->renderSamples(mBuffer.data(), mDevice->UpdateSize, frameStep);
|
||||
done += mDevice->UpdateSize;
|
||||
|
||||
if /*constexpr*/(al::endian::native != al::endian::little)
|
||||
{
|
||||
const uint bytesize{mDevice->bytesFromFmt()};
|
||||
|
||||
if(bytesize == 2)
|
||||
{
|
||||
ushort *samples = reinterpret_cast<ushort*>(mBuffer.data());
|
||||
const size_t len{mBuffer.size() / 2};
|
||||
for(size_t i{0};i < len;i++)
|
||||
{
|
||||
const ushort samp{samples[i]};
|
||||
samples[i] = static_cast<ushort>((samp>>8) | (samp<<8));
|
||||
}
|
||||
}
|
||||
else if(bytesize == 4)
|
||||
{
|
||||
uint *samples = reinterpret_cast<uint*>(mBuffer.data());
|
||||
const size_t len{mBuffer.size() / 4};
|
||||
for(size_t i{0};i < len;i++)
|
||||
{
|
||||
const uint samp{samples[i]};
|
||||
samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
|
||||
((samp<<8)&0x00ff0000) | (samp<<24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
|
||||
if(fs < mDevice->UpdateSize || ferror(mFile))
|
||||
{
|
||||
ERR("Error writing to file\n");
|
||||
mDevice->handleDisconnect("Failed to write playback samples");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* For every completed second, increment the start time and reduce the
|
||||
* samples done. This prevents the difference between the start time
|
||||
* and current time from growing too large, while maintaining the
|
||||
* correct number of samples to render.
|
||||
*/
|
||||
if(done >= mDevice->Frequency)
|
||||
{
|
||||
seconds s{done/mDevice->Frequency};
|
||||
done %= mDevice->Frequency;
|
||||
start += s;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WaveBackend::open(const char *name)
|
||||
{
|
||||
const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
|
||||
if(!fname[0]) throw al::backend_exception{al::backend_error::NoDevice,
|
||||
"No wave output filename"};
|
||||
|
||||
if(!name)
|
||||
name = waveDevice;
|
||||
else if(strcmp(name, waveDevice) != 0)
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
|
||||
name};
|
||||
|
||||
#ifdef _WIN32
|
||||
{
|
||||
std::wstring wname = utf8_to_wstr(fname);
|
||||
mFile = _wfopen(wname.c_str(), L"wb");
|
||||
}
|
||||
#else
|
||||
mFile = fopen(fname, "wb");
|
||||
#endif
|
||||
if(!mFile)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s",
|
||||
fname, strerror(errno)};
|
||||
|
||||
mDevice->DeviceName = name;
|
||||
}
|
||||
|
||||
bool WaveBackend::reset()
|
||||
{
|
||||
uint channels{0}, bytes{0}, chanmask{0};
|
||||
bool isbformat{false};
|
||||
size_t val;
|
||||
|
||||
fseek(mFile, 0, SEEK_SET);
|
||||
clearerr(mFile);
|
||||
|
||||
if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
|
||||
{
|
||||
mDevice->FmtChans = DevFmtAmbi3D;
|
||||
mDevice->mAmbiOrder = 1;
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtByte:
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
break;
|
||||
case DevFmtUShort:
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
break;
|
||||
case DevFmtUInt:
|
||||
mDevice->FmtType = DevFmtInt;
|
||||
break;
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
}
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono: chanmask = 0x04; break;
|
||||
case DevFmtStereo: chanmask = 0x01 | 0x02; break;
|
||||
case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
|
||||
case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
|
||||
case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break;
|
||||
case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
|
||||
case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
|
||||
case DevFmtAmbi3D:
|
||||
/* .amb output requires FuMa */
|
||||
mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
|
||||
mDevice->mAmbiLayout = DevAmbiLayout::FuMa;
|
||||
mDevice->mAmbiScale = DevAmbiScaling::FuMa;
|
||||
isbformat = true;
|
||||
chanmask = 0;
|
||||
break;
|
||||
}
|
||||
bytes = mDevice->bytesFromFmt();
|
||||
channels = mDevice->channelsFromFmt();
|
||||
|
||||
rewind(mFile);
|
||||
|
||||
fputs("RIFF", mFile);
|
||||
fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
|
||||
|
||||
fputs("WAVE", mFile);
|
||||
|
||||
fputs("fmt ", mFile);
|
||||
fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
|
||||
|
||||
// 16-bit val, format type id (extensible: 0xFFFE)
|
||||
fwrite16le(0xFFFE, mFile);
|
||||
// 16-bit val, channel count
|
||||
fwrite16le(static_cast<ushort>(channels), mFile);
|
||||
// 32-bit val, frequency
|
||||
fwrite32le(mDevice->Frequency, mFile);
|
||||
// 32-bit val, bytes per second
|
||||
fwrite32le(mDevice->Frequency * channels * bytes, mFile);
|
||||
// 16-bit val, frame size
|
||||
fwrite16le(static_cast<ushort>(channels * bytes), mFile);
|
||||
// 16-bit val, bits per sample
|
||||
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
|
||||
// 16-bit val, extra byte count
|
||||
fwrite16le(22, mFile);
|
||||
// 16-bit val, valid bits per sample
|
||||
fwrite16le(static_cast<ushort>(bytes * 8), mFile);
|
||||
// 32-bit val, channel mask
|
||||
fwrite32le(chanmask, mFile);
|
||||
// 16 byte GUID, sub-type format
|
||||
val = fwrite((mDevice->FmtType == DevFmtFloat) ?
|
||||
(isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
|
||||
(isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
|
||||
(void)val;
|
||||
|
||||
fputs("data", mFile);
|
||||
fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
|
||||
|
||||
if(ferror(mFile))
|
||||
{
|
||||
ERR("Error writing header: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
mDataStart = ftell(mFile);
|
||||
|
||||
setDefaultWFXChannelOrder();
|
||||
|
||||
const uint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
|
||||
mBuffer.resize(bufsize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaveBackend::start()
|
||||
{
|
||||
if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0)
|
||||
WARN("Failed to seek on output file\n");
|
||||
try {
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
void WaveBackend::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
if(mDataStart > 0)
|
||||
{
|
||||
long size{ftell(mFile)};
|
||||
if(size > 0)
|
||||
{
|
||||
long dataLen{size - mDataStart};
|
||||
if(fseek(mFile, 4, SEEK_SET) == 0)
|
||||
fwrite32le(static_cast<uint>(size-8), mFile); // 'WAVE' header len
|
||||
if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
|
||||
fwrite32le(static_cast<uint>(dataLen), mFile); // 'data' header len
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool WaveBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool WaveBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback; }
|
||||
|
||||
std::string WaveBackendFactory::probe(BackendType type)
|
||||
{
|
||||
std::string outnames;
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
/* Includes null char. */
|
||||
outnames.append(waveDevice, sizeof(waveDevice));
|
||||
break;
|
||||
case BackendType::Capture:
|
||||
break;
|
||||
}
|
||||
return outnames;
|
||||
}
|
||||
|
||||
BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new WaveBackend{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &WaveBackendFactory::getFactory()
|
||||
{
|
||||
static WaveBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_WAVE_H
|
||||
#define BACKENDS_WAVE_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct WaveBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_WAVE_H */
|
||||
@@ -1,631 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 1999-2007 by authors.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "backends/winmm.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <memory.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <mmreg.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alu.h"
|
||||
#include "compat.h"
|
||||
#include "core/logging.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "strutils.h"
|
||||
#include "threads.h"
|
||||
|
||||
#ifndef WAVE_FORMAT_IEEE_FLOAT
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
#define DEVNAME_HEAD "OpenAL Soft on "
|
||||
|
||||
|
||||
al::vector<std::string> PlaybackDevices;
|
||||
al::vector<std::string> CaptureDevices;
|
||||
|
||||
bool checkName(const al::vector<std::string> &list, const std::string &name)
|
||||
{ return std::find(list.cbegin(), list.cend(), name) != list.cend(); }
|
||||
|
||||
void ProbePlaybackDevices(void)
|
||||
{
|
||||
PlaybackDevices.clear();
|
||||
|
||||
UINT numdevs{waveOutGetNumDevs()};
|
||||
PlaybackDevices.reserve(numdevs);
|
||||
for(UINT i{0};i < numdevs;++i)
|
||||
{
|
||||
std::string dname;
|
||||
|
||||
WAVEOUTCAPSW WaveCaps{};
|
||||
if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(PlaybackDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
dname = std::move(newname);
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
|
||||
}
|
||||
PlaybackDevices.emplace_back(std::move(dname));
|
||||
}
|
||||
}
|
||||
|
||||
void ProbeCaptureDevices(void)
|
||||
{
|
||||
CaptureDevices.clear();
|
||||
|
||||
UINT numdevs{waveInGetNumDevs()};
|
||||
CaptureDevices.reserve(numdevs);
|
||||
for(UINT i{0};i < numdevs;++i)
|
||||
{
|
||||
std::string dname;
|
||||
|
||||
WAVEINCAPSW WaveCaps{};
|
||||
if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR)
|
||||
{
|
||||
const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)};
|
||||
|
||||
int count{1};
|
||||
std::string newname{basename};
|
||||
while(checkName(CaptureDevices, newname))
|
||||
{
|
||||
newname = basename;
|
||||
newname += " #";
|
||||
newname += std::to_string(++count);
|
||||
}
|
||||
dname = std::move(newname);
|
||||
|
||||
TRACE("Got device \"%s\", ID %u\n", dname.c_str(), i);
|
||||
}
|
||||
CaptureDevices.emplace_back(std::move(dname));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct WinMMPlayback final : public BackendBase {
|
||||
WinMMPlayback(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~WinMMPlayback() override;
|
||||
|
||||
void CALLBACK waveOutProc(HWAVEOUT device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
|
||||
static void CALLBACK waveOutProcC(HWAVEOUT device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
|
||||
{ reinterpret_cast<WinMMPlayback*>(instance)->waveOutProc(device, msg, param1, param2); }
|
||||
|
||||
int mixerProc();
|
||||
|
||||
void open(const char *name) override;
|
||||
bool reset() override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
|
||||
std::atomic<uint> mWritable{0u};
|
||||
al::semaphore mSem;
|
||||
uint mIdx{0u};
|
||||
std::array<WAVEHDR,4> mWaveBuffer{};
|
||||
|
||||
HWAVEOUT mOutHdl{nullptr};
|
||||
|
||||
WAVEFORMATEX mFormat{};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(WinMMPlayback)
|
||||
};
|
||||
|
||||
WinMMPlayback::~WinMMPlayback()
|
||||
{
|
||||
if(mOutHdl)
|
||||
waveOutClose(mOutHdl);
|
||||
mOutHdl = nullptr;
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
|
||||
}
|
||||
|
||||
/* WinMMPlayback::waveOutProc
|
||||
*
|
||||
* Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is
|
||||
* completed and returns to the application (for more data)
|
||||
*/
|
||||
void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
|
||||
{
|
||||
if(msg != WOM_DONE) return;
|
||||
mWritable.fetch_add(1, std::memory_order_acq_rel);
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
FORCE_ALIGN int WinMMPlayback::mixerProc()
|
||||
{
|
||||
SetRTPriority();
|
||||
althrd_setname(MIXER_THREAD_NAME);
|
||||
|
||||
const size_t frame_step{mDevice->channelsFromFmt()};
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire)
|
||||
&& mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
uint todo{mWritable.load(std::memory_order_acquire)};
|
||||
if(todo < 1)
|
||||
{
|
||||
mSem.wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t widx{mIdx};
|
||||
do {
|
||||
WAVEHDR &waveHdr = mWaveBuffer[widx];
|
||||
widx = (widx+1) % mWaveBuffer.size();
|
||||
|
||||
mDevice->renderSamples(waveHdr.lpData, mDevice->UpdateSize, frame_step);
|
||||
mWritable.fetch_sub(1, std::memory_order_acq_rel);
|
||||
waveOutWrite(mOutHdl, &waveHdr, sizeof(WAVEHDR));
|
||||
} while(--todo);
|
||||
mIdx = static_cast<uint>(widx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void WinMMPlayback::open(const char *name)
|
||||
{
|
||||
if(PlaybackDevices.empty())
|
||||
ProbePlaybackDevices();
|
||||
|
||||
// Find the Device ID matching the deviceName if valid
|
||||
auto iter = name ?
|
||||
std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) :
|
||||
PlaybackDevices.cbegin();
|
||||
if(iter == PlaybackDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
|
||||
name};
|
||||
auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter));
|
||||
|
||||
retry_open:
|
||||
mFormat = WAVEFORMATEX{};
|
||||
if(mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
|
||||
mFormat.wBitsPerSample = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
mFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte)
|
||||
mFormat.wBitsPerSample = 8;
|
||||
else
|
||||
mFormat.wBitsPerSample = 16;
|
||||
}
|
||||
mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2);
|
||||
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
|
||||
mFormat.nSamplesPerSec = mDevice->Frequency;
|
||||
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
|
||||
mFormat.cbSize = 0;
|
||||
|
||||
MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat,
|
||||
reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC),
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
|
||||
if(res != MMSYSERR_NOERROR)
|
||||
{
|
||||
if(mDevice->FmtType == DevFmtFloat)
|
||||
{
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
goto retry_open;
|
||||
}
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res};
|
||||
}
|
||||
|
||||
mDevice->DeviceName = PlaybackDevices[DeviceID];
|
||||
}
|
||||
|
||||
bool WinMMPlayback::reset()
|
||||
{
|
||||
mDevice->BufferSize = static_cast<uint>(uint64_t{mDevice->BufferSize} *
|
||||
mFormat.nSamplesPerSec / mDevice->Frequency);
|
||||
mDevice->BufferSize = (mDevice->BufferSize+3) & ~0x3u;
|
||||
mDevice->UpdateSize = mDevice->BufferSize / 4;
|
||||
mDevice->Frequency = mFormat.nSamplesPerSec;
|
||||
|
||||
if(mFormat.wFormatTag == WAVE_FORMAT_IEEE_FLOAT)
|
||||
{
|
||||
if(mFormat.wBitsPerSample == 32)
|
||||
mDevice->FmtType = DevFmtFloat;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled IEEE float sample depth: %d\n", mFormat.wBitsPerSample);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if(mFormat.wFormatTag == WAVE_FORMAT_PCM)
|
||||
{
|
||||
if(mFormat.wBitsPerSample == 16)
|
||||
mDevice->FmtType = DevFmtShort;
|
||||
else if(mFormat.wBitsPerSample == 8)
|
||||
mDevice->FmtType = DevFmtUByte;
|
||||
else
|
||||
{
|
||||
ERR("Unhandled PCM sample depth: %d\n", mFormat.wBitsPerSample);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unhandled format tag: 0x%04x\n", mFormat.wFormatTag);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint chanmask{};
|
||||
if(mFormat.nChannels == 2)
|
||||
{
|
||||
mDevice->FmtChans = DevFmtStereo;
|
||||
chanmask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
}
|
||||
else if(mFormat.nChannels == 1)
|
||||
{
|
||||
mDevice->FmtChans = DevFmtMono;
|
||||
chanmask = SPEAKER_FRONT_CENTER;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERR("Unhandled channel count: %d\n", mFormat.nChannels);
|
||||
return false;
|
||||
}
|
||||
setChannelOrderFromWFXMask(chanmask);
|
||||
|
||||
uint BufferSize{mDevice->UpdateSize * mDevice->frameSizeFromFmt()};
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
mWaveBuffer[0] = WAVEHDR{};
|
||||
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
|
||||
mWaveBuffer[0].dwBufferLength = BufferSize;
|
||||
for(size_t i{1};i < mWaveBuffer.size();i++)
|
||||
{
|
||||
mWaveBuffer[i] = WAVEHDR{};
|
||||
mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
|
||||
mWaveBuffer[i].dwBufferLength = BufferSize;
|
||||
}
|
||||
mIdx = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WinMMPlayback::start()
|
||||
{
|
||||
try {
|
||||
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
|
||||
[this](WAVEHDR &waveHdr) -> void
|
||||
{ waveOutPrepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); });
|
||||
mWritable.store(static_cast<uint>(mWaveBuffer.size()), std::memory_order_release);
|
||||
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WinMMPlayback::mixerProc), this};
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start mixing thread: %s", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
void WinMMPlayback::stop()
|
||||
{
|
||||
if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
|
||||
return;
|
||||
mThread.join();
|
||||
|
||||
while(mWritable.load(std::memory_order_acquire) < mWaveBuffer.size())
|
||||
mSem.wait();
|
||||
std::for_each(mWaveBuffer.begin(), mWaveBuffer.end(),
|
||||
[this](WAVEHDR &waveHdr) -> void
|
||||
{ waveOutUnprepareHeader(mOutHdl, &waveHdr, sizeof(WAVEHDR)); });
|
||||
mWritable.store(0, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
struct WinMMCapture final : public BackendBase {
|
||||
WinMMCapture(ALCdevice *device) noexcept : BackendBase{device} { }
|
||||
~WinMMCapture() override;
|
||||
|
||||
void CALLBACK waveInProc(HWAVEIN device, UINT msg, DWORD_PTR param1, DWORD_PTR param2) noexcept;
|
||||
static void CALLBACK waveInProcC(HWAVEIN device, UINT msg, DWORD_PTR instance, DWORD_PTR param1, DWORD_PTR param2) noexcept
|
||||
{ reinterpret_cast<WinMMCapture*>(instance)->waveInProc(device, msg, param1, param2); }
|
||||
|
||||
int captureProc();
|
||||
|
||||
void open(const char *name) override;
|
||||
void start() override;
|
||||
void stop() override;
|
||||
void captureSamples(al::byte *buffer, uint samples) override;
|
||||
uint availableSamples() override;
|
||||
|
||||
std::atomic<uint> mReadable{0u};
|
||||
al::semaphore mSem;
|
||||
uint mIdx{0};
|
||||
std::array<WAVEHDR,4> mWaveBuffer{};
|
||||
|
||||
HWAVEIN mInHdl{nullptr};
|
||||
|
||||
RingBufferPtr mRing{nullptr};
|
||||
|
||||
WAVEFORMATEX mFormat{};
|
||||
|
||||
std::atomic<bool> mKillNow{true};
|
||||
std::thread mThread;
|
||||
|
||||
DEF_NEWDEL(WinMMCapture)
|
||||
};
|
||||
|
||||
WinMMCapture::~WinMMCapture()
|
||||
{
|
||||
// Close the Wave device
|
||||
if(mInHdl)
|
||||
waveInClose(mInHdl);
|
||||
mInHdl = nullptr;
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{});
|
||||
}
|
||||
|
||||
/* WinMMCapture::waveInProc
|
||||
*
|
||||
* Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is
|
||||
* completed and returns to the application (with more data).
|
||||
*/
|
||||
void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept
|
||||
{
|
||||
if(msg != WIM_DATA) return;
|
||||
mReadable.fetch_add(1, std::memory_order_acq_rel);
|
||||
mSem.post();
|
||||
}
|
||||
|
||||
int WinMMCapture::captureProc()
|
||||
{
|
||||
althrd_setname(RECORD_THREAD_NAME);
|
||||
|
||||
while(!mKillNow.load(std::memory_order_acquire) &&
|
||||
mDevice->Connected.load(std::memory_order_acquire))
|
||||
{
|
||||
uint todo{mReadable.load(std::memory_order_acquire)};
|
||||
if(todo < 1)
|
||||
{
|
||||
mSem.wait();
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t widx{mIdx};
|
||||
do {
|
||||
WAVEHDR &waveHdr = mWaveBuffer[widx];
|
||||
widx = (widx+1) % mWaveBuffer.size();
|
||||
|
||||
mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign);
|
||||
mReadable.fetch_sub(1, std::memory_order_acq_rel);
|
||||
waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR));
|
||||
} while(--todo);
|
||||
mIdx = static_cast<uint>(widx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void WinMMCapture::open(const char *name)
|
||||
{
|
||||
if(CaptureDevices.empty())
|
||||
ProbeCaptureDevices();
|
||||
|
||||
// Find the Device ID matching the deviceName if valid
|
||||
auto iter = name ?
|
||||
std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) :
|
||||
CaptureDevices.cbegin();
|
||||
if(iter == CaptureDevices.cend())
|
||||
throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
|
||||
name};
|
||||
auto DeviceID = static_cast<UINT>(std::distance(CaptureDevices.cbegin(), iter));
|
||||
|
||||
switch(mDevice->FmtChans)
|
||||
{
|
||||
case DevFmtMono:
|
||||
case DevFmtStereo:
|
||||
break;
|
||||
|
||||
case DevFmtQuad:
|
||||
case DevFmtX51:
|
||||
case DevFmtX51Rear:
|
||||
case DevFmtX61:
|
||||
case DevFmtX71:
|
||||
case DevFmtAmbi3D:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported",
|
||||
DevFmtChannelsString(mDevice->FmtChans)};
|
||||
}
|
||||
|
||||
switch(mDevice->FmtType)
|
||||
{
|
||||
case DevFmtUByte:
|
||||
case DevFmtShort:
|
||||
case DevFmtInt:
|
||||
case DevFmtFloat:
|
||||
break;
|
||||
|
||||
case DevFmtByte:
|
||||
case DevFmtUShort:
|
||||
case DevFmtUInt:
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "%s samples not supported",
|
||||
DevFmtTypeString(mDevice->FmtType)};
|
||||
}
|
||||
|
||||
mFormat = WAVEFORMATEX{};
|
||||
mFormat.wFormatTag = (mDevice->FmtType == DevFmtFloat) ?
|
||||
WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
|
||||
mFormat.nChannels = static_cast<WORD>(mDevice->channelsFromFmt());
|
||||
mFormat.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
|
||||
mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8);
|
||||
mFormat.nSamplesPerSec = mDevice->Frequency;
|
||||
mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign;
|
||||
mFormat.cbSize = 0;
|
||||
|
||||
MMRESULT res{waveInOpen(&mInHdl, DeviceID, &mFormat,
|
||||
reinterpret_cast<DWORD_PTR>(&WinMMCapture::waveInProcC),
|
||||
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)};
|
||||
if(res != MMSYSERR_NOERROR)
|
||||
throw al::backend_exception{al::backend_error::DeviceError, "waveInOpen failed: %u", res};
|
||||
|
||||
// Ensure each buffer is 50ms each
|
||||
DWORD BufferSize{mFormat.nAvgBytesPerSec / 20u};
|
||||
BufferSize -= (BufferSize % mFormat.nBlockAlign);
|
||||
|
||||
// Allocate circular memory buffer for the captured audio
|
||||
// Make sure circular buffer is at least 100ms in size
|
||||
uint CapturedDataSize{mDevice->BufferSize};
|
||||
CapturedDataSize = static_cast<uint>(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size()));
|
||||
|
||||
mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false);
|
||||
|
||||
al_free(mWaveBuffer[0].lpData);
|
||||
mWaveBuffer[0] = WAVEHDR{};
|
||||
mWaveBuffer[0].lpData = static_cast<char*>(al_calloc(16, BufferSize * mWaveBuffer.size()));
|
||||
mWaveBuffer[0].dwBufferLength = BufferSize;
|
||||
for(size_t i{1};i < mWaveBuffer.size();++i)
|
||||
{
|
||||
mWaveBuffer[i] = WAVEHDR{};
|
||||
mWaveBuffer[i].lpData = mWaveBuffer[i-1].lpData + mWaveBuffer[i-1].dwBufferLength;
|
||||
mWaveBuffer[i].dwBufferLength = mWaveBuffer[i-1].dwBufferLength;
|
||||
}
|
||||
|
||||
mDevice->DeviceName = CaptureDevices[DeviceID];
|
||||
}
|
||||
|
||||
void WinMMCapture::start()
|
||||
{
|
||||
try {
|
||||
for(size_t i{0};i < mWaveBuffer.size();++i)
|
||||
{
|
||||
waveInPrepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
|
||||
waveInAddBuffer(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
|
||||
}
|
||||
|
||||
mKillNow.store(false, std::memory_order_release);
|
||||
mThread = std::thread{std::mem_fn(&WinMMCapture::captureProc), this};
|
||||
|
||||
waveInStart(mInHdl);
|
||||
}
|
||||
catch(std::exception& e) {
|
||||
throw al::backend_exception{al::backend_error::DeviceError,
|
||||
"Failed to start recording thread: %s", e.what()};
|
||||
}
|
||||
}
|
||||
|
||||
void WinMMCapture::stop()
|
||||
{
|
||||
waveInStop(mInHdl);
|
||||
|
||||
mKillNow.store(true, std::memory_order_release);
|
||||
if(mThread.joinable())
|
||||
{
|
||||
mSem.post();
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
waveInReset(mInHdl);
|
||||
for(size_t i{0};i < mWaveBuffer.size();++i)
|
||||
waveInUnprepareHeader(mInHdl, &mWaveBuffer[i], sizeof(WAVEHDR));
|
||||
|
||||
mReadable.store(0, std::memory_order_release);
|
||||
mIdx = 0;
|
||||
}
|
||||
|
||||
void WinMMCapture::captureSamples(al::byte *buffer, uint samples)
|
||||
{ mRing->read(buffer, samples); }
|
||||
|
||||
uint WinMMCapture::availableSamples()
|
||||
{ return static_cast<uint>(mRing->readSpace()); }
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
bool WinMMBackendFactory::init()
|
||||
{ return true; }
|
||||
|
||||
bool WinMMBackendFactory::querySupport(BackendType type)
|
||||
{ return type == BackendType::Playback || type == BackendType::Capture; }
|
||||
|
||||
std::string WinMMBackendFactory::probe(BackendType type)
|
||||
{
|
||||
std::string outnames;
|
||||
auto add_device = [&outnames](const std::string &dname) -> void
|
||||
{
|
||||
/* +1 to also append the null char (to ensure a null-separated list and
|
||||
* double-null terminated list).
|
||||
*/
|
||||
if(!dname.empty())
|
||||
outnames.append(dname.c_str(), dname.length()+1);
|
||||
};
|
||||
switch(type)
|
||||
{
|
||||
case BackendType::Playback:
|
||||
ProbePlaybackDevices();
|
||||
std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device);
|
||||
break;
|
||||
|
||||
case BackendType::Capture:
|
||||
ProbeCaptureDevices();
|
||||
std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device);
|
||||
break;
|
||||
}
|
||||
return outnames;
|
||||
}
|
||||
|
||||
BackendPtr WinMMBackendFactory::createBackend(ALCdevice *device, BackendType type)
|
||||
{
|
||||
if(type == BackendType::Playback)
|
||||
return BackendPtr{new WinMMPlayback{device}};
|
||||
if(type == BackendType::Capture)
|
||||
return BackendPtr{new WinMMCapture{device}};
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BackendFactory &WinMMBackendFactory::getFactory()
|
||||
{
|
||||
static WinMMBackendFactory factory{};
|
||||
return factory;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef BACKENDS_WINMM_H
|
||||
#define BACKENDS_WINMM_H
|
||||
|
||||
#include "backends/base.h"
|
||||
|
||||
struct WinMMBackendFactory final : public BackendFactory {
|
||||
public:
|
||||
bool init() override;
|
||||
|
||||
bool querySupport(BackendType type) override;
|
||||
|
||||
std::string probe(BackendType type) override;
|
||||
|
||||
BackendPtr createBackend(ALCdevice *device, BackendType type) override;
|
||||
|
||||
static BackendFactory &getFactory();
|
||||
};
|
||||
|
||||
#endif /* BACKENDS_WINMM_H */
|
||||
@@ -1,298 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "bformatdec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alu.h"
|
||||
#include "core/ambdec.h"
|
||||
#include "core/filters/splitter.h"
|
||||
#include "front_stablizer.h"
|
||||
#include "math_defs.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale{{
|
||||
1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale2O{{
|
||||
7.45355990e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
constexpr std::array<float,MaxAmbiOrder+1> Ambi3DDecoderHFScale3O{{
|
||||
5.89792205e-01f, 8.79693856e-01f, 1.00000000e+00f, 1.00000000e+00f
|
||||
}};
|
||||
|
||||
inline auto& GetDecoderHFScales(uint order) noexcept
|
||||
{
|
||||
if(order >= 3) return Ambi3DDecoderHFScale3O;
|
||||
if(order == 2) return Ambi3DDecoderHFScale2O;
|
||||
return Ambi3DDecoderHFScale;
|
||||
}
|
||||
|
||||
inline auto& GetAmbiScales(AmbDecScale scaletype) noexcept
|
||||
{
|
||||
if(scaletype == AmbDecScale::FuMa) return AmbiScale::FromFuMa();
|
||||
if(scaletype == AmbDecScale::SN3D) return AmbiScale::FromSN3D();
|
||||
return AmbiScale::FromN3D();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
BFormatDec::BFormatDec(const AmbDecConf *conf, const bool allow_2band, const size_t inchans,
|
||||
const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
|
||||
std::unique_ptr<FrontStablizer> stablizer)
|
||||
: mStablizer{std::move(stablizer)}, mDualBand{allow_2band && (conf->FreqBands == 2)}
|
||||
, mChannelDec{inchans}
|
||||
{
|
||||
const bool periphonic{(conf->ChanMask&AmbiPeriphonicMask) != 0};
|
||||
auto&& coeff_scale = GetAmbiScales(conf->CoeffScale);
|
||||
|
||||
if(!mDualBand)
|
||||
{
|
||||
for(size_t j{0},k{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
const size_t acn{periphonic ? j : AmbiIndex::FromACN2D()[j]};
|
||||
if(!(conf->ChanMask&(1u<<acn))) continue;
|
||||
const size_t order{AmbiIndex::OrderFromChannel()[acn]};
|
||||
const float gain{conf->HFOrderGain[order] / coeff_scale[acn]};
|
||||
for(size_t i{0u};i < conf->NumSpeakers;++i)
|
||||
{
|
||||
const size_t chanidx{chanmap[i]};
|
||||
mChannelDec[j].mGains.Single[chanidx] = conf->Matrix[i][k] * gain;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mChannelDec[0].mXOver.init(conf->XOverFreq / static_cast<float>(srate));
|
||||
for(size_t j{1};j < mChannelDec.size();++j)
|
||||
mChannelDec[j].mXOver = mChannelDec[0].mXOver;
|
||||
|
||||
const float ratio{std::pow(10.0f, conf->XOverRatio / 40.0f)};
|
||||
for(size_t j{0},k{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
const size_t acn{periphonic ? j : AmbiIndex::FromACN2D()[j]};
|
||||
if(!(conf->ChanMask&(1u<<acn))) continue;
|
||||
const size_t order{AmbiIndex::OrderFromChannel()[acn]};
|
||||
const float hfGain{conf->HFOrderGain[order] * ratio / coeff_scale[acn]};
|
||||
const float lfGain{conf->LFOrderGain[order] / ratio / coeff_scale[acn]};
|
||||
for(size_t i{0u};i < conf->NumSpeakers;++i)
|
||||
{
|
||||
const size_t chanidx{chanmap[i]};
|
||||
mChannelDec[j].mGains.Dual[sHFBand][chanidx] = conf->HFMatrix[i][k] * hfGain;
|
||||
mChannelDec[j].mGains.Dual[sLFBand][chanidx] = conf->LFMatrix[i][k] * lfGain;
|
||||
}
|
||||
++k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BFormatDec::BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
|
||||
const al::span<const ChannelDec> coeffslf, std::unique_ptr<FrontStablizer> stablizer)
|
||||
: mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans}
|
||||
{
|
||||
if(!mDualBand)
|
||||
{
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Single};
|
||||
for(const ChannelDec &incoeffs : coeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(size_t j{0};j < mChannelDec.size();++j)
|
||||
{
|
||||
float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]};
|
||||
for(const ChannelDec &incoeffs : coeffs)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
|
||||
outcoeffs = mChannelDec[j].mGains.Dual[sLFBand];
|
||||
for(const ChannelDec &incoeffs : coeffslf)
|
||||
*(outcoeffs++) = incoeffs[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BFormatDec::process(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
if(mDualBand)
|
||||
{
|
||||
const al::span<float> hfSamples{mSamples[sHFBand].data(), SamplesToDo};
|
||||
const al::span<float> lfSamples{mSamples[sLFBand].data(), SamplesToDo};
|
||||
for(auto &chandec : mChannelDec)
|
||||
{
|
||||
chandec.mXOver.process({InSamples->data(), SamplesToDo}, hfSamples.data(),
|
||||
lfSamples.data());
|
||||
MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand],
|
||||
chandec.mGains.Dual[sHFBand], 0, 0);
|
||||
MixSamples(lfSamples, OutBuffer, chandec.mGains.Dual[sLFBand],
|
||||
chandec.mGains.Dual[sLFBand], 0, 0);
|
||||
++InSamples;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto &chandec : mChannelDec)
|
||||
{
|
||||
MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single,
|
||||
chandec.mGains.Single, 0, 0);
|
||||
++InSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BFormatDec::processStablize(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
|
||||
const size_t SamplesToDo)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
/* Move the existing direct L/R signal out so it doesn't get processed by
|
||||
* the stablizer. Add a delay to it so it stays aligned with the stablizer
|
||||
* delay.
|
||||
*/
|
||||
float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())};
|
||||
float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())};
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
{
|
||||
mid[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
side[FrontStablizer::DelayLength+i] = OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
}
|
||||
std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f);
|
||||
std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f);
|
||||
|
||||
/* Decode the B-Format input to OutBuffer. */
|
||||
process(OutBuffer, InSamples, SamplesToDo);
|
||||
|
||||
/* Apply a delay to all channels, except the front-left and front-right, so
|
||||
* they maintain correct timing.
|
||||
*/
|
||||
const size_t NumChannels{OutBuffer.size()};
|
||||
for(size_t i{0u};i < NumChannels;i++)
|
||||
{
|
||||
if(i == lidx || i == ridx)
|
||||
continue;
|
||||
|
||||
auto &DelayBuf = mStablizer->DelayBuf[i];
|
||||
auto buffer_end = OutBuffer[i].begin() + SamplesToDo;
|
||||
if LIKELY(SamplesToDo >= FrontStablizer::DelayLength)
|
||||
{
|
||||
auto delay_end = std::rotate(OutBuffer[i].begin(),
|
||||
buffer_end - FrontStablizer::DelayLength, buffer_end);
|
||||
std::swap_ranges(OutBuffer[i].begin(), delay_end, DelayBuf.begin());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto delay_start = std::swap_ranges(OutBuffer[i].begin(), buffer_end,
|
||||
DelayBuf.begin());
|
||||
std::rotate(DelayBuf.begin(), delay_start, DelayBuf.end());
|
||||
}
|
||||
}
|
||||
|
||||
/* Include the side signal for what was just decoded. */
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
side[FrontStablizer::DelayLength+i] += OutBuffer[lidx][i] - OutBuffer[ridx][i];
|
||||
|
||||
/* Combine the delayed mid signal with the decoded mid signal. Note that
|
||||
* the samples are stored and combined in reverse, so the newest samples
|
||||
* are at the front and the oldest at the back.
|
||||
*/
|
||||
al::span<float> tmpbuf{mStablizer->TempBuf.data(), SamplesToDo+FrontStablizer::DelayLength};
|
||||
auto tmpiter = tmpbuf.begin() + SamplesToDo;
|
||||
std::copy(mStablizer->MidDelay.cbegin(), mStablizer->MidDelay.cend(), tmpiter);
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
*--tmpiter = OutBuffer[lidx][i] + OutBuffer[ridx][i];
|
||||
/* Save the newest samples for next time. */
|
||||
std::copy_n(tmpbuf.cbegin(), mStablizer->MidDelay.size(), mStablizer->MidDelay.begin());
|
||||
|
||||
/* Apply an all-pass on the reversed signal, then reverse the samples to
|
||||
* get the forward signal with a reversed phase shift. The future samples
|
||||
* are included with the all-pass to reduce the error in the output
|
||||
* samples (the smaller the delay, the more error is introduced).
|
||||
*/
|
||||
mStablizer->MidFilter.applyAllpass(tmpbuf);
|
||||
tmpbuf = tmpbuf.subspan<FrontStablizer::DelayLength>();
|
||||
std::reverse(tmpbuf.begin(), tmpbuf.end());
|
||||
|
||||
/* Now apply the band-splitter, combining its phase shift with the reversed
|
||||
* phase shift, restoring the original phase on the split signal.
|
||||
*/
|
||||
mStablizer->MidFilter.process(tmpbuf, mStablizer->MidHF.data(), mStablizer->MidLF.data());
|
||||
|
||||
/* This pans the separate low- and high-frequency signals between being on
|
||||
* the center channel and the left+right channels. The low-frequency signal
|
||||
* is panned 1/3rd toward center and the high-frequency signal is panned
|
||||
* 1/4th toward center. These values can be tweaked.
|
||||
*/
|
||||
const float cos_lf{std::cos(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f))};
|
||||
const float cos_hf{std::cos(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
|
||||
const float sin_lf{std::sin(1.0f/3.0f * (al::MathDefs<float>::Pi()*0.5f))};
|
||||
const float sin_hf{std::sin(1.0f/4.0f * (al::MathDefs<float>::Pi()*0.5f))};
|
||||
for(size_t i{0};i < SamplesToDo;i++)
|
||||
{
|
||||
const float m{mStablizer->MidLF[i]*cos_lf + mStablizer->MidHF[i]*cos_hf + mid[i]};
|
||||
const float c{mStablizer->MidLF[i]*sin_lf + mStablizer->MidHF[i]*sin_hf};
|
||||
const float s{side[i]};
|
||||
|
||||
/* The generated center channel signal adds to the existing signal,
|
||||
* while the modified left and right channels replace.
|
||||
*/
|
||||
OutBuffer[lidx][i] = (m + s) * 0.5f;
|
||||
OutBuffer[ridx][i] = (m - s) * 0.5f;
|
||||
OutBuffer[cidx][i] += c * 0.5f;
|
||||
}
|
||||
/* Move the delayed mid/side samples to the front for next time. */
|
||||
auto mid_end = mStablizer->MidDirect.cbegin() + SamplesToDo;
|
||||
std::copy(mid_end, mid_end+FrontStablizer::DelayLength, mStablizer->MidDirect.begin());
|
||||
auto side_end = mStablizer->Side.cbegin() + SamplesToDo;
|
||||
std::copy(side_end, side_end+FrontStablizer::DelayLength, mStablizer->Side.begin());
|
||||
}
|
||||
|
||||
|
||||
auto BFormatDec::GetHFOrderScales(const uint in_order, const uint out_order) noexcept
|
||||
-> std::array<float,MaxAmbiOrder+1>
|
||||
{
|
||||
std::array<float,MaxAmbiOrder+1> ret{};
|
||||
|
||||
assert(out_order >= in_order);
|
||||
|
||||
const auto &target = GetDecoderHFScales(out_order);
|
||||
const auto &input = GetDecoderHFScales(in_order);
|
||||
|
||||
for(size_t i{0};i < in_order+1;++i)
|
||||
ret[i] = input[i] / target[i];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<BFormatDec> BFormatDec::Create(const AmbDecConf *conf, const bool allow_2band,
|
||||
const size_t inchans, const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
|
||||
std::unique_ptr<FrontStablizer> stablizer)
|
||||
{
|
||||
return std::unique_ptr<BFormatDec>{new(FamCount(inchans))
|
||||
BFormatDec{conf, allow_2band, inchans, srate, chanmap, std::move(stablizer)}};
|
||||
}
|
||||
std::unique_ptr<BFormatDec> BFormatDec::Create(const size_t inchans,
|
||||
const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
|
||||
std::unique_ptr<FrontStablizer> stablizer)
|
||||
{
|
||||
return std::unique_ptr<BFormatDec>{new(FamCount(inchans))
|
||||
BFormatDec{inchans, coeffs, coeffslf, std::move(stablizer)}};
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
#ifndef BFORMATDEC_H
|
||||
#define BFORMATDEC_H
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/filters/splitter.h"
|
||||
|
||||
struct AmbDecConf;
|
||||
struct FrontStablizer;
|
||||
|
||||
|
||||
using ChannelDec = std::array<float,MaxAmbiChannels>;
|
||||
|
||||
class BFormatDec {
|
||||
static constexpr size_t sHFBand{0};
|
||||
static constexpr size_t sLFBand{1};
|
||||
static constexpr size_t sNumBands{2};
|
||||
|
||||
struct ChannelDecoder {
|
||||
union MatrixU {
|
||||
float Dual[sNumBands][MAX_OUTPUT_CHANNELS];
|
||||
float Single[MAX_OUTPUT_CHANNELS];
|
||||
} mGains{};
|
||||
|
||||
/* NOTE: BandSplitter filter is unused with single-band decoding. */
|
||||
BandSplitter mXOver;
|
||||
};
|
||||
|
||||
alignas(16) std::array<FloatBufferLine,2> mSamples;
|
||||
|
||||
const std::unique_ptr<FrontStablizer> mStablizer;
|
||||
const bool mDualBand{false};
|
||||
|
||||
al::FlexArray<ChannelDecoder> mChannelDec;
|
||||
|
||||
public:
|
||||
BFormatDec(const AmbDecConf *conf, const bool allow_2band, const size_t inchans,
|
||||
const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
|
||||
std::unique_ptr<FrontStablizer> stablizer);
|
||||
BFormatDec(const size_t inchans, const al::span<const ChannelDec> coeffs,
|
||||
const al::span<const ChannelDec> coeffslf, std::unique_ptr<FrontStablizer> stablizer);
|
||||
|
||||
bool hasStablizer() const noexcept { return mStablizer != nullptr; };
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels. */
|
||||
void process(const al::span<FloatBufferLine> OutBuffer, const FloatBufferLine *InSamples,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
/* Decodes the ambisonic input to the given output channels with stablization. */
|
||||
void processStablize(const al::span<FloatBufferLine> OutBuffer,
|
||||
const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx,
|
||||
const size_t SamplesToDo);
|
||||
|
||||
/* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */
|
||||
static std::array<float,MaxAmbiOrder+1> GetHFOrderScales(const uint in_order,
|
||||
const uint out_order) noexcept;
|
||||
|
||||
static std::unique_ptr<BFormatDec> Create(const AmbDecConf *conf, const bool allow_2band,
|
||||
const size_t inchans, const uint srate, const uint (&chanmap)[MAX_OUTPUT_CHANNELS],
|
||||
std::unique_ptr<FrontStablizer> stablizer);
|
||||
static std::unique_ptr<BFormatDec> Create(const size_t inchans,
|
||||
const al::span<const ChannelDec> coeffs, const al::span<const ChannelDec> coeffslf,
|
||||
std::unique_ptr<FrontStablizer> stablizer);
|
||||
|
||||
DEF_FAM_NEWDEL(BFormatDec, mChannelDec)
|
||||
};
|
||||
|
||||
#endif /* BFORMATDEC_H */
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "buffer_storage.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept
|
||||
{
|
||||
switch(type)
|
||||
{
|
||||
case FmtUByte: return sizeof(uint8_t);
|
||||
case FmtShort: return sizeof(int16_t);
|
||||
case FmtFloat: return sizeof(float);
|
||||
case FmtDouble: return sizeof(double);
|
||||
case FmtMulaw: return sizeof(uint8_t);
|
||||
case FmtAlaw: return sizeof(uint8_t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept
|
||||
{
|
||||
switch(chans)
|
||||
{
|
||||
case FmtMono: return 1;
|
||||
case FmtStereo: return 2;
|
||||
case FmtRear: return 2;
|
||||
case FmtQuad: return 4;
|
||||
case FmtX51: return 6;
|
||||
case FmtX61: return 7;
|
||||
case FmtX71: return 8;
|
||||
case FmtBFormat2D: return (ambiorder*2) + 1;
|
||||
case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#ifndef ALC_BUFFER_STORAGE_H
|
||||
#define ALC_BUFFER_STORAGE_H
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
/* Storable formats */
|
||||
enum FmtType : unsigned char {
|
||||
FmtUByte,
|
||||
FmtShort,
|
||||
FmtFloat,
|
||||
FmtDouble,
|
||||
FmtMulaw,
|
||||
FmtAlaw,
|
||||
};
|
||||
enum FmtChannels : unsigned char {
|
||||
FmtMono,
|
||||
FmtStereo,
|
||||
FmtRear,
|
||||
FmtQuad,
|
||||
FmtX51, /* (WFX order) */
|
||||
FmtX61, /* (WFX order) */
|
||||
FmtX71, /* (WFX order) */
|
||||
FmtBFormat2D,
|
||||
FmtBFormat3D,
|
||||
};
|
||||
|
||||
enum class AmbiLayout : unsigned char {
|
||||
FuMa,
|
||||
ACN,
|
||||
};
|
||||
enum class AmbiScaling : unsigned char {
|
||||
FuMa,
|
||||
SN3D,
|
||||
N3D,
|
||||
};
|
||||
|
||||
uint BytesFromFmt(FmtType type) noexcept;
|
||||
uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept;
|
||||
inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept
|
||||
{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); }
|
||||
|
||||
|
||||
using CallbackType = int(*)(void*, void*, int);
|
||||
|
||||
struct BufferStorage {
|
||||
al::vector<al::byte,16> mData;
|
||||
|
||||
CallbackType mCallback{nullptr};
|
||||
void *mUserData{nullptr};
|
||||
|
||||
uint mSampleRate{0u};
|
||||
FmtChannels mChannels{};
|
||||
FmtType mType{};
|
||||
uint mSampleLen{0u};
|
||||
|
||||
AmbiLayout mAmbiLayout{AmbiLayout::FuMa};
|
||||
AmbiScaling mAmbiScaling{AmbiScaling::FuMa};
|
||||
uint mAmbiOrder{0u};
|
||||
|
||||
uint mLoopStart{0u};
|
||||
uint mLoopEnd{0u};
|
||||
|
||||
inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); }
|
||||
inline uint channelsFromFmt() const noexcept
|
||||
{ return ChannelsFromFmt(mChannels, mAmbiOrder); }
|
||||
inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); }
|
||||
|
||||
inline bool isBFormat() const noexcept
|
||||
{ return mChannels == FmtBFormat2D || mChannels == FmtBFormat3D; }
|
||||
};
|
||||
|
||||
|
||||
struct BufferlistItem {
|
||||
std::atomic<BufferlistItem*> mNext{nullptr};
|
||||
uint mSampleLen{0u};
|
||||
BufferStorage *mBuffer{nullptr};
|
||||
|
||||
DEF_NEWDEL(BufferlistItem)
|
||||
};
|
||||
|
||||
#endif /* ALC_BUFFER_STORAGE_H */
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef AL_COMPAT_H
|
||||
#define AL_COMPAT_H
|
||||
|
||||
#include <string>
|
||||
|
||||
struct PathNamePair { std::string path, fname; };
|
||||
const PathNamePair &GetProcBinary(void);
|
||||
|
||||
#endif /* AL_COMPAT_H */
|
||||
@@ -1,370 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "converter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits.h>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alnumeric.h"
|
||||
#include "core/fpu_ctrl.h"
|
||||
|
||||
struct CTag;
|
||||
struct CopyTag;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint MaxPitch{10};
|
||||
|
||||
static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!");
|
||||
static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize,
|
||||
"MaxPitch and/or BufferLineSize are too large for MixerFracBits!");
|
||||
|
||||
/* Base template left undefined. Should be marked =delete, but Clang 3.8.1
|
||||
* chokes on that given the inline specializations.
|
||||
*/
|
||||
template<DevFmtType T>
|
||||
inline float LoadSample(DevFmtType_t<T> val) noexcept;
|
||||
|
||||
template<> inline float LoadSample<DevFmtByte>(DevFmtType_t<DevFmtByte> val) noexcept
|
||||
{ return val * (1.0f/128.0f); }
|
||||
template<> inline float LoadSample<DevFmtShort>(DevFmtType_t<DevFmtShort> val) noexcept
|
||||
{ return val * (1.0f/32768.0f); }
|
||||
template<> inline float LoadSample<DevFmtInt>(DevFmtType_t<DevFmtInt> val) noexcept
|
||||
{ return static_cast<float>(val) * (1.0f/2147483648.0f); }
|
||||
template<> inline float LoadSample<DevFmtFloat>(DevFmtType_t<DevFmtFloat> val) noexcept
|
||||
{ return val; }
|
||||
|
||||
template<> inline float LoadSample<DevFmtUByte>(DevFmtType_t<DevFmtUByte> val) noexcept
|
||||
{ return LoadSample<DevFmtByte>(static_cast<int8_t>(val - 128)); }
|
||||
template<> inline float LoadSample<DevFmtUShort>(DevFmtType_t<DevFmtUShort> val) noexcept
|
||||
{ return LoadSample<DevFmtShort>(static_cast<int16_t>(val - 32768)); }
|
||||
template<> inline float LoadSample<DevFmtUInt>(DevFmtType_t<DevFmtUInt> val) noexcept
|
||||
{ return LoadSample<DevFmtInt>(static_cast<int32_t>(val - 2147483648u)); }
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
inline void LoadSampleArray(float *RESTRICT dst, const void *src, const size_t srcstep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
dst[i] = LoadSample<T>(ssrc[i*srcstep]);
|
||||
}
|
||||
|
||||
void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: LoadSampleArray<T>(dst, src, srcstep, samples); break
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
inline DevFmtType_t<T> StoreSample(float) noexcept;
|
||||
|
||||
template<> inline float StoreSample<DevFmtFloat>(float val) noexcept
|
||||
{ return val; }
|
||||
template<> inline int32_t StoreSample<DevFmtInt>(float val) noexcept
|
||||
{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); }
|
||||
template<> inline int16_t StoreSample<DevFmtShort>(float val) noexcept
|
||||
{ return static_cast<int16_t>(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); }
|
||||
template<> inline int8_t StoreSample<DevFmtByte>(float val) noexcept
|
||||
{ return static_cast<int8_t>(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); }
|
||||
|
||||
/* Define unsigned output variations. */
|
||||
template<> inline uint32_t StoreSample<DevFmtUInt>(float val) noexcept
|
||||
{ return static_cast<uint32_t>(StoreSample<DevFmtInt>(val)) + 2147483648u; }
|
||||
template<> inline uint16_t StoreSample<DevFmtUShort>(float val) noexcept
|
||||
{ return static_cast<uint16_t>(StoreSample<DevFmtShort>(val) + 32768); }
|
||||
template<> inline uint8_t StoreSample<DevFmtUByte>(float val) noexcept
|
||||
{ return static_cast<uint8_t>(StoreSample<DevFmtByte>(val) + 128); }
|
||||
|
||||
template<DevFmtType T>
|
||||
inline void StoreSampleArray(void *dst, const float *RESTRICT src, const size_t dststep,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
DevFmtType_t<T> *sdst = static_cast<DevFmtType_t<T>*>(dst);
|
||||
for(size_t i{0u};i < samples;i++)
|
||||
sdst[i*dststep] = StoreSample<T>(src[i]);
|
||||
}
|
||||
|
||||
|
||||
void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) \
|
||||
case T: StoreSampleArray<T>(dst, src, dststep, samples); break
|
||||
switch(dsttype)
|
||||
{
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
template<DevFmtType T>
|
||||
void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i*2 + 1] = dst[i*2 + 0] = LoadSample<T>(ssrc[i]) * 0.707106781187f;
|
||||
}
|
||||
|
||||
template<DevFmtType T>
|
||||
void Multi2Mono(uint chanmask, const size_t step, const float scale, float *RESTRICT dst,
|
||||
const void *src, const size_t frames) noexcept
|
||||
{
|
||||
const DevFmtType_t<T> *ssrc = static_cast<const DevFmtType_t<T>*>(src);
|
||||
std::fill_n(dst, frames, 0.0f);
|
||||
for(size_t c{0};chanmask;++c)
|
||||
{
|
||||
if LIKELY((chanmask&1))
|
||||
{
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] += LoadSample<T>(ssrc[i*step + c]);
|
||||
}
|
||||
chanmask >>= 1;
|
||||
}
|
||||
for(size_t i{0u};i < frames;i++)
|
||||
dst[i] *= scale;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
uint srcRate, uint dstRate, Resampler resampler)
|
||||
{
|
||||
if(numchans < 1 || srcRate < 1 || dstRate < 1)
|
||||
return nullptr;
|
||||
|
||||
SampleConverterPtr converter{new(FamCount(numchans)) SampleConverter{numchans}};
|
||||
converter->mSrcType = srcType;
|
||||
converter->mDstType = dstType;
|
||||
converter->mSrcTypeSize = BytesFromDevFmt(srcType);
|
||||
converter->mDstTypeSize = BytesFromDevFmt(dstType);
|
||||
|
||||
converter->mSrcPrepCount = 0;
|
||||
converter->mFracOffset = 0;
|
||||
|
||||
/* Have to set the mixer FPU mode since that's what the resampler code expects. */
|
||||
FPUCtl mixer_mode{};
|
||||
auto step = static_cast<uint>(
|
||||
mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne));
|
||||
converter->mIncrement = maxu(step, 1);
|
||||
if(converter->mIncrement == MixerFracOne)
|
||||
converter->mResample = Resample_<CopyTag,CTag>;
|
||||
else
|
||||
converter->mResample = PrepareResampler(resampler, converter->mIncrement,
|
||||
&converter->mState);
|
||||
|
||||
return converter;
|
||||
}
|
||||
|
||||
uint SampleConverter::availableOut(uint srcframes) const
|
||||
{
|
||||
int prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<uint>(-prepcount) >= srcframes)
|
||||
return 0;
|
||||
srcframes -= static_cast<uint>(-prepcount);
|
||||
prepcount = 0;
|
||||
}
|
||||
|
||||
if(srcframes < 1)
|
||||
{
|
||||
/* No output samples if there's no input samples. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(prepcount < MaxResamplerPadding
|
||||
&& static_cast<uint>(MaxResamplerPadding - prepcount) >= srcframes)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += srcframes;
|
||||
DataSize64 -= MaxResamplerPadding;
|
||||
DataSize64 <<= MixerFracBits;
|
||||
DataSize64 -= mFracOffset;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
return static_cast<uint>(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1,
|
||||
std::numeric_limits<int>::max()));
|
||||
}
|
||||
|
||||
uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes)
|
||||
{
|
||||
const uint SrcFrameSize{static_cast<uint>(mChan.size()) * mSrcTypeSize};
|
||||
const uint DstFrameSize{static_cast<uint>(mChan.size()) * mDstTypeSize};
|
||||
const uint increment{mIncrement};
|
||||
auto SamplesIn = static_cast<const al::byte*>(*src);
|
||||
uint NumSrcSamples{*srcframes};
|
||||
|
||||
FPUCtl mixer_mode{};
|
||||
uint pos{0};
|
||||
while(pos < dstframes && NumSrcSamples > 0)
|
||||
{
|
||||
int prepcount{mSrcPrepCount};
|
||||
if(prepcount < 0)
|
||||
{
|
||||
/* Negative prepcount means we need to skip that many input samples. */
|
||||
if(static_cast<uint>(-prepcount) >= NumSrcSamples)
|
||||
{
|
||||
mSrcPrepCount = static_cast<int>(NumSrcSamples) + prepcount;
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
SamplesIn += SrcFrameSize*static_cast<uint>(-prepcount);
|
||||
NumSrcSamples -= static_cast<uint>(-prepcount);
|
||||
mSrcPrepCount = 0;
|
||||
continue;
|
||||
}
|
||||
const uint toread{minu(NumSrcSamples, BufferLineSize - MaxResamplerPadding)};
|
||||
|
||||
if(prepcount < MaxResamplerPadding
|
||||
&& static_cast<uint>(MaxResamplerPadding - prepcount) >= toread)
|
||||
{
|
||||
/* Not enough input samples to generate an output sample. Store
|
||||
* what we're given for later.
|
||||
*/
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan,
|
||||
mChan.size(), mSrcType, toread);
|
||||
|
||||
mSrcPrepCount = prepcount + static_cast<int>(toread);
|
||||
NumSrcSamples = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
float *RESTRICT SrcData{mSrcSamples};
|
||||
float *RESTRICT DstData{mDstSamples};
|
||||
uint DataPosFrac{mFracOffset};
|
||||
auto DataSize64 = static_cast<uint64_t>(prepcount);
|
||||
DataSize64 += toread;
|
||||
DataSize64 -= MaxResamplerPadding;
|
||||
DataSize64 <<= MixerFracBits;
|
||||
DataSize64 -= DataPosFrac;
|
||||
|
||||
/* If we have a full prep, we can generate at least one sample. */
|
||||
auto DstSize = static_cast<uint>(
|
||||
clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize));
|
||||
DstSize = minu(DstSize, dstframes-pos);
|
||||
|
||||
for(size_t chan{0u};chan < mChan.size();chan++)
|
||||
{
|
||||
const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan};
|
||||
al::byte *DstSamples = static_cast<al::byte*>(dst) + mDstTypeSize*chan;
|
||||
|
||||
/* Load the previous samples into the source data first, then the
|
||||
* new samples from the input buffer.
|
||||
*/
|
||||
std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData);
|
||||
LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, toread);
|
||||
|
||||
/* Store as many prep samples for next time as possible, given the
|
||||
* number of output samples being generated.
|
||||
*/
|
||||
uint SrcDataEnd{(DstSize*increment + DataPosFrac)>>MixerFracBits};
|
||||
if(SrcDataEnd >= static_cast<uint>(prepcount)+toread)
|
||||
std::fill(std::begin(mChan[chan].PrevSamples),
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
else
|
||||
{
|
||||
const size_t len{minz(al::size(mChan[chan].PrevSamples),
|
||||
static_cast<uint>(prepcount)+toread-SrcDataEnd)};
|
||||
std::copy_n(SrcData+SrcDataEnd, len, mChan[chan].PrevSamples);
|
||||
std::fill(std::begin(mChan[chan].PrevSamples)+len,
|
||||
std::end(mChan[chan].PrevSamples), 0.0f);
|
||||
}
|
||||
|
||||
/* Now resample, and store the result in the output buffer. */
|
||||
const float *ResampledData{mResample(&mState, SrcData+(MaxResamplerPadding>>1),
|
||||
DataPosFrac, increment, {DstData, DstSize})};
|
||||
|
||||
StoreSamples(DstSamples, ResampledData, mChan.size(), mDstType, DstSize);
|
||||
}
|
||||
|
||||
/* Update the number of prep samples still available, as well as the
|
||||
* fractional offset.
|
||||
*/
|
||||
DataPosFrac += increment*DstSize;
|
||||
mSrcPrepCount = mini(prepcount + static_cast<int>(toread - (DataPosFrac>>MixerFracBits)),
|
||||
MaxResamplerPadding);
|
||||
mFracOffset = DataPosFrac & MixerFracMask;
|
||||
|
||||
/* Update the src and dst pointers in case there's still more to do. */
|
||||
SamplesIn += SrcFrameSize*(DataPosFrac>>MixerFracBits);
|
||||
NumSrcSamples -= minu(NumSrcSamples, (DataPosFrac>>MixerFracBits));
|
||||
|
||||
dst = static_cast<al::byte*>(dst) + DstFrameSize*DstSize;
|
||||
pos += DstSize;
|
||||
}
|
||||
|
||||
*src = SamplesIn;
|
||||
*srcframes = NumSrcSamples;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
void ChannelConverter::convert(const void *src, float *dst, uint frames) const
|
||||
{
|
||||
if(mDstChans == DevFmtMono)
|
||||
{
|
||||
const float scale{std::sqrt(1.0f / static_cast<float>(PopCount(mChanMask)))};
|
||||
switch(mSrcType)
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: Multi2Mono<T>(mChanMask, mSrcStep, scale, dst, src, frames); break
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
}
|
||||
else if(mChanMask == 0x1 && mDstChans == DevFmtStereo)
|
||||
{
|
||||
switch(mSrcType)
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: Mono2Stereo<T>(dst, src, frames); break
|
||||
HANDLE_FMT(DevFmtByte);
|
||||
HANDLE_FMT(DevFmtUByte);
|
||||
HANDLE_FMT(DevFmtShort);
|
||||
HANDLE_FMT(DevFmtUShort);
|
||||
HANDLE_FMT(DevFmtInt);
|
||||
HANDLE_FMT(DevFmtUInt);
|
||||
HANDLE_FMT(DevFmtFloat);
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
#ifndef CONVERTER_H
|
||||
#define CONVERTER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/mixer/defs.h"
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
||||
struct SampleConverter {
|
||||
DevFmtType mSrcType{};
|
||||
DevFmtType mDstType{};
|
||||
uint mSrcTypeSize{};
|
||||
uint mDstTypeSize{};
|
||||
|
||||
int mSrcPrepCount{};
|
||||
|
||||
uint mFracOffset{};
|
||||
uint mIncrement{};
|
||||
InterpState mState{};
|
||||
ResamplerFunc mResample{};
|
||||
|
||||
alignas(16) float mSrcSamples[BufferLineSize]{};
|
||||
alignas(16) float mDstSamples[BufferLineSize]{};
|
||||
|
||||
struct ChanSamples {
|
||||
alignas(16) float PrevSamples[MaxResamplerPadding];
|
||||
};
|
||||
al::FlexArray<ChanSamples> mChan;
|
||||
|
||||
SampleConverter(size_t numchans) : mChan{numchans} { }
|
||||
|
||||
uint convert(const void **src, uint *srcframes, void *dst, uint dstframes);
|
||||
uint availableOut(uint srcframes) const;
|
||||
|
||||
DEF_FAM_NEWDEL(SampleConverter, mChan)
|
||||
};
|
||||
using SampleConverterPtr = std::unique_ptr<SampleConverter>;
|
||||
|
||||
SampleConverterPtr CreateSampleConverter(DevFmtType srcType, DevFmtType dstType, size_t numchans,
|
||||
uint srcRate, uint dstRate, Resampler resampler);
|
||||
|
||||
|
||||
struct ChannelConverter {
|
||||
DevFmtType mSrcType{};
|
||||
uint mSrcStep{};
|
||||
uint mChanMask{};
|
||||
DevFmtChannels mDstChans{};
|
||||
|
||||
bool is_active() const noexcept { return mChanMask != 0; }
|
||||
|
||||
void convert(const void *src, float *dst, uint frames) const;
|
||||
};
|
||||
|
||||
#endif /* CONVERTER_H */
|
||||
@@ -1,212 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "effectslot.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float GainScale{31621.0f};
|
||||
constexpr float MinFreq{20.0f};
|
||||
constexpr float MaxFreq{2500.0f};
|
||||
constexpr float QFactor{5.0f};
|
||||
|
||||
struct AutowahState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
float mAttackRate;
|
||||
float mReleaseRate;
|
||||
float mResonanceGain;
|
||||
float mPeakGain;
|
||||
float mFreqMinNorm;
|
||||
float mBandwidthNorm;
|
||||
float mEnvDelay;
|
||||
|
||||
/* Filter components derived from the envelope. */
|
||||
struct {
|
||||
float cos_w0;
|
||||
float alpha;
|
||||
} mEnv[BufferLineSize];
|
||||
|
||||
struct {
|
||||
/* Effect filters' history. */
|
||||
struct {
|
||||
float z1, z2;
|
||||
} Filter;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
float CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
float TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
/* Effects buffers */
|
||||
alignas(16) float mBufferOut[BufferLineSize];
|
||||
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(AutowahState)
|
||||
};
|
||||
|
||||
void AutowahState::deviceUpdate(const ALCdevice*, const BufferStorage* /*buffer*/)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
|
||||
mAttackRate = 1.0f;
|
||||
mReleaseRate = 1.0f;
|
||||
mResonanceGain = 10.0f;
|
||||
mPeakGain = 4.5f;
|
||||
mFreqMinNorm = 4.5e-4f;
|
||||
mBandwidthNorm = 0.05f;
|
||||
mEnvDelay = 0.0f;
|
||||
|
||||
for(auto &e : mEnv)
|
||||
{
|
||||
e.cos_w0 = 0.0f;
|
||||
e.alpha = 0.0f;
|
||||
}
|
||||
|
||||
for(auto &chan : mChans)
|
||||
{
|
||||
std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
|
||||
chan.Filter.z1 = 0.0f;
|
||||
chan.Filter.z2 = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void AutowahState::update(const ALCcontext *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
const auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
|
||||
|
||||
mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
|
||||
mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
|
||||
/* 0-20dB Resonance Peak gain */
|
||||
mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
|
||||
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale);
|
||||
mFreqMinNorm = MinFreq / frequency;
|
||||
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
|
||||
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
|
||||
}
|
||||
|
||||
void AutowahState::process(const size_t samplesToDo,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const float attack_rate{mAttackRate};
|
||||
const float release_rate{mReleaseRate};
|
||||
const float res_gain{mResonanceGain};
|
||||
const float peak_gain{mPeakGain};
|
||||
const float freq_min{mFreqMinNorm};
|
||||
const float bandwidth{mBandwidthNorm};
|
||||
|
||||
float env_delay{mEnvDelay};
|
||||
for(size_t i{0u};i < samplesToDo;i++)
|
||||
{
|
||||
float w0, sample, a;
|
||||
|
||||
/* Envelope follower described on the book: Audio Effects, Theory,
|
||||
* Implementation and Application.
|
||||
*/
|
||||
sample = peak_gain * std::fabs(samplesIn[0][i]);
|
||||
a = (sample > env_delay) ? attack_rate : release_rate;
|
||||
env_delay = lerp(sample, env_delay, a);
|
||||
|
||||
/* Calculate the cos and alpha components for this sample's filter. */
|
||||
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * al::MathDefs<float>::Tau();
|
||||
mEnv[i].cos_w0 = std::cos(w0);
|
||||
mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
|
||||
}
|
||||
mEnvDelay = env_delay;
|
||||
|
||||
auto chandata = std::addressof(mChans[0]);
|
||||
for(const auto &insamples : samplesIn)
|
||||
{
|
||||
/* This effectively inlines BiquadFilter_setParams for a peaking
|
||||
* filter and BiquadFilter_processC. The alpha and cosine components
|
||||
* for the filter coefficients were previously calculated with the
|
||||
* envelope. Because the filter changes for each sample, the
|
||||
* coefficients are transient and don't need to be held.
|
||||
*/
|
||||
float z1{chandata->Filter.z1};
|
||||
float z2{chandata->Filter.z2};
|
||||
|
||||
for(size_t i{0u};i < samplesToDo;i++)
|
||||
{
|
||||
const float alpha{mEnv[i].alpha};
|
||||
const float cos_w0{mEnv[i].cos_w0};
|
||||
float input, output;
|
||||
float a[3], b[3];
|
||||
|
||||
b[0] = 1.0f + alpha*res_gain;
|
||||
b[1] = -2.0f * cos_w0;
|
||||
b[2] = 1.0f - alpha*res_gain;
|
||||
a[0] = 1.0f + alpha/res_gain;
|
||||
a[1] = -2.0f * cos_w0;
|
||||
a[2] = 1.0f - alpha/res_gain;
|
||||
|
||||
input = insamples[i];
|
||||
output = input*(b[0]/a[0]) + z1;
|
||||
z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
|
||||
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
|
||||
mBufferOut[i] = output;
|
||||
}
|
||||
chandata->Filter.z1 = z1;
|
||||
chandata->Filter.z2 = z2;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
|
||||
chandata->TargetGains, samplesToDo, 0);
|
||||
++chandata;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct AutowahStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new AutowahState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *AutowahStateFactory_getFactory()
|
||||
{
|
||||
static AutowahStateFactory AutowahFactory{};
|
||||
return &AutowahFactory;
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
#ifndef EFFECTS_BASE_H
|
||||
#define EFFECTS_BASE_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "atomic.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct EffectSlot;
|
||||
struct BufferStorage;
|
||||
|
||||
|
||||
enum class ChorusWaveform {
|
||||
Sinusoid,
|
||||
Triangle
|
||||
};
|
||||
|
||||
constexpr float EchoMaxDelay{0.207f};
|
||||
constexpr float EchoMaxLRDelay{0.404f};
|
||||
|
||||
enum class FShifterDirection {
|
||||
Down,
|
||||
Up,
|
||||
Off
|
||||
};
|
||||
|
||||
enum class ModulatorWaveform {
|
||||
Sinusoid,
|
||||
Sawtooth,
|
||||
Square
|
||||
};
|
||||
|
||||
enum class VMorpherPhenome {
|
||||
A, E, I, O, U,
|
||||
AA, AE, AH, AO, EH, ER, IH, IY, UH, UW,
|
||||
B, D, F, G, J, K, L, M, N, P, R, S, T, V, Z
|
||||
};
|
||||
|
||||
enum class VMorpherWaveform {
|
||||
Sinusoid,
|
||||
Triangle,
|
||||
Sawtooth
|
||||
};
|
||||
|
||||
union EffectProps {
|
||||
struct {
|
||||
// Shared Reverb Properties
|
||||
float Density;
|
||||
float Diffusion;
|
||||
float Gain;
|
||||
float GainHF;
|
||||
float DecayTime;
|
||||
float DecayHFRatio;
|
||||
float ReflectionsGain;
|
||||
float ReflectionsDelay;
|
||||
float LateReverbGain;
|
||||
float LateReverbDelay;
|
||||
float AirAbsorptionGainHF;
|
||||
float RoomRolloffFactor;
|
||||
bool DecayHFLimit;
|
||||
|
||||
// Additional EAX Reverb Properties
|
||||
float GainLF;
|
||||
float DecayLFRatio;
|
||||
float ReflectionsPan[3];
|
||||
float LateReverbPan[3];
|
||||
float EchoTime;
|
||||
float EchoDepth;
|
||||
float ModulationTime;
|
||||
float ModulationDepth;
|
||||
float HFReference;
|
||||
float LFReference;
|
||||
} Reverb;
|
||||
|
||||
struct {
|
||||
float AttackTime;
|
||||
float ReleaseTime;
|
||||
float Resonance;
|
||||
float PeakGain;
|
||||
} Autowah;
|
||||
|
||||
struct {
|
||||
ChorusWaveform Waveform;
|
||||
int Phase;
|
||||
float Rate;
|
||||
float Depth;
|
||||
float Feedback;
|
||||
float Delay;
|
||||
} Chorus; /* Also Flanger */
|
||||
|
||||
struct {
|
||||
bool OnOff;
|
||||
} Compressor;
|
||||
|
||||
struct {
|
||||
float Edge;
|
||||
float Gain;
|
||||
float LowpassCutoff;
|
||||
float EQCenter;
|
||||
float EQBandwidth;
|
||||
} Distortion;
|
||||
|
||||
struct {
|
||||
float Delay;
|
||||
float LRDelay;
|
||||
|
||||
float Damping;
|
||||
float Feedback;
|
||||
|
||||
float Spread;
|
||||
} Echo;
|
||||
|
||||
struct {
|
||||
float LowCutoff;
|
||||
float LowGain;
|
||||
float Mid1Center;
|
||||
float Mid1Gain;
|
||||
float Mid1Width;
|
||||
float Mid2Center;
|
||||
float Mid2Gain;
|
||||
float Mid2Width;
|
||||
float HighCutoff;
|
||||
float HighGain;
|
||||
} Equalizer;
|
||||
|
||||
struct {
|
||||
float Frequency;
|
||||
FShifterDirection LeftDirection;
|
||||
FShifterDirection RightDirection;
|
||||
} Fshifter;
|
||||
|
||||
struct {
|
||||
float Frequency;
|
||||
float HighPassCutoff;
|
||||
ModulatorWaveform Waveform;
|
||||
} Modulator;
|
||||
|
||||
struct {
|
||||
int CoarseTune;
|
||||
int FineTune;
|
||||
} Pshifter;
|
||||
|
||||
struct {
|
||||
float Rate;
|
||||
VMorpherPhenome PhonemeA;
|
||||
VMorpherPhenome PhonemeB;
|
||||
int PhonemeACoarseTuning;
|
||||
int PhonemeBCoarseTuning;
|
||||
VMorpherWaveform Waveform;
|
||||
} Vmorpher;
|
||||
|
||||
struct {
|
||||
float Gain;
|
||||
} Dedicated;
|
||||
};
|
||||
|
||||
|
||||
struct EffectTarget {
|
||||
MixParams *Main;
|
||||
RealMixParams *RealOut;
|
||||
};
|
||||
|
||||
struct EffectState : public al::intrusive_ref<EffectState> {
|
||||
al::span<FloatBufferLine> mOutTarget;
|
||||
|
||||
|
||||
virtual ~EffectState() = default;
|
||||
|
||||
virtual void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) = 0;
|
||||
virtual void update(const ALCcontext *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target) = 0;
|
||||
virtual void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct EffectStateFactory {
|
||||
virtual ~EffectStateFactory() = default;
|
||||
|
||||
virtual al::intrusive_ptr<EffectState> create() = 0;
|
||||
};
|
||||
|
||||
|
||||
EffectStateFactory *NullStateFactory_getFactory(void);
|
||||
EffectStateFactory *ReverbStateFactory_getFactory(void);
|
||||
EffectStateFactory *StdReverbStateFactory_getFactory(void);
|
||||
EffectStateFactory *AutowahStateFactory_getFactory(void);
|
||||
EffectStateFactory *ChorusStateFactory_getFactory(void);
|
||||
EffectStateFactory *CompressorStateFactory_getFactory(void);
|
||||
EffectStateFactory *DistortionStateFactory_getFactory(void);
|
||||
EffectStateFactory *EchoStateFactory_getFactory(void);
|
||||
EffectStateFactory *EqualizerStateFactory_getFactory(void);
|
||||
EffectStateFactory *FlangerStateFactory_getFactory(void);
|
||||
EffectStateFactory *FshifterStateFactory_getFactory(void);
|
||||
EffectStateFactory *ModulatorStateFactory_getFactory(void);
|
||||
EffectStateFactory *PshifterStateFactory_getFactory(void);
|
||||
EffectStateFactory* VmorpherStateFactory_getFactory(void);
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory(void);
|
||||
|
||||
EffectStateFactory *ConvolutionStateFactory_getFactory(void);
|
||||
|
||||
#endif /* EFFECTS_BASE_H */
|
||||
@@ -1,287 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "alu.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "effects/base.h"
|
||||
#include "effectslot.h"
|
||||
#include "math_defs.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 256
|
||||
|
||||
struct ChorusState final : public EffectState {
|
||||
al::vector<float,16> mSampleBuffer;
|
||||
uint mOffset{0};
|
||||
|
||||
uint mLfoOffset{0};
|
||||
uint mLfoRange{1};
|
||||
float mLfoScale{0.0f};
|
||||
uint mLfoDisp{0};
|
||||
|
||||
/* Gains for left and right sides */
|
||||
struct {
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
} mGains[2];
|
||||
|
||||
/* effect parameters */
|
||||
ChorusWaveform mWaveform{};
|
||||
int mDelay{0};
|
||||
float mDepth{0.0f};
|
||||
float mFeedback{0.0f};
|
||||
|
||||
void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
|
||||
void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(ChorusState)
|
||||
};
|
||||
|
||||
void ChorusState::deviceUpdate(const ALCdevice *Device, const BufferStorage* /*buffer*/)
|
||||
{
|
||||
constexpr float max_delay{maxf(AL_CHORUS_MAX_DELAY, AL_FLANGER_MAX_DELAY)};
|
||||
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
|
||||
if(maxlen != mSampleBuffer.size())
|
||||
al::vector<float,16>(maxlen).swap(mSampleBuffer);
|
||||
|
||||
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
|
||||
for(auto &e : mGains)
|
||||
{
|
||||
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
|
||||
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void ChorusState::update(const ALCcontext *Context, const EffectSlot *Slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
|
||||
|
||||
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
|
||||
* delay and depth to allow enough padding for resampling.
|
||||
*/
|
||||
const ALCdevice *device{Context->mDevice.get()};
|
||||
const auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
mWaveform = props->Chorus.Waveform;
|
||||
|
||||
mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay);
|
||||
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
|
||||
static_cast<float>(mDelay - mindelay));
|
||||
|
||||
mFeedback = props->Chorus.Feedback;
|
||||
|
||||
/* Gains for left and right sides */
|
||||
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
|
||||
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
|
||||
ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target);
|
||||
|
||||
float rate{props->Chorus.Rate};
|
||||
if(!(rate > 0.0f))
|
||||
{
|
||||
mLfoOffset = 0;
|
||||
mLfoRange = 1;
|
||||
mLfoScale = 0.0f;
|
||||
mLfoDisp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Calculate LFO coefficient (number of samples per cycle). Limit the
|
||||
* max range to avoid overflow when calculating the displacement.
|
||||
*/
|
||||
uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))};
|
||||
|
||||
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
|
||||
mLfoRange = lfo_range;
|
||||
switch(mWaveform)
|
||||
{
|
||||
case ChorusWaveform::Triangle:
|
||||
mLfoScale = 4.0f / static_cast<float>(mLfoRange);
|
||||
break;
|
||||
case ChorusWaveform::Sinusoid:
|
||||
mLfoScale = al::MathDefs<float>::Tau() / static_cast<float>(mLfoRange);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Calculate lfo phase displacement */
|
||||
int phase{props->Chorus.Phase};
|
||||
if(phase < 0) phase = 360 + phase;
|
||||
mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
|
||||
{
|
||||
const uint lfo_range{mLfoRange};
|
||||
const float lfo_scale{mLfoScale};
|
||||
const float depth{mDepth};
|
||||
const int delay{mDelay};
|
||||
|
||||
ASSUME(lfo_range > 0);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
uint offset{mLfoOffset};
|
||||
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
|
||||
{
|
||||
offset = (offset+1)%lfo_range;
|
||||
const float offset_norm{static_cast<float>(offset) * lfo_scale};
|
||||
return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
|
||||
};
|
||||
std::generate_n(delays[0], todo, gen_lfo);
|
||||
|
||||
offset = (mLfoOffset+mLfoDisp) % lfo_range;
|
||||
std::generate_n(delays[1], todo, gen_lfo);
|
||||
|
||||
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
|
||||
}
|
||||
|
||||
void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
|
||||
{
|
||||
const uint lfo_range{mLfoRange};
|
||||
const float lfo_scale{mLfoScale};
|
||||
const float depth{mDepth};
|
||||
const int delay{mDelay};
|
||||
|
||||
ASSUME(lfo_range > 0);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
uint offset{mLfoOffset};
|
||||
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
|
||||
{
|
||||
offset = (offset+1)%lfo_range;
|
||||
const float offset_norm{static_cast<float>(offset) * lfo_scale};
|
||||
return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay);
|
||||
};
|
||||
std::generate_n(delays[0], todo, gen_lfo);
|
||||
|
||||
offset = (mLfoOffset+mLfoDisp) % lfo_range;
|
||||
std::generate_n(delays[1], todo, gen_lfo);
|
||||
|
||||
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
|
||||
}
|
||||
|
||||
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const size_t bufmask{mSampleBuffer.size()-1};
|
||||
const float feedback{mFeedback};
|
||||
const uint avgdelay{(static_cast<uint>(mDelay) + (MixerFracOne>>1)) >> MixerFracBits};
|
||||
float *RESTRICT delaybuf{mSampleBuffer.data()};
|
||||
uint offset{mOffset};
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
|
||||
|
||||
uint moddelays[2][MAX_UPDATE_SAMPLES];
|
||||
if(mWaveform == ChorusWaveform::Sinusoid)
|
||||
getSinusoidDelays(moddelays, todo);
|
||||
else /*if(mWaveform == ChorusWaveform::Triangle)*/
|
||||
getTriangleDelays(moddelays, todo);
|
||||
|
||||
alignas(16) float temps[2][MAX_UPDATE_SAMPLES];
|
||||
for(size_t i{0u};i < todo;++i)
|
||||
{
|
||||
// Feed the buffer's input first (necessary for delays < 1).
|
||||
delaybuf[offset&bufmask] = samplesIn[0][base+i];
|
||||
|
||||
// Tap for the left output.
|
||||
uint delay{offset - (moddelays[0][i]>>MixerFracBits)};
|
||||
float mu{static_cast<float>(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)};
|
||||
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
|
||||
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
|
||||
|
||||
// Tap for the right output.
|
||||
delay = offset - (moddelays[1][i]>>MixerFracBits);
|
||||
mu = static_cast<float>(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne);
|
||||
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
|
||||
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
|
||||
|
||||
// Accumulate feedback from the average delay of the taps.
|
||||
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
|
||||
++offset;
|
||||
}
|
||||
|
||||
for(ALsizei c{0};c < 2;++c)
|
||||
MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
samplesToDo-base, base);
|
||||
|
||||
base += todo;
|
||||
}
|
||||
|
||||
mOffset = offset;
|
||||
}
|
||||
|
||||
|
||||
struct ChorusStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
|
||||
};
|
||||
|
||||
|
||||
/* Flanger is basically a chorus with a really short delay. They can both use
|
||||
* the same processing functions, so piggyback flanger on the chorus functions.
|
||||
*/
|
||||
struct FlangerStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *ChorusStateFactory_getFactory()
|
||||
{
|
||||
static ChorusStateFactory ChorusFactory{};
|
||||
return &ChorusFactory;
|
||||
}
|
||||
|
||||
EffectStateFactory *FlangerStateFactory_getFactory()
|
||||
{
|
||||
static FlangerStateFactory FlangerFactory{};
|
||||
return &FlangerFactory;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Anis A. Hireche
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "effectslot.h"
|
||||
#include "vecmat.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#define AMP_ENVELOPE_MIN 0.5f
|
||||
#define AMP_ENVELOPE_MAX 2.0f
|
||||
|
||||
#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
|
||||
#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
|
||||
|
||||
|
||||
struct CompressorState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{};
|
||||
|
||||
/* Effect parameters */
|
||||
bool mEnabled{true};
|
||||
float mAttackMult{1.0f};
|
||||
float mReleaseMult{1.0f};
|
||||
float mEnvFollower{1.0f};
|
||||
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(CompressorState)
|
||||
};
|
||||
|
||||
void CompressorState::deviceUpdate(const ALCdevice *device, const BufferStorage* /*buffer*/)
|
||||
{
|
||||
/* Number of samples to do a full attack and release (non-integer sample
|
||||
* counts are okay).
|
||||
*/
|
||||
const float attackCount{static_cast<float>(device->Frequency) * ATTACK_TIME};
|
||||
const float releaseCount{static_cast<float>(device->Frequency) * RELEASE_TIME};
|
||||
|
||||
/* Calculate per-sample multipliers to attack and release at the desired
|
||||
* rates.
|
||||
*/
|
||||
mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
|
||||
mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
|
||||
}
|
||||
|
||||
void CompressorState::update(const ALCcontext*, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
mEnabled = props->Compressor.OnOff;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &gains, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); };
|
||||
SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains);
|
||||
}
|
||||
|
||||
void CompressorState::process(const size_t samplesToDo,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
float gains[256];
|
||||
const size_t td{minz(256, samplesToDo-base)};
|
||||
|
||||
/* Generate the per-sample gains from the signal envelope. */
|
||||
float env{mEnvFollower};
|
||||
if(mEnabled)
|
||||
{
|
||||
for(size_t i{0u};i < td;++i)
|
||||
{
|
||||
/* Clamp the absolute amplitude to the defined envelope limits,
|
||||
* then attack or release the envelope to reach it.
|
||||
*/
|
||||
const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
|
||||
AMP_ENVELOPE_MAX)};
|
||||
if(amplitude > env)
|
||||
env = minf(env*mAttackMult, amplitude);
|
||||
else if(amplitude < env)
|
||||
env = maxf(env*mReleaseMult, amplitude);
|
||||
|
||||
/* Apply the reciprocal of the envelope to normalize the volume
|
||||
* (compress the dynamic range).
|
||||
*/
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Same as above, except the amplitude is forced to 1. This helps
|
||||
* ensure smooth gain changes when the compressor is turned on and
|
||||
* off.
|
||||
*/
|
||||
for(size_t i{0u};i < td;++i)
|
||||
{
|
||||
const float amplitude{1.0f};
|
||||
if(amplitude > env)
|
||||
env = minf(env*mAttackMult, amplitude);
|
||||
else if(amplitude < env)
|
||||
env = maxf(env*mReleaseMult, amplitude);
|
||||
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
mEnvFollower = env;
|
||||
|
||||
/* Now compress the signal amplitude to output. */
|
||||
auto changains = std::addressof(mGain[0]);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
const float *outgains{*(changains++)};
|
||||
for(FloatBufferLine &output : samplesOut)
|
||||
{
|
||||
const float gain{*(outgains++)};
|
||||
if(!(std::fabs(gain) > GainSilenceThreshold))
|
||||
continue;
|
||||
|
||||
for(size_t i{0u};i < td;i++)
|
||||
output[base+i] += input[base+i] * gains[i] * gain;
|
||||
}
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct CompressorStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new CompressorState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *CompressorStateFactory_getFactory()
|
||||
{
|
||||
static CompressorStateFactory CompressorFactory{};
|
||||
return &CompressorFactory;
|
||||
}
|
||||
@@ -1,567 +0,0 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(HAVE_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcomplex.h"
|
||||
#include "alcontext.h"
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "bformatdec.h"
|
||||
#include "buffer_storage.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/filters/splitter.h"
|
||||
#include "core/fmt_traits.h"
|
||||
#include "core/logging.h"
|
||||
#include "effects/base.h"
|
||||
#include "effectslot.h"
|
||||
#include "math_defs.h"
|
||||
#include "polyphase_resampler.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* Convolution reverb is implemented using a segmented overlap-add method. The
|
||||
* impulse response is broken up into multiple segments of 128 samples, and
|
||||
* each segment has an FFT applied with a 256-sample buffer (the latter half
|
||||
* left silent) to get its frequency-domain response. The resulting response
|
||||
* has its positive/non-mirrored frequencies saved (129 bins) in each segment.
|
||||
*
|
||||
* Input samples are similarly broken up into 128-sample segments, with an FFT
|
||||
* applied to each new incoming segment to get its 129 bins. A history of FFT'd
|
||||
* input segments is maintained, equal to the length of the impulse response.
|
||||
*
|
||||
* To apply the reverberation, each impulse response segment is convolved with
|
||||
* its paired input segment (using complex multiplies, far cheaper than FIRs),
|
||||
* accumulating into a 256-bin FFT buffer. The input history is then shifted to
|
||||
* align with later impulse response segments for next time.
|
||||
*
|
||||
* An inverse FFT is then applied to the accumulated FFT buffer to get a 256-
|
||||
* sample time-domain response for output, which is split in two halves. The
|
||||
* first half is the 128-sample output, and the second half is a 128-sample
|
||||
* (really, 127) delayed extension, which gets added to the output next time.
|
||||
* Convolving two time-domain responses of lengths N and M results in a time-
|
||||
* domain signal of length N+M-1, and this holds true regardless of the
|
||||
* convolution being applied in the frequency domain, so these "overflow"
|
||||
* samples need to be accounted for.
|
||||
*
|
||||
* To avoid a delay with gathering enough input samples to apply an FFT with,
|
||||
* the first segment is applied directly in the time-domain as the samples come
|
||||
* in. Once enough have been retrieved, the FFT is applied on the input and
|
||||
* it's paired with the remaining (FFT'd) filter segments for processing.
|
||||
*/
|
||||
|
||||
|
||||
void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(FmtUByte);
|
||||
HANDLE_FMT(FmtShort);
|
||||
HANDLE_FMT(FmtFloat);
|
||||
HANDLE_FMT(FmtDouble);
|
||||
HANDLE_FMT(FmtMulaw);
|
||||
HANDLE_FMT(FmtAlaw);
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
||||
|
||||
inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
|
||||
{
|
||||
if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa();
|
||||
if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D();
|
||||
return AmbiScale::FromN3D();
|
||||
}
|
||||
|
||||
inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
|
||||
{
|
||||
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
|
||||
return AmbiIndex::FromACN();
|
||||
}
|
||||
|
||||
inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
|
||||
{
|
||||
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
|
||||
return AmbiIndex::FromACN2D();
|
||||
}
|
||||
|
||||
|
||||
struct ChanMap {
|
||||
Channel channel;
|
||||
float angle;
|
||||
float elevation;
|
||||
};
|
||||
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
constexpr size_t ConvolveUpdateSize{256};
|
||||
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
|
||||
|
||||
|
||||
void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter)
|
||||
{
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
for(float &output : dst)
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&filter[j])};
|
||||
const __m128 s{_mm_loadu_ps(&src[j])};
|
||||
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
output = _mm_cvtss_f32(r4);
|
||||
|
||||
++src;
|
||||
}
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
|
||||
for(float &output : dst)
|
||||
{
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
|
||||
r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j]));
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
|
||||
++src;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
for(float &output : dst)
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(size_t j{0};j < ConvolveUpdateSamples;++j)
|
||||
ret += src[j] * filter[j];
|
||||
output = ret;
|
||||
++src;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ConvolutionState final : public EffectState {
|
||||
FmtChannels mChannels{};
|
||||
AmbiLayout mAmbiLayout{};
|
||||
AmbiScaling mAmbiScaling{};
|
||||
uint mAmbiOrder{};
|
||||
|
||||
size_t mFifoPos{0};
|
||||
std::array<float,ConvolveUpdateSamples*2> mInput{};
|
||||
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
|
||||
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
|
||||
|
||||
alignas(16) std::array<complex_d,ConvolveUpdateSize> mFftBuffer{};
|
||||
|
||||
size_t mCurrentSegment{0};
|
||||
size_t mNumConvolveSegs{0};
|
||||
|
||||
struct ChannelData {
|
||||
alignas(16) FloatBufferLine mBuffer{};
|
||||
float mHfScale{};
|
||||
BandSplitter mFilter{};
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
};
|
||||
using ChannelDataArray = al::FlexArray<ChannelData>;
|
||||
std::unique_ptr<ChannelDataArray> mChans;
|
||||
std::unique_ptr<complex_d[]> mComplexData;
|
||||
|
||||
|
||||
ConvolutionState() = default;
|
||||
~ConvolutionState() override = default;
|
||||
|
||||
void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
|
||||
void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
|
||||
void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t)
|
||||
{&ConvolutionState::NormalMix};
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(ConvolutionState)
|
||||
};
|
||||
|
||||
void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut,
|
||||
const size_t samplesToDo)
|
||||
{
|
||||
for(auto &chan : *mChans)
|
||||
MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
|
||||
const size_t samplesToDo)
|
||||
{
|
||||
for(auto &chan : *mChans)
|
||||
{
|
||||
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
|
||||
chan.mFilter.processHfScale(src, chan.mHfScale);
|
||||
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConvolutionState::deviceUpdate(const ALCdevice *device, const BufferStorage *buffer)
|
||||
{
|
||||
constexpr uint MaxConvolveAmbiOrder{1u};
|
||||
|
||||
mFifoPos = 0;
|
||||
mInput.fill(0.0f);
|
||||
decltype(mFilter){}.swap(mFilter);
|
||||
decltype(mOutput){}.swap(mOutput);
|
||||
mFftBuffer.fill(complex_d{});
|
||||
|
||||
mCurrentSegment = 0;
|
||||
mNumConvolveSegs = 0;
|
||||
|
||||
mChans = nullptr;
|
||||
mComplexData = nullptr;
|
||||
|
||||
/* An empty buffer doesn't need a convolution filter. */
|
||||
if(!buffer || buffer->mSampleLen < 1) return;
|
||||
|
||||
constexpr size_t m{ConvolveUpdateSize/2 + 1};
|
||||
auto bytesPerSample = BytesFromFmt(buffer->mType);
|
||||
auto realChannels = ChannelsFromFmt(buffer->mChannels, buffer->mAmbiOrder);
|
||||
auto numChannels = ChannelsFromFmt(buffer->mChannels,
|
||||
minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder));
|
||||
|
||||
mChans = ChannelDataArray::Create(numChannels);
|
||||
|
||||
/* The impulse response needs to have the same sample rate as the input and
|
||||
* output. The bsinc24 resampler is decent, but there is high-frequency
|
||||
* attenation that some people may be able to pick up on. Since this is
|
||||
* called very infrequently, go ahead and use the polyphase resampler.
|
||||
*/
|
||||
PPhaseResampler resampler;
|
||||
if(device->Frequency != buffer->mSampleRate)
|
||||
resampler.init(buffer->mSampleRate, device->Frequency);
|
||||
const auto resampledCount = static_cast<uint>(
|
||||
(uint64_t{buffer->mSampleLen}*device->Frequency + (buffer->mSampleRate-1)) /
|
||||
buffer->mSampleRate);
|
||||
|
||||
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
|
||||
for(auto &e : *mChans)
|
||||
e.mFilter = splitter;
|
||||
|
||||
mFilter.resize(numChannels, {});
|
||||
mOutput.resize(numChannels, {});
|
||||
|
||||
/* Calculate the number of segments needed to hold the impulse response and
|
||||
* the input history (rounded up), and allocate them. Exclude one segment
|
||||
* which gets applied as a time-domain FIR filter. Make sure at least one
|
||||
* segment is allocated to simplify handling.
|
||||
*/
|
||||
mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples;
|
||||
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
|
||||
|
||||
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
|
||||
mComplexData = std::make_unique<complex_d[]>(complex_length);
|
||||
std::fill_n(mComplexData.get(), complex_length, complex_d{});
|
||||
|
||||
mChannels = buffer->mChannels;
|
||||
mAmbiLayout = buffer->mAmbiLayout;
|
||||
mAmbiScaling = buffer->mAmbiScaling;
|
||||
mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
|
||||
|
||||
auto srcsamples = std::make_unique<double[]>(maxz(buffer->mSampleLen, resampledCount));
|
||||
complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m;
|
||||
for(size_t c{0};c < numChannels;++c)
|
||||
{
|
||||
/* Load the samples from the buffer, and resample to match the device. */
|
||||
LoadSamples(srcsamples.get(), buffer->mData.data() + bytesPerSample*c, realChannels,
|
||||
buffer->mType, buffer->mSampleLen);
|
||||
if(device->Frequency != buffer->mSampleRate)
|
||||
resampler.process(buffer->mSampleLen, srcsamples.get(), resampledCount,
|
||||
srcsamples.get());
|
||||
|
||||
/* Store the first segment's samples in reverse in the time-domain, to
|
||||
* apply as a FIR filter.
|
||||
*/
|
||||
const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)};
|
||||
std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(),
|
||||
[](const double d) noexcept -> float { return static_cast<float>(d); });
|
||||
|
||||
size_t done{first_size};
|
||||
for(size_t s{0};s < mNumConvolveSegs;++s)
|
||||
{
|
||||
const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)};
|
||||
|
||||
auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin());
|
||||
done += todo;
|
||||
std::fill(iter, mFftBuffer.end(), complex_d{});
|
||||
|
||||
forward_fft(mFftBuffer);
|
||||
filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConvolutionState::update(const ALCcontext *context, const EffectSlot *slot,
|
||||
const EffectProps* /*props*/, const EffectTarget target)
|
||||
{
|
||||
/* NOTE: Stereo and Rear are slightly different from normal mixing (as
|
||||
* defined in alu.cpp). These are 45 degrees from center, rather than the
|
||||
* 30 degrees used there.
|
||||
*
|
||||
* TODO: LFE is not mixed to output. This will require each buffer channel
|
||||
* to have its own output target since the main mixing buffer won't have an
|
||||
* LFE channel (due to being B-Format).
|
||||
*/
|
||||
static const ChanMap MonoMap[1]{
|
||||
{ FrontCenter, 0.0f, 0.0f }
|
||||
}, StereoMap[2]{
|
||||
{ FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) },
|
||||
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }
|
||||
}, RearMap[2]{
|
||||
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
|
||||
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
|
||||
}, QuadMap[4]{
|
||||
{ FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
|
||||
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
|
||||
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
|
||||
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
|
||||
}, X51Map[6]{
|
||||
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
|
||||
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
|
||||
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
|
||||
{ LFE, 0.0f, 0.0f },
|
||||
{ SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
|
||||
{ SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
|
||||
}, X61Map[7]{
|
||||
{ FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
|
||||
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
|
||||
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
|
||||
{ LFE, 0.0f, 0.0f },
|
||||
{ BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
|
||||
{ SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
|
||||
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
|
||||
}, X71Map[8]{
|
||||
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
|
||||
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
|
||||
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
|
||||
{ LFE, 0.0f, 0.0f },
|
||||
{ BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
|
||||
{ BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
|
||||
{ SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
|
||||
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
|
||||
};
|
||||
|
||||
if(mNumConvolveSegs < 1)
|
||||
return;
|
||||
|
||||
mMix = &ConvolutionState::NormalMix;
|
||||
|
||||
for(auto &chan : *mChans)
|
||||
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
|
||||
const float gain{slot->Gain};
|
||||
if(mChannels == FmtBFormat3D || mChannels == FmtBFormat2D)
|
||||
{
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
if(device->mAmbiOrder > mAmbiOrder)
|
||||
{
|
||||
mMix = &ConvolutionState::UpsampleMix;
|
||||
const auto scales = BFormatDec::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
|
||||
(*mChans)[0].mHfScale = scales[0];
|
||||
for(size_t i{1};i < mChans->size();++i)
|
||||
(*mChans)[i].mHfScale = scales[1];
|
||||
}
|
||||
mOutTarget = target.Main->Buffer;
|
||||
|
||||
auto&& scales = GetAmbiScales(mAmbiScaling);
|
||||
const uint8_t *index_map{(mChannels == FmtBFormat2D) ?
|
||||
GetAmbi2DLayout(mAmbiLayout).data() :
|
||||
GetAmbiLayout(mAmbiLayout).data()};
|
||||
|
||||
std::array<float,MaxAmbiChannels> coeffs{};
|
||||
for(size_t c{0u};c < mChans->size();++c)
|
||||
{
|
||||
const size_t acn{index_map[c]};
|
||||
coeffs[acn] = scales[acn];
|
||||
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target);
|
||||
coeffs[acn] = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ALCdevice *device{context->mDevice.get()};
|
||||
al::span<const ChanMap> chanmap{};
|
||||
switch(mChannels)
|
||||
{
|
||||
case FmtMono: chanmap = MonoMap; break;
|
||||
case FmtStereo: chanmap = StereoMap; break;
|
||||
case FmtRear: chanmap = RearMap; break;
|
||||
case FmtQuad: chanmap = QuadMap; break;
|
||||
case FmtX51: chanmap = X51Map; break;
|
||||
case FmtX61: chanmap = X61Map; break;
|
||||
case FmtX71: chanmap = X71Map; break;
|
||||
case FmtBFormat2D:
|
||||
case FmtBFormat3D:
|
||||
break;
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
if(device->mRenderMode == RenderMode::Pairwise)
|
||||
{
|
||||
auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
|
||||
{
|
||||
const float abs_azi{std::fabs(azimuth)};
|
||||
if(!(abs_azi >= al::MathDefs<float>::Pi()*0.5f))
|
||||
return std::copysign(minf(abs_azi*scale, al::MathDefs<float>::Pi()*0.5f), azimuth);
|
||||
return azimuth;
|
||||
};
|
||||
|
||||
for(size_t i{0};i < chanmap.size();++i)
|
||||
{
|
||||
if(chanmap[i].channel == LFE) continue;
|
||||
const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f),
|
||||
chanmap[i].elevation, 0.0f);
|
||||
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
|
||||
}
|
||||
}
|
||||
else for(size_t i{0};i < chanmap.size();++i)
|
||||
{
|
||||
if(chanmap[i].channel == LFE) continue;
|
||||
const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f);
|
||||
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvolutionState::process(const size_t samplesToDo,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
if(mNumConvolveSegs < 1)
|
||||
return;
|
||||
|
||||
constexpr size_t m{ConvolveUpdateSize/2 + 1};
|
||||
size_t curseg{mCurrentSegment};
|
||||
auto &chans = *mChans;
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
|
||||
|
||||
std::copy_n(samplesIn[0].begin() + base, todo,
|
||||
mInput.begin()+ConvolveUpdateSamples+mFifoPos);
|
||||
|
||||
/* Apply the FIR for the newly retrieved input samples, and combine it
|
||||
* with the inverse FFT'd output samples.
|
||||
*/
|
||||
for(size_t c{0};c < chans.size();++c)
|
||||
{
|
||||
auto buf_iter = chans[c].mBuffer.begin() + base;
|
||||
apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos,
|
||||
mFilter[c].data());
|
||||
|
||||
auto fifo_iter = mOutput[c].begin() + mFifoPos;
|
||||
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
|
||||
}
|
||||
|
||||
mFifoPos += todo;
|
||||
base += todo;
|
||||
|
||||
/* Check whether the input buffer is filled with new samples. */
|
||||
if(mFifoPos < ConvolveUpdateSamples) break;
|
||||
mFifoPos = 0;
|
||||
|
||||
/* Move the newest input to the front for the next iteration's history. */
|
||||
std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin());
|
||||
|
||||
/* Calculate the frequency domain response and add the relevant
|
||||
* frequency bins to the FFT history.
|
||||
*/
|
||||
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
|
||||
std::fill(fftiter, mFftBuffer.end(), complex_d{});
|
||||
forward_fft(mFftBuffer);
|
||||
|
||||
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
|
||||
|
||||
const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
|
||||
for(size_t c{0};c < chans.size();++c)
|
||||
{
|
||||
std::fill_n(mFftBuffer.begin(), m, complex_d{});
|
||||
|
||||
/* Convolve each input segment with its IR filter counterpart
|
||||
* (aligned in time).
|
||||
*/
|
||||
const complex_d *RESTRICT input{&mComplexData[curseg*m]};
|
||||
for(size_t s{curseg};s < mNumConvolveSegs;++s)
|
||||
{
|
||||
for(size_t i{0};i < m;++i,++input,++filter)
|
||||
mFftBuffer[i] += *input * *filter;
|
||||
}
|
||||
input = mComplexData.get();
|
||||
for(size_t s{0};s < curseg;++s)
|
||||
{
|
||||
for(size_t i{0};i < m;++i,++input,++filter)
|
||||
mFftBuffer[i] += *input * *filter;
|
||||
}
|
||||
|
||||
/* Reconstruct the mirrored/negative frequencies to do a proper
|
||||
* inverse FFT.
|
||||
*/
|
||||
for(size_t i{m};i < ConvolveUpdateSize;++i)
|
||||
mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]);
|
||||
|
||||
/* Apply iFFT to get the 256 (really 255) samples for output. The
|
||||
* 128 output samples are combined with the last output's 127
|
||||
* second-half samples (and this output's second half is
|
||||
* subsequently saved for next time).
|
||||
*/
|
||||
inverse_fft(mFftBuffer);
|
||||
|
||||
/* The iFFT'd response is scaled up by the number of bins, so apply
|
||||
* the inverse to normalize the output.
|
||||
*/
|
||||
for(size_t i{0};i < ConvolveUpdateSamples;++i)
|
||||
mOutput[c][i] =
|
||||
static_cast<float>(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) +
|
||||
mOutput[c][ConvolveUpdateSamples+i];
|
||||
for(size_t i{0};i < ConvolveUpdateSamples;++i)
|
||||
mOutput[c][ConvolveUpdateSamples+i] =
|
||||
static_cast<float>(mFftBuffer[ConvolveUpdateSamples+i].real() *
|
||||
(1.0/double{ConvolveUpdateSize}));
|
||||
}
|
||||
|
||||
/* Shift the input history. */
|
||||
curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1);
|
||||
}
|
||||
mCurrentSegment = curseg;
|
||||
|
||||
/* Finally, mix to the output. */
|
||||
(this->*mMix)(samplesOut, samplesToDo);
|
||||
}
|
||||
|
||||
|
||||
struct ConvolutionStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new ConvolutionState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *ConvolutionStateFactory_getFactory()
|
||||
{
|
||||
static ConvolutionStateFactory ConvolutionFactory{};
|
||||
return &ConvolutionFactory;
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "alu.h"
|
||||
#include "effectslot.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct DedicatedState final : public EffectState {
|
||||
float mCurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
float mTargetGains[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(DedicatedState)
|
||||
};
|
||||
|
||||
void DedicatedState::deviceUpdate(const ALCdevice*, const BufferStorage*)
|
||||
{
|
||||
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
|
||||
}
|
||||
|
||||
void DedicatedState::update(const ALCcontext*, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
|
||||
|
||||
const float Gain{slot->Gain * props->Dedicated.Gain};
|
||||
|
||||
if(slot->EffectType == EffectSlotType::DedicatedLFE)
|
||||
{
|
||||
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
|
||||
GetChannelIdxByName(*target.RealOut, LFE)};
|
||||
if(idx != INVALID_CHANNEL_INDEX)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
}
|
||||
}
|
||||
else if(slot->EffectType == EffectSlotType::DedicatedDialog)
|
||||
{
|
||||
/* Dialog goes to the front-center speaker if it exists, otherwise it
|
||||
* plays from the front-center location. */
|
||||
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
|
||||
GetChannelIdxByName(*target.RealOut, FrontCenter)};
|
||||
if(idx != INVALID_CHANNEL_INDEX)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
|
||||
struct DedicatedStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new DedicatedState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory()
|
||||
{
|
||||
static DedicatedStateFactory DedicatedFactory{};
|
||||
return &DedicatedFactory;
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "effectslot.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct DistortionState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
float mGain[MAX_OUTPUT_CHANNELS]{};
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter mLowpass;
|
||||
BiquadFilter mBandpass;
|
||||
float mAttenuation{};
|
||||
float mEdgeCoeff{};
|
||||
|
||||
float mBuffer[2][BufferLineSize]{};
|
||||
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(DistortionState)
|
||||
};
|
||||
|
||||
void DistortionState::deviceUpdate(const ALCdevice*, const BufferStorage*)
|
||||
{
|
||||
mLowpass.clear();
|
||||
mBandpass.clear();
|
||||
}
|
||||
|
||||
void DistortionState::update(const ALCcontext *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
|
||||
/* Store waveshaper edge settings. */
|
||||
const float edge{minf(std::sin(al::MathDefs<float>::Pi()*0.5f * props->Distortion.Edge),
|
||||
0.99f)};
|
||||
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
|
||||
|
||||
float cutoff{props->Distortion.LowpassCutoff};
|
||||
/* Bandwidth value is constant in octaves. */
|
||||
float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
|
||||
/* Divide normalized frequency by the amount of oversampling done during
|
||||
* processing.
|
||||
*/
|
||||
auto frequency = static_cast<float>(device->Frequency);
|
||||
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
|
||||
|
||||
cutoff = props->Distortion.EQCenter;
|
||||
/* Convert bandwidth in Hz to octaves. */
|
||||
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
|
||||
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
|
||||
|
||||
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
|
||||
}
|
||||
|
||||
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const float fc{mEdgeCoeff};
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
/* Perform 4x oversampling to avoid aliasing. Oversampling greatly
|
||||
* improves distortion quality and allows to implement lowpass and
|
||||
* bandpass filters using high frequencies, at which classic IIR
|
||||
* filters became unstable.
|
||||
*/
|
||||
size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
|
||||
|
||||
/* Fill oversample buffer using zero stuffing. Multiply the sample by
|
||||
* the amount of oversampling to maintain the signal's power.
|
||||
*/
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
|
||||
|
||||
/* First step, do lowpass filtering of original signal. Additionally
|
||||
* perform buffer interpolation and lowpass cutoff for oversampling
|
||||
* (which is fortunately first step of distortion). So combine three
|
||||
* operations into the one.
|
||||
*/
|
||||
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
|
||||
|
||||
/* Second step, do distortion using waveshaper function to emulate
|
||||
* signal processing during tube overdriving. Three steps of
|
||||
* waveshaping are intended to modify waveform without boost/clipping/
|
||||
* attenuation process.
|
||||
*/
|
||||
auto proc_sample = [fc](float smp) -> float
|
||||
{
|
||||
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
|
||||
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
|
||||
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
|
||||
return smp;
|
||||
};
|
||||
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
|
||||
proc_sample);
|
||||
|
||||
/* Third step, do bandpass filtering of distorted signal. */
|
||||
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
|
||||
|
||||
todo >>= 2;
|
||||
const float *outgains{mGain};
|
||||
for(FloatBufferLine &output : samplesOut)
|
||||
{
|
||||
/* Fourth step, final, do attenuation and perform decimation,
|
||||
* storing only one sample out of four.
|
||||
*/
|
||||
const float gain{*(outgains++)};
|
||||
if(!(std::fabs(gain) > GainSilenceThreshold))
|
||||
continue;
|
||||
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
output[base+i] += gain * mBuffer[1][i*4];
|
||||
}
|
||||
|
||||
base += todo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DistortionStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new DistortionState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *DistortionStateFactory_getFactory()
|
||||
{
|
||||
static DistortionStateFactory DistortionFactory{};
|
||||
return &DistortionFactory;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "alcmain.h"
|
||||
#include "alcontext.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "effectslot.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float LowpassFreqRef{5000.0f};
|
||||
|
||||
struct EchoState final : public EffectState {
|
||||
al::vector<float,16> mSampleBuffer;
|
||||
|
||||
// The echo is two tap. The delay is the number of samples from before the
|
||||
// current offset
|
||||
struct {
|
||||
size_t delay{0u};
|
||||
} mTap[2];
|
||||
size_t mOffset{0u};
|
||||
|
||||
/* The panning gains for the two taps */
|
||||
struct {
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
} mGains[2];
|
||||
|
||||
BiquadFilter mFilter;
|
||||
float mFeedGain{0.0f};
|
||||
|
||||
alignas(16) float mTempBuffer[2][BufferLineSize];
|
||||
|
||||
void deviceUpdate(const ALCdevice *device, const BufferStorage *buffer) override;
|
||||
void update(const ALCcontext *context, const EffectSlot *slot, const EffectProps *props,
|
||||
const EffectTarget target) override;
|
||||
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(EchoState)
|
||||
};
|
||||
|
||||
void EchoState::deviceUpdate(const ALCdevice *Device, const BufferStorage* /*buffer*/)
|
||||
{
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
|
||||
// Use the next power of 2 for the buffer length, so the tap offsets can be
|
||||
// wrapped using a mask instead of a modulo
|
||||
const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) +
|
||||
float2uint(EchoMaxLRDelay*frequency + 0.5f))};
|
||||
if(maxlen != mSampleBuffer.size())
|
||||
al::vector<float,16>(maxlen).swap(mSampleBuffer);
|
||||
|
||||
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
|
||||
for(auto &e : mGains)
|
||||
{
|
||||
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
|
||||
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void EchoState::update(const ALCcontext *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const ALCdevice *device{context->mDevice.get()};
|
||||
const auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
|
||||
mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
|
||||
|
||||
const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
|
||||
mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f);
|
||||
|
||||
mFeedGain = props->Echo.Feedback;
|
||||
|
||||
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
|
||||
const float angle{std::asin(props->Echo.Spread)};
|
||||
|
||||
const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f);
|
||||
const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target);
|
||||
ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target);
|
||||
}
|
||||
|
||||
void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const size_t mask{mSampleBuffer.size()-1};
|
||||
float *RESTRICT delaybuf{mSampleBuffer.data()};
|
||||
size_t offset{mOffset};
|
||||
size_t tap1{offset - mTap[0].delay};
|
||||
size_t tap2{offset - mTap[1].delay};
|
||||
float z1, z2;
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
const BiquadFilter filter{mFilter};
|
||||
std::tie(z1, z2) = mFilter.getComponents();
|
||||
for(size_t i{0u};i < samplesToDo;)
|
||||
{
|
||||
offset &= mask;
|
||||
tap1 &= mask;
|
||||
tap2 &= mask;
|
||||
|
||||
size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
|
||||
do {
|
||||
/* Feed the delay buffer's input first. */
|
||||
delaybuf[offset] = samplesIn[0][i];
|
||||
|
||||
/* Get delayed output from the first and second taps. Use the
|
||||
* second tap for feedback.
|
||||
*/
|
||||
mTempBuffer[0][i] = delaybuf[tap1++];
|
||||
mTempBuffer[1][i] = delaybuf[tap2++];
|
||||
const float feedb{mTempBuffer[1][i++]};
|
||||
|
||||
/* Add feedback to the delay buffer with damping and attenuation. */
|
||||
delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
|
||||
} while(--td);
|
||||
}
|
||||
mFilter.setComponents(z1, z2);
|
||||
mOffset = offset;
|
||||
|
||||
for(ALsizei c{0};c < 2;c++)
|
||||
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
|
||||
struct EchoStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new EchoState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *EchoStateFactory_getFactory()
|
||||
{
|
||||
static EchoStateFactory EchoFactory{};
|
||||
return &EchoFactory;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user