Skip to content

Voice

Varion carries voice over a dedicated voice channel on the transport. The server is the source of truth and the traffic cop: it creates voice channels, holds their membership, relays voice between members of the same channel, and, for spatial channels, culls listeners by distance. The client only controls local playback and capture (volume, its own microphone, device, activation level, noise suppression, local mute); it cannot create channels or move players between them.

Audio is opaque to the server: voice frames are relayed as-is and are never decoded or transcoded. PCM and Opus codecs are supported; the format is 48 kHz, 20 ms frames.

Model

SideResponsibility
Servercreates and destroys channels, adds and removes players, authoritative speaker mute, priority, channel filter, relay and distance culling
Clientlocal playback volume, muting its own microphone, capture-device selection, activation level, noise suppression, local mute of a single player, reading channel state

A voice channel is owned by the server resource that created it and is destroyed when that resource stops or restarts — no manual cleanup is needed. A client only starts transmitting after the server adds it to a channel (that channel becomes its active channel). On disconnect a player is removed from all channels automatically.

Server side

Creating a channel

varion.createVoiceChannel(spatial, maxDistance?) returns a VoiceChannel — equivalent to new varion.VoiceChannel(spatial, maxDistance). It must be called in a resource context (otherwise it throws).

  • spatial = true — a spatial channel: a listener hears a speaker only within maxDistance (3D distance from the players' positions). maxDistance defaults to 50 and is clamped to 1..10000.
  • spatial = false — a non-spatial channel: every member hears everyone, distance is ignored (maxDistance is 0).
javascript
const proximity = varion.createVoiceChannel(true, 20);   // spatial, 20 m
const team = varion.createVoiceChannel(false);           // team-wide, no distance

VoiceChannel properties

PropertyTypeAccessPurpose
idnumberread-onlychannel id, allocated by the server
type'voiceChannel'read-onlyobject type
validbooleanread-onlywhether the channel is alive
isSpatialbooleanread-onlywhether the channel is spatial
maxDistancenumberread-onlyaudible radius (0 for a non-spatial channel)
playersPlayer[]read-onlycurrent members as Player proxies
playerCountnumberread-onlynumber of members
prioritynumberread/writepriority when channels overlap (defaults to 0)
filternumberread/writechannel audio-filter hash (0 — no filter)

VoiceChannel methods

MethodPurpose
addPlayer(player)add a player to the channel
removePlayer(player)remove a player from the channel
hasPlayer(player)whether the player is in the channel (alias of isPlayerInChannel)
isPlayerInChannel(player)whether the player is in the channel
isPlayerMuted(player)whether the player is muted on the server
mutePlayer(player, muted?)server mute (defaults to true; false unmutes)
unmutePlayer(player)remove the server mute
setFilter(filterHash)set the filter (same as filter = filterHash)
destroy()destroy the channel

The player argument accepts a Player proxy or a numeric id.

Static members: VoiceChannel.all (all valid channels) and VoiceChannel.getByID(id) (the channel or null).

Server mute (authoritative)

channel.mutePlayer(player) suppresses that member's outgoing voice for the whole channel — no one else in the channel hears them. This is a server-side, authoritative decision, unlike a local client mute, which affects only what a single player hears. Calling mutePlayer(player, false) is equivalent to unmutePlayer(player). Only a player who is already a member of the channel can be muted.

Priority

If a listener shares more than one channel with a speaker, the server picks a single channel to deliver the voice: first the one with the highest priority; on a tie a non-spatial channel is preferred; on a further tie the channel with the lower id wins. So priority decides whose voice "wins" when channels overlap (for example, a team channel outranks a general proximity channel).

Filters

channel.setFilter(hash) or channel.filter = hash sets an audio-filter hash. The server forwards this hash to clients, and the DSP is applied on the client to that channel's incoming voice. Hashes are computed with varion.hash(name) (JOAAT, lowercase). Built-in presets:

NameEffect
walkietalkieband-pass filter (radio)
phoneband-pass filter (telephone)
underwaterlow-pass filter (underwater)

Any other hash is delivered to clients but has no built-in effect — the voice passes through unchanged. channel.filter = 0 removes the filter.

javascript
const radio = varion.createVoiceChannel(false);
radio.setFilter(varion.hash('walkietalkie'));
radio.priority = 10;

How the server relays voice

  • A client uploads one microphone frame; VoiceChannel membership decides who hears it. The client-provided channel id is retained only for wire compatibility — the route is determined by channel membership on the server.
  • Audio is opaque: the server does not decode frames, it only validates their size and relays them.
  • For spatial channels the server culls members beyond maxDistance.
  • Voice frames travel over an unreliable transport channel, while channel membership and configuration changes travel over the reliable one.

Example (server)

javascript
const proximity = varion.createVoiceChannel(true, 15);

varion.on('playerReady', (player) => {
  proximity.addPlayer(player);           // the server decides who is in which channel
});

varion.onClient('voice:report', (reporter, targetId) => {
  proximity.mutePlayer(targetId);        // authoritatively mute them for the whole channel
});

// No need to remove a player on disconnect — they leave all channels automatically.

Client side

The client controls local playback and capture through varion.Voice. No client call changes channel membership — only the server does that.

varion.Voice — playback and capture

MemberType / signaturePurpose
activeChannelnumber (get)active channel id
pushToTalkboolean (get)whether the PTT key is currently held
volumenumber (get/set)local playback volume, 0..2
muteInputboolean (get/set)mute your own microphone
inputMutedboolean (get)alias of muteInput
activationLevelnumber (get/set)voice-activation threshold, 0..100
noiseSuppressionEnabledboolean (get/set)noise suppression
inputDevicestring | null (get/set)capture-device id
activityInputEnabledboolean (get/set)voice-activation mode (instead of PTT)
activationKeynumber (get)PTT key
voiceControlsEnabledboolean (get)whether the built-in voice controls are enabled
toggleInput(enabled)open or close the microphone manually
getAvailableInputDevices(){ name, uuid }[]capture devices (uuid is null for the default device)

Players and mute (client)

MethodPurpose
getChannel(channelId?)client VoiceChannel (defaults to the active one) or null
getPlayersInChannel(channelId?)Player[] in the channel (defaults to the active one)
isPlayerTalking(player)whether the player is talking right now
isPlayerMuted(player)whether the player is muted on the server in the active channel
isPlayerLocallyMuted(player)whether the player is muted locally
mutePlayer(player)locally silence the player's playback and send varion:voice:mute to the server
unmutePlayer(player)remove the local mute and notify the server
clearLocallyMuted()clear the local mute list

A local mute affects only what this client hears; the server mute (isPlayerMuted) is authoritative. Voice.mutePlayer additionally emits the server event varion:voice:mute (channelId, playerId, muted), which a resource can handle via varion.onClient — on its own it is not an authoritative server mute.

Client VoiceChannel (read-only)

Get a channel snapshot through varion.Voice.getChannel(id) or varion.VoiceChannel.getByID(id).

MemberPurpose
idchannel id
spatialwhether the channel is spatial
maxDistanceaudible radius (defaults to 50)
volume (get/set)playback volume for this channel, 0..2
membersarray of member ids
mutedarray of ids muted on the server
filterAudioFilter or null
getPlayers()Player[] of members
isActive()whether this is the active channel

Static member: VoiceChannel.getByID(id).

AudioFilter

varion.AudioFilter.create(nameOrHash) returns a cached AudioFilter by name (the name is JOAAT-hashed) or by numeric hash. The object exposes a hash field.

Top-level helpers

  • varion.muteInput(muted = true) — same as Voice.muteInput = ....
  • varion.toggleVoiceControls(enabled) — enable or disable the built-in voice controls.
  • varion.clearLocallyMuted() — same as Voice.clearLocallyMuted().

Example (client)

javascript
varion.Voice.volume = 1.2;
varion.Voice.noiseSuppressionEnabled = true;
varion.Voice.activationLevel = 35;

// "Talking" indicator in the HUD
varion.on('voice:playerStartTalking', (player) => hud.setTalking(player.id, true));
varion.on('voice:playerStopTalking', (player) => hud.setTalking(player.id, false));

// Locally silence an annoying player
varion.Voice.mutePlayer(annoyingPlayer);

// Manual push-to-talk on the N key
varion.on('keydown', (key) => { if (key === 0x4E) varion.Voice.toggleInput(true); });
varion.on('keyup', (key) => { if (key === 0x4E) varion.Voice.toggleInput(false); });

Voice events

Events arrive on the client; subscribe with varion.on(name, callback). The full event contract is described in Events and RPC — this is only a short summary.

EventArgumentsWhen
playerStartTalking / voice:playerStartTalking(player)a player started talking
playerStopTalking / voice:playerStopTalking(player)a player stopped talking
voice:channelChange(previousChannelId, activeChannelId)the active channel changed
voice:channelCreate(voiceChannel)the client learned about a new channel
voice:channelDestroy(channelId)a channel went away
voice:pushToTalk(active)the push-to-talk state changed

Source backing

This page is verified against sdk/Varion.Server/npm/@varion/server/index.d.ts (the VoiceChannel class and varion.createVoiceChannel), the Node-host runtime server/host/varion-host.cjs, the server relay server/src/CVoiceManager.{h,cpp}, and the client runtime sdk/client-js-runtime/injected/services-bootstrap.js (the Voice object and the VoiceChannel and AudioFilter classes). The voice event list is in Events and RPC.

Varion Multiplayer Platform