Add gamepad rumble support (#1876)

This commit is contained in:
ceski 2024-09-07 07:13:47 -07:00 committed by GitHub
parent 10c64223e9
commit 3ee1bb572c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 3250 additions and 45 deletions

View File

@ -41,7 +41,7 @@ If you turn the [Doom logo upside down](https://www.reddit.com/r/Doom/comments/8
* Support for voxels in KVX format.
* 3D audio, supporting stereo and up to 7.1 surround sound with an optional HRTF mode, as well as PC speaker emulation.
* Several music backends: native MIDI, FluidSynth with a bundled soundfont, built-in OPL3 emulator. Digital music and sound formats supported by libsndfile, module music supported by libxmp.
* Modern gamepad support, including gyro controls and the flick stick control scheme.
* Modern gamepad support, including rumble, gyro, and flick stick.
* Mouselook.
* Autoload directories.
* Savegame backward compatibility up to `MBF.EXE`.
@ -237,6 +237,12 @@ Copyright:
© 2013-2014 RAD Game Tools and Valve Software.
License: [MIT](https://opensource.org/licenses/MIT)
Files: `third-party/pffft/*`
Copyright:
© 2004 The University Corporation for Atmospheric Research ("UCAR");
© 2013 Julien Pommier.
License: [FFTPACK License](https://bitbucket.org/jpommier/pffft/src/master/pffft.h)
Files: `third-party/spng/*`
Copyright: © 2018-2023 Randy.
License: [BSD-2-Clause](https://opensource.org/license/bsd-2-clause)

View File

@ -48,6 +48,7 @@ set(WOOF_SOURCES
i_oalstream.h
i_oplmusic.c
i_printf.c i_printf.h
i_rumble.c i_rumble.h
i_sndfile.c i_sndfile.h
i_sound.c i_sound.h
i_system.c i_system.h
@ -186,6 +187,7 @@ target_link_libraries(woof PRIVATE
OpenAL::OpenAL
SndFile::sndfile
opl
pffft
textscreen
miniz
spng

View File

@ -46,6 +46,7 @@
#include "i_gyro.h"
#include "i_input.h"
#include "i_printf.h"
#include "i_rumble.h"
#include "i_system.h"
#include "i_timer.h"
#include "i_video.h"
@ -850,6 +851,7 @@ void G_ClearInput(void)
G_ClearCarry();
memset(&basecmd, 0, sizeof(basecmd));
I_ResetRelativeMouseState();
I_ResetAllRumbleChannels();
}
//
@ -3020,8 +3022,8 @@ static boolean G_CheckSpot(int playernum, mapthing_t *mthing)
ss->sector->floorheight, MT_TFOG);
}
if (players[consoleplayer].viewz != 1)
S_StartSound(mo, sfx_telept); // don't start sound on first frame
if (players[consoleplayer].viewz != 1) // don't start sound on first frame
S_StartSoundSource(players[consoleplayer].mo, mo, sfx_telept);
return true;
}

View File

@ -32,6 +32,7 @@
#include "i_printf.h"
#include "g_game.h"
#include "i_gamepad.h"
#include "i_rumble.h"
#include "i_timer.h"
#include "i_video.h"
#include "m_config.h"
@ -224,6 +225,7 @@ void I_UpdateGyroCalibrationState(void)
switch (cal.state)
{
case GYRO_CALIBRATION_INACTIVE:
I_DisableRumble();
cal.state = GYRO_CALIBRATION_STARTING;
cal.start_time = I_GetTimeMS();
break;
@ -252,6 +254,7 @@ void I_UpdateGyroCalibrationState(void)
if (I_GetTimeMS() - cal.finish_time > 1500)
{
cal.state = GYRO_CALIBRATION_INACTIVE;
I_UpdateRumbleEnabled();
}
break;
}

View File

@ -24,6 +24,7 @@
#include "i_gamepad.h"
#include "i_gyro.h"
#include "i_printf.h"
#include "i_rumble.h"
#include "i_system.h"
#include "i_timer.h"
#include "m_config.h"
@ -406,6 +407,7 @@ static void DisableGamepadEvents(void)
static void I_ShutdownGamepad(void)
{
I_ShutdownRumble();
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
}
@ -431,6 +433,7 @@ void I_InitGamepad(void)
}
DisableGamepadEvents();
I_InitRumble();
I_Printf(VB_INFO, "I_InitGamepad: Initialize gamepad.");
@ -453,6 +456,7 @@ void I_OpenGamepad(int which)
"I_OpenGamepad: Found a valid gamepad, named: %s",
SDL_GameControllerName(gamepad));
I_SetRumbleSupported(gamepad);
I_ResetGamepad();
I_LoadGyroCalibration();
UpdatePlatform();
@ -483,6 +487,7 @@ void I_CloseGamepad(SDL_JoystickID instance_id)
{
SDL_GameControllerClose(gamepad);
gamepad = NULL;
I_SetRumbleSupported(NULL);
DisableGamepadEvents();
UpdatePlatform();
I_ResetGamepad();

View File

@ -29,6 +29,7 @@
#include "i_oalequalizer.h"
#include "i_oalsound.h"
#include "i_printf.h"
#include "i_rumble.h"
#include "i_sndfile.h"
#include "i_sound.h"
#include "m_array.h"
@ -713,6 +714,7 @@ boolean I_OAL_CacheSound(sfxinfo_t *sfx)
sfx->buffer = buffer;
sfx->cached = true;
I_CacheRumble(sfx, format, sampledata, size, freq);
}
// don't need original lump data any more

799
src/i_rumble.c Normal file
View File

@ -0,0 +1,799 @@
//
// Copyright(C) 2024 ceski
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// Gamepad rumble.
//
#include "alext.h"
#include "pffft.h"
#include <math.h>
#include <string.h>
#include "doomstat.h"
#include "i_gamepad.h"
#include "i_rumble.h"
#include "i_sound.h"
#include "m_config.h"
#include "p_mobj.h"
#include "sounds.h"
#include "w_wad.h"
#define MAX_RUMBLE_COUNT 4
#define MAX_RUMBLE_SDL 0xFFFF
#define RUMBLE_DURATION 1000
#define R_CLOSE 64.0f
#define R_CLIP 320.0f
#define RUMBLE_TICS 7
#define MAX_TICLENGTH 70 // TICRATE * 2
static int joy_rumble;
static int last_rumble;
typedef struct rumble_channel_s
{
struct rumble_channel_s *prev, *next;
int handle; // Sound channel using this rumble channel.
rumble_type_t type; // Rumble type.
float scale; // Rumble scale for this channel.
int pos; // Position in rumble sequence.
const float *low; // Pointer to cached low frequency rumble values.
const float *high; // Pointer to cached high frequency rumble values.
int ticlength; // Array size equal to sound duration in tics.
} rumble_channel_t;
typedef struct
{
SDL_GameController *gamepad;
boolean enabled; // Rumble enabled?
boolean supported; // Rumble supported?
float scale; // Overall rumble scale, based on joy_rumble.
rumble_channel_t *channels; // Rumble channel for each sound channel.
rumble_channel_t base; // Linked list.
int count; // Number of active rumble channels.
} rumble_t;
static rumble_t rumble;
typedef struct
{
PFFFT_Setup *setup;
float *in;
float *out;
float *window;
int size;
float amp_scale;
float freq_scale;
int start;
int end;
} fft_t;
static fft_t fft;
// Rumble pattern presets.
static const float rumble_itemup[] = {0.12f, 0.0f};
static const float rumble_wpnup[] = {0.12f, 0.07f, 0.04f, 0.16f, 0.0f};
static const float rumble_getpow[] = {0.07f, 0.14f, 0.16f, 0.14f, 0.0f};
static const float rumble_oof[] = {0.14f, 0.12f, 0.0f};
static const float rumble_pain[] = {0.12f, 0.31f, 0.26f, 0.22f,
0.32f, 0.34f, 0.0f};
static const float rumble_hitfloor[] = {0.48f, 0.45f, 0.40f, 0.35f, 0.30f,
0.25f, 0.20f, 0.15f, 0.0f};
typedef struct
{
const float *data;
int ticlength;
} rumble_preset_t;
static const rumble_preset_t presets[] = {
[RUMBLE_NONE] = {NULL, 0 },
[RUMBLE_ITEMUP] = {rumble_itemup, arrlen(rumble_itemup) },
[RUMBLE_WPNUP] = {rumble_wpnup, arrlen(rumble_wpnup) },
[RUMBLE_GETPOW] = {rumble_getpow, arrlen(rumble_getpow) },
[RUMBLE_OOF] = {rumble_oof, arrlen(rumble_oof) },
[RUMBLE_PAIN] = {rumble_pain, arrlen(rumble_pain) },
[RUMBLE_HITFLOOR] = {rumble_hitfloor, arrlen(rumble_hitfloor)},
};
static const float default_scale[] = {
[RUMBLE_NONE] = 0.0f,
[RUMBLE_ITEMUP] = 1.0f,
[RUMBLE_WPNUP] = 1.0f,
[RUMBLE_GETPOW] = 1.0f,
[RUMBLE_OOF] = 1.0f,
[RUMBLE_PAIN] = 1.0f,
[RUMBLE_HITFLOOR] = 0.0f,
[RUMBLE_PLAYER] = 1.0f,
[RUMBLE_ORIGIN] = 0.8f,
[RUMBLE_PISTOL] = 0.55f,
[RUMBLE_SHOTGUN] = 1.0f,
[RUMBLE_SSG] = 1.25f,
[RUMBLE_CGUN] = 0.55f,
[RUMBLE_ROCKET] = 1.15f,
[RUMBLE_PLASMA] = 0.75f,
[RUMBLE_BFG] = 1.0f,
};
static void ResetChannel(int handle)
{
rumble_channel_t *node = &rumble.channels[handle];
memset(node, 0, sizeof(*node));
node->prev = node->next = node;
node->handle = handle;
}
static void ResetAllChannels(void)
{
for (int i = 0; i < MAX_CHANNELS; i++)
{
ResetChannel(i);
}
rumble.base.prev = rumble.base.next = &rumble.base;
rumble.count = 0;
}
static void UnlinkNode(rumble_channel_t *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
rumble.count--;
}
static boolean RemoveOldNodes(void)
{
do
{
rumble_channel_t *base = &rumble.base;
rumble_channel_t *node = base->next;
if (node == base)
{
break;
}
while (node->next != base)
{
node = node->next;
}
UnlinkNode(node);
ResetChannel(node->handle);
} while (rumble.count >= MAX_RUMBLE_COUNT);
return (rumble.count < MAX_RUMBLE_COUNT);
}
static void RemoveNode(int handle)
{
rumble_channel_t *base = &rumble.base;
rumble_channel_t *node;
for (node = base->next; node != base; node = node->next)
{
if (node->handle == handle)
{
UnlinkNode(node);
break;
}
}
ResetChannel(handle);
}
static void AddNode(int handle)
{
rumble_channel_t *base = &rumble.base;
rumble_channel_t *node = &rumble.channels[handle];
node->prev = base;
node->next = base->next;
base->next->prev = node;
base->next = node;
rumble.count++;
}
static void FreeFFT(void)
{
if (fft.window)
{
pffft_aligned_free(fft.window);
fft.window = NULL;
}
if (fft.out)
{
pffft_aligned_free(fft.out);
fft.out = NULL;
}
if (fft.in)
{
pffft_aligned_free(fft.in);
fft.in = NULL;
}
if (fft.setup)
{
pffft_destroy_setup(fft.setup);
fft.setup = NULL;
}
}
void I_ShutdownRumble(void)
{
if (!I_GamepadEnabled())
{
return;
}
if (rumble.enabled)
{
SDL_GameControllerRumble(rumble.gamepad, 0, 0, 0);
}
for (int i = 1; i < num_sfx; i++)
{
sfxinfo_t *sfx = &S_sfx[i];
if (sfx->name && sfx->cached)
{
free(sfx->rumble.low);
free(sfx->rumble.high);
}
}
free(rumble.channels);
FreeFFT();
}
void I_InitRumble(void)
{
if (!I_GamepadEnabled())
{
return;
}
last_rumble = joy_rumble;
rumble.channels = malloc(sizeof(*rumble.channels) * MAX_CHANNELS);
ResetAllChannels();
}
static uint32_t RoundUpPowerOfTwo(int n)
{
// https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
uint32_t v = n;
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static void InitFFT(int rate, int step)
{
static int last_rate = -1;
if (last_rate == rate)
{
return;
}
last_rate = rate;
FreeFFT();
// FFT size must be a power of two (see pffft.h).
const uint32_t step2 = RoundUpPowerOfTwo(step);
fft.size = BETWEEN(128, 8192, step2);
fft.setup = pffft_new_setup(fft.size, PFFFT_REAL);
const size_t size = sizeof(float) * fft.size;
fft.in = pffft_aligned_malloc(size);
fft.out = pffft_aligned_malloc(size);
fft.window = pffft_aligned_malloc(size);
const float mult = 2.0f * PI_F / (fft.size - 1.0f);
float sum = 0.0f;
for (int i = 0; i < fft.size; i++)
{
// Hann window.
fft.window[i] = 0.5f * (1.0f - cosf(i * mult));
sum += fft.window[i];
}
fft.amp_scale = 2.0f / sum;
fft.freq_scale = 0.5f * rate / fft.size;
fft.start = lroundf(ceilf(100.0f / fft.freq_scale * 0.5f)) * 2;
fft.end = lroundf(ceilf(0.9f * 0.5f * rate / fft.freq_scale * 0.5f)) * 2;
}
static float Normalize_Mono8(const void *data, int pos)
{
const float val = (((byte *)data)[pos] - 128) / 127.0f;
return MAX(-1.0f, val);
}
static float Normalize_Mono16(const void *data, int pos)
{
const float val = ((int16_t *)data)[pos] / 32767.0f;
return MAX(-1.0f, val);
}
static float Normalize_Mono32(const void *data, int pos)
{
const float val = ((float *)data)[pos];
return BETWEEN(-1.0f, 1.0f, val);
}
static float (*Normalize)(const void *data, int pos);
static void CalcPeakFFT(const byte *data, int rate, int length, int pos,
float *amp, float *freq)
{
const int remaining = length - pos;
const int size = MIN(fft.size, remaining);
for (int i = 0; i < size; i++)
{
// Fill input buffer with data.
fft.in[i] = Normalize(data, pos + i);
// Apply window.
fft.in[i] *= fft.window[i];
}
for (int i = size; i < fft.size; i++)
{
// Fill remaining space with zeros.
fft.in[i] = 0.0f;
}
// Forward transform.
pffft_transform_ordered(fft.setup, fft.in, fft.out, NULL, PFFFT_FORWARD);
// Find index of max value.
int max_index = fft.start;
float max_value = fft.out[max_index] * fft.out[max_index]
+ fft.out[max_index + 1] * fft.out[max_index + 1];
for (int i = fft.start + 2; i < fft.end; i += 2)
{
// PFFFT returns interleaved values.
const float current_value =
fft.out[i] * fft.out[i] + fft.out[i + 1] * fft.out[i + 1];
if (max_value < current_value)
{
max_value = current_value;
max_index = i;
}
}
*amp = sqrtf(max_value) * fft.amp_scale;
*freq = max_index * fft.freq_scale;
}
static void SfxToRumble(const byte *data, int rate, int length,
float **low, float **high, int *ticlength)
{
const int ticlen = *ticlength - 1;
*low = malloc(sizeof(float) * (ticlen + 1));
*high = malloc(sizeof(float) * (ticlen + 1));
(*low)[ticlen] = 0.0f;
(*high)[ticlen] = 0.0f;
const int step = rate / TICRATE;
InitFFT(rate, step);
for (int i = 0; i < ticlen; i++)
{
const int pos = i * step;
float amp_peak, freq_peak;
CalcPeakFFT(data, rate, length, pos, &amp_peak, &freq_peak);
if (amp_peak > 0.0001f)
{
float weight = (freq_peak - 640.0f) * 0.00625f; // 1/160
weight = BETWEEN(-1.0f, 1.0f, weight);
const float dBFS = 20.0f * log10f(amp_peak) + 6.0f;
const float dBFS_low = dBFS - 6.0f * weight;
const float dBFS_high = dBFS + 6.0f * weight;
(*low)[i] = powf(10.0f, MIN(dBFS_low, -12.0f) * 0.05f);
(*high)[i] = powf(10.0f, MIN(dBFS_high, -12.0f) * 0.05f);
}
else
{
(*low)[i] = 0.0f;
(*high)[i] = 0.0f;
}
}
for (int i = 0; i < ticlen; i++)
{
if ((*low)[i] > 0.0f || (*high)[i] > 0.0f)
{
return;
}
}
free(*low);
free(*high);
*low = NULL;
*high = NULL;
*ticlength = 0;
}
static int GetMonoLength(const sfxinfo_t *sfx, int size)
{
ALint bits = 0;
alGetError();
alGetBufferi(sfx->buffer, AL_BITS, &bits);
return ((alGetError() == AL_NO_ERROR && bits > 0) ? (size * 8 / bits) : 0);
}
void I_CacheRumble(sfxinfo_t *sfx, int format, const byte *data, int size,
int rate)
{
if (!I_GamepadEnabled() || !sfx || !alIsBuffer(sfx->buffer) || !data
|| size < 1 || rate < TICRATE)
{
return;
}
switch (format)
{
case AL_FORMAT_MONO8:
Normalize = Normalize_Mono8;
break;
case AL_FORMAT_MONO16:
Normalize = Normalize_Mono16;
break;
case AL_FORMAT_MONO_FLOAT32:
Normalize = Normalize_Mono32;
break;
default: // Unsupported format.
return;
}
const int length = GetMonoLength(sfx, size);
int ticlength = length * TICRATE / rate;
if (!length || !ticlength)
{
return;
}
ticlength++;
ticlength = MIN(ticlength, MAX_TICLENGTH);
float *low, *high;
SfxToRumble(data, rate, length, &low, &high, &ticlength);
sfx->rumble.low = low;
sfx->rumble.high = high;
sfx->rumble.ticlength = ticlength;
}
boolean I_RumbleEnabled(void)
{
return rumble.enabled;
}
boolean I_RumbleSupported(void)
{
return rumble.supported;
}
void I_RumbleMenuFeedback(void)
{
if (!rumble.supported || last_rumble == joy_rumble)
{
return;
}
last_rumble = joy_rumble;
const uint16_t test = (uint16_t)(rumble.scale * 0.25f);
SDL_GameControllerRumble(rumble.gamepad, test, test, 125);
}
void I_UpdateRumbleEnabled(void)
{
rumble.scale = 0.2f * joy_rumble * MAX_RUMBLE_SDL;
rumble.enabled = (joy_rumble && rumble.supported);
}
void I_SetRumbleSupported(SDL_GameController *gamepad)
{
rumble.gamepad = gamepad;
rumble.supported =
gamepad && (SDL_GameControllerHasRumble(gamepad) == SDL_TRUE);
I_UpdateRumbleEnabled();
}
void I_ResetRumbleChannel(int handle)
{
if (!rumble.supported)
{
return;
}
#ifdef RANGECHECK
if (handle < 0 || handle >= MAX_CHANNELS)
{
return;
}
#endif
RemoveNode(handle);
}
void I_ResetAllRumbleChannels(void)
{
if (!rumble.supported)
{
return;
}
ResetAllChannels();
SDL_GameControllerRumble(rumble.gamepad, 0, 0, 0);
}
static void GetNodeScale(const rumble_channel_t *node, float *scale_down,
float *scale)
{
*scale = node->scale;
if (node->type == RUMBLE_BFG)
{
if (node->pos > 40)
{
*scale *= 1.0f - (float)(node->pos - 40) / (node->ticlength - 40);
}
}
else if (node->pos > RUMBLE_TICS)
{
*scale *= 1.0f - (node->pos - RUMBLE_TICS) / 3.0f;
}
*scale = MAX(*scale, 0.0f);
if (node->type != RUMBLE_ORIGIN)
{
*scale *= *scale_down;
*scale_down *= 0.1f;
}
}
void I_UpdateRumble(void)
{
if (!rumble.enabled || menuactive || demoplayback)
{
return;
}
float scale_low = 0.0f;
float scale_high = 0.0f;
float scale_down = 1.0f;
float scale;
rumble_channel_t *base = &rumble.base;
rumble_channel_t *node, *next;
for (node = base->next; node != base; node = next)
{
next = node->next;
GetNodeScale(node, &scale_down, &scale);
scale_low += node->low[node->pos] * scale;
scale_high += node->high[node->pos] * scale;
node->pos++;
if (node->pos >= node->ticlength)
{
UnlinkNode(node);
ResetChannel(node->handle);
}
}
scale_low *= rumble.scale;
scale_high *= rumble.scale;
const uint16_t low = lroundf(MIN(scale_low, MAX_RUMBLE_SDL));
const uint16_t high = lroundf(MIN(scale_high, MAX_RUMBLE_SDL));
SDL_GameControllerRumble(rumble.gamepad, low, high, RUMBLE_DURATION);
}
static boolean CalcChannelScale(const mobj_t *listener, const mobj_t *origin,
rumble_channel_t *node)
{
if (node->type != RUMBLE_ORIGIN || !origin || !listener)
{
return true;
}
const float dx = (listener->x >> FRACBITS) - (origin->x >> FRACBITS);
const float dy = (listener->y >> FRACBITS) - (origin->y >> FRACBITS);
const float dist = sqrtf(dx * dx + dy * dy);
if (dist <= R_CLOSE)
{
node->scale = 1.0f;
}
else if (dist >= R_CLIP)
{
node->scale = 0.0f;
}
else
{
node->scale = (R_CLIP - dist) * R_CLOSE / ((R_CLIP - R_CLOSE) * dist);
}
return (node->scale > 0.0f);
}
void I_UpdateRumbleParams(const mobj_t *listener, const mobj_t *origin,
int handle)
{
if (!rumble.enabled || menuactive || demoplayback)
{
return;
}
#ifdef RANGECHECK
if (handle < 0 || handle >= MAX_CHANNELS)
{
return;
}
#endif
rumble_channel_t *node = &rumble.channels[handle];
if (node->type == RUMBLE_NONE)
{
return;
}
if (!CalcChannelScale(listener, origin, node))
{
RemoveNode(handle);
}
}
static float ScaleHitFloor(const mobj_t *listener)
{
if (!listener || listener->momz >= -8 * GRAVITY)
{
return 0.25f;
}
else if (listener->momz <= -40 * GRAVITY)
{
return 1.0f;
}
else
{
float scale = (float)FIXED2DOUBLE(listener->momz) + 8.0f;
//scale = (1 - 0.25) / pow(40 - 8, 2) * pow(scale, 2) + 0.25;
scale = 0.75f / 1024.0f * scale * scale + 0.25f;
return BETWEEN(0.25f, 1.0f, scale);
}
}
static boolean GetSFXParams(const mobj_t *listener, const sfxinfo_t *sfx,
rumble_channel_t *node)
{
switch (node->type)
{
case RUMBLE_ITEMUP:
case RUMBLE_WPNUP:
case RUMBLE_GETPOW:
case RUMBLE_OOF:
case RUMBLE_PAIN:
node->low = node->high = presets[node->type].data;
node->ticlength = presets[node->type].ticlength;
break;
case RUMBLE_HITFLOOR:
node->low = node->high = presets[node->type].data;
node->scale = ScaleHitFloor(listener);
node->ticlength =
lroundf(node->scale * presets[node->type].ticlength);
break;
case RUMBLE_PLAYER:
case RUMBLE_ORIGIN:
case RUMBLE_PISTOL:
case RUMBLE_SHOTGUN:
case RUMBLE_SSG:
case RUMBLE_CGUN:
case RUMBLE_ROCKET:
case RUMBLE_PLASMA:
case RUMBLE_BFG:
node->low = sfx->rumble.low;
node->high = sfx->rumble.high;
node->ticlength = sfx->rumble.ticlength;
break;
default:
node->low = node->high = NULL;
node->ticlength = 0;
break;
}
return (node->low && node->high && node->ticlength > 0);
}
void I_StartRumble(const mobj_t *listener, const mobj_t *origin,
const sfxinfo_t *sfx, int handle, rumble_type_t rumble_type)
{
if (!rumble.enabled || menuactive || demoplayback || !sfx
|| !sfx->rumble.low || !sfx->rumble.high)
{
return;
}
#ifdef RANGECHECK
if (handle < 0 || handle >= MAX_CHANNELS)
{
return;
}
#endif
rumble_channel_t *node = &rumble.channels[handle];
if (node->type != RUMBLE_NONE)
{
RemoveNode(handle);
}
if (rumble.count >= MAX_RUMBLE_COUNT && !RemoveOldNodes())
{
return;
}
node->type = rumble_type;
node->scale = default_scale[node->type];
if (CalcChannelScale(listener, origin, node)
&& GetSFXParams(listener, sfx, node))
{
AddNode(handle);
}
else
{
ResetChannel(handle);
}
}
// For gyro calibration only.
void I_DisableRumble(void)
{
if (!rumble.enabled)
{
return;
}
rumble.enabled = false;
ResetAllChannels();
SDL_GameControllerRumble(rumble.gamepad, 0, 0, 0);
}
void I_BindRumbleVariables(void)
{
BIND_NUM_GENERAL(joy_rumble, 5, 0, 10,
"Rumble intensity (0 = Off; 10 = 100%)");
}

78
src/i_rumble.h Normal file
View File

@ -0,0 +1,78 @@
//
// Copyright(C) 2024 ceski
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// Gamepad rumble.
//
#ifndef __I_RUMBLE__
#define __I_RUMBLE__
#include "SDL.h"
#include "doomtype.h"
struct mobj_s;
struct sfxinfo_s;
typedef enum
{
RUMBLE_NONE,
RUMBLE_ITEMUP,
RUMBLE_WPNUP,
RUMBLE_GETPOW,
RUMBLE_OOF,
RUMBLE_PAIN,
RUMBLE_HITFLOOR,
RUMBLE_PLAYER,
RUMBLE_ORIGIN,
RUMBLE_PISTOL,
RUMBLE_SHOTGUN,
RUMBLE_SSG,
RUMBLE_CGUN,
RUMBLE_ROCKET,
RUMBLE_PLASMA,
RUMBLE_BFG,
} rumble_type_t;
void I_ShutdownRumble(void);
void I_InitRumble(void);
void I_CacheRumble(struct sfxinfo_s *sfx, int format, const byte *data,
int size, int rate);
boolean I_RumbleEnabled(void);
boolean I_RumbleSupported(void);
void I_RumbleMenuFeedback(void);
void I_UpdateRumbleEnabled(void);
void I_SetRumbleSupported(SDL_GameController *gamepad);
void I_ResetRumbleChannel(int handle);
void I_ResetAllRumbleChannels(void);
void I_UpdateRumble(void);
void I_UpdateRumbleParams(const struct mobj_s *listener,
const struct mobj_s *origin, int handle);
void I_StartRumble(const struct mobj_s *listener, const struct mobj_s *origin,
const struct sfxinfo_s *sfx, int handle,
rumble_type_t rumble_type);
// For gyro calibration only.
void I_DisableRumble(void);
void I_BindRumbleVariables(void);
#endif

View File

@ -27,6 +27,7 @@
#include "doomtype.h"
#include "i_oalstream.h"
#include "i_printf.h"
#include "i_rumble.h"
#include "i_system.h"
#include "m_array.h"
#include "mn_menu.h"
@ -111,6 +112,8 @@ static void StopChannel(int channel)
channelinfo[channel].enabled = false;
}
I_ResetRumbleChannel(channel);
}
//

View File

@ -440,7 +440,7 @@ static void cheat_god()
// [crispy] spawn a teleport fog
an = plyr->mo->angle >> ANGLETOFINESHIFT;
P_SpawnMobj(plyr->mo->x+20*finecosine[an], plyr->mo->y+20*finesine[an], plyr->mo->z, MT_TFOG);
S_StartSound(plyr->mo, sfx_slop);
S_StartSoundEx(plyr->mo, sfx_slop);
P_MapEnd();
// Fix reviving as "zombie" if god mode was already enabled

View File

@ -39,6 +39,7 @@
#include "i_gamepad.h"
#include "i_gyro.h"
#include "i_printf.h"
#include "i_rumble.h"
#include "i_sound.h"
#include "i_system.h"
#include "i_video.h"
@ -121,6 +122,7 @@ void M_InitConfig(void)
G_BindMouseVariables();
I_BindGamepadVariables();
I_BindRumbleVariables();
I_BindFlickStickVariables();
I_BindGyroVaribales();

View File

@ -30,6 +30,7 @@
#include "i_input.h"
#include "i_oalequalizer.h"
#include "i_oalsound.h"
#include "i_rumble.h"
#include "i_sound.h"
#include "i_timer.h"
#include "i_video.h"
@ -303,6 +304,7 @@ enum
{
str_empty,
str_layout,
str_rumble,
str_curve,
str_center_weapon,
str_screensize,
@ -2451,6 +2453,16 @@ static const char *layout_strings[] = {
"Flick Stick Southpaw",
};
static void UpdateRumble(void)
{
I_UpdateRumbleEnabled();
I_RumbleMenuFeedback();
}
static const char *rumble_strings[] = {
"Off", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"
};
static const char *curve_strings[] = {
"", "", "", "", "", "", "",
"", "", "", // Dummy values, start at 1.0.
@ -2469,6 +2481,9 @@ static setup_menu_t gen_settings4[] = {
{"Invert Look", S_ONOFF, CNTR_X, M_SPC, {"joy_invert_look"},
.action = I_ResetGamepad},
{"Rumble", S_THERMO, CNTR_X, M_THRM_SPC, {"joy_rumble"},
.strings_id = str_rumble, .action = UpdateRumble},
MI_GAP,
{"Turn Speed", S_THERMO | S_THRM_SIZE11, CNTR_X, M_THRM_SPC,
@ -2494,14 +2509,9 @@ static setup_menu_t gen_settings4[] = {
static void UpdateGamepadItems(void)
{
boolean condition =
(!I_UseGamepad() || !I_GamepadEnabled() || !I_UseStickLayout());
(!I_UseGamepad() || !I_GamepadEnabled() || !I_RumbleSupported());
DisableItem(condition, gen_settings4, "joy_invert_look");
DisableItem(condition, gen_settings4, "joy_movement_inner_deadzone");
DisableItem(condition, gen_settings4, "joy_camera_inner_deadzone");
DisableItem(condition, gen_settings4, "joy_turn_speed");
DisableItem(condition, gen_settings4, "joy_look_speed");
DisableItem(condition, gen_settings4, "joy_camera_curve");
DisableItem(condition, gen_settings4, "joy_rumble");
// Allow padlook toggle when the gamepad is using gyro, even if the
// stick layout is set to off.
@ -2510,6 +2520,14 @@ static void UpdateGamepadItems(void)
|| (!I_UseStickLayout() && (!I_GyroEnabled() || !I_GyroSupported())));
DisableItem(condition, gen_settings4, "padlook");
condition = (!I_UseGamepad() || !I_GamepadEnabled() || !I_UseStickLayout());
DisableItem(condition, gen_settings4, "joy_invert_look");
DisableItem(condition, gen_settings4, "joy_movement_inner_deadzone");
DisableItem(condition, gen_settings4, "joy_camera_inner_deadzone");
DisableItem(condition, gen_settings4, "joy_turn_speed");
DisableItem(condition, gen_settings4, "joy_look_speed");
DisableItem(condition, gen_settings4, "joy_camera_curve");
}
static void UpdateGyroItems(void);
@ -4193,6 +4211,7 @@ void MN_DrawTitle(int x, int y, const char *patch, const char *alttext)
static const char **selectstrings[] = {
NULL, // str_empty
layout_strings,
rumble_strings,
curve_strings,
center_weapon_strings,
screensize_strings,

View File

@ -2197,13 +2197,13 @@ void A_Scream(mobj_t *actor)
void A_XScream(mobj_t *actor)
{
S_StartSound(actor, sfx_slop);
S_StartSoundEx(actor, sfx_slop);
}
void A_Pain(mobj_t *actor)
{
if (actor->info->painsound)
S_StartSound(actor, actor->info->painsound);
S_StartSoundPain(actor, actor->info->painsound);
}
void A_Fall(mobj_t *actor)
@ -2709,7 +2709,7 @@ void A_PlayerScream(mobj_t *mo)
int sound = sfx_pldeth; // Default death sound.
if (gamemode != shareware && mo->health < -50) // killough 12/98
sound = sfx_pdiehi; // IF THE PLAYER DIES LESS THAN -50% WITHOUT GIBBING
S_StartSound(mo, sound);
S_StartSoundEx(mo, sound);
}
//
@ -2790,7 +2790,7 @@ void A_PlaySound(mobj_t *mo)
{
if (demo_version < DV_MBF)
return;
S_StartSound(mo->state->misc2 ? NULL : mo, mo->state->misc1);
S_StartSoundOrigin(mo, mo->state->misc2 ? NULL : mo, mo->state->misc1);
}
void A_RandomJump(mobj_t *mo)

View File

@ -207,7 +207,7 @@ boolean P_GiveWeapon(player_t *player, weapontype_t weapon, boolean dropped)
P_GiveAmmo(player, weaponinfo[weapon].ammo, deathmatch ? 5 : 2);
player->pendingweapon = weapon;
S_StartSound(player->mo, sfx_wpnup); // killough 4/25/98, 12/98
S_StartSoundPreset(player->mo, sfx_wpnup, PITCH_FULL); // killough 4/25/98, 12/98
return false;
}
@ -652,8 +652,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher)
P_RemoveMobj (special);
player->bonuscount += BONUSADD;
S_StartSoundPitch(player->mo, sound, // killough 4/25/98, 12/98
sound == sfx_itemup ? PITCH_NONE : PITCH_FULL);
S_StartSoundPreset(player->mo, sound, // killough 4/25/98, 12/98
sound == sfx_itemup ? PITCH_NONE : PITCH_FULL);
}
//

View File

@ -1178,7 +1178,7 @@ static void P_HitSlideLine(line_t *ld)
{
if (icyfloor && abs(tmymove) > abs(tmxmove))
{
S_StartSound(slidemo,sfx_oof); // oooff!
S_StartSoundPreset(slidemo, sfx_oof, PITCH_FULL); // oooff!
tmxmove /= 2; // absorb half the momentum
tmymove = -tmymove/2;
}
@ -1191,7 +1191,7 @@ static void P_HitSlideLine(line_t *ld)
{
if (icyfloor && abs(tmxmove) > abs(tmymove))
{
S_StartSound(slidemo,sfx_oof); // oooff!
S_StartSoundPreset(slidemo, sfx_oof, PITCH_FULL); // oooff!
tmxmove = -tmxmove/2; // absorb half the momentum
tmymove /= 2;
}
@ -1223,7 +1223,7 @@ static void P_HitSlideLine(line_t *ld)
if (icyfloor && deltaangle > ANG45 && deltaangle < ANG90+ANG45)
{
S_StartSound(slidemo,sfx_oof); // oooff!
S_StartSoundPreset(slidemo, sfx_oof, PITCH_FULL); // oooff!
moveangle = lineangle - deltaangle;
movelen /= 2; // absorb
moveangle >>= ANGLETOFINESHIFT;

View File

@ -145,8 +145,16 @@ void P_ExplodeMissile (mobj_t* mo)
mo->flags &= ~MF_MISSILE;
if (mo->info->deathsound)
S_StartSoundPitch(mo, mo->info->deathsound,
brainexplode ? PITCH_NONE : PITCH_FULL);
{
if (brainexplode)
{
S_StartSoundPitch(mo, mo->info->deathsound, PITCH_NONE);
}
else
{
S_StartSoundOrigin(mo->target, mo, mo->info->deathsound);
}
}
}
//
@ -1533,7 +1541,7 @@ mobj_t* P_SpawnPlayerMissile(mobj_t* source,mobjtype_t type)
th = P_SpawnMobj (x,y,z, type);
if (th->info->seesound)
S_StartSound (th, th->info->seesound);
S_StartSoundMissile(source, th, th->info->seesound);
P_SetTarget(&th->target, source); // killough 11/98
th->angle = an;

View File

@ -155,7 +155,7 @@ static void P_BringUpWeapon(player_t *player)
player->pendingweapon = player->readyweapon;
if (player->pendingweapon == wp_chainsaw)
S_StartSoundPitch(player->mo, sfx_sawup, PITCH_HALF);
S_StartSoundPitchEx(player->mo, sfx_sawup, PITCH_HALF);
if (player->pendingweapon >= NUMWEAPONS)
{
@ -695,7 +695,7 @@ void A_Punch(player_t *player, pspdef_t *psp)
if (!linetarget)
return;
S_StartSound(player->mo, sfx_punch);
S_StartSoundEx(player->mo, sfx_punch);
// turn to face target
@ -735,11 +735,11 @@ void A_Saw(player_t *player, pspdef_t *psp)
if (!linetarget)
{
S_StartSoundPitch(player->mo, sfx_sawful, PITCH_HALF);
S_StartSoundPitchEx(player->mo, sfx_sawful, PITCH_HALF);
return;
}
S_StartSoundPitch(player->mo, sfx_sawhit, PITCH_HALF);
S_StartSoundPitchEx(player->mo, sfx_sawhit, PITCH_HALF);
// turn to face target
angle = R_PointToAngle2(player->mo->x, player->mo->y,
@ -929,7 +929,7 @@ void P_GunShot(mobj_t *mo, boolean accurate)
void A_FirePistol(player_t *player, pspdef_t *psp)
{
S_StartSound(player->mo, sfx_pistol);
S_StartSoundPistol(player->mo, sfx_pistol);
P_SetMobjState(player->mo, S_PLAY_ATK2);
P_SubtractAmmo(player, 1);
@ -948,7 +948,7 @@ void A_FireShotgun(player_t *player, pspdef_t *psp)
{
int i;
S_StartSound(player->mo, sfx_shotgn);
S_StartSoundShotgun(player->mo, sfx_shotgn);
P_SetMobjState(player->mo, S_PLAY_ATK2);
P_SubtractAmmo(player, 1);
@ -970,7 +970,7 @@ void A_FireShotgun2(player_t *player, pspdef_t *psp)
{
int i;
S_StartSound(player->mo, sfx_dshtgn);
S_StartSoundSSG(player->mo, sfx_dshtgn);
P_SetMobjState(player->mo, S_PLAY_ATK2);
P_SubtractAmmo(player, 2);
@ -998,7 +998,7 @@ void A_FireShotgun2(player_t *player, pspdef_t *psp)
void A_FireCGun(player_t *player, pspdef_t *psp)
{
S_StartSound(player->mo, sfx_pistol);
S_StartSoundCGun(player->mo, sfx_pistol);
if (!player->ammo[weaponinfo[player->readyweapon].ammo])
return;
@ -1079,7 +1079,7 @@ void A_BFGSpray(mobj_t *mo)
void A_BFGsound(player_t *player, pspdef_t *psp)
{
S_StartSound(player->mo, sfx_bfg);
S_StartSoundBFG(player->mo, sfx_bfg);
}
//
@ -1317,7 +1317,7 @@ void A_WeaponMeleeAttack(player_t *player, pspdef_t *psp)
return;
// un-missed!
S_StartSound(player->mo, hitsound);
S_StartSoundEx(player->mo, hitsound);
// turn to face target
SavePlayerAngle(player);
@ -1336,7 +1336,8 @@ void A_WeaponSound(player_t *player, pspdef_t *psp)
if (!mbf21 || !psp->state)
return;
S_StartSound(psp->state->args[1] ? NULL : player->mo, psp->state->args[0]);
S_StartSoundOrigin(player->mo, (psp->state->args[1] ? NULL : player->mo),
psp->state->args[0]);
}
//

View File

@ -235,7 +235,7 @@ void P_HitFloor (mobj_t *mo, int oof)
{sfx_lavsml, sfx_lvsiz} // terrain_lava
};
S_StartSound(mo, hitsound[terrain][oof]);
S_StartSoundHitFloor(mo, hitsound[terrain][oof]);
}
///////////////////////////////////////////////////////////////

View File

@ -92,12 +92,10 @@ int EV_Teleport(line_t *line, int side, mobj_t *thing)
S_StartSound(P_SpawnMobj(oldx, oldy, oldz, MT_TFOG), sfx_telept);
// spawn teleport fog and emit sound at destination
S_StartSound(P_SpawnMobj(m->x +
20*finecosine[m->angle>>ANGLETOFINESHIFT],
m->y +
20*finesine[m->angle>>ANGLETOFINESHIFT],
thing->z, MT_TFOG),
sfx_telept);
fixed_t fog_x = m->x + 20 * finecosine[m->angle >> ANGLETOFINESHIFT];
fixed_t fog_y = m->y + 20 * finesine[m->angle >> ANGLETOFINESHIFT];
mobj_t *fog_mo = P_SpawnMobj(fog_x, fog_y, thing->z, MT_TFOG);
S_StartSoundSource(thing, fog_mo, sfx_telept);
if (thing->player) // don't move for a bit // killough 10/98
// killough 10/98: beta teleporters were a bit faster

View File

@ -25,6 +25,7 @@
#include "doomdef.h"
#include "doomstat.h"
#include "i_printf.h"
#include "i_rumble.h"
#include "i_sound.h"
#include "i_system.h"
#include "m_misc.h"
@ -197,7 +198,8 @@ static int S_getChannel(const mobj_t *origin, sfxinfo_t *sfxinfo, int priority,
return cnum;
}
void S_StartSoundPitch(const mobj_t *origin, int sfx_id, const pitchrange_t pitch_range)
static void StartSound(const mobj_t *origin, int sfx_id,
pitchrange_t pitch_range, rumble_type_t rumble_type)
{
int sep, pitch, o_priority, priority, singularity, cnum, handle;
int volumeScale = 127;
@ -302,6 +304,12 @@ void S_StartSoundPitch(const mobj_t *origin, int sfx_id, const pitchrange_t pitc
channels[cnum].priority = priority; // scaled priority
channels[cnum].singularity = singularity;
channels[cnum].idnum = I_SoundID(handle); // unique instance id
if (rumble_type != RUMBLE_NONE)
{
I_StartRumble(players[displayplayer].mo, origin, sfx, handle,
rumble_type);
}
}
else // haleyjd: the sound didn't start, so clear the channel info
{
@ -309,6 +317,128 @@ void S_StartSoundPitch(const mobj_t *origin, int sfx_id, const pitchrange_t pitc
}
}
void S_StartSoundPitch(const mobj_t *origin, int sfx_id,
pitchrange_t pitch_range)
{
StartSound(origin, sfx_id, pitch_range, RUMBLE_NONE);
}
static boolean IsRumblePlayer(const mobj_t *mo)
{
return (I_RumbleEnabled() && mo && mo == players[displayplayer].mo);
}
static rumble_type_t RumbleType(const mobj_t *mo, rumble_type_t rumble_type)
{
return (IsRumblePlayer(mo) ? rumble_type : RUMBLE_NONE);
}
void S_StartSoundPitchEx(const mobj_t *origin, int sfx_id,
pitchrange_t pitch_range)
{
StartSound(origin, sfx_id, pitch_range, RumbleType(origin, RUMBLE_PLAYER));
}
void S_StartSoundPistol(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_PISTOL));
}
void S_StartSoundShotgun(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_SHOTGUN));
}
void S_StartSoundSSG(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_SSG));
}
void S_StartSoundCGun(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_CGUN));
}
void S_StartSoundBFG(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_BFG));
}
static rumble_type_t RumbleTypePreset(const mobj_t *origin, int sfx_id)
{
if (IsRumblePlayer(origin))
{
switch (sfx_id)
{
case sfx_itemup:
return RUMBLE_ITEMUP;
case sfx_wpnup:
return RUMBLE_WPNUP;
case sfx_getpow:
return RUMBLE_GETPOW;
case sfx_oof:
return RUMBLE_OOF;
}
}
return RUMBLE_NONE;
}
void S_StartSoundPreset(const mobj_t *origin, int sfx_id,
pitchrange_t pitch_range)
{
StartSound(origin, sfx_id, pitch_range, RumbleTypePreset(origin, sfx_id));
}
void S_StartSoundPain(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_PAIN));
}
void S_StartSoundHitFloor(const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(origin, RUMBLE_HITFLOOR));
}
void S_StartSoundSource(const mobj_t *source, const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(source, RUMBLE_PLAYER));
}
static rumble_type_t RumbleTypeMissile(const mobj_t *source,
const mobj_t *origin)
{
if (IsRumblePlayer(source))
{
if (origin)
{
switch (origin->type)
{
case MT_ROCKET:
return RUMBLE_ROCKET;
case MT_PLASMA:
case MT_PLASMA1:
case MT_PLASMA2:
return RUMBLE_PLASMA;
default:
break;
}
}
return RUMBLE_PLAYER;
}
return RUMBLE_NONE;
}
void S_StartSoundMissile(const mobj_t *source, const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleTypeMissile(source, origin));
}
void S_StartSoundOrigin(const mobj_t *source, const mobj_t *origin, int sfx_id)
{
StartSound(origin, sfx_id, PITCH_FULL, RumbleType(source, RUMBLE_ORIGIN));
}
//
// S_StopSound
//
@ -451,6 +581,8 @@ void S_UpdateSounds(const mobj_t *listener)
c->priority = pri; // haleyjd
}
}
I_UpdateRumbleParams(listener, c->origin, c->handle);
}
else // if channel is allocated but sound has stopped, free it
{
@ -461,6 +593,7 @@ void S_UpdateSounds(const mobj_t *listener)
I_UpdateListenerParams(listener);
I_ProcessSoundUpdates();
I_UpdateRumble();
}
void S_SetMusicVolume(int volume)

View File

@ -48,8 +48,39 @@ void S_Start(void);
// Start sound for thing at <origin>
// using <sound_id> from sounds.h
//
#define S_StartSound(o,i) S_StartSoundPitch((o),(i),PITCH_FULL)
void S_StartSoundPitch(const struct mobj_s *origin, int sound_id, const pitchrange_t pitch_range);
// Thing at <origin> emits sound. No rumble.
#define S_StartSound(o, i) S_StartSoundPitch((o), (i), PITCH_FULL)
void S_StartSoundPitch(const struct mobj_s *origin, int sfx_id,
pitchrange_t pitch_range);
// Thing at <origin> emits sound. Rumbles if displayplayer is <origin>.
#define S_StartSoundEx(o, i) S_StartSoundPitchEx((o), (i), PITCH_FULL)
void S_StartSoundPitchEx(const struct mobj_s *origin, int sfx_id,
pitchrange_t pitch_range);
void S_StartSoundPistol(const struct mobj_s *origin, int sfx_id);
void S_StartSoundShotgun(const struct mobj_s *origin, int sfx_id);
void S_StartSoundSSG(const struct mobj_s *origin, int sfx_id);
void S_StartSoundCGun(const struct mobj_s *origin, int sfx_id);
void S_StartSoundBFG(const struct mobj_s *origin, int sfx_id);
// Thing at <origin> emits sound. Rumbles preset if displayplayer is <origin>.
void S_StartSoundPreset(const struct mobj_s *origin, int sfx_id,
pitchrange_t pitch_range);
void S_StartSoundPain(const struct mobj_s *origin, int sfx_id);
void S_StartSoundHitFloor(const struct mobj_s *origin, int sfx_id);
// Thing at <source> causes sound. Thing at <origin> emits sound.
// Rumbles if displayplayer is <source>.
void S_StartSoundSource(const struct mobj_s *source,
const struct mobj_s *origin, int sfx_id);
void S_StartSoundMissile(const struct mobj_s *source,
const struct mobj_s *origin, int sfx_id);
// Thing at <source> causes sound. Thing at <origin> emits sound.
// Rumbles if displayplayer is <source>, based on distance to <origin>.
void S_StartSoundOrigin(const struct mobj_s *source,
const struct mobj_s *origin, int sfx_id);
// Stop sound for thing at <origin>
void S_StopSound(const struct mobj_s *origin);

View File

@ -27,6 +27,13 @@
// SoundFX struct.
//
typedef struct sfxrumble_s
{
float *low; // Pointer to array of low frequency rumble values.
float *high; // Pointer to array of high frequency rumble values.
int ticlength; // Array size equal to sound duration in tics.
} sfxrumble_t;
typedef struct sfxinfo_s
{
// up to 6-character name
@ -58,6 +65,8 @@ typedef struct sfxinfo_s
boolean cached;
sfxrumble_t rumble;
} sfxinfo_t;
//

View File

@ -5,6 +5,11 @@ target_woof_settings(miniz)
target_compile_definitions(miniz PRIVATE MINIZ_NO_TIME)
target_include_directories(miniz INTERFACE miniz)
add_library(pffft STATIC pffft/pffft.c)
target_woof_settings(pffft)
target_include_directories(pffft INTERFACE pffft)
target_link_libraries(pffft)
add_library(spng STATIC spng/spng.c)
target_woof_settings(spng)
target_compile_definitions(spng PRIVATE SPNG_USE_MINIZ INTERFACE SPNG_STATIC)

45
third-party/pffft/LICENSE vendored Normal file
View File

@ -0,0 +1,45 @@
Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
Based on original fortran 77 code from FFTPACKv4 from NETLIB,
authored by Dr Paul Swarztrauber of NCAR, in 1985.
As confirmed by the NCAR fftpack software curators, the following
FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
released under the same terms.
FFTPACK license:
http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
Copyright (c) 2004 the University Corporation for Atmospheric
Research ("UCAR"). All rights reserved. Developed by NCAR's
Computational and Information Systems Laboratory, UCAR,
www.cisl.ucar.edu.
Redistribution and use of the Software in source and binary forms,
with or without modification, is permitted provided that the
following conditions are met:
- Neither the names of NCAR's Computational and Information Systems
Laboratory, the University Corporation for Atmospheric Research,
nor the names of its sponsors or contributors may be used to
endorse or promote products derived from this Software without
specific prior written permission.
- Redistributions of source code must retain the above copyright
notices, this list of conditions, and the disclaimer below.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer below in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.

1877
third-party/pffft/pffft.c vendored Normal file

File diff suppressed because it is too large Load Diff

177
third-party/pffft/pffft.h vendored Normal file
View File

@ -0,0 +1,177 @@
/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
Based on original fortran 77 code from FFTPACKv4 from NETLIB,
authored by Dr Paul Swarztrauber of NCAR, in 1985.
As confirmed by the NCAR fftpack software curators, the following
FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
released under the same terms.
FFTPACK license:
http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
Copyright (c) 2004 the University Corporation for Atmospheric
Research ("UCAR"). All rights reserved. Developed by NCAR's
Computational and Information Systems Laboratory, UCAR,
www.cisl.ucar.edu.
Redistribution and use of the Software in source and binary forms,
with or without modification, is permitted provided that the
following conditions are met:
- Neither the names of NCAR's Computational and Information Systems
Laboratory, the University Corporation for Atmospheric Research,
nor the names of its sponsors or contributors may be used to
endorse or promote products derived from this Software without
specific prior written permission.
- Redistributions of source code must retain the above copyright
notices, this list of conditions, and the disclaimer below.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer below in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
*/
/*
PFFFT : a Pretty Fast FFT.
This is basically an adaptation of the single precision fftpack
(v4) as found on netlib taking advantage of SIMD instruction found
on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON).
For architectures where no SIMD instruction is available, the code
falls back to a scalar version.
Restrictions:
- 1D transforms only, with 32-bit single precision.
- supports only transforms for inputs of length N of the form
N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128,
144, 160, etc are all acceptable lengths). Performance is best for
128<=N<=8192.
- all (float*) pointers in the functions below are expected to
have an "simd-compatible" alignment, that is 16 bytes on x86 and
powerpc CPUs.
You can allocate such buffers with the functions
pffft_aligned_malloc / pffft_aligned_free (or with stuff like
posix_memalign..)
*/
#ifndef PFFFT_H
#define PFFFT_H
#include <stddef.h> // for size_t
#ifdef __cplusplus
extern "C" {
#endif
/* opaque struct holding internal stuff (precomputed twiddle factors)
this struct can be shared by many threads as it contains only
read-only data.
*/
typedef struct PFFFT_Setup PFFFT_Setup;
/* direction of the transform */
typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t;
/* type of transform */
typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t;
/*
prepare for performing transforms of size N -- the returned
PFFFT_Setup structure is read-only so it can safely be shared by
multiple concurrent threads.
*/
PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform);
void pffft_destroy_setup(PFFFT_Setup *);
/*
Perform a Fourier transform , The z-domain data is stored in the
most efficient order for transforming it back, or using it for
convolution. If you need to have its content sorted in the
"usual" way, that is as an array of interleaved complex numbers,
either use pffft_transform_ordered , or call pffft_zreorder after
the forward fft, and before the backward fft.
Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x.
Typically you will want to scale the backward transform by 1/N.
The 'work' pointer should point to an area of N (2*N for complex
fft) floats, properly aligned. If 'work' is NULL, then stack will
be used instead (this is probably the best strategy for small
FFTs, say for N < 16384).
input and output may alias.
*/
void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
/*
Similar to pffft_transform, but makes sure that the output is
ordered as expected (interleaved complex numbers). This is
similar to calling pffft_transform and then pffft_zreorder.
input and output may alias.
*/
void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
/*
call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(...,
PFFFT_FORWARD) if you want to have the frequency components in
the correct "canonical" order, as interleaved complex numbers.
(for real transforms, both 0-frequency and half frequency
components, which are real, are assembled in the first entry as
F(0)+i*F(n/2+1). Note that the original fftpack did place
F(n/2+1) at the end of the arrays).
input and output should not alias.
*/
void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
/*
Perform a multiplication of the frequency components of dft_a and
dft_b and accumulate them into dft_ab. The arrays should have
been obtained with pffft_transform(.., PFFFT_FORWARD) and should
*not* have been reordered with pffft_zreorder (otherwise just
perform the operation yourself as the dft coefs are stored as
interleaved complex numbers).
the operation performed is: dft_ab += (dft_a * fdt_b)*scaling
The dft_a, dft_b and dft_ab pointers may alias.
*/
void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
/*
the float buffers must have the correct alignment (16-byte boundary
on intel and powerpc). This function may be used to obtain such
correctly aligned buffers.
*/
void *pffft_aligned_malloc(size_t nb_bytes);
void pffft_aligned_free(void *);
/* return 4 or 1 wether support SSE/Altivec instructions was enable when building pffft.c */
int pffft_simd_size(void);
#ifdef __cplusplus
}
#endif
#endif // PFFFT_H