00001
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025 #include "s_local.h"
00026 #include "s_main.h"
00027 #include "s_music.h"
00028 #include "../../shared/parse.h"
00029 #include "../../ports/system.h"
00030
00031 enum {
00032 MUSIC_MAIN,
00033 MUSIC_GEOSCAPE,
00034 MUSIC_BATTLESCAPE,
00035 MUSIC_AIRCOMBAT,
00036
00037 MUSIC_MAX
00038 };
00039
00040 typedef struct music_s {
00041 char currentTrack[MAX_QPATH];
00042 char nextTrack[MAX_QPATH];
00043 Mix_Music *data;
00044 byte *buffer;
00045 qboolean playingStream;
00048 } music_t;
00049
00050 #define MUSIC_MAX_ENTRIES 64
00051 static char *musicArrays[MUSIC_MAX][MUSIC_MAX_ENTRIES];
00052 static int musicArrayLength[MUSIC_MAX];
00053 static music_t music;
00054 static int musicTrackCount;
00055 static cvar_t *snd_music;
00056 static cvar_t *snd_music_volume;
00057
00062 void M_ParseMusic (const char *name, const char **text)
00063 {
00064 const char *errhead = "M_ParseMusic: unexpected end of file (campaign ";
00065 const char *token;
00066 int i;
00067
00068 if (!strcmp(name, "geoscape"))
00069 i = MUSIC_GEOSCAPE;
00070 else if (!strcmp(name, "battlescape"))
00071 i = MUSIC_BATTLESCAPE;
00072 else if (!strcmp(name, "aircombat"))
00073 i = MUSIC_AIRCOMBAT;
00074 else if (!strcmp(name, "main"))
00075 i = MUSIC_MAIN;
00076 else {
00077 Com_Printf("M_ParseMusic: Invalid music id '%s'\n", name);
00078 FS_SkipBlock(text);
00079 return;
00080 }
00081
00082
00083 token = Com_Parse(text);
00084
00085 if (!*text || *token != '{') {
00086 Com_Printf("M_ParseMusic: music def \"%s\" without body ignored\n", name);
00087 return;
00088 }
00089
00090 do {
00091 token = Com_EParse(text, errhead, name);
00092 if (!*text)
00093 break;
00094 if (*token == '}')
00095 break;
00096 if (musicArrayLength[i] >= MUSIC_MAX_ENTRIES) {
00097 Com_Printf("M_ParseMusic: Too many music entries for category: '%s'\n", name);
00098 FS_SkipBlock(text);
00099 break;
00100 }
00101 musicArrays[i][musicArrayLength[i]] = Mem_PoolStrDup(token, cl_genericPool, 0);
00102 musicArrayLength[i]++;
00103 } while (*text);
00104 }
00105
00109 void M_Stop (void)
00110 {
00111
00112 if (music.playingStream)
00113 return;
00114
00115 if (music.data != NULL) {
00116 Mix_HaltMusic();
00117 Mix_FreeMusic(music.data);
00118 }
00119
00120 if (music.buffer)
00121 FS_FreeFile(music.buffer);
00122
00123 music.data = NULL;
00124 music.buffer = NULL;
00125 }
00126
00130 static void M_Start (const char *file)
00131 {
00132 char name[MAX_QPATH];
00133 size_t len;
00134 byte *musicBuf;
00135 int size;
00136 SDL_RWops *rw;
00137
00138 if (!file || file[0] == '\0')
00139 return;
00140
00141 if (!s_env.initialized) {
00142 Com_Printf("M_Start: no sound started\n");
00143 return;
00144 }
00145
00146 if (music.playingStream)
00147 return;
00148
00149 Com_StripExtension(file, name, sizeof(name));
00150 len = strlen(name);
00151 if (len + 4 >= MAX_QPATH) {
00152 Com_Printf("M_Start: MAX_QPATH exceeded: "UFO_SIZE_T"\n", len + 4);
00153 return;
00154 }
00155
00156
00157 if (!strcmp(name, music.currentTrack))
00158 return;
00159
00160
00161 if (music.data && Mix_PlayingMusic()) {
00162 if (!Mix_FadeOutMusic(1500))
00163 M_Stop();
00164 Q_strncpyz(music.nextTrack, name, sizeof(music.nextTrack));
00165 return;
00166 }
00167
00168
00169 M_Stop();
00170
00171
00172 if ((size = FS_LoadFile(va("music/%s.ogg", name), &musicBuf)) == -1) {
00173 Com_Printf("M_Start: Could not load '%s' background track\n", name);
00174 return;
00175 }
00176
00177 rw = SDL_RWFromMem(musicBuf, size);
00178 if (!rw) {
00179 Com_Printf("M_Start: Could not load music: 'music/%s'\n", name);
00180 FS_FreeFile(musicBuf);
00181 return;
00182 }
00183 music.data = Mix_LoadMUS_RW(rw);
00184 if (!music.data) {
00185 Com_Printf("M_Start: Could not load music: 'music/%s' (%s)\n", name, Mix_GetError());
00186 SDL_FreeRW(rw);
00187 FS_FreeFile(musicBuf);
00188 return;
00189 }
00190
00191 Q_strncpyz(music.currentTrack, name, sizeof(music.currentTrack));
00192 music.buffer = musicBuf;
00193 if (Mix_FadeInMusic(music.data, -1, 1500) == -1)
00194 Com_Printf("M_Start: Could not play music: 'music/%s' (%s)\n", name, Mix_GetError());
00195 }
00196
00200 static void M_Play_f (void)
00201 {
00202 if (Cmd_Argc() == 2)
00203 Cvar_Set("snd_music", Cmd_Argv(1));
00204
00205 M_Start(Cvar_GetString("snd_music"));
00206 }
00207
00211 static void M_RandomTrack_f (void)
00212 {
00213 const char *filename;
00214 char findname[MAX_OSPATH];
00215 const char *musicTrack;
00216
00217 if (!s_env.initialized)
00218 return;
00219
00220 musicTrackCount = FS_BuildFileList("music/*.ogg");
00221 if (musicTrackCount) {
00222 int randomID = rand() % musicTrackCount;
00223 Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: random track id: %i/%i\n", randomID, musicTrackCount);
00224
00225 while ((filename = FS_NextFileFromFileList("music/*.ogg")) != NULL) {
00226 if (!randomID) {
00227 Com_sprintf(findname, sizeof(findname), "%s", filename);
00228 musicTrack = Com_SkipPath(findname);
00229 Com_Printf("..playing next: '%s'\n", musicTrack);
00230 Cvar_Set("snd_music", musicTrack);
00231 }
00232 randomID--;
00233 }
00234 FS_NextFileFromFileList(NULL);
00235 } else {
00236 Com_DPrintf(DEBUG_SOUND, "M_RandomTrack_f: No musics found!\n");
00237 }
00238 }
00239
00244 static void M_Change_f (void)
00245 {
00246 const char* type;
00247 int rnd;
00248 int category;
00249
00250 if (!s_env.initialized)
00251 return;
00252
00253 if (Cmd_Argc() != 2) {
00254 Com_Printf("Usage: %s <geoscape|battlescape|main|aircombat>\n", Cmd_Argv(0));
00255 return;
00256 }
00257 type = Cmd_Argv(1);
00258 if (!strcmp(type, "geoscape")) {
00259 category = MUSIC_GEOSCAPE;
00260 } else if (!strcmp(type, "battlescape")) {
00261 category = MUSIC_BATTLESCAPE;
00262 } else if (!strcmp(type, "main")) {
00263 category = MUSIC_MAIN;
00264 } else if (!strcmp(type, "aircombat")) {
00265 category = MUSIC_AIRCOMBAT;
00266 } else {
00267 Com_Printf("Invalid parameter given\n");
00268 return;
00269 }
00270
00271 if (category != MUSIC_BATTLESCAPE && cls.state == ca_active) {
00272 Com_DPrintf(DEBUG_SOUND, "Not changing music to %s - we are in Battlescape\n", type);
00273 return;
00274 }
00275
00276 if (!musicArrayLength[category]) {
00277 Com_Printf("Could not find any %s music tracks\n", type);
00278 return;
00279 }
00280 rnd = rand() % musicArrayLength[category];
00281 Com_Printf("music change to %s (from %s)\n", musicArrays[category][rnd], snd_music->string);
00282 Cvar_Set("snd_music", musicArrays[category][rnd]);
00283 }
00284
00285 static int M_CompleteMusic (const char *partial, const char **match)
00286 {
00287 const char *filename;
00288 int matches = 0;
00289 const char *localMatch[MAX_COMPLETE];
00290 size_t len;
00291
00292 FS_BuildFileList("music/*.ogg");
00293
00294 len = strlen(partial);
00295 if (!len) {
00296 while ((filename = FS_NextFileFromFileList("music/*.ogg")) != NULL) {
00297 Com_Printf("%s\n", filename);
00298 }
00299 FS_NextFileFromFileList(NULL);
00300 return 0;
00301 }
00302
00303
00304 FS_NextFileFromFileList(NULL);
00305
00306
00307 while ((filename = FS_NextFileFromFileList("music/*.ogg")) != NULL) {
00308 if (!strncmp(partial, filename, len)) {
00309 Com_Printf("%s\n", filename);
00310 localMatch[matches++] = filename;
00311 if (matches >= MAX_COMPLETE)
00312 break;
00313 }
00314 }
00315 FS_NextFileFromFileList(NULL);
00316
00317 return Cmd_GenericCompleteFunction(len, match, matches, localMatch);
00318 }
00319
00320 void M_Frame (void)
00321 {
00322 if (snd_music->modified) {
00323 M_Start(snd_music->string);
00324 snd_music->modified = qfalse;
00325 }
00326 if (snd_music_volume->modified) {
00327 Mix_VolumeMusic(snd_music_volume->integer);
00328 snd_music_volume->modified = qfalse;
00329 }
00330 if (!music.playingStream && music.nextTrack[0] != '\0') {
00331 if (!Mix_PlayingMusic()) {
00332 M_Stop();
00333 M_Start(music.nextTrack);
00334 music.nextTrack[0] = '\0';
00335 }
00336 }
00337 }
00338
00339 void M_Init (void)
00340 {
00341 if (Cmd_Exists("music_change"))
00342 Cmd_RemoveCommand("music_change");
00343 Cmd_AddCommand("music_play", M_Play_f, "Plays a background sound track");
00344 Cmd_AddCommand("music_change", M_Change_f, "Changes the music theme");
00345 Cmd_AddCommand("music_stop", M_Stop, "Stops currently playing music tracks");
00346 Cmd_AddCommand("music_randomtrack", M_RandomTrack_f, "Plays a random background track");
00347 Cmd_AddParamCompleteFunction("music_play", M_CompleteMusic);
00348 snd_music = Cvar_Get("snd_music", "PsymongN3", 0, "Background music track");
00349 snd_music_volume = Cvar_Get("snd_music_volume", "128", CVAR_ARCHIVE, "Music volume - default is 128");
00350 snd_music_volume->modified = qtrue;
00351
00352 memset(&music, 0, sizeof(music));
00353 }
00354
00355 void M_Shutdown (void)
00356 {
00357 M_Stop();
00358
00359 Cmd_RemoveCommand("music_play");
00360 Cmd_RemoveCommand("music_change");
00361 Cmd_RemoveCommand("music_stop");
00362 Cmd_RemoveCommand("music_randomtrack");
00363 }
00364
00365 static void M_MusicStreamCallback (musicStream_t *userdata, byte *stream, int length)
00366 {
00367 while (1) {
00368 const int availableBytes = (userdata->mixerPos > userdata->samplePos) ? MAX_RAW_SAMPLES - userdata->mixerPos + userdata->samplePos : userdata->samplePos - userdata->mixerPos;
00369 if (!userdata->playing)
00370 return;
00371 if (length < availableBytes)
00372 break;
00373 Sys_Sleep(10);
00374 }
00375
00376 if (userdata->mixerPos + length <= MAX_RAW_SAMPLES) {
00377 memcpy(stream, userdata->sampleBuf + userdata->mixerPos, length);
00378 userdata->mixerPos += length;
00379 userdata->mixerPos %= MAX_RAW_SAMPLES;
00380 } else {
00381 const int end = MAX_RAW_SAMPLES - userdata->mixerPos;
00382 const int start = length - end;
00383 memcpy(stream, userdata->sampleBuf + userdata->mixerPos, end);
00384 memcpy(stream, userdata->sampleBuf, start);
00385 userdata->mixerPos = start;
00386 }
00387 }
00388
00398 void M_AddToSampleBuffer (musicStream_t *userdata, int rate, int samples, const byte *data)
00399 {
00400 int i;
00401
00402 if (!s_env.initialized)
00403 return;
00404
00405 if (rate != s_env.rate) {
00406 const float scale = (float)rate / s_env.rate;
00407 for (i = 0;; i++) {
00408 const int src = i * scale;
00409 short *ptr = (short *)&userdata->sampleBuf[userdata->samplePos];
00410 if (src >= samples)
00411 break;
00412 *ptr = LittleShort(((const short *) data)[src * 2]);
00413 ptr++;
00414 *ptr = LittleShort(((const short *) data)[src * 2 + 1]);
00415
00416 userdata->samplePos += 4;
00417 userdata->samplePos %= MAX_RAW_SAMPLES;
00418 }
00419 } else {
00420 for (i = 0; i < samples; i++) {
00421 short *ptr = (short *)&userdata->sampleBuf[userdata->samplePos];
00422 *ptr = LittleShort(((const short *) data)[i * 2]);
00423 ptr++;
00424 *ptr = LittleShort(((const short *) data)[i * 2 + 1]);
00425
00426 userdata->samplePos += 4;
00427 userdata->samplePos %= MAX_RAW_SAMPLES;
00428 }
00429 }
00430 }
00431
00432 void M_PlayMusicStream (musicStream_t *userdata)
00433 {
00434 M_Stop();
00435
00436 userdata->playing = qtrue;
00437 music.playingStream = qtrue;
00438 Mix_HookMusic((void (*)(void*, Uint8*, int)) M_MusicStreamCallback, userdata);
00439 }
00440
00441 void M_StopMusicStream (musicStream_t *userdata)
00442 {
00443 userdata->playing = qfalse;
00444 music.playingStream = qfalse;
00445 Mix_HookMusic(NULL, NULL);
00446 }
00447