From 4f5551dbec2459bfd73c228b4ec438c9e13790f6 Mon Sep 17 00:00:00 2001 From: Squareys Date: Sat, 24 Oct 2015 17:12:41 +0200 Subject: [PATCH] Audio: Initial Support for OpenAL Extensions (and ALC_SOFTX_HRTF) Signed-off-by: Squareys --- src/Magnum/Audio/Context.cpp | 93 ++++++++++++- src/Magnum/Audio/Context.h | 250 ++++++++++++++++++++++++++++++++++- 2 files changed, 338 insertions(+), 5 deletions(-) diff --git a/src/Magnum/Audio/Context.cpp b/src/Magnum/Audio/Context.cpp index 8c0bc5719..9720f5d68 100644 --- a/src/Magnum/Audio/Context.cpp +++ b/src/Magnum/Audio/Context.cpp @@ -3,6 +3,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015 Vladimír Vondruš + Copyright © 2015 Jonathan Hale Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -25,17 +26,36 @@ #include "Context.h" +#include + +#include #include + #include #include +#include -#include "Magnum/Magnum.h" +#include "Magnum/Audio/Extensions.h" namespace Magnum { namespace Audio { +const std::vector& Extension::extensions() { + #define _extension(prefix, vendor, extension) {Extensions::prefix::vendor::extension::Index, Extensions::prefix::vendor::extension::string()} + static const std::vector extensions{ + _extension(AL,EXT,FLOAT32), + _extension(AL,EXT,DOUBLE), + _extension(ALC,SOFTX,HRTF) + }; + #undef _entension + + return extensions; +} + Context* Context::_current = nullptr; -Context::Context() { +Context::Context(): Context{Configuration{}} {} + +Context::Context(const Configuration& config) { CORRADE_ASSERT(!_current, "Audio::Context: context already created", ); /* Open default device */ @@ -46,12 +66,26 @@ Context::Context() { std::exit(1); } - _context = alcCreateContext(_device, nullptr); - if(!_context) { + if(!tryCreateContext(config)) { Error() << "Audio::Context: cannot create context:" << alcGetError(_device); std::exit(1); } + /* Add all extensions to a map for faster lookup */ + std::unordered_map extensionMap; + for(const Extension& extension: Extension::extensions()) + extensionMap.emplace(extension._string, extension); + + /* Check for presence of extensions */ + const std::vector extensions = extensionStrings(); + for(const std::string& extension: extensions) { + const auto found = extensionMap.find(extension); + if(found != extensionMap.end()) { + _supportedExtensions.push_back(found->second); + _extensionStatus.set(found->second._index); + } + } + alcMakeContextCurrent(_context); _current = this; @@ -67,4 +101,55 @@ Context::~Context() { alcCloseDevice(_device); } +std::vector Context::extensionStrings() const { + std::vector extensions; + + /* Don't crash when glGetString() returns nullptr */ + const char* e = reinterpret_cast(alGetString(AL_EXTENSIONS)); + if(e) extensions = Utility::String::splitWithoutEmptyParts(e, ' '); + + return extensions; +} + +bool Context::tryCreateContext(const Configuration& config) { + /* The following parameters are order dependent! + Make sure to always add sufficient space at end of the attributes + array.*/ + Int attributes[]{ + ALC_FREQUENCY, config.frequency(), + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0 /* sentinel */ + }; + + /* last valid index in the attributes array */ + int last = 1; + + if(config.isHrtfEnabled() != Configuration::EnabledState::Default) { + attributes[++last] = ALC_HRTF_SOFT; + attributes[++last] = (config.isHrtfEnabled() == Configuration::EnabledState::Enabled) + ? ALC_TRUE : ALC_FALSE; + } + + if(config.monoSourcesCount() != -1) { + attributes[++last] = ALC_MONO_SOURCES; + attributes[++last] = config.monoSourcesCount(); + } + + if(config.stereoSourcesCount() != -1) { + attributes[++last] = ALC_STEREO_SOURCES; + attributes[++last] = config.stereoSourcesCount(); + } + + if(config.refreshRate() != -1) { + attributes[++last] = ALC_REFRESH; + attributes[++last] = config.refreshRate(); + } + + _context = alcCreateContext(_device, attributes); + return !!_context; +} + }} diff --git a/src/Magnum/Audio/Context.h b/src/Magnum/Audio/Context.h index ca79c3639..29e333c08 100644 --- a/src/Magnum/Audio/Context.h +++ b/src/Magnum/Audio/Context.h @@ -5,6 +5,7 @@ Copyright © 2010, 2011, 2012, 2013, 2014, 2015 Vladimír Vondruš + Copyright © 2015 Jonathan Hale Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -30,9 +31,18 @@ */ #include +#include +#include +#include #include +#include + +#include + #include "Magnum/Audio/visibility.h" +#include "Magnum/Audio/Buffer.h" + #ifndef DOXYGEN_GENERATING_OUTPUT typedef struct ALCdevice_struct ALCdevice; @@ -41,6 +51,36 @@ typedef struct ALCcontext_struct ALCcontext; namespace Magnum { namespace Audio { +class Context; + +/** +@brief Run-time information about OpenAL extension + +Encapsulates runtime information about OpenAL extension, such as name string, +minimal required OpenAL version and version in which the extension was adopted +to core. + +See also @ref Audio::Extensions namespace, which contain compile-time information +about OpenAL extensions. +*/ +class MAGNUM_AUDIO_EXPORT Extension { + friend Context; + + public: + /** @brief All OpenAL extensions */ + static const std::vector& extensions(); + + /** @brief Extension string */ + constexpr const char* string() const { return _string; } + + private: + /* MSVC seems to have problems with const members */ + std::size_t _index; + const char* _string; + + constexpr Extension(std::size_t index, const char* string): _index(index), _string(string) {} +}; + /** @brief OpenAL context */ @@ -49,12 +89,19 @@ class MAGNUM_AUDIO_EXPORT Context { /** @brief Current context */ static Context* current() { return _current; } + class Configuration; + /** * @brief Constructor * - * Creates OpenAL context. + * Creates OpenAL context with given configuration. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + explicit Context(const Configuration& configuration = Configuration()); + #else + explicit Context(const Configuration& configuration); explicit Context(); + #endif /** * @brief Destructor @@ -84,13 +131,214 @@ class MAGNUM_AUDIO_EXPORT Context { */ std::string versionString() const { return alGetString(AL_VERSION); } + /** + * @brief Extension strings + * + * The result is *not* cached, repeated queries will result in repeated + * OpenAL calls. Note that this function returns list of all extensions + * reported by the driver (even those not supported by Magnum), see + * @ref supportedExtensions(), @ref Extension::extensions() or + * @ref isExtensionSupported() for alternatives. + * @see @fn_al{Get} with @def_al{NUM_EXTENSIONS}, @fn_al{GetString} + * with @def_al{EXTENSIONS} + */ + std::vector extensionStrings() const; + + /** + * @brief Supported extensions + * + * The list contains only extensions from OpenAL versions newer than + * the current. + * @see @ref isExtensionSupported(), @ref Extension::extensions() + */ + const std::vector& supportedExtensions() const { + return _supportedExtensions; + } + + /** + * @brief Whether given extension is supported + * + * Extensions usable with this function are listed in @ref Extensions + * namespace in header @ref Extensions.h. Example usage: + * @code + * if(Context::current()->isExtensionSupported()) { + * // amazing binaural audio + * } else { + * // probably left/right stereo only + * } + * @endcode + * + * @see @ref isExtensionSupported(const Extension&) const, + * @ref MAGNUM_AUDIO_ASSERT_EXTENSION_SUPPORTED() + */ + template bool isExtensionSupported() const { + return _extensionStatus[T::Index]; + } + + /** + * @brief Whether given extension is supported + * + * Can be used e.g. for listing extensions available on current + * hardware, but for general usage prefer @ref isExtensionSupported() const, + * as it does most operations in compile time. + * @see @ref supportedExtensions(), @ref Extension::extensions(), + * @ref MAGNUM_AUDIO_ASSERT_EXTENSION_SUPPORTED() + */ + bool isExtensionSupported(const Extension& extension) const { + return _extensionStatus[extension._index]; + } + private: static Context* _current; + /* Create a context with given configuration. Returns `true` on success. + * @ref alcCreateContext(). */ + bool tryCreateContext(const Configuration& config); + ALCdevice* _device; ALCcontext* _context; + + std::bitset<64> _extensionStatus; + std::vector _supportedExtensions; +}; + +class MAGNUM_AUDIO_EXPORT Context::Configuration { + public: + + /** + * @brief Enum for boolean values with a driver specific default + * value + */ + enum class EnabledState: Byte { + Default = 0, + Enabled = 1, + Disabled = 2 + }; + + /** + * @brief Constructor + */ + explicit Configuration(): + _frequency(44100), + _enableHrtf(), + _monoSources(-1), + _stereoSources(-1), + _refreshRate(-1) + {} + + /** @brief Sampling rate in Hz */ + Int frequency() const { return _frequency; } + + /** + * @brief Set sampling rate (in Hz) + * + * Default is `44100`. + */ + Configuration& setFrequency(Int freq) { + _frequency = freq; + return *this; + } + + /** @brief Whether to use hrtfs */ + EnabledState isHrtfEnabled() const { return _enableHrtf; } + + /** + * @brief Set whether to use hrtfs + * + * Defaults to local OpenAL configuration or false. + */ + Configuration& setHrtfEnabled(EnabledState hrtf) { + _enableHrtf = hrtf; + return *this; + } + + /** + * @brief Hint for how mono sources to support + * + * Returns `-1`, if no hint was set. + */ + Int monoSourcesCount() const { return _monoSources; } + + /** + * @brief Set hint for how mono sources to support + * + * If not set, no hint will given to OpenAL. + */ + Configuration& setMonoSourcesCount(Int sources) { + _monoSources = sources; + return *this; + } + + /** + * @brief Hint for how stereo sources to support + * + * Returns `-1`, if no hint was set. + */ + Int stereoSourcesCount() const { return _stereoSources; } + + /** + * @brief Set hint for how stereo sources to support + * + * If not set, no hint will given to OpenAL. + */ + Configuration& setStereoSourcesCount(Int sources) { + _stereoSources = sources; + return *this; + } + + /** @brief Rate at which the OpenAL device is refreshed (in Hz) */ + Int refreshRate() const { return _refreshRate; } + + /** + * @brief Set rate at which the OpenAL device is refreshed (in Hz) + * @return Reference to self (for method chaining) + */ + Configuration& setRefreshRate(Int rate) { + _refreshRate = rate; + return *this; + } + + private: + + Int _frequency; + EnabledState _enableHrtf; + + Int _monoSources; + Int _stereoSources; + + Int _refreshRate; }; + +/** @hideinitializer +@brief Assert that given OpenAL extension is supported +@param extension Extension name (from + @ref Magnum::Audio::Extensions "Audio::Extensions" namespace) + +Useful for initial checks on availability of required features. + +By default, if assertion fails, an message is printed to error output and the +application aborts. If `CORRADE_NO_ASSERT` is defined, this macro does nothing. +Example usage: +@code +MAGNUM_ASSERT_AUDIO_EXTENSION_SUPPORTED(Extensions::ALC::SOFTX::HRTF); +@endcode + +@see @ref Magnum::Audio::Context::isExtensionSupported() "Audio::Context::isExtensionSupported()", + @ref CORRADE_ASSERT(), @ref CORRADE_INTERNAL_ASSERT() +*/ +#ifdef CORRADE_NO_ASSERT +#define MAGNUM_ASSERT_AUDIO_EXTENSION_SUPPORTED(extension) do {} while(0) +#else +#define MAGNUM_ASSERT_AUDIO_EXTENSION_SUPPORTED(extension) \ + do { \ + if(!Magnum::Audio::Context::current()->isExtensionSupported()) { \ + Corrade::Utility::Error() << "Magnum: required OpenAL extension" << extension::string() << "is not supported"; \ + std::abort(); \ + } \ + } while(0) +#endif + }} #endif