|
View:
New views
7 Messages
—
Rating Filter:
Alert me
|
|
|
suggestions on c++ game design with openAL most welcomeI'm working on a c++ project that's moving into the early stages of audio design and facing what Im guessing would be a fairly common problem, and wondering if anyone has any advice or input about the best way to approach it. For those interested - Let's say we have a car class classCar() { public: classCoords coordsPosition; classCoords coordsVelocity; float fltFuel; ...etc. }; where classCoords is simply coords { public: float x, y, z; }; and Cars are to be able to emit several sounds at once , an ambient engine roar, horn beeps, drivers yelling etc. Also there's to be literally millions of cars (OK, in the project itself , they're not cars :) ), so a method of culling sound emitters from the set needs to be there. Criteria for adding and removing Cars from the audio set is simply proximity to a viewing position, and the cars themselves are stored in something like multimap <classCoord*, classCar*> mapCars; (again, they're not cars- let's say the track consists of many discrete positions, each of which can contain many cars). I've been experimenting with two methods of handling this - the first with a global controller: Method 1 --------------------------------- class classAudioController() { int MAX_SOUNDS = 150; multimap <classCar*, ALuint> mapSounds; //where ALuint here is a ref to the standard openal source[MAX_SOURCES] //functions void AddCar(classCar* newCar, ALuint* newAudioSource); bool AssignBufferToSource(ALuint *intSource, ALbyte* filename); void UpdateAllPositionsAndVelocities(); void UpdateCarsPositionAndVelocity(classCar* findCar); etc. }; and also by a more object oriented Method 2 ---------------------------------- classCar { //position, fuel, health etc. //.... classAudioSource *soundSource[MAX_SOURCES]; //remember, each car can emit many simultaneous sounds } where classAudioSource is { ALuint intSource //funcs bool AssignBufferToThis(ALbyte *filename); void UpdatePosition(float x, float y, float z); void Play(); void Stop(); void PitchMod(float fltMod); void GainMod(float fltMod); void SetLooping(bool boolLooping); etc. } With Method 1, Cars are added and removed from the emitters map on movement events, In method 2 classAudioSources are triggered to Play/Stop based on listener proximity , again on movement events. I'm running into problems with both, mainly from unfamiliarity. If anyone here has input into handling large sets of sound sources in a good OO-heavy way, or can point me to any reading that does I'd be massively grateful. Thanks, Pingwah Find car news, reviews and more Looking to change your car this year? _______________________________________________ Openal mailing list Openal@... http://opensource.creative.com/mailman/listinfo/openal |
|
|
Re: suggestions on c++ game design with openAL most welcomeHello, I have a number of comments for you. "class classCar" is not a conventional name. If it suits you, that is fine, but it seems like you've got an incredibly verbose naming scheme which many people will find hard to look at. A good scheme is to have UpperCase class names which are typically common or proper nouns. Secondly, variables don't need to include their type in the name. This is generally considered tedious and unmaintainable. For example, fltFuel might change to dblFuel. But now you have to change a lot of code. There are lots of information regarding different kinds of coding conventions available on the internet. Also, you have made all your member variables public. This is also considered harmeful, for example if you change the implementation or storage of fltFuel, this now becomes a project wide change. The better option is to rely on encapsulation which generally means you have getters/setters for key values related to the object you are trying to represent. For example, rather than having setFuel(...) you might have a function addFuel(...) which carries out logic such as checking the maximum fuel that can be added, etc, which in my experience reduces the complexity of code elsewhere. Looking at the solutions you are proposing - consider what best models what you are trying to represent. For example, if you have multiple cars, each car has an engine, therefore each engine produces a sound. We can consider this the ideal world. In computer programming we need to use tricks to achieve adequate performance. So, it might be the case that you have up to 5 sources dedicated to engine sounds, and then choose the 5 engines closest to the "camera" for actually playing audio. If you have more than "x" sources close to the "camera" it may become impossible for the listener to identify individual sounds anyway, so we can limit to 5 without any loss of apparent quality. I would question your use of a multimap for storing cars. It seems incredibly complex and performance heavy. Consider the use of a simple std::vector. This is very fast and has guaranteed O(1) performance for all operations if you are not concerned about order. To remove an element from a vector simply use std::swap and pop: /// Provides an efficient way to erase elements from a std::vector template <typename t> bool eraseElementAtIndex (IndexT index, std::vector<t> & array) { if (array.size() == (index+1)) { array.pop_back(); return false; } else { array[index] = array.back(); array.pop_back(); return true; } } So we can achieve very quick storage with std::vector. If you need to do space querying, for a small number of cars (< 20) brute force algorithm will be fine, such as simply iterating through the list and choosing the closest cars, etc. Finally in terms of structuring the OpenAL code the best option is to look at keeping audio completely separate from your "model". Try to avoid things like: int MAX_SOUNDS = 150; This generally considered bad practice (there are specific cases where it is okay, but generally use std::vector for this kind of allocation). We should consider the importance of two kinds of sounds: - Sound that caused by specific event that occurred (crash sound). - Sound that has been started and stopped by a particular object (engine sound). These two kinds of sounds can be managed differently. For example, often with sounds based of event, my experience is that we can't tell the difference between single source and multiple source in typical cases as long as the sound is short - for example, explosion sound. So in this case you can simply have a single source which is allocated when the game starts. This is very convenient. Alternatively you can allocate a list of sources and use them in a round robin fashion if you need more realism or the sound is longer. For a sound that is continuous we either have a single source (i.e. music) or multiple sources (i.e. multiple engines). This case is slightly more complex especially if we separate audio function from object model (class Car). In this case, the easiest solution is to have the audio source as part of the class Car. This is the simplest solution and means that each car has its own audio source. But this isn't efficient for a large number of cars, and increases coupling between audio and your simulation which if it is running as a server / client, obviously you don't want the server making all sorts of sounds or sound simply may not be available. Therefore, I recommend you look at the following structure: using boost::shared_ptr; class Car { public: Coord getPosition (); }; class CarsController { typedef shared_ptr<Car> CarPtr; std::vector<CarPtr> m_cars; std::vector<CarPtr> findCarsClosestToPoint (Coord c, unsigned maxCars); void update (float dt); }; class AudioSource class CarAudioController { // OpenAL mixer, etc struct CarSource { CarPtr car; AudioSource source; } std::vector<CarSource> m_engineSources; void playEngineSound (CarsController * carsController) { // Step 1: Update source positions / stop sources that are no longer relevant, build a std::set of cars currently active // Step 2: Query for cars that are close to the listener // Step 3: For all these cars, check if they are currently in the set of active cars. If not, add it and start the appropriate engine sound. // Step 4: Profit. } }; In terms of implementation, playEngineSound could implement a round robin scheme that is updated at set time intervals - depending on RPM, distance, etc you can update or stop the sound. There are many many options and this pseudo code is not well developed, but it should give you an idea of how to separate out the code. There are many ways to do this kind of separation - another way is using a delegate. I also wrote an article you might find interesting/useful: I hope something here helps. The question you asked has a lot of possible solutions. Out of the methods you listed below, Method 2 is probably more in the right direction. Kind regards, Samuel On 2/06/2009, at 1:59 PM, Pingwah Leronz wrote:
_______________________________________________ Openal mailing list Openal@... http://opensource.creative.com/mailman/listinfo/openal |
|
|
Re: suggestions on c++ game design with openAL most welcome
Wow. Millions is a lot of cars (or whatever :-) ). You're obviously not going to be able to mix and play that many sounds in real time, so there will be some culling done. You've got the right approach in either case, in that you're allocating buffers and dynamically assigning them to sources. Either way would work, but the second method is probably closer to the "better" way. I've referred to this as "virtualized sources", because you effectively are representing lots of logical sources and then assigning real sources to them dynamically. Listener proximity is often an adequate metric for determining which sounds should be auralized. In my case, each update cycle I actually compute the effective gain (using the attenuation formula from the spec) for each virtual source, and then sort them by effective gain. I also throw in a user-defined "priority" metric (picked from LOW, NORMAL, HIGH, and ALWAYS_ON). The sounds with ALWAYS_ON priority are guaranteed sources, regardless of effective gain (obviously, you can't have too many of these). First, each virtual source is checked to see if it is in a playing state (it is removed from further processing if not). Next, the sounds are sorted by priority, then by effective gain. Finally, real sources are assigned to the sounds at the top of the list. This method is probably a bit too expensive for you if you really have to deal with millions of virtual sources. You might need to add in an extra layer of cheap, coarse level processing to deal with culling out most of the sources before you get to a method as fine-grained as this. This might involve some kind of spatial subdivision, where you ignore sounds that lie outside of some radius, or something like that. You might also consider a form of clustering. This technique pre-mixes sounds that lie some distance away, but are roughly in the same direction. Say you have three sources to the northeast, two to the west and five to the south. You could effectively auralize all of these virtual sources using only three real sources. You simply pre-mix the sound from the first three into one buffer, the next two into a second buffer, and the last five into a third buffer, and then play those three buffers on three real sources. In your case, I'm thinking you might not get enough of a soundfield by just picking, say, the sixteen closest sources. You might try pre-mixing some number of sources that are moderately distant into a single, non-directional "ambient" buffer. This might better represent the soundfield you'd really hear in that kind of situation. An easy way to make a non-directional buffer is to mix the sounds into a stereo buffer, with the same data in both left and right channels. That's all I can think of right now. Hope this helps! --"J"
_______________________________________________ Openal mailing list Openal@... http://opensource.creative.com/mailman/listinfo/openal |
|
|
RE: suggestions on c++ game design with openAL most welcomeAs for the bad class design /lack of encapsulation, oh yes definitely - I'd just posted up dummy classes like this, going public for simplicity's sake. I've also gone with the multimap as the program's main data store as I need to access objects by their position - the gameworld is a 3 dimensional grid, with objects having absolute positions. Initially I used a 3d array - AbstractObject[x][y][z] , but given i'm working with a large number of potential objects, using a predefined array like this eats up a *lot* of memory. A multimap using one of my Coords classes , which contains x,y,z as the key allows me to quickly access members, by position, in a dynamically sized manner, and given multimaps are sorted by key, and the .find(key) func is a fairly quick Binary search, this method seems to suit. I'm not sure you can do this with vectors? that is, quickly access a member based on one of its atributes, without iterating through them all to check? The boost shared pointer gives me some ideas, ta. I didn't know about it. >Out of the methods you listed below, Method 2 is probably more in the right direction. ...do you mean Method 1? If im reading your sample code correctly, it's more like a global container of some structure which links a pointer to the object, with its associated sound source, or list of sources? Thanks, Pingwah CC: openal@... From: space.ship.traveller@... To: pingwah_leronz@... Subject: Re: [Openal] suggestions on c++ game design with openAL most welcome Date: Tue, 2 Jun 2009 21:11:38 +1200 Hello, I have a number of comments for you. "class classCar" is not a conventional name. If it suits you, that is fine, but it seems like you've got an incredibly verbose naming scheme which many people will find hard to look at. A good scheme is to have UpperCase class names which are typically common or proper nouns. Secondly, variables don't need to include their type in the name. This is generally considered tedious and unmaintainable. For example, fltFuel might change to dblFuel. But now you have to change a lot of code. There are lots of information regarding different kinds of coding conventions available on the internet. Also, you have made all your member variables public. This is also considered harmeful, for example if you change the implementation or storage of fltFuel, this now becomes a project wide change. The better option is to rely on encapsulation which generally means you have getters/setters for key values related to the object you are trying to represent. For example, rather than having setFuel(...) you might have a function addFuel(...) which carries out logic such as checking the maximum fuel that can be added, etc, which in my experience reduces the complexity of code elsewhere. Looking at the solutions you are proposing - consider what best models what you are trying to represent. For example, if you have multiple cars, each car has an engine, therefore each engine produces a sound. We can consider this the ideal world. In computer programming we need to use tricks to achieve adequate performance. So, it might be the case that you have up to 5 sources dedicated to engine sounds, and then choose the 5 engines closest to the "camera" for actually playing audio. If you have more than "x" sources close to the "camera" it may become impossible for the listener to identify individual sounds anyway, so we can limit to 5 without any loss of apparent quality. I would question your use of a multimap for storing cars. It seems incredibly complex and performance heavy. Consider the use of a simple std::vector. This is very fast and has guaranteed O(1) performance for all operations if you are not concerned about order. To remove an element from a vector simply use std::swap and pop: /// Provides an efficient way to erase elements from a std::vector template <typename t> bool eraseElementAtIndex (IndexT index, std::vector<t> & array) { if (array.size() == (index+1)) { array.pop_back(); return false; } else { array[index] = array.back(); array.pop_back(); return true; } } So we can achieve very quick storage with std::vector. If you need to do space querying, for a small number of cars (< 20) brute force algorithm will be fine, such as simply iterating through the list and choosing the closest cars, etc. Finally in terms of structuring the OpenAL code the best option is to look at keeping audio completely separate from your "model". Try to avoid things like: int MAX_SOUNDS = 150; This generally considered bad practice (there are specific cases where it is okay, but generally use std::vector for this kind of allocation). We should consider the importance of two kinds of sounds: - Sound that caused by specific event that occurred (crash sound). - Sound that has been started and stopped by a particular object (engine sound). These two kinds of sounds can be managed differently. For example, often with sounds based of event, my experience is that we can't tell the difference between single source and multiple source in typical cases as long as the sound is short - for example, explosion sound. So in this case you can simply have a single source which is allocated when the game starts. This is very convenient. Alternatively you can allocate a list of sources and use them in a round robin fashion if you need more realism or the sound is longer. For a sound that is continuous we either have a single source (i.e. music) or multiple sources (i.e. multiple engines). This case is slightly more complex especially if we separate audio function from object model (class Car). In this case, the easiest solution is to have the audio source as part of the class Car. This is the simplest solution and means that each car has its own audio source. But this isn't efficient for a large number of cars, and increases coupling between audio and your simulation which if it is running as a server / client, obviously you don't want the server making all sorts of sounds or sound simply may not be available. Therefore, I recommend you look at the following structure: using boost::shared_ptr; class Car { public: Coord getPosition (); }; class CarsController { typedef shared_ptr<Car> CarPtr; std::vector<CarPtr> m_cars; std::vector<CarPtr> findCarsClosestToPoint (Coord c, unsigned maxCars); void update (float dt); }; class AudioSource class CarAudioController { // OpenAL mixer, etc struct CarSource { CarPtr car; AudioSource source; } std::vector<CarSource> m_engineSources; void playEngineSound (CarsController * carsController) { // Step 1: Update source positions / stop sources that are no longer relevant, build a std::set of cars currently active // Step 2: Query for cars that are close to the listener // Step 3: For all these cars, check if they are currently in the set of active cars. If not, add it and start the appropriate engine sound. // Step 4: Profit. } }; In terms of implementation, playEngineSound could implement a round robin scheme that is updated at set time intervals - depending on RPM, distance, etc you can update or stop the sound. There are many many options and this pseudo code is not well developed, but it should give you an idea of how to separate out the code. There are many ways to do this kind of separation - another way is using a delegate. I also wrote an article you might find interesting/useful: I hope something here helps. The question you asked has a lot of possible solutions. Out of the methods you listed below, Method 2 is probably more in the right direction. Kind regards, Samuel On 2/06/2009, at 1:59 PM, Pingwah Leronz wrote:
Make ninemsn your homepage! Get the latest news, goss and sport _______________________________________________ Openal mailing list Openal@... http://opensource.creative.com/mailman/listinfo/openal |
|
|
RE: suggestions on c++ game design with openAL most welcomeA question though - would the process of mixing many sources into one be pretty much as operation intensive as just playing the sources themselves? Maybe not, given that I'll only have to mix them each time an object moves. I'll have to experiment with this. thanks, Pingwah Date: Tue, 2 Jun 2009 11:11:52 -0400 From: jdaly@... To: pingwah_leronz@... CC: openal@... Subject: Re: [Openal] suggestions on c++ game design with openAL most welcome
Wow. Millions is a lot of cars (or whatever :-) ). You're obviously not going to be able to mix and play that many sounds in real time, so there will be some culling done. You've got the right approach in either case, in that you're allocating buffers and dynamically assigning them to sources. Either way would work, but the second method is probably closer to the "better" way. I've referred to this as "virtualized sources", because you effectively are representing lots of logical sources and then assigning real sources to them dynamically. Listener proximity is often an adequate metric for determining which sounds should be auralized. In my case, each update cycle I actually compute the effective gain (using the attenuation formula from the spec) for each virtual source, and then sort them by effective gain. I also throw in a user-defined "priority" metric (picked from LOW, NORMAL, HIGH, and ALWAYS_ON). The sounds with ALWAYS_ON priority are guaranteed sources, regardless of effective gain (obviously, you can't have too many of these). First, each virtual source is checked to see if it is in a playing state (it is removed from further processing if not). Next, the sounds are sorted by priority, then by effective gain. Finally, real sources are assigned to the sounds at the top of the list. This method is probably a bit too expensive for you if you really have to deal with millions of virtual sources. You might need to add in an extra layer of cheap, coarse level processing to deal with culling out most of the sources before you get to a method as fine-grained as this. This might involve some kind of spatial subdivision, where you ignore sounds that lie outside of some radius, or something like that. You might also consider a form of clustering. This technique pre-mixes sounds that lie some distance away, but are roughly in the same direction. Say you have three sources to the northeast, two to the west and five to the south. You could effectively auralize all of these virtual sources using only three real sources. You simply pre-mix the sound from the first three into one buffer, the next two into a second buffer, and the last five into a third buffer, and then play those three buffers on three real sources. In your case, I'm thinking you might not get enough of a soundfield by just picking, say, the sixteen closest sources. You might try pre-mixing some number of sources that are moderately distant into a single, non-directional "ambient" buffer. This might better represent the soundfield you'd really hear in that kind of situation. An easy way to make a non-directional buffer is to mix the sounds into a stereo buffer, with the same data in both left and right channels. That's all I can think of right now. Hope this helps! --"J"
Find your next place with Ninemsn property Looking for a place to rent, share or buy this winter? _______________________________________________ Openal mailing list Openal@... http://opensource.creative.com/mailman/listinfo/openal |
|
|
|
|
|
Re: suggestions on c++ game design with openAL most welcomeClustering seems a damn good idea! You do have the added complexity of the pre-mixing step, but you also eliminate the need to spatialize as many sources. Each pre-mixed source is only spatialized once, regardless of how many sounds are pre-mixed into it. In my example, there are only three sources spatialized, while there are ten sounds involved in all. Here's a link to a gamasutra article for more info. It's a little bit dated, but probably still relevant: http://www.gamasutra.com/view/feature/2850/breaking_the_64_spatialized_.php --"J" _______________________________________________ Openal mailing list Openal@... http://opensource.creative.com/mailman/listinfo/openal |
| Free embeddable forum powered by Nabble | Forum Help |