Initial commit
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
extends Node
|
||||
|
||||
var current_sample_rate: int = 48000
|
||||
var has_loopback: bool = false
|
||||
var max_buffer_size: int = 1000
|
||||
var voicechat_player_path : NodePath = ^"VoiceChat"
|
||||
var sample_rate_min : int = 11025
|
||||
var sample_rate_max : int = 48000
|
||||
|
||||
var local_playback: AudioStreamGeneratorPlayback = null
|
||||
var local_voice_buffer: PackedByteArray = PackedByteArray()
|
||||
|
||||
var network_playback: Dictionary[int, AudioStreamGeneratorPlayback] = {}
|
||||
var network_voice_buffer: Dictionary[int, PackedByteArray] = {}
|
||||
|
||||
@onready var loopback_player : AudioStreamPlayer= $/root/Main/VoiceLoopback
|
||||
|
||||
func _ready() -> void:
|
||||
if not Steamworks.is_initiallized:
|
||||
push_warning("[VoiceChat] VoiceChat disabled due to SteamAPI.")
|
||||
return
|
||||
if has_loopback:
|
||||
print("[VoiceChat] Loopback on")
|
||||
record_voice(true)
|
||||
loopback_player.stream.mix_rate = current_sample_rate
|
||||
loopback_player.play()
|
||||
local_playback = loopback_player.get_stream_playback()
|
||||
multiplayer.peer_disconnected.connect(remove_disconnected_peer)
|
||||
Multiplayer.player_ready.connect(add_joined_peer)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not Steamworks.is_initiallized:
|
||||
return
|
||||
check_for_voice_local()
|
||||
|
||||
func add_joined_peer(id): # Fix calling this on yourself
|
||||
var player : Node3D = Multiplayer.playerDict.get(id)
|
||||
var remote_audio_stream_playback = player.get_node_or_null(voicechat_player_path).get_stream_playback()
|
||||
network_playback.set(id, remote_audio_stream_playback)
|
||||
network_voice_buffer.set(id,PackedByteArray())
|
||||
current_sample_rate = Steam.getVoiceOptimalSampleRate()
|
||||
send_sample_rate.rpc_id(id, current_sample_rate)
|
||||
func remove_disconnected_peer(id):
|
||||
network_playback.erase(id)
|
||||
network_voice_buffer.erase(id)
|
||||
|
||||
func check_for_voice_local() -> void:
|
||||
var available_voice: Dictionary = Steam.getAvailableVoice()
|
||||
if !available_voice:
|
||||
return
|
||||
# Seems there is voice data
|
||||
if available_voice['result'] == Steam.VOICE_RESULT_OK and available_voice['buffer'] > 0:
|
||||
# Valve's getVoice uses 1024 but GodotSteam's is set at 8192?
|
||||
# Our sizes might be way off; internal GodotSteam notes that Valve suggests 8kb
|
||||
# However, this is not mentioned in the header nor the SpaceWar example but -is- in Valve's docs which are usually wrong
|
||||
var voice_data: Dictionary = Steam.getVoice()
|
||||
if voice_data['result'] == Steam.VOICE_RESULT_OK and voice_data['written']:
|
||||
#print("[VoiceChat] Voice message has data: %s / %s" % [voice_data['result'], voice_data['written']])
|
||||
|
||||
# Here we can pass this voice data off on the network
|
||||
#TODO: Only execute if multiplayer connected
|
||||
get_sample_rate()
|
||||
send_voice.rpc(voice_data['buffer'])
|
||||
|
||||
# If loopback is enable, play it back at this point
|
||||
if has_loopback:
|
||||
# Our sample rate function above without toggling
|
||||
process_voice_data(voice_data['buffer'], 1)
|
||||
|
||||
@rpc("any_peer","call_remote","unreliable")
|
||||
func send_voice(voice_buffer : PackedByteArray):
|
||||
var sender_id : int = multiplayer.get_remote_sender_id()
|
||||
if not network_playback.has(sender_id):
|
||||
push_error("[VoiceChat] Received audio data, but there is no AudioStreamPlayer available for that user. Emitter: " + str(sender_id))
|
||||
return
|
||||
if not network_voice_buffer.has(sender_id):
|
||||
push_error("[VoiceChat] Received audio data without allocated voice buffer for that user. Emitter: " + str(sender_id))
|
||||
return
|
||||
var local_peer_buffer : PackedByteArray = network_voice_buffer.get(sender_id)
|
||||
if local_peer_buffer.size() >= max_buffer_size:
|
||||
push_error("[VoiceChat] AudioBuffer exeeding size limit. Emitter: " + str(sender_id))
|
||||
process_voice_data(voice_buffer, sender_id)
|
||||
|
||||
@rpc("any_peer","call_remote","reliable")
|
||||
func send_sample_rate(sample_rate : int):
|
||||
var sender_id : int = multiplayer.get_remote_sender_id()
|
||||
if sample_rate < sample_rate_min || sample_rate > sample_rate_max:
|
||||
push_warning("[VoiceChat] Received invalid sample rate for peer. Emitter: " + str(sender_id))
|
||||
return
|
||||
if not network_playback.has(sender_id):
|
||||
push_warning("[VoiceChat] Received sample rate for invalid peer. Emitter: " + str(sender_id)) # Race condition?
|
||||
return
|
||||
if not Multiplayer.playerDict.has(sender_id):
|
||||
push_error("[VoiceChat] Player exists in VoiceChat variables, but not playerDict??? Emitter: " + str(sender_id))
|
||||
return
|
||||
if not Multiplayer.playerDict.has(sender_id): return
|
||||
var player : Node3D = Multiplayer.playerDict.get(sender_id)
|
||||
if not player.has_node(voicechat_player_path): return
|
||||
player.get_node(voicechat_player_path).stream.mix_rate = sample_rate # Fix this (ref: process_voice_data TODO:_1
|
||||
network_playback.get(sender_id).clear_buffer()
|
||||
network_voice_buffer.get(sender_id).clear()
|
||||
|
||||
func get_sample_rate() -> void:
|
||||
current_sample_rate = Steam.getVoiceOptimalSampleRate()
|
||||
if loopback_player.stream.mix_rate != current_sample_rate:
|
||||
send_sample_rate.rpc(current_sample_rate)
|
||||
loopback_player.stream.mix_rate = current_sample_rate
|
||||
print("[VoiceChat] Changed own sample rate to: " + str(current_sample_rate))
|
||||
|
||||
func process_voice_data(voice_data: PackedByteArray, voice_source: int) -> void:
|
||||
#TODO:_1 Fix this with a mix rate dict
|
||||
var decompressed_voice : Dictionary
|
||||
if voice_source == 0:
|
||||
decompressed_voice = Steam.decompressVoice(voice_data, current_sample_rate)
|
||||
elif Multiplayer.playerDict.has(voice_source):
|
||||
var player : Node3D = Multiplayer.playerDict.get(voice_source)
|
||||
if not player.has_node(voicechat_player_path):
|
||||
push_error("[VoiceChat] Player %s doesn't have a VoiceChat node", voice_source)
|
||||
return
|
||||
var mix_rate = player.get_node_or_null(voicechat_player_path).stream.mix_rate
|
||||
assert(mix_rate >= sample_rate_min and mix_rate <= sample_rate_max)
|
||||
decompressed_voice = Steam.decompressVoice(voice_data, mix_rate)
|
||||
else:
|
||||
push_error("[VoiceChat] Received voice data for non existing player")
|
||||
return
|
||||
if decompressed_voice['result'] == Steam.VOICE_RESULT_OK and decompressed_voice['size'] > 0:
|
||||
|
||||
if voice_source == 0:
|
||||
local_voice_buffer = decompressed_voice['uncompressed']
|
||||
local_voice_buffer.resize(decompressed_voice['size'])
|
||||
|
||||
# We now iterate through the local_voice_buffer and push the samples to the audio generator
|
||||
for i: int in local_playback.get_frames_available():
|
||||
if local_voice_buffer.size() == 0: break
|
||||
# Steam's audio data is represented as 16-bit single channel PCM audio, so we need to convert it to amplitudes
|
||||
# Combine the low and high bits to get full 16-bit value
|
||||
var raw_value: int = local_voice_buffer[0] | (local_voice_buffer[1] << 8)
|
||||
# Make it a 16-bit signed integer
|
||||
raw_value = (raw_value + 32768) & 0xffff
|
||||
# Convert the 16-bit integer to a float on from -1 to 1
|
||||
var amplitude: float = float(raw_value - 32768) / 32768.0
|
||||
|
||||
# push_frame() takes a Vector2. The x represents the left channel and the y represents the right channel
|
||||
local_playback.push_frame(Vector2(amplitude, amplitude))
|
||||
|
||||
# Delete the used samples
|
||||
local_voice_buffer.remove_at(0)
|
||||
local_voice_buffer.remove_at(0)
|
||||
else:
|
||||
#TODO: Better naming
|
||||
#TODO: Better explanation for 16-bit conversions
|
||||
var peer_voice_buffer : PackedByteArray = network_voice_buffer.get(voice_source)
|
||||
peer_voice_buffer = decompressed_voice['uncompressed']
|
||||
peer_voice_buffer.resize(decompressed_voice['size'])
|
||||
|
||||
var peer_playback : AudioStreamGeneratorPlayback = network_playback.get(voice_source)
|
||||
for i: int in peer_playback.get_frames_available():
|
||||
if peer_voice_buffer.size() == 0: break
|
||||
var raw_value: int = peer_voice_buffer[0] | (peer_voice_buffer[1] << 8)
|
||||
# Make it a 16-bit signed integer
|
||||
raw_value = (raw_value + 32768) & 0xffff
|
||||
# Convert the 16-bit integer to a float on from -1 to 1
|
||||
var amplitude: float = float(raw_value - 32768) / 32768.0
|
||||
|
||||
# push_frame() takes a Vector2. The x represents the left channel and the y represents the right channel
|
||||
peer_playback.push_frame(Vector2(amplitude, amplitude))
|
||||
|
||||
# Delete the used samples
|
||||
peer_voice_buffer.remove_at(0)
|
||||
peer_voice_buffer.remove_at(0)
|
||||
|
||||
func record_voice(is_recording: bool) -> void:
|
||||
# If talking, suppress all other audio or voice comms from the Steam UI
|
||||
Steam.setInGameVoiceSpeaking(Steamworks.steam_id, is_recording)
|
||||
|
||||
if is_recording:
|
||||
Steam.startVoiceRecording()
|
||||
else:
|
||||
Steam.stopVoiceRecording()
|
||||
Reference in New Issue
Block a user