UFO: Alien Invasion
Doxygen documentation generating
s_music.cpp
Go to the documentation of this file.
1 
5 /*
6 All original material Copyright (C) 2002-2023 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, 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 
39 enum {
44 
46 };
47 
48 typedef struct music_s {
49  char currentTrack[MAX_QPATH];
50  char nextTrack[MAX_QPATH];
51  Mix_Music* data;
52  int category;
58  bool playing;
59 } music_t;
60 
61 #define MUSIC_MAX_ENTRIES 64
62 static char* musicArrays[MUSIC_MAX][MUSIC_MAX_ENTRIES] = {{}, {}};
63 static int musicArrayLength[MUSIC_MAX] = {};
64 static music_t music = {{}, {}, nullptr, 0, nullptr, false, false, true};
65 static cvar_t* snd_music;
68 
73 void M_ParseMusic (const char* name, const char** text)
74 {
75  int i;
76 
77  if (Q_streq(name, "geoscape"))
78  i = MUSIC_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);
105  musicArrayLength[i]++;
106  }
107 
108  LIST_Delete(&list);
109 }
110 
114 void 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)
127 
128  music.data = nullptr;
129  music.buffer = nullptr;
130 }
131 
135 static 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 
206 static 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 
217 static void M_RandomTrack_f (void)
218 {
219  if (!s_env.initialized || !music.playing)
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  }
236  FS_NextFileFromFileList(nullptr);
237  } else {
238  Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: No music found!\n");
239  }
240 }
241 
242 static 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 
259 static void M_Change_f (void)
260 {
261  if (!s_env.initialized || !music.playing)
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 
296 static 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  }
305  FS_NextFileFromFileList(nullptr);
306  return n;
307 }
308 
309 static void M_MusicStreamUpdate (void)
310 {
311  if (music.interruptStream) {
312  music.interruptStream = false;
313  M_StopMusicStream(nullptr);
314  }
315 }
316 
317 void 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  }
332  if (snd_music_volume->modified) {
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 */
341  if (Q_strvalid(music.nextTrack)) {
343  music.nextTrack[0] = '\0';
344  } else {
347  }
348  }
349 }
350 
351 static 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 
359 void 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.");
367  snd_music_volume->modified = true;
368  snd_music_play = Cvar_Get ("snd_music_play", "1", CVAR_ARCHIVE, "Enable background music.");
370 }
371 
372 void M_Shutdown (void)
373 {
374  M_Stop();
375 
377 }
378 
379 static 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 
410 static 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 
431 void 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 }
int mixerPos
Definition: s_music.h:40
bool playing
Definition: s_music.h:38
bool Q_strnull(const char *string)
Definition: shared.h:138
bool initialized
Definition: s_local.h:70
const char * Cmd_Argv(int arg)
Returns a given argument.
Definition: cmd.cpp:516
static void M_MusicStreamCallback(musicStream_t *userdata, byte *stream, int length)
Definition: s_music.cpp:379
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 rate
Definition: s_local.h:66
char nextTrack[MAX_QPATH]
Definition: s_music.cpp:50
void Cmd_RemoveCommand(const char *cmdName)
Removes a command from script interface.
Definition: cmd.cpp:786
static void M_RandomTrack_f(void)
Sets the music cvar to a random track.
Definition: s_music.cpp:217
QGL_EXTERN GLint GLenum type
Definition: r_gl.h:94
static void M_PlayMusicStream(musicStream_t *userdata)
Definition: s_music.cpp:410
const char * Com_SkipPath(const char *pathname)
Returns just the filename from a given path.
Definition: shared.cpp:37
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...
const char * va(const char *format,...)
does a varargs printf into a temp buffer, so I don&#39;t need to have varargs versions of all text functi...
Definition: shared.cpp:410
void M_ParseMusic(const char *name, const char **text)
Parses music definitions for different situations.
Definition: s_music.cpp:73
static const vec3_t scale
static bool M_PlayRandomByCategory(int category)
Definition: s_music.cpp:242
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 * filename
Definition: ioapi.h:41
int integer
Definition: cvar.h:81
static char * musicArrays[MUSIC_MAX][MUSIC_MAX_ENTRIES]
Definition: s_music.cpp:62
voidpf void uLong size
Definition: ioapi.h:42
char currentTrack[MAX_QPATH]
Definition: s_music.cpp:49
QGL_EXTERN GLsizei const GLvoid * data
Definition: r_gl.h:89
int FS_LoadFile(const char *path, byte **buffer)
Filenames are relative to the quake search path.
Definition: files.cpp:384
void Com_Printf(const char *const fmt,...)
Definition: common.cpp:386
static void M_Change_f(void)
Changes the music if it suits the current situation.
Definition: s_music.cpp:259
bool Cmd_GenericCompleteFunction(char const *candidate, char const *partial, char const **match)
Definition: cmd.cpp:648
#define MUSIC_MAX_ENTRIES
Definition: s_music.cpp:61
void M_Stop(void)
Definition: s_music.cpp:114
int FS_BuildFileList(const char *fileList)
Build a filelist.
Definition: files.cpp:962
void LIST_Delete(linkedList_t **list)
Definition: list.cpp:195
Mix_Music * data
Definition: s_music.cpp:51
static int M_CompleteMusic(const char *partial, const char **match)
Definition: s_music.cpp:296
#define CVAR_ARCHIVE
Definition: cvar.h:40
void Cmd_TableRemoveList(const cmdList_t *cmdList)
Definition: cmd.cpp:859
static cvar_t * snd_music
Definition: s_music.cpp:65
memPool_t * cl_genericPool
Definition: cl_main.cpp:86
#define Q_strvalid(string)
Definition: shared.h:141
static cvar_t * snd_music_volume
Definition: s_music.cpp:66
void Com_Error(int code, const char *fmt,...)
Definition: common.cpp:417
static int musicArrayLength[MUSIC_MAX]
Definition: s_music.cpp:63
void M_Init(void)
Definition: s_music.cpp:359
void Sys_Sleep(int milliseconds)
Calls the win32 sleep function.
Definition: unix_shared.cpp:68
void Q_strncpyz(char *dest, const char *src, size_t destsize)
Safe strncpy that ensures a trailing zero.
Definition: shared.cpp:457
int category
Definition: s_music.cpp:52
void M_Shutdown(void)
Definition: s_music.cpp:372
#define LittleShort(X)
Definition: byte.h:35
#define ERR_DROP
Definition: common.h:211
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
QGL_EXTERN GLuint GLsizei GLsizei * length
Definition: r_gl.h:110
static void M_Start(const char *file)
Definition: s_music.cpp:135
bool Cmd_Exists(const char *cmdName)
Checks whether a function exists already.
Definition: cmd.cpp:887
static void M_Play_f(void)
Plays the music file given via commandline parameter.
Definition: s_music.cpp:206
s_env_t s_env
Definition: s_main.cpp:40
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
byte * buffer
Definition: s_music.cpp:53
static void M_MusicStreamUpdate(void)
Definition: s_music.cpp:309
linkedList_t * next
Definition: list.h:32
void M_StopMusicStream(musicStream_t *userdata)
Definition: s_music.cpp:465
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
bool interruptStream
Definition: s_music.cpp:57
#define MAX_RAW_SAMPLES
Definition: s_music.h:35
voidpf stream
Definition: ioapi.h:42
bool Com_ParseList(const char **text, linkedList_t **list)
Definition: scripts.cpp:1363
#define MAX_QPATH
Definition: filesys.h:40
QGL_EXTERN GLint i
Definition: r_gl.h:113
QGL_EXTERN GLuint GLchar GLuint * len
Definition: r_gl.h:99
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
QGL_EXTERN GLuint GLsizei GLsizei GLint GLenum GLchar * name
Definition: r_gl.h:110
static cvar_t * snd_music_play
Definition: s_music.cpp:67
#define UFO_SIZE_T
Definition: ufotypes.h:89
bool playing
Definition: s_music.cpp:58
bool playingStream
Definition: s_music.cpp:54
void Cmd_AddParamCompleteFunction(const char *cmdName, int(*function)(const char *partial, const char **match))
Definition: cmd.cpp:679
static music_t music
Definition: s_music.cpp:64
static const cmdList_t musicCmds[]
Definition: s_music.cpp:351
#define DEBUG_SOUND
Definition: defines.h:63
cvar_t * Cvar_Set(const char *varName, const char *value,...)
Sets a cvar value.
Definition: cvar.cpp:615
const char * Cvar_GetString(const char *varName)
Returns the value of cvar as string.
Definition: cvar.cpp:210
#define Q_streq(a, b)
Definition: shared.h:136
#define Mem_PoolStrDup(in, pool, tagNum)
Definition: mem.h:50
int samplePos
Definition: s_music.h:41
bool modified
Definition: cvar.h:79
void M_Frame(void)
Definition: s_music.cpp:317
Specifies music API.
uint8_t byte
Definition: ufotypes.h:34
This is a cvar definition. Cvars can be user modified and used in our menus e.g.
Definition: cvar.h:71
byte sampleBuf[MAX_RAW_SAMPLES]
Definition: s_music.h:39
void Cmd_TableAddList(const cmdList_t *cmdList)
Definition: cmd.cpp:853
void FS_FreeFile(void *buffer)
Definition: files.cpp:411
Definition: cmd.h:86
char * string
Definition: cvar.h:73