mirror of
https://github.com/fabiangreffrath/woof.git
synced 2025-09-21 02:43:26 -04:00
Add gamepad rumble support (#1876)
This commit is contained in:
parent
10c64223e9
commit
3ee1bb572c
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
799
src/i_rumble.c
Normal 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, &_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
78
src/i_rumble.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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;
|
||||
|
14
src/p_mobj.c
14
src/p_mobj.c
@ -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;
|
||||
|
23
src/p_pspr.c
23
src/p_pspr.c
@ -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]);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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]);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
@ -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
|
||||
|
135
src/s_sound.c
135
src/s_sound.c
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
//
|
||||
|
5
third-party/CMakeLists.txt
vendored
5
third-party/CMakeLists.txt
vendored
@ -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
45
third-party/pffft/LICENSE
vendored
Normal 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
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
177
third-party/pffft/pffft.h
vendored
Normal 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
|
Loading…
x
Reference in New Issue
Block a user