Skip to content

Instantly share code, notes, and snippets.

@bearlikelion
Last active May 3, 2025 05:27
Show Gist options
  • Save bearlikelion/c1c8552e42e995a39148dbd69d7107f6 to your computer and use it in GitHub Desktop.
Save bearlikelion/c1c8552e42e995a39148dbd69d7107f6 to your computer and use it in GitHub Desktop.
SurfsUp Voip.gd
class_name Voice
extends Node
const VOIP = preload("res://Scenes/Player/voip.tscn")
const REF_SAMPLE_RATE : int = 44100
@export var use_loopback: bool = false
var current_sample_rate: int = 48000
var local_playback: AudioStreamGeneratorPlayback = null
var local_voice_buffer: PackedByteArray = PackedByteArray()
var network_voice_buffer: PackedByteArray = PackedByteArray()
var packet_read_limit: int = 5
var is_speaking: bool = false
@onready var network: AudioStreamPlayer = $Network
func _ready() -> void:
if !is_multiplayer_authority():
queue_free()
Events.voip.connect(process_voice_data)
for peer: Dictionary in SteamInit.lobby_members:
setup_voip(peer["peer_id"])
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("push_to_talk") and !Global.is_typing:
NetCode.show_speaking.rpc(multiplayer.get_unique_id(), true)
if Input.is_action_pressed("push_to_talk") and !Global.is_typing:
is_speaking = true
record_voice(true)
if Input.is_action_just_released("push_to_talk") and !Global.is_typing:
is_speaking = false
record_voice(false)
if is_speaking:
check_for_voice()
func record_voice(recording: bool) -> void:
Steam.setInGameVoiceSpeaking(SteamInit.steam_id, recording)
if recording:
Steam.startVoiceRecording()
else:
Steam.stopVoiceRecording()
NetCode.show_speaking.rpc(multiplayer.get_unique_id(), false)
func check_for_voice() -> void:
var available_voice: Dictionary = Steam.getAvailableVoice()
# 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("Voice message has data: %s / %s" % [voice_data['result'], voice_data['written']])
# Here we can pass this voice data off on the network
# Networking.send_message(voice_data['buffer'])
# SteamInit.peer.send_direct_message(voice_data['buffer'])
# print("Send Voice Data: %s" % sent)
# If loopback is enable, play it back at this point
if use_loopback:
# print("Loopback on")
process_voice_data(voice_data, multiplayer.get_unique_id())
else:
NetCode.send_voice.rpc(voice_data)
func get_sample_rate() -> void:
current_sample_rate = Steam.getVoiceOptimalSampleRate()
# print("Current sample rate: %s" % current_sample_rate)
func process_voice_data(voice_data: Dictionary, peer_id: int) -> void:
# Our sample rate function above without toggling
get_sample_rate()
var pitch : float = float(current_sample_rate)/REF_SAMPLE_RATE
var network_voip: AudioStreamPlayer = get_node_or_null(str(peer_id))
if network_voip:
var network_playback: AudioStreamPlayback = network_voip.get_stream_playback()
network_voip.set_pitch_scale(pitch)
var decompressed_voice: Dictionary = Steam.decompressVoice(voice_data['buffer'], current_sample_rate)
if decompressed_voice['result'] == Steam.VOICE_RESULT_OK and decompressed_voice['size'] > 0:
# print("Decompressed voice: %s" % decompressed_voice['size'])
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 range(0, mini(network_playback.get_frames_available() * 2, local_voice_buffer.size()), 2):
# 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
network_playback.push_frame(Vector2(amplitude, amplitude))
# Delete the used samples
local_voice_buffer.remove_at(0)
local_voice_buffer.remove_at(0)
else:
setup_voip(peer_id)
func setup_voip(peer_id: int) -> void:
if peer_id > 0:
var voip_node: AudioStreamPlayer = VOIP.instantiate()
add_child(voip_node)
voip_node.play()
voip_node.name = str(peer_id)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment