diff --git a/nanoquake.h b/nanoquake.h new file mode 100644 index 0000000..c6c0364 --- /dev/null +++ b/nanoquake.h @@ -0,0 +1,203 @@ +/* +nanoquake.h -- common quake engine defintions as single-header library +Copyright (C) 1997 id Software +Copyright (C) 2024 Uncle Mike, Alibek Omarov + +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 3 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. +*/ +#ifndef NANOQUAKE_H +#define NANOQUAKE_H + +#include +#include +#include +#include +#include +#include + +#ifdef NANOQUAKE_IMPL +#define EXTERN +#else // NANOQUAKE_IMPL +#define EXTERN extern +#endif + +#define Assert assert + +// +// mathlib +// +#define Q_min( x, y ) (( x ) < ( y ) ? ( x ) : ( y )) +#define Q_max( x, y ) (( x ) > ( y ) ? ( x ) : ( y )) +#define bound( min, val, max ) Q_min( Q_max( min, val ), max ) +#define SetBits( x, y ) ((x) |= (y)) +#define ClearBits( x, y ) ((x) &= ~(y)) +#define FBitSet( x, y ) ((x) & (y)) +#define ARRAYSIZE( array ) ( sizeof( array ) / sizeof( array[0] )) + +// +// common +// +#ifndef __cplusplus +typedef enum { false, true } qboolean; +#else +typedef int qboolean; +#endif +typedef unsigned char byte; +typedef off_t fs_offset_t; +#define Z_Free free +#define Z_Calloc( x ) calloc( 1, x ) +#define Mem_Malloc( x, y ) malloc( y ) +#define Con_Printf printf +#define Con_DPrintf printf +#define Con_Reportf printf +#define Q_atof atof +#define Q_atoi atoi +#define COM_RandomLong( min, max ) ( min + rand() % ( max - min )) +#define Q_strncmp strncmp +#define S_NOTE "Note: " +#define S_WARN "Warning: " +#define S_ERROR "Error: " + +// +// cvars +// + +typedef struct cvar_s +{ + const char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; + +#define CVAR_SENTINEL 0xdeadbeef + +#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) \ + cvar_t cv = { (char*)cvname, (char*)cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL } + +#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) \ + CVAR_DEFINE( cv, #cv, cvstr, cvflags, cvdesc ) + +#define FCVAR_ARCHIVE (1 << 0) +#define FCVAR_CHANGED (1 << 13) + +static inline void Cvar_DirectSet( cvar_t *cv, const char *value ) +{ + free( cv->string ); + cv->string = strdup( value ); + cv->value = atoi( cv->string ); + cv->flags |= FCVAR_CHANGED; +} + +static inline void Cvar_DirectSetValue( cvar_t *cv, float value ) +{ + char str[32]; + + snprintf( str, sizeof( str ), "%f", value ); + + free( cv->string ); + cv->string = strdup( str ); + cv->value = value; + cv->flags |= FCVAR_CHANGED; +} + +static inline void Cvar_RegisterVariable( cvar_t *cv ) +{ + cv->string = strdup( cv->string ); + cv->value = atoi( cv->string ); +} + +// +// cmds +// +#define Cmd_Argc() cmd_argc +#define Cmd_Argv( i ) cmd_argv[i] + +EXTERN int cmd_argc; +EXTERN char **cmd_argv; + +static inline void Cmd_ExecCommand( void (*func)(), int argc, char **argv ) +{ + cmd_argc = argc; + cmd_argv = argv; + func(); +} + +static inline void Cmd_AddCommand( const char *cmd, void *func, const char *desc ) +{ +} +static inline void Cmd_RemoveCommand( const char *cmd ) +{ +} + +// +// time +// +static inline double Sys_DoubleTime( void ) +{ + struct timespec ts; +#if __irix__ + clock_gettime( CLOCK_SGI_CYCLE, &ts ); +#else + clock_gettime( CLOCK_MONOTONIC, &ts ); +#endif + return (double) ts.tv_sec + (double) ts.tv_nsec/1000000000.0; +} + +// +// sound +// +#define SOUND_11k (11025) +#define CLIP( x ) (( x ) > 32760 ? 32760 : (( x ) < -32760 ? -32760 : ( x ))) + +typedef struct +{ + int left; + int right; +} portable_samplepair_t; + +typedef struct +{ + int waterlevel; +} listener_t; + +typedef enum +{ + WF_UNKNOWN = 0, + WF_PCMDATA, + WF_MPGDATA, + WF_TOTALCOUNT, // must be last +} sndformat_t; + +typedef struct sndlib_s +{ + // current sound state + int type; // sound type + int rate; // num samples per second (e.g. 11025 - 11 khz) + int width; // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2) + int channels; // num channels (1 - mono, 2 - stereo) + int loopstart; // start looping from + uint samples; // total samplecount in sound + uint flags; // additional sound flags + size_t size; // sound unpacked size (for bounds checking) + byte *wav; // sound pointer (see sound_type for details) + + byte *tempbuffer; // for convert operations + int cmd_flags; +} sndlib_t; + +EXTERN sndlib_t sound; +EXTERN listener_t s_listener; + +qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesize ); + +#endif // NANOQUAKE_H diff --git a/run_url.sh b/run_url.sh new file mode 100755 index 0000000..129699b --- /dev/null +++ b/run_url.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +gcc *.c -I. -O3 -o hldsp +wget -O temp.opus $1 # not really opus of course +ffmpeg -i temp.opus -ar 22050 temp.wav +./hldsp $2 temp.wav out.wav +ffmpeg -i out.wav out.opus + +rm temp.wav out.wav temp.opus diff --git a/run_yt.sh b/run_yt.sh new file mode 100755 index 0000000..7b0579a --- /dev/null +++ b/run_yt.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +gcc *.c -I. -O3 -o hldsp +yt-dlp --extract-audio $1 --audio-format opus -o temp.opus +ffmpeg -i temp.opus -ar 22050 temp.wav +./hldsp $2 temp.wav out.wav +ffmpeg -i out.wav out.opus + +rm temp.wav out.wav temp.opus diff --git a/s_dsp.c b/s_dsp.c new file mode 100644 index 0000000..64ad40a --- /dev/null +++ b/s_dsp.c @@ -0,0 +1,1165 @@ +/* +s_dsp.c - digital signal processing algorithms for audio FX +Copyright (C) 2009 Uncle Mike +Copyright (C) 2016-2024 Alibek Omarov + +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 3 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. +*/ + +#define NANOQUAKE_IMPL +#include "nanoquake.h" + +#define MAX_DELAY 0.4f +#define MAX_ROOM_TYPES ARRAYSIZE( rgsxpre ) + +#define MONODLY 0 +#define MAX_MONO_DELAY 0.4f + +#define REVERBPOS 1 +#define MAX_REVERB_DELAY 0.1f + +#define STEREODLY 3 +#define MAX_STEREO_DELAY 0.1f + +#define REVERB_XFADE 32 + +#define MAXDLY (STEREODLY + 1) +#define MAXLP 10 + +typedef struct sx_preset_s +{ + float room_lp; // lowpass + float room_mod; // modulation + + // reverb + float room_size; + float room_refl; + float room_rvblp; + + // delay + float room_delay; + float room_feedback; + float room_dlylp; + float room_left; +} sx_preset_t; + +typedef struct dly_s +{ + size_t cdelaysamplesmax; // delay line array size + + // delay line pointers + size_t idelayinput; + size_t idelayoutput; + + // crossfade + int idelayoutputxf; // output pointer + int xfade; // value + + int delaysamples; // delay setting + int delayfeedback; // feedback setting + + // lowpass + int lp; // is lowpass enabled + int lp0, lp1, lp2; // lowpass buffer + + // modulation + int mod; + int modcur; + + // delay line + int *lpdelayline; +} dly_t; + +static const sx_preset_t rgsxpre[] = +{ +// -------reverb-------- -------delay-------- +// lp mod size refl rvblp delay feedback dlylp left +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0 }, // 0 off +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.065, 0.1, 0.0, 0.01 }, // 1 generic +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.02, 0.75, 0.0, 0.01 }, // 2 metalic +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.03, 0.78, 0.0, 0.02 }, // 3 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.77, 0.0, 0.03 }, // 4 +{ 0.0, 0.0, 0.05, 0.85, 1.0, 0.008, 0.96, 2.0, 0.01 }, // 5 tunnel +{ 0.0, 0.0, 0.05, 0.88, 1.0, 0.01, 0.98, 2.0, 0.02 }, // 6 +{ 0.0, 0.0, 0.05, 0.92, 1.0, 0.015, 0.995, 2.0, 0.04 }, // 7 +{ 0.0, 0.0, 0.05, 0.84, 1.0, 0.0, 0.0, 2.0, 0.012 }, // 8 chamber +{ 0.0, 0.0, 0.05, 0.9, 1.0, 0.0, 0.0, 2.0, 0.008 }, // 9 +{ 0.0, 0.0, 0.05, 0.95, 1.0, 0.0, 0.0, 2.0, 0.004 }, // 10 +{ 0.0, 0.0, 0.05, 0.7, 0.0, 0.0, 0.0, 2.0, 0.012 }, // 11 brite +{ 0.0, 0.0, 0.055, 0.78, 0.0, 0.0, 0.0, 2.0, 0.008 }, // 12 +{ 0.0, 0.0, 0.05, 0.86, 0.0, 0.0, 0.0, 2.0, 0.002 }, // 13 +{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.01 }, // 14 water +{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.85, 2.0, 0.02 }, // 15 +{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.2, 0.6, 2.0, 0.05 }, // 16 +{ 0.0, 0.0, 0.05, 0.8, 1.0, 0.0, 0.48, 2.0, 0.016 }, // 17 concrete +{ 0.0, 0.0, 0.06, 0.9, 1.0, 0.0, 0.52, 2.0, 0.01 }, // 18 +{ 0.0, 0.0, 0.07, 0.94, 1.0, 0.3, 0.6, 2.0, 0.008 }, // 19 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.3, 0.42, 2.0, 0.0 }, // 20 outside +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.35, 0.48, 2.0, 0.0 }, // 21 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.38, 0.6, 2.0, 0.0 }, // 22 +{ 0.0, 0.0, 0.05, 0.9, 1.0, 0.2, 0.28, 0.0, 0.0 }, // 23 cavern +{ 0.0, 0.0, 0.07, 0.9, 1.0, 0.3, 0.4, 0.0, 0.0 }, // 24 +{ 0.0, 0.0, 0.09, 0.9, 1.0, 0.35, 0.5, 0.0, 0.0 }, // 25 +{ 0.0, 1.0, 0.01, 0.9, 0.0, 0.0, 0.0, 2.0, 0.05 }, // 26 weirdo +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.009, 0.999, 2.0, 0.04 }, // 27 +{ 0.0, 0.0, 0.001, 0.999, 0.0, 0.2, 0.8, 2.0, 0.05 } // 28 +}; + +// 0x0045dca8 enginegl.exe +// SHA256: 42383d32cd712e59ee2c1bd78b7ba48814e680e7026c4223e730111f34a60d66 +static const sx_preset_t rgsxpre_hlalpha052[] = +{ +// -------reverb-------- -------delay-------- +// lp mod size refl rvblp delay feedback dlylp left +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0 }, // 0 off +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.08, 0.8, 2.0, 0.0 }, // 1 generic +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.02, 0.75, 0.0, 0.001 }, // 2 metalic +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.03, 0.78, 0.0, 0.002 }, // 3 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.77, 0.0, 0.003 }, // 4 +{ 0.0, 0.0, 0.05, 0.85, 1.0, 0.008, 0.96, 2.0, 0.01 }, // 5 tunnel +{ 0.0, 0.0, 0.05, 0.88, 1.0, 0.01, 0.98, 2.0, 0.02 }, // 6 +{ 0.0, 0.0, 0.05, 0.92, 1.0, 0.015, 0.995, 2.0, 0.04 }, // 7 +{ 0.0, 0.0, 0.05, 0.84, 1.0, 0.0, 0.0, 2.0, 0.003 }, // 8 chamber +{ 0.0, 0.0, 0.05, 0.9, 1.0, 0.0, 0.0, 2.0, 0.002 }, // 9 +{ 0.0, 0.0, 0.05, 0.95, 1.0, 0.0, 0.0, 2.0, 0.001 }, // 10 +{ 0.0, 0.0, 0.05, 0.7, 0.0, 0.0, 0.0, 2.0, 0.003 }, // 11 brite +{ 0.0, 0.0, 0.055, 0.78, 0.0, 0.0, 0.0, 2.0, 0.002 }, // 12 +{ 0.0, 0.0, 0.05, 0.86, 0.0, 0.0, 0.0, 2.0, 0.001 }, // 13 +{ 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.01 }, // 14 water +{ 1.0, 1.0, 0.0, 0.0, 1.0, 0.06, 0.85, 2.0, 0.02 }, // 15 +{ 1.0, 1.0, 0.0, 0.0, 1.0, 0.2, 0.6, 2.0, 0.05 }, // 16 +{ 0.0, 0.0, 0.05, 0.8, 1.0, 0.15, 0.48, 2.0, 0.008 }, // 17 concrete +{ 0.0, 0.0, 0.06, 0.9, 1.0, 0.22, 0.52, 2.0, 0.005 }, // 18 +{ 0.0, 0.0, 0.07, 0.94, 1.0, 0.3, 0.6, 2.0, 0.001 }, // 19 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.3, 0.42, 2.0, 0.0 }, // 20 outside +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.35, 0.48, 2.0, 0.0 }, // 21 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.38, 0.6, 2.0, 0.0 }, // 22 +{ 0.0, 0.0, 0.05, 0.9, 1.0, 0.2, 0.28, 0.0, 0.0 }, // 23 cavern +{ 0.0, 0.0, 0.07, 0.9, 1.0, 0.3, 0.4, 0.0, 0.0 }, // 24 +{ 0.0, 0.0, 0.09, 0.9, 1.0, 0.35, 0.5, 0.0, 0.0 }, // 25 +{ 0.0, 1.0, 0.01, 0.9, 0.0, 0.0, 0.0, 2.0, 0.05 }, // 26 weirdo +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.009, 0.999, 2.0, 0.04 }, // 27 +{ 0.0, 0.0, 0.001, 0.999, 0.0, 0.2, 0.8, 2.0, 0.05 }, // 28 +}; + +static const sx_preset_t *ptable = rgsxpre; + +// cvars +static CVAR_DEFINE_AUTO( dsp_off, "0", FCVAR_ARCHIVE, "disable DSP processing" ); +static CVAR_DEFINE_AUTO( dsp_coeff_table, "0", FCVAR_ARCHIVE, "select DSP coefficient table: 0 for release or 1 for alpha 0.52" ); +static CVAR_DEFINE_AUTO( room_type, "0", 0, "current room type preset" ); + +static CVAR_DEFINE( roomwater_type, "waterroom_type", "14", 0, "water room type" ); +static CVAR_DEFINE( hisound, "room_hires", "2", FCVAR_ARCHIVE, "dsp quality. 1 for 22k, 2 for 44k(recommended) and 3 for 96k" ); + +// underwater/special fx modulations +static CVAR_DEFINE( sxmod_mod, "room_mod", "0", 0, "stereo amptitude modulation for room" ); +static CVAR_DEFINE( sxmod_lowpass, "room_lp", "0", 0, "for water fx, lowpass for entire room" ); + +// stereo delay(no feedback) +static CVAR_DEFINE( sxste_delay, "room_left", "0", 0, "left channel delay time" ); + +// mono reverb +static CVAR_DEFINE( sxrvb_lp, "room_rvblp", "1", 0, "reverb: low pass filtering level" ); +static CVAR_DEFINE( sxrvb_feedback, "room_refl", "0", 0, "reverb: decay time" ); +static CVAR_DEFINE( sxrvb_size, "room_size", "0", 0, "reverb: initial reflection size" ); + +// mono delay +static CVAR_DEFINE( sxdly_lp, "room_dlylp", "1", 0, "mono delay: low pass filtering level" ); +static CVAR_DEFINE( sxdly_feedback, "room_feedback", "0.2", 0, "mono delay: decay time" ); +static CVAR_DEFINE( sxdly_delay, "room_delay", "0.8", 0, "mono delay: delay time" ); + +static int idsp_dma_speed; +int idsp_room; +static int room_typeprev; + +// routines +static int sxamodl, sxamodr; // amplitude modulation values +static int sxamodlt, sxamodrt; // modulation targets +static int sxmod1cur, sxmod2cur; +static int sxmod1, sxmod2; +static int sxhires; + +static portable_samplepair_t *paintto = NULL; + +static dly_t rgsxdly[MAXDLY]; // stereo is last +static int rgsxlp[MAXLP]; + +static void SX_Profiling_f( void ); + +/* +============ +SX_ReloadRoomFX + +============ +*/ +static void SX_ReloadRoomFX( void ) +{ + SetBits( sxste_delay.flags, FCVAR_CHANGED ); + SetBits( sxrvb_feedback.flags, FCVAR_CHANGED ); + SetBits( sxdly_delay.flags, FCVAR_CHANGED ); + SetBits( room_type.flags, FCVAR_CHANGED ); +} + +/* +============ +SX_Init() + +Starts sound crackling system +============ +*/ +void SX_Init( void ) +{ + memset( rgsxdly, 0, sizeof( rgsxdly )); + memset( rgsxlp, 0, sizeof( rgsxlp )); + + sxamodr = sxamodl = sxamodrt = sxamodlt = 255; + idsp_dma_speed = SOUND_11k; + + Cvar_RegisterVariable( &hisound ); + sxhires = 2; + + sxmod1cur = sxmod1 = 350 * ( idsp_dma_speed / SOUND_11k ); + sxmod2cur = sxmod2 = 450 * ( idsp_dma_speed / SOUND_11k ); + + Cvar_RegisterVariable( &dsp_off ); + Cvar_RegisterVariable( &dsp_coeff_table ); + + Cvar_RegisterVariable( &roomwater_type ); + Cvar_RegisterVariable( &room_type ); + + Cvar_RegisterVariable( &sxmod_lowpass ); + Cvar_RegisterVariable( &sxmod_mod ); + + Cvar_RegisterVariable( &sxrvb_size ); + Cvar_RegisterVariable( &sxrvb_feedback ); + Cvar_RegisterVariable( &sxrvb_lp ); + + Cvar_RegisterVariable( &sxdly_delay ); + Cvar_RegisterVariable( &sxdly_feedback ); + Cvar_RegisterVariable( &sxdly_lp ); + + Cvar_RegisterVariable( &sxste_delay ); + + Cmd_AddCommand( "dsp_profile", SX_Profiling_f, "dsp stress-test, first argument is room_type" ); + + SX_ReloadRoomFX(); +} + +/* +=========== +DLY_Free + +Free memory allocated for DSP +=========== +*/ +static void DLY_Free( int idelay ) +{ + Assert( idelay >= 0 && idelay < MAXDLY ); + + if( rgsxdly[idelay].lpdelayline ) + { + Z_Free( rgsxdly[idelay].lpdelayline ); + rgsxdly[idelay].lpdelayline = NULL; + } +} + +/* +========== +SX_Shutdown + +Stop DSP processor +========== +*/ +void SX_Free( void ) +{ + int i; + + for( i = 0; i <= 3; i++ ) + DLY_Free( i ); + + Cmd_RemoveCommand( "dsp_profile" ); +} + + +/* +=========== +DLY_Init + +Initialize dly +=========== +*/ +static int DLY_Init( int idelay, float delay ) +{ + dly_t *cur; + + // DLY_Init called anytime with constants. So valid it in debug builds only. + Assert( idelay >= 0 && idelay < MAXDLY ); + Assert( delay > 0.0f && delay <= MAX_DELAY ); + + DLY_Free( idelay ); // free dly if it's allocated + + cur = &rgsxdly[idelay]; + cur->cdelaysamplesmax = ((int)(delay * idsp_dma_speed) << sxhires) + 1; + cur->lpdelayline = (int *)Z_Calloc( cur->cdelaysamplesmax * sizeof( int )); + cur->xfade = 0; + + // init modulation + cur->mod = cur->modcur = 0; + + // init lowpass + cur->lp = 1; + cur->lp0 = cur->lp1 = cur->lp2 = 0; + + cur->idelayinput = 0; + cur->idelayoutput = cur->cdelaysamplesmax - cur->delaysamples; // NOTE: delaysamples must be set!!! + + + return 1; +} + +/* +============ +DLY_MovePointer + +Checks overflow and moves pointer +============ +*/ +static void DLY_MovePointer( dly_t *dly ) +{ + if( ++dly->idelayinput >= dly->cdelaysamplesmax ) + dly->idelayinput = 0; + + if( ++dly->idelayoutput >= dly->cdelaysamplesmax ) + dly->idelayoutput = 0; +} + +/* +============= +DLY_CheckNewStereoDelayVal + +Update stereo processor settings if we are in new room +============= +*/ +static void DLY_CheckNewStereoDelayVal( void ) +{ + dly_t *const dly = &rgsxdly[STEREODLY]; + float delay = sxste_delay.value; + + if( !FBitSet( sxste_delay.flags, FCVAR_CHANGED )) + return; + + if( delay == 0 ) + { + DLY_Free( STEREODLY ); + } + else + { + int samples; + + delay = Q_min( delay, MAX_STEREO_DELAY ); + samples = (int)(delay * idsp_dma_speed) << sxhires; + + // re-init dly + if( !dly->lpdelayline ) + { + dly->delaysamples = samples; + DLY_Init( STEREODLY, MAX_STEREO_DELAY ); + } + + if( dly->delaysamples != samples ) + { + dly->xfade = 128; + dly->idelayoutputxf = dly->idelayinput - samples; + if( dly->idelayoutputxf < 0 ) + dly->idelayoutputxf += dly->cdelaysamplesmax; + } + + dly->modcur = dly->mod = 0; + + if( dly->delaysamples == 0 ) + DLY_Free( STEREODLY ); + } +} + +/* +============= +DLY_DoStereoDelay + +Do stereo processing +============= +*/ +static void DLY_DoStereoDelay( int count ) +{ + int delay, samplexf; + dly_t *const dly = &rgsxdly[STEREODLY]; + portable_samplepair_t *paint = paintto; + + if( !dly->lpdelayline ) + return; // inactive + + for( ; count; count--, paint++ ) + { + if( dly->mod && --dly->modcur < 0 ) + dly->modcur = dly->mod; + + delay = dly->lpdelayline[dly->idelayoutput]; + + // process only if crossfading, active left value or delayline + if( delay || paint->left || dly->xfade ) + { + // set up new crossfade, if not crossfading, not modulating, but going to + if( !dly->xfade && !dly->modcur && dly->mod ) + { + dly->idelayoutputxf = dly->idelayoutput + ((COM_RandomLong( 0, 255 ) * dly->delaysamples ) >> 9 ); + + dly->xfade = 128; + } + + dly->idelayoutputxf %= dly->cdelaysamplesmax; + + // modify delay, if crossfading + if( dly->xfade ) + { + samplexf = dly->lpdelayline[dly->idelayoutputxf] * (128 - dly->xfade) >> 7; + delay = samplexf + ((delay * dly->xfade) >> 7); + + if( ++dly->idelayoutputxf >= dly->cdelaysamplesmax ) + dly->idelayoutputxf = 0; + + if( --dly->xfade == 0 ) + dly->idelayoutput = dly->idelayoutputxf; + } + + // save left value to delay line + dly->lpdelayline[dly->idelayinput] = CLIP( paint->left ); + + // paint new delay value + paint->left = delay; + } + else + { + // clear delay line + dly->lpdelayline[dly->idelayinput] = 0; + } + + DLY_MovePointer( dly ); + } +} + +/* +============= +DLY_CheckNewDelayVal + +Update delay processor settings if we are in new room +============= +*/ +static void DLY_CheckNewDelayVal( void ) +{ + float delay = sxdly_delay.value; + dly_t *const dly = &rgsxdly[MONODLY]; + + if( FBitSet( sxdly_delay.flags, FCVAR_CHANGED )) + { + if( delay == 0 ) + { + DLY_Free( MONODLY ); + } + else + { + delay = Q_min( delay, MAX_MONO_DELAY ); + dly->delaysamples = (int)(delay * idsp_dma_speed) << sxhires; + + // init dly + if( !dly->lpdelayline ) + DLY_Init( MONODLY, MAX_MONO_DELAY ); + + if( dly->lpdelayline ) + { + memset( dly->lpdelayline, 0, dly->cdelaysamplesmax * sizeof( int ) ); + dly->lp0 = dly->lp1 = dly->lp2 = 0; + } + + dly->idelayinput = 0; + dly->idelayoutput = dly->cdelaysamplesmax - dly->delaysamples; + + if( !dly->delaysamples ) + DLY_Free( MONODLY ); + + } + } + + dly->lp = sxdly_lp.value; + dly->delayfeedback = 255 * sxdly_feedback.value; +} + +/* +============= +DLY_DoDelay + +Do delay processing +============= +*/ +static void DLY_DoDelay( int count ) +{ + dly_t *const dly = &rgsxdly[MONODLY]; + portable_samplepair_t *paint = paintto; + int delay; + + if( !dly->lpdelayline || !count ) + return; // inactive + + for( ; count; count--, paint++ ) + { + delay = dly->lpdelayline[dly->idelayoutput]; + + // don't process if delay line and left/right samples are zero + if( delay || paint->left || paint->right ) + { + // calculate delayed value from average + int val = (( paint->left + paint->right ) >> 1 ) + (( dly->delayfeedback * delay ) >> 8); + val = CLIP( val ); + + if( dly->lp ) // lowpass + { + val = ( dly->lp0 + dly->lp1 + val ) / 3; + dly->lp0 = dly->lp1; + dly->lp1 = val; + } + + dly->lpdelayline[dly->idelayinput] = val; + + val >>= 2; + + paint->left = CLIP( paint->left + val ); + paint->right = CLIP( paint->right + val ); + } + else + { + dly->lpdelayline[dly->idelayinput] = 0; + dly->lp0 = dly->lp1 = dly->lp2 = 0; + } + + DLY_MovePointer( dly ); + } +} + +/* +=========== +RVB_SetUpDly + +Set up dly for reverb +=========== +*/ +static void RVB_SetUpDly( int pos, float delay, int kmod ) +{ + int samples; + + delay = Q_min( delay, MAX_REVERB_DELAY ); + samples = (int)(delay * idsp_dma_speed) << sxhires; + + if( !rgsxdly[pos].lpdelayline ) + { + rgsxdly[pos].delaysamples = samples; + DLY_Init( pos, MAX_REVERB_DELAY ); + } + + rgsxdly[pos].modcur = rgsxdly[pos].mod = (int)(kmod * idsp_dma_speed / SOUND_11k) << sxhires; + + // set up crossfade, if delay has changed + if( rgsxdly[pos].delaysamples != samples ) + { + rgsxdly[pos].idelayoutputxf = rgsxdly[pos].idelayinput - samples; + if( rgsxdly[pos].idelayoutputxf < 0 ) + rgsxdly[pos].idelayoutputxf += rgsxdly[pos].cdelaysamplesmax; + rgsxdly[pos].xfade = REVERB_XFADE; + } + + if( !rgsxdly[pos].delaysamples ) + DLY_Free( pos ); + +} + +/* +=========== +RVB_CheckNewReverbVal + +Update reverb settings if we are in new room +=========== +*/ +static void RVB_CheckNewReverbVal( void ) +{ + dly_t *const dly1 = &rgsxdly[REVERBPOS]; + dly_t *const dly2 = &rgsxdly[REVERBPOS + 1]; + float delay = sxrvb_size.value; + + if( FBitSet( sxrvb_size.flags, FCVAR_CHANGED )) + { + if( delay == 0.0f ) + { + DLY_Free( REVERBPOS ); + DLY_Free( REVERBPOS + 1 ); + } + else + { + RVB_SetUpDly( REVERBPOS, sxrvb_size.value, 500 ); + RVB_SetUpDly( REVERBPOS+1, sxrvb_size.value * 0.71f, 700 ); + } + } + + dly1->lp = dly2->lp = sxrvb_lp.value; + dly1->delayfeedback = dly2->delayfeedback = (int)(255 * sxrvb_feedback.value); +} + +/* +=========== +RVB_DoReverbForOneDly + +Do reverberation for one dly +=========== +*/ +static int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samplepair_t *samplepair ) +{ + int delay; + int samplexf; + int val, valt; + int voutm = 0; + + if( --dly->modcur < 0 ) + dly->modcur = dly->mod; + + delay = dly->lpdelayline[dly->idelayoutput]; + + if( dly->xfade || delay || samplepair->left || samplepair->right ) + { + // modulate delay rate + if( !dly->mod ) + { + dly->idelayoutputxf = dly->idelayoutput + ((COM_RandomLong( 0, 255 ) * delay) >> 9 ); + + dly->idelayoutputxf %= dly->cdelaysamplesmax; + + dly->xfade = REVERB_XFADE; + } + + if( dly->xfade ) + { + samplexf = (dly->lpdelayline[dly->idelayoutputxf] * (REVERB_XFADE - dly->xfade)) / REVERB_XFADE; + delay = ((delay * dly->xfade) / REVERB_XFADE) + samplexf; + + if( ++dly->idelayoutputxf >= dly->cdelaysamplesmax ) + dly->idelayoutputxf = 0; + + if( --dly->xfade == 0 ) + dly->idelayoutput = dly->idelayoutputxf; + } + + + if( delay ) + { + val = vlr + ( ( dly->delayfeedback * delay ) >> 8 ); + val = CLIP( val ); + } + else + val = vlr; + + if( dly->lp ) + { + valt = (dly->lp0 + val) >> 1; + dly->lp0 = val; + } + else + valt = val; + + voutm = dly->lpdelayline[dly->idelayinput] = valt; + } + else + { + voutm = dly->lpdelayline[dly->idelayinput] = 0; + dly->lp0 = 0; + } + + DLY_MovePointer( dly ); + + return voutm; + +} + +/* +=========== +RVB_DoReverb + +Do reverberation processing +=========== +*/ +static void RVB_DoReverb( int count ) +{ + dly_t *const dly1 = &rgsxdly[REVERBPOS]; + dly_t *const dly2 = &rgsxdly[REVERBPOS+1]; + portable_samplepair_t *paint = paintto; + int vlr, voutm; + + if( !dly1->lpdelayline ) + return; + + for( ; count; count--, paint++ ) + { + vlr = ( paint->left + paint->right ) >> 1; + + voutm = RVB_DoReverbForOneDly( dly1, vlr, paint ); + voutm += RVB_DoReverbForOneDly( dly2, vlr, paint ); + + if( dsp_coeff_table.value == 1.0f ) + voutm /= 6; // alpha + else voutm = (11 * voutm) >> 6; + + paint->left = CLIP( paint->left + voutm ); + paint->right = CLIP( paint->right + voutm ); + } +} + +/* +=========== +RVB_DoAMod + +Do amplification modulation processing +=========== +*/ +static void RVB_DoAMod( int count ) +{ + portable_samplepair_t *paint = paintto; + + if( !sxmod_lowpass.value && !sxmod_mod.value ) + return; + + for( ; count; count--, paint++ ) + { + portable_samplepair_t res = *paint; + + if( sxmod_lowpass.value ) + { + res.left = rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + res.left; + res.right = rgsxlp[5] + rgsxlp[6] + rgsxlp[7] + rgsxlp[8] + rgsxlp[9] + res.right; + + res.left >>= 2; + res.right >>= 2; + + rgsxlp[4] = paint->left; + rgsxlp[9] = paint->right; + + rgsxlp[0] = rgsxlp[1]; + rgsxlp[1] = rgsxlp[2]; + rgsxlp[2] = rgsxlp[3]; + rgsxlp[3] = rgsxlp[4]; + rgsxlp[4] = rgsxlp[5]; + rgsxlp[5] = rgsxlp[6]; + rgsxlp[6] = rgsxlp[7]; + rgsxlp[7] = rgsxlp[8]; + rgsxlp[8] = rgsxlp[9]; + } + + if( sxmod_mod.value ) + { + if( --sxmod1cur < 0 ) + sxmod1cur = sxmod1; + + if( !sxmod1 ) + sxamodlt = COM_RandomLong( 32, 255 ); + + if( --sxmod2cur < 0 ) + sxmod2cur = sxmod2; + + if( !sxmod2 ) + sxamodrt = COM_RandomLong( 32, 255 ); + + res.left = (sxamodl * res.left) >> 8; + res.right = (sxamodr * res.right) >> 8; + + if( sxamodl < sxamodlt ) + sxamodl++; + else if( sxamodl > sxamodlt ) + sxamodl--; + + if( sxamodr < sxamodrt ) + sxamodr++; + else if( sxamodr > sxamodrt ) + sxamodr--; + } + + paint->left = CLIP(res.left); + paint->right = CLIP(res.right); + } +} + +/* +=========== +DSP_Process + +(xash dsp interface) +=========== +*/ +void DSP_Process( int idsp, portable_samplepair_t *pbfront, int sampleCount ) +{ + if( dsp_off.value ) + return; + + if( !sampleCount ) + return; + + // preset is already installed by CheckNewDspPresets + paintto = pbfront; + + RVB_DoAMod( sampleCount ); + RVB_DoReverb( sampleCount ); + DLY_DoDelay( sampleCount ); + DLY_DoStereoDelay( sampleCount ); +} + +/* +=========== +DSP_ClearState + +(xash dsp interface) +=========== +*/ +void DSP_ClearState( void ) +{ + Cvar_DirectSet( &room_type, "0" ); + SX_ReloadRoomFX(); +} + +/* +=========== +CheckNewDspPresets + +(xash dsp interface) +=========== +*/ +void CheckNewDspPresets( void ) +{ + if( dsp_off.value != 0.0f ) + return; + + if( FBitSet( dsp_coeff_table.flags, FCVAR_CHANGED )) + { + switch( (int)dsp_coeff_table.value ) + { + case 0: // release + ptable = rgsxpre; + break; + case 1: // alpha + ptable = rgsxpre_hlalpha052; + break; + default: + ptable = rgsxpre; + break; + } + + SX_ReloadRoomFX(); + room_typeprev = -1; + + ClearBits( dsp_coeff_table.flags, FCVAR_CHANGED ); + } + + if( s_listener.waterlevel > 2 ) + idsp_room = roomwater_type.value; + else idsp_room = room_type.value; + + // don't pass invalid presets + idsp_room = bound( 0, idsp_room, MAX_ROOM_TYPES ); + + if( FBitSet( hisound.flags, FCVAR_CHANGED )) + { + sxhires = hisound.value; + ClearBits( hisound.flags, FCVAR_CHANGED ); + } + + if( idsp_room == room_typeprev && idsp_room == 0 ) + return; + + if( idsp_room != room_typeprev ) + { + const sx_preset_t *cur; + + cur = ptable + idsp_room; + + Cvar_DirectSetValue( &sxmod_lowpass, cur->room_lp ); + Cvar_DirectSetValue( &sxmod_mod, cur->room_mod ); + Cvar_DirectSetValue( &sxrvb_size, cur->room_size ); + Cvar_DirectSetValue( &sxrvb_feedback, cur->room_refl ); + Cvar_DirectSetValue( &sxrvb_lp, cur->room_rvblp ); + Cvar_DirectSetValue( &sxdly_delay, cur->room_delay ); + Cvar_DirectSetValue( &sxdly_feedback, cur->room_feedback ); + Cvar_DirectSetValue( &sxdly_lp, cur->room_dlylp ); + Cvar_DirectSetValue( &sxste_delay, cur->room_left ); + } + + room_typeprev = idsp_room; + + RVB_CheckNewReverbVal( ); + DLY_CheckNewDelayVal( ); + DLY_CheckNewStereoDelayVal(); + + ClearBits( sxrvb_size.flags, FCVAR_CHANGED ); + ClearBits( sxdly_delay.flags, FCVAR_CHANGED ); + ClearBits( sxste_delay.flags, FCVAR_CHANGED ); +} + +static void SX_Profiling_f( void ) +{ + portable_samplepair_t testbuffer[512]; + float oldroom = room_type.value; + double start, end; + int i, calls; + + for( i = 0; i < 512; i++ ) + { + testbuffer[i].left = COM_RandomLong( 0, 3000 ); + testbuffer[i].right = COM_RandomLong( 0, 3000 ); + } + + if( Cmd_Argc() > 1 ) + { + Cvar_DirectSet( &room_type, Cmd_Argv( 1 )); + SX_ReloadRoomFX(); + CheckNewDspPresets(); // we just need idsp_room immediately, for message below + } + + Con_Printf( "Profiling 10000 calls to DSP. Sample count is 512, room_type is %i\n", idsp_room ); + + start = Sys_DoubleTime(); + for( calls = 10000; calls; calls-- ) + { + DSP_Process( idsp_room, testbuffer, 512 ); + } + end = Sys_DoubleTime(); + + Con_Printf( "----------\nTook %g seconds.\n", end - start ); + + if( Cmd_Argc() > 1 ) + { + Cvar_DirectSetValue( &room_type, oldroom ); + SX_ReloadRoomFX(); + CheckNewDspPresets(); + } +} + +void dump_to_file( portable_samplepair_t *buffer, size_t size, FILE *out ) +{ + int16_t temp16; + + for( size_t k = 0; k < size; k++ ) + { + temp16 = buffer[k].left; + fwrite( &temp16, 2, 1, out ); + temp16 = buffer[k].right; + fwrite( &temp16, 2, 1, out ); + } +} + +size_t trim( portable_samplepair_t *buffer, size_t size ) +{ + int silence_samples = 0; + size_t i; + + for( i = 0; i < size; i++ ) + { + if( abs( buffer[i].left ) < 2 && abs( buffer[i].right ) < 2 ) + silence_samples++; + else silence_samples = 0; + + if( silence_samples == 16 ) // tune this + break; + } + + return i; +} + +int main( int argc, char **argv ) +{ + int room; + const char *in_path, *out_path; + FILE *in, *out; + char *in_buffer; + off_t in_size, out_size_off1, out_size_off2; + uint32_t final_size; + portable_samplepair_t buffer[512]; + + if( argc == 4 ) + { + room = Q_atoi( argv[1] ); + in_path = argv[2]; + out_path = argv[3]; + } + else + { + printf( "Usage: %s \n", argv[0] ); + return EXIT_FAILURE; + } + + in = fopen( in_path, "rb" ); + if( !in ) + { + perror( "Can't open file for reading" ); + return EXIT_FAILURE; + } + + fseek( in, 0, SEEK_END ); + in_size = ftell( in ); + fseek( in, 0, SEEK_SET ); + + in_buffer = malloc( in_size ); + if( !in_buffer ) + { + perror( "Out of memory" ); + fclose( in ); + return EXIT_FAILURE; + } + + if( fread( in_buffer, 1, in_size, in ) != in_size ) + { + perror( "Can't read file" ); + + free( in_buffer ); + fclose( in ); + return EXIT_FAILURE; + } + + fclose( in ); + + if( !Sound_LoadWAV( in_path, in_buffer, in_size )) + { + free( in_buffer ); + return EXIT_FAILURE; + } + + free( in_buffer ); + + out = fopen( out_path, "wb" ); + if( !out ) + { + perror( "Can't open file for writing" ); + free( sound.wav ); + return EXIT_FAILURE; + } + + // write header + fwrite( "RIFF", 1, 4, out ); + out_size_off1 = ftell( out ); // remember, will patch later + uint32_t temp32 = sound.size + 36; + fwrite( &temp32, 4, 1, out ); + fwrite( "WAVE", 1, 4, out ); + + // write fmt header + fwrite( "fmt ", 1, 4, out ); + temp32 = 16; + fwrite( &temp32, 4, 1, out ); + uint16_t temp16 = 1; // write pcm data + fwrite( &temp16, 2, 1, out ); + temp16 = 2; // channels + fwrite( &temp16, 2, 1, out ); + fwrite( &sound.rate, 4, 1, out ); + temp32 = sound.rate * 16 /* width */ * 2 /* channels */ / 8; + fwrite( &temp32, 4, 1, out ); + temp16 = 16 /* width */ * 2 /* channels */ / 8; + fwrite( &temp16, 2, 1, out ); + temp16 = 16; + fwrite( &temp16, 2, 1, out ); + + // prepare to write data + fwrite( "data", 1, 4, out ); + out_size_off2 = ftell( out ); // remember, will patch later + fwrite( &sound.size, 4, 1, out ); + + printf( "Loaded WAV file, starting processing...\n" ); + + SX_Init(); + + Cvar_DirectSetValue( &room_type, room ); + CheckNewDspPresets(); + + for( int i = 0, j = 0; i < sound.samples ; i++, j++ ) + { + if( j == ARRAYSIZE( buffer )) + { + putchar( '*' ); + DSP_Process( 0, buffer, ARRAYSIZE( buffer )); + dump_to_file( buffer, ARRAYSIZE( buffer ), out ); + j = 0; + } + + if( sound.width == 1 ) + { + int8_t *data = (int8_t *)&sound.wav[i * sound.channels * sound.width]; + + if( sound.channels == 1 ) + { + buffer[j].left = data[0] << 8; + buffer[j].right = data[0] << 8; + } + else if( sound.channels == 2 ) + { + buffer[j].left = data[0] << 8; + buffer[j].right = data[1] << 8; + } + } + else if( sound.width == 2 ) + { + int16_t *data = (int16_t *)&sound.wav[i * sound.channels * sound.width]; + + if( sound.channels == 1 ) + { + buffer[j].left = data[0]; + buffer[j].right = data[0]; + } + else if( sound.channels == 2 ) + { + buffer[j].left = data[0]; + buffer[j].right = data[1]; + } + } + } + + printf( "\nmain sfx is done, writing remaining dsp processed data...\n" ); + qboolean silence = false; + for( int i = 0; !silence && i < 256; i++ ) + { + memset( buffer, 0, sizeof( buffer )); + + DSP_Process( 0, buffer, ARRAYSIZE( buffer )); + + size_t pos = trim( buffer, ARRAYSIZE( buffer )); + + if( !pos ) + break; + + // some more data or we finished? + if( pos != ARRAYSIZE( buffer )) + silence = true; + + putchar( '.' ); + dump_to_file( buffer, pos, out ); + } + putchar( '\n' ); + + final_size = ftell( out ) - out_size_off2 - 4; + fseek( out, out_size_off1, SEEK_SET ); + temp32 = final_size + 36; + fwrite( &temp32, 4, 1, out ); + fseek( out, out_size_off2, SEEK_SET ); + fwrite( &final_size, 4, 1, out ); + + fclose( out ); + + SX_Free(); + + return EXIT_SUCCESS; +} diff --git a/snd_wav.c b/snd_wav.c new file mode 100644 index 0000000..b910358 --- /dev/null +++ b/snd_wav.c @@ -0,0 +1,255 @@ +/* +snd_wav.c - wav format load & save +Copyright (C) 2010 Uncle Mike +Copyright (C) 2023 FTEQW developers + +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 3 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. +*/ + +#include +#include "nanoquake.h" + +static const byte *iff_data; +static const byte *iff_dataPtr; +static const byte *iff_end; +static const byte *iff_lastChunk; +static int iff_chunkLen; + +/* +================= +GetLittleShort +================= +*/ +static short GetLittleShort( void ) +{ + short val = 0; + + val += (*(iff_dataPtr+0) << 0); + val += (*(iff_dataPtr+1) << 8); + iff_dataPtr += 2; + + return val; +} + +/* +================= +GetLittleLong +================= +*/ +static int GetLittleLong( void ) +{ + int val = 0; + + val += (*(iff_dataPtr+0) << 0); + val += (*(iff_dataPtr+1) << 8); + val += (*(iff_dataPtr+2) <<16); + val += (*(iff_dataPtr+3) <<24); + iff_dataPtr += 4; + + return val; +} + +/* +================= +FindNextChunk +================= +*/ +static void FindNextChunk( const char *filename, const char *name ) +{ + while( 1 ) + { + ptrdiff_t remaining = iff_end - iff_lastChunk; + + if( remaining < 8 ) + { + iff_dataPtr = NULL; + return; + } + + iff_dataPtr = iff_lastChunk + 4; + remaining -= 8; + + iff_chunkLen = GetLittleLong(); + if( iff_chunkLen < 0 ) + { + iff_dataPtr = NULL; + return; + } + + if( iff_chunkLen > remaining ) + { + Con_DPrintf( "%s: '%s' truncated by %i bytes\n", __func__, filename, iff_chunkLen - remaining ); + iff_chunkLen = remaining; + } + + remaining -= iff_chunkLen; + iff_dataPtr -= 8; + + iff_lastChunk = iff_dataPtr + 8 + iff_chunkLen; + if ((iff_chunkLen&1) && remaining) + iff_lastChunk++; + if (!Q_strncmp(iff_dataPtr, name, 4)) + return; + } +} + +/* +================= +FindChunk +================= +*/ +static void FindChunk( const char *filename, const char *name ) +{ + iff_lastChunk = iff_data; + FindNextChunk( filename, name ); +} + +/* +============= +Sound_LoadWAV +============= +*/ +qboolean Sound_LoadWAV( const char *name, const byte *buffer, fs_offset_t filesize ) +{ + int samples, fmt; + + if( !buffer || filesize <= 0 ) + return false; + + iff_data = buffer; + iff_end = buffer + filesize; + + // find "RIFF" chunk + FindChunk( name, "RIFF" ); + + if( !( iff_dataPtr && !Q_strncmp( (const char *)iff_dataPtr + 8, "WAVE", 4 ))) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'RIFF/WAVE' chunks\n", name ); + return false; + } + + // get "fmt " chunk + iff_data = iff_dataPtr + 12; + FindChunk( name, "fmt " ); + + if( !iff_dataPtr ) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'fmt ' chunk\n", name ); + return false; + } + + iff_dataPtr += 8; + fmt = GetLittleShort(); + + if( fmt != 1 ) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: %s not a microsoft PCM format\n", name ); + return false; + } + + sound.channels = GetLittleShort(); + if( sound.channels != 1 && sound.channels != 2 ) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: only mono and stereo WAV files supported (%s)\n", name ); + return false; + } + + sound.rate = GetLittleLong(); + iff_dataPtr += 6; + + sound.width = GetLittleShort() / 8; + + if( sound.width != 1 && sound.width != 2 ) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name ); + return false; + } + + // get cue chunk + FindChunk( name, "cue " ); + + if( iff_dataPtr ) + { + iff_dataPtr += 32; + sound.loopstart = GetLittleLong(); + FindNextChunk( name, "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker + + if( iff_dataPtr ) + { + if( !Q_strncmp( (const char *)iff_dataPtr + 28, "mark", 4 )) + { + // this is not a proper parse, but it works with CoolEdit... + iff_dataPtr += 24; + sound.samples = sound.loopstart + GetLittleLong(); // samples in loop + } + } + } + else + { + sound.loopstart = -1; + sound.samples = 0; + } + + // find data chunk + FindChunk( name, "data" ); + + if( !iff_dataPtr ) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: %s missing 'data' chunk\n", name ); + return false; + } + + iff_dataPtr += 4; + samples = GetLittleLong() / sound.width; + + if( sound.samples ) + { + if( samples < sound.samples ) + { + Con_DPrintf( S_ERROR "Sound_LoadWAV: %s has a bad loop length\n", name ); + return false; + } + } + else sound.samples = samples; + + if( sound.samples <= 0 ) + { + Con_Reportf( S_ERROR "Sound_LoadWAV: file with %i samples (%s)\n", sound.samples, name ); + return false; + } + + sound.type = WF_PCMDATA; + sound.samples /= sound.channels; + + // Load the data + sound.size = sound.samples * sound.width * sound.channels; + sound.wav = Mem_Malloc( host.soundpool, sound.size ); + + memcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size ); + + // now convert 8-bit sounds to signed + if( sound.width == 1 ) + { + int i, j; + signed char *pData = (signed char *)sound.wav; + + for( i = 0; i < sound.samples; i++ ) + { + for( j = 0; j < sound.channels; j++ ) + { + *pData = (byte)((int)((byte)*pData) - 128 ); + pData++; + } + } + } + + return true; +}