Initial Comit: First commit.
[SauerEngine.git] / src / engine / sound.cpp
bloba7a3a62ce479ac9c8627ddcb5ba08747675f60c4
1 // sound.cpp: basic positional sound using sdl_mixer
3 #include "pch.h"
4 #include "engine.h"
6 #include "SDL_mixer.h"
7 #define MAXVOL MIX_MAX_VOLUME
8 Mix_Music *mod = NULL;
10 bool nosound = true;
12 struct sample
14 char *name;
15 Mix_Chunk *sound;
17 sample() : name(NULL) {}
18 ~sample() { DELETEA(name); }
21 struct soundslot
23 sample *s;
24 int vol;
25 int uses, maxuses;
28 struct soundloc { vec loc; bool inuse; soundslot *slot; extentity *ent; };
29 vector<soundloc> soundlocs;
31 void setmusicvol(int musicvol)
33 if(nosound) return;
34 if(mod) Mix_VolumeMusic((musicvol*MAXVOL)/255);
37 VARP(soundvol, 0, 255, 255);
38 VARFP(musicvol, 0, 128, 255, setmusicvol(musicvol));
40 char *musicfile = NULL, *musicdonecmd = NULL;
42 void stopsound()
44 if(nosound) return;
45 DELETEA(musicfile);
46 DELETEA(musicdonecmd);
47 if(mod)
49 Mix_HaltMusic();
50 Mix_FreeMusic(mod);
51 mod = NULL;
55 VARF(soundchans, 0, 32, 128, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
56 VARF(soundfreq, 0, MIX_DEFAULT_FREQUENCY, 44100, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
57 VARF(soundbufferlen, 128, 1024, 4096, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
59 void initsound()
61 if(Mix_OpenAudio(soundfreq, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0)
63 conoutf(CON_ERROR, "sound init failed (SDL_mixer): %s", (size_t)Mix_GetError());
64 return;
66 Mix_AllocateChannels(soundchans);
67 nosound = false;
70 void musicdone()
72 if(!musicdonecmd) return;
73 if(mod) Mix_FreeMusic(mod);
74 mod = NULL;
75 DELETEA(musicfile);
76 char *cmd = musicdonecmd;
77 musicdonecmd = NULL;
78 execute(cmd);
79 delete[] cmd;
82 void music(char *name, char *cmd)
84 if(nosound) return;
85 stopsound();
86 if(soundvol && musicvol && *name)
88 if(cmd[0]) musicdonecmd = newstring(cmd);
89 s_sprintfd(sn)("packages/%s", name);
90 const char *file = findfile(path(sn), "rb");
91 if((mod = Mix_LoadMUS(file)))
93 musicfile = newstring(file);
94 Mix_PlayMusic(mod, cmd[0] ? 0 : -1);
95 Mix_VolumeMusic((musicvol*MAXVOL)/255);
97 else
99 conoutf(CON_ERROR, "could not play music: %s", sn);
104 COMMAND(music, "ss");
106 hashtable<const char *, sample> samples;
107 vector<soundslot> gamesounds, mapsounds;
109 int findsound(const char *name, int vol, vector<soundslot> &sounds)
111 loopv(sounds)
113 if(!strcmp(sounds[i].s->name, name) && (!vol || sounds[i].vol==vol)) return i;
115 return -1;
118 int addsound(const char *name, int vol, int maxuses, vector<soundslot> &sounds)
120 sample *s = samples.access(name);
121 if(!s)
123 char *n = newstring(name);
124 s = &samples[n];
125 s->name = n;
126 s->sound = NULL;
128 soundslot &slot = sounds.add();
129 slot.s = s;
130 slot.vol = vol ? vol : 100;
131 slot.uses = 0;
132 slot.maxuses = maxuses;
133 return sounds.length()-1;
136 void registersound(char *name, int *vol) { intret(addsound(name, *vol, 0, gamesounds)); }
137 COMMAND(registersound, "si");
139 void mapsound(char *name, int *vol, int *maxuses) { intret(addsound(name, *vol, *maxuses < 0 ? 0 : max(1, *maxuses), mapsounds)); }
140 COMMAND(mapsound, "sii");
142 void clear_sound()
144 closemumble();
145 if(nosound) return;
146 stopsound();
147 gamesounds.setsizenodelete(0);
148 mapsounds.setsizenodelete(0);
149 samples.clear();
150 Mix_CloseAudio();
153 void clearsoundlocs()
155 loopv(soundlocs) if(soundlocs[i].inuse && soundlocs[i].ent)
157 if(Mix_Playing(i)) Mix_HaltChannel(i);
158 soundlocs[i].inuse = false;
159 soundlocs[i].ent->visible = false;
160 soundlocs[i].slot->uses--;
162 soundlocs.setsize(0);
165 void clearmapsounds()
167 clearsoundlocs();
168 mapsounds.setsizenodelete(0);
171 void checkmapsounds()
173 const vector<extentity *> &ents = et->getents();
174 loopv(ents)
176 extentity &e = *ents[i];
177 if(e.type!=ET_SOUND || e.visible || camera1->o.dist(e.o)>=e.attr2) continue;
178 playsound(e.attr1, NULL, &e);
182 VAR(stereo, 0, 1, 1);
184 void updatechanvol(int chan, int svol, const vec *loc = NULL, extentity *ent = NULL)
186 int vol = soundvol, pan = 255/2;
187 if(loc)
189 vec v;
190 float dist = camera1->o.dist(*loc, v);
191 if(ent)
193 int rad = ent->attr2;
194 if(ent->attr3)
196 rad -= ent->attr3;
197 dist -= ent->attr3;
199 vol -= (int)(min(max(dist/rad, 0.0f), 1.0f)*soundvol);
201 else
203 vol -= (int)(dist*3/4*soundvol/255); // simple mono distance attenuation
204 if(vol<0) vol = 0;
206 if(stereo && (v.x != 0 || v.y != 0) && dist>0)
208 float yaw = -atan2f(v.x, v.y) - camera1->yaw*RAD; // relative angle of sound along X-Y axis
209 pan = int(255.9f*(0.5f*sinf(yaw)+0.5f)); // range is from 0 (left) to 255 (right)
212 vol = (vol*MAXVOL*svol)/255/255;
213 vol = min(vol, MAXVOL);
214 Mix_Volume(chan, vol);
215 Mix_SetPanning(chan, 255-pan, pan);
218 void newsoundloc(int chan, const vec *loc, soundslot *slot, extentity *ent = NULL)
220 while(chan >= soundlocs.length()) soundlocs.add().inuse = false;
221 soundlocs[chan].loc = *loc;
222 soundlocs[chan].inuse = true;
223 soundlocs[chan].slot = slot;
224 soundlocs[chan].ent = ent;
227 void updatevol()
229 updatemumble();
230 if(nosound) return;
231 loopv(soundlocs) if(soundlocs[i].inuse)
233 if(Mix_Playing(i))
234 updatechanvol(i, soundlocs[i].slot->vol, &soundlocs[i].loc, soundlocs[i].ent);
235 else
237 soundlocs[i].inuse = false;
238 if(soundlocs[i].ent)
240 soundlocs[i].ent->visible = false;
241 soundlocs[i].slot->uses--;
245 if(mod && !Mix_PlayingMusic()) musicdone();
248 VARP(maxsoundsatonce, 0, 5, 100);
250 void playsound(int n, const vec *loc, extentity *ent)
252 if(nosound) return;
253 if(!soundvol) return;
255 if(!ent)
257 static int soundsatonce = 0, lastsoundmillis = 0;
258 if(totalmillis==lastsoundmillis) soundsatonce++; else soundsatonce = 1;
259 lastsoundmillis = totalmillis;
260 if(maxsoundsatonce && soundsatonce>maxsoundsatonce) return; // avoid bursts of sounds with heavy packetloss and in sp
263 vector<soundslot> &sounds = ent ? mapsounds : gamesounds;
264 if(!sounds.inrange(n)) { conoutf(CON_WARN, "unregistered sound: %d", n); return; }
265 soundslot &slot = sounds[n];
266 if(ent && slot.maxuses && slot.uses>=slot.maxuses) return;
268 if(!slot.s->sound)
270 const char *exts[] = { "", ".wav", ".ogg" };
271 string buf;
272 loopi(sizeof(exts)/sizeof(exts[0]))
274 s_sprintf(buf)("packages/sounds/%s%s", slot.s->name, exts[i]);
275 const char *file = findfile(path(buf), "rb");
276 slot.s->sound = Mix_LoadWAV(file);
277 if(slot.s->sound) break;
280 if(!slot.s->sound) { conoutf(CON_ERROR, "failed to load sample: %s", buf); return; }
283 int chan = Mix_PlayChannel(-1, slot.s->sound, 0);
284 if(chan<0) return;
286 if(ent)
288 loc = &ent->o;
289 ent->visible = true;
290 slot.uses++;
292 if(loc) newsoundloc(chan, loc, &slot, ent);
293 updatechanvol(chan, slot.vol, loc, ent);
296 void playsoundname(const char *s, const vec *loc, int vol)
298 if(!vol) vol = 100;
299 int id = findsound(s, vol, gamesounds);
300 if(id < 0) id = addsound(s, vol, 0, gamesounds);
301 playsound(id, loc);
304 void sound(int *n) { playsound(*n); }
305 COMMAND(sound, "i");
307 void resetsound()
309 const SDL_version *v = Mix_Linked_Version();
310 if(SDL_VERSIONNUM(v->major, v->minor, v->patch) <= SDL_VERSIONNUM(1, 2, 8))
312 conoutf(CON_ERROR, "Sound reset not available in-game due to SDL_mixer-1.2.8 bug. Please restart for changes to take effect.");
313 return;
315 clearchanges(CHANGE_SOUND);
316 if(!nosound)
318 clearsoundlocs();
319 enumerate(samples, sample, s, { Mix_FreeChunk(s.sound); s.sound = NULL; });
320 if(mod)
322 Mix_HaltMusic();
323 Mix_FreeMusic(mod);
325 Mix_CloseAudio();
327 initsound();
328 if(nosound)
330 DELETEA(musicfile);
331 DELETEA(musicdonecmd);
332 mod = NULL;
333 gamesounds.setsizenodelete(0);
334 mapsounds.setsizenodelete(0);
335 samples.clear();
336 return;
338 if(mod && (mod = Mix_LoadMUS(musicfile)))
340 Mix_PlayMusic(mod, musicdonecmd[0] ? 0 : -1);
341 Mix_VolumeMusic((musicvol*MAXVOL)/255);
345 COMMAND(resetsound, "");
347 #ifdef WIN32
349 #include <wchar.h>
351 #else
353 #include <unistd.h>
355 #ifdef _POSIX_SHARED_MEMORY_OBJECTS
356 #include <sys/types.h>
357 #include <sys/stat.h>
358 #include <sys/mman.h>
359 #include <fcntl.h>
360 #include <wchar.h>
361 #endif
363 #endif
365 #if defined(WIN32) || defined(_POSIX_SHARED_MEMORY_OBJECTS)
366 struct MumbleInfo
368 int version, timestamp;
369 vec pos, front, top;
370 wchar_t name[256];
372 #endif
374 #ifdef WIN32
375 static HANDLE mumblelink = NULL;
376 static MumbleInfo *mumbleinfo = NULL;
377 #define VALID_MUMBLELINK (mumblelink && mumbleinfo)
378 #elif defined(_POSIX_SHARED_MEMORY_OBJECTS)
379 static int mumblelink = -1;
380 static MumbleInfo *mumbleinfo = (MumbleInfo *)-1;
381 #define VALID_MUMBLELINK (mumblelink >= 0 && mumbleinfo != (MumbleInfo *)-1)
382 #endif
384 #ifdef VALID_MUMBLELINK
385 VARFP(mumble, 0, 1, 1, { if(mumble) initmumble(); else closemumble(); });
386 #else
387 VARFP(mumble, 0, 0, 1, { if(mumble) initmumble(); else closemumble(); });
388 #endif
390 void initmumble()
392 if(!mumble) return;
393 #ifdef VALID_MUMBLELINK
394 if(VALID_MUMBLELINK) return;
396 #ifdef WIN32
397 mumblelink = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, "MumbleLink");
398 if(mumblelink)
400 mumbleinfo = (MumbleInfo *)MapViewOfFile(mumblelink, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(MumbleInfo));
401 if(mumbleinfo) wcsncpy(mumbleinfo->name, L"Sauerbraten", 256);
403 #elif defined(_POSIX_SHARED_MEMORY_OBJECTS)
404 s_sprintfd(shmname)("/MumbleLink.%d", getuid());
405 mumblelink = shm_open(shmname, O_RDWR, 0);
406 if(mumblelink >= 0)
408 mumbleinfo = (MumbleInfo *)mmap(NULL, sizeof(MumbleInfo), PROT_READ|PROT_WRITE, MAP_SHARED, mumblelink, 0);
409 if(mumbleinfo != (MumbleInfo *)-1) wcsncpy(mumbleinfo->name, L"Sauerbraten", 256);
411 #endif
412 if(!VALID_MUMBLELINK) closemumble();
413 #else
414 conoutf(CON_ERROR, "Mumble positional audio is not available on this platform.");
415 #endif
418 void closemumble()
420 #ifdef WIN32
421 if(mumbleinfo) { UnmapViewOfFile(mumbleinfo); mumbleinfo = NULL; }
422 if(mumblelink) { CloseHandle(mumblelink); mumblelink = NULL; }
423 #elif defined(_POSIX_SHARED_MEMORY_OBJECTS)
424 if(mumbleinfo != (MumbleInfo *)-1) { munmap(mumbleinfo, sizeof(MumbleInfo)); mumbleinfo = (MumbleInfo *)-1; }
425 if(mumblelink >= 0) { close(mumblelink); mumblelink = -1; }
426 #endif
429 static inline vec mumblevec(const vec &v, bool pos = false)
431 // change from Z up, -Y forward to Y up, +Z forward
432 // 8 cube units = 1 meter
433 vec m(v.x, v.z, -v.y);
434 if(pos) m.div(8);
435 return m;
438 void updatemumble()
440 #ifdef VALID_MUMBLELINK
441 if(!VALID_MUMBLELINK) return;
443 static int timestamp = 0;
445 mumbleinfo->version = 1;
446 mumbleinfo->timestamp = ++timestamp;
448 mumbleinfo->pos = mumblevec(player->o, true);
449 mumbleinfo->front = mumblevec(vec(RAD*player->yaw, RAD*player->pitch));
450 mumbleinfo->top = mumblevec(vec(RAD*player->yaw, RAD*(player->pitch+90)));
451 #endif