jamulussoftware / jamulussoftware/jamulus

Input Boost can clip with the clip indicator dark: the level meter reads only negative samples

Open
#3,951 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
C
Stars
1.1k
Forks
248
Avg merge
2d 3h
Merged PRs (30d)
9

Description

**🤖 AI:** Input Boost can drive the signal into hard clipping while the clip indicator stays dark and the feedback auto-mute stays silent, because the level meter reads only negative samples.

**Describe the bug**

[#3834](https://github.com/jamulussoftware/jamulus/pull/3834) made Input Boost clamp with [`Float2Short`](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/util.h#L116-L128) instead of wrapping, so overload now clips. [The level meter](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/client.cpp#L1544-L1556) runs on that same buffer inside the same block, and [its loop](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/util.cpp#L129-L160) takes [`std::min` over negatives](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/util.h#L775) only, every third one. A clip that goes positive reads zero.

One cause, two consumers: the [clip indicator](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/levelmeter.cpp#L319-L326) and the [feedback auto-mute](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/clientdlg.cpp#L1086-L1088) both read the single value [`Update()` writes](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/util.h#L759-L777).

**To Reproduce**

A real client sending into a real server, with the server's own recording as the evidence and the client's clip indicator read off the screen. Three arms, same source peak, only the skew differs:

| source, Input Boost | within 1% of full scale, positive | negative | clip indicator |
|---|---|---|---|
| symmetric, 4x | 15.16% | 15.16% | lights |
| 6:1 positive-skewed, 4x | 15.05% | 0.00% | dark |
| 6:1 positive-skewed, 10x | 29.41% | 0.00% | dark |
| 2:1 positive-skewed, 4x | 16.37% | 0.00% | dark |
| 2:1 positive-skewed, 10x | 31.16% | 22.59% | lights |

The control and the 4x skewed arm clip by the same amount. The last two rows set the boundary: at a 2:1 crest asymmetry, which is the most any real recording to hand reaches over a loud 100 ms window, the indicator is still dark on 16.37% clipped samples at 4x — and lights at 10x only because that boost drives the quiet half to full scale as well. So what hides a clip is the quiet half staying below full scale, not the ratio itself.

**Expected behavior**

Clipping lights the clip indicator whichever way the waveform clips.

**Version of Jamulus**

3.12.5dev, built at `267bf6b7`. The end-to-end arm ran on Linux (g++ 13.3.0, Qt 5.15.13, JACK). The meter itself was additionally built and run from the same `src/util.cpp` on macOS 12.7.6 (Apple clang 14, Qt 5.15.2), on Windows 11 (MSVC 19.44, **Qt 6.10.2**) and on **aarch64** Linux (g++ 14.2.0, Qt 5.15.15): all four emit byte-identical output over 54 lines of arms, so neither the platform, the architecture, nor the Qt major version changes what the indicator does.

**Additional context**

- Driving the shipped `CStereoSignalLevelMeter` directly gives the same split at block level: at Input Boost 4x a symmetric and a 6:1 skewed source both pin 18.6% of samples at full scale, and only the symmetric one lights the indicator.
- The auto-mute inherits the bias and does more than light an indicator: it [mutes the channel](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/clientdlg.cpp#L1091-L1092) and opens a modal. Run end to end with detection enabled, the symmetric control at 4x opens *"Audio feedback or loud signal detected"* and it stays up for 11 of 14 sampled frames, while the 6:1 skewed source at 4x and at 10x never opens it. Its reach is narrow either way: [`DETECT_FEEDBACK_TIME_MS`](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/clientdlg.h#L90) is a single-shot 3-second window armed on connection, so it never sees a level that rises later in a session.
- The indicator needs [exactly `-32768`](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/global.h#L318-L319): an all-`-32767` block reads 8.0000000 and stays dark, all-`-32768` reads 8.0000424 and lights. corrados [intended that](https://github.com/jamulussoftware/jamulus/issues/423#issuecomment-653788939) in 2020, when nothing on this path applied gain above 1.
- Decimation: the stereo loop steps `i += 6` reading `[i]` and `[i+1]`, so four of every six sample positions are never examined. A single-sample clip is seen at interleaved index 0 and 1 and missed at 2, 3, 4 and 5.
- The recording is taken after Opus, which rings around a flat top, so "within 1% of full scale" is the measure rather than a count of exact `+32767` samples.
- The server keeps its own meter of the same type — [`CChannel::SignalLevelMeter`](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/channel.h#L252) is also a `CStereoSignalLevelMeter` — so it inherits the same bias, and the [channel meter](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/audiomixerboard.cpp#L775) it feeds over [`CLM_CHANNEL_LEVEL_LIST`](https://github.com/jamulussoftware/jamulus/blob/267bf6b762202954200839135778cdc194a2ab4e/src/protocol.h#L105) has its own clip indicator. In the runs above that indicator is dark on exactly the arms where the client's is, and lit on exactly the arms where the client's is. Two meters, computed independently at each end, agree row for row.
- The same thing happens on Windows through ASIO rather than JACK, using the [CI build](https://github.com/jamulussoftware/jamulus/actions/runs/34758695683) of this commit: with the 6:1 source at Input Boost 4x the server's recording reaches `+32767` on 7.57% of samples and −7797 at the other extreme, while the client's input meter carries no red pixel at all.
- Not measured: the end-to-end client path on macOS, and any real recording with a crest asymmetry beyond 2:1.

---

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

Contributor guide

Open the contributing guide

Research direction

Start in src/util.cpp and src/util.h at CStereoSignalLevelMeter::Update and trace the value consumed by src/levelmeter.cpp and src/clientdlg.cpp. Reproduce the positive-skewed and symmetric cases described in the issue, then verify that positive and negative clipping are detected consistently by both the clip indicator and feedback auto-mute.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
audio-video-rtc
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.