├── .gitignore ├── src ├── decoders │ ├── flac.hpp │ ├── wave.hpp │ ├── sndfile.hpp │ ├── opusfile.hpp │ ├── vorbisfile.hpp │ ├── mp3.hpp │ ├── flac.cpp │ ├── vorbisfile.cpp │ ├── opusfile.cpp │ └── sndfile.cpp ├── effect.h ├── devicemanager.h ├── auxeffectslot.h ├── device.h ├── buffer.h ├── sourcegroup.h ├── auxeffectslot.cpp ├── main.h ├── devicemanager.cpp ├── effect.cpp ├── source.h ├── sourcegroup.cpp ├── device.cpp └── context.h ├── config.h.in ├── LICENSE ├── cmake ├── FindDUMB.cmake ├── FindOgg.cmake ├── FindSndFile.cmake ├── FindOpus.cmake └── FindVorbis.cmake ├── .travis.yml ├── appveyor.yml ├── include ├── mpark │ ├── in_place.hpp │ ├── LICENSE.md │ └── config.hpp ├── AL │ ├── alure2-aliases.h │ └── alure2-typeviews.h └── alc.h ├── .clang-format ├── examples ├── alure-enum.cpp ├── alure-play.cpp ├── alure-play3d.cpp ├── alure-stream.cpp ├── alure-hrtf.cpp ├── alure-reverb.cpp ├── alure-dumb.cpp └── alure-physfs.cpp ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .kdev4 2 | alure.kdev4 3 | build 4 | -------------------------------------------------------------------------------- /src/decoders/flac.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECODERS_FLAC_HPP 2 | #define DECODERS_FLAC_HPP 3 | 4 | #include "alure2.h" 5 | 6 | namespace alure { 7 | 8 | class FlacDecoderFactory final : public DecoderFactory { 9 | SharedPtr createDecoder(UniquePtr &file) noexcept override; 10 | }; 11 | 12 | } // namespace alure 13 | 14 | #endif /* DECODERS_FLAC_HPP */ 15 | -------------------------------------------------------------------------------- /src/decoders/wave.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECODERS_WAVE_HPP 2 | #define DECODERS_WAVE_HPP 3 | 4 | #include "alure2.h" 5 | 6 | namespace alure { 7 | 8 | class WaveDecoderFactory final : public DecoderFactory { 9 | SharedPtr createDecoder(UniquePtr &file) noexcept override; 10 | }; 11 | 12 | } // namespace alure 13 | 14 | #endif /* DECODERS_WAVE_HPP */ 15 | -------------------------------------------------------------------------------- /src/decoders/sndfile.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECODERS_SNDFILE_HPP 2 | #define DECODERS_SNDFILE_HPP 3 | 4 | #include "alure2.h" 5 | 6 | namespace alure { 7 | 8 | class SndFileDecoderFactory final : public DecoderFactory { 9 | SharedPtr createDecoder(UniquePtr &file) noexcept override; 10 | }; 11 | 12 | } // namespace alure 13 | 14 | #endif /* DECODERS_SNDFILE_HPP */ 15 | -------------------------------------------------------------------------------- /src/decoders/opusfile.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECODERS_OPUSFILE_HPP 2 | #define DECODERS_OPUSFILE_HPP 3 | 4 | #include "alure2.h" 5 | 6 | namespace alure { 7 | 8 | class OpusFileDecoderFactory final : public DecoderFactory { 9 | SharedPtr createDecoder(UniquePtr &file) noexcept override; 10 | }; 11 | 12 | } // namespace alure 13 | 14 | #endif /* DECODERS_OPUSFILE_HPP */ 15 | -------------------------------------------------------------------------------- /src/decoders/vorbisfile.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECODERS_VORBISFILE_HPP 2 | #define DECODERS_VORBISFILE_HPP 3 | 4 | #include "alure2.h" 5 | 6 | namespace alure { 7 | 8 | class VorbisFileDecoderFactory final : public DecoderFactory { 9 | SharedPtr createDecoder(UniquePtr &file) noexcept override; 10 | }; 11 | 12 | } // namespace alure 13 | 14 | #endif /* DECODERS_VORBISFILE_HPP */ 15 | -------------------------------------------------------------------------------- /src/decoders/mp3.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DECODERS_MP3_HPP 2 | #define DECODERS_MP3_HPP 3 | 4 | #include "alure2.h" 5 | 6 | namespace alure { 7 | 8 | class Mp3DecoderFactory final : public DecoderFactory { 9 | public: 10 | Mp3DecoderFactory() noexcept; 11 | ~Mp3DecoderFactory() override; 12 | 13 | SharedPtr createDecoder(UniquePtr &file) noexcept override; 14 | }; 15 | 16 | } // namespace alure 17 | 18 | #endif /* DECODERS_MP3_HPP */ 19 | 20 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* Define if we have wave file support */ 2 | #cmakedefine HAVE_WAVE 3 | 4 | /* Define if we have vorbisfile support */ 5 | #cmakedefine HAVE_VORBISFILE 6 | 7 | /* Define if we have FLAC support */ 8 | #cmakedefine HAVE_FLAC 9 | 10 | /* Define if we have opusfile support */ 11 | #cmakedefine HAVE_OPUSFILE 12 | 13 | /* Define if we have libsndfile support */ 14 | #cmakedefine HAVE_LIBSNDFILE 15 | 16 | /* Define if we have MINIMP3 support */ 17 | #cmakedefine HAVE_MINIMP3 18 | -------------------------------------------------------------------------------- /src/effect.h: -------------------------------------------------------------------------------- 1 | #ifndef EFFECT_H 2 | #define EFFECT_H 3 | 4 | #include "main.h" 5 | 6 | namespace alure { 7 | 8 | class EffectImpl { 9 | ContextImpl &mContext; 10 | ALuint mId{0}; 11 | ALenum mType{AL_NONE}; 12 | 13 | public: 14 | EffectImpl(ContextImpl &context); 15 | ~EffectImpl(); 16 | 17 | void setReverbProperties(const EFXEAXREVERBPROPERTIES &props); 18 | void setChorusProperties(const EFXCHORUSPROPERTIES &props); 19 | 20 | void destroy(); 21 | 22 | ContextImpl &getContext() const { return mContext; } 23 | ALuint getId() const { return mId; } 24 | }; 25 | 26 | } // namespace alure 27 | 28 | #endif /* EFFECT_H */ 29 | -------------------------------------------------------------------------------- /src/devicemanager.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICEMANAGER_H 2 | #define DEVICEMANAGER_H 3 | 4 | #include "main.h" 5 | 6 | namespace alure { 7 | 8 | class DeviceManagerImpl { 9 | static WeakPtr sInstance; 10 | 11 | Vector> mDevices; 12 | 13 | public: 14 | static ALCboolean (ALC_APIENTRY*SetThreadContext)(ALCcontext*); 15 | 16 | static SharedPtr getInstance(); 17 | 18 | DeviceManagerImpl(); 19 | ~DeviceManagerImpl(); 20 | 21 | void removeDevice(DeviceImpl *dev); 22 | 23 | bool queryExtension(const char *name) const; 24 | 25 | Vector enumerate(DeviceEnumeration type) const; 26 | String defaultDeviceName(DefaultDeviceType type) const; 27 | 28 | Device openPlayback(const char *name); 29 | Device openPlayback(const char *name, const std::nothrow_t&) noexcept; 30 | }; 31 | 32 | } // namespace alure 33 | 34 | #endif /* DEVICEMANAGER_H */ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Chris Robinsons 2 | 3 | This software is provided 'as-is', without any express or implied warranty. In 4 | no event will the authors be held liable for any damages arising from the use 5 | of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, including 8 | commercial applications, and to alter it and redistribute it freely, subject to 9 | the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software in 13 | a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /cmake/FindDUMB.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find DUMB 2 | # Once done this will define 3 | # 4 | # DUMB_FOUND - system has DUMB 5 | # DUMB_INCLUDE_DIRS - the DUMB include directory 6 | # DUMB_LIBRARIES - Link these to use DUMB 7 | # 8 | # Copyright © 2006 Wengo 9 | # Copyright © 2009 Guillaume Martres 10 | # 11 | # Redistribution and use is allowed according to the terms of the New 12 | # BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | # 15 | 16 | find_path(DUMB_INCLUDE_DIR NAMES dumb.h) 17 | 18 | find_library(DUMB_LIBRARY NAMES dumb) 19 | 20 | INCLUDE(FindPackageHandleStandardArgs) 21 | # handle the QUIETLY and REQUIRED arguments and set DUMB_FOUND to TRUE if all 22 | # listed variables are TRUE 23 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(DUMB DEFAULT_MSG DUMB_LIBRARY DUMB_INCLUDE_DIR) 24 | 25 | if(DUMB_FOUND) 26 | set(DUMB_INCLUDE_DIRS ${DUMB_INCLUDE_DIR}) 27 | set(DUMB_LIBRARIES ${DUMB_LIBRARY}) 28 | endif(DUMB_FOUND) 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | addons: 4 | apt: 5 | sources: 6 | - ubuntu-toolchain-r-test 7 | packages: 8 | - g++-4.9 9 | - libopenal-dev 10 | - libvorbis-dev 11 | - libopusfile-dev 12 | - libsndfile1-dev 13 | - libphysfs-dev 14 | - libdumb1-dev 15 | homebrew: 16 | packages: 17 | - openal-soft 18 | - libvorbis 19 | - opusfile 20 | - libsndfile 21 | - physfs 22 | - dumb 23 | update: true 24 | 25 | git: 26 | depth: 1 27 | 28 | jobs: 29 | include: 30 | - os: osx 31 | script: 32 | - cd build 33 | - OPENALDIR=`brew --prefix openal-soft` cmake -DCMAKE_FIND_FRAMEWORK=NEVER .. 34 | - cmake --build . --parallel `sysctl -n hw.ncpu` 35 | - os: osx 36 | env: NPROC=`sysctl -n hw.ncpu` 37 | - os: linux 38 | env: CC=gcc-4.9 CXX=g++-4.9 NPROC=`nproc` 39 | 40 | script: 41 | - cd build 42 | - cmake .. 43 | - cmake --build . --parallel $NPROC 44 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 4 | GEN: "Visual Studio 14 2015" 5 | ARCH: x64 6 | CFG: Release 7 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 8 | GEN: "Visual Studio 15 2017" 9 | ARCH: x64 10 | CFG: Release 11 | 12 | install: 13 | # Remove the VS Xamarin targets to reduce AppVeyor specific noise in build 14 | # logs. See: http://help.appveyor.com/discussions/problems/4569 15 | - del "C:\Program Files (x86)\MSBuild\14.0\Microsoft.Common.targets\ImportAfter\Xamarin.Common.targets" 16 | - curl "https://openal-soft.org/openal-binaries/openal-soft-1.20.1-bin.zip" -o openal-soft-1.20.1-bin.zip 17 | - 7z x -o%APPVEYOR_BUILD_FOLDER%\.. openal-soft-1.20.1-bin.zip 18 | - set OPENALDIR=%APPVEYOR_BUILD_FOLDER%\..\openal-soft-1.20.1-bin 19 | 20 | build_script: 21 | - cmd: | 22 | cd build 23 | cmake -G"%GEN%" -A %ARCH% .. 24 | cmake --build . --config %CFG% --clean-first 25 | -------------------------------------------------------------------------------- /cmake/FindOgg.cmake: -------------------------------------------------------------------------------- 1 | # - FindOgg.cmake 2 | # Find the native ogg includes and libraries 3 | # 4 | # OGG_INCLUDE_DIRS - where to find ogg/ogg.h, etc. 5 | # OGG_LIBRARIES - List of libraries when using ogg. 6 | # OGG_FOUND - True if ogg found. 7 | 8 | if(OGG_INCLUDE_DIR AND OGG_LIBRARY) 9 | # Already in cache, be silent 10 | set(OGG_FIND_QUIETLY TRUE) 11 | endif(OGG_INCLUDE_DIR AND OGG_LIBRARY) 12 | 13 | find_path(OGG_INCLUDE_DIR ogg/ogg.h) 14 | 15 | # MSVC built ogg may be named ogg_static. 16 | # The provided project files name the library with the lib prefix. 17 | find_library(OGG_LIBRARY NAMES ogg ogg_static libogg libogg_static) 18 | 19 | # Handle the QUIETLY and REQUIRED arguments and set OGG_FOUND 20 | # to TRUE if all listed variables are TRUE. 21 | include(FindPackageHandleStandardArgs) 22 | find_package_handle_standard_args(OGG DEFAULT_MSG OGG_LIBRARY OGG_INCLUDE_DIR) 23 | 24 | if(OGG_FOUND) 25 | set(OGG_LIBRARIES ${OGG_LIBRARY}) 26 | set(OGG_INCLUDE_DIRS ${OGG_INCLUDE_DIR}) 27 | endif(OGG_FOUND) 28 | -------------------------------------------------------------------------------- /include/mpark/in_place.hpp: -------------------------------------------------------------------------------- 1 | // MPark.Variant 2 | // 3 | // Copyright Michael Park, 2015-2017 4 | // 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) 7 | 8 | #ifndef MPARK_IN_PLACE_HPP 9 | #define MPARK_IN_PLACE_HPP 10 | 11 | #include 12 | 13 | #include "config.hpp" 14 | 15 | namespace mpark { 16 | 17 | struct in_place_t { explicit in_place_t() = default; }; 18 | 19 | template 20 | struct in_place_index_t { explicit in_place_index_t() = default; }; 21 | 22 | template 23 | struct in_place_type_t { explicit in_place_type_t() = default; }; 24 | 25 | #ifdef MPARK_VARIABLE_TEMPLATES 26 | constexpr in_place_t in_place{}; 27 | 28 | template constexpr in_place_index_t in_place_index{}; 29 | 30 | template constexpr in_place_type_t in_place_type{}; 31 | #endif 32 | 33 | } // namespace mpark 34 | 35 | #endif // MPARK_IN_PLACE_HPP 36 | -------------------------------------------------------------------------------- /src/auxeffectslot.h: -------------------------------------------------------------------------------- 1 | #ifndef AUXEFFECTSLOT_H 2 | #define AUXEFFECTSLOT_H 3 | 4 | #include 5 | 6 | #include "main.h" 7 | 8 | namespace alure { 9 | 10 | class AuxiliaryEffectSlotImpl { 11 | ContextImpl &mContext; 12 | ALuint mId{0}; 13 | 14 | Vector mSourceSends; 15 | 16 | public: 17 | AuxiliaryEffectSlotImpl(ContextImpl &context); 18 | ~AuxiliaryEffectSlotImpl(); 19 | 20 | void addSourceSend(SourceSend source_send); 21 | void removeSourceSend(SourceSend source_send); 22 | 23 | ContextImpl &getContext() { return mContext; } 24 | const ALuint &getId() const { return mId; } 25 | 26 | void setGain(ALfloat gain); 27 | void setSendAuto(bool sendauto); 28 | 29 | void applyEffect(Effect effect); 30 | 31 | void destroy(); 32 | 33 | Vector getSourceSends() const { return mSourceSends; } 34 | 35 | size_t getUseCount() const { return mSourceSends.size(); } 36 | }; 37 | 38 | } // namespace alure 39 | 40 | #endif /* AUXEFFECTSLOT_H */ 41 | -------------------------------------------------------------------------------- /cmake/FindSndFile.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find SndFile 2 | # Once done this will define 3 | # 4 | # SNDFILE_FOUND - system has SndFile 5 | # SNDFILE_INCLUDE_DIRS - the SndFile include directory 6 | # SNDFILE_LIBRARIES - Link these to use SndFile 7 | # 8 | # Copyright © 2006 Wengo 9 | # Copyright © 2009 Guillaume Martres 10 | # 11 | # Redistribution and use is allowed according to the terms of the New 12 | # BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | # 15 | 16 | find_path(SNDFILE_INCLUDE_DIR NAMES sndfile.h) 17 | 18 | find_library(SNDFILE_LIBRARY NAMES sndfile sndfile-1) 19 | 20 | set(SNDFILE_INCLUDE_DIRS ${SNDFILE_INCLUDE_DIR}) 21 | set(SNDFILE_LIBRARIES ${SNDFILE_LIBRARY}) 22 | 23 | INCLUDE(FindPackageHandleStandardArgs) 24 | # handle the QUIETLY and REQUIRED arguments and set SNDFILE_FOUND to TRUE if 25 | # all listed variables are TRUE 26 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR) 27 | 28 | # show the SNDFILE_INCLUDE_DIRS and SNDFILE_LIBRARIES variables only in the advanced view 29 | mark_as_advanced(SNDFILE_INCLUDE_DIRS SNDFILE_LIBRARIES) 30 | -------------------------------------------------------------------------------- /cmake/FindOpus.cmake: -------------------------------------------------------------------------------- 1 | # - FindOpus.cmake 2 | # Find the native opus includes and libraries 3 | # 4 | # OPUS_INCLUDE_DIRS - where to find opus/opus.h, etc. 5 | # OPUS_LIBRARIES - List of libraries when using libopus(file). 6 | # OPUS_FOUND - True if libopus found. 7 | 8 | if(OPUS_INCLUDE_DIR AND OPUS_LIBRARY AND OPUSFILE_LIBRARY) 9 | # Already in cache, be silent 10 | set(OPUS_FIND_QUIETLY TRUE) 11 | endif(OPUS_INCLUDE_DIR AND OPUS_LIBRARY AND OPUSFILE_LIBRARY) 12 | 13 | find_path(OPUS_INCLUDE_DIR 14 | NAMES opusfile.h 15 | PATH_SUFFIXES opus 16 | ) 17 | 18 | # MSVC built opus may be named opus_static 19 | # The provided project files name the library with the lib prefix. 20 | find_library(OPUS_LIBRARY 21 | NAMES opus opus_static libopus libopus_static 22 | ) 23 | find_library(OPUSFILE_LIBRARY 24 | NAMES opusfile opusfile_static libopusfile libopusfile_static 25 | ) 26 | 27 | # Handle the QUIETLY and REQUIRED arguments and set OPUS_FOUND 28 | # to TRUE if all listed variables are TRUE. 29 | include(FindPackageHandleStandardArgs) 30 | find_package_handle_standard_args(OPUS DEFAULT_MSG 31 | OPUSFILE_LIBRARY OPUS_LIBRARY OPUS_INCLUDE_DIR 32 | ) 33 | 34 | if(OPUS_FOUND) 35 | set(OPUS_LIBRARIES ${OPUSFILE_LIBRARY} ${OPUS_LIBRARY}) 36 | set(OPUS_INCLUDE_DIRS ${OPUS_INCLUDE_DIR}) 37 | endif(OPUS_FOUND) 38 | -------------------------------------------------------------------------------- /cmake/FindVorbis.cmake: -------------------------------------------------------------------------------- 1 | # - FindVorbis.cmake 2 | # Find the native vorbis includes and libraries 3 | # 4 | # VORBIS_INCLUDE_DIRS - where to find vorbis/vorbis.h, etc. 5 | # VORBIS_LIBRARIES - List of libraries when using vorbis(file). 6 | # VORBIS_FOUND - True if vorbis found. 7 | 8 | if(VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY) 9 | # Already in cache, be silent 10 | set(VORBIS_FIND_QUIETLY TRUE) 11 | endif(VORBIS_INCLUDE_DIR AND VORBIS_LIBRARY AND VORBISFILE_LIBRARY) 12 | 13 | find_path(VORBIS_INCLUDE_DIR vorbis/vorbisfile.h) 14 | 15 | # MSVC built vorbis may be named vorbis_static 16 | # The provided project files name the library with the lib prefix. 17 | find_library(VORBIS_LIBRARY 18 | NAMES vorbis vorbis_static libvorbis libvorbis_static 19 | ) 20 | find_library(VORBISFILE_LIBRARY 21 | NAMES vorbisfile vorbisfile_static libvorbisfile libvorbisfile_static 22 | ) 23 | 24 | # Handle the QUIETLY and REQUIRED arguments and set VORBIS_FOUND 25 | # to TRUE if all listed variables are TRUE. 26 | include(FindPackageHandleStandardArgs) 27 | find_package_handle_standard_args(VORBIS DEFAULT_MSG 28 | VORBISFILE_LIBRARY VORBIS_LIBRARY VORBIS_INCLUDE_DIR 29 | ) 30 | 31 | if(VORBIS_FOUND) 32 | set(VORBIS_LIBRARIES ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY}) 33 | set(VORBIS_INCLUDE_DIRS ${VORBIS_INCLUDE_DIR}) 34 | endif(VORBIS_FOUND) 35 | -------------------------------------------------------------------------------- /include/mpark/LICENSE.md: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | AccessModifierOffset: '-4' 4 | AlignConsecutiveDeclarations: 'false' 5 | AlignEscapedNewlinesLeft: 'false' 6 | AlignOperands: 'true' 7 | AlignTrailingComments: 'false' 8 | AllowAllParametersOfDeclarationOnNextLine: 'true' 9 | AllowShortBlocksOnASingleLine: 'false' 10 | AllowShortCaseLabelsOnASingleLine: 'true' 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: 'true' 13 | AlwaysBreakAfterReturnType: None 14 | AlwaysBreakBeforeMultilineStrings: 'true' 15 | AlwaysBreakTemplateDeclarations: 'true' 16 | BinPackArguments: 'true' 17 | BinPackParameters: 'true' 18 | BreakBeforeBinaryOperators: None 19 | BreakBeforeBraces: Custom 20 | BraceWrapping: 21 | AfterClass: 'false' 22 | AfterControlStatement: 'true' 23 | AfterEnum: 'false' 24 | AfterFunction: 'true' 25 | AfterNamespace: 'false' 26 | AfterStruct: 'false' 27 | AfterUnion: 'false' 28 | BeforeCatch: 'true' 29 | BeforeElse: 'true' 30 | IndentBraces: 'false' 31 | BreakConstructorInitializersBeforeComma: 'true' 32 | ColumnLimit: '99' 33 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 34 | ConstructorInitializerIndentWidth: '2' 35 | ContinuationIndentWidth: '4' 36 | Cpp11BracedListStyle: 'true' 37 | DerivePointerAlignment: 'true' 38 | IndentCaseLabels: 'true' 39 | IndentWidth: '4' 40 | KeepEmptyLinesAtTheStartOfBlocks: 'true' 41 | Language: Cpp 42 | MaxEmptyLinesToKeep: '2' 43 | NamespaceIndentation: Inner 44 | PointerAlignment: Right 45 | ReflowComments: 'true' 46 | SortIncludes: 'true' 47 | SpaceAfterCStyleCast: 'false' 48 | SpaceBeforeAssignmentOperators: 'true' 49 | SpaceBeforeParens: Never 50 | SpaceInEmptyParentheses: 'false' 51 | SpacesBeforeTrailingComments: '1' 52 | SpacesInAngles: 'false' 53 | SpacesInCStyleCastParentheses: 'false' 54 | SpacesInParentheses: 'false' 55 | SpacesInSquareBrackets: 'false' 56 | Standard: Cpp14 57 | TabWidth: '4' 58 | UseTab: Never 59 | 60 | ... 61 | -------------------------------------------------------------------------------- /src/device.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVICE_H 2 | #define DEVICE_H 3 | 4 | #include 5 | 6 | #include "main.h" 7 | 8 | 9 | namespace alure { 10 | 11 | enum class ALC { 12 | ENUMERATE_ALL_EXT, 13 | EXT_EFX, 14 | EXT_thread_local_context, 15 | SOFT_HRTF, 16 | SOFT_pause_device, 17 | 18 | EXTENSION_MAX 19 | }; 20 | 21 | class DeviceImpl { 22 | ALCdevice *mDevice{nullptr}; 23 | 24 | std::chrono::nanoseconds mTimeBase, mPauseTime; 25 | bool mIsPaused{false}; 26 | 27 | Vector> mContexts; 28 | 29 | Bitfield(ALC::EXTENSION_MAX)> mHasExt; 30 | 31 | std::once_flag mSetExts; 32 | void setupExts(); 33 | 34 | public: 35 | DeviceImpl(const char *name); 36 | ~DeviceImpl(); 37 | 38 | ALCdevice *getALCdevice() const { return mDevice; } 39 | 40 | bool hasExtension(ALC ext) const { return mHasExt[static_cast(ext)]; } 41 | 42 | LPALCDEVICEPAUSESOFT alcDevicePauseSOFT{nullptr}; 43 | LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{nullptr}; 44 | 45 | LPALCGETSTRINGISOFT alcGetStringiSOFT{nullptr}; 46 | LPALCRESETDEVICESOFT alcResetDeviceSOFT{nullptr}; 47 | 48 | void removeContext(ContextImpl *ctx); 49 | 50 | String getName(PlaybackName type) const; 51 | bool queryExtension(const char *name) const; 52 | 53 | Version getALCVersion() const; 54 | Version getEFXVersion() const; 55 | 56 | ALCuint getFrequency() const; 57 | 58 | ALCuint getMaxAuxiliarySends() const; 59 | 60 | Vector enumerateHRTFNames() const; 61 | bool isHRTFEnabled() const; 62 | String getCurrentHRTF() const; 63 | void reset(ArrayView attributes); 64 | 65 | Context createContext(ArrayView attributes); 66 | 67 | void pauseDSP(); 68 | void resumeDSP(); 69 | 70 | std::chrono::nanoseconds getClockTime(); 71 | 72 | void close(); 73 | }; 74 | 75 | } // namespace alure 76 | 77 | #endif /* DEVICE_H */ 78 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | 4 | #include 5 | 6 | #include "main.h" 7 | 8 | namespace alure { 9 | 10 | ALenum GetFormat(ChannelConfig chans, SampleType type); 11 | 12 | class BufferImpl { 13 | ContextImpl &mContext; 14 | ALuint mId; 15 | 16 | ALuint mFrequency; 17 | ChannelConfig mChannelConfig; 18 | SampleType mSampleType; 19 | 20 | Vector mSources; 21 | 22 | const String mName; 23 | size_t mNameHash; 24 | 25 | public: 26 | BufferImpl(ContextImpl &context, ALuint id, ALuint freq, ChannelConfig config, SampleType type, 27 | StringView name, size_t name_hash) 28 | : mContext(context), mId(id), mFrequency(freq), mChannelConfig(config), mSampleType(type) 29 | , mName(String(name)), mNameHash(name_hash) 30 | { } 31 | 32 | void cleanup(); 33 | 34 | ContextImpl &getContext() { return mContext; } 35 | ALuint getId() const { return mId; } 36 | 37 | void addSource(Source source) { mSources.push_back(source); } 38 | void removeSource(Source source) 39 | { 40 | auto iter = std::find(mSources.cbegin(), mSources.cend(), source); 41 | if(iter != mSources.cend()) mSources.erase(iter); 42 | } 43 | 44 | void load(ALuint frames, ALenum format, SharedPtr decoder, ContextImpl *ctx); 45 | 46 | ALuint getLength() const; 47 | 48 | ALuint getFrequency() const { return mFrequency; } 49 | ChannelConfig getChannelConfig() const { return mChannelConfig; } 50 | SampleType getSampleType() const { return mSampleType; } 51 | 52 | ALuint getSize() const; 53 | 54 | void setLoopPoints(ALuint start, ALuint end); 55 | std::pair getLoopPoints() const; 56 | 57 | Vector getSources() const { return mSources; } 58 | 59 | StringView getName() const { return mName; } 60 | 61 | size_t getSourceCount() const { return mSources.size(); } 62 | 63 | size_t getNameHash() const { return mNameHash; } 64 | }; 65 | 66 | } // namespace alure 67 | 68 | #endif /* BUFFER_H */ 69 | -------------------------------------------------------------------------------- /include/mpark/config.hpp: -------------------------------------------------------------------------------- 1 | // MPark.Variant 2 | // 3 | // Copyright Michael Park, 2015-2017 4 | // 5 | // Distributed under the Boost Software License, Version 1.0. 6 | // (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt) 7 | 8 | #ifndef MPARK_CONFIG_HPP 9 | #define MPARK_CONFIG_HPP 10 | 11 | // MSVC 2015 Update 3. 12 | #if __cplusplus < 201103L && (!defined(_MSC_VER) || _MSC_FULL_VER < 190024210) 13 | #error "MPark.Variant requires C++11 support." 14 | #endif 15 | 16 | #ifndef __has_builtin 17 | #define __has_builtin(x) 0 18 | #endif 19 | 20 | #ifndef __has_include 21 | #define __has_include(x) 0 22 | #endif 23 | 24 | #ifndef __has_feature 25 | #define __has_feature(x) 0 26 | #endif 27 | 28 | #if __has_builtin(__builtin_addressof) || \ 29 | (defined(__GNUC__) && __GNUC__ >= 7) || defined(_MSC_VER) 30 | #define MPARK_BUILTIN_ADDRESSOF 31 | #endif 32 | 33 | #if __has_builtin(__builtin_unreachable) 34 | #define MPARK_BUILTIN_UNREACHABLE 35 | #endif 36 | 37 | #if __has_builtin(__type_pack_element) 38 | #define MPARK_TYPE_PACK_ELEMENT 39 | #endif 40 | 41 | #if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 42 | #define MPARK_CPP14_CONSTEXPR 43 | #endif 44 | 45 | #if __has_feature(cxx_exceptions) || defined(__cpp_exceptions) || \ 46 | (defined(_MSC_VER) && defined(_CPPUNWIND)) 47 | #define MPARK_EXCEPTIONS 48 | #endif 49 | 50 | #if defined(__cpp_generic_lambdas) || defined(_MSC_VER) 51 | #define MPARK_GENERIC_LAMBDAS 52 | #endif 53 | 54 | #if defined(__cpp_lib_integer_sequence) 55 | #define MPARK_INTEGER_SEQUENCE 56 | #endif 57 | 58 | #if defined(__cpp_return_type_deduction) || defined(_MSC_VER) 59 | #define MPARK_RETURN_TYPE_DEDUCTION 60 | #endif 61 | 62 | #if defined(__cpp_lib_transparent_operators) || defined(_MSC_VER) 63 | #define MPARK_TRANSPARENT_OPERATORS 64 | #endif 65 | 66 | #if defined(__cpp_variable_templates) || defined(_MSC_VER) 67 | #define MPARK_VARIABLE_TEMPLATES 68 | #endif 69 | 70 | #if !defined(__GLIBCXX__) || __has_include() // >= libstdc++-5 71 | #define MPARK_TRIVIALITY_TYPE_TRAITS 72 | #endif 73 | 74 | #endif // MPARK_CONFIG_HPP 75 | -------------------------------------------------------------------------------- /examples/alure-enum.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * An example showing how to enumerate available devices and show its 3 | * capabilities. 4 | */ 5 | 6 | #include 7 | 8 | #include "alure2.h" 9 | 10 | int main(int argc, char *argv[]) 11 | { 12 | alure::DeviceManager devMgr = alure::DeviceManager::getInstance(); 13 | alure::Vector list; 14 | alure::String defname; 15 | 16 | list = devMgr.enumerate(alure::DeviceEnumeration::Basic); 17 | defname = devMgr.defaultDeviceName(alure::DefaultDeviceType::Basic); 18 | std::cout<< "Available basic devices:\n"; 19 | for(const auto &name : list) 20 | std::cout<< " "< 1) ? argv[1] : ""); 38 | std::cout<< "Info for device \""< mSources; 21 | Vector mSubGroups; 22 | 23 | SourceGroupProps mParentProps; 24 | SourceGroupImpl *mParent; 25 | 26 | void update(ALfloat gain, ALfloat pitch); 27 | 28 | void unsetParent(); 29 | 30 | void insertSubGroup(SourceGroupImpl *group); 31 | void eraseSubGroup(SourceGroupImpl *group); 32 | 33 | bool findInSubGroups(SourceGroupImpl *group) const; 34 | 35 | void collectPlayingSourceIds(Vector &sourceids) const; 36 | void updatePausedStatus() const; 37 | 38 | void collectPausedSourceIds(Vector &sourceids) const; 39 | void updatePlayingStatus() const; 40 | 41 | void collectSourceIds(Vector &sourceids) const; 42 | void updateStoppedStatus() const; 43 | 44 | public: 45 | SourceGroupImpl(ContextImpl &context) : mContext(context), mParent(nullptr) { } 46 | 47 | ALfloat getAppliedGain() const { return mGain * mParentProps.mGain; } 48 | ALfloat getAppliedPitch() const { return mPitch * mParentProps.mPitch; } 49 | 50 | void insertSource(SourceImpl *source); 51 | void eraseSource(SourceImpl *source); 52 | 53 | void setParentGroup(SourceGroup group); 54 | SourceGroup getParentGroup() const { return SourceGroup(mParent); } 55 | 56 | Vector getSources() const; 57 | 58 | Vector getSubGroups() const; 59 | 60 | void setGain(ALfloat gain); 61 | ALfloat getGain() const { return mGain; } 62 | 63 | void setPitch(ALfloat pitch); 64 | ALfloat getPitch() const { return mPitch; } 65 | 66 | void pauseAll() const; 67 | void resumeAll() const; 68 | 69 | void stopAll() const; 70 | 71 | void destroy(); 72 | }; 73 | 74 | } // namespace alure2 75 | 76 | #endif /* SOURCEGROUP_H */ 77 | -------------------------------------------------------------------------------- /examples/alure-play.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple example showing how to load and play a sound. 3 | */ 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "alure2.h" 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | alure::ArrayView args(argv, argc); 17 | 18 | if(args.size() < 2) 19 | { 20 | std::cerr<< "Usage: "< 2 && args[0] == alure::StringView("-device")) 29 | { 30 | dev = devMgr.openPlayback(args[1], std::nothrow); 31 | if(!dev) std::cerr<< "Failed to open \""< 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "alure2.h" 14 | 15 | #ifndef M_PI 16 | #define M_PI (3.14159265358979323846) 17 | #endif 18 | 19 | 20 | int main(int argc, char *argv[]) 21 | { 22 | alure::ArrayView args(argv, argc); 23 | 24 | if(args.size() < 2) 25 | { 26 | std::cerr<< "Usage: "< 2 && args[0] == alure::StringView("-device")) 35 | { 36 | dev = devMgr.openPlayback(args[1], std::nothrow); 37 | if(!dev) std::cerr<< "Failed to open \""< M_PI) 74 | angle -= M_PI*2.0; 75 | source.setPosition({(float)sin(angle), 0.0f, -(float)cos(angle)}); 76 | 77 | ctx.update(); 78 | } 79 | std::cout< 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "alure2.h" 13 | 14 | 15 | namespace { 16 | 17 | // Helper class+method to print the time with human-readable formatting. 18 | struct PrettyTime { 19 | alure::Seconds mTime; 20 | }; 21 | inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) 22 | { 23 | using hours = std::chrono::hours; 24 | using minutes = std::chrono::minutes; 25 | using seconds = std::chrono::seconds; 26 | using centiseconds = std::chrono::duration>; 27 | using std::chrono::duration_cast; 28 | 29 | centiseconds t = duration_cast(rhs.mTime); 30 | if(t.count() < 0) 31 | { 32 | os << '-'; 33 | t *= -1; 34 | } 35 | 36 | // Only handle up to hour formatting 37 | if(t >= hours(1)) 38 | os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) 39 | << duration_cast(t).count() << 'm'; 40 | else 41 | os << duration_cast(t).count() << 'm' << std::setfill('0'); 42 | os << std::setw(2) << (duration_cast(t).count() % 60) << '.' << std::setw(2) 43 | << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' '); 44 | return os; 45 | } 46 | 47 | } // namespace 48 | 49 | int main(int argc, char *argv[]) 50 | { 51 | alure::ArrayView args(argv, argc); 52 | 53 | if(args.size() < 2) 54 | { 55 | std::cerr<< "Usage: "< 2 && args[0] == alure::StringView("-device")) 64 | { 65 | dev = devMgr.openPlayback(args[1], std::nothrow); 66 | if(!dev) std::cerr<< "Failed to open \""< decoder = ctx.createDecoder(args.front()); 78 | alure::Source source = ctx.createSource(); 79 | 80 | source.play(decoder, 12000, 4); 81 | std::cout<< "Playing "<getSampleType())<<", " 83 | << alure::GetChannelConfigName(decoder->getChannelConfig())<<", " 84 | << decoder->getFrequency()<<"hz)" <getFrequency(); 87 | while(source.isPlaying()) 88 | { 89 | std::cout<< "\r "<getLength()*invfreq)}; 91 | std::cout.flush(); 92 | std::this_thread::sleep_for(std::chrono::milliseconds(25)); 93 | ctx.update(); 94 | } 95 | std::cout< 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace alure { 20 | 21 | // Convenience aliases 22 | template using RemoveRefT = typename std::remove_reference::type; 23 | template using EnableIfT = typename std::enable_if::type; 24 | 25 | 26 | // NOTE: Need to define this as a macro since we can't use the aliased type 27 | // names for explicit template instantiation, and the whole purpose of these 28 | // aliases is to avoid respecifying the desired implementation. 29 | #define ALURE_SHARED_PTR_TYPE std::shared_ptr 30 | 31 | 32 | // Duration in seconds, using double precision 33 | using Seconds = std::chrono::duration; 34 | 35 | // A SharedPtr implementation, defaults to C++11's std::shared_ptr. 36 | template using SharedPtr = ALURE_SHARED_PTR_TYPE; 37 | template 38 | inline SharedPtr MakeShared(Args&&... args) 39 | { return std::make_shared(std::forward(args)...); } 40 | 41 | // A WeakPtr implementation, defaults to C++11's std::weak_ptr. 42 | template using WeakPtr = std::weak_ptr; 43 | 44 | // A UniquePtr implementation, defaults to C++11's std::unique_ptr. 45 | template using UniquePtr = std::unique_ptr; 46 | // Implement MakeUnique for single objects and arrays. 47 | namespace _details { 48 | template 49 | struct MakeUniq { using object = UniquePtr; }; 50 | template 51 | struct MakeUniq { using array = UniquePtr; }; 52 | template 53 | struct MakeUniq { struct invalid_type { }; }; 54 | } // namespace _details 55 | // MakeUnique for a single object. 56 | template 57 | inline typename _details::MakeUniq::object MakeUnique(Args&&... args) 58 | { return UniquePtr(new T(std::forward(args)...)); } 59 | // MakeUnique for an array. 60 | template 61 | inline typename _details::MakeUniq::array MakeUnique(std::size_t num) 62 | { return UniquePtr(new typename std::remove_extent::type[num]()); } 63 | // Disable MakeUnique for an array of declared size. 64 | template 65 | inline typename _details::MakeUniq::invalid_type MakeUnique(Args&&...) = delete; 66 | 67 | // A Promise/Future (+SharedFuture) implementation, defaults to C++11's 68 | // std::promise, std::future, and std::shared_future. 69 | template using Promise = std::promise; 70 | template using Future = std::future; 71 | template using SharedFuture = std::shared_future; 72 | 73 | // A Vector implementation, defaults to C++'s std::vector. 74 | template using Vector = std::vector; 75 | 76 | // A static-sized Array implementation, defaults to C++11's std::array. 77 | template using Array = std::array; 78 | 79 | // A String implementation, default's to C++'s std::string. 80 | template using BasicString = std::basic_string; 81 | using String = BasicString; 82 | 83 | } // namespace alure 84 | 85 | #endif /* AL_ALURE2_ALIASES_H */ 86 | -------------------------------------------------------------------------------- /src/auxeffectslot.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "config.h" 3 | 4 | #include "auxeffectslot.h" 5 | 6 | #include 7 | 8 | #include "context.h" 9 | #include "effect.h" 10 | 11 | namespace alure { 12 | 13 | static inline bool operator<(const SourceSend &lhs, const SourceSend &rhs) 14 | { return lhs.mSource < rhs.mSource || (lhs.mSource == rhs.mSource && lhs.mSend < rhs.mSend); } 15 | static inline bool operator==(const SourceSend &lhs, const SourceSend &rhs) 16 | { return lhs.mSource == rhs.mSource && lhs.mSend == rhs.mSend; } 17 | static inline bool operator!=(const SourceSend &lhs, const SourceSend &rhs) 18 | { return !(lhs == rhs); } 19 | 20 | 21 | AuxiliaryEffectSlotImpl::AuxiliaryEffectSlotImpl(ContextImpl &context) : mContext(context) 22 | { 23 | alGetError(); 24 | mContext.alGenAuxiliaryEffectSlots(1, &mId); 25 | throw_al_error("Failed to create AuxiliaryEffectSlot"); 26 | } 27 | 28 | AuxiliaryEffectSlotImpl::~AuxiliaryEffectSlotImpl() 29 | { 30 | if(UNLIKELY(mId != 0) && alcGetCurrentContext() == mContext.getALCcontext()) 31 | { 32 | mContext.alDeleteAuxiliaryEffectSlots(1, &mId); 33 | mId = 0; 34 | } 35 | } 36 | 37 | void AuxiliaryEffectSlotImpl::addSourceSend(SourceSend source_send) 38 | { 39 | auto iter = std::lower_bound(mSourceSends.begin(), mSourceSends.end(), source_send); 40 | if(iter == mSourceSends.end() || *iter != source_send) 41 | mSourceSends.insert(iter, source_send); 42 | } 43 | 44 | void AuxiliaryEffectSlotImpl::removeSourceSend(SourceSend source_send) 45 | { 46 | auto iter = std::lower_bound(mSourceSends.begin(), mSourceSends.end(), source_send); 47 | if(iter != mSourceSends.end() && *iter == source_send) 48 | mSourceSends.erase(iter); 49 | } 50 | 51 | 52 | DECL_THUNK1(void, AuxiliaryEffectSlot, setGain,, ALfloat) 53 | void AuxiliaryEffectSlotImpl::setGain(ALfloat gain) 54 | { 55 | if(!(gain >= 0.0f && gain <= 1.0f)) 56 | throw std::domain_error("Gain out of range"); 57 | CheckContext(mContext); 58 | mContext.alAuxiliaryEffectSlotf(mId, AL_EFFECTSLOT_GAIN, gain); 59 | } 60 | 61 | DECL_THUNK1(void, AuxiliaryEffectSlot, setSendAuto,, bool) 62 | void AuxiliaryEffectSlotImpl::setSendAuto(bool sendauto) 63 | { 64 | CheckContext(mContext); 65 | mContext.alAuxiliaryEffectSloti(mId, AL_EFFECTSLOT_AUXILIARY_SEND_AUTO, sendauto ? AL_TRUE : AL_FALSE); 66 | } 67 | 68 | DECL_THUNK1(void, AuxiliaryEffectSlot, applyEffect,, Effect) 69 | void AuxiliaryEffectSlotImpl::applyEffect(Effect effect) 70 | { 71 | const EffectImpl *eff = effect.getHandle(); 72 | if(eff) CheckContexts(mContext, eff->getContext()); 73 | CheckContext(mContext); 74 | 75 | mContext.alAuxiliaryEffectSloti(mId, 76 | AL_EFFECTSLOT_EFFECT, eff ? eff->getId() : AL_EFFECT_NULL 77 | ); 78 | } 79 | 80 | 81 | void AuxiliaryEffectSlot::destroy() 82 | { 83 | AuxiliaryEffectSlotImpl *i = pImpl; 84 | pImpl = nullptr; 85 | i->destroy(); 86 | } 87 | void AuxiliaryEffectSlotImpl::destroy() 88 | { 89 | CheckContext(mContext); 90 | 91 | if(!mSourceSends.empty()) 92 | { 93 | Vector source_sends; 94 | source_sends.swap(mSourceSends); 95 | 96 | auto batcher = mContext.getBatcher(); 97 | for(const SourceSend &srcsend : source_sends) 98 | srcsend.mSource.getHandle()->setAuxiliarySend(nullptr, srcsend.mSend); 99 | } 100 | 101 | alGetError(); 102 | mContext.alDeleteAuxiliaryEffectSlots(1, &mId); 103 | throw_al_error("AuxiliaryEffectSlot failed to delete"); 104 | mId = 0; 105 | 106 | mContext.freeEffectSlot(this); 107 | } 108 | 109 | DECL_THUNK0(Vector, AuxiliaryEffectSlot, getSourceSends, const) 110 | DECL_THUNK0(size_t, AuxiliaryEffectSlot, getUseCount, const) 111 | 112 | } // namespace alure 113 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef ALURE_MAIN_H 2 | #define ALURE_MAIN_H 3 | 4 | #include "alure2.h" 5 | 6 | #include 7 | #if __cplusplus >= 201703L 8 | #include 9 | #else 10 | #include "mpark/variant.hpp" 11 | 12 | namespace std { 13 | using mpark::variant; 14 | using mpark::monostate; 15 | using mpark::get; 16 | using mpark::get_if; 17 | using mpark::holds_alternative; 18 | } // namespace std 19 | #endif 20 | 21 | #ifdef __GNUC__ 22 | #define LIKELY(x) __builtin_expect(static_cast(x), true) 23 | #define UNLIKELY(x) __builtin_expect(static_cast(x), false) 24 | #else 25 | #define LIKELY(x) static_cast(x) 26 | #define UNLIKELY(x) static_cast(x) 27 | #endif 28 | 29 | #define DECL_THUNK0(ret, C, Name, cv) \ 30 | ret C::Name() cv { return pImpl->Name(); } 31 | #define DECL_THUNK1(ret, C, Name, cv, T1) \ 32 | ret C::Name(T1 a) cv { return pImpl->Name(std::forward(a)); } 33 | #define DECL_THUNK2(ret, C, Name, cv, T1, T2) \ 34 | ret C::Name(T1 a, T2 b) cv \ 35 | { return pImpl->Name(std::forward(a), std::forward(b)); } 36 | #define DECL_THUNK3(ret, C, Name, cv, T1, T2, T3) \ 37 | ret C::Name(T1 a, T2 b, T3 c) cv \ 38 | { \ 39 | return pImpl->Name(std::forward(a), std::forward(b), \ 40 | std::forward(c)); \ 41 | } 42 | 43 | 44 | namespace alure { 45 | 46 | // Need to use these to avoid extraneous commas in macro parameter lists 47 | using Vector3Pair = std::pair; 48 | using UInt64NSecPair = std::pair; 49 | using SecondsPair = std::pair; 50 | using ALfloatPair = std::pair; 51 | using ALuintPair = std::pair; 52 | using BoolTriple = std::tuple; 53 | 54 | 55 | template 56 | inline std::future_status GetFutureState(const SharedFuture &future) 57 | { return future.wait_for(std::chrono::seconds::zero()); } 58 | 59 | // This variant is a poor man's optional 60 | std::variant ParseTimeval(StringView strval, double srate) noexcept; 61 | 62 | template 63 | struct Bitfield { 64 | private: 65 | Array mElems; 66 | 67 | public: 68 | Bitfield() { std::fill(mElems.begin(), mElems.end(), 0); } 69 | 70 | bool operator[](size_t i) const noexcept 71 | { return static_cast(mElems[i/8] & (1<<(i%8))); } 72 | 73 | void set(size_t i) noexcept { mElems[i/8] |= 1<<(i%8); } 74 | }; 75 | 76 | 77 | class alc_category : public std::error_category { 78 | alc_category() noexcept { } 79 | 80 | public: 81 | static alc_category sSingleton; 82 | 83 | const char *name() const noexcept override final { return "alc_category"; } 84 | std::error_condition default_error_condition(int code) const noexcept override final 85 | { return std::error_condition(code, *this); } 86 | 87 | bool equivalent(int code, const std::error_condition &condition) const noexcept override final 88 | { return default_error_condition(code) == condition; } 89 | bool equivalent(const std::error_code &code, int condition) const noexcept override final 90 | { return *this == code.category() && code.value() == condition; } 91 | 92 | std::string message(int condition) const override final; 93 | }; 94 | template 95 | inline std::system_error alc_error(int code, T&& what) 96 | { return std::system_error(code, alc_category::sSingleton, std::forward(what)); } 97 | inline std::system_error alc_error(int code) 98 | { return std::system_error(code, alc_category::sSingleton); } 99 | 100 | class al_category : public std::error_category { 101 | al_category() noexcept { } 102 | 103 | public: 104 | static al_category sSingleton; 105 | 106 | const char *name() const noexcept override final { return "al_category"; } 107 | std::error_condition default_error_condition(int code) const noexcept override final 108 | { return std::error_condition(code, *this); } 109 | 110 | bool equivalent(int code, const std::error_condition &condition) const noexcept override final 111 | { return default_error_condition(code) == condition; } 112 | bool equivalent(const std::error_code &code, int condition) const noexcept override final 113 | { return *this == code.category() && code.value() == condition; } 114 | 115 | std::string message(int condition) const override final; 116 | }; 117 | template 118 | inline std::system_error al_error(int code, T&& what) 119 | { return std::system_error(code, al_category::sSingleton, std::forward(what)); } 120 | inline std::system_error al_error(int code) 121 | { return std::system_error(code, al_category::sSingleton); } 122 | 123 | inline void throw_al_error(const char *str) 124 | { 125 | ALenum err = alGetError(); 126 | if(UNLIKELY(err != AL_NO_ERROR)) 127 | throw al_error(err, str); 128 | } 129 | 130 | } // namespace alure 131 | 132 | #endif /* ALURE_MAIN_H */ 133 | -------------------------------------------------------------------------------- /src/devicemanager.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "config.h" 3 | 4 | #include "devicemanager.h" 5 | #include "device.h" 6 | #include "main.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "alc.h" 13 | #include "al.h" 14 | 15 | 16 | namespace alure { 17 | 18 | std::string alc_category::message(int condition) const 19 | { 20 | switch(condition) 21 | { 22 | case ALC_NO_ERROR: return "No error"; 23 | case ALC_INVALID_ENUM: return "Invalid enum"; 24 | case ALC_INVALID_VALUE: return "Invalid value"; 25 | case ALC_INVALID_DEVICE: return "Invalid device"; 26 | case ALC_INVALID_CONTEXT: return "Invalid context"; 27 | case ALC_OUT_OF_MEMORY: return "Out of memory"; 28 | } 29 | return "Unknown ALC error "+std::to_string(condition); 30 | } 31 | 32 | std::string al_category::message(int condition) const 33 | { 34 | switch(condition) 35 | { 36 | case AL_NO_ERROR: return "No error"; 37 | case AL_INVALID_NAME: return "Invalid name"; 38 | case AL_INVALID_ENUM: return "Invalid enum"; 39 | case AL_INVALID_VALUE: return "Invalid value"; 40 | case AL_INVALID_OPERATION: return "Invalid operation"; 41 | case AL_OUT_OF_MEMORY: return "Out of memory"; 42 | } 43 | return "Unknown AL error "+std::to_string(condition); 44 | } 45 | 46 | alc_category alc_category::sSingleton; 47 | al_category al_category::sSingleton; 48 | 49 | 50 | template 51 | static inline void GetDeviceProc(T *&func, ALCdevice *device, const char *name) 52 | { func = reinterpret_cast(alcGetProcAddress(device, name)); } 53 | 54 | 55 | WeakPtr DeviceManagerImpl::sInstance; 56 | ALCboolean (ALC_APIENTRY*DeviceManagerImpl::SetThreadContext)(ALCcontext*); 57 | 58 | DeviceManager::DeviceManager(SharedPtr&& impl) noexcept 59 | : pImpl(std::move(impl)) 60 | { } 61 | 62 | DeviceManager::~DeviceManager() { } 63 | 64 | DeviceManager DeviceManager::getInstance() 65 | { return DeviceManager(DeviceManagerImpl::getInstance()); } 66 | SharedPtr DeviceManagerImpl::getInstance() 67 | { 68 | SharedPtr ret = sInstance.lock(); 69 | if(!ret) 70 | { 71 | ret = MakeShared(); 72 | sInstance = ret; 73 | } 74 | return ret; 75 | } 76 | 77 | 78 | DeviceManagerImpl::DeviceManagerImpl() 79 | { 80 | if(alcIsExtensionPresent(nullptr, "ALC_EXT_thread_local_context")) 81 | GetDeviceProc(SetThreadContext, nullptr, "alcSetThreadContext"); 82 | } 83 | 84 | DeviceManagerImpl::~DeviceManagerImpl() 85 | { 86 | } 87 | 88 | 89 | bool DeviceManager::queryExtension(const String &name) const 90 | { return queryExtension(name.c_str()); } 91 | DECL_THUNK1(bool, DeviceManager, queryExtension, const, const char*) 92 | bool DeviceManagerImpl::queryExtension(const char *name) const 93 | { 94 | return static_cast(alcIsExtensionPresent(nullptr, name)); 95 | } 96 | 97 | DECL_THUNK1(Vector, DeviceManager, enumerate, const, DeviceEnumeration) 98 | Vector DeviceManagerImpl::enumerate(DeviceEnumeration type) const 99 | { 100 | Vector list; 101 | if(type == DeviceEnumeration::Full && !alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) 102 | type = DeviceEnumeration::Basic; 103 | const ALCchar *names = alcGetString(nullptr, (ALenum)type); 104 | while(names && *names) 105 | { 106 | list.emplace_back(names); 107 | names += strlen(names)+1; 108 | } 109 | return list; 110 | } 111 | 112 | DECL_THUNK1(String, DeviceManager, defaultDeviceName, const, DefaultDeviceType) 113 | String DeviceManagerImpl::defaultDeviceName(DefaultDeviceType type) const 114 | { 115 | if(type == DefaultDeviceType::Full && !alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) 116 | type = DefaultDeviceType::Basic; 117 | const ALCchar *name = alcGetString(nullptr, (ALenum)type); 118 | return name ? String(name) : String(); 119 | } 120 | 121 | 122 | Device DeviceManager::openPlayback(const String &name) 123 | { return openPlayback(name.c_str()); } 124 | DECL_THUNK1(Device, DeviceManager, openPlayback,, const char*) 125 | Device DeviceManagerImpl::openPlayback(const char *name) 126 | { 127 | mDevices.emplace_back(MakeUnique(name)); 128 | return Device(mDevices.back().get()); 129 | } 130 | 131 | Device DeviceManager::openPlayback(const String &name, const std::nothrow_t &nt) noexcept 132 | { return openPlayback(name.c_str(), nt); } 133 | Device DeviceManager::openPlayback(const std::nothrow_t&) noexcept 134 | { return openPlayback(nullptr, std::nothrow); } 135 | DECL_THUNK2(Device, DeviceManager, openPlayback, noexcept, const char*, const std::nothrow_t&) 136 | Device DeviceManagerImpl::openPlayback(const char *name, const std::nothrow_t&) noexcept 137 | { 138 | try { 139 | return openPlayback(name); 140 | } 141 | catch(...) { 142 | } 143 | return Device(); 144 | } 145 | 146 | void DeviceManagerImpl::removeDevice(DeviceImpl *dev) 147 | { 148 | auto iter = std::find_if(mDevices.begin(), mDevices.end(), 149 | [dev](const UniquePtr &entry) -> bool 150 | { return entry.get() == dev; } 151 | ); 152 | if(iter != mDevices.end()) mDevices.erase(iter); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/effect.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "config.h" 3 | 4 | #include "effect.h" 5 | 6 | #include 7 | 8 | #include "context.h" 9 | 10 | 11 | namespace { 12 | 13 | template 14 | inline T clamp(const T& val, const T& min, const T& max) 15 | { return std::min(std::max(val, min), max); } 16 | 17 | } // namespace 18 | 19 | namespace alure { 20 | 21 | EffectImpl::EffectImpl(ContextImpl &context) : mContext(context) 22 | { 23 | alGetError(); 24 | mContext.alGenEffects(1, &mId); 25 | throw_al_error("Failed to create Effect"); 26 | } 27 | 28 | EffectImpl::~EffectImpl() 29 | { 30 | if(UNLIKELY(mId != 0) && alcGetCurrentContext() == mContext.getALCcontext()) 31 | { 32 | mContext.alDeleteEffects(1, &mId); 33 | mId = 0; 34 | } 35 | } 36 | 37 | 38 | DECL_THUNK1(void, Effect, setReverbProperties,, const EFXEAXREVERBPROPERTIES&) 39 | void EffectImpl::setReverbProperties(const EFXEAXREVERBPROPERTIES &props) 40 | { 41 | CheckContext(mContext); 42 | 43 | if(mType != AL_EFFECT_EAXREVERB && mType != AL_EFFECT_REVERB) 44 | { 45 | alGetError(); 46 | mContext.alEffecti(mId, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); 47 | if(alGetError() == AL_NO_ERROR) 48 | mType = AL_EFFECT_EAXREVERB; 49 | else 50 | { 51 | mContext.alEffecti(mId, AL_EFFECT_TYPE, AL_EFFECT_REVERB); 52 | throw_al_error("Failed to set reverb type"); 53 | mType = AL_EFFECT_REVERB; 54 | } 55 | } 56 | 57 | if(mType == AL_EFFECT_EAXREVERB) 58 | { 59 | #define SETPARAM(e,t,v) mContext.alEffectf((e), AL_EAXREVERB_##t, clamp((v), AL_EAXREVERB_MIN_##t, AL_EAXREVERB_MAX_##t)) 60 | SETPARAM(mId, DENSITY, props.flDensity); 61 | SETPARAM(mId, DIFFUSION, props.flDiffusion); 62 | SETPARAM(mId, GAIN, props.flGain); 63 | SETPARAM(mId, GAINHF, props.flGainHF); 64 | SETPARAM(mId, GAINLF, props.flGainLF); 65 | SETPARAM(mId, DECAY_TIME, props.flDecayTime); 66 | SETPARAM(mId, DECAY_HFRATIO, props.flDecayHFRatio); 67 | SETPARAM(mId, DECAY_LFRATIO, props.flDecayLFRatio); 68 | SETPARAM(mId, REFLECTIONS_GAIN, props.flReflectionsGain); 69 | SETPARAM(mId, REFLECTIONS_DELAY, props.flReflectionsDelay); 70 | mContext.alEffectfv(mId, AL_EAXREVERB_REFLECTIONS_PAN, props.flReflectionsPan); 71 | SETPARAM(mId, LATE_REVERB_GAIN, props.flLateReverbGain); 72 | SETPARAM(mId, LATE_REVERB_DELAY, props.flLateReverbDelay); 73 | mContext.alEffectfv(mId, AL_EAXREVERB_LATE_REVERB_PAN, props.flLateReverbPan); 74 | SETPARAM(mId, ECHO_TIME, props.flEchoTime); 75 | SETPARAM(mId, ECHO_DEPTH, props.flEchoDepth); 76 | SETPARAM(mId, MODULATION_TIME, props.flModulationTime); 77 | SETPARAM(mId, MODULATION_DEPTH, props.flModulationDepth); 78 | SETPARAM(mId, AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); 79 | SETPARAM(mId, HFREFERENCE, props.flHFReference); 80 | SETPARAM(mId, LFREFERENCE, props.flLFReference); 81 | SETPARAM(mId, ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); 82 | mContext.alEffecti(mId, AL_EAXREVERB_DECAY_HFLIMIT, (props.iDecayHFLimit ? AL_TRUE : AL_FALSE)); 83 | #undef SETPARAM 84 | } 85 | else if(mType == AL_EFFECT_REVERB) 86 | { 87 | #define SETPARAM(e,t,v) mContext.alEffectf((e), AL_REVERB_##t, clamp((v), AL_REVERB_MIN_##t, AL_REVERB_MAX_##t)) 88 | SETPARAM(mId, DENSITY, props.flDensity); 89 | SETPARAM(mId, DIFFUSION, props.flDiffusion); 90 | SETPARAM(mId, GAIN, props.flGain); 91 | SETPARAM(mId, GAINHF, props.flGainHF); 92 | SETPARAM(mId, DECAY_TIME, props.flDecayTime); 93 | SETPARAM(mId, DECAY_HFRATIO, props.flDecayHFRatio); 94 | SETPARAM(mId, REFLECTIONS_GAIN, props.flReflectionsGain); 95 | SETPARAM(mId, REFLECTIONS_DELAY, props.flReflectionsDelay); 96 | SETPARAM(mId, LATE_REVERB_GAIN, props.flLateReverbGain); 97 | SETPARAM(mId, LATE_REVERB_DELAY, props.flLateReverbDelay); 98 | SETPARAM(mId, AIR_ABSORPTION_GAINHF, props.flAirAbsorptionGainHF); 99 | SETPARAM(mId, ROOM_ROLLOFF_FACTOR, props.flRoomRolloffFactor); 100 | mContext.alEffecti(mId, AL_REVERB_DECAY_HFLIMIT, (props.iDecayHFLimit ? AL_TRUE : AL_FALSE)); 101 | #undef SETPARAM 102 | } 103 | } 104 | 105 | DECL_THUNK1(void, Effect, setChorusProperties,, const EFXCHORUSPROPERTIES&) 106 | void EffectImpl::setChorusProperties(const EFXCHORUSPROPERTIES &props) 107 | { 108 | CheckContext(mContext); 109 | 110 | if(mType != AL_EFFECT_CHORUS) 111 | { 112 | alGetError(); 113 | mContext.alEffecti(mId, AL_EFFECT_TYPE, AL_EFFECT_CHORUS); 114 | throw_al_error("Failed to set chorus type"); 115 | mType = AL_EFFECT_CHORUS; 116 | } 117 | 118 | #define SETPARAM(t,v) AL_CHORUS_##t, clamp((v), AL_CHORUS_MIN_##t, AL_CHORUS_MAX_##t) 119 | mContext.alEffecti(mId, SETPARAM(WAVEFORM, props.iWaveform)); 120 | mContext.alEffecti(mId, SETPARAM(PHASE, props.iPhase)); 121 | mContext.alEffectf(mId, SETPARAM(RATE, props.flRate)); 122 | mContext.alEffectf(mId, SETPARAM(DEPTH, props.flDepth)); 123 | mContext.alEffectf(mId, SETPARAM(FEEDBACK, props.flFeedback)); 124 | mContext.alEffectf(mId, SETPARAM(DELAY, props.flDelay)); 125 | #undef SETPARAM 126 | } 127 | 128 | void Effect::destroy() 129 | { 130 | EffectImpl *i = pImpl; 131 | pImpl = nullptr; 132 | i->destroy(); 133 | } 134 | void EffectImpl::destroy() 135 | { 136 | CheckContext(mContext); 137 | 138 | alGetError(); 139 | mContext.alDeleteEffects(1, &mId); 140 | throw_al_error("Effect failed to delete"); 141 | mId = 0; 142 | 143 | mContext.freeEffect(this); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /examples/alure-hrtf.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * An example showing how to enable HRTF rendering, using the ALC_SOFT_HRTF 3 | * extension. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "alure2.h" 14 | 15 | 16 | namespace { 17 | 18 | // Helper class+method to print the time with human-readable formatting. 19 | struct PrettyTime { 20 | alure::Seconds mTime; 21 | }; 22 | inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) 23 | { 24 | using hours = std::chrono::hours; 25 | using minutes = std::chrono::minutes; 26 | using seconds = std::chrono::seconds; 27 | using centiseconds = std::chrono::duration>; 28 | using std::chrono::duration_cast; 29 | 30 | centiseconds t = duration_cast(rhs.mTime); 31 | if(t.count() < 0) 32 | { 33 | os << '-'; 34 | t *= -1; 35 | } 36 | 37 | // Only handle up to hour formatting 38 | if(t >= hours(1)) 39 | os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) 40 | << duration_cast(t).count() << 'm'; 41 | else 42 | os << duration_cast(t).count() << 'm' << std::setfill('0'); 43 | os << std::setw(2) << (duration_cast(t).count() % 60) << '.' << std::setw(2) 44 | << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' '); 45 | return os; 46 | } 47 | 48 | } // namespace 49 | 50 | int main(int argc, char *argv[]) 51 | { 52 | alure::ArrayView args(argv, argc); 53 | 54 | if(args.size() < 2) 55 | { 56 | std::cerr<< "Usage: "< 2 && args[0] == alure::StringView("-device")) 65 | { 66 | dev = devMgr.openPlayback(args[1], std::nothrow); 67 | if(!dev) std::cerr<< "Failed to open \""< hrtf_names = dev.enumerateHRTFNames(); 75 | if(hrtf_names.empty()) 76 | std::cout<< "No HRTFs found!\n"; 77 | else 78 | { 79 | std::cout<< "Available HRTFs:\n"; 80 | for(const alure::String &name : hrtf_names) 81 | std::cout<< " "< attrs; 86 | attrs.push_back({ALC_HRTF_SOFT, ALC_TRUE}); 87 | if(args.size() > 1 && alure::StringView("-hrtf") == args[0]) 88 | { 89 | // Find the given HRTF and add it to the attributes list 90 | alure::StringView hrtf_name = args[1]; 91 | args = args.slice(2); 92 | 93 | size_t idx = std::distance( 94 | hrtf_names.begin(), std::find(hrtf_names.begin(), hrtf_names.end(), hrtf_name) 95 | ); 96 | if(idx == hrtf_names.size()) 97 | std::cerr<< "HRTF \""<(idx)}); 100 | } 101 | attrs.push_back(alure::AttributesEnd()); 102 | alure::Context ctx = dev.createContext(attrs); 103 | alure::Context::MakeCurrent(ctx); 104 | 105 | if(dev.isHRTFEnabled()) 106 | std::cout<< "Using HRTF \""< 1 && alure::StringView("-hrtf") == args[0]) 113 | { 114 | // Find the given HRTF and reset the device using it 115 | alure::StringView hrtf_name = args[1]; 116 | size_t idx = std::distance( 117 | hrtf_names.begin(), std::find(hrtf_names.begin(), hrtf_names.end(), hrtf_name) 118 | ); 119 | if(idx == hrtf_names.size()) 120 | std::cerr<< "HRTF \""< attrs{{ 124 | {ALC_HRTF_SOFT, ALC_TRUE}, 125 | {ALC_HRTF_ID_SOFT, static_cast(idx)}, 126 | alure::AttributesEnd() 127 | }}; 128 | dev.reset(attrs); 129 | if(dev.isHRTFEnabled()) 130 | std::cout<< "Using HRTF \""< decoder = ctx.createDecoder(args.front()); 140 | alure::Source source = ctx.createSource(); 141 | 142 | source.play(decoder, 12000, 4); 143 | std::cout<< "Playing "<getSampleType())<<", " 145 | << alure::GetChannelConfigName(decoder->getChannelConfig())<<", " 146 | << decoder->getFrequency()<<"hz)" <getFrequency(); 149 | while(source.isPlaying()) 150 | { 151 | std::cout<< "\r "<getLength()*invfreq)}; 153 | std::cout.flush(); 154 | std::this_thread::sleep_for(std::chrono::milliseconds(25)); 155 | ctx.update(); 156 | } 157 | std::cout< 5 | #include 6 | #include 7 | 8 | #include "main.h" 9 | 10 | #define DR_FLAC_NO_STDIO 11 | #define DR_FLAC_IMPLEMENTATION 12 | #include "dr_flac.h" 13 | 14 | 15 | namespace { 16 | 17 | struct FlacFileDeleter { 18 | void operator()(drflac *ptr) const { drflac_close(ptr); } 19 | }; 20 | using FlacFilePtr = alure::UniquePtr; 21 | 22 | } 23 | 24 | 25 | namespace alure { 26 | 27 | class FlacDecoder final : public Decoder { 28 | UniquePtr mFile; 29 | 30 | FlacFilePtr mFlacFile; 31 | ChannelConfig mChannelConfig{ChannelConfig::Mono}; 32 | SampleType mSampleType{SampleType::UInt8}; 33 | ALuint mFrequency{0}; 34 | std::pair mLoopPts{0, 0}; 35 | 36 | static void MetadataCallback(void *client_data, drflac_metadata *mdata) 37 | { 38 | FlacDecoder *self = static_cast(client_data); 39 | 40 | if(mdata->type == DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO) 41 | { 42 | // Ignore duplicate StreamInfo blocks 43 | if(self->mFrequency != 0) 44 | return; 45 | 46 | const drflac_streaminfo &info = mdata->data.streaminfo; 47 | if(info.channels == 1) 48 | self->mChannelConfig = ChannelConfig::Mono; 49 | else if(info.channels == 2) 50 | self->mChannelConfig = ChannelConfig::Stereo; 51 | else 52 | return; 53 | 54 | if(info.bitsPerSample > 16 && 55 | Context::GetCurrent().isSupported(self->mChannelConfig, SampleType::Float32)) 56 | self->mSampleType = SampleType::Float32; 57 | else 58 | self->mSampleType = SampleType::Int16; 59 | 60 | self->mFrequency = info.sampleRate; 61 | } 62 | else if(mdata->type == DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT) 63 | { 64 | const auto &vc = mdata->data.vorbis_comment; 65 | drflac_vorbis_comment_iterator iter; 66 | drflac_uint32 comment_len; 67 | const char *comment_str; 68 | 69 | drflac_init_vorbis_comment_iterator(&iter, vc.commentCount, vc.pComments); 70 | while((comment_str=drflac_next_vorbis_comment(&iter, &comment_len)) != nullptr) 71 | { 72 | auto seppos = StringView(comment_str, comment_len).find_first_of('='); 73 | if(seppos == StringView::npos) continue; 74 | 75 | StringView key(comment_str, seppos); 76 | StringView val(comment_str+seppos+1, comment_len-(seppos+1)); 77 | 78 | // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for 79 | // loop points in a Vorbis comment. ZDoom recognizes LOOP_START 80 | // and LOOP_END. We can recognize both. 81 | if(key == "LOOP_START" || key == "LOOPSTART") 82 | { 83 | auto pt = ParseTimeval(val, self->mFrequency); 84 | if(pt.index() == 1) self->mLoopPts.first = std::get<1>(pt); 85 | continue; 86 | } 87 | 88 | if(key == "LOOP_END") 89 | { 90 | auto pt = ParseTimeval(val, self->mFrequency); 91 | if(pt.index() == 1) self->mLoopPts.second = std::get<1>(pt); 92 | continue; 93 | } 94 | 95 | if(key == "LOOPLENGTH") 96 | { 97 | auto pt = ParseTimeval(val, self->mFrequency); 98 | if(pt.index() == 1) 99 | self->mLoopPts.second = self->mLoopPts.first + std::get<1>(pt); 100 | continue; 101 | } 102 | } 103 | } 104 | } 105 | 106 | static size_t ReadCallback(void *client_data, void *buffer, size_t bytes) 107 | { 108 | std::istream *stream = static_cast(client_data)->mFile.get(); 109 | stream->clear(); 110 | 111 | stream->read(reinterpret_cast(buffer), bytes); 112 | return stream->gcount(); 113 | } 114 | static drflac_bool32 SeekCallback(void *client_data, int offset, drflac_seek_origin origin) 115 | { 116 | std::istream *stream = static_cast(client_data)->mFile.get(); 117 | stream->clear(); 118 | 119 | if(!stream->seekg(offset, (origin==drflac_seek_origin_current) ? std::ios_base::cur 120 | : std::ios_base::beg)) 121 | return DRFLAC_FALSE; 122 | return DRFLAC_TRUE; 123 | } 124 | 125 | public: 126 | FlacDecoder() noexcept { } 127 | ~FlacDecoder() override { } 128 | 129 | bool open(UniquePtr &file) noexcept; 130 | 131 | ALuint getFrequency() const noexcept override; 132 | ChannelConfig getChannelConfig() const noexcept override; 133 | SampleType getSampleType() const noexcept override; 134 | 135 | uint64_t getLength() const noexcept override; 136 | bool seek(uint64_t pos) noexcept override; 137 | 138 | std::pair getLoopPoints() const noexcept override; 139 | 140 | ALuint read(ALvoid *ptr, ALuint count) noexcept override; 141 | }; 142 | 143 | 144 | bool FlacDecoder::open(UniquePtr &file) noexcept 145 | { 146 | mFile = std::move(file); 147 | mFlacFile = FlacFilePtr(drflac_open_with_metadata(ReadCallback, SeekCallback, MetadataCallback, this)); 148 | if(mFlacFile) 149 | { 150 | if(mFrequency != 0) 151 | return true; 152 | 153 | mFlacFile = nullptr; 154 | } 155 | 156 | file = std::move(mFile); 157 | return false; 158 | } 159 | 160 | 161 | ALuint FlacDecoder::getFrequency() const noexcept 162 | { 163 | return mFrequency; 164 | } 165 | 166 | ChannelConfig FlacDecoder::getChannelConfig() const noexcept 167 | { 168 | return mChannelConfig; 169 | } 170 | 171 | SampleType FlacDecoder::getSampleType() const noexcept 172 | { 173 | return mSampleType; 174 | } 175 | 176 | 177 | uint64_t FlacDecoder::getLength() const noexcept 178 | { 179 | // For some silly reason, dr_flac tracks sample counts as individual 180 | // samples rather than sample frames (e.g. doubled for stereo). 181 | return mFlacFile->totalSampleCount / mFlacFile->channels; 182 | } 183 | 184 | bool FlacDecoder::seek(uint64_t pos) noexcept 185 | { 186 | pos *= mFlacFile->channels; 187 | if(pos >= mFlacFile->totalSampleCount) 188 | return false; 189 | return drflac_seek_to_sample(mFlacFile.get(), pos); 190 | } 191 | 192 | std::pair FlacDecoder::getLoopPoints() const noexcept 193 | { 194 | return mLoopPts; 195 | } 196 | 197 | ALuint FlacDecoder::read(ALvoid *ptr, ALuint count) noexcept 198 | { 199 | count *= mFlacFile->channels; 200 | if(mSampleType == SampleType::Float32) 201 | count = drflac_read_f32(mFlacFile.get(), count, (float*)ptr); 202 | else if(mSampleType == SampleType::Int16) 203 | count = drflac_read_s16(mFlacFile.get(), count, (short*)ptr); 204 | else 205 | count = 0; 206 | return count / mFlacFile->channels; 207 | } 208 | 209 | 210 | SharedPtr FlacDecoderFactory::createDecoder(UniquePtr &file) noexcept 211 | { 212 | auto decoder = MakeShared(); 213 | if(!decoder->open(file)) decoder.reset(); 214 | return decoder; 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /src/source.h: -------------------------------------------------------------------------------- 1 | #ifndef SOURCE_H 2 | #define SOURCE_H 3 | 4 | #include "main.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace alure { 10 | 11 | class ALBufferStream; 12 | 13 | struct SendProps { 14 | ALuint mSendIdx; 15 | AuxiliaryEffectSlotImpl *mSlot{nullptr}; 16 | ALuint mFilter{AL_FILTER_NULL}; 17 | 18 | SendProps(ALuint send, AuxiliaryEffectSlotImpl *slot) : mSendIdx(send), mSlot(slot) 19 | { } 20 | SendProps(ALuint send, ALuint filter) : mSendIdx(send), mFilter(filter) 21 | { } 22 | SendProps(ALuint send, AuxiliaryEffectSlotImpl *slot, ALuint filter) 23 | : mSendIdx(send), mSlot(slot), mFilter(filter) 24 | { } 25 | }; 26 | 27 | struct SourceBufferUpdateEntry { 28 | SourceImpl *mSource; 29 | ALuint mId; 30 | }; 31 | struct SourceStreamUpdateEntry { 32 | SourceImpl *mSource; 33 | }; 34 | 35 | struct SourceFadeUpdateEntry { 36 | SourceImpl *mSource; 37 | 38 | std::chrono::nanoseconds mFadeTimeStart; 39 | std::chrono::nanoseconds mFadeTimeTarget; 40 | bool mIsFadeOut; 41 | ALfloat mFadeGainMult; 42 | }; 43 | 44 | 45 | class SourceImpl { 46 | ContextImpl &mContext; 47 | ALuint mId; 48 | 49 | BufferImpl *mBuffer; 50 | UniquePtr mStream; 51 | 52 | SourceGroupImpl *mGroup; 53 | ALfloat mGroupPitch; 54 | ALfloat mGroupGain; 55 | 56 | ALfloat mFadeGain; 57 | 58 | mutable std::mutex mMutex; 59 | std::atomic mIsAsync; 60 | 61 | std::atomic mPaused; 62 | uint64_t mOffset; 63 | ALfloat mPitch; 64 | ALfloat mGain; 65 | ALfloat mMinGain, mMaxGain; 66 | ALfloat mRefDist, mMaxDist; 67 | Vector3 mPosition; 68 | Vector3 mVelocity; 69 | Vector3 mDirection; 70 | Vector3 mOrientation[2]; 71 | ALfloat mConeInnerAngle, mConeOuterAngle; 72 | ALfloat mConeOuterGain, mConeOuterGainHF; 73 | ALfloat mRolloffFactor, mRoomRolloffFactor; 74 | ALfloat mDopplerFactor; 75 | ALfloat mAirAbsorptionFactor; 76 | ALfloat mRadius; 77 | ALfloat mStereoAngles[2]; 78 | Spatialize mSpatialize; 79 | ALsizei mResampler; 80 | bool mLooping : 1; 81 | bool mRelative : 1; 82 | bool mDryGainHFAuto : 1; 83 | bool mWetGainAuto : 1; 84 | bool mWetGainHFAuto : 1; 85 | 86 | ALuint mDirectFilter; 87 | Vector mEffectSlots; 88 | 89 | ALuint mPriority; 90 | 91 | void resetProperties(); 92 | void applyProperties(bool looping) const; 93 | 94 | ALint refillBufferStream(); 95 | 96 | void setFilterParams(ALuint &filterid, const FilterParams ¶ms); 97 | 98 | public: 99 | SourceImpl(ContextImpl &context); 100 | ~SourceImpl(); 101 | 102 | ALuint getId() const { return mId; } 103 | 104 | bool checkPending(SharedFuture &future); 105 | bool fadeUpdate(std::chrono::nanoseconds cur_fade_time, SourceFadeUpdateEntry &fade); 106 | bool playUpdate(ALuint id); 107 | bool playUpdate(); 108 | bool updateAsync(); 109 | 110 | void unsetGroup(); 111 | void groupPropUpdate(ALfloat gain, ALfloat pitch); 112 | 113 | void checkPaused(); 114 | void unsetPaused() { mPaused = false; } 115 | 116 | void play(Buffer buffer); 117 | void play(SharedPtr&& decoder, ALsizei chunk_len, ALsizei queue_size); 118 | void play(SharedFuture&& future_buffer); 119 | void stop(); 120 | void makeStopped(bool dolock=true); 121 | void fadeOutToStop(ALfloat gain, std::chrono::milliseconds duration); 122 | void pause(); 123 | void resume(); 124 | 125 | bool isPending() const; 126 | bool isPlaying() const; 127 | bool isPaused() const; 128 | bool isPlayingOrPending() const; 129 | 130 | void setGroup(SourceGroup group); 131 | SourceGroup getGroup() const { return SourceGroup(mGroup); } 132 | 133 | void setPriority(ALuint priority); 134 | ALuint getPriority() const { return mPriority; } 135 | 136 | void setOffset(uint64_t offset); 137 | std::pair getSampleOffsetLatency() const; 138 | std::pair getSecOffsetLatency() const; 139 | 140 | void setLooping(bool looping); 141 | bool getLooping() const { return mLooping; } 142 | 143 | void setPitch(ALfloat pitch); 144 | ALfloat getPitch() const { return mPitch; } 145 | 146 | void setGain(ALfloat gain); 147 | ALfloat getGain() const { return mGain; } 148 | 149 | void setGainRange(ALfloat mingain, ALfloat maxgain); 150 | std::pair getGainRange() const 151 | { return {mMinGain, mMaxGain}; } 152 | 153 | void setDistanceRange(ALfloat refdist, ALfloat maxdist); 154 | std::pair getDistanceRange() const 155 | { return {mRefDist, mMaxDist}; } 156 | 157 | void set3DParameters(const Vector3 &position, const Vector3 &velocity, const Vector3 &direction); 158 | void set3DParameters(const Vector3 &position, const Vector3 &velocity, const std::pair &orientation); 159 | 160 | void setPosition(const Vector3 &position); 161 | void setPosition(const ALfloat *pos); 162 | Vector3 getPosition() const { return mPosition; } 163 | 164 | void setVelocity(const Vector3 &velocity); 165 | void setVelocity(const ALfloat *vel); 166 | Vector3 getVelocity() const { return mVelocity; } 167 | 168 | void setDirection(const Vector3 &direction); 169 | void setDirection(const ALfloat *dir); 170 | Vector3 getDirection() const { return mDirection; } 171 | 172 | void setOrientation(const std::pair &orientation); 173 | void setOrientation(const ALfloat *at, const ALfloat *up); 174 | void setOrientation(const ALfloat *ori); 175 | std::pair getOrientation() const 176 | { return {mOrientation[0], mOrientation[1]}; } 177 | 178 | void setConeAngles(ALfloat inner, ALfloat outer); 179 | std::pair getConeAngles() const 180 | { return {mConeInnerAngle, mConeOuterAngle}; } 181 | 182 | void setOuterConeGains(ALfloat gain, ALfloat gainhf=1.0f); 183 | std::pair getOuterConeGains() const 184 | { return {mConeOuterGain, mConeOuterGainHF}; } 185 | 186 | void setRolloffFactors(ALfloat factor, ALfloat roomfactor=0.0f); 187 | std::pair getRolloffFactors() const 188 | { return {mRolloffFactor, mRoomRolloffFactor}; } 189 | 190 | void setDopplerFactor(ALfloat factor); 191 | ALfloat getDopplerFactor() const { return mDopplerFactor; } 192 | 193 | void setRelative(bool relative); 194 | bool getRelative() const { return mRelative; } 195 | 196 | void setRadius(ALfloat radius); 197 | ALfloat getRadius() const { return mRadius; } 198 | 199 | void setStereoAngles(ALfloat leftAngle, ALfloat rightAngle); 200 | std::pair getStereoAngles() const 201 | { return std::make_pair(mStereoAngles[0], mStereoAngles[1]); } 202 | 203 | void set3DSpatialize(Spatialize spatialize); 204 | Spatialize get3DSpatialize() const { return mSpatialize; } 205 | 206 | void setResamplerIndex(ALsizei index); 207 | ALsizei getResamplerIndex() const { return mResampler; } 208 | 209 | void setAirAbsorptionFactor(ALfloat factor); 210 | ALfloat getAirAbsorptionFactor() const { return mAirAbsorptionFactor; } 211 | 212 | void setGainAuto(bool directhf, bool send, bool sendhf); 213 | std::tuple getGainAuto() const 214 | { return std::make_tuple(mDryGainHFAuto, mWetGainAuto, mWetGainHFAuto); } 215 | 216 | void setDirectFilter(const FilterParams &filter); 217 | void setSendFilter(ALuint send, const FilterParams &filter); 218 | void setAuxiliarySend(AuxiliaryEffectSlot slot, ALuint send); 219 | void setAuxiliarySendFilter(AuxiliaryEffectSlot slot, ALuint send, const FilterParams &filter); 220 | 221 | void destroy(); 222 | }; 223 | 224 | } // namespace alure 225 | 226 | #endif /* SOURCE_H */ 227 | 228 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Alure 2 | ===== 3 | [![AppVeyor][AppVeyor Badge]][AppVeyor URL] 4 | [![Travis][Travis Badge]][Travis URL] 5 | 6 | [AppVeyor Badge]: https://ci.appveyor.com/api/projects/status/github/kcat/alure?branch=master&svg=true 7 | [AppVeyor URL]: https://ci.appveyor.com/project/ChrisRobinson/alure/branch/master 8 | [Travis Badge]: https://api.travis-ci.org/kcat/alure.svg 9 | [Travis URL]: https://travis-ci.org/kcat/alure 10 | 11 | Alure is a C++ 3D audio API. It uses OpenAL for audio rendering, and provides 12 | common higher-level features such as file loading and decoding, buffer caching, 13 | background streaming, and source management for virtually unlimited sound 14 | source handles. 15 | 16 | Features 17 | -------- 18 | Alure supports 3D sound rendering based on standard OpenAL features. Extra 3D 19 | sound features may be available depending on the extensions supported by OpenAL 20 | (newer versions of OpenAL Soft provide the necessary extensions and is thus 21 | recommended, but is not required if those features aren't of interest). 22 | 23 | Environmental audio effects are supported with the ALC_EXT_EFX extension. This 24 | provides multi-zone reverb, sound occlusion and obstruction (simulating sounds 25 | being behind doors or walls), and atmospheric air absorption. 26 | 27 | Binaural (HRTF) rendering is provided with the ALC_SOFT_HRTF extension. When 28 | used with headphones, this provides an unparalleled sense of 3D sound 29 | positioning. Multiple and custom profiles can also be select from to get a 30 | closer match for different people. 31 | 32 | Alure supports decoding audio files using external libraries: VorbisFile, 33 | OpusFile, and libsndfile. A built-in wave file reader is also available to 34 | support basic PCM formats, as well as built-in decoders for FLAC (dr_flac) and 35 | MP3 (minimp3). Application-defined decoders are also supported in case the 36 | default set are insufficient. 37 | 38 | And much more… 39 | 40 | Building 41 | -------- 42 | ### Dependencies 43 | Before even building, Alure requires the OpenAL development files installed, 44 | for example, through Creative's OpenAL SDK (available from openal.org) or from 45 | OpenAL Soft. Additionally you will need a C++14 or above compliant compiler to 46 | be able to build Alure. 47 | 48 | These following dependencies are only needed to *automatically* support the 49 | formats they handle: 50 | 51 | * [ogg](https://xiph.org/ogg/): ogg playback 52 | * [vorbis](https://xiph.org/vorbis/): ogg vorbis playback 53 | * [opusfile](http://opus-codec.org/): opus playback 54 | * [SndFile](http://www.mega-nerd.com/libsndfile/): various multi-format playback 55 | 56 | Two of the packaged examples require the following dependencies to be built. 57 | 58 | * [PhysFS](https://icculus.org/physfs/): alure-physfs 59 | * [dumb](https://github.com/kode54/dumb): alure-dumb 60 | 61 | If any dependency isn't found at build time the relevant decoders or examples 62 | will be disabled and skipped during build. 63 | 64 | ### On Windows 65 | If your are using [MinGW-w64](https://mingw-w64.org/doku.php), the easiest way 66 | to get all of the dependencies above is to use [MSYS2](http://www.msys2.org/), 67 | which has up-to-date binaries for all of the optional and required dependencies 68 | above (so you don't need to build each from scratch). 69 | 70 | Follow the MSYS2 installation guide and then look for each dependency on MSYS2 71 | package repo and `pacman -S [packagename]` each package to acquire all 72 | dependencies. 73 | 74 | After acquiring all dependencies, you will need to make sure that the includes, 75 | libraries, and binaries for each file are in your path. For most dependencies 76 | this isn't a big deal, if you are using msys these directories are simply 77 | `msys/mingw64/bin`, `msys/mingw64/lib` and `msys/mingw64/include`. However the 78 | cmake file for Alure requires you to use the direct directory where OpenAL Soft 79 | headers are located (so instead of `msys/mingw64/include`, it's 80 | `msys/mingw64/include/AL`). 81 | 82 | Inside the `build` directory, run `cmake ..`. After CMake generation 83 | you should have something that looks like the following output 84 | if you have every single dependency: 85 | 86 | -- Found OpenAL: C:/msys64/mingw64/lib/libopenal.dll.a 87 | -- Found Threads: TRUE 88 | -- Found OGG: C:/msys64/mingw64/lib/libogg.dll.a 89 | -- Found VORBIS: C:/msys64/mingw64/lib/libvorbisfile.dll.a 90 | -- Found OPUS: C:/msys64/mingw64/lib/libopusfile.dll.a 91 | -- Found SndFile: C:/msys64/mingw64/lib/libsndfile.dll.a 92 | -- Found PhysFS: C:/msys64/mingw64/lib/libphysfs.dll.a 93 | -- Found DUMB: C:/msys64/mingw64/lib/libdumb.dll.a 94 | -- Configuring done 95 | -- Generating done 96 | -- Build files have been written to: .../alure/build 97 | 98 | Now you may compile the library and examples by running `cmake --build .`. 99 | Use `cmake --install .`, which probably requires administrative privilege, 100 | to install Alure library in `C:\Program Files (x86)` for it to be available 101 | on your system. 102 | 103 | ### On GNU/Linux 104 | On Debian-based systems, the library's dependencies can be installed using 105 | 106 | apt install libopenal-dev libvorbis-dev libopusfile-dev libsndfile1-dev 107 | 108 | Optional dependencies for examples might be install via 109 | 110 | apt install libphysfs-dev libdumb1-dev 111 | 112 | On other distributions, the packages names can be adapted similarly. 113 | 114 | Then inside `build`, the output of `cmake ..` should contains lines similar to 115 | the following 116 | 117 | -- Found OpenAL: /usr/lib/x86_64-linux-gnu/libopenal.so 118 | -- Found Threads: TRUE 119 | -- Found OGG: /usr/lib/x86_64-linux-gnu/libogg.so 120 | -- Found VORBIS: /usr/lib/x86_64-linux-gnu/libvorbisfile.so 121 | -- Found OPUS: /usr/lib/libopusfile.so 122 | -- Found SndFile: /usr/lib/x86_64-linux-gnu/libsndfile.so 123 | -- Found PhysFS: /usr/lib/x86_64-linux-gnu/libphysfs.so 124 | -- Found DUMB: /usr/lib/x86_64-linux-gnu/libdumb.so 125 | -- Configuring done 126 | -- Generating done 127 | -- Build files have been written to: .../alure/build 128 | 129 | To build the library and each example you have the dependencies for, 130 | run `cmake --build . --parallel $(nproc)`. Use `sudo cmake --install .` 131 | to install Alure library on your system. 132 | 133 | ### On macOS 134 | OpenAL is provided by Apple via [Core Audio], while optional codecs and 135 | examples' dependencies are available on [Homebrew](https://brew.sh/): 136 | 137 | brew install libvorbis opusfile libsndfile 138 | brew install phyfs dumb 139 | 140 | Then inside `build`, the output of `cmake ..` should contains lines similar to 141 | the following 142 | 143 | -- Found OpenAL: /System/Library/Frameworks/OpenAL.framework 144 | -- Found Threads: TRUE 145 | -- Found OGG: /usr/local/lib/libogg.dylib 146 | -- Found VORBIS: /usr/local/lib/libvorbisfile.dylib 147 | -- Found OPUS: /usr/local/lib/libopusfile.dylib 148 | -- Found SndFile: /usr/local/lib/libsndfile.dylib 149 | -- Found PhysFS: /usr/local/lib/libphysfs.dylib 150 | -- Found DUMB: /usr/local/lib/libdumb.a 151 | -- Configuring done 152 | -- Generating done 153 | -- Build files have been written to: .../alure/build 154 | 155 | If OpenAL Soft is preferred, one may install it from Homebrew and specify 156 | CMake prefix path accordingly 157 | 158 | brew install openal-soft 159 | OPENALDIR=`brew --prefix openal-soft` cmake -DCMAKE_FIND_FRAMEWORK=NEVER .. 160 | 161 | and get 162 | 163 | -- Found OpenAL: /usr/local/opt/openal-soft/lib/libopenal.dylib 164 | 165 | To build the library and each example you have the dependencies for, 166 | run `cmake --build . --parallel $(sysctl -n hw.ncpu)`. 167 | Use `sudo cmake --install .` to install Alure library on your system. 168 | 169 | [Core Audio]: https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/WhatsinCoreAudio/WhatsinCoreAudio.html 170 | -------------------------------------------------------------------------------- /src/decoders/vorbisfile.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "vorbisfile.hpp" 3 | 4 | #include 5 | 6 | #include "context.h" 7 | 8 | #include "vorbis/vorbisfile.h" 9 | 10 | namespace { 11 | 12 | int istream_seek(void *user_data, ogg_int64_t offset, int whence) 13 | { 14 | std::istream *stream = static_cast(user_data); 15 | stream->clear(); 16 | 17 | if(whence == SEEK_CUR) 18 | stream->seekg(offset, std::ios_base::cur); 19 | else if(whence == SEEK_SET) 20 | stream->seekg(offset, std::ios_base::beg); 21 | else if(whence == SEEK_END) 22 | stream->seekg(offset, std::ios_base::end); 23 | else 24 | return -1; 25 | 26 | return stream->tellg(); 27 | } 28 | 29 | size_t istream_read(void *ptr, size_t size, size_t nmemb, void *user_data) 30 | { 31 | std::istream *stream = static_cast(user_data); 32 | stream->clear(); 33 | 34 | stream->read(static_cast(ptr), nmemb*size); 35 | size_t ret = stream->gcount(); 36 | return ret/size; 37 | } 38 | 39 | long istream_tell(void *user_data) 40 | { 41 | std::istream *stream = static_cast(user_data); 42 | stream->clear(); 43 | return stream->tellg(); 44 | } 45 | 46 | int istream_close(void*) { return 0; } 47 | 48 | 49 | struct OggVorbisfileHolder : public OggVorbis_File { 50 | OggVorbisfileHolder() { this->datasource = nullptr; } 51 | ~OggVorbisfileHolder() { if(this->datasource) ov_clear(this); } 52 | }; 53 | using OggVorbisfilePtr = alure::UniquePtr; 54 | 55 | } // namespace 56 | 57 | namespace alure { 58 | 59 | class VorbisFileDecoder final : public Decoder { 60 | UniquePtr mFile; 61 | 62 | OggVorbisfilePtr mOggFile; 63 | vorbis_info *mVorbisInfo{nullptr}; 64 | int mOggBitstream{0}; 65 | 66 | ChannelConfig mChannelConfig{ChannelConfig::Mono}; 67 | 68 | std::pair mLoopPoints{0, 0}; 69 | 70 | public: 71 | VorbisFileDecoder(UniquePtr file, OggVorbisfilePtr oggfile, 72 | vorbis_info *vorbisinfo, ChannelConfig sconfig, 73 | std::pair loop_points) noexcept 74 | : mFile(std::move(file)), mOggFile(std::move(oggfile)), mVorbisInfo(vorbisinfo) 75 | , mChannelConfig(sconfig), mLoopPoints(loop_points) 76 | { } 77 | ~VorbisFileDecoder() override { } 78 | 79 | ALuint getFrequency() const noexcept override; 80 | ChannelConfig getChannelConfig() const noexcept override; 81 | SampleType getSampleType() const noexcept override; 82 | 83 | uint64_t getLength() const noexcept override; 84 | bool seek(uint64_t pos) noexcept override; 85 | 86 | std::pair getLoopPoints() const noexcept override; 87 | 88 | ALuint read(ALvoid *ptr, ALuint count) noexcept override; 89 | }; 90 | 91 | ALuint VorbisFileDecoder::getFrequency() const noexcept { return mVorbisInfo->rate; } 92 | ChannelConfig VorbisFileDecoder::getChannelConfig() const noexcept { return mChannelConfig; } 93 | SampleType VorbisFileDecoder::getSampleType() const noexcept { return SampleType::Int16; } 94 | 95 | uint64_t VorbisFileDecoder::getLength() const noexcept 96 | { 97 | ogg_int64_t len = ov_pcm_total(mOggFile.get(), -1); 98 | return std::max(len, 0); 99 | } 100 | 101 | bool VorbisFileDecoder::seek(uint64_t pos) noexcept 102 | { 103 | return ov_pcm_seek(mOggFile.get(), pos) == 0; 104 | } 105 | 106 | std::pair VorbisFileDecoder::getLoopPoints() const noexcept 107 | { 108 | return mLoopPoints; 109 | } 110 | 111 | ALuint VorbisFileDecoder::read(ALvoid *ptr, ALuint count) noexcept 112 | { 113 | ALuint total = 0; 114 | ALshort *samples = (ALshort*)ptr; 115 | while(total < count) 116 | { 117 | int len = (count-total) * mVorbisInfo->channels * 2; 118 | #ifdef __BIG_ENDIAN__ 119 | long got = ov_read(mOggFile.get(), reinterpret_cast(samples), len, 1, 2, 1, &mOggBitstream); 120 | #else 121 | long got = ov_read(mOggFile.get(), reinterpret_cast(samples), len, 0, 2, 1, &mOggBitstream); 122 | #endif 123 | if(got <= 0) break; 124 | 125 | got /= 2; 126 | samples += got; 127 | got /= mVorbisInfo->channels; 128 | total += got; 129 | } 130 | 131 | // 1, 2, and 4 channel files decode into the same channel order as 132 | // OpenAL, however 6 (5.1), 7 (6.1), and 8 (7.1) channel files need to be 133 | // re-ordered. 134 | if(mChannelConfig == ChannelConfig::X51) 135 | { 136 | samples = (ALshort*)ptr; 137 | for(ALuint i = 0;i < total;++i) 138 | { 139 | // OpenAL : FL, FR, FC, LFE, RL, RR 140 | // Vorbis : FL, FC, FR, RL, RR, LFE 141 | std::swap(samples[i*6 + 1], samples[i*6 + 2]); 142 | std::swap(samples[i*6 + 3], samples[i*6 + 5]); 143 | std::swap(samples[i*6 + 4], samples[i*6 + 5]); 144 | } 145 | } 146 | else if(mChannelConfig == ChannelConfig::X61) 147 | { 148 | samples = (ALshort*)ptr; 149 | for(ALuint i = 0;i < total;++i) 150 | { 151 | // OpenAL : FL, FR, FC, LFE, RC, SL, SR 152 | // Vorbis : FL, FC, FR, SL, SR, RC, LFE 153 | std::swap(samples[i*7 + 1], samples[i*7 + 2]); 154 | std::swap(samples[i*7 + 3], samples[i*7 + 6]); 155 | std::swap(samples[i*7 + 4], samples[i*7 + 5]); 156 | std::swap(samples[i*7 + 5], samples[i*7 + 6]); 157 | } 158 | } 159 | else if(mChannelConfig == ChannelConfig::X71) 160 | { 161 | samples = (ALshort*)ptr; 162 | for(ALuint i = 0;i < total;++i) 163 | { 164 | // OpenAL : FL, FR, FC, LFE, RL, RR, SL, SR 165 | // Vorbis : FL, FC, FR, SL, SR, RL, RR, LFE 166 | std::swap(samples[i*8 + 1], samples[i*8 + 2]); 167 | std::swap(samples[i*8 + 3], samples[i*8 + 7]); 168 | std::swap(samples[i*8 + 4], samples[i*8 + 5]); 169 | std::swap(samples[i*8 + 5], samples[i*8 + 6]); 170 | std::swap(samples[i*8 + 6], samples[i*8 + 7]); 171 | } 172 | } 173 | 174 | return total; 175 | } 176 | 177 | 178 | SharedPtr VorbisFileDecoderFactory::createDecoder(UniquePtr &file) noexcept 179 | { 180 | static const ov_callbacks streamIO = { 181 | istream_read, istream_seek, istream_close, istream_tell 182 | }; 183 | 184 | auto oggfile = MakeUnique(); 185 | if(ov_open_callbacks(file.get(), oggfile.get(), NULL, 0, streamIO) != 0) 186 | return nullptr; 187 | 188 | vorbis_info *vorbisinfo = ov_info(oggfile.get(), -1); 189 | if(!vorbisinfo) return nullptr; 190 | 191 | std::pair loop_points = { 0, std::numeric_limits::max() }; 192 | if(vorbis_comment *vc = ov_comment(oggfile.get(), -1)) 193 | { 194 | for(int i = 0;i < vc->comments;i++) 195 | { 196 | StringView val(vc->user_comments[i], vc->comment_lengths[i]); 197 | auto seppos = val.find_first_of('='); 198 | if(seppos == StringView::npos) continue; 199 | 200 | StringView key = val.substr(0, seppos); 201 | val = val.substr(seppos+1); 202 | 203 | // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for loop 204 | // points in a Vorbis comment. ZDoom recognizes LOOP_START and 205 | // LOOP_END. We can recognize both. 206 | if(key == "LOOP_START" || key == "LOOPSTART") 207 | { 208 | auto pt = ParseTimeval(val, vorbisinfo->rate); 209 | if(pt.index() == 1) loop_points.first = std::get<1>(pt); 210 | continue; 211 | } 212 | 213 | if(key == "LOOP_END") 214 | { 215 | auto pt = ParseTimeval(val, vorbisinfo->rate); 216 | if(pt.index() == 1) loop_points.second = std::get<1>(pt); 217 | continue; 218 | } 219 | 220 | if(key == "LOOPLENGTH") 221 | { 222 | auto pt = ParseTimeval(val, vorbisinfo->rate); 223 | if(pt.index() == 1) 224 | loop_points.second = loop_points.first + std::get<1>(pt); 225 | continue; 226 | } 227 | } 228 | } 229 | 230 | ChannelConfig channels = ChannelConfig::Mono; 231 | if(vorbisinfo->channels == 1) 232 | channels = ChannelConfig::Mono; 233 | else if(vorbisinfo->channels == 2) 234 | channels = ChannelConfig::Stereo; 235 | else if(vorbisinfo->channels == 4) 236 | channels = ChannelConfig::Quad; 237 | else if(vorbisinfo->channels == 6) 238 | channels = ChannelConfig::X51; 239 | else if(vorbisinfo->channels == 7) 240 | channels = ChannelConfig::X61; 241 | else if(vorbisinfo->channels == 8) 242 | channels = ChannelConfig::X71; 243 | else 244 | return nullptr; 245 | 246 | return MakeShared( 247 | std::move(file), std::move(oggfile), vorbisinfo, channels, loop_points 248 | ); 249 | } 250 | 251 | } // namespace alure 252 | -------------------------------------------------------------------------------- /src/sourcegroup.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "sourcegroup.h" 3 | 4 | #include 5 | 6 | #include "source.h" 7 | #include "context.h" 8 | 9 | namespace alure { 10 | 11 | void SourceGroupImpl::insertSubGroup(SourceGroupImpl *group) 12 | { 13 | auto iter = std::lower_bound(mSubGroups.begin(), mSubGroups.end(), group); 14 | if(iter == mSubGroups.end() || *iter != group) 15 | mSubGroups.insert(iter, group); 16 | } 17 | 18 | void SourceGroupImpl::eraseSubGroup(SourceGroupImpl *group) 19 | { 20 | auto iter = std::lower_bound(mSubGroups.begin(), mSubGroups.end(), group); 21 | if(iter != mSubGroups.end() && *iter == group) mSubGroups.erase(iter); 22 | } 23 | 24 | 25 | void SourceGroupImpl::unsetParent() 26 | { 27 | mParent = nullptr; 28 | update(1.0f, 1.0f); 29 | } 30 | 31 | void SourceGroupImpl::update(ALfloat gain, ALfloat pitch) 32 | { 33 | mParentProps.mGain = gain; 34 | mParentProps.mPitch = pitch; 35 | 36 | gain *= mGain; 37 | pitch *= mPitch; 38 | for(SourceImpl *alsrc : mSources) 39 | alsrc->groupPropUpdate(gain, pitch); 40 | for(SourceGroupImpl *group : mSubGroups) 41 | group->update(gain, pitch); 42 | } 43 | 44 | 45 | bool SourceGroupImpl::findInSubGroups(SourceGroupImpl *group) const 46 | { 47 | auto iter = std::lower_bound(mSubGroups.begin(), mSubGroups.end(), group); 48 | if(iter != mSubGroups.end() && *iter == group) return true; 49 | 50 | for(SourceGroupImpl *grp : mSubGroups) 51 | { 52 | if(grp->findInSubGroups(group)) 53 | return true; 54 | } 55 | return false; 56 | } 57 | 58 | 59 | void SourceGroupImpl::insertSource(SourceImpl *source) 60 | { 61 | auto iter = std::lower_bound(mSources.begin(), mSources.end(), source); 62 | if(iter == mSources.end() || *iter != source) 63 | mSources.insert(iter, source); 64 | } 65 | 66 | void SourceGroupImpl::eraseSource(SourceImpl *source) 67 | { 68 | auto iter = std::lower_bound(mSources.begin(), mSources.end(), source); 69 | if(iter != mSources.end() && *iter == source) 70 | mSources.erase(iter); 71 | } 72 | 73 | 74 | DECL_THUNK1(void, SourceGroup, setParentGroup,, SourceGroup) 75 | void SourceGroupImpl::setParentGroup(SourceGroup group) 76 | { 77 | CheckContext(mContext); 78 | 79 | SourceGroupImpl *parent = group.getHandle(); 80 | if(!parent) 81 | { 82 | if(mParent) 83 | mParent->eraseSubGroup(this); 84 | mParent = nullptr; 85 | update(1.0f, 1.0f); 86 | } 87 | else 88 | { 89 | if(this == parent || findInSubGroups(parent)) 90 | throw std::runtime_error("Attempted circular group chain"); 91 | 92 | parent->insertSubGroup(this); 93 | 94 | Batcher batcher = mContext.getBatcher(); 95 | if(mParent) 96 | mParent->eraseSubGroup(this); 97 | mParent = parent; 98 | update(mParent->getAppliedGain(), mParent->getAppliedPitch()); 99 | } 100 | } 101 | 102 | 103 | DECL_THUNK0(Vector, SourceGroup, getSources, const) 104 | Vector SourceGroupImpl::getSources() const 105 | { 106 | Vector ret; 107 | ret.reserve(mSources.size()); 108 | for(SourceImpl *src : mSources) 109 | ret.emplace_back(src); 110 | return ret; 111 | } 112 | 113 | DECL_THUNK0(Vector, SourceGroup, getSubGroups, const) 114 | Vector SourceGroupImpl::getSubGroups() const 115 | { 116 | Vector ret; 117 | ret.reserve(mSubGroups.size()); 118 | for(SourceGroupImpl *grp : mSubGroups) 119 | ret.emplace_back(grp); 120 | return ret; 121 | } 122 | 123 | 124 | DECL_THUNK1(void, SourceGroup, setGain,, ALfloat) 125 | void SourceGroupImpl::setGain(ALfloat gain) 126 | { 127 | if(!(gain >= 0.0f)) 128 | throw std::domain_error("Gain out of range"); 129 | CheckContext(mContext); 130 | mGain = gain; 131 | gain *= mParentProps.mGain; 132 | ALfloat pitch = mPitch * mParentProps.mPitch; 133 | Batcher batcher = mContext.getBatcher(); 134 | for(SourceImpl *alsrc : mSources) 135 | alsrc->groupPropUpdate(gain, pitch); 136 | for(SourceGroupImpl *group : mSubGroups) 137 | group->update(gain, pitch); 138 | } 139 | 140 | DECL_THUNK1(void, SourceGroup, setPitch,, ALfloat) 141 | void SourceGroupImpl::setPitch(ALfloat pitch) 142 | { 143 | if(!(pitch > 0.0f)) 144 | throw std::domain_error("Pitch out of range"); 145 | CheckContext(mContext); 146 | mPitch = pitch; 147 | ALfloat gain = mGain * mParentProps.mGain; 148 | pitch *= mParentProps.mPitch; 149 | Batcher batcher = mContext.getBatcher(); 150 | for(SourceImpl *alsrc : mSources) 151 | alsrc->groupPropUpdate(gain, pitch); 152 | for(SourceGroupImpl *group : mSubGroups) 153 | group->update(gain, pitch); 154 | } 155 | 156 | 157 | void SourceGroupImpl::collectPlayingSourceIds(Vector &sourceids) const 158 | { 159 | for(SourceImpl *alsrc : mSources) 160 | { 161 | if(ALuint id = alsrc->getId()) 162 | sourceids.push_back(id); 163 | } 164 | for(SourceGroupImpl *group : mSubGroups) 165 | group->collectPlayingSourceIds(sourceids); 166 | } 167 | 168 | void SourceGroupImpl::updatePausedStatus() const 169 | { 170 | for(SourceImpl *alsrc : mSources) 171 | alsrc->checkPaused(); 172 | for(SourceGroupImpl *group : mSubGroups) 173 | group->updatePausedStatus(); 174 | } 175 | 176 | DECL_THUNK0(void, SourceGroup, pauseAll, const) 177 | void SourceGroupImpl::pauseAll() const 178 | { 179 | CheckContext(mContext); 180 | auto lock = mContext.getSourceStreamLock(); 181 | 182 | Vector sourceids; 183 | sourceids.reserve(16); 184 | collectPlayingSourceIds(sourceids); 185 | if(!sourceids.empty()) 186 | { 187 | alSourcePausev(static_cast(sourceids.size()), sourceids.data()); 188 | updatePausedStatus(); 189 | } 190 | lock.unlock(); 191 | } 192 | 193 | 194 | void SourceGroupImpl::collectPausedSourceIds(Vector &sourceids) const 195 | { 196 | for(SourceImpl *alsrc : mSources) 197 | { 198 | if(alsrc->isPaused()) 199 | sourceids.push_back(alsrc->getId()); 200 | } 201 | for(SourceGroupImpl *group : mSubGroups) 202 | group->collectPausedSourceIds(sourceids); 203 | } 204 | 205 | void SourceGroupImpl::updatePlayingStatus() const 206 | { 207 | for(SourceImpl *alsrc : mSources) 208 | alsrc->unsetPaused(); 209 | for(SourceGroupImpl *group : mSubGroups) 210 | group->updatePlayingStatus(); 211 | } 212 | 213 | DECL_THUNK0(void, SourceGroup, resumeAll, const) 214 | void SourceGroupImpl::resumeAll() const 215 | { 216 | CheckContext(mContext); 217 | auto lock = mContext.getSourceStreamLock(); 218 | 219 | Vector sourceids; 220 | sourceids.reserve(16); 221 | collectPausedSourceIds(sourceids); 222 | if(!sourceids.empty()) 223 | { 224 | alSourcePlayv(static_cast(sourceids.size()), sourceids.data()); 225 | updatePlayingStatus(); 226 | } 227 | lock.unlock(); 228 | } 229 | 230 | 231 | void SourceGroupImpl::collectSourceIds(Vector &sourceids) const 232 | { 233 | for(SourceImpl *alsrc : mSources) 234 | { 235 | if(ALuint id = alsrc->getId()) 236 | sourceids.push_back(id); 237 | } 238 | for(SourceGroupImpl *group : mSubGroups) 239 | group->collectSourceIds(sourceids); 240 | } 241 | 242 | void SourceGroupImpl::updateStoppedStatus() const 243 | { 244 | for(SourceImpl *alsrc : mSources) 245 | { 246 | mContext.removePendingSource(alsrc); 247 | mContext.removeFadingSource(alsrc); 248 | mContext.removePlayingSource(alsrc); 249 | alsrc->makeStopped(false); 250 | mContext.send(&MessageHandler::sourceForceStopped, alsrc); 251 | } 252 | for(SourceGroupImpl *group : mSubGroups) 253 | group->updateStoppedStatus(); 254 | } 255 | 256 | DECL_THUNK0(void, SourceGroup, stopAll, const) 257 | void SourceGroupImpl::stopAll() const 258 | { 259 | CheckContext(mContext); 260 | 261 | Vector sourceids; 262 | sourceids.reserve(16); 263 | collectSourceIds(sourceids); 264 | if(!sourceids.empty()) 265 | { 266 | auto lock = mContext.getSourceStreamLock(); 267 | alSourceRewindv(static_cast(sourceids.size()), sourceids.data()); 268 | updateStoppedStatus(); 269 | } 270 | } 271 | 272 | 273 | void SourceGroup::destroy() 274 | { 275 | SourceGroupImpl *i = pImpl; 276 | pImpl = nullptr; 277 | i->destroy(); 278 | } 279 | void SourceGroupImpl::destroy() 280 | { 281 | CheckContext(mContext); 282 | Batcher batcher = mContext.getBatcher(); 283 | for(SourceImpl *source : mSources) 284 | source->unsetGroup(); 285 | mSources.clear(); 286 | for(SourceGroupImpl *group : mSubGroups) 287 | group->unsetParent(); 288 | mSubGroups.clear(); 289 | if(mParent) 290 | mParent->eraseSubGroup(this); 291 | mParent = nullptr; 292 | 293 | mContext.freeSourceGroup(this); 294 | } 295 | 296 | 297 | DECL_THUNK0(SourceGroup, SourceGroup, getParentGroup, const) 298 | DECL_THUNK0(ALfloat, SourceGroup, getGain, const) 299 | DECL_THUNK0(ALfloat, SourceGroup, getPitch, const) 300 | 301 | } // namespace alure 302 | -------------------------------------------------------------------------------- /examples/alure-reverb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * An example showing how to load and apply a reverb effect to a source. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "alure2.h" 14 | 15 | #include "efx-presets.h" 16 | 17 | namespace { 18 | 19 | // Not UTF-8 aware! 20 | int ci_compare(alure::StringView lhs, alure::StringView rhs) 21 | { 22 | using traits = alure::StringView::traits_type; 23 | 24 | auto left = lhs.begin(); 25 | auto right = rhs.begin(); 26 | for(;left != lhs.end() && right != rhs.end();++left,++right) 27 | { 28 | int diff = std::tolower(traits::to_int_type(*left)) - 29 | std::tolower(traits::to_int_type(*right)); 30 | if(diff != 0) return (diff < 0) ? -1 : 1; 31 | } 32 | if(right != rhs.end()) return -1; 33 | if(left != lhs.end()) return 1; 34 | return 0; 35 | } 36 | 37 | #define DECL(x) { #x, EFX_REVERB_PRESET_##x } 38 | const struct ReverbEntry { 39 | const char name[32]; 40 | EFXEAXREVERBPROPERTIES props; 41 | } reverblist[] = { 42 | DECL(GENERIC), 43 | DECL(PADDEDCELL), 44 | DECL(ROOM), 45 | DECL(BATHROOM), 46 | DECL(LIVINGROOM), 47 | DECL(STONEROOM), 48 | DECL(AUDITORIUM), 49 | DECL(CONCERTHALL), 50 | DECL(CAVE), 51 | DECL(ARENA), 52 | DECL(HANGAR), 53 | DECL(CARPETEDHALLWAY), 54 | DECL(HALLWAY), 55 | DECL(STONECORRIDOR), 56 | DECL(ALLEY), 57 | DECL(FOREST), 58 | DECL(CITY), 59 | DECL(MOUNTAINS), 60 | DECL(QUARRY), 61 | DECL(PLAIN), 62 | DECL(PARKINGLOT), 63 | DECL(SEWERPIPE), 64 | DECL(UNDERWATER), 65 | DECL(DRUGGED), 66 | DECL(DIZZY), 67 | DECL(PSYCHOTIC), 68 | 69 | DECL(CASTLE_SMALLROOM), 70 | DECL(CASTLE_SHORTPASSAGE), 71 | DECL(CASTLE_MEDIUMROOM), 72 | DECL(CASTLE_LARGEROOM), 73 | DECL(CASTLE_LONGPASSAGE), 74 | DECL(CASTLE_HALL), 75 | DECL(CASTLE_CUPBOARD), 76 | DECL(CASTLE_COURTYARD), 77 | DECL(CASTLE_ALCOVE), 78 | 79 | DECL(FACTORY_SMALLROOM), 80 | DECL(FACTORY_SHORTPASSAGE), 81 | DECL(FACTORY_MEDIUMROOM), 82 | DECL(FACTORY_LARGEROOM), 83 | DECL(FACTORY_LONGPASSAGE), 84 | DECL(FACTORY_HALL), 85 | DECL(FACTORY_CUPBOARD), 86 | DECL(FACTORY_COURTYARD), 87 | DECL(FACTORY_ALCOVE), 88 | 89 | DECL(ICEPALACE_SMALLROOM), 90 | DECL(ICEPALACE_SHORTPASSAGE), 91 | DECL(ICEPALACE_MEDIUMROOM), 92 | DECL(ICEPALACE_LARGEROOM), 93 | DECL(ICEPALACE_LONGPASSAGE), 94 | DECL(ICEPALACE_HALL), 95 | DECL(ICEPALACE_CUPBOARD), 96 | DECL(ICEPALACE_COURTYARD), 97 | DECL(ICEPALACE_ALCOVE), 98 | 99 | DECL(SPACESTATION_SMALLROOM), 100 | DECL(SPACESTATION_SHORTPASSAGE), 101 | DECL(SPACESTATION_MEDIUMROOM), 102 | DECL(SPACESTATION_LARGEROOM), 103 | DECL(SPACESTATION_LONGPASSAGE), 104 | DECL(SPACESTATION_HALL), 105 | DECL(SPACESTATION_CUPBOARD), 106 | DECL(SPACESTATION_ALCOVE), 107 | 108 | DECL(WOODEN_SMALLROOM), 109 | DECL(WOODEN_SHORTPASSAGE), 110 | DECL(WOODEN_MEDIUMROOM), 111 | DECL(WOODEN_LARGEROOM), 112 | DECL(WOODEN_LONGPASSAGE), 113 | DECL(WOODEN_HALL), 114 | DECL(WOODEN_CUPBOARD), 115 | DECL(WOODEN_COURTYARD), 116 | DECL(WOODEN_ALCOVE), 117 | 118 | DECL(SPORT_EMPTYSTADIUM), 119 | DECL(SPORT_SQUASHCOURT), 120 | DECL(SPORT_SMALLSWIMMINGPOOL), 121 | DECL(SPORT_LARGESWIMMINGPOOL), 122 | DECL(SPORT_GYMNASIUM), 123 | DECL(SPORT_FULLSTADIUM), 124 | DECL(SPORT_STADIUMTANNOY), 125 | 126 | DECL(PREFAB_WORKSHOP), 127 | DECL(PREFAB_SCHOOLROOM), 128 | DECL(PREFAB_PRACTISEROOM), 129 | DECL(PREFAB_OUTHOUSE), 130 | DECL(PREFAB_CARAVAN), 131 | 132 | DECL(DOME_TOMB), 133 | DECL(PIPE_SMALL), 134 | DECL(DOME_SAINTPAULS), 135 | DECL(PIPE_LONGTHIN), 136 | DECL(PIPE_LARGE), 137 | DECL(PIPE_RESONANT), 138 | 139 | DECL(OUTDOORS_BACKYARD), 140 | DECL(OUTDOORS_ROLLINGPLAINS), 141 | DECL(OUTDOORS_DEEPCANYON), 142 | DECL(OUTDOORS_CREEK), 143 | DECL(OUTDOORS_VALLEY), 144 | 145 | DECL(MOOD_HEAVEN), 146 | DECL(MOOD_HELL), 147 | DECL(MOOD_MEMORY), 148 | 149 | DECL(DRIVING_COMMENTATOR), 150 | DECL(DRIVING_PITGARAGE), 151 | DECL(DRIVING_INCAR_RACER), 152 | DECL(DRIVING_INCAR_SPORTS), 153 | DECL(DRIVING_INCAR_LUXURY), 154 | DECL(DRIVING_FULLGRANDSTAND), 155 | DECL(DRIVING_EMPTYGRANDSTAND), 156 | DECL(DRIVING_TUNNEL), 157 | 158 | DECL(CITY_STREETS), 159 | DECL(CITY_SUBWAY), 160 | DECL(CITY_MUSEUM), 161 | DECL(CITY_LIBRARY), 162 | DECL(CITY_UNDERPASS), 163 | DECL(CITY_ABANDONED), 164 | 165 | DECL(DUSTYROOM), 166 | DECL(CHAPEL), 167 | DECL(SMALLWATERROOM), 168 | }; 169 | #undef DECL 170 | 171 | 172 | // Helper class+method to print the time with human-readable formatting. 173 | struct PrettyTime { 174 | alure::Seconds mTime; 175 | }; 176 | inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) 177 | { 178 | using hours = std::chrono::hours; 179 | using minutes = std::chrono::minutes; 180 | using seconds = std::chrono::seconds; 181 | using centiseconds = std::chrono::duration>; 182 | using std::chrono::duration_cast; 183 | 184 | centiseconds t = duration_cast(rhs.mTime); 185 | if(t.count() < 0) 186 | { 187 | os << '-'; 188 | t *= -1; 189 | } 190 | 191 | // Only handle up to hour formatting 192 | if(t >= hours(1)) 193 | os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) 194 | << duration_cast(t).count() << 'm'; 195 | else 196 | os << duration_cast(t).count() << 'm' << std::setfill('0'); 197 | os << std::setw(2) << (duration_cast(t).count() % 60) << '.' << std::setw(2) 198 | << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' '); 199 | return os; 200 | } 201 | 202 | } // namespace 203 | 204 | int main(int argc, char *argv[]) 205 | { 206 | alure::ArrayView args(argv, argc); 207 | 208 | if(args.size() < 2) 209 | { 210 | std::cerr<< "Usage: "< 2 && args[0] == alure::StringView("-device")) 219 | { 220 | dev = devMgr.openPlayback(args[1], std::nothrow); 221 | if(!dev) std::cerr<< "Failed to open \""< 1 && alure::StringView("-preset") == args[0]) 234 | { 235 | alure::StringView reverb_name = args[1]; 236 | args = args.slice(2); 237 | 238 | auto iter = std::find_if(std::begin(reverblist), std::end(reverblist), 239 | [reverb_name](const ReverbEntry &entry) -> bool 240 | { return ci_compare(reverb_name, entry.name) == 0; } 241 | ); 242 | if(iter != std::end(reverblist)) 243 | { 244 | std::cout<< "Loading preset "<name <props); 246 | } 247 | else 248 | std::cout<< "Failed to find preset "< decoder = ctx.createDecoder(args.front()); 259 | alure::Source source = ctx.createSource(); 260 | 261 | source.setAuxiliarySend(auxslot, 0); 262 | 263 | source.play(decoder, 12000, 4); 264 | std::cout<< "Playing "<getSampleType())<<", " 266 | << alure::GetChannelConfigName(decoder->getChannelConfig())<<", " 267 | << decoder->getFrequency()<<"hz)" <getFrequency(); 270 | while(source.isPlaying()) 271 | { 272 | std::cout<< "\r "<getLength()*invfreq)}; 274 | std::cout.flush(); 275 | std::this_thread::sleep_for(std::chrono::milliseconds(25)); 276 | ctx.update(); 277 | } 278 | std::cout< Hz. */ 87 | #define ALC_FREQUENCY 0x1007 88 | 89 | /** Context attribute: Hz. */ 90 | #define ALC_REFRESH 0x1008 91 | 92 | /** Context attribute: AL_TRUE or AL_FALSE. */ 93 | #define ALC_SYNC 0x1009 94 | 95 | /** Context attribute: requested Mono (3D) Sources. */ 96 | #define ALC_MONO_SOURCES 0x1010 97 | 98 | /** Context attribute: requested Stereo Sources. */ 99 | #define ALC_STEREO_SOURCES 0x1011 100 | 101 | /** No error. */ 102 | #define ALC_NO_ERROR 0 103 | 104 | /** Invalid device handle. */ 105 | #define ALC_INVALID_DEVICE 0xA001 106 | 107 | /** Invalid context handle. */ 108 | #define ALC_INVALID_CONTEXT 0xA002 109 | 110 | /** Invalid enum parameter passed to an ALC call. */ 111 | #define ALC_INVALID_ENUM 0xA003 112 | 113 | /** Invalid value parameter passed to an ALC call. */ 114 | #define ALC_INVALID_VALUE 0xA004 115 | 116 | /** Out of memory. */ 117 | #define ALC_OUT_OF_MEMORY 0xA005 118 | 119 | 120 | /** Runtime ALC version. */ 121 | #define ALC_MAJOR_VERSION 0x1000 122 | #define ALC_MINOR_VERSION 0x1001 123 | 124 | /** Context attribute list properties. */ 125 | #define ALC_ATTRIBUTES_SIZE 0x1002 126 | #define ALC_ALL_ATTRIBUTES 0x1003 127 | 128 | /** String for the default device specifier. */ 129 | #define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 130 | /** 131 | * String for the given device's specifier. 132 | * 133 | * If device handle is NULL, it is instead a null-char separated list of 134 | * strings of known device specifiers (list ends with an empty string). 135 | */ 136 | #define ALC_DEVICE_SPECIFIER 0x1005 137 | /** String for space-separated list of ALC extensions. */ 138 | #define ALC_EXTENSIONS 0x1006 139 | 140 | 141 | /** Capture extension */ 142 | #define ALC_EXT_CAPTURE 1 143 | /** 144 | * String for the given capture device's specifier. 145 | * 146 | * If device handle is NULL, it is instead a null-char separated list of 147 | * strings of known capture device specifiers (list ends with an empty string). 148 | */ 149 | #define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 150 | /** String for the default capture device specifier. */ 151 | #define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER 0x311 152 | /** Number of sample frames available for capture. */ 153 | #define ALC_CAPTURE_SAMPLES 0x312 154 | 155 | 156 | /** Enumerate All extension */ 157 | #define ALC_ENUMERATE_ALL_EXT 1 158 | /** String for the default extended device specifier. */ 159 | #define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 160 | /** 161 | * String for the given extended device's specifier. 162 | * 163 | * If device handle is NULL, it is instead a null-char separated list of 164 | * strings of known extended device specifiers (list ends with an empty string). 165 | */ 166 | #define ALC_ALL_DEVICES_SPECIFIER 0x1013 167 | 168 | 169 | /** Context management. */ 170 | ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint* attrlist); 171 | ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context); 172 | ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context); 173 | ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context); 174 | ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context); 175 | ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void); 176 | ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context); 177 | 178 | /** Device management. */ 179 | ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename); 180 | ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device); 181 | 182 | 183 | /** 184 | * Error support. 185 | * 186 | * Obtain the most recent Device error. 187 | */ 188 | ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); 189 | 190 | /** 191 | * Extension support. 192 | * 193 | * Query for the presence of an extension, and obtain any appropriate 194 | * function pointers and enum values. 195 | */ 196 | ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname); 197 | ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname); 198 | ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname); 199 | 200 | /** Query function. */ 201 | ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param); 202 | ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); 203 | 204 | /** Capture function. */ 205 | ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); 206 | ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device); 207 | ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device); 208 | ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device); 209 | ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); 210 | 211 | /** Pointer-to-function type, useful for dynamically getting ALC entry points. */ 212 | typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist); 213 | typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context); 214 | typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context); 215 | typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)(ALCcontext *context); 216 | typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)(ALCcontext *context); 217 | typedef ALCcontext* (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)(void); 218 | typedef ALCdevice* (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)(ALCcontext *context); 219 | typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename); 220 | typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device); 221 | typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device); 222 | typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname); 223 | typedef void* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname); 224 | typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname); 225 | typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param); 226 | typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); 227 | typedef ALCdevice* (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); 228 | typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)(ALCdevice *device); 229 | typedef void (ALC_APIENTRY *LPALCCAPTURESTART)(ALCdevice *device); 230 | typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)(ALCdevice *device); 231 | typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); 232 | 233 | #if defined(__cplusplus) 234 | } 235 | #endif 236 | 237 | #endif /* AL_ALC_H */ 238 | -------------------------------------------------------------------------------- /src/decoders/opusfile.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "opusfile.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "buffer.h" 9 | 10 | #include "opusfile.h" 11 | 12 | namespace { 13 | 14 | int istream_read(void *user_data, unsigned char *ptr, int size) 15 | { 16 | std::istream *stream = static_cast(user_data); 17 | stream->clear(); 18 | 19 | if(size < 0 || !stream->read(reinterpret_cast(ptr), size)) 20 | return -1; 21 | return stream->gcount(); 22 | } 23 | 24 | int istream_seek(void *user_data, opus_int64 offset, int whence) 25 | { 26 | std::istream *stream = static_cast(user_data); 27 | stream->clear(); 28 | 29 | if(whence == SEEK_CUR) 30 | stream->seekg(offset, std::ios_base::cur); 31 | else if(whence == SEEK_SET) 32 | stream->seekg(offset, std::ios_base::beg); 33 | else if(whence == SEEK_END) 34 | stream->seekg(offset, std::ios_base::end); 35 | else 36 | return -1; 37 | 38 | return stream->good() ? 0 : -1; 39 | } 40 | 41 | opus_int64 istream_tell(void *user_data) 42 | { 43 | std::istream *stream = static_cast(user_data); 44 | stream->clear(); 45 | return stream->tellg(); 46 | } 47 | 48 | 49 | template struct OggTypeInfo { }; 50 | template<> 51 | struct OggTypeInfo { 52 | template 53 | static int read(Args&& ...args) 54 | { return op_read(std::forward(args)...); } 55 | }; 56 | template<> 57 | struct OggTypeInfo { 58 | template 59 | static int read(Args&& ...args) 60 | { return op_read_float(std::forward(args)...); } 61 | }; 62 | 63 | 64 | struct OggOpusFileDeleter { 65 | void operator()(OggOpusFile *ptr) const { op_free(ptr); } 66 | }; 67 | using OggOpusFilePtr = alure::UniquePtr; 68 | 69 | } // namespace 70 | 71 | namespace alure { 72 | 73 | class OpusFileDecoder final : public Decoder { 74 | UniquePtr mFile; 75 | 76 | OggOpusFilePtr mOggFile; 77 | int mOggBitstream{0}; 78 | 79 | ChannelConfig mChannelConfig{ChannelConfig::Mono}; 80 | SampleType mSampleType{SampleType::UInt8}; 81 | 82 | std::pair mLoopPts{0, 0}; 83 | 84 | template 85 | ALuint do_read(T *ptr, ALuint count) noexcept 86 | { 87 | ALuint total = 0; 88 | T *samples = ptr; 89 | int num_chans = FramesToBytes(1, mChannelConfig, SampleType::UInt8); 90 | while(total < count) 91 | { 92 | if(num_chans != op_head(mOggFile.get(), -1)->channel_count) 93 | break; 94 | int len = (count-total) * num_chans; 95 | 96 | long got = OggTypeInfo::read(mOggFile.get(), samples, len, &mOggBitstream); 97 | if(got <= 0) break; 98 | 99 | samples += got*num_chans; 100 | total += got; 101 | } 102 | 103 | // 1, 2, and 4 channel files decode into the same channel order as 104 | // OpenAL, however 6 (5.1), 7 (6.1), and 8 (7.1) channel files need to be 105 | // re-ordered. 106 | if(mChannelConfig == ChannelConfig::X51) 107 | { 108 | samples = ptr; 109 | for(ALuint i = 0;i < total;++i) 110 | { 111 | // OpenAL : FL, FR, FC, LFE, RL, RR 112 | // Opus : FL, FC, FR, RL, RR, LFE 113 | std::swap(samples[i*6 + 1], samples[i*6 + 2]); 114 | std::swap(samples[i*6 + 3], samples[i*6 + 5]); 115 | std::swap(samples[i*6 + 4], samples[i*6 + 5]); 116 | } 117 | } 118 | else if(mChannelConfig == ChannelConfig::X61) 119 | { 120 | samples = ptr; 121 | for(ALuint i = 0;i < total;++i) 122 | { 123 | // OpenAL : FL, FR, FC, LFE, RC, SL, SR 124 | // Opus : FL, FC, FR, SL, SR, RC, LFE 125 | std::swap(samples[i*7 + 1], samples[i*7 + 2]); 126 | std::swap(samples[i*7 + 3], samples[i*7 + 6]); 127 | std::swap(samples[i*7 + 4], samples[i*7 + 5]); 128 | std::swap(samples[i*7 + 5], samples[i*7 + 6]); 129 | } 130 | } 131 | else if(mChannelConfig == ChannelConfig::X71) 132 | { 133 | samples = ptr; 134 | for(ALuint i = 0;i < total;++i) 135 | { 136 | // OpenAL : FL, FR, FC, LFE, RL, RR, SL, SR 137 | // Opus : FL, FC, FR, SL, SR, RL, RR, LFE 138 | std::swap(samples[i*8 + 1], samples[i*8 + 2]); 139 | std::swap(samples[i*8 + 3], samples[i*8 + 7]); 140 | std::swap(samples[i*8 + 4], samples[i*8 + 5]); 141 | std::swap(samples[i*8 + 5], samples[i*8 + 6]); 142 | std::swap(samples[i*8 + 6], samples[i*8 + 7]); 143 | } 144 | } 145 | 146 | return total; 147 | } 148 | 149 | public: 150 | OpusFileDecoder(UniquePtr file, OggOpusFilePtr oggfile, ChannelConfig sconfig, 151 | SampleType stype, const std::pair &loop_points) noexcept 152 | : mFile(std::move(file)), mOggFile(std::move(oggfile)), mChannelConfig(sconfig) 153 | , mSampleType(stype), mLoopPts(loop_points) 154 | { } 155 | ~OpusFileDecoder() override { } 156 | 157 | ALuint getFrequency() const noexcept override; 158 | ChannelConfig getChannelConfig() const noexcept override; 159 | SampleType getSampleType() const noexcept override; 160 | 161 | uint64_t getLength() const noexcept override; 162 | bool seek(uint64_t pos) noexcept override; 163 | 164 | std::pair getLoopPoints() const noexcept override; 165 | 166 | ALuint read(ALvoid *ptr, ALuint count) noexcept override; 167 | }; 168 | 169 | // libopusfile always decodes to 48khz. 170 | ALuint OpusFileDecoder::getFrequency() const noexcept { return 48000; } 171 | ChannelConfig OpusFileDecoder::getChannelConfig() const noexcept { return mChannelConfig; } 172 | SampleType OpusFileDecoder::getSampleType() const noexcept { return mSampleType; } 173 | 174 | uint64_t OpusFileDecoder::getLength() const noexcept 175 | { 176 | ogg_int64_t len = op_pcm_total(mOggFile.get(), -1); 177 | return std::max(len, 0); 178 | } 179 | 180 | bool OpusFileDecoder::seek(uint64_t pos) noexcept 181 | { 182 | return op_pcm_seek(mOggFile.get(), pos) == 0; 183 | } 184 | 185 | std::pair OpusFileDecoder::getLoopPoints() const noexcept 186 | { 187 | return mLoopPts; 188 | } 189 | 190 | ALuint OpusFileDecoder::read(ALvoid *ptr, ALuint count) noexcept 191 | { 192 | if(mSampleType == SampleType::Float32) 193 | return do_read(reinterpret_cast(ptr), count); 194 | return do_read(reinterpret_cast(ptr), count); 195 | } 196 | 197 | 198 | SharedPtr OpusFileDecoderFactory::createDecoder(UniquePtr &file) noexcept 199 | { 200 | static const OpusFileCallbacks streamIO = { 201 | istream_read, istream_seek, istream_tell, nullptr 202 | }; 203 | 204 | OggOpusFilePtr oggfile(op_open_callbacks(file.get(), &streamIO, nullptr, 0, nullptr)); 205 | if(!oggfile) return nullptr; 206 | 207 | std::pair loop_points = { 0, std::numeric_limits::max() }; 208 | if(const OpusTags *tags = op_tags(oggfile.get(), -1)) 209 | { 210 | for(int i = 0;i < tags->comments;i++) 211 | { 212 | StringView val(tags->user_comments[i], tags->comment_lengths[i]); 213 | auto seppos = val.find_first_of('='); 214 | if(seppos == StringView::npos) continue; 215 | 216 | StringView key = val.substr(0, seppos); 217 | val = val.substr(seppos+1); 218 | 219 | // RPG Maker seems to recognize LOOPSTART and LOOPLENGTH for loop 220 | // points in a Vorbis comment. ZDoom recognizes LOOP_START and 221 | // LOOP_END. We can recognize both. 222 | if(key == "LOOP_START" || key == "LOOPSTART") 223 | { 224 | auto pt = ParseTimeval(val, 48000.0); 225 | if(pt.index() == 1) loop_points.first = std::get<1>(pt); 226 | continue; 227 | } 228 | 229 | if(key == "LOOP_END") 230 | { 231 | auto pt = ParseTimeval(val, 48000.0); 232 | if(pt.index() == 1) loop_points.second = std::get<1>(pt); 233 | continue; 234 | } 235 | 236 | if(key == "LOOPLENGTH") 237 | { 238 | auto pt = ParseTimeval(val, 48000.0); 239 | if(pt.index() == 1) 240 | loop_points.second = loop_points.first + std::get<1>(pt); 241 | continue; 242 | } 243 | } 244 | } 245 | 246 | int num_chans = op_head(oggfile.get(), -1)->channel_count; 247 | ChannelConfig channels = ChannelConfig::Mono; 248 | if(num_chans == 1) 249 | channels = ChannelConfig::Mono; 250 | else if(num_chans == 2) 251 | channels = ChannelConfig::Stereo; 252 | else if(num_chans == 4) 253 | channels = ChannelConfig::Quad; 254 | else if(num_chans == 6) 255 | channels = ChannelConfig::X51; 256 | else if(num_chans == 7) 257 | channels = ChannelConfig::X61; 258 | else if(num_chans == 8) 259 | channels = ChannelConfig::X71; 260 | else 261 | return nullptr; 262 | 263 | if(Context::GetCurrent().isSupported(channels, SampleType::Float32)) 264 | return MakeShared(std::move(file), std::move(oggfile), channels, 265 | SampleType::Float32, loop_points); 266 | return MakeShared(std::move(file), std::move(oggfile), channels, 267 | SampleType::Int16, loop_points); 268 | } 269 | 270 | } // namespace alure 271 | -------------------------------------------------------------------------------- /examples/alure-dumb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * An example showing how to use an external decoder to play files through the 3 | * DUMB library. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "dumb.h" 14 | 15 | #include "alure2.h" 16 | 17 | namespace { 18 | 19 | // Some I/O function callback wrappers for DUMB to read from an std::istream 20 | #if DUMB_VERSION >= 2*10000 21 | int cb_skip(void *user_data, dumb_off_t offset) 22 | #else 23 | int cb_skip(void *user_data, long offset) 24 | #endif 25 | { 26 | std::istream *stream = static_cast(user_data); 27 | stream->clear(); 28 | 29 | if(stream->seekg(offset, std::ios_base::cur)) 30 | return 0; 31 | return 1; 32 | } 33 | 34 | #if DUMB_VERSION >= 2*10000 35 | dumb_ssize_t cb_read(char *ptr, size_t size, void *user_data) 36 | #else 37 | long cb_read(char *ptr, long size, void *user_data) 38 | #endif 39 | { 40 | std::istream *stream = static_cast(user_data); 41 | stream->clear(); 42 | 43 | stream->read(ptr, size); 44 | return stream->gcount(); 45 | } 46 | 47 | static int cb_read_char(void *user_data) 48 | { 49 | std::istream *stream = static_cast(user_data); 50 | stream->clear(); 51 | 52 | unsigned char ret; 53 | stream->read(reinterpret_cast(&ret), 1); 54 | if(stream->gcount() > 0) return ret; 55 | return -1; 56 | } 57 | 58 | 59 | // Inherit from alure::Decoder to make a custom decoder (DUMB for this example) 60 | class DumbDecoder final : public alure::Decoder { 61 | alure::UniquePtr mFile; 62 | 63 | alure::UniquePtr mDfs; 64 | DUMBFILE *mDumbfile{nullptr}; 65 | DUH *mDuh{nullptr}; 66 | DUH_SIGRENDERER *mRenderer{nullptr}; 67 | 68 | alure::SampleType mSampleType{alure::SampleType::UInt8}; 69 | ALuint mFrequency{0}; 70 | 71 | alure::Vector mSampleBuf; 72 | 73 | public: 74 | DumbDecoder(alure::UniquePtr file, alure::UniquePtr dfs, 75 | DUMBFILE *dfile, DUH *duh, DUH_SIGRENDERER *renderer, alure::SampleType stype, 76 | ALuint srate) noexcept 77 | : mFile(std::move(file)), mDfs(std::move(dfs)), mDumbfile(dfile), mDuh(duh) 78 | , mRenderer(renderer), mSampleType(stype), mFrequency(srate) 79 | { } 80 | ~DumbDecoder() override 81 | { 82 | duh_end_sigrenderer(mRenderer); 83 | mRenderer = nullptr; 84 | 85 | unload_duh(mDuh); 86 | mDuh = nullptr; 87 | 88 | dumbfile_close(mDumbfile); 89 | mDumbfile = nullptr; 90 | } 91 | 92 | ALuint getFrequency() const noexcept override 93 | { return mFrequency; } 94 | alure::ChannelConfig getChannelConfig() const noexcept override 95 | { 96 | // We always have DUMB render to stereo 97 | return alure::ChannelConfig::Stereo; 98 | } 99 | alure::SampleType getSampleType() const noexcept override 100 | { 101 | // DUMB renders to 8.24 normalized fixed point, which we convert to 102 | // 32-bit float or signed 16-bit samples 103 | return mSampleType; 104 | } 105 | 106 | uint64_t getLength() const noexcept override 107 | { 108 | // Modules have no explicit length, they just keep playing as long as 109 | // more samples get generated. 110 | return 0; 111 | } 112 | 113 | bool seek(uint64_t) noexcept override 114 | { 115 | // Cannot seek 116 | return false; 117 | } 118 | 119 | std::pair getLoopPoints() const noexcept override 120 | { 121 | // No loop points 122 | return std::make_pair(0, 0); 123 | } 124 | 125 | ALuint read(ALvoid *ptr, ALuint count) noexcept override 126 | { 127 | ALuint ret = 0; 128 | 129 | mSampleBuf.resize(count*2); 130 | alure::Array samples{{mSampleBuf.data()}}; 131 | 132 | dumb_silence(samples[0], mSampleBuf.size()); 133 | ret = duh_sigrenderer_generate_samples(mRenderer, 1.0f, 65536.0f/mFrequency, count, 134 | samples.data()); 135 | if(mSampleType == alure::SampleType::Float32) 136 | { 137 | ALfloat *out = reinterpret_cast(ptr); 138 | for(ALuint i = 0;i < ret*2;i++) 139 | out[i] = (ALfloat)samples[0][i] * (1.0f/8388608.0f); 140 | } 141 | else 142 | { 143 | ALshort *out = reinterpret_cast(ptr); 144 | for(ALuint i = 0;i < ret*2;i++) 145 | { 146 | sample_t smp = samples[0][i]>>8; 147 | if(smp < -32768) smp = -32768; 148 | else if(smp > 32767) smp = 32767; 149 | out[i] = smp; 150 | } 151 | } 152 | 153 | return ret; 154 | } 155 | }; 156 | 157 | // Inherit from alure::DecoderFactory to use our custom decoder 158 | class DumbFactory final : public alure::DecoderFactory { 159 | alure::SharedPtr createDecoder(alure::UniquePtr &file) noexcept override 160 | { 161 | static const alure::Array init_funcs{{ 162 | dumb_read_it, dumb_read_xm, dumb_read_s3m 163 | }}; 164 | 165 | auto dfs = alure::MakeUnique(); 166 | std::memset(dfs.get(), 0, sizeof(DUMBFILE_SYSTEM)); 167 | dfs->open = nullptr; 168 | dfs->skip = cb_skip; 169 | dfs->getc = cb_read_char; 170 | dfs->getnc = cb_read; 171 | dfs->close = nullptr; 172 | 173 | alure::Context ctx = alure::Context::GetCurrent(); 174 | alure::SampleType stype = alure::SampleType::Float32; 175 | if(!ctx.isSupported(alure::ChannelConfig::Stereo, stype)) 176 | stype = alure::SampleType::Int16; 177 | ALuint freq = ctx.getDevice().getFrequency(); 178 | 179 | DUMBFILE *dfile = nullptr; 180 | for(auto init : init_funcs) 181 | { 182 | dfile = dumbfile_open_ex(file.get(), dfs.get()); 183 | if(!dfile) return nullptr; 184 | 185 | DUH *duh; 186 | if((duh=init(dfile)) != nullptr) 187 | { 188 | DUH_SIGRENDERER *renderer; 189 | if((renderer=duh_start_sigrenderer(duh, 0, 2, 0)) != nullptr) 190 | return alure::MakeShared( 191 | std::move(file), std::move(dfs), dfile, duh, renderer, stype, freq 192 | ); 193 | 194 | unload_duh(duh); 195 | duh = nullptr; 196 | } 197 | 198 | dumbfile_close(dfile); 199 | dfile = nullptr; 200 | 201 | file->clear(); 202 | if(!file->seekg(0)) 203 | break; 204 | } 205 | 206 | return nullptr; 207 | } 208 | }; 209 | 210 | 211 | // Helper class+method to print the time with human-readable formatting. 212 | struct PrettyTime { 213 | alure::Seconds mTime; 214 | }; 215 | inline std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) 216 | { 217 | using hours = std::chrono::hours; 218 | using minutes = std::chrono::minutes; 219 | using seconds = std::chrono::seconds; 220 | using centiseconds = std::chrono::duration>; 221 | using std::chrono::duration_cast; 222 | 223 | centiseconds t = duration_cast(rhs.mTime); 224 | if(t.count() < 0) 225 | { 226 | os << '-'; 227 | t *= -1; 228 | } 229 | 230 | // Only handle up to hour formatting 231 | if(t >= hours(1)) 232 | os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) 233 | << duration_cast(t).count() << 'm'; 234 | else 235 | os << duration_cast(t).count() << 'm' << std::setfill('0'); 236 | os << std::setw(2) << (duration_cast(t).count() % 60) << '.' << std::setw(2) 237 | << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' '); 238 | return os; 239 | } 240 | 241 | } // namespace 242 | 243 | 244 | int main(int argc, char *argv[]) 245 | { 246 | alure::ArrayView args(argv, argc); 247 | 248 | if(args.size() < 2) 249 | { 250 | std::cerr<< "Usage: "<()); 257 | 258 | alure::DeviceManager devMgr = alure::DeviceManager::getInstance(); 259 | 260 | alure::Device dev; 261 | if(args.size() > 2 && args[0] == alure::StringView("-device")) 262 | { 263 | dev = devMgr.openPlayback(args[1], std::nothrow); 264 | if(!dev) std::cerr<< "Failed to open \""< decoder = ctx.createDecoder(args.front()); 276 | alure::Source source = ctx.createSource(); 277 | 278 | source.play(decoder, 12000, 4); 279 | std::cout<< "Playing "<getSampleType())<<", " 281 | << alure::GetChannelConfigName(decoder->getChannelConfig())<<", " 282 | << decoder->getFrequency()<<"hz)" < 5 | #include 6 | 7 | #include "sndfile.h" 8 | 9 | namespace { 10 | 11 | constexpr alure::Array CHANNELS_MONO {{SF_CHANNEL_MAP_MONO}}; 12 | constexpr alure::Array CHANNELS_STEREO {{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT}}; 13 | constexpr alure::Array CHANNELS_REAR {{SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; 14 | constexpr alure::Array CHANNELS_QUAD {{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; 15 | constexpr alure::Array CHANNELS_5DOT1 {{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; 16 | constexpr alure::Array CHANNELS_5DOT1_REAR{{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT}}; 17 | constexpr alure::Array CHANNELS_6DOT1 {{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, SF_CHANNEL_MAP_REAR_CENTER, SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; 18 | constexpr alure::Array CHANNELS_7DOT1 {{SF_CHANNEL_MAP_LEFT, SF_CHANNEL_MAP_RIGHT, SF_CHANNEL_MAP_CENTER, SF_CHANNEL_MAP_LFE, SF_CHANNEL_MAP_REAR_LEFT, SF_CHANNEL_MAP_REAR_RIGHT, SF_CHANNEL_MAP_SIDE_LEFT, SF_CHANNEL_MAP_SIDE_RIGHT}}; 19 | constexpr alure::Array CHANNELS_BFORMAT2D {{SF_CHANNEL_MAP_AMBISONIC_B_W, SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y}}; 20 | constexpr alure::Array CHANNELS_BFORMAT3D {{SF_CHANNEL_MAP_AMBISONIC_B_W, SF_CHANNEL_MAP_AMBISONIC_B_X, SF_CHANNEL_MAP_AMBISONIC_B_Y, SF_CHANNEL_MAP_AMBISONIC_B_Z}}; 21 | 22 | 23 | sf_count_t istream_get_filelen(void *user_data) 24 | { 25 | std::istream *file = reinterpret_cast(user_data); 26 | file->clear(); 27 | 28 | sf_count_t len = -1; 29 | std::streampos pos = file->tellg(); 30 | if(pos != static_cast(-1) && file->seekg(0, std::ios::end)) 31 | { 32 | len = file->tellg(); 33 | file->seekg(pos); 34 | } 35 | return len; 36 | } 37 | 38 | sf_count_t istream_seek(sf_count_t offset, int whence, void *user_data) 39 | { 40 | std::istream *file = reinterpret_cast(user_data); 41 | file->clear(); 42 | 43 | if(!file->seekg(offset, std::ios::seekdir(whence))) 44 | return -1; 45 | return file->tellg(); 46 | } 47 | 48 | sf_count_t istream_read(void *ptr, sf_count_t count, void *user_data) 49 | { 50 | std::istream *file = reinterpret_cast(user_data); 51 | file->clear(); 52 | 53 | file->read(reinterpret_cast(ptr), count); 54 | return file->gcount(); 55 | } 56 | 57 | sf_count_t istream_write(const void*, sf_count_t, void*) 58 | { 59 | return -1; 60 | } 61 | 62 | sf_count_t istream_tell(void *user_data) 63 | { 64 | std::istream *file = reinterpret_cast(user_data); 65 | file->clear(); 66 | 67 | return file->tellg(); 68 | } 69 | 70 | 71 | struct SndfileDeleter { 72 | void operator()(SNDFILE *ptr) const { sf_close(ptr); } 73 | }; 74 | using SndfilePtr = alure::UniquePtr; 75 | 76 | } // namespace 77 | 78 | namespace alure { 79 | 80 | class SndFileDecoder final : public Decoder { 81 | UniquePtr mFile; 82 | 83 | SndfilePtr mSndFile; 84 | SF_INFO mSndInfo; 85 | 86 | ChannelConfig mChannelConfig{ChannelConfig::Mono}; 87 | SampleType mSampleType{SampleType::UInt8}; 88 | std::pair mLoopPts{0, 0}; 89 | 90 | public: 91 | SndFileDecoder(UniquePtr file, SndfilePtr sndfile, const SF_INFO &sndinfo, 92 | ChannelConfig sconfig, SampleType stype, uint64_t loopstart, uint64_t loopend) noexcept 93 | : mFile(std::move(file)), mSndFile(std::move(sndfile)), mSndInfo(sndinfo) 94 | , mChannelConfig(sconfig), mSampleType(stype), mLoopPts{loopstart, loopend} 95 | { } 96 | ~SndFileDecoder() override { } 97 | 98 | ALuint getFrequency() const noexcept override; 99 | ChannelConfig getChannelConfig() const noexcept override; 100 | SampleType getSampleType() const noexcept override; 101 | 102 | uint64_t getLength() const noexcept override; 103 | bool seek(uint64_t pos) noexcept override; 104 | 105 | std::pair getLoopPoints() const noexcept override; 106 | 107 | ALuint read(ALvoid *ptr, ALuint count) noexcept override; 108 | }; 109 | 110 | ALuint SndFileDecoder::getFrequency() const noexcept { return mSndInfo.samplerate; } 111 | ChannelConfig SndFileDecoder::getChannelConfig() const noexcept { return mChannelConfig; } 112 | SampleType SndFileDecoder::getSampleType() const noexcept { return mSampleType; } 113 | 114 | uint64_t SndFileDecoder::getLength() const noexcept 115 | { 116 | return std::max(mSndInfo.frames, 0); 117 | } 118 | 119 | bool SndFileDecoder::seek(uint64_t pos) noexcept 120 | { 121 | sf_count_t newpos = sf_seek(mSndFile.get(), pos, SEEK_SET); 122 | if(newpos < 0) return false; 123 | return true; 124 | } 125 | 126 | std::pair SndFileDecoder::getLoopPoints() const noexcept { return mLoopPts; } 127 | 128 | ALuint SndFileDecoder::read(ALvoid *ptr, ALuint count) noexcept 129 | { 130 | sf_count_t got = 0; 131 | switch (mSampleType) 132 | { 133 | case SampleType::Mulaw: 134 | case SampleType::UInt8: 135 | got = sf_read_raw(mSndFile.get(), static_cast(ptr), 136 | FramesToBytes(count, mChannelConfig, mSampleType)); 137 | got = BytesToFrames(got, mChannelConfig, mSampleType); 138 | break; 139 | case SampleType::Int16: 140 | got = sf_readf_short(mSndFile.get(), static_cast(ptr), count); 141 | break; 142 | case SampleType::Float32: 143 | got = sf_readf_float(mSndFile.get(), static_cast(ptr), count); 144 | break; 145 | } 146 | return (ALuint)std::max(got, 0); 147 | } 148 | 149 | 150 | SharedPtr SndFileDecoderFactory::createDecoder(UniquePtr &file) noexcept 151 | { 152 | SF_VIRTUAL_IO vio = { 153 | istream_get_filelen, istream_seek, 154 | istream_read, istream_write, istream_tell 155 | }; 156 | SF_INFO sndinfo; 157 | SndfilePtr sndfile(sf_open_virtual(&vio, SFM_READ, &sndinfo, file.get())); 158 | if(!sndfile) return nullptr; 159 | 160 | std::pair cue_points{0, std::numeric_limits::max()}; 161 | { 162 | // Needed for compatibility with older sndfile libraries. 163 | struct SNDFILE_CUE_POINT { 164 | int32_t indx; 165 | uint32_t position; 166 | int32_t fcc_chunk; 167 | int32_t chunk_start; 168 | int32_t block_start; 169 | uint32_t sample_offset; 170 | char name[256]; 171 | }; 172 | 173 | struct { 174 | uint32_t cue_count; 175 | SNDFILE_CUE_POINT cue_points[100]; 176 | } cues; 177 | 178 | enum { SNDFILE_GET_CUE = 0x10CE }; 179 | 180 | if(sf_command(sndfile.get(), SNDFILE_GET_CUE, &cues, sizeof(cues))) 181 | { 182 | cue_points.first = cues.cue_points[0].sample_offset; 183 | if(cues.cue_count > 1) 184 | { 185 | cue_points.second = cues.cue_points[1].sample_offset; 186 | } 187 | } 188 | } 189 | 190 | ChannelConfig sconfig; 191 | Vector chanmap(sndinfo.channels); 192 | if(sf_command(sndfile.get(), SFC_GET_CHANNEL_MAP_INFO, chanmap.data(), chanmap.size()*sizeof(int)) == SF_TRUE) 193 | { 194 | auto matches = [](const Vector &first, ArrayView second) -> bool 195 | { 196 | return (first.size() == second.size()) && 197 | std::equal(first.begin(), first.end(), second.begin()); 198 | }; 199 | 200 | if(matches(chanmap, CHANNELS_MONO)) 201 | sconfig = ChannelConfig::Mono; 202 | else if(matches(chanmap, CHANNELS_STEREO)) 203 | sconfig = ChannelConfig::Stereo; 204 | else if(matches(chanmap, CHANNELS_REAR)) 205 | sconfig = ChannelConfig::Rear; 206 | else if(matches(chanmap, CHANNELS_QUAD)) 207 | sconfig = ChannelConfig::Quad; 208 | else if(matches(chanmap, CHANNELS_5DOT1) || matches(chanmap, CHANNELS_5DOT1_REAR)) 209 | sconfig = ChannelConfig::X51; 210 | else if(matches(chanmap, CHANNELS_6DOT1)) 211 | sconfig = ChannelConfig::X61; 212 | else if(matches(chanmap, CHANNELS_7DOT1)) 213 | sconfig = ChannelConfig::X71; 214 | else if(matches(chanmap, CHANNELS_BFORMAT2D)) 215 | sconfig = ChannelConfig::BFormat2D; 216 | else if(matches(chanmap, CHANNELS_BFORMAT3D)) 217 | sconfig = ChannelConfig::BFormat3D; 218 | else 219 | return nullptr; 220 | } 221 | else if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) 222 | { 223 | if(sndinfo.channels == 3) 224 | sconfig = ChannelConfig::BFormat2D; 225 | else if(sndinfo.channels == 4) 226 | sconfig = ChannelConfig::BFormat3D; 227 | else 228 | return nullptr; 229 | } 230 | else if(sndinfo.channels == 1) 231 | sconfig = ChannelConfig::Mono; 232 | else if(sndinfo.channels == 2) 233 | sconfig = ChannelConfig::Stereo; 234 | else 235 | return nullptr; 236 | 237 | SampleType stype = SampleType::Int16; 238 | switch(sndinfo.format&SF_FORMAT_SUBMASK) 239 | { 240 | case SF_FORMAT_PCM_U8: 241 | stype = SampleType::UInt8; 242 | break; 243 | case SF_FORMAT_ULAW: 244 | if(Context::GetCurrent().isSupported(sconfig, SampleType::Mulaw)) 245 | stype = SampleType::Mulaw; 246 | break; 247 | case SF_FORMAT_FLOAT: 248 | case SF_FORMAT_DOUBLE: 249 | case SF_FORMAT_VORBIS: 250 | if(Context::GetCurrent().isSupported(sconfig, SampleType::Float32)) 251 | stype = SampleType::Float32; 252 | break; 253 | default: 254 | // For everything else, decode to signed 16-bit 255 | stype = SampleType::Int16; 256 | break; 257 | } 258 | 259 | return MakeShared(std::move(file), std::move(sndfile), sndinfo, sconfig, stype, 260 | cue_points.first, cue_points.second); 261 | } 262 | 263 | } // namespace alure 264 | -------------------------------------------------------------------------------- /include/AL/alure2-typeviews.h: -------------------------------------------------------------------------------- 1 | /********* 2 | * Implements the classes alure::ArrayView (non-owning, read-only access to a 3 | * contiguous array of elements), and alure::StringView (non-owning, read-only 4 | * access to an array of chars). These help pass around contiguous arrays and 5 | * strings from various sources without copying. 6 | */ 7 | 8 | #ifndef AL_ALURE2_TYPEVIEWS_H 9 | #define AL_ALURE2_TYPEVIEWS_H 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "alure2-aliases.h" 16 | 17 | namespace alure { 18 | 19 | // Tag specific containers that guarantee contiguous storage. The standard 20 | // provides no such mechanism, so we have to manually specify which are 21 | // acceptable. 22 | template struct IsContiguousTag : std::false_type {}; 23 | template struct IsContiguousTag> : std::true_type {}; 24 | template struct IsContiguousTag> : std::true_type {}; 25 | template struct IsContiguousTag> : std::true_type {}; 26 | 27 | // A rather simple ArrayView container. This allows accepting various array 28 | // types (Array, Vector, a static-sized array, a dynamic array + size) without 29 | // copying its elements. 30 | template 31 | class ArrayView { 32 | public: 33 | using value_type = T; 34 | 35 | using iterator = const value_type*; 36 | using const_iterator = const value_type*; 37 | 38 | using reverse_iterator = std::reverse_iterator; 39 | using const_reverse_iterator = std::reverse_iterator; 40 | 41 | using size_type = size_t; 42 | 43 | static constexpr size_type npos = static_cast(-1); 44 | 45 | private: 46 | const value_type *mStart; 47 | const value_type *mEnd; 48 | 49 | public: 50 | ArrayView() noexcept : mStart(nullptr), mEnd(nullptr) { } 51 | ArrayView(const ArrayView&) noexcept = default; 52 | ArrayView(ArrayView&&) noexcept = default; 53 | ArrayView(const value_type *elems, size_type num_elems) noexcept 54 | : mStart(elems), mEnd(elems+num_elems) { } 55 | // TODO: Allow this? As a function parameter, making a view to a temporary 56 | // is fine since the temporary will exist for the duration of the call. 57 | // It's just a problem when done as a local variable. 58 | template ArrayView(RemoveRefT&&) = delete; 59 | template>::value>> 61 | ArrayView(const OtherT &rhs) noexcept : mStart(rhs.data()), mEnd(rhs.data()+rhs.size()) { } 62 | template 63 | ArrayView(const value_type (&elems)[N]) noexcept : mStart(elems), mEnd(elems+N) { } 64 | 65 | ArrayView& operator=(const ArrayView&) noexcept = default; 66 | 67 | const value_type *data() const noexcept { return mStart; } 68 | 69 | size_type size() const noexcept { return mEnd - mStart; } 70 | bool empty() const noexcept { return mStart == mEnd; } 71 | 72 | const value_type& operator[](size_t i) const noexcept { return mStart[i]; } 73 | 74 | const value_type& front() const noexcept { return *mStart; } 75 | const value_type& back() const noexcept { return *(mEnd - 1); } 76 | 77 | const value_type& at(size_t i) const 78 | { 79 | if(i >= size()) 80 | throw std::out_of_range("alure::ArrayView::at: element out of range"); 81 | return mStart[i]; 82 | } 83 | 84 | const_iterator begin() const noexcept { return mStart; } 85 | const_iterator cbegin() const noexcept { return mStart; } 86 | 87 | const_iterator end() const noexcept { return mEnd; } 88 | const_iterator cend() const noexcept { return mEnd; } 89 | 90 | const_reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } 91 | const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); } 92 | 93 | const_reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } 94 | const_reverse_iterator crend() const noexcept { return const_reverse_iterator(cbegin()); } 95 | 96 | ArrayView slice(size_type pos, size_type len = npos) const 97 | { 98 | if(pos > size()) 99 | throw std::out_of_range("alure::ArrayView::slice: pos out of range"); 100 | if(size()-pos < len) return ArrayView(data()+pos, size()-pos); 101 | return ArrayView(data()+pos, len); 102 | } 103 | 104 | template 105 | ArrayView reinterpret_as() const 106 | { 107 | // Make sure the current view is properly aligned to be interpreted as 108 | // the new type. 109 | if((reinterpret_cast(mStart) & (alignof(U)-1)) != 0) 110 | throw std::runtime_error( 111 | "alure::ArrayView::reinterpret_as: invalid alignment for type"); 112 | 113 | size_t new_length = 114 | (reinterpret_cast(mEnd) - reinterpret_cast(mStart)) / 115 | sizeof(U); 116 | return ArrayView(reinterpret_cast(mStart), new_length); 117 | } 118 | }; 119 | 120 | template> 121 | class BasicStringView : public ArrayView { 122 | public: 123 | using char_type = T; 124 | using traits_type = Tr; 125 | using size_type = size_t; 126 | 127 | static constexpr size_type npos = static_cast(-1); 128 | 129 | BasicStringView() noexcept = default; 130 | BasicStringView(const BasicStringView&) noexcept = default; 131 | BasicStringView(const char_type *elems, size_type num_elems) noexcept 132 | : ArrayView(elems, num_elems) { } 133 | BasicStringView(const char_type *elems) : ArrayView(elems, traits_type::length(elems)) { } 134 | template 135 | BasicStringView(BasicString&&) = delete; 136 | template 137 | BasicStringView(const BasicString &rhs) noexcept : ArrayView(rhs) { } 138 | #if __cplusplus >= 201703L 139 | BasicStringView(const std::basic_string_view &rhs) noexcept 140 | : ArrayView(rhs.data(), rhs.length()) { } 141 | #endif 142 | 143 | BasicStringView& operator=(const BasicStringView&) noexcept = default; 144 | 145 | size_type length() const noexcept { return this->size(); } 146 | 147 | template 148 | explicit operator BasicString() const 149 | { return BasicString(this->data(), length()); } 150 | #if __cplusplus >= 201703L 151 | operator std::basic_string_view() const noexcept 152 | { return std::basic_string_view(this->data(), length()); } 153 | #endif 154 | 155 | template 156 | BasicString operator+(const BasicString &rhs) const 157 | { 158 | BasicString ret = BasicString(*this); 159 | ret += rhs; 160 | return ret; 161 | } 162 | 163 | int compare(BasicStringView other) const noexcept 164 | { 165 | int ret = traits_type::compare( 166 | this->data(), other.data(), std::min(length(), other.length()) 167 | ); 168 | if(ret == 0) 169 | { 170 | if(length() > other.length()) return 1; 171 | if(length() < other.length()) return -1; 172 | return 0; 173 | } 174 | return ret; 175 | } 176 | bool operator==(BasicStringView rhs) const noexcept { return compare(rhs) == 0; } 177 | bool operator!=(BasicStringView rhs) const noexcept { return compare(rhs) != 0; } 178 | bool operator<=(BasicStringView rhs) const noexcept { return compare(rhs) <= 0; } 179 | bool operator>=(BasicStringView rhs) const noexcept { return compare(rhs) >= 0; } 180 | bool operator<(BasicStringView rhs) const noexcept { return compare(rhs) < 0; } 181 | bool operator>(BasicStringView rhs) const noexcept { return compare(rhs) > 0; } 182 | 183 | BasicStringView substr(size_type pos, size_type len = npos) const 184 | { 185 | if(pos > length()) 186 | throw std::out_of_range("alure::BasicStringView::substr: pos out of range"); 187 | if(length()-pos < len) return BasicStringView(this->data()+pos, length()-pos); 188 | return BasicStringView(this->data()+pos, len); 189 | } 190 | 191 | size_type find_first_of(char_type ch, size_type pos = 0) const noexcept 192 | { 193 | if(pos >= length()) return npos; 194 | const char_type *chpos = traits_type::find(this->data()+pos, length()-pos, ch); 195 | if(chpos) return chpos - this->data(); 196 | return npos; 197 | } 198 | size_type find_first_of(BasicStringView other, size_type pos = 0) const noexcept 199 | { 200 | size_type ret = npos; 201 | for(auto ch : other) 202 | ret = std::min(ret, find_first_of(ch, pos)); 203 | return ret; 204 | } 205 | }; 206 | using StringView = BasicStringView; 207 | 208 | // Inline operators to concat Strings with StringViews. 209 | template 210 | inline BasicString operator+(const BasicString &lhs, BasicStringView rhs) 211 | { return BasicString(lhs).append(rhs.data(), rhs.size()); } 212 | template 213 | inline BasicString operator+(BasicString&& lhs, BasicStringView rhs) 214 | { return std::move(lhs.append(rhs.data(), rhs.size())); } 215 | template 216 | inline BasicString& operator+=(BasicString &lhs, BasicStringView rhs) 217 | { return lhs.append(rhs.data(), rhs.size()); } 218 | 219 | // Inline operators to compare String and C-style strings with StringViews. 220 | #define ALURE_DECL_STROP(op) \ 221 | template \ 222 | inline bool operator op(const BasicString &lhs, \ 223 | BasicStringView rhs) noexcept \ 224 | { return BasicStringView(lhs) op rhs; } \ 225 | template \ 226 | inline bool operator op(const typename BasicStringView::value_type *lhs, \ 227 | BasicStringView rhs) \ 228 | { return BasicStringView(lhs) op rhs; } 229 | ALURE_DECL_STROP(==) 230 | ALURE_DECL_STROP(!=) 231 | ALURE_DECL_STROP(<=) 232 | ALURE_DECL_STROP(>=) 233 | ALURE_DECL_STROP(<) 234 | ALURE_DECL_STROP(>) 235 | #undef ALURE_DECL_STROP 236 | 237 | // Inline operator to write out a StringView to an ostream 238 | template 239 | inline std::basic_ostream& operator<<(std::basic_ostream &lhs, BasicStringView rhs) 240 | { 241 | for(auto ch : rhs) 242 | lhs << ch; 243 | return lhs; 244 | } 245 | 246 | } // namespace alure 247 | 248 | #endif /* AL_ALURE2_TYPEVIEWS_H */ 249 | -------------------------------------------------------------------------------- /examples/alure-physfs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * An example showing how to read files using custom I/O routines. This 3 | * specific example uses PhysFS to read files from zip, 7z, and some other 4 | * archive formats. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "physfs.h" 15 | 16 | #include "alure2.h" 17 | 18 | namespace { 19 | 20 | // Inherit from std::streambuf to handle custom I/O (PhysFS for this example) 21 | class PhysFSBuf final : public std::streambuf { 22 | alure::Array mBuffer; 23 | PHYSFS_File *mFile{nullptr}; 24 | 25 | int_type underflow() override 26 | { 27 | if(mFile && gptr() == egptr()) 28 | { 29 | // Read in the next chunk of data, and set the read pointers on 30 | // success 31 | PHYSFS_sint64 got = PHYSFS_read(mFile, 32 | mBuffer.data(), sizeof(char_type), mBuffer.size() 33 | ); 34 | if(got != -1) setg(mBuffer.data(), mBuffer.data(), mBuffer.data()+got); 35 | } 36 | if(gptr() == egptr()) 37 | return traits_type::eof(); 38 | return traits_type::to_int_type(*gptr()); 39 | } 40 | 41 | pos_type seekoff(off_type offset, std::ios_base::seekdir whence, std::ios_base::openmode mode) override 42 | { 43 | if(!mFile || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) 44 | return traits_type::eof(); 45 | 46 | // PhysFS only seeks using absolute offsets, so we have to convert cur- 47 | // and end-relative offsets. 48 | PHYSFS_sint64 fpos; 49 | switch(whence) 50 | { 51 | case std::ios_base::beg: 52 | break; 53 | 54 | case std::ios_base::cur: 55 | // Need to offset for the file offset being at egptr() while 56 | // the requested offset is relative to gptr(). 57 | offset -= off_type(egptr()-gptr()); 58 | if((fpos=PHYSFS_tell(mFile)) == -1) 59 | return traits_type::eof(); 60 | // If the offset remains in the current buffer range, just 61 | // update the pointer. 62 | if(offset < 0 && -offset <= off_type(egptr()-eback())) 63 | { 64 | setg(eback(), egptr()+offset, egptr()); 65 | return fpos + offset; 66 | } 67 | offset += fpos; 68 | break; 69 | 70 | case std::ios_base::end: 71 | if((fpos=PHYSFS_fileLength(mFile)) == -1) 72 | return traits_type::eof(); 73 | offset += fpos; 74 | break; 75 | 76 | default: 77 | return traits_type::eof(); 78 | } 79 | 80 | if(offset < 0) return traits_type::eof(); 81 | if(PHYSFS_seek(mFile, offset) == 0) 82 | { 83 | // HACK: Workaround a bug in PhysFS. Certain archive types error 84 | // when trying to seek to the end of the file. So if seeking to the 85 | // end of the file fails, instead try seeking to the last byte and 86 | // read it. 87 | if(offset != PHYSFS_fileLength(mFile)) 88 | return traits_type::eof(); 89 | if(PHYSFS_seek(mFile, offset-1) == 0) 90 | return traits_type::eof(); 91 | PHYSFS_read(mFile, mBuffer.data(), 1, 1); 92 | } 93 | // Clear read pointers so underflow() gets called on the next read 94 | // attempt. 95 | setg(nullptr, nullptr, nullptr); 96 | return offset; 97 | } 98 | 99 | pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override 100 | { 101 | // Simplified version of seekoff 102 | if(!mFile || (mode&std::ios_base::out) || !(mode&std::ios_base::in)) 103 | return traits_type::eof(); 104 | 105 | if(PHYSFS_seek(mFile, pos) == 0) 106 | return traits_type::eof(); 107 | setg(nullptr, nullptr, nullptr); 108 | return pos; 109 | } 110 | 111 | public: 112 | bool open(const char *filename) noexcept 113 | { 114 | mFile = PHYSFS_openRead(filename); 115 | if(!mFile) return false; 116 | return true; 117 | } 118 | 119 | PhysFSBuf() = default; 120 | ~PhysFSBuf() override 121 | { 122 | PHYSFS_close(mFile); 123 | mFile = nullptr; 124 | } 125 | }; 126 | 127 | // Inherit from std::istream to use our custom streambuf 128 | class Stream final : public std::istream { 129 | PhysFSBuf mStreamBuf; 130 | 131 | public: 132 | Stream(const char *filename) : std::istream(nullptr) 133 | { 134 | init(&mStreamBuf); 135 | 136 | // Set the failbit if the file failed to open. 137 | if(!mStreamBuf.open(filename)) clear(failbit); 138 | } 139 | }; 140 | 141 | // Inherit from alure::FileIOFactory to use our custom istream 142 | class FileFactory final : public alure::FileIOFactory { 143 | public: 144 | FileFactory(const char *argv0) 145 | { 146 | // Need to initialize PhysFS before using it 147 | if(PHYSFS_init(argv0) == 0) 148 | throw std::runtime_error(alure::String("Failed to initialize PhysFS: ") + 149 | PHYSFS_getLastError()); 150 | 151 | std::cout<< "Initialized PhysFS, supported archive formats:"; 152 | for(const PHYSFS_ArchiveInfo **i = PHYSFS_supportedArchiveTypes();*i != NULL;i++) 153 | std::cout<< "\n "<<(*i)->extension<<": "<<(*i)->description; 154 | std::cout< openFile(const alure::String &name) noexcept override 162 | { 163 | auto stream = alure::MakeUnique(name.c_str()); 164 | if(stream->fail()) stream = nullptr; 165 | return std::move(stream); 166 | } 167 | 168 | // A PhysFS-specific function to mount a new path to the virtual directory 169 | // tree. 170 | static bool Mount(const char *path, const char *mountPoint=nullptr, int append=0) 171 | { 172 | std::cout<< "Adding new file source "<>; 210 | using std::chrono::duration_cast; 211 | 212 | centiseconds t = duration_cast(rhs.mTime); 213 | if(t.count() < 0) 214 | { 215 | os << '-'; 216 | t *= -1; 217 | } 218 | 219 | // Only handle up to hour formatting 220 | if(t >= hours(1)) 221 | os << duration_cast(t).count() << 'h' << std::setfill('0') << std::setw(2) 222 | << duration_cast(t).count() << 'm'; 223 | else 224 | os << duration_cast(t).count() << 'm' << std::setfill('0'); 225 | os << std::setw(2) << (duration_cast(t).count() % 60) << '.' << std::setw(2) 226 | << (t.count() % 100) << 's' << std::setw(0) << std::setfill(' '); 227 | return os; 228 | } 229 | 230 | } // namespace 231 | 232 | 233 | int main(int argc, char *argv[]) 234 | { 235 | alure::ArrayView args(argv, argc); 236 | 237 | if(args.size() < 2) 238 | { 239 | std::cerr<< "Usage: "<] " 240 | "-add file_entries ..." <(args.front())); 247 | args = args.slice(1); 248 | 249 | alure::DeviceManager devMgr = alure::DeviceManager::getInstance(); 250 | 251 | alure::Device dev; 252 | if(args.size() > 2 && args[0] == alure::StringView("-device")) 253 | { 254 | dev = devMgr.openPlayback(args[1], std::nothrow); 255 | if(!dev) std::cerr<< "Failed to open \""< 1 && alure::StringView("-add") == args[0]) 267 | { 268 | args = args.slice(1); 269 | FileFactory::Mount(args.front()); 270 | std::cout<<"Available files:\n"; 271 | FileFactory::ListDirectory("/"); 272 | std::cout.flush(); 273 | continue; 274 | } 275 | 276 | alure::SharedPtr decoder = ctx.createDecoder(args.front()); 277 | alure::Source source = ctx.createSource(); 278 | source.play(decoder, 12000, 4); 279 | std::cout<< "Playing "<getSampleType())<<", " 281 | << alure::GetChannelConfigName(decoder->getChannelConfig())<<", " 282 | << decoder->getFrequency()<<"hz)" <getFrequency(); 285 | while(source.isPlaying()) 286 | { 287 | std::cout<< "\r "<getLength()*invfreq)}; 289 | std::cout.flush(); 290 | std::this_thread::sleep_for(std::chrono::milliseconds(25)); 291 | ctx.update(); 292 | } 293 | std::cout< 91 | int main() 92 | { 93 | std::vector test; 94 | return test.size(); 95 | }" 96 | HAVE_STATIC_GCCRT_SWITCHES 97 | ) 98 | if(HAVE_STATIC_GCCRT_SWITCHES) 99 | set(LINKER_OPTS ${LINKER_OPTS} -static-libgcc -static-libstdc++) 100 | endif() 101 | set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) 102 | unset(OLD_REQUIRED_LIBRARIES) 103 | endif() 104 | endif() 105 | 106 | 107 | set(alure_srcs src/devicemanager.cpp 108 | src/device.cpp 109 | src/context.cpp 110 | src/buffer.cpp 111 | src/source.cpp 112 | src/sourcegroup.cpp 113 | src/auxeffectslot.cpp 114 | src/effect.cpp 115 | ) 116 | set(alure_libs ${OPENAL_LIBRARY}) 117 | set(decoder_incls ) 118 | 119 | unset(HAVE_WAVE) 120 | unset(HAVE_VORBISFILE) 121 | unset(HAVE_FLAC) 122 | unset(HAVE_OPUSFILE) 123 | unset(HAVE_LIBSNDFILE) 124 | unset(HAVE_MINIMP3) 125 | 126 | option(ALURE_INSTALL "Install library and import module" ON) 127 | option(ALURE_BUILD_SHARED "Build shared library" ON) 128 | option(ALURE_BUILD_STATIC "Build static library" ON) 129 | 130 | if(NOT ALURE_BUILD_SHARED AND NOT ALURE_BUILD_STATIC) 131 | message(FATAL_ERROR "Neither shared or static libraries are enabled!") 132 | endif() 133 | 134 | option(ALURE_ENABLE_WAVE "Enables the built-in wave file decoder" ON) 135 | option(ALURE_ENABLE_VORBIS "Enables the built-in libvorbisfile decoder" ON) 136 | option(ALURE_ENABLE_FLAC "Enables the built-in FLAC decoder" ON) 137 | option(ALURE_ENABLE_OPUS "Enables the built-in libopusfile decoder" ON) 138 | option(ALURE_ENABLE_SNDFILE "Enables the built-in libsndfile decoder" ON) 139 | option(ALURE_ENABLE_MINIMP3 "Enables the built-in MINIMP3 decoder" ON) 140 | 141 | if(ALURE_ENABLE_WAVE) 142 | set(alure_srcs ${alure_srcs} src/decoders/wave.cpp) 143 | set(HAVE_WAVE 1) 144 | endif() 145 | 146 | find_package(Ogg) 147 | if(OGG_FOUND) 148 | set(decoder_incls ${decoder_incls} ${OGG_INCLUDE_DIRS}) 149 | find_package(Vorbis) 150 | if(VORBIS_FOUND AND ALURE_ENABLE_VORBIS) 151 | set(decoder_incls ${decoder_incls} ${VORBIS_INCLUDE_DIRS}) 152 | set(alure_libs ${alure_libs} ${VORBIS_LIBRARIES}) 153 | set(alure_srcs ${alure_srcs} src/decoders/vorbisfile.cpp) 154 | set(HAVE_VORBISFILE 1) 155 | endif() 156 | 157 | find_package(Opus) 158 | if(OPUS_FOUND AND ALURE_ENABLE_OPUS) 159 | set(decoder_incls ${decoder_incls} ${OPUS_INCLUDE_DIRS}) 160 | set(alure_libs ${alure_libs} ${OPUS_LIBRARIES}) 161 | set(alure_srcs ${alure_srcs} src/decoders/opusfile.cpp) 162 | set(HAVE_OPUSFILE 1) 163 | endif() 164 | set(alure_libs ${alure_libs} ${OGG_LIBRARIES}) 165 | endif() 166 | 167 | if(ALURE_ENABLE_FLAC) 168 | set(alure_srcs ${alure_srcs} src/decoders/dr_flac.h src/decoders/flac.cpp) 169 | set(HAVE_FLAC 1) 170 | endif() 171 | 172 | find_package(SndFile) 173 | if(SNDFILE_FOUND AND ALURE_ENABLE_SNDFILE) 174 | set(decoder_incls ${decoder_incls} ${SNDFILE_INCLUDE_DIRS}) 175 | set(alure_libs ${alure_libs} ${SNDFILE_LIBRARIES}) 176 | set(alure_srcs ${alure_srcs} src/decoders/sndfile.cpp) 177 | set(HAVE_LIBSNDFILE 1) 178 | endif() 179 | 180 | if(ALURE_ENABLE_MINIMP3) 181 | set(alure_srcs ${alure_srcs} src/decoders/minimp3.h src/decoders/mp3.hpp src/decoders/mp3.cpp) 182 | set(HAVE_MINIMP3 1) 183 | endif() 184 | 185 | 186 | CONFIGURE_FILE( 187 | "${alure_SOURCE_DIR}/config.h.in" 188 | "${alure_BINARY_DIR}/config.h") 189 | 190 | 191 | unset(TARGET_NAMES) 192 | unset(MAIN_TARGET) 193 | 194 | if(ALURE_BUILD_SHARED) 195 | add_library(alure2 SHARED ${alure_srcs}) 196 | if(EXPORT_DECL) 197 | target_compile_definitions(alure2 PRIVATE ALURE_API=${EXPORT_DECL} ALURE_TEMPLATE=template 198 | NOMINMAX) 199 | endif() 200 | target_include_directories(alure2 201 | PUBLIC $ ${OPENAL_INCLUDE_DIR} 202 | PRIVATE ${alure_SOURCE_DIR}/include ${alure_SOURCE_DIR}/src ${alure_BINARY_DIR} 203 | ${decoder_incls} 204 | ) 205 | target_compile_options(alure2 PRIVATE ${CXX_FLAGS} ${VISIBILITY_FLAGS}) 206 | target_link_libraries(alure2 PUBLIC ${alure_libs} PRIVATE Threads::Threads ${LINKER_OPTS}) 207 | 208 | set(TARGET_NAMES ${TARGET_NAMES} alure2) 209 | if(NOT MAIN_TARGET) 210 | set(MAIN_TARGET alure2) 211 | endif() 212 | endif() 213 | 214 | if(ALURE_BUILD_STATIC) 215 | add_library(alure2_s STATIC ${alure_srcs}) 216 | target_compile_definitions(alure2_s PUBLIC ALURE_STATIC_LIB PRIVATE NOMINMAX) 217 | target_include_directories(alure2_s 218 | PUBLIC $ ${OPENAL_INCLUDE_DIR} 219 | PRIVATE ${alure_SOURCE_DIR}/include ${alure_SOURCE_DIR}/src ${alure_BINARY_DIR} 220 | ${decoder_incls} 221 | ) 222 | target_compile_options(alure2_s PRIVATE ${CXX_FLAGS} ${VISIBILITY_FLAGS}) 223 | target_link_libraries(alure2_s PUBLIC ${alure_libs} PRIVATE Threads::Threads) 224 | 225 | set(TARGET_NAMES ${TARGET_NAMES} alure2_s) 226 | if(NOT MAIN_TARGET) 227 | set(MAIN_TARGET alure2_s) 228 | endif() 229 | endif() 230 | 231 | export( 232 | TARGETS ${TARGET_NAMES} 233 | NAMESPACE Alure2:: 234 | FILE Alure2Config.cmake 235 | ) 236 | if(ALURE_INSTALL) 237 | install(TARGETS ${TARGET_NAMES} EXPORT alure2 238 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 239 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 240 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 241 | INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ${CMAKE_INSTALL_INCLUDEDIR}/AL 242 | ) 243 | install( 244 | EXPORT alure2 245 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Alure2 246 | NAMESPACE Alure2:: 247 | FILE Alure2Config.cmake 248 | ) 249 | install(FILES 250 | include/AL/alure2.h 251 | include/AL/alure2-aliases.h 252 | include/AL/alure2-typeviews.h 253 | include/AL/alure2-alext.h 254 | include/AL/efx.h 255 | include/AL/efx-presets.h 256 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/AL 257 | ) 258 | endif() 259 | 260 | 261 | option(ALURE_BUILD_EXAMPLES "Build example programs" ON) 262 | if(ALURE_BUILD_EXAMPLES) 263 | add_executable(alure-enum examples/alure-enum.cpp) 264 | target_compile_options(alure-enum PRIVATE ${CXX_FLAGS}) 265 | target_link_libraries(alure-enum PRIVATE ${MAIN_TARGET} ${LINKER_OPTS}) 266 | 267 | add_executable(alure-play examples/alure-play.cpp) 268 | target_compile_options(alure-play PRIVATE ${CXX_FLAGS}) 269 | target_link_libraries(alure-play PRIVATE ${MAIN_TARGET} ${LINKER_OPTS}) 270 | 271 | add_executable(alure-play3d examples/alure-play3d.cpp) 272 | target_compile_options(alure-play3d PRIVATE ${CXX_FLAGS}) 273 | target_link_libraries(alure-play3d PRIVATE ${MAIN_TARGET} ${LINKER_OPTS}) 274 | 275 | add_executable(alure-stream examples/alure-stream.cpp) 276 | target_compile_options(alure-stream PRIVATE ${CXX_FLAGS}) 277 | target_link_libraries(alure-stream PRIVATE ${MAIN_TARGET} ${LINKER_OPTS}) 278 | 279 | add_executable(alure-reverb examples/alure-reverb.cpp) 280 | target_compile_options(alure-reverb PRIVATE ${CXX_FLAGS}) 281 | target_link_libraries(alure-reverb PRIVATE ${MAIN_TARGET} ${LINKER_OPTS}) 282 | 283 | add_executable(alure-hrtf examples/alure-hrtf.cpp) 284 | target_compile_options(alure-hrtf PRIVATE ${CXX_FLAGS}) 285 | target_link_libraries(alure-hrtf PRIVATE ${MAIN_TARGET} ${LINKER_OPTS}) 286 | 287 | find_package(PhysFS) 288 | if(PHYSFS_FOUND) 289 | add_executable(alure-physfs examples/alure-physfs.cpp) 290 | target_include_directories(alure-physfs PRIVATE ${PHYSFS_INCLUDE_DIR}) 291 | target_compile_options(alure-physfs PRIVATE ${CXX_FLAGS}) 292 | target_link_libraries(alure-physfs PRIVATE ${MAIN_TARGET} ${PHYSFS_LIBRARY} ${LINKER_OPTS}) 293 | endif() 294 | 295 | find_package(DUMB) 296 | if(DUMB_FOUND) 297 | add_executable(alure-dumb examples/alure-dumb.cpp) 298 | target_include_directories(alure-dumb PRIVATE ${DUMB_INCLUDE_DIRS}) 299 | target_compile_options(alure-dumb PRIVATE ${CXX_FLAGS}) 300 | target_link_libraries(alure-dumb PRIVATE ${MAIN_TARGET} ${DUMB_LIBRARIES} ${LINKER_OPTS}) 301 | endif() 302 | endif() 303 | -------------------------------------------------------------------------------- /src/device.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "config.h" 3 | 4 | #include "device.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "devicemanager.h" 12 | #include "context.h" 13 | #include "buffer.h" 14 | 15 | 16 | namespace { 17 | 18 | using alure::DeviceImpl; 19 | using alure::ALC; 20 | 21 | 22 | template 23 | inline void LoadALCFunc(ALCdevice *device, T **func, const char *name) 24 | { *func = reinterpret_cast(alcGetProcAddress(device, name)); } 25 | 26 | void LoadHrtf(DeviceImpl *device) 27 | { 28 | LoadALCFunc(device->getALCdevice(), &device->alcGetStringiSOFT, "alcGetStringiSOFT"); 29 | LoadALCFunc(device->getALCdevice(), &device->alcResetDeviceSOFT, "alcResetDeviceSOFT"); 30 | } 31 | 32 | void LoadPauseDevice(DeviceImpl *device) 33 | { 34 | LoadALCFunc(device->getALCdevice(), &device->alcDevicePauseSOFT, "alcDevicePauseSOFT"); 35 | LoadALCFunc(device->getALCdevice(), &device->alcDeviceResumeSOFT, "alcDeviceResumeSOFT"); 36 | } 37 | 38 | void LoadNothing(DeviceImpl*) { } 39 | 40 | static const struct { 41 | ALC extension; 42 | const char name[32]; 43 | void (&loader)(DeviceImpl*); 44 | } ALCExtensionList[] = { 45 | { ALC::ENUMERATE_ALL_EXT, "ALC_ENUMERATE_ALL_EXT", LoadNothing }, 46 | { ALC::EXT_EFX, "ALC_EXT_EFX", LoadNothing }, 47 | { ALC::EXT_thread_local_context, "ALC_EXT_thread_local_context", LoadNothing }, 48 | { ALC::SOFT_HRTF, "ALC_SOFT_HRTF", LoadHrtf }, 49 | { ALC::SOFT_pause_device, "ALC_SOFT_pause_device", LoadPauseDevice }, 50 | }; 51 | 52 | } // namespace 53 | 54 | namespace alure { 55 | 56 | void DeviceImpl::setupExts() 57 | { 58 | for(const auto &entry : ALCExtensionList) 59 | { 60 | if(!alcIsExtensionPresent(mDevice, entry.name)) 61 | continue; 62 | mHasExt.set(static_cast(entry.extension)); 63 | entry.loader(this); 64 | } 65 | } 66 | 67 | 68 | DeviceImpl::DeviceImpl(const char *name) 69 | { 70 | mDevice = alcOpenDevice(name); 71 | if(!mDevice) throw alc_error(alcGetError(nullptr), "alcOpenDevice failed"); 72 | 73 | setupExts(); 74 | mPauseTime = mTimeBase = std::chrono::steady_clock::now().time_since_epoch(); 75 | } 76 | 77 | DeviceImpl::~DeviceImpl() 78 | { 79 | mContexts.clear(); 80 | 81 | if(mDevice) 82 | alcCloseDevice(mDevice); 83 | mDevice = nullptr; 84 | } 85 | 86 | 87 | void DeviceImpl::removeContext(ContextImpl *ctx) 88 | { 89 | auto iter = std::find_if(mContexts.begin(), mContexts.end(), 90 | [ctx](const UniquePtr &entry) -> bool 91 | { return entry.get() == ctx; } 92 | ); 93 | if(iter != mContexts.end()) mContexts.erase(iter); 94 | if(mContexts.empty() && mPauseTime == mPauseTime.zero()) 95 | mPauseTime = std::chrono::steady_clock::now().time_since_epoch(); 96 | } 97 | 98 | 99 | DECL_THUNK1(String, Device, getName, const, PlaybackName) 100 | String DeviceImpl::getName(PlaybackName type) const 101 | { 102 | if(type == PlaybackName::Full && !hasExtension(ALC::ENUMERATE_ALL_EXT)) 103 | type = PlaybackName::Basic; 104 | alcGetError(mDevice); 105 | const ALCchar *name = alcGetString(mDevice, (ALenum)type); 106 | if(alcGetError(mDevice) != ALC_NO_ERROR || !name) 107 | name = alcGetString(mDevice, (ALenum)PlaybackName::Basic); 108 | return name ? String(name) : String(); 109 | } 110 | 111 | bool Device::queryExtension(const String &name) const 112 | { return pImpl->queryExtension(name.c_str()); } 113 | DECL_THUNK1(bool, Device, queryExtension, const, const char*) 114 | bool DeviceImpl::queryExtension(const char *name) const 115 | { 116 | return static_cast(alcIsExtensionPresent(mDevice, name)); 117 | } 118 | 119 | DECL_THUNK0(Version, Device, getALCVersion, const) 120 | Version DeviceImpl::getALCVersion() const 121 | { 122 | ALCint major=-1, minor=-1; 123 | alcGetIntegerv(mDevice, ALC_MAJOR_VERSION, 1, &major); 124 | alcGetIntegerv(mDevice, ALC_MINOR_VERSION, 1, &minor); 125 | if(major < 0 || minor < 0) 126 | throw std::runtime_error("ALC version error"); 127 | return Version{ (ALCuint)major, (ALCuint)minor }; 128 | } 129 | 130 | DECL_THUNK0(Version, Device, getEFXVersion, const) 131 | Version DeviceImpl::getEFXVersion() const 132 | { 133 | if(!hasExtension(ALC::EXT_EFX)) 134 | return Version{ 0u, 0u }; 135 | 136 | ALCint major=-1, minor=-1; 137 | alcGetIntegerv(mDevice, ALC_EFX_MAJOR_VERSION, 1, &major); 138 | alcGetIntegerv(mDevice, ALC_EFX_MINOR_VERSION, 1, &minor); 139 | if(major < 0 || minor < 0) 140 | throw std::runtime_error("EFX version error"); 141 | return Version{ (ALCuint)major, (ALCuint)minor }; 142 | } 143 | 144 | DECL_THUNK0(ALCuint, Device, getFrequency, const) 145 | ALCuint DeviceImpl::getFrequency() const 146 | { 147 | ALCint freq = -1; 148 | alcGetIntegerv(mDevice, ALC_FREQUENCY, 1, &freq); 149 | if(freq < 0) 150 | throw std::runtime_error("Frequency error"); 151 | return freq; 152 | } 153 | 154 | DECL_THUNK0(ALCuint, Device, getMaxAuxiliarySends, const) 155 | ALCuint DeviceImpl::getMaxAuxiliarySends() const 156 | { 157 | if(!hasExtension(ALC::EXT_EFX)) 158 | return 0; 159 | 160 | ALCint sends=-1; 161 | alcGetIntegerv(mDevice, ALC_MAX_AUXILIARY_SENDS, 1, &sends); 162 | if(sends < 0) 163 | throw std::runtime_error("Max auxiliary sends error"); 164 | return sends; 165 | } 166 | 167 | 168 | DECL_THUNK0(Vector, Device, enumerateHRTFNames, const) 169 | Vector DeviceImpl::enumerateHRTFNames() const 170 | { 171 | Vector hrtfs; 172 | if(!hasExtension(ALC::SOFT_HRTF)) 173 | return hrtfs; 174 | 175 | ALCint num_hrtfs = -1; 176 | alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtfs); 177 | if(num_hrtfs < 0) 178 | throw std::runtime_error("HRTF specifier count error"); 179 | 180 | hrtfs.reserve(num_hrtfs); 181 | for(int i = 0;i < num_hrtfs;++i) 182 | hrtfs.emplace_back(alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i)); 183 | return hrtfs; 184 | } 185 | 186 | DECL_THUNK0(bool, Device, isHRTFEnabled, const) 187 | bool DeviceImpl::isHRTFEnabled() const 188 | { 189 | if(!hasExtension(ALC::SOFT_HRTF)) 190 | return false; 191 | 192 | ALCint hrtf_state = -1; 193 | alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); 194 | if(hrtf_state == -1) 195 | throw std::runtime_error("HRTF state error"); 196 | return hrtf_state != ALC_FALSE; 197 | } 198 | 199 | DECL_THUNK0(String, Device, getCurrentHRTF, const) 200 | String DeviceImpl::getCurrentHRTF() const 201 | { 202 | if(!hasExtension(ALC::SOFT_HRTF)) 203 | return String(); 204 | return String(alcGetString(mDevice, ALC_HRTF_SPECIFIER_SOFT)); 205 | } 206 | 207 | DECL_THUNK1(void, Device, reset,, ArrayView) 208 | void DeviceImpl::reset(ArrayView attributes) 209 | { 210 | if(!hasExtension(ALC::SOFT_HRTF)) 211 | return; 212 | ALCboolean success = ALC_FALSE; 213 | if(attributes.end()) /* No explicit attributes. */ 214 | success = alcResetDeviceSOFT(mDevice, nullptr); 215 | else 216 | { 217 | auto attr_end = std::find_if(attributes.rbegin(), attributes.rend(), 218 | [](const AttributePair &attr) -> bool 219 | { return attr.mAttribute == 0; } 220 | ); 221 | if(attr_end == attributes.rend()) 222 | { 223 | /* Attribute list was not properly terminated. Copy the attribute 224 | * list and add the 0 sentinel. 225 | */ 226 | Vector attrs; 227 | attrs.reserve(attributes.size() + 1); 228 | std::copy(attributes.begin(), attributes.end(), std::back_inserter(attrs)); 229 | attrs.push_back(AttributesEnd()); 230 | success = alcResetDeviceSOFT(mDevice, &attrs.front().mAttribute); 231 | } 232 | else 233 | success = alcResetDeviceSOFT(mDevice, &attributes.front().mAttribute); 234 | }; 235 | if(!success) 236 | throw alc_error(alcGetError(mDevice), "alcResetDeviceSOFT failed"); 237 | } 238 | 239 | 240 | DECL_THUNK1(Context, Device, createContext,, ArrayView) 241 | Context DeviceImpl::createContext(ArrayView attributes) 242 | { 243 | auto cur_time = std::chrono::steady_clock::now().time_since_epoch(); 244 | Vector attrs; 245 | if(!attributes.empty()) 246 | { 247 | auto attr_end = std::find_if(attributes.rbegin(), attributes.rend(), 248 | [](const AttributePair &attr) -> bool 249 | { return attr.mAttribute == 0; } 250 | ); 251 | if(attr_end == attributes.rend()) 252 | { 253 | /* Attribute list was not properly terminated. Copy the attribute 254 | * list and add the 0 sentinel. 255 | */ 256 | attrs.reserve(attributes.size() + 1); 257 | std::copy(attributes.begin(), attributes.end(), std::back_inserter(attrs)); 258 | attrs.push_back(AttributesEnd()); 259 | attributes = attrs; 260 | } 261 | } 262 | 263 | mContexts.emplace_back(MakeUnique(*this, attributes)); 264 | if(!mIsPaused && mPauseTime != mPauseTime.zero()) 265 | { 266 | mTimeBase += cur_time - mPauseTime; 267 | mPauseTime = mPauseTime.zero(); 268 | } 269 | return Context(mContexts.back().get()); 270 | } 271 | 272 | Context Device::createContext(const std::nothrow_t&) noexcept 273 | { return createContext({}, std::nothrow); } 274 | Context Device::createContext(ArrayView attrs, const std::nothrow_t&) noexcept 275 | { 276 | try { 277 | return pImpl->createContext(attrs); 278 | } 279 | catch(...) { 280 | } 281 | return Context(); 282 | } 283 | 284 | 285 | DECL_THUNK0(void, Device, pauseDSP,) 286 | void DeviceImpl::pauseDSP() 287 | { 288 | if(!hasExtension(ALC::SOFT_pause_device)) 289 | throw std::runtime_error("ALC_SOFT_pause_device not supported"); 290 | alcDevicePauseSOFT(mDevice); 291 | if(!mIsPaused && mPauseTime == mPauseTime.zero()) 292 | mPauseTime = std::chrono::steady_clock::now().time_since_epoch(); 293 | mIsPaused = true; 294 | } 295 | 296 | DECL_THUNK0(void, Device, resumeDSP,) 297 | void DeviceImpl::resumeDSP() 298 | { 299 | auto cur_time = std::chrono::steady_clock::now().time_since_epoch(); 300 | if(hasExtension(ALC::SOFT_pause_device)) 301 | alcDeviceResumeSOFT(mDevice); 302 | if(!mContexts.empty() && mPauseTime != mPauseTime.zero()) 303 | { 304 | mTimeBase += cur_time - mPauseTime; 305 | mPauseTime = mPauseTime.zero(); 306 | } 307 | mIsPaused = false; 308 | } 309 | 310 | DECL_THUNK0(std::chrono::nanoseconds, Device, getClockTime,) 311 | std::chrono::nanoseconds DeviceImpl::getClockTime() 312 | { 313 | std::chrono::nanoseconds cur_time = std::chrono::steady_clock::now().time_since_epoch(); 314 | if(UNLIKELY(mPauseTime != mPauseTime.zero())) 315 | { 316 | auto diff = cur_time - mPauseTime; 317 | mTimeBase += diff; 318 | mPauseTime += diff; 319 | cur_time = mPauseTime; 320 | } 321 | return cur_time - mTimeBase; 322 | } 323 | 324 | 325 | void Device::close() 326 | { 327 | DeviceImpl *i = pImpl; 328 | pImpl = nullptr; 329 | i->close(); 330 | } 331 | void DeviceImpl::close() 332 | { 333 | if(!mContexts.empty()) 334 | throw std::runtime_error("Trying to close device with contexts"); 335 | 336 | if(alcCloseDevice(mDevice) == ALC_FALSE) 337 | throw alc_error(alcGetError(mDevice), "alcCloseDevice failed"); 338 | mDevice = nullptr; 339 | 340 | DeviceManagerImpl::getInstance()->removeDevice(this); 341 | } 342 | 343 | } // namespace alure 344 | -------------------------------------------------------------------------------- /src/context.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTEXT_H 2 | #define CONTEXT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "main.h" 14 | 15 | #include "device.h" 16 | #include "source.h" 17 | 18 | 19 | #define F_PI (3.14159265358979323846f) 20 | 21 | namespace alure { 22 | 23 | enum class AL { 24 | EXT_EFX, 25 | 26 | EXT_FLOAT32, 27 | EXT_MCFORMATS, 28 | EXT_BFORMAT, 29 | 30 | EXT_MULAW, 31 | EXT_MULAW_MCFORMATS, 32 | EXT_MULAW_BFORMAT, 33 | 34 | SOFT_loop_points, 35 | SOFT_source_latency, 36 | SOFT_source_resampler, 37 | SOFT_source_spatialize, 38 | 39 | EXT_disconnect, 40 | 41 | EXT_SOURCE_RADIUS, 42 | EXT_STEREO_ANGLES, 43 | 44 | EXTENSION_MAX 45 | }; 46 | 47 | // Batches OpenAL updates while the object is alive, if batching isn't already 48 | // in progress. 49 | class Batcher { 50 | ALCcontext *mContext; 51 | 52 | public: 53 | Batcher(ALCcontext *context) : mContext(context) { } 54 | Batcher(Batcher&& rhs) : mContext(rhs.mContext) { rhs.mContext = nullptr; } 55 | Batcher(const Batcher&) = delete; 56 | ~Batcher() 57 | { 58 | if(mContext) 59 | alcProcessContext(mContext); 60 | } 61 | 62 | Batcher& operator=(Batcher&&) = delete; 63 | Batcher& operator=(const Batcher&) = delete; 64 | }; 65 | 66 | 67 | class ListenerImpl { 68 | ContextImpl *const mContext; 69 | 70 | public: 71 | ListenerImpl(ContextImpl *ctx) : mContext(ctx) { } 72 | 73 | void setGain(ALfloat gain); 74 | 75 | void set3DParameters(const Vector3 &position, const Vector3 &velocity, const std::pair &orientation); 76 | 77 | void setPosition(const Vector3 &position); 78 | void setPosition(const ALfloat *pos); 79 | 80 | void setVelocity(const Vector3 &velocity); 81 | void setVelocity(const ALfloat *vel); 82 | 83 | void setOrientation(const std::pair &orientation); 84 | void setOrientation(const ALfloat *at, const ALfloat *up); 85 | void setOrientation(const ALfloat *ori); 86 | 87 | void setMetersPerUnit(ALfloat m_u); 88 | }; 89 | 90 | 91 | using DecoderOrExceptT = std::variant,std::exception_ptr>; 92 | using BufferOrExceptT = std::variant; 93 | 94 | class ContextImpl { 95 | static ContextImpl *sCurrentCtx; 96 | static thread_local ContextImpl *sThreadCurrentCtx; 97 | 98 | public: 99 | static void MakeCurrent(ContextImpl *context); 100 | static ContextImpl *GetCurrent() 101 | { 102 | auto thrd_ctx = sThreadCurrentCtx; 103 | return thrd_ctx ? thrd_ctx : sCurrentCtx; 104 | } 105 | 106 | static void MakeThreadCurrent(ContextImpl *context); 107 | static ContextImpl *GetThreadCurrent() { return sThreadCurrentCtx; } 108 | 109 | static std::atomic sContextSetCount; 110 | mutable uint64_t mContextSetCounter{std::numeric_limits::max()}; 111 | 112 | struct ContextDeleter { 113 | void operator()(ALCcontext *ptr) const { alcDestroyContext(ptr); } 114 | }; 115 | using ContextPtr = UniquePtr; 116 | 117 | private: 118 | ListenerImpl mListener; 119 | 120 | ContextPtr mContext; 121 | Vector mSourceIds; 122 | 123 | struct PendingBuffer { BufferImpl *mBuffer; SharedFuture mFuture; }; 124 | struct PendingSource { SourceImpl *mSource; SharedFuture mFuture; }; 125 | using BufferListT = Vector>; 126 | using FutureBufferListT = Vector; 127 | 128 | DeviceImpl &mDevice; 129 | FutureBufferListT mFutureBuffers; 130 | BufferListT mBuffers; 131 | Vector> mSourceGroups; 132 | Vector> mEffectSlots; 133 | Vector> mEffects; 134 | std::deque mAllSources; 135 | Vector mFreeSources; 136 | 137 | Vector mPendingSources; 138 | Vector mFadingSources; 139 | Vector mPlaySources; 140 | Vector mStreamSources; 141 | 142 | Vector mStreamingSources; 143 | std::mutex mSourceStreamMutex; 144 | 145 | std::atomic mWakeInterval{std::chrono::milliseconds::zero()}; 146 | std::mutex mWakeMutex; 147 | std::condition_variable mWakeThread; 148 | 149 | SharedPtr mMessage; 150 | 151 | struct PendingPromise { 152 | BufferImpl *mBuffer{nullptr}; 153 | SharedPtr mDecoder; 154 | ALenum mFormat{AL_NONE}; 155 | ALuint mFrames{0}; 156 | Promise mPromise; 157 | 158 | std::atomic mNext{nullptr}; 159 | 160 | PendingPromise() = default; 161 | PendingPromise(BufferImpl *buffer, SharedPtr decoder, ALenum format, 162 | ALuint frames, Promise promise) 163 | : mBuffer(buffer), mDecoder(std::move(decoder)), mFormat(format), mFrames(frames) 164 | , mPromise(std::move(promise)) 165 | { } 166 | }; 167 | std::atomic mPendingCurrent{nullptr}; 168 | PendingPromise *mPendingTail{nullptr}; 169 | PendingPromise *mPendingHead{nullptr}; 170 | 171 | std::atomic mQuitThread{false}; 172 | std::thread mThread; 173 | void backgroundProc(); 174 | 175 | size_t mRefs{0}; 176 | 177 | Vector mResamplers; 178 | 179 | Bitfield(AL::EXTENSION_MAX)> mHasExt; 180 | 181 | std::once_flag mSetExts; 182 | void setupExts(); 183 | 184 | DecoderOrExceptT findDecoder(StringView name); 185 | BufferOrExceptT doCreateBuffer(StringView name, size_t name_hash, BufferListT::const_iterator iter, SharedPtr decoder); 186 | BufferOrExceptT doCreateBufferAsync(StringView name, size_t name_hash, BufferListT::const_iterator iter, SharedPtr decoder, Promise promise); 187 | 188 | bool mIsConnected : 1; 189 | bool mIsBatching : 1; 190 | 191 | public: 192 | ContextImpl(DeviceImpl &device, ArrayView attrs); 193 | ~ContextImpl(); 194 | 195 | ALCcontext *getALCcontext() const { return mContext.get(); } 196 | size_t addRef() { return ++mRefs; } 197 | size_t decRef() { return --mRefs; } 198 | 199 | bool hasExtension(AL ext) const { return mHasExt[static_cast(ext)]; } 200 | 201 | LPALGETSTRINGISOFT alGetStringiSOFT{nullptr}; 202 | LPALGETSOURCEI64VSOFT alGetSourcei64vSOFT{nullptr}; 203 | LPALGETSOURCEDVSOFT alGetSourcedvSOFT{nullptr}; 204 | 205 | LPALGENEFFECTS alGenEffects{nullptr}; 206 | LPALDELETEEFFECTS alDeleteEffects{nullptr}; 207 | LPALISEFFECT alIsEffect{nullptr}; 208 | LPALEFFECTI alEffecti{nullptr}; 209 | LPALEFFECTIV alEffectiv{nullptr}; 210 | LPALEFFECTF alEffectf{nullptr}; 211 | LPALEFFECTFV alEffectfv{nullptr}; 212 | LPALGETEFFECTI alGetEffecti{nullptr}; 213 | LPALGETEFFECTIV alGetEffectiv{nullptr}; 214 | LPALGETEFFECTF alGetEffectf{nullptr}; 215 | LPALGETEFFECTFV alGetEffectfv{nullptr}; 216 | 217 | LPALGENFILTERS alGenFilters{nullptr}; 218 | LPALDELETEFILTERS alDeleteFilters{nullptr}; 219 | LPALISFILTER alIsFilter{nullptr}; 220 | LPALFILTERI alFilteri{nullptr}; 221 | LPALFILTERIV alFilteriv{nullptr}; 222 | LPALFILTERF alFilterf{nullptr}; 223 | LPALFILTERFV alFilterfv{nullptr}; 224 | LPALGETFILTERI alGetFilteri{nullptr}; 225 | LPALGETFILTERIV alGetFilteriv{nullptr}; 226 | LPALGETFILTERF alGetFilterf{nullptr}; 227 | LPALGETFILTERFV alGetFilterfv{nullptr}; 228 | 229 | LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots{nullptr}; 230 | LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots{nullptr}; 231 | LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot{nullptr}; 232 | LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti{nullptr}; 233 | LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv{nullptr}; 234 | LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf{nullptr}; 235 | LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv{nullptr}; 236 | LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti{nullptr}; 237 | LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv{nullptr}; 238 | LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf{nullptr}; 239 | LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv{nullptr}; 240 | 241 | FutureBufferListT::const_iterator findFutureBufferName(StringView name, size_t name_hash) const; 242 | BufferListT::const_iterator findBufferName(StringView name, size_t name_hash) const; 243 | 244 | ALuint getSourceId(ALuint maxprio); 245 | void insertSourceId(ALuint id) { mSourceIds.push_back(id); } 246 | 247 | void addPendingSource(SourceImpl *source, SharedFuture future); 248 | void removePendingSource(SourceImpl *source); 249 | bool isPendingSource(const SourceImpl *source) const; 250 | void addFadingSource(SourceImpl *source, std::chrono::nanoseconds duration, ALfloat gain); 251 | void removeFadingSource(SourceImpl *source); 252 | void addPlayingSource(SourceImpl *source, ALuint id); 253 | void addPlayingSource(SourceImpl *source); 254 | void removePlayingSource(SourceImpl *source); 255 | 256 | void addStream(SourceImpl *source); 257 | void removeStream(SourceImpl *source); 258 | void removeStreamNoLock(SourceImpl *source); 259 | 260 | void freeSource(SourceImpl *source) { mFreeSources.push_back(source); } 261 | void freeSourceGroup(SourceGroupImpl *group); 262 | void freeEffectSlot(AuxiliaryEffectSlotImpl *slot); 263 | void freeEffect(EffectImpl *effect); 264 | 265 | Batcher getBatcher() 266 | { 267 | if(mIsBatching) 268 | return Batcher(nullptr); 269 | alcSuspendContext(mContext.get()); 270 | return Batcher(mContext.get()); 271 | } 272 | 273 | std::unique_lock getSourceStreamLock() 274 | { return std::unique_lock(mSourceStreamMutex); } 275 | 276 | template 277 | void send(R MessageHandler::* func, Args&&... args) 278 | { if(mMessage.get()) (mMessage.get()->*func)(std::forward(args)...); } 279 | 280 | Device getDevice() { return Device(&mDevice); } 281 | 282 | void destroy(); 283 | 284 | void startBatch(); 285 | void endBatch(); 286 | 287 | Listener getListener() { return Listener(&mListener); } 288 | 289 | SharedPtr setMessageHandler(SharedPtr&& handler); 290 | SharedPtr getMessageHandler() const { return mMessage; } 291 | 292 | void setAsyncWakeInterval(std::chrono::milliseconds interval); 293 | std::chrono::milliseconds getAsyncWakeInterval() const { return mWakeInterval.load(); } 294 | 295 | SharedPtr createDecoder(StringView name); 296 | 297 | bool isSupported(ChannelConfig channels, SampleType type) const; 298 | 299 | ArrayView getAvailableResamplers(); 300 | ALsizei getDefaultResamplerIndex() const; 301 | 302 | Buffer getBuffer(StringView name); 303 | SharedFuture getBufferAsync(StringView name); 304 | void precacheBuffersAsync(ArrayView names); 305 | Buffer createBufferFrom(StringView name, SharedPtr&& decoder); 306 | SharedFuture createBufferAsyncFrom(StringView name, SharedPtr&& decoder); 307 | Buffer findBuffer(StringView name); 308 | SharedFuture findBufferAsync(StringView name); 309 | void removeBuffer(StringView name); 310 | void removeBuffer(Buffer buffer) { removeBuffer(buffer.getName()); } 311 | 312 | Source createSource(); 313 | 314 | AuxiliaryEffectSlot createAuxiliaryEffectSlot(); 315 | 316 | Effect createEffect(); 317 | 318 | SourceGroup createSourceGroup(); 319 | 320 | void setDopplerFactor(ALfloat factor); 321 | 322 | void setSpeedOfSound(ALfloat speed); 323 | 324 | void setDistanceModel(DistanceModel model); 325 | 326 | void update(); 327 | }; 328 | 329 | 330 | inline void CheckContext(const ContextImpl &ctx) 331 | { 332 | auto count = ContextImpl::sContextSetCount.load(std::memory_order_acquire); 333 | if(UNLIKELY(count != ctx.mContextSetCounter)) 334 | { 335 | if(UNLIKELY(&ctx != ContextImpl::GetCurrent())) 336 | throw std::runtime_error("Called context is not current"); 337 | ctx.mContextSetCounter = count; 338 | } 339 | } 340 | 341 | inline void CheckContexts(const ContextImpl &ctx0, const ContextImpl &ctx1) 342 | { 343 | if(UNLIKELY(&ctx0 != &ctx1)) 344 | throw std::runtime_error("Mismatched object contexts"); 345 | } 346 | 347 | 348 | } // namespace alure 349 | 350 | #endif /* CONTEXT_H */ 351 | --------------------------------------------------------------------------------