mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
Add netgame voice chat
Implemented using libopus for the Opus codec, same as is used in Discord. This adds the following cvars: - `voice_chat` On/Off, triggers self-deafen state on server via weaponprefs - `voice_mode` Activity/PTT - `voice_selfmute` On/Off, triggers self-mute state on server via weaponprefs - `voice_inputamp` -30 to 30, scales input by value in decibels - `voice_activationthreshold` -30 to 0, if any peak in a frame is higher, activates voice - `voice_loopback` On/Off, plays back local transcoded voice - `voice_proximity` On/Off, enables proximity effects for server - `voice_distanceattenuation_distance` distance in fracunits to scale voice volume over - `voice_distanceattenuation_factor` distance in logarithmic factor to scale voice volume by distance to. e.g. 0.5 for "half as loud" at or above max distance - `voice_stereopanning_factor` at 1.0, player voices are panned to left or right speaker, scaling to no effect at 0.0 - `voice_concurrentattenuation_factor` the logarithmic factor to attenuate player voices with concurrent speakers - `voice_concurrentattenuation_min` the minimum concurrent speakers before global concurrent speaker attenuation - `voice_concurrentattenuation_max` the maximum concurrent speakers for full global concurrent speaker attenuation - `voice_servermute` whether voice chat is enabled on this server. visible from MS via bitflag - `voicevolume` local volume of all voice playback A Voice Options menu is added with a subset of these options, and Server Options has server mute.
This commit is contained in:
parent
6ffdeb6c44
commit
22b20b5877
40 changed files with 15772 additions and 14142 deletions
|
|
@ -100,6 +100,7 @@ find_package(ZLIB REQUIRED)
|
|||
find_package(PNG REQUIRED)
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
find_package(Opus REQUIRED)
|
||||
# Use the one in thirdparty/fmt to guarantee a minimum version
|
||||
#find_package(FMT CONFIG REQUIRED)
|
||||
|
||||
|
|
|
|||
7
cmake/Modules/FindOpus.cmake
Normal file
7
cmake/Modules/FindOpus.cmake
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
find_package(Opus CONFIG)
|
||||
if(NOT TARGET Opus::opus)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus)
|
||||
set_target_properties(PkgConfig::Opus PROPERTIES IMPORTED_GLOBAL TRUE)
|
||||
add_library(Opus::opus ALIAS PkgConfig::Opus)
|
||||
endif()
|
||||
|
|
@ -258,6 +258,7 @@ endif()
|
|||
target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE PNG::PNG)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE CURL::libcurl)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE Opus::opus)
|
||||
if("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD")
|
||||
target_link_libraries(SRB2SDL2 PRIVATE -lexecinfo)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE -lpthread)
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ consvar_t cv_controlperkey = Player("controlperkey", "One").values({{1, "One"},
|
|||
consvar_t cv_mastervolume = Player("volume", "80").min_max(0, 100);
|
||||
consvar_t cv_digmusicvolume = Player("musicvolume", "80").min_max(0, 100);
|
||||
consvar_t cv_soundvolume = Player("soundvolume", "80").min_max(0, 100);
|
||||
consvar_t cv_voicevolume = Player("voicevolume", "100").min_max(0, 100);
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
void DRPC_UpdatePresence(void);
|
||||
|
|
@ -1351,8 +1352,71 @@ consvar_t cv_chatwidth = Player("chatwidth", "150").min_max(64, 150);
|
|||
// old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.)
|
||||
consvar_t cv_consolechat = Player("chatmode", "Yes").values({{0, "Yes"}, {2, "No"}});
|
||||
|
||||
// When off, inbound voice packets are ignored
|
||||
void VoiceChat_OnChange(void);
|
||||
consvar_t cv_voice_chat = Player("voice_chat", "Off")
|
||||
.on_off()
|
||||
.onchange(VoiceChat_OnChange)
|
||||
.description("Whether voice chat is played or not. Shown as self-deafen to others.");
|
||||
|
||||
// When on, local player won't transmit voice
|
||||
consvar_t cv_voice_mode = Player("voice_mode", "Activity")
|
||||
.values({{0, "Activity"}, {1, "PTT"}})
|
||||
.description("How to activate voice transmission");
|
||||
|
||||
consvar_t cv_voice_selfmute = Player("voice_selfmute", "Off")
|
||||
.on_off()
|
||||
.onchange(weaponPrefChange)
|
||||
.description("Whether the local microphone is muted. Shown as self-mute to others.");
|
||||
|
||||
consvar_t cv_voice_inputamp = Player("voice_inputamp", "14")
|
||||
.min_max(-30, 30)
|
||||
.description("How much louder or quieter to make voice input, in decibels.");
|
||||
|
||||
consvar_t cv_voice_activationthreshold = Player("voice_activationthreshold", "-20")
|
||||
.min_max(-30, 0)
|
||||
.description("The voice activation threshold, in decibels from maximum amplitude.");
|
||||
|
||||
// When on, local voice is played back out
|
||||
consvar_t cv_voice_loopback = Player("voice_loopback", "Off")
|
||||
.on_off()
|
||||
.dont_save()
|
||||
.description("When on, plays the local player's voice");
|
||||
|
||||
consvar_t cv_voice_proximity = NetVar("voice_proximity", "On")
|
||||
.on_off()
|
||||
.description("Whether proximity effects for voice chat are enabled on the server.");
|
||||
|
||||
// The relative distance for maximum voice attenuation
|
||||
consvar_t cv_voice_distanceattenuation_distance = NetVar("voice_distanceattenuation_distance", "4096")
|
||||
.floating_point()
|
||||
.description("Voice speaker's distance from listener at which positional voice is fully attenuated");
|
||||
|
||||
// The volume factor (scaled logarithmically, i.e. 0.5 = "half as loud") for voice distance attenuation
|
||||
consvar_t cv_voice_distanceattenuation_factor = NetVar("voice_distanceattenuation_factor", "0.2")
|
||||
.floating_point()
|
||||
.description("Maximum attenuation, in perceived loudness, when a voice speaker is at voice_distanceattenuation_distance units or further from the listener");
|
||||
|
||||
// The scale factor applied to stereo separation for voice panning
|
||||
consvar_t cv_voice_stereopanning_factor = NetVar("voice_stereopanning_factor", "1.0")
|
||||
.floating_point()
|
||||
.description("Scale of stereo panning applied to a voice speaker relative to their in-game position, from 0.0-1.0");
|
||||
|
||||
consvar_t cv_voice_concurrentattenuation_factor = NetVar("voice_concurrentattenuation_factor", "0.6")
|
||||
.floating_point()
|
||||
.description("The maximum attenuation factor, in perceived loudness, when at or exceeding voice_concurrentattenuation_max speakers");
|
||||
consvar_t cv_voice_concurrentattenuation_min = NetVar("voice_concurrentattenuation_min", "3")
|
||||
.description("Minimum concurrent speakers before global attenuation starts");
|
||||
consvar_t cv_voice_concurrentattenuation_max = NetVar("voice_concurrentattenuation_max", "8")
|
||||
.description("Maximum concurrent speakers at which full global attenuation is applied");
|
||||
|
||||
void Mute_OnChange(void);
|
||||
void VoiceMute_OnChange(void);
|
||||
consvar_t cv_mute = UnsavedNetVar("mute", "Off").on_off().onchange(Mute_OnChange);
|
||||
consvar_t cv_voice_servermute = NetVar("voice_servermute", "On")
|
||||
.on_off()
|
||||
.onchange(VoiceMute_OnChange)
|
||||
.description("If On, the server will not broadcast voice chat to clients");
|
||||
|
||||
|
||||
//
|
||||
|
|
|
|||
14796
src/d_clisrv.c
14796
src/d_clisrv.c
File diff suppressed because it is too large
Load diff
|
|
@ -137,6 +137,8 @@ typedef enum
|
|||
|
||||
PT_REQMAPQUEUE, // Client requesting a roundqueue operation
|
||||
|
||||
PT_VOICE, // Voice packet for either side
|
||||
|
||||
NUMPACKETTYPE
|
||||
} packettype_t;
|
||||
|
||||
|
|
@ -283,6 +285,7 @@ struct clientconfig_pak
|
|||
|
||||
#define SV_SPEEDMASK 0x03 // used to send kartspeed
|
||||
#define SV_DEDICATED 0x40 // server is dedicated
|
||||
#define SV_VOICEENABLED 0x80 // voice_mute is off/voice chat is enabled
|
||||
#define SV_LOTSOFADDONS 0x20 // flag used to ask for full file list in d_netfil
|
||||
|
||||
#define MAXFILENEEDED 915
|
||||
|
|
@ -418,6 +421,22 @@ struct netinfo_pak
|
|||
UINT32 delay[MAXPLAYERS+1];
|
||||
} ATTRPACK;
|
||||
|
||||
// Sent by both sides. Contains Opus-encoded voice packet
|
||||
// flags bitset map (left to right, low to high)
|
||||
// | PPPPPTRR | -- P = Player num, T = Terminal, R = Reserved (0)
|
||||
// Data following voice header is a single Opus frame
|
||||
struct voice_pak
|
||||
{
|
||||
UINT64 frame;
|
||||
UINT8 flags;
|
||||
} ATTRPACK;
|
||||
|
||||
#define VOICE_PAK_FLAGS_PLAYERNUM_BITS 0x1F
|
||||
#define VOICE_PAK_FLAGS_TERMINAL_BIT 0x20
|
||||
#define VOICE_PAK_FLAGS_RESERVED0_BIT 0x40
|
||||
#define VOICE_PAK_FLAGS_RESERVED1_BIT 0x80
|
||||
#define VOICE_PAK_FLAGS_RESERVED_BITS (VOICE_PAK_FLAGS_RESERVED0_BIT | VOICE_PAK_FLAGS_RESERVED1_BIT)
|
||||
|
||||
//
|
||||
// Network packet data
|
||||
//
|
||||
|
|
@ -462,6 +481,7 @@ struct doomdata_t
|
|||
resultsall_pak resultsall; // 1024 bytes. Also, you really shouldn't trust anything here.
|
||||
say_pak say; // I don't care anymore.
|
||||
reqmapqueue_pak reqmapqueue; // Formerly XD_REQMAPQUEUE
|
||||
voice_pak voice; // Unreliable voice data, variable length
|
||||
} u; // This is needed to pack diff packet types data together
|
||||
} ATTRPACK;
|
||||
|
||||
|
|
@ -606,6 +626,7 @@ void SendKick(UINT8 playernum, UINT8 msg);
|
|||
// Create any new ticcmds and broadcast to other players.
|
||||
void NetKeepAlive(void);
|
||||
void NetUpdate(void);
|
||||
void NetVoiceUpdate(void);
|
||||
|
||||
void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame);
|
||||
boolean SV_SpawnServer(void);
|
||||
|
|
@ -710,6 +731,7 @@ void HandleSigfail(const char *string);
|
|||
|
||||
void DoSayPacket(SINT8 target, UINT8 flags, UINT8 source, char *message);
|
||||
void DoSayPacketFromCommand(SINT8 target, size_t usedargs, UINT8 flags);
|
||||
void DoVoicePacket(SINT8 target, UINT64 frame, const UINT8* opusdata, size_t len);
|
||||
void SendServerNotice(SINT8 target, char *message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ INT32 postimgparam[MAXSPLITSCREENPLAYERS];
|
|||
|
||||
boolean sound_disabled = false;
|
||||
boolean digital_disabled = false;
|
||||
boolean g_voice_disabled = false;
|
||||
|
||||
#ifdef DEBUGFILE
|
||||
INT32 debugload = 0;
|
||||
|
|
@ -1079,6 +1080,7 @@ void D_SRB2Loop(void)
|
|||
|
||||
// consoleplayer -> displayplayers (hear sounds from viewpoint)
|
||||
S_UpdateSounds(); // move positional sounds
|
||||
NetVoiceUpdate(); // update voice recording whenever possible
|
||||
if (realtics > 0 || singletics)
|
||||
{
|
||||
S_UpdateClosedCaptions();
|
||||
|
|
@ -1095,6 +1097,7 @@ void D_SRB2Loop(void)
|
|||
#endif
|
||||
|
||||
Music_Tick();
|
||||
S_UpdateVoicePositionalProperties();
|
||||
|
||||
// Fully completed frame made.
|
||||
finishprecise = I_GetPreciseTime();
|
||||
|
|
@ -1885,12 +1888,14 @@ void D_SRB2Main(void)
|
|||
{
|
||||
sound_disabled = true;
|
||||
digital_disabled = true;
|
||||
g_voice_disabled = true;
|
||||
}
|
||||
|
||||
if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
|
||||
{
|
||||
sound_disabled = true;
|
||||
digital_disabled = true;
|
||||
g_voice_disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1905,9 +1910,13 @@ void D_SRB2Main(void)
|
|||
if (M_CheckParm("-nodigmusic"))
|
||||
digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
|
||||
}
|
||||
if (M_CheckParm("-novoice"))
|
||||
{
|
||||
g_voice_disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!( sound_disabled && digital_disabled ))
|
||||
if (!( sound_disabled && digital_disabled && g_voice_disabled ))
|
||||
{
|
||||
CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
|
||||
I_StartupSound();
|
||||
|
|
|
|||
|
|
@ -1175,6 +1175,8 @@ enum {
|
|||
WP_AUTOROULETTE = 1<<2,
|
||||
WP_ANALOGSTICK = 1<<3,
|
||||
WP_AUTORING = 1<<4,
|
||||
WP_SELFMUTE = 1<<5,
|
||||
WP_SELFDEAFEN = 1<<6
|
||||
};
|
||||
|
||||
void WeaponPref_Send(UINT8 ssplayer)
|
||||
|
|
@ -1196,6 +1198,15 @@ void WeaponPref_Send(UINT8 ssplayer)
|
|||
if (cv_autoring[ssplayer].value)
|
||||
prefs |= WP_AUTORING;
|
||||
|
||||
if (ssplayer == 0)
|
||||
{
|
||||
if (cv_voice_selfmute.value)
|
||||
prefs |= WP_SELFMUTE;
|
||||
|
||||
if (!cv_voice_chat.value)
|
||||
prefs |= WP_SELFDEAFEN;
|
||||
}
|
||||
|
||||
UINT8 buf[2];
|
||||
buf[0] = prefs;
|
||||
buf[1] = cv_mindelay.value;
|
||||
|
|
@ -1235,6 +1246,7 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum)
|
|||
UINT8 prefs = READUINT8(p);
|
||||
|
||||
player->pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME|PF_AUTOROULETTE|PF_AUTORING);
|
||||
player->pflags2 &= ~(PF2_SELFMUTE | PF2_SELFDEAFEN);
|
||||
|
||||
if (prefs & WP_KICKSTARTACCEL)
|
||||
player->pflags |= PF_KICKSTARTACCEL;
|
||||
|
|
@ -1253,6 +1265,12 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum)
|
|||
if (prefs & WP_AUTORING)
|
||||
player->pflags |= PF_AUTORING;
|
||||
|
||||
if (prefs & WP_SELFMUTE)
|
||||
player->pflags2 |= PF2_SELFMUTE;
|
||||
|
||||
if (prefs & WP_SELFDEAFEN)
|
||||
player->pflags2 |= PF2_SELFDEAFEN;
|
||||
|
||||
if (leveltime < 2)
|
||||
{
|
||||
// BAD HACK: No other place I tried to slot this in
|
||||
|
|
@ -7032,6 +7050,18 @@ void Mute_OnChange(void)
|
|||
HU_AddChatText(M_GetText("\x82*Chat is no longer muted."), false);
|
||||
}
|
||||
|
||||
void VoiceMute_OnChange(void);
|
||||
void VoiceMute_OnChange(void)
|
||||
{
|
||||
if (leveltime <= 1)
|
||||
return; // avoid having this notification put in our console / log when we boot the server.
|
||||
|
||||
if (cv_voice_servermute.value)
|
||||
HU_AddChatText(M_GetText("\x82*Voice chat has been muted."), false);
|
||||
else
|
||||
HU_AddChatText(M_GetText("\x82*Voice chat is no longer muted."), false);
|
||||
}
|
||||
|
||||
/** Hack to clear all changed flags after game start.
|
||||
* A lot of code (written by dummies, obviously) uses COM_BufAddText() to run
|
||||
* commands and change consvars, especially on game start. This is problematic
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ extern consvar_t cv_netstat;
|
|||
|
||||
extern consvar_t cv_countdowntime;
|
||||
extern consvar_t cv_mute;
|
||||
extern consvar_t cv_voice_servermute;
|
||||
extern consvar_t cv_pause;
|
||||
|
||||
extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers;
|
||||
|
|
@ -185,6 +186,8 @@ typedef enum
|
|||
XD_CALLZVOTE, // 39
|
||||
XD_SETZVOTE, // 40
|
||||
XD_TEAMCHANGE, // 41
|
||||
XD_SERVERMUTEPLAYER, // 42
|
||||
XD_SERVERDEAFENPLAYER, // 43
|
||||
|
||||
MAXNETXCMD
|
||||
} netxcmd_t;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,14 @@ typedef enum
|
|||
PF_NOFASTFALL = (INT32)(1U<<31), // Has already done ebrake/fastfall behavior for this input. Fastfalling needs a new input to prevent unwanted bounces on unexpected airtime.
|
||||
} pflags_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PF2_SELFMUTE = 1<<1,
|
||||
PF2_SELFDEAFEN = 1<<2,
|
||||
PF2_SERVERMUTE = 1<<3,
|
||||
PF2_SERVERDEAFEN = 1<<4,
|
||||
} pflags2_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
// Are animation frames playing?
|
||||
|
|
@ -634,6 +642,7 @@ struct player_t
|
|||
// Bit flags.
|
||||
// See pflags_t, above.
|
||||
UINT32 pflags;
|
||||
UINT32 pflags2;
|
||||
|
||||
// playing animation.
|
||||
panim_t panim;
|
||||
|
|
@ -1054,7 +1063,7 @@ struct player_t
|
|||
UINT8 amps;
|
||||
UINT8 amppickup;
|
||||
UINT8 ampspending;
|
||||
|
||||
|
||||
UINT16 overdrive;
|
||||
UINT16 overshield;
|
||||
fixed_t overdrivepower;
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ extern boolean forceresetplayers, deferencoremode, forcespecialstage;
|
|||
|
||||
extern boolean sound_disabled;
|
||||
extern boolean digital_disabled;
|
||||
extern boolean g_voice_disabled;
|
||||
|
||||
// =========================
|
||||
// Status flags for refresh.
|
||||
|
|
|
|||
|
|
@ -224,3 +224,30 @@ boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
|
|||
(void)looping;
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean I_SoundInputIsEnabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean I_SoundInputSetEnabled(boolean enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
||||
{
|
||||
}
|
||||
|
||||
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep)
|
||||
{
|
||||
}
|
||||
|
||||
void I_ResetVoiceQueue(INT32 playernum)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2201,6 +2201,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
|||
UINT32 followitem;
|
||||
|
||||
INT32 pflags;
|
||||
INT32 pflags2;
|
||||
|
||||
UINT8 team;
|
||||
|
||||
|
|
@ -2348,6 +2349,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
|||
xtralife = players[player].xtralife;
|
||||
|
||||
pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_ANALOGSTICK|PF_AUTORING));
|
||||
pflags2 = (players[player].pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN));
|
||||
|
||||
// SRB2kart
|
||||
memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette));
|
||||
|
|
@ -2539,6 +2541,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
|
|||
p->roundscore = roundscore;
|
||||
p->lives = lives;
|
||||
p->pflags = pflags;
|
||||
p->pflags2 = pflags2;
|
||||
p->team = team;
|
||||
p->jointime = jointime;
|
||||
p->splitscreenindex = splitscreenindex;
|
||||
|
|
@ -5916,7 +5919,7 @@ boolean G_SameTeam(const player_t *a, const player_t *b)
|
|||
}
|
||||
|
||||
// Free for all.
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT8 G_CountTeam(UINT8 team)
|
||||
|
|
|
|||
|
|
@ -910,6 +910,7 @@ static const char *gamecontrolname[num_gamecontrols] =
|
|||
"screenshot",
|
||||
"startmovie",
|
||||
"startlossless",
|
||||
"voicepushtotalk"
|
||||
};
|
||||
|
||||
#define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t))
|
||||
|
|
@ -1332,7 +1333,7 @@ INT32 G_FindPlayerBindForGameControl(INT32 player, gamecontrols_e control)
|
|||
}
|
||||
}
|
||||
|
||||
return (bestbind != -1) ? bestbind : anybind; // If we couldn't find a device-appropriate bind, try to at least use something
|
||||
return (bestbind != -1) ? bestbind : anybind; // If we couldn't find a device-appropriate bind, try to at least use something
|
||||
}
|
||||
|
||||
static void setcontrol(UINT8 player)
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ typedef enum
|
|||
gc_screenshot,
|
||||
gc_startmovie,
|
||||
gc_startlossless,
|
||||
gc_voicepushtotalk,
|
||||
|
||||
num_gamecontrols,
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ patch_t *frameslash; // framerate stuff. Used in screen.c
|
|||
static player_t *plr;
|
||||
boolean hu_keystrokes; // :)
|
||||
boolean chat_on; // entering a chat message?
|
||||
boolean g_voicepushtotalk_on; // holding PTT?
|
||||
static char w_chat[HU_MAXMSGLEN + 1];
|
||||
static size_t c_input = 0; // let's try to make the chat input less shitty.
|
||||
static boolean headsupactive = false;
|
||||
|
|
@ -1102,6 +1103,24 @@ void HU_clearChatChars(void)
|
|||
//
|
||||
boolean HU_Responder(event_t *ev)
|
||||
{
|
||||
// Handle Push-to-Talk
|
||||
if (ev->data1 == gamecontrol[0][gc_voicepushtotalk][0]
|
||||
|| ev->data1 == gamecontrol[0][gc_voicepushtotalk][1]
|
||||
|| ev->data1 == gamecontrol[0][gc_voicepushtotalk][2]
|
||||
|| ev->data1 == gamecontrol[0][gc_voicepushtotalk][3])
|
||||
{
|
||||
if (ev->type == ev_keydown)
|
||||
{
|
||||
g_voicepushtotalk_on = true;
|
||||
return true;
|
||||
}
|
||||
else if (ev->type == ev_keyup)
|
||||
{
|
||||
g_voicepushtotalk_on = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev->type != ev_keydown)
|
||||
return false;
|
||||
|
||||
|
|
@ -1912,25 +1931,25 @@ static void HU_DrawTitlecardCEcho(size_t num)
|
|||
{
|
||||
INT32 ofs;
|
||||
INT32 timer = (INT32)(elapsed - timeroffset);
|
||||
|
||||
|
||||
if (timer <= 0)
|
||||
return; // we don't care.
|
||||
|
||||
|
||||
line = strchr(echoptr, '\\');
|
||||
|
||||
|
||||
if (line == NULL)
|
||||
break;
|
||||
|
||||
*line = '\0';
|
||||
|
||||
|
||||
ofs = V_CenteredTitleCardStringOffset(echoptr, p4);
|
||||
V_DrawTitleCardString(x - ofs, y, echoptr, 0, false, timer, fadeout, p4);
|
||||
|
||||
y += p4 ? 18 : 32;
|
||||
|
||||
|
||||
// offset the timer for the next line.
|
||||
timeroffset += strlen(echoptr);
|
||||
|
||||
|
||||
// set the ptr to the \0 we made and advance it because we don't want an empty string.
|
||||
echoptr = line;
|
||||
echoptr++;
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ void HU_AddChatText(const char *text, boolean playsound);
|
|||
// set true when entering a chat message
|
||||
extern boolean chat_on;
|
||||
|
||||
// set true when push-to-talk is held
|
||||
extern boolean g_voicepushtotalk_on;
|
||||
|
||||
// keystrokes in the console or chat window
|
||||
extern boolean hu_keystrokes;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include "../k_hud.h"
|
||||
#include "../p_local.h"
|
||||
#include "../r_fps.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
extern "C" consvar_t cv_maxplayers;
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,8 @@ void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch);
|
|||
*/
|
||||
void I_SetSfxVolume(int volume);
|
||||
|
||||
void I_SetVoiceVolume(int volume);
|
||||
|
||||
/// ------------------------
|
||||
// MUSIC SYSTEM
|
||||
/// ------------------------
|
||||
|
|
@ -246,6 +248,22 @@ boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void));
|
|||
boolean I_FadeOutStopSong(UINT32 ms);
|
||||
boolean I_FadeInPlaySong(UINT32 ms, boolean looping);
|
||||
|
||||
// AUDIO INPUT (Microphones)
|
||||
boolean I_SoundInputIsEnabled(void);
|
||||
boolean I_SoundInputSetEnabled(boolean enabled);
|
||||
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len);
|
||||
|
||||
// VOICE CHAT
|
||||
|
||||
/// Queue a frame of samples of voice data from a player. Voice format is MONO F32 SYSTEM ENDIANNESS.
|
||||
/// If there is too much data being queued, old samples will be truncated
|
||||
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal);
|
||||
|
||||
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep);
|
||||
|
||||
/// Reset the voice queue for the given player. Use when server connection ends
|
||||
void I_ResetVoiceQueue(INT32 playernum);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
14088
src/k_hud.cpp
14088
src/k_hud.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -346,6 +346,7 @@ typedef enum
|
|||
mopt_profiles = 0,
|
||||
mopt_video,
|
||||
mopt_sound,
|
||||
mopt_voice,
|
||||
mopt_hud,
|
||||
mopt_gameplay,
|
||||
mopt_server,
|
||||
|
|
@ -468,6 +469,9 @@ extern menu_t OPTIONS_VideoAdvancedDef;
|
|||
extern menuitem_t OPTIONS_Sound[];
|
||||
extern menu_t OPTIONS_SoundDef;
|
||||
|
||||
extern menuitem_t OPTIONS_Voice[];
|
||||
extern menu_t OPTIONS_VoiceDef;
|
||||
|
||||
extern menuitem_t OPTIONS_HUD[];
|
||||
extern menu_t OPTIONS_HUDDef;
|
||||
|
||||
|
|
|
|||
|
|
@ -4178,6 +4178,8 @@ void M_DrawMPServerBrowser(void)
|
|||
servpats[i] = W_CachePatchName(va("M_SERV%c", i + '1'), PU_CACHE);
|
||||
gearpats[i] = W_CachePatchName(va("M_SGEAR%c", i + '1'), PU_CACHE);
|
||||
}
|
||||
patch_t *voicepat;
|
||||
voicepat = W_CachePatchName("VOCRMU", PU_CACHE);
|
||||
|
||||
fixed_t text1loop = SHORT(text1->height)*FRACUNIT;
|
||||
fixed_t text2loop = SHORT(text2->width)*FRACUNIT;
|
||||
|
|
@ -4279,6 +4281,12 @@ void M_DrawMPServerBrowser(void)
|
|||
V_DrawFixedPatch((startx + 251)*FRACUNIT, (starty + ypos + 9)*FRACUNIT, FRACUNIT, transflag, gearpats[speed], NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// voice chat enabled
|
||||
if (serverlist[i].info.kartvars & SV_VOICEENABLED)
|
||||
{
|
||||
V_DrawFixedPatch((startx - 3) * FRACUNIT, (starty + 2) * FRACUNIT, FRACUNIT, 0, voicepat, NULL);
|
||||
}
|
||||
}
|
||||
ypos += SERVERSPACE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,10 +310,12 @@ void PR_SaveProfiles(void)
|
|||
|
||||
for (size_t j = 0; j < num_gamecontrols; j++)
|
||||
{
|
||||
std::vector<int32_t> mappings;
|
||||
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
||||
{
|
||||
jsonprof.controls[j][k] = cprof->controls[j][k];
|
||||
mappings.push_back(cprof->controls[j][k]);
|
||||
}
|
||||
jsonprof.controls.emplace_back(std::move(mappings));
|
||||
}
|
||||
|
||||
ng.profiles.emplace_back(std::move(jsonprof));
|
||||
|
|
@ -498,9 +500,24 @@ void PR_LoadProfiles(void)
|
|||
{
|
||||
for (size_t j = 0; j < num_gamecontrols; j++)
|
||||
{
|
||||
if (jsprof.controls.size() <= j)
|
||||
{
|
||||
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
||||
{
|
||||
newprof->controls[j][k] = gamecontroldefault[j][k];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& mappings = jsprof.controls.at(j);
|
||||
for (size_t k = 0; k < MAXINPUTMAPPING; k++)
|
||||
{
|
||||
newprof->controls[j][k] = jsprof.controls.at(j).at(k);
|
||||
if (mappings.size() <= k)
|
||||
{
|
||||
newprof->controls[j][k] = 0;
|
||||
continue;
|
||||
}
|
||||
newprof->controls[j][k] = mappings.at(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ struct ProfileJson
|
|||
std::string followercolorname;
|
||||
ProfileRecordsJson records;
|
||||
ProfilePreferencesJson preferences;
|
||||
std::array<std::array<int32_t, MAXINPUTMAPPING>, gamecontrols_e::num_gamecontrols> controls = {{{{}}}};
|
||||
std::vector<std::vector<int32_t>> controls = {};
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
|
||||
ProfileJson,
|
||||
|
|
|
|||
17
src/k_vote.c
17
src/k_vote.c
|
|
@ -1015,6 +1015,23 @@ void Y_VoteDrawer(void)
|
|||
);
|
||||
}
|
||||
|
||||
// TODO better voice chat speaking indicator integration
|
||||
{
|
||||
char speakingstring[2048];
|
||||
memset(speakingstring, 0, sizeof(speakingstring));
|
||||
|
||||
for (int i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (S_IsPlayerVoiceActive(i))
|
||||
{
|
||||
strcat(speakingstring, player_names[i]);
|
||||
strcat(speakingstring, " ");
|
||||
}
|
||||
}
|
||||
|
||||
V_DrawThinString(0, 0, 0, speakingstring);
|
||||
}
|
||||
|
||||
M_DrawMenuForeground();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,25 @@ static void K_MidVoteKick(void)
|
|||
SendKick(g_midVote.victim - players, KICK_MSG_VOTE_KICK);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------
|
||||
static void K_MidVoteMute(void)
|
||||
|
||||
MVT_MUTE's success function.
|
||||
--------------------------------------------------*/
|
||||
static void K_MidVoteMute(void)
|
||||
{
|
||||
UINT8 buf[2];
|
||||
|
||||
if (g_midVote.victim == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
buf[0] = g_midVote.victim - players;
|
||||
buf[1] = 1;
|
||||
SendNetXCmd(XD_SERVERMUTEPLAYER, &buf, 2);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------
|
||||
static void K_MidVoteRockTheVote(void)
|
||||
|
||||
|
|
@ -99,6 +118,13 @@ static midVoteTypeDef_t g_midVoteTypeDefs[MVT__MAX] =
|
|||
K_MidVoteKick
|
||||
},
|
||||
|
||||
{ // MVT_MUTE
|
||||
"MUTE",
|
||||
"Mute Player?",
|
||||
CVAR_INIT ("zvote_mute_allowed", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL),
|
||||
K_MidVoteMute
|
||||
},
|
||||
|
||||
{ // MVT_RTV
|
||||
"RTV",
|
||||
"Skip Level?",
|
||||
|
|
@ -127,6 +153,10 @@ boolean K_MidVoteTypeUsesVictim(midVoteType_e voteType)
|
|||
{
|
||||
return true;
|
||||
}
|
||||
case MVT_MUTE:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
|
|
@ -1186,6 +1216,7 @@ void K_DrawMidVote(void)
|
|||
switch (g_midVote.type)
|
||||
{
|
||||
case MVT_KICK:
|
||||
case MVT_MUTE:
|
||||
{
|
||||
// Draw victim name
|
||||
if (g_midVote.victim != NULL)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ extern "C" {
|
|||
typedef enum
|
||||
{
|
||||
MVT_KICK, // Kick another player in the server
|
||||
MVT_MUTE, // Mute another player in the server (Voice Chat)
|
||||
MVT_RTV, // Exit level early
|
||||
MVT_RUNITBACK, // Restart level fresh
|
||||
MVT__MAX, // Total number of vote types
|
||||
|
|
|
|||
21
src/m_swap.h
21
src/m_swap.h
|
|
@ -35,18 +35,39 @@ extern "C" {
|
|||
| \
|
||||
(((UINT32)(x) & (UINT32)0xff000000UL) >> 24)))
|
||||
|
||||
#define SWAP_LONGLONG(x) ((INT64)(\
|
||||
(((UINT64)(x) & (UINT64)0x00000000000000ffULL) << 56) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0x000000000000ff00ULL) << 40) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0x0000000000ff0000ULL) << 24) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0x00000000ff000000ULL) << 8) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0x000000ff00000000ULL) >> 8) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0x0000ff0000000000ULL) >> 24) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0x00ff000000000000ULL) >> 40) \
|
||||
| \
|
||||
(((UINT64)(x) & (UINT64)0xff00000000000000ULL) >> 56)))
|
||||
|
||||
// Endianess handling.
|
||||
// WAD files are stored little endian.
|
||||
#ifdef SRB2_BIG_ENDIAN
|
||||
#define SHORT SWAP_SHORT
|
||||
#define LONG SWAP_LONG
|
||||
#define LONGLON SWAP_LONGLONG
|
||||
#define MSBF_SHORT(x) ((INT16)(x))
|
||||
#define MSBF_LONG(x) ((INT32)(x))
|
||||
#define MSBF_LONGLONG(x) ((INT64)(x))
|
||||
#else
|
||||
#define SHORT(x) ((INT16)(x))
|
||||
#define LONG(x) ((INT32)(x))
|
||||
#define LONGLONG(x) ((INT64)(x))
|
||||
#define MSBF_SHORT SWAP_SHORT
|
||||
#define MSBF_LONG SWAP_LONG
|
||||
#define MSBF_LONGLONG SWAP_LONGLONG
|
||||
#endif
|
||||
|
||||
// Big to little endian
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
options-video-1.c
|
||||
options-video-advanced.c
|
||||
options-video-modes.c
|
||||
options-voice.cpp
|
||||
play-1.c
|
||||
play-char-select.c
|
||||
play-local-1.c
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ menuitem_t OPTIONS_Main[] =
|
|||
{IT_STRING | IT_CALL, "Sound Options", "Adjust the volume.",
|
||||
NULL, {.routine = M_SoundOptions}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Voice Options", "Adjust voice chat.",
|
||||
NULL, {.submenu = &OPTIONS_VoiceDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "HUD Options", "Tweak the Heads-Up Display.",
|
||||
NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0},
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,9 @@ menuitem_t OPTIONS_ProfileControls[] = {
|
|||
{IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0},
|
||||
|
||||
{IT_CONTROL, "PUSH-TO-TALK", "Activates voice chat transmission in Push-to-Talk (PTT) mode.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_voicepushtotalk, 0},
|
||||
|
||||
{IT_CONTROL, "LUA/1", "May be used by add-ons.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_lua1, 0},
|
||||
|
||||
|
|
@ -310,14 +313,14 @@ boolean M_ProfileControlsInputs(INT32 ch)
|
|||
S_StartSound(NULL, sfx_kc69);
|
||||
if (newbuttons & MBT_R)
|
||||
S_StartSound(NULL, sfx_s3ka2);
|
||||
|
||||
|
||||
if (newbuttons & MBT_A)
|
||||
S_StartSound(NULL, sfx_kc3c);
|
||||
if (newbuttons & MBT_B)
|
||||
S_StartSound(NULL, sfx_3db09);
|
||||
if (newbuttons & MBT_C)
|
||||
S_StartSound(NULL, sfx_s1be);
|
||||
|
||||
|
||||
if (newbuttons & MBT_X)
|
||||
S_StartSound(NULL, sfx_s1a4);
|
||||
if (newbuttons & MBT_Y)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ menuitem_t OPTIONS_Server[] =
|
|||
|
||||
{IT_HEADER, "Players...", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
|
||||
{IT_STRING | IT_CVAR, "Maximum Players", "How many players can play at once.",
|
||||
NULL, {.cvar = &cv_maxplayers}, 0, 0},
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ menuitem_t OPTIONS_Server[] =
|
|||
NULL, {.cvar = &cv_kartbot}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Use PWR.LV", "Should players should be rated on their performance?",
|
||||
NULL, {.cvar = &cv_kartusepwrlv}, 0, 0},
|
||||
NULL, {.cvar = &cv_kartusepwrlv}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Antigrief Timer (seconds)", "How long can players stop progressing before they're removed?",
|
||||
NULL, {.cvar = &cv_antigrief}, 0, 0},
|
||||
|
|
@ -78,6 +78,9 @@ menuitem_t OPTIONS_Server[] =
|
|||
{IT_STRING | IT_CVAR, "Mute Chat", "Prevent everyone but admins from sending chat messages.",
|
||||
NULL, {.cvar = &cv_mute}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Mute Voice Chat", "Prevent everyone from sending voice chat.",
|
||||
NULL, {.cvar = &cv_voice_servermute}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Chat Spam Protection", "Prevent too many messages from a single player.",
|
||||
NULL, {.cvar = &cv_chatspamprotection}, 0, 0},
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ struct Slider
|
|||
kMasterVolume,
|
||||
kMusicVolume,
|
||||
kSfxVolume,
|
||||
kVoiceVolume,
|
||||
kNumSliders
|
||||
};
|
||||
|
||||
|
|
@ -120,6 +121,7 @@ std::array<Slider, Slider::kNumSliders> sliders{{
|
|||
n = !n;
|
||||
CV_SetValue(&cv_gamedigimusic, n);
|
||||
CV_SetValue(&cv_gamesounds, n);
|
||||
CV_SetValue(&cv_voice_chat, n);
|
||||
}
|
||||
|
||||
return n;
|
||||
|
|
@ -150,6 +152,18 @@ std::array<Slider, Slider::kNumSliders> sliders{{
|
|||
},
|
||||
cv_soundvolume,
|
||||
},
|
||||
{
|
||||
[](bool toggle) -> bool
|
||||
{
|
||||
if (toggle)
|
||||
{
|
||||
CV_AddValue(&cv_voice_chat, 1);
|
||||
}
|
||||
|
||||
return !S_VoiceDisabled();
|
||||
},
|
||||
cv_voicevolume,
|
||||
},
|
||||
}};
|
||||
|
||||
void slider_routine(INT32 c)
|
||||
|
|
@ -266,6 +280,9 @@ menuitem_t OPTIONS_Sound[] =
|
|||
{IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Music Volume", "Loudness of music.",
|
||||
NULL, {.routine = slider_routine}, 0, Slider::kMusicVolume},
|
||||
|
||||
{IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Voice Volume", "Loudness of voice chat.",
|
||||
NULL, {.routine = slider_routine}, 0, Slider::kVoiceVolume},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
|
|
|
|||
91
src/menus/options-voice.cpp
Normal file
91
src/menus/options-voice.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// DR. ROBOTNIK'S RING RACERS
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) 2024 by Kart Krew.
|
||||
//
|
||||
// This program is free software distributed under the
|
||||
// terms of the GNU General Public License, version 2.
|
||||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
/// \file menus/options-voice.cpp
|
||||
/// \brief Voice Options
|
||||
|
||||
#include "../m_easing.h"
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h" // sounds consvars
|
||||
#include "../v_video.h"
|
||||
|
||||
menuitem_t OPTIONS_Voice[] =
|
||||
{
|
||||
{IT_STRING | IT_CVAR, "Voice Chat", "Turn on or off all voice chat for yourself.",
|
||||
NULL, {.cvar = &cv_voice_chat}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Voice Mode", "When to transmit your own voice.",
|
||||
NULL, {.cvar = &cv_voice_mode}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Input Amplifier", "Amplify your voice, in decibels. Negative values are quieter.",
|
||||
NULL, {.cvar = &cv_voice_inputamp}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Activation Threshold", "Voice higher than this threshold will transmit, in decibels.",
|
||||
NULL, {.cvar = &cv_voice_activationthreshold}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Self Voice Mute", "Whether your voice is transmitted or not.",
|
||||
NULL, {.cvar = &cv_voice_selfmute}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Voice Loopback", "Play your own recording voice back.",
|
||||
NULL, {.cvar = &cv_voice_loopback}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_HEADER, "Server Voice Options...", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Server Voice Mute", "All voice chat will be disabled on your server.",
|
||||
NULL, {.cvar = &cv_voice_servermute}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Proximity Effects", "Player voices will be adjusted relative to you.",
|
||||
NULL, {.cvar = &cv_voice_proximity}, 0, 0},
|
||||
};
|
||||
|
||||
static void draw_routine()
|
||||
{
|
||||
M_DrawGenericOptions();
|
||||
|
||||
int x = currentMenu->x - M_EaseWithTransition(Easing_Linear, 5 * 48);
|
||||
int y = currentMenu->y - 12;
|
||||
int range = 220;
|
||||
float last_peak = g_local_voice_last_peak * range;
|
||||
boolean detected = g_local_voice_detected;
|
||||
INT32 color = detected ? 65 : 23;
|
||||
|
||||
V_DrawFill(x, y, range + 2, 10, 31);
|
||||
V_DrawFill(x + 1, y + 1, (int) last_peak, 8, color);
|
||||
}
|
||||
|
||||
static void tick_routine()
|
||||
{
|
||||
M_OptionsTick();
|
||||
}
|
||||
|
||||
static boolean input_routine(INT32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
menu_t OPTIONS_VoiceDef = {
|
||||
sizeof (OPTIONS_Voice) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Voice,
|
||||
48, 80,
|
||||
SKINCOLOR_ULTRAMARINE, 0,
|
||||
MBF_DRAWBGWHILEPLAYING,
|
||||
NULL,
|
||||
2, 5,
|
||||
draw_routine,
|
||||
M_DrawOptionsCogs,
|
||||
tick_routine,
|
||||
NULL,
|
||||
NULL,
|
||||
input_routine,
|
||||
};
|
||||
234
src/s_sound.c
234
src/s_sound.c
|
|
@ -266,6 +266,14 @@ boolean S_SoundDisabled(void)
|
|||
);
|
||||
}
|
||||
|
||||
boolean S_VoiceDisabled(void)
|
||||
{
|
||||
return (
|
||||
g_voice_disabled ||
|
||||
(g_fast_forward > 0)
|
||||
);
|
||||
}
|
||||
|
||||
// Stop all sounds, load level info, THEN start sounds.
|
||||
void S_StopSounds(void)
|
||||
{
|
||||
|
|
@ -676,6 +684,13 @@ void S_StopSound(void *origin)
|
|||
static INT32 actualsfxvolume; // check for change through console
|
||||
static INT32 actualdigmusicvolume;
|
||||
static INT32 actualmastervolume;
|
||||
static INT32 actualvoicevolume;
|
||||
|
||||
static boolean PointIsLeft(float ax, float ay, float bx, float by)
|
||||
{
|
||||
// return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x) > 0;
|
||||
return ax * by - ay * bx > 0;
|
||||
}
|
||||
|
||||
void S_UpdateSounds(void)
|
||||
{
|
||||
|
|
@ -694,6 +709,8 @@ void S_UpdateSounds(void)
|
|||
S_SetMusicVolume();
|
||||
if (actualmastervolume != cv_mastervolume.value)
|
||||
S_SetMasterVolume();
|
||||
if (actualvoicevolume != cv_voicevolume.value)
|
||||
S_SetVoiceVolume();
|
||||
|
||||
// We're done now, if we're not in a level.
|
||||
if (gamestate != GS_LEVEL)
|
||||
|
|
@ -853,6 +870,154 @@ notinlevel:
|
|||
I_UpdateSound();
|
||||
}
|
||||
|
||||
void S_UpdateVoicePositionalProperties(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (gamestate != GS_LEVEL)
|
||||
{
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
I_SetPlayerVoiceProperties(i, 1.0f, 0.0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
player_t *consoleplr = &players[consoleplayer];
|
||||
listener_t listener = {0};
|
||||
mobj_t *listenmobj = NULL;
|
||||
|
||||
if (consoleplr)
|
||||
{
|
||||
if (consoleplr->awayview.tics)
|
||||
{
|
||||
listenmobj = consoleplr->awayview.mobj;
|
||||
}
|
||||
else
|
||||
{
|
||||
listenmobj = consoleplr->mo;
|
||||
}
|
||||
|
||||
if (camera[0].chase && !consoleplr->awayview.tics)
|
||||
{
|
||||
listener.x = camera[0].x;
|
||||
listener.y = camera[0].y;
|
||||
listener.z = camera[0].z;
|
||||
listener.angle = camera[0].angle;
|
||||
}
|
||||
else if (listenmobj)
|
||||
{
|
||||
listener.x = listenmobj->x;
|
||||
listener.y = listenmobj->y;
|
||||
listener.z = listenmobj->z;
|
||||
listener.angle = listenmobj->angle;
|
||||
}
|
||||
}
|
||||
|
||||
float playerdistances[MAXPLAYERS];
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
player_t *plr = &players[i];
|
||||
mobj_t *mo = plr->mo;
|
||||
if (plr->spectator || !mo)
|
||||
{
|
||||
playerdistances[i] = 0.f;
|
||||
continue;
|
||||
}
|
||||
float px = FixedToFloat(mo->x - listener.x);
|
||||
float py = FixedToFloat(mo->y - listener.y);
|
||||
float pz = FixedToFloat(mo->z - listener.z);
|
||||
playerdistances[i] = sqrtf(px * px + py * py + pz * pz);
|
||||
}
|
||||
|
||||
// Positional voice audio
|
||||
boolean voice_proximity_enabled = cv_voice_proximity.value == 1;
|
||||
float voice_distanceattenuation_distance = FixedToFloat(cv_voice_distanceattenuation_distance.value) * FixedToFloat(mapheaderinfo[gamemap-1]->mobj_scale);
|
||||
float voice_distanceattenuation_factor = FixedToFloat(cv_voice_distanceattenuation_factor.value);
|
||||
float voice_stereopanning_factor = FixedToFloat(cv_voice_stereopanning_factor.value);
|
||||
float voice_concurrentattenuation_min = max(0, min(MAXPLAYERS, cv_voice_concurrentattenuation_min.value));
|
||||
float voice_concurrentattenuation_max = max(0, min(MAXPLAYERS, cv_voice_concurrentattenuation_max.value));
|
||||
voice_concurrentattenuation_min = min(voice_concurrentattenuation_max, voice_concurrentattenuation_min);
|
||||
float voice_concurrentattenuation_factor = FixedToFloat(cv_voice_concurrentattenuation_factor.value);
|
||||
|
||||
// Derive concurrent speaker attenuation
|
||||
float speakingplayers = 0;
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (S_IsPlayerVoiceActive(i))
|
||||
{
|
||||
if (voice_distanceattenuation_distance > 0)
|
||||
{
|
||||
speakingplayers += 1.f - (playerdistances[i] / voice_distanceattenuation_distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// invalid distance attenuation
|
||||
speakingplayers += 1.f;
|
||||
}
|
||||
}
|
||||
}
|
||||
speakingplayers = min(voice_concurrentattenuation_max, max(0, speakingplayers - voice_concurrentattenuation_min));
|
||||
float speakingplayerattenuation = 1.f - (speakingplayers * (voice_concurrentattenuation_factor / voice_concurrentattenuation_max));
|
||||
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
if (!playeringame[i] || !voice_proximity_enabled)
|
||||
{
|
||||
I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == consoleplayer)
|
||||
{
|
||||
I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
player_t *plr = &players[i];
|
||||
if (plr->spectator)
|
||||
{
|
||||
I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
mobj_t *plrmobj = plr->mo;
|
||||
|
||||
if (!plrmobj)
|
||||
{
|
||||
I_SetPlayerVoiceProperties(i, 1.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
float lx = FixedToFloat(listener.x);
|
||||
float ly = FixedToFloat(listener.y);
|
||||
float lz = FixedToFloat(listener.z);
|
||||
float px = FixedToFloat(plrmobj->x) - lx;
|
||||
float py = FixedToFloat(plrmobj->y) - ly;
|
||||
float pz = FixedToFloat(plrmobj->z) - lz;
|
||||
|
||||
float ldirx = cosf(ANG2RAD(listener.angle));
|
||||
float ldiry = sinf(ANG2RAD(listener.angle));
|
||||
float pdistance = sqrtf(px * px + py * py + pz * pz);
|
||||
float p2ddistance = sqrtf(px * px + py * py);
|
||||
float pdirx = px / p2ddistance;
|
||||
float pdiry = py / p2ddistance;
|
||||
float angle = acosf(pdirx * ldirx + pdiry * ldiry);
|
||||
angle = PointIsLeft(ldirx, ldiry, pdirx, pdiry) ? -angle : angle;
|
||||
|
||||
float plrvolume = 1.0f;
|
||||
if (voice_distanceattenuation_distance > 0 && voice_distanceattenuation_factor >= 0 && voice_distanceattenuation_factor <= 1.0f)
|
||||
{
|
||||
float invfactor = 1.0f - voice_distanceattenuation_factor;
|
||||
float distfactor = max(0.f, min(voice_distanceattenuation_distance, pdistance)) / voice_distanceattenuation_distance;
|
||||
plrvolume = max(0.0f, min(1.0f, 1.0f - (invfactor * distfactor)));
|
||||
}
|
||||
|
||||
float fsep = sinf(angle) * max(0.0f, min(1.0f, voice_stereopanning_factor));
|
||||
I_SetPlayerVoiceProperties(i, plrvolume * speakingplayerattenuation, fsep);
|
||||
}
|
||||
}
|
||||
|
||||
void S_UpdateClosedCaptions(void)
|
||||
{
|
||||
UINT8 i;
|
||||
|
|
@ -887,6 +1052,13 @@ void S_SetSfxVolume(void)
|
|||
I_SetSfxVolume(actualsfxvolume);
|
||||
}
|
||||
|
||||
void S_SetVoiceVolume(void)
|
||||
{
|
||||
actualvoicevolume = cv_voicevolume.value;
|
||||
|
||||
I_SetVoiceVolume(actualvoicevolume);
|
||||
}
|
||||
|
||||
void S_SetMasterVolume(void)
|
||||
{
|
||||
actualmastervolume = cv_mastervolume.value;
|
||||
|
|
@ -2639,6 +2811,18 @@ void GameDigiMusic_OnChange(void)
|
|||
}
|
||||
}
|
||||
|
||||
void VoiceChat_OnChange(void);
|
||||
void weaponPrefChange(INT32 ssplayer);
|
||||
void VoiceChat_OnChange(void)
|
||||
{
|
||||
if (M_CheckParm("-novoice") || M_CheckParm("-noaudio"))
|
||||
return;
|
||||
|
||||
g_voice_disabled = !cv_voice_chat.value;
|
||||
|
||||
weaponPrefChange(0);
|
||||
}
|
||||
|
||||
void BGAudio_OnChange(void);
|
||||
void BGAudio_OnChange(void)
|
||||
{
|
||||
|
|
@ -2656,3 +2840,53 @@ void BGAudio_OnChange(void)
|
|||
if (window_notinfocus && !(cv_bgaudio.value & 2))
|
||||
S_StopSounds();
|
||||
}
|
||||
|
||||
|
||||
boolean S_SoundInputIsEnabled(void)
|
||||
{
|
||||
return I_SoundInputIsEnabled();
|
||||
}
|
||||
|
||||
boolean S_SoundInputSetEnabled(boolean enabled)
|
||||
{
|
||||
return I_SoundInputSetEnabled(enabled);
|
||||
}
|
||||
|
||||
UINT32 S_SoundInputDequeueSamples(void *data, UINT32 len)
|
||||
{
|
||||
return I_SoundInputDequeueSamples(data, len);
|
||||
}
|
||||
|
||||
static INT32 g_playerlastvoiceactive[MAXPLAYERS];
|
||||
|
||||
void S_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
||||
{
|
||||
if (dedicated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (cv_voice_chat.value != 0)
|
||||
{
|
||||
I_QueueVoiceFrameFromPlayer(playernum, data, len, terminal);
|
||||
}
|
||||
}
|
||||
|
||||
void S_SetPlayerVoiceActive(INT32 playernum)
|
||||
{
|
||||
g_playerlastvoiceactive[playernum] = I_GetTime();
|
||||
}
|
||||
|
||||
boolean S_IsPlayerVoiceActive(INT32 playernum)
|
||||
{
|
||||
return I_GetTime() - g_playerlastvoiceactive[playernum] < 5;
|
||||
}
|
||||
|
||||
void S_ResetVoiceQueue(INT32 playernum)
|
||||
{
|
||||
if (dedicated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
I_ResetVoiceQueue(playernum);
|
||||
g_playerlastvoiceactive[playernum] = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ extern "C" {
|
|||
|
||||
extern consvar_t stereoreverse;
|
||||
extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume;
|
||||
extern consvar_t cv_voicevolume;
|
||||
|
||||
extern consvar_t surround;
|
||||
extern consvar_t cv_numChannels;
|
||||
|
|
@ -47,6 +48,22 @@ extern consvar_t cv_gamesounds;
|
|||
extern consvar_t cv_bgaudio;
|
||||
extern consvar_t cv_streamersafemusic;
|
||||
|
||||
extern consvar_t cv_voice_chat;
|
||||
extern consvar_t cv_voice_mode;
|
||||
extern consvar_t cv_voice_selfmute;
|
||||
extern consvar_t cv_voice_loopback;
|
||||
extern consvar_t cv_voice_inputamp;
|
||||
extern consvar_t cv_voice_activationthreshold;
|
||||
extern consvar_t cv_voice_proximity;
|
||||
extern consvar_t cv_voice_distanceattenuation_distance;
|
||||
extern consvar_t cv_voice_distanceattenuation_factor;
|
||||
extern consvar_t cv_voice_stereopanning_factor;
|
||||
extern consvar_t cv_voice_concurrentattenuation_factor;
|
||||
extern consvar_t cv_voice_concurrentattenuation_min;
|
||||
extern consvar_t cv_voice_concurrentattenuation_max;
|
||||
extern float g_local_voice_last_peak;
|
||||
extern boolean g_local_voice_detected;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SF_TOTALLYSINGLE = 1, // Only play one of these sounds at a time...GLOBALLY
|
||||
|
|
@ -122,6 +139,8 @@ lumpnum_t S_GetSfxLumpNum(sfxinfo_t *sfx);
|
|||
|
||||
boolean S_SoundDisabled(void);
|
||||
|
||||
boolean S_VoiceDisabled(void);
|
||||
|
||||
//
|
||||
// Start sound for thing at <origin> using <sound_id> from sounds.h
|
||||
//
|
||||
|
|
@ -249,6 +268,7 @@ void S_AttemptToRestoreMusic(void);
|
|||
//
|
||||
void S_UpdateSounds(void);
|
||||
void S_UpdateClosedCaptions(void);
|
||||
void S_UpdateVoicePositionalProperties(void);
|
||||
|
||||
FUNCMATH fixed_t S_CalculateSoundDistance(fixed_t px1, fixed_t py1, fixed_t pz1, fixed_t px2, fixed_t py2, fixed_t pz2);
|
||||
|
||||
|
|
@ -257,6 +277,7 @@ INT32 S_GetSoundVolume(sfxinfo_t *sfx, INT32 volume);
|
|||
void S_SetSfxVolume(void);
|
||||
void S_SetMusicVolume(void);
|
||||
void S_SetMasterVolume(void);
|
||||
void S_SetVoiceVolume(void);
|
||||
|
||||
INT32 S_OriginPlaying(void *origin);
|
||||
INT32 S_IdPlaying(sfxenum_t id);
|
||||
|
|
@ -270,6 +291,15 @@ void S_StopSoundByNum(sfxenum_t sfxnum);
|
|||
#define S_StartAttackSound S_StartSound
|
||||
#define S_StartScreamSound S_StartSound
|
||||
|
||||
boolean S_SoundInputIsEnabled(void);
|
||||
boolean S_SoundInputSetEnabled(boolean enabled);
|
||||
UINT32 S_SoundInputDequeueSamples(void *data, UINT32 len);
|
||||
|
||||
void S_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal);
|
||||
void S_SetPlayerVoiceActive(INT32 playernum);
|
||||
boolean S_IsPlayerVoiceActive(INT32 playernum);
|
||||
void S_ResetVoiceQueue(INT32 playernum);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -53,6 +53,125 @@ using srb2::audio::Source;
|
|||
using namespace srb2;
|
||||
using namespace srb2::io;
|
||||
|
||||
namespace
|
||||
{
|
||||
class SdlAudioStream final
|
||||
{
|
||||
SDL_AudioStream* stream_;
|
||||
public:
|
||||
SdlAudioStream(const SDL_AudioFormat format, const Uint8 channels, const int src_rate, const SDL_AudioFormat dst_format, const Uint8 dst_channels, const int dst_rate) noexcept
|
||||
{
|
||||
stream_ = SDL_NewAudioStream(format, channels, src_rate, dst_format, dst_channels, dst_rate);
|
||||
}
|
||||
SdlAudioStream(const SdlAudioStream&) = delete;
|
||||
SdlAudioStream(SdlAudioStream&&) = default;
|
||||
SdlAudioStream& operator=(const SdlAudioStream&) = delete;
|
||||
SdlAudioStream& operator=(SdlAudioStream&&) = default;
|
||||
~SdlAudioStream()
|
||||
{
|
||||
SDL_FreeAudioStream(stream_);
|
||||
}
|
||||
|
||||
void put(tcb::span<const std::byte> buf)
|
||||
{
|
||||
int result = SDL_AudioStreamPut(stream_, buf.data(), buf.size_bytes());
|
||||
if (result < 0)
|
||||
{
|
||||
char errbuf[512];
|
||||
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
||||
throw std::runtime_error(errbuf);
|
||||
}
|
||||
}
|
||||
|
||||
size_t available() const
|
||||
{
|
||||
int result = SDL_AudioStreamAvailable(stream_);
|
||||
if (result < 0)
|
||||
{
|
||||
char errbuf[512];
|
||||
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
||||
throw std::runtime_error(errbuf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t get(tcb::span<std::byte> out)
|
||||
{
|
||||
int result = SDL_AudioStreamGet(stream_, out.data(), out.size_bytes());
|
||||
if (result < 0)
|
||||
{
|
||||
char errbuf[512];
|
||||
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
||||
throw std::runtime_error(errbuf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
SDL_AudioStreamClear(stream_);
|
||||
}
|
||||
};
|
||||
|
||||
class SdlVoiceStreamPlayer : public Source<2>
|
||||
{
|
||||
SdlAudioStream stream_;
|
||||
float volume_ = 1.0f;
|
||||
float sep_ = 0.0f;
|
||||
bool terminal_ = true;
|
||||
|
||||
public:
|
||||
SdlVoiceStreamPlayer() : stream_(AUDIO_F32SYS, 1, 48000, AUDIO_F32SYS, 2, 44100) {}
|
||||
virtual ~SdlVoiceStreamPlayer() = default;
|
||||
|
||||
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override
|
||||
{
|
||||
size_t written = stream_.get(tcb::as_writable_bytes(buffer)) / sizeof(Sample<2>);
|
||||
|
||||
for (size_t i = written; i < buffer.size(); i++)
|
||||
{
|
||||
buffer[i] = {0.f, 0.f};
|
||||
}
|
||||
|
||||
// Apply gain de-popping if the last generation was terminal
|
||||
if (terminal_)
|
||||
{
|
||||
for (size_t i = 0; i < std::min<size_t>(16, written); i++)
|
||||
{
|
||||
buffer[i].amplitudes[0] *= (float)(i) / 16;
|
||||
buffer[i].amplitudes[1] *= (float)(i) / 16;
|
||||
}
|
||||
terminal_ = false;
|
||||
}
|
||||
|
||||
if (written < buffer.size())
|
||||
{
|
||||
terminal_ = true;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < written; i++)
|
||||
{
|
||||
float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f);
|
||||
|
||||
float left_scale = std::cos(sep_pan);
|
||||
float right_scale = std::sin(sep_pan);
|
||||
buffer[i] = {buffer[i].amplitudes[0] * volume_ * left_scale, buffer[i].amplitudes[1] * volume_ * right_scale};
|
||||
}
|
||||
|
||||
return buffer.size();
|
||||
};
|
||||
|
||||
SdlAudioStream& stream() noexcept { return stream_; }
|
||||
|
||||
void set_properties(float volume, float sep) noexcept
|
||||
{
|
||||
volume_ = volume;
|
||||
sep_ = sep;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// extern in i_sound.h
|
||||
UINT8 sound_started = false;
|
||||
|
||||
|
|
@ -60,13 +179,16 @@ static unique_ptr<Gain<2>> master_gain;
|
|||
static shared_ptr<Mixer<2>> master;
|
||||
static shared_ptr<Mixer<2>> mixer_sound_effects;
|
||||
static shared_ptr<Mixer<2>> mixer_music;
|
||||
static shared_ptr<Mixer<2>> mixer_voice;
|
||||
static shared_ptr<MusicPlayer> music_player;
|
||||
static shared_ptr<Resampler<2>> resample_music_player;
|
||||
static shared_ptr<Gain<2>> gain_sound_effects;
|
||||
static shared_ptr<Gain<2>> gain_music_player;
|
||||
static shared_ptr<Gain<2>> gain_music_channel;
|
||||
static shared_ptr<Gain<2>> gain_voice_channel;
|
||||
|
||||
static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
|
||||
static vector<shared_ptr<SdlVoiceStreamPlayer>> player_voice_channels;
|
||||
|
||||
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
||||
static shared_ptr<srb2::media::AVRecorder> av_recorder;
|
||||
|
|
@ -74,6 +196,10 @@ static shared_ptr<srb2::media::AVRecorder> av_recorder;
|
|||
|
||||
static void (*music_fade_callback)();
|
||||
|
||||
static SDL_AudioDeviceID g_device_id;
|
||||
static SDL_AudioDeviceID g_input_device_id;
|
||||
static boolean g_input_device_paused;
|
||||
|
||||
void* I_GetSfx(sfxinfo_t* sfx)
|
||||
{
|
||||
if (sfx->lumpnum == LUMPERROR)
|
||||
|
|
@ -120,8 +246,8 @@ namespace
|
|||
class SdlAudioLockHandle
|
||||
{
|
||||
public:
|
||||
SdlAudioLockHandle() { SDL_LockAudio(); }
|
||||
~SdlAudioLockHandle() { SDL_UnlockAudio(); }
|
||||
SdlAudioLockHandle() { SDL_LockAudioDevice(g_device_id); }
|
||||
~SdlAudioLockHandle() { SDL_UnlockAudioDevice(g_device_id); }
|
||||
};
|
||||
|
||||
#ifdef TRACY_ENABLE
|
||||
|
|
@ -180,21 +306,21 @@ void initialize_sound()
|
|||
return;
|
||||
}
|
||||
|
||||
SDL_AudioSpec desired;
|
||||
SDL_AudioSpec desired {};
|
||||
desired.format = AUDIO_F32SYS;
|
||||
desired.channels = 2;
|
||||
desired.samples = cv_soundmixingbuffersize.value;
|
||||
desired.freq = 44100;
|
||||
desired.callback = audio_callback;
|
||||
|
||||
if (SDL_OpenAudio(&desired, NULL) < 0)
|
||||
if ((g_device_id = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, NULL, 0)) == 0)
|
||||
{
|
||||
CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError());
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_PauseAudio(SDL_FALSE);
|
||||
SDL_PauseAudioDevice(g_device_id, SDL_FALSE);
|
||||
|
||||
{
|
||||
SdlAudioLockHandle _;
|
||||
|
|
@ -204,16 +330,20 @@ void initialize_sound()
|
|||
master_gain->bind(master);
|
||||
mixer_sound_effects = make_shared<Mixer<2>>();
|
||||
mixer_music = make_shared<Mixer<2>>();
|
||||
mixer_voice = make_shared<Mixer<2>>();
|
||||
music_player = make_shared<MusicPlayer>();
|
||||
resample_music_player = make_shared<Resampler<2>>(music_player, 1.f);
|
||||
gain_sound_effects = make_shared<Gain<2>>();
|
||||
gain_music_player = make_shared<Gain<2>>();
|
||||
gain_music_channel = make_shared<Gain<2>>();
|
||||
gain_voice_channel = make_shared<Gain<2>>();
|
||||
gain_sound_effects->bind(mixer_sound_effects);
|
||||
gain_music_player->bind(resample_music_player);
|
||||
gain_music_channel->bind(mixer_music);
|
||||
gain_voice_channel->bind(mixer_voice);
|
||||
master->add_source(gain_sound_effects);
|
||||
master->add_source(gain_music_channel);
|
||||
master->add_source(gain_voice_channel);
|
||||
mixer_music->add_source(gain_music_player);
|
||||
sound_effect_channels.clear();
|
||||
for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++)
|
||||
|
|
@ -222,6 +352,13 @@ void initialize_sound()
|
|||
sound_effect_channels.push_back(player);
|
||||
mixer_sound_effects->add_source(player);
|
||||
}
|
||||
player_voice_channels.clear();
|
||||
for (size_t i = 0; i < MAXPLAYERS; i++)
|
||||
{
|
||||
shared_ptr<SdlVoiceStreamPlayer> player = make_shared<SdlVoiceStreamPlayer>();
|
||||
player_voice_channels.push_back(player);
|
||||
mixer_voice->add_source(player);
|
||||
}
|
||||
}
|
||||
|
||||
sound_started = true;
|
||||
|
|
@ -237,7 +374,17 @@ void I_StartupSound(void)
|
|||
|
||||
void I_ShutdownSound(void)
|
||||
{
|
||||
SDL_CloseAudio();
|
||||
if (g_device_id)
|
||||
{
|
||||
SDL_CloseAudioDevice(g_device_id);
|
||||
g_device_id = 0;
|
||||
}
|
||||
if (g_input_device_id)
|
||||
{
|
||||
SDL_CloseAudioDevice(g_input_device_id);
|
||||
g_input_device_id = 0;
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
|
||||
sound_started = false;
|
||||
|
|
@ -380,6 +527,17 @@ void I_SetSfxVolume(int volume)
|
|||
}
|
||||
}
|
||||
|
||||
void I_SetVoiceVolume(int volume)
|
||||
{
|
||||
SdlAudioLockHandle _;
|
||||
float vol = static_cast<float>(volume) / 100.f;
|
||||
|
||||
if (gain_voice_channel)
|
||||
{
|
||||
gain_voice_channel->gain(std::clamp(vol * vol * vol, 0.f, 1.f));
|
||||
}
|
||||
}
|
||||
|
||||
void I_SetMasterVolume(int volume)
|
||||
{
|
||||
SdlAudioLockHandle _;
|
||||
|
|
@ -824,3 +982,93 @@ void I_UpdateAudioRecorder(void)
|
|||
av_recorder = g_av_recorder;
|
||||
#endif
|
||||
}
|
||||
|
||||
boolean I_SoundInputIsEnabled(void)
|
||||
{
|
||||
return g_input_device_id != 0 && !g_input_device_paused;
|
||||
}
|
||||
|
||||
boolean I_SoundInputSetEnabled(boolean enabled)
|
||||
{
|
||||
if (g_input_device_id == 0 && enabled)
|
||||
{
|
||||
SDL_AudioSpec input_desired {};
|
||||
input_desired.format = AUDIO_F32SYS;
|
||||
input_desired.channels = 1;
|
||||
input_desired.samples = 2048;
|
||||
input_desired.freq = 48000;
|
||||
SDL_AudioSpec input_obtained {};
|
||||
g_input_device_id = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &input_desired, &input_obtained, 0);
|
||||
if (!g_input_device_id)
|
||||
{
|
||||
CONS_Alert(CONS_WARNING, "Failed to open input audio device: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
g_input_device_paused = true;
|
||||
}
|
||||
|
||||
if (enabled && g_input_device_paused)
|
||||
{
|
||||
SDL_PauseAudioDevice(g_input_device_id, SDL_FALSE);
|
||||
g_input_device_paused = false;
|
||||
}
|
||||
else if (!enabled && !g_input_device_paused)
|
||||
{
|
||||
SDL_PauseAudioDevice(g_input_device_id, SDL_TRUE);
|
||||
SDL_ClearQueuedAudio(g_input_device_id);
|
||||
g_input_device_paused = true;
|
||||
}
|
||||
return !g_input_device_paused;
|
||||
}
|
||||
|
||||
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len)
|
||||
{
|
||||
if (!g_input_device_id)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
UINT32 avail = SDL_GetQueuedAudioSize(g_input_device_id);
|
||||
if (avail == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
UINT32 ret = SDL_DequeueAudio(g_input_device_id, data, std::min(len, avail));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
||||
{
|
||||
if (!sound_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SdlAudioLockHandle _;
|
||||
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
||||
player->stream().put(tcb::span((std::byte*)data, len));
|
||||
}
|
||||
|
||||
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep)
|
||||
{
|
||||
if (!sound_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SdlAudioLockHandle _;
|
||||
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
||||
player->set_properties(volume * volume * volume, sep);
|
||||
}
|
||||
|
||||
void I_ResetVoiceQueue(INT32 playernum)
|
||||
{
|
||||
if (!sound_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SdlAudioLockHandle _;
|
||||
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
||||
player->stream().clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ TYPEDEF (resultsall_pak);
|
|||
TYPEDEF (say_pak);
|
||||
TYPEDEF (reqmapqueue_pak);
|
||||
TYPEDEF (netinfo_pak);
|
||||
TYPEDEF (voice_pak);
|
||||
|
||||
// d_event.h
|
||||
TYPEDEF (event_t);
|
||||
|
|
|
|||
|
|
@ -799,6 +799,39 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset)
|
|||
player_names[pnum]
|
||||
);
|
||||
|
||||
{
|
||||
patch_t *voxpat;
|
||||
int voxxoffs = 0;
|
||||
int voxyoffs = 0;
|
||||
if (players[pnum].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN))
|
||||
{
|
||||
voxpat = (patch_t*) W_CachePatchName("VOXCRD", PU_HUDGFX);
|
||||
voxxoffs = 1;
|
||||
voxyoffs = -5;
|
||||
}
|
||||
else if (players[pnum].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE))
|
||||
{
|
||||
voxpat = (patch_t*) W_CachePatchName("VOXCRM", PU_HUDGFX);
|
||||
voxxoffs = 1;
|
||||
voxyoffs = -6;
|
||||
}
|
||||
else if (S_IsPlayerVoiceActive(pnum))
|
||||
{
|
||||
voxpat = (patch_t*) W_CachePatchName("VOXCRA", PU_HUDGFX);
|
||||
voxyoffs = -4;
|
||||
}
|
||||
else
|
||||
{
|
||||
voxpat = NULL;
|
||||
}
|
||||
|
||||
if (voxpat)
|
||||
{
|
||||
int namewidth = V_ThinStringWidth(player_names[pnum], 0);
|
||||
V_DrawFixedPatch((x + 27 + namewidth + voxxoffs) * FRACUNIT, (y + voxyoffs) * FRACUNIT, FRACUNIT, 0, voxpat, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
V_DrawRightAlignedThinString(
|
||||
x+118, y-2,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"libvpx",
|
||||
"libvorbis",
|
||||
"libyuv",
|
||||
"opus",
|
||||
"zlib"
|
||||
],
|
||||
"builtin-baseline": "c591ac6466a55ef0a05a3d56bb1489ca36e50102"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue