Files
3d_audio/lib_audio_dsp/test/reverb/test_reverb_converters.py

165 lines
4.2 KiB
Python
Raw Normal View History

2025-12-11 09:43:42 +08:00
# Copyright 2024-2025 XMOS LIMITED.
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
import pytest
import numpy as np
from audio_dsp.dsp.reverb_base import Q_VERB
from audio_dsp.dsp.reverb import reverb_room
from audio_dsp.dsp.reverb_stereo import reverb_room_stereo
from audio_dsp.dsp.reverb_plate import reverb_plate_stereo
from subprocess import run
from pathlib import Path
import os
CWD = Path(__file__).parent
TOL_KWARGS = dict(rtol=2**-16, atol=0)
LESS_THAN_1 = ((2**Q_VERB) - 1) / (2**Q_VERB)
def q_verb(x):
return int(x * 2**Q_VERB)
def db2lin(db):
return 10 ** (db / 20)
def new_reverb(**kwargs):
return reverb_room(48000, 1, **kwargs)
def get_c(config, val):
bin_dir = CWD / "bin" / config
out_dir = CWD / "bin" / f"{config}_{val}"
os.makedirs(out_dir, exist_ok=True)
sig_fl32 = np.array(val).astype(np.float32)
name = "test_vector"
sig_fl32.tofile(out_dir / f"{name}.bin")
xe = bin_dir / f"reverb_converters_{config}.xe"
run(["xsim", str(xe)], check=True, cwd=out_dir)
#print(out_dir)
return np.fromfile(out_dir / "out_vector.bin", dtype=np.int32)
def db2int(db):
return q_verb(db2lin(db))
def get_output(config, input, sattr, gattr):
c_val = get_c(config, input)[0]
r = new_reverb()
setattr(r, sattr, input)
p_val = getattr(r, gattr)
return c_val, p_val
@pytest.mark.parametrize(
"sattr,gattr",
[
["wet_db", "wet_int"],
["dry_db", "dry_int"],
],
)
@pytest.mark.parametrize(
"input,expected",
[
[-6, db2int(-6)],
[0, q_verb(1)],
[1, q_verb(1)],
[6, q_verb(1)],
],
)
def test_reverb_db2int(sattr, gattr, input, expected):
np.testing.assert_allclose(
get_output("DB2INT", input, sattr, gattr), expected, **TOL_KWARGS
)
@pytest.mark.parametrize(
"input,expected",
[
[0, q_verb(0.7)],
[-1, q_verb(0.7)],
[1, q_verb(0.98)],
[1.1, q_verb(0.98)],
],
)
def test_reverb_decay2feedback(input, expected):
np.testing.assert_allclose(
get_output("DECAY2FEEDBACK", input, "decay", "feedback_int"),
expected,
**TOL_KWARGS,
)
@pytest.mark.parametrize(
"input,expected",
[
[-0.5, 0],
[0, 0],
[0.5, q_verb(0.5)],
[LESS_THAN_1, q_verb(LESS_THAN_1)],
[1, q_verb(LESS_THAN_1)],
[2, q_verb(LESS_THAN_1)],
],
)
def test_reverb_float2int(input, expected):
np.testing.assert_allclose(
get_output("FLOAT2INT", input, "pregain", "pregain_int"), expected, **TOL_KWARGS
)
@pytest.mark.parametrize(
"input,expected",
[
[-0.5, 1],
[0, 1],
[0.5, q_verb(0.5)],
[LESS_THAN_1, q_verb(LESS_THAN_1)],
[1, q_verb(LESS_THAN_1)],
[2, q_verb(LESS_THAN_1)],
],
)
def test_reverb_damping(input, expected):
np.testing.assert_allclose(
get_output("CALCULATE_DAMPING", input, "damping", "damping_int"),
expected,
**TOL_KWARGS,
)
@pytest.mark.parametrize(
"input", [-1, 0, 0.07, 0.23, 0.5, 0.64, 0.92, 1, 2]
)
def test_reverb_wet_dry_mix_conv(input):
c_vals = get_c("WET_DRY_MIX", input)
r = new_reverb()
r.set_wet_dry_mix(input)
p_vals = np.array([r.dry_int, r.wet_int], dtype=np.int32)
# -23 cause of the float to int conversion
np.testing.assert_allclose(c_vals, p_vals, rtol=2**-23, atol=0)
@pytest.mark.parametrize(
"input", [-1, 0, 0.07, 0.23, 0.5, 0.64, 0.92, 1, 2]
)
def test_reverb_st_wet_dry_mix_conv(input):
c_vals = get_c("WET_DRY_MIX_ST", input)
r = reverb_room_stereo(48000, 2)
r.set_wet_dry_mix(input)
p_vals = np.array([r.dry_int, r.wet_1_int, r.wet_2_int], dtype=np.int32)
# -23 cause of the float to int conversion
np.testing.assert_allclose(c_vals, p_vals, rtol=2**-23, atol=0)
@pytest.mark.parametrize(
"input", [-1, 100, 1000, 2000, 4000, 8000, 16000, 32000]
)
def test_reverb_cutoff(input):
c_vals = get_c("CUTOFF", input)
r = reverb_plate_stereo(48000, 2)
r.bandwidth = input
p_vals = r.lowpasses[0].coeff_b0_int
# -23 cause of the float to int conversion
np.testing.assert_allclose(c_vals, p_vals, rtol=2**-13, atol=0)
if __name__ == "__main__":
test_reverb_cutoff(8000)