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