Skip to content

Voip integrated with filtering settings - #23

Open
absol89 wants to merge 12 commits into
devfrom
voip-integrated
Open

absol89 wants to merge 12 commits into
devfrom
voip-integrated

Conversation

@absol89

@absol89 absol89 commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Why?

Other voip solutions were either a) not allowed in this fork, b) not preferred after mumble investigation. So: c) Opus integrated

image

Built-in proximity voice chat

Players can talk to each other in game over the existing game connection. There's no Mumble, no separate program and no extra port, so it works the same on VPS-hosted servers and on servers players host themselves.

How it works

  • Client: the microphone is captured with miniaudio (48 kHz mono, 20 ms frames), scaled by the mic volume, put through the noise gate and Opus-encoded at 24 kbit/s. Frames are sent unreliably (kUnreliableNoNagle), about 3 KB/s per player while talking.
  • Server: relays each frame to players in cell range of the speaker who have voip on and whose channel (Everyone, Party or Strangers) includes the speaker. It never decodes audio. Each speaker is rate-limited to 75 frames per second.
  • Playback: each remote player has an Opus decoder (packet loss concealment covers up to 3 missing frames) and a 60–200 ms jitter buffer. Voices are mixed on the voip output device.
  • Distance: a voice is full volume up to the fade start, falls off linearly, and is silent at twice that distance, measured from the camera. Voices are panned left and right with constant power (at most 70%) by direction from the camera.
  • Who is talking: a player counts as talking while their frames arrive. The UI shows one speaker wave (below their gate) or two (talking).

Settings

All voice settings are in [Voice] of Data/SkyrimTogetherReborn/config/STServer.ini. bVoip is one switch: for a server it allows voip, for a player it is the Connect to voip checkbox. Voice runs only when both the server's and the player's bVoip are on.

Key Default Notes
bVoip false Server: allow voice. Player: Connect to voip
sInputDevice empty (None) Nothing is captured or sent until a device is picked. default follows Windows
sOutputDevice empty The Windows default device, which the game normally uses
sVoipChannel everyone everyone, party or strangers
fVoipFadeStart 2500 Game units. Silent at twice this
fMicVolume 1.0 0 to 1
fNoiseGate 0.10 0 to 1 peak level, the same scale as the Windows "Test your microphone" bar, held open 300 ms
  • Per-player volumes: these are saved in the UI store as playerUi_voiceVolumes, keyed by username, so they carry across servers and sessions.
  • Saving STServer.ini: SaveSettingsToIni now starts from the file on disk, so the server keeps keys it doesn't own (the player's [Voice] keys) instead of rewriting the file from scratch.

UI

  • Settings: a new Voice chat section under Audio with input device, Connect to voip, output device, channel and fade start.
  • Player List and Party Menu tabs: a Voip column. The header mic opens Your microphone volume and a Noise gate slider with a live level meter and the gate marked on it. Each player's speaker opens <Name> volume.
  • Speaker icons: crossed out and grey when voip is off or the channel excludes that player, crossed out and red at 0, one wave when quiet, two waves while talking. Party members use the party colour.
  • F2 menu: unchanged.
  • Mockup: https://claude.ai/artifact/1nRX7H2SXCw1SewTpAb8Gj (v14)

Protocol

  • New messages: VoiceDataRequest, VoiceStateRequest and NotifyVoiceData. Frame data is capped at 512 bytes when reading.
  • Server settings: ServerSettings.VoipEnabled is new.
  • Compatibility: opcodes are appended, and the build-commit check already rejects mismatched clients.

Dependencies and licences

  • libopus 1.5.2 (BSD-3-Clause), Windows client only. THIRD_PARTY_NOTICES.txt has its notice and is copied next to SkyrimTogether.exe on install, so it ends up in the release zip.
  • miniaudio 0.11.22 (public domain / MIT-0), Windows client only, header-only.

Reverse engineering (Ghidra)

  • Verified in Ghidra on SkyrimSE 1.7.104:
    • NiAVObject world transform: rotate at 0x7C, translate at 0xA0 (NiAVObject::UpdateWorldData).
    • TESCamera::cameraNode at +0x20 (camera position getter, ID 50785).
    • The world rotate's Y column is the camera's forward (audio listener update, ID 40449).
  • Assumed: the same offsets on 1.6.1170. Not checked; AGENTS.md says that database should be checked first.
  • No new hooks or address IDs: the code uses the existing PlayerCamera::Get, PlayerCharacter::Get and actor positions. If the camera node can't be read, the player's position and heading are used instead.

CEF and the Skyrim Together UI

  • Already used in the UI: FontAwesome icons, range and number inputs, app-dropdown and the existing slider styling.
  • New to the app: CDK Overlay for the popups (@angular/cdk was already a dependency). cdk/overlay-prebuilt.css is now imported in styles.scss.
  • Font sizes: all sizes are in rem/em, so the new parts follow the Settings font size (it sets the root font size). Checked at XS and XL in the browser mock.
  • Text: new strings are in en.json under COMPONENT.SETTINGS.VOICE and COMPONENT.VOIP. Other languages fall back to English. Device names come from Windows and aren't translated.
  • Mockups: the HTML mockups followed the menu's look but were drawn at a fixed size. They were not checked against the font size setting; the implementation was.
  • In-game CEF: not tested yet (see below).

Tested

  • Unit tests: the encoding round trip for the 3 new messages, oversize frames rejected, and the noise gate, falloff, panning, sequence wrap and jitter buffer (TPTests, built and run with g++ on Linux)
  • Server, encoding and console changes compile with g++ (Linux)
  • VoiceAudio.cpp (miniaudio and Opus) compiles with g++ against the real headers
  • UI production build (ng build --configuration=production)
  • UI in the browser mock: the Settings section, dropdowns, Connect, the Voip column on both tabs, mic popup meter and gate, per-player volume saved by name, party colour, one or two waves, font sizes XS and XL
  • Windows client build (MSVC), 1.7.104 and 1.6.1170: CI
  • In game, two clients and a local server: hearing each other, falloff and panning, channels, noise gate, mic and player volumes
  • In game, with the server's bVoip=false: no audio is captured or relayed
  • The UI in the in-game CEF overlay: popups, meter and dropdowns
  • Device changes: unplugging a device, a device missing at startup, a non-ASCII device name
  • Editing STServer.ini by hand while the game runs (it is re-read when Settings opens)

Known limitations and follow-ups

  • Output device: "Game audio device" is the Windows default output device. If someone moved the game to another device, voices still go to the default.
  • Server switch in the UI: the UI doesn't know the server's bVoip, so speakers can look on when the server has voip off.
  • No push-to-talk yet: the noise gate is the only trigger.
  • Volume range: 0–100%, no boost above 100%.
  • Shared ini: a server started from the game folder rewrites STServer.ini on shutdown with the values it loaded at startup. A bVoip change made in game while that server runs can be lost.

Players talk over the existing game connection, no separate program or
port. Clients send Opus frames unreliably, the server relays them to
players in cell range who have voip on and want that speaker's channel
(everyone, party or strangers), with a per speaker rate limit.

Voice:bVoip in STServer.ini turns it on for a server and is sent to
clients in the server settings. The game client keeps its own [Voice]
keys in the same file, so saving the server's ini now keeps keys it
doesn't own instead of rewriting the file from scratch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
The microphone is captured with miniaudio, gated, Opus encoded and sent
to the server. Remote voices are decoded into small jitter buffers and
mixed on the voip output device, full volume up to the fade start and
silent at twice that distance from the camera, panned left and right.

Settings live in [Voice] of Data/SkyrimTogetherReborn/config/STServer.ini:
input and output device, bVoip (Connect to voip, the same key a server
uses), channel, fade start, mic volume and noise gate. The input device
defaults to none, so nothing is captured until a player picks one.

The noise gate, falloff, panning and jitter buffer are game independent
and unit tested in TPTests. The camera transform offsets were checked in
Ghidra on 1.7.104 earlier (see the comment in VoiceService.cpp).

New dependencies: libopus 1.5.2 (BSD-3-Clause) and miniaudio 0.11.22
(public domain or MIT-0), Windows client only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
Settings gets a Voice chat section: input device (None by default),
Connect to voip, output device (the game's device by default), channel
and fade start. The Player List and Party Menu tabs get a Voip column:
the header mic opens your microphone volume and a noise gate with a live
level meter, each player's speaker opens their volume. Speakers show one
wave when the player is quiet and two while their voice gets through,
party members in the party colour.

Per player volumes are kept in the UI store as playerUi_voiceVolumes,
by username, so they carry across servers and sessions. The F2 menu
itself is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
Opus is BSD-3-Clause, which asks binary redistributions to include its
copyright notice and licence. THIRD_PARTY_NOTICES.txt is copied next to
SkyrimTogether.exe on install, so it ends up in the release zip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
@absol89

absol89 commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner Author

Testing in game 2 Nov:
imageimageimage

default non selected device makes my laptop on 1.7.104 not send voice, it received noise gated voip from my 1.6.1170 pc.
laptop had the stranger setting, and after joining the party, the 1.6.1170 PC could hear them if they selected a mic. That player was on Everyone setting. So the settings and filters check out. After partying the laptop didnt send to the party channel, only to strangers. The max range was tested with the same range the 5000 unit nametags fade out at.

Upstream dev renamed TESCamera::cameraNode (NiNode*) to cameraRoot
(NiPointer<NiNode>). Both are a single pointer at 0x20, so read it by
offset and the voice listener compiles against either header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
@absol89

absol89 commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner Author

Follow ups from testing: Need a volume slider scalar from other people's voip. default 100% and between 0 to 500%?

Push to talk still deferred as it gets a bit frantic in combat and people would forget to use it, or not set it up properly.

image

absol89 and others added 6 commits November 3, 2026 18:33
Default received voice playback to 250 percent and save the setting in STServer.ini. Multiply per-player and distance gains without changing microphone capture, and retain the final output clamp.

Validation: client release build and production UI build passed; TPTests passed 134 assertions in 12 cases. In-game listening and persistence checks remain.
The master volume slider still goes up to 500%. Invalid saved values now
fall back to 100% too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxNiMZ7kPLL1PGMuxYo
On the world map the camera sits high in the sky, so every nearby player
fell outside the voice range. When the camera is more than 1000 units
from the character (map, free camera, some cutscenes), use the
character's position and heading as the listener instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
The map camera check now uses the player's fade start (2500 by default)
instead of a fixed 1000 units, so one setting controls both. Vanilla
third person tops out around 750 units, which leaves room for camera
mods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
Past the fade start voices now lose 6 dB each time the distance doubles
(inverse square law) instead of dropping in a straight line, and ease
out to silence between 2x and 4x the fade start instead of cutting off
at 2x. The default fade start goes from 2500 to 2000: full volume to
about 29 m, -6 dB at 57 m, about -16 dB at 86 m, silent at 114 m.
Saved ini values are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
The settings hint names where voices start to fade and where they go
silent, without describing the curve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fvQxRbmmR1Uv7KRvfWVgW
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants