UFO: Alien Invasion
s_music.cpp
Go to the documentation of this file.
1
5/*
6All original material Copyright (C) 2002-2022 UFO: Alien Invasion.
7
8This program is free software; you can redistribute it and/or
9modify it under the terms of the GNU General Public License
10as published by the Free Software Foundation; either version 2
11of the License, or (at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17See the GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23 */
24
25#include "s_music.h"
26#include "s_local.h"
27#include "../cl_shared.h" /* cl_genericPool */
28#include "../../shared/parse.h"
29#include "../../ports/system.h"
30#include "../../common/filesys.h" /* for MAX_QPATH */
31#include "../../common/common.h" /* for many */
32#include "../../common/scripts.h"
33#include "../cl_renderer.h"
34#include "../cl_video.h"
35#include "../battlescape/cl_camera.h"
36#include "../battlescape/cl_localentity.h"
37#include "../battlescape/cl_battlescape.h"
38
39enum {
44
46};
47
48typedef struct music_s {
49 char currentTrack[MAX_QPATH];
50 char nextTrack[MAX_QPATH];
51 Mix_Music* data;
53 byte* buffer;
58 bool playing;
59} music_t;
60
61#define MUSIC_MAX_ENTRIES 64
62static char* musicArrays[MUSIC_MAX][MUSIC_MAX_ENTRIES] = {{}, {}};
63static int musicArrayLength[MUSIC_MAX] = {};
64static music_t music = {{}, {}, nullptr, 0, nullptr, false, false, true};
68
73void M_ParseMusic (const char* name, const char** text)
74{
75 int i;
76
77 if (Q_streq(name, "geoscape"))
79 else if (Q_streq(name, "battlescape"))
81 else if (Q_streq(name, "aircombat"))
83 else if (Q_streq(name, "main"))
84 i = MUSIC_MAIN;
85 else {
86 Com_Printf("M_ParseMusic: Invalid music id '%s'!\n", name);
87 linkedList_t* list;
88 Com_ParseList(text, &list);
89 LIST_Delete(&list);
90 return;
91 }
92
93 /* get it's body */
94 linkedList_t* list;
95 if (!Com_ParseList(text, &list)) {
96 Com_Error(ERR_DROP, "M_ParseMusic: error while reading music \"%s\"", name);
97 }
98
99 for (linkedList_t* element = list; element != nullptr; element = element->next) {
101 Com_Printf("M_ParseMusic: Too many music entries for category: '%s'!\n", name);
102 break;
103 }
104 musicArrays[i][musicArrayLength[i]] = Mem_PoolStrDup((char*)element->data, cl_genericPool, 0);
106 }
107
108 LIST_Delete(&list);
109}
110
114void M_Stop (void)
115{
116 /* we should not even have a buffer nor data set - but ... just to be sure */
118 return;
119
120 if (music.data != nullptr) {
121 Mix_HaltMusic();
122 Mix_FreeMusic(music.data);
123 }
124
125 if (music.buffer)
127
128 music.data = nullptr;
129 music.buffer = nullptr;
130}
131
135static void M_Start (const char* file)
136{
137 if (Q_strnull(file))
138 return;
139
140 if (!s_env.initialized) {
141 Com_Printf("M_Start: No sound started!\n");
142 return;
143 }
144
146 return;
147
148 char name[MAX_QPATH];
149 Com_StripExtension(file, name, sizeof(name));
150 const size_t len = strlen(name);
151 if (len + 4 >= MAX_QPATH) {
152 Com_Printf("M_Start: MAX_QPATH exceeded: " UFO_SIZE_T "\n", len + 4);
153 return;
154 }
155
156 /* we are already playing that track */
157 if (Q_streq(name, music.currentTrack) && music.data && Mix_PlayingMusic())
158 return;
159
160 /* we are still playing some background track - fade it out */
161 if (music.data && Mix_PlayingMusic()) {
162 if (!Mix_FadeOutMusic(1500))
163 M_Stop();
165 return;
166 }
167
168 /* make really sure the last track is closed and freed */
169 M_Stop();
170
171 /* load it in */
172 byte* musicBuf;
173 const int size = FS_LoadFile(va("music/%s.ogg", name), &musicBuf);
174 if (size == -1) {
175 Com_Printf("M_Start: Could not load '%s' background track!\n", name);
176 return;
177 }
178
179 SDL_RWops* rw = SDL_RWFromMem(musicBuf, size);
180 if (!rw) {
181 Com_Printf("M_Start: Could not load music: 'music/%s'!\n", name);
182 FS_FreeFile(musicBuf);
183 return;
184 }
185#if SDL_VERSION_ATLEAST(2,0,0)
186 music.data = Mix_LoadMUS_RW(rw, 1);
187#else
188 music.data = Mix_LoadMUS_RW(rw);
189#endif
190 if (!music.data) {
191 Com_Printf("M_Start: Could not load music: 'music/%s' (%s)!\n", name, Mix_GetError());
192 SDL_FreeRW(rw);
193 FS_FreeFile(musicBuf);
194 return;
195 }
196
198 music.buffer = musicBuf;
199 if (Mix_FadeInMusic(music.data, 1, 1500) == -1)
200 Com_Printf("M_Start: Could not play music: 'music/%s' (%s)!\n", name, Mix_GetError());
201}
202
206static void M_Play_f (void)
207{
208 if (Cmd_Argc() == 2)
209 Cvar_Set("snd_music", "%s", Cmd_Argv(1));
210
211 M_Start(Cvar_GetString("snd_music"));
212}
213
217static void M_RandomTrack_f (void)
218{
220 return;
221
222 const int musicTrackCount = FS_BuildFileList("music/*.ogg");
223 if (musicTrackCount) {
224 int randomID = rand() % musicTrackCount;
225 Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: random track id: %i/%i\n", randomID, musicTrackCount);
226
227 const char* filename;
228 while ((filename = FS_NextFileFromFileList("music/*.ogg")) != nullptr) {
229 if (!randomID) {
230 const char* musicTrack = Com_SkipPath(filename);
231 Com_Printf("..playing next music track: '%s'\n", musicTrack);
232 Cvar_Set("snd_music", "%s", musicTrack);
233 }
234 randomID--;
235 }
237 } else {
238 Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: No music found!\n");
239 }
240}
241
242static bool M_PlayRandomByCategory (int category)
243{
244 if (category != MUSIC_BATTLESCAPE && CL_OnBattlescape())
245 return false;
246 if (!musicArrayLength[category])
247 return false;
248 const int rnd = rand() % musicArrayLength[category];
249 music.category = category;
250 Com_Printf("Music: track changed from %s to %s.\n", music.currentTrack, musicArrays[category][rnd]);
251 Cvar_Set("snd_music", "%s", musicArrays[category][rnd]);
252 return snd_music->modified;
253}
254
259static void M_Change_f (void)
260{
262 return;
263
264 if (Cmd_Argc() != 2) {
265 Com_Printf("Usage: %s <geoscape|battlescape|main|aircombat>\n", Cmd_Argv(0));
266 return;
267 }
268 const char* type = Cmd_Argv(1);
269 int category;
270 if (Q_streq(type, "geoscape")) {
271 category = MUSIC_GEOSCAPE;
272 } else if (Q_streq(type, "battlescape")) {
273 category = MUSIC_BATTLESCAPE;
274 } else if (Q_streq(type, "main")) {
275 category = MUSIC_MAIN;
276 } else if (Q_streq(type, "aircombat")) {
277 category = MUSIC_AIRCOMBAT;
278 } else {
279 Com_Printf("Invalid parameter given!\n");
280 return;
281 }
282
283 if (category != MUSIC_BATTLESCAPE && CL_OnBattlescape()) {
284 Com_DPrintf(DEBUG_SOUND, "Not changing music to %s - we are on the battlescape!\n", type);
285 return;
286 }
287
288 if (!musicArrayLength[category]) {
289 Com_Printf("M_Change_f: Could not find any %s themed music tracks!\n", type);
290 return;
291 }
292
293 M_PlayRandomByCategory(category);
294}
295
296static int M_CompleteMusic (const char* partial, const char** match)
297{
298 int n = 0;
299 while (char const* const filename = FS_NextFileFromFileList("music/*.ogg")) {
300 if (Cmd_GenericCompleteFunction(filename, partial, match)) {
301 Com_Printf("%s\n", filename);
302 ++n;
303 }
304 }
306 return n;
307}
308
309static void M_MusicStreamUpdate (void)
310{
312 music.interruptStream = false;
313 M_StopMusicStream(nullptr);
314 }
315}
316
317void M_Frame (void)
318{
321 snd_music_play->modified = false;
322 }
323 if (!music.playing) {
324 if (Mix_PlayingMusic())
325 M_Stop();
326 return;
327 }
328 if (snd_music->modified) {
330 snd_music->modified = false;
331 }
333 Mix_VolumeMusic(snd_music_volume->integer);
334 snd_music_volume->modified = false;
335 }
336
337 if (music.playingStream) {
339 } else if (!Mix_PlayingMusic()) {
340 M_Stop(); /* free the allocated memory */
343 music.nextTrack[0] = '\0';
344 } else {
347 }
348 }
349}
350
351static const cmdList_t musicCmds[] = {
352 {"music_play", M_Play_f, "Plays a music track."},
353 {"music_change", M_Change_f, "Changes the music theme (valid values:battlescape/geoscape/main/aircombat)."},
354 {"music_stop", M_Stop, "Stops currently playing music track."},
355 {"music_randomtrack", M_RandomTrack_f, "Plays a random music track."},
356 {nullptr, nullptr, nullptr}
357};
358
359void M_Init (void)
360{
361 if (Cmd_Exists("music_change"))
362 Cmd_RemoveCommand("music_change");
365 snd_music = Cvar_Get("snd_music", "PsymongN3", 0, "Background music track");
366 snd_music_volume = Cvar_Get("snd_music_volume", "128", CVAR_ARCHIVE, "Music volume - default is 128.");
368 snd_music_play = Cvar_Get ("snd_music_play", "1", CVAR_ARCHIVE, "Enable background music.");
370}
371
372void M_Shutdown (void)
373{
374 M_Stop();
375
377}
378
379static void M_MusicStreamCallback (musicStream_t* userdata, byte* stream, int length)
380{
381 int tries = 0;
382 while (1) {
383 if (!userdata->playing) {
384 music.interruptStream = true;
385 return;
386 }
387 const int availableBytes = (userdata->mixerPos > userdata->samplePos) ? MAX_RAW_SAMPLES - userdata->mixerPos + userdata->samplePos : userdata->samplePos - userdata->mixerPos;
388 if (length < availableBytes)
389 break;
390 if (++tries > 50) {
391 userdata->playing = false;
392 return;
393 }
394 Sys_Sleep(10);
395 }
396
397 if (userdata->mixerPos + length <= MAX_RAW_SAMPLES) {
398 memcpy(stream, userdata->sampleBuf + userdata->mixerPos, length);
399 userdata->mixerPos += length;
400 userdata->mixerPos %= MAX_RAW_SAMPLES;
401 } else {
402 const int end = MAX_RAW_SAMPLES - userdata->mixerPos;
403 const int start = length - end;
404 memcpy(stream, userdata->sampleBuf + userdata->mixerPos, end);
405 memcpy(stream, userdata->sampleBuf, start);
406 userdata->mixerPos = start;
407 }
408}
409
410static void M_PlayMusicStream (musicStream_t* userdata)
411{
412 if (userdata->playing)
413 return;
414
415 M_Stop();
416
417 userdata->playing = true;
418 music.playingStream = true;
419 Mix_HookMusic((void (*)(void*, Uint8*, int)) M_MusicStreamCallback, userdata);
420}
421
431void M_AddToSampleBuffer (musicStream_t* userdata, int rate, int samples, const byte* data)
432{
433 if (!s_env.initialized)
434 return;
435
436 M_PlayMusicStream(userdata);
437
438 if (rate != s_env.rate) {
439 const float scale = (float)rate / s_env.rate;
440 for (int i = 0;; i++) {
441 const int src = i * scale;
442 short* ptr = (short*)&userdata->sampleBuf[userdata->samplePos];
443 if (src >= samples)
444 break;
445 *ptr = LittleShort(((const short*) data)[src * 2]);
446 ptr++;
447 *ptr = LittleShort(((const short*) data)[src * 2 + 1]);
448
449 userdata->samplePos += 4;
450 userdata->samplePos %= MAX_RAW_SAMPLES;
451 }
452 } else {
453 for (int i = 0; i < samples; i++) {
454 short* ptr = (short*)&userdata->sampleBuf[userdata->samplePos];
455 *ptr = LittleShort(((const short*) data)[i * 2]);
456 ptr++;
457 *ptr = LittleShort(((const short*) data)[i * 2 + 1]);
458
459 userdata->samplePos += 4;
460 userdata->samplePos %= MAX_RAW_SAMPLES;
461 }
462 }
463}
464
466{
467 if (userdata != nullptr)
468 userdata->playing = false;
469 music.playingStream = false;
470 music.interruptStream = false;
471 Mix_HookMusic(nullptr, nullptr);
472}
#define LittleShort(X)
Definition: byte.h:35
bool CL_OnBattlescape(void)
Check whether we are in a tactical mission as server or as client. But this only means that we are ab...
memPool_t * cl_genericPool
Definition: cl_main.cpp:86
bool Cmd_Exists(const char *cmdName)
Checks whether a function exists already.
Definition: cmd.cpp:887
const char * Cmd_Argv(int arg)
Returns a given argument.
Definition: cmd.cpp:516
void Cmd_AddParamCompleteFunction(const char *cmdName, int(*function)(const char *partial, const char **match))
Definition: cmd.cpp:679
void Cmd_TableAddList(const cmdList_t *cmdList)
Definition: cmd.cpp:853
void Cmd_RemoveCommand(const char *cmdName)
Removes a command from script interface.
Definition: cmd.cpp:786
bool Cmd_GenericCompleteFunction(char const *candidate, char const *partial, char const **match)
Definition: cmd.cpp:648
int Cmd_Argc(void)
Return the number of arguments of the current command. "command parameter" will result in a argc of 2...
Definition: cmd.cpp:505
void Cmd_TableRemoveList(const cmdList_t *cmdList)
Definition: cmd.cpp:859
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition: common.cpp:398
void Com_Error(int code, const char *fmt,...)
Definition: common.cpp:417
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
#define ERR_DROP
Definition: common.h:211
cvar_t * Cvar_Set(const char *varName, const char *value,...)
Sets a cvar value.
Definition: cvar.cpp:615
cvar_t * Cvar_Get(const char *var_name, const char *var_value, int flags, const char *desc)
Init or return a cvar.
Definition: cvar.cpp:342
const char * Cvar_GetString(const char *varName)
Returns the value of cvar as string.
Definition: cvar.cpp:210
#define CVAR_ARCHIVE
Definition: cvar.h:40
#define DEBUG_SOUND
Definition: defines.h:63
int FS_LoadFile(const char *path, byte **buffer)
Filenames are relative to the quake search path.
Definition: files.cpp:384
void FS_FreeFile(void *buffer)
Definition: files.cpp:411
const char * FS_NextFileFromFileList(const char *files)
Returns the next file that is found in the virtual filesystem identified by the given file pattern.
Definition: files.cpp:1081
int FS_BuildFileList(const char *fileList)
Build a filelist.
Definition: files.cpp:962
#define MAX_QPATH
Definition: filesys.h:40
voidpf void uLong size
Definition: ioapi.h:42
voidpf stream
Definition: ioapi.h:42
const char * filename
Definition: ioapi.h:41
void LIST_Delete(linkedList_t **list)
Definition: list.cpp:195
#define Mem_PoolStrDup(in, pool, tagNum)
Definition: mem.h:50
QGL_EXTERN GLuint GLchar GLuint * len
Definition: r_gl.h:99
QGL_EXTERN GLsizei const GLvoid * data
Definition: r_gl.h:89
QGL_EXTERN GLuint GLsizei GLsizei * length
Definition: r_gl.h:110
QGL_EXTERN GLint i
Definition: r_gl.h:113
QGL_EXTERN GLint GLenum type
Definition: r_gl.h:94
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
s_env_t s_env
Definition: s_main.cpp:40
static void M_RandomTrack_f(void)
Sets the music cvar to a random track.
Definition: s_music.cpp:217
@ MUSIC_MAIN
Definition: s_music.cpp:40
@ MUSIC_GEOSCAPE
Definition: s_music.cpp:41
@ MUSIC_AIRCOMBAT
Definition: s_music.cpp:43
@ MUSIC_MAX
Definition: s_music.cpp:45
@ MUSIC_BATTLESCAPE
Definition: s_music.cpp:42
static void M_PlayMusicStream(musicStream_t *userdata)
Definition: s_music.cpp:410
static int M_CompleteMusic(const char *partial, const char **match)
Definition: s_music.cpp:296
static void M_MusicStreamCallback(musicStream_t *userdata, byte *stream, int length)
Definition: s_music.cpp:379
static char * musicArrays[MUSIC_MAX][MUSIC_MAX_ENTRIES]
Definition: s_music.cpp:62
void M_StopMusicStream(musicStream_t *userdata)
Definition: s_music.cpp:465
static cvar_t * snd_music_play
Definition: s_music.cpp:67
static const cmdList_t musicCmds[]
Definition: s_music.cpp:351
static cvar_t * snd_music
Definition: s_music.cpp:65
void M_Stop(void)
Definition: s_music.cpp:114
void M_Init(void)
Definition: s_music.cpp:359
static void M_Change_f(void)
Changes the music if it suits the current situation.
Definition: s_music.cpp:259
static void M_Play_f(void)
Plays the music file given via commandline parameter.
Definition: s_music.cpp:206
static music_t music
Definition: s_music.cpp:64
void M_Frame(void)
Definition: s_music.cpp:317
void M_AddToSampleBuffer(musicStream_t *userdata, int rate, int samples, const byte *data)
Add stereo samples with a 16 byte width to the stream buffer.
Definition: s_music.cpp:431
void M_ParseMusic(const char *name, const char **text)
Parses music definitions for different situations.
Definition: s_music.cpp:73
static cvar_t * snd_music_volume
Definition: s_music.cpp:66
static int musicArrayLength[MUSIC_MAX]
Definition: s_music.cpp:63
void M_Shutdown(void)
Definition: s_music.cpp:372
#define MUSIC_MAX_ENTRIES
Definition: s_music.cpp:61
static void M_Start(const char *file)
Definition: s_music.cpp:135
static bool M_PlayRandomByCategory(int category)
Definition: s_music.cpp:242
static void M_MusicStreamUpdate(void)
Definition: s_music.cpp:309
Specifies music API.
#define MAX_RAW_SAMPLES
Definition: s_music.h:35
bool Com_ParseList(const char **text, linkedList_t **list)
Definition: scripts.cpp:1363
#define Q_strvalid(string)
Definition: shared.h:141
#define Q_streq(a, b)
Definition: shared.h:136
bool Q_strnull(const char *string)
Definition: shared.h:138
void Com_StripExtension(const char *in, char *out, const size_t size)
Removes the file extension from a filename.
Definition: shared.cpp:259
const char * Com_SkipPath(const char *pathname)
Returns just the filename from a given path.
Definition: shared.cpp:37
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don't need to have varargs versions of all text functi...
Definition: shared.cpp:410
Definition: cmd.h:86
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition: cvar.h:71
bool modified
Definition: cvar.h:79
int integer
Definition: cvar.h:81
char * string
Definition: cvar.h:73
linkedList_t * next
Definition: list.h:32
int category
Definition: s_music.cpp:52
bool playingStream
Definition: s_music.cpp:54
Mix_Music * data
Definition: s_music.cpp:51
bool playing
Definition: s_music.cpp:58
byte * buffer
Definition: s_music.cpp:53
char nextTrack[MAX_QPATH]
Definition: s_music.cpp:50
char currentTrack[MAX_QPATH]
Definition: s_music.cpp:49
bool interruptStream
Definition: s_music.cpp:57
bool playing
Definition: s_music.h:38
int mixerPos
Definition: s_music.h:40
int samplePos
Definition: s_music.h:41
byte sampleBuf[MAX_RAW_SAMPLES]
Definition: s_music.h:39
bool initialized
Definition: s_local.h:70
int rate
Definition: s_local.h:66
void Sys_Sleep(int milliseconds)
Calls the win32 sleep function.
Definition: unix_shared.cpp:68
#define UFO_SIZE_T
Definition: ufotypes.h:89
static const vec3_t scale