From ef777c8078d72c30f4f1322bc609df248ca8f642 Mon Sep 17 00:00:00 2001 From: Squareys Date: Tue, 29 Sep 2015 00:28:19 +0200 Subject: [PATCH] Audio: Add Playable feature, PlayableGroup and test Playable manages the position, orientation and gain of an `Magnum::Audio::Source`. PlayableGroup is its corresponding FeatureGroup, which enables playing/pausing/stopping multiple sounds sources, aswell as setting a common gain and sound transformation for them. Signed-off-by: Squareys --- src/Magnum/Audio/Audio.h | 10 ++ src/Magnum/Audio/CMakeLists.txt | 10 ++ src/Magnum/Audio/Playable.h | 189 +++++++++++++++++++++ src/Magnum/Audio/PlayableGroup.h | 221 +++++++++++++++++++++++++ src/Magnum/Audio/Test/CMakeLists.txt | 5 + src/Magnum/Audio/Test/PlayableTest.cpp | 97 +++++++++++ 6 files changed, 532 insertions(+) create mode 100644 src/Magnum/Audio/Playable.h create mode 100644 src/Magnum/Audio/PlayableGroup.h create mode 100644 src/Magnum/Audio/Test/PlayableTest.cpp diff --git a/src/Magnum/Audio/Audio.h b/src/Magnum/Audio/Audio.h index 13488dae8..fadb678e8 100644 --- a/src/Magnum/Audio/Audio.h +++ b/src/Magnum/Audio/Audio.h @@ -29,6 +29,8 @@ * @brief Forward declarations for @ref Magnum::Audio namespace */ +#include + namespace Magnum { namespace Audio { #ifndef DOXYGEN_GENERATING_OUTPUT @@ -39,6 +41,14 @@ class Source; /* Renderer used only statically */ #endif +template class Playable; +using Playable2D = Playable<2>; +using Playable3D = Playable<3>; + +template class PlayableGroup; +using PlayableGroup2D = PlayableGroup<2>; +using PlayableGroup3D = PlayableGroup<3>; + }} #endif diff --git a/src/Magnum/Audio/CMakeLists.txt b/src/Magnum/Audio/CMakeLists.txt index da1aec30f..87b25fb09 100644 --- a/src/Magnum/Audio/CMakeLists.txt +++ b/src/Magnum/Audio/CMakeLists.txt @@ -45,6 +45,12 @@ set(MagnumAudio_HEADERS visibility.h) +if(WITH_SCENEGRAPH) + list(APPEND MagnumAudio_HEADERS + Playable.h + PlayableGroup.h) +endif() + # Audio library add_library(MagnumAudio ${SHARED_OR_STATIC} ${MagnumAudio_SRCS} @@ -56,6 +62,10 @@ endif() target_link_libraries(MagnumAudio Magnum ${CORRADE_PLUGINMANAGER_LIBRARIES} ${OPENAL_LIBRARY}) +if(WITH_SCENEGRAPH) + target_link_libraries(MagnumAudio MagnumSceneGraph) +endif() + install(TARGETS MagnumAudio RUNTIME DESTINATION ${MAGNUM_BINARY_INSTALL_DIR} LIBRARY DESTINATION ${MAGNUM_LIBRARY_INSTALL_DIR} diff --git a/src/Magnum/Audio/Playable.h b/src/Magnum/Audio/Playable.h new file mode 100644 index 000000000..2e3ab17cc --- /dev/null +++ b/src/Magnum/Audio/Playable.h @@ -0,0 +1,189 @@ +#ifndef Magnum_Audio_Playable_h +#define Magnum_Audio_Playable_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::Playable, typedef @ref Magnum::Audio::Playable2D, @ref Magnum::Audio::Playable3D + */ + +#include +#include + +#include +#include +#include + +#include "Magnum/Audio/PlayableGroup.h" +#include "Magnum/Audio/Source.h" +#include "Magnum/Audio/visibility.h" + +namespace Magnum { namespace Audio { + +/** +@brief Playable + +Feature which manages the position, orientation and gain of a @ref Source for +an @ref SceneGraph::Object. + +## Usage + +@code +Object3D object; +Source source; +Playable3D playable{object, source}; + +// ... + +object.translate(offset); + +// ... and every frame, update the sources positions by cleaning the object: +object.setClean(); +@endcode + +Position of the source will be updated to the translation of `object` when +@ref SceneGraph::Object::setClean() is called, which is done in +@ref Audio::Listener::update() or @ref Audio::PlayableGroup::setClean() for example. + +To manage multiple Playables at once, use @ref PlayableGroup. + +- @ref Playable2D +- @ref Playable3D + +@see @ref Source, @ref PlayableGroup, @ref Listener +*/ +template class Playable: public SceneGraph::AbstractGroupedFeature, Float> { + friend PlayableGroup; + + public: + + /** + * @brief Constructor + * @param object Object this playable belongs to + * @param group Group this playable belongs to + * + * Creates playable with a source and a forward vector of `{0.0f, -1.0f}` + * for 2D and `{0.0f, 0.0f, -1.0f}` for 3D scenes. This forward vector + * cannot be changed, the sources orientation and translation can be + * instead affected by `object` or via + * @ref PlayableGroup::setSoundTransformation(). + * @see @ref setGain(), @ref PlayableGroup::add() + */ + explicit Playable(SceneGraph::AbstractObject& object, PlayableGroup* group = nullptr): + SceneGraph::AbstractGroupedFeature, Float>(object, group), + _fwd(0.0f), + _gain(1.0f), + _source() + { + SceneGraph::AbstractFeature::setCachedTransformations(SceneGraph::CachedTransformation::Absolute); + _fwd[dimensions - 1] = -1; + } + + /** @brief Source which is managed by this feature */ + Source& source() { + return _source; + } + + /** + * @brief Gain + * @see @ref Playable::setGain() + */ + Float gain() const { + return _gain; + } + + /** + * @brief Set gain of the playable and source respecting the PlayableGroups gain + * @return Reference to self (for method chaining) + * + * The sources gain is computed as `sourceGain = playableGain*groupGain`. + * Default for the playables gain is `1.0f`. + * @see @ref PlayableGroup::setGain(), @ref Source::setGain() + * @see @ref Playable::gain() + */ + Playable& setGain(const Float gain) { + _gain = gain; + cleanGain(); + return *this; + } + + /** + * @brief Group containing this playable + * + * If the playable doesn't belong to any group, returns `nullptr`. + */ + PlayableGroup* playables() { + return static_cast*>(this->group()); + } + + const PlayableGroup* playables() const { /**< @overload */ + return static_cast*>(this->group()); + } + + private: + + void clean(const MatrixTypeFor& absoluteTransformationMatrix) override { + Vector3 position = Vector3::pad(absoluteTransformationMatrix.translation(), 0); + if(playables()) { + position = playables()->soundTransformation().transformVector(position); + } + _source.setPosition(position); + _source.setDirection(Vector3::pad(absoluteTransformationMatrix.rotation()*_fwd)); + + // TODO: velocity + } + + /* Update the gain of the underlying source to reflect changes in _group and/or _gain. + Called in Playable::setGain() and PlayableGroup::setGain() */ + void cleanGain() { + if(playables()) { + _source.setGain(_gain*playables()->gain()); + } else { + _source.setGain(_gain); + } + } + + VectorTypeFor _fwd; + Float _gain; + Source _source; +}; + +/** + * @brief Playable for two dimensional float scenes + * + * @see @ref Playable3D + */ +typedef Playable<2> Playable2D; +/** + * @brief Playable for three dimensional float scenes + * + * @see @ref Playable2D + */ +typedef Playable<3> Playable3D; + +}} + +#endif diff --git a/src/Magnum/Audio/PlayableGroup.h b/src/Magnum/Audio/PlayableGroup.h new file mode 100644 index 000000000..a826c4eb5 --- /dev/null +++ b/src/Magnum/Audio/PlayableGroup.h @@ -0,0 +1,221 @@ +#ifndef Magnum_Audio_PlayableGroup_h +#define Magnum_Audio_PlayableGroup_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::PlayableGroup, typedef @ref Magnum::Audio::PlayableGroup2D, @ref Magnum::Audio::PlayableGroup3D + */ + +#include +#include +#include + +#include +#include +#include + +#include "Magnum/Audio/Audio.h" +#include "Magnum/Audio/Source.h" +#include "Magnum/Audio/visibility.h" + +namespace Magnum { namespace Audio { + +/** +@brief PlayableGroup + +Manages @ref Audio::Playable features and provides means of setting gain or +transformation of a group of Playables, aswell as the ability of playing, +pausing, stopping or cleaning all sources of all Playables. + +## Usage + +@code +Object3D object; +Source source; +PlayableGroup3D group; +Playable3D playable{object, source, &group}; + +// ... + +object.translate(offset); + +// ... and every frame, update the sources positions: +group.setClean(); +@endcode + +For two dimensional scenes simply replace `3D` with `2D`. When using a +@ref Listener, prefer @ref Listener::update() over +@ref PlayableGroup::setClean(). + +- @ref PlayableGroup2D +- @ref PlayableGroup3D + +@see @ref Playable, @ref SceneGraph::FeatureGroup, @ref Listener +*/ +template class PlayableGroup: public SceneGraph::FeatureGroup, Float> { + friend Playable; + + public: + + /** @brief Constructor */ + explicit PlayableGroup(): + SceneGraph::FeatureGroup, Float>(), + _gain{1.0f} + {} + + /** + * @brief Play all sound sources in this group + * @return Reference to self (for method chaining) + * @see @ref Source::play() + */ + PlayableGroup& play() { + Source::play(sources()); + return *this; + } + + /** + * @brief Pause all sound sources in this group + * @return Reference to self (for method chaining) + * @see @ref Source::pause() + */ + PlayableGroup& pause() { + Source::stop(sources()); + return *this; + } + + /** + * @brief Stop all sound sources in this group + * @return Reference to self (for method chaining) + * @see @ref Source::stop() + */ + PlayableGroup& stop() { + Source::stop(sources()); + return *this; + } + + /** + * @brief Gain + * @see @ref PlayableGroup::setGain() + */ + Float gain() const { + return _gain; + } + + /** + * @brief Set gain for all sound sources of Playables in this group + * @param gain Gain + * + * Will calculate the sound sources gain relative to the gain of the + * Playable and this playable group. The sources gain is computed as + * `sourceGain = playableGain*groupGain`. Default of the groups gain + * is `1.0f`. + * @return Reference to self (for method chaining) + * @see @ref PlayableGroup::gain() + */ + PlayableGroup& setGain(const Float gain) { + _gain = gain; + for(UnsignedInt i = 0; i < this->size(); ++i) + (*this)[i].cleanGain(); + + return *this; + } + + /** + * @brief Sound transformation + * @see @ref PlayableGroup::setSoundTransformation() + */ + const Matrix4& soundTransformation() const { + return _soundTransform; + } + + /** + * @brief Set transformation of the sounds in this group + * @return Reference to self (for method chaining) + * @see @ref PlayableGroup::soundTransformation() + */ + PlayableGroup& setSoundTransformation(const Matrix4& matrix); + + /** + * @brief Set all contained Playables clean + * @see @ref AbstractObject::setClean() + */ + void setClean(); + + private: + + /* @brief Sources of all Playables in this group */ + std::vector> sources() { + std::vector> srcs; + srcs.reserve(this->size()); + for(UnsignedInt i = 0; i < this->size(); ++i) + srcs.push_back((*this)[i].source()); + return srcs; + } + + Matrix4 _soundTransform; + Float _gain; +}; + +template inline PlayableGroup& PlayableGroup::setSoundTransformation(const Matrix4& matrix) { + _soundTransform = matrix; + + /* I cannot come up with a use case for which the sound + transformation would be set frequently, so we are setting + objects dirty whether the matrix changed or not. */ + for(UnsignedInt i = 0; i < this->size(); ++i) + (*this)[i].object().setDirty(); + + return *this; +} + +template inline void PlayableGroup::setClean() { + std::vector>> objects; + objects.reserve(this->size()); + + for(UnsignedInt i = 0; i < this->size(); ++i) + objects.push_back((*this)[i].object()); + + SceneGraph::AbstractObject::setClean(objects); +} + +/** + * @brief Playable group for two dimensional float scenes + * + * @see @ref PlayableGroup3D + */ +using PlayableGroup2D = PlayableGroup<2>; + +/** + * @brief Playable group for three dimensional float scenes + * + * @see @ref PlayableGroup2D + */ +using PlayableGroup3D = PlayableGroup<3>; + +}} + +#endif diff --git a/src/Magnum/Audio/Test/CMakeLists.txt b/src/Magnum/Audio/Test/CMakeLists.txt index a7dfefa5d..9b2ca6463 100644 --- a/src/Magnum/Audio/Test/CMakeLists.txt +++ b/src/Magnum/Audio/Test/CMakeLists.txt @@ -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"), @@ -32,3 +33,7 @@ corrade_add_test(AudioAbstractImporterTest AbstractImporterTest.cpp LIBRARIES Ma corrade_add_test(AudioBufferTest BufferTest.cpp LIBRARIES MagnumAudio) corrade_add_test(AudioRendererTest RendererTest.cpp LIBRARIES MagnumAudio) corrade_add_test(AudioSourceTest SourceTest.cpp LIBRARIES MagnumAudio) + +if(WITH_SCENEGRAPH) + corrade_add_test(AudioListenerTest ListenerTest.cpp LIBRARIES MagnumSceneGraph MagnumAudio) +endif() diff --git a/src/Magnum/Audio/Test/PlayableTest.cpp b/src/Magnum/Audio/Test/PlayableTest.cpp new file mode 100644 index 000000000..9b0fd424a --- /dev/null +++ b/src/Magnum/Audio/Test/PlayableTest.cpp @@ -0,0 +1,97 @@ +/* + 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 "Magnum/Audio/Context.h" +#include "Magnum/Audio/Playable.h" + +namespace Magnum { namespace Audio { namespace Test { + +typedef SceneGraph::Scene Scene3D; +typedef SceneGraph::Object Object3D; + +struct PlayableTest: TestSuite::Tester { + explicit PlayableTest(); + + void testFeature(); + void testGroup(); + + Context _context; +}; + +PlayableTest::PlayableTest() { + addTests({&PlayableTest::testFeature, + &PlayableTest::testGroup}); +} + +void PlayableTest::testFeature() { + Scene3D scene; + Object3D object{&scene}; + Source source; + Playable3D playable{object}; + + constexpr Vector3 offset{1.0f, 2.0f, 3.0f}; + object.translate(offset); + object.setClean(); + + CORRADE_COMPARE(playable.source().position(), offset); +} + +void PlayableTest::testGroup() { + Scene3D scene; + Object3D object{&scene}; + Source source; + PlayableGroup3D group; + Playable3D playable{object, &group}; + + constexpr Vector3 offset{-3.0f, 2.0f, 1.0f}; + object.translate(offset); + group.setClean(); + CORRADE_COMPARE(playable.source().position(), offset); + + group.setGain(0.5f); + CORRADE_COMPARE(playable.source().gain(), 0.5f); + + playable.setGain(0.5f); + CORRADE_COMPARE(playable.source().gain(), 0.25f); + + group.setSoundTransformation(Matrix4::fromDiagonal(Vector4{10.0f, 10.0f, 10.0f, 1.0f})); + group.setClean(); + CORRADE_COMPARE(playable.source().position(), offset*10.0f); + + group.play(); + group.pause(); + group.stop(); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Audio::Test::PlayableTest)