Voice Chat¶
BlackICE has built-in proximity voice chat. Without any bundle code, players hear each other when they are near each other in the world. Voices come from the speaker's character, pan with it and fade with distance.
The server-side component can change how voice is routed and how it sounds:
- Range - How far a player can be heard
- Direct connections - Voice between two players regardless of distance (radios, phone calls, team channels)
- Speakers and mics - Pick up voices and game audio at one place and play them from an entity somewhere else (PA systems, radios, intercoms)
- Effects - An effect chain on a voice or a speaker: filters, distortion, compression, pitch, reverb and more
- Effect presets - Named sets of effect parameters you define once and apply by name or handle
All of this is server API, under re.Voip. Clients apply whatever the server
decides, players can't change another player's voice. The examples on this
page assume your source file has using re;.
How voice reaches a listener¶
A listener hears a speaker when one of these is true:
- The speaker's character is in the listener's network range (the area the listener's game streams other players in), and closer than the speaker's voice range if one is set. This is spatial voice, it comes from the speaker's character and is affected by distance, direction, occlusion and doppler
- A script created a direct connection between them. This is direct voice, it plays without position, like a phone call
- The speaker is near a mic that a script connected to a speaker entity the listener can hear. The voice then plays from that entity, see Speakers and mics
Spatial voice always applies doppler to moving speakers, at 75 % of the physical shift. Nothing needs to be configured for it.
Voice range¶
- Listeners further away than the range stop receiving the speaker. The voice fades out towards the range and is silent at it
- The range only affects spatial voice, direct connections ignore it
- The range is per speaker. Shouting and whispering are a range change:
void SetVoiceMode(ClientId player, string mode)
{
switch (mode)
{
case "whisper": Voip.SetVoiceRange(player, 5f); break;
case "normal": Voip.SetVoiceRange(player, 20f); break;
case "shout": Voip.SetVoiceRange(player, 60f); break;
}
}
Info
A short range also saves bandwidth. The server stops sending voice to listeners outside it
Direct connections¶
// Two way radio between two players
Voip.CreateDirectConnection(alice, bob, false);
Voip.CreateDirectConnection(bob, alice, false);
// A whole channel at once
Voip.CreateDirectConnection(dispatcher, members, false);
Voip.RemoveDirectConnection(alice, bob);
Voip.RemoveAllDirectConnections(alice);
- A connection is "one way".
CreateDirectConnection(speaker, listener, ...)letslistenerhearspeaker. Create both directions for a conversation - With
exclusiveset totrue, a listener that would also hear the speaker spatially hears them only through the connection. Use it so a radio partner standing next to you doesn't sound doubled
Speakers and mics¶
A mic picks up voices and game audio around a point in the world. A speaker plays what its connected mics pick up, from an entity such as a radio prop or a car.
Note
Currently all radio emitters are filtered, even the ones in world space. This might change in the future
// A mic fixed in the world, picking up within 15 m
var mic = Voip.CreateMic(position, 15f);
// Or a mic that moves with an entity
var mic = Voip.CreateEntityMic(entity, 15f);
// The speaker plays from an entity
var speaker = Voip.CreateSpeaker(speakerEntity);
Voip.ConnectMic(mic, speaker);
Voip.DisconnectMic(mic, speaker);
Voip.DestroyMic(mic);
Voip.DestroySpeaker(speaker);
- A mic only plays through the speakers you connect it to. Connections are many-to-many. One mic can feed several speakers and one speaker can play several mics. To have one player heard on several speakers, use one mic and connect it to each speaker
- A mic picks up the voices of players within its range and the game audio around it. Front-end sounds such as menus and radio music are left out
SetMicPositionmoves a fixed mic,SetMicRangechanges how far it picks up. The range has to be above 0, a mic created with an invalid range has the id0and does nothingSetMicAttenuationscales how far game sounds carry to the mic. Above1they reach further, below1they fall off sooner. It doesn't change which players' voices are picked up, that is the mic's range- Listeners hear a speaker like any other sound on its entity. Positioned, fading with distance, and at their own broadcast volume setting
- Mics and speakers together are limited to 511 per server
Mic hosts¶
Every mic is captured by a host, which defaults to the closest player within the mic's range. The host's game records what it hears at the mic and sends it on. A mic picks its host as follows:
- Each client hosts at most one mic at a time. A client already hosting another mic is skipped
- The host keeps the mic until they move further than 1.1x the mic's range from it, so a mic doesn't flip between two players standing close together
- A mic only gets a host while someone other than that host can hear one of its connected speakers. A mic without listeners stays idle
A mic without a host is silent. You can create several mics on or around the same player, but with nobody else nearby only one of them captures. The others start once another player comes within their range to host them.
Note
The host's own volume settings don't affect what their mic captures
Effects¶
Effects are set on a target. A target is one of:
| Target | What it changes |
|---|---|
FxTarget.Voice(player) |
The player's spatial voice |
FxTarget.Direct(player) |
The player's voice over direct connections |
FxTarget.Emitter(entity) |
Everything a speaker entity plays |
var voice = FxTarget.Voice(player);
Voip.SetFxParameter(voice, "reverb_mix", 40f);
Voip.SetFxParameter(voice, "reverb_decay", 3f);
// Only for one listener, on top of the target's own parameters
Voip.SetFxParameterForListener(FxTarget.Direct(speaker), listener, "lowpass", 3000f);
// Remove one parameter, or all of them
Voip.SetFxParameter(voice, "reverb_mix", float.NaN);
Voip.ResetFx(voice);
- Spatial and direct voice are separate targets, so a player can sound plain to the people next to them and filtered on the radio at the same time
- Per-listener parameters override the target's parameters for that one listener
- Values are clamped to the ranges in the table below. Unknown names are ignored and logged on the server
ResetFxalso clears the target's presets. It keeps the voice range- Everything a player had is cleared when they disconnect, and an emitter's effects are dropped when its entity is deleted
The first parameter set on a voice switches it to the effect chain. The switch takes effect from the next time they start transmitting. Value changes on a voice that is already playing apply right away. Speakers always play through the effect chain, with every parameter at its neutral default until you set one.
Parameter reference¶
| Name | Range | Default | What it does |
|---|---|---|---|
volume |
-96 to 12 dB | 0 | Output level |
highpass |
20 to 5000 Hz | 20 | Cuts everything below, 24 dB/oct |
lowpass |
100 to 20000 Hz | 20000 | Cuts everything above, 24 dB/oct |
eq_freq |
20 to 20000 Hz | 1000 | Centre of the peak EQ band |
eq_gain |
-24 to 24 dB | 0 | Boost or cut of the peak EQ band |
eq_q |
0.5 to 20 | 1 | Width of the peak EQ band, higher is narrower |
drive |
0 to 100 | 0 | Distortion amount |
distortion_type |
0 to 4 | 1 | 0 none, 1 overdrive, 2 heavy, 3 fuzz, 4 clip |
distortion_tone |
0 to 100 | 50 | Distortion brightness |
distortion_mix |
0 to 100 % | 0 | How much distorted signal is mixed in |
compressor_threshold |
-60 to 0 dB | 0 | Level above which the compressor works |
compressor_ratio |
1 to 50 | 1 | Compression strength, 1 is off |
compressor_attack |
0 to 2 s | 0.01 | How fast the compressor reacts |
compressor_release |
0 to 2 s | 0.1 | How fast the compressor lets go |
compressor_gain |
-24 to 24 dB | 0 | Makeup gain after the compressor |
tremolo_depth |
0 to 100 % | 0 | Amplitude modulation depth |
tremolo_rate |
0.02 to 2000 Hz | 5 | Modulation rate. Above ~30 Hz it turns metallic and robotic |
flanger_mix |
0 to 100 % | 0 | Flanger amount |
flanger_rate |
0.002 to 20 Hz | 0.5 | Flanger sweep speed |
flanger_depth |
0 to 100 % | 50 | Flanger sweep depth |
flanger_feedback |
-1 to 1 | 0 | Flanger resonance |
pitch |
-2400 to 2400 cents | 0 | Pitch shift, 1200 cents is one octave |
delay_mix |
0 to 100 % | 0 | Echo amount, 0.3 s echo |
delay_feedback |
0 to 95 % | 0 | How long the echo repeats |
reverb_mix |
0 to 100 % | 0 | Room reverb amount |
reverb_decay |
0.2 to 10 s | 1.5 | Reverb length |
reverb_predelay |
0 to 500 ms | 20 | Gap before the reverb starts, bigger feels like a bigger room |
reverb_damping |
0.5 to 10 | 2.25 | Damps the reverb's highs, higher sounds softer |
Effect presets¶
A preset is a named set of parameters. Define it once, then apply it to as many targets as you like. Editing a preset updates everything that uses it.
var radio = Voip.CreateFxPreset("radio");
Voip.SetFxPresetParameter(radio, "highpass", 500f);
Voip.SetFxPresetParameter(radio, "lowpass", 2800f);
Voip.SetFxPresetParameter(radio, "distortion_mix", 30f);
Voip.SetFxPreset(FxTarget.Direct(player), radio);
Voip.SetFxPreset(FxTarget.Direct(player), "radio"); // the same, by name
Voip.ClearFxPreset(FxTarget.Direct(player));
CreateFxPresetreturns the existing preset when the name is already taken, so a restarted bundle gets its presets back instead of new ones- Presets don't contain a voice range, set it with
SetVoiceRange - A target's own parameters are layered on top of its preset:
SetFxPreset(voice, "radio")and thenSetFxParameter(voice, "drive", 60f)is "radio, but dirtier" SetFxPresetForListenerapplies a preset for one listener only. An empty or unknown name clears it, the listener then hears the target's own preset again- Presets exist until the server stops. A server holds up to 1024
Radio channel example¶
Everyone on the channel hears each other through a radio, the people standing next to a speaker still hear their plain voice:
var radio = Voip.CreateFxPreset("radio");
// ... parameters, see below
void JoinChannel(ClientId player, List<ClientId> channel)
{
foreach (var member in channel)
{
Voip.CreateDirectConnection(player, member, true);
Voip.CreateDirectConnection(member, player, true);
}
Voip.SetFxPreset(FxTarget.Direct(player), radio);
channel.Add(player);
}
PA system example¶
A mic on a stage, speakers around a plaza, all of them sounding like a megaphone:
var megaphone = Voip.CreateFxPreset("megaphone");
// ... parameters, see below
var mic = Voip.CreateMic(stagePosition, 5f);
foreach (var entity in speakerProps)
{
var speaker = Voip.CreateSpeaker(entity);
Voip.ConnectMic(mic, speaker);
Voip.SetFxPreset(FxTarget.Emitter(entity), megaphone);
}
Preset templates¶
Starting points for common voices. Tune them by ear until you are happy with the result:
var p = Voip.CreateFxPreset("radio");
Voip.SetFxPresetParameter(p, "highpass", 500f);
Voip.SetFxPresetParameter(p, "lowpass", 2800f);
Voip.SetFxPresetParameter(p, "eq_freq", 1500f);
Voip.SetFxPresetParameter(p, "eq_gain", 5f);
Voip.SetFxPresetParameter(p, "eq_q", 1.2f);
Voip.SetFxPresetParameter(p, "distortion_type", 1f);
Voip.SetFxPresetParameter(p, "drive", 30f);
Voip.SetFxPresetParameter(p, "distortion_tone", 60f);
Voip.SetFxPresetParameter(p, "distortion_mix", 30f);
Voip.SetFxPresetParameter(p, "compressor_threshold", -24f);
Voip.SetFxPresetParameter(p, "compressor_ratio", 8f);
Voip.SetFxPresetParameter(p, "compressor_attack", 0.003f);
Voip.SetFxPresetParameter(p, "compressor_release", 0.08f);
Voip.SetFxPresetParameter(p, "compressor_gain", 4f);
var p = Voip.CreateFxPreset("walkie_talkie");
Voip.SetFxPresetParameter(p, "highpass", 800f);
Voip.SetFxPresetParameter(p, "lowpass", 2400f);
Voip.SetFxPresetParameter(p, "eq_freq", 1800f);
Voip.SetFxPresetParameter(p, "eq_gain", 6f);
Voip.SetFxPresetParameter(p, "eq_q", 1.5f);
Voip.SetFxPresetParameter(p, "distortion_type", 3f);
Voip.SetFxPresetParameter(p, "drive", 45f);
Voip.SetFxPresetParameter(p, "distortion_mix", 45f);
Voip.SetFxPresetParameter(p, "compressor_threshold", -28f);
Voip.SetFxPresetParameter(p, "compressor_ratio", 8f);
Voip.SetFxPresetParameter(p, "compressor_attack", 0.005f);
Voip.SetFxPresetParameter(p, "compressor_release", 0.12f);
Voip.SetFxPresetParameter(p, "compressor_gain", 5f);
var p = Voip.CreateFxPreset("phone");
Voip.SetFxPresetParameter(p, "highpass", 300f);
Voip.SetFxPresetParameter(p, "lowpass", 3400f);
Voip.SetFxPresetParameter(p, "eq_freq", 1200f);
Voip.SetFxPresetParameter(p, "eq_gain", 4f);
Voip.SetFxPresetParameter(p, "distortion_type", 1f);
Voip.SetFxPresetParameter(p, "drive", 15f);
Voip.SetFxPresetParameter(p, "distortion_mix", 10f);
Voip.SetFxPresetParameter(p, "compressor_threshold", -20f);
Voip.SetFxPresetParameter(p, "compressor_ratio", 4f);
Voip.SetFxPresetParameter(p, "compressor_attack", 0.005f);
Voip.SetFxPresetParameter(p, "compressor_release", 0.1f);
Voip.SetFxPresetParameter(p, "compressor_gain", 3f);
var p = Voip.CreateFxPreset("megaphone");
Voip.SetFxPresetParameter(p, "highpass", 600f);
Voip.SetFxPresetParameter(p, "lowpass", 4500f);
Voip.SetFxPresetParameter(p, "eq_freq", 2500f);
Voip.SetFxPresetParameter(p, "eq_gain", 6f);
Voip.SetFxPresetParameter(p, "distortion_type", 2f);
Voip.SetFxPresetParameter(p, "drive", 50f);
Voip.SetFxPresetParameter(p, "distortion_mix", 50f);
Voip.SetFxPresetParameter(p, "compressor_threshold", -18f);
Voip.SetFxPresetParameter(p, "compressor_ratio", 8f);
Voip.SetFxPresetParameter(p, "compressor_attack", 0.003f);
Voip.SetFxPresetParameter(p, "compressor_release", 0.08f);
Voip.SetFxPresetParameter(p, "compressor_gain", 3f);
Voip.SetFxPresetParameter(p, "reverb_mix", 15f);
var p = Voip.CreateFxPreset("robot");
Voip.SetFxPresetParameter(p, "tremolo_depth", 100f);
Voip.SetFxPresetParameter(p, "tremolo_rate", 50f);
Voip.SetFxPresetParameter(p, "pitch", -300f);
Voip.SetFxPresetParameter(p, "flanger_mix", 25f);
Voip.SetFxPresetParameter(p, "flanger_rate", 0.2f);
Voip.SetFxPresetParameter(p, "flanger_feedback", 0.6f);
var p = Voip.CreateFxPreset("cave");
Voip.SetFxPresetParameter(p, "reverb_mix", 55f);
Voip.SetFxPresetParameter(p, "reverb_decay", 4f);
Voip.SetFxPresetParameter(p, "reverb_predelay", 45f);
Voip.SetFxPresetParameter(p, "reverb_damping", 4f);
Voip.SetFxPresetParameter(p, "delay_mix", 15f);
Voip.SetFxPresetParameter(p, "delay_feedback", 30f);
What players control¶
Players set their own volumes in the game's audio settings, and bundles can't change them:
| Setting | Affects |
|---|---|
| Output Volume Spatial | Other players' spatial voice |
| Output Volume Direct | Voice over direct connections |
| Output Volume Broadcast | Everything played from speakers |
| Output Volume Media Spatial | Web content and video on in-game screens |
| Output Volume Media Direct | Web content and video in the player's own interface |
Network cost¶
Voice audio itself is what costs bandwidth. Each player's voice is sent to every listener who can hear them, at the server's voice bitrate, while they transmit. Nothing is sent while nobody talks. A mic host also uploads one stream for the mic it captures.
The API itself is cheap, only changes are networked. This means that you should aim to reduce voice ranges and target lists to the smallest amount of targets needed at any given time.
The server is optimized for throughput, but very large crodws of people are yet to be tested.