init
This commit is contained in:
78
lib_audio_dsp/test/reverb/CMakeLists.txt
Normal file
78
lib_audio_dsp/test/reverb/CMakeLists.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake)
|
||||
|
||||
set(CMAKE_OBJECT_PATH_MAX 128)
|
||||
set(CMAKE_OBJECT_NAME_MAX 1)
|
||||
|
||||
project(reverb_test)
|
||||
|
||||
set(APP_HW_TARGET XCORE-AI-EXPLORER)
|
||||
|
||||
set(APP_COMPILER_FLAGS
|
||||
-O3
|
||||
-g
|
||||
-report
|
||||
-Wall
|
||||
-Werror
|
||||
-fxscope)
|
||||
|
||||
set(APP_DEPENDENT_MODULES lib_audio_dsp)
|
||||
set(APP_C_SRCS src/reverb_room.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
|
||||
project(reverb_st_test)
|
||||
|
||||
set(APP_HW_TARGET XCORE-AI-EXPLORER)
|
||||
|
||||
set(APP_COMPILER_FLAGS
|
||||
-O3
|
||||
-g
|
||||
-report
|
||||
-Wall
|
||||
-Werror
|
||||
-fxscope)
|
||||
|
||||
set(APP_DEPENDENT_MODULES lib_audio_dsp)
|
||||
set(APP_C_SRCS src/reverb_room_st.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
|
||||
project(reverb_plate_test)
|
||||
|
||||
set(APP_HW_TARGET XCORE-AI-EXPLORER)
|
||||
|
||||
set(APP_COMPILER_FLAGS
|
||||
-O3
|
||||
-g
|
||||
-report
|
||||
-Wall
|
||||
-Werror
|
||||
-fxscope)
|
||||
|
||||
set(APP_DEPENDENT_MODULES lib_audio_dsp)
|
||||
set(APP_C_SRCS src/reverb_plate.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
|
||||
project(reverb_converters)
|
||||
set(APP_C_SRCS src/converters.c)
|
||||
set(APP_COMPILER_FLAGS_FLOAT2INT ${APP_COMPILER_FLAGS} -DFLOAT2INT)
|
||||
set(APP_COMPILER_FLAGS_DB2INT ${APP_COMPILER_FLAGS} -DDB2INT)
|
||||
set(APP_COMPILER_FLAGS_DECAY2FEEDBACK ${APP_COMPILER_FLAGS} -DDECAY2FEEDBACK)
|
||||
set(APP_COMPILER_FLAGS_CALCULATE_DAMPING ${APP_COMPILER_FLAGS} -DCALCULATE_DAMPING)
|
||||
set(APP_COMPILER_FLAGS_WET_DRY_MIX ${APP_COMPILER_FLAGS} -DWET_DRY_MIX)
|
||||
set(APP_COMPILER_FLAGS_WET_DRY_MIX_ST ${APP_COMPILER_FLAGS} -DWET_DRY_MIX_ST)
|
||||
set(APP_COMPILER_FLAGS_CUTOFF ${APP_COMPILER_FLAGS} -DCUTOFF)
|
||||
|
||||
unset(APP_COMPILER_FLAGS)
|
||||
XMOS_REGISTER_APP()
|
||||
0
lib_audio_dsp/test/reverb/__init__.py
Normal file
0
lib_audio_dsp/test/reverb/__init__.py
Normal file
71
lib_audio_dsp/test/reverb/src/converters.c
Normal file
71
lib_audio_dsp/test/reverb/src/converters.c
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "control/reverb.h"
|
||||
#include "control/reverb_plate.h"
|
||||
|
||||
FILE * _fopen(char * fname, char* mode) {
|
||||
FILE * fp = fopen(fname, mode);
|
||||
if (fp == NULL)
|
||||
{
|
||||
printf("Error opening a file %s\n", fname);
|
||||
exit(1);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int n_inputs = 1;
|
||||
FILE * in = _fopen("test_vector.bin", "rb");
|
||||
FILE * out = _fopen("out_vector.bin", "wb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / (n_inputs*sizeof(float));
|
||||
//printf("inlen %d", in_len);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
float samp = 0;
|
||||
fread(&samp, sizeof(float), 1, in);
|
||||
|
||||
#if defined(FLOAT2INT)
|
||||
int32_t ival = adsp_reverb_float2int(samp);
|
||||
fwrite(&ival, sizeof(int32_t), 1, out);
|
||||
#elif defined(DB2INT)
|
||||
int32_t ival = adsp_reverb_db2int(samp);
|
||||
fwrite(&ival, sizeof(int32_t), 1, out);
|
||||
#elif defined(DECAY2FEEDBACK)
|
||||
int32_t ival = adsp_reverb_calculate_feedback(samp);
|
||||
fwrite(&ival, sizeof(int32_t), 1, out);
|
||||
#elif defined(CALCULATE_DAMPING)
|
||||
int32_t ival = adsp_reverb_calculate_damping(samp);
|
||||
fwrite(&ival, sizeof(int32_t), 1, out);
|
||||
#elif defined(WET_DRY_MIX)
|
||||
int32_t gains[2];
|
||||
adsp_reverb_wet_dry_mix(gains, samp);
|
||||
fwrite(gains, sizeof(int32_t), 2, out);
|
||||
#elif defined(WET_DRY_MIX_ST)
|
||||
int32_t gains[3];
|
||||
adsp_reverb_st_wet_dry_mix(gains, samp, 1.0);
|
||||
fwrite(gains, sizeof(int32_t), 3, out);
|
||||
#elif defined(CUTOFF)
|
||||
int32_t ival = adsp_reverb_plate_calc_bandwidth(samp, 48000.0f);
|
||||
fwrite(&ival, sizeof(int32_t), 1, out);
|
||||
#else
|
||||
#error "config not defined"
|
||||
#endif
|
||||
//printf("returned: %ld\n", ival);
|
||||
|
||||
//fwrite(&ival, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
79
lib_audio_dsp/test/reverb/src/reverb_plate.c
Normal file
79
lib_audio_dsp/test/reverb/src/reverb_plate.c
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "dsp/adsp.h"
|
||||
#include "control/adsp_control.h"
|
||||
|
||||
#define FS 48000
|
||||
#define MAX_ROOM 1.0
|
||||
#define PD_MS 1
|
||||
#define PD_SAMPS (uint32_t)(PD_MS * FS / 1000)
|
||||
|
||||
FILE *_fopen(char *fname, char *mode)
|
||||
{
|
||||
FILE *fp = fopen(fname, mode);
|
||||
if (fp == NULL)
|
||||
{
|
||||
printf("Error opening a file\n");
|
||||
exit(1);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
float const fs = FS;
|
||||
|
||||
FILE *in = _fopen("../sig_2ch_48k.bin", "rb");
|
||||
FILE *out = _fopen("rv_sig_out.bin", "wb");
|
||||
FILE *info = _fopen("rv_info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = (ftell(in) / sizeof(int32_t)) / 2; // stereo
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t pregain, wet1, wet2, dry, decay, dc_diff1, damp, dc_diff2, bw, in_diff1, in_diff2;
|
||||
|
||||
fread(&pregain, sizeof(int32_t), 1, info);
|
||||
fread(&wet1, sizeof(int32_t), 1, info);
|
||||
fread(&wet2, sizeof(int32_t), 1, info);
|
||||
fread(&dry, sizeof(int32_t), 1, info);
|
||||
fread(&decay, sizeof(int32_t), 1, info);
|
||||
fread(&dc_diff2, sizeof(int32_t), 1, info);
|
||||
fread(&damp, sizeof(int32_t), 1, info);
|
||||
fread(&dc_diff1, sizeof(int32_t), 1, info);
|
||||
fread(&bw, sizeof(int32_t), 1, info);
|
||||
fread(&in_diff1, sizeof(int32_t), 1, info);
|
||||
fread(&in_diff2, sizeof(int32_t), 1, info);
|
||||
fclose(info);
|
||||
|
||||
uint8_t reverb_heap[ADSP_RVP_HEAP_SZ(FS, PD_SAMPS)] = {0};
|
||||
reverb_plate_t rv;
|
||||
rv.pre_gain = pregain;
|
||||
rv.wet_gain1 = wet1;
|
||||
rv.wet_gain2 = wet2;
|
||||
rv.dry_gain = dry;
|
||||
rv.decay = decay;
|
||||
rv.lowpasses[0] = lowpass_1ord_init(bw);
|
||||
rv.lowpasses[1] = lowpass_1ord_init(damp);
|
||||
rv.lowpasses[2] = lowpass_1ord_init(damp);
|
||||
|
||||
adsp_reverb_plate_init_filters(&rv, fs, dc_diff1, dc_diff2, in_diff1, in_diff2, PD_SAMPS, PD_SAMPS, reverb_heap);
|
||||
|
||||
for (int i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp_l = 0, samp_r = 0, samp_out[2] = {0};
|
||||
fread(&samp_l, sizeof(int32_t), 1, in);
|
||||
fread(&samp_r, sizeof(int32_t), 1, in);
|
||||
adsp_reverb_plate(&rv, samp_out, samp_l, samp_r);
|
||||
fwrite(samp_out, sizeof(int32_t), 2, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
69
lib_audio_dsp/test/reverb/src/reverb_room.c
Normal file
69
lib_audio_dsp/test/reverb/src/reverb_room.c
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "dsp/adsp.h"
|
||||
|
||||
#define FS 48000
|
||||
#define MAX_ROOM 1.0
|
||||
#define PD_MS 10
|
||||
#define PD_SAMPS (uint32_t)(PD_MS * FS / 1000)
|
||||
|
||||
FILE *_fopen(char *fname, char *mode)
|
||||
{
|
||||
FILE *fp = fopen(fname, mode);
|
||||
if (fp == NULL)
|
||||
{
|
||||
printf("Error opening a file\n");
|
||||
exit(1);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
float const fs = FS;
|
||||
float const max_room_size = MAX_ROOM;
|
||||
float const room_size = 1.0;
|
||||
|
||||
FILE *in = _fopen("../rv_sig_48k.bin", "rb");
|
||||
FILE *out = _fopen("rv_sig_out.bin", "wb");
|
||||
FILE *info = _fopen("rv_info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t pregain, wet, dry, feedback, damping;
|
||||
|
||||
fread(&pregain, sizeof(int32_t), 1, info);
|
||||
fread(&wet, sizeof(int32_t), 1, info);
|
||||
fread(&dry, sizeof(int32_t), 1, info);
|
||||
fread(&feedback, sizeof(int32_t), 1, info);
|
||||
fread(&damping, sizeof(int32_t), 1, info);
|
||||
fclose(info);
|
||||
|
||||
uint8_t reverb_heap[ADSP_RVR_HEAP_SZ(FS, MAX_ROOM, PD_SAMPS)] = {0};
|
||||
reverb_room_t rv;
|
||||
rv.pre_gain = pregain;
|
||||
rv.wet_gain = wet;
|
||||
rv.dry_gain = dry;
|
||||
|
||||
adsp_reverb_room_init_filters(&rv, fs, max_room_size, PD_SAMPS, PD_SAMPS, feedback, damping, reverb_heap);
|
||||
adsp_reverb_room_set_room_size(&rv, room_size);
|
||||
|
||||
for (int i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
samp_out = adsp_reverb_room(&rv, samp);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
72
lib_audio_dsp/test/reverb/src/reverb_room_st.c
Normal file
72
lib_audio_dsp/test/reverb/src/reverb_room_st.c
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2024-2025 XMOS LIMITED.
|
||||
// This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "dsp/adsp.h"
|
||||
|
||||
#define FS 48000
|
||||
#define MAX_ROOM 1.0
|
||||
#define PD_MS 1
|
||||
#define PD_SAMPS (uint32_t)(PD_MS * FS / 1000)
|
||||
|
||||
FILE *_fopen(char *fname, char *mode)
|
||||
{
|
||||
FILE *fp = fopen(fname, mode);
|
||||
if (fp == NULL)
|
||||
{
|
||||
printf("Error opening a file\n");
|
||||
exit(1);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
float const fs = FS;
|
||||
float const max_room_size = MAX_ROOM;
|
||||
float const room_size = 1.0;
|
||||
|
||||
FILE *in = _fopen("../sig_2ch_48k.bin", "rb");
|
||||
FILE *out = _fopen("rv_sig_out.bin", "wb");
|
||||
FILE *info = _fopen("rv_info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = (ftell(in) / sizeof(int32_t)) / 2; // stereo
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t pregain, wet1, wet2, dry, feedback, damping;
|
||||
|
||||
fread(&pregain, sizeof(int32_t), 1, info);
|
||||
fread(&wet1, sizeof(int32_t), 1, info);
|
||||
fread(&wet2, sizeof(int32_t), 1, info);
|
||||
fread(&dry, sizeof(int32_t), 1, info);
|
||||
fread(&feedback, sizeof(int32_t), 1, info);
|
||||
fread(&damping, sizeof(int32_t), 1, info);
|
||||
fclose(info);
|
||||
|
||||
uint8_t reverb_heap[ADSP_RVRST_HEAP_SZ(FS, MAX_ROOM, PD_SAMPS)] = {0};
|
||||
reverb_room_st_t rv;
|
||||
rv.pre_gain = pregain;
|
||||
rv.wet_gain1 = wet1;
|
||||
rv.wet_gain2 = wet2;
|
||||
rv.dry_gain = dry;
|
||||
|
||||
adsp_reverb_room_st_init_filters(&rv, fs, max_room_size, PD_SAMPS, PD_SAMPS, feedback, damping, reverb_heap);
|
||||
adsp_reverb_room_st_set_room_size(&rv, room_size);
|
||||
|
||||
for (int i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp_l = 0, samp_r = 0, samp_out[2] = {0};
|
||||
fread(&samp_l, sizeof(int32_t), 1, in);
|
||||
fread(&samp_r, sizeof(int32_t), 1, in);
|
||||
adsp_reverb_room_st(&rv, samp_out, samp_l, samp_r);
|
||||
fwrite(samp_out, sizeof(int32_t), 2, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
99
lib_audio_dsp/test/reverb/test_reverb_c.py
Normal file
99
lib_audio_dsp/test/reverb/test_reverb_c.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# Copyright 2024-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
from audio_dsp.dsp.generic import Q_SIG
|
||||
import audio_dsp.dsp.reverb as reverb
|
||||
import audio_dsp.dsp.signal_gen as gen
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
import shutil
|
||||
import soundfile as sf
|
||||
import subprocess
|
||||
from test.test_utils import xdist_safe_bin_write, float_to_qxx, qxx_to_float, q_convert_flt
|
||||
|
||||
BIN_DIR = Path(__file__).parent / "bin"
|
||||
GEN_DIR = Path(__file__).parent / "autogen"
|
||||
FS = 48000
|
||||
|
||||
|
||||
def get_sig(len=0.05):
|
||||
sig_fl = gen.log_chirp(FS, len, 0.5)
|
||||
sig_fl = q_convert_flt(sig_fl, 23, Q_SIG)
|
||||
|
||||
sig_int = float_to_qxx(sig_fl)
|
||||
|
||||
name = "rv_sig_48k"
|
||||
sig_path = BIN_DIR / str(name + ".bin")
|
||||
|
||||
xdist_safe_bin_write(sig_int, sig_path)
|
||||
|
||||
# wav file does not need to be locked as it is only used for debugging outside pytest
|
||||
wav_path = GEN_DIR / str(name + ".wav")
|
||||
sf.write(wav_path, sig_fl, int(FS), "PCM_32")
|
||||
return sig_fl
|
||||
|
||||
|
||||
def get_c_wav(dir_name, app_name, verbose=False, sim=True):
|
||||
app = "xsim" if sim else "xrun --io"
|
||||
run_cmd = app + " " + str(BIN_DIR / app_name)
|
||||
stdout = subprocess.check_output(run_cmd, cwd=dir_name, shell=True)
|
||||
if verbose: print("run msg:\n", stdout.decode())
|
||||
|
||||
sig_bin = dir_name / "rv_sig_out.bin"
|
||||
assert sig_bin.is_file(), f"Could not find output bin {sig_bin}"
|
||||
sig_int = np.fromfile(sig_bin, dtype=np.int32)
|
||||
|
||||
sig_fl = qxx_to_float(sig_int)
|
||||
sf.write(GEN_DIR / "sig_c.wav", sig_fl, FS, "PCM_32")
|
||||
return sig_fl
|
||||
|
||||
|
||||
def run_py(uut: reverb.reverb_room, sig_fl, use_float_sig=True):
|
||||
out_int = np.zeros(sig_fl.size)
|
||||
sig_int = float_to_qxx(sig_fl)
|
||||
|
||||
if use_float_sig:
|
||||
for n in range(sig_fl.size):
|
||||
out_int[n] = uut.process_xcore(sig_fl[n])
|
||||
else:
|
||||
for n in range(sig_fl.size):
|
||||
out_int[n] = uut.process_xcore(sig_int[n])
|
||||
out_int = qxx_to_float(out_int)
|
||||
|
||||
# sf.write(GEN_DIR / "sig_py_int.wav", out_int, FS, "PCM_32")
|
||||
|
||||
return out_int
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def in_signal():
|
||||
BIN_DIR.mkdir(exist_ok=True, parents=True)
|
||||
GEN_DIR.mkdir(exist_ok=True, parents=True)
|
||||
return get_sig()
|
||||
|
||||
@pytest.mark.parametrize("decay, damping", [[1.0, 1.0],
|
||||
[0.1, 0.5]])
|
||||
@pytest.mark.parametrize("wet, dry, pregain", [[-1.0, -1.0, 0.015]])
|
||||
def test_reverb_room(in_signal, decay, damping, wet, dry, pregain):
|
||||
n_chans = 1
|
||||
fs = FS
|
||||
max_room_size = 1.0
|
||||
room_size = 1.0
|
||||
predelay = 10
|
||||
|
||||
uut = reverb.reverb_room(fs, n_chans, max_room_size, room_size, decay, damping, wet, dry, pregain, predelay)
|
||||
test_name = f"reverb_room_{decay}_{damping}_{wet}_{dry}_{pregain}"
|
||||
|
||||
test_dir = BIN_DIR / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
rv_info = [uut.pregain_int, uut.wet_int, uut.dry_int, uut.combs[0].feedback_int, uut.combs[0].damp1_int]
|
||||
rv_info = np.array(rv_info, dtype=np.int32)
|
||||
rv_info.tofile(test_dir / "rv_info.bin")
|
||||
|
||||
out_py_int = run_py(uut, in_signal)
|
||||
out_c = get_c_wav(test_dir, "reverb_test.xe")
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=0)
|
||||
165
lib_audio_dsp/test/reverb/test_reverb_converters.py
Normal file
165
lib_audio_dsp/test/reverb/test_reverb_converters.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# 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)
|
||||
510
lib_audio_dsp/test/reverb/test_reverb_python.py
Normal file
510
lib_audio_dsp/test/reverb/test_reverb_python.py
Normal file
@@ -0,0 +1,510 @@
|
||||
# Copyright 2024-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
import numpy as np
|
||||
import pytest
|
||||
from functools import partial
|
||||
import warnings
|
||||
|
||||
import audio_dsp.dsp.utils as utils
|
||||
import audio_dsp.dsp.signal_gen as gen
|
||||
import audio_dsp.dsp.generic as dspg
|
||||
import audio_dsp.dsp.reverb as rv
|
||||
import audio_dsp.dsp.reverb_stereo as rvs
|
||||
import audio_dsp.dsp.reverb_plate as rvp
|
||||
|
||||
|
||||
@pytest.mark.parametrize("signal, freq", [["sine", 20],
|
||||
["sine", 1000],
|
||||
["sine", 10000],
|
||||
["sine", 23000],
|
||||
["noise", None]])
|
||||
@pytest.mark.parametrize("algo, param", [["mono_room", 0.1],
|
||||
["mono_room", 1],
|
||||
["mono_room", 4],
|
||||
["stereo_room", 0.1],
|
||||
["stereo_room", 1],
|
||||
["stereo_room", 4],
|
||||
["stereo_plate", 0.95],]
|
||||
)
|
||||
def test_reverb_overflow(signal, freq, algo, param):
|
||||
# check no overflow errors occur
|
||||
fs = 48000
|
||||
q_format = 31
|
||||
|
||||
if signal == "sine":
|
||||
sig = gen.sin(fs, 5, freq, 1)
|
||||
elif signal == "chirp":
|
||||
sig = gen.log_chirp(fs, 5, 1, 20, 20000)
|
||||
elif signal == "noise":
|
||||
sig = gen.white_noise(fs, 5, 1)
|
||||
|
||||
sig = sig/np.max(np.abs(sig))
|
||||
|
||||
if algo == "stereo_room":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
reverb = rvs.reverb_room_stereo(fs, 2, max_room_size=param, room_size=1, decay=1.0, damping=0.0, Q_sig=q_format)
|
||||
elif algo == "mono_room":
|
||||
reverb = rv.reverb_room(fs, 1, max_room_size=param, room_size=1, decay=1.0, damping=0.0, Q_sig=q_format)
|
||||
elif algo == "stereo_plate":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
reverb = rvp.reverb_plate_stereo(fs, 2, decay=param, damping=0.0, Q_sig=q_format)
|
||||
|
||||
#print(reverb.get_buffer_lens())
|
||||
|
||||
output_xcore = np.zeros_like(sig)
|
||||
output_flt = np.zeros_like(sig)
|
||||
|
||||
if "stereo" in algo:
|
||||
for n in range(sig.shape[1]):
|
||||
output_xcore[:, n] = reverb.process_channels_xcore(sig[:, n])
|
||||
reverb.reset_state()
|
||||
for n in range(sig.shape[1]):
|
||||
output_flt[:, n] = reverb.process_channels(sig[:, n])
|
||||
else:
|
||||
for n in range(len(sig)):
|
||||
output_xcore[n] = reverb.process_xcore(sig[n])
|
||||
reverb.reset_state()
|
||||
for n in range(len(sig)):
|
||||
output_flt[n] = reverb.process(sig[n])
|
||||
|
||||
|
||||
def calc_reverb_time(in_sig, reverb_output):
|
||||
# extend by 2x
|
||||
sig = np.concatenate((in_sig, np.zeros_like(in_sig)))
|
||||
output_xcore = np.concatenate((reverb_output, np.zeros_like(reverb_output)))
|
||||
|
||||
sig_spect = np.fft.rfft(sig)
|
||||
output_xcore_spect = np.fft.rfft(output_xcore)
|
||||
|
||||
# Y = HX, Y/X = H
|
||||
H_xcore_spect = output_xcore_spect/sig_spect
|
||||
h_xcore = np.fft.irfft(H_xcore_spect)
|
||||
h_xcore = h_xcore[:len(h_xcore)//2]
|
||||
|
||||
return h_xcore
|
||||
|
||||
|
||||
@pytest.mark.parametrize("max_room_size_diffusion", [0.5, 0.9])
|
||||
@pytest.mark.parametrize("decay, damping", [[0.5, 0.35],
|
||||
[1.0, 0.0]])
|
||||
@pytest.mark.parametrize("q_format", [27, 31])
|
||||
@pytest.mark.parametrize("algo, width", [["mono_room", None],
|
||||
["stereo_room", 1.0],
|
||||
["stereo_plate", 1.0],]
|
||||
)
|
||||
@pytest.mark.parametrize("wdmix", [0.5, 1.0])
|
||||
def test_reverb_time(max_room_size_diffusion, decay, damping, q_format, width, algo, wdmix):
|
||||
# measure reverb time with chirp
|
||||
fs = 48000
|
||||
|
||||
if "plate" in algo:
|
||||
pregain = 0.5**(q_format - 26)
|
||||
else:
|
||||
pregain = 0.015 * 2**(27 - q_format)
|
||||
|
||||
sig = np.zeros(int(fs*max_room_size_diffusion*6) + fs)
|
||||
sig[:1*fs] = gen.log_chirp(fs, 1, 1, 20, 20000)
|
||||
sig = sig* (2**q_format - 1)/(2**q_format)
|
||||
|
||||
if algo =="stereo_room":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
reverb = rvs.reverb_room_stereo(fs, 2, max_room_size=max_room_size_diffusion, room_size=1, decay=decay, damping=damping, Q_sig=q_format, pregain=pregain, width=width)
|
||||
elif algo =="mono_room":
|
||||
reverb = rv.reverb_room(fs, 1, max_room_size=max_room_size_diffusion, room_size=1, decay=decay, damping=damping, Q_sig=q_format, pregain=pregain)
|
||||
elif algo =="stereo_plate":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
reverb = rvp.reverb_plate_stereo(fs, 2, early_diffusion=max_room_size_diffusion,
|
||||
late_diffusion=max_room_size_diffusion,
|
||||
decay=decay, damping=damping, Q_sig=q_format,
|
||||
pregain=pregain, width=width)
|
||||
reverb.set_wet_dry_mix(wdmix)
|
||||
|
||||
output_xcore = np.zeros_like(sig)
|
||||
output_flt = np.zeros_like(sig)
|
||||
|
||||
if "stereo" in algo:
|
||||
for n in range(sig.shape[1]):
|
||||
output_flt[:, n] = reverb.process_channels(sig[:, n])
|
||||
|
||||
reverb.reset_state()
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter('always', utils.SaturationWarning)
|
||||
for n in range(sig.shape[1]):
|
||||
output_xcore[:, n] = reverb.process_channels_xcore(sig[:, n])
|
||||
|
||||
else:
|
||||
for n in range(len(sig)):
|
||||
output_flt[n] = reverb.process(sig[n])
|
||||
|
||||
reverb.reset_state()
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter('always', utils.SaturationWarning)
|
||||
for n in range(len(sig)):
|
||||
output_xcore[n] = reverb.process_xcore(sig[n])
|
||||
|
||||
# if we triggered a saturation warning, can't guarantee arrays are the same
|
||||
sat_warn_flag = any([wi.category is utils.SaturationWarning for wi in w])
|
||||
|
||||
# # in this case, pregain should be adjusted
|
||||
# if sat_warn_flag: assert False
|
||||
|
||||
# small signals are always going to be ropey due to quantizing, so just check average error of top half
|
||||
top_half = np.logical_and(utils.db(output_flt) > -50, utils.db(output_flt) < (6*(31-q_format)))
|
||||
if np.any(top_half):
|
||||
error_flt = np.abs(utils.db(output_xcore[top_half])-utils.db(output_flt[top_half]))
|
||||
mean_error_flt = utils.db(np.nanmean(utils.db2gain(error_flt)))
|
||||
assert mean_error_flt < 0.055
|
||||
|
||||
|
||||
@pytest.mark.parametrize("max_room_size", [0.5])
|
||||
@pytest.mark.parametrize("decay", [0.5])
|
||||
@pytest.mark.parametrize("damping", [0.5])
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_noise_floor(max_room_size, decay, damping, algo):
|
||||
# check the reverb decays to 0 (no limit cycle noise)
|
||||
fs = 48000
|
||||
q_format = 27
|
||||
|
||||
sig = np.zeros(int(fs*max_room_size*40) + fs)
|
||||
sig[:1*fs] = gen.log_chirp(fs, 1, 1, 20, 20000)
|
||||
sig = sig* (2**q_format - 1)/(2**q_format)
|
||||
|
||||
if algo =="stereo_room":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
reverb = rvs.reverb_room_stereo(fs, 2, max_room_size=max_room_size, room_size=1, decay=decay, damping=damping, Q_sig=q_format)
|
||||
elif algo =="mono_room":
|
||||
reverb = rv.reverb_room(fs, 1, max_room_size=max_room_size, room_size=1, decay=decay, damping=damping, Q_sig=q_format)
|
||||
elif algo == "stereo_plate":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
reverb = rvp.reverb_plate_stereo(fs, 2, decay=decay, damping=damping, Q_sig=q_format)
|
||||
#print(reverb.get_buffer_lens())
|
||||
|
||||
output_xcore = np.zeros_like(sig)
|
||||
output_flt = np.zeros_like(sig)
|
||||
|
||||
if "stereo" in algo:
|
||||
for n in range(sig.shape[1]):
|
||||
output_flt[:, n] = reverb.process_channels(sig[:, n])
|
||||
|
||||
reverb.reset_state()
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
for n in range(sig.shape[1]):
|
||||
output_xcore[:, n] = reverb.process_channels_xcore(sig[:, n])
|
||||
else:
|
||||
for n in range(len(sig)):
|
||||
output_flt[n] = reverb.process(sig[n])
|
||||
|
||||
reverb.reset_state()
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
for n in range(len(sig)):
|
||||
output_xcore[n] = reverb.process_xcore(sig[n])
|
||||
|
||||
# check noise floor
|
||||
if "stereo" in algo:
|
||||
assert np.max(np.abs(output_xcore[:, -1000:])) < 2**-(reverb.Q_sig + 1)
|
||||
else:
|
||||
assert np.max(np.abs(output_xcore[-1000:])) < 2**-(reverb.Q_sig + 1)
|
||||
|
||||
# small signals are always going to be ropey due to quantizing, so just check average error of top half
|
||||
top_half = np.logical_and(utils.db(output_flt) > -50, utils.db(output_flt) < (6*(31-q_format)))
|
||||
if np.any(top_half):
|
||||
error_flt = np.abs(utils.db(output_xcore[top_half])-utils.db(output_flt[top_half]))
|
||||
mean_error_flt = utils.db(np.nanmean(utils.db2gain(error_flt)))
|
||||
assert mean_error_flt < 0.055
|
||||
|
||||
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_bypass(algo):
|
||||
# test that a drc component is bit exact when the signal is below
|
||||
# the threshold (or above in the case of a noise gate).
|
||||
fs = 48000
|
||||
signal = gen.log_chirp(fs, 0.5, 1)
|
||||
|
||||
if algo == "stereo_room":
|
||||
signal = np.tile(signal, [2, 1])
|
||||
reverb = rvs.reverb_room_stereo(fs, 2, dry_gain_db=0, wet_gain_db=-np.inf)
|
||||
elif algo == "mono_room":
|
||||
reverb = rv.reverb_room(fs, 1, dry_gain_db=0, wet_gain_db=-np.inf)
|
||||
elif algo == "stereo_plate":
|
||||
signal = np.tile(signal, [2, 1])
|
||||
reverb = rvp.reverb_plate_stereo(fs, 2, dry_gain_db=0, wet_gain_db=-np.inf)
|
||||
|
||||
output_xcore = np.zeros_like(signal)
|
||||
output_flt = np.zeros_like(signal)
|
||||
|
||||
if "stereo" in algo:
|
||||
for n in range(signal.shape[1]):
|
||||
output_xcore[:, n] = reverb.process_channels_xcore(signal[:, n])
|
||||
reverb.reset_state()
|
||||
for n in range(signal.shape[1]):
|
||||
output_flt[:, n] = reverb.process_channels(signal[:, n])
|
||||
else:
|
||||
for n in range(len(signal)):
|
||||
output_xcore[n] = reverb.process_xcore(signal[n])
|
||||
reverb.reset_state()
|
||||
for n in range(len(signal)):
|
||||
output_flt[n] = reverb.process(signal[n])
|
||||
|
||||
np.testing.assert_array_equal(signal, output_flt)
|
||||
# quantization noise from multiply by dry gain
|
||||
np.testing.assert_allclose(signal, output_xcore, atol=2**-(reverb.Q_sig-1))
|
||||
|
||||
@pytest.mark.parametrize("algo", ["stereo_room", "stereo_plate"])
|
||||
@pytest.mark.parametrize("width", [0, 1])
|
||||
def test_reverb_width(algo, width):
|
||||
# test that a drc component is bit exact when the signal is below
|
||||
# the threshold (or above in the case of a noise gate).
|
||||
fs = 48000
|
||||
signal = gen.log_chirp(fs, 0.5, 1)
|
||||
|
||||
if algo == "stereo_room":
|
||||
signal = np.tile(signal, [2, 1])
|
||||
reverb = rvs.reverb_room_stereo(fs, 2)
|
||||
elif algo == "mono_room":
|
||||
reverb = rv.reverb_room(fs, 1)
|
||||
elif algo == "stereo_plate":
|
||||
signal = np.tile(signal, [2, 1])
|
||||
reverb = rvp.reverb_plate_stereo(fs, 2)
|
||||
|
||||
reverb.width = width
|
||||
|
||||
output_xcore = np.zeros_like(signal)
|
||||
output_flt = np.zeros_like(signal)
|
||||
|
||||
if "stereo" in algo:
|
||||
for n in range(signal.shape[1]):
|
||||
output_xcore[:, n] = reverb.process_channels_xcore(signal[:, n])
|
||||
reverb.reset_state()
|
||||
for n in range(signal.shape[1]):
|
||||
output_flt[:, n] = reverb.process_channels(signal[:, n])
|
||||
else:
|
||||
for n in range(len(signal)):
|
||||
output_xcore[n] = reverb.process_xcore(signal[n])
|
||||
reverb.reset_state()
|
||||
for n in range(len(signal)):
|
||||
output_flt[n] = reverb.process(signal[n])
|
||||
|
||||
# small signals are always going to be ropey due to quantizing, so just check average error of top half
|
||||
q_format = 27
|
||||
top_half = np.logical_and(utils.db(output_flt) > -50, utils.db(output_flt) < (6*(31-q_format)))
|
||||
if np.any(top_half):
|
||||
error_flt = np.abs(utils.db(output_xcore[top_half])-utils.db(output_flt[top_half]))
|
||||
mean_error_flt = utils.db(np.nanmean(utils.db2gain(error_flt)))
|
||||
assert mean_error_flt < 0.055
|
||||
|
||||
if width == 0:
|
||||
assert np.all(output_xcore[0, :] == output_xcore)
|
||||
assert np.all(output_flt[0, :] == output_flt)
|
||||
else:
|
||||
assert not np.all(output_xcore[0, :] == output_xcore[1:, :])
|
||||
assert not np.all(output_flt[0, :] == output_flt[1:, :])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("q_format", [27, 31])
|
||||
@pytest.mark.parametrize("algo, param", [["mono_room", 0.1],
|
||||
["mono_room", 1],
|
||||
["mono_room", 4],
|
||||
["stereo_room", 0.1],
|
||||
["stereo_room", 1],
|
||||
["stereo_room", 4],
|
||||
["stereo_plate", 0.1],
|
||||
["stereo_plate", 0.5],
|
||||
["stereo_plate", 0.9],]
|
||||
)
|
||||
def test_reverb_frames(fs, q_format, algo, param):
|
||||
# test the process_frame functions of the reverb components
|
||||
|
||||
# if q_format > 27:
|
||||
# pytest.xfail("This test is not meant to pass with a q more then 27")
|
||||
|
||||
if algo == "stereo_room":
|
||||
reverb = rvs.reverb_room_stereo(fs, 2, max_room_size=param, width=0, Q_sig=q_format)
|
||||
|
||||
signal = gen.log_chirp(fs, 0.5, 1)
|
||||
t = np.arange(len(signal))/fs
|
||||
signal *= np.sin(t*2*np.pi*0.5)
|
||||
signal = np.tile(signal, [2, 1])
|
||||
elif algo == "mono_room":
|
||||
reverb = rv.reverb_room(fs, 1, max_room_size=param, Q_sig=q_format)
|
||||
|
||||
signal = gen.log_chirp(fs, 0.5, 1)
|
||||
t = np.arange(len(signal))/fs
|
||||
signal *= np.sin(t*2*np.pi*0.5)
|
||||
signal = np.tile(signal, [1, 1])
|
||||
elif algo == "stereo_plate":
|
||||
reverb = rvp.reverb_plate_stereo(fs, 2, decay=param, width=0, Q_sig=q_format)
|
||||
|
||||
signal = gen.log_chirp(fs, 0.5, 1)
|
||||
t = np.arange(len(signal))/fs
|
||||
signal *= np.sin(t*2*np.pi*0.5)
|
||||
signal = np.tile(signal, [2, 1])
|
||||
|
||||
frame_size = 1
|
||||
signal_frames = utils.frame_signal(signal, frame_size, 1)
|
||||
|
||||
output_int = np.zeros_like(signal)
|
||||
output_flt = np.zeros_like(signal)
|
||||
|
||||
for n in range(len(signal_frames)):
|
||||
output_int[:, n*frame_size:(n+1)*frame_size] = reverb.process_frame_xcore(signal_frames[n])
|
||||
reverb.reset_state()
|
||||
for n in range(len(signal_frames)):
|
||||
output_flt[:, n*frame_size:(n+1)*frame_size] = reverb.process_frame(signal_frames[n])
|
||||
|
||||
assert np.all(output_int[0, :] == output_int)
|
||||
assert np.all(output_flt[0, :] == output_flt)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ratio", [0, 0.5, 1])
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_wet_dry_mix(ratio, algo):
|
||||
fs = 48000
|
||||
q_format = 27
|
||||
max_room_sz = 1
|
||||
room_sz = 1
|
||||
damp = 0.22
|
||||
|
||||
a = utils.db2gain(-10)
|
||||
sig = gen.pink_noise(fs, 1, a)
|
||||
|
||||
if algo == "stereo_room":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
verb = rvs.reverb_room_stereo(fs, 2, max_room_size=max_room_sz, damping=damp, room_size=room_sz, Q_sig=q_format)
|
||||
elif algo =="mono_room":
|
||||
verb = rv.reverb_room(fs, 1, max_room_size=max_room_sz, damping=damp, room_size=room_sz, Q_sig=q_format)
|
||||
elif algo == "stereo_plate":
|
||||
sig = np.tile(sig, [2, 1])
|
||||
verb = rvp.reverb_plate_stereo(fs, 2, Q_sig=q_format)
|
||||
|
||||
verb.set_wet_dry_mix(ratio)
|
||||
sig_py = np.zeros_like(sig)
|
||||
sig_xc = np.zeros_like(sig)
|
||||
if "stereo" in algo:
|
||||
for i in range(sig.shape[1]):
|
||||
sig_py[:, i] = verb.process_channels(sig[:, i])
|
||||
verb.reset_state()
|
||||
for i in range(sig.shape[1]):
|
||||
sig_xc[:, i] = verb.process_channels_xcore(sig[:, i])
|
||||
else:
|
||||
for i in range(len(sig)):
|
||||
sig_py[i] = verb.process(sig[i])
|
||||
verb.reset_state()
|
||||
for i in range(len(sig)):
|
||||
sig_xc[i] = verb.process_xcore(sig[i])
|
||||
|
||||
# small signals are always going to be ropey due to quantizing, so just check average error of top half
|
||||
top_half = utils.db(sig_py) > -50
|
||||
if np.any(top_half):
|
||||
error_vpu = np.abs(utils.db(sig_py[top_half])-utils.db(sig_xc[top_half]))
|
||||
mean_error_vpu = utils.db(np.nanmean(utils.db2gain(error_vpu)))
|
||||
assert mean_error_vpu < 0.005
|
||||
|
||||
def get_algo_partial(algo):
|
||||
if algo =="stereo_room":
|
||||
r = partial(rvs.reverb_room_stereo, 48000, 2)
|
||||
elif algo =="mono_room":
|
||||
r = partial(rv.reverb_room, 48000, 1)
|
||||
elif algo =="stereo_plate":
|
||||
r = partial(rvp.reverb_plate_stereo, 48000, 2)
|
||||
|
||||
return r
|
||||
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_properties_decay(algo):
|
||||
"""Basic tests to check for consistency when setting the properties."""
|
||||
r = get_algo_partial(algo)
|
||||
|
||||
val = 0.1
|
||||
a = r(decay=val)
|
||||
b = r()
|
||||
b.decay = val
|
||||
|
||||
should_be_val = np.array([i.decay for i in (a, b)])
|
||||
np.testing.assert_allclose(should_be_val, val)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_properties_pregain(algo):
|
||||
"""Basic tests to check for consistency when setting the properties."""
|
||||
r = get_algo_partial(algo)
|
||||
|
||||
val = 0.1
|
||||
a = r(pregain=val)
|
||||
b = r()
|
||||
b.pregain = val
|
||||
|
||||
should_be_val = np.array([i.pregain for i in (a, b)])
|
||||
np.testing.assert_allclose(should_be_val, val)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_properties_wet_db(algo):
|
||||
"""Basic tests to check for consistency when setting the properties."""
|
||||
r = get_algo_partial(algo)
|
||||
|
||||
val = -6
|
||||
a = r(wet_gain_db=val)
|
||||
b = r()
|
||||
b.wet_db = val
|
||||
|
||||
should_be_val = np.array([i.wet_db for i in (a, b)])
|
||||
np.testing.assert_allclose(should_be_val, val)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_properties_dry_db(algo):
|
||||
"""Basic tests to check for consistency when setting the properties."""
|
||||
r = get_algo_partial(algo)
|
||||
|
||||
val = -6
|
||||
a = r(dry_gain_db=val)
|
||||
b = r()
|
||||
b.dry_db = val
|
||||
|
||||
should_be_val = np.array([i.dry_db for i in (a, b)])
|
||||
np.testing.assert_allclose(should_be_val, val)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("algo", ["mono_room", "stereo_room", "stereo_plate"])
|
||||
def test_reverb_properties_damping(algo):
|
||||
"""Basic tests to check for consistency when setting the properties."""
|
||||
r = get_algo_partial(algo)
|
||||
|
||||
val = 0.5
|
||||
a = r(damping=val)
|
||||
b = r()
|
||||
b.damping = val
|
||||
|
||||
should_be_val = np.array([i.damping for i in (a, b)])
|
||||
np.testing.assert_allclose(should_be_val, val)
|
||||
|
||||
@pytest.mark.parametrize("stereo", [True, False])
|
||||
def test_reverb_properties_room_size(stereo):
|
||||
"""Basic tests to check for consistency when setting the properties."""
|
||||
if stereo:
|
||||
r = partial(rvs.reverb_room_stereo, 48000, 2)
|
||||
else:
|
||||
r = partial(rv.reverb_room, 48000, 1)
|
||||
|
||||
val = 0.5
|
||||
a = r(room_size=val)
|
||||
b = r()
|
||||
b.room_size = val
|
||||
|
||||
should_be_val = np.array([i.room_size for i in (a, b)])
|
||||
np.testing.assert_allclose(should_be_val, val)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_reverb_width("stereo_plate", 1)
|
||||
# test_reverb_time(0.5, 0.25, 0.35, 29, 0.5, 1, "stereo_plate")
|
||||
# test_reverb_overflow("sine", 20, "stereo_plate", 0.1)
|
||||
# test_reverb_time(0.01, 1)
|
||||
# test_reverb_frames(48000, 27, "stereo_plate", 0.5)
|
||||
# test_reverb_wet_dry_mix(1.0, "stereo_plate")
|
||||
# test_reverb_bypass_stereo()
|
||||
# test_reverb_noise_floor_stereo(1.0, 1.0, 0)
|
||||
# test_reverb_time(0.01, 1, 0, 31, 0.001, 0.5)
|
||||
# test_reverb_properties_room_size(True)
|
||||
126
lib_audio_dsp/test/reverb/test_reverb_st_c.py
Normal file
126
lib_audio_dsp/test/reverb/test_reverb_st_c.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# Copyright 2024-2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import audio_dsp.dsp.reverb_stereo as rvs
|
||||
import audio_dsp.dsp.reverb_plate as rvp
|
||||
import audio_dsp.dsp.signal_gen as gen
|
||||
import audio_dsp.dsp.utils as utils
|
||||
import audio_dsp.dsp.generic as dspg
|
||||
import pytest
|
||||
import subprocess
|
||||
from test.test_utils import xdist_safe_bin_write, float_to_qxx, qxx_to_float, q_convert_flt
|
||||
|
||||
bin_dir = Path(__file__).parent / "bin"
|
||||
gen_dir = Path(__file__).parent / "autogen"
|
||||
|
||||
fs = 48000
|
||||
|
||||
def get_sig_2ch(len=0.05):
|
||||
sig_l = []
|
||||
sig_l.append(gen.sin(fs, len, 997, 0.7))
|
||||
sig_l.append(gen.log_chirp(fs, len, 0.5))
|
||||
|
||||
sig_fl_t = np.stack(sig_l, axis=1)
|
||||
sig_fl_t = utils.saturate_float_array(sig_fl_t, dspg.Q_SIG)
|
||||
sig_fl_t = q_convert_flt(sig_fl_t, 23, dspg.Q_SIG)
|
||||
|
||||
|
||||
sig_int = float_to_qxx(sig_fl_t)
|
||||
sig_path = bin_dir / "sig_2ch_48k.bin"
|
||||
xdist_safe_bin_write(sig_int, sig_path)
|
||||
|
||||
sf.write(gen_dir / "sig_2ch_48k.wav", sig_fl_t, int(fs), "PCM_24")
|
||||
return sig_fl_t.T
|
||||
|
||||
def get_c_wav(dir_name, app_name, verbose=False, sim=True):
|
||||
app = "xsim" if sim else "xrun --io"
|
||||
run_cmd = app + " " + str(bin_dir / app_name)
|
||||
stdout = subprocess.check_output(run_cmd, cwd=dir_name, shell=True)
|
||||
if verbose: print("run msg:\n", stdout.decode())
|
||||
|
||||
sig_bin = dir_name / "rv_sig_out.bin"
|
||||
assert sig_bin.is_file(), f"Could not find output bin {sig_bin}"
|
||||
sig_int = np.fromfile(sig_bin, dtype=np.int32)
|
||||
#deinterleave channels
|
||||
sig_int0 = sig_int[0::2]
|
||||
sig_int1 = sig_int[1::2]
|
||||
sig_int = [sig_int0, sig_int1]
|
||||
sig_int = np.stack(sig_int, axis=0)
|
||||
|
||||
sig_fl = qxx_to_float(sig_int)
|
||||
sf.write(gen_dir / "sig_c.wav", sig_fl.T, fs, "PCM_24")
|
||||
return sig_fl
|
||||
|
||||
def run_py(rv, sig_fl):
|
||||
out_py = np.zeros_like(sig_fl)
|
||||
|
||||
for n in range(sig_fl.shape[1]):
|
||||
out_py[:,n] = rv.process_channels_xcore(sig_fl[:,n])
|
||||
sf.write(gen_dir / "sig_py_int.wav", out_py.T, fs, "PCM_24")
|
||||
|
||||
return out_py
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def in_signal():
|
||||
bin_dir.mkdir(exist_ok=True, parents=True)
|
||||
gen_dir.mkdir(exist_ok=True, parents=True)
|
||||
return get_sig_2ch()
|
||||
|
||||
@pytest.mark.parametrize("decay, damping", [[1.0, 1.0],
|
||||
[0.1, 0.5]
|
||||
])
|
||||
@pytest.mark.parametrize("wet, dry, pregain", [[-1.0, -1.0, 0.015]])
|
||||
def test_reverb_room_st_c(in_signal, decay, damping, wet, dry, pregain):
|
||||
n_chans = 2
|
||||
max_room_size = 1.0
|
||||
room_size = 1.0
|
||||
predelay = 1
|
||||
width = 1.0
|
||||
|
||||
rv = rvs.reverb_room_stereo(fs, n_chans, max_room_size, room_size, decay, damping, width, wet, dry, pregain, predelay)
|
||||
test_name = f"reverb_room_stereo_{decay}_{damping}_{wet}_{dry}_{pregain}"
|
||||
rv_info = [rv.pregain_int, rv.wet_1_int, rv.wet_2_int, rv.dry_int, rv.combs_l[0].feedback_int, rv.combs_l[0].damp1_int]
|
||||
rv_info = np.array(rv_info, dtype=np.int32)
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
rv_info.tofile(test_dir / "rv_info.bin")
|
||||
|
||||
out_py_int = run_py(rv, in_signal)
|
||||
out_c = get_c_wav(test_dir, "reverb_st_test.xe")
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=0)
|
||||
|
||||
@pytest.mark.parametrize("decay, damping", [[1.0, 1.0],
|
||||
[0.1, 0.5]
|
||||
])
|
||||
@pytest.mark.parametrize("wet, dry, pregain", [[-1.0, -1.0, 0.015]])
|
||||
def test_reverb_plate_c(in_signal, decay, damping, wet, dry, pregain):
|
||||
n_chans = 2
|
||||
predelay = 1
|
||||
#width = 1.0
|
||||
|
||||
rv = rvp.reverb_plate_stereo(fs, n_chans, decay = decay, damping = damping, predelay = predelay, pregain = pregain, wet_gain_db = wet, dry_gain_db = dry)
|
||||
test_name = f"reverb_plate_{decay}_{damping}_{wet}_{dry}_{pregain}"
|
||||
|
||||
# [pregain, we1, we2, dry, decay, decay_dif, damp, diffusion, bandwidth, in_dif1, in_dif2]
|
||||
rv_info = [rv.pregain_int, rv.wet_1_int, rv.wet_2_int, rv.dry_int, rv.decay_int, rv.allpasses[4].feedback_int, rv.lowpasses[1].coeff_b0_int,
|
||||
rv.mod_allpasses[0].feedback_int, rv.lowpasses[0].coeff_b0_int, rv.allpasses[0].feedback_int, rv.allpasses[2].feedback_int]
|
||||
rv_info = np.array(rv_info, dtype=np.int32)
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
rv_info.tofile(test_dir / "rv_info.bin")
|
||||
|
||||
out_py_int = run_py(rv, in_signal)
|
||||
|
||||
out_c = get_c_wav(test_dir, "reverb_plate_test.xe")
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=0)
|
||||
Reference in New Issue
Block a user