Jump to content
  • Advertisement
Sign in to follow this  
  • entries
  • comments
  • views

Sound Manager Coming Together

Sign in to follow this  
Polymorphic OOP


Huge update concerning the inner workings of the sound managers I mentioned in my last update here, particularly for those interested in making their own personal implementations.

Overview for those unbriefed:
The purpose of the sound manager types, as explained in my previous post, is to provide a free and portable encapsulation of the concept of the play and management of 3d sounds and listeners in a C++ environment. The end result is a system for quickly and safely setting up 3d sound on a target platform without having to directly work with non-portable code. The idea is obviously nothing new to the world of programming. This particular application of abstraction is the basis for the 3d portion the FSOUND API of fmod, as well as OpenAL, only I'm taking it to an even higher level of abstraction. More low-level concepts are abstracted away, and, as well, initialization and destruction of objects such as sound generators are handled automatically through C++ objects, leaving little room for programmer error (since it's a C++ API, not a C portable API such as those I just mentioned). Since the abstraction is a layer above that of most other sound APIs, the implementation of a conforming sound manager can internally use DirectMusic and DirectSound, or OpenAL, or fmod, without impacting the standard external interface of the type. The most notable high-level feature in my proposed standard that is not a part of OpenAL or fmod include automatic management of sound generators based on user-defined priority types and comparison predicates.

Anyway, for the passed couple of weeks, up until a few hours ago, I have been working with this idea directly as stated, towards an end result where I would have a group of types and templates that would be implementation specific, yet all share a common interface, such that you download an OpenAL implementation if you are working on linux or possibly the DirectSound and DirectMusic implementation if you were running windows. Each implementation just goes into the same namespace with the same type and template names.

The problem, as I've realized for some time yet just accepted for some reason, is that this limits you to having one implementation installed on your system at any given time (unless you just change your include directory settings depending on the implementation you wish to use). The more I thought about it, the more I realized that this is totally unacceptable. It is very conceivable that one might want to have different implementations installed, and going further, one might even want to use multiple implementations in the same application, similar to having an engine which can optionally render with Direct3D or OpenGL.

So, I decided to change the concept from a set of absolute types and templates in a common namespace with a standardized interface, to a pure abstraction concept, where not even the location of the object is standardized; they just must conform to the interface. The types and templates can exist anywhere and have any name. This way, multiple implementations can exist on the same computer. No revolutionary idea there, just a change in my approach.

However, I also realized that the addition of a priority concept as well as the standard way I provide of handling the insertion of new generators in a saturated manager, could be completely and portably implemented on top of a much more simple pure abstraction than what I described earlier. Since all of the high-level functionality can be implemented via the interface of the provided abstraction, default implementations of the high-level priority and basic sound manager concepts can exist for any system by just being templated with one parameter representing the type which conforms to the abstraction to be used for implementation!

This has some very powerful results. Now, implementing different versions of the sound managers is even more simple than I had planned. All the implementor must do is provide a very basic low-level abstraction and now those types can be directly used by the high-level templates without any changes. Further, if the internal API already allows things such as priorities, like fmod, there is nothing stopping someone from partially specializing the encapsulator template to take advantage of it (though in the case of fmod only integral priorities in ascending order are supported whereas in my implementation both the priority type as well as the comparison predicate are template parameters, so it would only be a partial specialization for integral types where the Predicate defines an ascending priority organization).

Not only this, but that also brings back the fact that the overall types used are in the same exact namespace regardless of implementation, though now without conflict since you use the encapsulator types instead of the implementation-specific types. So now you have a common set of templates which exist in a standard namespace, have a standard interface, and even have a portable default implementation based off of a given pure abstraction that you can use portably with a provided underlying implementation (of which I personally will be providing a DirectMusic and DirectSound implementation as well as an OpenAL implementation)!

So, as an example:

namespace some_namespace
class openal_implementation
// Implementation whose interface
// conforms to the standardized
// simple, low-level pure abstraction.

namespace audio_library // I haven't decided on a namespace name yet
template< typename PureAbstraction >
class basic_sound_manager_3d
// Default implementation uses
// the provided pure abstraction.
// This version doesn't use priorities.

template< typename PureAbstraction
, typename PriorityType = unsigned int
, typename PriorityPredicate = ::std::less< PriorityType >
class sound_manager_3d
// Default implementation uses
// the provided pure abstraction.
// This version uses priorities.

int main()
audio_library::sound_manager_3d< some_namespace::openal_implementation > sound_manager;

Aside from this, I have also developed what I feel is the best way to handle extensions for the managers. This is, again, nothing revolutionary, though I feel it is important that I standardize the concept for the project.

Since different audio APIs provide different ways of dealing with certain concepts which can't necessarily be universally translated between, such as environmental effects and lower-level audio effects, it becomes necessary to expose those interfaces in the high-level encapsolator. The problem is, since I made the high-level encapsulator have a default implementation that I want people to be able to use even if their implementation has extensions, how do I create a way of exposing all of those extensions through the encapsolator types?

Here is the solution I came up with:

For the case of functions which have a high-level implementation that internally must call the lower-level version as a part of the process (IE initialization), there is an extra type at the end of the function parameter list which is declared in the low-level implementation type (I described this in my previous post). The argument must have a default value to conform to the standard. If someone needs to pass implementation-specific initialization information (such as a window handle), then they just explicitly pass the parameter. Again, this is simple and nothing special, but succeeds at providing a way to expose internal initialization without changing the implementation of the high-level encapsulator template.

In cases where there is no high-level function that corresponds to low-level extensions, such as specific effects applied to sounds, an "extension" member function template is used. This function is an instantiation of a template whose sole template parameter is a tag type which specifies the extension being used. If the extension is supported then the function is defined, otherwise, the user will get a compiler error (which is good). Right now, I am leaning towards using SFINAE to allow the functions to actually take the amount of arguments required for the extension, however, since lots of compilers do not fully support the proper use of SFINAE, I may end up having the function always take one parameter and rely on explicit specialization instead.

So, for example, to use an I3DL2 extension for environmental effects on a sound generator with a compiler that has fully compliant SFINAE support, you'd simply do something like:

your_sound_generator.extension< I3DL2_extension >( all
, of
, your
, parameters
, go
, here

The corresponding code for a non-SFINAE compliant compiler would just pass a structure with all of the parameters.

What's good about this design is that since it uses a compile-time tagging system and since the varying implementations are known at compile-time, you will get an error at compile time if the extension you are attempting to use is not supported rather than finding out at runtime.

That's it for now. Any ideas are welcome and feedback is appreciated.
Sign in to follow this  


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!