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
	
	 Eidolon
						Eidolon