diff --git a/change.log b/change.log index cadbc969..361936c0 100644 --- a/change.log +++ b/change.log @@ -1,8 +1,13 @@ -build ???? +build 1313 Launch: code revision, remove dead code, implement some missed functions Sound: fix some errors and crash Sound: implement music volume +Sound: reworking sound mixer +Sound: implement DSP +Engine: add support for five mouse extra-buttons +Engine: fix some physics bugs +SDK: add blue-shift game source to SDK (combined with main game source) build 1305 diff --git a/dlls/bshift.dsp b/dlls/bshift.dsp new file mode 100644 index 00000000..e4efaa0f --- /dev/null +++ b/dlls/bshift.dsp @@ -0,0 +1,705 @@ +# Microsoft Developer Studio Project File - Name="bshift" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=bshift - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "bshift.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "bshift.mak" CFG="bshift - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "bshift - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "bshift - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "bshift - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\bshift\!release" +# PROP Intermediate_Dir "..\temp\bshift\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\dlls" /I "..\common" /I "..\game_shared" /I "..\\" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "BSHIFT_DLL" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 msvcrt.lib /nologo /subsystem:windows /dll /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /def:".\hl.def" /out:"..\temp\bshift\!release/hl.dll" +# SUBTRACT LINK32 /profile /map /debug +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\bshift\!release +InputPath=\Xash3D\src_main\temp\bshift\!release\hl.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bshift\bin\server.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hl.dll "D:\Xash3D\bshift\bin\server.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "bshift - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\temp\bshift\!debug" +# PROP Intermediate_Dir "..\temp\bshift\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "..\dlls" /I "..\common" /I "..\game_shared" /I "..\\" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "BSHIFT_DLL" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:windows /dll /debug /machine:I386 /nodefaultlib:"libc.lib" /def:".\hl.def" /out:"..\temp\bshift\!debug/hl.dll" /pdbtype:sept +# SUBTRACT LINK32 /profile +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\bshift\!debug +InputPath=\Xash3D\src_main\temp\bshift\!debug\hl.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bshift\bin\server.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hl.dll "D:\Xash3D\bshift\bin\server.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "bshift - Win32 Release" +# Name "bshift - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\aflock.cpp +# End Source File +# Begin Source File + +SOURCE=.\agrunt.cpp +# End Source File +# Begin Source File + +SOURCE=.\airtank.cpp +# End Source File +# Begin Source File + +SOURCE=.\animating.cpp +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp +# End Source File +# Begin Source File + +SOURCE=.\apache.cpp +# End Source File +# Begin Source File + +SOURCE=.\barnacle.cpp +# End Source File +# Begin Source File + +SOURCE=.\barney.cpp +# End Source File +# Begin Source File + +SOURCE=.\bigmomma.cpp +# End Source File +# Begin Source File + +SOURCE=.\bloater.cpp +# End Source File +# Begin Source File + +SOURCE=.\bmodels.cpp +# End Source File +# Begin Source File + +SOURCE=.\bullsquid.cpp +# End Source File +# Begin Source File + +SOURCE=.\buttons.cpp +# End Source File +# Begin Source File + +SOURCE=.\cbase.cpp +# End Source File +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp +# End Source File +# Begin Source File + +SOURCE=.\controller.cpp +# End Source File +# Begin Source File + +SOURCE=.\crossbow.cpp +# End Source File +# Begin Source File + +SOURCE=.\crowbar.cpp +# End Source File +# Begin Source File + +SOURCE=.\defaultai.cpp +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp +# End Source File +# Begin Source File + +SOURCE=.\egon.cpp +# End Source File +# Begin Source File + +SOURCE=.\explode.cpp +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_break.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_tank.cpp +# End Source File +# Begin Source File + +SOURCE=.\game.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\gargantua.cpp +# End Source File +# Begin Source File + +SOURCE=.\gauss.cpp +# End Source File +# Begin Source File + +SOURCE=.\genericmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\ggrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\globals.cpp +# End Source File +# Begin Source File + +SOURCE=.\glock.cpp +# End Source File +# Begin Source File + +SOURCE=.\gman.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_cine.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_cycler.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\handgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\hassassin.cpp +# End Source File +# Begin Source File + +SOURCE=.\headcrab.cpp +# End Source File +# Begin Source File + +SOURCE=.\healthkit.cpp +# End Source File +# Begin Source File + +SOURCE=.\hgrunt.cpp +# End Source File +# Begin Source File + +SOURCE=.\hornet.cpp +# End Source File +# Begin Source File + +SOURCE=.\hornetgun.cpp +# End Source File +# Begin Source File + +SOURCE=.\houndeye.cpp +# End Source File +# Begin Source File + +SOURCE=.\ichthyosaur.cpp +# End Source File +# Begin Source File + +SOURCE=.\islave.cpp +# End Source File +# Begin Source File + +SOURCE=.\items.cpp +# End Source File +# Begin Source File + +SOURCE=.\leech.cpp +# End Source File +# Begin Source File + +SOURCE=.\lights.cpp +# End Source File +# Begin Source File + +SOURCE=.\maprules.cpp +# End Source File +# Begin Source File + +SOURCE=.\monstermaker.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsterstate.cpp +# End Source File +# Begin Source File + +SOURCE=.\mortar.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp5.cpp +# End Source File +# Begin Source File + +SOURCE=.\multiplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\nihilanth.cpp +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\osprey.cpp +# End Source File +# Begin Source File + +SOURCE=.\pathcorner.cpp +# End Source File +# Begin Source File + +SOURCE=.\plane.cpp +# End Source File +# Begin Source File + +SOURCE=.\plats.cpp +# End Source File +# Begin Source File + +SOURCE=.\player.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_debug.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_math.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_shared.cpp +# End Source File +# Begin Source File + +SOURCE=.\python.cpp +# End Source File +# Begin Source File + +SOURCE=.\rat.cpp +# End Source File +# Begin Source File + +SOURCE=.\roach.cpp +# End Source File +# Begin Source File + +SOURCE=.\rpg.cpp +# End Source File +# Begin Source File + +SOURCE=.\satchel.cpp +# End Source File +# Begin Source File + +SOURCE=.\schedule.cpp +# End Source File +# Begin Source File + +SOURCE=.\scientist.cpp +# End Source File +# Begin Source File + +SOURCE=.\scripted.cpp +# End Source File +# Begin Source File + +SOURCE=.\shotgun.cpp +# End Source File +# Begin Source File + +SOURCE=.\singleplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp +# End Source File +# Begin Source File + +SOURCE=.\soundent.cpp +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\squadmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\squeakgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\tempmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\tentacle.cpp +# End Source File +# Begin Source File + +SOURCE=.\triggers.cpp +# End Source File +# Begin Source File + +SOURCE=.\tripmine.cpp +# End Source File +# Begin Source File + +SOURCE=.\turret.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\world.cpp +# End Source File +# Begin Source File + +SOURCE=.\xen.cpp +# End Source File +# Begin Source File + +SOURCE=.\zombie.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\activitymap.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\basemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cbase.h +# End Source File +# Begin Source File + +SOURCE=.\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\defaultai.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=..\engine\eiface.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\hornet.h +# End Source File +# Begin Source File + +SOURCE=.\items.h +# End Source File +# Begin Source File + +SOURCE=.\monsterevent.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\plane.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\saverestore.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=.\scripted.h +# End Source File +# Begin Source File + +SOURCE=.\scriptevent.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\soundent.h +# End Source File +# Begin Source File + +SOURCE=.\spectator.h +# End Source File +# Begin Source File + +SOURCE=.\squadmonster.h +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.h +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\trains.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/dlls/effects.cpp b/dlls/effects.cpp index 5d7a1b38..2a648ad1 100644 --- a/dlls/effects.cpp +++ b/dlls/effects.cpp @@ -2265,4 +2265,122 @@ void CItemSoda::CanTouch ( CBaseEntity *pOther ) SetTouch ( NULL ); SetThink ( SUB_Remove ); pev->nextthink = gpGlobals->time; -} \ No newline at end of file +} + +#ifdef BSHIFT_DLL +//========================================================= +// env_warpball +//========================================================= +#define SF_REMOVE_ON_FIRE 0x0001 +#define SF_KILL_CENTER 0x0002 + +class CEnvWarpBall : public CBaseEntity +{ +public: + void Precache( void ); + void Spawn( void ) { Precache(); } + void Think( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + Vector vecOrigin; +}; + +LINK_ENTITY_TO_CLASS( env_warpball, CEnvWarpBall ); + +void CEnvWarpBall :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->button = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "warp_target")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "damage_delay")) + { + pev->frags = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvWarpBall::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); + PRECACHE_MODEL( "sprites/Fexplo1.spr" ); + PRECACHE_SOUND( "debris/beamstart2.wav" ); + PRECACHE_SOUND( "debris/beamstart7.wav" ); +} + +void CEnvWarpBall::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int iTimes = 0; + int iDrawn = 0; + TraceResult tr; + Vector vecDest; + CBeam *pBeam; + CBaseEntity *pEntity = UTIL_FindEntityByTargetname ( NULL, STRING(pev->message)); + edict_t *pos; + + if(pEntity)//target found ? + { + vecOrigin = pEntity->pev->origin; + pos = pEntity->edict(); + } + else + { //use as center + vecOrigin = pev->origin; + pos = edict(); + } + EMIT_SOUND( pos, CHAN_BODY, "debris/beamstart2.wav", 1, ATTN_NORM ); + UTIL_ScreenShake( vecOrigin, 6, 160, 1.0, pev->button ); + CSprite *pSpr = CSprite::SpriteCreate( "sprites/Fexplo1.spr", vecOrigin, TRUE ); + pSpr->AnimateAndDie( 18 ); + pSpr->SetTransparency(kRenderGlow, 77, 210, 130, 255, kRenderFxNoDissipation); + EMIT_SOUND( pos, CHAN_ITEM, "debris/beamstart7.wav", 1, ATTN_NORM ); + int iBeams = RANDOM_LONG(20, 40); + while (iDrawn < iBeams && iTimes < (iBeams * 3)) + { + vecDest = pev->button * (Vector(RANDOM_FLOAT(-1,1), RANDOM_FLOAT(-1,1), RANDOM_FLOAT(-1,1)).Normalize()); + UTIL_TraceLine( vecOrigin, vecOrigin + vecDest, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + { + // we hit something. + iDrawn++; + pBeam = CBeam::BeamCreate("sprites/lgtning.spr", 200); + pBeam->PointsInit( vecOrigin, tr.vecEndPos ); + pBeam->SetColor( 20, 243, 20 ); + pBeam->SetNoise( 65 ); + pBeam->SetBrightness( 220 ); + pBeam->SetWidth( 30 ); + pBeam->SetScrollRate( 35 ); + pBeam->SetThink(&CBeam:: SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.5, 1.6); + } + iTimes++; + } + pev->nextthink = gpGlobals->time + pev->frags; +} + +void CEnvWarpBall::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0); + + if ( pev->spawnflags & SF_KILL_CENTER ) + { + CBaseEntity *pMonster = NULL; + + while ((pMonster = UTIL_FindEntityInSphere( pMonster, vecOrigin, 72 )) != NULL) + { + if ( FBitSet( pMonster->pev->flags, FL_MONSTER ) || FClassnameIs( pMonster->pev, "player")) + pMonster->TakeDamage ( pev, pev, 100, DMG_GENERIC ); + } + } + if ( pev->spawnflags & SF_REMOVE_ON_FIRE ) UTIL_Remove( this ); +} +#endif \ No newline at end of file diff --git a/dlls/genericmonster.cpp b/dlls/genericmonster.cpp index 8f40be61..0d926a14 100644 --- a/dlls/genericmonster.cpp +++ b/dlls/genericmonster.cpp @@ -20,15 +20,17 @@ #include "cbase.h" #include "monsters.h" #include "schedule.h" +#include "talkmonster.h" // For holograms, make them not solid so the player can walk through them -#define SF_GENERICMONSTER_NOTSOLID 4 +#define SF_GENERICMONSTER_NOTSOLID 4 +#define SF_HEAD_CONTROLLER 8 //========================================================= // Monster's Anim Events Go Here //========================================================= -class CGenericMonster : public CBaseMonster +class CGenericMonster : public CTalkMonster { public: void Spawn( void ); @@ -120,6 +122,11 @@ void CGenericMonster :: Spawn() MonsterInit(); + if ( pev->spawnflags & SF_HEAD_CONTROLLER ) + { + m_afCapability = bits_CAP_TURN_HEAD; + } + if ( pev->spawnflags & SF_GENERICMONSTER_NOTSOLID ) { pev->solid = SOLID_NOT; @@ -132,6 +139,8 @@ void CGenericMonster :: Spawn() //========================================================= void CGenericMonster :: Precache() { + CTalkMonster::Precache(); + TalkInit(); PRECACHE_MODEL( (char *)STRING(pev->model) ); } diff --git a/dlls/items.cpp b/dlls/items.cpp index dec47540..76c95296 100644 --- a/dlls/items.cpp +++ b/dlls/items.cpp @@ -340,3 +340,72 @@ class CItemLongJump : public CItem }; LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump ); + +#ifdef BSHIFT_DLL +class CItemArmorVest : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/barney_vest.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/barney_vest.mdl"); + PRECACHE_SOUND( "items/gunpickup2.wav" ); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ((pPlayer->pev->armorvalue < MAX_NORMAL_BATTERY) && + (pPlayer->pev->weapons & (1<pev->armorvalue += 60; + pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS(item_armorvest, CItemArmorVest); + + +class CItemHelmet : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/barney_helmet.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/barney_helmet.mdl"); + PRECACHE_SOUND( "items/gunpickup2.wav" ); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ((pPlayer->pev->armorvalue < MAX_NORMAL_BATTERY) && + (pPlayer->pev->weapons & (1<pev->armorvalue += 40; + pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS(item_helmet, CItemHelmet); +#endif \ No newline at end of file diff --git a/dlls/scientist.cpp b/dlls/scientist.cpp index ab37d9cd..529cf65b 100644 --- a/dlls/scientist.cpp +++ b/dlls/scientist.cpp @@ -115,6 +115,10 @@ private: LINK_ENTITY_TO_CLASS( monster_scientist, CScientist ); +#ifdef BSHIFT_DLL +LINK_ENTITY_TO_CLASS( monster_rosenberg, CScientist ); +#endif + TYPEDESCRIPTION CScientist::m_SaveData[] = { DEFINE_FIELD( CScientist, m_painTime, FIELD_TIME ), @@ -428,7 +432,9 @@ void CScientist::DeclineFollowing( void ) { Talk( 10 ); m_hTalkTarget = m_hEnemy; - PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM ); + if ( FClassnameIs(pev, "monster_rosenberg")) + PlaySentence( "RO_POK", 2, VOL_NORM, ATTN_NORM ); + else PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM ); } @@ -438,7 +444,9 @@ void CScientist :: Scream( void ) { Talk( 10 ); m_hTalkTarget = m_hEnemy; - PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + if ( FClassnameIs(pev, "monster_rosenberg")) + PlaySentence( "RO_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + else PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); } } @@ -459,7 +467,9 @@ void CScientist :: StartTask( Task_t *pTask ) // if ( FOkToSpeak() ) Talk( 2 ); m_hTalkTarget = m_hTargetEnt; - PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); + if ( FClassnameIs(pev, "monster_rosenberg")) + PlaySentence( "RO_HEAL", 2, VOL_NORM, ATTN_IDLE ); + else PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); TaskComplete(); break; @@ -480,10 +490,16 @@ void CScientist :: StartTask( Task_t *pTask ) { Talk( 2 ); m_hTalkTarget = m_hEnemy; - if ( m_hEnemy->IsPlayer() ) - PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); + if ( FClassnameIs(pev, "monster_rosenberg")) + { + PlaySentence( "RO_FEAR", 5, VOL_NORM, ATTN_NORM ); + } else - PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); + { + if ( m_hEnemy->IsPlayer() ) + PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); + else PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); + } } TaskComplete(); break; @@ -664,7 +680,11 @@ void CScientist :: Spawn( void ) pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_RED; - pev->health = gSkillData.scientistHealth; + + if ( FClassnameIs(pev, "monster_rosenberg")) + pev->health = gSkillData.scientistHealth * 2; + else pev->health = gSkillData.scientistHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello m_MonsterState = MONSTERSTATE_NONE; @@ -700,7 +720,9 @@ void CScientist :: Precache( void ) PRECACHE_SOUND("scientist/sci_pain3.wav"); PRECACHE_SOUND("scientist/sci_pain4.wav"); PRECACHE_SOUND("scientist/sci_pain5.wav"); - +#ifdef BSHIFT_DLL + PRECACHE_SOUND("rosenberg/ro_pain1.wav"); +#endif // every new scientist must call this, otherwise // when a level is loaded, nobody will talk (time is reset to 0) TalkInit(); @@ -722,27 +744,55 @@ void CScientist :: TalkInit() // scientists speach group names (group names are in sentences.txt) - m_szGrp[TLK_ANSWER] = "SC_ANSWER"; - m_szGrp[TLK_QUESTION] = "SC_QUESTION"; - m_szGrp[TLK_IDLE] = "SC_IDLE"; - m_szGrp[TLK_STARE] = "SC_STARE"; - m_szGrp[TLK_USE] = "SC_OK"; - m_szGrp[TLK_UNUSE] = "SC_WAIT"; - m_szGrp[TLK_STOP] = "SC_STOP"; - m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; - m_szGrp[TLK_HELLO] = "SC_HELLO"; + // scientists speach group names (group names are in sentences.txt) + if ( FClassnameIs(pev, "monster_rosenberg")) + { + m_szGrp[TLK_ANSWER] = "RO_ANSWER"; + m_szGrp[TLK_QUESTION] = "RO_QUESTION"; + m_szGrp[TLK_IDLE] = "RO_IDLE"; + m_szGrp[TLK_STARE] = "RO_STARE"; + m_szGrp[TLK_USE] = "RO_OK"; + m_szGrp[TLK_UNUSE] = "RO_WAIT"; + m_szGrp[TLK_STOP] = "RO_STOP"; + m_szGrp[TLK_NOSHOOT] = "RO_SCARED"; + m_szGrp[TLK_HELLO] = "RO_HELLO"; - m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; - m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; - m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + m_szGrp[TLK_PLHURT1] = "!RO_CUREA"; + m_szGrp[TLK_PLHURT2] = "!RO_CUREB"; + m_szGrp[TLK_PLHURT3] = "!RO_CUREC"; - m_szGrp[TLK_PHELLO] = "SC_PHELLO"; - m_szGrp[TLK_PIDLE] = "SC_PIDLE"; - m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; - m_szGrp[TLK_SMELL] = "SC_SMELL"; + m_szGrp[TLK_PHELLO] = "RO_PHELLO"; + m_szGrp[TLK_PIDLE] = "RO_PIDLE"; + m_szGrp[TLK_PQUESTION] = "RO_PQUEST"; + m_szGrp[TLK_SMELL] = "RO_SMELL"; - m_szGrp[TLK_WOUND] = "SC_WOUND"; - m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + m_szGrp[TLK_WOUND] = "RO_WOUND"; + m_szGrp[TLK_MORTAL] = "RO_MORTAL"; + } + else + { + m_szGrp[TLK_ANSWER] = "SC_ANSWER"; + m_szGrp[TLK_QUESTION] = "SC_QUESTION"; + m_szGrp[TLK_IDLE] = "SC_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + m_szGrp[TLK_USE] = "SC_OK"; + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "SC_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "SC_PHELLO"; + m_szGrp[TLK_PIDLE] = "SC_PIDLE"; + m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + } // get voice for head switch (pev->body % 3) @@ -753,6 +803,9 @@ void CScientist :: TalkInit() case HEAD_LUTHER: m_voicePitch = 95; break; //luther case HEAD_SLICK: m_voicePitch = 100; break;//slick } + + if ( FClassnameIs(pev, "monster_rosenberg")) + m_voicePitch = 95; // rosenberg has too low voice } int CScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) @@ -760,8 +813,11 @@ int CScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, f if ( pevInflictor && pevInflictor->flags & FL_CLIENT ) { - Remember( bits_MEMORY_PROVOKED ); - StopFollowing( TRUE ); + if ( !FClassnameIs(pev, "monster_rosenberg")) + { + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } } // make sure friends talk about it if player hurts scientist... @@ -792,6 +848,12 @@ void CScientist :: PainSound ( void ) m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + if ( FClassnameIs(pev, "monster_rosenberg")) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "rosenberg/ro_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); + return; + } + switch (RANDOM_LONG(0,4)) { case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; @@ -971,7 +1033,7 @@ Schedule_t *CScientist :: GetSchedule ( void ) } return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. } - else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared + else if ( !FClassnameIs(pev, "monster_rosenberg")) // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared { if ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react return GetScheduleOfType( SCHED_FEAR ); // React to something scary diff --git a/dlls/talkmonster.cpp b/dlls/talkmonster.cpp index 76ac05b7..7826a8ee 100644 --- a/dlls/talkmonster.cpp +++ b/dlls/talkmonster.cpp @@ -928,7 +928,7 @@ int CTalkMonster :: FOkToSpeak( void ) if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) return FALSE; - if ( pev->spawnflags & SF_MONSTER_GAG ) + if ( pev->spawnflags & SF_MONSTER_GAG && !FClassnameIs(pev, "monster_generic") ) return FALSE; if ( m_MonsterState == MONSTERSTATE_PRONE ) diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp index bd36a082..08dc88a7 100644 --- a/dlls/triggers.cpp +++ b/dlls/triggers.cpp @@ -2072,11 +2072,25 @@ void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) pOther->pev->gravity = pev->gravity; } +#ifdef BSHIFT_DLL +class CTriggerPlayerFreeze : public CBaseDelay +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; +LINK_ENTITY_TO_CLASS( trigger_playerfreeze, CTriggerPlayerFreeze ); +void CTriggerPlayerFreeze::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); - - - + if (pActivator->pev->flags & FL_FROZEN) + ((CBasePlayer *)((CBaseEntity *)pActivator))->EnableControl(TRUE); + else ((CBasePlayer *)((CBaseEntity *)pActivator))->EnableControl(FALSE); +} +#endif // this is a really bad idea. class CTriggerChangeTarget : public CBaseDelay diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index d62b6096..9707c070 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -21,8 +21,8 @@ bool CL_CopyEntityToPhysEnt( physent_t *pe, cl_entity_t *ent ) { model_t *mod = CM_ClipHandleToModel( ent->curstate.modelindex ); - // bad model ? - if( !mod || mod->type == mod_bad ) + // NOTE: player never collide with sprites (even with solid sprites) + if( !mod || mod->type == mod_bad || mod->type == mod_sprite ) return false; pe->player = ent->player; @@ -435,8 +435,8 @@ void CL_SetSolidEntities( void ) for( i = 0; i < 3; i++ ) { - absmin[i] = cl.frame.cd.origin[i] - 256; - absmax[i] = cl.frame.cd.origin[i] + 256; + absmin[i] = cl.frame.cd.origin[i] - 1024; + absmax[i] = cl.frame.cd.origin[i] + 1024; } CL_CopyEntityToPhysEnt( &clgame.pmove->physents[0], &clgame.entities[0] ); diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index b805b62e..82a84644 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -303,35 +303,36 @@ bool Cmd_GetMusicList( const char *s, char *completedname, int length ) string matchbuf; int i, numtracks; - t = FS_Search( va( "media/%s*.ogg", s ), true ); + t = FS_Search( va( "media/%s*.*", s ), true ); if( !t ) return false; - FS_FileBase(t->filenames[0], matchbuf ); - if(completedname && length) com.strncpy( completedname, matchbuf, length ); - if(t->numfilenames == 1) return true; + FS_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) com.strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; for(i = 0, numtracks = 0; i < t->numfilenames; i++) { const char *ext = FS_FileExtension( t->filenames[i] ); - if( com.stricmp(ext, "ogg" )) continue; - FS_FileBase(t->filenames[i], matchbuf ); - Msg("%16s\n", matchbuf ); + if( !com.stricmp( ext, "wav" ) || !com.stricmp( ext, "ogg" )); + else continue; + + FS_FileBase( t->filenames[i], matchbuf ); + Msg( "%16s\n", matchbuf ); numtracks++; } - Msg("\n^3 %i soundtracks found.\n", numtracks ); + Msg( "\n^3 %i soundtracks found.\n", numtracks ); Mem_Free(t); // cut shortestMatch to the amount common with s - if(completedname && length) + if( completedname && length ) { for( i = 0; matchbuf[i]; i++ ) { - if(com.tolower(completedname[i]) != com.tolower(matchbuf[i])) + if( com.tolower( completedname[i]) != com.tolower( matchbuf[i] )) completedname[i] = 0; } } - return true; } @@ -451,7 +452,9 @@ bool Cmd_GetSoundList( const char *s, char *completedname, int length ) { const char *ext = FS_FileExtension( t->filenames[i] ); - if( com.stricmp( ext, "wav" ) && com.stricmp( ext, "ogg" )) continue; + if( !com.stricmp( ext, "wav" ) || !com.stricmp( ext, "ogg" )); + else continue; + com.strncpy( matchbuf, t->filenames[i] + com.strlen(snddir), MAX_STRING ); FS_StripExtension( matchbuf ); Msg( "%16s\n", matchbuf ); @@ -783,7 +786,7 @@ autocomplete_list_t cmd_list[] = { "playdemo", Cmd_GetDemoList, }, { "menufont", Cmd_GetFontList, }, { "setfont", Cmd_GetFontList, }, -{ "music", Cmd_GetSoundList, }, +{ "music", Cmd_GetMusicList, }, { "movie", Cmd_GetMovieList }, { "exec", Cmd_GetConfigList }, { "give", Cmd_GetItemsList }, diff --git a/engine/common/input.c b/engine/common/input.c index 10e73bc3..85d11021 100644 --- a/engine/common/input.c +++ b/engine/common/input.c @@ -8,6 +8,14 @@ #include "client.h" #define WM_MOUSEWHEEL ( WM_MOUSELAST + 1 ) // message that will be supported by the OS +#define MK_XBUTTON1 0x0020 +#define MK_XBUTTON2 0x0040 +#define MK_XBUTTON3 0x0080 +#define MK_XBUTTON4 0x0100 +#define MK_XBUTTON5 0x0200 +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDOWN 0x020B + #define WND_HEADSIZE wnd_caption // some offset #define WND_BORDER 3 // sentinel border in pixels @@ -42,6 +50,19 @@ static byte scan_to_key[128] = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; +// extra mouse buttons +static int mouse_buttons[] = +{ + MK_LBUTTON, + MK_RBUTTON, + MK_MBUTTON, + MK_XBUTTON1, + MK_XBUTTON2, + MK_XBUTTON3, + MK_XBUTTON4, + MK_XBUTTON5 +}; + /* ======= Host_MapKey @@ -102,7 +123,7 @@ void IN_StartupMouse( void ) if( host.type == HOST_DEDICATED ) return; if( FS_CheckParm( "-nomouse" )) return; - in_mouse_buttons = 3; + in_mouse_buttons = 8; in_mouseparmsvalid = SystemParametersInfo( SPI_GETMOUSE, 0, in_originalmouseparms, 0 ); in_mouseinitialized = true; in_mouse_wheel = RegisterWindowMessage( "MSWHEEL_ROLLMSG" ); @@ -372,7 +393,7 @@ main window procedure */ long IN_WndProc( void *hWnd, uint uMsg, uint wParam, long lParam ) { - int temp = 0; + int i, temp = 0; if( uMsg == in_mouse_wheel ) uMsg = WM_MOUSEWHEEL; @@ -454,10 +475,14 @@ long IN_WndProc( void *hWnd, uint uMsg, uint wParam, long lParam ) case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: case WM_MOUSEMOVE: - if( wParam & MK_LBUTTON ) temp |= 1; - if( wParam & MK_RBUTTON ) temp |= 2; - if( wParam & MK_MBUTTON ) temp |= 4; + for( i = 0; i < in_mouse_buttons; i++ ) + { + if( wParam & mouse_buttons[i] ) + temp |= (1<movetype == MOVETYPE_PUSH ) + if( pe->solid == SOLID_BSP ) { vec3_t size; + if( pe->movetype != MOVETYPE_PUSH ) + Host_Error( "SOLID_BSP without MOVETYPE_PUSH\n" ); + if( !pe->model || pe->model->type != mod_brush && pe->model->type != mod_world ) - Host_Error( "Entity %i has MOVETYPE_PUSH with a non bsp model\n", pe->info ); + Host_Error( "Entity %s has MOVETYPE_PUSH with a non bsp model\n", pe->name ); VectorSubtract( maxs, mins, size ); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index cc9479b2..228528a5 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -600,6 +600,7 @@ void SV_Init( void ) Cvar_Get ("lservercfgfile","listenserver.cfg", 0, "name of listen server configuration file" ); Cvar_Get ("motdfile", "motd.txt", 0, "name of 'message of the day' file" ); Cvar_Get ("sv_language", "0", 0, "game language (currently unused)" ); + Cvar_Get ("suitvolume", "0.25", CVAR_ARCHIVE, "HEV suit volume" ); // half-life shared variables sv_zmax = Cvar_Get ("sv_zmax", "0", CVAR_PHYSICINFO, "zfar server value" ); diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c index 56ba7e5e..fe8802b6 100644 --- a/engine/server/sv_save.c +++ b/engine/server/sv_save.c @@ -700,7 +700,7 @@ SAVERESTOREDATA *SV_LoadSaveData( const char *level ) int i, id, size, version; com.snprintf( name, sizeof( name ), "save/%s.HL1", level ); - MsgDev( D_INFO, "Loading game from %s...\n", name + 1 ); + MsgDev( D_INFO, "Loading game from %s...\n", name ); pFile = FS_OpenEx( name, "rb", true ); if( !pFile ) @@ -1646,7 +1646,7 @@ bool SV_LoadGame( const char *pName ) com.snprintf( name, sizeof( name ), "save/%s.sav", pName ); - MsgDev( D_INFO, "Loading game from %s...\n", name + 1 ); + MsgDev( D_INFO, "Loading game from %s...\n", name ); SV_ClearSaveDir(); if( !svs.initialized ) SV_InitGame (); diff --git a/game_shared/pm_shared.cpp b/game_shared/pm_shared.cpp index 588fd779..e6ad529d 100644 --- a/game_shared/pm_shared.cpp +++ b/game_shared/pm_shared.cpp @@ -2800,13 +2800,15 @@ void PM_CheckParamters( void ) pmove->cmd.upmove *= fRatio; } - if ( pmove->flags & FL_FROZEN || - pmove->flags & FL_ONTRAIN || - pmove->dead ) + if ( pmove->flags & FL_FROZEN || pmove->flags & FL_ONTRAIN || pmove->dead ) { pmove->cmd.forwardmove = 0; pmove->cmd.sidemove = 0; pmove->cmd.upmove = 0; + + // clearing IN_JUMP flag when frozen + if( pmove->flags & FL_FROZEN ) + pmove->cmd.buttons = 0; } diff --git a/gameui/menu_audio.cpp b/gameui/menu_audio.cpp index 25b4507a..98e5da40 100644 --- a/gameui/menu_audio.cpp +++ b/gameui/menu_audio.cpp @@ -32,8 +32,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define ID_SOUNDLIBRARY 4 #define ID_SOUNDVOLUME 5 #define ID_MUSICVOLUME 6 -#define ID_EAX 7 -#define ID_A3D 8 +#define ID_INTERP 7 +#define ID_NODSP 8 #define ID_MSGHINT 9 typedef struct @@ -41,8 +41,6 @@ typedef struct int soundLibrary; float soundVolume; float musicVolume; - int enableEAX; - int enableA3D; } uiAudioValues_t; static uiAudioValues_t uiAudioInitial; @@ -63,8 +61,8 @@ typedef struct menuSpinControl_s soundLibrary; menuSlider_s soundVolume; menuSlider_s musicVolume; - menuCheckBox_s EAX; - menuCheckBox_s A3D; + menuCheckBox_s lerping; + menuCheckBox_s noDSP; menuAction_s hintMessage; char hintText[MAX_HINT_TEXT]; @@ -103,18 +101,16 @@ static void UI_Audio_GetConfig( void ) uiAudio.soundVolume.curValue = CVAR_GET_FLOAT( "volume" ); uiAudio.musicVolume.curValue = CVAR_GET_FLOAT( "musicvolume" ); - if( CVAR_GET_FLOAT( "s_allowEAX" )) - uiAudio.EAX.enabled = 1; + if( CVAR_GET_FLOAT( "s_lerping" )) + uiAudio.lerping.enabled = 1; - if( CVAR_GET_FLOAT( "s_allowA3D" )) - uiAudio.A3D.enabled = 1; + if( CVAR_GET_FLOAT( "dsp_off" )) + uiAudio.noDSP.enabled = 1; // save initial values uiAudioInitial.soundLibrary = uiAudio.soundLibrary.curValue; uiAudioInitial.soundVolume = uiAudio.soundVolume.curValue; uiAudioInitial.musicVolume = uiAudio.musicVolume.curValue; - uiAudioInitial.enableEAX = uiAudio.EAX.enabled; - uiAudioInitial.enableA3D = uiAudio.A3D.enabled; } /* @@ -126,8 +122,8 @@ static void UI_Audio_SetConfig( void ) { CVAR_SET_FLOAT( "volume", uiAudio.soundVolume.curValue ); CVAR_SET_FLOAT( "musicvolume", uiAudio.musicVolume.curValue ); - CVAR_SET_FLOAT( "s_allowEAX", uiAudio.EAX.enabled ); - CVAR_SET_FLOAT( "s_allowA3D", uiAudio.A3D.enabled ); + CVAR_SET_FLOAT( "s_lerping", uiAudio.lerping.enabled ); + CVAR_SET_FLOAT( "dsp_off", uiAudio.noDSP.enabled ); CHANGE_AUDIO( uiAudio.audioList[(int)uiAudio.soundLibrary.curValue] ); } @@ -143,6 +139,8 @@ static void UI_Audio_UpdateConfig( void ) CVAR_SET_FLOAT( "volume", uiAudio.soundVolume.curValue ); CVAR_SET_FLOAT( "musicvolume", uiAudio.musicVolume.curValue ); + CVAR_SET_FLOAT( "s_lerping", uiAudio.lerping.enabled ); + CVAR_SET_FLOAT( "dsp_off", uiAudio.noDSP.enabled ); // See if the apply button should be enabled or disabled uiAudio.apply.generic.flags |= QMF_GRAYED; @@ -152,18 +150,6 @@ static void UI_Audio_UpdateConfig( void ) uiAudio.apply.generic.flags &= ~QMF_GRAYED; return; } - - if( uiAudioInitial.enableEAX != uiAudio.EAX.enabled ) - { - uiAudio.apply.generic.flags &= ~QMF_GRAYED; - return; - } - - if( uiAudioInitial.enableA3D != uiAudio.A3D.enabled ) - { - uiAudio.apply.generic.flags &= ~QMF_GRAYED; - return; - } } /* @@ -177,8 +163,8 @@ static void UI_Audio_Callback( void *self, int event ) switch( item->id ) { - case ID_EAX: - case ID_A3D: + case ID_INTERP: + case ID_NODSP: if( event == QM_PRESSED ) ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; @@ -300,23 +286,23 @@ static void UI_Audio_Init( void ) uiAudio.musicVolume.maxValue = 1.0; uiAudio.musicVolume.range = 0.05f; - uiAudio.EAX.generic.id = ID_EAX; - uiAudio.EAX.generic.type = QMTYPE_CHECKBOX; - uiAudio.EAX.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; - uiAudio.EAX.generic.name = "Enable EAX 2.0 support"; - uiAudio.EAX.generic.x = 320; - uiAudio.EAX.generic.y = 370; - uiAudio.EAX.generic.callback = UI_Audio_Callback; - uiAudio.EAX.generic.statusText = "enable/disable Environmental Audio eXtensions"; + uiAudio.lerping.generic.id = ID_INTERP; + uiAudio.lerping.generic.type = QMTYPE_CHECKBOX; + uiAudio.lerping.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAudio.lerping.generic.name = "Enable sound interpolation"; + uiAudio.lerping.generic.x = 320; + uiAudio.lerping.generic.y = 370; + uiAudio.lerping.generic.callback = UI_Audio_Callback; + uiAudio.lerping.generic.statusText = "enable/disable interpolation on sound output"; - uiAudio.A3D.generic.id = ID_A3D; - uiAudio.A3D.generic.type = QMTYPE_CHECKBOX; - uiAudio.A3D.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_GRAYED|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; - uiAudio.A3D.generic.name = "Enable A3D 2.0 support"; - uiAudio.A3D.generic.x = 320; - uiAudio.A3D.generic.y = 420; - uiAudio.A3D.generic.callback = UI_Audio_Callback; - uiAudio.A3D.generic.statusText = "enable/disable Aureal Audio 3D environment sound"; + uiAudio.noDSP.generic.id = ID_NODSP; + uiAudio.noDSP.generic.type = QMTYPE_CHECKBOX; + uiAudio.noDSP.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAudio.noDSP.generic.name = "Disable DSP effects"; + uiAudio.noDSP.generic.x = 320; + uiAudio.noDSP.generic.y = 420; + uiAudio.noDSP.generic.callback = UI_Audio_Callback; + uiAudio.noDSP.generic.statusText = "this disables sound processing (like echo, flanger etc)"; UI_Audio_GetConfig(); @@ -328,8 +314,8 @@ static void UI_Audio_Init( void ) UI_AddItem( &uiAudio.menu, (void *)&uiAudio.soundLibrary ); UI_AddItem( &uiAudio.menu, (void *)&uiAudio.soundVolume ); UI_AddItem( &uiAudio.menu, (void *)&uiAudio.musicVolume ); - UI_AddItem( &uiAudio.menu, (void *)&uiAudio.EAX ); - UI_AddItem( &uiAudio.menu, (void *)&uiAudio.A3D ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.lerping ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.noDSP ); } /* diff --git a/launch/soundlib/snd_utils.c b/launch/soundlib/snd_utils.c index f71440bb..4244deb7 100644 --- a/launch/soundlib/snd_utils.c +++ b/launch/soundlib/snd_utils.c @@ -303,8 +303,8 @@ bool Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int outrate } else { - sample = (int)( (unsigned char)(data[srcsample*2+0]) - 128) << 8; - sample2 = (int)( (unsigned char)(data[srcsample*2+1]) - 128) << 8; + sample = (int)((char)(data[srcsample*2+0])) << 8; + sample2 = (int)((char)(data[srcsample*2+1])) << 8; } if( outwidth == 2 ) @@ -327,7 +327,7 @@ bool Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int outrate samplefrac += fracstep; if( inwidth == 2 ) sample = LittleShort(((short *)data)[srcsample] ); - else sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + else sample = (int)( (char)(data[srcsample])) << 8; if( outwidth == 2 ) ((short *)sound.tempbuffer)[i] = sample; else ((signed char *)sound.tempbuffer)[i] = sample >> 8; diff --git a/launch/soundlib/snd_wav.c b/launch/soundlib/snd_wav.c index 14491c25..8b44046e 100644 --- a/launch/soundlib/snd_wav.c +++ b/launch/soundlib/snd_wav.c @@ -239,6 +239,21 @@ bool Sound_LoadWAV( const char *name, const byte *buffer, size_t filesize ) Mem_Copy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size ); + // now convert 8-bit sounds to signed + if( sound.width == 1 ) + { + int i, j; + char *pData = sound.wav; + + for( i = 0; i < sound.samples; i++ ) + { + for( j = 0; j < sound.channels; j++ ) + { + *pData = (byte)((int)((byte)*pData) - 128 ); + pData++; + } + } + } return true; } diff --git a/public/mathlib.h b/public/mathlib.h index 7c0255a1..4fc6941a 100644 --- a/public/mathlib.h +++ b/public/mathlib.h @@ -20,6 +20,9 @@ #define M_PI2 (float)6.28318530717958647692 #endif +#define M_PI_F ((float)(M_PI)) +#define M_PI2_F ((float)(M_PI2)) + #define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI)) #define DEG2RAD( x ) ((float)(x) * (float)(M_PI / 180.f)) diff --git a/snd_dx/s_backend.c b/snd_dx/s_backend.c index 111f5f01..0839d51f 100644 --- a/snd_dx/s_backend.c +++ b/snd_dx/s_backend.c @@ -18,6 +18,8 @@ static dllfunc_t dsound_funcs[] = dll_info_t dsound_dll = { "dsound.dll", dsound_funcs, NULL, NULL, NULL, false, 0, 0 }; +#define SAMPLE_16BIT_SHIFT 1 + // 64K is > 1 second at 16-bit, 22050 Hz #define WAV_BUFFERS 64 #define WAV_MASK 0x3F @@ -41,7 +43,6 @@ static bool snd_isdirect, snd_iswave; static bool primary_format_set; static int snd_buffer_count = 0; static int snd_sent, snd_completed; -static int sample16; /* ======================================================================= @@ -95,9 +96,9 @@ static bool DS_CreateBuffers( void *hInst ) Mem_Set( &format, 0, sizeof( format )); format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = dma.channels; - format.wBitsPerSample = dma.samplebits; - format.nSamplesPerSec = dma.speed; + format.nChannels = 2; + format.wBitsPerSample = 16; + format.nSamplesPerSec = SOUND_DMA_SPEED; format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.cbSize = 0; @@ -177,10 +178,6 @@ static bool DS_CreateBuffers( void *hInst ) MsgDev( D_INFO, "- failed. forced to software\n" ); } - dma.channels = format.nChannels; - dma.samplebits = format.wBitsPerSample; - dma.speed = format.nSamplesPerSec; - if( pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) != DS_OK ) { MsgDev( D_ERROR, "DS_CreateBuffers: GetCaps failed\n"); @@ -223,13 +220,11 @@ static bool DS_CreateBuffers( void *hInst ) dma.samplepos = 0; snd_hwnd = (HWND)hInst; gSndBufSize = dsbcaps.dwBufferBytes; - dma.samples = gSndBufSize / (dma.samplebits / 8 ); - dma.submission_chunk = 1; + dma.samples = gSndBufSize / 2; dma.buffer = (byte *)lpData; - sample16 = (dma.samplebits / 8) - 1; SNDDMA_BeginPainting(); - if( dma.buffer ) Mem_Set( dma.buffer, 0, dma.samples * dma.samplebits / 8 ); + if( dma.buffer ) Mem_Set( dma.buffer, 0, dma.samples * 2 ); SNDDMA_Submit(); return true; @@ -335,16 +330,6 @@ si_state_t SNDDMA_InitDirect( void *hInst ) DSCAPS dscaps; HRESULT hresult; - dma.channels = 2; - dma.samplebits = 16; - - switch( s_khz->integer ) - { - case 44: dma.speed = 44100; break; - case 22: dma.speed = 22050; break; - default: dma.speed = 11025; break; - } - if( !dsound_dll.link ) { if( !Sys_LoadLibrary( NULL, &dsound_dll )) @@ -402,21 +387,11 @@ si_state_t SNDDMA_InitWav( void ) snd_sent = 0; snd_completed = 0; - dma.channels = 2; - dma.samplebits = 16; - - switch( s_khz->integer ) - { - case 44: dma.speed = 44100; break; - case 22: dma.speed = 22050; break; - default: dma.speed = 11025; break; - } - Mem_Set( &format, 0, sizeof( format )); format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = dma.channels; - format.wBitsPerSample = dma.samplebits; - format.nSamplesPerSec = dma.speed; + format.nChannels = 2; + format.wBitsPerSample = 16; + format.nSamplesPerSec = SOUND_DMA_SPEED; format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; format.cbSize = 0; @@ -493,10 +468,8 @@ si_state_t SNDDMA_InitWav( void ) } dma.samplepos = 0; - dma.samples = gSndBufSize / ( dma.samplebits / 8 ); - dma.submission_chunk = 512; + dma.samples = gSndBufSize / 2; dma.buffer = (byte *)lpData; - sample16 = (dma.samplebits / 8) - 1; wav_init = true; return SIS_SUCCESS; @@ -598,7 +571,7 @@ int SNDDMA_GetDMAPos( void ) } - s >>= sample16; + s >>= SAMPLE_16BIT_SHIFT; s &= (dma.samples - 1); return s; @@ -616,7 +589,7 @@ int SNDDMA_GetSoundtime( void ) static int buffers, oldsamplepos; int samplepos, fullsamples; - fullsamples = dma.samples / dma.channels; + fullsamples = dma.samples / 2; // it is possible to miscount buffers // if it has wrapped twice between @@ -637,7 +610,7 @@ int SNDDMA_GetSoundtime( void ) } oldsamplepos = samplepos; - return (buffers * fullsamples + samplepos / dma.channels); + return (buffers * fullsamples + samplepos / 2); } /* @@ -719,7 +692,7 @@ void SNDDMA_Submit( void ) } // submit a few new sound blocks - while((( snd_sent - snd_completed ) >> sample16 ) < 8 ) + while((( snd_sent - snd_completed ) >> SAMPLE_16BIT_SHIFT ) < 8 ) { h = lpWaveHdr + ( snd_sent & WAV_MASK ); diff --git a/snd_dx/s_dsp.c b/snd_dx/s_dsp.c index 15484428..35f19c4a 100644 --- a/snd_dx/s_dsp.c +++ b/snd_dx/s_dsp.c @@ -5,1294 +5,5440 @@ #include "sound.h" -#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 -#define SOUNDCLIP(x) ((x) > (32767*256) ? (32767*256) : ((x) < (-32767*256) ? (-32767*256) : (x))) -#define CSXROOM 29 -#define DSP_CONSTANT_GAIN 128 +#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 ) +#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 -typedef int sample_t; // delay lines must be 32 bit, now that we have 16 bit samples +// Performance notes: -typedef struct dlyline_s +// 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 cdelaysamplesmax; // size of delay line in samples - int lp; // lowpass flag 0 = off, 1 = on + int i, y; - 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; - sample_t *lpdelayline; // buffer -} dlyline_t; - -#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 -#define CSXDLYMAX 4 - -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 - -// 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 - -// stereo delay (no feedback) -cvar_t *sxste_delay; // straight left delay - -// Underwater/special fx modulations -cvar_t *sxmod_lowpass; -cvar_t *sxmod_mod; -int sxroom_typeprev; - -// Main interface -cvar_t *sxroom_type; // legacy support -cvar_t *sxroomwater_type; // legacy support -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", "0", 0, "no description" ); - sxroomwater_type = Cvar_Get( "waterroom_type", "14", 0, "no description" ); - sxroom_off = Cvar_Get( "room_off", "0", 0, "no description" ); - - Mem_Set( rgsxdly, 0, sizeof( dlyline_t ) * CSXDLYMAX ); - Mem_Set( rgsxlp, 0, sizeof( int ) * CSXLPMAX ); - - sxroom_typeprev = -1; - - // init amplitude modulation params - sxamodl = sxamodr = 255; - sxamodlt = sxamodrt = 255; - - sxmod1 = 350 * (dma.speed / 11025); // 11k was the original sample rate all dsp was tuned at - sxmod2 = 450 * (dma.speed / 11025); - sxmod1cur = sxmod1; - sxmod2cur = sxmod2; - - MsgDev( D_INFO, "FX Processor Initialized\n" ); + for( y = 0, i = 0; i <= M; i++ ) + y += ( h[i] * w[i] ) >> PBITS; + return y; } -void SX_Free( void ) +// 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; - // release mono delay line - SXDLY_Free( ISXMONODLY ); - - // release reverb lines - for( i = 0; i < CSXRVBMAX; i++ ) - SXDLY_Free( i + ISXRVB ); - SXDLY_Free( ISXSTEREODLY ); + for( i = D; i >= 1; i-- ) // reverse order updating + w[i] = w[i-1]; } -// 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 ) +// 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 ) { - size_t cbsamples; - dlyline_t *pdly; + 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 +} - pdly = &(rgsxdly[idelay]); +// 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; - if( delay > SXDLY_MAX ) - delay = SXDLY_MAX; - - if( pdly->lpdelayline ) + w[0] = x; + + // output adder + switch( L ) { - Mem_Free( pdly->lpdelayline ); - pdly->lpdelayline = NULL; + 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; } - if( delay == 0.0 ) + 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, y, 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, portable_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 +{ + 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 +{ + 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/44100 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; +} + + +// 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) + com.sqrt(( e ) * ( e ) - 1) )) +#define asinh( e ) ( lnf( (e) + com.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; + double 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 = com.sqrt( pow( 10, Apass/10 ) - 1 ); + estop = com.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 = com.sin( th ) / Wstop; + Wi2 = Wi * Wi; + + D = 1 - 2 * W0 * com.cos( th ) + W02 + Wi2; + G = ( 1 + Wi2 ) / D; + + b1 = 2 * ( 1 - Wi2 ) / ( 1 + Wi2 ); + a1 = 2 * ( 1 - W02 - Wi2) / D; + a2 = ( 1 + 2 * W0 * com.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, 22050 }, // cutoff frequency in hz at -3db gain +{ flt_iqwidth, 100, 11025 }, // 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 = bound( 1, M, FLT_M ); + L = bound( 1, L, FLT_M ); + break; + case QUA_HI: + // type 2 chebyshev N = 4 IIR + FLT_Design_Cheb( 4, cutoff, ftype, qwidth, &M, &L, a, b ); + + M = bound( 1, M, FLT_M ); + L = bound( 1, L, FLT_M ); + break; + case QUA_VHI: + // type 2 chebyshev N = 7 IIR + FLT_Design_Cheb( 8, cutoff, ftype, qwidth, &M, &L, a, b ); + + M = bound( 1, M, FLT_M ); + L = bound( 1, L, 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 ) +{ +} + +// 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 ); +} + +// batch version for performance +_inline void FLT_GetNextN( flt_t *pflt, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = FLT_GetNext( pflt, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = FLT_GetNext( pflt, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = FLT_GetNext( pflt, pb->left ); + pb++; + } + break; + } +} + +/////////////////////////////////////////////////////////////////////////// +// 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 +{ + + 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_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 +{ + 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, 22050.0 }, +{ dly_iqwidth, 100.0, 11025.0 }, +{ 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; + + // 0,1,2 - high, medium, low (low quality implies faster execution time) + prcf.prm[flt_iquality] = qual; + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = DLY_GetNext( pdly, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = DLY_GetNext( pdly, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = DLY_GetNext( pdly, pb->left ); + pb++; + } + break; + } +} + +// 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 ) +{ +} + +/////////////////// +// 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 +{ + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = RVA_GetNext( prva, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = RVA_GetNext( prva, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = RVA_GetNext( prva, pb->left ); + pb++; + } + break; + } +} + +#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, 22050 }, +{ rva_iqwidth, 100, 11025 }, +{ 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 ) +{ + flt_t *pflt; + rva_t *prva; + 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]; + + // 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; + int i; + + m = density * CRVA_DLYS / 2; + + // limit # delays 3-12 + m = bound( RVA_BASEM, m, 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 ) +{ +} + + +//////////// +// 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 +{ + 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 ) + { + int i; + + // free all delays + 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, y; + int n = pdfr->n; + + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = DFR_GetNext( pdfr, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = DFR_GetNext( pdfr, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = DFR_GetNext( pdfr, pb->left ); + pb++; + } + break; + } +} + +#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, 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 ) +{ +} + +////////////////////// +// 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_F * (float)i / (float)count ) + ( M_PI_F * 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( void ) +{ + 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 +{ + 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; +} + +// 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = LFO_GetNext( plfo, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = LFO_GetNext( plfo, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = LFO_GetNext( plfo, pb->left ); + pb++; + } + break; + } +} + +// 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 +{ + 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, 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 +{ + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = PTC_GetNext( pptc, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = PTC_GetNext( pptc, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = PTC_GetNext( pptc, pb->left ); + pb++; + } + break; + } +} + +// 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 +{ + 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, 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = ENV_GetNext( penv, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = ENV_GetNext( penv, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = ENV_GetNext( penv, pb->left ); + pb++; + } + break; + } +} + +// 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.0f; + float decay = pprc->prm[env_idecay] / 1000.0f; + float sustain = pprc->prm[env_isustain] / 1000.0f; + float release = pprc->prm[env_irelease] / 1000.0f; + + 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 ) +{ +} + +//////////////////// +// 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 +{ + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = EFO_GetNext( pefo, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = EFO_GetNext( pefo, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = EFO_GetNext( pefo, pb->left ); + pb++; + } + break; + } +} + + +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 ) +{ +} + +////////////// +// 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 +{ + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_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, 22050.0 }, +{ mdy_iqwidth, 100.0, 11025.0 }, +{ 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 ); +} + + +/////////////////////////////////////////// +// Chorus - lfo modulated delay +/////////////////////////////////////////// + + +#define CCRSS 64 // max number chorus' active + +typedef struct +{ + 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, D; + crs_t *pcrs; + dly_t *pdly; + mdy_t *pmdy; + lfo_t *plfo; + float ramptime; + + // 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.0f; // # 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, 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = CRS_GetNext( pcrs, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = CRS_GetNext( pcrs, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = CRS_GetNext( pcrs, pb->left ); + pb++; + } + break; + } +} + +// 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 ) +{ +} + +//////////////////////////////////////////////////// +// amplifier - modulatable gain, distortion +//////////////////////////////////////////////////// + +#define CAMPS 64 // max number amps active +#define AMPSLEW 10 // milliseconds of slew time between gain changes + +typedef struct +{ + 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, portable_samplepair_t *pbuffer, int SampleCount, int op ) +{ + int count = SampleCount; + portable_samplepair_t *pb = pbuffer; + + switch( op ) + { + default: + case OP_LEFT: + while( count-- ) + { + pb->left = AMP_GetNext( pamp, pb->left ); + pb++; + } + break; + case OP_RIGHT: + while( count-- ) + { + pb->right = AMP_GetNext( pamp, pb->right ); + pb++; + } + break; + case OP_LEFT_DUPLICATE: + while( count-- ) + { + pb->left = pb->right = AMP_GetNext( pamp, pb->left ); + pb++; + } + break; + } +} + +_inline void AMP_Mod( amp_t *pamp, float v ) +{ + float vmod = bound( 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.0f ) + 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 +{ + 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, portable_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 cprm = prng[0].iprm; + int i; + + for( i = 0; i < cprm; i++) + { + // if parameter is 0.0f, always allow it (this is 'off' for most params) + if( pprc->prm[i] != 0.0f && ( 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] = bound( prng[i+1].lo, pprc->prm[i], 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 +{ + 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 )) + +// init a preset - just clear state array +void PSET_Init( pset_t *ppset ) +{ + // clear state array + if( ppset ) Mem_Set( ppset->w, 0, sizeof( int ) * ( CPSET_STATES )); +} + +// clear runtime slots +void PSET_InitAll( void ) +{ + int i; + + for( i = 0; i < CPSETS; i++ ) + Mem_Set( &psets[i], 0, sizeof( pset_t )); +} + +// 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, portable_samplepair_t *pbf, int SampleCount, int op ) +{ + prc_t *pprc; + int i, count = ppset->cprcs; + + switch( ppset->type ) + { + default: + case PSET_SIMPLE: + { + // x(n)--->P(0)--->y(n) + ppset->prcs[0].pfnGetNextN( ppset->prcs[0].pdata, pbf, SampleCount, op ); + break; + } + 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++; + } + break; + } + } +} + + +// 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]); + } + 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) + +int ipset_room_prev; + +// legacy room_type support +cvar_t *dsp_room_type; +int ipset_room_typeprev; + + +// DSP processors + +int idsp_room; +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 +{ + 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; + + // 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 ); + + // initialize DSP cvars + dsp_room = Cvar_Get( "dsp_room", "0", 0, "room dsp preset - sounds more distant from player (1ch)" ); + dsp_room_type = Cvar_Get( "room_type", "0", 0, "duplicate for dsp_room cvar for backward compatibility" ); + dsp_stereo = Cvar_Get( "dsp_stereo", "0", 0, "set to 1 for true stereo processing. 2x perf hits" ); +} + +// 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, idsp; + int cchans = bound( 1, cchan, 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.0f; + + 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.0f; + + pdsp = &dsps[idsp]; + + // get current preset's gain + if( pdsp->ppset[0] ) + gain_target = pdsp->ppset[0]->gain; + else gain_target = 1.0f; + + // 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.0f ) + return 1.0f; + 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; + } +} - pdly->cdelaysamplesmax = dma.speed * delay; - pdly->cdelaysamplesmax += 1; +// Helper: called only from DSP_Process +// mix front stereo buffer to mono buffer, apply dsp fx +_inline void DSP_ProcessStereoToMono( dsp_t *pdsp, portable_samplepair_t *pbfront, int sampleCount, bool bcrossfading ) +{ + portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process + int count = sampleCount; + int av, x; - cbsamples = pdly->cdelaysamplesmax * sizeof( sample_t ); - pdly->lpdelayline = (sample_t *)Z_Malloc( cbsamples ); + if( !bcrossfading ) + { + if( FBatchPreset( pdsp->ppset[0] )) + { + // convert Stereo to Mono in place, then batch process fx: perf KDB - // init delay loop input and output counters. - // NOTE: init of idelayoutput only valid if pdly->delaysamples is set - // NOTE: before this call! + // 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; + } - pdly->idelayinput = 0; - pdly->idelayoutput = pdly->cdelaysamplesmax - pdly->delaysamples; - pdly->xfade = 0; - pdly->lp = 1; - pdly->mod = 0; - pdly->modcur = 0; + // crossfading to current preset from previous preset + if( bcrossfading ) + { + int r = -1; + int fl, flp; + int xf_fl; + + while( count-- ) + { + av = ( ( pbf->left + pbf->right ) >> 1 ); - // init lowpass filter memory - pdly->lp0 = pdly->lp1 = pdly->lp2 = pdly->lp3 = pdly->lp4 = pdly->lp5 = 0; + // 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, portable_samplepair_t *pbfront, int sampleCount, bool bcrossfading ) +{ + portable_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, 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.0f ); + Cvar_SetValue( "room_type", 0.0f ); + + CheckNewDspPresets(); + + // don't crossfade + dsps[0].xramp.fhitend = true; +} + +// 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, portable_samplepair_t *pbfront, int sampleCount ) +{ + bool bcrossfading; + 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 ); + + 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( cprocs == 1 ) + { + DSP_ProcessStereoToMono( pdsp, pbfront, sampleCount, bcrossfading ); + return; + } + + // DSP_Process stereo in to stereo out (if more than 2 procs, ignore them) + if( cprocs >= 2 ) + { + DSP_ProcessStereoToStereo( pdsp, pbfront, sampleCount, bcrossfading ); + return; + } +} + +// DSP helpers + +// free all dsp processors +void FreeDsps( void ) +{ + DSP_Free( idsp_room ); + idsp_room = 0; + + DSP_FreeAll(); +} + +// alloc dsp processors +bool AllocDsps( void ) +{ + DSP_InitAll(); + + idsp_room = -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 ); + + // init prev values + ipset_room_prev = dsp_room->integer; + ipset_room_typeprev = dsp_room_type->integer; + + if( idsp_room < 0 ) + { + MsgDev( D_WARN, "DSP processor failed to initialize! \n" ); + + FreeDsps(); + return false; + } return true; } -// release delay buffer and deactivate delay -void SXDLY_Free( int idelay ) + +// 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 ) { - dlyline_t *pdly = &(rgsxdly[idelay]); + int iroomtype = dsp_room_type->integer; + int iroom; - if( pdly->lpdelayline ) - { - Mem_Free( pdly->lpdelayline ); - pdly->lpdelayline = NULL; // this deactivates the delay - } -} - - -// check for new stereo delay param -void SXDLY_CheckNewStereoDelayVal( void ) -{ - dlyline_t *pdly = &(rgsxdly[ISXSTEREODLY]); - int delaysamples; - - if( dma.channels < 2 ) - return; - - // set up stereo delay - if( sxste_delay->modified ) - { - sxste_delay->modified = false; - - if( sxste_delay->value == 0.0 ) - { - // deactivate delay line - SXDLY_Free( ISXSTEREODLY ); - } - else - { - delaysamples = min( sxste_delay->value, SXSTE_MAX ) * 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; - } - - 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; - portable_samplepair_t *pbuf; - int countr; - - if( dma.channels < 2 ) - return; - - // 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) = SOUNDCLIP( left ); - - // save delay line sample into output buffer - pbuf->left = SOUNDCLIP( 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->modified ) - { - sxdly_delay->modified = false; - - if( sxdly_delay->value == 0.0f ) - { - // deactivate delay line - SXDLY_Free( ISXMONODLY ); - } - else - { - // init delay line if not active - - pdly->delaysamples = min( sxdly_delay->value, SXDLY_MAX ) * 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; - - // deactivate line if rounded down to 0 delay - if( pdly->delaysamples == 0 ) - SXDLY_Free( ISXMONODLY ); - - } - } - - pdly->lp = sxdly_lp->integer; - 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[2]; - int valt, valt2; - int samp[2]; - int sampsum, dfdly; - sample_t sampledly; - portable_samplepair_t *pbuf; - int countr; - int gain; - - // process mono delay line if active - if( rgsxdly[ISXMONODLY].lpdelayline ) - { - gain = DSP_CONSTANT_GAIN; - pbuf = paintbuffer; - countr = count; - - // process each sample in the paintbuffer... - while( countr-- ) - { - // get delay line sample - sampledly = *(gdly0.lpdelayline + gdly0.idelayoutput); - - if( dma.channels == 2 ) - { - samp[0] = pbuf->left; - samp[1] = pbuf->right; - sampsum = samp[0] + samp[1]; - } - else - { - samp[0] = pbuf->left; - sampsum = samp[0]; - } - - // only process if delay line and paintbuffer samples are non zero - if( sampledly || sampsum ) - { - // get current sample from delay buffer - dfdly = ((gdly0.delayfeed * sampledly) >> 8); - - if( dma.channels == 2 ) - { - val[0] = SOUNDCLIP( samp[0] + dfdly ); - val[1] = SOUNDCLIP( samp[1] + dfdly ); - valt = SOUNDCLIP(( sampsum + (dfdly * 2)) >> 1); - } - else - { - val[0] = SOUNDCLIP(samp[0] + dfdly); - valt = val[0]; - } - - // lowpass - if( gdly0.lp ) - { - if( dma.channels == 2 ) - { - val[0] = (gdly0.lp0 + gdly0.lp1 + gdly0.lp2 + gdly0.lp3 + val[0]) / 5; - val[1] = (gdly0.lp0 + gdly0.lp1 + gdly0.lp2 + gdly0.lp3 + val[1]) / 5; - valt2 = (val[0] + val[1]) >> 1; - } - else - { - val[0] = (gdly0.lp0 + gdly0.lp1 + gdly0.lp2 + gdly0.lp3 + val[0]) / 5; - valt2 = val[0]; - } - - gdly0.lp0 = gdly0.lp1; - gdly0.lp1 = gdly0.lp2; - gdly0.lp2 = gdly0.lp3; - gdly0.lp3 = valt; - } - else - { - valt2 = valt; - } - - // store delay output value into output buffer - - *(gdly0.lpdelayline + gdly0.idelayinput) = SOUNDCLIP( valt2 ); - - // decrease output value by max gain of delay with feedback - // to provide for unity gain reverb - // note: this gain varies with the feedback value. - if( dma.channels == 2 ) - { - pbuf->left = ((val[0] * gain) >> 8); - pbuf->right = ((val[1] * gain) >> 8); - } - else - { - pbuf->left = ((val[0] * gain) >> 8); - } - } - else - { - // not playing samples, but must still flush lowpass buffer and delay line - gdly0.lp0 = gdly0.lp1 = gdly0.lp2 = gdly0.lp3 = 0; - *(gdly0.lpdelayline + gdly0.idelayinput) = valt2; - - } - - // 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 * dma.speed / 11025) // xfade time between new delays -#define RVB_MODRATE1 (500 * (dma.speed / 11025)) // how often, in samples, to change delay (1st rvb) -#define RVB_MODRATE2 (700 * (dma.speed / 11025)) // how often, in samples, to change delay (2nd rvb) - -void SXRVB_CheckNewReverbVal( void ) -{ - dlyline_t *pdly; - int delaysamples; - int i, mod; - - if( sxrvb_size->modified ) - { - sxrvb_size->modified = false; - - 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) * dma.speed; - pdly->mod = RVB_MODRATE1; - break; - case ISXRVB+1: - delaysamples = min(sxrvb_size->value * 0.71, SXRVB_MAX) * dma.speed; - pdly->mod = RVB_MODRATE2; - break; - default: - delaysamples = 0; - break; - } - - mod = pdly->mod; - - if( pdly->lpdelayline == NULL ) - { - pdly->delaysamples = delaysamples; - SXDLY_Init( i, SXRVB_MAX ); - } - - pdly->modcur = pdly->mod = mod; - - // 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_DoReverbMono( int count ) -{ - portable_samplepair_t *pbuf; - int val; - int valt; - int mono; - sample_t sampledly; - sample_t samplexf; - int countr; - int voutm; - int gain; - - // process reverb lines if active - if( rgsxdly[ISXRVB].lpdelayline ) - { - gain = DSP_CONSTANT_GAIN; - - pbuf = paintbuffer; - countr = count; - - // process each sample in the paintbuffer... - - while( countr-- ) - { - mono = pbuf->left; - voutm = 0; - - 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 || mono ) - { - // 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 - val = SOUNDCLIP(mono + ((gdly1.delayfeed * sampledly) >> 8)); - } - else - { - val = mono; - } - - // 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======================== - - 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 || mono ) - { - // 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 - val = SOUNDCLIP(mono + ((gdly2.delayfeed * sampledly) >> 8)); - } - else - { - val = mono; - } - - // 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; - - pbuf->left = SOUNDCLIP( voutm ); - pbuf++; - } - } -} - -void SXRVB_DoReverb( int count ) -{ - int val[2]; - int valt[2]; - int left; - int right; - sample_t sampledly; - sample_t samplexf; - portable_samplepair_t *pbuf; - int countr; - int voutm[2]; - int gain; - - if( dma.channels < 2 ) - { - SXRVB_DoReverbMono( count ); - return; - } - - // process reverb lines if active - if( rgsxdly[ISXRVB].lpdelayline ) - { - gain = DSP_CONSTANT_GAIN; - - pbuf = paintbuffer; - countr = count; - - // process each sample in the paintbuffer... - - while( countr-- ) - { - left = pbuf->left; - right = pbuf->right; - voutm[0] = 0; - voutm[1] = 0; - - 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 - val[0] = SOUNDCLIP(left + ((gdly1.delayfeed * sampledly) >> 8)); - val[1] = SOUNDCLIP(right + ((gdly1.delayfeed * sampledly) >> 8)); - - } - else - { - val[0] = left; - val[1] = right; - } - - // lowpass - if( gdly1.lp ) - { - valt[0] = (gdly1.lp0 + gdly1.lp1 + (val[0]<<1)) >> 2; - valt[1] = (gdly1.lp0 + gdly1.lp1 + (val[1]<<1)) >> 2; - - gdly1.lp1 = gdly1.lp0; - gdly1.lp0 = (val[0] + val[1]) >> 1; - } - else - { - valt[0] = val[0]; - valt[1] = val[1]; - } - - // store delay output value into output buffer - *(gdly1.lpdelayline + gdly1.idelayinput) = (valt[0] + valt[1]) >> 1; - voutm[0] = valt[0]; - voutm[1] = valt[1]; - } - else - { - // not playing samples, but still must flush lowpass buffer & delay line - - gdly1.lp0 = gdly1.lp1 = 0; - *(gdly1.lpdelayline + gdly1.idelayinput) = 0; - voutm[0] = 0; - voutm[1] = 0; - } - - // update delay buffer pointers - if( ++gdly1.idelayinput >= gdly1.cdelaysamplesmax ) - gdly1.idelayinput = 0; - - if( ++gdly1.idelayoutput >= gdly1.cdelaysamplesmax ) - gdly1.idelayoutput = 0; - - // ========================== ISXRVB + 1======================== - - 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 - val[0] = SOUNDCLIP(left + ((gdly2.delayfeed * sampledly) >> 8)); - val[1] = SOUNDCLIP(right + ((gdly2.delayfeed * sampledly) >> 8)); - } - else - { - val[0] = left; - val[1] = right; - } - - // lowpass - if( gdly2.lp ) - { - valt[0] = (gdly2.lp0 + gdly2.lp1 + (val[0]<<1)) >> 2; - valt[1] = (gdly2.lp0 + gdly2.lp1 + (val[1]<<1)) >> 2; - gdly2.lp0 = (val[0] + val[1]) >> 1; - } - else - { - valt[0] = val[0]; - valt[1] = val[1]; - } - - // store delay output value into output buffer - *(gdly2.lpdelayline + gdly2.idelayinput) = (valt[0] + valt[1]) >> 1; - voutm[0] += valt[0]; - voutm[1] += valt[1]; - } - 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[0] = (gain * voutm[0]) >> 8; - voutm[1] = (gain * voutm[1]) >> 8; - - pbuf->left = SOUNDCLIP( voutm[0] ); - pbuf->right = SOUNDCLIP( voutm[1] ); - - pbuf++; - } - } -} - -// amplitude modulator, low pass filter for underwater weirdness -void SXRVB_DoAMod( int count ) -{ - int sample[2]; - int valtsample[2]; - portable_samplepair_t *pbuf; - int countr; - int fLowpass; - int fmod; - - // process reverb lines if active - if( sxmod_lowpass->integer > 0 || sxmod_mod->integer > 0 ) - { - pbuf = paintbuffer; - countr = count; - - fLowpass = (sxmod_lowpass->integer > 0); - fmod = (sxmod_mod->integer > 0); - - // process each sample in the paintbuffer... - while( countr-- ) - { - if( dma.channels == 2 ) - { - sample[0] = pbuf->left; - sample[1] = pbuf->right; - } - else - { - sample[0] = pbuf->left; - } - - // only process if non-zero - if( fLowpass ) - { - valtsample[0] = sample[0]; - sample[0] = (rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + sample[0]); - sample[0] = ((sample[0] << 1) + (sample[0] << 3)) >> 6; - - rgsxlp[0] = rgsxlp[1]; - rgsxlp[1] = rgsxlp[2]; - rgsxlp[2] = rgsxlp[3]; - rgsxlp[3] = rgsxlp[4]; - rgsxlp[4] = valtsample[0]; - - if (dma.channels > 1) - { - valtsample[1] = sample[1]; - sample[1] = (rgsxlp[5] + rgsxlp[6] + rgsxlp[7] + rgsxlp[8] + rgsxlp[9] + sample[1]); - sample[1] = ((sample[1] << 1) + (sample[1] << 3)) >> 6; - - rgsxlp[5] = rgsxlp[6]; - rgsxlp[6] = rgsxlp[7]; - rgsxlp[7] = rgsxlp[8]; - rgsxlp[8] = rgsxlp[9]; - rgsxlp[9] = valtsample[1]; - } - - } - - if( fmod ) - { - if( --sxmod1cur < 0 ) - sxmod1cur = sxmod1; - - if( !sxmod1cur ) - sxamodlt = Com_RandomLong(32,255); - - if( --sxmod2cur < 0 ) - sxmod2cur = sxmod2; - - if( !sxmod2cur ) - sxamodrt = Com_RandomLong(32,255); - - if( dma.channels == 2 ) - { - sample[0] = (sample[0] * sxamodl) >> 8; - sample[1] = (sample[1] * sxamodr) >> 8; - } - else - { - sample[0] = (sample[0] * (sxamodl+sxamodr)) >> 9; - } - - if( sxamodl < sxamodlt ) - sxamodl++; - else if( sxamodl > sxamodlt ) - sxamodl--; - - if( sxamodr < sxamodrt ) - sxamodr++; - else if( sxamodr > sxamodrt ) - sxamodr--; - } - - if( dma.channels == 2 ) - { - pbuf->left = SOUNDCLIP( sample[0] ); - pbuf->right = SOUNDCLIP( sample[1] ); - } - else - { - pbuf->left = SOUNDCLIP( sample[0] ); - } - 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 -// 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; - int roomType; - - // don't apply dsp in menu - if( s_listener.inmenu ) return; - - // return right away if fx processing is turned off - if( sxroom_off->value != 0.0f ) - return; - - sampleCount = endtime - paintedtime; - if( sampleCount < 0 ) return; - - fReset = false; if( s_listener.waterlevel > 2 ) - roomType = sxroomwater_type->integer; - else roomType = sxroom_type->integer; + iroom = 15; + else if( s_listener.inmenu ) + iroom = 0; + else iroom = dsp_room->integer; - // only process legacy roomtypes here - if( roomType >= CSXROOM ) return; - - if( roomType != sxroom_typeprev ) + // legacy code support for "room_type" Cvar + if( iroomtype != ipset_room_typeprev ) { - MsgDev( D_NOTE, "svc_roomtype: set to %i\n", roomType ); - - sxroom_typeprev = roomType; - - i = 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; + // force dsp_room = room_type + ipset_room_typeprev = iroomtype; + Cvar_SetValue( "dsp_room", iroomtype ); } - if( fReset || roomType != 0 ) + if( iroom != ipset_room_prev ) { - // debug code - SXRVB_CheckNewReverbVal(); - SXDLY_CheckNewDelayVal(); - SXDLY_CheckNewStereoDelayVal(); - // debug code + DSP_SetPreset( idsp_room, iroom ); + ipset_room_prev = iroom; - if( fFilter ) SXRVB_DoAMod( sampleCount ); - - if( fTimefx ) - { - SXRVB_DoReverb( sampleCount ); - SXDLY_DoDelay( sampleCount ); - SXDLY_DoStereoDelay( sampleCount ); - } - } + // force room_type = dsp_room + Cvar_SetValue( "room_type", iroom ); + ipset_room_typeprev = iroom; + } } \ No newline at end of file diff --git a/snd_dx/s_dsp.old b/snd_dx/s_dsp.old new file mode 100644 index 00000000..638a57bd --- /dev/null +++ b/snd_dx/s_dsp.old @@ -0,0 +1,1060 @@ +//======================================================================= +// Copyright XashXT Group 2009 © +// s_dsp.c - digital signal processing algorithms for audio FX +//======================================================================= + +#include "sound.h" + +#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 +#define CSXROOM 29 + +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; + sample_t *lpdelayline; // buffer +} dlyline_t; + +#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 +#define CSXDLYMAX 4 + +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 + +// 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 + +// stereo delay (no feedback) +cvar_t *sxste_delay; // straight left delay + +// Underwater/special fx modulations +cvar_t *sxmod_lowpass; +cvar_t *sxmod_mod; +int sxroom_typeprev; + +// Main interface +cvar_t *sxroom_type; // legacy support +cvar_t *sxroomwater_type; // legacy support +cvar_t *sxroom_off; // legacy support + +bool SXDLY_Init( int idelay, float delay ); +void SXDLY_Free( int idelay ); +void SXDLY_DoDelay( portable_samplepair_t *pbuf, int count ); +void SXRVB_DoReverb( portable_samplepair_t *pbuf, int count ); +void SXDLY_DoStereoDelay( portable_samplepair_t *pbuf, int count ); +void SXRVB_DoAMod( portable_samplepair_t *pbuf, 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", "0", 0, "no description" ); + sxroomwater_type = Cvar_Get( "waterroom_type", "14", 0, "no description" ); + sxroom_off = Cvar_Get( "room_off", "0", 0, "no description" ); + + Mem_Set( rgsxdly, 0, sizeof( dlyline_t ) * CSXDLYMAX ); + Mem_Set( rgsxlp, 0, sizeof( int ) * CSXLPMAX ); + + sxroom_typeprev = -1; + + // 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 ) +{ + size_t cbsamples; + dlyline_t *pdly; + + pdly = &(rgsxdly[idelay]); + + if( delay > SXDLY_MAX ) + delay = SXDLY_MAX; + + if( pdly->lpdelayline ) + { + Mem_Free( pdly->lpdelayline ); + pdly->lpdelayline = NULL; + } + + if( delay == 0.0 ) + return true; + + pdly->cdelaysamplesmax = SOUND_DMA_SPEED * delay; + pdly->cdelaysamplesmax += 1; + + cbsamples = pdly->cdelaysamplesmax * sizeof( sample_t ); + pdly->lpdelayline = (sample_t *)Z_Malloc( cbsamples ); + + // 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 ) + { + Mem_Free( pdly->lpdelayline ); + 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->modified ) + { + sxste_delay->modified = false; + + if( sxste_delay->value == 0.0 ) + { + // deactivate delay line + SXDLY_Free( ISXSTEREODLY ); + } + 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; + } + + // 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( portable_samplepair_t *pbuf, int count ) +{ + int left; + sample_t sampledly; + sample_t samplexf; + int countr; + + // process delay line if active + if( rgsxdly[ISXSTEREODLY].lpdelayline ) + { + 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->modified ) + { + sxdly_delay->modified = false; + + if( sxdly_delay->value == 0.0f ) + { + // deactivate delay line + SXDLY_Free( ISXMONODLY ); + } + 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; + + // deactivate line if rounded down to 0 delay + if( pdly->delaysamples == 0 ) + SXDLY_Free( ISXMONODLY ); + + } + } + + pdly->lp = sxdly_lp->integer; + 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( portable_samplepair_t *pbuf, int count ) +{ + int val; + int valt; + int left; + int right; + sample_t sampledly; + 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.0f / ( 1.0f - gdly0.delayfeed / 255.0f ); + gain = (int)(( 1.0f / fgain ) * 255.0f); + + gain <<= 2; + if( gain > 255 ) gain = 255; + 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 = valt; + } + + // 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->modified ) + { + sxrvb_size->modified = false; + + 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: + 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; + + // 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( portable_samplepair_t *pbuf, int count ) +{ + int val; + int valt; + int left; + int right; + sample_t sampledly; + sample_t samplexf; + 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; + + 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( portable_samplepair_t *pbuf, int count ) +{ + int valtl, valtr; + int left; + int right; + int countr; + int fLowpass; + int fmod; + + // process reverb lines if active + if( sxmod_lowpass->integer != 0 || sxmod_mod->integer != 0 ) + { + countr = count; + + fLowpass = (sxmod_lowpass->integer != 0); + fmod = (sxmod_mod->integer != 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 +// 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( portable_samplepair_t *pbuf, int sampleCount ) +{ + int i, fReset; + int roomType; + + // don't apply dsp in menu + if( s_listener.inmenu ) return; + + // return right away if fx processing is turned off + if( sxroom_off->integer ) return; + + if( sampleCount < 0 ) return; + + fReset = false; + if( s_listener.waterlevel > 2 ) + roomType = sxroomwater_type->integer; + else roomType = sxroom_type->integer; + + // only process legacy roomtypes here + if( roomType >= CSXROOM ) return; + + if( roomType != sxroom_typeprev ) + { + MsgDev( D_NOTE, "svc_roomtype: set to %i\n", roomType ); + + sxroom_typeprev = roomType; + + i = 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 ) + { + // debug code + SXRVB_CheckNewReverbVal(); + SXDLY_CheckNewDelayVal(); + SXDLY_CheckNewStereoDelayVal(); + + // debug code + SXRVB_DoAMod( pbuf, sampleCount ); + + SXRVB_DoReverb( pbuf, sampleCount ); + SXDLY_DoDelay( pbuf, sampleCount ); + SXDLY_DoStereoDelay( pbuf, sampleCount ); + } +} \ No newline at end of file diff --git a/snd_dx/s_load.c b/snd_dx/s_load.c index 9bd838d1..e1f4f5b6 100644 --- a/snd_dx/s_load.c +++ b/snd_dx/s_load.c @@ -127,10 +127,6 @@ wavdata_t *S_LoadSound( sfx_t *sfx ) else sc = FS_LoadSound( sfx->name, NULL, 0 ); if( !sc ) sc = S_CreateDefaultSound(); - - // upload and resample - Sound_Process( &sc, dma.speed, sc->width, SOUND_RESAMPLE ); - sfx->cache = sc; return sfx->cache; diff --git a/snd_dx/s_main.c b/snd_dx/s_main.c index ac7284bb..e824963b 100644 --- a/snd_dx/s_main.c +++ b/snd_dx/s_main.c @@ -20,14 +20,12 @@ int paintedtime; // sample PAIRS cvar_t *s_check_errors; cvar_t *s_volume; -cvar_t *s_suitvolume; // game.dll requires this cvar_t *s_musicvolume; -cvar_t *s_khz; cvar_t *s_show; cvar_t *s_mixahead; cvar_t *s_primary; -cvar_t *s_allowEAX; -cvar_t *s_allowA3D; +cvar_t *s_lerping; +cvar_t *dsp_off; // set to 1 to disable all dsp processing /* ============================================================================= @@ -93,6 +91,7 @@ void S_FreeChannel( channel_t *ch ) ch->use_loop = false; ch->isSentence = false; + // clear mixer Mem_Set( &ch->pMixer, 0, sizeof( ch->pMixer )); SND_CloseMouth( ch ); @@ -366,16 +365,8 @@ void SND_Spatialize( channel_t *ch ) dist = VectorNormalizeLength( source_vec ) * ch->dist_mult; dot = DotProduct( s_listener.right, source_vec ); - if( dma.channels == 1 ) - { - rscale = 1.0f; - lscale = 1.0f; - } - else - { - rscale = 1.0f + dot; - lscale = 1.0f - dot; - } + rscale = 1.0f + dot; + lscale = 1.0f - dot; // add in distance effect scale = ( 1.0f - dist ) * rscale; @@ -634,17 +625,13 @@ S_ClearBuffer */ void S_ClearBuffer( void ) { - int clear; - s_rawend = 0; - if( dma.samplebits == 8 ) - clear = 0x80; - else clear = 0; - SNDDMA_BeginPainting (); - if( dma.buffer ) Mem_Set( dma.buffer, clear, dma.samples * dma.samplebits / 8 ); + if( dma.buffer ) Mem_Set( dma.buffer, 0, dma.samples * 2 ); SNDDMA_Submit (); + + MIX_ClearAllPaintBuffers( PAINTBUFFER_SIZE, true ); } /* @@ -679,13 +666,13 @@ void S_StopAllSounds( void ) S_FreeChannel( &channels[i] ); } - // clear any remaining soundfade - Mem_Set( &soundfade, 0, sizeof( soundfade )); - s_volume->modified = true; // rebuild scaletable - // clear all the channels Mem_Set( channels, 0, sizeof( channels )); + S_ClearBuffer (); + + // clear any remaining soundfade + Mem_Set( &soundfade, 0, sizeof( soundfade )); } //============================================================================= @@ -700,19 +687,37 @@ void S_UpdateChannels( void ) // updates DMA time soundtime = SNDDMA_GetSoundtime(); - +#if 0 // check to make sure that we haven't overshot if( paintedtime < soundtime ) paintedtime = soundtime; // mix ahead of current position - endtime = soundtime + s_mixahead->value * dma.speed; + endtime = soundtime + s_mixahead->value * SOUND_DMA_SPEED; // mix to an even submission block size endtime = (endtime + dma.submission_chunk - 1) & ~(dma.submission_chunk - 1); - samps = dma.samples >> (dma.channels - 1); + samps = dma.samples >> 1; if( endtime - soundtime > samps ) endtime = soundtime + samps; +#else + // soundtime - total samples that have been played out to hardware at dmaspeed + // paintedtime - total samples that have been mixed at speed + // endtime - target for samples in mixahead buffer at speed + endtime = soundtime + s_mixahead->value * SOUND_DMA_SPEED; + samps = dma.samples >> 1; + + if((int)( endtime - soundtime ) > samps ) + endtime = soundtime + samps; + + if(( endtime - paintedtime ) & 0x3 ) + { + // The difference between endtime and painted time should align on + // boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz. + endtime -= ( endtime - paintedtime ) & 0x3; + } +#endif + + MIX_PaintChannels( endtime ); - S_PaintChannels( endtime ); SNDDMA_Submit(); } @@ -761,10 +766,6 @@ void S_RenderFrame( ref_params_t *fd ) VectorCopy( fd->right, s_listener.right ); VectorCopy( fd->up, s_listener.up ); - // rebuild scale tables if volume is modified - if( s_volume->modified || soundfade.percent != 0 ) - S_InitScaletable(); - // update spatialization for static and dynamic sounds for( i = 0, ch = channels; i < total_channels; i++, ch++ ) { @@ -818,8 +819,44 @@ void S_Music_f( void ) { int c = Cmd_Argc(); - if( c == 2 ) S_StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(1) ); - else if( c == 3 ) S_StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(2) ); + // run background track + if( c == 1 ) + { + // blank name stopped last track + S_StopBackgroundTrack(); + } + else if( c == 2 ) + { + string intro, main, track; + char *ext[] = { "wav", "ogg" }; + int i; + + com.strncpy( track, Cmd_Argv( 1 ), sizeof( track )); + com.snprintf( intro, sizeof( intro ), "%s_intro", Cmd_Argv( 1 )); + com.snprintf( main, sizeof( main ), "%s_main", Cmd_Argv( 1 )); + + for( i = 0; i < 2; i++ ) + { + if( FS_FileExists( va( "media/%s.%s", intro, ext[i] )) + && FS_FileExists( va( "media/%s.%s", main, ext[i] ))) + { + // combined track with introduction and main loop theme + S_StartBackgroundTrack( intro, main ); + break; + } + else if( FS_FileExists( va( "media/%s.%s", track, ext[i] ))) + { + // single looped theme + S_StartBackgroundTrack( track, track ); + break; + } + } + + } + else if( c == 3 ) + { + S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 )); + } else Msg( "Usage: music [loopfile]\n" ); } @@ -841,15 +878,12 @@ S_SoundInfo_f void S_SoundInfo_f( void ) { S_PrintDeviceName(); - Msg( "%5d channel(s)\n", dma.channels ); + + Msg( "%5d channel(s)\n", 2 ); Msg( "%5d samples\n", dma.samples ); - Msg( "%5d bits/sample\n", dma.samplebits ); - Msg( "%5d bytes/sec\n", dma.speed ); + Msg( "%5d bits/sample\n", 16 ); + Msg( "%5d bytes/sec\n", SOUND_DMA_SPEED ); Msg( "%5d total_channels\n", total_channels ); - - MsgDev( D_NOTE, "%5d samplepos\n", dma.samplepos ); - MsgDev( D_NOTE, "%5d submission_chunk\n", dma.submission_chunk ); - MsgDev( D_NOTE, "0x%x dma buffer\n", dma.buffer ); } /* @@ -862,15 +896,13 @@ bool S_Init( void *hInst ) Cmd_ExecuteString( "sndlatch\n" ); s_volume = Cvar_Get( "volume", "0.7", CVAR_ARCHIVE, "sound volume" ); - s_suitvolume = Cvar_Get( "suitvolume", "0.5", CVAR_ARCHIVE, "HEV suit volume" ); s_musicvolume = Cvar_Get( "musicvolume", "1.0", CVAR_ARCHIVE, "background music volume" ); - s_khz = Cvar_Get( "s_khz", "22", CVAR_LATCH_AUDIO|CVAR_ARCHIVE, "output sound frequency" ); s_mixahead = Cvar_Get( "s_mixahead", "0.1", 0, "how much sound to mix ahead of time" ); s_show = Cvar_Get( "s_show", "0", 0, "show playing sounds" ); s_primary = Cvar_Get( "s_primary", "0", CVAR_LATCH_AUDIO|CVAR_ARCHIVE, "use direct primary buffer" ); s_check_errors = Cvar_Get( "s_check_errors", "1", CVAR_ARCHIVE, "ignore audio engine errors" ); - s_allowEAX = Cvar_Get("s_allowEAX", "1", CVAR_LATCH_AUDIO|CVAR_ARCHIVE, "allow EAX 2.0 extension" ); - s_allowA3D = Cvar_Get("s_allowA3D", "1", CVAR_LATCH_AUDIO|CVAR_ARCHIVE, "allow A3D 2.0 extension" ); + s_lerping = Cvar_Get( "s_lerping", "0", CVAR_ARCHIVE, "apply interpolation to sound output" ); + dsp_off = Cvar_Get( "dsp_off", "0", CVAR_ARCHIVE, "set to 1 to disable all dsp processing" ); Cmd_AddCommand( "play", S_Play_f, "playing a specified sound file" ); Cmd_AddCommand( "stopsound", S_StopSound_f, "stop all sounds" ); @@ -888,10 +920,12 @@ bool S_Init( void *hInst ) soundtime = 0; paintedtime = 0; + MIX_InitAllPaintbuffers (); + S_InitScaletable (); S_StopAllSounds (); VOX_Init (); - SX_Init (); + AllocDsps (); return true; } @@ -910,8 +944,9 @@ void S_Shutdown( void ) S_StopAllSounds (); S_FreeSounds (); VOX_Shutdown (); - SX_Free (); + FreeDsps (); SNDDMA_Shutdown (); + MIX_FreeAllPaintbuffers (); Mem_FreePool( &sndpool ); } \ No newline at end of file diff --git a/snd_dx/s_mix.c b/snd_dx/s_mix.c index 99e130f2..aea69703 100644 --- a/snd_dx/s_mix.c +++ b/snd_dx/s_mix.c @@ -6,57 +6,38 @@ #include "sound.h" #include "byteorder.h" -#define PAINTBUFFER_SIZE 512 -portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; -int snd_scaletable[32][256]; -int *snd_p, snd_linear_count, snd_vol; -short *snd_out; +#define IPAINTBUFFER 0 +#define IROOMBUFFER 1 +#define ISTREAMBUFFER 2 -void S_WriteLinearBlastStereo16( void ) +#define FILTERTYPE_NONE 0 +#define FILTERTYPE_LINEAR 1 +#define FILTERTYPE_CUBIC 2 + +#define CCHANVOLUMES 2 + +#define SND_SCALE_BITS 7 +#define SND_SCALE_SHIFT (8 - SND_SCALE_BITS) +#define SND_SCALE_LEVELS (1 << SND_SCALE_BITS) + +portable_samplepair_t *g_curpaintbuffer; +portable_samplepair_t streambuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t paintbuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t facingbuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t temppaintbuffer[(PAINTBUFFER_SIZE+1)]; +paintbuffer_t paintbuffers[CPAINTBUFFERS]; + +int snd_scaletable[SND_SCALE_LEVELS][256]; + +void S_InitScaletable( void ) { - int i, val; + int i, j; - for( i = 0; i < snd_linear_count; i += 2 ) + for( i = 0; i < SND_SCALE_LEVELS; i++ ) { - val = snd_p[i]>>8; - if( val > 0x7fff ) snd_out[i] = 0x7fff; - else if( val < (short)0x8000 ) - snd_out[i] = (short)0x8000; - else snd_out[i] = val; - - val = snd_p[i+1]>>8; - if( val > 0x7fff ) snd_out[i+1] = 0x7fff; - else if( val < (short)0x8000 ) - snd_out[i+1] = (short)0x8000; - else snd_out[i+1] = val; - } -} - -void S_TransferStereo16( dword *pbuf, int endtime ) -{ - int lpos, lpaintedtime; - - snd_p = (int *)paintbuffer; - lpaintedtime = paintedtime; - - while( lpaintedtime < endtime ) - { - // handle recirculating buffer issues - lpos = lpaintedtime & ((dma.samples >> 1) - 1); - - snd_out = (short *) pbuf + (lpos << 1); - - snd_linear_count = (dma.samples>>1) - lpos; - if( lpaintedtime + snd_linear_count > endtime ) - snd_linear_count = endtime - lpaintedtime; - - snd_linear_count <<= 1; - - // write a linear blast of samples - S_WriteLinearBlastStereo16(); - - snd_p += snd_linear_count; - lpaintedtime += (snd_linear_count >> 1); + for( j = 0; j < 256; j++ ) + snd_scaletable[i][j] = ((signed char)j) * i * (1<> 1) - 1); - 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; + snd_vol = S_GetMasterVolume() * 256; - if( dma.samplebits == 16 ) + while( lpaintedtime < endtime ) + { + // handle recirculating buffer issues + lpos = lpaintedtime & sampleMask; + + snd_out = (short *)pbuf + (lpos << 1); + + snd_linear_count = (dma.samples>>1) - lpos; + if( lpaintedtime + snd_linear_count > endtime ) + snd_linear_count = endtime - lpaintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + for( i = 0; i < snd_linear_count; i += 2 ) { - short *out = (short *)pbuf; + val = (snd_p[i+0] * snd_vol) >> 8; - 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; - } - } - else if( dma.samplebits == 8 ) - { - byte *out = (byte *)pbuf; + if( val > 0x7fff ) snd_out[i+0] = 0x7fff; + else if( val < (short)0x8000 ) + snd_out[i+0] = (short)0x8000; + else snd_out[i+0] = 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>>8) + 128; - out_idx = (out_idx + 1) & out_mask; - } + val = (snd_p[i+1] * snd_vol) >> 8; + if( val > 0x7fff ) snd_out[i+1] = 0x7fff; + else if( val < (short)0x8000 ) + snd_out[i+1] = (short)0x8000; + else snd_out[i+1] = val; } + + snd_p += snd_linear_count; + lpaintedtime += (snd_linear_count >> 1); } } +//=============================================================================== +// Mix buffer (paintbuffer) management routines +//=============================================================================== +// Activate a paintbuffer. All active paintbuffers are mixed in parallel within +// MIX_MixChannelsToPaintbuffer, according to flags +_inline void MIX_ActivatePaintbuffer( int ipaintbuffer ) +{ + ASSERT( ipaintbuffer < CPAINTBUFFERS ); + paintbuffers[ipaintbuffer].factive = true; +} + +// don't mix into this paintbuffer +_inline void MIX_DeactivatePaintbuffer( int ipaintbuffer ) +{ + ASSERT( ipaintbuffer < CPAINTBUFFERS ); + paintbuffers[ipaintbuffer].factive = false; +} + +_inline void MIX_SetCurrentPaintbuffer( int ipaintbuffer ) +{ + ASSERT( ipaintbuffer < CPAINTBUFFERS ); + g_curpaintbuffer = paintbuffers[ipaintbuffer].pbuf; + ASSERT( g_curpaintbuffer != NULL ); +} + +_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]; +} + +// Don't mix into any paintbuffers +_inline void MIX_DeactivateAllPaintbuffers( void ) +{ + int i; + + for( i = 0; i < CPAINTBUFFERS; i++ ) + paintbuffers[i].factive = false; +} + +// set upsampling filter indexes back to 0 +_inline void MIX_ResetPaintbufferFilterCounters( void ) +{ + int i; + + for( i = 0; i < CPAINTBUFFERS; i++ ) + paintbuffers[i].ifilter = FILTERTYPE_NONE; +} + +_inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer ) +{ + ASSERT( ipaintbuffer < CPAINTBUFFERS ); + paintbuffers[ipaintbuffer].ifilter = 0; +} + +// return pointer to front paintbuffer pbuf, given index +_inline portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer ) +{ + ASSERT( ipaintbuffer < CPAINTBUFFERS ); + return paintbuffers[ipaintbuffer].pbuf; +} + +_inline paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaint ) +{ + ASSERT( ipaint < CPAINTBUFFERS ); + return &paintbuffers[ipaint]; +} + +void MIX_FreeAllPaintbuffers( void ) +{ + // clear paintbuffer structs + Mem_Set( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t )); +} + +// Initialize paintbuffers array, set current paint buffer to main output buffer IPAINTBUFFER +void MIX_InitAllPaintbuffers( void ) +{ + // clear paintbuffer structs + Mem_Set( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t )); + + paintbuffers[IPAINTBUFFER].pbuf = paintbuffer; + paintbuffers[IROOMBUFFER].pbuf = roombuffer; + paintbuffers[ISTREAMBUFFER].pbuf = streambuffer; + + MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); +} + /* =============================================================================== @@ -134,8 +210,8 @@ void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, in int i, data; int *lscale, *rscale; - lscale = snd_scaletable[volume[0] >> 3]; - rscale = snd_scaletable[volume[1] >> 3]; + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; for( i = 0; i < outCount; i++ ) { @@ -152,8 +228,8 @@ void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, word *data; int i; - lscale = snd_scaletable[volume[0] >> 3]; - rscale = snd_scaletable[volume[1] >> 3]; + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; data = (word *)pData; for( i = 0; i < outCount; i++, data++ ) @@ -169,16 +245,12 @@ void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, { int i, data; int left, right; - int lscale, rscale; - - lscale = volume[0] * snd_vol; - rscale = volume[1] * snd_vol; for( i = 0; i < outCount; i++ ) { data = pData[i]; - left = ( data * lscale ) >> 8; - right = (data * rscale ) >> 8; + left = ( data * volume[0]) >> 8; + right = (data * volume[1]) >> 8; pbuf[i].left += left; pbuf[i].right += right; } @@ -187,12 +259,9 @@ void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) { uint *data; - int lscale, rscale; int left, right; int i; - lscale = volume[0] * snd_vol; - rscale = volume[1] * snd_vol; data = (uint *)pData; for( i = 0; i < outCount; i++, data++ ) @@ -200,126 +269,144 @@ void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData left = (signed short)((*data & 0x0000FFFF)); right = (signed short)((*data & 0xFFFF0000) >> 16); - left = (left * lscale ) >> 8; - right = (right * rscale) >> 8; + left = (left * volume[0]) >> 8; + right = (right * volume[1]) >> 8; pbuf[i].left += left; pbuf[i].right += right; } } -void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount ) +void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; int *lscale, *rscale; // Not using pitch shift? - if( rateScaleFix == FIX( 1 )) + if( rateScale == FIX( 1 )) { S_PaintMonoFrom8( pbuf, volume, pData, outCount ); return; } - lscale = snd_scaletable[volume[0] >> 3]; - rscale = snd_scaletable[volume[1] >> 3]; + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; for( i = 0; i < outCount; i++ ) { pbuf[i].left += lscale[pData[sampleIndex]]; pbuf[i].right += rscale[pData[sampleIndex]]; - sampleFrac += rateScaleFix; + sampleFrac += rateScale; sampleIndex += FIX_INTPART( sampleFrac ); sampleFrac = FIX_FRACPART( sampleFrac ); } } -void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScaleFix, int outCount ) +void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; int *lscale, *rscale; // Not using pitch shift? - if( rateScaleFix == FIX( 1 )) + if( rateScale == FIX( 1 )) { S_PaintStereoFrom8( pbuf, volume, pData, outCount ); return; } - lscale = snd_scaletable[volume[0] >> 3]; - rscale = snd_scaletable[volume[1] >> 3]; + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; for( i = 0; i < outCount; i++ ) { pbuf[i].left += lscale[pData[sampleIndex+0]]; pbuf[i].right += rscale[pData[sampleIndex+1]]; - sampleFrac += rateScaleFix; + sampleFrac += rateScale; sampleIndex += FIX_INTPART( sampleFrac )<<1; sampleFrac = FIX_FRACPART( sampleFrac ); } } -void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount ) +void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; - int lscale, rscale; // Not using pitch shift? - if( rateScaleFix == FIX( 1 )) + if( rateScale == FIX( 1 )) { S_PaintMonoFrom16( pbuf, volume, pData, outCount ); return; } - lscale = volume[0] * snd_vol; - rscale = volume[1] * snd_vol; - for( i = 0; i < outCount; i++ ) { - pbuf[i].left += (lscale * (int)( pData[sampleIndex] ))>>8; - pbuf[i].right += (rscale * (int)( pData[sampleIndex] ))>>8; - sampleFrac += rateScaleFix; + pbuf[i].left += (volume[0] * (int)( pData[sampleIndex] ))>>8; + pbuf[i].right += (volume[1] * (int)( pData[sampleIndex] ))>>8; + sampleFrac += rateScale; sampleIndex += FIX_INTPART( sampleFrac ); sampleFrac = FIX_FRACPART( sampleFrac ); } } -void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScaleFix, int outCount ) +void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) { int i, sampleIndex = 0; uint sampleFrac = inputOffset; - int lscale, rscale; // Not using pitch shift? - if( rateScaleFix == FIX( 1 )) + if( rateScale == FIX( 1 )) { S_PaintStereoFrom16( pbuf, volume, pData, outCount ); return; } - - lscale = volume[0] * snd_vol; - rscale = volume[1] * snd_vol; for( i = 0; i < outCount; i++ ) { - pbuf[i].left += (lscale * (int)( pData[sampleIndex+0] ))>>8; - pbuf[i].right += (rscale * (int)( pData[sampleIndex+1] ))>>8; + pbuf[i].left += (volume[0] * (int)( pData[sampleIndex+0] ))>>8; + pbuf[i].right += (volume[1] * (int)( pData[sampleIndex+1] ))>>8; - sampleFrac += rateScaleFix; + sampleFrac += rateScale; sampleIndex += FIX_INTPART(sampleFrac)<<1; sampleFrac = FIX_FRACPART(sampleFrac); } } +void S_MixChannel( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, uint fracRate, int outCount ) +{ + int pvol[CCHANVOLUMES]; + paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); + wavdata_t *pSource = pChannel->sfx->cache; + portable_samplepair_t *pbuf; + + ASSERT( pSource != NULL ); + + pvol[0] = bound( 0, pChannel->leftvol, 255 ); + pvol[1] = bound( 0, pChannel->rightvol, 255 ); + pbuf = ppaint->pbuf + outputOffset; + + if( pSource->channels == 1 ) + { + if( pSource->width == 1 ) + S_Mix8Mono( pbuf, pvol, (char *)pData, inputOffset, fracRate, outCount ); + else S_Mix16Mono( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount ); + } + else + { + if( pSource->width == 1 ) + S_Mix8Stereo( pbuf, pvol, (char *)pData, inputOffset, fracRate, outCount ); + else S_Mix16Stereo( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount ); + } +} + int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ) { // save this to compute total output int startingOffset = outputOffset; float inputRate = ( pChannel->pitch * pChannel->sfx->cache->rate ); float rate = inputRate / outputRate; - int pvol[2]; // shouldn't be playing this if finished, but return if we are if( pChannel->pMixer.finished ) @@ -336,22 +423,18 @@ int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int { // yes, mark finished and truncate the sample request pChannel->pMixer.finished = true; - sampleCount = (int)(( pChannel->pMixer.forcedEndSample - pChannel->pMixer.sample ) / rate ); + sampleCount = (int)((pChannel->pMixer.forcedEndSample - pChannel->pMixer.sample) / rate ); } } - pvol[0] = pChannel->leftvol; - pvol[1] = pChannel->rightvol; - while( sampleCount > 0 ) { double sampleFraction; - portable_samplepair_t *pbuf; - bool advanceSample = true; int availableSamples, outputSampleCount; wavdata_t *pSource = pChannel->sfx->cache; bool use_loop = pChannel->use_loop; char *pData = NULL; + int i, j; // compute number of input samples required double end = pChannel->pMixer.sample + rate * sampleCount; @@ -377,27 +460,29 @@ int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int // Verify that we won't get a buffer overrun. ASSERT( floor( sampleFraction + rate * ( outputSampleCount - 1 )) <= availableSamples ); - // mix this data to all active paintbuffers - pbuf = &paintbuffer[outputOffset]; + // save current paintbuffer + j = MIX_GetCurrentPaintbufferIndex(); - if( pSource->channels == 1 ) + for( i = 0; i < CPAINTBUFFERS; i++ ) { - if( pSource->width == 1 ) - S_Mix8Mono( pbuf, pvol, (char *)pData, FIX_FLOAT(sampleFraction), FIX_FLOAT(rate), outputSampleCount ); - else S_Mix16Mono( pbuf, pvol, (short *)pData, FIX_FLOAT(sampleFraction), FIX_FLOAT(rate), outputSampleCount ); - } - else - { - if( pSource->width == 1 ) - S_Mix8Stereo( pbuf, pvol, (char *)pData, FIX_FLOAT(sampleFraction), FIX_FLOAT(rate), outputSampleCount ); - else S_Mix16Stereo( pbuf, pvol, (short *)pData, FIX_FLOAT(sampleFraction), FIX_FLOAT(rate), outputSampleCount ); - } + if( paintbuffers[i].factive ) + { + // mix chan into all active paintbuffers + MIX_SetCurrentPaintbuffer( i ); - if( advanceSample ) - { - pChannel->pMixer.sample += outputSampleCount * rate; + S_MixChannel( + pChannel, // Channel. + pData, // Input buffer. + outputOffset, // Output position. + FIX_FLOAT( sampleFraction ), // Iterators. + FIX_FLOAT( rate ), + outputSampleCount + ); + } } + MIX_SetCurrentPaintbuffer( j ); + pChannel->pMixer.sample += outputSampleCount * rate; outputOffset += outputSampleCount; sampleCount -= outputSampleCount; } @@ -424,26 +509,41 @@ bool S_ShouldContinueMixing( channel_t *ch ) return !ch->pMixer.finished; } -void S_MixAllChannels( int endtime, int end ) +// Mix all channels into active paintbuffers until paintbuffer is full or 'endtime' is reached. +// endtime: time in 44khz samples to mix +// rate: ignore samples which are not natively at this rate (for multipass mixing/filtering) +// if rate == SOUND_ALL_RATES then mix all samples this pass +// flags: if SOUND_MIX_DRY, then mix only samples with channel flagged as 'dry' +// outputRate: target mix rate for all samples. Note, if outputRate = SOUND_DMA_SPEED, then +// this routine will fill the paintbuffer to endtime. Otherwise, fewer samples are mixed. +// if( endtime - paintedtime ) is not aligned on boundaries of 4, +// we'll miss data if outputRate < SOUND_DMA_SPEED! +void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) { channel_t *ch; - wavdata_t *sc; - int i, count = end - paintedtime; + wavdata_t *pSource; + int i, sampleCount; + bool bZeroVolume; - // paint in the channels. - for( i = 0, ch = channels; i < total_channels; i++, ch++ ) + // mix each channel into paintbuffer + ch = channels; + + // validate parameters + ASSERT( outputRate <= SOUND_DMA_SPEED ); + + // make sure we're not discarding data + ASSERT( !(( endtime - paintedtime ) & 0x3 ) || ( outputRate == SOUND_DMA_SPEED )); + + // 44k: try to mix this many samples at outputRate + sampleCount = ( endtime - paintedtime ) / ( SOUND_DMA_SPEED / outputRate ); + + if( sampleCount <= 0 ) return; + + for( i = 0; i < total_channels; i++, ch++ ) { if( !ch->sfx ) continue; - if( !ch->leftvol && !ch->rightvol && !ch->isSentence ) - { - // sentences must be playing even if not hearing - continue; - } - sc = S_LoadSound( ch->sfx ); - if( !sc ) continue; - - if( s_listener.inmenu && !ch->localsound ) + if(( s_listener.inmenu || s_listener.paused ) && !ch->localsound ) { // play only local sounds, keep pause for other continue; @@ -453,29 +553,60 @@ void S_MixAllChannels( int endtime, int end ) // play only ambient sounds, keep pause for other continue; } - else if( s_listener.paused ) + + pSource = S_LoadSound( ch->sfx ); + + // Don't mix sound data for sounds with zero volume. If it's a non-looping sound, + // just remove the sound when its volume goes to zero. + bZeroVolume = !ch->leftvol && !ch->rightvol; + + if( !bZeroVolume ) + { + if( ch->leftvol <= 5 && ch->rightvol <= 5 ) + bZeroVolume = true; + } + + if( !pSource || ( bZeroVolume && pSource->loopStart == -1 )) + { + if( !pSource ) + { + S_FreeChannel( ch ); + continue; + } + } + else if( bZeroVolume ) { - // play only ambient sounds, keep pause for other continue; } + // multipass mixing - only mix samples of specified sample rate + switch( rate ) + { + case SOUND_11k: + case SOUND_22k: + case SOUND_44k: + if( rate != pSource->rate ) + continue; + break; + default: break; + } + // get playback pitch if( ch->isSentence ) ch->pitch = VOX_ModifyPitch( ch, ch->basePitch * 0.01f ); else ch->pitch = ch->basePitch * 0.01f; - // check volume - ch->leftvol = bound( 0, ch->leftvol, 255 ); - ch->rightvol = bound( 0, ch->rightvol, 255 ); - if( si.GetClientEdict( ch->entnum ) && ( ch->entchannel == CHAN_VOICE )) { - SND_MoveMouth8( ch, sc, count ); + // UNDONE: implement SND_MoveMouth16 too + SND_MoveMouth8( ch, pSource, sampleCount ); } + // mix channel to all active paintbuffers. + // NOTE: must be called once per channel only - consecutive calls retrieve additional data. if( ch->isSentence ) - VOX_MixDataToDevice( ch, count, dma.speed, 0 ); - else S_MixDataToDevice( ch, count, dma.speed, 0 ); + VOX_MixDataToDevice( ch, sampleCount, outputRate, 0 ); + else S_MixDataToDevice( ch, sampleCount, outputRate, 0 ); if( !S_ShouldContinueMixing( ch )) { @@ -484,11 +615,401 @@ void S_MixAllChannels( int endtime, int end ) } } -void S_PaintChannels( int endtime ) +// pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer +_inline portable_samplepair_t *S_GetNextpFilter( int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem ) { - int end; + // 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])); - snd_vol = S_GetMasterVolume () * 256; + // 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( portable_samplepair_t *pbuffer, portable_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; + portable_samplepair_t *psamp0; + portable_samplepair_t *psamp1; + portable_samplepair_t *psamp2; + portable_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 <= ( sizeof( temppaintbuffer ) / sizeof( temppaintbuffer[0] ))); + } + + 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( portable_samplepair_t *pbuffer, portable_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, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype ) +{ + int i, j; + int 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]; + } + + if( !s_lerping->integer ) return; + + // pass forward through buffer, interpolate all even slots + switch( filtertype ) + { + case FILTERTYPE_LINEAR: + S_Interpolate2xLinear( pbuffer, pfiltermem, cfltmem, count ); + break; + case FILTERTYPE_CUBIC: + S_Interpolate2xCubic( pbuffer, pfiltermem, cfltmem, count ); + break; + default: // no filter + break; + } +} + +// zero out all paintbuffers +void MIX_ClearAllPaintBuffers( int SampleCount, bool clearFilters ) +{ + int count = min( SampleCount, PAINTBUFFER_SIZE ); + int i; + + // zero out all paintbuffer data (ignore sampleCount) + for( i = 0; i < CPAINTBUFFERS; i++ ) + { + if( paintbuffers[i].pbuf != NULL ) + Mem_Set( paintbuffers[i].pbuf, 0, (count+1) * sizeof( portable_samplepair_t )); + + if( clearFilters ) + { + Mem_Set( paintbuffers[i].fltmem, 0, sizeof( paintbuffers[i].fltmem )); + } + } + + if( clearFilters ) + { + MIX_ResetPaintbufferFilterCounters(); + } +} + +// mixes pbuf1 + pbuf2 into pbuf3, count samples +// fgain is output gain 0-1.0 +// NOTE: pbuf3 may equal pbuf1 or pbuf2! +void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgain ) +{ + portable_samplepair_t *pbuf1, *pbuf2, *pbuf3; + int i, gain; + + gain = 256 * fgain; + + ASSERT( count <= PAINTBUFFER_SIZE ); + ASSERT( ibuf1 < CPAINTBUFFERS ); + ASSERT( ibuf2 < CPAINTBUFFERS ); + ASSERT( ibuf3 < CPAINTBUFFERS ); + + pbuf1 = paintbuffers[ibuf1].pbuf; + pbuf2 = paintbuffers[ibuf2].pbuf; + pbuf3 = paintbuffers[ibuf3].pbuf; + + // destination buffer stereo - average n chans down to stereo + + // destination 2ch: + // pb1 2ch + pb2 2ch -> pb3 2ch + // pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch + // pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch + + // mix front channels + for( i = 0; i < count; i++ ) + { + pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; + pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; + } + + if( gain == 256 ) return; + + for( i = 0; i < count; i++ ) + { + pbuf3[i].left = (pbuf3[i].left * gain) >> 8; + pbuf3[i].right = (pbuf3[i].right * gain) >> 8; + } +} + +void MIX_CompressPaintbuffer( int ipaint, int count ) +{ + portable_samplepair_t *pbuf; + paintbuffer_t *ppaint; + int i; + + ppaint = MIX_GetPPaintFromIPaint( ipaint ); + pbuf = ppaint->pbuf; + + for( i = 0; i < count; i++, pbuf++ ) + { + pbuf->left = CLIP( pbuf->left ); + pbuf->right = CLIP( pbuf->right ); + } +} + +void S_MixUpsample( int sampleCount, int filtertype ) +{ + paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); + int ifilter = ppaint->ifilter; + + ASSERT( ifilter < CPAINTFILTERS ); + + S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype ); + + // make sure on next upsample pass for this paintbuffer, new filter memory is used + ppaint->ifilter++; +} + +// mix and upsample channels to 44khz 'ipaintbuffer' +// mix channels matching 'flags' (SOUND_MIX_DRY or SOUND_MIX_WET) into specified paintbuffer +// upsamples 11khz, 22khz channels to 44khz. + +// NOTE: only call this on channels that will be mixed into only 1 paintbuffer +// and that will not be mixed until the next mix pass! otherwise, MIX_MixChannelsToPaintbuffer +// will advance any internal pointers on mixed channels; subsequent calls will be at +// incorrect offset. + +void MIX_MixUpsampleBuffer( int ipaintbuffer, int end, int count ) +{ + int ipaintcur = MIX_GetCurrentPaintbufferIndex(); // save current paintbuffer + + // reset paintbuffer upsampling filter index + MIX_ResetPaintbufferFilterCounter( ipaintbuffer ); + + // prevent other paintbuffers from being mixed + MIX_DeactivateAllPaintbuffers(); + + MIX_ActivatePaintbuffer( ipaintbuffer ); // operates on MIX_MixChannelsToPaintbuffer + MIX_SetCurrentPaintbuffer( ipaintbuffer ); // operates on MixUpSample + + // mix 11khz channels to buffer + MIX_MixChannelsToPaintbuffer( end, SOUND_11k, SOUND_11k ); + + // upsample 11khz buffer by 2x + S_MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + + // mix 22khz channels to buffer + MIX_MixChannelsToPaintbuffer( end, SOUND_22k, SOUND_22k ); + + // upsample 22khz buffer by 2x +#if (SOUND_DMA_SPEED > SOUND_22k) + S_MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); +#endif + // mix 44khz channels to buffer + MIX_MixChannelsToPaintbuffer( end, SOUND_44k, SOUND_DMA_SPEED ); + + MIX_DeactivateAllPaintbuffers(); + + // restore previous paintbuffer + MIX_SetCurrentPaintbuffer( ipaintcur ); +} + +void MIX_MixStreamBuffer( int end ) +{ + portable_samplepair_t *pbuf; + + pbuf = MIX_GetPFrontFromIPaint( ISTREAMBUFFER ); + + // clear the paint buffer + if( s_listener.paused || s_rawend < paintedtime ) + { + Mem_Set( pbuf, 0, (end - paintedtime) * sizeof( portable_samplepair_t )); + } + else + { + int i, stop; + + // copy from the streaming sound source + stop = (end < s_rawend) ? end : s_rawend; + + for( i = paintedtime; i < stop; i++ ) + pbuf[i - paintedtime] = s_rawsamples[i & (MAX_RAW_SAMPLES - 1)]; + + for( ; i < end; i++ ) + pbuf[i-paintedtime].left = pbuf[i-paintedtime].right = 0; + } +} + +// upsample and mix sounds into final 44khz versions of: +// IROOMBUFFER, IFACINGBUFFER, IFACINGAWAY, IDRYBUFFER +// dsp fx are then applied to these buffers by the caller. +// caller also remixes all into final IPAINTBUFFER output. + +void MIX_UpsampleAllPaintbuffers( int end, int count ) +{ + // process stream buffer + MIX_MixStreamBuffer( end ); + + // 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction + // These buffers are facing, facingaway, room + // These 3 mixed buffers are then each upsampled to 22khz. + + // 22khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction + // These 3 mixed buffers are then each upsampled to 44khz. + + // 44khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction + + MIX_DeactivateAllPaintbuffers(); + + // set paintbuffer upsample filter indices to 0 + MIX_ResetPaintbufferFilterCounters(); + + // only mix to roombuffer if dsp fx are on KDB: perf + MIX_ActivatePaintbuffer( IROOMBUFFER ); // operates on MIX_MixChannelsToPaintbuffer + + // mix 11khz sounds: + MIX_MixChannelsToPaintbuffer( end, SOUND_11k, SOUND_11k ); + + // upsample all 11khz buffers by 2x + // only upsample roombuffer if dsp fx are on KDB: perf + MIX_SetCurrentPaintbuffer( IROOMBUFFER ); // operates on MixUpSample + S_MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + + // mix 22khz sounds: + MIX_MixChannelsToPaintbuffer( end, SOUND_22k, SOUND_22k ); + + // upsample all 22khz buffers by 2x +#if (SOUND_DMA_SPEED > SOUND_22k) + // only upsample roombuffer if dsp fx are on KDB: perf + MIX_SetCurrentPaintbuffer( IROOMBUFFER ); + S_MixUpsample( count / ( SOUND_DMA_SPEED / SOUND_22k ), FILTERTYPE_LINEAR ); +#endif + // mix all 44khz sounds to all active paintbuffers + MIX_MixChannelsToPaintbuffer( end, SOUND_44k, SOUND_DMA_SPEED ); + + MIX_DeactivateAllPaintbuffers(); + MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); +} + +void MIX_PaintChannels( int endtime ) +{ + int end, count; + float dsp_room_gain; + + CheckNewDspPresets(); + + // get dsp preset gain values, update gain crossfaders, + // used when mixing dsp processed buffers into paintbuffer + dsp_room_gain = DSP_GetGain( idsp_room ); // update crossfader - gain only used in MIX_ScaleChannelVolume while( paintedtime < endtime ) { @@ -497,44 +1018,28 @@ void S_PaintChannels( int endtime ) if( endtime - paintedtime > PAINTBUFFER_SIZE ) end = paintedtime + PAINTBUFFER_SIZE; - // clear the paint buffer - if( s_rawend < paintedtime ) - { - Mem_Set( paintbuffer, 0, (end - paintedtime) * sizeof( portable_samplepair_t )); - } - else - { - int i, stop; + // number of 44khz samples to mix into paintbuffer, up to paintbuffer size + count = end - paintedtime; - // copy from the streaming sound source - stop = (end < s_rawend) ? end : s_rawend; + // clear the all mix buffers + MIX_ClearAllPaintBuffers( count, false ); - for( i = paintedtime; i < stop; i++ ) - paintbuffer[i - paintedtime] = s_rawsamples[i & (MAX_RAW_SAMPLES - 1)]; - - for( ; i < end; i++ ) - paintbuffer[i-paintedtime].left = paintbuffer[i-paintedtime].right = 0; - } + MIX_UpsampleAllPaintbuffers( end, count ); - S_MixAllChannels( endtime, end ); + // process all sounds with DSP + DSP_Process( idsp_room, MIX_GetPFrontFromIPaint( IROOMBUFFER ), count ); -// SX_RoomFX( endtime, true, true ); + // add music or soundtrack from movie (no dsp) + MIX_MixPaintbuffers( IROOMBUFFER, ISTREAMBUFFER, IPAINTBUFFER, count, s_musicvolume->value ); + + // clip all values > 16 bit down to 16 bit + MIX_CompressPaintbuffer( IPAINTBUFFER, count ); + + // transfer IPAINTBUFFER paintbuffer out to DMA buffer + MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); // transfer out according to DMA format S_TransferPaintBuffer( end ); paintedtime = end; } -} - -void S_InitScaletable( void ) -{ - int i, j; - int scale; - - for( i = 0; i < 32; i++ ) - { - scale = i * 8 * 256 * S_GetMasterVolume(); - for( j = 0; j < 256; j++ ) snd_scaletable[i][j] = ((signed char)j) * scale; - } - s_volume->modified = false; -} +} \ No newline at end of file diff --git a/snd_dx/s_stream.c b/snd_dx/s_stream.c index 8105bbb3..540b621d 100644 --- a/snd_dx/s_stream.c +++ b/snd_dx/s_stream.c @@ -26,10 +26,12 @@ void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack ) { introTrack = ""; } + if( !mainTrack || !*mainTrack ) { mainTrack = introTrack; } + if( !*introTrack ) return; if( mainTrack ) @@ -78,7 +80,7 @@ void S_StreamBackgroundTrack( void ) if( !s_bgTrack.stream ) return; // don't bother playing anything if musicvolume is 0 - if( !s_musicvolume->value ) return; + if( !s_musicvolume->value || s_listener.paused ) return; // see how many samples should be copied into the raw buffer if( s_rawend < soundtime ) @@ -91,7 +93,7 @@ void S_StreamBackgroundTrack( void ) bufferSamples = MAX_RAW_SAMPLES - (s_rawend - soundtime); // decide how much data needs to be read from the file - fileSamples = bufferSamples * info->rate / dma.speed; + fileSamples = bufferSamples * info->rate / SOUND_DMA_SPEED; // our max buffer size fileBytes = fileSamples * ( info->width * info->channels ); @@ -164,18 +166,14 @@ Cinematic streaming and voice over network */ void S_StreamRawSamples( int samples, int rate, int width, int channels, const byte *data ) { - int i, snd_vol; - int a, b, src, dst; + int i, a, b, src, dst; int fracstep, samplefrac; int incount, outcount; - snd_vol = (int)(s_musicvolume->value * 256); - if( snd_vol < 0 ) snd_vol = 0; - src = 0; samplefrac = 0; - fracstep = (((double)rate) / (double)dma.speed) * 256.0; - outcount = (double)samples * (double) dma.speed / (double)rate; + fracstep = (((double)rate) / (double)SOUND_DMA_SPEED) * 256.0; + outcount = (double)samples * (double)SOUND_DMA_SPEED / (double)rate; incount = samples * channels; #define TAKE_SAMPLE( s ) (sizeof(*in) == 1 ? (a = (in[src+(s)]-128)<<8,\ @@ -183,7 +181,7 @@ void S_StreamRawSamples( int samples, int rate, int width, int channels, const b (a = in[src+(s)],\ b = (src < incount - channels) ? (in[src+channels+(s)]) : 0)) -#define LERP_SAMPLE ((((((b - a) * (samplefrac & 255)) >> 8) + a) * snd_vol)) +#define LERP_SAMPLE (((((b - a) * (samplefrac & 255)) >> 8) + a)) #define RESAMPLE_RAW \ if( channels == 2 ) { \ diff --git a/snd_dx/s_utils.c b/snd_dx/s_utils.c index 24263ee2..7d09df8b 100644 --- a/snd_dx/s_utils.c +++ b/snd_dx/s_utils.c @@ -6,8 +6,8 @@ #include "sound.h" // hardcoded macros to test for zero crossing -#define ZERO_X_8( b ) (( b )< 2 && ( b ) > -2 ) -#define ZERO_X_16( b ) (( b )< 512 && ( b ) > -512 ) +#define ZERO_X_8( b ) (( b ) < 2 && ( b ) > -2 ) +#define ZERO_X_16( b ) (( b ) < 512 && ( b ) > -512 ) //----------------------------------------------------------------------------- // Purpose: Search backward for a zero crossing starting at sample diff --git a/snd_dx/s_vox.c b/snd_dx/s_vox.c index cd7a500a..efd9e16f 100644 --- a/snd_dx/s_vox.c +++ b/snd_dx/s_vox.c @@ -368,6 +368,8 @@ void VOX_FreeWord( channel_t *pchan ) pchan->currentWord = NULL; // sentence is finished Mem_Set( &pchan->pMixer, 0, sizeof( pchan->pMixer )); +// UNDONE: add references to sources +return; if( pchan->words[pchan->wordIndex].sfx ) { // If this wave wasn't precached by the game code diff --git a/snd_dx/sound.h b/snd_dx/sound.h index a27f1203..03cb0fb3 100644 --- a/snd_dx/sound.h +++ b/snd_dx/sound.h @@ -22,6 +22,12 @@ extern byte *sndpool; #define SND_LOCALSOUND (1<<9) // not paused, not looped, for internal use #define SND_STOP_LOOPING (1<<10) // stop all looping sounds on the entity. +// sound engine rate defines +#define SOUND_DMA_SPEED 44100 // hardware playback rate +#define SOUND_11k 11025 // 11khz sample rate +#define SOUND_22k 22050 // 22khz sample rate +#define SOUND_44k 44100 // 44khz sample rate + // fixed point stuff for real-time resampling #define FIX_BITS 28 #define FIX_SCALE (1 << FIX_BITS) @@ -32,12 +38,34 @@ extern byte *sndpool; #define FIX_FRACTION(a,b) (FIX(a)/(b)) #define FIX_FRACPART(a) ((a) & FIX_MASK) +#define CLIP( x ) (( x ) > 32767 ? 32767 : (( x ) < -32767 ? -32767 : ( x ))) +#define SWAP( a, b, t ) {(t) = (a); (a) = (b); (b) = (t);} +#define AVG( a, b ) (((a) + (b)) >> 1 ) +#define AVG4( a, b, c, d ) (((a) + (b) + (c) + (d)) >> 2 ) + +#define PAINTBUFFER_SIZE 1024 // 44k: was 512 +#define PAINTBUFFER (g_curpaintbuffer) +#define CPAINTBUFFERS 3 + typedef struct { int left; int right; } portable_samplepair_t; +// sound mixing buffer + +#define CPAINTFILTERMEM 3 +#define CPAINTFILTERS 4 // maximum number of consecutive upsample passes per paintbuffer + +typedef struct +{ + bool factive; // if true, mix to this paintbuffer using flags + portable_samplepair_t *pbuf; // front stereo mix buffer, for 2 or 4 channel mixing + int ifilter; // current filter memory buffer to use for upsampling pass + portable_samplepair_t fltmem[CPAINTFILTERS][CPAINTFILTERMEM]; +} paintbuffer_t; + typedef struct sfx_s { string name; @@ -48,6 +76,13 @@ typedef struct sfx_s struct sfx_s *hashNext; } sfx_t; +extern portable_samplepair_t drybuffer[]; +extern portable_samplepair_t paintbuffer[]; +extern portable_samplepair_t roombuffer[]; +extern portable_samplepair_t temppaintbuffer[]; +extern portable_samplepair_t *g_curpaintbuffer; +extern paintbuffer_t paintbuffers[]; + // structure used for fading in and out client sound volume. typedef struct { @@ -61,12 +96,8 @@ typedef struct typedef struct { - int channels; int samples; // mono samples in buffer - int submission_chunk; // don't mix less than this # int samplepos; // in mono samples - int samplebits; - int speed; byte *buffer; } dma_t; @@ -99,6 +130,7 @@ typedef struct bool use_loop; // don't loop default and local sounds bool staticsound; // use origin instead of fetching entnum's origin bool localsound; // it's a local menu sound (not looped, not paused) + bool bdry; // if true, bypass all dsp processing for this sound (ie: music) mixer_t pMixer; // sentence mixer @@ -152,7 +184,6 @@ void SNDDMA_Submit( void ); #define MAX_CHANNELS 128 #define MAX_RAW_SAMPLES 8192 -extern portable_samplepair_t paintbuffer[]; extern channel_t channels[MAX_CHANNELS]; extern int total_channels; extern int paintedtime; @@ -160,20 +191,21 @@ extern int s_rawend; extern int soundtime; extern dma_t dma; extern listener_t s_listener; +extern int idsp_room; extern cvar_t *s_check_errors; extern cvar_t *s_volume; extern cvar_t *s_musicvolume; -extern cvar_t *s_khz; extern cvar_t *s_show; extern cvar_t *s_mixahead; extern cvar_t *s_primary; +extern cvar_t *s_lerping; +extern cvar_t *dsp_off; extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; void S_InitScaletable( void ); wavdata_t *S_LoadSound( sfx_t *sfx ); -void S_PaintChannels( int endtime ); float S_GetMasterVolume( void ); void S_PrintDeviceName( void ); @@ -187,6 +219,10 @@ void S_BeginFrame( void ); // s_mix.c // int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ); +void MIX_ClearAllPaintBuffers( int SampleCount, bool clearFilters ); +void MIX_InitAllPaintbuffers( void ); +void MIX_FreeAllPaintbuffers( void ); +void MIX_PaintChannels( int endtime ); // s_load.c bool S_TestSoundChar( const char *pch, char c ); @@ -195,9 +231,16 @@ sfx_t *S_FindName( const char *name, int *pfInCache ); void S_FreeSound( sfx_t *sfx ); // s_dsp.c +bool AllocDsps( void ); +void FreeDsps( void ); +void CheckNewDspPresets( void ); +void DSP_Process( int idsp, portable_samplepair_t *pbfront, int sampleCount ); +float DSP_GetGain( int idsp ); +void DSP_ClearState( void ); + void SX_Init( void ); void SX_Free( void ); -void SX_RoomFX( int endtime, int fFilter, int fTimefx ); +void SX_RoomFX( portable_samplepair_t *pbuf, int sampleCount ); bool S_Init( void *hInst ); void S_Shutdown( void ); diff --git a/xash.dsw b/xash.dsw index 4bc2aa61..944dce28 100644 --- a/xash.dsw +++ b/xash.dsw @@ -3,7 +3,19 @@ Microsoft Developer Studio Workspace File, Format Version 6.00 ############################################################################### -Project: "bshift"=".\dlls\hl.dsp" - Package Owner=<4> +Project: "hl"=".\dlls\hl.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "bshift"=".\dlls\bshift.dsp" - Package Owner=<4> Package=<5> {{{