Seeking inheritance design ideas/feedback

I'm seeking design ideas:

I'm working on a sound mixing library. The general way I have it set up is there's a generic Sound abstract base class, from which other sound types are derived (StreamedSound, BufferedSound, etc). The idea is you play these sounds by adding them to a Mixer (another Sound derived object). A Mixer would then be owned by an AudioOut derived class (which would then take the Mixer's output and feed it to whatever audio output system you're using, OpenAL, DirectSound, SDL, etc)

To make this work, Sound has a series of functions which provide access to the audio data it contains. However I don't want these functions to be public, because the lib user isn't supposed to use them (they're part of the internal workings, not part of the interface). I'd like to make them protected.

However! Other objects... for example, Mixer, all AudioOut derived classes, and even some other Sound derived classes (like ones which do DSP effects on another sound) do need access to those functions. My first thought was to make them friends, however I can't make them all friends because friendship is not inherited. IE: every time I make a new AudioOut derived class, I would have to specifically add that class as a friend in Sound, and Sound would end up having a huge list of friends.

The only halfway reasonable way I can think of to accomplish this is to make a new class SoundOwner and derive classes that need to be friends from it, then duplicate the Sound functions they'll need access to in SoundOwner. The derived classes can then call the SoundOwner version, which calls the Sound version (which will be be okay because SoundOwner will be a friend of Sound).

For example say the key Sound functions are GetSamples() and Mix(). I would have to lay things out like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Sound
{
protected:
  friend class SoundOwner;
  void GetSamples();
  void Mix();
};

class SoundOwner
{
protected:
  void GetSoundSamples(Sound* snd) { snd->GetSamples(); }
  void MixSound(Sound* snd)        { snd->Mix();        }
};

class Mixer : public Sound, public SoundOwner
{
  void DoSomethingWhereINeedToMixASound()
  {
    MixSound( blah );    // call the SoundOwner version instead of
                         //   directly calling the Sound version 'blah->Mix()'
  }
};


This seems a little funky, though. Does anyone else have any better ideas?
One way to accomplish your goal is to have set of classes that have just the functionality that should be exposed outside the library. Think of your classes in terms of externally exposed and internal classes. These external classes then contain handles to the internal classes and delegate the exposed functions to the internal objects.

This has at least one additional (major) benefit: you are free to change the internal structure of your library without impacting library users.

This class structure can be designed in terms of the bridge pattern.
To begin with, I wouldn't get hung up on how C++ or any other language approaches object theory. You will be much better off in the long run if you specify your system in detail according to the rules (and guidelines) of object theory. You will then be able to implement your ideas properly in any language.

Do not make the 'nannying' of the users of your library a priority. If a user cannot be bothered to read your documentation on how to use your library, then that's their problem.

I find that the best way to 'computerise' a system is to start with relational theory. It's all the rage now for designing relational databases (and developing data warehouses from these), but has existed for thousands of years. Application 'classes' and 'associations' between the classes arise naturally from 'entity types' and 'relationships'. The correspondence between a database schema and an application schema is remarkably similar, and once some minor wrinkles are ironed out, application use-cases become so much easier to specify and implement.

Some points regarding your requirements...

> A Mixer is not a Sound. (This is just as ridiculous as a Bicycle having a HeightWeightRatio! ;))

> StreamedSound and BufferedSound may turn out to be attribute values of a Type attribute of Sound rather than subclasses of Sound. It depends on whether they have their own properties which you want to record.

Your 'statement of requirements' is contained in the first paragraph, and I have identified the following classes and attributes...
1
2
3
4
5
6
7
Sound( Type ... )
StreamedSound( ... )
BufferedSound( ... )
Mixer( ... )
AudioPlayer( Type ... ) // (Values of AudioPlayer.Type include 'OpenAL', 'DirectSound', and 'SDL'.)
DSPEffect( ... )
--------------------------------------

(I have replaced your "AudioOut" entity with AudioPlayer.)

The rest of your post is you as 'coder' trying to be an 'engineer', and you are right to ask for "design ideas".

What you should do now is describe your system, its entity types (classes) and the relationships between those classes. You should include any technical aspects about sound engineering which the system engineer will probably not be familiar with (he will ask you about the meaning of these), and, if you really want to be a system engineer as well as a coder, embark on a relevant course of academic study which will be immensely helpful and rewarding.

Good luck,

Dave

One way to accomplish your goal is to have set of classes that have just the functionality that should be exposed outside the library.


An interesting idea, but it is subject to the same problem. Consider that Sound contains an InternalData member, that member would still either need to be public (exposed) or I'm stuck wrestling with the same issues.

To begin with, I wouldn't get hung up on how C++ or any other language approaches object theory [snip] You will then be able to implement your ideas properly in any language.


Well -- considering I'm writing the lib in C++, and having an issue with my design vs. C++'s friendship system, it seems logical to me to ask this in the context of C++.


> A Mixer is not a Sound. (This is just as ridiculous as a Bicycle having a HeightWeightRatio! ;))


My Sound base class provides volume/panning/fading/etc controls. Consider the following scenarios:

- Game wants to mix sound effects seperately from music so that volume levels can be adjusted independently. Easily accomplished by playing sound effects in their own mixer, using that mixer's volume level as a "master" volume for sound effects, then playing that mixer and the BGM in the main mixer for output. Easily accomplished because the Mixer can be played like any other Sound.

- Game wants to play file types of varying samplerates without having to resample each sound prior to mixing. Say you want to play two files at 32000 Hz but the overall output is 44100 Hz. The two 32K files can be mixed, then the mixer can be resampled via a DSP effect (which takes a Sound type as a source), then the DSP sound can be played in the final mixer. (I'm planning on adding SPC support, of which files natively output 32KHz -- however PCs typically don't output that, so resampling will be required in that case)

Making a Mixer a Sound provides all sorts of additional functionality and very little additional code (none, in fact, with the layout I have set up)

StreamedSound and BufferedSound may turn out to be attribute values of a Type attribute of Sound rather than subclasses of Sound. It depends on whether they have their own properties which you want to record.


Well they both function completely differently. If I have them attributes of the same Sound object then I have to have if/else chains in every Sound function (not to mention bodies for functions which should be pure virtual). The whole point of inheritance/polymorphism is to eliminate that.

What you should do now is describe your system, its entity types (classes) and the relationships between those classes.


Very well. I have others planned from what's listed here but here's the basics:

- SoundSource: Abstract base class. Takes an input File (or stream) and decodes output to PCM. Various derived objects will support different file types (one for .ogg, one for .wav, one for <insert format here>, etc)

- BufferedSoundData (to be renamed): No parent class. Takes a SoundSource and loads it in full (or in part) to a buffer.

- Sound: Abstract base class, provides volume/fading/panning effects, etc.
- StreamedSound: Derived from Sound, owns a SoundSource object. Progressively reads from given source as audio data is needed.
- BufferedSound: Derived from Sound, owns a BufferedSoundData pointer. Reads data from given sound data pointer
- Mixer: Derived from Sound, owns a list of Sound* pointers. Different Sound* objects are "played" in a mixer (played=added to the list). Mixer simply mixes together all the playing Sound objects and uses the result as its output.

- AudioPlayer / AudioOut (to be renamed): Abstract base class for audio output. Owns a Mixer object. Takes the output of the owned Mixer and outputs it via whatever means. Derived classes will accomplish this with different audio libs (SDL,OAL,ALSA,DS,whathaveyou)

-----
AudioPlayer needs to get the audio output from its owned Mixer object (since Mixer is a Sound, this is via the Sound interface)

Mixer needs to get the audio output from its referenced Sound objects

The lib user will have access to these Sound objects as well (so they can pause/stop them, change pan/volume/etc), so the user still needs access to the interface, but I don't want to the user taking the audio output the way AudioPlayer and Mixer need to.

if you really want to be a system engineer as well as a coder, embark on a relevant course of academic study which will be immensely helpful and rewarding.


Well I'm working on this project solo for fun in my free time. So there is no "system engineer" other than myself. And as for the "go to school" suggestion: =P


Do not make the 'nannying' of the users of your library a priority. If a user cannot be bothered to read your documentation on how to use your library, then that's their problem.


So you're saying I should just make the audio output stuff public. I'm actually leaning towards this, as it's the most straightforward solution. Plus who knows, there might even be a time where the user might want to do that for some reason.
- One way to accomplish your goal is to have set of classes that have just the functionality that should be exposed outside the library.

An interesting idea, but it is subject to the same problem. Consider that Sound contains an InternalData member, that member would still either need to be public (exposed) or I'm stuck wrestling with the same issues.


Why does InternalData member need to be exposed? You don't state why that is the case and it seems a rather curious statement to me.

The risk with exposing internal data and structures is that as soon as one person uses that internal functionality, it becomes part of the API of that library permanently.

In my experience, it is far more important to think about how users will interact with a library than it is with the internal design. You rarely get the opportunity to fix a busted API (unless no one uses it). Only expose what the user will need and hide away everything else. If that's done, you can fix a lot of internal design mistakes.
That's exactly why I don't want to expose it. That's the problem I'm facing.

Sound needs to have a way for external objects to be able to actually get the sound data. Mixers will use this data to perform the mixing, output devices will use it to output audio, etc. Basically a Sound object is useless unless the sound actually goes somewhere.

Therefore, Sound has a GetSamples kind of pure virtual function that outside objects call to get the sound data. However I only want this function exposed to select classes (Mixer, AudioOut derived, DSPSound derived, etc). I do not want the user to access it.

The problem is the only way to hide this function from the user is to make it protected, which means the external classes that need to access it can't access it. One solution is to make all the classes that need to access it friends -- however since friends aren't inherited, I can't possibly have a comprehensive friend list in Sound (every time a new DSP effect is added, or a new output device wrapper is added, you'd have to modify Sound to add the new class as a friend -- very bad).

Replacing the function with an InternalData member doesn't solve the access problem. Either the InternalData member is public (bad/exposed), or outside classes need to be friends in order to access it.
Last edited on
So, are Mixer, AudioOut derived, etc. all library objects or can users implement their own Mixer for example?

In any case, if you just want to expose certain functionality from one class, there are a number of patterns and idioms that accomplish that: proxy, handle/body, etc. You can have a Public interface that exposes the "user interface", and an Internal interface that inherits from Public but adds your private methods.

You can also have a method that returns a pointer to an object, but that object type is only forward declared in the external (user) header. Sure, the user can call the method and get the pointer, but they have no way to actually use the pointer because it has no definition.

Just some thoughts...
Topic archived. No new replies allowed.