/
/
/
1"""Unit tests for the _pcm_bytes_to_audio decoder."""
2
3import struct
4
5import numpy as np
6from music_assistant_models.enums import ContentType
7from music_assistant_models.media_items import AudioFormat
8
9from music_assistant.providers.sonic_analysis import _pcm_bytes_to_audio
10
11
12def _af(content_type: ContentType, channels: int = 1) -> AudioFormat:
13 """Build a minimal AudioFormat for the PCM decoder tests."""
14 return AudioFormat(content_type=content_type, channels=channels)
15
16
17def _make_pcm_16bit(samples: list[int]) -> bytes:
18 """Build raw 16-bit little-endian PCM bytes from integer sample values."""
19 return struct.pack(f"<{len(samples)}h", *samples)
20
21
22def _make_pcm_32bit_int(samples: list[int]) -> bytes:
23 """Build raw 32-bit signed little-endian PCM bytes from integer sample values."""
24 return struct.pack(f"<{len(samples)}i", *samples)
25
26
27def _make_pcm_32bit_float(samples: list[float]) -> bytes:
28 """Build raw 32-bit float little-endian PCM bytes from float sample values."""
29 return struct.pack(f"<{len(samples)}f", *samples)
30
31
32def test_pcm_16bit_mono() -> None:
33 """16-bit mono: max positive sample should convert to ~1.0."""
34 pcm = _make_pcm_16bit([0, 16384, -16384, 32767])
35 audio = _pcm_bytes_to_audio(_af(ContentType.PCM_S16LE), pcm)
36 assert audio.dtype == np.float32
37 assert len(audio) == 4
38 assert abs(audio[0]) < 1e-6
39 assert abs(audio[1] - 0.5) < 0.001
40 assert abs(audio[2] + 0.5) < 0.001
41 assert abs(audio[3] - 1.0) < 0.001
42
43
44def test_pcm_16bit_stereo_downmix() -> None:
45 """16-bit stereo: two channels should be averaged to mono."""
46 pcm = _make_pcm_16bit([32767, 0, 0, 32767])
47 audio = _pcm_bytes_to_audio(_af(ContentType.PCM_S16LE, channels=2), pcm)
48 assert len(audio) == 2
49 assert abs(audio[0] - 0.5) < 0.001
50 assert abs(audio[1] - 0.5) < 0.001
51
52
53def test_pcm_s32_mono() -> None:
54 """Signed 32-bit int mono: max positive sample should convert to ~1.0."""
55 pcm = _make_pcm_32bit_int([0, 2147483647])
56 audio = _pcm_bytes_to_audio(_af(ContentType.PCM_S32LE), pcm)
57 assert audio.dtype == np.float32
58 assert len(audio) == 2
59 assert abs(audio[0]) < 1e-6
60 assert abs(audio[1] - 1.0) < 0.01
61
62
63def test_pcm_f32_mono_round_trips() -> None:
64 """Float32 PCM passes through verbatim â the case the bit-depth-only dispatch broke."""
65 pcm = _make_pcm_32bit_float([0.0, 0.5, -0.5, 1.0])
66 audio = _pcm_bytes_to_audio(_af(ContentType.PCM_F32LE), pcm)
67 assert audio.dtype == np.float32
68 np.testing.assert_array_almost_equal(audio, np.array([0.0, 0.5, -0.5, 1.0], dtype=np.float32))
69
70
71def test_pcm_24bit_mono() -> None:
72 """24-bit mono: verify positive and negative values convert correctly."""
73 pos_max = (0x7FFFFF).to_bytes(3, byteorder="little", signed=False)
74 zero = (0).to_bytes(3, byteorder="little", signed=False)
75 neg_one = (0xFFFFFF).to_bytes(3, byteorder="little", signed=False)
76 pcm = zero + pos_max + neg_one
77 audio = _pcm_bytes_to_audio(_af(ContentType.PCM_S24LE), pcm)
78 assert len(audio) == 3
79 assert abs(audio[0]) < 1e-6
80 assert abs(audio[1] - 1.0) < 0.001
81 assert abs(audio[2] + (1.0 / 8388608.0)) < 0.001
82