feat: smooth volume ramping over 200ms in audio streaming
Volume changes now ramp linearly per-sample via _scale_pcm_ramp instead of jumping abruptly. Each frame steps _cur_vol toward target by at most 0.1, giving ~200ms for a full 0-to-1 sweep. Fast path unchanged when volume is stable.
This commit is contained in:
@@ -2,13 +2,14 @@
|
||||
|
||||
import asyncio
|
||||
import struct
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from derp.mumble import (
|
||||
MumbleBot,
|
||||
MumbleMessage,
|
||||
_escape_html,
|
||||
_scale_pcm,
|
||||
_scale_pcm_ramp,
|
||||
_shell_quote,
|
||||
_strip_html,
|
||||
)
|
||||
@@ -583,3 +584,64 @@ class TestShellQuote:
|
||||
quoted = _shell_quote(url)
|
||||
assert quoted.startswith("'")
|
||||
assert quoted.endswith("'")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# TestPcmRamping
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestPcmRamping:
|
||||
def test_flat_when_equal(self):
|
||||
"""When vol_start == vol_end, behaves like _scale_pcm."""
|
||||
pcm = struct.pack("<hh", 1000, -1000)
|
||||
result = _scale_pcm_ramp(pcm, 0.5, 0.5)
|
||||
expected = _scale_pcm(pcm, 0.5)
|
||||
assert result == expected
|
||||
|
||||
def test_linear_interpolation(self):
|
||||
"""Volume ramps linearly from start to end across samples."""
|
||||
pcm = struct.pack("<hhhh", 10000, 10000, 10000, 10000)
|
||||
result = _scale_pcm_ramp(pcm, 0.0, 1.0)
|
||||
samples = struct.unpack("<hhhh", result)
|
||||
# At i=0: vol=0.0, i=1: vol=0.25, i=2: vol=0.5, i=3: vol=0.75
|
||||
assert samples[0] == 0
|
||||
assert samples[1] == 2500
|
||||
assert samples[2] == 5000
|
||||
assert samples[3] == 7500
|
||||
|
||||
def test_clamp_positive(self):
|
||||
"""Ramping up with loud samples clamps to 32767."""
|
||||
pcm = struct.pack("<h", 32767)
|
||||
result = _scale_pcm_ramp(pcm, 2.0, 2.0)
|
||||
samples = struct.unpack("<h", result)
|
||||
assert samples[0] == 32767
|
||||
|
||||
def test_clamp_negative(self):
|
||||
"""Ramping up with negative samples clamps to -32768."""
|
||||
pcm = struct.pack("<h", -32768)
|
||||
result = _scale_pcm_ramp(pcm, 2.0, 2.0)
|
||||
samples = struct.unpack("<h", result)
|
||||
assert samples[0] == -32768
|
||||
|
||||
def test_preserves_length(self):
|
||||
"""Output length equals input length."""
|
||||
pcm = b"\x00" * 1920
|
||||
result = _scale_pcm_ramp(pcm, 0.0, 1.0)
|
||||
assert len(result) == 1920
|
||||
|
||||
def test_empty_data(self):
|
||||
"""Empty input returns empty output."""
|
||||
result = _scale_pcm_ramp(b"", 0.0, 1.0)
|
||||
assert result == b""
|
||||
|
||||
def test_reverse_direction(self):
|
||||
"""Volume ramps down from start to end."""
|
||||
pcm = struct.pack("<hhhh", 10000, 10000, 10000, 10000)
|
||||
result = _scale_pcm_ramp(pcm, 1.0, 0.0)
|
||||
samples = struct.unpack("<hhhh", result)
|
||||
# At i=0: vol=1.0, i=1: vol=0.75, i=2: vol=0.5, i=3: vol=0.25
|
||||
assert samples[0] == 10000
|
||||
assert samples[1] == 7500
|
||||
assert samples[2] == 5000
|
||||
assert samples[3] == 2500
|
||||
|
||||
Reference in New Issue
Block a user