UFO: Alien Invasion
Loading...
Searching...
No Matches
s_music.cpp
Go to the documentation of this file.
1
4
5/*
6All original material Copyright (C) 2002-2025 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 */
33#include "../cl_renderer.h"
34#include "../cl_video.h"
38
39enum {
44
46};
47
48typedef struct music_s {
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 */
117 if (music.playingStream)
118 return;
119
120 if (music.data != nullptr) {
121 Mix_HaltMusic();
122 Mix_FreeMusic(music.data);
123 }
124
125 if (music.buffer)
126 FS_FreeFile(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
145 if (music.playingStream || !music.playing)
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();
164 Q_strncpyz(music.nextTrack, name, sizeof(music.nextTrack));
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
186 music.data = Mix_LoadMUS_RW(rw, 1);
187 if (!music.data) {
188 Com_Printf("M_Start: Could not load music: 'music/%s' (%s)!\n", name, Mix_GetError());
189 SDL_FreeRW(rw);
190 FS_FreeFile(musicBuf);
191 return;
192 }
193
194 Q_strncpyz(music.currentTrack, name, sizeof(music.currentTrack));
195 music.buffer = musicBuf;
196 if (Mix_FadeInMusic(music.data, 1, 1500) == -1)
197 Com_Printf("M_Start: Could not play music: 'music/%s' (%s)!\n", name, Mix_GetError());
198}
199
203static void M_Play_f (void)
204{
205 if (Cmd_Argc() == 2)
206 Cvar_Set("snd_music", "%s", Cmd_Argv(1));
207
208 M_Start(Cvar_GetString("snd_music"));
209}
210
214static void M_RandomTrack_f (void)
215{
216 if (!s_env.initialized || !music.playing)
217 return;
218
219 const int musicTrackCount = FS_BuildFileList("music/*.ogg");
220 if (musicTrackCount) {
221 int randomID = rand() % musicTrackCount;
222 Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: random track id: %i/%i\n", randomID, musicTrackCount);
223
224 const char* filename;
225 while ((filename = FS_NextFileFromFileList("music/*.ogg")) != nullptr) {
226 if (!randomID) {
227 const char* musicTrack = Com_SkipPath(filename);
228 Com_Printf("..playing next music track: '%s'\n", musicTrack);
229 Cvar_Set("snd_music", "%s", musicTrack);
230 }
231 randomID--;
232 }
234 } else {
235 Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: No music found!\n");
236 }
237}
238
239static bool M_PlayRandomByCategory (int category)
240{
241 if (category != MUSIC_BATTLESCAPE && CL_OnBattlescape())
242 return false;
243 if (!musicArrayLength[category])
244 return false;
245 const int rnd = rand() % musicArrayLength[category];
246 music.category = category;
247 Com_Printf("Music: track changed from %s to %s.\n", music.currentTrack, musicArrays[category][rnd]);
248 Cvar_Set("snd_music", "%s", musicArrays[category][rnd]);
249 return snd_music->modified;
250}
251
256static void M_Change_f (void)
257{
258 if (!s_env.initialized || !music.playing)
259 return;
260
261 if (Cmd_Argc() != 2) {
262 Com_Printf("Usage: %s <geoscape|battlescape|main|aircombat>\n", Cmd_Argv(0));
263 return;
264 }
265 const char* type = Cmd_Argv(1);
266 int category;
267 if (Q_streq(type, "geoscape")) {
268 category = MUSIC_GEOSCAPE;
269 } else if (Q_streq(type, "battlescape")) {
270 category = MUSIC_BATTLESCAPE;
271 } else if (Q_streq(type, "main")) {
272 category = MUSIC_MAIN;
273 } else if (Q_streq(type, "aircombat")) {
274 category = MUSIC_AIRCOMBAT;
275 } else {
276 Com_Printf("Invalid parameter given!\n");
277 return;
278 }
279
280 if (category != MUSIC_BATTLESCAPE && CL_OnBattlescape()) {
281 Com_DPrintf(DEBUG_SOUND, "Not changing music to %s - we are on the battlescape!\n", type);
282 return;
283 }
284
285 if (!musicArrayLength[category]) {
286 Com_Printf("M_Change_f: Could not find any %s themed music tracks!\n", type);
287 return;
288 }
289
290 M_PlayRandomByCategory(category);
291}
292
293static int M_CompleteMusic (const char* partial, const char** match)
294{
295 int n = 0;
296 while (char const* const filename = FS_NextFileFromFileList("music/*.ogg")) {
297 if (Cmd_GenericCompleteFunction(filename, partial, match)) {
298 Com_Printf("%s\n", filename);
299 ++n;
300 }
301 }
303 return n;
304}
305
306static void M_MusicStreamUpdate (void)
307{
308 if (music.interruptStream) {
309 music.interruptStream = false;
310 M_StopMusicStream(nullptr);
311 }
312}
313
314void M_Frame (void)
315{
316 if (snd_music_play && snd_music_play->modified) {
317 music.playing = snd_music_play->integer != 0;
318 snd_music_play->modified = false;
319 }
320 if (!music.playing) {
321 if (Mix_PlayingMusic())
322 M_Stop();
323 return;
324 }
325 if (snd_music->modified) {
326 M_Start(snd_music->string);
327 snd_music->modified = false;
328 }
329 if (snd_music_volume->modified) {
330 Mix_VolumeMusic(snd_music_volume->integer);
331 snd_music_volume->modified = false;
332 }
333
334 if (music.playingStream) {
336 } else if (!Mix_PlayingMusic()) {
337 M_Stop(); /* free the allocated memory */
338 if (Q_strvalid(music.nextTrack)) {
339 M_Start(music.nextTrack);
340 music.nextTrack[0] = '\0';
341 } else {
342 if (!M_PlayRandomByCategory(music.category))
343 M_Start(music.currentTrack);
344 }
345 }
346}
347
348static const cmdList_t musicCmds[] = {
349 {"music_play", M_Play_f, "Plays a music track."},
350 {"music_change", M_Change_f, "Changes the music theme (valid values:battlescape/geoscape/main/aircombat)."},
351 {"music_stop", M_Stop, "Stops currently playing music track."},
352 {"music_randomtrack", M_RandomTrack_f, "Plays a random music track."},
353 {nullptr, nullptr, nullptr}
354};
355
356void M_Init (void)
357{
358 if (Cmd_Exists("music_change"))
359 Cmd_RemoveCommand("music_change");
362 snd_music = Cvar_Get("snd_music", "PsymongN3", 0, "Background music track");
363 snd_music_volume = Cvar_Get("snd_music_volume", "128", CVAR_ARCHIVE, "Music volume - default is 128.");
364 snd_music_volume->modified = true;
365 snd_music_play = Cvar_Get ("snd_music_play", "1", CVAR_ARCHIVE, "Enable background music.");
366 music.playing = snd_music_play->integer != 0;
367}
368
369void M_Shutdown (void)
370{
371 M_Stop();
372
374}
375
376static void M_MusicStreamCallback (musicStream_t* userdata, byte* stream, int length)
377{
378 int tries = 0;
379 while (1) {
380 if (!userdata->playing) {
381 music.interruptStream = true;
382 return;
383 }
384 const int availableBytes = (userdata->mixerPos > userdata->samplePos) ? MAX_RAW_SAMPLES - userdata->mixerPos + userdata->samplePos : userdata->samplePos - userdata->mixerPos;
385 if (length < availableBytes)
386 break;
387 if (++tries > 50) {
388 userdata->playing = false;
389 return;
390 }
391 Sys_Sleep(10);
392 }
393
394 if (userdata->mixerPos + length <= MAX_RAW_SAMPLES) {
395 memcpy(stream, userdata->sampleBuf + userdata->mixerPos, length);
396 userdata->mixerPos += length;
397 userdata->mixerPos %= MAX_RAW_SAMPLES;
398 } else {
399 const int end = MAX_RAW_SAMPLES - userdata->mixerPos;
400 const int start = length - end;
401 memcpy(stream, userdata->sampleBuf + userdata->mixerPos, end);
402 memcpy(stream, userdata->sampleBuf, start);
403 userdata->mixerPos = start;
404 }
405}
406
407static void M_PlayMusicStream (musicStream_t* userdata)
408{
409 if (userdata->playing)
410 return;
411
412 M_Stop();
413
414 userdata->playing = true;
415 music.playingStream = true;
416 Mix_HookMusic((void (*)(void*, Uint8*, int)) M_MusicStreamCallback, userdata);
417}
418
428void M_AddToSampleBuffer (musicStream_t* userdata, int rate, int samples, const byte* data)
429{
430 if (!s_env.initialized)
431 return;
432
433 M_PlayMusicStream(userdata);
434
435 if (rate != s_env.rate) {
436 const float scale = (float)rate / s_env.rate;
437 for (int i = 0;; i++) {
438 const int src = i * scale;
439 short* ptr = (short*)&userdata->sampleBuf[userdata->samplePos];
440 if (src >= samples)
441 break;
442 *ptr = LittleShort(((const short*) data)[src * 2]);
443 ptr++;
444 *ptr = LittleShort(((const short*) data)[src * 2 + 1]);
445
446 userdata->samplePos += 4;
447 userdata->samplePos %= MAX_RAW_SAMPLES;
448 }
449 } else {
450 for (int i = 0; i < samples; i++) {
451 short* ptr = (short*)&userdata->sampleBuf[userdata->samplePos];
452 *ptr = LittleShort(((const short*) data)[i * 2]);
453 ptr++;
454 *ptr = LittleShort(((const short*) data)[i * 2 + 1]);
455
456 userdata->samplePos += 4;
457 userdata->samplePos %= MAX_RAW_SAMPLES;
458 }
459 }
460}
461
463{
464 if (userdata != nullptr)
465 userdata->playing = false;
466 music.playingStream = false;
467 music.interruptStream = false;
468 Mix_HookMusic(nullptr, nullptr);
469}
#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
Share stuff between the different cgame implementations.
Video driver defs.
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:440
void Com_Error(int code, const char *fmt,...)
Definition common.cpp:459
void Com_Printf(const char *const fmt,...)
Definition common.cpp:428
definitions common between client and server, but not game lib
#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
Filesystem header file.
#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
Shared parsing functions.
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:214
static void M_PlayMusicStream(musicStream_t *userdata)
Definition s_music.cpp:407
static int M_CompleteMusic(const char *partial, const char **match)
Definition s_music.cpp:293
static void M_MusicStreamCallback(musicStream_t *userdata, byte *stream, int length)
Definition s_music.cpp:376
static char * musicArrays[MUSIC_MAX][MUSIC_MAX_ENTRIES]
Definition s_music.cpp:62
void M_StopMusicStream(musicStream_t *userdata)
Definition s_music.cpp:462
static cvar_t * snd_music_play
Definition s_music.cpp:67
static const cmdList_t musicCmds[]
Definition s_music.cpp:348
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:356
static void M_Change_f(void)
Changes the music if it suits the current situation.
Definition s_music.cpp:256
static void M_Play_f(void)
Plays the music file given via commandline parameter.
Definition s_music.cpp:203
static music_t music
Definition s_music.cpp:64
void M_Frame(void)
Definition s_music.cpp:314
@ 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
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:428
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:369
#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:239
static void M_MusicStreamUpdate(void)
Definition s_music.cpp:306
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
Header for script parsing functions.
#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
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition cvar.h:71
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 samplePos
Definition s_music.h:41
byte sampleBuf[MAX_RAW_SAMPLES]
Definition s_music.h:39
System specific stuff.
void Sys_Sleep(int milliseconds)
Calls the win32 sleep function.
#define UFO_SIZE_T
Definition ufotypes.h:89
static const vec3_t scale