diff --git a/src/Magnum/Audio/Context.cpp b/src/Magnum/Audio/Context.cpp index 17434247b..16e166b86 100644 --- a/src/Magnum/Audio/Context.cpp +++ b/src/Magnum/Audio/Context.cpp @@ -117,19 +117,73 @@ Context& Context::current() { Context::Context(): Context{Configuration{}} {} #endif -Context::Context(const Configuration& config) { - CORRADE_ASSERT(!_current, "Audio::Context: context already created", ); +Context::Context(const Configuration& configuration) { + create(configuration); +} + +Context::Context(NoCreateT) noexcept: _device{}, _context{} {} + +void Context::create(const Configuration& configuration) { + if(!tryCreate(configuration)) std::exit(1); +} + +bool Context::tryCreate(const Configuration& configuration) { + CORRADE_ASSERT(!_current, "Audio::Context: context already created", false); /* Open the device */ - const ALCchar* const deviceSpecifier = config.deviceSpecifier().empty() ? alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER) : config.deviceSpecifier().data(); + const ALCchar* const deviceSpecifier = configuration.deviceSpecifier().empty() ? alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER) : configuration.deviceSpecifier().data(); if(!(_device = alcOpenDevice(deviceSpecifier))) { Error() << "Audio::Context: cannot open sound device" << deviceSpecifier; - std::exit(1); + return false; + } + + /* The following parameters are order dependent! + Make sure to always add sufficient space at end of the attributes + array.*/ + Int attributes[]{ + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0, 0, + 0 /* sentinel */ + }; + + /* last valid index in the attributes array */ + Int last = 0; + + if(configuration.frequency() != -1) { + attributes[last++] = ALC_FREQUENCY; + attributes[last++] = configuration.frequency(); + } + if(configuration.hrtf() != Configuration::Hrtf::Default) { + attributes[last++] = ALC_HRTF_SOFT; + attributes[last++] = (configuration.hrtf() == Configuration::Hrtf::Enabled) + ? ALC_TRUE : ALC_FALSE; + } + if(configuration.monoSourceCount() != -1) { + attributes[last++] = ALC_MONO_SOURCES; + attributes[last++] = configuration.monoSourceCount(); + } + if(configuration.stereoSourceCount() != -1) { + attributes[last++] = ALC_STEREO_SOURCES; + attributes[last++] = configuration.stereoSourceCount(); + } + if(configuration.refreshRate() != -1) { + attributes[last++] = ALC_REFRESH; + attributes[last++] = configuration.refreshRate(); } - if(!tryCreateContext(config)) { + #ifndef CORRADE_TARGET_EMSCRIPTEN + _context = alcCreateContext(_device, attributes); + #else + if(last != 0) + Warning() << "Audio::Context::tryCreateContext(): specifying attributes is not supported with Emscripten, ignoring"; + _context = alcCreateContext(_device, nullptr); + #endif + if(!_context) { Error() << "Audio::Context: cannot create context:" << alcErrorString(alcGetError(_device)); - std::exit(1); + return false; } alcMakeContextCurrent(_context); @@ -153,6 +207,8 @@ Context::Context(const Configuration& config) { /* Print some info */ Debug() << "Audio Renderer:" << rendererString() << "by" << vendorString(); Debug() << "OpenAL version:" << versionString(); + + return true; } Context::Context(Context&& other) noexcept: _device{other._device}, _context{other._context}, _extensionStatus{std::move(other._extensionStatus)}, _supportedExtensions{std::move(other._supportedExtensions)} { @@ -220,54 +276,6 @@ std::string Context::versionString() const { return alGetString(AL_VERSION); } -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[]{ - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0, 0, - 0 /* sentinel */ - }; - - /* last valid index in the attributes array */ - Int last = 0; - - if(config.frequency() != -1) { - attributes[last++] = ALC_FREQUENCY; - attributes[last++] = config.frequency(); - } - if(config.hrtf() != Configuration::Hrtf::Default) { - attributes[last++] = ALC_HRTF_SOFT; - attributes[last++] = (config.hrtf() == Configuration::Hrtf::Enabled) - ? ALC_TRUE : ALC_FALSE; - } - if(config.monoSourceCount() != -1) { - attributes[last++] = ALC_MONO_SOURCES; - attributes[last++] = config.monoSourceCount(); - } - if(config.stereoSourceCount() != -1) { - attributes[last++] = ALC_STEREO_SOURCES; - attributes[last++] = config.stereoSourceCount(); - } - if(config.refreshRate() != -1) { - attributes[last++] = ALC_REFRESH; - attributes[last++] = config.refreshRate(); - } - - #ifndef CORRADE_TARGET_EMSCRIPTEN - _context = alcCreateContext(_device, attributes); - #else - if(last != 0) - Warning() << "Audio::Context::tryCreateContext(): specifying attributes is not supported with Emscripten, ignoring"; - _context = alcCreateContext(_device, nullptr); - #endif - return !!_context; -} - Context::Configuration::Configuration() = default; Context::Configuration::~Configuration() = default; diff --git a/src/Magnum/Audio/Context.h b/src/Magnum/Audio/Context.h index 0d03ff6f9..07ee1f18f 100644 --- a/src/Magnum/Audio/Context.h +++ b/src/Magnum/Audio/Context.h @@ -39,6 +39,7 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/Tags.h" #include "Magnum/Audio/visibility.h" #include "MagnumExternal/OpenAL/extensions.h" @@ -163,6 +164,15 @@ class MAGNUM_AUDIO_EXPORT Context { explicit Context(); #endif + /** + * @brief Construct without creating the underlying OpenAL context + * + * Useful in cases where you need to defer context creation to a later + * time, for example to do a more involved configuration. Call + * @ref create() or @ref tryCreate() to create the actual context. + */ + explicit Context(NoCreateT) noexcept; + /** @brief Copying is not allowed */ Context(const Context&) = delete; @@ -187,6 +197,24 @@ class MAGNUM_AUDIO_EXPORT Context { CORRADE_DEPRECATED("Audio::Context::current() returns reference now") operator Context*() { return this; } #endif + /** + * @brief Complete the context setup and exit on failure + * + * Finalizes the setup after the class was created using + * @ref Context(NoCreateT). If any error occurs, a message is printed + * to error output and the application exits. See @ref tryCreate() for + * an alternative. + */ + void create(const Configuration& configuration); + + /** + * @brief Complete the context setup + * + * Unlike @ref create() just prints a message to error output and + * returns `false` on error. + */ + bool tryCreate(const Configuration& configuration); + /** * @brief Whether HRTFs (Head Related Transfer Functions) are enabled * @@ -318,10 +346,6 @@ class MAGNUM_AUDIO_EXPORT Context { private: MAGNUM_AUDIO_LOCAL static Context* _current; - /* Create a context with given configuration. Returns `true` on success. - * @ref alcCreateContext(). */ - MAGNUM_AUDIO_LOCAL bool tryCreateContext(const Configuration& config); - ALCdevice* _device; ALCcontext* _context; diff --git a/src/Magnum/Audio/Test/ContextTest.cpp b/src/Magnum/Audio/Test/ContextTest.cpp index e524e54af..9ec89ec32 100644 --- a/src/Magnum/Audio/Test/ContextTest.cpp +++ b/src/Magnum/Audio/Test/ContextTest.cpp @@ -34,6 +34,7 @@ namespace Magnum { namespace Audio { namespace Test { struct ContextTest: TestSuite::Tester { explicit ContextTest(); + void constructNoCreate(); void constructCopyMove(); void extensions(); @@ -42,13 +43,20 @@ struct ContextTest: TestSuite::Tester { }; ContextTest::ContextTest() { - addTests({&ContextTest::constructCopyMove, + addTests({&ContextTest::constructNoCreate, + &ContextTest::constructCopyMove, &ContextTest::extensions, &ContextTest::debugHrtfStatus}); } +void ContextTest::constructNoCreate() { + Context context{NoCreate}; + + CORRADE_VERIFY(!Context::hasCurrent()); +} + void ContextTest::constructCopyMove() { /* Only move-construction allowed */ CORRADE_VERIFY(!(std::is_constructible{}));