#include "sound.h" #if ENABLE_SOUND #define FMOD_DYN_IMPL #define FMOD_DYN_NOASSERT #include "cake.h" #include "vars.h" #include "commands.h" #include "console.h" #include "system.h" #include "files.h" #include "types.h" #include "math.h" #include "timer.h" #if defined(WIN32) || defined(__WATCOMC__) #include #include #pragma comment(lib, "cake/fmod/lib/fmodvc.lib") #pragma warning(disable : 4311) /* pointer trunctation from 'FARPROC' to 'unsigned int' */ #elif defined(__linux__) #include "fmod/wincompat.h" #endif #include "fmod/fmod.h" #include "fmod/fmoddyn.h" #include "fmod/fmod_errors.h" int musicvolume = 64; int soundvolume = 128; int mixrate = 22050; #define MAX_SOFTWARE_CHANNELS 256 // max is 1024 #define BGMUSIC_CHANNELS 2 #define SOUND_CHANNELS (MAX_SOFTWARE_CHANNELS-BGMUSIC_CHANNELS) #define MAX_SOUNDNAME_LENGTH 256 typedef struct { FSOUND_SAMPLE *sample; int channel; bool loop; char name[MAX_SOUNDNAME_LENGTH]; bool free; // sound is free } sound_t; sound_t bgmusic[BGMUSIC_CHANNELS]; bool playing_intro = false; sound_t sounds[SOUND_CHANNELS]; Var snd_mindistance("snd_mindistance", 64.f); Var snd_maxdistance("snd_maxdistance", 16384.f); void cmd_musicvolume(int argc, char *argv[]) { if (argc > 1) { musicvolume = atol(argv[1]); if (musicvolume < 0) musicvolume = 0; if (musicvolume > 255) musicvolume = 255; if (bgmusic[0].channel != -1) FSOUND_SetVolume(bgmusic[0].channel, musicvolume); if (bgmusic[1].channel != -1) FSOUND_SetVolume(bgmusic[1].channel, musicvolume); } else { gConsole->Insertln("usage: %s ", argv[0]); } gConsole->Insertln("current music volume is %d", musicvolume); } void cmd_soundvolume(int argc, char *argv[]) { if (argc > 1) { soundvolume = atol(argv[1]); if (soundvolume < 0) soundvolume = 0; if (soundvolume > 255) soundvolume = 255; for (int i = 0; i < SOUND_CHANNELS; ++i) if (sounds[i].channel != -1) FSOUND_SetVolume(sounds[i].channel, soundvolume); } else { gConsole->Insertln("usage: %s ", argv[0]); } gConsole->Insertln("current sound volume is %d", soundvolume); } void cmd_setbgmusic(int argc, char *argv[]) { if (argc > 1) { char args[256] = { '\0' }; if (argc > 2) sprintf(args, "%s %s", argv[1], argv[2]); else strcpy(args, argv[1]); setBGMusic(args); } else gConsole->Insertln("usage: %s []", argv[0]); } void cmd_stopbgmusic(int argc, char *argv[]) { freeBGMusic(); } void cmd_playsound(int argc, char *argv[]) { if (argc > 1) { if (argc > 4) { vec3_t pos; pos[0] = (float) atof(argv[2]); pos[1] = (float) atof(argv[3]); pos[2] = (float) atof(argv[4]); if (argc > 5 && atoi(argv[5])) loadSound(argv[1], pos, true, true, false); else loadSound(argv[1], pos, false, true, false); } else { if (argc > 2 && atoi(argv[5])) loadSound(argv[1], NULL, true, true, false); else loadSound(argv[1], NULL, false, true, false); } } else gConsole->Insertln("usage: %s [ ] []", argv[0]); } void cmd_snd_distancefactor(int argc, char *argv[]) { if (argc > 1) { FSOUND_3D_SetDistanceFactor((float) atof(argv[1])); FSOUND_Update(); } else { gConsole->Insertln("usage: %s ", argv[0]); } } #endif bool initSoundSystem(void) { #if !ENABLE_SOUND return false; #else int i; gCommands->AddCommand("musicvolume", cmd_musicvolume, "Sets the music volume. The valid volume range is defined between 0 to 255."); gCommands->AddCommand("setbgmusic", cmd_setbgmusic, "Set the current background music. If command has 2 parameters, first music is played once as intro and second is played in loop."); gCommands->AddCommand("stopbgmusic", cmd_stopbgmusic, "Stops the current background music."); gCommands->AddCommand("soundvolume", cmd_soundvolume, "Sets the sound volume. The valid volume range is defined between 0 to 255."); gCommands->AddCommand("playsound", cmd_playsound, "Play a sound."); gCommands->AddCommand("snd_distancefactor", cmd_snd_distancefactor, "Sets the 3d sound distance factor."); gVars->RegisterVar(snd_mindistance); gVars->RegisterVar(snd_maxdistance); gConsole->Insertln("
^6----- Initializing sound system -------------------------------"); for (i = 0; i < BGMUSIC_CHANNELS; ++i) { bgmusic[i].channel = -1; bgmusic[i].sample = NULL; memset(bgmusic[i].name, '\0', MAX_SOUNDNAME_LENGTH*sizeof(char)); // not used for background music bgmusic[i].loop = false; // not used for background music bgmusic[i].free = true; } for (i = 0; i < SOUND_CHANNELS; ++i) { sounds[i].channel = -1; sounds[i].sample = NULL; memset(sounds[i].name, '\0', MAX_SOUNDNAME_LENGTH*sizeof(char)); sounds[i].loop = false; sounds[i].free = true; } if (FSOUND_GetVersion() < FMOD_VERSION) { gConsole->Insertln("^1ERROR: You are using the wrong DLL version!"); gConsole->Insertln("^1You should be using FMOD %.02f", FMOD_VERSION); return true; } else { #if defined(WIN32) || defined(__CYGWIN32__) || defined(__WATCOMC__) FSOUND_SetOutput(FSOUND_OUTPUT_WINMM); //FSOUND_SetOutput(FSOUND_OUTPUT_DSOUND); //FSOUND_SetOutput(FSOUND_OUTPUT_ASIO); //FSOUND_SetOutput(FSOUND_OUTPUT_A3D); #elif defined(__linux__) FSOUND_SetOutput(FSOUND_OUTPUT_OSS); //FSOUND_SetOutput(FSOUND_OUTPUT_ESD); //FSOUND_SetOutput(FSOUND_OUTPUT_ALSA); #endif //FSOUND_SetOutput(FSOUND_OUTPUT_NOSOUND); switch (FSOUND_GetOutput()) { case FSOUND_OUTPUT_NOSOUND: gConsole->Insertln("NoSound"); break; #if defined(WIN32) || defined(__CYGWIN32__) || defined(__WATCOMC__) case FSOUND_OUTPUT_WINMM: gConsole->Insertln("Using Windows Multimedia Waveout output"); break; case FSOUND_OUTPUT_DSOUND: gConsole->Insertln("Using Direct Sound output"); break; case FSOUND_OUTPUT_ASIO: gConsole->Insertln("Using ASIO output"); break; case FSOUND_OUTPUT_A3D: gConsole->Insertln("Using A3D output"); break; #elif defined(__linux__) case FSOUND_OUTPUT_OSS: gConsole->Insertln("Using Open Sound System output"); break; case FSOUND_OUTPUT_ESD: gConsole->Insertln("Using Enlightment Sound Daemon output"); break; case FSOUND_OUTPUT_ALSA: gConsole->Insertln("Using Alsa output"); break; #endif default: gConsole->Insertln("^5WARNING: Not using any output !"); break; } if (FSOUND_Init(mixrate, MAX_SOFTWARE_CHANNELS, 0)) { gConsole->Insertln("fmod %.2f successfully loaded, sound enabled", FMOD_VERSION); gConsole->Insertln("current settings:"); gConsole->Insertln("\tsound volume: %d", soundvolume); gConsole->Insertln("\tmusic volume: %d", musicvolume); gConsole->Insertln("\tmix rate: %d", mixrate); gConsole->Insertln("\tmax software channels: %d", MAX_SOFTWARE_CHANNELS); gConsole->Insertln("sound driver list:"); for (i = 0; i < FSOUND_GetNumDrivers(); ++i) { gConsole->Insertln("\t%s", FSOUND_GetDriverName(i)); // print driver names unsigned int caps = 0; FSOUND_GetDriverCaps(i, &caps); if (caps & FSOUND_CAPS_HARDWARE) gConsole->Insertln("\t\t * Driver supports hardware 3D sound"); if (caps & FSOUND_CAPS_EAX2) gConsole->Insertln("\t\t * Driver supports EAX 2 reverb"); if (caps & FSOUND_CAPS_EAX3) gConsole->Insertln("\t\t * Driver supports EAX 3 reverb"); } FSOUND_3D_SetDistanceFactor(64.f); FSOUND_Update(); return true; } else { gConsole->Insertln("^1ERROR: sound initialisation failed"); return false; } } #endif } void shutdownSoundSystem(void) { #if !ENABLE_SOUND return; #else freeBGMusic(); freeSound(); FSOUND_StopSound(FSOUND_ALL); FSOUND_Close(); gCommands->RemoveCommand("musicvolume"); gCommands->RemoveCommand("setbgmusic"); gCommands->RemoveCommand("stopbgmusic"); gCommands->RemoveCommand("soundvolume"); gCommands->RemoveCommand("playsound"); gCommands->RemoveCommand("snd_distancefactor"); gVars->UnregisterVar(snd_mindistance); gVars->UnregisterVar(snd_maxdistance); #endif } void setBGMusic(const char* filename, bool start_paused) { #if !ENABLE_SOUND return; #else freeBGMusic(); if (!filename || !strlen(filename)) return; int argc = GetNArgs(filename); int errnum = 0; if (argc > 1) { int l = (int) strlen(filename); char *tmp = new char[l+1]; if (!tmp) ThrowException(ALLOCATION_ERROR, "setBGMusic.tmp"); memset(tmp, '\0', l+1); GetArg(filename, 0, tmp); VFile *file_intro = new VFile(tmp); if (!file_intro) ThrowException(ALLOCATION_ERROR, "setBGMusic.file_intro"); if (!file_intro->mem) { gConsole->Insertln("^5WARNING: setBackgroundMusic() could not open file %s", file_intro->fname); delete file_intro; return; } memset(tmp, '\0', l+1); GetArg(filename, 1, tmp); VFile *file_loop = new VFile(tmp); if (!file_loop) ThrowException(ALLOCATION_ERROR, "setBGMusic.file_loop"); if (!file_loop->mem) { gConsole->Insertln("^5WARNING: setBackgroundMusic() could not open file %s", file_loop->fname); delete file_loop; return; } bgmusic[0].sample = FSOUND_Sample_Load(FSOUND_FREE, (char*) file_loop->mem, FSOUND_LOADMEMORY|FSOUND_LOOP_NORMAL|FSOUND_2D, file_loop->size); delete file_loop; if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Load(%s): %s", filename, GetSoundErrorString(errnum)); return; } bgmusic[1].sample = FSOUND_Sample_Load(FSOUND_FREE, (char*) file_intro->mem, FSOUND_LOADMEMORY|FSOUND_2D, file_intro->size); delete file_intro; if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Load(%s): %s", filename, GetSoundErrorString(errnum)); return; } if (bgmusic[1].sample) { bgmusic[1].channel = FSOUND_PlaySoundEx(FSOUND_FREE, bgmusic[1].sample, NULL, TRUE); if (bgmusic[1].channel == -1) { bgmusic[1].free = true; gConsole->Insertln("^1setBGMusic()->FSOUND_PlaySoundEx() failed"); freeBGMusic(1); return; } bgmusic[0].free = false; bgmusic[1].free = false; playing_intro = true; } else { gConsole->Insertln("^5WARNING: cannot play %s", filename); gConsole->Insertln("^5FSOUND_Sample_Load() return NULL"); playing_intro = false; } FSOUND_SetVolume(bgmusic[1].channel, musicvolume); if (!start_paused) FSOUND_SetPaused(bgmusic[1].channel, FALSE); } else { VFile *file = new VFile(filename); if (!file) ThrowException(ALLOCATION_ERROR, "setBGMusic.file"); if (!file->mem) { gConsole->Insertln("^5WARNING: setBackgroundMusic() could not open file %s", filename); delete file; return; } bgmusic[0].sample = FSOUND_Sample_Load(0, (char*) file->mem, FSOUND_LOADMEMORY|FSOUND_LOOP_NORMAL|FSOUND_2D, file->size); delete file; if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Load(%s): %s", filename, GetSoundErrorString(errnum)); return; } if (bgmusic[0].sample) { bgmusic[0].channel = FSOUND_PlaySoundEx(FSOUND_FREE, bgmusic[0].sample, NULL, TRUE); if (bgmusic[0].channel == -1) { gConsole->Insertln("^1setBGMusic()->FSOUND_PlaySoundEx() failed"); freeBGMusic(0); return; } bgmusic[0].free = false; } else { gConsole->Insertln("^5WARNING: cannot play %s", filename); gConsole->Insertln("^5FSOUND_Sample_Load() return NULL"); } playing_intro = false; FSOUND_SetVolume(bgmusic[0].channel, musicvolume); if (!start_paused) FSOUND_SetPaused(bgmusic[0].channel, FALSE); } #endif } void toggleBGMusicPause(void) { #if !ENABLE_SOUND return; #else for (int i = 0; i < BGMUSIC_CHANNELS; ++i) { if (!bgmusic[i].free && bgmusic[i].sample) { if (FSOUND_GetPaused(bgmusic[i].channel)) FSOUND_SetPaused(bgmusic[i].channel, FALSE); else FSOUND_SetPaused(bgmusic[i].channel, TRUE); } } #endif } void playSound(int num, float x, float y, float z, bool stop_if_playing) { #if !ENABLE_SOUND return; #else if (num < 0 || num >= SOUND_CHANNELS) return; if (!sounds[num].sample || sounds[num].free) return; vec3_t pos; VectorSet(pos, x, y, z); playSound(num, pos, stop_if_playing); #endif } void playSound(int num, vec3_t location, bool stop_if_playing) { #if !ENABLE_SOUND return; #else if (num < 0 || num >= SOUND_CHANNELS) return; if (!sounds[num].sample || sounds[num].free) return; if (stop_if_playing && FSOUND_IsPlaying(sounds[num].channel)) return; sounds[num].channel = FSOUND_PlaySoundEx(FSOUND_FREE, sounds[num].sample, NULL, FALSE); if (sounds[num].channel == -1) { gConsole->Insertln("^5WARNING: playSound()->FSOUND_PlaySoundEx() failed"); return; } if (location) { vec3_t pos; pos[0] = location[0]; pos[1] = location[2]; pos[2] = location[1]; FSOUND_3D_SetAttributes(sounds[num].channel, pos, NULL); } FSOUND_SetVolume(sounds[num].channel, soundvolume); #endif } int loadSound(const char *filename, float x, float y, float z, bool loop, bool start_playing, bool start_paused) { #if !ENABLE_SOUND return -1; #else if (!filename || !strlen(filename)) return -1; vec3_t pos; VectorSet(pos, x, y, z); return loadSound(filename, pos, loop, start_playing, start_paused); #endif } int loadSound(const char *filename, vec3_t location, bool loop, bool start_playing, bool start_paused) { #if !ENABLE_SOUND return -1; #else if (!filename || !strlen(filename)) return -1; // search for a free sound channel int freesound; for (freesound = 0; freesound < SOUND_CHANNELS; ++freesound) { if (sounds[freesound].free) break; } if (freesound >= SOUND_CHANNELS) { gConsole->Insertln("^5WARNING: No more free sound."); return -1; } // sound is not already loaded, need to load it VFile *file = new VFile(filename); if (!file) ThrowException(ALLOCATION_ERROR, "loadSound.file"); if (!file->mem) { gConsole->Insertln("^5WARNING: loadSound() could not open file %s", filename); delete file; return -1; } int errnum = 0, flags = 0; if (loop) flags |= FSOUND_LOOP_NORMAL; if (location) { flags |= FSOUND_LOADMEMORY|FSOUND_HW3D; sounds[freesound].sample = FSOUND_Sample_Load(FSOUND_FREE, (char*) file->mem, flags, file->size); delete file; if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Load(%s): %s", filename, GetSoundErrorString(errnum)); return -1; } FSOUND_Sample_SetMinMaxDistance(sounds[freesound].sample, snd_mindistance.fvalue, snd_maxdistance.fvalue); if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_SetMinMaxDistance: %s", GetSoundErrorString(errnum)); return -1; } } else { flags |= FSOUND_LOADMEMORY|FSOUND_2D; sounds[freesound].sample = FSOUND_Sample_Load(FSOUND_FREE, (char*) file->mem, flags, file->size); delete file; if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Load(%s): %s", filename, GetSoundErrorString(errnum)); return -1; } } if (sounds[freesound].sample) { memset(sounds[freesound].name, '\0', MAX_SOUNDNAME_LENGTH*sizeof(char)); strcpy(sounds[freesound].name, filename); sounds[freesound].loop = loop; if (start_playing) { sounds[freesound].channel = FSOUND_PlaySoundEx(FSOUND_FREE, sounds[freesound].sample, NULL, TRUE); if (sounds[freesound].channel == -1) { gConsole->Insertln("^1playSound()->FSOUND_PlaySoundEx() failed"); return -1; } if (location) { vec3_t pos; pos[0] = location[0]; pos[1] = location[2]; pos[2] = location[1]; FSOUND_3D_SetAttributes(sounds[freesound].channel, pos, NULL); } FSOUND_SetVolume(sounds[freesound].channel, soundvolume); if (!start_paused) FSOUND_SetPaused(sounds[freesound].channel, FALSE); } sounds[freesound].free = false; } else { gConsole->Insertln("^5WARNING: cannot play %s", filename); gConsole->Insertln("^5FSOUND_Sample_Load() return NULL"); } return freesound; #endif } int load3DSound(const char *filename, vec3_t location, bool loop, bool start_playing, bool start_paused) { #if !ENABLE_SOUND return -1; #else if (!filename || !strlen(filename)) return -1; vec3_t pos; if (!location) VectorClear(pos); else VectorCopy(location, pos); return loadSound(filename, pos, loop, start_playing, start_paused); #endif } void toggleSoundPause(int num) { #if !ENABLE_SOUND return; #else if (num < 0 || num >= SOUND_CHANNELS) { for (int i = 0; i < SOUND_CHANNELS; ++i) toggleSoundPause(i); } else { if (!sounds[num].free && sounds[num].sample) { if (FSOUND_GetPaused(sounds[num].channel)) FSOUND_SetPaused(sounds[num].channel, FALSE); else FSOUND_SetPaused(sounds[num].channel, TRUE); } } #endif } void SoundUpdate(vec3_t position, vec3_t forward) { #if !ENABLE_SOUND return; #else // update background music if (playing_intro) { if (!FSOUND_IsPlaying(bgmusic[1].channel)) { freeBGMusic(1); if (!bgmusic[0].free && bgmusic[0].sample) { bgmusic[0].channel = FSOUND_PlaySoundEx(FSOUND_FREE, bgmusic[0].sample, NULL, TRUE); if (bgmusic[0].channel == -1) { gConsole->Insertln("^1setBGMusic()->FSOUND_PlaySoundEx() failed"); freeBGMusic(); return; } } else { gConsole->Insertln("^1FSOUND_Sample_Load() return NULL"); bgmusic[0].free = true; } FSOUND_SetVolume(bgmusic[0].channel, musicvolume); FSOUND_SetPaused(bgmusic[0].channel, FALSE); playing_intro = false; } } // update listener location vec3_t pos; pos[0] = position[0]; pos[1] = position[2]; pos[2] = position[1]; FSOUND_3D_Listener_SetAttributes(pos, NULL, forward[0], forward[2], forward[1], 0, 1, 0); FSOUND_Update(); #endif } void freeBGMusic(int num) { #if !ENABLE_SOUND return; #else if (num < 0 || num >= BGMUSIC_CHANNELS) { for (int i = 0; i < BGMUSIC_CHANNELS; ++i) freeBGMusic(i); } else { int errnum = 0; if (!bgmusic[num].free && bgmusic[num].sample) { if (FSOUND_IsPlaying(bgmusic[num].channel)) FSOUND_StopSound(bgmusic[num].channel); FSOUND_Sample_Free(bgmusic[num].sample); if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Free: %s", GetSoundErrorString(errnum)); return; } bgmusic[num].channel = -1; bgmusic[num].sample = NULL; memset(bgmusic[num].name, '\0', MAX_SOUNDNAME_LENGTH*sizeof(char)); bgmusic[num].loop = false; bgmusic[num].free = true; } } #endif } void freeSound(int num) { #if !ENABLE_SOUND return; #else if (num < 0 || num >= SOUND_CHANNELS) { for (int i = 0; i < SOUND_CHANNELS; ++i) freeSound(i); } else { if (!sounds[num].free && sounds[num].sample) { int errnum = 0; if (sounds[num].channel >= 0 && FSOUND_IsPlaying(sounds[num].channel)) FSOUND_StopSound(sounds[num].channel); FSOUND_Sample_Free(sounds[num].sample); if ((errnum = FSOUND_GetError())) { gConsole->Insertln("FSOUND_Sample_Free: %s", GetSoundErrorString(errnum)); return; } sounds[num].channel = -1; sounds[num].sample = NULL; memset(sounds[num].name, '\0', MAX_SOUNDNAME_LENGTH*sizeof(char)); sounds[num].loop = false; sounds[num].free = true; } } #endif } const char* GetSoundErrorString(int errnum) { #if !ENABLE_SOUND return NULL; #else switch (errnum) { case FMOD_ERR_BUSY: return "^1Cannot call this command after FSOUND_Init. Call FSOUND_Close first."; case FMOD_ERR_UNINITIALIZED: return "^1This command failed because FSOUND_Init or FSOUND_SetOutput was not called"; case FMOD_ERR_INIT: return "^1Error initializing output device."; case FMOD_ERR_ALLOCATED: return "^1Error initializing output device, but more specifically, the output device is already in use and cannot be reused."; case FMOD_ERR_PLAY: return "^1Playing the sound failed."; case FMOD_ERR_OUTPUT_FORMAT: return "^1Soundcard does not support the features needed for this soundsystem (16bit stereo output)"; case FMOD_ERR_COOPERATIVELEVEL: return "^1Error setting cooperative level for hardware."; case FMOD_ERR_CREATEBUFFER: return "^1Error creating hardware sound buffer."; case FMOD_ERR_FILE_NOTFOUND: return "^1File not found"; case FMOD_ERR_FILE_FORMAT: return "^1Unknown file format"; case FMOD_ERR_FILE_BAD: return "^1Error loading file"; case FMOD_ERR_MEMORY: return "^1Not enough memory or resources"; case FMOD_ERR_VERSION: return "^1The version number of this file format is not supported"; case FMOD_ERR_INVALID_PARAM: return "^1An invalid parameter was passed to this function"; case FMOD_ERR_NO_EAX: return "^1Tried to use an EAX command on a non EAX enabled channel or output."; case FMOD_ERR_CHANNEL_ALLOC: return "^1Failed to allocate a new channel"; case FMOD_ERR_RECORD: return "^1Recording is not supported on this machine"; case FMOD_ERR_MEDIAPLAYER: return "^1Windows Media Player not installed so cannot play wma or use internet streaming."; case FMOD_ERR_CDDEVICE: return "^1An error occured trying to open the specified CD device"; case FMOD_ERR_NONE: default: return "^1No errors"; } #endif }