From cbb7eb0428a3c42a1cd4e94633c2701ab63d3a2b Mon Sep 17 00:00:00 2001 From: Ozkan Sezer Date: Wed, 5 Jan 2011 19:50:43 +0000 Subject: [PATCH] Backported external music files support using decoder libraries and the new raw samples interface from Hammer of Thyrion (uhexen2) : - bgmusic.c, bgmusic.h: New BGM interface for background music handling. Handles streaming music as raw sound samples. - bgmnull.c: BGM source for cases where the engine is configured for no sound. - cl_main.c: Include bgmusic.h. Call BGM_Stop() and CDAudio_Stop() in CL_Disconnect(). - cd_sdl.c: Moved bgmvolume boundary checking to bgmusic.c upon value changes. - gl_vidnt.c, gl_vidsdl.c, cl_parse.c: Include bgmusic.h. Add BGM_Pause() and BGM_Resume() calls along with CDAudio_ counterparts. - cl_parse.c: Replace CDAudio_Play() call by the new BGM_PlayCDtrack() which first tries CDAudio_Play() and then streaming music if it fails. - host.c: Include bgmusic.h. Call BGM_Update() just before S_Update() in Host_Frame(). In Host_Init(), call BGM_Init() after other audio init calls. In Host_Shutdown(), call BGM_Shutdown() before all other audio shutdown calls. - snd_dma.c: Include snd_codec.h and bgmusic.h. Call S_CodecInit() from S_Init(). Call S_CodecShutdown() from S_Shutdown(). - snd_codec.c, snd_codec.h: New public codec interface for streaming music as raw samples. Adapted from quake2 and ioquake3 with changes. Individual codecs are responsible for handling any necessary byte swap operations. - snd_codeci.h: New header for snd_codec internals. - snd_wave.c, snd_wave.h: Codec for WAV format streaming music. Adapted from ioquake3 with changes. - snd_vorbis.c, snd_vorbis.h: Codec for Ogg/Vorbis format streaming music. - snd_mp3.c, snd_mp3.h: Codec for MP3 format streaming music using libmad. Adapted from the SoX project with changes. - Makefile: Adjusted for the new sources. Added switches USE_CODEC_WAVE, USE_CODEC_MP3, USE_CODEC_VORBIS for enabling and disabling individual codecs. - Windows makefiles and project files as well as other CodeBlocks project files will be updated shortly. git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@374 af15c1b1-3010-417e-b628-4374ebc0bcbd --- Quake/Makefile | 27 ++- Quake/bgmnull.c | 71 +++++++ Quake/bgmusic.c | 456 ++++++++++++++++++++++++++++++++++++++++++++ Quake/bgmusic.h | 43 +++++ Quake/cd_sdl.c | 6 - Quake/cl_main.c | 3 + Quake/cl_parse.c | 7 +- Quake/gl_vidnt.c | 3 + Quake/gl_vidsdl.c | 3 + Quake/host.c | 4 + Quake/snd_codec.c | 270 ++++++++++++++++++++++++++ Quake/snd_codec.h | 101 ++++++++++ Quake/snd_codeci.h | 52 +++++ Quake/snd_dma.c | 9 + Quake/snd_mp3.c | 466 +++++++++++++++++++++++++++++++++++++++++++++ Quake/snd_mp3.h | 13 ++ Quake/snd_vorbis.c | 169 ++++++++++++++++ Quake/snd_vorbis.h | 13 ++ Quake/snd_wave.c | 282 +++++++++++++++++++++++++++ Quake/snd_wave.h | 13 ++ 20 files changed, 2001 insertions(+), 10 deletions(-) create mode 100644 Quake/bgmnull.c create mode 100644 Quake/bgmusic.c create mode 100644 Quake/bgmusic.h create mode 100644 Quake/snd_codec.c create mode 100644 Quake/snd_codec.h create mode 100644 Quake/snd_codeci.h create mode 100644 Quake/snd_mp3.c create mode 100644 Quake/snd_mp3.h create mode 100644 Quake/snd_vorbis.c create mode 100644 Quake/snd_vorbis.h create mode 100644 Quake/snd_wave.c create mode 100644 Quake/snd_wave.h diff --git a/Quake/Makefile b/Quake/Makefile index 62db1e9c..cc81c72f 100644 --- a/Quake/Makefile +++ b/Quake/Makefile @@ -19,6 +19,11 @@ check_gcc = $(shell if echo | $(CC) $(1) -S -o /dev/null -xc - > /dev/null 2>&1; # ============================================================================ +# Enable/disable codecs for streaming music support: +USE_CODEC_WAVE=yes +USE_CODEC_MP3=yes +USE_CODEC_VORBIS=yes + DEBUG ?= 0 SDLNET ?= 0 @@ -109,9 +114,22 @@ else NET_LIBS := endif +CODECLIBS := +ifeq ($(USE_CODEC_WAVE),yes) +CFLAGS+= -DUSE_CODEC_WAVE +endif +ifeq ($(USE_CODEC_VORBIS),yes) +CFLAGS+= -DUSE_CODEC_VORBIS +CODECLIBS+= vorbis vorbisfile ogg +endif +ifeq ($(USE_CODEC_MP3),yes) +CFLAGS+= -DUSE_CODEC_MP3 +CODECLIBS+= mad +endif + COMMON_LIBS:= m GL -LIBS := $(patsubst %,-l%,$(COMMON_LIBS) $(NET_LIBS)) +LIBS := $(patsubst %,-l%,$(COMMON_LIBS) $(NET_LIBS) $(CODECLIBS)) # --------------------------- # targets @@ -132,7 +150,12 @@ DEFAULT_TARGET := quakespasm # objects # ---------------------------------------------------------------------------- -COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o +MUSIC_OBJS:= bgmusic.o \ + snd_codec.o \ + snd_wave.o \ + snd_vorbis.o \ + snd_mp3.o +COMOBJ_SND := snd_dma.o snd_mix.o snd_mem.o $(MUSIC_OBJS) SYSOBJ_SND := snd_sdl.o SYSOBJ_CDA := cd_sdl.o SYSOBJ_INPUT := in_sdl.o diff --git a/Quake/bgmnull.c b/Quake/bgmnull.c new file mode 100644 index 00000000..5dba2681 --- /dev/null +++ b/Quake/bgmnull.c @@ -0,0 +1,71 @@ +/* + * Background music handling for Hexen II: Hammer of Thyrion (uHexen2) + * Handle cases when we are configured for no sound and no midi driver, + * nada... + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" +#include "bgmusic.h" + +qboolean bgmloop = true; + +static float old_volume = -1.0f; + +qboolean BGM_Init (void) +{ + return false; +} + +void BGM_Play (const char *filename) +{ +} + +void BGM_PlayCDtrack (byte track, qboolean looping) +{ + bgmloop = looping; + CDAudio_Play(track, looping); +} + +void BGM_Stop (void) +{ +} + +void BGM_Pause (void) +{ +} + +void BGM_Resume (void) +{ +} + +void BGM_Update (void) +{ + if (old_volume != bgmvolume.value) + { + if (bgmvolume.value < 0) + Cvar_Set ("bgmvolume", "0"); + else if (bgmvolume.value > 1) + Cvar_Set ("bgmvolume", "1"); + old_volume = bgmvolume.value; + } +} + diff --git a/Quake/bgmusic.c b/Quake/bgmusic.c new file mode 100644 index 00000000..867dc0ab --- /dev/null +++ b/Quake/bgmusic.c @@ -0,0 +1,456 @@ +/* + * Background music handling for Hexen II: Hammer of Thyrion (uHexen2) + * Handles streaming music as raw sound samples and runs the midi driver + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" +#include "snd_codec.h" +#include "bgmusic.h" + +/* + * MUSIC FILE DIRECTORIES: + * + * Midi music file have always lived under "midi" directory, we are + * not changing that. Any other music files, including cd-rips, are + * expected under the "music" directory. + */ + +#define MUSIC_DIRNAME "music" + +qboolean bgmloop; + +static float old_volume = -1.0f; + +typedef enum _bgm_player +{ + BGM_NONE = -1, + BGM_MIDIDRV, + BGM_STREAMER, +} bgm_player_t; + +typedef struct music_handler_s +{ + unsigned int type; /* power of two (snd_codec.h) */ + bgm_player_t player; /* Enumerated bgm player type */ + int is_available; /* -1 means not present */ + const char *ext; /* Expected file extension */ + const char *dir; /* Where to look for music file */ + struct music_handler_s *next; +} music_handler_t; + +static music_handler_t wanted_handlers[] = +{ + { CODECTYPE_OGG, BGM_STREAMER, -1, ".ogg", MUSIC_DIRNAME, NULL }, + { CODECTYPE_MP3, BGM_STREAMER, -1, ".mp3", MUSIC_DIRNAME, NULL }, + { CODECTYPE_WAV, BGM_STREAMER, -1, ".wav", MUSIC_DIRNAME, NULL }, + { CODECTYPE_FLAC, BGM_STREAMER, -1, ".flac", MUSIC_DIRNAME, NULL }, + { CODECTYPE_NONE, BGM_NONE, -1, NULL, NULL, NULL } +}; + +static music_handler_t *music_handlers = NULL; + +#define ANY_CODECTYPE 0xFFFFFFFF +#define CDRIP_TYPES (CODECTYPE_OGG | CODECTYPE_MP3 | CODECTYPE_WAV) +#define CDRIPTYPE(x) (((x) & CDRIP_TYPES) != 0) + +static snd_stream_t *bgmstream = NULL; + +static void BGM_Play_f (void) +{ + if (Cmd_Argc() == 2) + { + BGM_Play (Cmd_Argv(1)); + } + else + { + Con_Printf ("music \n"); + return; + } +} + +static void BGM_Pause_f (void) +{ + BGM_Pause (); +} + +static void BGM_Resume_f (void) +{ + BGM_Resume (); +} + +static void BGM_Loop_f (void) +{ + if (Cmd_Argc() == 2) + { + if (Q_strcasecmp(Cmd_Argv(1), "0") == 0 || + Q_strcasecmp(Cmd_Argv(1),"off") == 0) + bgmloop = false; + else if (Q_strcasecmp(Cmd_Argv(1), "1") == 0 || + Q_strcasecmp(Cmd_Argv(1),"on") == 0) + bgmloop = true; + else if (Q_strcasecmp(Cmd_Argv(1),"toggle") == 0) + bgmloop = !bgmloop; + } + + if (bgmloop) + Con_Printf("Music will be looped\n"); + else + Con_Printf("Music will not be looped\n"); +} + +static void BGM_Stop_f (void) +{ + BGM_Stop(); +} + +qboolean BGM_Init (void) +{ + music_handler_t *handlers = NULL; + int i; + + Cmd_AddCommand("music", BGM_Play_f); + Cmd_AddCommand("music_pause", BGM_Pause_f); + Cmd_AddCommand("music_resume", BGM_Resume_f); + Cmd_AddCommand("music_loop", BGM_Loop_f); + Cmd_AddCommand("music_stop", BGM_Stop_f); + + bgmloop = true; + + for (i = 0; wanted_handlers[i].type != CODECTYPE_NONE; i++) + { + switch (wanted_handlers[i].player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + wanted_handlers[i].is_available = + S_CodecIsAvailable(wanted_handlers[i].type); + break; + case BGM_NONE: + default: + break; + } + if (wanted_handlers[i].is_available != -1) + { + if (handlers) + { + handlers->next = &wanted_handlers[i]; + handlers = handlers->next; + } + else + { + music_handlers = &wanted_handlers[i]; + handlers = music_handlers; + } + } + } + + return true; +} + +void BGM_Shutdown (void) +{ + BGM_Stop(); +/* sever our connections to + * midi_drv and snd_codec */ + music_handlers = NULL; +} + +static void BGM_Play_noext (const char *filename, unsigned int allowed_types) +{ + char tmp[MAX_QPATH]; + music_handler_t *handler; + + handler = music_handlers; + while (handler) + { + if (! (handler->type & allowed_types)) + { + handler = handler->next; + continue; + } + if (!handler->is_available) + { + /* skip handlers which failed to initialize */ + /* TODO: implement re-init, make BGM aware of it */ + handler = handler->next; + continue; + } + q_snprintf(tmp, sizeof(tmp), "%s/%s%s", + handler->dir, filename, handler->ext); + switch (handler->player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + bgmstream = S_CodecOpenStreamType(tmp, handler->type); + if (bgmstream) + return; /* success */ + break; + case BGM_NONE: + default: + break; + } + handler = handler->next; + } + + Con_Printf("Couldn't handle music file %s\n", filename); +} + +void BGM_Play (const char *filename) +{ + char tmp[MAX_QPATH]; + const char *ext; + music_handler_t *handler; + + if (!filename || !*filename) + { + Con_DPrintf("null music file name\n"); + return; + } + + BGM_Stop(); + + ext = S_FileExtension(filename); + if (!ext) /* try all things */ + { + BGM_Play_noext(filename, ANY_CODECTYPE); + return; + } + + handler = music_handlers; + while (handler) + { + /* skip handlers which failed to initialize */ + /* TODO: implement re-init, make BGM aware of it */ + if (handler->is_available && + !Q_strcasecmp(ext, handler->ext)) + break; + handler = handler->next; + } + if (!handler) + { + Con_Printf("Unhandled extension for %s\n", filename); + return; + } + q_snprintf(tmp, sizeof(tmp), "%s/%s", handler->dir, filename); + switch (handler->player) + { + case BGM_MIDIDRV: + /* not supported in quake */ + break; + case BGM_STREAMER: + bgmstream = S_CodecOpenStreamType(tmp, handler->type); + if (bgmstream) + return; /* success */ + break; + case BGM_NONE: + default: + break; + } + + Con_Printf("Couldn't handle music file %s\n", filename); +} + +void BGM_PlayCDtrack (byte track, qboolean looping) +{ +#if 0 /* see below */ + char tmp[MAX_QPATH]; + + BGM_Stop(); + + bgmloop = looping; + + if (CDAudio_Play(track, looping) == 0) + return; /* success */ + + q_snprintf(tmp, sizeof(tmp), "track%02d", (int)track); + BGM_Play_noext(tmp, CDRIP_TYPES); +#endif +/* instead of searching by the order of music_handlers, do so by + * the order of searchpath priority: the file from the searchpath + * with the highest path_id is most likely from our own gamedir + * itself. This way, if a mod has track02 as a *.mp3 file, which + * is below *.ogg in the music_handler order, the mp3 will still + * have priority over track02.ogg from, say, id1. + */ + char tmp[MAX_QPATH]; + const char *ext; + unsigned int path_id, prev_id, type; + music_handler_t *handler; + + BGM_Stop(); + if (CDAudio_Play(track, looping) == 0) + return; /* success */ + + prev_id = 0; + type = 0; + ext = NULL; + handler = music_handlers; + while (handler) + { + if (! handler->is_available) + goto _next; + q_snprintf(tmp, sizeof(tmp), "%s/track%02d%s", + MUSIC_DIRNAME, (int)track, handler->ext); + if (COM_FileExists(tmp, &path_id) == -1) + goto _next; + if (path_id > prev_id) + { + prev_id = path_id; + type = handler->type; + ext = handler->ext; + } + _next: + handler = handler->next; + } + if (ext == NULL) + Con_Printf("Couldn't find a cdrip for track %d\n", (int)track); + else + { + q_snprintf(tmp, sizeof(tmp), "%s/track%02d%s", + MUSIC_DIRNAME, (int)track, ext); + bgmstream = S_CodecOpenStreamType(tmp, type); + if (! bgmstream) + Con_Printf("Couldn't handle music file %s\n", tmp); + } +} + +void BGM_Stop (void) +{ + if (bgmstream) + { + bgmstream->status = STREAM_NONE; + S_CodecCloseStream(bgmstream); + bgmstream = NULL; + s_rawend = 0; + } +} + +void BGM_Pause (void) +{ + if (bgmstream) + { + if (bgmstream->status == STREAM_PLAY) + bgmstream->status = STREAM_PAUSE; + } +} + +void BGM_Resume (void) +{ + if (bgmstream) + { + if (bgmstream->status == STREAM_PAUSE) + bgmstream->status = STREAM_PLAY; + } +} + +static void BGM_UpdateStream (void) +{ + int res; /* Number of bytes read. */ + int bufferSamples; + int fileSamples; + int fileBytes; + byte raw[16384]; + + if (bgmstream->status != STREAM_PLAY) + return; + + /* don't bother playing anything if musicvolume is 0 */ + if (bgmvolume.value <= 0) + return; + + /* see how many samples should be copied into the raw buffer */ + if (s_rawend < paintedtime) + s_rawend = paintedtime; + + while (s_rawend < paintedtime + MAX_RAW_SAMPLES) + { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend - paintedtime); + + /* decide how much data needs to be read from the file */ + fileSamples = bufferSamples * bgmstream->info.rate / shm->speed; + if (!fileSamples) + return; + + /* our max buffer size */ + fileBytes = fileSamples * (bgmstream->info.width * bgmstream->info.channels); + if (fileBytes > sizeof(raw)) + { + fileBytes = sizeof(raw); + fileSamples = fileBytes / (bgmstream->info.width * bgmstream->info.channels); + } + + /* Read */ + res = S_CodecReadStream(bgmstream, fileBytes, raw); + if (res < fileBytes) + { + fileBytes = res; + fileSamples = res / (bgmstream->info.width * bgmstream->info.channels); + } + + if (res > 0) /* data: add to raw buffer */ + { + S_RawSamples(fileSamples, bgmstream->info.rate, + bgmstream->info.width, bgmstream->info.channels, raw, bgmvolume.value); + } + else if (res == 0) /* EOF */ + { + if (bgmloop) + { + if (S_CodecRewindStream(bgmstream) < 0) + { + BGM_Stop(); + return; + } + } + else + { + BGM_Stop(); + return; + } + } + else /* res < 0: some read error */ + { + Con_Printf("Stream read error (%i), stopping.\n", res); + BGM_Stop(); + return; + } + } +} + +void BGM_Update (void) +{ + if (old_volume != bgmvolume.value) + { + if (bgmvolume.value < 0) + Cvar_Set ("bgmvolume", "0"); + else if (bgmvolume.value > 1) + Cvar_Set ("bgmvolume", "1"); + old_volume = bgmvolume.value; + } + if (bgmstream) + BGM_UpdateStream (); +} + diff --git a/Quake/bgmusic.h b/Quake/bgmusic.h new file mode 100644 index 00000000..8d71ea2b --- /dev/null +++ b/Quake/bgmusic.h @@ -0,0 +1,43 @@ +/* + * Background music handling for Hexen II: Hammer of Thyrion (uHexen2) + * Handles streaming music as raw sound samples and runs the midi driver + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2010 O.Sezer + * + * $Id: bgmusic.h 3818 2010-12-19 09:04:17Z sezero $ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _BGMUSIC_H_ +#define _BGMUSIC_H_ + +extern qboolean bgmloop; + +qboolean BGM_Init (void); +void BGM_Shutdown (void); + +void BGM_Play (const char *filename); +void BGM_PlayCDtrack (byte track, qboolean looping); +void BGM_Stop (void); +void BGM_Update (void); +void BGM_Pause (void); +void BGM_Resume (void); + +#endif /* _BGMUSIC_H_ */ + diff --git a/Quake/cd_sdl.c b/Quake/cd_sdl.c index de83e318..9653842d 100644 --- a/Quake/cd_sdl.c +++ b/Quake/cd_sdl.c @@ -426,13 +426,7 @@ void CDAudio_Update(void) return; if (old_cdvolume != bgmvolume.value) - { - if (bgmvolume.value < 0) - Cvar_Set ("bgmvolume", "0.0"); - else if (bgmvolume.value > 1) - Cvar_Set ("bgmvolume", "1.0"); CDAudio_SetVolume (bgmvolume.value); - } if (playing && realtime > endOfTrack) { diff --git a/Quake/cl_main.c b/Quake/cl_main.c index d3c0beda..3a3fde11 100644 --- a/Quake/cl_main.c +++ b/Quake/cl_main.c @@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // cl_main.c -- client main loop #include "quakedef.h" +#include "bgmusic.h" // we need to declare some mouse variables here, because the menu system // references them even when on a unix system. @@ -111,6 +112,8 @@ void CL_Disconnect (void) { // stop sounds (especially looping!) S_StopAllSounds (true); + BGM_Stop(); + CDAudio_Stop(); // if running a local server, shut it down if (cls.demoplayback) diff --git a/Quake/cl_parse.c b/Quake/cl_parse.c index 1d14c159..c38bd971 100644 --- a/Quake/cl_parse.c +++ b/Quake/cl_parse.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // cl_parse.c -- parse a message received from the server #include "quakedef.h" +#include "bgmusic.h" const char *svc_strings[] = { @@ -1104,10 +1105,12 @@ void CL_ParseServerMessage (void) if (cl.paused) { CDAudio_Pause (); + BGM_Pause (); } else { CDAudio_Resume (); + BGM_Resume (); } } break; @@ -1151,9 +1154,9 @@ void CL_ParseServerMessage (void) cl.cdtrack = MSG_ReadByte (); cl.looptrack = MSG_ReadByte (); if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) - CDAudio_Play ((byte)cls.forcetrack, true); + BGM_PlayCDtrack ((byte)cls.forcetrack, true); else - CDAudio_Play ((byte)cl.cdtrack, true); + BGM_PlayCDtrack ((byte)cl.cdtrack, true); break; case svc_intermission: diff --git a/Quake/gl_vidnt.c b/Quake/gl_vidnt.c index 84a8aa95..cd235d11 100644 --- a/Quake/gl_vidnt.c +++ b/Quake/gl_vidnt.c @@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // gl_vidnt.c -- NT GL vid component #include "quakedef.h" +#include "bgmusic.h" #include "winquake.h" #include "resource.h" #include @@ -543,6 +544,7 @@ int VID_SetMode (int modenum) scr_disabled_for_loading = true; CDAudio_Pause (); + BGM_Pause (); if (vid_modenum == NO_MODE) original_mode = windowed_default; @@ -591,6 +593,7 @@ int VID_SetMode (int modenum) VID_UpdateWindowStatus (); CDAudio_Resume (); + BGM_Resume (); scr_disabled_for_loading = temp; // now we try to make sure we get the focus on the mode switch, because diff --git a/Quake/gl_vidsdl.c b/Quake/gl_vidsdl.c index 83074a03..fdc2c2bb 100644 --- a/Quake/gl_vidsdl.c +++ b/Quake/gl_vidsdl.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // gl_vidnt.c -- NT GL vid component #include "quakedef.h" +#include "bgmusic.h" #include "resource.h" #include "SDL.h" @@ -260,6 +261,7 @@ int VID_SetMode (int modenum) scr_disabled_for_loading = true; CDAudio_Pause (); + BGM_Pause (); // set vertical sync if (gl_swap_control) @@ -328,6 +330,7 @@ int VID_SetMode (int modenum) VID_UpdateWindowStatus (); CDAudio_Resume (); + BGM_Resume (); scr_disabled_for_loading = temp; vid_modenum = modenum; diff --git a/Quake/host.c b/Quake/host.c index 9fd7cb00..2bd4ad3e 100644 --- a/Quake/host.c +++ b/Quake/host.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // host.c -- coordinates spawning and killing of local servers #include "quakedef.h" +#include "bgmusic.h" #include /* @@ -728,6 +729,7 @@ void _Host_Frame (float time) time2 = Sys_FloatTime (); // update audio + BGM_Update(); // adds music raw samples and/or advances midi driver if (cls.signon == SIGNONS) { S_Update (r_origin, vpn, vright, vup); @@ -851,6 +853,7 @@ void Host_Init (quakeparms_t *parms) R_Init (); S_Init (); CDAudio_Init (); + BGM_Init(); Sbar_Init (); CL_Init (); @@ -917,6 +920,7 @@ void Host_Shutdown(void) { if (con_initialized) History_Shutdown (); + BGM_Shutdown(); CDAudio_Shutdown (); S_Shutdown (); IN_Shutdown (); // input is only initialized in Host_Init if we're not dedicated -- kristian diff --git a/Quake/snd_codec.c b/Quake/snd_codec.c new file mode 100644 index 00000000..5c93dc1b --- /dev/null +++ b/Quake/snd_codec.c @@ -0,0 +1,270 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" +#include "snd_codec.h" +#include "snd_codeci.h" + +/* headers for individual codecs */ +#include "snd_wave.h" +#include "snd_mp3.h" +#include "snd_vorbis.h" + + +static snd_codec_t *codecs; + +/* +================= +S_CodecRegister +================= +*/ +static void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit (void) +{ + snd_codec_t *codec; + codecs = NULL; + + /* Register in the inverse order + * of codec choice preference: */ +#ifdef USE_CODEC_WAVE + S_CodecRegister(&wav_codec); +#endif +#ifdef USE_CODEC_MP3 + S_CodecRegister(&mp3_codec); +#endif +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&ogg_codec); +#endif + + codec = codecs; + while (codec) + { + codec->initialize(); + codec = codec->next; + } +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown (void) +{ + snd_codec_t *codec = codecs; + while (codec) + { + codec->shutdown(); + codec = codec->next; + } + codecs = NULL; +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type) +{ + snd_codec_t *codec; + snd_stream_t *stream; + + if (type == CODECTYPE_NONE) + { + Con_Printf("Bad type for %s\n", filename); + return NULL; + } + + codec = codecs; + while (codec) + { + if (type == codec->type) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown type for %s\n", filename); + return NULL; + } + stream = codec->codec_open(filename); + if (stream) + stream->status = STREAM_PLAY; + return stream; +} + +snd_stream_t *S_CodecOpenStreamExt (const char *filename) +{ + snd_codec_t *codec; + snd_stream_t *stream; + const char *ext; + + ext = S_FileExtension(filename); + if (!ext) + { + Con_Printf("No extension for %s\n", filename); + return NULL; + } + + codec = codecs; + while (codec) + { + if (!Q_strcasecmp(ext, codec->ext)) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown extension for %s\n", filename); + return NULL; + } + stream = codec->codec_open(filename); + if (stream) + stream->status = STREAM_PLAY; + return stream; +} + +snd_stream_t *S_CodecOpenStreamAny (const char *filename) +{ + snd_codec_t *codec; + snd_stream_t *stream; + const char *ext; + + ext = S_FileExtension(filename); + if (!ext) /* try all available */ + { + char tmp[MAX_QPATH]; + + codec = codecs; + while (codec) + { + q_snprintf(tmp, sizeof(tmp), "%s%s", filename, codec->ext); + stream = codec->codec_open(tmp); + if (stream) + { + stream->status = STREAM_PLAY; + return stream; + } + codec = codec->next; + } + + return NULL; + } + else /* use the name as is */ + { + codec = codecs; + while (codec) + { + if (!Q_strcasecmp(ext, codec->ext)) + break; + codec = codec->next; + } + if (!codec) + { + Con_Printf("Unknown extension for %s\n", filename); + return NULL; + } + stream = codec->codec_open(filename); + if (stream) + stream->status = STREAM_PLAY; + return stream; + } +} + +void S_CodecCloseStream (snd_stream_t *stream) +{ + stream->status = STREAM_NONE; + stream->codec->codec_close(stream); +} + +int S_CodecRewindStream (snd_stream_t *stream) +{ + return stream->codec->codec_rewind(stream); +} + +int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->codec_read(stream, bytes, buffer); +} + +/* Util functions (used by codecs) */ + +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +{ + snd_stream_t *stream; + FILE *handle; + qboolean pak; + size_t length; + + /* Try to open the file */ + length = COM_FOpenFile(filename, &handle, NULL); + pak = file_from_pak; + if (length == (size_t)-1) + { + Con_DPrintf("Couldn't open %s\n", filename); + return NULL; + } + + /* Allocate a stream, Z_Malloc zeroes its content */ + stream = (snd_stream_t *) Z_Malloc(sizeof(snd_stream_t)); + stream->codec = codec; + stream->fh.file = handle; + stream->fh.start = ftell(handle); + stream->fh.pos = 0; + stream->fh.length = (int) length; + stream->fh.pak = stream->pak = pak; + return stream; +} + +void S_CodecUtilClose(snd_stream_t **stream) +{ + fclose((*stream)->fh.file); + Z_Free(*stream); + *stream = NULL; +} + +int S_CodecIsAvailable (unsigned int type) +{ + snd_codec_t *codec = codecs; + while (codec) + { + if (type == codec->type) + return codec->initialized; + codec = codec->next; + } + return -1; +} + diff --git a/Quake/snd_codec.h b/Quake/snd_codec.h new file mode 100644 index 00000000..360fd7bb --- /dev/null +++ b/Quake/snd_codec.h @@ -0,0 +1,101 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +typedef struct snd_info_s +{ + int rate; + int width; + int channels; + int samples; + int size; + int dataofs; +} snd_info_t; + +typedef enum { + STREAM_NONE = -1, + STREAM_INIT, + STREAM_PAUSE, + STREAM_PLAY +} stream_status_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + fshandle_t fh; + qboolean pak; + snd_info_t info; + stream_status_t status; + snd_codec_t *codec; /* codec handling this stream */ + void *priv; /* data private to the codec. */ +} snd_stream_t; + + +void S_CodecInit (void); +void S_CodecShutdown (void); + +/* Callers of the following S_CodecOpenStream* functions + * are reponsible for attaching any path to the filename */ + +snd_stream_t *S_CodecOpenStreamType (const char *filename, unsigned int type); + /* Decides according to the required type. */ + +snd_stream_t *S_CodecOpenStreamAny (const char *filename); + /* Decides according to file extension. if the + * name has no extension, try all available. */ + +snd_stream_t *S_CodecOpenStreamExt (const char *filename); + /* Decides according to file extension. the name + * MUST have an extension. */ + +void S_CodecCloseStream (snd_stream_t *stream); +int S_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer); +int S_CodecRewindStream (snd_stream_t *stream); + +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +void S_CodecUtilClose(snd_stream_t **stream); + + +#define CODECTYPE_NONE 0 +#define CODECTYPE_MID (1 << 0) +#define CODECTYPE_MOD (1 << 1) +#define CODECTYPE_FLAC (1 << 2) +#define CODECTYPE_WAV (1 << 3) +#define CODECTYPE_MP3 (1 << 4) +#define CODECTYPE_OGG (1 << 5) + +#define CODECTYPE_VORBIS CODECTYPE_OGG +#define CODECTYPE_WAVE CODECTYPE_WAV +#define CODECTYPE_MIDI CODECTYPE_MID + +int S_CodecIsAvailable (unsigned int type); + /* return 1 if available, 0 if codec failed init + * or -1 if no such codec is present. */ + +#endif /* _SND_CODEC_H_ */ + diff --git a/Quake/snd_codeci.h b/Quake/snd_codeci.h new file mode 100644 index 00000000..8d59dd6a --- /dev/null +++ b/Quake/snd_codeci.h @@ -0,0 +1,52 @@ +/* + * Audio Codecs: Adapted from ioquake3 with changes. + * For now, only handles streaming music, not sound effects. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _SND_CODECI_H_ +#define _SND_CODECI_H_ + +/* Codec internals */ +typedef qboolean (*CODEC_INIT)(void); +typedef void (*CODEC_SHUTDOWN)(void); +typedef snd_stream_t *(*CODEC_OPEN)(const char *filename); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef int (*CODEC_REWIND)(snd_stream_t *stream); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +struct snd_codec_s +{ + unsigned int type; /* handled data type. */ + qboolean initialized; /* init succeedded */ + const char *ext; /* expected extension */ + CODEC_INIT initialize; + CODEC_SHUTDOWN shutdown; + CODEC_OPEN codec_open; + CODEC_READ codec_read; + CODEC_REWIND codec_rewind; + CODEC_CLOSE codec_close; + snd_codec_t *next; +}; + +#endif /* _SND_CODECI_H_ */ + diff --git a/Quake/snd_dma.c b/Quake/snd_dma.c index d1175521..cce8e99d 100644 --- a/Quake/snd_dma.c +++ b/Quake/snd_dma.c @@ -22,6 +22,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // snd_dma.c -- main control for any streaming sound output device #include "quakedef.h" +#include "snd_codec.h" +#include "bgmusic.h" static void S_Play (void); static void S_PlayVol (void); @@ -192,6 +194,8 @@ void S_Init (void) ambient_sfx[AMBIENT_WATER] = S_PrecacheSound ("ambience/water1.wav"); ambient_sfx[AMBIENT_SKY] = S_PrecacheSound ("ambience/wind2.wav"); + S_CodecInit (); + S_StopAllSounds (true); } @@ -207,6 +211,8 @@ void S_Shutdown (void) sound_started = 0; snd_blocked = 0; + S_CodecShutdown(); + SNDDMA_Shutdown(); shm = NULL; } @@ -856,6 +862,9 @@ void S_Update (vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) Con_Printf ("----(%i)----\n", total); } +// add raw data from streamed samples +// BGM_Update(); // moved to the main loop just before S_Update () + // mix some sound S_Update_(); } diff --git a/Quake/snd_mp3.c b/Quake/snd_mp3.c new file mode 100644 index 00000000..79710677 --- /dev/null +++ b/Quake/snd_mp3.c @@ -0,0 +1,466 @@ +/* + * MP3 decoding support using libmad: Adapted from the SoX library at + * http://sourceforge.net/projects/sox/, LGPLv2, Copyright (c) 2007-2009 + * SoX contributors, written by Fabrizio Gennari , + * with the decoding part based on the decoder tutorial program madlld + * written by Bertrand Petit (BSD license, see at + * http://www.bsd-dk.dk/~elrond/audio/madlld/). The tag identification + * functions were adapted from the GPL-licensed libid3tag library, see at + * http://www.underbit.com/products/mad/. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_MP3) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_mp3.h" +#include + +#define ID3_TAG_FLAG_FOOTERPRESENT 0x10 + +/* Under Windows, importing data from DLLs is a dicey proposition. This is true + * when using dlopen, but also true if linking directly against the DLL if the + * header does not mark the data as __declspec(dllexport), which mad.h does not. + * Sidestep the issue by defining our own mad_timer_zero. This is needed because + * mad_timer_zero is used in some of the mad.h macros. + */ +#define mad_timer_zero mad_timer_zero_stub +static mad_timer_t const mad_timer_zero_stub = {0, 0}; + +/* MAD returns values with MAD_F_FRACBITS (28) bits of precision, though it's + not certain that all of them are meaningful. Default to 16 bits to + align with most users expectation of output file should be 16 bits. */ +#define MP3_MAD_SAMPLEBITS 16 +#define MP3_MAD_SAMPLEWIDTH (MP3_MAD_SAMPLEBITS / 8) +#define MP3_BUFFER_SIZE (5 * 8192) + +/* Private data */ +typedef struct _mp3_priv_t +{ + unsigned char mp3_buffer[MP3_BUFFER_SIZE]; + struct mad_stream Stream; + struct mad_frame Frame; + struct mad_synth Synth; + mad_timer_t Timer; + ptrdiff_t cursamp; + size_t FrameCount; +} mp3_priv_t; + +/* This function merges the functions tagtype() and id3_tag_query() + * from MAD's libid3tag, so we don't have to link to it + * Returns 0 if the frame is not an ID3 tag, tag length if it is */ +static qboolean tag_is_id3v1(const unsigned char *data, size_t length) +{ + if (length >= 3 && + data[0] == 'T' && data[1] == 'A' && data[2] == 'G') + { + return true; + } + return false; +} + +static qboolean tag_is_id3v2(const unsigned char *data, size_t length) +{ + if (length >= 10 && + (data[0] == 'I' && data[1] == 'D' && data[2] == '3') && + data[3] < 0xff && data[4] < 0xff && + data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80) + { + return true; + } + return false; +} + +static int mp3_tagsize(const unsigned char *data, size_t length) +{ + if (tag_is_id3v1(data, length)) + return 128; + + if (tag_is_id3v2(data, length)) + { + unsigned char flags; + unsigned int size; + flags = data[5]; + size = 10 + (data[6]<<21) + (data[7]<<14) + (data[8]<<7) + data[9]; + if (flags & ID3_TAG_FLAG_FOOTERPRESENT) + size += 10; + for ( ; size < length && !data[size]; ++size) + ; /* Consume padding */ + return size; + } + + return 0; +} + +/* Attempts to read an ID3 tag at the current location in stream and + * consume it all. Returns -1 if no tag is found. Its up to caller + * to recover. */ +static int mp3_inputtag(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + int rc = -1; + size_t remaining; + size_t tagsize; + + /* FIXME: This needs some more work if we are to ever + * look at the ID3 frame. This is because the Stream + * may not be able to hold the complete ID3 frame. + * We should consume the whole frame inside tagtype() + * instead of outside of tagframe(). That would support + * recovering when Stream contains less then 8-bytes (header) + * and also when ID3v2 is bigger then Stream buffer size. + * Need to pass in stream so that buffer can be + * consumed as well as letting additional data to be + * read in. + */ + remaining = p->Stream.bufend - p->Stream.next_frame; + tagsize = mp3_tagsize(p->Stream.this_frame, remaining); + if (tagsize != 0) + { + mad_stream_skip(&p->Stream, tagsize); + rc = 0; + } + + /* We know that a valid frame hasn't been found yet + * so help libmad out and go back into frame seek mode. + * This is true whether an ID3 tag was found or not. + */ + mad_stream_sync(&p->Stream); + + return rc; +} + +/* (Re)fill the stream buffer that is to be decoded. If any data + * still exists in the buffer then they are first shifted to be + * front of the stream buffer. */ +static int mp3_inputdata(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t bytes_read; + size_t remaining; + + remaining = p->Stream.bufend - p->Stream.next_frame; + + /* libmad does not consume all the buffer it's given. Some + * data, part of a truncated frame, is left unused at the + * end of the buffer. That data must be put back at the + * beginning of the buffer and taken in account for + * refilling the buffer. This means that the input buffer + * must be large enough to hold a complete frame at the + * highest observable bit-rate (currently 448 kb/s). + * TODO: Is 2016 bytes the size of the largest frame? + * (448000*(1152/32000))/8 + */ + memmove(p->mp3_buffer, p->Stream.next_frame, remaining); + + bytes_read = FS_fread(p->mp3_buffer + remaining, 1, + MP3_BUFFER_SIZE - remaining, &stream->fh); + if (bytes_read == 0) + { + return -1; + } + + mad_stream_buffer(&p->Stream, p->mp3_buffer, bytes_read+remaining); + p->Stream.error = MAD_ERROR_NONE; + + return 0; +} + +static int mp3_startread(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + size_t ReadSize; + + mad_stream_init(&p->Stream); + mad_frame_init(&p->Frame); + mad_synth_init(&p->Synth); + mad_timer_reset(&p->Timer); + + /* Decode at least one valid frame to find out the input + * format. The decoded frame will be saved off so that it + * can be processed later. + */ + ReadSize = FS_fread(p->mp3_buffer, 1, MP3_BUFFER_SIZE, &stream->fh); + if (ReadSize != MP3_BUFFER_SIZE) + { + if (FS_feof(&stream->fh) || FS_ferror(&stream->fh)) + return -1; + } + + mad_stream_buffer(&p->Stream, p->mp3_buffer, ReadSize); + + /* Find a valid frame before starting up. This makes sure + * that we have a valid MP3 and also skips past ID3v2 tags + * at the beginning of the audio file. + */ + p->Stream.error = MAD_ERROR_NONE; + while (mad_frame_decode(&p->Frame,&p->Stream)) + { + /* check whether input buffer needs a refill */ + if (p->Stream.error == MAD_ERROR_BUFLEN) + { + if (mp3_inputdata(stream) == -1) + return -1; + + continue; + } + + /* Consume any ID3 tags */ + mp3_inputtag(stream); + + /* FIXME: We should probably detect when we've read + * a bunch of non-ID3 data and still haven't found a + * frame. In that case we can abort early without + * scanning the whole file. + */ + p->Stream.error = MAD_ERROR_NONE; + } + + if (p->Stream.error) + { + Con_Printf("MP3: No valid MP3 frame found\n"); + return -1; + } + + switch(p->Frame.header.mode) + { + case MAD_MODE_SINGLE_CHANNEL: + case MAD_MODE_DUAL_CHANNEL: + case MAD_MODE_JOINT_STEREO: + case MAD_MODE_STEREO: + stream->info.channels = MAD_NCHANNELS(&p->Frame.header); + break; + default: + Con_Printf("MP3: Cannot determine number of channels\n"); + return -1; + } + + p->FrameCount = 1; + + mad_timer_add(&p->Timer,p->Frame.header.duration); + mad_synth_frame(&p->Synth,&p->Frame); + stream->info.width = MP3_MAD_SAMPLEWIDTH; + stream->info.rate = p->Synth.pcm.samplerate; + + p->cursamp = 0; + + return 0; +} + +/* Read up to len samples from p->Synth + * If needed, read some more MP3 data, decode them and synth them + * Place in buf[]. + * Return number of samples read. */ +static int mp3_decode(snd_stream_t *stream, byte *buf, int len) +{ + mp3_priv_t *p = (mp3_priv_t *) stream->priv; + int donow, i, done = 0; + mad_fixed_t sample; + int chan, x; + + do + { + x = (p->Synth.pcm.length - p->cursamp) * stream->info.channels; + donow = q_min(len, x); + i = 0; + while (i < donow) + { + for (chan = 0; chan < stream->info.channels; chan++) + { + sample = p->Synth.pcm.samples[chan][p->cursamp]; + /* convert from fixed to short, + * write in host-endian format. */ + if (sample <= -MAD_F_ONE) + sample = -0x7FFF; + else if (sample >= MAD_F_ONE) + sample = 0x7FFF; + else + sample >>= (MAD_F_FRACBITS + 1 - 16); + if (bigendien) + { + *buf++ = (sample >> 8) & 0xFF; + *buf++ = sample & 0xFF; + } + else + { /* LITTLE_ENDIAN */ + *buf++ = sample & 0xFF; + *buf++ = (sample >> 8) & 0xFF; + } + i++; + } + p->cursamp++; + }; + + len -= donow; + done += donow; + + if (len == 0) + break; + + /* check whether input buffer needs a refill */ + if (p->Stream.error == MAD_ERROR_BUFLEN) + { + if (mp3_inputdata(stream) == -1) + { + /* check feof() ?? */ + Con_DPrintf("mp3 EOF\n"); + break; + } + } + + if (mad_frame_decode(&p->Frame, &p->Stream)) + { + if (MAD_RECOVERABLE(p->Stream.error)) + { + mp3_inputtag(stream); + continue; + } + else + { + if (p->Stream.error == MAD_ERROR_BUFLEN) + continue; + else + { + Con_Printf("MP3: unrecoverable frame level error (%s)\n", + mad_stream_errorstr(&p->Stream)); + break; + } + } + } + p->FrameCount++; + mad_timer_add(&p->Timer, p->Frame.header.duration); + mad_synth_frame(&p->Synth, &p->Frame); + p->cursamp = 0; + } while (1); + + return done; +} + +static int mp3_stopread(snd_stream_t *stream) +{ + mp3_priv_t *p = (mp3_priv_t*) stream->priv; + + mad_synth_finish(&p->Synth); + mad_frame_finish(&p->Frame); + mad_stream_finish(&p->Stream); + + return 0; +} + +static qboolean S_MP3_CodecInitialize (void) +{ + return true; +} + +static void S_MP3_CodecShutdown (void) +{ +} + +static int MP3_check_file (snd_stream_t *stream) +{ + unsigned char magic[16]; + + if (FS_fread(magic, 1, 16, &stream->fh) != 16) + return -1; + if ((magic[0] == 0xFF && (magic[1] & 0xF0) == 0xF0) || + tag_is_id3v2(magic, 16)) + return 0; + if (FS_fseek(&stream->fh, -128, SEEK_END) < 0) + return -1; + if (FS_fread(magic, 1, 16, &stream->fh) != 16) + return -1; + if (tag_is_id3v1(magic, 16)) + return 0; + + return -1; +} + +static snd_stream_t *S_MP3_CodecOpenStream (const char *filename) +{ + snd_stream_t *stream; + + stream = S_CodecUtilOpen(filename, &mp3_codec); + if (!stream) + return NULL; + + stream->priv = Z_Malloc(sizeof(mp3_priv_t)); + + /* check some rudimentary info before feeding it to libmad. */ + if (MP3_check_file(stream) < 0) + { + /* + Con_Printf("%s format couldn't be identified.\n", filename); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); + return NULL; + */ + Con_DPrintf("%s has no identifier tags.\n", filename); + } + + FS_rewind(&stream->fh); + + if (mp3_startread(stream) < 0) + { + Con_Printf("%s is not a valid MP3 file.\n", filename); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); + return NULL; + } + + return stream; +} + +static int S_MP3_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int res = mp3_decode(stream, (byte *)buffer, bytes / stream->info.width); + return res * stream->info.width; +} + +static void S_MP3_CodecCloseStream (snd_stream_t *stream) +{ + mp3_stopread(stream); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_MP3_CodecRewindStream (snd_stream_t *stream) +{ + /* FIXME: do this better */ + mp3_stopread(stream); + FS_rewind(&stream->fh); + return mp3_startread(stream); +} + +snd_codec_t mp3_codec = +{ + CODECTYPE_MP3, + true, /* always available. */ + ".mp3", + S_MP3_CodecInitialize, + S_MP3_CodecShutdown, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecRewindStream, + S_MP3_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_MP3 */ + diff --git a/Quake/snd_mp3.h b/Quake/snd_mp3.h new file mode 100644 index 00000000..69a1c973 --- /dev/null +++ b/Quake/snd_mp3.h @@ -0,0 +1,13 @@ +/* MP3 decoding support using libmad. */ + +#if !defined(_SND_MP3_H_) +#define _SND_MP3_H_ + +#if defined(USE_CODEC_MP3) + +extern snd_codec_t mp3_codec; + +#endif /* USE_CODEC_MP3 */ + +#endif /* ! _SND_MP3_H_ */ + diff --git a/Quake/snd_vorbis.c b/Quake/snd_vorbis.c new file mode 100644 index 00000000..dc07dc52 --- /dev/null +++ b/Quake/snd_vorbis.c @@ -0,0 +1,169 @@ +/* + * Ogg/Vorbis streaming music support, adapted from several open source + * Quake engine based projects with many modifications. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_VORBIS) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_vorbis.h" +#define OV_EXCLUDE_STATIC_CALLBACKS +#include + +/* The OGG codec can return the samples in a number of different + * formats, we use the standard signed short format. */ +#define OGG_SAMPLEWIDTH 2 +#define OGG_SIGNED_DATA 1 + +static int ogg_bigendian = 0; + +/* CALLBACK FUNCTIONS: */ + +static int ovc_fclose (void *f) +{ + return 0; /* we fclose() elsewhere. */ +} + +static int ovc_fseek (void *f, ogg_int64_t off, int whence) +{ + if (f == NULL) return (-1); + return FS_fseek((fshandle_t *)f, (long) off, whence); +} + +static const ov_callbacks ovc_qfs = +{ + (size_t (*)(void *, size_t, size_t, void *)) FS_fread, + (int (*)(void *, ogg_int64_t, int)) ovc_fseek, + (int (*)(void *)) ovc_fclose, + (long (*)(void *)) FS_ftell +}; + +static qboolean S_OGG_CodecInitialize (void) +{ + ogg_bigendian = (bigendien == true) ? 1 : 0; + ogg_codec.initialized = true; + return true; +} + +static void S_OGG_CodecShutdown (void) +{ +} + +static snd_stream_t *S_OGG_CodecOpenStream (const char *filename) +{ + snd_stream_t *stream; + OggVorbis_File *ovFile; + vorbis_info *ogg_info; + int res; + + stream = S_CodecUtilOpen(filename, &ogg_codec); + if (!stream) + return NULL; + + ovFile = (OggVorbis_File *) Z_Malloc(sizeof(OggVorbis_File)); + res = ov_open_callbacks(&stream->fh, ovFile, NULL, 0, ovc_qfs); + if (res < 0) + { + Con_Printf("%s is not a valid Ogg Vorbis file (error %i).\n", filename, res); + Z_Free(ovFile); + S_CodecUtilClose(&stream); + return NULL; + } + + stream->priv = ovFile; + + if (!ov_seekable(ovFile)) + { + Con_Printf("OGG_Open: stream %s not seekable.\n", filename); + ov_clear(ovFile); + Z_Free(ovFile); + S_CodecUtilClose(&stream); + return NULL; + } + + ogg_info = ov_info(ovFile, 0); + if (!ogg_info) + { + Con_Printf("Unable to get stream information for %s.\n", filename); + ov_clear(ovFile); + Z_Free(ovFile); + S_CodecUtilClose(&stream); + return NULL; + } + + stream->info.rate = ogg_info->rate; + stream->info.channels = ogg_info->channels; + stream->info.width = OGG_SAMPLEWIDTH; + + return stream; +} + +static int S_OGG_CodecReadStream (snd_stream_t *stream, int bytes, void *buffer) +{ + int section; /* FIXME: handle section changes */ + int cnt, res, rem; + char * ptr; + + cnt = 0; rem = bytes; + ptr = (char *) buffer; + while (1) + { + res = ov_read((OggVorbis_File *) stream->priv, ptr, rem, ogg_bigendian, + OGG_SAMPLEWIDTH, OGG_SIGNED_DATA, §ion); + if (res <= 0) + break; + rem -= res; + cnt += res; + if (rem <= 0) + break; + ptr += res; + } + return (res >= 0) ? cnt : res; +} + +static void S_OGG_CodecCloseStream (snd_stream_t *stream) +{ + ov_clear((OggVorbis_File *)stream->priv); + Z_Free(stream->priv); + S_CodecUtilClose(&stream); +} + +static int S_OGG_CodecRewindStream (snd_stream_t *stream) +{ + return ov_time_seek ((OggVorbis_File *)stream->priv, 0.0); +} + +snd_codec_t ogg_codec = +{ + CODECTYPE_OGG, + false, + ".ogg", + S_OGG_CodecInitialize, + S_OGG_CodecShutdown, + S_OGG_CodecOpenStream, + S_OGG_CodecReadStream, + S_OGG_CodecRewindStream, + S_OGG_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_VORBIS */ + diff --git a/Quake/snd_vorbis.h b/Quake/snd_vorbis.h new file mode 100644 index 00000000..7efe615c --- /dev/null +++ b/Quake/snd_vorbis.h @@ -0,0 +1,13 @@ +/* Ogg/Vorbis streaming music support. */ + +#if !defined(_SND_VORBIS_H_) +#define _SND_VORBIS_H_ + +#if defined(USE_CODEC_VORBIS) + +extern snd_codec_t ogg_codec; + +#endif /* USE_CODEC_VORBIS */ + +#endif /* ! _SND_VORBIS_H_ */ + diff --git a/Quake/snd_wave.c b/Quake/snd_wave.c new file mode 100644 index 00000000..c3a0a19e --- /dev/null +++ b/Quake/snd_wave.c @@ -0,0 +1,282 @@ +/* + * WAV streaming music support. Adapted from ioquake3 with changes. + * + * Copyright (C) 1999-2005 Id Software, Inc. + * Copyright (C) 2005 Stuart Dalton + * Copyright (C) 2010 O.Sezer + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "quakedef.h" + +#if defined(USE_CODEC_WAVE) +#include "snd_codec.h" +#include "snd_codeci.h" +#include "snd_wave.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong (FILE *f) +{ + int v; + + fread(&v, 1, sizeof(v), f); + + return LittleLong(v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort(FILE *f) +{ + short v; + + fread(&v, 1, sizeof(v), f); + + return LittleShort(v); +} + +/* +================= +WAV_ReadChunkInfo +================= +*/ +static int WAV_ReadChunkInfo(FILE *f, char *name) +{ + int len, r; + + name[4] = 0; + + r = fread(name, 1, 4, f); + if (r != 4) + return -1; + + len = FGetLittleLong(f); + if (len < 0) + { + Con_Printf("WAV: Negative chunk length\n"); + return -1; + } + + return len; +} + +/* +================= +WAV_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int WAV_FindRIFFChunk(FILE *f, const char *chunk) +{ + char name[5]; + int len; + + while ((len = WAV_ReadChunkInfo(f, name)) >= 0) + { + /* If this is the right chunk, return */ + if (!strncmp(name, chunk, 4)) + return len; + len = ((len + 1) & ~1); /* pad by 2 . */ + + /* Not the right chunk - skip it */ + fseek(f, len, SEEK_CUR); + } + + return -1; +} + +/* +================= +WAV_ReadRIFFHeader +================= +*/ +static qboolean WAV_ReadRIFFHeader(const char *name, FILE *file, snd_info_t *info) +{ + char dump[16]; + int wav_format; + int bits; + int fmtlen = 0; + + if (fread(dump, 1, 12, file) < 12 || + strncmp(dump, "RIFF", 4) != 0 || + strncmp(&dump[8], "WAVE", 4) != 0) + { + Con_Printf("%s is missing RIFF/WAVE chunks\n", name); + return false; + } + + /* Scan for the format chunk */ + if ((fmtlen = WAV_FindRIFFChunk(file, "fmt ")) < 0) + { + Con_Printf("%s is missing fmt chunk\n", name); + return false; + } + + /* Save the parameters */ + wav_format = FGetLittleShort(file); + if (wav_format != WAV_FORMAT_PCM) + { + Con_Printf("%s is not Microsoft PCM format\n", name); + return false; + } + + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + bits = FGetLittleShort(file); + + if (bits != 8 && bits != 16) + { + Con_Printf("%s is not 8 or 16 bit\n", name); + return false; + } + + info->width = bits / 8; + info->dataofs = 0; + + /* Skip the rest of the format chunk if required */ + if (fmtlen > 16) + { + fmtlen -= 16; + fseek(file, fmtlen, SEEK_CUR); + } + + /* Scan for the data chunk */ + if ((info->size = WAV_FindRIFFChunk(file, "data")) < 0) + { + Con_Printf("%s is missing data chunk\n", name); + return false; + } + + info->samples = (info->size / info->width) / info->channels; + if (info->samples == 0) + { + Con_Printf("%s has zero samples\n", name); + return false; + } + + return true; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +snd_stream_t *S_WAV_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + long start; + + stream = S_CodecUtilOpen(filename, &wav_codec); + if (!stream) + return NULL; + + start = stream->fh.start; + + /* Read the RIFF header */ + /* The file reads are sequential, therefore no need + * for the FS_*() functions: We will manipulate the + * file by ourselves from now on. */ + if (!WAV_ReadRIFFHeader(filename, stream->fh.file, &stream->info)) + { + S_CodecUtilClose(&stream); + return NULL; + } + + stream->fh.start = ftell(stream->fh.file); /* reset to data position */ + if (stream->fh.start - start + stream->info.size > stream->fh.length) + { + Con_Printf("%s data size mismatch\n", filename); + S_CodecUtilClose(&stream); + return NULL; + } + + return stream; +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->fh.pos; + int i, samples; + + if (remaining <= 0) + return 0; + if (bytes > remaining) + bytes = remaining; + stream->fh.pos += bytes; + fread(buffer, 1, bytes, stream->fh.file); + if (stream->info.width == 2) + { + samples = bytes / 2; + for (i = 0; i < samples; i++) + ((short *)buffer)[i] = LittleShort( ((short *)buffer)[i] ); + } + return bytes; +} + +static void S_WAV_CodecCloseStream (snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +static int S_WAV_CodecRewindStream (snd_stream_t *stream) +{ + FS_rewind(&stream->fh); + return 0; +} + +static qboolean S_WAV_CodecInitialize (void) +{ + return true; +} + +static void S_WAV_CodecShutdown (void) +{ +} + +snd_codec_t wav_codec = +{ + CODECTYPE_WAVE, + true, /* always available. */ + ".wav", + S_WAV_CodecInitialize, + S_WAV_CodecShutdown, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecRewindStream, + S_WAV_CodecCloseStream, + NULL +}; + +#endif /* USE_CODEC_WAVE */ + diff --git a/Quake/snd_wave.h b/Quake/snd_wave.h new file mode 100644 index 00000000..7f7f50bd --- /dev/null +++ b/Quake/snd_wave.h @@ -0,0 +1,13 @@ +/* WAV streaming music support. */ + +#if !defined(_SND_WAVE_H_) +#define _SND_WAVE_H_ + +#if defined(USE_CODEC_WAVE) + +extern snd_codec_t wav_codec; + +#endif /* USE_CODEC_WAVE */ + +#endif /* ! _SND_WAVE_H_ */ +