1 // short living sound occurrence, dies once the sound stops
2 
3 #include "cube.h"
4 
5 #define DEBUGCOND (audiodebug==1)
6 
7 VARP(gainscale, 1, 90, 100);
8 int warn_about_unregistered_sound = 0;
location(int sound,const worldobjreference & r,int priority)9 location::location(int sound, const worldobjreference &r, int priority) : cfg(NULL), src(NULL), ref(NULL), stale(false), playmillis(0)
10 {
11     vector<soundconfig> &sounds = (r.type==worldobjreference::WR_ENTITY ? mapsounds : gamesounds);
12     if(!sounds.inrange(sound))
13     {
14         if (lastmillis - warn_about_unregistered_sound > 30 * 1000) // delay message to every 30 secs so console is not spammed.
15         {
16             // occurs when a map contains an ambient sound entity, but sound entity is not found in map cfg file.
17             conoutf("\f3ERROR: this map contains at least one unregistered ambient sound (sound entity# %d)", sound);
18             warn_about_unregistered_sound = lastmillis;
19         }
20         stale = true;
21         return;
22     }
23 
24     // get sound config
25     cfg = &sounds[sound];
26     cfg->onattach();
27     const float dist = camera1->o.dist(r.currentposition());
28     if((r.type==worldobjreference::WR_ENTITY && cfg->maxuses >= 0 && cfg->uses >= cfg->maxuses) || cfg->muted || (cfg->audibleradius && dist>cfg->audibleradius)) // check max-use limits and audible radius
29     {
30         stale = true;
31         return;
32     }
33 
34     // assign buffer
35     sbuffer *buf = cfg->buf;
36     if(!buf || !buf->id)
37     {
38         stale = true;
39         return;
40     }
41 
42     // obtain source
43     src = sourcescheduler::instance().newsource(priority, r.currentposition());
44     // apply configuration
45     if(!src || !src->valid || !src->buffer(cfg->buf->id) || !src->looping(cfg->loop) || !setvolume(1.0f))
46     {
47         stale = true;
48         return;
49     }
50     src->init(this);
51 
52     // set position
53     attachworldobjreference(r);
54 }
55 
~location()56 location::~location()
57 {
58     if(src) sourcescheduler::instance().releasesource(src);
59     if(cfg) cfg->ondetach();
60     if(ref)
61     {
62         ref->detach();
63         DELETEP(ref);
64     }
65 }
66 
67 // attach a reference to a world object to get the 3D position from
68 
attachworldobjreference(const worldobjreference & r)69 void location::attachworldobjreference(const worldobjreference &r)
70 {
71     ASSERT(!stale && src && src->valid);
72     if(stale) return;
73     if(ref)
74     {
75         ref->detach();
76         DELETEP(ref);
77     }
78     ref = r.clone();
79     evaluateworldobjref();
80     ref->attach();
81 }
82 
83 // enable/disable distance calculations
evaluateworldobjref()84 void location::evaluateworldobjref()
85 {
86     src->sourcerelative(ref->nodistance());
87 }
88 
89 // marks itself for deletion if source got lost
onsourcereassign(source * s)90 void location::onsourcereassign(source *s)
91 {
92     if(s==src)
93     {
94         stale = true;
95         src = NULL;
96     }
97 }
98 
updatepos()99 void location::updatepos()
100 {
101     ASSERT(!stale && ref);
102     if(stale) return;
103 
104     const vec &pos = ref->currentposition();
105 
106     // forced fadeout radius
107     bool volumeadjust = (cfg->model==soundconfig::DM_LINEAR);
108     float forcedvol = 1.0f;
109     if(volumeadjust)
110     {
111         float dist = camera1->o.dist(pos);
112         if(dist>cfg->audibleradius) forcedvol = 0.0f;
113         else if(dist<0) forcedvol = 1.0f;
114         else forcedvol = 1.0f-(dist/cfg->audibleradius);
115     }
116 
117     // reference determines the used model
118     switch(ref->type)
119     {
120         case worldobjreference::WR_CAMERA: break;
121         case worldobjreference::WR_PHYSENT:
122         {
123             if(!ref->nodistance()) src->position(pos);
124             if(volumeadjust) setvolume(forcedvol);
125             break;
126         }
127         case worldobjreference::WR_ENTITY:
128         {
129             entityreference &eref = *(entityreference *)ref;
130             const float vol = eref.ent->attr4<=0.0f ? 1.0f : eref.ent->attr4/255.0f;
131             float dist = camera1->o.dist(pos);
132 
133             if(ref->nodistance())
134             {
135                 // own distance model for entities/mapsounds: linear & clamping
136 
137                 const float innerradius = float(eref.ent->attr3); // full gain area / size property
138                 const float outerradius = float(eref.ent->attr2); // fading gain area / radius property
139 
140                 if(dist <= innerradius) src->gain(1.0f*vol); // inside full gain area
141                 else if(dist <= outerradius) // inside fading gain area
142                 {
143                     const float fadeoutdistance = outerradius-innerradius;
144                     const float fadeout = dist-innerradius;
145                     src->gain((1.0f - fadeout/fadeoutdistance)*vol);
146                 }
147                 else src->gain(0.0f); // outside entity
148             }
149             else
150             {
151                 // use openal distance model to make the sound appear from a certain direction (non-ambient)
152                 src->position(pos);
153                 src->gain(vol);
154             }
155             break;
156         }
157         case worldobjreference::WR_STATICPOS:
158         {
159             if(!ref->nodistance()) src->position(pos);
160             if(volumeadjust) setvolume(forcedvol);
161             break;
162         }
163     }
164 }
165 
update()166 void location::update()
167 {
168     if(stale) return;
169 
170     switch(src->state())
171     {
172         case AL_PLAYING:
173             updatepos();
174             break;
175         case AL_STOPPED:
176         case AL_PAUSED:
177         case AL_INITIAL:
178             stale = true;
179             DEBUG("location is stale");
180             break;
181     }
182 }
183 
play(bool loop)184 void location::play(bool loop)
185 {
186     if(stale) return;
187 
188     updatepos();
189     if(loop) src->looping(loop);
190     if(src->play()) playmillis = totalmillis;
191 }
192 
pitch(float p)193 void location::pitch(float p)
194 {
195     if(stale) return;
196     src->pitch(p);
197 }
198 
setvolume(float v)199 bool location::setvolume(float v)
200 {
201     if(stale) return false;
202     return src->gain(cfg->vol/100.0f*((float)gainscale)/100.0f*v);
203 }
204 
offset(float secs)205 void location::offset(float secs)
206 {
207     ASSERT(!stale);
208     if(stale) return;
209     src->secoffset(secs);
210 }
211 
offset()212 float location::offset()
213 {
214     ASSERT(!stale);
215     if(stale) return 0.0f;
216     return src->secoffset();
217 }
218 
drop()219 void location::drop()
220 {
221     src->stop();
222     stale = true; // drop from collection on next update cycle
223 }
224 
225 
226 // location collection
227 
find(int sound,worldobjreference * ref,const vector<soundconfig> & soundcollection)228 location *locvector::find(int sound, worldobjreference *ref, const vector<soundconfig> &soundcollection /* = gamesounds*/)
229 {
230     if(sound<0 || sound>=soundcollection.length()) return NULL;
231     loopi(ulen) if(buf[i] && !buf[i]->stale)
232     {
233         if(buf[i]->cfg != &soundcollection[sound]) continue; // check if its the same sound
234         if(ref && *buf[i]->ref!=*ref) continue; // optionally check if its the same reference
235         return buf[i]; // found
236     }
237     return NULL;
238 }
239 
delete_(int i)240 void locvector::delete_(int i)
241 {
242     delete remove(i);
243 }
244 
replaceworldobjreference(const worldobjreference & oldr,const worldobjreference & newr)245 void locvector::replaceworldobjreference(const worldobjreference &oldr, const worldobjreference &newr)
246 {
247     loopv(*this)
248     {
249         location *l = buf[i];
250         if(!l || !l->ref) continue;
251         if(*l->ref==oldr) l->attachworldobjreference(newr);
252     }
253 }
254 
255 // update stuff, remove stale data
updatelocations()256 void locvector::updatelocations()
257 {
258     // check if camera carrier changed
259     bool camchanged = false;
260     static physent *lastcamera = NULL;
261     if(lastcamera!=camera1)
262     {
263         if(lastcamera!=NULL) camchanged = true;
264         lastcamera = camera1;
265     }
266 
267     // update all locations
268     loopv(*this)
269     {
270         location *l = buf[i];
271         if(!l) continue;
272 
273         l->update();
274         if(l->stale) delete_(i--);
275         else if(camchanged) l->evaluateworldobjref(); // cam changed, evaluate world reference again
276     }
277 }
278 
279 // force pitch across all locations
forcepitch(float pitch)280 void locvector::forcepitch(float pitch)
281 {
282     loopv(*this)
283     {
284         location *l = buf[i];
285         if(!l) continue;
286         if(l->src && l->src->locked) l->src->pitch(pitch);
287     }
288 }
289 
290 // delete all sounds except world-neutral sounds like GUI/notification
deleteworldobjsounds()291 void locvector::deleteworldobjsounds()
292 {
293     loopv(*this)
294     {
295         location *l = buf[i];
296         if(!l) continue;
297         // world-neutral sounds
298         if(l->cfg == &gamesounds[S_MENUENTER] ||
299             l->cfg == &gamesounds[S_MENUSELECT] ||
300             l->cfg == &gamesounds[S_CALLVOTE] ||
301             l->cfg == &gamesounds[S_VOTEPASS] ||
302             l->cfg == &gamesounds[S_VOTEFAIL]) continue;
303 
304         delete_(i--);
305     }
306 };
307 
308