Line data Source code
1 : // sound.cpp: basic positional sound using sdl_mixer
2 :
3 : #include "../libprimis-headers/cube.h"
4 : #include "../libprimis-headers/sound.h"
5 : #include "SDL_mixer.h"
6 :
7 : #include "console.h"
8 : #include "control.h"
9 : #include "cs.h"
10 : #include "input.h"
11 : #include "menus.h"
12 :
13 : #include "render/rendergl.h" //needed to get camera position
14 :
15 : #include "world/entities.h"
16 : #include "world/world.h"
17 :
18 0 : SoundEngine::SoundEngine() : gamesounds("game/", *this), mapsounds("mapsound/", *this)
19 : {
20 0 : }
21 :
22 0 : SoundEngine::SoundSample::SoundSample(SoundEngine& p) : parent(&p), name(""), chunk(nullptr)
23 : {
24 0 : }
25 :
26 0 : SoundEngine::SoundSample::~SoundSample()
27 : {
28 0 : }
29 :
30 0 : void SoundEngine::SoundSample::cleanup()
31 : {
32 0 : if(chunk)
33 : {
34 0 : Mix_FreeChunk(chunk);
35 0 : chunk = nullptr;
36 : }
37 0 : }
38 :
39 0 : bool SoundEngine::SoundConfig::hasslot(const soundslot *p, const std::vector<soundslot> &v) const
40 : {
41 0 : return p >= v.data() + slots && p < v.data() + slots+numslots && slots+numslots < static_cast<long>(v.size());
42 : }
43 :
44 0 : int SoundEngine::SoundConfig::chooseslot(int flags) const
45 : {
46 0 : if(flags&Music_NoAlt || numslots <= 1)
47 : {
48 0 : return slots;
49 : }
50 0 : if(flags&Music_UseAlt)
51 : {
52 0 : return slots + 1 + randomint(numslots - 1);
53 : }
54 0 : return slots + randomint(numslots);
55 : }
56 :
57 0 : SoundEngine::SoundChannel::SoundChannel(int id, SoundEngine& p) : parent(&p), id(id)
58 : {
59 0 : reset();
60 0 : }
61 :
62 0 : bool SoundEngine::SoundChannel::hasloc() const
63 : {
64 0 : return loc.x >= -1e15f;
65 : }
66 :
67 0 : void SoundEngine::SoundChannel::clearloc()
68 : {
69 0 : loc = vec(-1e16f, -1e16f, -1e16f);
70 0 : }
71 :
72 0 : void SoundEngine::SoundChannel::reset()
73 : {
74 0 : inuse = false;
75 0 : clearloc();
76 0 : slot = nullptr;
77 0 : ent = nullptr;
78 0 : radius = 0;
79 0 : volume = -1;
80 0 : pan = -1;
81 0 : flags = 0;
82 0 : dirty = false;
83 0 : }
84 :
85 0 : void SoundEngine::SoundChannel::setloc(const vec& newloc)
86 : {
87 0 : loc = newloc;
88 0 : }
89 :
90 0 : void SoundEngine::SoundChannel::setupchannel(int newn, soundslot *newslot, const vec *newloc, extentity *newent, int newflags, int newradius)
91 : {
92 0 : reset();
93 0 : inuse = true;
94 0 : if(newloc)
95 : {
96 0 : loc = *newloc;
97 : }
98 0 : slot = newslot;
99 0 : ent = newent;
100 0 : flags = 0;
101 0 : radius = newradius;
102 0 : }
103 :
104 : //creates a new SoundChannel object with passed properties
105 0 : SoundEngine::SoundChannel& SoundEngine::newchannel(int n, soundslot *slot, const vec *loc, extentity *ent, int flags, int radius)
106 : {
107 0 : if(ent)
108 : {
109 0 : loc = &ent->o;
110 0 : ent->flags |= EntFlag_Sound;
111 : }
112 0 : while(!(static_cast<long>(channels.size()) > n))
113 : {
114 0 : channels.emplace_back(channels.size(), *this);
115 : }
116 0 : SoundChannel &chan = channels[n];
117 0 : chan.setupchannel(n, slot, loc, ent, flags, radius);
118 0 : return chan;
119 : }
120 :
121 : //sets a channel as not being in use
122 0 : void SoundEngine::freechannel(int n)
123 : {
124 0 : if(!(static_cast<long>(channels.size()) > n) || !channels[n].inuse)
125 : {
126 0 : return;
127 : }
128 0 : SoundChannel &chan = channels[n];
129 0 : chan.inuse = false;
130 0 : if(chan.ent)
131 : {
132 0 : chan.ent->flags &= ~EntFlag_Sound;
133 : }
134 : }
135 :
136 0 : void SoundEngine::SoundChannel::syncchannel()
137 : {
138 0 : if(!dirty)
139 : {
140 0 : return;
141 : }
142 0 : if(!Mix_FadingChannel(id))
143 : {
144 0 : Mix_Volume(id, volume);
145 : }
146 0 : Mix_SetPanning(id, 255-pan, pan);
147 0 : dirty = false;
148 : }
149 :
150 0 : void SoundEngine::stopchannels()
151 : {
152 0 : for(uint i = 0; i < channels.size(); i++)
153 : {
154 0 : SoundChannel &chan = channels[i];
155 0 : if(!chan.inuse) //don't clear channels that are already flagged as unused
156 : {
157 0 : continue;
158 : }
159 0 : Mix_HaltChannel(i);
160 0 : freechannel(i);
161 : }
162 0 : }
163 :
164 0 : void SoundEngine::setsoundvol(const int * const vol)
165 : {
166 0 : soundvol = std::clamp(*vol, 0, 255);
167 0 : if(!vol)
168 : {
169 0 : stopchannels();
170 0 : setmusicvol(0);
171 : }
172 0 : }
173 :
174 0 : int SoundEngine::getsoundvol()
175 : {
176 0 : return soundvol;
177 : }
178 :
179 0 : void SoundEngine::setmusicvol(const int * const vol)
180 : {
181 0 : soundvol = std::clamp(*vol, 0, 255);
182 0 : setmusicvol(soundvol ? musicvol : 0);
183 0 : }
184 :
185 0 : int SoundEngine::getmusicvol()
186 : {
187 0 : return musicvol;
188 : }
189 :
190 0 : void SoundEngine::setmusicvol(int musicvol)
191 : {
192 0 : if(nosound) //don't modulate music that isn't there
193 : {
194 0 : return;
195 : }
196 0 : if(music)
197 : {
198 0 : Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255);
199 : }
200 : }
201 :
202 0 : void SoundEngine::stopmusic()
203 : {
204 0 : if(nosound) //don't stop music that isn't there
205 : {
206 0 : return;
207 : }
208 0 : delete[] musicfile;
209 0 : delete[] musicdonecmd;
210 0 : musicfile = musicdonecmd = nullptr;
211 :
212 0 : if(music)
213 : {
214 0 : Mix_HaltMusic();
215 0 : Mix_FreeMusic(music);
216 0 : music = nullptr;
217 : }
218 0 : if(musicrw)
219 : {
220 0 : SDL_FreeRW(musicrw);
221 0 : musicrw = nullptr;
222 : }
223 0 : if(musicstream)
224 : {
225 0 : delete musicstream;
226 0 : musicstream = nullptr;
227 : }
228 : }
229 :
230 : //SVARF(audiodriver, AUDIODRIVER, { shouldinitaudio = true; initwarning("sound configuration", Init_Reset, Change_Sound); });
231 0 : void SoundEngine::setaudiodriver(char * f)
232 : {
233 0 : audiodriver = std::string(f);
234 0 : shouldinitaudio = true;
235 0 : initwarning("sound configuration", Init_Reset, Change_Sound);
236 0 : }
237 :
238 0 : void SoundEngine::setsound(const int * const on)
239 : {
240 0 : sound = *on;
241 0 : shouldinitaudio = true;
242 0 : initwarning("sound configuration", Init_Reset, Change_Sound);
243 0 : }
244 0 : int SoundEngine::getsound()
245 : {
246 0 : if(sound)
247 : {
248 0 : return 1;
249 : }
250 0 : return 0;
251 : }
252 :
253 : //VARF(soundchans, 1, 32, 128, initwarning("sound configuration", Init_Reset, Change_Sound));
254 0 : void SoundEngine::setsoundchans(const int * const val)
255 : {
256 0 : soundchans = std::clamp(*val, 1, 128);
257 0 : initwarning("sound configuration", Init_Reset, Change_Sound);
258 0 : }
259 0 : int SoundEngine::getsoundchans()
260 : {
261 0 : return soundchans;
262 : }
263 :
264 0 : bool SoundEngine::initaudio()
265 : {
266 0 : static std::string fallback = "";
267 : static bool initfallback = true;
268 0 : if(initfallback)
269 : {
270 0 : initfallback = false;
271 0 : if(const char *env = SDL_getenv("SDL_AUDIODRIVER"))
272 : {
273 0 : fallback = std::string(env);
274 : }
275 : }
276 0 : if(!fallback[0] && audiodriver[0])
277 : {
278 0 : std::vector<std::string> drivers;
279 0 : explodelist(audiodriver.c_str(), drivers);
280 0 : for(uint i = 0; i < drivers.size(); i++)
281 : {
282 0 : SDL_setenv("SDL_AUDIODRIVER", drivers[i].c_str(), 1);
283 0 : if(SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0)
284 : {
285 0 : return true;
286 : }
287 : }
288 0 : }
289 0 : SDL_setenv("SDL_AUDIODRIVER", fallback.c_str(), 1);
290 0 : if(SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0)
291 : {
292 0 : return true;
293 : }
294 0 : conoutf(Console_Error, "sound init failed: %s", SDL_GetError());
295 0 : return false;
296 : }
297 :
298 : //used in iengine
299 0 : void SoundEngine::initsound()
300 : {
301 : //get sdl version info
302 : SDL_version version;
303 0 : SDL_GetVersion(&version);
304 : //SDL 2.0.6 error check: 2.0.6 audio doesn't work
305 0 : if(version.major == 2 && version.minor == 0 && version.patch == 6)
306 : {
307 0 : nosound = true;
308 0 : if(sound)
309 : {
310 0 : conoutf(Console_Error, "audio is broken in SDL 2.0.6");
311 : }
312 0 : return;
313 : }
314 :
315 0 : if(shouldinitaudio)
316 : {
317 0 : shouldinitaudio = false; //don't init more than once
318 0 : if(SDL_WasInit(SDL_INIT_AUDIO))
319 : {
320 0 : SDL_QuitSubSystem(SDL_INIT_AUDIO);
321 : }
322 0 : if(!sound || !initaudio())
323 : {
324 0 : nosound = true;
325 0 : return;
326 : }
327 : }
328 :
329 0 : if(Mix_OpenAudio(soundfreq, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0)
330 : {
331 0 : nosound = true;
332 0 : conoutf(Console_Error, "sound init failed (SDL_mixer): %s", Mix_GetError());
333 0 : return;
334 : }
335 0 : Mix_AllocateChannels(soundchans);
336 0 : maxchannels = soundchans;
337 0 : nosound = false;
338 : }
339 :
340 : //clears and deletes cached music for playback
341 0 : void SoundEngine::musicdone()
342 : {
343 0 : if(music)
344 : {
345 0 : Mix_HaltMusic();
346 0 : Mix_FreeMusic(music);
347 0 : music = nullptr;
348 : }
349 0 : if(musicrw)
350 : {
351 0 : SDL_FreeRW(musicrw);
352 0 : musicrw = nullptr;
353 : }
354 0 : if(musicstream)
355 : {
356 0 : delete musicstream;
357 0 : musicstream = nullptr;
358 : }
359 0 : delete[] musicfile;
360 0 : musicfile = nullptr;
361 0 : if(!musicdonecmd)
362 : {
363 0 : return;
364 : }
365 0 : char *cmd = musicdonecmd;
366 0 : musicdonecmd = nullptr;
367 0 : execute(cmd);
368 0 : delete[] cmd;
369 0 : cmd = nullptr;
370 : }
371 :
372 : //uses Mix_Music object from libSDL
373 0 : Mix_Music *SoundEngine::loadmusic(const char *name)
374 : {
375 0 : if(!musicstream)
376 : {
377 0 : musicstream = openzipfile(name, "rb");
378 : }
379 0 : if(musicstream)
380 : {
381 0 : if(!musicrw)
382 : {
383 0 : musicrw = musicstream->rwops();
384 : }
385 0 : if(!musicrw)
386 : {
387 0 : if(musicstream)
388 : {
389 0 : delete musicstream;
390 0 : musicstream = nullptr;
391 : }
392 : }
393 : }
394 0 : if(musicrw)
395 : {
396 0 : music = Mix_LoadMUSType_RW(musicrw, MUS_NONE, 0);
397 : }
398 : else
399 : {
400 0 : music = Mix_LoadMUS(findfile(name, "rb"));
401 : }
402 0 : if(!music)
403 : {
404 0 : if(musicrw)
405 : {
406 0 : SDL_FreeRW(musicrw);
407 0 : musicrw = nullptr;
408 : }
409 0 : if(musicstream)
410 : {
411 0 : delete musicstream;
412 0 : musicstream = nullptr;
413 : }
414 : }
415 0 : return music;
416 : }
417 :
418 : //cmd
419 0 : void SoundEngine::startmusic(char *name, char *cmd)
420 : {
421 0 : if(nosound)
422 : {
423 0 : return;
424 : }
425 0 : stopmusic();
426 0 : if(soundvol && musicvol && *name) //if volume > 0 and music name passed
427 : {
428 0 : std::string file = "media/";
429 0 : file.append(name);
430 0 : path(file);
431 0 : if(loadmusic(file.c_str()))
432 : {
433 0 : delete[] musicfile;
434 0 : delete[] musicdonecmd;
435 :
436 0 : musicfile = newstring(file.c_str());
437 0 : if(cmd[0])
438 : {
439 0 : musicdonecmd = newstring(cmd);
440 : }
441 : else
442 : {
443 0 : musicdonecmd = nullptr;
444 : }
445 0 : Mix_PlayMusic(music, cmd[0] ? 0 : -1);
446 0 : Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255);
447 0 : intret(1);
448 : }
449 : else //note that there is no error message for soundvol/musicvol/name null
450 : {
451 0 : conoutf(Console_Error, "could not play music: %s", file.c_str());
452 0 : intret(0);
453 : }
454 0 : }
455 : }
456 :
457 0 : Mix_Chunk *SoundEngine::loadwav(const char *name)
458 : {
459 0 : Mix_Chunk *c = nullptr;
460 0 : stream *z = openzipfile(name, "rb");
461 0 : if(z)
462 : {
463 0 : SDL_RWops *rw = z->rwops();
464 0 : if(rw)
465 : {
466 0 : c = Mix_LoadWAV_RW(rw, 0);
467 0 : SDL_FreeRW(rw);
468 : }
469 0 : delete z;
470 : }
471 0 : if(!c)
472 : {
473 0 : c = Mix_LoadWAV(findfile(name, "rb"));
474 : }
475 0 : return c;
476 : }
477 :
478 0 : bool SoundEngine::SoundSample::load(const char *dir)
479 : {
480 0 : if(chunk)
481 : {
482 0 : return true;
483 : }
484 0 : if(!name.size())
485 : {
486 0 : return false;
487 : }
488 : static const char * const exts[] = { "", ".ogg" };
489 : string filename;
490 0 : for(int i = 0; i < static_cast<int>(sizeof(exts)/sizeof(exts[0])); ++i)
491 : {
492 0 : formatstring(filename, "media/sound/%s%s%s", dir, name.c_str(), exts[i]);
493 0 : path(filename);
494 0 : chunk = parent->loadwav(filename);
495 0 : if(chunk)
496 : {
497 0 : return true;
498 : }
499 : }
500 0 : conoutf(Console_Error, "failed to load sample: media/sound/%s%s", dir, name.c_str());
501 0 : return false;
502 : }
503 :
504 : //SoundType
505 0 : SoundEngine::SoundType::SoundType(const char *dir, SoundEngine& p) : parent(&p), dir(dir) {}
506 :
507 0 : int SoundEngine::SoundType::findsound(const char *name, int vol)
508 : {
509 0 : for(uint i = 0; i < configs.size(); i++)
510 : {
511 0 : SoundConfig &s = configs[i];
512 0 : for(int j = 0; j < s.numslots; ++j)
513 : {
514 0 : soundslot &c = slots[s.slots+j];
515 0 : if(!std::strcmp(c.sample->name.c_str(), name) && (!vol || c.volume==vol))
516 : {
517 0 : return i;
518 : }
519 : }
520 : }
521 0 : return -1;
522 : }
523 0 : int SoundEngine::SoundType::addslot(const char *name, int vol)
524 : {
525 0 : SoundSample * sample = nullptr;
526 0 : auto itr = samples.find(std::string(name));
527 0 : if(itr == samples.end())
528 : {
529 0 : SoundSample s(*parent);
530 0 : s.name = std::string(name);
531 0 : s.chunk = nullptr;
532 0 : samples.insert(std::pair<std::string, SoundSample>(std::string(name), s));
533 0 : itr = samples.find(std::string(name));
534 0 : }
535 : else
536 : {
537 0 : sample = &(*(itr)).second;
538 : }
539 0 : soundslot *oldslots = slots.data();
540 0 : int oldlen = slots.size();
541 0 : slots.emplace_back();
542 0 : soundslot &slot = slots.back();
543 : // soundslots.add() may relocate slot pointers
544 0 : if(slots.data() != oldslots)
545 : {
546 0 : for(uint i = 0; i < parent->channels.size(); i++)
547 : {
548 0 : SoundChannel &chan = parent->channels[i];
549 0 : if(chan.inuse && chan.slot >= oldslots && chan.slot < &oldslots[oldlen])
550 : {
551 0 : chan.slot = &slots[chan.slot - oldslots];
552 : }
553 : }
554 : }
555 0 : slot.sample = sample;
556 0 : slot.volume = vol ? vol : 100;
557 0 : return oldlen;
558 : }
559 0 : int SoundEngine::SoundType::addsound(const char *name, int vol, int maxuses)
560 : {
561 : SoundConfig s;
562 0 : s.slots = addslot(name, vol);
563 0 : s.numslots = 1;
564 0 : s.maxuses = maxuses;
565 0 : configs.push_back(s);
566 0 : return configs.size()-1;
567 : }
568 :
569 0 : void SoundEngine::SoundType::addalt(const char *name, int vol)
570 : {
571 0 : if(configs.empty())
572 : {
573 0 : return;
574 : }
575 0 : addslot(name, vol);
576 0 : configs.back().numslots++;
577 : }
578 0 : void SoundEngine::SoundType::clear()
579 : {
580 0 : slots.clear();
581 0 : configs.clear();
582 0 : }
583 0 : void SoundEngine::SoundType::reset() //cleanup each channel
584 : {
585 0 : for(uint i = 0; i < parent->channels.size(); i++)
586 : {
587 0 : SoundChannel &chan = parent->channels[i];
588 0 : soundslot * array = slots.data();
589 0 : uint size = slots.size();
590 0 : bool inbuf = chan.slot >= array + size && chan.slot < array; //within bounds of utilized vector spaces
591 0 : if(chan.inuse && inbuf)
592 : {
593 0 : Mix_HaltChannel(i);
594 0 : parent->freechannel(i);
595 : }
596 : }
597 0 : clear();
598 0 : }
599 0 : void SoundEngine::SoundType::cleanupsamples()
600 : {
601 0 : for (auto& [k, v]: samples)
602 : {
603 0 : v.cleanup();
604 : }
605 0 : }
606 0 : void SoundEngine::SoundType::cleanup()
607 : {
608 0 : cleanupsamples();
609 0 : slots.clear();
610 0 : configs.clear();
611 0 : samples.clear();
612 0 : }
613 0 : void SoundEngine::SoundType::preloadsound(int n)
614 : {
615 0 : if(parent->nosound || !(static_cast<long>(configs.size()) > n))
616 : {
617 0 : return;
618 : }
619 0 : SoundConfig &config = configs[n];
620 0 : for(int k = 0; k < config.numslots; ++k)
621 : {
622 0 : slots[config.slots+k].sample->load(dir);
623 : }
624 : }
625 0 : bool SoundEngine::SoundType::playing(const SoundChannel &chan, const SoundConfig &config) const
626 : {
627 0 : return chan.inuse && config.hasslot(chan.slot, slots);
628 : }
629 :
630 : //free all channels
631 0 : void SoundEngine::resetchannels()
632 : {
633 0 : for(uint i = 0; i < channels.size(); i++)
634 : {
635 0 : if(channels[i].inuse)
636 : {
637 0 : freechannel(i);
638 : }
639 : }
640 0 : channels.clear();
641 0 : }
642 :
643 : //used externally in iengine
644 0 : void SoundEngine::clear_sound()
645 : {
646 0 : if(nosound) //don't bother closing stuff that isn't there
647 : {
648 0 : return;
649 : }
650 0 : stopmusic();
651 :
652 0 : gamesounds.cleanup();
653 0 : mapsounds.cleanup();
654 0 : Mix_CloseAudio();
655 0 : resetchannels();
656 : }
657 :
658 0 : void SoundEngine::stopmapsound(extentity *e)
659 : {
660 0 : for(uint i = 0; i < channels.size(); i++)
661 : {
662 0 : SoundChannel &chan = channels[i];
663 0 : if(chan.inuse && chan.ent == e)
664 : {
665 0 : Mix_HaltChannel(i);
666 0 : freechannel(i);
667 : }
668 : }
669 0 : }
670 :
671 0 : void SoundEngine::setstereo(const int * const on)
672 : {
673 0 : stereo = on;
674 0 : }
675 0 : int SoundEngine::getstereo()
676 : {
677 0 : if(stereo)
678 : {
679 0 : return 1;
680 : }
681 0 : return 0;
682 : }
683 : //VAR(stereo, 0, 1, 1); //toggles mixing of sounds by direction
684 :
685 : //distance in cubits: how far away sound entities can be heard at(340 = 42.5m)
686 0 : void SoundEngine::setmaxradius(const int * const dist)
687 : {
688 0 : maxsoundradius = *dist;
689 0 : }
690 0 : int SoundEngine::getmaxradius()
691 : {
692 0 : return maxsoundradius;
693 : }
694 :
695 : //recalculates stereo mix & volume for a soundchannel (sound ent, or player generated sound)
696 : //(unless stereo is disabled, in which case the mix is only by distance)
697 0 : bool SoundEngine::SoundChannel::updatechannel()
698 : {
699 0 : if(!slot)
700 : {
701 0 : return false;
702 : }
703 0 : int vol = parent->soundvol,
704 0 : middlepan = 255/2;
705 0 : if(hasloc())
706 : {
707 0 : vec v;
708 0 : float dist = loc.dist(camera1->o, v);
709 0 : int rad = parent->maxsoundradius;
710 0 : if(ent)
711 : {
712 0 : rad = ent->attr2;
713 0 : if(ent->attr3)
714 : {
715 0 : rad -= ent->attr3;
716 0 : dist -= ent->attr3;
717 : }
718 : }
719 0 : else if(radius > 0)
720 : {
721 0 : rad = parent->maxsoundradius ? std::min(parent->maxsoundradius, radius) : radius;
722 : }
723 0 : if(rad > 0) //rad = 0 means no attenuation ever
724 : {
725 0 : vol -= static_cast<int>(std::clamp(dist/rad, 0.0f, 1.0f)*parent->soundvol); // simple mono distance attenuation
726 : }
727 0 : if(parent->stereo && (v.x != 0 || v.y != 0) && dist>0)
728 : {
729 0 : v.rotate_around_z(-camera1->yaw/RAD);
730 0 : pan = static_cast<int>(255.9f*(0.5f - 0.5f*v.x/v.magnitude2())); // range is from 0 (left) to 255 (right)
731 : }
732 : }
733 0 : vol = (vol*MIX_MAX_VOLUME*slot->volume)/255/255;
734 0 : vol = std::min(vol, MIX_MAX_VOLUME);
735 0 : if(vol == volume && pan == middlepan)
736 : {
737 0 : return false;
738 : }
739 0 : volume = vol;
740 0 : pan = middlepan;
741 0 : dirty = true;
742 0 : return true;
743 : }
744 :
745 : //free channels that are not playing sounds
746 0 : void SoundEngine::reclaimchannels()
747 : {
748 0 : for(uint i = 0; i < channels.size(); i++)
749 : {
750 0 : SoundChannel &chan = channels[i];
751 0 : if(chan.inuse && !Mix_Playing(i))
752 : {
753 0 : freechannel(i);
754 : }
755 : }
756 0 : }
757 :
758 0 : void SoundEngine::syncchannels()
759 : {
760 0 : for(uint i = 0; i < channels.size(); i++)
761 : {
762 0 : SoundChannel &chan = channels[i];
763 0 : if(chan.inuse && chan.hasloc() && chan.updatechannel())
764 : {
765 0 : chan.syncchannel();
766 : }
767 : }
768 0 : }
769 :
770 : //VARP(minimizedsounds, 0, 0, 1); //toggles playing sound when window minimized
771 0 : int SoundEngine::getminimizedsounds()
772 : {
773 0 : return minimizedsounds;
774 : }
775 0 : void SoundEngine::setminimizedsounds(int minimize)
776 : {
777 0 : minimizedsounds = minimize;
778 0 : }
779 :
780 : //number of sounds before the game will refuse to play another sound (with `playsound()`);
781 : //set to 0 to disable checking (0 does not set no sounds to be playable)
782 : //VARP(maxsoundsatonce, 0, 7, 100);
783 0 : int SoundEngine::getmaxsoundsatonce()
784 : {
785 0 : return maxsoundsatonce;
786 : }
787 0 : void SoundEngine::setmaxsoundsatonce(const int * num)
788 : {
789 0 : maxsoundsatonce = std::clamp(*num, 0, 100);
790 0 : }
791 :
792 : //used in iengine.h
793 0 : void SoundEngine::preloadsound(int n)
794 : {
795 0 : gamesounds.preloadsound(n);
796 0 : }
797 :
798 0 : void SoundEngine::preloadmapsounds()
799 : {
800 0 : const std::vector<extentity *> &ents = entities::getents();
801 0 : for(uint i = 0; i < ents.size(); i++)
802 : {
803 0 : extentity &e = *ents[i];
804 0 : if(e.type==EngineEnt_Sound)
805 : {
806 0 : mapsounds.preloadsound(e.attr1); //load sounds by first ent attr (index)
807 : }
808 : }
809 0 : }
810 :
811 : //used in iengine.h
812 0 : int SoundEngine::playsound(int n, const vec *loc, extentity *ent, int flags, int loops, int fade, int chanid, int radius, int expire)
813 : {
814 0 : if(nosound || !soundvol || (minimized && !minimizedsounds)) //mute check
815 : {
816 0 : return -1;
817 : }
818 0 : SoundType &sounds = ent || flags&Music_Map ? mapsounds : gamesounds;
819 0 : if(!(static_cast<long>(sounds.configs.size()) > n)) //sound isn't within index
820 : {
821 0 : conoutf(Console_Warn, "unregistered sound: %d", n);
822 0 : return -1;
823 : }
824 0 : SoundConfig &config = sounds.configs[n];
825 0 : if(loc && (maxsoundradius || radius > 0))
826 : {
827 : // cull sounds that are unlikely to be heard
828 : //if radius is greater than zero, clamp to maxsoundradius if maxsound radius is nonzero; if radius is zero, clamp to maxsoundradius
829 0 : int rad = radius > 0 ? (maxsoundradius ? std::min(maxsoundradius, radius) : radius) : maxsoundradius;
830 0 : if(camera1->o.dist(*loc) > 1.5f*rad)
831 : {
832 0 : if(channels.size() > static_cast<size_t>(chanid) && sounds.playing(channels[chanid], config))
833 : {
834 0 : Mix_HaltChannel(chanid);
835 0 : freechannel(chanid);
836 : }
837 0 : return -1;
838 : }
839 : }
840 0 : if(chanid < 0)
841 : {
842 0 : if(config.maxuses)
843 : {
844 0 : int uses = 0;
845 0 : for(const SoundChannel &s : channels)
846 : {
847 0 : if(sounds.playing(s, config) && ++uses >= config.maxuses)
848 : {
849 0 : return -1;
850 : }
851 : }
852 : }
853 : // avoid bursts of sounds with heavy packetloss and in sp
854 : static int soundsatonce = 0,
855 : lastsoundmillis = 0;
856 0 : if(totalmillis == lastsoundmillis)
857 : {
858 0 : soundsatonce++;
859 : }
860 : else
861 : {
862 0 : soundsatonce = 1;
863 : }
864 0 : lastsoundmillis = totalmillis;
865 0 : if(maxsoundsatonce && soundsatonce > maxsoundsatonce)
866 : {
867 0 : return -1;
868 : }
869 : }
870 0 : if(channels.size() > static_cast<size_t>(chanid))
871 : {
872 0 : SoundChannel &chan = channels[chanid];
873 0 : if(sounds.playing(chan, config))
874 : {
875 0 : if(loc)
876 : {
877 0 : chan.setloc(*loc);
878 : }
879 0 : else if(chan.hasloc())
880 : {
881 0 : chan.clearloc();
882 : }
883 0 : return chanid;
884 : }
885 : }
886 0 : if(fade < 0) //attenuation past zero
887 : {
888 0 : return -1;
889 : }
890 0 : soundslot &slot = sounds.slots[config.chooseslot(flags)];
891 0 : if(!slot.sample->chunk && !slot.sample->load(sounds.dir))
892 : {
893 0 : return -1;
894 : }
895 0 : if(debugsound)
896 : {
897 0 : conoutf("sound: %s%s", sounds.dir, slot.sample->name.c_str());
898 : }
899 0 : chanid = -1;
900 0 : for(uint i = 0; i < channels.size(); i++)
901 : {
902 0 : if(!channels[i].inuse)
903 : {
904 0 : chanid = i;
905 0 : break;
906 : }
907 : }
908 0 : if(chanid < 0 && static_cast<long>(channels.size()) < maxchannels)
909 : {
910 0 : chanid = channels.size();
911 : }
912 0 : if(chanid < 0)
913 : {
914 0 : for(uint i = 0; i < channels.size(); i++)
915 : {
916 0 : if(!channels[i].volume)
917 : {
918 0 : Mix_HaltChannel(i);
919 0 : freechannel(i);
920 0 : chanid = i;
921 0 : break;
922 : }
923 : }
924 : }
925 0 : if(chanid < 0)
926 : {
927 0 : return -1;
928 : }
929 0 : SoundChannel &chan = newchannel(chanid, &slot, loc, ent, flags, radius);
930 0 : chan.updatechannel();
931 0 : int playing = -1;
932 : //some ugly ternary assignments
933 0 : if(fade)
934 : {
935 0 : Mix_Volume(chanid, chan.volume);
936 0 : playing = expire >= 0 ?
937 0 : Mix_FadeInChannelTimed(chanid, slot.sample->chunk, loops, fade, expire) :
938 0 : Mix_FadeInChannel(chanid, slot.sample->chunk, loops, fade);
939 : }
940 : else
941 : {
942 0 : playing = expire >= 0 ?
943 0 : Mix_PlayChannelTimed(chanid, slot.sample->chunk, loops, expire) :
944 0 : Mix_PlayChannel(chanid, slot.sample->chunk, loops);
945 : }
946 0 : if(playing >= 0)
947 : {
948 0 : chan.syncchannel();
949 : }
950 : else
951 : {
952 0 : freechannel(chanid);
953 : }
954 0 : return playing;
955 : }
956 :
957 0 : bool SoundEngine::stopsound(int n, int chanid, int fade)
958 : {
959 0 : if(!(static_cast<long>(gamesounds.configs.size()) > n) || !(static_cast<long>(channels.size()) > chanid) || !gamesounds.playing(channels[chanid], gamesounds.configs[n]))
960 : {
961 0 : return false;
962 : }
963 0 : if(debugsound)
964 : {
965 0 : conoutf("stopsound: %s%s", gamesounds.dir, channels[chanid].slot->sample->name.c_str());
966 : }
967 0 : if(!fade || !Mix_FadeOutChannel(chanid, fade)) //clear and free channel allocation
968 : {
969 0 : Mix_HaltChannel(chanid);
970 0 : freechannel(chanid);
971 : }
972 0 : return true;
973 : }
974 :
975 0 : void SoundEngine::stopmapsounds()
976 : {
977 0 : for(uint i = 0; i < channels.size(); i++)
978 : {
979 0 : if(channels[i].inuse && channels[i].ent)
980 : {
981 0 : Mix_HaltChannel(i);
982 0 : freechannel(i);
983 : }
984 : }
985 0 : }
986 :
987 : //check map entities to see what sounds need to be played because of them
988 0 : void SoundEngine::checkmapsounds()
989 : {
990 0 : const std::vector<extentity *> &ents = entities::getents();
991 0 : for(uint i = 0; i < ents.size(); i++)
992 : {
993 0 : extentity &e = *ents[i];
994 0 : if(e.type!=EngineEnt_Sound) //ents that aren't soundents don't make sound (!)
995 : {
996 0 : continue;
997 : }
998 0 : if(camera1->o.dist(e.o) < e.attr2) //if distance to entity < ent attr 2 (radius)
999 : {
1000 0 : if(!(e.flags&EntFlag_Sound))
1001 : {
1002 0 : playsound(e.attr1, nullptr, &e, Music_Map, -1);
1003 : }
1004 : }
1005 0 : else if(e.flags&EntFlag_Sound)
1006 : {
1007 0 : stopmapsound(&e);
1008 : }
1009 : }
1010 0 : }
1011 :
1012 0 : void SoundEngine::stopsounds()
1013 : {
1014 0 : for(uint i = 0; i < channels.size(); i++)
1015 : {
1016 0 : if(channels[i].inuse)
1017 : {
1018 0 : Mix_HaltChannel(i);
1019 0 : freechannel(i);
1020 : }
1021 : }
1022 0 : }
1023 :
1024 0 : void SoundEngine::updatesounds()
1025 : {
1026 0 : if(nosound) //don't update sounds if disabled
1027 : {
1028 0 : return;
1029 : }
1030 0 : if(minimized && !minimizedsounds)//minimizedsounds check
1031 : {
1032 0 : stopsounds();
1033 : }
1034 : else
1035 : {
1036 0 : reclaimchannels(); //cull channels first
1037 0 : if(mainmenu) //turn off map sounds if you reach main menu
1038 : {
1039 0 : stopmapsounds();
1040 : }
1041 : else
1042 : {
1043 0 : checkmapsounds();
1044 : }
1045 0 : syncchannels();
1046 : }
1047 0 : if(music)
1048 : {
1049 0 : if(!Mix_PlayingMusic())
1050 : {
1051 0 : musicdone();
1052 : }
1053 0 : else if(Mix_PausedMusic())
1054 : {
1055 0 : Mix_ResumeMusic();
1056 : }
1057 : }
1058 : }
1059 :
1060 : //used in iengine.h
1061 0 : int SoundEngine::playsoundname(const char *s, const vec *loc, int vol, int flags, int loops, int fade, int chanid, int radius, int expire)
1062 : {
1063 0 : if(!vol) //default to 100 volume
1064 : {
1065 0 : vol = 100;
1066 : }
1067 0 : int id = gamesounds.findsound(s, vol);
1068 0 : if(id < 0)
1069 : {
1070 0 : id = gamesounds.addsound(s, vol);
1071 : }
1072 0 : return playsound(id, loc, nullptr, flags, loops, fade, chanid, radius, expire);
1073 : }
1074 :
1075 0 : void SoundEngine::resetsound()
1076 : {
1077 0 : clearchanges(Change_Sound);
1078 0 : if(!nosound)
1079 : {
1080 0 : gamesounds.cleanupsamples();
1081 0 : mapsounds.cleanupsamples();
1082 0 : if(music)
1083 : {
1084 0 : Mix_HaltMusic();
1085 0 : Mix_FreeMusic(music);
1086 : }
1087 0 : if(musicstream)
1088 : {
1089 0 : musicstream->seek(0, SEEK_SET);
1090 : }
1091 0 : Mix_CloseAudio();
1092 : }
1093 0 : initsound();
1094 0 : resetchannels();
1095 0 : if(nosound) //clear stuff if muted
1096 : {
1097 0 : delete[] musicfile;
1098 0 : delete[] musicdonecmd;
1099 :
1100 0 : musicfile = musicdonecmd = nullptr;
1101 0 : music = nullptr;
1102 0 : gamesounds.cleanupsamples();
1103 0 : mapsounds.cleanupsamples();
1104 0 : return;
1105 : }
1106 0 : if(music && loadmusic(musicfile))
1107 : {
1108 0 : Mix_PlayMusic(music, musicdonecmd ? 0 : -1);
1109 0 : Mix_VolumeMusic((musicvol*MIX_MAX_VOLUME)/255);
1110 : }
1111 : else
1112 : {
1113 0 : delete[] musicfile;
1114 0 : delete[] musicdonecmd;
1115 :
1116 0 : musicfile = musicdonecmd = nullptr;
1117 : }
1118 : }
1119 :
1120 0 : void SoundEngine::registersound (char *name, int *vol)
1121 : {
1122 0 : intret(gamesounds.addsound(name, *vol, 0));
1123 0 : }
1124 :
1125 0 : void SoundEngine::mapsound(char *name, int *vol, int *maxuses)
1126 : {
1127 0 : intret(mapsounds.addsound(name, *vol, *maxuses < 0 ? 0 : std::max(1, *maxuses)));
1128 0 : }
1129 :
1130 0 : void SoundEngine::altsound(char *name, int *vol)
1131 : {
1132 0 : gamesounds.addalt(name, *vol);
1133 0 : }
1134 :
1135 0 : void SoundEngine::altmapsound(char *name, int *vol)
1136 : {
1137 0 : mapsounds.addalt(name, *vol);
1138 0 : }
1139 :
1140 0 : void SoundEngine::numsounds()
1141 : {
1142 0 : intret(gamesounds.configs.size());
1143 0 : }
1144 :
1145 0 : void SoundEngine::nummapsounds()
1146 : {
1147 0 : intret(mapsounds.configs.size());
1148 0 : }
1149 :
1150 0 : void SoundEngine::soundreset()
1151 : {
1152 0 : gamesounds.reset();
1153 0 : }
1154 :
1155 0 : void SoundEngine::mapsoundreset()
1156 : {
1157 0 : mapsounds.reset();
1158 0 : }
|