diff --git a/baserc/baserc.plg b/baserc/baserc.plg
new file mode 100644
index 00000000..a6b1f243
--- /dev/null
+++ b/baserc/baserc.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: baserc - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+baserc.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/client/client.plg b/client/client.plg
new file mode 100644
index 00000000..33666b63
--- /dev/null
+++ b/client/client.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: client - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+client.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/client/hud/hud.cpp b/client/hud/hud.cpp
index 16581a5a..cc9a7048 100644
--- a/client/hud/hud.cpp
+++ b/client/hud/hud.cpp
@@ -71,7 +71,6 @@ void CHud :: VidInit( void )
// Load Sprites
// ---------
- m_iDrawPlaque = 1;
m_hsprCursor = 0;
m_hHudError = 0;
m_hHudFont = 0;
diff --git a/client/hud/hud_msg.cpp b/client/hud/hud_msg.cpp
index 1cee60e6..ab593f6d 100644
--- a/client/hud/hud_msg.cpp
+++ b/client/hud/hud_msg.cpp
@@ -104,7 +104,6 @@ int CHud :: InitMessages( void )
void CHud :: UserCmd_LoadingPlaque( void )
{
- ALERT( at_console, "SCR_DisablePlaque()\n" );
m_iDrawPlaque = 0; // disable plaque rendering
}
@@ -127,6 +126,9 @@ int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf )
m_iIntermission = 0;
+ // enable plaque drawing
+ m_iDrawPlaque = 1;
+
// reset fog
m_fStartDist = 0;
m_fEndDist = 0;
diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c
index a8eb7b69..12041161 100644
--- a/engine/client/cl_cmds.c
+++ b/engine/client/cl_cmds.c
@@ -257,7 +257,7 @@ void SCR_TimeRefresh_f( void )
if( Cmd_Argc() == 2 )
{
// run without page flipping
- re->BeginFrame();
+ re->BeginFrame( &cl.refdef );
for( i = 0; i < 128; i++ )
{
cl.refdef.viewangles[1] = i / 128.0 * 360.0f;
@@ -271,7 +271,7 @@ void SCR_TimeRefresh_f( void )
{
cl.refdef.viewangles[1] = i / 128.0 * 360.0f;
- re->BeginFrame();
+ re->BeginFrame( &cl.refdef );
re->RenderFrame( &cl.refdef );
re->EndFrame();
}
diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c
index 698facd4..dea223a8 100644
--- a/engine/client/cl_game.c
+++ b/engine/client/cl_game.c
@@ -28,6 +28,9 @@ edict_t *CL_GetEdictByIndex( int index )
MsgDev( D_ERROR, "CL_GetEntityByIndex: invalid entindex %i\n", index );
return NULL;
}
+
+ if( EDICT_NUM( index )->free )
+ return NULL;
return EDICT_NUM( index );
}
diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c
index f2834842..9db8cbf9 100644
--- a/engine/client/cl_main.c
+++ b/engine/client/cl_main.c
@@ -1203,9 +1203,6 @@ void CL_Frame( int time )
SCR_MakeScreenShot();
- if( cls.state > ca_disconnected && cls.state < ca_active )
- cl.refdef.paused = true; // force sound.dll to pause
-
// update audio
S_Update( &cl.refdef );
diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c
index 44d32f5c..bb20a8f1 100644
--- a/engine/client/cl_view.c
+++ b/engine/client/cl_view.c
@@ -68,7 +68,6 @@ void V_SetupRefDef( void )
cl.refdef.time = cl.time * 0.001f;
cl.refdef.frametime = cls.frametime;
cl.refdef.demoplayback = cls.demoplayback;
- cl.refdef.paused = cl_paused->integer;
cl.refdef.smoothing = cl_predict->integer;
cl.refdef.waterlevel = clent->v.waterlevel;
cl.refdef.flags = cl.render_flags;
@@ -142,7 +141,7 @@ void V_RenderView( void )
clgame.globals->time = cl.time * 0.001f; // clamped
clgame.globals->frametime = cl.serverframetime * 0.001f; // !!!
- if( cl.frame.valid && (cl.force_refdef || !cl_paused->integer ))
+ if( cl.frame.valid && (cl.force_refdef || !cl.refdef.paused ))
{
cl.force_refdef = false;
@@ -165,7 +164,11 @@ bool V_PreRender( void )
// too early
if( !re ) return false;
- re->BeginFrame();
+ if( cls.state > ca_disconnected && cls.state < ca_active )
+ cl.refdef.paused = true; // force audio\video to pause
+ else cl.refdef.paused = cl_paused->integer;
+
+ re->BeginFrame( &cl.refdef );
return true;
}
diff --git a/engine/engine.plg b/engine/engine.plg
new file mode 100644
index 00000000..b5840df7
--- /dev/null
+++ b/engine/engine.plg
@@ -0,0 +1,96 @@
+
+
+
+Build Log
+
+--------------------Configuration: engine - Win32 Debug--------------------
+
+Command Lines
+Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP199C.tmp" with contents
+[
+/nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "./" /I "common" /I "server" /I "client" /I "uimenu" /I "../public" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"..\temp\engine\!debug/" /Fo"..\temp\engine\!debug/" /Fd"..\temp\engine\!debug/" /FD /c
+"D:\Xash3D\src_main\engine\client\cl_game.c"
+]
+Creating command line "cl.exe @"C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP199C.tmp""
+Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP199D.tmp" with contents
+[
+user32.lib msvcrtd.lib /nologo /subsystem:windows /dll /incremental:yes /pdb:"..\temp\engine\!debug/engine.pdb" /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"..\temp\engine\!debug/engine.dll" /implib:"..\temp\engine\!debug/engine.lib" /pdbtype:sept
+"\Xash3D\src_main\temp\engine\!debug\cinematic.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_cmds.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_demo.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_effects.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_frame.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_game.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_input.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_main.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_parse.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_phys.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_scrn.obj"
+"\Xash3D\src_main\temp\engine\!debug\cl_view.obj"
+"\Xash3D\src_main\temp\engine\!debug\com_library.obj"
+"\Xash3D\src_main\temp\engine\!debug\con_keys.obj"
+"\Xash3D\src_main\temp\engine\!debug\con_main.obj"
+"\Xash3D\src_main\temp\engine\!debug\con_utils.obj"
+"\Xash3D\src_main\temp\engine\!debug\engfuncs.obj"
+"\Xash3D\src_main\temp\engine\!debug\engine.obj"
+"\Xash3D\src_main\temp\engine\!debug\host.obj"
+"\Xash3D\src_main\temp\engine\!debug\infostring.obj"
+"\Xash3D\src_main\temp\engine\!debug\input.obj"
+"\Xash3D\src_main\temp\engine\!debug\net_chan.obj"
+"\Xash3D\src_main\temp\engine\!debug\net_huff.obj"
+"\Xash3D\src_main\temp\engine\!debug\net_msg.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_client.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_cmds.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_frame.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_game.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_init.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_main.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_move.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_phys.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_save.obj"
+"\Xash3D\src_main\temp\engine\!debug\sv_world.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_advanced.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_audio.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_controls.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_credits.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_defaults.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_demos.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_gameoptions.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_gotosite.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_ingame.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_loadgame.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_main.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_menu.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_mods.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_multiplayer.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_network.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_options.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_performance.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_playersetup.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_qmenu.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_quit.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_savegame.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_singleplayer.obj"
+"\Xash3D\src_main\temp\engine\!debug\ui_video.obj"
+]
+Creating command line "link.exe @"C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP199D.tmp""
+Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP199E.bat" with contents
+[
+@echo off
+copy \Xash3D\src_main\temp\engine\!debug\engine.dll "D:\Xash3D\bin\engine.dll"
+]
+Creating command line ""C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP199E.bat""
+Compiling...
+cl_game.c
+Linking...
+Output Window
+Performing Custom Build Step on \Xash3D\src_main\temp\engine\!debug\engine.dll
+‘ª®¯¨à®¢ ® ä ©«®¢: 1.
+
+
+
+Results
+engine.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/engine/host.c b/engine/host.c
index d41e71dc..b50b8057 100644
--- a/engine/host.c
+++ b/engine/host.c
@@ -172,6 +172,7 @@ bool Host_InitSound( void )
si.api_size = sizeof( vsound_imp_t );
si.GetSoundSpatialization = CL_GetEntitySoundSpatialization;
si.PointContents = CL_PointContents;
+ si.GetClientEdict = CL_GetEdictByIndex;
si.AddLoopingSounds = CL_AddLoopingSounds;
si.GetServerTime = CL_GetServerTime;
diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c
index 76011979..8d2765c2 100644
--- a/engine/server/sv_init.c
+++ b/engine/server/sv_init.c
@@ -62,7 +62,6 @@ int SV_FindIndex( const char *name, int start, int end, bool create )
return i;
}
-
int SV_ModelIndex( const char *name )
{
return SV_FindIndex( name, CS_MODELS, MAX_MODELS, true );
diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c
index f61dc417..1cf1ff1c 100644
--- a/engine/server/sv_save.c
+++ b/engine/server/sv_save.c
@@ -560,14 +560,6 @@ void SV_ReadEntities( wfile_t *l )
if( sv.loadgame ) // allocate edicts
while( svgame.globals->numEntities < shdr.numEntities ) SV_AllocEdict();
- else if( sv.changelevel )
- {
- // NOTE: we don't need allocate too many ents
- // just set it number to match with old map and use
- // SV_InitEdict istead of SV_AllocEdict, first SV_Physics call
- // will be fixup entities count to actual
- svgame.globals->numEntities = pSaveData->tableCount;
- }
// set client fields on player ents
for( i = 0; i < svgame.globals->maxClients; i++ )
@@ -613,6 +605,7 @@ void SV_ReadEntities( wfile_t *l )
{
if( pent->free ) SV_InitEdict( pent );
pent = SV_AllocPrivateData( pent, pTable->classname );
+ svgame.globals->numEntities++;
num_moveables++;
}
}
diff --git a/launch/launch.plg b/launch/launch.plg
new file mode 100644
index 00000000..7d3da586
--- /dev/null
+++ b/launch/launch.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: launch - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+launch.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/physic/physic.plg b/physic/physic.plg
new file mode 100644
index 00000000..3835ce2a
--- /dev/null
+++ b/physic/physic.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: physic - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+physic.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/public/render_api.h b/public/render_api.h
index d890eebc..601c9671 100644
--- a/public/render_api.h
+++ b/public/render_api.h
@@ -87,8 +87,8 @@ typedef struct render_exp_s
bool (*AddLightStyle)( int stylenum, vec3_t color );
void (*ClearScene)( void );
- void (*BeginFrame)( void );
- void (*RenderFrame)( ref_params_t *fd );
+ void (*BeginFrame)( const ref_params_t *fd );
+ void (*RenderFrame)( const ref_params_t *fd );
void (*EndFrame)( void );
// misc utilities
diff --git a/public/vsound_api.h b/public/vsound_api.h
index 20968265..a3cb0330 100644
--- a/public/vsound_api.h
+++ b/public/vsound_api.h
@@ -58,6 +58,7 @@ typedef struct vsound_imp_s
void (*GetSoundSpatialization)( int entnum, vec3_t origin, vec3_t velocity );
int (*PointContents)( const vec3_t point );
+ edict_t *(*GetClientEdict)( int index );
void (*AddLoopingSounds)( void );
int (*GetServerTime)( void );
} vsound_imp_t;
diff --git a/server/game/lights.cpp b/server/game/lights.cpp
index e3567fe4..e3c1ea24 100644
--- a/server/game/lights.cpp
+++ b/server/game/lights.cpp
@@ -174,9 +174,10 @@ void CLight :: Think( void )
void CLight :: Spawn( void )
{
- if (FStringNull(pev->targetname))
- { // inert light
- REMOVE_ENTITY(ENT(pev));
+ if( FStringNull( pev->targetname ))
+ {
+ // inert light
+ REMOVE_ENTITY(ENT( pev ));
return;
}
diff --git a/server/server.plg b/server/server.plg
new file mode 100644
index 00000000..35b71426
--- /dev/null
+++ b/server/server.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: server - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+server.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/snd_dx/dsp.c b/snd_dx/dsp.c
new file mode 100644
index 00000000..f3526952
--- /dev/null
+++ b/snd_dx/dsp.c
@@ -0,0 +1,1119 @@
+#include "sound.h"
+
+//===============================================================================
+//
+// Digital Signal Processing algorithms for audio FX.
+//
+// KellyB 1/24/97
+//===============================================================================
+#define SXDLY_MAX 0.400 // max delay in seconds
+#define SXRVB_MAX 0.100 // max reverb reflection time
+#define SXSTE_MAX 0.100 // max stereo delay line time
+
+typedef int sample_t; // delay lines must be 32 bit, now that we have 16 bit samples
+
+typedef struct dlyline_s
+{
+ int cdelaysamplesmax; // size of delay line in samples
+ int lp; // lowpass flag 0 = off, 1 = on
+
+ int idelayinput; // i/o indices into circular delay line
+ int idelayoutput;
+ int idelayoutputxf; // crossfade output pointer
+ int xfade; // crossfade value
+
+ int delaysamples; // current delay setting
+ int delayfeed; // current feedback setting
+
+ int lp0, lp1, lp2, lp3, lp4, lp5; // lowpass filter buffer
+
+ int mod; // sample modulation count
+ int modcur;
+ HANDLE hdelayline; // handle to delay line buffer
+ sample_t *lpdelayline; // buffer
+} dlyline_t;
+
+#define CSXDLYMAX 4
+#define ISXMONODLY 0 // mono delay line
+#define ISXRVB 1 // first of the reverb delay lines
+#define CSXRVBMAX 2
+#define ISXSTEREODLY 3 // 50ms left side delay
+
+dlyline_t rgsxdly[CSXDLYMAX]; // array of delay lines
+
+#define gdly0 (rgsxdly[ISXMONODLY])
+#define gdly1 (rgsxdly[ISXRVB])
+#define gdly2 (rgsxdly[ISXRVB + 1])
+#define gdly3 (rgsxdly[ISXSTEREODLY])
+
+#define CSXLPMAX 10 // lowpass filter memory
+
+int rgsxlp[CSXLPMAX];
+int sxamodl, sxamodr; // amplitude modulation values
+int sxamodlt, sxamodrt; // modulation targets
+int sxmod1, sxmod2;
+int sxmod1cur, sxmod2cur;
+
+// Mono Delay parameters
+cvar_t *sxdly_delay; // current delay in seconds
+cvar_t *sxdly_feedback; // cycles
+cvar_t *sxdly_lp; // lowpass filter
+
+float sxdly_delayprev; // previous delay setting value
+
+// Mono Reverb parameters
+cvar_t *sxrvb_size; // room size 0 (off) 0.1 small - 0.35 huge
+cvar_t *sxrvb_feedback; // reverb decay 0.1 short - 0.9 long
+cvar_t *sxrvb_lp; // lowpass filter
+
+float sxrvb_sizeprev;
+
+// stereo delay (no feedback)
+cvar_t *sxste_delay; // straight left delay
+float sxste_delayprev;
+
+// Underwater/special fx modulations
+cvar_t *sxmod_lowpass;
+cvar_t *sxmod_mod;
+
+// Main interface
+cvar_t *sxroom_type; // legacy support
+cvar_t *sxroomwater_type; // legacy support
+float sxroom_typeprev;
+cvar_t *sxroom_off; // legacy support
+
+bool SXDLY_Init( int idelay, float delay );
+void SXDLY_Free( int idelay );
+void SXDLY_DoDelay( int count );
+void SXRVB_DoReverb( int count );
+void SXDLY_DoStereoDelay( int count );
+void SXRVB_DoAMod( int count );
+
+//=====================================================================
+// Init/release all structures for sound effects
+//=====================================================================
+void SX_Init( void )
+{
+ sxdly_delay = Cvar_Get( "room_delay", "0", 0, "current delay in seconds" );
+ sxdly_feedback = Cvar_Get( "room_feedback", "0.2", 0, "cycles" );
+ sxdly_lp = Cvar_Get( "room_dlylp", "1", 0, "lowpass filter" );
+
+ sxrvb_size = Cvar_Get( "room_size", "0", 0, "room size 0 (off) 0.1 small - 0.35 huge" );
+ sxrvb_feedback = Cvar_Get( "room_refl", "0.7", 0, "reverb decay 0.1 short - 0.9 long" );
+ sxrvb_lp = Cvar_Get( "room_rvblp", "1", 0, "lowpass filter" );
+
+ sxste_delay = Cvar_Get( "room_left", "0", 0, "straight left delay" );
+ sxmod_lowpass = Cvar_Get( "room_lp", "0", 0, "no description" );
+ sxmod_mod = Cvar_Get( "room_mod", "0", 0, "no description" );
+
+ sxroom_type = Cvar_Get( "room_type_l", "0", 0, "no description" );
+ sxroomwater_type = Cvar_Get( "waterroom_type_l", "14", 0, "no description" );
+ sxroom_off = Cvar_Get( "room_off_l", "0", 0, "no description" );
+
+ Mem_Set( rgsxdly, 0, sizeof( dlyline_t ) * CSXDLYMAX );
+ Mem_Set( rgsxlp, 0, sizeof( int ) * CSXLPMAX );
+
+ sxdly_delayprev = -1.0;
+ sxrvb_sizeprev = -1.0;
+ sxste_delayprev = -1.0;
+ sxroom_typeprev = -1.0;
+
+ // init amplitude modulation params
+ sxamodl = sxamodr = 255;
+ sxamodlt = sxamodrt = 255;
+
+ sxmod1 = 350 * (SOUND_DMA_SPEED / SOUND_11k); // 11k was the original sample rate all dsp was tuned at
+ sxmod2 = 450 * (SOUND_DMA_SPEED / SOUND_11k);
+ sxmod1cur = sxmod1;
+ sxmod2cur = sxmod2;
+
+ MsgDev( D_INFO, "FX Processor Initialized\n" );
+}
+
+void SX_Free( void )
+{
+ int i;
+
+ // release mono delay line
+ SXDLY_Free( ISXMONODLY );
+
+ // release reverb lines
+ for( i = 0; i < CSXRVBMAX; i++ )
+ SXDLY_Free( i + ISXRVB );
+ SXDLY_Free(ISXSTEREODLY);
+}
+
+// Set up a delay line buffer allowing a max delay of 'delay' seconds
+// Frees current buffer if it already exists. idelay indicates which of
+// the available delay lines to init.
+bool SXDLY_Init( int idelay, float delay )
+{
+ int cbsamples;
+ HANDLE hData;
+ HPSTR lpData;
+ dlyline_t *pdly;
+
+ pdly = &(rgsxdly[idelay]);
+
+ if( delay > SXDLY_MAX )
+ delay = SXDLY_MAX;
+
+ if( pdly->lpdelayline )
+ {
+ GlobalUnlock( pdly->hdelayline );
+ GlobalFree( pdly->hdelayline );
+ pdly->hdelayline = NULL;
+ pdly->lpdelayline = NULL;
+ }
+
+ if( delay == 0.0 )
+ return true;
+
+ pdly->cdelaysamplesmax = SOUND_DMA_SPEED * delay;
+ pdly->cdelaysamplesmax += 1;
+
+ cbsamples = pdly->cdelaysamplesmax * sizeof( sample_t );
+
+ hData = GlobalAlloc( GMEM_MOVEABLE|GMEM_SHARE, cbsamples );
+ if( !hData )
+ {
+ MsgDev( D_ERROR, "Sound FX: Out of memory.\n" );
+ return false;
+ }
+
+ lpData = (char *)GlobalLock( hData );
+ if( !lpData )
+ {
+ MsgDev( D_ERROR, "Sound FX: Failed to lock.\n" );
+ GlobalFree( hData );
+ return false;
+ }
+
+ Mem_Set( lpData, 0, cbsamples );
+
+ pdly->hdelayline = hData;
+ pdly->lpdelayline = (sample_t *)lpData;
+
+ // init delay loop input and output counters.
+ // NOTE: init of idelayoutput only valid if pdly->delaysamples is set
+ // NOTE: before this call!
+
+ pdly->idelayinput = 0;
+ pdly->idelayoutput = pdly->cdelaysamplesmax - pdly->delaysamples;
+ pdly->xfade = 0;
+ pdly->lp = 1;
+ pdly->mod = 0;
+ pdly->modcur = 0;
+
+ // init lowpass filter memory
+ pdly->lp0 = pdly->lp1 = pdly->lp2 = pdly->lp3 = pdly->lp4 = pdly->lp5 = 0;
+
+ return true;
+}
+
+// release delay buffer and deactivate delay
+void SXDLY_Free( int idelay )
+{
+ dlyline_t *pdly = &(rgsxdly[idelay]);
+
+ if( pdly->lpdelayline )
+ {
+ GlobalUnlock( pdly->hdelayline );
+ GlobalFree( pdly->hdelayline );
+ pdly->hdelayline = NULL;
+ pdly->lpdelayline = NULL; // this deactivates the delay
+ }
+}
+
+
+// check for new stereo delay param
+void SXDLY_CheckNewStereoDelayVal( void )
+{
+ dlyline_t *pdly = &(rgsxdly[ISXSTEREODLY]);
+ int delaysamples;
+
+ // set up stereo delay
+ if( sxste_delay->value != sxste_delayprev )
+ {
+ if( sxste_delay->value == 0.0 )
+ {
+ // deactivate delay line
+ SXDLY_Free( ISXSTEREODLY );
+ sxste_delayprev = 0.0;
+ }
+ else
+ {
+ delaysamples = min(sxste_delay->value, SXSTE_MAX) * SOUND_DMA_SPEED;
+
+ // init delay line if not active
+ if( pdly->lpdelayline == NULL )
+ {
+
+ pdly->delaysamples = delaysamples;
+ SXDLY_Init( ISXSTEREODLY, SXSTE_MAX );
+ }
+
+ // do crossfade to new delay if delay has changed
+ if( delaysamples != pdly->delaysamples )
+ {
+
+ // set up crossfade from old pdly->delaysamples to new delaysamples
+ pdly->idelayoutputxf = pdly->idelayinput - delaysamples;
+
+ if( pdly->idelayoutputxf < 0 )
+ pdly->idelayoutputxf += pdly->cdelaysamplesmax;
+ pdly->xfade = 128;
+ }
+
+ sxste_delayprev = sxste_delay->value;
+
+ // UNDONE: modulation disabled
+ // pdly->mod = 500 * (SOUND_DMA_SPEED / SOUND_11k); // change delay every n samples
+ pdly->mod = 0;
+ pdly->modcur = pdly->mod;
+
+ // deactivate line if rounded down to 0 delay
+ if( pdly->delaysamples == 0 )
+ SXDLY_Free( ISXSTEREODLY );
+
+ }
+ }
+}
+
+// stereo delay, left channel only, no feedback
+
+void SXDLY_DoStereoDelay( int count )
+{
+ int left;
+ sample_t sampledly;
+ sample_t samplexf;
+ samplepair_t *pbuf;
+ int countr;
+
+ // process delay line if active
+ if( rgsxdly[ISXSTEREODLY].lpdelayline )
+ {
+ pbuf = paintbuffer;
+ countr = count;
+
+ // process each sample in the paintbuffer...
+ while( countr-- )
+ {
+ if( gdly3.mod && ( --gdly3.modcur < 0 ))
+ gdly3.modcur = gdly3.mod;
+
+ // get delay line sample from left line
+ sampledly = *(gdly3.lpdelayline + gdly3.idelayoutput);
+ left = pbuf->left;
+
+ // only process if left value or delayline value are non-zero or xfading
+ if( gdly3.xfade || sampledly || left )
+ {
+ // if we're not crossfading, and we're not modulating, but we'd like to be modulating,
+ // then setup a new crossfade.
+
+ if( !gdly3.xfade && !gdly3.modcur && gdly3.mod )
+ {
+ // set up crossfade to new delay value, if we're not already doing an xfade
+ gdly3.idelayoutputxf = gdly3.idelayoutput + ((Com_RandomLong(0, 0xFF) * gdly3.delaysamples) >> 9); // 100 = ~ 9ms
+
+ if( gdly3.idelayoutputxf >= gdly3.cdelaysamplesmax )
+ gdly3.idelayoutputxf -= gdly3.cdelaysamplesmax;
+
+ gdly3.xfade = 128;
+ }
+
+ // modify sampledly if crossfading to new delay value
+ if( gdly3.xfade )
+ {
+ samplexf = (*(gdly3.lpdelayline + gdly3.idelayoutputxf) * (128 - gdly3.xfade)) >> 7;
+ sampledly = ((sampledly * gdly3.xfade) >> 7) + samplexf;
+
+ if( ++gdly3.idelayoutputxf >= gdly3.cdelaysamplesmax )
+ gdly3.idelayoutputxf = 0;
+
+ if( --gdly3.xfade == 0 )
+ gdly3.idelayoutput = gdly3.idelayoutputxf;
+ }
+
+ // save output value into delay line
+
+ // left = CLIP(left);
+ *(gdly3.lpdelayline + gdly3.idelayinput) = left;
+
+ // save delay line sample into output buffer
+ pbuf->left = sampledly;
+
+ }
+ else
+ {
+ // keep clearing out delay line, even if no signal in or out
+ *(gdly3.lpdelayline + gdly3.idelayinput) = 0;
+ }
+
+ // update delay buffer pointers
+ if( ++gdly3.idelayinput >= gdly3.cdelaysamplesmax )
+ gdly3.idelayinput = 0;
+
+ if( ++gdly3.idelayoutput >= gdly3.cdelaysamplesmax )
+ gdly3.idelayoutput = 0;
+ pbuf++;
+ }
+
+ }
+}
+
+// If sxdly_delay or sxdly_feedback have changed, update delaysamples
+// and delayfeed values. This applies only to delay 0, the main echo line.
+
+void SXDLY_CheckNewDelayVal( void )
+{
+ dlyline_t *pdly = &(rgsxdly[ISXMONODLY]);
+
+ if (sxdly_delay->value != sxdly_delayprev) {
+
+ if (sxdly_delay->value == 0.0) {
+
+ // deactivate delay line
+
+ SXDLY_Free(ISXMONODLY);
+ sxdly_delayprev = sxdly_delay->value;
+
+ } else {
+ // init delay line if not active
+
+ pdly->delaysamples = min(sxdly_delay->value, SXDLY_MAX) * SOUND_DMA_SPEED;
+
+ if (pdly->lpdelayline == NULL)
+ SXDLY_Init(ISXMONODLY, SXDLY_MAX);
+
+ // flush delay line and filters
+
+ if (pdly->lpdelayline) {
+ Mem_Set(pdly->lpdelayline, 0, pdly->cdelaysamplesmax * sizeof(sample_t));
+ pdly->lp0 = 0;
+ pdly->lp1 = 0;
+ pdly->lp2 = 0;
+ pdly->lp3 = 0;
+ pdly->lp4 = 0;
+ pdly->lp5 = 0;
+ }
+
+ // init delay loop input and output counters
+
+ pdly->idelayinput = 0;
+ pdly->idelayoutput = pdly->cdelaysamplesmax - pdly->delaysamples;
+
+ sxdly_delayprev = sxdly_delay->value;
+
+ // deactivate line if rounded down to 0 delay
+
+ if (pdly->delaysamples == 0)
+ SXDLY_Free(ISXMONODLY);
+
+ }
+ }
+
+ pdly->lp = (int)(sxdly_lp->value);
+ pdly->delayfeed = sxdly_feedback->value * 255;
+}
+
+
+// This routine updates both left and right output with
+// the mono delayed signal. Delay is set through console vars room_delay
+// and room_feedback.
+
+void SXDLY_DoDelay(int count)
+{
+ int val;
+ int valt;
+ int left;
+ int right;
+ sample_t sampledly;
+ samplepair_t *pbuf;
+ int countr;
+ float fgain;
+ int gain;
+
+
+ // process mono delay line if active
+
+ if (rgsxdly[ISXMONODLY].lpdelayline)
+ {
+
+ // calculate gain of delay line with feedback, and use it to
+ // reduce output. ie: make delay line approx unity gain
+
+ // for constant input x with feedback fb:
+
+ // out = x + x*fb + x * fb^2 + x * fb^3...
+ // gain = out/x
+ // so gain = 1 + fb + fb^2 + fb^3...
+ // which, by the miracle of geometric series, equates to 1/1-fb
+ // thus, gain = 1/1-fb
+
+ fgain = 1.0 / (1.0 - gdly0.delayfeed / 255.0);
+ gain = (int)((1.0 / fgain)* 255.0);
+
+ gain <<= 2;
+ if( gain > 255 ) gain = 255;
+
+ pbuf = paintbuffer;
+ countr = count;
+
+ // process each sample in the paintbuffer...
+ while( countr-- )
+ {
+ // get delay line sample
+ sampledly = *(gdly0.lpdelayline + gdly0.idelayoutput);
+
+ left = pbuf->left;
+ right = pbuf->right;
+
+ // only process if delay line and paintbuffer samples are non zero
+ if( sampledly || left || right )
+ {
+ // get current sample from delay buffer
+
+ // calculate delayed value from avg of left and right channels
+ val = ((left + right) >> 1) + ((gdly0.delayfeed * sampledly) >> 8);
+
+ // limit val to short
+ // val = CLIP( val );
+
+ // lowpass
+ if( gdly0.lp )
+ {
+ //valt = (gdly0.lp0 + gdly0.lp1 + val) / 3; // performance
+ //valt = (gdly0.lp0 + gdly0.lp1 + (val<<1)) >> 2;
+
+ valt = (gdly0.lp0 + gdly0.lp1 + gdly0.lp2 + gdly0.lp3 + val) / 5;
+
+ gdly0.lp0 = gdly0.lp1;
+ gdly0.lp1 = gdly0.lp2;
+ gdly0.lp2 = gdly0.lp3;
+ gdly0.lp3 = val;
+ }
+ else
+ {
+ valt = val;
+ }
+
+ // store delay output value into output buffer
+
+ *(gdly0.lpdelayline + gdly0.idelayinput) = valt;
+
+ // mono delay in left and right channels
+
+ // decrease output value by max gain of delay with feedback
+ // to provide for unity gain reverb
+ // note: this gain varies with the feedback value.
+
+ pbuf->left = (valt * gain) >> 8;
+ pbuf->right = (valt * gain) >> 8;
+ }
+ else
+ {
+ // not playing samples, but must still flush lowpass buffer and delay line
+ valt = gdly0.lp0 = gdly0.lp1 = gdly0.lp2 = gdly0.lp3 = 0;
+ *(gdly0.lpdelayline + gdly0.idelayinput) = valt;
+
+ }
+
+ // update delay buffer pointers
+ if( ++gdly0.idelayinput >= gdly0.cdelaysamplesmax )
+ gdly0.idelayinput = 0;
+
+ if( ++gdly0.idelayoutput >= gdly0.cdelaysamplesmax )
+ gdly0.idelayoutput = 0;
+ pbuf++;
+ }
+ }
+}
+
+// check for a parameter change on the reverb processor
+#define RVB_XFADE (32 * SOUND_DMA_SPEED / SOUND_11k) // xfade time between new delays
+#define RVB_MODRATE1 (500 * (SOUND_DMA_SPEED / SOUND_11k)) // how often, in samples, to change delay (1st rvb)
+#define RVB_MODRATE2 (700 * (SOUND_DMA_SPEED / SOUND_11k)) // how often, in samples, to change delay (2nd rvb)
+
+void SXRVB_CheckNewReverbVal( void )
+{
+ dlyline_t *pdly;
+ int delaysamples;
+ int i, mod;
+
+ if( sxrvb_size->value != sxrvb_sizeprev )
+ {
+ sxrvb_sizeprev = sxrvb_size->value;
+
+ if( sxrvb_size->value == 0.0 )
+ {
+ // deactivate all delay lines
+ SXDLY_Free( ISXRVB );
+ SXDLY_Free( ISXRVB + 1 );
+
+ }
+ else
+ {
+ for( i = ISXRVB; i < ISXRVB + CSXRVBMAX; i++ )
+ {
+ // init delay line if not active
+ pdly = &(rgsxdly[i]);
+
+ switch( i )
+ {
+ case ISXRVB:
+ delaysamples = min(sxrvb_size->value, SXRVB_MAX) * SOUND_DMA_SPEED;
+ pdly->mod = RVB_MODRATE1;
+ break;
+ case ISXRVB+1:
+ delaysamples = min(sxrvb_size->value * 0.71, SXRVB_MAX) * SOUND_DMA_SPEED;
+ pdly->mod = RVB_MODRATE2;
+ break;
+ default:
+ Com_Assert( 1 );
+ delaysamples = 0;
+ break;
+ }
+
+ mod = pdly->mod; // KB: bug, SXDLY_Init clears mod, modcur, xfade and lp - save mod before call
+
+ if( pdly->lpdelayline == NULL )
+ {
+ pdly->delaysamples = delaysamples;
+ SXDLY_Init( i, SXRVB_MAX );
+ }
+
+ pdly->modcur = pdly->mod = mod; // KB: bug, SXDLY_Init clears mod, modcur, xfade and lp - restore mod after call
+
+ // do crossfade to new delay if delay has changed
+ if( delaysamples != pdly->delaysamples )
+ {
+ // set up crossfade from old pdly->delaysamples to new delaysamples
+ pdly->idelayoutputxf = pdly->idelayinput - delaysamples;
+
+ if( pdly->idelayoutputxf < 0 )
+ pdly->idelayoutputxf += pdly->cdelaysamplesmax;
+
+ pdly->xfade = RVB_XFADE;
+ }
+
+ // deactivate line if rounded down to 0 delay
+ if( pdly->delaysamples == 0 )
+ SXDLY_Free( i );
+ }
+ }
+ }
+
+ rgsxdly[ISXRVB].delayfeed = (sxrvb_feedback->value) * 255;
+ rgsxdly[ISXRVB].lp = sxrvb_lp->value;
+
+ rgsxdly[ISXRVB + 1].delayfeed = (sxrvb_feedback->value) * 255;
+ rgsxdly[ISXRVB + 1].lp = sxrvb_lp->value;
+
+}
+
+
+// main routine for updating the paintbuffer with new reverb values.
+// This routine updates both left and right lines with
+// the mono reverb signal. Delay is set through console vars room_reverb
+// and room_feedback. 2 reverbs operating in parallel.
+void SXRVB_DoReverb( int count )
+{
+ int val;
+ int valt;
+ int left;
+ int right;
+ sample_t sampledly;
+ sample_t samplexf;
+ samplepair_t *pbuf;
+ int countr;
+ int voutm;
+ int vlr;
+ float fgain1;
+ float fgain2;
+ int gain;
+
+ // process reverb lines if active
+ if( rgsxdly[ISXRVB].lpdelayline )
+ {
+ // calculate reverb gains
+ fgain1 = 1.0 / (1.0 - gdly1.delayfeed / 255.0);
+ fgain2 = 1.0 / (1.0 - gdly2.delayfeed / 255.0) + fgain1;
+
+ // inverse gain of parallel reverbs
+ gain = (int)((1.0 / fgain2) * 255.0);
+
+ gain <<= 2;
+
+ if( gain > 255 ) gain = 255;
+
+ pbuf = paintbuffer;
+ countr = count;
+
+ // process each sample in the paintbuffer...
+
+ while (countr--)
+ {
+
+ left = pbuf->left;
+ right = pbuf->right;
+ voutm = 0;
+ vlr = (left + right) >> 1;
+
+ // UNDONE: ignored
+ if( --gdly1.modcur < 0 )
+ gdly1.modcur = gdly1.mod;
+
+ // ========================== ISXRVB============================
+
+ // get sample from delay line
+
+ sampledly = *(gdly1.lpdelayline + gdly1.idelayoutput);
+
+ // only process if something is non-zero
+ if( gdly1.xfade || sampledly || left || right )
+ {
+ // modulate delay rate
+ // UNDONE: modulation disabled
+ if( 0 && !gdly1.xfade && !gdly1.modcur && gdly1.mod )
+ {
+ // set up crossfade to new delay value, if we're not already doing an xfade
+ gdly1.idelayoutputxf = gdly1.idelayoutput + ((Com_RandomLong(0, 0xFF) * gdly1.delaysamples) >> 9); // 100 = ~ 9ms
+
+ if( gdly1.idelayoutputxf >= gdly1.cdelaysamplesmax )
+ gdly1.idelayoutputxf -= gdly1.cdelaysamplesmax;
+
+ gdly1.xfade = RVB_XFADE;
+ }
+
+ // modify sampledly if crossfading to new delay value
+
+ if( gdly1.xfade )
+ {
+ samplexf = (*(gdly1.lpdelayline + gdly1.idelayoutputxf) * (RVB_XFADE - gdly1.xfade)) / RVB_XFADE;
+ sampledly = ((sampledly * gdly1.xfade) / RVB_XFADE) + samplexf;
+
+ if( ++gdly1.idelayoutputxf >= gdly1.cdelaysamplesmax )
+ gdly1.idelayoutputxf = 0;
+
+ if( --gdly1.xfade == 0 )
+ gdly1.idelayoutput = gdly1.idelayoutputxf;
+ }
+
+ if( sampledly )
+ {
+ // get current sample from delay buffer
+
+ // calculate delayed value from avg of left and right channels
+ val = vlr + ((gdly1.delayfeed * sampledly) >> 8);
+
+ // limit to short
+ // val = CLIP(val);
+
+ }
+ else
+ {
+ val = vlr;
+ }
+
+ // lowpass
+ if( gdly1.lp )
+ {
+ valt = (gdly1.lp0 + gdly1.lp1 + (val<<1)) >> 2;
+ gdly1.lp1 = gdly1.lp0;
+ gdly1.lp0 = val;
+ }
+ else
+ {
+ valt = val;
+ }
+
+ // store delay output value into output buffer
+ *(gdly1.lpdelayline + gdly1.idelayinput) = valt;
+ voutm = valt;
+ }
+ else
+ {
+ // not playing samples, but still must flush lowpass buffer & delay line
+
+ gdly1.lp0 = gdly1.lp1 = 0;
+ *(gdly1.lpdelayline + gdly1.idelayinput) = 0;
+ voutm = 0;
+ }
+
+ // update delay buffer pointers
+ if( ++gdly1.idelayinput >= gdly1.cdelaysamplesmax )
+ gdly1.idelayinput = 0;
+
+ if( ++gdly1.idelayoutput >= gdly1.cdelaysamplesmax )
+ gdly1.idelayoutput = 0;
+
+ // ========================== ISXRVB + 1========================
+
+ // UNDONE: ignored
+ if( --gdly2.modcur < 0 )
+ gdly2.modcur = gdly2.mod;
+
+ if( gdly2.lpdelayline )
+ {
+ // get sample from delay line
+
+ sampledly = *(gdly2.lpdelayline + gdly2.idelayoutput);
+
+ // only process if something is non-zero
+ if( gdly2.xfade || sampledly || left || right )
+ {
+ // UNDONE: modulation disabled
+ if( 0 && !gdly2.xfade && gdly2.modcur && gdly2.mod )
+ {
+ // set up crossfade to new delay value, if we're not already doing an xfade
+ gdly2.idelayoutputxf = gdly2.idelayoutput + ((Com_RandomLong(0,0xFF) * gdly2.delaysamples) >> 9); // 100 = ~ 9ms
+
+ if( gdly2.idelayoutputxf >= gdly2.cdelaysamplesmax )
+ gdly2.idelayoutputxf -= gdly2.cdelaysamplesmax;
+
+ gdly2.xfade = RVB_XFADE;
+ }
+
+ // modify sampledly if crossfading to new delay value
+ if( gdly2.xfade )
+ {
+ samplexf = (*(gdly2.lpdelayline + gdly2.idelayoutputxf) * (RVB_XFADE - gdly2.xfade)) / RVB_XFADE;
+ sampledly = ((sampledly * gdly2.xfade) / RVB_XFADE) + samplexf;
+
+ if( ++gdly2.idelayoutputxf >= gdly2.cdelaysamplesmax )
+ gdly2.idelayoutputxf = 0;
+
+ if( --gdly2.xfade == 0 )
+ gdly2.idelayoutput = gdly2.idelayoutputxf;
+ }
+
+ if( sampledly )
+ {
+ // get current sample from delay buffer
+
+ // calculate delayed value from avg of left and right channels
+ val = vlr + ((gdly2.delayfeed * sampledly) >> 8);
+
+ // limit to short
+ // val = CLIP(val);
+ }
+ else
+ {
+ val = vlr;
+ }
+
+ // lowpass
+ if( gdly2.lp )
+ {
+ valt = (gdly2.lp0 + gdly2.lp1 + (val<<1)) >> 2;
+ gdly2.lp0 = val;
+ }
+ else
+ {
+ valt = val;
+ }
+
+ // store delay output value into output buffer
+ *(gdly2.lpdelayline + gdly2.idelayinput) = valt;
+ voutm += valt;
+ }
+ else
+ {
+ // not playing samples, but still must flush lowpass buffer
+ gdly2.lp0 = gdly2.lp1 = 0;
+ *(gdly2.lpdelayline + gdly2.idelayinput) = 0;
+ }
+
+ // update delay buffer pointers
+ if( ++gdly2.idelayinput >= gdly2.cdelaysamplesmax )
+ gdly2.idelayinput = 0;
+
+ if( ++gdly2.idelayoutput >= gdly2.cdelaysamplesmax )
+ gdly2.idelayoutput = 0;
+ }
+
+ // ============================ Mix================================
+
+ // add mono delay to left and right channels
+
+ // drop output by inverse of cascaded gain for both reverbs
+ voutm = (gain * voutm) >> 8;
+ // voutm = CLIP( voutm );
+
+ left = voutm;
+ right = voutm;
+
+ pbuf->left = left;
+ pbuf->right = right;
+
+ pbuf++;
+ }
+ }
+}
+
+// amplitude modulator, low pass filter for underwater weirdness
+void SXRVB_DoAMod( int count )
+{
+ int valtl, valtr;
+ int left;
+ int right;
+ samplepair_t *pbuf;
+ int countr;
+ int fLowpass;
+ int fmod;
+
+ // process reverb lines if active
+ if( sxmod_lowpass->value != 0.0 || sxmod_mod->value != 0.0 )
+ {
+ pbuf = paintbuffer;
+ countr = count;
+
+ fLowpass = (sxmod_lowpass->value != 0.0);
+ fmod = (sxmod_mod->value != 0.0);
+
+ // process each sample in the paintbuffer...
+
+ while( countr-- )
+ {
+ left = pbuf->left;
+ right = pbuf->right;
+
+ // only process if non-zero
+ if( fLowpass )
+ {
+ valtl = left;
+ valtr = right;
+
+ left = (rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + left);
+ right = (rgsxlp[5] + rgsxlp[6] + rgsxlp[7]+ rgsxlp[8] + rgsxlp[9] + right);
+
+ left = ((left << 1) + (left << 3)) >> 6; // * 10/64
+ right = ((right << 1) + (right << 3)) >> 6; // * 10/64
+
+ rgsxlp[4] = valtl;
+ rgsxlp[9] = valtr;
+
+ 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( fmod )
+ {
+ if( --sxmod1cur < 0 )
+ sxmod1cur = sxmod1;
+
+ if( !sxmod1 )
+ sxamodlt = Com_RandomLong(32,255);
+
+ if( --sxmod2cur < 0 )
+ sxmod2cur = sxmod2;
+
+ if( !sxmod2 )
+ sxamodlt = Com_RandomLong(32,255);
+
+ left = (left * sxamodl) >> 8;
+ right = (right * sxamodr) >> 8;
+
+ if( sxamodl < sxamodlt )
+ sxamodl++;
+ else if( sxamodl > sxamodlt )
+ sxamodl--;
+
+ if( sxamodr < sxamodrt )
+ sxamodr++;
+ else if( sxamodr > sxamodrt )
+ sxamodr--;
+ }
+
+ left = CLIP( left );
+ right = CLIP( right );
+
+ pbuf->left = left;
+ pbuf->right = right;
+
+ pbuf++;
+ }
+ }
+}
+
+typedef struct sx_preset_s
+{
+ float room_lp; // for water fx, lowpass for entire room
+ float room_mod; // stereo amplitude modulation for room
+ float room_size; // reverb: initial reflection size
+ float room_refl; // reverb: decay time
+ float room_rvblp; // reverb: low pass filtering level
+ float room_delay; // mono delay: delay time
+ float room_feedback; // mono delay: decay time
+ float room_dlylp; // mono delay: low pass filtering level
+ float room_left; // left channel delay time
+} sx_preset_t;
+
+sx_preset_t rgsxpre[CSXROOM] =
+{
+// SXROOM_OFF 0
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0},
+
+// SXROOM_GENERIC 1 // general, low reflective, diffuse room
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.065, 0.1, 0.0, 0.01},
+
+// SXROOM_METALIC_S 2 // highly reflective, parallel surfaces
+// SXROOM_METALIC_M 3
+// SXROOM_METALIC_L 4
+
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.02, 0.75, 0.0, 0.01}, // 0.001
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.03, 0.78, 0.0, 0.02}, // 0.002
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.77, 0.0, 0.03}, // 0.003
+
+// SXROOM_TUNNEL_S 5 // resonant reflective, long surfaces
+// SXROOM_TUNNEL_M 6
+// SXROOM_TUNNEL_L 7
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.85, 1.0, 0.018, 0.7, 2.0, 0.01}, // 0.01
+ {0.0, 0.0, 0.05, 0.88, 1.0, 0.020, 0.7, 2.0, 0.02}, // 0.02
+ {0.0, 0.0, 0.05, 0.92, 1.0, 0.025, 0.7, 2.0, 0.04}, // 0.04
+
+// SXROOM_CHAMBER_S 8 // diffuse, moderately reflective surfaces
+// SXROOM_CHAMBER_M 9
+// SXROOM_CHAMBER_L 10
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.84, 1.0, 0.0, 0.0, 2.0, 0.012}, // 0.003
+ {0.0, 0.0, 0.05, 0.90, 1.0, 0.0, 0.0, 2.0, 0.008}, // 0.002
+ {0.0, 0.0, 0.05, 0.95, 1.0, 0.0, 0.0, 2.0, 0.004}, // 0.001
+
+// SXROOM_BRITE_S 11 // diffuse, highly reflective
+// SXROOM_BRITE_M 12
+// SXROOM_BRITE_L 13
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.7, 0.0, 0.0, 0.0, 2.0, 0.012}, // 0.003
+ {0.0, 0.0, 0.055, 0.78, 0.0, 0.0, 0.0, 2.0, 0.008}, // 0.002
+ {0.0, 0.0, 0.05, 0.86, 0.0, 0.0, 0.0, 2.0, 0.002}, // 0.001
+
+// SXROOM_WATER1 14 // underwater fx
+// SXROOM_WATER2 15
+// SXROOM_WATER3 16
+// lp mod size refl rvblp delay feedbk dlylp left
+ {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.01},
+ {1.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.85, 2.0, 0.02},
+ {1.0, 0.0, 0.0, 0.0, 1.0, 0.2, 0.6, 2.0, 0.05},
+
+// SXROOM_CONCRETE_S 17 // bare, reflective, parallel surfaces
+// SXROOM_CONCRETE_M 18
+// SXROOM_CONCRETE_L 19
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.8, 1.0, 0.0, 0.48, 2.0, 0.016}, // 0.15 delay, 0.008 left
+ {0.0, 0.0, 0.06, 0.9, 1.0, 0.0, 0.52, 2.0, 0.01 }, // 0.22 delay, 0.005 left
+ {0.0, 0.0, 0.07, 0.94, 1.0, 0.3, 0.6, 2.0, 0.008}, // 0.001
+
+// SXROOM_OUTSIDE1 20 // echoing, moderately reflective
+// SXROOM_OUTSIDE2 21 // echoing, dull
+// SXROOM_OUTSIDE3 22 // echoing, very dull
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.3, 0.42, 2.0, 0.0},
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.35, 0.48, 2.0, 0.0},
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.38, 0.6, 2.0, 0.0},
+
+// SXROOM_CAVERN_S 23 // large, echoing area
+// SXROOM_CAVERN_M 24
+// SXROOM_CAVERN_L 25
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.9, 1.0, 0.2, 0.28, 0.0, 0.0},
+ {0.0, 0.0, 0.07, 0.9, 1.0, 0.3, 0.4, 0.0, 0.0},
+ {0.0, 0.0, 0.09, 0.9, 1.0, 0.35, 0.5, 0.0, 0.0},
+
+// SXROOM_WEIRDO1 26
+// SXROOM_WEIRDO2 27
+// SXROOM_WEIRDO3 28
+// SXROOM_WEIRDO3 29
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 1.0, 0.01, 0.9, 0.0, 0.0, 0.0, 2.0, 0.05},
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.009, 0.999, 2.0, 0.04},
+ {0.0, 0.0, 0.001, 0.999, 0.0, 0.2, 0.8, 2.0, 0.05}
+
+};
+
+// main routine for processing room sound fx
+// if fFilter is true, then run in-line filter (for underwater fx)
+// if fTimefx is true, then run reverb and delay fx
+// NOTE: only processes preset room_types from 0-29 (CSXROOM)
+void SX_RoomFX( int endtime, int fFilter, int fTimefx )
+{
+ int i, fReset;
+ int sampleCount;
+ float roomType;
+
+ // return right away if fx processing is turned off
+ if( sxroom_off->value != 0.0 )
+ return;
+
+ sampleCount = endtime - paintedtime;
+ if( sampleCount < 0 )
+ return;
+
+ fReset = false;
+ if( listener_waterlevel > 2 )
+ roomType = sxroomwater_type->value;
+ else roomType = sxroom_type->value;
+
+ // only process legacy roomtypes here
+ if( (int)roomType >= CSXROOM )
+ return;
+
+ if( roomType != sxroom_typeprev )
+ {
+ Msg( "Room_type: %2.1f\n", roomType );
+
+ sxroom_typeprev = roomType;
+
+ i = (int)(roomType);
+ if( i < CSXROOM && i >= 0 )
+ {
+ Cvar_SetValue( "room_lp", rgsxpre[i].room_lp );
+ Cvar_SetValue( "room_mod", rgsxpre[i].room_mod );
+ Cvar_SetValue( "room_size", rgsxpre[i].room_size );
+ Cvar_SetValue( "room_refl", rgsxpre[i].room_refl );
+ Cvar_SetValue( "room_rvblp", rgsxpre[i].room_rvblp );
+ Cvar_SetValue( "room_delay", rgsxpre[i].room_delay );
+ Cvar_SetValue( "room_feedback", rgsxpre[i].room_feedback );
+ Cvar_SetValue( "room_dlylp", rgsxpre[i].room_dlylp );
+ Cvar_SetValue( "room_left", rgsxpre[i].room_left );
+ }
+
+ SXRVB_CheckNewReverbVal();
+ SXDLY_CheckNewDelayVal();
+ SXDLY_CheckNewStereoDelayVal();
+
+ fReset = true;
+ }
+
+ if( fReset || roomType != 0.0 )
+ {
+ // debug code
+ SXRVB_CheckNewReverbVal();
+ SXDLY_CheckNewDelayVal();
+ SXDLY_CheckNewStereoDelayVal();
+ // debug code
+
+ if( fFilter ) SXRVB_DoAMod( sampleCount );
+
+ if( fTimefx )
+ {
+ SXRVB_DoReverb( sampleCount );
+ SXDLY_DoDelay( sampleCount );
+ SXDLY_DoStereoDelay( sampleCount );
+ }
+ }
+}
\ No newline at end of file
diff --git a/snd_dx/s_direct.c b/snd_dx/s_direct.c
index a187fa0c..07b745b7 100644
--- a/snd_dx/s_direct.c
+++ b/snd_dx/s_direct.c
@@ -34,13 +34,13 @@ typedef enum
cvar_t *s_wavonly;
static HWND snd_hwnd;
-static bool dsound_init;
-static bool wav_init;
static bool snd_firsttime = true, snd_isdirect, snd_iswave;
static bool primary_format_set;
static int snd_buffer_count = 0;
static int sample16;
static int snd_sent, snd_completed;
+bool dsound_init;
+bool wavout_init;
/*
=======================================================================
@@ -316,7 +316,7 @@ void SNDDMA_FreeSound( void )
lpData = NULL;
lpWaveHdr = NULL;
dsound_init = false;
- wav_init = false;
+ wavout_init = false;
}
/*
@@ -336,9 +336,9 @@ si_state_t SNDDMA_InitDirect( void *hInst )
switch( s_khz->integer )
{
- case 44: dma.speed = 44100; break;
- case 22: dma.speed = 22050; break;
- default: dma.speed = 11025; break;
+ case 44: dma.speed = SOUND_44k; break;
+ case 22: dma.speed = SOUND_22k; break;
+ default: dma.speed = SOUND_11k; break;
}
MsgDev( D_NOTE, "SNDDMA_InitDirect: initializing DirectSound ");
@@ -409,9 +409,9 @@ si_state_t SNDDMA_InitWav( void )
switch( s_khz->integer )
{
- case 44: dma.speed = 44100; break;
- case 22: dma.speed = 22050; break;
- default: dma.speed = 11025; break;
+ case 44: dma.speed = SOUND_44k; break;
+ case 22: dma.speed = SOUND_22k; break;
+ default: dma.speed = SOUND_11k; break;
}
Mem_Set( &format, 0, sizeof( format ));
@@ -499,7 +499,7 @@ si_state_t SNDDMA_InitWav( void )
dma.submission_chunk = 512;
dma.buffer = (byte *)lpData;
sample16 = (dma.samplebits / 8) - 1;
- wav_init = true;
+ wavout_init = true;
return SIS_SUCCESS;
}
@@ -519,7 +519,7 @@ int SNDDMA_Init( void *hInst )
Mem_Set( &dma, 0, sizeof( dma ));
s_wavonly = Cvar_Get( "s_wavonly", "0", CVAR_LATCH_AUDIO|CVAR_ARCHIVE, "force to use WaveOutput only" );
- dsound_init = wav_init = 0;
+ dsound_init = wavout_init = 0;
// init DirectSound
if( !s_wavonly->integer )
@@ -560,7 +560,7 @@ int SNDDMA_Init( void *hInst )
}
snd_buffer_count = 1;
- if( !dsound_init && !wav_init )
+ if( !dsound_init && !wavout_init )
{
if( snd_firsttime )
MsgDev( D_ERROR, "SNDDMA_Init: can't initialize sound device\n" );
@@ -593,7 +593,7 @@ int SNDDMA_GetDMAPos( void )
pDSBuf->lpVtbl->GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite );
s = mmtime.u.sample - mmstarttime.u.sample;
}
- else if( wav_init )
+ else if( wavout_init )
{
s = snd_sent * WAV_BUFFER_SIZE;
}
@@ -651,6 +651,57 @@ void SNDDMA_BeginPainting( void )
dma.buffer = (byte *)pbuf;
}
+void *SNDDMA_LockBuffer( void )
+{
+ int reps = 0;
+ void *pbuf = NULL, *pbuf2 = NULL;
+ DWORD dwSize2, dwStatus;
+ HRESULT hr;
+
+ if( !pDSBuf ) return dma.buffer;
+
+ // if the buffer was lost or stopped, restore it and/or restart it
+ if( pDSBuf->lpVtbl->GetStatus( pDSBuf, &dwStatus ) != DS_OK )
+ MsgDev( D_WARN, "SNDDMA_LockBuffer: couldn't get sound buffer status\n" );
+
+ if( dwStatus & DSBSTATUS_BUFFERLOST )
+ pDSBuf->lpVtbl->Restore( pDSBuf );
+
+ if(!( dwStatus & DSBSTATUS_PLAYING ))
+ pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING );
+
+ // lock the dsound buffer
+ dma.buffer = NULL;
+ reps = 0;
+
+ while(( hr = pDSBuf->lpVtbl->Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize, &pbuf2, &dwSize2, 0 )) != DS_OK )
+ {
+ if( hr != DSERR_BUFFERLOST )
+ {
+ MsgDev( D_ERROR, "SNDDMA_LockBuffer: lock failed with error '%s'\n", DSoundError( hr ));
+ S_Shutdown ();
+ S_Init ( snd_hwnd );
+ return NULL;
+ }
+ else pDSBuf->lpVtbl->Restore( pDSBuf );
+
+ if( ++reps > 100 )
+ {
+ MsgDev( D_ERROR, "SNDDMA_LockBuffer: couldn't restore buffer\n");
+ S_Shutdown ();
+ S_Init ( snd_hwnd );
+ return NULL;
+ }
+ }
+
+ dma.buffer = (byte *)pbuf;
+ return pbuf;
+}
+
+void SNDDMA_UnlockBuffer( void )
+{
+}
+
/*
==============
SNDDMA_Submit
@@ -670,7 +721,7 @@ void SNDDMA_Submit( void )
// unlock the dsound buffer
if( pDSBuf ) pDSBuf->lpVtbl->Unlock( pDSBuf, dma.buffer, locksize, NULL, 0 );
- if( !wav_init ) return;
+ if( !wavout_init ) return;
// find which sound blocks have completed
while( 1 )
diff --git a/snd_dx/s_dsp.c b/snd_dx/s_dsp.c
new file mode 100644
index 00000000..792eb971
--- /dev/null
+++ b/snd_dx/s_dsp.c
@@ -0,0 +1,6986 @@
+//=======================================================================
+// Copyright XashXT Group 2009 ©
+// s_dsp.c - audio processing routines
+//=======================================================================
+
+#include "sound.h"
+
+// Xash3D definitions
+#define clamp( val, min, max ) bound( min, val, max )
+#define CSXROOM 29
+
+#define SIGN( d ) ((d)<0?-1:1)
+#define ABS( a ) abs(a)
+#define MSEC_TO_SAMPS( a ) (((a)*SOUND_DMA_SPEED) / 1000)// convert milliseconds to # samples in equivalent time
+#define SEC_TO_SAMPS( a ) ((a)*SOUND_DMA_SPEED) // conver seconds to # samples in equivalent time
+
+//#define CLIP_DSP( x ) ((x) > 32767 ? 32767 : ((x) < -32767 ? -32767 : (x)))
+#define CLIP_DSP( x ) (x)
+
+extern bool SURROUND_ON;
+
+#define SOUND_MS_PER_FT 1 // sound travels approx 1 foot per millisecond
+#define ROOM_MAX_SIZE 1000 // max size in feet of room simulation for dsp
+
+//===============================================================================
+//
+// Digital Signal Processing algorithms for audio FX.
+//
+// KellyB 2/18/03
+//===============================================================================
+
+// Performance notes:
+
+// DSP processing should take no more than 3ms total time per frame to remain on par with hl1
+// assume a min frame rate of 24fps = 42ms per frame at 24fps, to maintain 44.1khz output rate,
+// we must process about 1840 mono samples per frame. So we must process 1840 samples in 3ms.
+// on a 1Ghz CPU (mid-low end CPU) 3ms provides roughly 3,000,000 cycles.
+// Thus we have 3e6 / 1840 = 1630 cycles per sample.
+
+#define PBITS 12 // parameter bits
+#define PMAX ((1 << PBITS)-1) // parameter max size
+
+// crossfade from y2 to y1 at point r (0 < r < PMAX)
+#define XFADE( y1, y2, r ) (((y1) * (r)) >> PBITS) + (((y2) * (PMAX - (r))) >> PBITS);
+#define XFADEF( y1, y2,r ) (((y1) * (r)) / (float)(PMAX)) + (((y2) * (PMAX - (r))) / (float)(PMAX));
+
+/////////////////////
+// dsp helpers
+/////////////////////
+
+// dot two integer vectors of length M+1
+// M is filter order, h is filter vector, w is filter state vector
+_inline int dot( int M, int *h, int *w )
+{
+ int i, y;
+
+ for( y = 0, i = 0; i <= M; i++ )
+ y += ( h[i] * w[i] ) >> PBITS;
+ return y;
+}
+
+// delay array w[] by D samples
+// w[0] = input, w[D] = output
+// practical for filters, but not for large values of D
+
+_inline void delay( int D, int *w )
+{
+ int i;
+ for( i = D; i >= 1; i-- ) // reverse order updating
+ w[i] = w[i-1];
+}
+
+// circular wrap of pointer p, relative to array w
+// D delay line size in samples w[0...D]
+// w delay line buffer pointer, dimension D+1
+// p circular pointer
+
+_inline void wrap( int D, int *w, int **p )
+{
+ if( *p > w + D ) *p -= D + 1; // when *p = w + D + 1, it wraps around to *p = w
+ if( *p < w ) *p += D + 1; // when *p = w - 1, it wraps around to *p = w + D
+}
+
+// simple averaging filter for performance - a[] is 0, b[] is 1, L is # of samples to average
+_inline int avg_filter( int M, int *a, int L, int *b, int *w, int x )
+{
+ int i, y = 0;
+
+ w[0] = x;
+
+ // output adder
+ switch( L )
+ {
+ default:
+ case 12: y += w[12];
+ case 11: y += w[11];
+ case 10: y += w[10];
+ case 9: y += w[9];
+ case 8: y += w[8];
+ case 7: y += w[7];
+ case 6: y += w[6];
+ case 5: y += w[5];
+ case 4: y += w[4];
+ case 3: y += w[3];
+ case 2: y += w[2];
+ case 1: y += w[1];
+ case 0: y += w[0];
+ }
+
+ for( i = L; i >= 1; i-- ) // reverse update internal state
+ w[i] = w[i-1];
+
+ switch( L )
+ {
+ default:
+ case 12: return y / 13;
+ case 11: return y / 12;
+ case 10: return y / 11;
+ case 9: return y / 10;
+ case 8: return y / 9;
+ case 7: return y >> 3;
+ case 6: return y / 7;
+ case 5: return y / 6;
+ case 4: return y / 5;
+ case 3: return y >> 2;
+ case 2: return y / 3;
+ case 1: return y >> 1;
+ case 0: return y;
+ }
+ return y; // current output sample
+}
+
+// IIR filter, cannonical form
+// returns single sample y for current input value x
+// x is input sample
+// w = internal state vector, dimension max(M,L) + 1
+// L, M numerator and denominator filter orders
+// a,b are M+1 dimensional arrays of filter params
+//
+// for M = 4:
+//
+// 1 w0(n) b0
+// x(n)--->(+)--(*)-----.------(*)->(+)---> y(n)
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -a1 |W1 b1 |
+// ----(*)---.------(*)----
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -a2 |W2 b2 |
+// ----(*)---.------(*)----
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -a3 |W3 b3 |
+// ----(*)---.------(*)----
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -a4 |W4 b4 |
+// ----(*)---.------(*)----
+//
+// for each input sample x, do:
+// w0 = x - a1*w1 - a2*w2 - ... aMwM
+// y = b0*w0 + b1*w1 + ...bL*wL
+// wi = wi-1, i = K, K-1, ..., 1
+
+
+_inline int iir_filter( int M, int *a, int L, int *b, int *w, int x )
+{
+ int K, i;
+ int y;
+ int x0;
+
+ if( M == 0 )
+ return( avg_filter( M, a, L, b, w, x ));
+
+ y = 0;
+ x0 = x;
+
+ K = max( M, L );
+
+ // for ( i = 1; i <= M; i++ ) // input adder
+ // w[0] -= ( a[i] * w[i] ) >> PBITS;
+
+ // M is clamped between 1 and FLT_M
+ // change this switch statement if FLT_M changes!
+
+ switch( M )
+ {
+ case 12: x0 -= ( a[12] * w[12] ) >> PBITS;
+ case 11: x0 -= ( a[11] * w[11] ) >> PBITS;
+ case 10: x0 -= ( a[10] * w[10] ) >> PBITS;
+ case 9: x0 -= ( a[9] * w[9] ) >> PBITS;
+ case 8: x0 -= ( a[8] * w[8] ) >> PBITS;
+ case 7: x0 -= ( a[7] * w[7] ) >> PBITS;
+ case 6: x0 -= ( a[6] * w[6] ) >> PBITS;
+ case 5: x0 -= ( a[5] * w[5] ) >> PBITS;
+ case 4: x0 -= ( a[4] * w[4] ) >> PBITS;
+ case 3: x0 -= ( a[3] * w[3] ) >> PBITS;
+ case 2: x0 -= ( a[2] * w[2] ) >> PBITS;
+ default:
+ case 1: x0 -= ( a[1] * w[1] ) >> PBITS;
+ }
+
+ w[0] = x0;
+
+ // for (i = 0; i <= L; i++) // output adder
+ // y += ( b[i] * w[i] ) >> PBITS;
+
+ switch( L )
+ {
+ case 12: y += ( b[12] * w[12] ) >> PBITS;
+ case 11: y += ( b[11] * w[11] ) >> PBITS;
+ case 10: y += ( b[10] * w[10] ) >> PBITS;
+ case 9: y += ( b[9] * w[9] ) >> PBITS;
+ case 8: y += ( b[8] * w[8] ) >> PBITS;
+ case 7: y += ( b[7] * w[7] ) >> PBITS;
+ case 6: y += ( b[6] * w[6] ) >> PBITS;
+ case 5: y += ( b[5] * w[5] ) >> PBITS;
+ case 4: y += ( b[4] * w[4] ) >> PBITS;
+ case 3: y += ( b[3] * w[3] ) >> PBITS;
+ case 2: y += ( b[2] * w[2] ) >> PBITS;
+ default:
+ case 1: y += ( b[1] * w[1] ) >> PBITS;
+ case 0: y += ( b[0] * w[0] ) >> PBITS;
+ }
+
+ for (i = K; i >= 1; i--) // reverse update internal state
+ w[i] = w[i-1];
+
+ return y; // current output sample
+}
+
+// IIR filter, cannonical form, using dot product and delay implementation
+// (may be easier to optimize this routine.)
+_inline int iir_filter2( int M, int *a, int L, int *b, int *w, int x )
+{
+ int K, y;
+
+ K = max( M, L ); // K = max (M, L)
+ w[0] = 0; // needed for dot (M, a, w)
+
+ w[0] = x - dot ( M, a, w ); // input adder
+ y = dot ( L, b, w ); // output adder
+
+ delay( K, w ); // update delay line
+
+ return y; // current output sample
+}
+
+// fir filter - no feedback = high stability but also may be more expensive computationally
+_inline int fir_filter ( int M, int *h, int *w, int x )
+{
+ int i, y;
+
+ w[0] = x;
+
+ for ( y = 0, i = 0; i <= M; i++ )
+ y += h[i] * w[i];
+
+ for ( i = M; i >= -1; i-- )
+ w[i] = w[i-1];
+
+ return y;
+}
+
+// fir filter, using dot product and delay implementation
+_inline int fir_filter2 ( int M, int *h, int *w, int x )
+{
+ int y;
+
+ w[0] = x;
+
+ y = dot( M, h, w );
+
+ delay( M, w );
+
+ return y;
+}
+
+
+// tap - i-th tap of circular delay line buffer
+// D delay line size in samples
+// w delay line buffer pointer, of dimension D+1
+// p circular pointer
+// t = 0...D
+int tap ( int D, int *w, int *p, int t )
+{
+ return w[(p - w + t) % (D + 1)];
+}
+
+// tapi - interpolated tap output of a delay line
+// interpolates sample between adjacent samples in delay line for 'frac' part of delay
+// D delay line size in samples
+// w delay line buffer pointer, of dimension D+1
+// p circular pointer
+// t - delay tap integer value 0...D. (complete delay is t.frac )
+// frac - varying 16 bit fractional delay value 0...32767 (normalized to 0.0 - 1.0)
+_inline int tapi ( int D, int *w, int *p, int t, int frac )
+{
+ int i, j;
+ int si, sj;
+
+ i = t; // tap value, interpolate between adjacent samples si and sj
+ j = (i + 1) % (D+1); // if i = D, then j = 0; otherwise, j = i + 1
+
+ si = tap( D, w, p, i ); // si(n) = x(n - i)
+ sj = tap( D, w, p, j ); // sj(n) = x(n - j)
+
+ return si + (((frac) * (sj - si) ) >> 16);
+}
+
+// circular delay line, D-fold delay
+// D delay line size in samples w[0..D]
+// w delay line buffer pointer, dimension D+1
+// p circular pointer
+_inline void cdelay( int D, int *w, int **p )
+{
+ (*p)--; // decrement pointer and wrap modulo (D+1)
+ wrap ( D, w, p ); // when *p = w-1, it wraps around to *p = w+D
+}
+
+// plain reverberator with circular delay line
+// D delay line size in samples
+// t tap from this location - <= D
+// w delay line buffer pointer of dimension D+1
+// p circular pointer, must be init to &w[0] before first call
+// a feedback value, 0-PMAX (normalized to 0.0-1.0)
+// b gain
+// x input sample
+
+// w0(n) b
+// x(n)--->(+)--------.-----(*)-> y(n)
+// ^ |
+// | [Delay d]
+// | |
+// | a |Wd(n)
+// ----(*)---.
+
+_inline int dly_plain( int D, int t, int *w, int **p, int a, int b, int x )
+{
+ int y, sD;
+
+ sD = tap ( D, w, *p, t ); // Tth tap delay output
+ y = x + (( a * sD ) >> PBITS); // filter output
+ **p = y; // delay input
+ cdelay( D, w, p ); // update delay line
+
+ return ( (y * b) >> PBITS );
+}
+
+// straight delay line
+//
+// D delay line size in samples
+// t tap from this location - <= D
+// w delay line buffer pointer of dimension D+1
+// p circular pointer, must be init to &w[0] before first call
+// x input sample
+//
+// x(n)--->[Delay d]---> y(n)
+//
+_inline int dly_linear( int D, int t, int *w, int **p, int x )
+{
+ int y;
+
+ y = tap ( D, w, *p, t ); // Tth tap delay output
+ **p = x; // delay input
+ cdelay ( D, w, p ); // update delay line
+
+ return ( y );
+}
+
+// lowpass reverberator, replace feedback multiplier 'a' in
+// plain reverberator with a low pass filter
+// D delay line size in samples
+// t tap from this location - <= D
+// w delay line buffer pointer of dimension D+1
+// p circular pointer, must be init to &w[0] before first call
+// a feedback gain
+// b output gain
+// M filter order
+// bf filter numerator, 0-PMAX (normalized to 0.0-1.0), M+1 dimensional
+// af filter denominator, 0-PMAX (normalized to 0.0-1.0), M+1 dimensional
+// vf filter state, M+1 dimensional
+// x input sample
+// w0(n) b
+// x(n)--->(+)--------------.----(*)--> y(n)
+// ^ |
+// | [Delay d]
+// | |
+// | a |Wd(n)
+// --(*)--[Filter])-
+
+int dly_lowpass( int D, int t, int *w, int **p, int a, int b, int M, int *af, int L, int *bf, int *vf, int x )
+{
+ int y, sD;
+
+ sD = tap( D, w, *p, t ); // delay output is filter input
+ y = x + ((iir_filter ( M, af, L, bf, vf, sD ) * a) >> PBITS); // filter output with gain
+ **p = y; // delay input
+ cdelay ( D, w, p ); // update delay line
+
+ return ( (y * b) >> PBITS ); // output with gain
+}
+
+// allpass reverberator with circular delay line
+// D delay line size in samples
+// t tap from this location - <= D
+// w delay line buffer pointer of dimension D+1
+// p circular pointer, must be init to &w[0] before first call
+// a feedback value, 0-PMAX (normalized to 0.0-1.0)
+// b gain
+
+// w0(n) -a b
+// x(n)--->(+)--------.-----(*)-->(+)--(*)-> y(n)
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | a |Wd(n) |
+// ----(*)---.-------------
+//
+// for each input sample x, do:
+// w0 = x + a*Wd
+// y = -a*w0 + Wd
+// delay (d, W) - w is the delay buffer array
+//
+// or, using circular delay, for each input sample x do:
+//
+// Sd = tap (D,w,p,D)
+// S0 = x + a*Sd
+// y = -a*S0 + Sd
+// *p = S0
+// cdelay(D, w, &p)
+
+_inline int dly_allpass( int D, int t, int *w, int **p, int a, int b, int x )
+{
+ int y, s0, sD;
+
+ sD = tap ( D, w, *p, t ); // Dth tap delay output
+ s0 = x + (( a * sD ) >> PBITS);
+
+ y = ( ( -a * s0 ) >> PBITS ) + sD; // filter output
+ **p = s0; // delay input
+ cdelay ( D, w, p ); // update delay line
+
+ return ( (y * b) >> PBITS );
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////
+// fixed point math for real-time wave table traversing, pitch shifting, resampling
+///////////////////////////////////////////////////////////////////////////////////
+
+#define FIX20_BITS 20 // 20 bits of fractional part
+#define FIX20_SCALE (1 << FIX20_BITS)
+
+#define FIX20_INTMAX ((1 << (32 - FIX20_BITS))-1) // maximum step integer
+
+#define FLOAT_TO_FIX20(a) ((int)((a) * (float)FIX20_SCALE)) // convert float to fixed point
+#define INT_TO_FIX20(a) (((int)(a)) << FIX20_BITS) // convert int to fixed point
+#define FIX20_TO_FLOAT(a) ((float)(a) / (float)FIX20_SCALE) // convert fix20 to float
+#define FIX20_INTPART(a) (((int)(a)) >> FIX20_BITS) // get integer part of fixed point
+#define FIX20_FRACPART(a) ((a) - (((a) >> FIX20_BITS) << FIX20_BITS)) // get fractional part of fixed point
+
+#define FIX20_FRACTION(a,b) (FIX(a)/(b)) // convert int a to fixed point, divide by b
+
+typedef int fix20int;
+
+/////////////////////////////////
+// DSP processor parameter block
+/////////////////////////////////
+
+// NOTE: these prototypes must match the XXX_Params ( prc_t *pprc ) and XXX_GetNext ( XXX_t *p, int x ) functions
+
+typedef void * (*prc_Param_t)( void *pprc ); // individual processor allocation functions
+typedef int (*prc_GetNext_t)( void *pdata, int x ); // get next function for processor
+typedef int (*prc_GetNextN_t)( void *pdata, samplepair_t *pbuffer, int SampleCount, int op ); // batch version of getnext
+typedef void (*prc_Free_t)( void *pdata ); // free function for processor
+typedef void (*prc_Mod_t)( void *pdata, float v ); // modulation function for processor
+
+#define OP_LEFT 0 // batch process left channel in place
+#define OP_RIGHT 1 // batch process right channel in place
+#define OP_LEFT_DUPLICATE 2 // batch process left channel in place, duplicate to right channel
+
+#define PRC_NULL 0 // pass through - must be 0
+#define PRC_DLY 1 // simple feedback reverb
+#define PRC_RVA 2 // parallel reverbs
+#define PRC_FLT 3 // lowpass or highpass filter
+#define PRC_CRS 4 // chorus
+#define PRC_PTC 5 // pitch shifter
+#define PRC_ENV 6 // adsr envelope
+#define PRC_LFO 7 // lfo
+#define PRC_EFO 8 // envelope follower
+#define PRC_MDY 9 // mod delay
+#define PRC_DFR 10 // diffusor - n series allpass delays
+#define PRC_AMP 11 // amplifier with distortion
+
+#define QUA_LO 0 // quality of filter or reverb. Must be 0,1,2,3.
+#define QUA_MED 1
+#define QUA_HI 2
+#define QUA_VHI 3
+#define QUA_MAX QUA_VHI
+#define CPRCPARAMS 16 // up to 16 floating point params for each processor type
+
+// processor definition - one for each running instance of a dsp processor
+
+typedef struct prc_s
+{
+ int type; // PRC type
+ float prm[CPRCPARAMS]; // dsp processor parameters - array of floats
+ prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type
+ prc_GetNext_t pfnGetNext; // get next function
+ prc_GetNextN_t pfnGetNextN; // batch version of get next
+ prc_Free_t pfnFree; // free function
+ prc_Mod_t pfnMod; // modulation function
+ void *pdata; // processor state data - ie: pdly, pflt etc.
+} prc_t;
+
+// processor parameter ranges - for validating parameters during allocation of new processor
+
+typedef struct prm_rng_s
+{
+ int iprm; // parameter index
+ float lo; // min value of parameter
+ float hi; // max value of parameter
+} prm_rng_t;
+
+void PRC_CheckParams( prc_t *pprc, prm_rng_t *prng );
+
+///////////
+// Filters
+///////////
+#define CFLTS 64 // max number of filters simultaneously active
+#define FLT_M 12 // max order of any filter
+
+#define FLT_LP 0 // lowpass filter
+#define FLT_HP 1 // highpass filter
+#define FTR_MAX FLT_HP
+
+// flt parameters
+
+typedef struct flt_s
+{
+ bool fused; // true if slot in use
+
+ int b[FLT_M+1]; // filter numerator parameters (convert 0.0-1.0 to 0-PMAX representation)
+ int a[FLT_M+1]; // filter denominator parameters (convert 0.0-1.0 to 0-PMAX representation)
+ int w[FLT_M+1]; // filter state - samples (dimension of max (M, L))
+ int L; // filter order numerator (dimension of a[M+1])
+ int M; // filter order denominator (dimension of b[L+1])
+} flt_t;
+
+// flt flts
+
+flt_t flts[CFLTS];
+
+void FLT_Init( flt_t *pf ) { if ( pf ) Mem_Set ( pf, 0, sizeof (flt_t) ); }
+void FLT_InitAll( void ) { int i; for ( i = 0 ; i < CFLTS; i++ ) FLT_Init ( &flts[i] ); }
+void FLT_Free( flt_t *pf ) { if ( pf ) Mem_Set ( pf, 0, sizeof (flt_t) ); }
+void FLT_FreeAll( void ) { int i; for ( i = 0 ; i < CFLTS; i++) FLT_Free ( &flts[i] ); }
+
+
+// find a free filter from the filter pool
+// initialize filter numerator, denominator b[0..M], a[0..L]
+flt_t *FLT_Alloc ( int M, int L, int *a, int *b )
+{
+ int i, j;
+ flt_t *pf = NULL;
+
+ for (i = 0; i < CFLTS; i++)
+ {
+ if( !flts[i].fused )
+ {
+ pf = &flts[i];
+
+ // transfer filter params into filter struct
+ pf->M = M;
+ pf->L = L;
+ for (j = 0; j <= M; j++)
+ pf->a[j] = a[j];
+
+ for (j = 0; j <= L; j++)
+ pf->b[j] = b[j];
+
+ pf->fused = true;
+ break;
+ }
+ }
+
+ Assert(pf); // make sure we're not trying to alloc more than CFLTS flts
+
+ return pf;
+}
+
+// convert filter params cutoff and type into
+// iir transfer function params M, L, a[], b[]
+
+// iir filter, 1st order, transfer function is H(z) = b0 + b1 Z^-1 / a0 + a1 Z^-1
+// or H(z) = b0 - b1 Z^-1 / a0 + a1 Z^-1 for lowpass
+
+// design cutoff filter at 3db (.5 gain) p579
+
+void FLT_Design_3db_IIR ( float cutoff, float ftype, int *pM, int *pL, int *a, int *b )
+{
+ // ftype: FLT_LP, FLT_HP, FLT_BP
+
+ double Wc = 2.0 * M_PI * cutoff / SOUND_DMA_SPEED; // radians per sample
+ double Oc;
+ double fa;
+ double fb;
+
+ // calculations:
+ // Wc = 2pi * fc/SOUND_44k convert to radians
+ // Oc = tan (Wc/2) * Gc / sqt ( 1 - Gc^2) get analog version, low pass
+ // Oc = tan (Wc/2) * (sqt (1 - Gc^2)) / Gc analog version, high pass
+ // Gc = 10 ^ (-Ac/20) gain at cutoff. Ac = 3db, so Gc^2 = 0.5
+ // a = ( 1 - Oc ) / ( 1 + Oc )
+ // b = ( 1 - a ) / 2
+
+ Oc = tan ( Wc / 2.0 );
+
+ fa = ( 1.0 - Oc ) / ( 1.0 + Oc );
+
+ fb = ( 1.0 - fa ) / 2.0;
+
+ if ( ftype == FLT_HP )
+ fb = ( 1.0 + fa ) / 2.0;
+
+ a[0] = 0; // a0 always ignored
+ a[1] = (int)( -fa * PMAX ); // quantize params down to 0-PMAX >> PBITS
+ b[0] = (int)( fb * PMAX );
+ b[1] = b[0];
+
+ if ( ftype == FLT_HP )
+ b[1] = -b[1];
+
+ *pM = *pL = 1;
+
+ return;
+}
+
+
+// convolution of x[n] with h[n], resulting in y[n]
+// h, x, y filter, input and output arrays (double precision)
+// M = filter order, L = input length
+// h is M+1 dimensional
+// x is L dimensional
+// y is L+M dimensional
+
+void conv ( int M, double *h, int L, double *x, double *y )
+{
+ int n, m;
+
+ for ( n = 0; n < L+M; n++ )
+ {
+ for (y[n] = 0, m = max(0, n-L+1); m <= min(n, M); m++ )
+ {
+ y[n] += h[m] * x[n-m];
+ }
+ }
+}
+
+// cas2can - convert cascaded, second order section parameter arrays to
+// canonical numerator/denominator arrays. Canonical implementations
+// have half as many multiplies as cascaded implementations.
+
+// K is number of cascaded sections
+// A is Kx3 matrix of sos params A[K] = A[0]..A[K-1]
+// a is (2K + 1) -dimensional output of canonical params
+
+#define KMAX 32 // max # of sos sections - 8 is the most we should ever see at runtime
+
+void cas2can ( int K, double A[KMAX+1][3], int *aout )
+{
+ int i, j;
+ double d[2*KMAX + 1];
+ double a[2*KMAX + 1];
+
+ Assert ( K <= KMAX );
+
+ Mem_Set(d, 0, sizeof (double) * (2 * KMAX + 1));
+ Mem_Set(a, 0, sizeof (double) * (2 * KMAX + 1));
+
+ a[0] = 1;
+
+ for (i = 0; i < K; i++)
+ {
+ conv( 2, A[i], 2*i + 1, a, d );
+
+ for ( j = 0; j < 2*i + 3; j++ )
+ a[j] = d[j];
+ }
+
+ for (i = 0; i < (2*K + 1); i++)
+ aout[i] = a[i] * PMAX;
+}
+
+
+// chebyshev IIR design, type 2, Lowpass or Highpass
+
+#define lnf(e) (2.303 * log10 (e))
+
+#define acosh(e) ( lnf( (e) + sqrt((e)*(e) - 1) ) )
+#define asinh(e) ( lnf( (e) + sqrt((e)*(e) + 1) ) )
+
+
+// returns a[], b[] which are Kx3 matrices of cascaded second-order sections
+// these matrices may be passed directly to the iir_cas() routine for evaluation
+// Nmax - maximum order of filter
+// cutoff, ftype, qwidth - filter cutoff in hz, filter type FLT_LOWPASS/HIGHPASS, qwidth in hz
+// pM - denominator order
+// pL - numerator order
+// a - array of canonical filter params
+// b - array of canonical filter params
+
+void FLT_Design_Cheb ( int Nmax, float cutoff, float ftype, float qwidth, int *pM, int *pL, int *a, int *b )
+{
+// p769 - converted from MATLAB
+
+ double s = (ftype == FLT_LP ? 1 : -1 ); // 1 for LP, -1 for HP
+ double fs = SOUND_DMA_SPEED; // sampling frequency
+ double fpass = cutoff; // cutoff frequency
+ double fstop = fpass + max (2000, qwidth); // stop frequency
+ double Apass = 0.5; // max attenuation of pass band UNDONE: use Quality to select this
+ double Astop = 10; // max amplitude of stop band UNDONE: use Quality to select this
+
+ double Wpass, Wstop, epass, estop, Nex, aa, W3, f3, W0, G, Wi2, W02, a1, a2, th, Wi, D, b1;
+ int i, K, r, N;
+ double A[KMAX+1][3]; // denominator output matrices, second order sections
+ double B[KMAX+1][3]; // numerator output matrices, second order sections
+
+ Wpass = tan( M_PI * fpass / fs );
+ Wpass = pow( Wpass, s );
+ Wstop = tan( M_PI * fstop / fs );
+ Wstop = pow( Wstop, s );
+
+ epass = sqrt( pow( 10, Apass/10 ) - 1 );
+ estop = sqrt( pow( 10, Astop/10 ) - 1 );
+
+ // calculate filter order N
+
+ Nex = acosh( estop/epass ) / acosh ( Wstop/Wpass );
+ N = min ( ceil(Nex), Nmax ); // don't exceed Nmax for filter order
+ r = ( (int)N & 1); // r == 1 if N is odd
+ K = (N - r ) / 2;
+
+ aa = asinh ( estop ) / N;
+ W3 = Wstop / cosh( acosh(estop)/N );
+ f3 = (fs / M_PI) * atan( pow( W3, s ) );
+
+ W0 = sinh( aa ) / Wstop;
+ W02 = W0 * W0;
+
+ // 1st order section for N odd
+
+ if ( r == 1 )
+ {
+ G = 1 / (1 + W0);
+ A[0][0] = 1; A[0][1] = s * (2*G-1); A[0][2] = 0;
+ B[0][0] = G; B[0][1] = G*s; B[0][2] = 0;
+ }
+ else
+ {
+ A[0][0] = 1; A[0][1] = 0; A[0][2] = 0;
+ B[0][0] = 1; B[0][1] = 0; B[0][2] = 0;
+ }
+
+ for( i = 1; i <= K ; i++ )
+ {
+ th = M_PI * (N - 1 + 2 * i) / (2 * N);
+ Wi = sin(th) / Wstop;
+ Wi2 = Wi * Wi;
+
+ D = 1 - 2 * W0 * cos(th) + W02 + Wi2;
+ G = ( 1 + Wi2 ) / D;
+
+ b1 = 2 * ( 1 - Wi2 ) / ( 1 + Wi2 );
+ a1 = 2 * ( 1 - W02 - Wi2) / D;
+ a2 = ( 1 + 2 * W0 * cos(th) + W02 + Wi2) / D;
+
+ A[i][0] = 1;
+ A[i][1] = s * a1;
+ A[i][2] = a2;
+
+ B[i][0] = G;
+ B[i][1] = G* s* b1;
+ B[i][2] = G;
+ }
+
+ // convert cascade parameters to canonical parameters
+ cas2can ( K, A, a );
+ *pM = 2*K + 1;
+
+ cas2can ( K, B, b );
+ *pL = 2*K + 1;
+}
+
+// filter parameter order
+
+typedef enum
+{
+ flt_iftype,
+ flt_icutoff,
+ flt_iqwidth,
+ flt_iquality,
+
+ flt_cparam // # of params
+} flt_e;
+
+// filter parameter ranges
+
+prm_rng_t flt_rng[] = {
+
+ {flt_cparam, 0, 0}, // first entry is # of parameters
+ {flt_iftype, 0, FTR_MAX}, // filter type FLT_LP, FLT_HP, FLT_BP (UNDONE: FLT_BP currently ignored)
+ {flt_icutoff, 10, SOUND_22k}, // cutoff frequency in hz at -3db gain
+ {flt_iqwidth, 100, SOUND_11k}, // width of BP, or steepness of LP/HP (ie: fcutoff + qwidth = -60db gain point)
+ {flt_iquality, 0, QUA_MAX}, // QUA_LO, _MED, _HI 0,1,2,3
+};
+
+
+// convert prc float params to iir filter params, alloc filter and return ptr to it
+// filter quality set by prc quality - 0,1,2
+flt_t * FLT_Params ( prc_t *pprc )
+{
+ float qual = pprc->prm[flt_iquality];
+ float cutoff = pprc->prm[flt_icutoff];
+ float ftype = pprc->prm[flt_iftype];
+ float qwidth = pprc->prm[flt_iqwidth];
+
+ int L = 0; // numerator order
+ int M = 0; // denominator order
+ int b[FLT_M+1]; // numerator params 0..PMAX
+ int a[FLT_M+1]; // denominator params 0..PMAX
+
+ // low pass and highpass filter design
+
+ if ( (int) qual == QUA_LO) qual = QUA_MED; // disable lowest quality filter - check perf on lowend KDB
+
+ switch ( (int)qual )
+ {
+ case QUA_LO:
+ // lowpass averaging filter: perf KDB
+ Assert ( ftype == FLT_LP );
+ Assert ( cutoff <= SOUND_DMA_SPEED );
+ M = 0;
+
+ // L is # of samples to average
+
+ L = 0;
+ if ( cutoff <= SOUND_DMA_SPEED / 4) L = 1; // 11k
+ if ( cutoff <= SOUND_DMA_SPEED / 8) L = 2; // 5.5k
+ if ( cutoff <= SOUND_DMA_SPEED / 16) L = 4; // 2.75k
+ if ( cutoff <= SOUND_DMA_SPEED / 32) L = 8; // 1.35k
+ if ( cutoff <= SOUND_DMA_SPEED / 64) L = 12; // 750hz
+
+ break;
+ case QUA_MED:
+ // 1st order IIR filter, 3db cutoff at fc
+ FLT_Design_3db_IIR ( cutoff, ftype, &M, &L, a, b );
+
+ M = clamp (M, 1, FLT_M);
+ L = clamp (L, 1, FLT_M);
+
+ break;
+ case QUA_HI:
+ // type 2 chebyshev N = 4 IIR
+ FLT_Design_Cheb ( 4, cutoff, ftype, qwidth, &M, &L, a, b );
+
+ M = clamp (M, 1, FLT_M);
+ L = clamp (L, 1, FLT_M);
+
+ break;
+ case QUA_VHI:
+ // type 2 chebyshev N = 7 IIR
+ FLT_Design_Cheb ( 8, cutoff, ftype, qwidth, &M, &L, a, b );
+
+
+ M = clamp (M, 1, FLT_M);
+ L = clamp (L, 1, FLT_M);
+
+ break;
+ }
+
+ return FLT_Alloc ( M, L, a, b );
+}
+
+_inline void * FLT_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, flt_rng);
+ return (void *) FLT_Params ((prc_t *)p);
+}
+
+_inline void FLT_Mod ( void *p, float v ) { return; }
+
+// get next filter value for filter pf and input x
+
+_inline int FLT_GetNext ( flt_t *pf, int x )
+{
+ return iir_filter (pf->M, pf->a, pf->L, pf->b, pf->w, x);
+ // return iir_filter2 (pf->M, pf->a, pf->L, pf->b, pf->w, x);
+}
+
+// batch version for performance
+
+_inline void FLT_GetNextN( flt_t *pflt, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = FLT_GetNext( pflt, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = FLT_GetNext( pflt, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = FLT_GetNext( pflt, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Positional updaters for pitch shift etc
+///////////////////////////////////////////////////////////////////////////
+
+// looping position within a wav, with integer and fractional parts
+// used for pitch shifting, upsampling/downsampling
+// 20 bits of fraction, 8+ bits of integer
+
+typedef struct pos_s
+{
+ fix20int step; // wave table whole and fractional step value
+ fix20int cstep; // current cummulative step value
+ int pos; // current position within wav table
+ int D; // max dimension of array w[0...D] ie: # of samples = D+1
+} pos_t;
+
+// circular wrap of pointer p, relative to array w
+// D max buffer index w[0...D] (count of samples in buffer is D+1)
+// i circular index
+
+_inline void POS_Wrap ( int D, int *i )
+{
+ if ( *i > D )
+ *i -= D + 1; // when *pi = D + 1, it wraps around to *pi = 0
+
+ if ( *i < 0 )
+ *i += D + 1; // when *pi = - 1, it wraps around to *pi = D
+}
+
+// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
+// D is array max dimension w[0...D] (ie: size D+1)
+// w is ptr to array
+// p is ptr to pos_t to initialize
+_inline void POS_Init( pos_t *p, int D, float fstep )
+{
+ float step = fstep;
+
+ // make sure int part of step is capped at fix20_intmax
+
+ if ((int)step > FIX20_INTMAX)
+ step = (step - (int)step) + FIX20_INTMAX;
+
+ p->step = FLOAT_TO_FIX20(step); // convert fstep to fixed point
+ p->cstep = 0;
+ p->pos = 0; // current update value
+
+ p->D = D; // always init to end value, in case we're stepping backwards
+}
+
+// change step value - this is an instantaneous change, not smoothed.
+
+_inline void POS_ChangeVal( pos_t *p, float fstepnew )
+{
+ p->step = FLOAT_TO_FIX20( fstepnew ); // convert fstep to fixed point
+}
+
+// return current integer position, then update internal position value
+
+_inline int POS_GetNext ( pos_t *p )
+{
+
+ //float f = FIX20_TO_FLOAT(p->cstep);
+ //int i1 = FIX20_INTPART(p->cstep);
+ //float f1 = FIX20_TO_FLOAT(FIX20_FRACPART(p->cstep));
+ //float f2 = FIX20_TO_FLOAT(p->step);
+
+ p->cstep += p->step; // update accumulated fraction step value (fixed point)
+ p->pos += FIX20_INTPART( p->cstep ); // update pos with integer part of accumulated step
+ p->cstep = FIX20_FRACPART( p->cstep ); // throw away the integer part of accumulated step
+
+ // wrap pos around either end of buffer if needed
+
+ POS_Wrap(p->D, &(p->pos));
+
+ // make sure returned position is within array bounds
+
+ Assert (p->pos <= p->D);
+
+ return p->pos;
+}
+
+// oneshot position within wav
+typedef struct pos_one_s
+{
+ pos_t p; // pos_t
+ bool fhitend; // flag indicating we hit end of oneshot wav
+} pos_one_t;
+
+// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
+// one shot position - play only once, don't wrap, when hit end of buffer, return last position
+
+_inline void POS_ONE_Init( pos_one_t *p1, int D, float fstep )
+{
+ POS_Init( &p1->p, D, fstep ) ;
+
+ p1->fhitend = false;
+}
+
+// return current integer position, then update internal position value
+
+_inline int POS_ONE_GetNext ( pos_one_t *p1 )
+{
+ int pos;
+ pos_t *p0;
+
+ pos = p1->p.pos; // return current position
+
+ if (p1->fhitend)
+ return pos;
+
+ p0 = &(p1->p);
+ p0->cstep += p0->step; // update accumulated fraction step value (fixed point)
+ p0->pos += FIX20_INTPART( p0->cstep ); // update pos with integer part of accumulated step
+ //p0->cstep = SIGN(p0->cstep) * FIX20_FRACPART( p0->cstep );
+ p0->cstep = FIX20_FRACPART( p0->cstep ); // throw away the integer part of accumulated step
+
+ // if we wrapped, stop updating, always return last position
+ // if step value is 0, return hit end
+
+ if (!p0->step || p0->pos < 0 || p0->pos >= p0->D )
+ p1->fhitend = true;
+ else
+ pos = p0->pos;
+
+ // make sure returned value is within array bounds
+
+ Assert ( pos <= p0->D );
+
+ return pos;
+}
+
+
+/////////////////////
+// Reverbs and delays
+/////////////////////
+
+#define CDLYS 128 // max delay lines active. Also used for lfos.
+#define DLY_PLAIN 0 // single feedback loop
+#define DLY_ALLPASS 1 // feedback and feedforward loop - flat frequency response (diffusor)
+#define DLY_LOWPASS 2 // lowpass filter in feedback loop
+#define DLY_LINEAR 3 // linear delay, no feedback, unity gain
+#define DLY_MAX DLY_LINEAR
+
+// delay line
+typedef struct dly_s
+{
+
+ bool fused; // true if dly is in use
+ int type; // delay type
+
+ int D; // delay size, in samples
+ int t; // current tap, <= D
+ int D0; // original delay size (only relevant if calling DLY_ChangeVal)
+ int *p; // circular buffer pointer
+ int *w; // array of samples
+
+ int a; // feedback value 0..PMAX,normalized to 0-1.0
+ int b; // gain value 0..PMAX, normalized to 0-1.0
+ flt_t *pflt; // pointer to filter, if type DLY_LOWPASS
+ HANDLE h; // memory handle for sample array
+} dly_t;
+
+dly_t dlys[CDLYS]; // delay lines
+
+void DLY_Init ( dly_t *pdly ) { if ( pdly ) Mem_Set( pdly, 0, sizeof( dly_t )); }
+void DLY_InitAll ( void ) { int i; for( i = 0 ; i < CDLYS; i++) DLY_Init ( &dlys[i] ); }
+void DLY_Free ( dly_t *pdly )
+{
+ // free memory buffer
+ if( pdly )
+ {
+ FLT_Free ( pdly->pflt );
+
+ if ( pdly->w )
+ {
+ GlobalUnlock( pdly->h );
+ GlobalFree( pdly->h );
+ }
+
+ // free dly slot
+ Mem_Set( pdly, 0, sizeof (dly_t) );
+ }
+}
+
+
+void DLY_FreeAll ( void ) { int i; for ( i = 0; i < CDLYS; i++ ) DLY_Free ( &dlys[i] ); }
+
+// set up 'b' gain parameter of feedback delay to
+// compensate for gain caused by feedback.
+
+void DLY_SetNormalizingGain( dly_t *pdly )
+{
+ // compute normalized gain, set as output gain
+
+ // calculate gain of delay line with feedback, and use it to
+ // reduce output. ie: force delay line with feedback to unity gain
+
+ // for constant input x with feedback fb:
+
+ // out = x + x*fb + x * fb^2 + x * fb^3...
+ // gain = out/x
+ // so gain = 1 + fb + fb^2 + fb^3...
+ // which, by the miracle of geometric series, equates to 1/1-fb
+ // thus, gain = 1/(1-fb)
+
+ float fgain = 0;
+ float gain;
+ int b;
+
+ // if b is 0, set b to PMAX (1)
+
+ b = pdly->b ? pdly->b : PMAX;
+
+ // fgain = b * (1.0 / (1.0 - (float)pdly->a / (float)PMAX)) / (float)PMAX;
+
+ fgain = (1.0 / (1.0 - (float)pdly->a / (float)PMAX));
+
+ // compensating gain - multiply rva output by gain then >> PBITS
+
+ gain = (int)((1.0 / fgain) * PMAX);
+
+ gain = gain * 4; // compensate for fact that gain calculation is for +/- 32767 amplitude wavs
+ // ie: ok to allow a bit more gain because most wavs are not at theoretical peak amplitude at all times
+
+ gain = min (gain, PMAX); // cap at PMAX
+
+ gain = ((float)b/(float)PMAX) * gain; // scale final gain by pdly->b.
+
+ pdly->b = (int)gain;
+}
+
+// allocate a new delay line
+// D number of samples to delay
+// a feedback value (0-PMAX normalized to 0.0-1.0)
+// b gain value (0-PMAX normalized to 0.0-1.0)
+// if DLY_LOWPASS:
+// L - numerator order of filter
+// M - denominator order of filter
+// fb - numerator params, M+1
+// fa - denominator params, L+1
+
+dly_t * DLY_AllocLP( int D, int a, int b, int type, int M, int L, int *fa, int *fb )
+{
+ HANDLE h;
+ int cb;
+ int *w;
+ int i;
+ dly_t *pdly = NULL;
+
+ // find open slot
+ for (i = 0; i < CDLYS; i++)
+ {
+ if (!dlys[i].fused)
+ {
+ pdly = &dlys[i];
+ DLY_Init( pdly );
+ break;
+ }
+ }
+
+ if ( i == CDLYS )
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate delay line.\n" );
+ return NULL; // all delay lines in use
+ }
+
+ cb = (D + 1) * sizeof ( int ); // assume all samples are signed integers
+
+ if( type == DLY_LOWPASS )
+ {
+ // alloc lowpass fir_filter
+
+ pdly->pflt = FLT_Alloc( M, L, fa, fb );
+ if ( !pdly->pflt )
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate filter for delay line.\n" );
+ return NULL;
+ }
+ }
+
+ // alloc delay memory
+
+ h = GlobalAlloc( GMEM_MOVEABLE|GMEM_SHARE, cb );
+ if( !h )
+ {
+ MsgDev( D_ERROR, "Sound DSP: Out of memory.\n" );
+ FLT_Free( pdly->pflt );
+ return NULL;
+ }
+
+ // lock delay memory
+ w = (int *)GlobalLock( h );
+
+ if( !w )
+ {
+ MsgDev( D_ERROR, "Sound DSP: Failed to lock.\n" );
+ GlobalFree( h );
+ FLT_Free ( pdly->pflt );
+ return NULL;
+ }
+
+ // clear delay array
+
+ Mem_Set( w, 0, cb );
+
+ // init values
+
+ pdly->type = type;
+ pdly->D = D;
+ pdly->t = D; // set delay tap to full delay
+ pdly->D0 = D;
+ pdly->p = w; // init circular pointer to head of buffer
+ pdly->w = w;
+ pdly->h = h;
+ pdly->a = min( a, PMAX ); // do not allow 100% feedback
+ pdly->b = b;
+ pdly->fused = true;
+
+ if ( type == DLY_LINEAR )
+ {
+ // linear delay has no feedback and unity gain
+
+ pdly->a = 0;
+ pdly->b = PMAX;
+ }
+ else
+ {
+ // adjust b to compensate for feedback gain
+
+ DLY_SetNormalizingGain( pdly );
+ }
+
+ return (pdly);
+}
+
+// allocate lowpass or allpass delay
+
+dly_t * DLY_Alloc( int D, int a, int b, int type )
+{
+ return DLY_AllocLP( D, a, b, type, 0, 0, 0, 0 );
+}
+
+
+// Allocate new delay, convert from float params in prc preset to internal parameters
+// Uses filter params in prc if delay is type lowpass
+
+// delay parameter order
+
+typedef enum
+{
+ dly_idtype, // NOTE: first 8 params must match those in mdy_e
+ dly_idelay,
+ dly_ifeedback,
+ dly_igain,
+ dly_iftype,
+ dly_icutoff,
+ dly_iqwidth,
+ dly_iquality,
+ dly_cparam
+} dly_e;
+
+
+// delay parameter ranges
+prm_rng_t dly_rng[] = {
+
+ {dly_cparam, 0, 0}, // first entry is # of parameters
+
+ // delay params
+
+ {dly_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
+ {dly_idelay, 0.0, 1000.0}, // delay in milliseconds
+ {dly_ifeedback, 0.0, 0.99}, // feedback 0-1.0
+ {dly_igain, 0.0, 1.0}, // final gain of output stage, 0-1.0
+
+ // filter params if dly type DLY_LOWPASS
+
+ {dly_iftype, 0, FTR_MAX},
+ {dly_icutoff, 10.0, SOUND_22k},
+ {dly_iqwidth, 100.0, SOUND_11k},
+ {dly_iquality, 0, QUA_MAX},
+};
+
+dly_t * DLY_Params ( prc_t *pprc )
+{
+ dly_t *pdly = NULL;
+ int D, a, b;
+
+ float delay = pprc->prm[dly_idelay];
+ float feedback = pprc->prm[dly_ifeedback];
+ float gain = pprc->prm[dly_igain];
+ int type = pprc->prm[dly_idtype];
+
+ float ftype = pprc->prm[dly_iftype];
+ float cutoff = pprc->prm[dly_icutoff];
+ float qwidth = pprc->prm[dly_iqwidth];
+ float qual = pprc->prm[dly_iquality];
+
+ D = MSEC_TO_SAMPS(delay); // delay samples
+ a = feedback * PMAX; // feedback
+ b = gain * PMAX; // gain
+
+ switch ( type )
+ {
+ case DLY_PLAIN:
+ case DLY_ALLPASS:
+ case DLY_LINEAR:
+ pdly = DLY_Alloc( D, a, b, type );
+ break;
+
+ case DLY_LOWPASS:
+ {
+ // set up dummy lowpass filter to convert params
+
+ prc_t prcf;
+ flt_t *pflt;
+
+ prcf.prm[flt_iquality] = qual; // 0,1,2 - high, medium, low (low quality implies faster execution time)
+ prcf.prm[flt_icutoff] = cutoff;
+ prcf.prm[flt_iftype] = ftype;
+ prcf.prm[flt_iqwidth] = qwidth;
+
+ pflt = (flt_t *)FLT_Params ( &prcf );
+
+ if ( !pflt )
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate filter.\n" );
+ return NULL;
+ }
+
+ pdly = DLY_AllocLP ( D, a, b, type, pflt->M, pflt->L, pflt->a, pflt->b );
+
+ FLT_Free ( pflt );
+ break;
+ }
+ }
+
+ return pdly;
+}
+
+_inline void * DLY_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, dly_rng );
+ return (void *) DLY_Params ((prc_t *)p);
+}
+
+// get next value from delay line, move x into delay line
+
+int DLY_GetNext ( dly_t *pdly, int x )
+{
+ switch (pdly->type)
+ {
+ default:
+ case DLY_PLAIN:
+ return dly_plain( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_ALLPASS:
+ return dly_allpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_LOWPASS:
+ return dly_lowpass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->M, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ case DLY_LINEAR:
+ return dly_linear( pdly->D, pdly->t, pdly->w, &pdly->p, x );
+ }
+}
+
+// batch version for performance
+void DLY_GetNextN( dly_t *pdly, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = DLY_GetNext( pdly, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = DLY_GetNext( pdly, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = DLY_GetNext( pdly, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// get tap on t'th sample in delay - don't update buffer pointers, this is done via DLY_GetNext
+
+_inline int DLY_GetTap ( dly_t *pdly, int t )
+{
+ return tap (pdly->D, pdly->w, pdly->p, t );
+}
+
+
+// make instantaneous change to new delay value D.
+// t tap value must be <= original D (ie: we don't do any reallocation here)
+
+void DLY_ChangeVal ( dly_t *pdly, int t )
+{
+ // never set delay > original delay
+
+ pdly->t = min ( t, pdly->D0 );
+}
+
+// ignored - use MDY_ for modulatable delay
+
+_inline void DLY_Mod ( void *p, float v ) { return; }
+
+
+///////////////////
+// Parallel reverbs
+///////////////////
+
+// Reverb A
+// M parallel reverbs, mixed to mono output
+
+#define CRVAS 64 // max number of parallel series reverbs active
+
+#define CRVA_DLYS 12 // max number of delays making up reverb_a
+
+typedef struct rva_s
+{
+ bool fused;
+ int m; // number of parallel plain or lowpass delays
+ int fparallel; // true if filters in parallel with delays, otherwise single output filter
+ flt_t *pflt;
+
+ dly_t *pdlys[CRVA_DLYS]; // array of pointers to delays
+} rva_t;
+
+rva_t rvas[CRVAS];
+
+void RVA_Init ( rva_t *prva ) { if ( prva ) Mem_Set (prva, 0, sizeof (rva_t)); }
+void RVA_InitAll( void ) { int i; for ( i = 0; i < CRVAS; i++) RVA_Init ( &rvas[i] ); }
+
+// free parallel series reverb
+
+void RVA_Free( rva_t *prva )
+{
+ if ( prva )
+ {
+ int i;
+
+ // free all delays
+ for ( i = 0; i < CRVA_DLYS; i++)
+ DLY_Free ( prva->pdlys[i] );
+
+ FLT_Free( prva->pflt );
+
+ Mem_Set( prva, 0, sizeof (rva_t) );
+ }
+}
+
+
+void RVA_FreeAll( void ) { int i; for ( i = 0; i < CRVAS; i++) RVA_Free( &rvas[i] ); }
+
+// create parallel reverb - m parallel reverbs summed
+
+// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+// a array of reverb feedback parms for parallel reverbs (CRVB_P_DLYS)
+// b array of CRVB_P_DLYS - mix params for parallel reverbs
+// m - number of parallel delays
+// pflt - filter template, to be used by all parallel delays
+// fparallel - true if filter operates in parallel with delays, otherwise filter output only
+rva_t * RVA_Alloc ( int *D, int *a, int *b, int m, flt_t *pflt, int fparallel )
+{
+
+ int i;
+ rva_t *prva;
+ flt_t *pflt2 = NULL;
+
+ // find open slot
+
+ for ( i = 0; i < CRVAS; i++ )
+ {
+ if ( !rvas[i].fused )
+ break;
+ }
+
+ // return null if no free slots
+
+ if (i == CRVAS)
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate reverb.\n" );
+ return NULL;
+ }
+
+ prva = &rvas[i];
+
+ // if series filter specified, alloc
+
+ if ( pflt && !fparallel)
+ {
+ // use filter data as template for a filter on output
+
+ pflt2 = FLT_Alloc (pflt->M, pflt->L, pflt->a, pflt->b );
+
+ if (!pflt2)
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate flt for reverb.\n" );
+ return NULL;
+ }
+ }
+
+ // alloc parallel reverbs
+
+ if ( pflt && fparallel )
+ {
+
+ // use this filter data as a template to alloc a filter for each parallel delay
+
+ for (i = 0; i < m; i++)
+ prva->pdlys[i] = DLY_AllocLP( D[i], a[i], b[i], DLY_LOWPASS, pflt->M, pflt->L, pflt->a, pflt->b );
+ }
+ else
+ {
+ // no filter specified, use plain delays in parallel sections
+
+ for (i = 0; i < m; i++)
+ prva->pdlys[i] = DLY_Alloc( D[i], a[i], b[i], DLY_PLAIN );
+ }
+
+
+ // if we failed to alloc any reverb, free all, return NULL
+
+ for (i = 0; i < m; i++)
+ {
+ if ( !prva->pdlys[i])
+ {
+ FLT_Free( pflt2 );
+ RVA_Free( prva );
+ MsgDev( D_WARN, "DSP: failed to allocate delay for reverb.\n" );
+ return NULL;
+ }
+ }
+
+ prva->fused = true;
+ prva->m = m;
+ prva->fparallel = fparallel;
+ prva->pflt = pflt2;
+
+ return prva;
+}
+
+
+// parallel reverberator
+//
+// for each input sample x do:
+// x0 = plain(D0,w0,&p0,a0,x)
+// x1 = plain(D1,w1,&p1,a1,x)
+// x2 = plain(D2,w2,&p2,a2,x)
+// x3 = plain(D3,w3,&p3,a3,x)
+// y = b0*x0 + b1*x1 + b2*x2 + b3*x3
+//
+// rgdly - array of 6 delays:
+// D - Delay values (typical - 29, 37, 44, 50, 27, 31)
+// w - array of delayed values
+// p - array of pointers to circular delay line pointers
+// a - array of 6 feedback values (typical - all equal, like 0.75 * PMAX)
+// b - array of 6 gain values for plain reverb outputs (1, .9, .8, .7)
+// xin - input value
+// if fparallel, filters are built into delays,
+// otherwise, filter output
+
+_inline int RVA_GetNext( rva_t *prva, int x )
+{
+ int m = prva->m;
+ int i, y, sum;
+
+ sum = 0;
+
+ for ( i = 0; i < m; i++ )
+ sum += DLY_GetNext( prva->pdlys[i], x );
+
+ // m is clamped between RVA_BASEM & CRVA_DLYS
+
+ if (m)
+ y = sum/m;
+ else
+ y = x;
+#if 0
+ // PERFORMANCE:
+ // UNDONE: build as array
+ int mm;
+
+ switch (m)
+ {
+ case 12: mm = (PMAX/12); break;
+ case 11: mm = (PMAX/11); break;
+ case 10: mm = (PMAX/10); break;
+ case 9: mm = (PMAX/9); break;
+ case 8: mm = (PMAX/8); break;
+ case 7: mm = (PMAX/7); break;
+ case 6: mm = (PMAX/6); break;
+ case 5: mm = (PMAX/5); break;
+ case 4: mm = (PMAX/4); break;
+ case 3: mm = (PMAX/3); break;
+ case 2: mm = (PMAX/2); break;
+ default:
+ case 1: mm = (PMAX/1); break;
+ }
+
+ y = (sum * mm) >> PBITS;
+
+#endif // 0
+
+ // run series filter if present
+
+ if ( prva->pflt && !prva->fparallel )
+ y = FLT_GetNext( prva->pflt, y);
+
+ return y;
+}
+
+// batch version for performance
+
+_inline void RVA_GetNextN( rva_t *prva, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = RVA_GetNext( prva, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = RVA_GetNext( prva, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = RVA_GetNext( prva, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+#define RVA_BASEM 3 // base number of parallel delays
+
+// nominal delay and feedback values
+
+//float rvadlys[] = {29, 37, 44, 50, 62, 75, 96, 118, 127, 143, 164, 175};
+float rvadlys[] = {18, 23, 28, 36, 47, 21, 26, 33, 40, 49, 45, 38};
+float rvafbs[] = {0.7, 0.7, 0.7, 0.8, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9};
+
+// reverb parameter order
+
+typedef enum
+{
+
+// parameter order
+
+ rva_isize,
+ rva_idensity,
+ rva_idecay,
+
+ rva_iftype,
+ rva_icutoff,
+ rva_iqwidth,
+
+ rva_ifparallel,
+
+ rva_cparam // # of params
+} rva_e;
+
+// filter parameter ranges
+
+prm_rng_t rva_rng[] = {
+
+ {rva_cparam, 0, 0}, // first entry is # of parameters
+
+ // reverb params
+
+ {rva_isize, 0.0, 2.0}, // 0-2.0 scales nominal delay parameters (starting at approx 20ms)
+ {rva_idensity, 0.0, 2.0}, // 0-2.0 density of reverbs (room shape) - controls # of parallel or series delays
+ {rva_idecay, 0.0, 2.0}, // 0-2.0 scales feedback parameters (starting at approx 0.15)
+
+ // filter params for each parallel reverb (quality set to 0 for max execution speed)
+
+ {rva_iftype, 0, FTR_MAX},
+ {rva_icutoff, 10, SOUND_22k},
+ {rva_iqwidth, 100, SOUND_11k},
+
+ {rva_ifparallel, 0, 1} // if 1, then all filters operate in parallel with delays. otherwise filter output only
+};
+
+rva_t * RVA_Params ( prc_t *pprc )
+{
+ rva_t *prva;
+ int i;
+ float size = pprc->prm[rva_isize]; // 0-2.0 controls scale of delay parameters
+ float density = pprc->prm[rva_idensity]; // 0-2.0 density of reverbs (room shape) - controls # of parallel delays
+ float decay = pprc->prm[rva_idecay]; // 0-1.0 controls feedback parameters
+
+ float ftype = pprc->prm[rva_iftype];
+ float cutoff = pprc->prm[rva_icutoff];
+ float qwidth = pprc->prm[rva_iqwidth];
+
+ float fparallel = pprc->prm[rva_ifparallel];
+ flt_t *pflt;
+
+ // D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+ // a array of reverb feedback parms for parallel delays
+ // b array of CRVB_P_DLYS - mix params for parallel reverbs
+ // m - number of parallel delays
+
+ int D[CRVA_DLYS];
+ int a[CRVA_DLYS];
+ int b[CRVA_DLYS];
+ int m = RVA_BASEM;
+
+ m = density * CRVA_DLYS / 2;
+
+ // limit # delays 3-12
+
+ m = clamp (m, RVA_BASEM, CRVA_DLYS);
+
+
+ // average time sound takes to travel from most distant wall
+ // (cap at 1000 ft room)
+
+ for ( i = 0; i < m; i++ )
+ {
+ // delays of parallel reverb
+
+ D[i] = MSEC_TO_SAMPS( rvadlys[i] * size );
+
+ // feedback and gain of parallel reverb
+
+ a[i] = (int) min (0.9 * PMAX, rvafbs[i] * (float)PMAX * decay);
+ b[i] = PMAX;
+ }
+
+ // add filter
+ pflt = NULL;
+
+ if ( cutoff )
+ {
+
+ // set up dummy lowpass filter to convert params
+
+ prc_t prcf;
+
+ prcf.prm[flt_iquality] = QUA_LO; // force filter to low quality for faster execution time
+ prcf.prm[flt_icutoff] = cutoff;
+ prcf.prm[flt_iftype] = ftype;
+ prcf.prm[flt_iqwidth] = qwidth;
+
+ pflt = (flt_t *)FLT_Params ( &prcf );
+ }
+
+ prva = RVA_Alloc ( D, a, b, m, pflt, fparallel );
+
+ FLT_Free( pflt );
+
+ return prva;
+}
+
+_inline void * RVA_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, rva_rng );
+ return (void *) RVA_Params ((prc_t *)p);
+}
+
+_inline void RVA_Mod ( void *p, float v ) { return; }
+
+
+
+////////////
+// Diffusor
+///////////
+
+// (N series allpass reverbs)
+
+#define CDFRS 64 // max number of series reverbs active
+
+#define CDFR_DLYS 16 // max number of delays making up diffusor
+
+typedef struct dfr_s
+{
+ bool fused;
+ int n; // series allpass delays
+ int w[CDFR_DLYS]; // internal state array for series allpass filters
+
+ dly_t *pdlys[CDFR_DLYS]; // array of pointers to delays
+} dfr_t;
+
+dfr_t dfrs[CDFRS];
+
+void DFR_Init ( dfr_t *pdfr ) { if ( pdfr ) Mem_Set (pdfr, 0, sizeof (dfr_t)); }
+void DFR_InitAll( void ) { int i; for ( i = 0; i < CDFRS; i++) DFR_Init ( &dfrs[i] ); }
+
+// free parallel series reverb
+
+void DFR_Free( dfr_t *pdfr )
+{
+ if ( pdfr )
+ {
+ // free all delays
+ int i;
+
+ for ( i = 0; i < CDFR_DLYS; i++)
+ DLY_Free ( pdfr->pdlys[i] );
+
+ Mem_Set( pdfr, 0, sizeof (dfr_t) );
+ }
+}
+
+
+void DFR_FreeAll( void ) { int i; for ( i = 0; i < CDFRS; i++) DFR_Free( &dfrs[i] ); }
+
+// create n series allpass reverbs
+
+// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+// a array of reverb feedback parms for series delays
+// b array of gain params for parallel reverbs
+// n - number of series delays
+
+dfr_t * DFR_Alloc ( int *D, int *a, int *b, int n )
+{
+
+ int i;
+ dfr_t *pdfr;
+
+ // find open slot
+
+ for (i = 0; i < CDFRS; i++)
+ {
+ if (!dfrs[i].fused)
+ break;
+ }
+
+ // return null if no free slots
+
+ if (i == CDFRS)
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate diffusor.\n" );
+ return NULL;
+ }
+
+ pdfr = &dfrs[i];
+
+ DFR_Init( pdfr );
+
+ // alloc reverbs
+
+ for (i = 0; i < n; i++)
+ pdfr->pdlys[i] = DLY_Alloc( D[i], a[i], b[i], DLY_ALLPASS );
+
+ // if we failed to alloc any reverb, free all, return NULL
+
+ for (i = 0; i < n; i++)
+ {
+ if ( !pdfr->pdlys[i])
+ {
+ DFR_Free( pdfr );
+ MsgDev( D_WARN, "DSP: failed to allocate delay for diffusor.\n" );
+ return NULL;
+ }
+ }
+
+ pdfr->fused = true;
+ pdfr->n = n;
+
+ return pdfr;
+}
+
+
+// series reverberator
+
+_inline int DFR_GetNext( dfr_t *pdfr, int x )
+{
+ int i;
+ int n = pdfr->n;
+ int y;
+
+ y = x;
+ for (i = 0; i < n; i++)
+ y = DLY_GetNext(pdfr->pdlys[i], y);
+ return y;
+
+#if 0
+ // alternate method, using internal state - causes PREDELAY = sum of delay times
+
+ int *v = pdfr->w; // intermediate results
+
+ v[0] = x;
+
+ // reverse evaluate series delays
+
+ // w[0] w[1] w[2] w[n-1] w[n]
+ // x---->D[0]--->D[1]--->D[2]...-->D[n-1]--->out
+ //
+
+ for (i = n; i > 0; i--)
+ v[i] = DLY_GetNext(pdfr->pdlys[i-1], v[i-1]);
+
+ return v[n];
+#endif
+}
+
+// batch version for performance
+
+_inline void DFR_GetNextN( dfr_t *pdfr, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = DFR_GetNext( pdfr, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = DFR_GetNext( pdfr, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = DFR_GetNext( pdfr, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+#define DFR_BASEN 2 // base number of series allpass delays
+
+// nominal diffusor delay and feedback values
+
+//float dfrdlys[] = {20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95};
+float dfrdlys[] = {13, 19, 26, 21, 32, 36, 38, 16, 24, 28, 41, 35, 10, 46, 50, 27};
+float dfrfbs[] = {0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15};
+
+
+// diffusor parameter order
+
+typedef enum
+{
+
+// parameter order
+
+ dfr_isize,
+ dfr_idensity,
+ dfr_idecay,
+
+ dfr_cparam // # of params
+
+} dfr_e;
+
+// diffusor parameter ranges
+
+prm_rng_t dfr_rng[] = {
+
+ {dfr_cparam, 0, 0}, // first entry is # of parameters
+
+ {dfr_isize, 0.0, 1.0}, // 0-1.0 scales all delays
+ {dfr_idensity, 0.0, 1.0}, // 0-1.0 controls # of series delays
+ {dfr_idecay, 0.0, 1.0}, // 0-1.0 scales all feedback parameters
+};
+
+
+dfr_t * DFR_Params ( prc_t *pprc )
+{
+ dfr_t *pdfr;
+ int i;
+ int s;
+ float size = pprc->prm[dfr_isize]; // 0-1.0 scales all delays
+ float density = pprc->prm[dfr_idensity]; // 0-1.0 controls # of series delays
+ float diffusion = pprc->prm[dfr_idecay]; // 0-1.0 scales all feedback parameters
+
+ // D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+ // a array of reverb feedback parms for series delays (CRVB_S_DLYS)
+ // b gain of each reverb section
+ // n - number of series delays
+
+ int D[CDFR_DLYS];
+ int a[CDFR_DLYS];
+ int b[CDFR_DLYS];
+ int n = DFR_BASEN;
+
+ // increase # of series diffusors with increased density
+
+ n += density * 2;
+
+ // limit m, n to half max number of delays
+
+ n = min (CDFR_DLYS/2, n);
+
+ // compute delays for diffusors
+
+ for (i = 0; i < n; i++)
+ {
+ s = (int)( dfrdlys[i] * size );
+
+ // delay of diffusor
+
+ D[i] = MSEC_TO_SAMPS(s);
+
+ // feedback and gain of diffusor
+
+ a[i] = min (0.9 * PMAX, dfrfbs[i] * PMAX * diffusion);
+ b[i] = PMAX;
+ }
+
+
+ pdfr = DFR_Alloc ( D, a, b, n );
+
+ return pdfr;
+}
+
+_inline void * DFR_VParams ( void *p )
+{
+ PRC_CheckParams ((prc_t *)p, dfr_rng);
+ return (void *) DFR_Params ((prc_t *)p);
+}
+
+_inline void DFR_Mod ( void *p, float v ) { return; }
+
+
+//////////////////////
+// LFO wav definitions
+//////////////////////
+
+#define CLFOSAMPS 512 // samples per wav table - single cycle only
+#define LFOBITS 14 // bits of peak amplitude of lfo wav
+#define LFOAMP ((1<pdly );
+
+ Mem_Set( plw, 0, sizeof (lfowav_t) );
+}
+
+// deallocate all lfo wave tables. Called only when sound engine exits.
+
+void LFOWAV_FreeAll( void )
+{
+ int i;
+ for ( i = 0; i < CLFOWAV; i++ )
+ LFOWAV_Free( &lfowavs[i] );
+}
+
+// fill lfo array w with count samples of lfo type 'type'
+// all lfo wavs except fade out, rnd, and log_out should start with 0 output
+
+void LFOWAV_Fill( int *w, int count, int type )
+{
+ int i,x;
+ switch (type)
+ {
+ default:
+ case LFO_SIN: // sine wav, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++ )
+ {
+ x = ( int )( (float)(LFOAMP) * com.sin( (2.0 * M_PI * (float)i / (float)count ) + (M_PI * 1.5) ) );
+ w[i] = (x + LFOAMP)/2;
+ }
+ break;
+ case LFO_TRI: // triangle wav, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ {
+ w[i] = ( int ) ( (float)(2 * LFOAMP * i ) / (float)(count) );
+
+ if ( i > count / 2 )
+ w[i] = ( int ) ( (float) (2 * LFOAMP) - (float)( 2 * LFOAMP * i ) / (float)(count) );
+ }
+ break;
+ case LFO_SQR: // square wave, 50% duty cycle, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = i > count / 2 ? 0 : LFOAMP;
+ break;
+ case LFO_SAW: // forward saw wav, aall values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
+ break;
+ case LFO_RND: // random wav, all values 0 <= x <= LFOAMP
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( Com_RandomLong(0, LFOAMP) );
+ break;
+ case LFO_LOG_IN: // logarithmic fade in, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * pow( (float)i / (float)count, 2));
+ break;
+ case LFO_LOG_OUT: // logarithmic fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * pow( 1.0 - ((float)i / (float)count), 2 ));
+ break;
+ case LFO_LIN_IN: // linear fade in, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
+ break;
+ case LFO_LIN_OUT: // linear fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP
+ for (i = 0; i < count; i++)
+ w[i] = LFOAMP - ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
+ break;
+ }
+}
+
+// allocate all lfo wave tables. Called only when sound engine loads.
+
+void LFOWAV_InitAll()
+{
+ int i;
+ dly_t *pdly;
+
+ Mem_Set( lfowavs, 0, sizeof( lfowavs ) );
+
+ // alloc space for each lfo wav type
+
+ for (i = 0; i < CLFOWAV; i++)
+ {
+ pdly = DLY_Alloc( CLFOSAMPS, 0, 0 , DLY_PLAIN);
+
+ lfowavs[i].pdly = pdly;
+ lfowavs[i].type = i;
+
+ LFOWAV_Fill( pdly->w, CLFOSAMPS, i );
+ }
+
+ // if any dlys fail to alloc, free all
+
+ for (i = 0; i < CLFOWAV; i++)
+ {
+ if ( !lfowavs[i].pdly )
+ LFOWAV_FreeAll();
+ }
+}
+
+
+////////////////////////////////////////
+// LFO iterators - one shot and looping
+////////////////////////////////////////
+
+#define CLFO 16 // max active lfos (this steals from active delays)
+
+typedef struct lfo_s
+{
+ bool fused; // true if slot take
+ dly_t *pdly; // delay points to lfo wav within lfowav_t (don't free this)
+ float f; // playback frequency in hz
+ pos_t pos; // current position within wav table, looping
+ pos_one_t pos1; // current position within wav table, one shot
+ int foneshot; // true - one shot only, don't repeat
+} lfo_t;
+
+lfo_t lfos[CLFO];
+
+void LFO_Init( lfo_t *plfo ) { if ( plfo ) Mem_Set( plfo, 0, sizeof (lfo_t) ); }
+void LFO_InitAll( void ) { int i; for ( i = 0; i < CLFO; i++) LFO_Init(&lfos[i]); }
+void LFO_Free( lfo_t *plfo ) { if ( plfo ) Mem_Set( plfo, 0, sizeof (lfo_t) ); }
+void LFO_FreeAll( void ) { int i; for ( i = 0; i < CLFO; i++) LFO_Free(&lfos[i]); }
+
+
+// get step value given desired playback frequency
+
+_inline float LFO_HzToStep ( float freqHz )
+{
+ float lfoHz;
+
+ // calculate integer and fractional step values,
+ // assume an update rate of SOUND_DMA_SPEED samples/sec
+
+ // 1 cycle/CLFOSAMPS * SOUND_DMA_SPEED samps/sec = cycles/sec = current lfo rate
+ //
+ // lforate * X = freqHz so X = freqHz/lforate = update rate
+
+ lfoHz = (float)(SOUND_DMA_SPEED) / (float)(CLFOSAMPS);
+
+ return freqHz / lfoHz;
+}
+
+// return pointer to new lfo
+
+lfo_t * LFO_Alloc( int wtype, float freqHz, bool foneshot )
+{
+ int i, type = min ( CLFOWAV - 1, wtype );
+ float lfostep;
+
+ for( i = 0; i < CLFO; i++ )
+ {
+ if (!lfos[i].fused)
+ {
+ lfo_t *plfo = &lfos[i];
+
+ LFO_Init( plfo );
+
+ plfo->fused = true;
+ plfo->pdly = lfowavs[type].pdly; // pdly in lfo points to wav table data in lfowavs
+ plfo->f = freqHz;
+ plfo->foneshot = foneshot;
+
+ lfostep = LFO_HzToStep( freqHz );
+
+ // init positional pointer (ie: fixed point updater for controlling pitch of lfo)
+
+ if ( !foneshot )
+ POS_Init(&(plfo->pos), plfo->pdly->D, lfostep );
+ else
+ POS_ONE_Init(&(plfo->pos1), plfo->pdly->D,lfostep );
+
+ return plfo;
+ }
+ MsgDev( D_WARN, "DSP: failed to allocate LFO.\n" );
+ return NULL;
+ }
+ return NULL;
+}
+
+// get next lfo value
+// Value returned is 0..LFOAMP. can be normalized by shifting right by LFOBITS
+// To play back at correct passed in frequency, routien should be
+// called once for every output sample (ie: at SOUND_DMA_SPEED)
+// x is dummy param
+
+_inline int LFO_GetNext( lfo_t *plfo, int x )
+{
+ int i;
+
+ // get current position
+
+ if ( !plfo->foneshot )
+ i = POS_GetNext( &plfo->pos );
+ else
+ i = POS_ONE_GetNext( &plfo->pos1 );
+
+ // return current sample
+
+ return plfo->pdly->w[i];
+}
+
+// batch version for performance
+
+_inline void LFO_GetNextN( lfo_t *plfo, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = LFO_GetNext( plfo, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = LFO_GetNext( plfo, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = LFO_GetNext( plfo, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// uses lfowav, rate, foneshot
+
+typedef enum
+{
+ // parameter order
+ lfo_iwav,
+ lfo_irate,
+ lfo_ifoneshot,
+ lfo_cparam // # of params
+
+} lfo_e;
+
+// parameter ranges
+
+prm_rng_t lfo_rng[] = {
+
+ {lfo_cparam, 0, 0}, // first entry is # of parameters
+
+ {lfo_iwav, 0.0, LFO_MAX}, // lfo type to use (LFO_SIN, LFO_RND...)
+ {lfo_irate, 0.0, 16000.0}, // modulation rate in hz. for MDY, 1/rate = 'glide' time in seconds
+ {lfo_ifoneshot, 0.0, 1.0}, // 1.0 if lfo is oneshot
+};
+
+
+lfo_t * LFO_Params ( prc_t *pprc )
+{
+ lfo_t *plfo;
+ bool foneshot = pprc->prm[lfo_ifoneshot] > 0 ? true : false;
+
+ plfo = LFO_Alloc ( pprc->prm[lfo_iwav], pprc->prm[lfo_irate], foneshot );
+
+ return plfo;
+}
+
+void LFO_ChangeVal ( lfo_t *plfo, float fhz )
+{
+ float fstep = LFO_HzToStep( fhz );
+
+ // change lfo playback rate to new frequency fhz
+
+ if ( plfo->foneshot )
+ POS_ChangeVal( &plfo->pos, fstep );
+ else
+ POS_ChangeVal( &plfo->pos1.p, fstep );
+}
+
+_inline void * LFO_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, lfo_rng );
+ return (void *) LFO_Params ((prc_t *)p);
+}
+
+// v is +/- 0-1.0
+// v changes current lfo frequency up/down by +/- v%
+
+_inline void LFO_Mod ( lfo_t *plfo, float v )
+{
+ float fhz;
+ float fhznew;
+
+ fhz = plfo->f;
+ fhznew = fhz * (1.0 + v);
+
+ LFO_ChangeVal ( plfo, fhznew );
+
+ return;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Ramp - used for varying smoothly between int parameters ie: modulation delays
+/////////////////////////////////////////////////////////////////////////////
+typedef struct rmp_s
+{
+ int initval; // initial ramp value
+ int target; // final ramp value
+ int sign; // increasing (1) or decreasing (-1) ramp
+ int yprev; // previous output value
+ bool fhitend; // true if hit end of ramp
+
+ pos_one_t ps; // current ramp output
+} rmp_t;
+
+// ramp smoothly between initial value and target value in approx 'ramptime' seconds.
+// (initial value may be greater or less than target value)
+// never changes output by more than +1 or -1 (which can cause the ramp to take longer to complete than ramptime)
+// called once per sample while ramping
+// ramptime - duration of ramp in seconds
+// initval - initial ramp value
+// targetval - target ramp value
+
+void RMP_Init( rmp_t *prmp, float ramptime, int initval, int targetval )
+{
+ int rise;
+ int run;
+
+ if (prmp)
+ Mem_Set( prmp, 0, sizeof (rmp_t) );
+
+
+ run = (int) (ramptime * SOUND_DMA_SPEED); // 'samples' in ramp
+ rise = (targetval - initval); // height of ramp
+
+ // init fixed point iterator to iterate along the height of the ramp 'rise'
+ // always iterates from 0..'rise', increasing in value
+
+ POS_ONE_Init( &prmp->ps, ABS( rise ), ABS((float) rise) / ((float) run) );
+
+ prmp->yprev = initval;
+ prmp->initval = initval;
+ prmp->target = targetval;
+ prmp->sign = SIGN( rise );
+
+}
+
+// continues from current position to new target position
+
+void RMP_SetNext( rmp_t *prmp, float ramptime, int targetval )
+{
+ RMP_Init ( prmp, ramptime, prmp->yprev, targetval );
+}
+
+_inline bool RMP_HitEnd ( rmp_t *prmp )
+{
+ return prmp->fhitend;
+}
+
+_inline void RMP_SetEnd ( rmp_t *prmp )
+{
+ prmp->fhitend = true;
+}
+
+// get next ramp value & update ramp, never varies by more than +1 or -1 between calls
+// when ramp hits target value, it thereafter always returns last value
+
+_inline int RMP_GetNext( rmp_t *prmp )
+{
+ int y;
+ int d;
+
+ // if we hit ramp end, return last value
+
+ if (prmp->fhitend)
+ return prmp->yprev;
+
+ // get next integer position in ramp height.
+
+ d = POS_ONE_GetNext( &prmp->ps );
+
+ if ( prmp->ps.fhitend )
+ prmp->fhitend = true;
+
+ // increase or decrease from initval, depending on ramp sign
+
+ if ( prmp->sign > 0 )
+ y = prmp->initval + d;
+ else
+ y = prmp->initval - d;
+
+ // only update current height by a max of +1 or -1
+ // this means that for short ramp times, we may not hit target
+
+ if ( ABS( y - prmp->yprev ) >= 1 )
+ prmp->yprev += prmp->sign;
+
+ return prmp->yprev;
+}
+
+// get current ramp value, don't update ramp
+
+_inline int RMP_GetCurrent( rmp_t *prmp )
+{
+ return prmp->yprev;
+}
+
+////////////////////////////////////////
+// Time Compress/expand with pitch shift
+////////////////////////////////////////
+
+// realtime pitch shift - ie: pitch shift without change to playback rate
+
+#define CPTCS 64
+
+typedef struct ptc_s
+{
+ bool fused;
+
+ dly_t *pdly_in; // input buffer space
+ dly_t *pdly_out; // output buffer space
+
+ int *pin; // input buffer (pdly_in->w)
+ int *pout; // output buffer (pdly_out->w)
+
+ int cin; // # samples in input buffer
+ int cout; // # samples in output buffer
+
+ int cxfade; // # samples in crossfade segment
+ int ccut; // # samples to cut
+ int cduplicate; // # samples to duplicate (redundant - same as ccut)
+
+ int iin; // current index into input buffer (reading)
+
+ pos_one_t psn; // stepping index through output buffer
+
+ bool fdup; // true if duplicating, false if cutting
+
+ float fstep; // pitch shift & time compress/expand
+} ptc_t;
+
+ptc_t ptcs[CPTCS];
+
+void PTC_Init( ptc_t *pptc ) { if (pptc) Mem_Set( pptc, 0, sizeof (ptc_t) ); };
+void PTC_Free( ptc_t *pptc )
+{
+ if (pptc)
+ {
+ DLY_Free (pptc->pdly_in);
+ DLY_Free (pptc->pdly_out);
+
+ Mem_Set( pptc, 0, sizeof (ptc_t) );
+ }
+};
+
+void PTC_InitAll() { int i; for ( i = 0; i < CPTCS; i++) PTC_Init( &ptcs[i] ); };
+void PTC_FreeAll() { int i; for ( i = 0; i < CPTCS; i++) PTC_Free( &ptcs[i] ); };
+
+// Time compressor/expander with pitch shift (ie: pitch changes, playback rate does not)
+//
+// Algorithm:
+
+// 1) Duplicate or discard chunks of sound to provide tslice * fstep seconds of sound.
+// (The user-selectable size of the buffer to process is tslice milliseconds in length)
+// 2) Resample this compressed/expanded buffer at fstep to produce a pitch shifted
+// output with the same duration as the input (ie: #samples out = # samples in, an
+// obvious requirement for realtime _inline processing).
+
+// timeslice is size in milliseconds of full buffer to process.
+// timeslice * fstep is the size of the expanded/compressed buffer
+// timexfade is length in milliseconds of crossfade region between duplicated or cut sections
+// fstep is % expanded/compressed sound normalized to 0.01-2.0 (1% - 200%)
+
+// input buffer:
+
+// iin-->
+
+// [0... tslice ...D] input samples 0...D (D is NEWEST sample)
+// [0... ...n][m... tseg ...D] region to be cut or duplicated m...D
+
+// [0... [p..txf1..n][m... tseg ...D] fade in region 1 txf1 p...n
+// [0... ...n][m..[q..txf2..D] fade out region 2 txf2 q...D
+
+
+// pitch up: duplicate into output buffer: tdup = tseg
+
+// [0... ...n][m... tdup ...D][m... tdup ...D] output buffer size with duplicate region
+// [0... ...n][m..[p...xf1..n][m... tdup ...D] fade in p...n while fading out q...D
+// [0... ...n][m..[q...xf2..D][m... tdup ...D]
+// [0... ...n][m..[.XFADE...n][m... tdup ...D] final duplicated output buffer - resample at fstep
+
+// pitch down: cut into output buffer: tcut = tseg
+
+// [0... ...n][m... tcut ...D] input samples with cut region delineated m...D
+// [0... ...n] output buffer size after cut
+// [0... [q..txf2...D] fade in txf1 q...D while fade out txf2 p...n
+// [0... [.XFADE ...D] final cut output buffer - resample at fstep
+
+
+ptc_t * PTC_Alloc( float timeslice, float timexfade, float fstep )
+{
+
+ int i;
+ ptc_t *pptc;
+ float tout;
+ int cin, cout;
+ float tslice = timeslice;
+ float txfade = timexfade;
+ float tcutdup;
+
+ // find time compressor slot
+
+ for ( i = 0; i < CPTCS; i++ )
+ {
+ if ( !ptcs[i].fused )
+ break;
+ }
+
+ if ( i == CPTCS )
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate pitch shifter.\n" );
+ return NULL;
+ }
+
+ pptc = &ptcs[i];
+
+ PTC_Init ( pptc );
+
+ // get size of region to cut or duplicate
+
+ tcutdup = abs((fstep - 1.0) * timeslice);
+
+ // to prevent buffer overruns:
+
+ // make sure timeslice is greater than cut/dup time
+
+ tslice = max ( tslice, 1.1 * tcutdup);
+
+ // make sure xfade time smaller than cut/dup time, and smaller than (timeslice-cutdup) time
+
+ txfade = min ( txfade, 0.9 * tcutdup );
+ txfade = min ( txfade, 0.9 * (tslice - tcutdup));
+
+ pptc->cxfade = MSEC_TO_SAMPS( txfade );
+ pptc->ccut = MSEC_TO_SAMPS( tcutdup );
+ pptc->cduplicate = MSEC_TO_SAMPS( tcutdup );
+
+ // alloc delay lines (buffers)
+
+ tout = tslice * fstep;
+
+ cin = MSEC_TO_SAMPS( tslice );
+ cout = MSEC_TO_SAMPS( tout );
+
+ pptc->pdly_in = DLY_Alloc( cin, 0, 1, DLY_LINEAR ); // alloc input buffer
+ pptc->pdly_out = DLY_Alloc( cout, 0, 1, DLY_LINEAR); // alloc output buffer
+
+ if ( !pptc->pdly_in || !pptc->pdly_out )
+ {
+ PTC_Free( pptc );
+ MsgDev( D_WARN, "DSP: failed to allocate delay for pitch shifter.\n" );
+ return NULL;
+ }
+
+ // buffer pointers
+ pptc->pin = pptc->pdly_in->w;
+ pptc->pout = pptc->pdly_out->w;
+
+ // input buffer index
+
+ pptc->iin = 0;
+
+ // output buffer index
+
+ POS_ONE_Init ( &pptc->psn, cout, fstep );
+
+ // if fstep > 1.0 we're pitching shifting up, so fdup = true
+
+ pptc->fdup = fstep > 1.0 ? true : false;
+
+ pptc->cin = cin;
+ pptc->cout = cout;
+
+ pptc->fstep = fstep;
+ pptc->fused = true;
+
+ return pptc;
+}
+
+// linear crossfader
+// yfadein - instantaneous value fading in
+// ydafeout -instantaneous value fading out
+// nsamples - duration in #samples of fade
+// isample - index in to fade 0...nsamples-1
+
+_inline int xfade ( int yfadein, int yfadeout, int nsamples, int isample )
+{
+ int yout;
+ int m = (isample << PBITS ) / nsamples;
+
+ yout = ((yfadein * m) >> PBITS) + ((yfadeout * (PMAX - m)) >> PBITS);
+
+ return yout;
+}
+
+// w - pointer to start of input buffer samples
+// v - pointer to start of output buffer samples
+// cin - # of input buffer samples
+// cout = # of output buffer samples
+// cxfade = # of crossfade samples
+// cduplicate = # of samples in duplicate/cut segment
+
+void TimeExpand( int *w, int *v, int cin, int cout, int cxfade, int cduplicate )
+{
+ int i,j;
+ int m;
+ int p;
+ int q;
+ int D;
+
+ // input buffer
+ // xfade source duplicate
+ // [0...........][p.......n][m...........D]
+
+ // output buffer
+ // xfade region duplicate
+ // [0.....................n][m..[q.......D][m...........D]
+
+ // D - index of last sample in input buffer
+ // m - index of 1st sample in duplication region
+ // p - index of 1st sample of crossfade source
+ // q - index of 1st sample in crossfade region
+
+ D = cin - 1;
+ m = cin - cduplicate;
+ p = m - cxfade;
+ q = cin - cxfade;
+
+ // copy up to crossfade region
+
+ for (i = 0; i < q; i++)
+ v[i] = w[i];
+
+ // crossfade region
+
+ j = p;
+
+ for (i = q; i <= D; i++)
+ v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D
+
+ // duplicate region
+
+ j = D+1;
+
+ for (i = m; i <= D; i++)
+ v[j++] = w[i];
+
+}
+
+// cut ccut samples from end of input buffer, crossfade end of cut section
+// with end of remaining section
+
+// w - pointer to start of input buffer samples
+// v - pointer to start of output buffer samples
+// cin - # of input buffer samples
+// cout = # of output buffer samples
+// cxfade = # of crossfade samples
+// ccut = # of samples in cut segment
+
+void TimeCompress( int *w, int *v, int cin, int cout, int cxfade, int ccut )
+{
+ int i,j;
+ int m;
+ int p;
+ int q;
+ int D;
+
+ // input buffer
+ // xfade source
+ // [0.....................n][m..[p.......D]
+
+ // xfade region cut
+ // [0...........][q.......n][m...........D]
+
+ // output buffer
+ // xfade to source
+ // [0...........][p.......D]
+
+ // D - index of last sample in input buffer
+ // m - index of 1st sample in cut region
+ // p - index of 1st sample of crossfade source
+ // q - index of 1st sample in crossfade region
+
+ D = cin - 1;
+ m = cin - ccut;
+ p = cin - cxfade;
+ q = m - cxfade;
+
+ // copy up to crossfade region
+
+ for (i = 0; i < q; i++)
+ v[i] = w[i];
+
+ // crossfade region
+
+ j = p;
+
+ for (i = q; i < m; i++)
+ v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D
+
+ // skip rest of input buffer
+}
+
+// get next sample
+
+// put input sample into input (delay) buffer
+// get output sample from output buffer, step by fstep %
+// output buffer is time expanded or compressed version of previous input buffer
+
+_inline int PTC_GetNext( ptc_t *pptc, int x )
+{
+ int iout, xout;
+ bool fhitend = false;
+
+ // write x into input buffer
+ Assert (pptc->iin < pptc->cin);
+
+ pptc->pin[pptc->iin] = x;
+
+ pptc->iin++;
+
+ // check for end of input buffer
+
+ if ( pptc->iin >= pptc->cin )
+ fhitend = true;
+
+ // read sample from output buffer, resampling at fstep
+
+ iout = POS_ONE_GetNext( &pptc->psn );
+ Assert (iout < pptc->cout);
+ xout = pptc->pout[iout];
+
+ if ( fhitend )
+ {
+ // if hit end of input buffer (ie: input buffer is full)
+ // reset input buffer pointer
+ // reset output buffer pointer
+ // rebuild entire output buffer (TimeCompress/TimeExpand)
+
+ pptc->iin = 0;
+
+ POS_ONE_Init( &pptc->psn, pptc->cout, pptc->fstep );
+
+ if ( pptc->fdup )
+ TimeExpand ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->cduplicate );
+ else
+ TimeCompress ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->ccut );
+ }
+
+ return xout;
+}
+
+// batch version for performance
+_inline void PTC_GetNextN( ptc_t *pptc, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = PTC_GetNext( pptc, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = PTC_GetNext( pptc, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = PTC_GetNext( pptc, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// change time compression to new value
+// fstep is new value
+// ramptime is how long change takes in seconds (ramps smoothly), 0 for no ramp
+
+void PTC_ChangeVal( ptc_t *pptc, float fstep, float ramptime )
+{
+// UNDONE: ignored
+// UNDONE: just realloc time compressor with new fstep
+}
+
+// uses pitch:
+// 1.0 = playback normal rate
+// 0.5 = cut 50% of sound (2x playback)
+// 1.5 = add 50% sound (0.5x playback)
+
+typedef enum
+{
+ // parameter order
+ ptc_ipitch,
+ ptc_itimeslice,
+ ptc_ixfade,
+ ptc_cparam // # of params
+
+} ptc_e;
+
+// diffusor parameter ranges
+
+prm_rng_t ptc_rng[] =
+{
+{ ptc_cparam, 0, 0 }, // first entry is # of parameters
+{ ptc_ipitch, 0.1, 4.0 }, // 0-n.0 where 1.0 = 1 octave up and 0.5 is one octave down
+{ ptc_itimeslice, 20.0, 300.0 }, // in milliseconds - size of sound chunk to analyze and cut/duplicate - 100ms nominal
+{ ptc_ixfade, 1.0, 200.0 }, // in milliseconds - size of crossfade region between spliced chunks - 20ms nominal
+};
+
+ptc_t * PTC_Params ( prc_t *pprc )
+{
+ ptc_t *pptc;
+
+ float pitch = pprc->prm[ptc_ipitch];
+ float timeslice = pprc->prm[ptc_itimeslice];
+ float txfade = pprc->prm[ptc_ixfade];
+
+ pptc = PTC_Alloc( timeslice, txfade, pitch );
+
+ return pptc;
+}
+
+_inline void * PTC_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, ptc_rng );
+ return (void *) PTC_Params ((prc_t *)p);
+}
+
+// change to new pitch value
+// v is +/- 0-1.0
+// v changes current pitch up/down by +/- v%
+
+void PTC_Mod ( ptc_t *pptc, float v )
+{
+ float fstep;
+ float fstepnew;
+
+ fstep = pptc->fstep;
+ fstepnew = fstep * (1.0 + v);
+
+ PTC_ChangeVal( pptc, fstepnew, 0.01 );
+}
+
+
+////////////////////
+// ADSR envelope
+////////////////////
+
+#define CENVS 64 // max # of envelopes active
+#define CENVRMPS 4 // A, D, S, R
+
+#define ENV_LIN 0 // linear a,d,s,r
+#define ENV_EXP 1 // exponential a,d,s,r
+#define ENV_MAX ENV_EXP
+
+#define ENV_BITS 14 // bits of resolution of ramp
+
+typedef struct env_s
+{
+ bool fused;
+ bool fhitend; // true if done
+ int ienv; // current ramp
+ rmp_t rmps[CENVRMPS]; // ramps
+} env_t;
+
+env_t envs[CENVS];
+
+void ENV_Init( env_t *penv ) { if (penv) Mem_Set( penv, 0, sizeof (env_t) ); };
+void ENV_Free( env_t *penv ) { if (penv) Mem_Set( penv, 0, sizeof (env_t) ); };
+void ENV_InitAll() { int i; for ( i = 0; i < CENVS; i++) ENV_Init( &envs[i] ); };
+void ENV_FreeAll() { int i; for ( i = 0; i < CENVS; i++) ENV_Free( &envs[i] ); };
+
+
+// allocate ADSR envelope
+// all times are in seconds
+// amp1 - attack amplitude multiplier 0-1.0
+// amp2 - sustain amplitude multiplier 0-1.0
+// amp3 - end of sustain amplitude multiplier 0-1.0
+
+env_t *ENV_Alloc ( int type, float famp1, float famp2, float famp3, float attack, float decay, float sustain, float release )
+{
+ int i;
+ env_t *penv;
+
+ for (i = 0; i < CENVS; i++)
+ {
+ if ( !envs[i].fused )
+ {
+
+ int amp1 = famp1 * (1 << ENV_BITS); // ramp resolution
+ int amp2 = famp2 * (1 << ENV_BITS);
+ int amp3 = famp3 * (1 << ENV_BITS);
+
+ penv = &envs[i];
+
+ ENV_Init (penv);
+
+ // UNDONE: ignoring type = ENV_EXP - use oneshot LFOS instead with sawtooth/exponential
+
+ // set up ramps
+
+ RMP_Init( &penv->rmps[0], attack, 0, amp1 );
+ RMP_Init( &penv->rmps[1], decay, amp1, amp2 );
+ RMP_Init( &penv->rmps[2], sustain, amp2, amp3 );
+ RMP_Init( &penv->rmps[3], release, amp3, 0 );
+
+ penv->ienv = 0;
+ penv->fused = true;
+ penv->fhitend = false;
+
+ return penv;
+ }
+ }
+ MsgDev( D_WARN, "DSP: failed to allocate envelope.\n" );
+ return NULL;
+}
+
+
+_inline int ENV_GetNext( env_t *penv, int x )
+{
+ if ( !penv->fhitend )
+ {
+ int i;
+ int y;
+
+ i = penv->ienv;
+ y = RMP_GetNext ( &penv->rmps[i] );
+
+ // check for next ramp
+
+ if ( penv->rmps[i].fhitend )
+ i++;
+
+ penv->ienv = i;
+
+ // check for end of all ramps
+
+ if ( i > 3)
+ penv->fhitend = true;
+
+ // multiply input signal by ramp
+
+ return (x * y) >> ENV_BITS;
+ }
+
+ return 0;
+}
+
+// batch version for performance
+
+_inline void ENV_GetNextN( env_t *penv, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = ENV_GetNext( penv, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = ENV_GetNext( penv, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = ENV_GetNext( penv, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// uses lfowav, amp1, amp2, amp3, attack, decay, sustain, release
+// lfowav is type, currently ignored - ie: LFO_LIN_IN, LFO_LOG_IN
+
+// parameter order
+
+typedef enum
+{
+ env_itype,
+ env_iamp1,
+ env_iamp2,
+ env_iamp3,
+ env_iattack,
+ env_idecay,
+ env_isustain,
+ env_irelease,
+
+ env_cparam // # of params
+
+} env_e;
+
+// parameter ranges
+
+prm_rng_t env_rng[] = {
+
+ {env_cparam, 0, 0}, // first entry is # of parameters
+
+ {env_itype, 0.0,ENV_MAX}, // ENV_LINEAR, ENV_LOG - currently ignored
+ {env_iamp1, 0.0, 1.0}, // attack peak amplitude 0-1.0
+ {env_iamp2, 0.0, 1.0}, // decay target amplitued 0-1.0
+ {env_iamp3, 0.0, 1.0}, // sustain target amplitude 0-1.0
+ {env_iattack, 0.0, 20000.0}, // attack time in milliseconds
+ {env_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds
+ {env_isustain, 0.0, 20000.0}, // sustain time in milliseconds
+ {env_irelease, 0.0, 20000.0}, // release time in milliseconds
+};
+
+env_t * ENV_Params ( prc_t *pprc )
+{
+ env_t *penv;
+
+ float type = pprc->prm[env_itype];
+ float amp1 = pprc->prm[env_iamp1];
+ float amp2 = pprc->prm[env_iamp2];
+ float amp3 = pprc->prm[env_iamp3];
+ float attack = pprc->prm[env_iattack]/1000.0;
+ float decay = pprc->prm[env_idecay]/1000.0;
+ float sustain = pprc->prm[env_isustain]/1000.0;
+ float release = pprc->prm[env_irelease]/1000.0;
+
+ penv = ENV_Alloc ( type, amp1, amp2, amp3, attack, decay, sustain, release );
+ return penv;
+}
+
+_inline void * ENV_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, env_rng );
+ return (void *) ENV_Params ((prc_t *)p);
+}
+
+_inline void ENV_Mod ( void *p, float v ) { return; }
+
+////////////////////
+// envelope follower
+////////////////////
+
+#define CEFOS 64 // max # of envelope followers active
+
+#define CEFOBITS 6 // size 2^6 = 64
+#define CEFOWINDOW (1 << (CEFOBITS)) // size of sample window
+
+typedef struct efo_s
+{
+ bool fused;
+ int avg; // accumulating average over sample window
+ int cavg; // count down
+ int xout; // current output value
+
+} efo_t;
+
+efo_t efos[CEFOS];
+
+void EFO_Init( efo_t *pefo ) { if (pefo) Mem_Set( pefo, 0, sizeof (efo_t) ); };
+void EFO_Free( efo_t *pefo ) { if (pefo) Mem_Set( pefo, 0, sizeof (efo_t) ); };
+void EFO_InitAll() { int i; for ( i = 0; i < CEFOS; i++) EFO_Init( &efos[i] ); };
+void EFO_FreeAll() { int i; for ( i = 0; i < CEFOS; i++) EFO_Free( &efos[i] ); };
+
+
+// allocate enveloper follower
+
+efo_t *EFO_Alloc ( void )
+{
+ int i;
+ efo_t *pefo;
+
+ for (i = 0; i < CEFOS; i++)
+ {
+ if ( !efos[i].fused )
+ {
+ pefo = &efos[i];
+
+ EFO_Init ( pefo );
+
+ pefo->xout = 0;
+ pefo->cavg = CEFOWINDOW;
+ pefo->fused = true;
+
+ return pefo;
+ }
+ }
+
+ MsgDev ( D_WARN, "DSP: failed to allocate envelope follower.\n" );
+ return NULL;
+}
+
+
+_inline int EFO_GetNext( efo_t *pefo, int x )
+{
+ int xa = ABS( x ); // rectify input wav
+
+ // get running sum / 2
+ pefo->avg += xa >> 1; // divide by 2 to prevent overflow
+ pefo->cavg--;
+
+ if ( !pefo->cavg )
+ {
+ // new output value - end of window
+
+ // get average over window
+ pefo->xout = pefo->avg >> (CEFOBITS - 1); // divide by window size / 2
+ pefo->cavg = CEFOWINDOW;
+ pefo->avg = 0;
+ }
+
+ return pefo->xout;
+}
+
+// batch version for performance
+
+_inline void EFO_GetNextN( efo_t *pefo, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = EFO_GetNext( pefo, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = EFO_GetNext( pefo, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = EFO_GetNext( pefo, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+
+efo_t * EFO_Params ( prc_t *pprc )
+{
+ return EFO_Alloc();
+}
+
+_inline void * EFO_VParams ( void *p )
+{
+ // PRC_CheckParams ( (prc_t *)p, efo_rng ); - efo has no params
+ return (void *) EFO_Params ((prc_t *)p);
+}
+
+_inline void EFO_Mod ( void *p, float v ) { return; }
+
+//////////////
+// mod delay
+//////////////
+
+// modulate delay time anywhere from 0..D using MDY_ChangeVal. no output glitches (uses RMP)
+
+#define CMDYS 64 // max # of mod delays active (steals from delays)
+
+typedef struct mdy_s
+{
+ bool fused;
+ bool fchanging; // true if modulating to new delay value
+ dly_t *pdly; // delay
+ int Dcur; // current delay value
+ float ramptime; // ramp 'glide' time - time in seconds to change between values
+ int mtime; // time in samples between delay changes. 0 implies no self-modulating
+ int mtimecur; // current time in samples until next delay change
+ float depth; // modulate delay from D to D - (D*depth) depth 0-1.0
+ int xprev; // previous delay output, used to smooth transitions between delays
+ rmp_t rmp; // ramp
+} mdy_t;
+
+mdy_t mdys[CMDYS];
+
+void MDY_Init( mdy_t *pmdy ) { if (pmdy) Mem_Set( pmdy, 0, sizeof (mdy_t) ); };
+void MDY_Free( mdy_t *pmdy ) { if (pmdy) { DLY_Free (pmdy->pdly); Mem_Set( pmdy, 0, sizeof (mdy_t) ); } };
+void MDY_InitAll() { int i; for ( i = 0; i < CMDYS; i++) MDY_Init( &mdys[i] ); };
+void MDY_FreeAll() { int i; for ( i = 0; i < CMDYS; i++) MDY_Free( &mdys[i] ); };
+
+
+// allocate mod delay, given previously allocated dly
+// ramptime is time in seconds for delay to change from dcur to dnew
+// modtime is time in seconds between modulations. 0 if no self-modulation
+// depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D)
+
+mdy_t *MDY_Alloc ( dly_t *pdly, float ramptime, float modtime, float depth )
+{
+ int i;
+ mdy_t *pmdy;
+
+ if ( !pdly )
+ return NULL;
+
+ for (i = 0; i < CMDYS; i++)
+ {
+ if ( !mdys[i].fused )
+ {
+ pmdy = &mdys[i];
+
+ MDY_Init ( pmdy );
+
+ pmdy->pdly = pdly;
+
+ if ( !pmdy->pdly )
+ {
+ MsgDev ( D_WARN, "DSP: failed to allocate delay for mod delay.\n" );
+ return NULL;
+ }
+
+ pmdy->Dcur = pdly->D0;
+ pmdy->fused = true;
+ pmdy->ramptime = ramptime;
+ pmdy->mtime = SEC_TO_SAMPS( modtime );
+ pmdy->mtimecur = pmdy->mtime;
+ pmdy->depth = depth;
+
+ return pmdy;
+ }
+ }
+
+ MsgDev ( D_WARN, "DSP: failed to allocate mod delay.\n" );
+ return NULL;
+}
+
+// change to new delay tap value t samples, ramp linearly over ramptime seconds
+void MDY_ChangeVal ( mdy_t *pmdy, int t )
+{
+ // if D > original delay value, cap at original value
+
+ t = min (pmdy->pdly->D0, t);
+
+ pmdy->fchanging = true;
+
+ RMP_Init ( &pmdy->rmp, pmdy->ramptime, pmdy->Dcur, t );
+}
+
+// get next value from modulating delay
+int MDY_GetNext( mdy_t *pmdy, int x )
+{
+ int xout;
+ int xcur;
+
+ // get current delay output
+
+ xcur = DLY_GetNext( pmdy->pdly, x );
+
+ // return right away if not modulating (not changing and not self modulating)
+
+ if ( !pmdy->fchanging && !pmdy->mtime )
+ {
+ pmdy->xprev = xcur;
+ return xcur;
+ }
+
+ xout = xcur;
+
+ // if currently changing to new delay target, get next delay value
+
+ if ( pmdy->fchanging )
+ {
+ // get next ramp value, test for done
+
+ int r = RMP_GetNext ( &pmdy->rmp );
+
+ if ( RMP_HitEnd( &pmdy->rmp ) )
+ pmdy->fchanging = false;
+
+ // if new delay different from current delay, change delay
+
+ if ( r != pmdy->Dcur )
+ {
+ // ramp never changes by more than + or - 1
+
+ // change delay tap value to r
+
+ DLY_ChangeVal( pmdy->pdly, r );
+
+ pmdy->Dcur = r;
+
+ // filter delay output within transitions.
+ // note: xprev = xcur = 0 if changing delay on 1st sample
+
+ xout = ( xcur + pmdy->xprev ) >> 1;
+ }
+ }
+
+ // if self-modulating and timer has expired, get next change
+
+ if ( pmdy->mtime && !pmdy->mtimecur-- )
+ {
+ int D0 = pmdy->pdly->D0;
+ int Dnew;
+ float D1;
+
+ pmdy->mtimecur = pmdy->mtime;
+
+ // modulate between 0 and 100% of d0
+ D1 = (float)D0 * (1.0 - pmdy->depth);
+
+ Dnew = Com_RandomLong( (int)D1, D0 );
+
+ MDY_ChangeVal ( pmdy, Dnew );
+ }
+
+ pmdy->xprev = xcur;
+
+ return xout;
+}
+
+// batch version for performance
+
+_inline void MDY_GetNextN( mdy_t *pmdy, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = MDY_GetNext( pmdy, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = MDY_GetNext( pmdy, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = MDY_GetNext( pmdy, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// parameter order
+
+typedef enum
+{
+ mdy_idtype, // NOTE: first 8 params must match params in dly_e
+ mdy_idelay,
+ mdy_ifeedback,
+ mdy_igain,
+ mdy_iftype,
+ mdy_icutoff,
+ mdy_iqwidth,
+ mdy_iquality,
+ mdy_imodrate,
+ mdy_imoddepth,
+ mdy_imodglide,
+ mdy_cparam
+} mdy_e;
+
+
+// parameter ranges
+
+prm_rng_t mdy_rng[] =
+{
+{ mdy_cparam, 0, 0 }, // first entry is # of parameters
+// delay params
+{ mdy_idtype, 0, DLY_MAX }, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
+{ mdy_idelay, 0.0, 1000.0 }, // delay in milliseconds
+{ mdy_ifeedback, 0.0, 0.99 }, // feedback 0-1.0
+{ mdy_igain, 0.0, 1.0 }, // final gain of output stage, 0-1.0
+
+// filter params if mdy type DLY_LOWPASS
+
+{ mdy_iftype, 0, FTR_MAX },
+{ mdy_icutoff, 10.0, SOUND_22k },
+{ mdy_iqwidth, 100.0, SOUND_11k },
+{ mdy_iquality, 0, QUA_MAX },
+{ mdy_imodrate, 0.01, 200.0 }, // frequency at which delay values change to new random value. 0 is no self-modulation
+{ mdy_imoddepth, 0.0, 1.0 }, // how much delay changes (decreases) from current value (0-1.0)
+{ mdy_imodglide, 0.01, 100.0 }, // glide time between dcur and dnew in milliseconds
+};
+
+// convert user parameters to internal parameters, allocate and return
+mdy_t * MDY_Params ( prc_t *pprc )
+{
+ mdy_t *pmdy;
+ dly_t *pdly;
+
+ float ramptime = pprc->prm[mdy_imodglide] / 1000.0; // get ramp time in seconds
+ float modtime = 1.0 / pprc->prm[mdy_imodrate]; // time between modulations in seconds
+ float depth = pprc->prm[mdy_imoddepth]; // depth of modulations 0-1.0
+
+ // alloc plain, allpass or lowpass delay
+
+ pdly = DLY_Params( pprc );
+
+ if ( !pdly )
+ return NULL;
+
+ pmdy = MDY_Alloc ( pdly, ramptime, modtime, depth );
+
+ return pmdy;
+}
+
+_inline void * MDY_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, mdy_rng );
+ return (void *) MDY_Params ((prc_t *)p);
+}
+
+// v is +/- 0-1.0
+// change current delay value 0..D
+
+void MDY_Mod ( mdy_t *pmdy, float v )
+{
+
+ int D = pmdy->Dcur;
+ float v2 = -(v + 1.0)/2.0; // v2 varies -1.0-0.0
+
+ // D varies 0..D
+
+ D = D + (int)((float)D * v2);
+
+ // change delay
+
+ MDY_ChangeVal( pmdy, D );
+
+ return;
+}
+
+
+///////////////////////////////////////////
+// Chorus - lfo modulated delay
+///////////////////////////////////////////
+#define CCRSS 64 // max number chorus' active
+
+typedef struct crs_s
+{
+ bool fused;
+ mdy_t *pmdy; // modulatable delay
+ lfo_t *plfo; // modulating lfo
+ int lfoprev; // previous modulator value from lfo
+ int mix; // mix of clean & chorus signal - 0..PMAX
+
+} crs_t;
+
+crs_t crss[CCRSS];
+
+void CRS_Init( crs_t *pcrs ) { if (pcrs) Mem_Set( pcrs, 0, sizeof (crs_t) ); };
+void CRS_Free( crs_t *pcrs )
+{
+ if( pcrs )
+ {
+ MDY_Free ( pcrs->pmdy );
+ LFO_Free ( pcrs->plfo );
+ Mem_Set( pcrs, 0, sizeof (crs_t) );
+ }
+}
+
+
+void CRS_InitAll() { int i; for ( i = 0; i < CCRSS; i++) CRS_Init( &crss[i] ); }
+void CRS_FreeAll() { int i; for ( i = 0; i < CCRSS; i++) CRS_Free( &crss[i] ); }
+
+// fstep is base pitch shift, ie: floating point step value, where 1.0 = +1 octave, 0.5 = -1 octave
+// lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
+// fHz is modulation frequency in Hz
+// depth is modulation depth, 0-1.0
+// mix is mix of chorus and clean signal
+
+#define CRS_DELAYMAX 100 // max milliseconds of sweepable delay
+#define CRS_RAMPTIME 5 // milliseconds to ramp between new delay values
+
+crs_t * CRS_Alloc( int lfotype, float fHz, float fdepth, float mix )
+{
+ int i;
+ crs_t *pcrs;
+ dly_t *pdly;
+ mdy_t *pmdy;
+ lfo_t *plfo;
+ float ramptime;
+ int D;
+
+ // find free chorus slot
+ for ( i = 0; i < CCRSS; i++ )
+ {
+ if ( !crss[i].fused )
+ break;
+ }
+
+ if ( i == CCRSS )
+ {
+ MsgDev ( D_WARN, "DSP: failed to allocate chorus.\n" );
+ return NULL;
+ }
+
+ pcrs = &crss[i];
+
+ CRS_Init ( pcrs );
+
+ D = fdepth * MSEC_TO_SAMPS(CRS_DELAYMAX); // sweep from 0 - n milliseconds
+
+ ramptime = (float) CRS_RAMPTIME / 1000.0; // # milliseconds to ramp between new values
+
+ pdly = DLY_Alloc ( D, 0, 1, DLY_LINEAR );
+
+ pmdy = MDY_Alloc ( pdly, ramptime, 0.0, 0.0 );
+
+ plfo = LFO_Alloc ( lfotype, fHz, false );
+
+ if ( !plfo || !pmdy )
+ {
+ LFO_Free ( plfo );
+ MDY_Free ( pmdy );
+ MsgDev ( D_WARN, "DSP: failed to allocate lfo or mdy for chorus.\n" );
+ return NULL;
+ }
+
+ pcrs->pmdy = pmdy;
+ pcrs->plfo = plfo;
+ pcrs->mix = (int) ( PMAX * mix );
+ pcrs->fused = true;
+
+ return pcrs;
+}
+
+// return next chorused sample (modulated delay) mixed with input sample
+_inline int CRS_GetNext( crs_t *pcrs, int x )
+{
+ int l;
+ int y;
+
+ // get current mod delay value
+
+ y = MDY_GetNext ( pcrs->pmdy, x );
+
+ // get next lfo value for modulation
+ // note: lfo must return 0 as first value
+
+ l = LFO_GetNext ( pcrs->plfo, x );
+
+ // if modulator has changed, change mdy
+
+ if ( l != pcrs->lfoprev )
+ {
+ // calculate new tap (starts at D)
+
+ int D = pcrs->pmdy->pdly->D0;
+ int tap;
+
+ // lfo should always output values 0 <= l <= LFOMAX
+
+ if (l < 0)
+ l = 0;
+
+ tap = D - ((l * D) >> LFOBITS);
+
+ MDY_ChangeVal ( pcrs->pmdy, tap );
+
+ pcrs->lfoprev = l;
+ }
+
+ return ((y * pcrs->mix) >> PBITS) + x;
+}
+
+// batch version for performance
+
+_inline void CRS_GetNextN( crs_t *pcrs, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = CRS_GetNext( pcrs, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = CRS_GetNext( pcrs, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = CRS_GetNext( pcrs, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// parameter order
+
+typedef enum
+{
+ crs_ilfotype,
+ crs_irate,
+ crs_idepth,
+ crs_imix,
+ crs_cparam
+} crs_e;
+
+
+// parameter ranges
+prm_rng_t crs_rng[] =
+{
+{ crs_cparam, 0, 0 }, // first entry is # of parameters
+{ crs_ilfotype, 0, LFO_MAX }, // lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
+{ crs_irate, 0.0, 1000.0 }, // rate is modulation frequency in Hz
+{ crs_idepth, 0.0, 1.0 }, // depth is modulation depth, 0-1.0
+{ crs_imix, 0.0, 1.0 }, // mix is mix of chorus and clean signal
+};
+
+// uses pitch, lfowav, rate, depth
+crs_t * CRS_Params ( prc_t *pprc )
+{
+ crs_t *pcrs;
+
+ pcrs = CRS_Alloc ( pprc->prm[crs_ilfotype], pprc->prm[crs_irate], pprc->prm[crs_idepth], pprc->prm[crs_imix] );
+
+ return pcrs;
+}
+
+_inline void * CRS_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, crs_rng );
+ return (void *) CRS_Params ((prc_t *)p);
+}
+
+_inline void CRS_Mod ( void *p, float v ) { return; }
+
+
+////////////////////////////////////////////////////
+// amplifier - modulatable gain, distortion
+////////////////////////////////////////////////////
+
+#define CAMPS 64 // max number amps active
+#define AMPSLEW 10 // milliseconds of slew time between gain changes
+
+typedef struct amp_s
+{
+ bool fused;
+ float gain; // amplification 0-6.0
+ float vthresh; // clip distortion threshold 0-1.0
+ float distmix; // 0-1.0 mix of distortion with clean
+ float vfeed; // 0-1.0 feedback with distortion;
+ float gaintarget; // new gain
+ float gaindif; // incrementer
+} amp_t;
+
+amp_t amps[CAMPS];
+
+void AMP_Init( amp_t *pamp ) { if( pamp ) Mem_Set( pamp, 0, sizeof( amp_t )); };
+void AMP_Free( amp_t *pamp ) { if( pamp ) Mem_Set( pamp, 0, sizeof( amp_t )); }
+
+
+void AMP_InitAll() { int i; for ( i = 0; i < CAMPS; i++) AMP_Init( &s[i] ); }
+void AMP_FreeAll() { int i; for ( i = 0; i < CAMPS; i++) AMP_Free( &s[i] ); }
+
+amp_t * AMP_Alloc( float gain, float vthresh, float distmix, float vfeed )
+{
+ int i;
+ amp_t *pamp;
+
+ // find free amp slot
+
+ for ( i = 0; i < CAMPS; i++ )
+ {
+ if ( !amps[i].fused )
+ break;
+ }
+
+ if ( i == CAMPS )
+ {
+ MsgDev( D_WARN, "DSP: failed to allocate amp.\n" );
+ return NULL;
+ }
+
+ pamp = &s[i];
+
+ AMP_Init ( pamp );
+
+ pamp->gain = gain;
+ pamp->vthresh = vthresh;
+ pamp->distmix = distmix;
+ pamp->vfeed = vfeed;
+
+ return pamp;
+}
+
+// return next amplified sample
+
+_inline int AMP_GetNext( amp_t *pamp, int x )
+{
+ float y = (float)x;
+ float yin;
+ float gain = pamp->gain;
+
+ yin = y;
+
+ // slew between gains
+
+ if ( gain != pamp->gaintarget )
+ {
+ float gaintarget = pamp->gaintarget;
+ float gaindif = pamp->gaindif;
+
+ if (gain > gaintarget)
+ {
+ gain -= gaindif;
+ if (gain <= gaintarget)
+ pamp->gaintarget = gain;
+ }
+ else
+ {
+ gain += gaindif;
+ if (gain >= gaintarget)
+ pamp->gaintarget = gain;
+ }
+
+ pamp->gain = gain;
+ }
+
+ // if distortion is on, add distortion, feedback
+
+ if ( pamp->vthresh < 1.0 )
+ {
+ float fclip = pamp->vthresh * 32767.0;
+
+ if ( pamp->vfeed > 0.0 )
+ {
+ // UNDONE: feedback
+ }
+
+ // clip distort
+
+ y = ( y > fclip ? fclip : ( y < -fclip ? -fclip : y));
+
+ // mix distorted with clean (1.0 = full distortion)
+
+ if ( pamp->distmix > 0.0 )
+ y = y * pamp->distmix + yin * (1.0 - pamp->distmix);
+ }
+
+ // amplify
+
+ y *= gain;
+
+ return (int)y;
+}
+
+// batch version for performance
+_inline void AMP_GetNextN( amp_t *pamp, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = AMP_GetNext( pamp, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = AMP_GetNext( pamp, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = AMP_GetNext( pamp, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+_inline void AMP_Mod( amp_t *pamp, float v )
+{
+ float vmod = clamp (v, 0.0, 1.0);
+ float samps = MSEC_TO_SAMPS(AMPSLEW); // # samples to slew between amp values
+
+ // ramp to new amplification value
+
+ pamp->gaintarget = pamp->gain * vmod;
+
+ pamp->gaindif = fabs (pamp->gain - pamp->gaintarget) / samps;
+
+ if (pamp->gaindif == 0.0)
+ pamp->gaindif = fabs(pamp->gain - pamp->gaintarget)/100;
+}
+
+
+// parameter order
+
+typedef enum
+{
+ amp_gain,
+ amp_vthresh,
+ amp_distmix,
+ amp_vfeed,
+ amp_cparam
+} amp_e;
+
+
+// parameter ranges
+prm_rng_t amp_rng[] =
+{
+{ amp_cparam, 0, 0 }, // first entry is # of parameters
+{ amp_gain, 0.0, 10.0 }, // amplification
+{ amp_vthresh, 0.0, 1.0 }, // threshold for distortion (1.0 = no distortion)
+{ amp_distmix, 0.0, 1.0 }, // mix of clean and distortion (1.0 = full distortion, 0.0 = full clean)
+{ amp_vfeed, 0.0, 1.0 }, // distortion feedback
+};
+
+amp_t * AMP_Params ( prc_t *pprc )
+{
+ amp_t *pamp;
+
+ pamp = AMP_Alloc ( pprc->prm[amp_gain], pprc->prm[amp_vthresh], pprc->prm[amp_distmix], pprc->prm[amp_vfeed] );
+
+ return pamp;
+}
+
+_inline void * AMP_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, amp_rng );
+ return (void *) AMP_Params ((prc_t *)p);
+}
+
+
+/////////////////
+// NULL processor
+/////////////////
+
+typedef struct nul_s
+{
+ int type;
+} nul_t;
+
+nul_t nuls[] = {0};
+
+void NULL_Init ( nul_t *pnul ) { }
+void NULL_InitAll( ) { }
+void NULL_Free ( nul_t *pnul ) { }
+void NULL_FreeAll ( ) { }
+nul_t *NULL_Alloc ( ) { return &nuls[0]; }
+
+_inline int NULL_GetNext ( void *p, int x) { return x; }
+
+_inline void NULL_GetNextN( nul_t *pnul, samplepair_t *pbuffer, int SampleCount, int op ) { return; }
+
+_inline void NULL_Mod ( void *p, float v ) { return; }
+
+_inline void * NULL_VParams ( void *p ) { return (void *) (&nuls[0]); }
+
+//////////////////////////
+// DSP processors presets
+//////////////////////////
+
+// A dsp processor (prc) performs a single-sample function, such as pitch shift, delay, reverb, filter
+
+// note, this array must have CPRCPARMS entries
+
+#define PRMZERO 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
+#define PFNZERO NULL,NULL,NULL,NULL,NULL // zero pointers for pfnparam...pdata within prc_t
+
+//////////////////
+// NULL processor
+/////////////////
+
+#define PRC_NULL1 {PRC_NULL, PRMZERO, PFNZERO}
+
+#define PRC0 PRC_NULL1
+
+//////////////
+// Amplifiers
+//////////////
+// {amp_gain, 0.0, 10.0 }, // amplification
+// {amp_vthresh, 0.0, 1.0 }, // threshold for distortion (1.0 = no distortion)
+// {amp_distmix, 0.0, 1.0 }, // mix of clean and distortion (1.0 = full distortion, 0.0 = full clean)
+// {amp_vfeed, 0.0, 1.0 }, // distortion feedback
+
+// prctype gain vthresh distmix vfeed
+#define PRC_AMP1 {PRC_AMP, {1.0, 1.0, 0.0, 0.0, }, PFNZERO} // modulatable unity gain amp
+#define PRC_AMP2 {PRC_AMP, {1.5, 0.75, 1.0, 0.0, }, PFNZERO} // amp with light distortion
+#define PRC_AMP3 {PRC_AMP, {2.0, 0.5, 1.0, 0.0, }, PFNZERO} // amp with medium distortion
+#define PRC_AMP4 {PRC_AMP, {4.0, 0.25, 1.0, 0.0, }, PFNZERO} // amp with heavy distortion
+#define PRC_AMP5 {PRC_AMP, {10.0, 0.10, 1.0, 0.0, }, PFNZERO} // mega distortion
+
+#define PRC_AMP6 {PRC_AMP, {0.1, 1.0, 0.0, 0.0,}, PFNZERO} // fade out
+#define PRC_AMP7 {PRC_AMP, {0.2, 1.0, 0.0, 0.0,}, PFNZERO} // fade out
+#define PRC_AMP8 {PRC_AMP, {0.3, 1.0, 0.0, 0.0,}, PFNZERO} // fade out
+
+#define PRC_AMP9 {PRC_AMP, {0.75, 1.0, 0.0, 0.0,}, PFNZERO} // duck out
+
+
+///////////
+// Filters
+///////////
+
+// ftype: filter type FLT_LP, FLT_HP, FLT_BP (UNDONE: FLT_BP currently ignored)
+// cutoff: cutoff frequency in hz at -3db gain
+// qwidth: width of BP, or steepness of LP/HP (ie: fcutoff + qwidth = -60db gain point)
+// quality: QUA_LO, _MED, _HI 0,1,2
+
+// prctype ftype cutoff qwidth quality
+#define PRC_FLT1 {PRC_FLT, {FLT_LP, 3000, 1000, QUA_MED,}, PFNZERO}
+#define PRC_FLT2 {PRC_FLT, {FLT_LP, 2000, 2000, QUA_MED,}, PFNZERO} // lowpass for facing away
+#define PRC_FLT3 {PRC_FLT, {FLT_LP, 1000, 1000, QUA_MED,}, PFNZERO}
+#define PRC_FLT4 {PRC_FLT, {FLT_LP, 700, 700, QUA_LO,}, PFNZERO} // muffle filter
+
+#define PRC_FLT5 {PRC_FLT, {FLT_HP, 700, 200, QUA_MED,}, PFNZERO} // highpass (bandpass pair)
+#define PRC_FLT6 {PRC_FLT, {FLT_HP, 2000, 1000, QUA_MED,}, PFNZERO} // lowpass (bandpass pair)
+
+//////////
+// Delays
+//////////
+
+ // dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
+ // delay: delay in milliseconds
+ // feedback: feedback 0-1.0
+ // gain: final gain of output stage, 0-1.0
+
+// prctype dtype delay feedbk gain ftype cutoff qwidth quality
+#define PRC_DLY1 {PRC_DLY, {DLY_PLAIN, 500.0, 0.5, 0.6, 0.0, 0.0, 0.0, 0.0,}, PFNZERO}
+#define PRC_DLY2 {PRC_DLY, {DLY_LOWPASS, 45.0, 0.8, 0.6, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO}
+
+#define PRC_DLY3 {PRC_DLY, {DLY_LOWPASS, 300.0, 0.5, 0.6, FLT_LP, 2000, 2000, QUA_LO,}, PFNZERO} // outside S
+#define PRC_DLY4 {PRC_DLY, {DLY_LOWPASS, 400.0, 0.5, 0.6, FLT_LP, 1500, 1500, QUA_LO,}, PFNZERO} // outside M
+#define PRC_DLY5 {PRC_DLY, {DLY_LOWPASS, 750.0, 0.5, 0.6, FLT_LP, 1000, 1000, QUA_LO,}, PFNZERO} // outside L
+#define PRC_DLY6 {PRC_DLY, {DLY_LOWPASS, 1000.0, 0.5, 0.6, FLT_LP, 800, 400, QUA_LO,}, PFNZERO} // outside VL
+
+#define PRC_DLY7 {PRC_DLY, {DLY_LOWPASS, 45.0, 0.4, 0.5, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO} // tunnel S
+#define PRC_DLY8 {PRC_DLY, {DLY_LOWPASS, 55.0, 0.4, 0.5, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO} // tunnel M
+#define PRC_DLY9 {PRC_DLY, {DLY_LOWPASS, 65.0, 0.4, 0.5, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO} // tunnel L
+
+#define PRC_DLY10 {PRC_DLY, {DLY_LOWPASS, 150.0, 0.5, 0.6, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO} // cavern S
+#define PRC_DLY11 {PRC_DLY, {DLY_LOWPASS, 200.0, 0.7, 0.6, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO} // cavern M
+#define PRC_DLY12 {PRC_DLY, {DLY_LOWPASS, 300.0, 0.7, 0.6, FLT_LP, 3000, 3000, QUA_LO,}, PFNZERO} // cavern L
+
+#define PRC_DLY13 {PRC_DLY, {DLY_LINEAR, 300.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,}, PFNZERO} // straight delay 300ms
+#define PRC_DLY14 {PRC_DLY, {DLY_LINEAR, 80.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,}, PFNZERO} // straight delay 80ms
+
+///////////
+// Reverbs
+///////////
+
+ // size: 0-2.0 scales nominal delay parameters (starting at approx 20ms)
+ // density: 0-2.0 density of reverbs (room shape) - controls # of parallel or series delays
+ // decay: 0-2.0 scales feedback parameters (starting at approx 0.15)
+
+// prctype size density decay ftype cutoff qwidth fparallel
+
+#define PRC_RVA1 {PRC_RVA, {2.0, 0.5, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO}
+#define PRC_RVA2 {PRC_RVA, {1.0, 0.2, 1.5, 0, 0, 0, 0}, PFNZERO}
+
+#define PRC_RVA3 {PRC_RVA, {0.8, 0.5, 1.5, FLT_LP, 2500, 2000, 0}, PFNZERO} // metallic S
+#define PRC_RVA4 {PRC_RVA, {1.0, 0.5, 1.5, FLT_LP, 2500, 2000, 0}, PFNZERO} // metallic M
+#define PRC_RVA5 {PRC_RVA, {1.2, 0.5, 1.5, FLT_LP, 2500, 2000, 0}, PFNZERO} // metallic L
+
+#define PRC_RVA6 {PRC_RVA, {0.8, 0.3, 1.5, FLT_LP, 4000, 2000, 0}, PFNZERO} // tunnel S
+#define PRC_RVA7 {PRC_RVA, {0.9, 0.3, 1.5, FLT_LP, 4000, 2000, 0}, PFNZERO} // tunnel M
+#define PRC_RVA8 {PRC_RVA, {1.0, 0.3, 1.5, FLT_LP, 4000, 2000, 0}, PFNZERO} // tunnel L
+
+#define PRC_RVA9 {PRC_RVA, {2.0, 1.5, 2.0, FLT_LP, 1500, 1500, 1}, PFNZERO} // cavern S
+#define PRC_RVA10 {PRC_RVA, {2.0, 1.5, 2.0, FLT_LP, 1500, 1500, 1}, PFNZERO} // cavern M
+#define PRC_RVA11 {PRC_RVA, {2.0, 1.5, 2.0, FLT_LP, 1500, 1500, 1}, PFNZERO} // cavern L
+
+#define PRC_RVA12 {PRC_RVA, {2.0, 0.5, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO} // chamber S
+#define PRC_RVA13 {PRC_RVA, {2.0, 1.0, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO} // chamber M
+#define PRC_RVA14 {PRC_RVA, {2.0, 2.0, 1.5, FLT_LP, 6000, 2000, 1}, PFNZERO} // chamber L
+
+#define PRC_RVA15 {PRC_RVA, {1.7, 1.0, 1.2, FLT_LP, 5000, 4000, 1}, PFNZERO} // brite S
+#define PRC_RVA16 {PRC_RVA, {1.75, 1.0, 1.5, FLT_LP, 5000, 4000, 1}, PFNZERO} // brite M
+#define PRC_RVA17 {PRC_RVA, {1.85, 1.0, 2.0, FLT_LP, 6000, 4000, 1}, PFNZERO} // brite L
+
+#define PRC_RVA18 {PRC_RVA, {1.0, 1.5, 1.0, FLT_LP, 1000, 1000, 0}, PFNZERO} // generic
+
+#define PRC_RVA19 {PRC_RVA, {1.9, 1.8, 1.25, FLT_LP, 4000, 2000, 1}, PFNZERO} // concrete S
+#define PRC_RVA20 {PRC_RVA, {2.0, 1.8, 1.5, FLT_LP, 3500, 2000, 1}, PFNZERO} // concrete M
+#define PRC_RVA21 {PRC_RVA, {2.0, 1.8, 1.75, FLT_LP, 3000, 2000, 1}, PFNZERO} // concrete L
+
+#define PRC_RVA22 {PRC_RVA, {1.8, 1.5, 1.5, FLT_LP, 1000, 1000, 0}, PFNZERO} // water S
+#define PRC_RVA23 {PRC_RVA, {1.9, 1.75, 1.5, FLT_LP, 1000, 1000, 0}, PFNZERO} // water M
+#define PRC_RVA24 {PRC_RVA, {2.0, 2.0, 1.5, FLT_LP, 1000, 1000, 0}, PFNZERO} // water L
+
+
+/////////////
+// Diffusors
+/////////////
+
+ // size: 0-1.0 scales all delays
+ // density: 0-1.0 controls # of series delays
+ // decay: 0-1.0 scales all feedback parameters
+
+// prctype size density decay
+
+#define PRC_DFR1 {PRC_DFR, {1.0, 0.5, 1.0}, PFNZERO}
+
+#define PRC_DFR2 {PRC_DFR, {0.5, 0.3, 0.5}, PFNZERO} // S
+#define PRC_DFR3 {PRC_DFR, {0.75, 0.5, 0.75}, PFNZERO} // M
+#define PRC_DFR4 {PRC_DFR, {1.0, 0.5, 1.0}, PFNZERO} // L
+#define PRC_DFR5 {PRC_DFR, {1.0, 1.0, 1.0}, PFNZERO} // VL
+
+////////
+// LFOs
+////////
+ // wavtype: lfo type to use (LFO_SIN, LFO_RND...)
+ // rate: modulation rate in hz. for MDY, 1/rate = 'glide' time in seconds
+ // foneshot: 1.0 if lfo is oneshot
+
+// prctype wavtype rate foneshot
+
+#define PRC_LFO1 {PRC_LFO, {LFO_SIN, 440.0, 0.0,}, PFNZERO}
+#define PRC_LFO2 {PRC_LFO, {LFO_SIN, 3000.0, 0.0,}, PFNZERO} // ear noise ring
+#define PRC_LFO3 {PRC_LFO, {LFO_SIN, 4500.0, 0.0,}, PFNZERO} // ear noise ring
+#define PRC_LFO4 {PRC_LFO, {LFO_SIN, 6000.0, 0.0,}, PFNZERO} // ear noise ring
+#define PRC_LFO5 {PRC_LFO, {LFO_SAW, 100.0, 0.0,}, PFNZERO} // sub bass
+
+/////////
+// Pitch
+/////////
+
+ // pitch: 0-n.0 where 1.0 = 1 octave up and 0.5 is one octave down
+ // timeslice: in milliseconds - size of sound chunk to analyze and cut/duplicate - 100ms nominal
+ // xfade: in milliseconds - size of crossfade region between spliced chunks - 20ms nominal
+
+// prctype pitch timeslice xfade
+
+#define PRC_PTC1 {PRC_PTC, {1.1, 100.0, 20.0}, PFNZERO} // pitch up 10%
+#define PRC_PTC2 {PRC_PTC, {0.9, 100.0, 20.0}, PFNZERO} // pitch down 10%
+#define PRC_PTC3 {PRC_PTC, {0.95, 100.0, 20.0}, PFNZERO} // pitch down 5%
+#define PRC_PTC4 {PRC_PTC, {1.01, 100.0, 20.0}, PFNZERO} // pitch up 1%
+#define PRC_PTC5 {PRC_PTC, {0.5, 100.0, 20.0}, PFNZERO} // pitch down 50%
+
+/////////////
+// Envelopes
+/////////////
+
+ // etype: ENV_LINEAR, ENV_LOG - currently ignored
+ // amp1: attack peak amplitude 0-1.0
+ // amp2: decay target amplitued 0-1.0
+ // amp3: sustain target amplitude 0-1.0
+ // attack time in milliseconds
+ // envelope decay time in milliseconds
+ // sustain time in milliseconds
+ // release time in milliseconds
+
+// prctype etype amp1 amp2 amp3 attack decay sustain release
+
+#define PRC_ENV1 {PRC_ENV,{ENV_LIN, 1.0, 0.5, 0.4, 500, 500, 3000, 6000}, PFNZERO}
+
+
+//////////////
+// Mod delays
+//////////////
+
+ // dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
+ // delay: delay in milliseconds
+ // feedback: feedback 0-1.0
+ // gain: final gain of output stage, 0-1.0
+
+ // modrate: frequency at which delay values change to new random value. 0 is no self-modulation
+ // moddepth: how much delay changes (decreases) from current value (0-1.0)
+ // modglide: glide time between dcur and dnew in milliseconds
+
+// prctype dtype delay feedback gain ftype cutoff qwidth qual modrate moddepth modglide
+#define PRC_MDY1 {PRC_MDY, {DLY_PLAIN, 500.0, 0.5, 1.0, 0, 0, 0, 0, 10, 0.8, 5,}, PFNZERO}
+#define PRC_MDY2 {PRC_MDY, {DLY_PLAIN, 50.0, 0.8, 1.0, 0, 0, 0, 0, 5, 0.8, 5,}, PFNZERO}
+
+#define PRC_MDY3 {PRC_MDY, {DLY_PLAIN, 300.0, 0.2, 1.0, 0, 0, 0, 0, 30, 0.01, 15,}, PFNZERO} // weird 1
+#define PRC_MDY4 {PRC_MDY, {DLY_PLAIN, 400.0, 0.3, 1.0, 0, 0, 0, 0, 0.25, 0.01, 15,}, PFNZERO} // weird 2
+#define PRC_MDY5 {PRC_MDY, {DLY_PLAIN, 500.0, 0.4, 1.0, 0, 0, 0, 0, 0.25, 0.01, 15,}, PFNZERO} // weird 3
+
+//////////
+// Chorus
+//////////
+
+ // lfowav: lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
+ // rate: rate is modulation frequency in Hz
+ // depth: depth is modulation depth, 0-1.0
+ // mix: mix is mix of chorus and clean signal
+
+// prctype lfowav rate depth mix
+#define PRC_CRS1 {PRC_CRS, {LFO_SIN, 10, 1.0, 0.5,}, PFNZERO}
+
+/////////////////////
+// Envelope follower
+/////////////////////
+
+ // takes no parameters
+#define PRC_EFO1 {PRC_EFO, {PRMZERO}, PFNZERO}
+
+
+
+
+
+// init array of processors - first store pfnParam, pfnGetNext and pfnFree functions for type,
+// then call the pfnParam function to initialize each processor
+
+// prcs - an array of prc structures, all with initialized params
+// count - number of elements in the array
+
+// returns false if failed to init one or more processors
+
+bool PRC_InitAll( prc_t *prcs, int count )
+{
+ int i;
+ prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type
+ prc_GetNext_t pfnGetNext; // get next function
+ prc_GetNextN_t pfnGetNextN; // get next function, batch version
+ prc_Free_t pfnFree;
+ prc_Mod_t pfnMod;
+
+ bool fok = true;;
+
+ // set up pointers to XXX_Free, XXX_GetNext and XXX_Params functions
+
+ for (i = 0; i < count; i++)
+ {
+ switch (prcs[i].type)
+ {
+ default:
+ case PRC_NULL:
+ pfnFree = &(prc_Free_t)NULL_Free;
+ pfnGetNext = &(prc_GetNext_t)NULL_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)NULL_GetNextN;
+ pfnParam = &NULL_VParams;
+ pfnMod = &(prc_Mod_t)NULL_Mod;
+ break;
+ case PRC_DLY:
+ pfnFree = &(prc_Free_t)DLY_Free;
+ pfnGetNext = &(prc_GetNext_t)DLY_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)DLY_GetNextN;
+ pfnParam = &DLY_VParams;
+ pfnMod = &(prc_Mod_t)DLY_Mod;
+ break;
+ case PRC_RVA:
+ pfnFree = &(prc_Free_t)RVA_Free;
+ pfnGetNext = &(prc_GetNext_t)RVA_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)RVA_GetNextN;
+ pfnParam = &RVA_VParams;
+ pfnMod = &(prc_Mod_t)RVA_Mod;
+ break;
+ case PRC_FLT:
+ pfnFree = &(prc_Free_t)FLT_Free;
+ pfnGetNext = &(prc_GetNext_t)FLT_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)FLT_GetNextN;
+ pfnParam = &FLT_VParams;
+ pfnMod = &(prc_Mod_t)FLT_Mod;
+ break;
+ case PRC_CRS:
+ pfnFree = &(prc_Free_t)CRS_Free;
+ pfnGetNext = &(prc_GetNext_t)CRS_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)CRS_GetNextN;
+ pfnParam = &CRS_VParams;
+ pfnMod = &(prc_Mod_t)CRS_Mod;
+ break;
+ case PRC_PTC:
+ pfnFree = &(prc_Free_t)PTC_Free;
+ pfnGetNext = &(prc_GetNext_t)PTC_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)PTC_GetNextN;
+ pfnParam = &PTC_VParams;
+ pfnMod = &(prc_Mod_t)PTC_Mod;
+ break;
+ case PRC_ENV:
+ pfnFree = &(prc_Free_t)ENV_Free;
+ pfnGetNext = &(prc_GetNext_t)ENV_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)ENV_GetNextN;
+ pfnParam = &ENV_VParams;
+ pfnMod = &(prc_Mod_t)ENV_Mod;
+ break;
+ case PRC_LFO:
+ pfnFree = &(prc_Free_t)LFO_Free;
+ pfnGetNext = &(prc_GetNext_t)LFO_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)LFO_GetNextN;
+ pfnParam = &LFO_VParams;
+ pfnMod = &(prc_Mod_t)LFO_Mod;
+ break;
+ case PRC_EFO:
+ pfnFree = &(prc_Free_t)EFO_Free;
+ pfnGetNext = &(prc_GetNext_t)EFO_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)EFO_GetNextN;
+ pfnParam = &EFO_VParams;
+ pfnMod = &(prc_Mod_t)EFO_Mod;
+ break;
+ case PRC_MDY:
+ pfnFree = &(prc_Free_t)MDY_Free;
+ pfnGetNext = &(prc_GetNext_t)MDY_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)MDY_GetNextN;
+ pfnParam = &MDY_VParams;
+ pfnMod = &(prc_Mod_t)MDY_Mod;
+ break;
+ case PRC_DFR:
+ pfnFree = &(prc_Free_t)DFR_Free;
+ pfnGetNext = &(prc_GetNext_t)DFR_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)DFR_GetNextN;
+ pfnParam = &DFR_VParams;
+ pfnMod = &(prc_Mod_t)DFR_Mod;
+ break;
+ case PRC_AMP:
+ pfnFree = &(prc_Free_t)AMP_Free;
+ pfnGetNext = &(prc_GetNext_t)AMP_GetNext;
+ pfnGetNextN = &(prc_GetNextN_t)AMP_GetNextN;
+ pfnParam = &_VParams;
+ pfnMod = &(prc_Mod_t)AMP_Mod;
+ break;
+ }
+
+ // set up function pointers
+
+ prcs[i].pfnParam = pfnParam;
+ prcs[i].pfnGetNext = pfnGetNext;
+ prcs[i].pfnGetNextN = pfnGetNextN;
+ prcs[i].pfnFree = pfnFree;
+
+ // call param function, store pdata for the processor type
+
+ prcs[i].pdata = pfnParam ( (void *) (&prcs[i]) );
+
+ if ( !prcs[i].pdata )
+ fok = false;
+ }
+
+ return fok;
+}
+
+// free individual processor's data
+
+void PRC_Free ( prc_t *pprc )
+{
+ if ( pprc->pfnFree && pprc->pdata )
+ pprc->pfnFree ( pprc->pdata );
+}
+
+// free all processors for supplied array
+// prcs - array of processors
+// count - elements in array
+
+void PRC_FreeAll ( prc_t *prcs, int count ) { int i; for ( i = 0; i < count; i++) PRC_Free( &prcs[i] ); }
+
+// get next value for processor - (usually called directly by PSET_GetNext)
+
+_inline int PRC_GetNext ( prc_t *pprc, int x )
+{
+ return pprc->pfnGetNext ( pprc->pdata, x );
+}
+
+// automatic parameter range limiting
+// force parameters between specified min/max in param_rng
+
+void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng )
+{
+ // first entry in param_rng is # of parameters
+
+ int i, cprm = prng[0].iprm;
+
+ for ( i = 0; i < cprm; i++)
+ {
+ // if parameter is 0.0, always allow it (this is 'off' for most params)
+
+ if ( pprc->prm[i] != 0.0 && (pprc->prm[i] > prng[i+1].hi || pprc->prm[i] < prng[i+1].lo) )
+ {
+ MsgDev ( D_WARN, "DSP: clamping out of range parameter.\n" );
+ pprc->prm[i] = clamp (pprc->prm[i], prng[i+1].lo, prng[i+1].hi);
+ }
+ }
+}
+
+
+// DSP presets
+
+// A dsp preset comprises one or more dsp processors in linear, parallel or feedback configuration
+
+// preset configurations
+//
+#define PSET_SIMPLE 0
+
+// x(n)--->P(0)--->y(n)
+
+#define PSET_LINEAR 1
+
+// x(n)--->P(0)-->P(1)-->...P(m)--->y(n)
+
+#define PSET_PARALLEL6 4
+
+// x(n)-P(0)-->P(1)-->P(2)-->(+)-P(5)->y(n)
+// | ^
+// | |
+// -->P(3)-->P(4)---->
+
+
+#define PSET_PARALLEL2 5
+
+// x(n)--->P(0)-->(+)-->y(n)
+// ^
+// |
+// x(n)--->P(1)-----
+
+#define PSET_PARALLEL4 6
+
+// x(n)--->P(0)-->P(1)-->(+)-->y(n)
+// ^
+// |
+// x(n)--->P(2)-->P(3)-----
+
+#define PSET_PARALLEL5 7
+
+// x(n)--->P(0)-->P(1)-->(+)-->P(4)-->y(n)
+// ^
+// |
+// x(n)--->P(2)-->P(3)-----
+
+#define PSET_FEEDBACK 8
+
+// x(n)-P(0)--(+)-->P(1)-->P(2)-->P(5)->y(n)
+// ^ |
+// | v
+// -----P(4)<--P(3)--
+
+#define PSET_FEEDBACK3 9
+
+// x(n)---(+)-->P(0)--------->y(n)
+// ^ |
+// | v
+// -----P(2)<--P(1)--
+
+#define PSET_FEEDBACK4 10
+
+// x(n)---(+)-->P(0)-------->P(3)--->y(n)
+// ^ |
+// | v
+// ---P(2)<--P(1)--
+
+#define PSET_MOD 11
+
+//
+// x(n)------>P(1)--P(2)--P(3)--->y(n)
+// ^
+// x(n)------>P(0)....:
+
+#define PSET_MOD2 12
+
+//
+// x(n)-------P(1)-->y(n)
+// ^
+// x(n)-->P(0)..:
+
+
+#define PSET_MOD3 13
+
+//
+// x(n)-------P(1)-->P(2)-->y(n)
+// ^
+// x(n)-->P(0)..:
+
+
+#define CPSETS 64 // max number of presets simultaneously active
+
+#define CPSET_PRCS 6 // max # of processors per dsp preset
+#define CPSET_STATES (CPSET_PRCS+3) // # of internal states
+
+// NOTE: do not reorder members of pset_t - psettemplates relies on it!!!
+
+typedef struct pset_s
+{
+ int type; // preset configuration type
+ int cprcs; // number of processors for this preset
+ prc_t prcs[CPSET_PRCS]; // processor preset data
+ float gain; // preset gain 0.1->2.0
+ int w[CPSET_STATES]; // internal states
+ int fused;
+} pset_t;
+
+pset_t psets[CPSETS];
+
+// array of dsp presets, each with up to 6 processors per preset
+
+#define WZERO {0,0,0,0,0,0,0,0,0},0
+
+pset_t psettemplates[] =
+{
+// presets 0-29 map to legacy room_type 0-29
+
+// type # proc P0 P1 P2 P3 P4 P5 GAIN
+{PSET_SIMPLE, 1, { PRC_NULL1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // OFF 0
+
+{PSET_SIMPLE, 1, { PRC_RVA18, PRC0, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // GENERIC 1 // general, low reflective, diffuse room
+
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA3, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // METALIC_S 2 // highly reflective, parallel surfaces
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA4, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // METALIC_M 3
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA5, PRC0, PRC0, PRC0, PRC0 },1.4, WZERO }, // METALIC_L 4
+
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA6, PRC0, PRC0, PRC0, PRC0 },2.0, WZERO }, // TUNNEL_S 5 // resonant reflective, long surfaces
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA7, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // TUNNEL_M 6
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA8, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // TUNNEL_L 7
+
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA12, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // CHAMBER_S 8 // diffuse, moderately reflective surfaces
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA13, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // CHAMBER_M 9
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA14, PRC0, PRC0, PRC0, PRC0 },1.9, WZERO }, // CHAMBER_L 10
+
+{PSET_SIMPLE, 1, { PRC_RVA15, PRC0, PRC0, PRC0, PRC0, PRC0 },1.5, WZERO }, // BRITE_S 11 // diffuse, highly reflective
+{PSET_SIMPLE, 1, { PRC_RVA16, PRC0, PRC0, PRC0, PRC0, PRC0 },1.6, WZERO }, // BRITE_M 12
+{PSET_SIMPLE, 1, { PRC_RVA17, PRC0, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // BRITE_L 13
+
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA22, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WATER1 14 // underwater fx
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA23, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WATER2 15
+{PSET_LINEAR, 3, { PRC_DFR1, PRC_RVA24, PRC_MDY5, PRC0, PRC0, PRC0 },1.8, WZERO }, // WATER3 16
+
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA19, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // CONCRTE_S 17 // bare, reflective, parallel surfaces
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA20, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // CONCRTE_M 18
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA21, PRC0, PRC0, PRC0, PRC0 },1.9, WZERO }, // CONCRTE_L 19
+
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_DLY3, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // OUTSIDE1 20 // echoing, moderately reflective
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_DLY4, PRC0, PRC0, PRC0, PRC0 },1.7, WZERO }, // OUTSIDE2 21 // echoing, dull
+{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_DLY5, PRC0, PRC0, PRC0 },1.6, WZERO }, // OUTSIDE3 22 // echoing, very dull
+
+{PSET_LINEAR, 2, { PRC_DLY10, PRC_RVA10, PRC0, PRC0, PRC0, PRC0 },2.8, WZERO }, // CAVERN_S 23 // large, echoing area
+{PSET_LINEAR, 2, { PRC_DLY11, PRC_RVA10, PRC0, PRC0, PRC0, PRC0 },2.6, WZERO }, // CAVERN_M 24
+{PSET_LINEAR, 3, { PRC_DFR1, PRC_DLY12, PRC_RVA11, PRC0, PRC0, PRC0 },2.6, WZERO }, // CAVERN_L 25
+
+{PSET_LINEAR, 2, { PRC_DLY7, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },2.0, WZERO }, // WEIRDO1 26
+{PSET_LINEAR, 2, { PRC_DLY8, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },1.9, WZERO }, // WEIRDO2 27
+{PSET_LINEAR, 2, { PRC_DLY9, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WEIRDO3 28
+{PSET_LINEAR, 2, { PRC_DLY9, PRC_DFR1, PRC0, PRC0, PRC0, PRC0 },1.8, WZERO }, // WEIRDO4 29
+
+// presets 30-40 are new presets
+
+{PSET_SIMPLE, 1, { PRC_FLT2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 30 lowpass - facing away
+{PSET_LINEAR, 2, { PRC_FLT3, PRC_DLY14, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 31 lowpass - facing away+80ms delay
+
+//{PSET_PARALLEL2,2, { PRC_AMP6, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 32 explosion ring 1
+//{PSET_PARALLEL2,2, { PRC_AMP7, PRC_LFO3, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 33 explosion ring 2
+//{PSET_PARALLEL2,2, { PRC_AMP8, PRC_LFO4, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 34 explosion ring 3
+{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_FLT3, PRC0, PRC0, PRC0 },0.25, WZERO }, // 32 explosion ring
+{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_FLT3, PRC0, PRC0, PRC0 },0.25, WZERO }, // 33 explosion ring 2
+{PSET_LINEAR, 3, { PRC_DFR1, PRC_DFR1, PRC_FLT3, PRC0, PRC0, PRC0 },0.25, WZERO }, // 34 explosion ring 3
+
+{PSET_PARALLEL2,2, { PRC_DFR1, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },0.25, WZERO }, // 35 shock muffle 1
+{PSET_PARALLEL2,2, { PRC_DFR1, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },0.25, WZERO }, // 36 shock muffle 2
+{PSET_PARALLEL2,2, { PRC_DFR1, PRC_LFO2, PRC0, PRC0, PRC0, PRC0 },0.25, WZERO }, // 37 shock muffle 3
+//{PSET_LINEAR, 3, { PRC_DFR1, PRC_LFO4, PRC_FLT3, PRC0, PRC0, PRC0 },1.0, WZERO }, // 35 shock muffle 1
+//{PSET_LINEAR, 3, { PRC_DFR1, PRC_LFO4, PRC_FLT3, PRC0, PRC0, PRC0 },1.0, WZERO }, // 36 shock muffle 2
+//{PSET_LINEAR, 3, { PRC_DFR1, PRC_LFO4, PRC_FLT3, PRC0, PRC0, PRC0 },1.0, WZERO }, // 37 shock muffle 3
+
+{PSET_FEEDBACK3,3, { PRC_DLY13, PRC_PTC4, PRC_FLT2, PRC0, PRC0, PRC0 },0.25, WZERO }, // 38 fade pitchdown 1
+{PSET_LINEAR, 3, { PRC_AMP3, PRC_FLT5, PRC_FLT6, PRC0, PRC0, PRC0 },2.0, WZERO }, // 39 distorted speaker 1
+
+// fade out fade in
+
+// presets 40+ are test presets
+
+{PSET_SIMPLE, 1, { PRC_NULL1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 39 null
+{PSET_SIMPLE, 1, { PRC_DLY1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 40 delay
+{PSET_SIMPLE, 1, { PRC_RVA1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 41 parallel reverb
+{PSET_SIMPLE, 1, { PRC_DFR1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 42 series diffusor
+{PSET_LINEAR, 2, { PRC_DFR1, PRC_RVA1, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 43 diff & reverb
+{PSET_SIMPLE, 1, { PRC_DLY2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 44 lowpass delay
+{PSET_SIMPLE, 1, { PRC_MDY2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 45 modulating delay
+
+{PSET_SIMPLE, 1, { PRC_PTC1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 46 pitch shift
+{PSET_SIMPLE, 1, { PRC_PTC2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 47 pitch shift
+{PSET_SIMPLE, 1, { PRC_FLT1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 48 filter
+{PSET_SIMPLE, 1, { PRC_CRS1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 49 chorus
+{PSET_SIMPLE, 1, { PRC_ENV1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 50
+{PSET_SIMPLE, 1, { PRC_LFO1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 51 lfo
+{PSET_SIMPLE, 1, { PRC_EFO1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 52
+{PSET_SIMPLE, 1, { PRC_MDY1, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 53 modulating delay
+
+{PSET_SIMPLE, 1, { PRC_FLT2, PRC0, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 54 lowpass - facing away
+{PSET_PARALLEL2, 2, { PRC_PTC2, PRC_PTC1, PRC0, PRC0, PRC0, PRC0 },1.0, WZERO }, // 55 ptc1/ptc2
+{PSET_FEEDBACK, 6, { PRC_DLY1, PRC0, PRC0, PRC_PTC1, PRC_FLT1, PRC0 },1.0, WZERO }, // 56 dly/ptc1
+{PSET_MOD, 4, { PRC_EFO1, PRC0, PRC_PTC1, PRC0, PRC0, PRC0 },1.0, WZERO }, // 57 efo mod ptc
+{PSET_LINEAR, 3, { PRC_DLY1, PRC_RVA1, PRC_CRS1, PRC0, PRC0, PRC0 },1.0, WZERO } // 58 dly/rvb/crs
+};
+
+
+// number of presets currently defined above
+
+#define CPSETTEMPLATES (sizeof(psets)/sizeof(pset_t))
+
+
+void PSET_Init( pset_t *ppset ) { if( ppset ) Mem_Set( ppset->w, 0, sizeof( int ) * ( CPSET_STATES )); } // init a preset - just clear state array
+void PSET_InitAll( void ) { int i; for( i = 0; i < CPSETS; i++ ) Mem_Set( &psets[i], 0, sizeof( pset_t )); } // clear runtime slots
+
+// free the preset - free all processors
+
+void PSET_Free( pset_t *ppset )
+{
+ if( ppset )
+ {
+ // free processors
+ PRC_FreeAll ( ppset->prcs, ppset->cprcs );
+
+ // clear
+ Mem_Set( ppset, 0, sizeof (pset_t));
+ }
+}
+
+void PSET_FreeAll() { int i; for ( i = 0; i < CPSETS; i++) PSET_Free( &psets[i] ); };
+
+// return preset struct, given index into preset template array
+// NOTE: should not ever be more than 2 or 3 of these active simultaneously
+
+pset_t * PSET_Alloc ( int ipsettemplate )
+{
+ pset_t *ppset;
+ bool fok;
+ int i;
+
+ // don't excede array bounds
+ if( ipsettemplate >= CPSETTEMPLATES )
+ ipsettemplate = 0;
+
+ // find free slot
+ for( i = 0; i < CPSETS; i++)
+ {
+ if ( !psets[i].fused )
+ break;
+ }
+
+ if( i == CPSETS )
+ return NULL;
+
+ ppset = &psets[i];
+
+ // copy template into preset
+
+ *ppset = psettemplates[ipsettemplate];
+
+ ppset->fused = true;
+
+ // clear state array
+
+ PSET_Init ( ppset );
+
+ // init all processors, set up processor function pointers
+
+ fok = PRC_InitAll( ppset->prcs, ppset->cprcs );
+
+ if ( !fok )
+ {
+ // failed to init one or more processors
+ MsgDev( D_ERROR, "Sound DSP: preset failed to init.\n" );
+ PRC_FreeAll ( ppset->prcs, ppset->cprcs );
+ return NULL;
+ }
+
+ return ppset;
+}
+
+// batch version of PSET_GetNext for linear array of processors. For performance.
+
+// ppset - preset array
+// pbuffer - input sample data
+// SampleCount - size of input buffer
+// OP: OP_LEFT - process left channel in place
+// OP_RIGHT - process right channel in place
+// OP_LEFT_DUPLICATe - process left channel, duplicate into right
+
+_inline void PSET_GetNextN( pset_t *ppset, samplepair_t *pbuffer, int SampleCount, int op )
+{
+ samplepair_t *pbf = pbuffer;
+ int i, count = ppset->cprcs;
+ prc_t *pprc;
+
+ switch ( ppset->type )
+ {
+ default:
+ case PSET_SIMPLE:
+ {
+ // x(n)--->P(0)--->y(n)
+
+ ppset->prcs[0].pfnGetNextN (ppset->prcs[0].pdata, pbf, SampleCount, op);
+ return;
+ }
+ case PSET_LINEAR:
+ {
+
+ // w0 w1 w2
+ // x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n)
+
+ // w0 w1 w2 w3 w4 w5
+ // x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n)
+
+ // call batch processors in sequence - no internal state for batch processing
+
+ // point to first processor
+
+ pprc = &ppset->prcs[0];
+
+ for ( i = 0; i < count; i++)
+ {
+ pprc->pfnGetNextN (pprc->pdata, pbf, SampleCount, op);
+ pprc++;
+ }
+
+ return;
+ }
+ }
+}
+
+
+// Get next sample from this preset. called once for every sample in buffer
+// ppset is pointer to preset
+// x is input sample
+
+_inline int PSET_GetNext ( pset_t *ppset, int x )
+{
+ int *w = ppset->w;
+ prc_t *pprc;
+ int count = ppset->cprcs;
+
+ // initialized 0'th element of state array
+
+ w[0] = x;
+
+ switch ( ppset->type )
+ {
+ default:
+ case PSET_SIMPLE:
+ {
+ // x(n)--->P(0)--->y(n)
+
+ return ppset->prcs[0].pfnGetNext (ppset->prcs[0].pdata, x);
+ }
+ case PSET_LINEAR:
+ {
+ // w0 w1 w2
+ // x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n)
+
+ // w0 w1 w2 w3 w4 w5
+ // x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n)
+
+ // call processors in reverse order, from count to 1
+
+ // point to last processor
+
+ pprc = &ppset->prcs[count-1];
+
+ switch (count)
+ {
+ default:
+ case 5:
+ w[5] = pprc->pfnGetNext (pprc->pdata, w[4]);
+ pprc--;
+ case 4:
+ w[4] = pprc->pfnGetNext (pprc->pdata, w[3]);
+ pprc--;
+ case 3:
+ w[3] = pprc->pfnGetNext (pprc->pdata, w[2]);
+ pprc--;
+ case 2:
+ w[2] = pprc->pfnGetNext (pprc->pdata, w[1]);
+ pprc--;
+ case 1:
+ w[1] = pprc->pfnGetNext (pprc->pdata, w[0]);
+ }
+
+ //for (int i = count; i > 0; i--, pprc--)
+ // w[i] = pprc->pfnGetNext (pprc->pdata, w[i-1]);
+
+ return w[count];
+ }
+
+ case PSET_PARALLEL6:
+ {
+ // w0 w1 w2 w3 w6 w7
+ // x(n)-P(0)-->P(1)-->P(2)-->(+)---P(5)--->y(n)
+ // | ^
+ // | w4 w5 |
+ // -->P(3)-->P(4)---->
+
+ pprc = &ppset->prcs[0];
+
+ // start with all adders
+
+ w[6] = w[3] + w[5];
+
+ // top branch - evaluate in reverse order
+
+ w[7] = pprc[5].pfnGetNext( pprc[5].pdata, w[6] );
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[2] );
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
+
+ // bottom branch - evaluate in reverse order
+
+ w[5] = pprc[4].pfnGetNext( pprc[4].pdata, w[4] );
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[1] );
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ return w[7];
+ }
+ case PSET_PARALLEL2:
+ { // w0 w1 w3
+ // x(n)--->P(0)-->(+)-->y(n)
+ // ^
+ // w0 w2 |
+ // x(n)--->P(1)-----
+
+ pprc = &ppset->prcs[0];
+
+ w[3] = w[1] + w[2];
+
+ w[1] = pprc->pfnGetNext( pprc->pdata, w[0] );
+ pprc++;
+ w[2] = pprc->pfnGetNext( pprc->pdata, w[0] );
+
+ return w[3];
+ }
+
+ case PSET_PARALLEL4:
+ { // w0 w1 w2 w5
+ // x(n)--->P(0)-->P(1)-->(+)-->y(n)
+ // ^
+ // w0 w3 w4 |
+ // x(n)--->P(2)-->P(3)-----
+
+
+ pprc = &ppset->prcs[0];
+
+ w[5] = w[2] + w[4];
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] );
+
+ return w[5];
+ }
+
+ case PSET_PARALLEL5:
+ { // w0 w1 w2 w5 w6
+ // x(n)--->P(0)-->P(1)-->(+)-->P(4)-->y(n)
+ // ^
+ // w0 w3 w4 |
+ // x(n)--->P(2)-->P(3)-----
+
+ pprc = &ppset->prcs[0];
+
+ w[5] = w[2] + w[4];
+
+ w[6] = pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] );
+
+ return w[6];
+ }
+
+ case PSET_FEEDBACK:
+ {
+ // w0 w1 w2 w3 w4 w7
+ // x(n)-P(0)--(+)-->P(1)-->P(2)-->P(5)->y(n)
+ // ^ |
+ // | w6 w5 v
+ // -----P(4)<--P(3)--
+
+ pprc = &ppset->prcs[0];
+
+ // start with adders
+
+ w[2] = w[1] + w[6];
+
+ // evaluate in reverse order
+
+ w[7] = pprc[5].pfnGetNext( pprc[5].pdata, w[4] );
+ w[6] = pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
+ w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[4] );
+ w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
+ w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ return w[7];
+ }
+ case PSET_FEEDBACK3:
+ {
+ // w0 w1 w2
+ // x(n)---(+)-->P(0)--------->y(n)
+ // ^ |
+ // | w4 w3 v
+ // -----P(2)<--P(1)--
+
+ pprc = &ppset->prcs[0];
+
+ // start with adders
+
+ w[1] = w[0] + w[4];
+
+ // evaluate in reverse order
+
+ w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
+ w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
+ w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] );
+
+ return w[2];
+ }
+ case PSET_FEEDBACK4:
+ {
+ // w0 w1 w2 w5
+ // x(n)---(+)-->P(0)-------->P(3)--->y(n)
+ // ^ |
+ // | w4 w3 v
+ // ---P(2)<--P(1)--
+
+ pprc = &ppset->prcs[0];
+
+ // start with adders
+
+ w[1] = w[0] + w[4];
+
+ // evaluate in reverse order
+
+ w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[2] );
+ w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
+ w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
+ w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] );
+
+ return w[2];
+ }
+ case PSET_MOD:
+ {
+ // w0 w1 w3 w4
+ // x(n)------>P(1)--P(2)--P(3)--->y(n)
+ // w0 w2 ^
+ // x(n)------>P(0)....:
+
+ pprc = &ppset->prcs[0];
+
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
+
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[1] );
+
+ // modulate processor 2
+
+ pprc[2].pfnMod( pprc[2].pdata, ((float)w[2] / (float)PMAX));
+
+ // get modulator output
+
+ w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ w[1] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
+
+ return w[4];
+ }
+ case PSET_MOD2:
+ {
+ // w0 w2
+ // x(n)---------P(1)-->y(n)
+ // w0 w1 ^
+ // x(n)-->P(0)....:
+
+ pprc = &ppset->prcs[0];
+
+ // modulate processor 1
+
+ pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX));
+
+ // get modulator output
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
+
+ return w[2];
+
+ }
+ case PSET_MOD3:
+ {
+ // w0 w2 w3
+ // x(n)----------P(1)-->P(2)-->y(n)
+ // w0 w1 ^
+ // x(n)-->P(0).....:
+
+ pprc = &ppset->prcs[0];
+
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[2] );
+
+ // modulate processor 1
+
+ pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX));
+
+ // get modulator output
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
+
+ return w[2];
+ }
+ }
+}
+
+
+/////////////
+// DSP system
+/////////////
+
+// Main interface
+
+// Whenever the preset # changes on any of these processors, the old processor is faded out, new is faded in.
+// dsp_chan is optionally set when a sound is played - a preset is sent with the start_static/dynamic sound.
+//
+// sound1---->dsp_chan--> -------------(+)---->dsp_water--->dsp_player--->out
+// sound2---->dsp_chan--> | |
+// sound3---------------> ----dsp_room---
+// | |
+// --dsp_indirect-
+
+// dsp_room - set this cvar to a preset # to change the room dsp. room fx are more prevalent farther from player.
+// use: when player moves into a new room, all sounds played in room take on its reverberant character
+// dsp_water - set this cvar (once) to a preset # for serial underwater sound.
+// use: when player goes under water, all sounds pass through this dsp (such as low pass filter)
+// dsp_player - set this cvar to a preset # to cause all sounds to run through the effect (serial, in-line).
+// use: player is deafened, player fires special weapon, player is hit by special weapon.
+// dsp_facingaway- set this cvar to a preset # appropriate for sounds which are played facing away from player (weapon,voice)
+
+// Dsp presets
+
+cvar_t *dsp_room; // room dsp preset - sounds more distant from player (1ch)
+cvar_t *dsp_water; // "15" underwater dsp preset - sound when underwater (1-2ch)
+cvar_t *dsp_player; // dsp on player - sound when player hit by special device (1-2ch)
+cvar_t *dsp_facingaway; // "30" sounds that face away from player (weapons, voice) (1-4ch)
+
+int ipset_room_prev;
+int ipset_water_prev;
+int ipset_player_prev;
+int ipset_facingaway_prev;
+
+// legacy room_type support
+cvar_t *dsp_room_type;
+int ipset_room_typeprev;
+
+// DSP processors
+int idsp_room;
+int idsp_water;
+int idsp_player;
+int idsp_facingaway;
+
+// set to "1" to deactivate all dsp processing
+cvar_t *dsp_off; // set to 1 to disable all dsp processing
+cvar_t *snd_profile; // 1 - profile dsp, 2 - mix, 3 - load sound, 4 - all sound
+cvar_t *dsp_stereo; // set to 1 for true stereo processing. 2x perf hit.
+
+// DSP preset executor
+#define CDSPS 32 // max number dsp executors active
+#define DSPCHANMAX 4 // max number of channels dsp can process (allocs a separte processor for each chan)
+
+typedef struct dsp_s
+{
+ bool fused;
+ int cchan; // 1-4 channels, ie: mono, FrontLeft, FrontRight, RearLeft, RearRight
+ pset_t *ppset[DSPCHANMAX]; // current preset (1-4 channels)
+ int ipset; // current ipreset
+ pset_t *ppsetprev[DSPCHANMAX]; // previous preset (1-4 channels)
+ int ipsetprev; // previous ipreset
+ float xfade; // crossfade time between previous preset and new
+ rmp_t xramp; // crossfade ramp
+} dsp_t;
+
+dsp_t dsps[CDSPS];
+
+void DSP_Init( int idsp )
+{
+ dsp_t *pdsp;
+
+ if (idsp < 0 || idsp > CDSPS)
+ return;
+
+ pdsp = &dsps[idsp];
+
+ Mem_Set( pdsp, 0, sizeof (dsp_t) );
+}
+
+void DSP_Free( int idsp )
+{
+ dsp_t *pdsp;
+ int i;
+
+ if( idsp < 0 || idsp > CDSPS )
+ return;
+
+ pdsp = &dsps[idsp];
+
+ for( i = 0; i < pdsp->cchan; i++ )
+ {
+ if( pdsp->ppset[i] )
+ PSET_Free( pdsp->ppset[i] );
+
+ if( pdsp->ppsetprev[i] )
+ PSET_Free( pdsp->ppsetprev[i] );
+ }
+ Mem_Set( pdsp, 0, sizeof (dsp_t) );
+}
+
+// Init all dsp processors - called once, during engine startup
+void DSP_InitAll ( void )
+{
+ int idsp;
+
+ // initalize our variables
+ dsp_room = Cvar_Get( "dsp_room", "0", 0, "room dsp preset - sounds more distant from player (1ch)" );
+ dsp_water = Cvar_Get( "dsp_water", "0", 0, "\"15\" underwater dsp preset - sound when underwater (1-2ch)" );
+ dsp_player = Cvar_Get( "dsp_player", "0", 0, "dsp on player - sound when player hit by special device (1-2ch)" );
+ dsp_facingaway = Cvar_Get( "dsp_facingaway", "0", 0, "\"30\" sounds that face away from player (weapons, voice) (1-4ch)" );
+ dsp_room_type = Cvar_Get( "room_type", "0", 0, "Half-Life 'room_type' preset" );
+ dsp_off = Cvar_Get( "dsp_off", "0", 0, "set to 1 to disable all dsp processing" );
+ snd_profile = Cvar_Get( "snd_profile", "0", 0, "1 - profile dsp, 2 - mix, 3 - load sound, 4 - all sound" );
+ dsp_stereo = Cvar_Get( "dsp_stereo", "0", CVAR_ARCHIVE, "set to 1 for true stereo processing. 2x perf hit" );
+
+ // order is important, don't rearange.
+ FLT_InitAll();
+ DLY_InitAll();
+ RVA_InitAll();
+ LFOWAV_InitAll();
+ LFO_InitAll();
+
+ CRS_InitAll();
+ PTC_InitAll();
+ ENV_InitAll();
+ EFO_InitAll();
+ MDY_InitAll();
+ AMP_InitAll();
+
+ PSET_InitAll();
+
+ for( idsp = 0; idsp < CDSPS; idsp++)
+ DSP_Init( idsp );
+}
+
+// free all resources associated with dsp - called once, during engine shutdown
+
+void DSP_FreeAll( void )
+{
+ int idsp;
+
+ // order is important, don't rearange.
+ for( idsp = 0; idsp < CDSPS; idsp++ )
+ DSP_Free( idsp );
+
+ AMP_FreeAll();
+ MDY_FreeAll();
+ EFO_FreeAll();
+ ENV_FreeAll();
+ PTC_FreeAll();
+ CRS_FreeAll();
+
+ LFO_FreeAll();
+ LFOWAV_FreeAll();
+ RVA_FreeAll();
+ DLY_FreeAll();
+ FLT_FreeAll();
+}
+
+
+// allocate a new dsp processor chain, kill the old processor. Called by DSP_CheckNewPreset()
+// ipset is new preset
+// xfade is crossfade time when switching between presets (milliseconds)
+// cchan is how many simultaneous preset channels to allocate (1-4)
+// return index to new dsp
+int DSP_Alloc( int ipset, float xfade, int cchan )
+{
+ dsp_t *pdsp;
+ int i;
+ int idsp;
+ int cchans = clamp( cchan, 1, DSPCHANMAX);
+
+ // find free slot
+
+ for ( idsp = 0; idsp < CDSPS; idsp++ )
+ {
+ if ( !dsps[idsp].fused )
+ break;
+ }
+
+ if ( idsp == CDSPS )
+ return -1;
+
+ pdsp = &dsps[idsp];
+
+ DSP_Init ( idsp );
+
+ pdsp->fused = true;
+
+ pdsp->cchan = cchans;
+
+ // allocate a preset processor for each channel
+
+ pdsp->ipset = ipset;
+ pdsp->ipsetprev = 0;
+
+ for (i = 0; i < pdsp->cchan; i++)
+ {
+ pdsp->ppset[i] = PSET_Alloc ( ipset );
+ pdsp->ppsetprev[i] = NULL;
+ }
+
+ // set up crossfade time in seconds
+
+ pdsp->xfade = xfade / 1000.0;
+
+ RMP_SetEnd(&pdsp->xramp);
+
+ return idsp;
+}
+
+// return gain for current preset associated with dsp
+// get crossfade to new gain if switching from previous preset (from preset crossfader value)
+// Returns 1.0 gain if no preset (preset 0)
+float DSP_GetGain( int idsp )
+{
+ float gain_target = 0.0;
+ float gain_prev = 0.0;
+ float gain;
+ dsp_t *pdsp;
+ int r;
+
+ if( idsp < 0 || idsp > CDSPS )
+ return 1.0;
+
+ pdsp = &dsps[idsp];
+
+ // get current preset's gain
+ if( pdsp->ppset[0] )
+ gain_target = pdsp->ppset[0]->gain;
+ else gain_target = 1.0;
+
+ // if not crossfading, return current preset gain
+ if( RMP_HitEnd( &pdsp->xramp ))
+ {
+ // return current preset's gain
+ return gain_target;
+ }
+
+ // get previous preset gain
+
+ if( pdsp->ppsetprev[0] )
+ gain_prev = pdsp->ppsetprev[0]->gain;
+ else gain_prev = 1.0;
+
+ // if current gain = target preset gain, return
+
+ if( gain_target == gain_prev )
+ {
+ if( gain_target == 0.0 )
+ return 1.0;
+ return gain_target;
+ }
+
+ // get crossfade ramp value (updated elsewhere, when actually crossfading preset data)
+ r = RMP_GetCurrent( &pdsp->xramp );
+
+ // crossfade from previous to current preset gain
+ if( gain_target > gain_prev )
+ {
+ // ramping gain up - ramp up gain to target in last 10% of ramp
+
+ float rf = (float) r;
+ float pmax = (float)PMAX;
+
+ rf = rf / pmax; // rf 0->1.0
+
+ if ( rf < 0.9)
+ rf = 0.0;
+ else
+ rf = (rf - 0.9) / (1.0 - 0.9); // 0->1.0 after rf > 0.9
+
+ // crossfade gain from prev to target over rf
+
+ gain = gain_prev + (gain_target - gain_prev) * rf;
+
+ return gain;
+ }
+ else
+ {
+ // ramping gain down - drop gain to target in first 10% of ramp
+
+ float rf = (float) r;
+ float pmax = (float)PMAX;
+
+ rf = rf / pmax; // rf 0.0->1.0
+
+ if ( rf < 0.1)
+ rf = (rf - 0.1) / (0.0 - 0.1); // 1.0->0.0 if rf < 0.1
+ else
+ rf = 0.0;
+
+ // crossfade gain from prev to target over rf
+
+ gain = gain_prev + (gain_target - gain_prev) * (1.0 - rf);
+
+ return gain;
+ }
+}
+
+// free previous preset if not 0
+_inline void DSP_FreePrevPreset( dsp_t *pdsp )
+{
+ // free previous presets if non-null - ie: rapid change of preset just kills old without xfade
+ if( pdsp->ipsetprev )
+ {
+ int i;
+
+ for( i = 0; i < pdsp->cchan; i++)
+ {
+ if( pdsp->ppsetprev[i] )
+ {
+ PSET_Free( pdsp->ppsetprev[i] );
+ pdsp->ppsetprev[i] = NULL;
+ }
+ }
+ pdsp->ipsetprev = 0;
+ }
+}
+
+
+// alloc new preset if different from current
+// xfade from prev to new preset
+// free previous preset, copy current into previous, set up xfade from previous to new
+void DSP_SetPreset( int idsp, int ipsetnew )
+{
+ dsp_t *pdsp;
+ pset_t *ppsetnew[DSPCHANMAX];
+ int i;
+
+ Assert( idsp >= 0 && idsp < CDSPS );
+
+ pdsp = &dsps[idsp];
+
+ // validate new preset range
+ if( ipsetnew >= CPSETTEMPLATES || ipsetnew < 0 )
+ return;
+
+ // ignore if new preset is same as current preset
+ if( ipsetnew == pdsp->ipset )
+ return;
+
+ // alloc new presets (each channel is a duplicate preset)
+ Assert( pdsp->cchan <= DSPCHANMAX );
+
+ for( i = 0; i < pdsp->cchan; i++ )
+ {
+ ppsetnew[i] = PSET_Alloc( ipsetnew );
+ if( !ppsetnew[i] )
+ {
+ MsgDev( D_WARN, "DSP preset failed to allocate.\n" );
+ return;
+ }
+ }
+
+ Assert( pdsp );
+
+ // free PREVIOUS previous preset if not 0
+ DSP_FreePrevPreset( pdsp );
+
+ for( i = 0; i < pdsp->cchan; i++ )
+ {
+ // current becomes previous
+
+ pdsp->ppsetprev[i] = pdsp->ppset[i];
+
+ // new becomes current
+
+ pdsp->ppset[i] = ppsetnew[i];
+ }
+
+ pdsp->ipsetprev = pdsp->ipset;
+ pdsp->ipset = ipsetnew;
+
+ // clear ramp
+ RMP_SetEnd( &pdsp->xramp );
+
+ // make sure previous dsp preset has data
+ Assert( pdsp->ppsetprev[0] );
+
+ // shouldn't be crossfading if current dsp preset == previous dsp preset
+ Assert( pdsp->ipset != pdsp->ipsetprev );
+
+ RMP_Init( &pdsp->xramp, pdsp->xfade, 0, PMAX );
+}
+
+///////////////////////////////////////
+// Helpers: called only from DSP_Process
+///////////////////////////////////////
+
+// return true if batch processing version of preset exists
+_inline bool FBatchPreset( pset_t *ppset )
+{
+
+ switch( ppset->type )
+ {
+ case PSET_LINEAR:
+ return true;
+ case PSET_SIMPLE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Helper: called only from DSP_Process
+// mix front stereo buffer to mono buffer, apply dsp fx
+
+_inline void DSP_ProcessStereoToMono( dsp_t *pdsp, samplepair_t *pbfront, int sampleCount, bool bcrossfading )
+{
+ samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ int count = sampleCount;
+ int av;
+ int x;
+
+ if ( !bcrossfading )
+ {
+ if (FBatchPreset(pdsp->ppset[0]))
+ {
+ // convert Stereo to Mono in place, then batch process fx: perf KDB
+
+ // front->left + front->right / 2 into front->left, front->right duplicated.
+
+ while ( count-- )
+ {
+ pbf->left = (pbf->left + pbf->right) >> 1;
+ pbf++;
+ }
+
+ // process left (mono), duplicate output into right
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
+ }
+ else
+ {
+ // avg left and right -> mono fx -> duplcate out left and right
+ while ( count-- )
+ {
+ av = ( ( pbf->left + pbf->right ) >> 1 );
+ x = PSET_GetNext( pdsp->ppset[0], av );
+ x = CLIP_DSP( x );
+ pbf->left = pbf->right = x;
+ pbf++;
+ }
+ }
+ return;
+ }
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r = -1;
+ int fl;
+ int flp;
+ int xf_fl;
+
+ while ( count-- )
+ {
+ av = ( ( pbf->left + pbf->right ) >> 1 );
+
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], av );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+
+ fl = CLIP_DSP(fl);
+ flp = CLIP_DSP(flp);
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+
+ pbf->left = xf_fl; // crossfaded front left, duplicate in right channel
+ pbf->right = xf_fl;
+
+ pbf++;
+
+ }
+
+ }
+}
+
+// Helper: called only from DSP_Process
+// DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
+
+_inline void DSP_ProcessStereoToStereo(dsp_t *pdsp, samplepair_t *pbfront, int sampleCount, bool bcrossfading )
+{
+ samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ int count = sampleCount;
+ int fl, fr;
+
+ if( !bcrossfading )
+ {
+ if( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) )
+ {
+
+ // process left & right
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT );
+ PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
+ }
+ else
+ {
+ // left -> left fx, right -> right fx
+ while ( count-- )
+ {
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+
+ fl = CLIP_DSP( fl );
+ fr = CLIP_DSP( fr );
+
+ pbf->left = fl;
+ pbf->right = fr;
+ pbf++;
+ }
+ }
+ return;
+ }
+
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r;
+ int flp, frp;
+ int xf_fl, xf_fr;
+
+ while ( count-- )
+ {
+ // get current preset values
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
+ frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
+
+ // get current ramp value
+ r = RMP_GetNext( &pdsp->xramp );
+
+ fl = CLIP_DSP( fl );
+ fr = CLIP_DSP( fr );
+ flp = CLIP_DSP( flp );
+ frp = CLIP_DSP( frp );
+
+ // crossfade from previous to current preset
+
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r);
+
+ pbf->left = xf_fl; // crossfaded front left
+ pbf->right = xf_fr;
+
+ pbf++;
+ }
+ }
+}
+
+void DSP_ClearState( void )
+{
+ Cvar_SetValue( "dsp_room", 0 );
+ Cvar_SetValue( "dsp_water", 0 );
+ Cvar_SetValue( "dsp_player", 0 );
+ Cvar_SetValue( "dsp_facingaway", 0 );
+
+ CheckNewDspPresets();
+
+ dsps[0].xramp.fhitend = true; // don't crossfade;
+}
+
+// Main DSP processing routine:
+// process samples in buffers using pdsp processor
+// continue crossfade between 2 dsp processors if crossfading on switch
+// pfront - front stereo buffer to process
+// prear - rear stereo buffer to process (may be NULL)
+// sampleCount - number of samples in pbuf to process
+// This routine also maps the # processing channels in the pdsp to the number of channels
+// supplied. ie: if the pdsp has 4 channels and pbfront and pbrear are both non-null, the channels
+// map 1:1 through the processors.
+
+void DSP_Process( int idsp, samplepair_t *pbfront, int sampleCount )
+{
+ bool bcrossfading;
+ int cchan_in; // input channels (2 or 4)
+ int cprocs; // output cannels (1, 2 or 4)
+ dsp_t *pdsp;
+
+ if( idsp < 0 || idsp >= CDSPS )
+ return;
+
+ Assert( idsp < CDSPS ); // make sure idsp is valid
+
+ pdsp = &dsps[idsp];
+
+ // if current and previous preset 0, return - preset 0 is 'off'
+
+ if( !pdsp->ipset && !pdsp->ipsetprev )
+ return;
+
+ Assert( pbfront );
+
+ // return right away if fx processing is turned off
+
+ if( dsp_off->integer )
+ return;
+
+ if( sampleCount < 0 )
+ return;
+
+ bcrossfading = !RMP_HitEnd( &pdsp->xramp );
+
+ // if not crossfading, and previous channel is not null, free previous
+ if( !bcrossfading ) DSP_FreePrevPreset( pdsp );
+
+ cchan_in = 2;
+ cprocs = pdsp->cchan;
+
+ // NOTE: when mixing between different channel sizes,
+ // always AVERAGE down to fewer channels and DUPLICATE up more channels.
+ // The following routines always process cchan_in channels.
+ // ie: QuadToMono still updates 4 values in buffer
+
+ // DSP_Process stereo in to mono out (ie: left and right are averaged)
+
+ if( cchan_in == 2 && cprocs == 1 )
+ {
+ DSP_ProcessStereoToMono( pdsp, pbfront, sampleCount, bcrossfading );
+ return;
+ }
+
+ // DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
+ if( cchan_in == 2 && cprocs >= 2 )
+ {
+ DSP_ProcessStereoToStereo( pdsp, pbfront, sampleCount, bcrossfading );
+ return;
+ }
+}
+
+// DSP helpers
+
+// free all dsp processors
+
+void FreeDsps( void )
+{
+ DSP_Free( idsp_room );
+ DSP_Free( idsp_water );
+ DSP_Free( idsp_player );
+ DSP_Free( idsp_facingaway );
+
+ idsp_room = 0;
+ idsp_water = 0;
+ idsp_player = 0;
+ idsp_facingaway = 0;
+
+ DSP_FreeAll();
+
+ SX_Free ();
+}
+
+// alloc dsp processors
+
+bool AllocDsps( void )
+{
+ DSP_InitAll();
+
+ idsp_room = -1.0;
+ idsp_water = -1.0;
+ idsp_player = -1.0;
+ idsp_facingaway = -1.0;
+
+ // alloc dsp room channel (mono, stereo if dsp_stereo is 1)
+
+ // dsp room is mono, 300ms fade time
+ idsp_room = DSP_Alloc( dsp_room->integer, 300, dsp_stereo->integer * 2 );
+
+ // alloc stereo or quad series processors for player or water
+
+ // UNDONE: performance not yet good enough for quad surround on 1mhz machines
+ // idsp_water = DSP_Alloc( dsp_water->integer, 100, 2 );
+ // idsp_player = DSP_Alloc( dsp_player->integer, 1000, 2 );
+
+ idsp_water = DSP_Alloc( dsp_water->integer, 100, 1 );
+ idsp_player = DSP_Alloc( dsp_player->integer, 1000, 1 );
+
+ // alloc facing away filters (stereo)
+ idsp_facingaway = DSP_Alloc( dsp_facingaway->integer, 200, 2 );
+
+ // init prev values
+ ipset_room_prev = dsp_room->integer;
+ ipset_water_prev = dsp_water->integer;
+ ipset_player_prev = dsp_player->integer;
+ ipset_facingaway_prev = dsp_facingaway->integer;
+ ipset_room_typeprev = dsp_room_type->integer;
+
+ SX_Init ();
+
+ if( idsp_room < 0 || idsp_water < 0 || idsp_player < 0 || idsp_facingaway < 0 )
+ {
+ MsgDev( D_WARN, "DSP processor failed to initialize! \n" );
+ FreeDsps();
+ return false;
+ }
+ return true;
+}
+
+
+// Helper to check for change in preset of any of 4 processors
+// if switching to a new preset, alloc new preset, simulate both presets in DSP_Process & xfade,
+void CheckNewDspPresets( void )
+{
+ int iroom = dsp_room->integer;
+ int iwater = dsp_water->integer;
+ int iplayer = dsp_player->integer;
+ int ifacingaway = dsp_facingaway->integer;
+ int iroomtype = dsp_room_type->integer;
+
+ // legacy code support for "room_type" Cvar
+ if( iroomtype != ipset_room_typeprev )
+ {
+ // force dsp_room = room_type
+ ipset_room_typeprev = iroomtype;
+ Cvar_SetValue( "dsp_room", iroomtype );
+ }
+
+ if( iroom != ipset_room_prev )
+ {
+ DSP_SetPreset( idsp_room, iroom );
+ ipset_room_prev = iroom;
+
+ // force room_type = dsp_room
+ Cvar_SetValue( "room_type", iroom );
+ ipset_room_typeprev = iroom;
+ }
+
+ if( iwater != ipset_water_prev )
+ {
+ DSP_SetPreset( idsp_water, iwater );
+ ipset_water_prev = iwater;
+ }
+
+ if( iplayer != ipset_player_prev )
+ {
+ DSP_SetPreset( idsp_player, iplayer );
+ ipset_player_prev = iplayer;
+ }
+
+ if( ifacingaway != ipset_facingaway_prev )
+ {
+ DSP_SetPreset( idsp_facingaway, ifacingaway );
+ ipset_facingaway_prev = ifacingaway;
+ }
+}
+
+// NEXT: check outputs of modulators - normalize to +/- 0-1.0 and all inputs normalized to same
+// NEXT: performance tune
+// NEXT: rename base routines
+// NEXT: rva may use mda if lfo params set
+// NEXT: perf
+// - chained processor cost high? test with nulls and with release build
+// - all getnext funcitons take samplecount and pbuffer!
+// - use globals to cut down on params to function calls
+// - note: release build is more than 2x faster due to _inline calls
+// - note: mmx for buffer mixing, multiplying etc
+// NEXT: stereoize delays - alternate taps with l/r as if sound is reflecting
+// NEXT: filter only rva output
+
+// NEXT: test ptc, crs, flt, env, efo
+// NEXT: add MDY to RVA
+// NEXT: add clipper/distorter, amplifier, noise generator LFO
+
+// NEXT: stereoize all room sounds using l/r roomsize delays
+// NEXT: stereo delay using l/r/u/d/f/b dimensions of room and wall materials
+// NEXT: spatialize all sounds based on inter-ear time delay (headphone mode)
+// NEXT: filter all sounds based on inter-ear filter (headphone mode)
+
+
+//===============================================================================
+//
+// Digital Signal Processing algorithms for audio FX.
+//
+// KellyB 1/24/97
+//===============================================================================
+#define SXDLY_MAX 0.400 // max delay in seconds
+#define SXRVB_MAX 0.100 // max reverb reflection time
+#define SXSTE_MAX 0.100 // max stereo delay line time
+
+typedef int sample_t; // delay lines must be 32 bit, now that we have 16 bit samples
+
+typedef struct dlyline_s
+{
+ int cdelaysamplesmax; // size of delay line in samples
+ int lp; // lowpass flag 0 = off, 1 = on
+
+ int idelayinput; // i/o indices into circular delay line
+ int idelayoutput;
+ int idelayoutputxf; // crossfade output pointer
+ int xfade; // crossfade value
+
+ int delaysamples; // current delay setting
+ int delayfeed; // current feedback setting
+
+ int lp0, lp1, lp2, lp3, lp4, lp5; // lowpass filter buffer
+
+ int mod; // sample modulation count
+ int modcur;
+ HANDLE hdelayline; // handle to delay line buffer
+ sample_t *lpdelayline; // buffer
+} dlyline_t;
+
+#define CSXDLYMAX 4
+#define ISXMONODLY 0 // mono delay line
+#define ISXRVB 1 // first of the reverb delay lines
+#define CSXRVBMAX 2
+#define ISXSTEREODLY 3 // 50ms left side delay
+
+dlyline_t rgsxdly[CSXDLYMAX]; // array of delay lines
+
+#define gdly0 (rgsxdly[ISXMONODLY])
+#define gdly1 (rgsxdly[ISXRVB])
+#define gdly2 (rgsxdly[ISXRVB + 1])
+#define gdly3 (rgsxdly[ISXSTEREODLY])
+
+#define CSXLPMAX 10 // lowpass filter memory
+
+int rgsxlp[CSXLPMAX];
+int sxamodl, sxamodr; // amplitude modulation values
+int sxamodlt, sxamodrt; // modulation targets
+int sxmod1, sxmod2;
+int sxmod1cur, sxmod2cur;
+
+// Mono Delay parameters
+cvar_t *sxdly_delay; // current delay in seconds
+cvar_t *sxdly_feedback; // cycles
+cvar_t *sxdly_lp; // lowpass filter
+
+float sxdly_delayprev; // previous delay setting value
+
+// Mono Reverb parameters
+cvar_t *sxrvb_size; // room size 0 (off) 0.1 small - 0.35 huge
+cvar_t *sxrvb_feedback; // reverb decay 0.1 short - 0.9 long
+cvar_t *sxrvb_lp; // lowpass filter
+
+float sxrvb_sizeprev;
+
+// stereo delay (no feedback)
+cvar_t *sxste_delay; // straight left delay
+float sxste_delayprev;
+
+// Underwater/special fx modulations
+cvar_t *sxmod_lowpass;
+cvar_t *sxmod_mod;
+
+// Main interface
+cvar_t *sxroom_type; // legacy support
+cvar_t *sxroomwater_type; // legacy support
+float sxroom_typeprev;
+cvar_t *sxroom_off; // legacy support
+
+bool SXDLY_Init( int idelay, float delay );
+void SXDLY_Free( int idelay );
+void SXDLY_DoDelay( int count );
+void SXRVB_DoReverb( int count );
+void SXDLY_DoStereoDelay( int count );
+void SXRVB_DoAMod( int count );
+
+//=====================================================================
+// Init/release all structures for sound effects
+//=====================================================================
+void SX_Init( void )
+{
+ sxdly_delay = Cvar_Get( "room_delay", "0", 0, "current delay in seconds" );
+ sxdly_feedback = Cvar_Get( "room_feedback", "0.2", 0, "cycles" );
+ sxdly_lp = Cvar_Get( "room_dlylp", "1", 0, "lowpass filter" );
+
+ sxrvb_size = Cvar_Get( "room_size", "0", 0, "room size 0 (off) 0.1 small - 0.35 huge" );
+ sxrvb_feedback = Cvar_Get( "room_refl", "0.7", 0, "reverb decay 0.1 short - 0.9 long" );
+ sxrvb_lp = Cvar_Get( "room_rvblp", "1", 0, "lowpass filter" );
+
+ sxste_delay = Cvar_Get( "room_left", "0", 0, "straight left delay" );
+ sxmod_lowpass = Cvar_Get( "room_lp", "0", 0, "no description" );
+ sxmod_mod = Cvar_Get( "room_mod", "0", 0, "no description" );
+
+ sxroom_type = Cvar_Get( "room_type_l", "0", 0, "no description" );
+ sxroomwater_type = Cvar_Get( "waterroom_type_l", "14", 0, "no description" );
+ sxroom_off = Cvar_Get( "room_off_l", "0", 0, "no description" );
+
+ Mem_Set( rgsxdly, 0, sizeof( dlyline_t ) * CSXDLYMAX );
+ Mem_Set( rgsxlp, 0, sizeof( int ) * CSXLPMAX );
+
+ sxdly_delayprev = -1.0;
+ sxrvb_sizeprev = -1.0;
+ sxste_delayprev = -1.0;
+ sxroom_typeprev = -1.0;
+
+ // init amplitude modulation params
+ sxamodl = sxamodr = 255;
+ sxamodlt = sxamodrt = 255;
+
+ sxmod1 = 350 * (SOUND_DMA_SPEED / SOUND_11k); // 11k was the original sample rate all dsp was tuned at
+ sxmod2 = 450 * (SOUND_DMA_SPEED / SOUND_11k);
+ sxmod1cur = sxmod1;
+ sxmod2cur = sxmod2;
+
+ MsgDev( D_INFO, "FX Processor Initialized\n" );
+}
+
+void SX_Free( void )
+{
+ int i;
+
+ // release mono delay line
+ SXDLY_Free( ISXMONODLY );
+
+ // release reverb lines
+ for( i = 0; i < CSXRVBMAX; i++ )
+ SXDLY_Free( i + ISXRVB );
+ SXDLY_Free(ISXSTEREODLY);
+}
+
+// Set up a delay line buffer allowing a max delay of 'delay' seconds
+// Frees current buffer if it already exists. idelay indicates which of
+// the available delay lines to init.
+bool SXDLY_Init( int idelay, float delay )
+{
+ int cbsamples;
+ HANDLE hData;
+ HPSTR lpData;
+ dlyline_t *pdly;
+
+ pdly = &(rgsxdly[idelay]);
+
+ if( delay > SXDLY_MAX )
+ delay = SXDLY_MAX;
+
+ if( pdly->lpdelayline )
+ {
+ GlobalUnlock( pdly->hdelayline );
+ GlobalFree( pdly->hdelayline );
+ pdly->hdelayline = NULL;
+ pdly->lpdelayline = NULL;
+ }
+
+ if( delay == 0.0 )
+ return true;
+
+ pdly->cdelaysamplesmax = SOUND_DMA_SPEED * delay;
+ pdly->cdelaysamplesmax += 1;
+
+ cbsamples = pdly->cdelaysamplesmax * sizeof( sample_t );
+
+ hData = GlobalAlloc( GMEM_MOVEABLE|GMEM_SHARE, cbsamples );
+ if( !hData )
+ {
+ MsgDev( D_ERROR, "Sound FX: Out of memory.\n" );
+ return false;
+ }
+
+ lpData = (char *)GlobalLock( hData );
+ if( !lpData )
+ {
+ MsgDev( D_ERROR, "Sound FX: Failed to lock.\n" );
+ GlobalFree( hData );
+ return false;
+ }
+
+ Mem_Set( lpData, 0, cbsamples );
+
+ pdly->hdelayline = hData;
+ pdly->lpdelayline = (sample_t *)lpData;
+
+ // init delay loop input and output counters.
+ // NOTE: init of idelayoutput only valid if pdly->delaysamples is set
+ // NOTE: before this call!
+
+ pdly->idelayinput = 0;
+ pdly->idelayoutput = pdly->cdelaysamplesmax - pdly->delaysamples;
+ pdly->xfade = 0;
+ pdly->lp = 1;
+ pdly->mod = 0;
+ pdly->modcur = 0;
+
+ // init lowpass filter memory
+ pdly->lp0 = pdly->lp1 = pdly->lp2 = pdly->lp3 = pdly->lp4 = pdly->lp5 = 0;
+
+ return true;
+}
+
+// release delay buffer and deactivate delay
+void SXDLY_Free( int idelay )
+{
+ dlyline_t *pdly = &(rgsxdly[idelay]);
+
+ if( pdly->lpdelayline )
+ {
+ GlobalUnlock( pdly->hdelayline );
+ GlobalFree( pdly->hdelayline );
+ pdly->hdelayline = NULL;
+ pdly->lpdelayline = NULL; // this deactivates the delay
+ }
+}
+
+
+// check for new stereo delay param
+void SXDLY_CheckNewStereoDelayVal( void )
+{
+ dlyline_t *pdly = &(rgsxdly[ISXSTEREODLY]);
+ int delaysamples;
+
+ // set up stereo delay
+ if( sxste_delay->value != sxste_delayprev )
+ {
+ if( sxste_delay->value == 0.0 )
+ {
+ // deactivate delay line
+ SXDLY_Free( ISXSTEREODLY );
+ sxste_delayprev = 0.0;
+ }
+ else
+ {
+ delaysamples = min(sxste_delay->value, SXSTE_MAX) * SOUND_DMA_SPEED;
+
+ // init delay line if not active
+ if( pdly->lpdelayline == NULL )
+ {
+
+ pdly->delaysamples = delaysamples;
+ SXDLY_Init( ISXSTEREODLY, SXSTE_MAX );
+ }
+
+ // do crossfade to new delay if delay has changed
+ if( delaysamples != pdly->delaysamples )
+ {
+
+ // set up crossfade from old pdly->delaysamples to new delaysamples
+ pdly->idelayoutputxf = pdly->idelayinput - delaysamples;
+
+ if( pdly->idelayoutputxf < 0 )
+ pdly->idelayoutputxf += pdly->cdelaysamplesmax;
+ pdly->xfade = 128;
+ }
+
+ sxste_delayprev = sxste_delay->value;
+
+ // UNDONE: modulation disabled
+ // pdly->mod = 500 * (SOUND_DMA_SPEED / SOUND_11k); // change delay every n samples
+ pdly->mod = 0;
+ pdly->modcur = pdly->mod;
+
+ // deactivate line if rounded down to 0 delay
+ if( pdly->delaysamples == 0 )
+ SXDLY_Free( ISXSTEREODLY );
+
+ }
+ }
+}
+
+// stereo delay, left channel only, no feedback
+
+void SXDLY_DoStereoDelay( int count )
+{
+ int left;
+ sample_t sampledly;
+ sample_t samplexf;
+ samplepair_t *pbuf;
+ int countr;
+
+ // process delay line if active
+ if( rgsxdly[ISXSTEREODLY].lpdelayline )
+ {
+ pbuf = paintbuffer;
+ countr = count;
+
+ // process each sample in the paintbuffer...
+ while( countr-- )
+ {
+ if( gdly3.mod && ( --gdly3.modcur < 0 ))
+ gdly3.modcur = gdly3.mod;
+
+ // get delay line sample from left line
+ sampledly = *(gdly3.lpdelayline + gdly3.idelayoutput);
+ left = pbuf->left;
+
+ // only process if left value or delayline value are non-zero or xfading
+ if( gdly3.xfade || sampledly || left )
+ {
+ // if we're not crossfading, and we're not modulating, but we'd like to be modulating,
+ // then setup a new crossfade.
+
+ if( !gdly3.xfade && !gdly3.modcur && gdly3.mod )
+ {
+ // set up crossfade to new delay value, if we're not already doing an xfade
+ gdly3.idelayoutputxf = gdly3.idelayoutput + ((Com_RandomLong(0, 0xFF) * gdly3.delaysamples) >> 9); // 100 = ~ 9ms
+
+ if( gdly3.idelayoutputxf >= gdly3.cdelaysamplesmax )
+ gdly3.idelayoutputxf -= gdly3.cdelaysamplesmax;
+
+ gdly3.xfade = 128;
+ }
+
+ // modify sampledly if crossfading to new delay value
+ if( gdly3.xfade )
+ {
+ samplexf = (*(gdly3.lpdelayline + gdly3.idelayoutputxf) * (128 - gdly3.xfade)) >> 7;
+ sampledly = ((sampledly * gdly3.xfade) >> 7) + samplexf;
+
+ if( ++gdly3.idelayoutputxf >= gdly3.cdelaysamplesmax )
+ gdly3.idelayoutputxf = 0;
+
+ if( --gdly3.xfade == 0 )
+ gdly3.idelayoutput = gdly3.idelayoutputxf;
+ }
+
+ // save output value into delay line
+
+ // left = CLIP(left);
+ *(gdly3.lpdelayline + gdly3.idelayinput) = left;
+
+ // save delay line sample into output buffer
+ pbuf->left = sampledly;
+
+ }
+ else
+ {
+ // keep clearing out delay line, even if no signal in or out
+ *(gdly3.lpdelayline + gdly3.idelayinput) = 0;
+ }
+
+ // update delay buffer pointers
+ if( ++gdly3.idelayinput >= gdly3.cdelaysamplesmax )
+ gdly3.idelayinput = 0;
+
+ if( ++gdly3.idelayoutput >= gdly3.cdelaysamplesmax )
+ gdly3.idelayoutput = 0;
+ pbuf++;
+ }
+
+ }
+}
+
+// If sxdly_delay or sxdly_feedback have changed, update delaysamples
+// and delayfeed values. This applies only to delay 0, the main echo line.
+
+void SXDLY_CheckNewDelayVal( void )
+{
+ dlyline_t *pdly = &(rgsxdly[ISXMONODLY]);
+
+ if (sxdly_delay->value != sxdly_delayprev) {
+
+ if (sxdly_delay->value == 0.0) {
+
+ // deactivate delay line
+
+ SXDLY_Free(ISXMONODLY);
+ sxdly_delayprev = sxdly_delay->value;
+
+ } else {
+ // init delay line if not active
+
+ pdly->delaysamples = min(sxdly_delay->value, SXDLY_MAX) * SOUND_DMA_SPEED;
+
+ if (pdly->lpdelayline == NULL)
+ SXDLY_Init(ISXMONODLY, SXDLY_MAX);
+
+ // flush delay line and filters
+
+ if (pdly->lpdelayline) {
+ Mem_Set(pdly->lpdelayline, 0, pdly->cdelaysamplesmax * sizeof(sample_t));
+ pdly->lp0 = 0;
+ pdly->lp1 = 0;
+ pdly->lp2 = 0;
+ pdly->lp3 = 0;
+ pdly->lp4 = 0;
+ pdly->lp5 = 0;
+ }
+
+ // init delay loop input and output counters
+
+ pdly->idelayinput = 0;
+ pdly->idelayoutput = pdly->cdelaysamplesmax - pdly->delaysamples;
+
+ sxdly_delayprev = sxdly_delay->value;
+
+ // deactivate line if rounded down to 0 delay
+
+ if (pdly->delaysamples == 0)
+ SXDLY_Free(ISXMONODLY);
+
+ }
+ }
+
+ pdly->lp = (int)(sxdly_lp->value);
+ pdly->delayfeed = sxdly_feedback->value * 255;
+}
+
+
+// This routine updates both left and right output with
+// the mono delayed signal. Delay is set through console vars room_delay
+// and room_feedback.
+
+void SXDLY_DoDelay(int count)
+{
+ int val;
+ int valt;
+ int left;
+ int right;
+ sample_t sampledly;
+ samplepair_t *pbuf;
+ int countr;
+ float fgain;
+ int gain;
+
+
+ // process mono delay line if active
+
+ if (rgsxdly[ISXMONODLY].lpdelayline)
+ {
+
+ // calculate gain of delay line with feedback, and use it to
+ // reduce output. ie: make delay line approx unity gain
+
+ // for constant input x with feedback fb:
+
+ // out = x + x*fb + x * fb^2 + x * fb^3...
+ // gain = out/x
+ // so gain = 1 + fb + fb^2 + fb^3...
+ // which, by the miracle of geometric series, equates to 1/1-fb
+ // thus, gain = 1/1-fb
+
+ fgain = 1.0 / (1.0 - gdly0.delayfeed / 255.0);
+ gain = (int)((1.0 / fgain)* 255.0);
+
+ gain <<= 2;
+ if( gain > 255 ) gain = 255;
+
+ pbuf = paintbuffer;
+ countr = count;
+
+ // process each sample in the paintbuffer...
+ while( countr-- )
+ {
+ // get delay line sample
+ sampledly = *(gdly0.lpdelayline + gdly0.idelayoutput);
+
+ left = pbuf->left;
+ right = pbuf->right;
+
+ // only process if delay line and paintbuffer samples are non zero
+ if( sampledly || left || right )
+ {
+ // get current sample from delay buffer
+
+ // calculate delayed value from avg of left and right channels
+ val = ((left + right) >> 1) + ((gdly0.delayfeed * sampledly) >> 8);
+
+ // limit val to short
+ // val = CLIP( val );
+
+ // lowpass
+ if( gdly0.lp )
+ {
+ //valt = (gdly0.lp0 + gdly0.lp1 + val) / 3; // performance
+ //valt = (gdly0.lp0 + gdly0.lp1 + (val<<1)) >> 2;
+
+ valt = (gdly0.lp0 + gdly0.lp1 + gdly0.lp2 + gdly0.lp3 + val) / 5;
+
+ gdly0.lp0 = gdly0.lp1;
+ gdly0.lp1 = gdly0.lp2;
+ gdly0.lp2 = gdly0.lp3;
+ gdly0.lp3 = val;
+ }
+ else
+ {
+ valt = val;
+ }
+
+ // store delay output value into output buffer
+
+ *(gdly0.lpdelayline + gdly0.idelayinput) = valt;
+
+ // mono delay in left and right channels
+
+ // decrease output value by max gain of delay with feedback
+ // to provide for unity gain reverb
+ // note: this gain varies with the feedback value.
+
+ pbuf->left = (valt * gain) >> 8;
+ pbuf->right = (valt * gain) >> 8;
+ }
+ else
+ {
+ // not playing samples, but must still flush lowpass buffer and delay line
+ valt = gdly0.lp0 = gdly0.lp1 = gdly0.lp2 = gdly0.lp3 = 0;
+ *(gdly0.lpdelayline + gdly0.idelayinput) = valt;
+
+ }
+
+ // update delay buffer pointers
+ if( ++gdly0.idelayinput >= gdly0.cdelaysamplesmax )
+ gdly0.idelayinput = 0;
+
+ if( ++gdly0.idelayoutput >= gdly0.cdelaysamplesmax )
+ gdly0.idelayoutput = 0;
+ pbuf++;
+ }
+ }
+}
+
+// check for a parameter change on the reverb processor
+#define RVB_XFADE (32 * SOUND_DMA_SPEED / SOUND_11k) // xfade time between new delays
+#define RVB_MODRATE1 (500 * (SOUND_DMA_SPEED / SOUND_11k)) // how often, in samples, to change delay (1st rvb)
+#define RVB_MODRATE2 (700 * (SOUND_DMA_SPEED / SOUND_11k)) // how often, in samples, to change delay (2nd rvb)
+
+void SXRVB_CheckNewReverbVal( void )
+{
+ dlyline_t *pdly;
+ int delaysamples;
+ int i, mod;
+
+ if( sxrvb_size->value != sxrvb_sizeprev )
+ {
+ sxrvb_sizeprev = sxrvb_size->value;
+
+ if( sxrvb_size->value == 0.0 )
+ {
+ // deactivate all delay lines
+ SXDLY_Free( ISXRVB );
+ SXDLY_Free( ISXRVB + 1 );
+
+ }
+ else
+ {
+ for( i = ISXRVB; i < ISXRVB + CSXRVBMAX; i++ )
+ {
+ // init delay line if not active
+ pdly = &(rgsxdly[i]);
+
+ switch( i )
+ {
+ case ISXRVB:
+ delaysamples = min(sxrvb_size->value, SXRVB_MAX) * SOUND_DMA_SPEED;
+ pdly->mod = RVB_MODRATE1;
+ break;
+ case ISXRVB+1:
+ delaysamples = min(sxrvb_size->value * 0.71, SXRVB_MAX) * SOUND_DMA_SPEED;
+ pdly->mod = RVB_MODRATE2;
+ break;
+ default:
+ Com_Assert( 1 );
+ delaysamples = 0;
+ break;
+ }
+
+ mod = pdly->mod; // KB: bug, SXDLY_Init clears mod, modcur, xfade and lp - save mod before call
+
+ if( pdly->lpdelayline == NULL )
+ {
+ pdly->delaysamples = delaysamples;
+ SXDLY_Init( i, SXRVB_MAX );
+ }
+
+ pdly->modcur = pdly->mod = mod; // KB: bug, SXDLY_Init clears mod, modcur, xfade and lp - restore mod after call
+
+ // do crossfade to new delay if delay has changed
+ if( delaysamples != pdly->delaysamples )
+ {
+ // set up crossfade from old pdly->delaysamples to new delaysamples
+ pdly->idelayoutputxf = pdly->idelayinput - delaysamples;
+
+ if( pdly->idelayoutputxf < 0 )
+ pdly->idelayoutputxf += pdly->cdelaysamplesmax;
+
+ pdly->xfade = RVB_XFADE;
+ }
+
+ // deactivate line if rounded down to 0 delay
+ if( pdly->delaysamples == 0 )
+ SXDLY_Free( i );
+ }
+ }
+ }
+
+ rgsxdly[ISXRVB].delayfeed = (sxrvb_feedback->value) * 255;
+ rgsxdly[ISXRVB].lp = sxrvb_lp->value;
+
+ rgsxdly[ISXRVB + 1].delayfeed = (sxrvb_feedback->value) * 255;
+ rgsxdly[ISXRVB + 1].lp = sxrvb_lp->value;
+
+}
+
+
+// main routine for updating the paintbuffer with new reverb values.
+// This routine updates both left and right lines with
+// the mono reverb signal. Delay is set through console vars room_reverb
+// and room_feedback. 2 reverbs operating in parallel.
+void SXRVB_DoReverb( int count )
+{
+ int val;
+ int valt;
+ int left;
+ int right;
+ sample_t sampledly;
+ sample_t samplexf;
+ samplepair_t *pbuf;
+ int countr;
+ int voutm;
+ int vlr;
+ float fgain1;
+ float fgain2;
+ int gain;
+
+ // process reverb lines if active
+ if( rgsxdly[ISXRVB].lpdelayline )
+ {
+ // calculate reverb gains
+ fgain1 = 1.0 / (1.0 - gdly1.delayfeed / 255.0);
+ fgain2 = 1.0 / (1.0 - gdly2.delayfeed / 255.0) + fgain1;
+
+ // inverse gain of parallel reverbs
+ gain = (int)((1.0 / fgain2) * 255.0);
+
+ gain <<= 2;
+
+ if( gain > 255 ) gain = 255;
+
+ pbuf = paintbuffer;
+ countr = count;
+
+ // process each sample in the paintbuffer...
+
+ while (countr--)
+ {
+
+ left = pbuf->left;
+ right = pbuf->right;
+ voutm = 0;
+ vlr = (left + right) >> 1;
+
+ // UNDONE: ignored
+ if( --gdly1.modcur < 0 )
+ gdly1.modcur = gdly1.mod;
+
+ // ========================== ISXRVB============================
+
+ // get sample from delay line
+
+ sampledly = *(gdly1.lpdelayline + gdly1.idelayoutput);
+
+ // only process if something is non-zero
+ if( gdly1.xfade || sampledly || left || right )
+ {
+ // modulate delay rate
+ // UNDONE: modulation disabled
+ if( 0 && !gdly1.xfade && !gdly1.modcur && gdly1.mod )
+ {
+ // set up crossfade to new delay value, if we're not already doing an xfade
+ gdly1.idelayoutputxf = gdly1.idelayoutput + ((Com_RandomLong(0, 0xFF) * gdly1.delaysamples) >> 9); // 100 = ~ 9ms
+
+ if( gdly1.idelayoutputxf >= gdly1.cdelaysamplesmax )
+ gdly1.idelayoutputxf -= gdly1.cdelaysamplesmax;
+
+ gdly1.xfade = RVB_XFADE;
+ }
+
+ // modify sampledly if crossfading to new delay value
+
+ if( gdly1.xfade )
+ {
+ samplexf = (*(gdly1.lpdelayline + gdly1.idelayoutputxf) * (RVB_XFADE - gdly1.xfade)) / RVB_XFADE;
+ sampledly = ((sampledly * gdly1.xfade) / RVB_XFADE) + samplexf;
+
+ if( ++gdly1.idelayoutputxf >= gdly1.cdelaysamplesmax )
+ gdly1.idelayoutputxf = 0;
+
+ if( --gdly1.xfade == 0 )
+ gdly1.idelayoutput = gdly1.idelayoutputxf;
+ }
+
+ if( sampledly )
+ {
+ // get current sample from delay buffer
+
+ // calculate delayed value from avg of left and right channels
+ val = vlr + ((gdly1.delayfeed * sampledly) >> 8);
+
+ // limit to short
+ // val = CLIP(val);
+
+ }
+ else
+ {
+ val = vlr;
+ }
+
+ // lowpass
+ if( gdly1.lp )
+ {
+ valt = (gdly1.lp0 + gdly1.lp1 + (val<<1)) >> 2;
+ gdly1.lp1 = gdly1.lp0;
+ gdly1.lp0 = val;
+ }
+ else
+ {
+ valt = val;
+ }
+
+ // store delay output value into output buffer
+ *(gdly1.lpdelayline + gdly1.idelayinput) = valt;
+ voutm = valt;
+ }
+ else
+ {
+ // not playing samples, but still must flush lowpass buffer & delay line
+
+ gdly1.lp0 = gdly1.lp1 = 0;
+ *(gdly1.lpdelayline + gdly1.idelayinput) = 0;
+ voutm = 0;
+ }
+
+ // update delay buffer pointers
+ if( ++gdly1.idelayinput >= gdly1.cdelaysamplesmax )
+ gdly1.idelayinput = 0;
+
+ if( ++gdly1.idelayoutput >= gdly1.cdelaysamplesmax )
+ gdly1.idelayoutput = 0;
+
+ // ========================== ISXRVB + 1========================
+
+ // UNDONE: ignored
+ if( --gdly2.modcur < 0 )
+ gdly2.modcur = gdly2.mod;
+
+ if( gdly2.lpdelayline )
+ {
+ // get sample from delay line
+
+ sampledly = *(gdly2.lpdelayline + gdly2.idelayoutput);
+
+ // only process if something is non-zero
+ if( gdly2.xfade || sampledly || left || right )
+ {
+ // UNDONE: modulation disabled
+ if( 0 && !gdly2.xfade && gdly2.modcur && gdly2.mod )
+ {
+ // set up crossfade to new delay value, if we're not already doing an xfade
+ gdly2.idelayoutputxf = gdly2.idelayoutput + ((Com_RandomLong(0,0xFF) * gdly2.delaysamples) >> 9); // 100 = ~ 9ms
+
+ if( gdly2.idelayoutputxf >= gdly2.cdelaysamplesmax )
+ gdly2.idelayoutputxf -= gdly2.cdelaysamplesmax;
+
+ gdly2.xfade = RVB_XFADE;
+ }
+
+ // modify sampledly if crossfading to new delay value
+ if( gdly2.xfade )
+ {
+ samplexf = (*(gdly2.lpdelayline + gdly2.idelayoutputxf) * (RVB_XFADE - gdly2.xfade)) / RVB_XFADE;
+ sampledly = ((sampledly * gdly2.xfade) / RVB_XFADE) + samplexf;
+
+ if( ++gdly2.idelayoutputxf >= gdly2.cdelaysamplesmax )
+ gdly2.idelayoutputxf = 0;
+
+ if( --gdly2.xfade == 0 )
+ gdly2.idelayoutput = gdly2.idelayoutputxf;
+ }
+
+ if( sampledly )
+ {
+ // get current sample from delay buffer
+
+ // calculate delayed value from avg of left and right channels
+ val = vlr + ((gdly2.delayfeed * sampledly) >> 8);
+
+ // limit to short
+ // val = CLIP(val);
+ }
+ else
+ {
+ val = vlr;
+ }
+
+ // lowpass
+ if( gdly2.lp )
+ {
+ valt = (gdly2.lp0 + gdly2.lp1 + (val<<1)) >> 2;
+ gdly2.lp0 = val;
+ }
+ else
+ {
+ valt = val;
+ }
+
+ // store delay output value into output buffer
+ *(gdly2.lpdelayline + gdly2.idelayinput) = valt;
+ voutm += valt;
+ }
+ else
+ {
+ // not playing samples, but still must flush lowpass buffer
+ gdly2.lp0 = gdly2.lp1 = 0;
+ *(gdly2.lpdelayline + gdly2.idelayinput) = 0;
+ }
+
+ // update delay buffer pointers
+ if( ++gdly2.idelayinput >= gdly2.cdelaysamplesmax )
+ gdly2.idelayinput = 0;
+
+ if( ++gdly2.idelayoutput >= gdly2.cdelaysamplesmax )
+ gdly2.idelayoutput = 0;
+ }
+
+ // ============================ Mix================================
+
+ // add mono delay to left and right channels
+
+ // drop output by inverse of cascaded gain for both reverbs
+ voutm = (gain * voutm) >> 8;
+ // voutm = CLIP( voutm );
+
+ left = voutm;
+ right = voutm;
+
+ pbuf->left = left;
+ pbuf->right = right;
+
+ pbuf++;
+ }
+ }
+}
+
+// amplitude modulator, low pass filter for underwater weirdness
+void SXRVB_DoAMod( int count )
+{
+ int valtl, valtr;
+ int left;
+ int right;
+ samplepair_t *pbuf;
+ int countr;
+ int fLowpass;
+ int fmod;
+
+ // process reverb lines if active
+ if( sxmod_lowpass->value != 0.0 || sxmod_mod->value != 0.0 )
+ {
+ pbuf = paintbuffer;
+ countr = count;
+
+ fLowpass = (sxmod_lowpass->value != 0.0);
+ fmod = (sxmod_mod->value != 0.0);
+
+ // process each sample in the paintbuffer...
+
+ while( countr-- )
+ {
+ left = pbuf->left;
+ right = pbuf->right;
+
+ // only process if non-zero
+ if( fLowpass )
+ {
+ valtl = left;
+ valtr = right;
+
+ left = (rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + left);
+ right = (rgsxlp[5] + rgsxlp[6] + rgsxlp[7]+ rgsxlp[8] + rgsxlp[9] + right);
+
+ left = ((left << 1) + (left << 3)) >> 6; // * 10/64
+ right = ((right << 1) + (right << 3)) >> 6; // * 10/64
+
+ rgsxlp[4] = valtl;
+ rgsxlp[9] = valtr;
+
+ 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( fmod )
+ {
+ if( --sxmod1cur < 0 )
+ sxmod1cur = sxmod1;
+
+ if( !sxmod1 )
+ sxamodlt = Com_RandomLong(32,255);
+
+ if( --sxmod2cur < 0 )
+ sxmod2cur = sxmod2;
+
+ if( !sxmod2 )
+ sxamodlt = Com_RandomLong(32,255);
+
+ left = (left * sxamodl) >> 8;
+ right = (right * sxamodr) >> 8;
+
+ if( sxamodl < sxamodlt )
+ sxamodl++;
+ else if( sxamodl > sxamodlt )
+ sxamodl--;
+
+ if( sxamodr < sxamodrt )
+ sxamodr++;
+ else if( sxamodr > sxamodrt )
+ sxamodr--;
+ }
+
+ left = CLIP( left );
+ right = CLIP( right );
+
+ pbuf->left = left;
+ pbuf->right = right;
+
+ pbuf++;
+ }
+ }
+}
+
+typedef struct sx_preset_s
+{
+ float room_lp; // for water fx, lowpass for entire room
+ float room_mod; // stereo amplitude modulation for room
+ float room_size; // reverb: initial reflection size
+ float room_refl; // reverb: decay time
+ float room_rvblp; // reverb: low pass filtering level
+ float room_delay; // mono delay: delay time
+ float room_feedback; // mono delay: decay time
+ float room_dlylp; // mono delay: low pass filtering level
+ float room_left; // left channel delay time
+} sx_preset_t;
+
+sx_preset_t rgsxpre[CSXROOM] =
+{
+// SXROOM_OFF 0
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0},
+
+// SXROOM_GENERIC 1 // general, low reflective, diffuse room
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.065, 0.1, 0.0, 0.01},
+
+// SXROOM_METALIC_S 2 // highly reflective, parallel surfaces
+// SXROOM_METALIC_M 3
+// SXROOM_METALIC_L 4
+
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.02, 0.75, 0.0, 0.01}, // 0.001
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.03, 0.78, 0.0, 0.02}, // 0.002
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.77, 0.0, 0.03}, // 0.003
+
+// SXROOM_TUNNEL_S 5 // resonant reflective, long surfaces
+// SXROOM_TUNNEL_M 6
+// SXROOM_TUNNEL_L 7
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.85, 1.0, 0.018, 0.7, 2.0, 0.01}, // 0.01
+ {0.0, 0.0, 0.05, 0.88, 1.0, 0.020, 0.7, 2.0, 0.02}, // 0.02
+ {0.0, 0.0, 0.05, 0.92, 1.0, 0.025, 0.7, 2.0, 0.04}, // 0.04
+
+// SXROOM_CHAMBER_S 8 // diffuse, moderately reflective surfaces
+// SXROOM_CHAMBER_M 9
+// SXROOM_CHAMBER_L 10
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.84, 1.0, 0.0, 0.0, 2.0, 0.012}, // 0.003
+ {0.0, 0.0, 0.05, 0.90, 1.0, 0.0, 0.0, 2.0, 0.008}, // 0.002
+ {0.0, 0.0, 0.05, 0.95, 1.0, 0.0, 0.0, 2.0, 0.004}, // 0.001
+
+// SXROOM_BRITE_S 11 // diffuse, highly reflective
+// SXROOM_BRITE_M 12
+// SXROOM_BRITE_L 13
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.7, 0.0, 0.0, 0.0, 2.0, 0.012}, // 0.003
+ {0.0, 0.0, 0.055, 0.78, 0.0, 0.0, 0.0, 2.0, 0.008}, // 0.002
+ {0.0, 0.0, 0.05, 0.86, 0.0, 0.0, 0.0, 2.0, 0.002}, // 0.001
+
+// SXROOM_WATER1 14 // underwater fx
+// SXROOM_WATER2 15
+// SXROOM_WATER3 16
+// lp mod size refl rvblp delay feedbk dlylp left
+ {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.01},
+ {1.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.85, 2.0, 0.02},
+ {1.0, 0.0, 0.0, 0.0, 1.0, 0.2, 0.6, 2.0, 0.05},
+
+// SXROOM_CONCRETE_S 17 // bare, reflective, parallel surfaces
+// SXROOM_CONCRETE_M 18
+// SXROOM_CONCRETE_L 19
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.8, 1.0, 0.0, 0.48, 2.0, 0.016}, // 0.15 delay, 0.008 left
+ {0.0, 0.0, 0.06, 0.9, 1.0, 0.0, 0.52, 2.0, 0.01 }, // 0.22 delay, 0.005 left
+ {0.0, 0.0, 0.07, 0.94, 1.0, 0.3, 0.6, 2.0, 0.008}, // 0.001
+
+// SXROOM_OUTSIDE1 20 // echoing, moderately reflective
+// SXROOM_OUTSIDE2 21 // echoing, dull
+// SXROOM_OUTSIDE3 22 // echoing, very dull
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.3, 0.42, 2.0, 0.0},
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.35, 0.48, 2.0, 0.0},
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.38, 0.6, 2.0, 0.0},
+
+// SXROOM_CAVERN_S 23 // large, echoing area
+// SXROOM_CAVERN_M 24
+// SXROOM_CAVERN_L 25
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 0.0, 0.05, 0.9, 1.0, 0.2, 0.28, 0.0, 0.0},
+ {0.0, 0.0, 0.07, 0.9, 1.0, 0.3, 0.4, 0.0, 0.0},
+ {0.0, 0.0, 0.09, 0.9, 1.0, 0.35, 0.5, 0.0, 0.0},
+
+// SXROOM_WEIRDO1 26
+// SXROOM_WEIRDO2 27
+// SXROOM_WEIRDO3 28
+// SXROOM_WEIRDO3 29
+// lp mod size refl rvblp delay feedbk dlylp left
+ {0.0, 1.0, 0.01, 0.9, 0.0, 0.0, 0.0, 2.0, 0.05},
+ {0.0, 0.0, 0.0, 0.0, 1.0, 0.009, 0.999, 2.0, 0.04},
+ {0.0, 0.0, 0.001, 0.999, 0.0, 0.2, 0.8, 2.0, 0.05}
+
+};
+
+// main routine for processing room sound fx
+// if fFilter is true, then run in-line filter (for underwater fx)
+// if fTimefx is true, then run reverb and delay fx
+// NOTE: only processes preset room_types from 0-29 (CSXROOM)
+void SX_RoomFX( int endtime, int fFilter, int fTimefx )
+{
+ int i, fReset;
+ int sampleCount;
+ float roomType;
+
+ // return right away if fx processing is turned off
+ if( sxroom_off->value != 0.0 )
+ return;
+
+ sampleCount = endtime - paintedtime;
+ if( sampleCount < 0 )
+ return;
+
+ fReset = false;
+ if( listener_waterlevel > 2 )
+ roomType = sxroomwater_type->value;
+ else roomType = sxroom_type->value;
+
+ // only process legacy roomtypes here
+ if( (int)roomType >= CSXROOM )
+ return;
+
+ if( roomType != sxroom_typeprev )
+ {
+ Msg( "Room_type: %2.1f\n", roomType );
+
+ sxroom_typeprev = roomType;
+
+ i = (int)(roomType);
+ if( i < CSXROOM && i >= 0 )
+ {
+ Cvar_SetValue( "room_lp", rgsxpre[i].room_lp );
+ Cvar_SetValue( "room_mod", rgsxpre[i].room_mod );
+ Cvar_SetValue( "room_size", rgsxpre[i].room_size );
+ Cvar_SetValue( "room_refl", rgsxpre[i].room_refl );
+ Cvar_SetValue( "room_rvblp", rgsxpre[i].room_rvblp );
+ Cvar_SetValue( "room_delay", rgsxpre[i].room_delay );
+ Cvar_SetValue( "room_feedback", rgsxpre[i].room_feedback );
+ Cvar_SetValue( "room_dlylp", rgsxpre[i].room_dlylp );
+ Cvar_SetValue( "room_left", rgsxpre[i].room_left );
+ }
+
+ SXRVB_CheckNewReverbVal();
+ SXDLY_CheckNewDelayVal();
+ SXDLY_CheckNewStereoDelayVal();
+
+ fReset = true;
+ }
+
+ if( fReset || roomType != 0.0 )
+ {
+ // debug code
+ SXRVB_CheckNewReverbVal();
+ SXDLY_CheckNewDelayVal();
+ SXDLY_CheckNewStereoDelayVal();
+ // debug code
+
+ if( fFilter ) SXRVB_DoAMod( sampleCount );
+
+ if( fTimefx )
+ {
+ SXRVB_DoReverb( sampleCount );
+ SXDLY_DoDelay( sampleCount );
+ SXDLY_DoStereoDelay( sampleCount );
+ }
+ }
+}
\ No newline at end of file
diff --git a/snd_dx/s_load.c b/snd_dx/s_load.c
index ec46ebe3..edb2d963 100644
--- a/snd_dx/s_load.c
+++ b/snd_dx/s_load.c
@@ -331,6 +331,50 @@ static bool S_LoadWAV( const char *name, byte **wav, wavinfo_t *info )
return true;
}
+/*
+=================
+S_CreateMixer
+=================
+*/
+static void S_CreateMixer( sfx_t *sfx )
+{
+ // first time to load? Create the mixer
+ if( sfx->cache && !sfx->mixer )
+ {
+ mixer_t *pMixer = Z_Malloc( sizeof( mixer_t ));
+
+ pMixer->m_pData = sfx->cache;
+
+ if( sfx->cache->stereo )
+ {
+ if( sfx->cache->width == 1 )
+ {
+ Msg( "S_LoadSound: use Mix8Stereo( %s )\n", sfx->name );
+ pMixer->MixFunc = Mix8Stereo;
+ }
+ else
+ {
+ Msg( "S_LoadSound: use Mix16Stereo( %s )\n", sfx->name );
+ pMixer->MixFunc = Mix16Stereo;
+ }
+ }
+ else
+ {
+ if( sfx->cache->width == 1 )
+ {
+ Msg( "S_LoadSound: use Mix8Mono( %s )\n", sfx->name );
+ pMixer->MixFunc = Mix8Mono;
+ }
+ else
+ {
+ Msg( "S_LoadSound: use Mix16Mono( %s )\n", sfx->name );
+ pMixer->MixFunc = Mix16Mono;
+ }
+ }
+ sfx->mixer = pMixer; // done
+ }
+}
+
/*
=================
S_UploadSound
@@ -355,6 +399,7 @@ static void S_UploadSound( byte *data, wavinfo_t *info, sfx_t *sfx )
sc->stereo = info->channels;
S_ResampleSfx( sfx, sc->speed, sc->width, data + info->dataofs );
+ S_CreateMixer( sfx );
}
/*
@@ -400,7 +445,7 @@ loadformat_t load_formats[] =
{ NULL, NULL }
};
-sfxcache_t *S_LoadSound( sfx_t *sfx )
+sfxcache_t *S_LoadSound( sfx_t *sfx, channel_t *ch )
{
byte *data;
wavinfo_t info;
@@ -409,6 +454,10 @@ sfxcache_t *S_LoadSound( sfx_t *sfx )
loadformat_t *format;
bool anyformat;
+ // setup channel mixer
+ if( ch && !ch->pMixer && sfx && sfx->mixer )
+ ch->pMixer = sfx->mixer;
+
if( !sfx ) return NULL;
if( sfx->name[0] == '*' ) return NULL;
if( sfx->cache ) return sfx->cache; // see if still in memory
@@ -538,6 +587,7 @@ void S_EndRegistration( void )
{
// don't need this sound
if( sfx->cache ) Mem_Free( sfx->cache );
+ if( sfx->mixer ) Mem_Free( sfx->mixer );
Mem_Set( sfx, 0, sizeof( *sfx ));
}
}
@@ -546,7 +596,7 @@ void S_EndRegistration( void )
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
{
if( !sfx->name[0] ) continue;
- S_LoadSound( sfx );
+ S_LoadSound( sfx, NULL );
}
s_registering = false;
}
@@ -568,7 +618,7 @@ sound_t S_RegisterSound( const char *name )
if( !sfx ) return -1;
sfx->registration_sequence = s_registration_sequence;
- if( !s_registering ) S_LoadSound( sfx );
+ if( !s_registering ) S_LoadSound( sfx, NULL );
return sfx - s_knownSfx;
}
@@ -601,6 +651,7 @@ void S_FreeSounds( void )
{
if( !sfx->name[0] ) continue;
if( sfx->cache ) Mem_Free( sfx->cache );
+ if( sfx->mixer ) Mem_Free( sfx->mixer );
Mem_Set( sfx, 0, sizeof( *sfx ));
}
diff --git a/snd_dx/s_main.c b/snd_dx/s_main.c
index ed6392c3..01b50567 100644
--- a/snd_dx/s_main.c
+++ b/snd_dx/s_main.c
@@ -10,9 +10,22 @@
#define SOUND_LOOPATTENUATE 0.003
#define MAX_PLAYSOUNDS 128
+// Structure used for fading in and out client sound volume.
+typedef struct
+{
+ float initial_percent;
+ float percent; // how far to adjust client's volume down by.
+ float starttime; // si.GetServerTime() when we started adjusting volume
+ float fadeouttime; // # of seconds to get to faded out state
+ float holdtime; // # of seconds to hold
+ float fadeintime; // # of seconds to restore
+} soundfade_t;
+
dma_t dma;
+static soundfade_t soundfade; // client sound fading
channel_t channels[MAX_CHANNELS];
bool sound_started = false;
+int listener_waterlevel;
vec3_t listener_origin;
vec3_t listener_velocity;
vec3_t listener_forward;
@@ -48,6 +61,19 @@ cvar_t *s_pause;
=============================================================================
*/
+
+float S_GetMasterVolume( void )
+{
+ float scale = 1.0f;
+
+ if( soundfade.percent != 0 )
+ {
+ scale = bound( 0.0f, soundfade.percent / 100.0f, 1.0f );
+ scale = 1.0f - scale;
+ }
+ return s_volume->value * scale;
+}
+
/*
=================
S_PickChannel
@@ -262,7 +288,7 @@ void S_IssuePlaysound( playsound_t *ps )
S_SpatializeChannel( ch );
ch->pos = 0;
- sc = S_LoadSound( ch->sfx );
+ sc = S_LoadSound( ch->sfx, ch );
ch->end = paintedtime + sc->length;
// free the playsound
@@ -296,7 +322,7 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv
if( !sfx ) return;
// make sure the sound is loaded
- sc = S_LoadSound( sfx );
+ sc = S_LoadSound( sfx, NULL );
if( !sc ) return; // couldn't load the sound's data
vol = fvol * 255;
@@ -580,6 +606,7 @@ void S_Update( ref_params_t *fd )
if( s_volume->modified ) S_InitScaletable();
s_clientnum = fd->viewentity;
+ listener_waterlevel = fd->waterlevel;
VectorCopy( fd->simorg, listener_origin );
VectorCopy( fd->simvel, listener_velocity );
VectorCopy( fd->forward, listener_forward );
@@ -669,6 +696,9 @@ void S_SoundInfo_f( void )
Msg( "sound system not started\n" );
return;
}
+
+ if( dsound_init ) Msg( "Sound Device: DirectSound\n" );
+ if( wavout_init ) Msg( "Sound Device: Windows WAV\n" );
Msg( "%5d channel(s)\n", dma.channels );
Msg( "%5d samples\n", dma.samples );
@@ -725,6 +755,8 @@ bool S_Init( void *hInst )
S_StopAllSounds ();
+ AllocDsps();
+
return true;
}
@@ -745,4 +777,6 @@ void S_Shutdown( void )
S_FreeSounds();
Mem_FreePool( &sndpool );
+
+ FreeDsps();
}
\ No newline at end of file
diff --git a/snd_dx/s_mix.c b/snd_dx/s_mix.c
index 5a1fd32f..bc298647 100644
--- a/snd_dx/s_mix.c
+++ b/snd_dx/s_mix.c
@@ -21,11 +21,79 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "sound.h"
-#define PAINTBUFFER_SIZE 2048
-portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE];
-int snd_scaletable[32][256];
-int *snd_p, snd_linear_count, snd_vol;
-short *snd_out;
+// global sound filters
+#define FILTERTYPE_NONE 0
+#define FILTERTYPE_LINEAR 1
+#define FILTERTYPE_CUBIC 2
+
+#define SOUND_MIX_WET 0 // mix only samples that don't have channel set to 'dry' (default)
+#define SOUND_MIX_DRY 1 // mix only samples with channel set to 'dry' (ie: music)
+
+samplepair_t paintbuffer[(PAINTBUFFER_SIZE+1)];
+samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)];
+samplepair_t facingbuffer[(PAINTBUFFER_SIZE+1)];
+samplepair_t facingawaybuffer[(PAINTBUFFER_SIZE+1)];
+samplepair_t drybuffer[(PAINTBUFFER_SIZE+1)];
+
+// filter memory for upsampling
+samplepair_t cubicfilter1[3] = {{0,0},{0,0},{0,0}};
+samplepair_t cubicfilter2[3] = {{0,0},{0,0},{0,0}};
+
+samplepair_t linearfilter1[1] = {0,0};
+samplepair_t linearfilter2[1] = {0,0};
+samplepair_t linearfilter3[1] = {0,0};
+samplepair_t linearfilter4[1] = {0,0};
+samplepair_t linearfilter5[1] = {0,0};
+samplepair_t linearfilter6[1] = {0,0};
+samplepair_t linearfilter7[1] = {0,0};
+samplepair_t linearfilter8[1] = {0,0};
+
+// temp paintbuffer - not included in main list of paintbuffers
+samplepair_t temppaintbuffer[(PAINTBUFFER_SIZE+1)];
+samplepair_t *g_curpaintbuffer;
+
+paintbuffer_t paintbuffers[CPAINTBUFFERS];
+
+bool g_bDspOff;
+bool g_bdirectionalfx;
+int snd_scaletable[SND_SCALE_LEVELS][256];
+int *snd_p, snd_linear_count, snd_vol;
+short *snd_out;
+
+//===============================================================================
+// Mix buffer (paintbuffer) management routines
+//===============================================================================
+void MIX_FreeAllPaintbuffers( void )
+{
+ // clear paintbuffer structs
+ Mem_Set( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t ));
+}
+
+bool MIX_InitAllPaintbuffers(void)
+{
+ // clear paintbuffer structs
+ MIX_FreeAllPaintbuffers ();
+
+ // front, rear & dry paintbuffers
+ paintbuffers[IPAINTBUFFER].pbuf = paintbuffer;
+ paintbuffers[IROOMBUFFER].pbuf = roombuffer;
+ paintbuffers[IFACINGBUFFER].pbuf = facingbuffer;
+ paintbuffers[IFACINGAWAYBUFFER].pbuf = facingawaybuffer;
+ paintbuffers[IDRYBUFFER].pbuf = drybuffer;
+
+ // buffer flags
+ paintbuffers[IROOMBUFFER].flags = SOUND_BUSS_ROOM;
+ paintbuffers[IFACINGBUFFER].flags = SOUND_BUSS_FACING;
+ paintbuffers[IFACINGAWAYBUFFER].flags = SOUND_BUSS_FACINGAWAY;
+
+ MIX_SetCurrentPaintbuffer( IPAINTBUFFER );
+ return true;
+}
+
+void S_ApplyDSPEffects( int idsp, samplepair_t *pbuffront, int samplecount )
+{
+ DSP_Process( idsp, pbuffront, samplecount );
+}
void S_WriteLinearBlastStereo16( void )
{
@@ -50,10 +118,13 @@ void S_WriteLinearBlastStereo16( void )
void S_TransferStereo16( dword *pbuf, int endtime )
{
int lpos, lpaintedtime;
-
- snd_p = (int *)paintbuffer;
+
+ snd_vol = S_GetMasterVolume() * 256;
+ snd_p = (int *)PAINTBUFFER;
lpaintedtime = paintedtime;
+ if( !pbuf ) return;
+
while( lpaintedtime < endtime )
{
// handle recirculating buffer issues
@@ -103,50 +174,42 @@ void S_TransferPaintBuffer( int endtime )
}
}
+ // general case
+ p = (int *)paintbuffer;
+ count = (endtime - paintedtime) * dma.channels;
+ out_mask = dma.samples - 1;
+ out_idx = paintedtime * dma.channels & out_mask;
+ step = 3 - dma.channels;
+ snd_vol = S_GetMasterVolume() * 256;
- if( dma.samplebits == 16 && dma.channels == 2 )
- {
- // optimized case
- S_TransferStereo16( pbuf, endtime );
- }
- else
- {
- // general case
- p = (int *)paintbuffer;
- count = (endtime - paintedtime) * dma.channels;
- out_mask = dma.samples - 1;
- out_idx = paintedtime * dma.channels & out_mask;
- step = 3 - dma.channels;
+ if( !pbuf ) return;
- if( dma.samplebits == 16 )
+ if( dma.samplebits == 16 )
+ {
+ short *out = (short *)pbuf;
+
+ while( count-- )
{
- short *out = (short *)pbuf;
+ val = (*p * snd_vol) >> 8;
+ p += step;
+ val = CLIP( val );
- while( count-- )
- {
- val = *p >> 8;
- p += step;
- if( val > 0x7fff ) val = 0x7fff;
- else if( val < (short)0x8000 )
- val = (short)0x8000;
- out[out_idx] = val;
- out_idx = (out_idx + 1) & out_mask;
- }
+ out[out_idx] = val;
+ out_idx = (out_idx + 1) & out_mask;
}
- else if( dma.samplebits == 8 )
- {
- byte *out = (byte *)pbuf;
+ }
+ else if( dma.samplebits == 8 )
+ {
+ byte *out = (byte *)pbuf;
- while( count-- )
- {
- val = *p >> 8;
- p += step;
- if( val > 0x7fff ) val = 0x7fff;
- else if( val < (short)0x8000 )
- val = (short)0x8000;
- out[out_idx] = (val>>8) + 128;
- out_idx = (out_idx + 1) & out_mask;
- }
+ while( count-- )
+ {
+ val = (*p * snd_vol) >> 8;
+ p += step;
+ val = CLIP( val );
+
+ out[out_idx] = (val>>8) + 128;
+ out_idx = (out_idx + 1) & out_mask;
}
}
}
@@ -158,12 +221,411 @@ CHANNEL MIXING
===============================================================================
*/
+
+// pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer
+_inline samplepair_t * S_GetNextpFilter(int i, samplepair_t *pbuffer, samplepair_t *pfiltermem)
+{
+ // The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples
+ if (i == -1)
+ return (&(pfiltermem[0]));
+ if (i == 0)
+ return (&(pfiltermem[1]));
+ if (i == 1)
+ return (&(pfiltermem[2]));
+
+ // return from paintbuffer, where samples are doubled.
+ // even samples are to be replaced with interpolated value.
+
+ return (&(pbuffer[(i-2)*2 + 1]));
+}
+
+// pass forward over passed in buffer and cubic interpolate all odd samples
+// pbuffer: buffer to filter (in place)
+// prevfilter: filter memory. NOTE: this must match the filtertype ie: filtercubic[] for FILTERTYPE_CUBIC
+// if NULL then perform no filtering. UNDONE: should have a filter memory array type
+// count: how many samples to upsample. will become count*2 samples in buffer, in place.
+
+void S_Interpolate2xCubic( samplepair_t *pbuffer, samplepair_t *pfiltermem, int cfltmem, int count )
+{
+
+// implement cubic interpolation on 2x upsampled buffer. Effectively delays buffer contents by 2 samples.
+// pbuffer: contains samples at 0, 2, 4, 6...
+// temppaintbuffer is temp buffer, same size as paintbuffer, used to store processed values
+// count: number of samples to process in buffer ie: how many samples at 0, 2, 4, 6...
+
+// finpos is the fractional, inpos the integer part.
+// finpos = 0.5 for upsampling by 2x
+// inpos is the position of the sample
+
+// xm1 = x [inpos - 1];
+// x0 = x [inpos + 0];
+// x1 = x [inpos + 1];
+// x2 = x [inpos + 2];
+// a = (3 * (x0-x1) - xm1 + x2) / 2;
+// b = 2*x1 + xm1 - (5*x0 + x2) / 2;
+// c = (x1 - xm1) / 2;
+// y [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0;
+
+ int i, upCount = count << 1;
+ int a, b, c;
+ int xm1, x0, x1, x2;
+ samplepair_t *psamp0;
+ samplepair_t *psamp1;
+ samplepair_t *psamp2;
+ samplepair_t *psamp3;
+ int outpos = 0;
+
+ Assert (upCount <= PAINTBUFFER_SIZE);
+
+ // pfiltermem holds 6 samples from previous buffer pass
+
+ // process 'count' samples
+
+ for ( i = 0; i < count; i++)
+ {
+
+ // get source sample pointer
+
+ psamp0 = S_GetNextpFilter(i-1, pbuffer, pfiltermem);
+ psamp1 = S_GetNextpFilter(i, pbuffer, pfiltermem);
+ psamp2 = S_GetNextpFilter(i+1, pbuffer, pfiltermem);
+ psamp3 = S_GetNextpFilter(i+2, pbuffer, pfiltermem);
+
+ // write out original sample to interpolation buffer
+
+ temppaintbuffer[outpos++] = *psamp1;
+
+ // get all left samples for interpolation window
+
+ xm1 = psamp0->left;
+ x0 = psamp1->left;
+ x1 = psamp2->left;
+ x2 = psamp3->left;
+
+ // interpolate
+
+ a = (3 * (x0-x1) - xm1 + x2) / 2;
+ b = 2*x1 + xm1 - (5*x0 + x2) / 2;
+ c = (x1 - xm1) / 2;
+
+ // write out interpolated sample
+
+ temppaintbuffer[outpos].left = a/8 + b/4 + c/2 + x0;
+
+ // get all right samples for window
+
+ xm1 = psamp0->right;
+ x0 = psamp1->right;
+ x1 = psamp2->right;
+ x2 = psamp3->right;
+
+ // interpolate
+
+ a = (3 * (x0-x1) - xm1 + x2) / 2;
+ b = 2*x1 + xm1 - (5*x0 + x2) / 2;
+ c = (x1 - xm1) / 2;
+
+ // write out interpolated sample, increment output counter
+
+ temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0;
+
+ Assert( outpos <= ARRAYSIZE( temppaintbuffer ));
+ }
+
+ Assert(cfltmem >= 3);
+
+ // save last 3 samples from paintbuffer
+
+ pfiltermem[0] = pbuffer[upCount - 5];
+ pfiltermem[1] = pbuffer[upCount - 3];
+ pfiltermem[2] = pbuffer[upCount - 1];
+
+ // copy temppaintbuffer back into paintbuffer
+
+ for (i = 0; i < upCount; i++)
+ pbuffer[i] = temppaintbuffer[i];
+}
+
+// pass forward over passed in buffer and linearly interpolate all odd samples
+// pbuffer: buffer to filter (in place)
+// prevfilter: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR
+// if NULL then perform no filtering.
+// count: how many samples to upsample. will become count*2 samples in buffer, in place.
+
+void S_Interpolate2xLinear( samplepair_t *pbuffer, samplepair_t *pfiltermem, int cfltmem, int count )
+{
+ int i, upCount = count<<1;
+
+ Assert (upCount <= PAINTBUFFER_SIZE);
+ Assert (cfltmem >= 1);
+
+ // use interpolation value from previous mix
+
+ pbuffer[0].left = (pfiltermem->left + pbuffer[0].left) >> 1;
+ pbuffer[0].right = (pfiltermem->right + pbuffer[0].right) >> 1;
+
+ for ( i = 2; i < upCount; i+=2)
+ {
+ // use linear interpolation for upsampling
+
+ pbuffer[i].left = (pbuffer[i].left + pbuffer[i-1].left) >> 1;
+ pbuffer[i].right = (pbuffer[i].right + pbuffer[i-1].right) >> 1;
+ }
+
+ // save last value to be played out in buffer
+
+ *pfiltermem = pbuffer[upCount - 1];
+}
+
+// upsample by 2x, optionally using interpolation
+// count: how many samples to upsample. will become count*2 samples in buffer, in place.
+// pbuffer: buffer to upsample into (in place)
+// pfiltermem: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR
+// if NULL then perform no filtering.
+// cfltmem: max number of sample pairs filter can use
+// filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc. Must match prevfilter.
+
+void S_MixBufferUpsample2x( int count, samplepair_t *pbuffer, samplepair_t *pfiltermem, int cfltmem, int filtertype )
+{
+ int i, j, upCount = count<<1;
+
+ // reverse through buffer, duplicating contents for 'count' samples
+
+ for (i = upCount - 1, j = count - 1; j >= 0; i-=2, j--)
+ {
+ pbuffer[i] = pbuffer[j];
+ pbuffer[i-1] = pbuffer[j];
+ }
+
+ // pass forward through buffer, interpolate all even slots
+
+ switch (filtertype)
+ {
+ default:
+ break;
+ case FILTERTYPE_LINEAR:
+ S_Interpolate2xLinear(pbuffer, pfiltermem, cfltmem, count);
+ break;
+ case FILTERTYPE_CUBIC:
+ S_Interpolate2xCubic(pbuffer, pfiltermem, cfltmem, count);
+ break;
+ }
+}
+
+//===============================================================================
+// PAINTBUFFER ROUTINES
+//===============================================================================
+// Set current paintbuffer to pbuf.
+// The set paintbuffer is used by all subsequent mixing, upsampling and dsp routines.
+// Also sets the rear paintbuffer if paintbuffer has fsurround true.
+// (otherwise, rearpaintbuffer is NULL)
+_inline void MIX_SetCurrentPaintbuffer(int ipaintbuffer)
+{
+ // set front and rear paintbuffer
+ Assert( ipaintbuffer < CPAINTBUFFERS );
+ g_curpaintbuffer = paintbuffers[ipaintbuffer].pbuf;
+ Assert( g_curpaintbuffer != NULL );
+}
+
+// return index to current paintbuffer
+_inline int MIX_GetCurrentPaintbufferIndex( void )
+{
+ int i;
+
+ for( i = 0; i < CPAINTBUFFERS; i++ )
+ {
+ if( g_curpaintbuffer == paintbuffers[i].pbuf )
+ return i;
+ }
+ return 0;
+}
+
+_inline paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void )
+{
+ int ipaint = MIX_GetCurrentPaintbufferIndex();
+
+ Assert( ipaint < CPAINTBUFFERS );
+ return &paintbuffers[ipaint];
+}
+
+int S_ConvertLoopedPosition( sfxcache_t *cache, int samplePosition )
+{
+ // if the wave is looping and we're past the end of the sample
+ // convert to a position within the loop
+ // At the end of the loop, we return a short buffer, and subsequent call
+ // will loop back and get the rest of the buffer
+ if( cache->loopstart >= 0 && samplePosition >= cache->length )
+ {
+ // size of loop
+ int loopSize = cache->length - cache->loopstart;
+
+ // subtract off starting bit of the wave
+ samplePosition -= cache->loopstart;
+
+ if( loopSize )
+ {
+ // "real" position in memory (mod off extra loops)
+ samplePosition = cache->loopstart + (samplePosition % loopSize);
+ }
+ // ERROR? if no loopSize
+ }
+ return samplePosition;
+}
+
+int S_GetOutputData( sfxcache_t *cache, void **pData, int samplePosition, int sampleCount )
+{
+ int totalSampleCount;
+
+ // handle position looping
+ samplePosition = S_ConvertLoopedPosition( cache, samplePosition );
+
+ // how many samples are available (linearly not counting looping)
+ totalSampleCount = cache->length - samplePosition;
+
+ // may be asking for a sample out of range, clip at zero
+ if( totalSampleCount < 0 ) totalSampleCount = 0;
+
+ // clip max output samples to max available
+ if( sampleCount > totalSampleCount ) sampleCount = totalSampleCount;
+
+ // byte offset in sample database
+ samplePosition *= cache->width;
+
+ // if we are returning some samples, store the pointer
+ if( sampleCount )
+ {
+ *pData = cache->data + samplePosition;
+ Assert( *pData );
+ }
+ return sampleCount;
+}
+
+int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
+{
+ float inputRate, rate;
+ int startingOffset = outputOffset; // save this to compute total output
+ mixer_t *pMixer;
+
+ // shouldn't be playing this if finished, but return if we are
+ if( !pChannel || !pChannel->pMixer || pChannel->pMixer->m_finished )
+ return 0;
+
+ pMixer = pChannel->pMixer;
+ inputRate = ( pChannel->pitch * pChannel->pMixer->m_pData->speed );
+ rate = inputRate / outputRate;
+
+ // if we are terminating this wave prematurely, then make sure we detect the limit
+ if( pMixer->m_forcedEndSample )
+ {
+ // How many total input samples will we need?
+ int samplesRequired = (int)(sampleCount * rate);
+
+ // will this hit the end?
+ if( pMixer->m_sample + samplesRequired >= pMixer->m_forcedEndSample )
+ {
+ // yes, mark finished and truncate the sample request
+ pMixer->m_finished = true;
+ sampleCount = (int)((pMixer->m_forcedEndSample - pMixer->m_sample) / rate );
+ }
+ }
+
+ while( sampleCount > 0 )
+ {
+ bool advanceSample = true;
+ int availableSamples, outputSampleCount;
+ char *pData = NULL;
+
+ // compute number of input samples required
+ double end = pMixer->m_sample + rate * sampleCount;
+ int i, j, inputSampleCount = (int)(ceil(end) - floor(pMixer->m_sample));
+ double sampleFraction;
+
+ // ask the source for the data
+ char copyBuf[4096];
+
+ if( pMixer->m_delaySamples > 0 )
+ {
+ int num_zero_samples = min( pMixer->m_delaySamples, inputSampleCount );
+ int sampleSize, readBytes;
+
+ // decrement data amount
+ pMixer->m_delaySamples -= num_zero_samples;
+
+ sampleSize = pMixer->m_pData->width;
+ readBytes = sampleSize * num_zero_samples;
+
+ Assert( readBytes <= sizeof( copyBuf ));
+
+ pData = ©Buf[0];
+
+ // now copy in some zeroes
+ Mem_Set( pData, 0, readBytes );
+ availableSamples = num_zero_samples;
+ advanceSample = false;
+ }
+ else
+ {
+ availableSamples = S_GetOutputData( pMixer->m_pData, (void**)&pData,
+ pMixer->m_sample, inputSampleCount );
+ }
+
+ // none available, bail out
+ if( !availableSamples )
+ {
+ break;
+ }
+
+ sampleFraction = pMixer->m_sample - floor( pMixer->m_sample );
+ if( availableSamples < inputSampleCount )
+ {
+ // How many samples are there given the number of input samples and the rate.
+ outputSampleCount = (int)ceil((availableSamples - sampleFraction) / rate);
+ }
+ else
+ {
+ outputSampleCount = sampleCount;
+ }
+
+ // Verify that we won't get a buffer overrun.
+ Assert( floor( sampleFraction + rate * ( outputSampleCount - 1 )) <= availableSamples);
+
+ // mix this data to all active paintbuffers
+
+ // save current paintbuffer
+ j = MIX_GetCurrentPaintbufferIndex();
+
+ for( i = 0; i < CPAINTBUFFERS; i++ )
+ {
+ if( paintbuffers[i].factive && pMixer->MixFunc )
+ {
+ // mix chan into all active paintbuffers
+ MIX_SetCurrentPaintbuffer(i);
+
+ pMixer->MixFunc( pChannel, pData, outputOffset,
+ FIX_FLOAT(sampleFraction), FIX_FLOAT(rate), outputSampleCount );
+ }
+ }
+ MIX_SetCurrentPaintbuffer( j );
+
+ if( advanceSample )
+ pMixer->m_sample += outputSampleCount * rate;
+ outputOffset += outputSampleCount;
+ sampleCount -= outputSampleCount;
+ }
+
+ // did we run out of samples? if so, mark finished
+ if( sampleCount > 0 ) pMixer->m_finished = true;
+
+ // total number of samples mixed !!! at the output clock rate !!!
+ return outputOffset - startingOffset;
+}
+
void S_PaintChannelFrom8( channel_t *ch, sfxcache_t *sc, int count, int offset )
{
int data;
int *lscale, *rscale;
byte *sfx;
- portable_samplepair_t *samp;
+ samplepair_t *samp;
int i;
if( ch->leftvol > 255 ) ch->leftvol = 255;
@@ -190,7 +652,7 @@ void S_PaintChannelFrom16( channel_t *ch, sfxcache_t *sc, int count, int offset
int left, right;
int leftvol, rightvol;
signed short *sfx;
- portable_samplepair_t *samp;
+ samplepair_t *samp;
int i;
leftvol = ch->leftvol * snd_vol;
@@ -215,10 +677,32 @@ void S_PaintChannels( int endtime )
channel_t *ch;
sfxcache_t *sc;
playsound_t *ps;
- int i, end, ltime, count;
+ int i, end, ltime, count = 0;
+ float dsp_room_gain;
+ float dsp_facingaway_gain;
+ float dsp_player_gain;
+ float dsp_water_gain;
snd_vol = s_volume->value * 256;
+ CheckNewDspPresets();
+
+ g_bDspOff = dsp_off->integer ? 1 : 0;
+
+ if( !g_bDspOff )
+ g_bdirectionalfx = dsp_facingaway->integer ? 1 : 0;
+ else g_bdirectionalfx = 0;
+
+ // get dsp preset gain values, update gain crossfaders,
+ // used when mixing dsp processed buffers into paintbuffer
+
+ // update crossfader - gain only used in MIX_ScaleChannelVolume
+ dsp_room_gain = DSP_GetGain( idsp_room );
+ // update crossfader - gain only used in MIX_ScaleChannelVolume
+ dsp_facingaway_gain = DSP_GetGain( idsp_facingaway );
+ dsp_player_gain = DSP_GetGain( idsp_player );
+ dsp_water_gain = DSP_GetGain( idsp_water );
+
while( paintedtime < endtime )
{
// if paintbuffer is smaller than DMA buffer
@@ -245,7 +729,7 @@ void S_PaintChannels( int endtime )
// clear the paint buffer
if( s_rawend < paintedtime )
{
- Mem_Set( paintbuffer, 0, (end - paintedtime) * sizeof( portable_samplepair_t ));
+ Mem_Set( paintbuffer, 0, (end - paintedtime) * sizeof( samplepair_t ));
}
else
{
@@ -277,7 +761,7 @@ void S_PaintChannels( int endtime )
// might be stopped by running out of data
if( ch->end - ltime < count ) count = ch->end - ltime;
- sc = S_LoadSound( ch->sfx );
+ sc = S_LoadSound( ch->sfx, ch );
if( !sc ) break;
if( count > 0 && ch->sfx )
@@ -307,6 +791,8 @@ void S_PaintChannels( int endtime )
}
}
+ SX_RoomFX( endtime, count, endtime - paintedtime );
+
// transfer out according to DMA format
S_TransferPaintBuffer( end );
paintedtime = end;
@@ -316,12 +802,8 @@ void S_PaintChannels( int endtime )
void S_InitScaletable( void )
{
int i, j;
- int scale;
- for( i = 0; i < 32; i++ )
- {
- scale = i * 8 * 256 * s_volume->value;
- for( j = 0; j < 256; j++ ) snd_scaletable[i][j] = ((signed char)j) * scale;
- }
- s_volume->modified = false;
-}
+ for( i = 0; i < SND_SCALE_LEVELS; i++ )
+ for( j = 0; j < 256; j++ )
+ snd_scaletable[i][j] = ((signed char)j) * i * (1<> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for( i = 0; i < count; i++ )
+ {
+ data = pData8[i];
+
+ paintbuffer[i].left += lscale[data];
+ paintbuffer[i].right += rscale[data];
+ }
+}
+
+// Applies volume scaling (evenly) to all fl,fr,rl,rr volumes
+// used for voice ducking and panning between various mix busses
+
+// Called just before mixing wav data to current paintbuffer.
+// a) if another player in a multiplayer game is speaking, scale all volumes down.
+// b) if mixing to IROOMBUFFER, scale all volumes by ch.dspmix and dsp_room gain
+// c) if mixing to IFACINGAWAYBUFFER, scale all volumes by ch.dspface and dsp_facingaway gain
+// d) If SURROUND_ON, but buffer is not surround, recombined front/rear volumes
+
+void MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans )
+{
+ float scale, cone;
+ int i, *pvol;
+ int mixflag = ppaint->flags;
+ char wavtype = pChannel->wavtype;
+
+ pvol = &pChannel->leftvol;
+
+ // copy channel volumes into output array
+ for( i = 0; i < CCHANVOLUMES; i++ )
+ volume[i] = pvol[i];
+
+ // If mixing to the room buss, adjust volume based on channel's dspmix setting.
+ // dspmix is DSP_MIX_MAX (~0.78) if sound is far from player, DSP_MIX_MIN (~0.24) if sound is near player
+
+ if( mixflag & SOUND_BUSS_ROOM )
+ {
+ // get current idsp_room gain
+ float dsp_gain = DSP_GetGain( idsp_room );
+
+ // if dspmix is 1.0, 100% of sound goes to both IROOMBUFFER and IFACINGBUFFER
+ for( i = 0; i < CCHANVOLUMES; i++ )
+ volume[i] = (int)((float)(volume[i]) * pChannel->dspmix * dsp_gain );
+ }
+
+ // If mixing to facing/facingaway buss, adjust volume based on sound entity's facing direction.
+
+ // If sound directly faces player, ch->dspface = 1.0. If facing directly away, ch->dspface = -1.0.
+ // mix to lowpass buffer if facing away, to allpass if facing
+
+ // scale 1.0 - facing player, scale 0, facing away
+
+ scale = (pChannel->dspface + 1.0) / 2.0;
+
+ // UNDONE: get front cone % from channel to set this.
+
+ // bias scale such that 1.0 to 'cone' is considered facing. Facing cone narrows as cone -> 1.0
+ // and 'cone' -> 0.0 becomes 1.0 -> 0.0
+
+ cone = 0.6f;
+ scale = scale * ( 1 / cone );
+ scale = bound( 0.0f, scale, 1.0f );
+
+ // pan between facing and facing away buffers
+
+ if( !g_bdirectionalfx || wavtype != CHAR_DIRECTIONAL )
+ {
+ // if no directional fx mix 0% to facingaway buffer
+ // if wavtype is DOPPLER, mix 0% to facingaway buffer - DOPPLER wavs have a custom mixer
+ // if wavtype is OMNI, mix 0% to faceingaway buffer - OMNI wavs have no directionality
+ // if wavtype is DIRECTIONAL and stereo encoded, mix 0% to facingaway buffer -
+ // DIRECTIONAL STEREO wavs have a custom mixer
+ scale = 1.0;
+ }
+
+ if( mixflag & SOUND_BUSS_FACING )
+ {
+ // facing player
+ // if dspface is 1.0, 100% of sound goes to IFACINGBUFFER
+ for( i = 0; i < CCHANVOLUMES; i++ )
+ volume[i] = (int)((float)(volume[i]) * scale * (1.0 - pChannel->dspmix));
+ }
+ else if( mixflag & SOUND_BUSS_FACINGAWAY )
+ {
+ // facing away from player
+ // if dspface is 0.0, 100% of sound goes to IFACINGAWAYBUFFER
+
+ // get current idsp_facingaway gain
+ float dsp_gain = DSP_GetGain( idsp_facingaway );
+
+ for( i = 0; i < CCHANVOLUMES; i++ )
+ volume[i] = (int)((float)(volume[i]) * (1.0-scale) * dsp_gain * (1.0-pChannel->dspmix));
+ }
+
+ // NOTE: this must occur last in this routine:
+ for( i = 0; i < CCHANVOLUMES; i++ )
+ volume[i] = bound( 0, volume[i], 255 );
+}
+
+//===============================================================================
+// SOFTWARE MIXING ROUTINES
+//===============================================================================
+
+// UNDONE: optimize these
+
+// grab samples from left source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler left (incoming sound)
+
+void SW_Mix8StereoDopplerLeft( samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex]];
+ pOutput[i].right += rscale[pData[sampleIndex]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART( sampleFrac )<<1;
+ sampleFrac = FIX_FRACPART( sampleFrac );
+ }
+}
+
+// grab samples from right source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler right (outgoing sound)
+void SW_Mix8StereoDopplerRight( samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex+1]];
+ pOutput[i].right += rscale[pData[sampleIndex+1]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+
+}
+
+// grab samples from left source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler left (incoming sound)
+void SW_Mix16StereoDopplerLeft( samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int i, sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex]))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART( sampleFrac )<<1;
+ sampleFrac = FIX_FRACPART( sampleFrac );
+ }
+}
+
+
+// grab samples from right source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler right (outgoing sound)
+
+void SW_Mix16StereoDopplerRight( samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int i, sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex+1]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex+1]))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction
+void SW_Mix8StereoDirectional( float soundfacing, samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, x;
+ int l, r;
+ signed char lb,rb;
+
+ // if soundfacing -1.0, sound source is facing away from player
+ // if soundfacing 0.0, sound source is perpendicular to player
+ // if soundfacing 1.0, sound source is facing player
+
+ int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256
+ int rearmix = (int)(256.0f * ((1.f - soundfacing) / 2.f)); // 256 -> 0
+
+ for( i = 0; i < outCount; i++ )
+ {
+ lb = (pData[sampleIndex]); // get left byte
+ rb = (pData[sampleIndex+1]); // get right byte
+
+ l = ((int)lb) << 8; // convert to 16 bit. UNDONE: better dithering
+ r = ((int)rb) << 8;
+
+ x = ((l * frontmix) >> 8) + ((r * rearmix) >> 8);
+
+ pOutput[i].left += (volume[0] * (int)(x))>>8;
+ pOutput[i].right += (volume[1] * (int)(x))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART( sampleFrac )<<1;
+ sampleFrac = FIX_FRACPART( sampleFrac );
+ }
+}
+
+// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction
+void SW_Mix16StereoDirectional( float soundfacing, samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, x;
+ int l, r;
+
+ // if soundfacing -1.0, sound source is facing away from player
+ // if soundfacing 0.0, sound source is perpendicular to player
+ // if soundfacing 1.0, sound source is facing player
+
+ int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256
+ int rearmix = (int)(256.0f * ((1.f - soundfacing) / 2.f)); // 256 -> 0
+
+ for( i = 0; i < outCount; i++ )
+ {
+ l = (int)(pData[sampleIndex]);
+ r = (int)(pData[sampleIndex+1]);
+
+ x = ((l * frontmix) >> 8) + ((r * rearmix) >> 8);
+
+ pOutput[i].left += (volume[0] * (int)(x))>>8;
+ pOutput[i].right += (volume[1] * (int)(x))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+void SW_Mix8StereoDistVar( float distmix, samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, x;
+ int l, r;
+ signed char lb, rb;
+
+ // distmix 0 - sound is near player (100% wav left)
+ // distmix 1.0 - sound is far from player (100% wav right)
+ int nearmix = (int)(256.0f * (1.0f - distmix));
+ int farmix = (int)(256.0f * distmix);
+
+ // if mixing at max or min range, skip crossfade (KDB: perf)
+
+ if( !nearmix )
+ {
+ for( i = 0; i < outCount; i++ )
+ {
+ rb = (pData[sampleIndex+1]); // get right byte
+ x = ((int)rb) << 8; // convert to 16 bit. UNDONE: better dithering
+
+ pOutput[i].left += (volume[0] * (int)(x))>>8;
+ pOutput[i].right += (volume[1] * (int)(x))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ if( !farmix )
+ {
+ for( i = 0; i < outCount; i++ )
+ {
+ lb = (pData[sampleIndex]); // get left byte
+ x = ((int)lb) << 8; // convert to 16 bit. UNDONE: better dithering
+
+ pOutput[i].left += (volume[0] * (int)(x))>>8;
+ pOutput[i].right += (volume[1] * (int)(x))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ // crossfade left/right
+ for( i = 0; i < outCount; i++ )
+ {
+ lb = (pData[sampleIndex]); // get left byte
+ rb = (pData[sampleIndex+1]); // get right byte
+
+ l = ((int)lb) << 8; // convert to 16 bit. UNDONE: better dithering
+ r = ((int)rb) << 8;
+
+ x = ((l * nearmix) >> 8) + ((r * farmix) >> 8);
+
+ pOutput[i].left += (volume[0] * (int)(x))>>8;
+ pOutput[i].right += (volume[1] * (int)(x))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+void SW_Mix16StereoDistVar( float distmix, samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, x;
+ int l, r;
+
+ // distmix 0 - sound is near player (100% wav left)
+ // distmix 1.0 - sound is far from player (100% wav right)
+ int nearmix = (int)( 256.0f * ( 1.0f - distmix ));
+ int farmix = (int)( 256.0f * distmix );
+
+ // if mixing at max or min range, skip crossfade (KDB: perf)
+ if( !nearmix )
+ {
+ for( i = 0; i < outCount; i++ )
+ {
+ x = pData[sampleIndex+1]; // right sample
+
+ pOutput[i].left += (volume[0] * x)>>8;
+ pOutput[i].right += (volume[1] * x)>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ if( !farmix )
+ {
+ for( i = 0; i < outCount; i++ )
+ {
+ x = pData[sampleIndex]; // left sample
+
+ pOutput[i].left += (volume[0] * x)>>8;
+ pOutput[i].right += (volume[1] * x)>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ // crossfade left/right
+ for( i = 0; i < outCount; i++ )
+ {
+ l = pData[sampleIndex];
+ r = pData[sampleIndex+1];
+
+ x = ((l * nearmix) >> 8) + ((r * farmix) >> 8);
+
+ pOutput[i].left += (volume[0] * x)>>8;
+ pOutput[i].right += (volume[1] * x)>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+void SW_Mix8Mono( samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ // UNDONE: Native code this and use adc to integrate the int/frac parts separately
+ // UNDONE: Optimize when not pitch shifting?
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, *lscale, *rscale;
+
+ // not using pitch shift?
+ if( rateScaleFix == FIX( 1 ))
+ {
+ // paintbuffer native code
+ if( pOutput == paintbuffer )
+ {
+ SND_PaintChannelFrom8( volume, (byte *)pData, outCount );
+ return;
+ }
+
+ }
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex]];
+ pOutput[i].right += rscale[pData[sampleIndex]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac);
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+void SW_Mix8Stereo( samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+ int i, *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex]];
+ pOutput[i].right += rscale[pData[sampleIndex+1]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+void SW_Mix16Mono( samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int i, sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex]))>>8;
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac);
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+void SW_Mix16Stereo( samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int i, sampleIndex = 0;
+ uint sampleFrac = inputOffset;
+
+ for( i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex+1]))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+//===============================================================================
+// DISPATCHERS FOR MIXING ROUTINES
+//===============================================================================
+void Mix8MonoWavtype( channel_t *pChannel, samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ SW_Mix8Mono( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+}
+
+void Mix16MonoWavtype( channel_t *pChannel, samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ SW_Mix16Mono( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+}
+
+void Mix8StereoWavtype( channel_t *pChannel, samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ switch ( pChannel->wavtype )
+ {
+ case CHAR_DOPPLER:
+ SW_Mix8StereoDopplerLeft( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ SW_Mix8StereoDopplerRight( pOutput, &volume[IFRONT_LEFTD], pData, inputOffset, rateScaleFix, outCount );
+ break;
+ case CHAR_DIRECTIONAL:
+ SW_Mix8StereoDirectional( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+ case CHAR_DISTVARIANT:
+ SW_Mix8StereoDistVar( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount);
+ break;
+ default:
+ SW_Mix8Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+ }
+}
+
+void Mix16StereoWavtype( channel_t *pChannel, samplepair_t *pOutput, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount )
+{
+ switch ( pChannel->wavtype )
+ {
+ case CHAR_DOPPLER:
+ SW_Mix16StereoDopplerLeft( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ SW_Mix16StereoDopplerRight( pOutput, &volume[IFRONT_LEFTD], pData, inputOffset, rateScaleFix, outCount );
+ break;
+ case CHAR_DIRECTIONAL:
+ SW_Mix16StereoDirectional( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+ case CHAR_DISTVARIANT:
+ SW_Mix16StereoDistVar( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount);
+ break;
+ default:
+ SW_Mix16Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+ }
+}
+
+void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int volume[CCHANVOLUMES];
+ paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
+
+ MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 );
+
+ Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
+}
+
+
+void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int volume[CCHANVOLUMES];
+ paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
+
+ MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 );
+ Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
+}
+
+
+void Mix16Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int volume[CCHANVOLUMES];
+ paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
+
+ MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 );
+ Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (short *)pData, inputOffset, rateScaleFix, outCount );
+}
+
+
+void Mix16Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, uint rateScaleFix, int outCount )
+{
+ int volume[CCHANVOLUMES];
+ paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
+
+ MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 );
+ Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (short *)pData, inputOffset, rateScaleFix, outCount );
+}
diff --git a/snd_dx/s_stream.c b/snd_dx/s_stream.c
index fb545a45..38c17ba4 100644
--- a/snd_dx/s_stream.c
+++ b/snd_dx/s_stream.c
@@ -6,9 +6,9 @@
#include "sound.h"
#include "byteorder.h"
-portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
-int s_rawend;
-static bg_track_t s_bgTrack;
+samplepair_t s_rawsamples[MAX_RAW_SAMPLES];
+int s_rawend;
+static bg_track_t s_bgTrack;
/*
=================
diff --git a/snd_dx/snd_dx.dsp b/snd_dx/snd_dx.dsp
index 78cd625e..67931658 100644
--- a/snd_dx/snd_dx.dsp
+++ b/snd_dx/snd_dx.dsp
@@ -118,6 +118,10 @@ SOURCE=.\s_direct.c
# End Source File
# Begin Source File
+SOURCE=.\s_dsp.c
+# End Source File
+# Begin Source File
+
SOURCE=.\s_export.c
# End Source File
# Begin Source File
@@ -134,6 +138,10 @@ SOURCE=.\s_mix.c
# End Source File
# Begin Source File
+SOURCE=.\s_mix_chan.c
+# End Source File
+# Begin Source File
+
SOURCE=.\s_stream.c
# End Source File
# End Group
diff --git a/snd_dx/snd_dx.plg b/snd_dx/snd_dx.plg
index 890c1caa..5e33910b 100644
--- a/snd_dx/snd_dx.plg
+++ b/snd_dx/snd_dx.plg
@@ -6,13 +6,13 @@
--------------------Configuration: snd_dx - Win32 Debug--------------------
Command Lines
-Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSPABE.tmp" with contents
+Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP1D17.tmp" with contents
[
/nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "./" /I "../public" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"..\temp\snd_dx\!debug/" /Fo"..\temp\snd_dx\!debug/" /Fd"..\temp\snd_dx\!debug/" /FD /GZ /c
-"D:\Xash3D\src_main\snd_dx\s_main.c"
+"D:\Xash3D\src_main\snd_dx\s_direct.c"
]
-Creating command line "cl.exe @"C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSPABE.tmp""
-Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSPABF.tmp" with contents
+Creating command line "cl.exe @"C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP1D17.tmp""
+Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP1D18.tmp" with contents
[
winmm.lib /nologo /dll /incremental:yes /pdb:"..\temp\snd_dx\!debug/snd_dx.pdb" /debug /machine:I386 /nodefaultlib:"libcmt.lib" /out:"..\temp\snd_dx\!debug/snd_dx.dll" /implib:"..\temp\snd_dx\!debug/snd_dx.lib" /pdbtype:sept
"\Xash3D\src_main\temp\snd_dx\!debug\s_direct.obj"
@@ -21,17 +21,20 @@ winmm.lib /nologo /dll /incremental:yes /pdb:"..\temp\snd_dx\!debug/snd_dx.pdb"
"\Xash3D\src_main\temp\snd_dx\!debug\s_main.obj"
"\Xash3D\src_main\temp\snd_dx\!debug\s_mix.obj"
"\Xash3D\src_main\temp\snd_dx\!debug\s_stream.obj"
+"\Xash3D\src_main\temp\snd_dx\!debug\s_dsp.obj"
+"\Xash3D\src_main\temp\snd_dx\!debug\s_mix_chan.obj"
]
-Creating command line "link.exe @"C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSPABF.tmp""
-Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSPAC0.bat" with contents
+Creating command line "link.exe @"C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP1D18.tmp""
+Creating temporary file "C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP1D19.bat" with contents
[
@echo off
copy \Xash3D\src_main\temp\snd_dx\!debug\snd_dx.dll "D:\Xash3D\bin\snd_dx.dll"
]
-Creating command line ""C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSPAC0.bat""
+Creating command line ""C:\DOCUME~1\ÌÈØÀ\LOCALS~1\Temp\RSP1D19.bat""
Compiling...
-s_main.c
+s_direct.c
Linking...
+ Creating library ..\temp\snd_dx\!debug/snd_dx.lib and object ..\temp\snd_dx\!debug/snd_dx.exp
Output Window
Performing Custom Build Step on \Xash3D\src_main\temp\snd_dx\!debug\snd_dx.dll
‘ª®¯¨à®¢ ® ä ©«®¢: 1.
diff --git a/snd_dx/sound.h b/snd_dx/sound.h
index f85bb9aa..1b35d7a5 100644
--- a/snd_dx/sound.h
+++ b/snd_dx/sound.h
@@ -15,13 +15,66 @@ extern stdlib_api_t com;
extern vsound_imp_t si;
extern byte *sndpool;
+enum
+{
+ IPAINTBUFFER = 0,
+ IROOMBUFFER,
+ IFACINGBUFFER,
+ IFACINGAWAYBUFFER,
+ IDRYBUFFER,
+ CPAINTBUFFERS // count of buffers
+};
+
+enum
+{
+ IFRONT_LEFT = 0, // NOTE: must correspond to order of leftvol...drrightvol above!
+ IFRONT_RIGHT,
+ IFRONT_LEFTD, // start of doppler right array
+ IFRONT_RIGHTD,
+ CCHANVOLUMES
+};
+
+// fixed point stuff for real-time resampling
+#define FIX_BITS 28
+#define FIX_SCALE (1 << FIX_BITS)
+#define FIX_MASK ((1 << FIX_BITS)-1)
+#define FIX_FLOAT( a ) ((int)((a) * FIX_SCALE))
+#define FIX( a ) (((int)(a)) << FIX_BITS)
+#define FIX_INTPART( a ) (((int)(a)) >> FIX_BITS)
+#define FIX_FRACTION( a, b ) (FIX(a)/(b))
+#define FIX_FRACPART( a ) ((a) & FIX_MASK)
+
+#define CPAINTFILTERMEM 3
+#define CPAINTFILTERS 4 // maximum number of consecutive upsample passes per paintbuffer
+#define PAINTBUFFER_SIZE 1024 // 44k: was 512
+#define PAINTBUFFER (g_curpaintbuffer)
+
+#define CHAR_SENTENCE '!' // as one of 1st 2 chars in name, indicates sentence wav
+#define CHAR_DRYMIX '#' // as one of 1st 2 chars in name, indicates wav bypasses dsp fx
+#define CHAR_DOPPLER '>' // as one of 1st 2 chars in name, indicates doppler encoded stereo wav
+#define CHAR_DIRECTIONAL '<' // as one of 1st 2 chars in name, indicates mono or stereo wav has direction cone
+#define CHAR_DISTVARIANT '^' // as one of 1st 2 chars in name, indicates distance variant encoded stereo wav
+
#include "mathlib.h"
typedef struct
{
int left;
int right;
-} portable_samplepair_t;
+} samplepair_t;
+
+typedef struct
+{
+ bool factive; // if true, mix to this paintbuffer using flags
+ int flags; // SOUND_BUSS_ROOM, SOUND_BUSS_FACING, SOUND_BUSS_FACINGAWAY
+ samplepair_t *pbuf; // front stereo mix buffer, for 2 channel mixing
+ int ifilter; // current filter memory buffer to use for upsampling pass
+
+ // filter memory, for upsampling with linear or cubic interpolation
+ samplepair_t fltmem[CPAINTFILTERS][CPAINTFILTERMEM];
+} paintbuffer_t;
+
+typedef void (*MixFn)( struct channel_s *chan, char *data, int outofs, int inofs, uint rate, int count );
typedef struct
{
@@ -30,18 +83,45 @@ typedef struct
int speed; // not needed, because converted on load?
int width;
int stereo;
+ bool sentence; // set to true if it's a sentence
byte data[1]; // variable sized
} sfxcache_t;
+typedef struct mixer_s
+{
+ double m_sample;
+
+ sfxcache_t *m_pData;
+ double m_forcedEndSample;
+ bool m_finished;
+ int m_delaySamples;
+
+ MixFn MixFunc;
+} mixer_t;
+
typedef struct sfx_s
{
string name;
sfxcache_t *cache;
+ mixer_t *mixer;
int registration_sequence;
bool default_sound;
} sfx_t;
+typedef struct voxword_s
+{
+ int volume; // increase percent, ie: 125 = 125% increase
+ int pitch; // pitch shift up percent
+ int start; // offset start of wave percent
+ int end; // offset end of wave percent
+ int cbtrim; // end of wave after being trimmed to 'end'
+ int fKeepCached; // 1 if this word was already in cache before sentence referenced it
+ int samplefrac; // if pitch shifting, this is position into wav * 256
+ int timecompress; // % of wave to skip during playback (causes no pitch shift)
+ sfx_t *sfx; // name and cache pointer
+} voxword_t;
+
// a playsound_t will be generated by each call to S_StartSound,
// when the mixer reaches playsound->begin, the playsound will
// be assigned to a channel
@@ -70,11 +150,23 @@ typedef struct
byte *buffer;
} dma_t;
-typedef struct
+typedef enum
{
+ src_sound = 0,
+ src_sentence
+} srctype_t;
+
+typedef struct channel_s
+{
+ srctype_t type;
sfx_t *sfx; // sfx number
+ mixer_t *pMixer; // sound mixer data
+
int leftvol; // 0-255 volume
int rightvol; // 0-255 volume
+ int dleftvol; // 0-255 front left volume - doppler outgoing wav
+ int drightvol; // 0-255 front right volume - doppler outgoing wav
+
int end; // end time in global paintsamples
int pos; // sample position in sfx
int looping; // where to loop, -1 = no looping OBSOLETE?
@@ -83,8 +175,28 @@ typedef struct
int loopframe; // for stopping looping sounds
int entchannel; //
vec3_t origin; // only use if fixed_origin is set
+ vec3_t direction; // direction of the sound
vec_t dist_mult; // distance multiplier (attenuation/clipK)
int master_vol; // 0-255 master volume
+ int basePitch; // base pitch percent (100% is normal pitch playback)
+ float pitch; // real-time pitch after any modulation or shift by dynamic data
+
+ float dspmix; // 0 - 1.0 proportion of dsp to mix with original sound,
+ // based on distance
+ float dspface; // -1.0 - 1.0 (1.0 = facing listener)
+ float distmix; // 0 - 1.0 proportion based on distance from listener
+ // (1.0 - 100% wav right - far)
+ bool bdry; // if true, bypass all dsp processing for this sound (ie: music)
+ bool bstereowav; // if true, a stereo .wav file is the sample data source
+ char wavtype; // 0 default, CHAR_DOPPLER, CHAR_DIRECTIONAL, CHAR_DISTVARIANT
+ float radius; // Radius of this sound effect
+ // (spatialization is different within the radius)
+ bool bfirstpass; // true if this is first time sound is spatialized
+ float ob_gain; // gain drop if sound source obscured from listener
+ float ob_gain_target; // target gain while crossfading between ob_gain & ob_gain_target
+ float ob_gain_inc; // crossfade increment
+ bool delayed_start; // If true, sound had a delay and so same sound on same channel
+ // won't channel steal from it
bool fixed_origin; // use origin instead of fetching entnum's origin
bool autosound; // from an entity->sound, cleared each frame
bool use_loop; // don't loop default and local sounds
@@ -122,6 +234,27 @@ typedef struct
#define Host_Error com.error
#define Z_Malloc( size ) Mem_Alloc( sndpool, size )
+// hl2 used backward assertation if( !x )
+#define Assert( x ) if(!(x)) com.abort( "assert failed at %s:%i\n", __FILE__, __LINE__ )
+#define ARRAYSIZE( p ) (sizeof( p ) / sizeof( p[0] ))
+#define SOUND_DMA_SPEED (dma.speed)
+#define SOUND_11k 11025 // 11khz sample rate
+#define SOUND_22k 22050 // 22khz sample rate
+#define SOUND_44k 44100 // 44khz sample rate
+#define SOUND_ALL_RATES 1 // mix all sample rates
+
+#define SND_SCALE_BITS 7
+#define SND_SCALE_SHIFT (8 - SND_SCALE_BITS)
+#define SND_SCALE_LEVELS (1<integer && gl_delayfinish->integer )
{
@@ -1736,7 +1736,7 @@ void R_BeginFrame( void )
r_environment_color->modified = false;
}
- if( r_clear->integer )
+ if( r_clear->integer && !fd->paused )
{
rgba_t color;
@@ -1744,6 +1744,7 @@ void R_BeginFrame( void )
pglClearColor( color[0]*( 1.0/255.0 ), color[1]*( 1.0/255.0 ), color[2]*( 1.0/255.0 ), 1 );
pglClear( GL_COLOR_BUFFER_BIT );
}
+
// update gamma
if( r_gamma->modified )
{
diff --git a/vid_gl/vid_gl.plg b/vid_gl/vid_gl.plg
new file mode 100644
index 00000000..f7fba9f6
--- /dev/null
+++ b/vid_gl/vid_gl.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: vid_gl - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+vid_gl.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/vprogs/vprogs.plg b/vprogs/vprogs.plg
new file mode 100644
index 00000000..d5eb623c
--- /dev/null
+++ b/vprogs/vprogs.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: vprogs - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+vprogs.dll - 0 error(s), 0 warning(s)
+
+
+
diff --git a/xtools/xtools.plg b/xtools/xtools.plg
new file mode 100644
index 00000000..2a5b16d7
--- /dev/null
+++ b/xtools/xtools.plg
@@ -0,0 +1,16 @@
+
+
+
+Build Log
+
+--------------------Configuration: xtools - Win32 Debug--------------------
+
+Command Lines
+
+
+
+Results
+xtools.dll - 0 error(s), 0 warning(s)
+
+
+