1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 // interface header
14 #include "sound.h"
15 
16 // system headers
17 #include <vector>
18 #include <map>
19 #include <string.h>
20 
21 // common headers
22 #include "BzfMedia.h"
23 #include "TimeKeeper.h"
24 #include "PlatformFactory.h"
25 #include "BZDBCache.h"
26 #include "TextUtils.h"
27 
28 static const float SpeedOfSound(343.0f);        // meters/sec
29 static const size_t MaxEvents(30);
30 static const float InterAuralDistance(0.1f);        // meters
31 
32 /* NOTE:
33  *  world sounds use the ptrFrac member, local sounds the ptr member.
34  *  world sounds only use the monoaural samples so the ptrFrac member
35  *  is incremented by 1 for each sample.  local sounds are in stereo
36  *  and ptr is incremented by 2 for each (stereo) sample.
37  */
38 
39 /* last position of the receiver */
40 static float        lastX, lastY, lastZ, lastTheta;
41 static float        lastXLeft, lastYLeft;
42 static float        lastXRight, lastYRight;
43 static float        forwardX, forwardY;
44 static float        leftX, leftY;
45 static int      positionDiscontinuity;
46 
47 /* motion info for Doppler shift */
48 static float        velX;
49 static float        velY;
50 
51 /* volume */
52 static float        volumeAtten = 1.0f;
53 static int      mutingOn = 0;
54 
55 /*
56  * producer/consumer shared data types and defines
57  */
58 
59 /* sound queue commands */
60 #define SQC_CLEAR   0       /* no code; no data */
61 #define SQC_SET_POS 1       /* no code; x,y,z,t */
62 #define SQC_SET_VEL 2       /* no code; x,y,z */
63 #define SQC_SET_VOLUME  3       /* code = new volume; no data */
64 #define SQC_LOCAL_SFX   4       /* code=sfx; no data */
65 #define SQC_WORLD_SFX   5       /* code=sfx; x,y,z of sfx source */
66 #define SQC_FIXED_SFX   6       /* code=sfx; x,y,z of sfx source */
67 #define SQC_JUMP_POS    7       /* no code; x,y,z,t */
68 #define SQC_QUIT    8       /* no code; no data */
69 #define SQC_IWORLD_SFX  9       /* code=sfx; x,y,z of sfx source */
70 
71 // This structure is held in the audio queue until it is processed here
72 struct SoundCommand
73 {
74 public:
75 
76     enum CMD {CLEAR,      // no code, no data
77               SET_POS,        // no code; x,y,z,t
78               SET_VEL,        // no code; x,y,z
79               SET_VOL,        // code = new volume; no data
80               LOCAL_SFX,      // code = sfx; no data
81               WORLD_SFX,      // code = sfx; x,y,z of sfx source
82               FIXED_SFX,      // code = sfx; x,y,z of sfx source
83               JUMP_POS,       // no code; x,y,z,y
84               QUIT,       // no code; no data
85               IWORLD_SFX      // code = sfx; x,y,z of sfx source
86              };
87 
88     SoundCommand();
89     explicit SoundCommand(SoundCommand::CMD, int code_=-1,
90                           float x_=0, float y_=0, float z_=0, float t_=0);
91 
92     CMD           cmd;
93     int           code;
94     float         x,y,z,t;
95 };
96 
SoundCommand()97 SoundCommand::SoundCommand()
98     : cmd(CLEAR), code(-1), x(0), y(0), z(0), t(0)
99 {
100 }
101 
SoundCommand(CMD cmd_,int code_,float x_,float y_,float z_,float t_)102 SoundCommand::SoundCommand(CMD cmd_, int code_,
103                            float x_, float y_, float z_, float t_)
104     : cmd(cmd_), code(code_), x(x_), y(y_), z(z_), t(t_)
105 {
106 }
107 
108 class AudioSamples
109 {
110 public:
111     AudioSamples();
112     bool resample(const float* in,    // stereo sampled data
113                   int frames,     // number of stereo (left, right) frames
114                   int rate,       // sample rate
115                   std::string const& name); // sound name
116 
117     size_t        length() const; // size of the (stereo) data
118 
119     long          mlength;    /* total number samples in mono */
120     double        dmlength;   /* mlength as a double minus one */
121     std::vector<float>    data;       /* left in even, right in odd */
122     std::vector<float>    monoRaw;    /* mono with silence before & after */
123     size_t        monoIdx;    // index to start of mono samples
124     double        duration;   /* time to play sound */
125     std::string       file;
126 };
127 
128 
AudioSamples()129 AudioSamples::AudioSamples()
130     : mlength(0), dmlength(0), data(), monoRaw(), monoIdx(0), duration(0)
131 {
132 }
133 
length() const134 size_t AudioSamples::length() const
135 {
136     return data.size();
137 }
138 
resample(const float * in,int frames,int rate,std::string const & name)139 bool AudioSamples::resample(const float* in, int frames, int rate,
140                             std::string const& name)
141 {
142     // safety check... SNH
143     if (in == 0) return false;
144 
145     // attenuation on all sounds
146     static const float GlobalAtten(0.5f);
147     float const outputRate( (float)PlatformFactory::getMedia()->getAudioOutputRate() );
148 
149     if (rate != outputRate)
150     {
151         std::cout << name << " rate is " << rate
152                   << "; audio output is " << outputRate
153                   << "\n";
154         // FIXME -- should resample  -- let it through at wrong sample rate
155         // return false;
156     }
157 
158     // compute safety margin for left/right ear time discrepancy.  since
159     // each ear can hear a sound at slightly different times one ear might
160     // be hearing a sound before the other hears it or one ear may no longer
161     // be hearing a sound that the other is still hearing.  if we didn't
162     // account for this we'd end up sampling outside the mono buffer (and
163     // only the mono buffer because the data buffer is only used for local
164     // sounds, which don't have this effect).  to avoid doing a lot of tests
165     // in inner loops, we'll just put some silent samples before and after
166     // the sound effect.  here we compute how many samples can be needed.
167     const int safetyMargin((int)(1.5f + InterAuralDistance / SpeedOfSound * outputRate));
168 
169     // fill in samples structure
170     file = name;
171     data.resize( 2 * frames );
172     mlength = frames;
173     dmlength = double(mlength - 1);
174     duration = (float)mlength / outputRate;
175     monoRaw.assign( frames + (2 * safetyMargin), 0.0f );  // fill with silence
176     monoIdx = safetyMargin;       // start after the (leading) safety margin
177 
178     // filter samples
179     size_t localIdx( monoIdx );
180     for (long dst(0); dst < (2 * frames); dst += 2)
181     {
182         data[dst] = GlobalAtten * in[dst];
183         data[dst+1] = GlobalAtten * in[dst+1];
184         monoRaw[ localIdx++ ] = (data[dst] + data[dst+1])/2;
185     }
186 
187     return 1;
188 }
189 
190 /*
191  * local functions
192  */
193 
194 static void     sendSound(SoundCommand* s);
195 static void     audioLoop(void*);
196 static bool     allocAudioSamples();
197 static void     freeAudioSamples(void);
198 
199 
200 /*
201  * general purpose audio stuff
202  */
203 
204 static bool     usingAudio(false);
205 static const char*  soundFiles[] =
206 {
207     "fire",
208     "explosion",
209     "ricochet",
210     "flag_grab",
211     "flag_drop",
212     "flag_won",
213     "flag_lost",
214     "flag_alert",
215     "jump",
216     "land",
217     "teleport",
218     "laser",
219     "shock",
220     "pop",
221     "explosion",
222     "flag_grab",
223     "boom",
224     "killteam",
225     "phantom",
226     "missile",
227     "lock",
228     "teamgrab",
229     "hunt",
230     "hunt_select",
231     "steamroller",
232     "thief",
233     "burrow",
234     "message_private",
235     "message_team",
236     "message_admin",
237     "flap",
238     "bounce"
239 };
240 
241 // the number of "Standard" sounds
242 #define STD_SFX_COUNT   ((int)(bzcountof(soundFiles)))
243 
244 /*
245  * producer/consumer shared arena
246  */
247 
248 static std::vector<AudioSamples>    soundSamples;
249 
250 static std::map<std::string, int>   customSamples;
251 
252 static size_t       audioBufferSize(0);
253 static int      soundLevel(0);
254 
255 static BzfMedia*    media(0);
256 
257 /* speed of sound stuff */
258 static float        timeSizeOfWorld(0);     /* in seconds */
259 static TimeKeeper   startTime;
260 static double       prevTime(0), curTime(0);
261 
262 struct SoundEvent
263 {
264     SoundEvent();
265 
266     void reset(AudioSamples* audioSample, float attenuation, float x=0, float y=0, float z=0);
267 
268     bool isWorld() const;
269     bool isFixed() const;
270     bool isIgnoring() const;
271     bool isImportant() const;
272 
273     void setWorld(bool val);
274     void setFixed(bool val);
275     void setIgnoring(bool val);
276     void setImportant(bool val);
277 
278     void recalcDistance();
279 
280     AudioSamples*     samples;        /* event sound effect */
281     bool          busy;           /* true iff in use */
282     size_t        ptr;            /* current sample */
283     double        ptrFracLeft;        /* fractional step ptr */
284     double        ptrFracRight;       /* fractional step ptr */
285     float         x, y, z;        /* event location */
286     double        time;           /* time of event */
287     float         lastLeftAtten;
288     float         lastRightAtten;
289     float         dx, dy, dz;     /* last relative position */
290     float         d;          /* last relative distance */
291     float         dLeft;          /* last relative distance */
292     float         dRight;         /* last relative distance */
293     float         amplitude;      /* last sfx amplitude */
294 
295 private:
296     float calcDistance(float prevX, float prevY, float d3);
297 
298     bool          world;
299     bool          fixed;
300     bool          ignoring;
301     bool          important;
302 };
303 
SoundEvent()304 SoundEvent::SoundEvent()
305     : samples(0), busy(false), ptr(0), ptrFracLeft(0), ptrFracRight(0), x(0), y(0), z(0), time(0),
306       lastLeftAtten(0), lastRightAtten(0), dx(0), dy(0), dz(0), d(0), dLeft(0), dRight(0), amplitude(0),
307       world(false), fixed(false), ignoring(false), important(false)
308 {
309 }
310 
reset(AudioSamples * sample_,float attenuation_,float x_,float y_,float z_)311 void SoundEvent::reset(AudioSamples* sample_, float attenuation_, float x_, float y_, float z_)
312 {
313     samples = sample_;
314     busy = true;
315     ptr = 0;
316     ptrFracLeft = ptrFracRight = 0; // not explicit?
317     x = x_;
318     y = y_;
319     z = z_;
320     time = curTime;
321     lastLeftAtten = lastRightAtten = attenuation_;
322     dx = dy = dz = 0;     // not explicit?
323     d = dLeft = dRight = 0;   // not explicit?
324     amplitude = 0;        // not explicit?
325     world = fixed = ignoring = important = false;
326 }
327 
isWorld() const328 bool SoundEvent::isWorld() const
329 {
330     return world;
331 }
332 
isFixed() const333 bool SoundEvent::isFixed() const
334 {
335     return fixed;
336 }
337 
isIgnoring() const338 bool SoundEvent::isIgnoring() const
339 {
340     return ignoring;
341 }
342 
isImportant() const343 bool SoundEvent::isImportant() const
344 {
345     return important;
346 }
347 
setWorld(bool val)348 void SoundEvent::setWorld(bool val)
349 {
350     world = val;
351 }
352 
setFixed(bool val)353 void SoundEvent::setFixed(bool val)
354 {
355     fixed = val;
356 }
357 
setIgnoring(bool val)358 void SoundEvent::setIgnoring(bool val)
359 {
360     ignoring = val;
361 }
362 
setImportant(bool val)363 void SoundEvent::setImportant(bool val)
364 {
365     important = val;
366 }
367 
recalcDistance()368 void SoundEvent::recalcDistance()
369 {
370     dx = x - lastX;
371     dy = y - lastY;
372     dz = z - lastZ;
373     const float d2 = dx * dx + dy * dy;
374     const float d3 = dz * dz;
375     if (d2 <= 1.0f)
376     {
377         d = 0.0f;
378         dLeft = 0.0f;
379         dRight = 0.0f;
380     }
381     else
382     {
383         const float delta = 1.0f / sqrtf(d2);
384         dx *= delta;
385         dy *= delta;
386         d = sqrtf(d2 + d3);
387         // Since we are on a forked process, we cannot use BZDB_TANKRADIUS
388         // We put in the old tankRadius value (4.32)
389         float minEventDist = 20.0f * 4.32f;
390         amplitude = (d < minEventDist) ? 1.0f : minEventDist / d;
391 
392         // compute distance to each ear
393         dLeft  = calcDistance(lastXLeft,  lastYLeft,  d3);
394         dRight = calcDistance(lastXRight, lastYRight, d3);
395     }
396 }
397 
calcDistance(float prevX,float prevY,float d3)398 float SoundEvent::calcDistance(float prevX, float prevY, float d3)
399 {
400     float deltaX( x - prevX );
401     float deltaY( y - prevY );
402     return sqrtf( deltaX*deltaX + deltaY*deltaY + d3 );
403 }
404 
405 
406 /* list of events currently pending */
407 static SoundEvent   events[MaxEvents];
408 static int      portUseCount;
409 static double       endTime;
410 
411 /* fade in/out table */
412 const size_t        FadeDuration(16); // is this supposed to be global?
413 static std::vector<float> fadeIn(FadeDuration);
414 static std::vector<float> fadeOut(FadeDuration);
415 
416 /* scratch buffer for adding contributions from sources */
417 static std::vector<float> scratch;
418 
419 static bool     usingSameThread = false;
420 
421 static bool     audioInnerLoop();
422 
openSound(const char *)423 void            openSound(const char*)
424 {
425     if (usingAudio) return;           // already opened
426 
427     media = PlatformFactory::getMedia();
428     if (!media->openAudio())
429         return;
430 
431     // open audio data files
432     if (!allocAudioSamples())
433     {
434         media->closeAudio();
435 #ifndef DEBUG
436         std::cout << "WARNING: Unable to open audio data files" << std::endl;
437 #endif
438         return;                 // couldn't get samples
439     }
440 
441     audioBufferSize = media->getAudioBufferChunkSize() * 2;
442 
443     /* initialize */
444     timeSizeOfWorld = 1.414f * BZDBCache::worldSize / SpeedOfSound;
445     portUseCount = 0;
446     for (int i = 0; i < (int)FadeDuration; i += 2)
447     {
448         fadeIn[i] = fadeIn[i+1] =
449                         sinf((float)(M_PI / 2.0 * (double)i / (double)(FadeDuration-2)));
450         fadeOut[i] = fadeOut[i+1] = 1.0f - fadeIn[i];
451     }
452     scratch.resize(audioBufferSize);
453 
454     startTime = TimeKeeper::getCurrent();
455     curTime = 0.0;
456     endTime = -1.0;
457 
458     usingSameThread = !media->hasAudioThread();
459 
460     if (media->hasAudioCallback())
461         media->startAudioCallback(audioInnerLoop);
462     else
463     {
464         // start audio thread
465         if (!usingSameThread && !media->startAudioThread(audioLoop, NULL))
466         {
467             media->closeAudio();
468             freeAudioSamples();
469 #ifndef DEBUG
470             std::cout << "WARNING: Unable to start the audio thread" << std::endl;
471 #endif
472             return;
473         }
474     }
475 
476     setSoundVolume(10);
477 
478     usingAudio = true;
479 }
480 
closeSound(void)481 void            closeSound(void)
482 {
483     if (!usingAudio) return;
484 
485     // send stop command to audio thread
486     SoundCommand s(SoundCommand::QUIT);
487     sendSound(&s);
488 
489     // stop audio thread
490     PlatformFactory::getMedia()->stopAudioThread();
491 
492     // reset audio hardware
493     PlatformFactory::getMedia()->closeAudio();
494 
495     // free memory used for sfx samples
496     freeAudioSamples();
497 
498     usingAudio = false;
499 }
500 
isSoundOpen()501 bool            isSoundOpen()
502 {
503     return usingAudio;
504 }
505 
allocAudioSamples()506 static bool     allocAudioSamples()
507 {
508     bool anyFile = false;
509 
510     soundSamples.reserve(STD_SFX_COUNT + 10); // if the world loads sounds, we don't want to resize
511 
512     // load the default samples
513     for (int i = 0; i < STD_SFX_COUNT; i++)
514     {
515         std::string sound( TextUtils::tolower(soundFiles[i]) );
516         // read it
517         int numFrames, rate;
518         float* samples = PlatformFactory::getMedia()->readSound(sound.c_str(), numFrames, rate);
519         AudioSamples newSample;
520         if (newSample.resample(samples, numFrames, rate, sound))
521             anyFile = true;
522         soundSamples.push_back(newSample);
523         // If a "custom" sound wants to play this, make it available
524         customSamples[sound] = i;
525 
526         delete[] samples;
527     }
528 
529     return anyFile;
530 }
531 
freeAudioSamples(void)532 static void     freeAudioSamples(void)
533 {
534     // do nothing.
535     // the samples are self freeing now
536 }
537 
538 /*
539  * sound fx producer stuff
540  */
541 
sendSound(SoundCommand * s)542 static void     sendSound(SoundCommand* s)
543 {
544     if (!usingAudio) return;
545     PlatformFactory::getMedia()->writeSoundCommand(s, sizeof(SoundCommand));
546 }
547 
moveSoundReceiver(float x,float y,float z,float t,int discontinuity)548 void            moveSoundReceiver(float x, float y, float z, float t,
549                                   int discontinuity)
550 {
551     if (soundLevel <= 0)
552         return;
553     SoundCommand::CMD cmd( discontinuity ? SoundCommand::JUMP_POS
554                            : SoundCommand::SET_POS );
555     SoundCommand s(cmd, 0, x, y, z, t);
556 
557     sendSound(&s);
558 }
559 
speedSoundReceiver(float vx,float vy,float vz)560 void            speedSoundReceiver(float vx, float vy, float vz)
561 {
562     SoundCommand s(SoundCommand::SET_VEL, 0, vx, vy, vz);
563     sendSound(&s);
564 }
565 
playSound(int soundCode,const float pos[3],bool important,bool localSound)566 void            playSound(int soundCode, const float pos[3],
567                           bool important, bool localSound)
568 {
569     if (localSound)
570         playLocalSound(soundCode);
571     else
572         playWorldSound(soundCode, pos, important);
573     return;
574 }
575 
playWorldSound(int soundCode,const float pos[3],bool important)576 void            playWorldSound(int soundCode, const float pos[3],
577                                bool important)
578 {
579     if (soundLevel <= 0)
580         return;
581     if ((int)soundSamples.size() <= soundCode) return;
582     if (soundSamples[soundCode].length() == 0) return;
583     SoundCommand s(important ? SoundCommand::IWORLD_SFX : SoundCommand::WORLD_SFX,
584                    soundCode, pos[0], pos[1], pos[2]);
585     sendSound(&s);
586 }
587 
playLocalSound(int soundCode)588 void            playLocalSound(int soundCode)
589 {
590     // Check for conditions which preclude sounds
591     if (soundLevel <= 0                   // no volume
592             || soundCode >= (int)soundSamples.size()      // unknown sound
593             || soundSamples[soundCode].length() == 0)     // empty sound
594         return;
595     SoundCommand s(SoundCommand::LOCAL_SFX, soundCode);
596     sendSound(&s);
597 }
598 
playLocalSound(std::string sound)599 void            playLocalSound(std::string sound)
600 {
601     sound = TextUtils::tolower(sound); // modify the local copy
602     int  soundCode( -1 );
603 
604     std::map<std::string,int>::iterator itr = customSamples.find(sound);
605     if (itr == customSamples.end())
606     {
607         int numFrames(0), rate(0);
608         float* samples( PlatformFactory::getMedia()->readSound(sound.c_str(), numFrames, rate) );
609         AudioSamples newSample;
610         if (newSample.resample(samples, numFrames, rate, sound))
611         {
612             soundSamples.push_back(newSample);
613             soundCode = (int)soundSamples.size()-1;
614             customSamples[sound] = soundCode;
615         }
616         delete[] samples;
617     }
618     else
619         soundCode = itr->second;
620 
621     if (soundCode > 0)
622         playLocalSound(soundCode);
623 }
624 
playFixedSound(int soundCode,float x,float y,float z)625 void            playFixedSound(int soundCode,
626                                float x, float y, float z)
627 {
628     // Check for conditions which preclude sounds
629     if (soundLevel <= 0                   // no volume
630             || soundCode > (int)soundSamples.size()       // unknown sound
631             || soundSamples[soundCode].length() == 0)     // empty sound
632         return;
633     SoundCommand s(SoundCommand::FIXED_SFX, soundCode, x, y, z);
634     sendSound(&s);
635 }
636 
setSoundVolume(int newLevel)637 void            setSoundVolume(int newLevel)
638 {
639     soundLevel = newLevel;
640     if (soundLevel < 0) soundLevel = 0;
641     else if (soundLevel > 10) soundLevel = 10;
642 
643     SoundCommand s(SoundCommand::SET_VOL, soundLevel);
644     sendSound(&s);
645 }
646 
getSoundVolume()647 int         getSoundVolume()
648 {
649     return soundLevel;
650 }
651 
652 
653 /*
654  * Below this point is stuff for real-time audio thread
655  */
656 
657 
recalcEventIgnoring(SoundEvent * e)658 static int      recalcEventIgnoring(SoundEvent* e)
659 {
660     if (e->isFixed() || !e->isWorld()) return 0;
661 
662     float travelTime = (float)(curTime - e->time);
663     if (travelTime > e->samples->duration + timeSizeOfWorld)
664     {
665         // sound front has passed all points in world
666         e->busy = false;
667         return (e->isIgnoring()) ? 0 : -1;
668     }
669 
670     int useChange = 0;
671     float eventDistance = e->d / SpeedOfSound;
672     if (travelTime < eventDistance)
673     {
674         if (e->isIgnoring())
675         {
676             /* do nothing -- still ignoring */
677         }
678         else
679         {
680             /* ignoring again */
681             e->setIgnoring(true);
682             useChange = -1;
683         }
684         /* don't sleep past the time the sound front will pass by */
685         endTime = eventDistance;
686     }
687     else
688     {
689         if (e->isIgnoring())
690         {
691             float timeFromFront;
692             /* compute time from sound front */
693             timeFromFront = travelTime - e->dLeft / SpeedOfSound;
694             if (!positionDiscontinuity && timeFromFront < 0.0f) timeFromFront = 0.0f;
695 
696             /* recompute sample pointers */
697             e->ptrFracLeft = timeFromFront *
698                              (float)PlatformFactory::getMedia()->getAudioOutputRate();
699             if (e->ptrFracLeft >= 0.0 && e->ptrFracLeft < e->samples->dmlength)
700             {
701                 /* not ignoring anymore */
702                 e->setIgnoring(false);
703                 useChange = 1;
704             }
705 
706             /* now do it again for right ear */
707             timeFromFront = travelTime - e->dRight / SpeedOfSound;
708             if (!positionDiscontinuity && timeFromFront < 0.0f) timeFromFront = 0.0f;
709             e->ptrFracRight = timeFromFront *
710                               (float)PlatformFactory::getMedia()->getAudioOutputRate();
711             if (e->ptrFracRight >= 0.0 && e->ptrFracRight < e->samples->dmlength)
712             {
713                 e->setIgnoring(false);
714                 useChange = 1;
715             }
716         }
717         else
718         {
719             /* do nothing -- still not ignoring */
720         }
721     }
722     return useChange;
723 }
724 
receiverMoved(float x,float y,float z,float t)725 static void     receiverMoved(float x, float y, float z, float t)
726 {
727     // save data
728     lastX = x;
729     lastY = y;
730     lastZ = z;
731     lastTheta = t;
732 
733     // compute forward and left vectors
734     forwardX = cosf(lastTheta);
735     forwardY = sinf(lastTheta);
736     leftX = -forwardY;
737     leftY = forwardX;
738 
739     // compute position of each ear
740     lastXLeft = lastX + 0.5f * InterAuralDistance * leftX;
741     lastYLeft = lastY + 0.5f * InterAuralDistance * leftY;
742     lastXRight = lastX - 0.5f * InterAuralDistance * leftX;
743     lastYRight = lastY - 0.5f * InterAuralDistance * leftY;
744 
745     for (size_t i(0); i < MaxEvents; i++)
746         if (events[i].busy && (events[i].isWorld()))
747             events[i].recalcDistance();
748 }
749 
receiverVelocity(float vx,float vy)750 static void     receiverVelocity(float vx, float vy)
751 {
752     static const float s = 1.0f / SpeedOfSound;
753 
754     velX = s * vx;
755     velY = s * vy;
756 }
757 
addLocalContribution(SoundEvent * e,size_t & len)758 static int      addLocalContribution(SoundEvent* e, size_t& len)
759 {
760     size_t numSamples( e->samples->length() - e->ptr );
761     if (numSamples > audioBufferSize) numSamples = audioBufferSize;
762 
763     if (!mutingOn && numSamples != 0)
764     {
765         /* initialize new areas of scratch space and adjust output sample count */
766         if (numSamples > len)
767         {
768             for (size_t n = len; n < numSamples; n += 2)
769                 scratch[n] = scratch[n+1] = 0.0f;
770             len = numSamples;
771         }
772 
773         float* src( &e->samples->data.at(e->ptr) );
774         try
775         {
776             if (numSamples <= FadeDuration)
777             {
778                 for (size_t n = 0; n < numSamples; n += 2)
779                 {
780                     int fs = int(FadeDuration * float(n) / float(numSamples)) & ~1;
781                     scratch[n] += src[n] * (fadeIn[fs] * volumeAtten +
782                                             fadeOut[fs] * e->lastLeftAtten);
783                     scratch[n+1] += src[n+1] * (fadeIn[fs] * volumeAtten +
784                                                 fadeOut[fs] * e->lastRightAtten);
785                 }
786             }
787             else
788             {
789                 for (size_t n(0); n < numSamples; n += 2)
790                 {
791                     if (n < FadeDuration)
792                     {
793                         scratch[n] += src[n] * (fadeIn[n] * volumeAtten +
794                                                 fadeOut[n] * e->lastLeftAtten);
795                         scratch[n+1] += src[n+1] * (fadeIn[n] * volumeAtten +
796                                                     fadeOut[n] * e->lastRightAtten);
797                     }
798                     else
799                     {
800                         scratch[n] += src[n] * volumeAtten;
801                         scratch[n+1] += src[n+1] * volumeAtten;
802                     }
803                 }
804             }
805 
806             e->lastLeftAtten = e->lastRightAtten = volumeAtten;
807         }
808         catch (std::exception const& ex)
809         {
810             std::cout << "Exception on sound " << e->samples->file << "\n" << ex.what() << std::endl;
811         }
812     }
813 
814     /* free event if ran out of samples */
815     if ((e->ptr += numSamples) == e->samples->length())
816     {
817         e->busy = false;
818         return -1;
819     }
820 
821     return 0;
822 }
823 
getWorldStuff(SoundEvent * e,float * la,float * ra,double * sampleStep)824 static void     getWorldStuff(SoundEvent *e, float* la, float* ra,
825                               double* sampleStep)
826 {
827     float leftAtten, rightAtten;
828 
829     // compute left and right attenuation factors
830     // FIXME -- should be a more general HRTF
831     if (e->d == 0.0f)
832     {
833         leftAtten = 1.0f;
834         rightAtten = 1.0f;
835     }
836     else
837     {
838         const float ff = (2.0f + forwardX * e->dx + forwardY * e->dy) / 3.0f;
839         const float fl = (2.0f + leftX * e->dx + leftY * e->dy) / 3.0f;
840         leftAtten = ff * fl * e->amplitude;
841         rightAtten = ff * (4.0f/3.0f - fl) * e->amplitude;
842     }
843     if (e->ptrFracLeft == 0.0f || e->ptrFracRight == 0.0f)
844     {
845         e->lastLeftAtten = leftAtten;
846         e->lastRightAtten = rightAtten;
847     }
848     *la = mutingOn ? 0.0f : leftAtten * volumeAtten;
849     *ra = mutingOn ? 0.0f : rightAtten * volumeAtten;
850 
851     /* compute doppler effect */
852     // FIXME -- should be per ear
853     *sampleStep = double(1.0 + velX * e->dx + velY * e->dy);
854 }
855 
addWorldContribution(SoundEvent * e,size_t & len)856 static int      addWorldContribution(SoundEvent* e, size_t& len)
857 {
858     bool      fini(false);
859     size_t    n;
860     long      nmL, nmR;
861     float*    src( &e->samples->monoRaw[ e->samples->monoIdx ] );
862     float     leftAtten, rightAtten, fracL, fracR, fsampleL, fsampleR;
863     double    sampleStep;
864 
865     if (e->isIgnoring()) return 0;
866 
867     getWorldStuff(e, &leftAtten, &rightAtten, &sampleStep);
868     if (sampleStep <= 0.0) fini = true;
869 
870     /* initialize new areas of scratch space and adjust output sample count */
871     if (audioBufferSize > len)
872     {
873         for (n = len; n < audioBufferSize; n += 2)
874             scratch[n] = scratch[n+1] = 0.0f;
875         len = audioBufferSize;
876     }
877 
878     try
879     {
880         // add contribution with crossfade
881         for (n = 0; !fini && n < audioBufferSize; n += 2)
882         {
883             // get sample position (to subsample resolution)
884             nmL = (long)e->ptrFracLeft;
885             nmR = (long)e->ptrFracRight;
886             fracL = (float)(e->ptrFracLeft - floor(e->ptrFracLeft));
887             fracR = (float)(e->ptrFracRight - floor(e->ptrFracRight));
888 
889             // get sample (lerp closest two samples)
890             fsampleL = (1.0f - fracL) * src[nmL] + fracL * src[nmL+1];
891             fsampleR = (1.0f - fracR) * src[nmR] + fracR * src[nmR+1];
892 
893             // filter and accumulate
894             if (n < FadeDuration)
895             {
896                 scratch[n] += fsampleL * (fadeIn[n] * leftAtten +
897                                           fadeOut[n] * e->lastLeftAtten);
898                 scratch[n+1] += fsampleR * (fadeIn[n] * rightAtten +
899                                             fadeOut[n] * e->lastRightAtten);
900             }
901             else
902             {
903                 scratch[n] += fsampleL * leftAtten;
904                 scratch[n+1] += fsampleR * rightAtten;
905             }
906 
907             // next sample
908             if ((e->ptrFracLeft += sampleStep) >= e->samples->dmlength)
909                 fini = true;
910             if ((e->ptrFracRight += sampleStep) >= e->samples->dmlength)
911                 fini = true;
912         }
913 
914         e->lastLeftAtten = leftAtten;
915         e->lastRightAtten = rightAtten;
916     }
917     catch (std::exception const& ex)
918     {
919         std::cout << "Exception on sound " << e->samples->file << "\n" << ex.what() << std::endl;
920     }
921 
922     /* NOTE: running out of samples just means the world sound front
923      *    has passed our location.  if we teleport it may pass us again.
924      *    so we can't free the event until the front passes out of the
925      *    world.  compute time remaining until that happens and set
926      *    endTime if smaller than current endTime. */
927     if (fini)
928     {
929         double et = e->samples->duration + timeSizeOfWorld - (prevTime - e->time);
930         if (endTime == -1.0 || et < endTime) endTime = et;
931         e->setIgnoring(true);
932         return -1;
933     }
934     return 0;
935 }
936 
addFixedContribution(SoundEvent * e,size_t & len)937 static int      addFixedContribution(SoundEvent* e, size_t& len)
938 {
939     size_t    n;
940     long      nmL, nmR;
941     float*    src( &e->samples->monoRaw[ e->samples->monoIdx ] );
942     float     leftAtten, rightAtten, fracL, fracR, fsampleL, fsampleR;
943     double    sampleStep;
944 
945     getWorldStuff(e, &leftAtten, &rightAtten, &sampleStep);
946 
947     /* initialize new areas of scratch space and adjust output sample count */
948     if (audioBufferSize > len)
949     {
950         for (n = len; n < audioBufferSize; n += 2)
951             scratch[n] = scratch[n+1] = 0.0f;
952         len = audioBufferSize;
953     }
954 
955     // add contribution with crossfade
956     for (n = 0; n < audioBufferSize; n += 2)
957     {
958         // get sample position (to subsample resolution)
959         nmL = (long)e->ptrFracLeft;
960         nmR = (long)e->ptrFracRight;
961         fracL = (float)(e->ptrFracLeft - floor(e->ptrFracLeft));
962         fracR = (float)(e->ptrFracRight - floor(e->ptrFracRight));
963 
964         // get sample (lerp closest two samples)
965         fsampleL = (1.0f - fracL) * src[nmL] + fracL * src[nmL+1];
966         fsampleR = (1.0f - fracR) * src[nmR] + fracR * src[nmR+1];
967 
968         // filter and accumulate
969         if (n < FadeDuration)
970         {
971             scratch[n] += fsampleL * (fadeIn[n] * leftAtten +
972                                       fadeOut[n] * e->lastLeftAtten);
973             scratch[n+1] += fsampleR * (fadeIn[n] * rightAtten +
974                                         fadeOut[n] * e->lastRightAtten);
975         }
976         else
977         {
978             scratch[n] += fsampleL * leftAtten;
979             scratch[n+1] += fsampleR * rightAtten;
980         }
981 
982         // next sample
983         if ((e->ptrFracLeft += sampleStep) >= e->samples->dmlength)
984             e->ptrFracLeft -= e->samples->dmlength;
985         if ((e->ptrFracRight += sampleStep) >= e->samples->dmlength)
986             e->ptrFracRight -= e->samples->dmlength;
987     }
988 
989     e->lastLeftAtten = leftAtten;
990     e->lastRightAtten = rightAtten;
991 
992     return 0;
993 }
994 
findBestWorldSlot()995 static size_t       findBestWorldSlot()
996 {
997     size_t i;
998 
999     // the best slot is an empty one
1000     for (i = 0; i < MaxEvents; i++)
1001         if (!events[i].busy)
1002             return i;
1003 
1004     // no available slots.  find an existing sound that won't be missed
1005     // (much).  this will cause a pop or crackle if the replaced sound is
1006     // currently playing.  first see if there are any world events.
1007     for (i = 0; i < MaxEvents; i++)
1008         if (events[i].isWorld() && !events[i].isFixed())
1009             break;
1010 
1011     // give up if no (non-fixed) world events
1012     if (i == MaxEvents) return MaxEvents;
1013 
1014     // found a world event.  see if there's an event that's
1015     // completely passed us.
1016     const size_t first(i);
1017     for (i = first; i < MaxEvents; i++)
1018     {
1019         if (events[i].isFixed() || !events[i].isWorld()) continue;
1020         if (!(events[i].isIgnoring())) continue;
1021         const float travelTime = (float)(curTime - events[i].time);
1022         const float eventDistance = events[i].d / SpeedOfSound;
1023         if (travelTime > eventDistance) return i;
1024     }
1025 
1026     // if no sound front has completely passed our position
1027     // then pick the most distant one that hasn't reached us
1028     // yet that isn't important.
1029     int farthestEvent = -1;
1030     float farthestDistance = 0.0f;
1031     for (i = first; i < MaxEvents; i++)
1032     {
1033         if (events[i].isImportant()) continue;
1034         if (events[i].isFixed() || !events[i].isWorld()) continue;
1035         if (!(events[i].isIgnoring())) continue;
1036         const float eventDistance = events[i].d / SpeedOfSound;
1037         if (eventDistance > farthestDistance)
1038         {
1039             farthestEvent = i;
1040             farthestDistance = eventDistance;
1041         }
1042     }
1043     if (farthestEvent != -1) return farthestEvent;
1044 
1045     // same thing but look at important sounds
1046     for (i = first; i < MaxEvents; i++)
1047     {
1048         if (!(events[i].isImportant())) continue;
1049         if (events[i].isFixed() || !events[i].isWorld()) continue;
1050         if (!(events[i].isIgnoring())) continue;
1051         const float eventDistance = events[i].d / SpeedOfSound;
1052         if (eventDistance > farthestDistance)
1053         {
1054             farthestEvent = i;
1055             farthestDistance = eventDistance;
1056         }
1057     }
1058     if (farthestEvent != -1) return farthestEvent;
1059 
1060     // we've only got playing world sounds to choose from.  pick the
1061     // most distant one since it's probably the quietest.
1062     farthestEvent = first;
1063     farthestDistance = events[farthestEvent].d / SpeedOfSound;
1064     for (i = first + 1; i < MaxEvents; i++)
1065     {
1066         if (events[i].isFixed() || !events[i].isWorld()) continue;
1067         const float eventDistance = events[i].d / SpeedOfSound;
1068         if (eventDistance > farthestDistance)
1069         {
1070             farthestEvent = i;
1071             farthestDistance = eventDistance;
1072         }
1073     }
1074 
1075     // replacing an active sound
1076     portUseCount--;
1077     return farthestEvent;
1078 }
1079 
findBestLocalSlot()1080 static size_t       findBestLocalSlot()
1081 {
1082     // better to lose a world sound
1083     size_t slot( findBestWorldSlot() );
1084     if (slot != MaxEvents) return slot;
1085 
1086     // find the first local event
1087     size_t i;
1088     for (i = 0; i < MaxEvents; i++)
1089         if (!(events[i].isFixed()))
1090             break;
1091 
1092     // no available slot if only fixed sounds are playing (highly unlikely)
1093     if (i == MaxEvents) return MaxEvents;
1094 
1095     // find the local sound closest to completion.
1096     size_t minEvent = i;
1097     size_t minSamplesLeft = events[i].samples->length() - events[i].ptr;
1098     for (i++; i < MaxEvents; i++)
1099     {
1100         if (events[i].isFixed()) continue;
1101         if (events[i].samples->length() - events[i].ptr < minSamplesLeft)
1102         {
1103             minEvent = i;
1104             minSamplesLeft = events[i].samples->length() - events[i].ptr;
1105         }
1106     }
1107 
1108     // replacing an active sound
1109     portUseCount--;
1110     return minEvent;
1111 }
1112 
1113 //
1114 // audioLoop() simply generates samples and keeps the audio hw fed
1115 //
audioInnerLoop()1116 static bool     audioInnerLoop()
1117 {
1118     size_t slot(MaxEvents);
1119 
1120     /* get time step */
1121     prevTime = curTime;
1122     curTime = TimeKeeper::getCurrent() - startTime;
1123     endTime = -1.0;
1124     positionDiscontinuity = 0;
1125 
1126     /* get new commands from queue */
1127     SoundCommand cmd;
1128     SoundEvent* event(0);
1129     while (media->readSoundCommand(&cmd, sizeof(SoundCommand)))
1130     {
1131         switch (cmd.cmd)
1132         {
1133         case SoundCommand::QUIT:
1134             return true;
1135 
1136         case SoundCommand::CLEAR:
1137             /* FIXME */
1138             break;
1139 
1140         case SoundCommand::SET_POS:
1141         case SoundCommand::JUMP_POS:
1142         {
1143             positionDiscontinuity = (cmd.cmd == SoundCommand::JUMP_POS);
1144             receiverMoved(cmd.x, cmd.y, cmd.z, cmd.t);
1145             break;
1146         }
1147 
1148         case SoundCommand::SET_VEL:
1149             receiverVelocity(cmd.x, cmd.y);
1150             break;
1151 
1152         case SoundCommand::SET_VOL:
1153             // The provided volume value is multiplied by itself to compensate for
1154             // human hearing
1155             volumeAtten = 0.02f * cmd.code * cmd.code;
1156             if (volumeAtten <= 0.0f)
1157             {
1158                 mutingOn = true;
1159                 volumeAtten = 0.0f;
1160             }
1161             else if (volumeAtten >= 2.0f)
1162             {
1163                 mutingOn = false;
1164                 volumeAtten = 2.0f;
1165             }
1166             else
1167                 mutingOn = false;
1168             break;
1169 
1170         case SoundCommand::LOCAL_SFX:
1171             slot = findBestLocalSlot();
1172             if (slot == MaxEvents) break;
1173             event = events + slot;
1174 
1175             event->reset(&soundSamples[cmd.code], volumeAtten);
1176             portUseCount++;
1177             break;
1178 
1179         case SoundCommand::IWORLD_SFX:
1180         case SoundCommand::WORLD_SFX:
1181             if (cmd.cmd == SoundCommand::IWORLD_SFX)
1182                 slot = findBestWorldSlot();
1183             else
1184             {
1185                 for (slot = 0; slot < MaxEvents; slot++)
1186                     if (!events[slot].busy)
1187                         break;
1188             }
1189             if (slot == MaxEvents) break;
1190 
1191             event = events + slot;
1192             event->reset(&soundSamples[cmd.code], volumeAtten, cmd.x, cmd.y, cmd.z);
1193             event->setWorld(true);
1194             event->setIgnoring(true);
1195             if (cmd.cmd == SoundCommand::IWORLD_SFX) event->setImportant(true);
1196 
1197             /* don't increment use count because we're ignoring the sound */
1198             event->recalcDistance();
1199             break;
1200 
1201         case SoundCommand::FIXED_SFX:
1202             for (slot = 0; slot < MaxEvents; slot++)
1203                 if (!events[slot].busy)
1204                     break;
1205             if (slot == MaxEvents) break;
1206 
1207             event = events + slot;
1208             event->reset(&soundSamples[cmd.code], volumeAtten, cmd.x, cmd.y, cmd.z);
1209             event->setFixed(true);
1210             event->setWorld(true);
1211 
1212             portUseCount++;
1213             event->recalcDistance();
1214             break;
1215         }
1216     }
1217     for (slot = 0; slot < MaxEvents; slot++)
1218         if (events[slot].busy)
1219         {
1220             int deltaCount = recalcEventIgnoring(events + slot);
1221             portUseCount += deltaCount;
1222         }
1223 
1224     /* sum contributions to the port and output samples */
1225     if (media->isAudioTooEmpty())
1226     {
1227         size_t numSamples(0);
1228         if (portUseCount != 0)
1229         {
1230             for (size_t j = 0; j < MaxEvents; j++)
1231             {
1232                 if (!events[j].busy) continue;
1233 
1234                 int deltaCount;
1235                 if (events[j].isWorld())
1236                 {
1237                     if (events[j].isFixed())
1238                         deltaCount = addFixedContribution(events + j, numSamples);
1239                     else
1240                         deltaCount = addWorldContribution(events + j, numSamples);
1241                 }
1242                 else
1243                     deltaCount = addLocalContribution(events + j, numSamples);
1244                 portUseCount += deltaCount;
1245             }
1246         }
1247 
1248         // replace all samples with silence if muting is on
1249         if (mutingOn)
1250             numSamples = 0;
1251 
1252         // fill out partial buffers with silence
1253         for (size_t j = numSamples; j < audioBufferSize; j++)
1254             scratch[j] = 0.0f;
1255 
1256         // write samples
1257         media->writeAudioFrames(&scratch.front(), audioBufferSize/2);
1258     }
1259 
1260     return false;
1261 }
1262 
audioLoop(void *)1263 static void       audioLoop(void*)
1264 {
1265     // loop until requested to stop
1266     while (true)
1267     {
1268         // sleep until audio buffers hit low water mark or new command available
1269         media->audioSleep(true, endTime);
1270 
1271         if (audioInnerLoop())
1272             break;
1273     }
1274 }
1275 
updateSound()1276 void          updateSound()
1277 {
1278     if (isSoundOpen() && usingSameThread)
1279     {
1280         // sleep until audio buffers hit low water mark or new command available
1281         media->audioSleep(true, 0.0);
1282         audioInnerLoop();
1283     }
1284 }
1285 
1286 // Local Variables: ***
1287 // mode: C++ ***
1288 // tab-width: 4 ***
1289 // c-basic-offset: 4 ***
1290 // indent-tabs-mode: nil ***
1291 // End: ***
1292 // ex: shiftwidth=4 tabstop=4
1293