From 2d6f6af974584b8f1ba91465c1a2e2e47f378d8d Mon Sep 17 00:00:00 2001 From: Squareys Date: Tue, 29 Sep 2015 00:30:56 +0200 Subject: [PATCH] Audio: Add Listener feature and test Listener manages position, orientation and gain of the OpenAL listener and provides a method to update mutliple PlayableGroups efficiently aswell as making sure there is at most one active Listener used at any given time (since OpenAL only supports the notion of exactly one listener). Signed-off-by: Squareys --- src/Magnum/Audio/Audio.h | 4 + src/Magnum/Audio/CMakeLists.txt | 4 + src/Magnum/Audio/Listener.cpp | 113 +++++++++++++ src/Magnum/Audio/Listener.h | 224 +++++++++++++++++++++++++ src/Magnum/Audio/Test/CMakeLists.txt | 1 + src/Magnum/Audio/Test/ListenerTest.cpp | 110 ++++++++++++ 6 files changed, 456 insertions(+) create mode 100644 src/Magnum/Audio/Listener.cpp create mode 100644 src/Magnum/Audio/Listener.h create mode 100644 src/Magnum/Audio/Test/ListenerTest.cpp diff --git a/src/Magnum/Audio/Audio.h b/src/Magnum/Audio/Audio.h index fadb678e8..f6b8b8e68 100644 --- a/src/Magnum/Audio/Audio.h +++ b/src/Magnum/Audio/Audio.h @@ -49,6 +49,10 @@ template class PlayableGroup; using PlayableGroup2D = PlayableGroup<2>; using PlayableGroup3D = PlayableGroup<3>; +template class Listener; +using Listener2D = Listener<2>; +using Listener3D = Listener<3>; + }} #endif diff --git a/src/Magnum/Audio/CMakeLists.txt b/src/Magnum/Audio/CMakeLists.txt index 87b25fb09..af4034e2d 100644 --- a/src/Magnum/Audio/CMakeLists.txt +++ b/src/Magnum/Audio/CMakeLists.txt @@ -47,8 +47,12 @@ set(MagnumAudio_HEADERS if(WITH_SCENEGRAPH) list(APPEND MagnumAudio_HEADERS + Listener.h Playable.h PlayableGroup.h) + + list(APPEND MagnumAudio_SRCS + Listener.cpp) endif() # Audio library diff --git a/src/Magnum/Audio/Listener.cpp b/src/Magnum/Audio/Listener.cpp new file mode 100644 index 000000000..0f996f303 --- /dev/null +++ b/src/Magnum/Audio/Listener.cpp @@ -0,0 +1,113 @@ +/* + This file is part of Magnum. + + 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"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/Audio/Listener.h" +#include "Magnum/Audio/Playable.h" +#include "Magnum/Audio/PlayableGroup.h" + + +namespace Magnum { namespace Audio { + +using namespace SceneGraph; + +namespace Implementation { + void* activeListener = nullptr; +} + +namespace { + +/* Create a Matrix4 from a Matrix3/Matrix4 */ +inline Matrix4 padMatrix4(const Matrix3& m) { + return Matrix4{Vector4::pad(m[0]), Vector4::pad(m[1]), Vector4::pad(m[2]), Vector4{}}; +} + +inline Matrix4 padMatrix4(const Matrix4& m) { + return m; +} + +} + +template void Listener::clean(const MatrixTypeFor& absoluteTransformationMatrix) { + if(!isActive()) { + /* Only clean if this Listener is active */ + return; + } + + Renderer::setListenerPosition(soundTransformation().transformVector(Vector3::pad(absoluteTransformationMatrix.translation()))); + + const Vector3 fwd = soundTransformation().transformVector(-padMatrix4(absoluteTransformationMatrix).backward()); + const Vector3 up = soundTransformation().transformVector(padMatrix4(absoluteTransformationMatrix).up()); + + Renderer::setListenerOrientation(fwd, up); + + Renderer::setListenerGain(_gain); + + // TODO: velocity +} + +template void Listener::update(std::initializer_list>> groups) { + + /* Check if active listener just changed to this */ + if(this != Implementation::activeListener) { + /* Ensure that clean() is called also when switching between (clean) + listeners */ + Implementation::activeListener = this; + this->object().setDirty(); + + /* Listener gain needs to be updated only when active listener changed + and in setGain() */ + Renderer::setListenerGain(_gain); + } + + /* Add all objects of the Playables in the PlayableGroups to a vector to + later setClean() */ + std::vector>> objects; + + objects.push_back(this->object()); + for(PlayableGroup& group : groups) { + for(UnsignedInt i = 0; i < groups.size(); ++i) { + objects.push_back(group[i].object()); + } + } + + /* Use the more performant way to set multiple objects clean */ + AbstractObject::setClean(objects); +} + +/* On non-MinGW Windows the instantiations are already marked with extern + template */ +#if !defined(CORRADE_TARGET_WINDOWS) || defined(__MINGW32__) +#define MAGNUM_AUDIO_EXPORT_HPP MAGNUM_AUDIO_EXPORT +#else +#define MAGNUM_AUDIO_EXPORT_HPP +#endif + +#ifndef DOXYGEN_GENERATING_OUTPUT +template class MAGNUM_AUDIO_EXPORT_HPP Listener<2>; +template class MAGNUM_AUDIO_EXPORT_HPP Listener<3>; +#endif + +}} diff --git a/src/Magnum/Audio/Listener.h b/src/Magnum/Audio/Listener.h new file mode 100644 index 000000000..565aa623c --- /dev/null +++ b/src/Magnum/Audio/Listener.h @@ -0,0 +1,224 @@ +#ifndef Magnum_Audio_Listener_h +#define Magnum_Audio_Listener_h +/* + This file is part of Magnum. + + 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"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Audio::Listener, @ref Magnum::Audio::Listener2D, @ref Magnum::Audio::Listener3D + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "Magnum/Audio/Renderer.h" +#include "Magnum/Audio/PlayableGroup.h" + +namespace Magnum { namespace Audio { + +namespace Implementation { + /* Pointer to the currently active listener. Should never be dereferenced, + since may be null at any given time. */ + extern MAGNUM_AUDIO_EXPORT void* activeListener; +} + +/** +@brief Listener + +Feature which manages the position, orientation and gain of the OpenAL listener +for an @ref SceneGraph::Object. + +## Usage + +Minimal scene setup for a @ref Listener3D will require the following code: + +@code +Scene3D scene; +Object3D object{&scene}; +Listener3D listener{object}; + +// ... every frame, update the listener to changes in object transformation: +listener.update({}); + +@endcode + +For two dimensional scenes simply replace all `3D` with `2D`. + +### Using Listener with PlayableGroup + +When using @ref PlayableGroup, you can update the listener and groups as +follows: + +@code +// ... +Listener3D listener{object}; +PlayableGroup3D group1, group2; + +// ... and every frame: +listener.update({group1, group2}); +@endcode + +## Active Listener + +There can only be at the most *one* active listener at a given time, i.e. the +one on which @ref Listener::update() was called last. This is because OpenAL +only supports notion of one listener. Having multiple @ref Listener2D or +@ref Listener3D instances can still be useful for conveniently switching +between them for cinematics for example. + +@ref AbstractObject::setClean() will not affect inactive listeners. + +## Sound Transformation + +@ref Listener::setSoundTransformation() enables you to set a transformation +matrix which is applied to the listeners orientation and position before passed +onto OpenAL. This can be used for rotating two dimensional scenes as planes +into the three dimensional audio space or even scaling the audio scene to match +a certain world scale. In the later case you might want to instead consider +@ref Renderer::setSpeedOfSound(). + +- @ref Listener2D +- @ref Listener3D + +@see @ref Audio::Renderer, @ref Playable, @ref PlayableGroup +*/ +template class Listener: public SceneGraph::AbstractFeature { + friend class ActiveListenerHolder; + + public: + + /** + * @brief Constructor + * @param object Object this listener belongs to + * + * Creates a listener with a forward vector of `{0.0f, 0.0f, -1.0f}` and + * up vector of `{0.0f, 1.0f, 0.0f}`. These vectors cannot be changed, + * the listeners orientation and translation can be instead affected by + * `object` or via @ref Listener::setSoundTransformation(). + * @see @ref setGain() + */ + explicit Listener(SceneGraph::AbstractObject& object): + SceneGraph::AbstractFeature(object), + _gain{1.0f} + { + SceneGraph::AbstractFeature::setCachedTransformations(SceneGraph::CachedTransformation::Absolute); + } + + /** + * @brief Sound transformation + * @see @ref Listener::setSoundTransformation() + */ + const Matrix4& soundTransformation() const { + return _soundTransformation; + } + + /** + * @brief Set sound transformation + * @param soundTransformation Transformation for transforming from + * world to listener space + * @return Reference to self (for method chaining) + * @see @ref Listener::soundTransformation() + */ + Listener& setSoundTransformation(const Matrix4& soundTransformation) { + _soundTransformation = soundTransformation; + this->object().setDirty(); + return *this; + } + + /** + * @brief Update the listener + * @param groups Groups to update + * + * Makes this Listener the active listener and calls + * @ref SceneGraph::AbstractObject::setClean() on its parent object and + * all objects of the @ref Playable s in the group. Updates listene + * related configuration for @ref Renderer (position, orientation, gain). + */ + void update(std::initializer_list>> groups); + + /** + * @brief Listener gain + * @see @ref Listener::setGain() + */ + Float gain() const { + return _gain; + } + + /** + * @brief Set listener gain + * @param gain Gain + * @return Reference to self (for method chaining) + * @see @ref Listener::gain() + */ + Listener& setGain(Float gain) { + _gain = gain; + if(isActive()) { + Renderer::setListenerGain(_gain); + } + return *this; + } + + /** @brief Whether this listener is the active listener */ + bool isActive() const { + return this == Implementation::activeListener; + } + + private: + + virtual void clean(const MatrixTypeFor& absoluteTransformationMatrix) override; + + Matrix4 _soundTransformation; + Float _gain; +}; + + +/** + * @brief Listener for two dimensional float scenes + * + * @see @ref Listener3D + */ +using Listener2D = Listener<2>; + +/** + * @brief Listener for three dimensional float scenes + * + * @see @ref Listener2D + */ +using Listener3D = Listener<3>; + +#if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) +extern template class MAGNUM_AUDIO_EXPORT Listener<2>; +extern template class MAGNUM_AUDIO_EXPORT Listener<3>; +#endif + +}} + +#endif diff --git a/src/Magnum/Audio/Test/CMakeLists.txt b/src/Magnum/Audio/Test/CMakeLists.txt index 9b2ca6463..d4c693a1a 100644 --- a/src/Magnum/Audio/Test/CMakeLists.txt +++ b/src/Magnum/Audio/Test/CMakeLists.txt @@ -36,4 +36,5 @@ corrade_add_test(AudioSourceTest SourceTest.cpp LIBRARIES MagnumAudio) if(WITH_SCENEGRAPH) corrade_add_test(AudioListenerTest ListenerTest.cpp LIBRARIES MagnumSceneGraph MagnumAudio) + corrade_add_test(AudioPlayableTest PlayableTest.cpp LIBRARIES MagnumSceneGraph MagnumAudio) endif() diff --git a/src/Magnum/Audio/Test/ListenerTest.cpp b/src/Magnum/Audio/Test/ListenerTest.cpp new file mode 100644 index 000000000..2127e15ef --- /dev/null +++ b/src/Magnum/Audio/Test/ListenerTest.cpp @@ -0,0 +1,110 @@ +/* + This file is part of Magnum. + + 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"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include +#include +#include +#include + +#include "Magnum/Audio/Playable.h" +#include "Magnum/Audio/Context.h" +#include "Magnum/Audio/Listener.h" +#include "Magnum/Audio/PlayableGroup.h" + +namespace Magnum { namespace Audio { namespace Test { + +typedef SceneGraph::Scene Scene2D; +typedef SceneGraph::Object Object2D; + +typedef SceneGraph::Scene Scene3D; +typedef SceneGraph::Object Object3D; + +struct ListenerTest: TestSuite::Tester { + explicit ListenerTest(); + + void testFeature2D(); + void testFeature3D(); + void testUpdateGroups(); + + Context _context; +}; + +ListenerTest::ListenerTest(): _context() { + addTests({&ListenerTest::testFeature2D, + &ListenerTest::testFeature3D, + &ListenerTest::testUpdateGroups}); +} + +void ListenerTest::testFeature2D() { + Scene2D scene; + Object2D object{&scene}; + Listener2D listener{object}; + + constexpr Vector3 offset{1.0f, 2.0f, 0.0f}; + object.translate(offset.xy()); + listener.update({}); + + CORRADE_COMPARE(Renderer::listenerPosition(), offset); +} + +void ListenerTest::testFeature3D() { + Scene3D scene; + Object3D object{&scene}; + Listener3D listener{object}; + + constexpr Vector3 offset{2.0f, 4.0f, 7.0f}; + object.translate(offset); + listener.update({}); + + CORRADE_COMPARE(Renderer::listenerPosition(), offset); +} + +void ListenerTest::testUpdateGroups() { + Scene3D scene; + Object3D sourceObject{&scene}; + Object3D object{&scene}; + PlayableGroup3D group; + Playable3D playable{sourceObject, &group}; + Listener3D listener{object}; + + constexpr Vector3 offset{6.0f, 2.0f, -2.0f}; + object.rotateY(Deg(90.0f)); + object.translate(offset); + sourceObject.translate(offset*13.0f); + + listener.update({group}); + + CORRADE_COMPARE(Renderer::listenerPosition(), offset); + constexpr Vector3 rotatedFwd{-1.0f, 0.0f, 0.0f}; + CORRADE_COMPARE(Renderer::listenerOrientation()[0], rotatedFwd); + CORRADE_COMPARE(playable.source().position(), offset*13.0f); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Audio::Test::ListenerTest)