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