Browse Source

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 <Squareys@googlemail.com>
pull/110/head
Squareys 11 years ago
parent
commit
2d6f6af974
  1. 4
      src/Magnum/Audio/Audio.h
  2. 4
      src/Magnum/Audio/CMakeLists.txt
  3. 113
      src/Magnum/Audio/Listener.cpp
  4. 224
      src/Magnum/Audio/Listener.h
  5. 1
      src/Magnum/Audio/Test/CMakeLists.txt
  6. 110
      src/Magnum/Audio/Test/ListenerTest.cpp

4
src/Magnum/Audio/Audio.h

@ -49,6 +49,10 @@ template<UnsignedInt> class PlayableGroup;
using PlayableGroup2D = PlayableGroup<2>;
using PlayableGroup3D = PlayableGroup<3>;
template<UnsignedInt> class Listener;
using Listener2D = Listener<2>;
using Listener3D = Listener<3>;
}}
#endif

4
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

113
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š <mosra@centrum.cz>
Copyright © 2015 Jonathan Hale <squareys@googlemail.com>
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<UnsignedInt dimensions> void Listener<dimensions>::clean(const MatrixTypeFor<dimensions, Float>& 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<UnsignedInt dimensions> void Listener<dimensions>::update(std::initializer_list<std::reference_wrapper<PlayableGroup<dimensions>>> 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<std::reference_wrapper<AbstractObject<dimensions, Float>>> objects;
objects.push_back(this->object());
for(PlayableGroup<dimensions>& 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<dimensions, Float>::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
}}

224
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š <mosra@centrum.cz>
Copyright © 2015 Jonathan Hale <squareys@googlemail.com>
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 <string>
#include <al.h>
#include <Magnum/Math/Matrix3.h>
#include <Magnum/Math/Matrix4.h>
#include <Magnum/Math/Quaternion.h>
#include <Magnum/SceneGraph/AbstractFeature.h>
#include <Magnum/SceneGraph/AbstractObject.h>
#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 <UnsignedInt dimensions> class Listener: public SceneGraph::AbstractFeature<dimensions, Float> {
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<dimensions, Float>& object):
SceneGraph::AbstractFeature<dimensions, Float>(object),
_gain{1.0f}
{
SceneGraph::AbstractFeature<dimensions, Float>::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<dimensions>& 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<std::reference_wrapper<PlayableGroup<dimensions>>> 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<dimensions>& 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<dimensions, Float>& 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

1
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()

110
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š <mosra@centrum.cz>
Copyright © 2015 Jonathan Hale <squareys@googlemail.com>
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 <Corrade/TestSuite/Tester.h>
#include <Magnum/SceneGraph/Scene.h>
#include <Magnum/SceneGraph/Object.h>
#include <Magnum/SceneGraph/MatrixTransformation2D.h>
#include <Magnum/SceneGraph/MatrixTransformation3D.h>
#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<SceneGraph::MatrixTransformation2D> Scene2D;
typedef SceneGraph::Object<SceneGraph::MatrixTransformation2D> Object2D;
typedef SceneGraph::Scene<SceneGraph::MatrixTransformation3D> Scene3D;
typedef SceneGraph::Object<SceneGraph::MatrixTransformation3D> 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)
Loading…
Cancel
Save