1 // sound.cpp: basic positional sound using sdl_mixer
7 #define MAXVOL MIX_MAX_VOLUME
17 sample() : name(NULL
) {}
18 ~sample() { DELETEA(name
); }
28 struct soundloc
{ vec loc
; bool inuse
; soundslot
*slot
; extentity
*ent
; };
29 vector
<soundloc
> soundlocs
;
31 void setmusicvol(int musicvol
)
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
;
46 DELETEA(musicdonecmd
);
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
));
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());
66 Mix_AllocateChannels(soundchans
);
72 if(!musicdonecmd
) return;
73 if(mod
) Mix_FreeMusic(mod
);
76 char *cmd
= musicdonecmd
;
82 void music(char *name
, char *cmd
)
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);
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
)
113 if(!strcmp(sounds
[i
].s
->name
, name
) && (!vol
|| sounds
[i
].vol
==vol
)) return i
;
118 int addsound(const char *name
, int vol
, int maxuses
, vector
<soundslot
> &sounds
)
120 sample
*s
= samples
.access(name
);
123 char *n
= newstring(name
);
128 soundslot
&slot
= sounds
.add();
130 slot
.vol
= vol
? vol
: 100;
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");
147 gamesounds
.setsizenodelete(0);
148 mapsounds
.setsizenodelete(0);
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()
168 mapsounds
.setsizenodelete(0);
171 void checkmapsounds()
173 const vector
<extentity
*> &ents
= et
->getents();
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;
190 float dist
= camera1
->o
.dist(*loc
, v
);
193 int rad
= ent
->attr2
;
199 vol
-= (int)(min(max(dist
/rad
, 0.0f
), 1.0f
)*soundvol
);
203 vol
-= (int)(dist
*3/4*soundvol
/255); // simple mono distance attenuation
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
;
231 loopv(soundlocs
) if(soundlocs
[i
].inuse
)
234 updatechanvol(i
, soundlocs
[i
].slot
->vol
, &soundlocs
[i
].loc
, soundlocs
[i
].ent
);
237 soundlocs
[i
].inuse
= false;
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
)
253 if(!soundvol
) return;
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;
270 const char *exts
[] = { "", ".wav", ".ogg" };
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);
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
)
299 int id
= findsound(s
, vol
, gamesounds
);
300 if(id
< 0) id
= addsound(s
, vol
, 0, gamesounds
);
304 void sound(int *n
) { playsound(*n
); }
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.");
315 clearchanges(CHANGE_SOUND
);
319 enumerate(samples
, sample
, s
, { Mix_FreeChunk(s
.sound
); s
.sound
= NULL
; });
331 DELETEA(musicdonecmd
);
333 gamesounds
.setsizenodelete(0);
334 mapsounds
.setsizenodelete(0);
338 if(mod
&& (mod
= Mix_LoadMUS(musicfile
)))
340 Mix_PlayMusic(mod
, musicdonecmd
[0] ? 0 : -1);
341 Mix_VolumeMusic((musicvol
*MAXVOL
)/255);
345 COMMAND(resetsound
, "");
355 #ifdef _POSIX_SHARED_MEMORY_OBJECTS
356 #include <sys/types.h>
357 #include <sys/stat.h>
358 #include <sys/mman.h>
365 #if defined(WIN32) || defined(_POSIX_SHARED_MEMORY_OBJECTS)
368 int version
, timestamp
;
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)
384 #ifdef VALID_MUMBLELINK
385 VARFP(mumble
, 0, 1, 1, { if(mumble
) initmumble(); else closemumble(); });
387 VARFP(mumble
, 0, 0, 1, { if(mumble
) initmumble(); else closemumble(); });
393 #ifdef VALID_MUMBLELINK
394 if(VALID_MUMBLELINK
) return;
397 mumblelink
= OpenFileMapping(FILE_MAP_ALL_ACCESS
, FALSE
, "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);
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);
412 if(!VALID_MUMBLELINK
) closemumble();
414 conoutf(CON_ERROR
, "Mumble positional audio is not available on this platform.");
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; }
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
);
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)));