s_music.c

Go to the documentation of this file.
00001 
00005 /*
00006 All original material Copyright (C) 2002-2010 UFO: Alien Invasion.
00007 
00008 This program is free software; you can redistribute it and/or
00009 modify it under the terms of the GNU General Public License
00010 as published by the Free Software Foundation; either version 2
00011 of the License, or (at your option) any later version.
00012 
00013 This program is distributed in the hope that it will be useful,
00014 but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00016 
00017 See the GNU General Public License for more details.
00018 
00019 You should have received a copy of the GNU General Public License
00020 along with this program; if not, write to the Free Software
00021 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
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     /* get it's body */
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     /* we should not even have a buffer nor data set - but ... just to be sure */
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     /* we are already playing that track */
00157     if (!strcmp(name, music.currentTrack))
00158         return;
00159 
00160     /* we are still playing some background track - fade it out */
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     /* make really sure the last track is closed and freed */
00169     M_Stop();
00170 
00171     /* load it in */
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     /* start from first file entry */
00304     FS_NextFileFromFileList(NULL);
00305 
00306     /* check for partial matches */
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(); /* free the allocated memory */
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 

Generated by  doxygen 1.6.2