Author Topic: Sound (thinking out loud...clack, click, clack)  (Read 4257 times)

daniel.santos

  • Guest
Sound (thinking out loud...clack, click, clack)
« on: 13 December 2009, 14:55:44 »
I'm stuck on my other project at the moment (waiting on somebody else) so I'm getting some time in on GAE.  My trunk tmp_program_refactor branch is down to 27 errors but I could use some opinions on something.

I've added a new small class library that's a sort of grab bag for retrieving objects (intended for media objects, but it's generic) based upon a variety of rules.  I'm calling them "Selections" because that word can mean either a single item (e.g., "This is my selection.") or a collection of items (e.g., "Would you like to have a look at our selection?").  Among other things, they are supposed to behave like a sort of "play list" for various media files (sounds, animations, etc.) and replace the current SoundContainer class.

So this is a  Shared::Util::Selection:
Code: [Select]
template<class T> class Selection {
public:
virtual ~Selection() {}
virtual const T *getNext() const = 0;
virtual const T *getLast() const = 0;
};

getNext() will always retrieve the next item and getLast() will just return the last item returned by getNext() (as a helper).  I'm completely open on using different names for any of this, I'm not 100% comfortable with the way I've named them.  But here are the current specializations:
  • SingleSelection - a simple wrapper around a single object.  The getNext() function will always return the same object.
  • ManagedSingleSelection - like SingleSelection, except that it takes either a pointer to the object or a shared_ptr and manages the lifecycle of the object (i.e., cleans it up when it goes away).
  • SerialSelection - a list of Selection objects that will be returned in the order they were added, optionally looping from end to start again.  For each item, you can specify a repeat count (defaulting to one) so you can have some items play more than once or infinitely (which will, of course, prevent it from ever reaching the end).  Each Selection can be either one of the SingleSelection objects or simple another collection, like SerialSelection.
  • RandomSelection - returns one of the items in the list based upon the float chance value you specify, defaulting to one.  So you can add item A with a chance of 0.2f and item B with 0.8f and 80% of the time, it will return item B.
  • RandomSelectionWithNoRepeat - this is a specialization of RandomSelection that allows you to say that you don't want to return an item that you've already returned within the last X seconds or X times ago.  (this is actually broken and commented out right now)

I got started on this to replace the ugly way that Sound objects (StaticSound and StreamSound) were being modified, and how StreamSound used to have a getNext() function.  But this will also support having multiple music files that are played either in order, or random or some mix of those.

I've refactored the StreamSound and StaticSound so that they have a common base class and can be dealt with in a generic fashion, so I think the 1st thing is that I'm going to change all of the Sound "play" functions to accept either a Sound object or a Selection<Sound> instead of either StreamSound or StaticSound.  But the challenge I'm trying to figure out how to address is a new way to send streaming audio content to the SoundPlayer and be able to later refer to that and tell it to stop.  I'm thinking of doing this by naming the SoundPlayers, so I can refer to it later by name and tell it to stop or play something different, but if I abstract the play functions to take either a static or streaming sound, that might muddy things....

Example:
Code: [Select]
class SoundRenderer {
    //.....
    void play(const Sound &); // plays either a static or streaming sound file
    void play(const Selection<Sound> &); // plays whatever the Selection<Sound> returns and when it finishes playing, it will call... ooh, I think I just answered my own question
    void play(const string &playerName, const Sound &);
    void play(const string &playerName, const Selection<Sound> &);
    bool isPlaying(const string &playerName); // returns true if this player is still playing
    void stop(const string &playerName, int64 fade);
};

w00t! all of that typing and explaining showed me where my design flaw is! :)  I need a better distinction between Selection objects that should be repeatedly played and those that should only be played once.  Maybe I can add a bool loop parameter to the play functions that defaults to false.  I'll have to think on this more later, I'm getting sleepy :)

hailstone

  • Local Moderator
  • Battle Machine
  • ********
  • Posts: 1,568
    • View Profile
Re: Sound (thinking out loud...clack, click, clack)
« Reply #1 on: 14 December 2009, 00:21:03 »
Is volume done per sound or managed by the renderer or player?
Glest Advanced Engine - Admin/Programmer
https://sourceforge.net/projects/glestae/

John.d.h

  • Moderator
  • Airship
  • ********
  • Posts: 3,757
  • I have to go now. My planet needs me.
    • View Profile
Re: Sound (thinking out loud...clack, click, clack)
« Reply #2 on: 14 December 2009, 06:08:53 »
Among other things, they are supposed to behave like a sort of "play list" for various media files (sounds, animations, etc.) and replace the current SoundContainer class.
Does this mean it might lead to multiple animations for the same action?  ;D

daniel.santos

  • Guest
Re: Sound (thinking out loud...clack, click, clack)
« Reply #3 on: 14 December 2009, 20:13:17 »
Is volume done per sound or managed by the renderer or player?
Ohh, that's why we need playFx, playAmbient and playMusic! :)  So perhaps the correct solution is to retain those functions, only make them accept type Sound instead of StaticSound or StreamSound (formerly StrSound by the way) and then implement them under the covers using the name/player pair idea.  Then, we can also have arbitrary sounds with arbitrary volumes.  Finally, I would like the engine to support sounds that are attenuated (optionally) so that the volume is based upon the sound level for fx, ambient or music, but is attenuated, if that is the desire.

Lastly, I want the design to support multiple sounds of the same category (i.e, fx, ambient or music) -- this doesn't make that much sense for music, but it can for ambient.  With the new Selection library, very interesting combinations can be glued together to create much more versatile ambient sounds.  For it to be effective, we'll need to add the ability for Selections to have a delay between sounds, possibly random and probably implemented as a Selection object.  Then, sounds can be added with random delays in between. Then, multiples of these can "play" at once, even though they will often be silent.  Example:
  • Bird Chirps - a random bird chirp from a list of birds followed by a delay of 1 to 30 seconds.
  • Animal Sounds - a random sound from various (non-bird) animals followed by a delay of 60 to 120 seconds
  • Wind Sounds - a random wind blowing sound with no delay

Then, you can have 3x Bird Chirps, and 1x of the other two for a more dynamic ambient daytime forest sound (just an example).  The downside is the consumption of audio voices and I'm not entirely sure if OpenAL mixes sounds on its own or leaves that up to the hardware.  I'm also not sure how many voices modern sound cards have these days.

But none of this has to work for now, I just want it to be considered and the design implemented in a way so that it can be supported later on with minimal changes (preferably, without changing the interface).  So for now, I'm thinking of modifying all of the play functions to accept an optional string value specifying a named player to use and returning a string value specifying the actual player that's used with a default of an empty string meaning "any" for playXx() functions and "all" for stopXx() functions.  So if I say soundRenderer.playAmbient(mySound) then it will return the name of the sound player, which will be "ambient0" if no other ambient sounds are playing.  Then, if I later say playAmbient(myOtherSound, "ambient0") it will stop the current ambient sound and play the new one.  However, if I want to play an additional ambient sound, I can say playAmbient(myOtherSound) and it will return "ambient1".  Alternately, I can say soundRenderer.playAmbient(myOtherSound, "mySpecialAmbient") and the return value will always be "mySpecialAmbient".

Thanks hailstone! :)

Does this mean it might lead to multiple animations for the same action?  ;D
Precisely.  In addition, you will have a fairly insane level of control (in your mod's definition) of how this will take place.  Currently, my design does not support Star/Warcraft-style unit acknowledgments (i.e., complete with easter eggs if you continue to click on it and do not allow more than X milliseconds to pass between clicks (otherwise it starts over), but the "start over if more than X milliseconds pass between invcations is the only thing that's missing.  You can currently have a collection of sounds that will play 3 random sounds, followed by a series of 5 sounds and then loops back to the begnning.
Code: [Select]
SerialSelection<Sound> mySounds(true); // loop = true
RandomSelect<Sound> someRandomSounds;
someRandomSounds.add(mySoundA, 1.f);
someRandomSounds.add(mySoundB, 2.f);   // this one is twice as likely to play as the others
someRandomSounds.add(mySoundC, 1.f);
someRandomSounds.add(mySoundD, 1.f);
mySounds.add(someRandomSounds, 3);     // add the someRandomSounds list as the 1st element of mySounds and have it repeat 3 times.
                                       // This will actually cause someRandomSounds to be invoked 3 times and thus, produce
                                       // a random selection each time
mySounds.add(someOtherSoundA);
mySounds.add(someOtherSoundB);
mySounds.add(someOtherSoundC);
mySounds.add(someOtherSoundD);
mySounds.add(someOtherSoundE);

The same is true for animations.  For instance, for a martial artist warrior, you can have them attack using a few random attacks, but then have them execute a series of other attacks in a similar fashion, like a martial arts form.  I'm only proposing this visually, but I suppose it's possible to have skills behave the same way, so that a smaller attack skill can be executed, followed by a more powerful one and finally the most powerful one if the attack on the same unit is continuing.  Just an idea there.