Skip to content

Server: read the fade-in gain once per channel per frame - #3959

Open
mcfnord wants to merge 1 commit into
jamulussoftware:mainfrom
mcfnord:fadein-perframe
Open

mcfnord wants to merge 1 commit into
jamulussoftware:mainfrom
mcfnord:fadein-perframe

Conversation

@mcfnord

@mcfnord mcfnord commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

🤖 AI: Follow-up to #3948, opened at ann0see's request for the change described in #3945. Rebased onto main after #3948 merged; the diff is the one commit.

Short description of changes

The pair loop in DecodeReceiveData calls GetFadeInGain() twice per channel pair, once for the source channel and once for the current one: 2N²−N int-to-float conversions and divisions per frame, on N values that do not change within the frame. Both counters are written only in PutAudioData and OnNetTranspPropsReceived, each reached under CServer::Mutex (audio, protocol), and OnTimer holds that mutex from the connected-channel scan through both decode paths, single-threaded and thread pool alike.

The commit reads each connected channel's fade-in gain into vecfFadeInGains in the loop that builds vecChanIDsCurConChan, in the same order and inside the same locked block, and the pair loop multiplies from that vector. The same two factors are applied in the same order as before, so the gain row that reaches the mixer is unchanged. vecfFadeInGains is sized to iMaxNumChannels with the other per-frame vectors, so the timer path still allocates nothing. GetFadeInGain() keeps one call site.

Two review notes from #3948 are folded in as pljones suggested: the getter's comment is reflowed wider on one block, and the two in-loop comments become one above the loop that names both fade-in factors and #628.

Per-client server cost on the rig behind the #3948 numbers: Raspberry Pi 4, headless serveronly, server under -T, both arms rebuilt clean in one session (the #3948 head and the #3948 head plus this commit), three reps per cell with the arms interleaved inside each cell, perf stat over 30 s of steady load:

N instructions/client, #3948 head with this commit Δ instructions Δ cycles
12 1.291 bn ±1.8 M 1.283 bn ±5.7 M −0.61% +1.28%
24 1.240 bn ±2.6 M 1.237 bn ±0.4 M −0.30% +0.19%
36 1.247 bn ±0.7 M 1.243 bn ±0.9 M −0.36% +0.42%
48 1.268 bn ±3.5 M 1.257 bn ±4.6 M −0.92% +0.35%

Instructions separate at every N and cycles at none: all four cycle deltas sit inside ±1 sd, so the time saved is below this rig's resolution at N ≤ 48, where the #3945 benchmark puts the removed term at 10.0 µs per frame at N=50. What is measured here is the work, fewer instructions per client at every size and most at N=48; the rig saturates at N=60, so the sizes where the term reaches percent of a frame are out of its reach.

All 24 cells cleared the saturation gate, delivered ratio 1.011 against a 0.99 floor, and every client was mixed in every cell.

Builds: x86-64 (g++ 13.3) and aarch64 (g++ 14.2), CONFIG+=headless serveronly, zero warnings in server.cpp/server.h, the server up after 6 s on both; clang-format-14 -style=file byte-clean on both files.

CHANGELOG: Server: Reduced the mixing cost per frame by computing each channel's fade-in gain once per frame instead of once per channel pair

Context: Fixes an issue?

Fixes: #3945, which was itself opened at ann0see's request on the fork PR behind #3948.

Does this change need documentation? What needs to be documented and how?

No.

Status of this Pull Request

Working implementation, rebased onto main after #3948 merged.

What is missing until this pull request can be merged?

Review.

Checklist

  • I've verified that this Pull Request follows the general code principles
  • I tested my code and it does what I want
  • My code follows the style guide
  • I waited some time after this Pull Request was opened and all GitHub checks completed without errors.
  • I've filled all the content above

🤖 This message was written by AI and reviewed by @mcfnord.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: QUIET

Plan: Advanced

Run ID: 469d07df-5cf3-4e10-a4ea-bffc00f3d312

📥 Commits

Reviewing files that changed from the base of the PR and between 100f2cc and d01be45.

📒 Files selected for processing (1)
  • src/server.cpp

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The server now reads each connected channel’s fade-in gain once per frame in OnTimer(). DecodeReceiveData() uses those stored gains when scaling channel contributions.

Changes

Channel gain processing

Layer / File(s) Summary
Per-frame fade-in integration
src/server.h, src/server.cpp
CServer stores fade-in gains for connected channels. OnTimer() updates the values once per frame, and DecodeReceiveData() uses them while retaining the bulk gain-and-panning fetch.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Refactor

Suggested reviewers: softins

Merge Risk: ⚪ Minimal · up to d01be

The supplied evidence indicates cached gains are populated and applied consistently. No actionable merge-blocking risk is identified.

Architecture Summary

Architecture risk: 🔵 Low · up to d01be

The change affects 1 system.

Changed systems: src

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in src/server.h: Added vecfFadeInGains to CServer for storing fade-in gain values.
  • observed — Modified behavior in src/server.cpp: Allocates vecfFadeInGains with capacity for iMaxNumChannels.
  • observed — Modified behavior in src/server.cpp: While holding the server mutex, OnTimer() stores each connected channel’s fade-in gain once per frame, indexed by its position in vecChanIDsCurConChan.
  • observed — Modified behavior in src/server.cpp: DecodeReceiveData() retains the bulk gain-and-panning fetch, replacing per-client GetFadeInGain() calls with values read in OnTimer(). Each connected source’s fade-in scales its own contribution; for other sources, the current channel’s fade-in is applied as well.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies [#3945]. CServer stores a pre-sized CVector<float> and reads each connected channel's GetFadeInGain() once while OnTimer() builds the connected-channel list. `DecodeReceiveDat…
Out of Scope Changes check ✅ Passed The reviewed changes stay within [#3945]. The new member, allocation, per-frame population, pair-loop substitution, and comment updates support the fade-in gain cache. No unrelated product or subsyste…
Title check ✅ Passed The title clearly and concisely describes the main change: caching each channel's fade-in gain once per frame in the server.
Description check ✅ Passed The description follows the required template and provides the change summary, changelog entry, issue context, documentation impact, status, remaining work, testing details, and completed checklist.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ann0see

ann0see commented Oct 1, 2026

Copy link
Copy Markdown
Member

@mcfnord please rebase this and resolve conflicts.

DecodeReceiveData() called CChannel::GetFadeInGain() twice per channel
pair, so every frame performed 2N^2-N int-to-float conversions and
divisions on N values that do not change within the frame: the fade-in
counters are only written under CServer::Mutex, which OnTimer() holds
for the whole decode phase.

Read each connected channel's fade-in gain into vecfFadeInGains in the
loop that builds vecChanIDsCurConChan, in the same order and under the
same lock, and multiply from that vector instead. The two factors are
applied in the same order as before.

Closes jamulussoftware#3945

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

Server: compute the fade-in gain once per channel per frame, not once per channel pair

2 participants