init
This commit is contained in:
211
lib_audio_dsp/test/drc/CMakeLists.txt
Normal file
211
lib_audio_dsp/test/drc/CMakeLists.txt
Normal file
@@ -0,0 +1,211 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake)
|
||||
|
||||
project(envelope_detector_peak_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/envelope_detector_peak.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(envelope_detector_rms_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/envelope_detector_rms.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(limiter_peak_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/limiter_peak.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(limiter_rms_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/limiter_rms.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(compressor_rms_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/compressor_rms.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(compressor_rms_sidechain_mono_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/compressor_rms_sidechain_mono.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(noise_gate_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/noise_gate.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(noise_suppressor_expander_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/noise_suppressor_expander.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(hard_limiter_peak_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/hard_limiter_peak.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(clipper_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/clipper.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
|
||||
project(compressor_rms_sidechain_stereo_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/compressor_rms_sidechain_stereo.c)
|
||||
|
||||
set(XMOS_SANDBOX_DIR ${CMAKE_SOURCE_DIR}/../../..)
|
||||
|
||||
XMOS_REGISTER_APP()
|
||||
0
lib_audio_dsp/test/drc/__init__.py
Normal file
0
lib_audio_dsp/test/drc/__init__.py
Normal file
50
lib_audio_dsp/test/drc/src/clipper.c
Normal file
50
lib_audio_dsp/test/drc/src/clipper.c
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, info);
|
||||
fclose(info);
|
||||
|
||||
clipper_t clip = th;
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_clipper(clip, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
55
lib_audio_dsp/test/drc/src/compressor_rms.c
Normal file
55
lib_audio_dsp/test/drc/src/compressor_rms.c
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * comp_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
float sl;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, comp_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, comp_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, comp_info);
|
||||
fread(&sl, sizeof(float), 1, comp_info);
|
||||
fclose(comp_info);
|
||||
|
||||
compressor_t comp = (compressor_t){
|
||||
(env_detector_t){at_al, re_al, 0}, th, INT32_MAX, sl};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_compressor_rms(&comp, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
59
lib_audio_dsp/test/drc/src/compressor_rms_sidechain_mono.c
Normal file
59
lib_audio_dsp/test/drc/src/compressor_rms_sidechain_mono.c
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
//FILE * in0 = _fopen("ch0.bin", "rb");
|
||||
//FILE * in1 = _fopen("ch1.bin", "rb");
|
||||
FILE * in = _fopen("../sig_2ch_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * comp_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / (sizeof(int32_t) * 2); // two channels
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
float sl;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, comp_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, comp_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, comp_info);
|
||||
fread(&sl, sizeof(float), 1, comp_info);
|
||||
fclose(comp_info);
|
||||
|
||||
compressor_t comp = (compressor_t){
|
||||
(env_detector_t){at_al, re_al, 0}, th, INT32_MAX, sl};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp0 = 0, samp1 = 0, samp_out = 0;
|
||||
fread(&samp0, sizeof(int32_t), 1, in);
|
||||
fread(&samp1, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_compressor_rms_sidechain(&comp, samp0, samp1);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
//fclose(in1);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
63
lib_audio_dsp/test/drc/src/compressor_rms_sidechain_stereo.c
Normal file
63
lib_audio_dsp/test/drc/src/compressor_rms_sidechain_stereo.c
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
//FILE * in0 = _fopen("ch0.bin", "rb");
|
||||
//FILE * in1 = _fopen("ch1.bin", "rb");
|
||||
FILE * in = _fopen("../sig_4ch_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * comp_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / (sizeof(int32_t) * 4); // two channels
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
float sl;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, comp_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, comp_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, comp_info);
|
||||
fread(&sl, sizeof(float), 1, comp_info);
|
||||
fclose(comp_info);
|
||||
|
||||
compressor_stereo_t comp = (compressor_stereo_t){
|
||||
(env_detector_t){at_al, re_al, 0},
|
||||
(env_detector_t){at_al, re_al, 0}, th, INT32_MAX, sl};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp0 = 0, samp1 = 0, samp2 = 0, samp3 = 0, samp_out[2] = {0};
|
||||
fread(&samp0, sizeof(int32_t), 1, in);
|
||||
fread(&samp1, sizeof(int32_t), 1, in);
|
||||
fread(&samp2, sizeof(int32_t), 1, in);
|
||||
fread(&samp3, sizeof(int32_t), 1, in);
|
||||
|
||||
//printf("%ld ", samp);
|
||||
adsp_compressor_rms_sidechain_stereo(&comp, samp_out, samp0, samp1, samp2, samp3);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(samp_out, sizeof(int32_t), 2, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
//fclose(in1);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
49
lib_audio_dsp/test/drc/src/envelope_detector_peak.c
Normal file
49
lib_audio_dsp/test/drc/src/envelope_detector_peak.c
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * env_info = _fopen("env_info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t at_al, re_al;
|
||||
|
||||
fread(&at_al, sizeof(int32_t), 1, env_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, env_info);
|
||||
fclose(env_info);
|
||||
|
||||
env_detector_t env_det = (env_detector_t){at_al, re_al, 0};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
adsp_env_detector_peak(&env_det, samp);
|
||||
fwrite(&env_det.envelope, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
49
lib_audio_dsp/test/drc/src/envelope_detector_rms.c
Normal file
49
lib_audio_dsp/test/drc/src/envelope_detector_rms.c
Normal file
@@ -0,0 +1,49 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * env_info = _fopen("env_info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t at_al, re_al;
|
||||
|
||||
fread(&at_al, sizeof(int32_t), 1, env_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, env_info);
|
||||
fclose(env_info);
|
||||
|
||||
env_detector_t env_det = (env_detector_t){at_al, re_al, 0};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
adsp_env_detector_rms(&env_det, samp);
|
||||
fwrite(&env_det.envelope, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
lib_audio_dsp/test/drc/src/hard_limiter_peak.c
Normal file
53
lib_audio_dsp/test/drc/src/hard_limiter_peak.c
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * lim_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, lim_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, lim_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, lim_info);
|
||||
fclose(lim_info);
|
||||
|
||||
limiter_t lim = (limiter_t){
|
||||
(env_detector_t){at_al, re_al, 0}, th, INT32_MAX};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_hard_limiter_peak(&lim, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
lib_audio_dsp/test/drc/src/limiter_peak.c
Normal file
53
lib_audio_dsp/test/drc/src/limiter_peak.c
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * lim_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, lim_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, lim_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, lim_info);
|
||||
fclose(lim_info);
|
||||
|
||||
limiter_t lim = (limiter_t){
|
||||
(env_detector_t){at_al, re_al, 0}, th, INT32_MAX};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_limiter_peak(&lim, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
lib_audio_dsp/test/drc/src/limiter_rms.c
Normal file
53
lib_audio_dsp/test/drc/src/limiter_rms.c
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * lim_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, lim_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, lim_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, lim_info);
|
||||
fclose(lim_info);
|
||||
|
||||
limiter_t lim = (limiter_t){
|
||||
(env_detector_t){at_al, re_al, 0}, th, INT32_MAX};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_limiter_rms(&lim, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
lib_audio_dsp/test/drc/src/noise_gate.c
Normal file
53
lib_audio_dsp/test/drc/src/noise_gate.c
Normal file
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * ng_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, ng_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, ng_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, ng_info);
|
||||
fclose(ng_info);
|
||||
|
||||
noise_gate_t ng = (noise_gate_t){
|
||||
(env_detector_t){at_al, re_al, (1 << (-SIG_EXP)) - 1}, th, INT32_MAX};
|
||||
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_noise_gate(&ng, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
57
lib_audio_dsp/test/drc/src/noise_suppressor_expander.c
Normal file
57
lib_audio_dsp/test/drc/src/noise_suppressor_expander.c
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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"
|
||||
|
||||
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()
|
||||
{
|
||||
FILE * in = _fopen("../sig_48k.bin", "rb");
|
||||
FILE * out = _fopen("sig_out.bin", "wb");
|
||||
FILE * nse_info = _fopen("info.bin", "rb");
|
||||
|
||||
fseek(in, 0, SEEK_END);
|
||||
int in_len = ftell(in) / sizeof(int32_t);
|
||||
fseek(in, 0, SEEK_SET);
|
||||
|
||||
int32_t th, at_al, re_al;
|
||||
float slope;
|
||||
|
||||
fread(&th, sizeof(int32_t), 1, nse_info);
|
||||
fread(&at_al, sizeof(int32_t), 1, nse_info);
|
||||
fread(&re_al, sizeof(int32_t), 1, nse_info);
|
||||
fread(&slope, sizeof(float), 1, nse_info);
|
||||
|
||||
fclose(nse_info);
|
||||
if (!th) th = 1;
|
||||
noise_suppressor_expander_t nse = (noise_suppressor_expander_t){
|
||||
(env_detector_t){at_al, re_al, (1 << (Q_SIG)) - 1}, 0, 0, INT32_MAX, slope};
|
||||
adsp_noise_suppressor_expander_set_th(&nse, th);
|
||||
for (unsigned i = 0; i < in_len; i++)
|
||||
{
|
||||
int32_t samp = 0, samp_out = 0;
|
||||
fread(&samp, sizeof(int32_t), 1, in);
|
||||
//printf("%ld ", samp);
|
||||
samp_out = adsp_noise_suppressor_expander(&nse, samp);
|
||||
//printf("%ld ", samp_out);
|
||||
fwrite(&samp_out, sizeof(int32_t), 1, out);
|
||||
}
|
||||
|
||||
fclose(in);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
199
lib_audio_dsp/test/drc/test_drc_c.py
Normal file
199
lib_audio_dsp/test/drc/test_drc_c.py
Normal file
@@ -0,0 +1,199 @@
|
||||
# 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 subprocess
|
||||
import audio_dsp.dsp.drc as drc
|
||||
from audio_dsp.dsp.generic import Q_SIG
|
||||
from audio_dsp.dsp.signal_gen import quantize_signal
|
||||
import pytest
|
||||
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):
|
||||
time = np.arange(0, len, 1/fs)
|
||||
sig_fl = 0.8 * np.sin(2 * np.pi * 997 * time) * np.sin(2 * np.pi * 100 * time)
|
||||
sig_fl = quantize_signal(sig_fl, 24)
|
||||
sig_fl = q_convert_flt(sig_fl, 23, Q_SIG)
|
||||
sig_int = float_to_qxx(sig_fl)
|
||||
|
||||
name = "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_24")
|
||||
|
||||
return sig_fl
|
||||
|
||||
def get_c_wav(dir_name, bin_name, verbose = False, sim = True):
|
||||
app = "xsim" if sim else "xrun --io"
|
||||
run_cmd = app + " " + str(bin_dir / bin_name) + "_test.xe"
|
||||
stdout = subprocess.check_output(run_cmd, cwd = dir_name, shell = True)
|
||||
if verbose: print("run msg:\n", stdout.decode())
|
||||
|
||||
sig_bin = dir_name / "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_24")
|
||||
return sig_fl
|
||||
|
||||
|
||||
def run_py(filt, sig_fl, single_output, run_f64 = False):
|
||||
out_xpy = np.zeros(sig_fl.size)
|
||||
out_pypy = np.zeros(sig_fl.size)
|
||||
|
||||
if single_output:
|
||||
for n in range(sig_fl.size):
|
||||
out_xpy[n] = filt.process_xcore(sig_fl[n])
|
||||
if run_f64:
|
||||
filt.reset_state()
|
||||
out_pypy[n] = filt.process(sig_fl[n])
|
||||
else:
|
||||
for n in range(sig_fl.size):
|
||||
out_xpy[n] = filt.process_xcore(sig_fl[n])[0]
|
||||
if run_f64:
|
||||
filt.reset_state()
|
||||
out_pypy[n] = filt.process(sig_fl[n])[0]
|
||||
|
||||
sf.write(gen_dir / "sig_py_int.wav", out_xpy, fs, "PCM_24")
|
||||
|
||||
if run_f64:
|
||||
sf.write(gen_dir / "sig_py_flt.wav", out_pypy, fs, "PCM_24")
|
||||
return out_pypy, out_xpy
|
||||
else:
|
||||
return out_xpy
|
||||
|
||||
@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("env_name", ["envelope_detector_peak",
|
||||
"envelope_detector_rms"])
|
||||
@pytest.mark.parametrize("at", [0.007, 0.15])
|
||||
@pytest.mark.parametrize("rt", [0.005, 0.2])
|
||||
def test_env_det_c(in_signal, env_name, at, rt):
|
||||
env_handle = getattr(drc, env_name)
|
||||
env = env_handle(fs, 1, at, rt)
|
||||
test_name = f"{env_name}_{at}_{rt}"
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
env_info = [env.attack_alpha_int, env.release_alpha_int]
|
||||
env_info = np.array(env_info, dtype = np.int32)
|
||||
env_info.tofile(test_dir / "env_info.bin")
|
||||
|
||||
out_py_int = run_py(env, in_signal, True)
|
||||
out_c = get_c_wav(test_dir, env_name)
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=0)
|
||||
|
||||
@pytest.mark.parametrize("component_name", ["limiter_peak",
|
||||
"limiter_rms",
|
||||
"hard_limiter_peak",
|
||||
"clipper",
|
||||
"noise_gate"
|
||||
])
|
||||
@pytest.mark.parametrize("at", [0.001, 0.1])
|
||||
@pytest.mark.parametrize("rt", [0.01, 0.2])
|
||||
@pytest.mark.parametrize("threshold", [-20, 0])
|
||||
def test_limiter_c(in_signal, component_name, at, rt, threshold):
|
||||
# Skip the test as the clipper doesn't use attack and release times
|
||||
if component_name == "clipper" and (at == 0.1 or rt == 0.2):
|
||||
return
|
||||
|
||||
component_handle = getattr(drc, component_name)
|
||||
if component_name == "clipper":
|
||||
comp = component_handle(fs, 1, threshold)
|
||||
test_name = f"{component_name}_{threshold}"
|
||||
else:
|
||||
comp = component_handle(fs, 1, threshold, at, rt)
|
||||
test_name = f"{component_name}_{threshold}_{at}_{rt}"
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
if component_name == "clipper":
|
||||
info = [comp.threshold_int]
|
||||
single_output = True
|
||||
else:
|
||||
info = [comp.threshold_int, comp.attack_alpha_int, comp.release_alpha_int]
|
||||
single_output = False
|
||||
info = np.array(info, dtype = np.int32)
|
||||
info.tofile(test_dir / "info.bin")
|
||||
|
||||
|
||||
out_py_int = run_py(comp, in_signal, single_output)
|
||||
out_c = get_c_wav(test_dir, component_name)
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
if component_name == "limiter_rms" and threshold != 0:
|
||||
# Python uses float sqrt when C uses the fixed point one, so expect some diff
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=7.5e-9)
|
||||
else:
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=0)
|
||||
|
||||
@pytest.mark.parametrize("comp_name", ["compressor_rms",
|
||||
"noise_suppressor_expander"])
|
||||
@pytest.mark.parametrize("at", [0.005])
|
||||
@pytest.mark.parametrize("rt", [0.120])
|
||||
@pytest.mark.parametrize("threshold", [-12, 0])
|
||||
@pytest.mark.parametrize("ratio", [1, 6])
|
||||
def test_compressor_c(in_signal, comp_name, at, rt, threshold, ratio):
|
||||
# for the noise suppressor (expander) the lowest sensible threshold is -35
|
||||
if comp_name == "noise_suppressor_expander" and threshold == -12:
|
||||
threshold = -35
|
||||
comp_handle = getattr(drc, comp_name)
|
||||
comp = comp_handle(fs, 1, ratio, threshold, at, rt)
|
||||
test_name = f"{comp_name}_{ratio}_{threshold}_{at}_{rt}"
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
# numpy doesn't like to have an array with different types
|
||||
# so create separate arrays, cast to bytes, append, write
|
||||
info = [comp.threshold_int, comp.attack_alpha_int, comp.release_alpha_int]
|
||||
info = np.array(info, dtype=np.int32)
|
||||
info1 = np.array(comp.slope_f32, dtype=np.float32)
|
||||
info = info.tobytes()
|
||||
info1 = info1.tobytes()
|
||||
info = np.append(info, info1)
|
||||
info.tofile(test_dir / "info.bin")
|
||||
|
||||
out_py_int = run_py(comp, in_signal, False)
|
||||
out_c = get_c_wav(test_dir, comp_name)
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
# when ratio is 1, the result should be bit-exact as we don't have to use powf
|
||||
if ratio == 1 or (threshold == 0 and comp_name != "noise_suppressor_expander"):
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=0)
|
||||
else:
|
||||
# tolerace is the 24b float32 mantissa
|
||||
tol = 2**(np.ceil(np.log2(np.max(out_c))) - 24)
|
||||
np.testing.assert_allclose(out_c, out_py_int, rtol=0, atol=tol)
|
||||
|
||||
if __name__ == "__main__":
|
||||
bin_dir.mkdir(exist_ok=True, parents=True)
|
||||
gen_dir.mkdir(exist_ok=True, parents=True)
|
||||
sig_fl = get_sig()
|
||||
|
||||
test_env_det_c(sig_fl, "envelope_detector_rms", 0.001, 0.01)
|
||||
test_limiter_c(sig_fl, "limiter_rms", 0.001, 0.07, -10)
|
||||
test_compressor_c(sig_fl, "noise_suppressor_expander", 0.001, 0.01, -1, 5)
|
||||
160
lib_audio_dsp/test/drc/test_drc_mch_c.py
Normal file
160
lib_audio_dsp/test/drc/test_drc_mch_c.py
Normal file
@@ -0,0 +1,160 @@
|
||||
# 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.drc as drc
|
||||
import audio_dsp.dsp.signal_gen as gen
|
||||
import audio_dsp.dsp.generic as dspg
|
||||
import pytest
|
||||
|
||||
from test.test_utils import xdist_safe_bin_write, float_to_qxx, q_convert_flt
|
||||
from test_drc_c import get_c_wav
|
||||
|
||||
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.square(fs, len, 50, 0.5) + 0.5)
|
||||
sig_fl_t = np.stack(sig_l, axis=1)
|
||||
sig_fl_t = q_convert_flt(sig_fl_t, 23, dspg.Q_SIG)
|
||||
|
||||
sig_int = float_to_qxx(sig_fl_t)
|
||||
|
||||
name = "sig_2ch_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_t, int(fs), "PCM_24")
|
||||
|
||||
return sig_fl_t.T
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def in_signal_2ch():
|
||||
bin_dir.mkdir(exist_ok=True, parents=True)
|
||||
gen_dir.mkdir(exist_ok=True, parents=True)
|
||||
return get_sig_2ch()
|
||||
|
||||
|
||||
def get_sig_4ch(len=0.05):
|
||||
sig_l = []
|
||||
sig_l.append(gen.sin(fs, len, 997, 0.7))
|
||||
sig_l.append(gen.sin(fs, len, 435, 0.7))
|
||||
sig_l.append(gen.square(fs, len, 50, 0.5) + 0.5)
|
||||
sig_l.append(gen.square(fs, len, 55, 0.5) + 0.5)
|
||||
sig_fl_t = np.stack(sig_l, axis=1)
|
||||
sig_fl_t = q_convert_flt(sig_fl_t, 23, dspg.Q_SIG)
|
||||
|
||||
sig_int = float_to_qxx(sig_fl_t)
|
||||
|
||||
name = "sig_4ch_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_t, int(fs), "PCM_24")
|
||||
|
||||
return sig_fl_t.T
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def in_signal_4ch():
|
||||
bin_dir.mkdir(exist_ok=True, parents=True)
|
||||
gen_dir.mkdir(exist_ok=True, parents=True)
|
||||
return get_sig_4ch()
|
||||
|
||||
@pytest.mark.parametrize("comp_name", ["compressor_rms_sidechain_mono"])
|
||||
@pytest.mark.parametrize("at", [0.001])
|
||||
@pytest.mark.parametrize("rt", [0.01])
|
||||
@pytest.mark.parametrize("threshold", [-12, 0])
|
||||
@pytest.mark.parametrize("ratio", [1, 6])
|
||||
def test_sidechain_c(in_signal_2ch, comp_name, at, rt, threshold, ratio):
|
||||
component_handle = getattr(drc, comp_name)
|
||||
comp = component_handle(fs, ratio, threshold, at, rt)
|
||||
test_name = f"{comp_name}_{ratio}_{threshold}_{at}_{rt}"
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
# numpy doesn't like to have an array with different types
|
||||
# so create separate arrays, cast to bytes, append, write
|
||||
comp_info = [comp.threshold_int, comp.attack_alpha_int, comp.release_alpha_int]
|
||||
comp_info = np.array(comp_info, dtype=np.int32)
|
||||
comp_info1 = np.array(comp.slope_f32, dtype=np.float32)
|
||||
comp_info = comp_info.tobytes()
|
||||
comp_info1 = comp_info1.tobytes()
|
||||
comp_info = np.append(comp_info, comp_info1)
|
||||
comp_info.tofile(test_dir / "info.bin")
|
||||
|
||||
out_py = np.zeros(in_signal_2ch.shape[1])
|
||||
for n in range(in_signal_2ch.shape[1]):
|
||||
out_py[n], _, _ = comp.process_xcore(in_signal_2ch[0][n], in_signal_2ch[1][n])
|
||||
sf.write(gen_dir / "sig_py_int.wav", out_py, fs, "PCM_24")
|
||||
|
||||
out_c = get_c_wav(test_dir, comp_name)
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
# when ratio is 1, the result should be bit-exact as we don't have to use powf
|
||||
if ratio == 1 or threshold == 0:
|
||||
np.testing.assert_allclose(out_c, out_py, rtol=0, atol=0)
|
||||
else:
|
||||
# tolerace is the 24b float32 mantissa
|
||||
tol = 2**(np.ceil(np.log2(np.max(out_c))) - 24)
|
||||
np.testing.assert_allclose(out_c, out_py, rtol=0, atol=tol)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("comp_name", ["compressor_rms_sidechain_stereo"])
|
||||
@pytest.mark.parametrize("at", [0.001])
|
||||
@pytest.mark.parametrize("rt", [0.01])
|
||||
@pytest.mark.parametrize("threshold", [-12, 0])
|
||||
@pytest.mark.parametrize("ratio", [1, 6])
|
||||
def test_sidechain_stereo_c(in_signal_4ch, comp_name, at, rt, threshold, ratio):
|
||||
component_handle = getattr(drc, comp_name)
|
||||
comp = component_handle(fs, ratio, threshold, at, rt)
|
||||
test_name = f"{comp_name}_{ratio}_{threshold}_{at}_{rt}"
|
||||
|
||||
test_dir = bin_dir / test_name
|
||||
test_dir.mkdir(exist_ok = True, parents = True)
|
||||
|
||||
# numpy doesn't like to have an array with different types
|
||||
# so create separate arrays, cast to bytes, append, write
|
||||
comp_info = [comp.threshold_int, comp.attack_alpha_int, comp.release_alpha_int]
|
||||
comp_info = np.array(comp_info, dtype=np.int32)
|
||||
comp_info1 = np.array(comp.slope_f32, dtype=np.float32)
|
||||
comp_info = comp_info.tobytes()
|
||||
comp_info1 = comp_info1.tobytes()
|
||||
comp_info = np.append(comp_info, comp_info1)
|
||||
comp_info.tofile(test_dir / "info.bin")
|
||||
|
||||
out_py = np.zeros((in_signal_4ch.shape[1], 2))
|
||||
for n in range(in_signal_4ch.shape[1]):
|
||||
out_py[n], _, _ = comp.process_channels_xcore(in_signal_4ch[0:2, n], in_signal_4ch[2:, n])
|
||||
sf.write(gen_dir / "sig_py_int.wav", out_py, fs, "PCM_24")
|
||||
|
||||
out_c = get_c_wav(test_dir, comp_name)
|
||||
out_c_deinter = np.array([out_c[0::2], out_c[1::2]]).T
|
||||
shutil.rmtree(test_dir)
|
||||
|
||||
# when ratio is 1, the result should be bit-exact as we don't have to use powf
|
||||
if ratio == 1 or threshold == 0:
|
||||
np.testing.assert_allclose(out_c_deinter, out_py, rtol=0, atol=0)
|
||||
else:
|
||||
# tolerace is the 24b float32 mantissa
|
||||
tol = 2**(np.ceil(np.log2(np.max(out_c))) - 24)
|
||||
np.testing.assert_allclose(out_c_deinter, out_py, rtol=0, atol=tol)
|
||||
|
||||
if __name__ == "__main__":
|
||||
bin_dir.mkdir(exist_ok=True, parents=True)
|
||||
gen_dir.mkdir(exist_ok=True, parents=True)
|
||||
sig_fl = get_sig_4ch()
|
||||
|
||||
test_sidechain_stereo_c(sig_fl, "compressor_rms_sidechain_stereo", 0.001, 0.01, -6, 4)
|
||||
725
lib_audio_dsp/test/drc/test_drc_python.py
Normal file
725
lib_audio_dsp/test/drc/test_drc_python.py
Normal file
@@ -0,0 +1,725 @@
|
||||
# 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
|
||||
import soundfile as sf
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import audio_dsp.dsp.drc as drc
|
||||
import audio_dsp.dsp.utils as utils
|
||||
import audio_dsp.dsp.signal_gen as gen
|
||||
import audio_dsp.dsp.generic as dspg
|
||||
|
||||
from test.test_utils import q_convert_flt
|
||||
|
||||
def make_noisy_speech():
|
||||
hydra_audio_path = os.environ['hydra_audio_PATH']
|
||||
filepath = Path(hydra_audio_path, 'acoustic_team_test_audio',
|
||||
'speech', "010_male_female_single-talk_seq.wav")
|
||||
sig, fs = sf.read(filepath)
|
||||
amp = utils.db2gain(-30)
|
||||
noise_sig = gen.pink_noise(fs, len(sig)/fs, amp)
|
||||
|
||||
out_sig = sig + noise_sig
|
||||
out_sig = utils.saturate_float_array(out_sig, dspg.Q_SIG)
|
||||
|
||||
return out_sig, fs
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("at", [0.001, 0.01, 0.1, 0.5])
|
||||
@pytest.mark.parametrize("threshold", [-20, -10, 0])
|
||||
def test_limiter_peak_attack(fs, at, threshold):
|
||||
# Attack time test bads on Figure 2 in Guy McNally's "Dynamic Range Control
|
||||
# of Digital Audio Signals"
|
||||
|
||||
if threshold >= dspg.HEADROOM_DB:
|
||||
pytest.skip("Threshold is above headroom")
|
||||
return
|
||||
|
||||
# Make a constant signal at 6 dB above the threshold, make 2* length of
|
||||
# attack time to keep the test quick
|
||||
x = np.ones(int(at*2*fs))
|
||||
x[:] = utils.db2gain(threshold + 6)
|
||||
x = utils.saturate_float_array(x, dspg.Q_SIG)
|
||||
|
||||
t = np.arange(len(x))/fs
|
||||
|
||||
# fixed release time, not that we're going to use it
|
||||
lt = drc.limiter_peak(fs, 1, threshold, at, 0.3)
|
||||
|
||||
y = np.zeros_like(x)
|
||||
f = np.zeros_like(x)
|
||||
env = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y)):
|
||||
y[n], f[n], env[n] = lt.process(x[n])
|
||||
|
||||
# attack time is defined as how long to get within 2 dB of final value,
|
||||
# in this case the threshold. Find when we get to this
|
||||
thresh_passed = np.argmax(utils.db(env) > threshold)
|
||||
sig_3dB = np.argmax(utils.db(y) < threshold + 2)
|
||||
|
||||
measured_at = t[sig_3dB] - t[thresh_passed]
|
||||
print("target: %.3f, measured: %.3f" % (at, measured_at))
|
||||
|
||||
# be somewhere vaugely near the spec, attack time definition is variable!
|
||||
assert measured_at/at > 0.85
|
||||
assert measured_at/at < 1.15
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("rt", [0.001, 0.01, 0.1, 0.5])
|
||||
@pytest.mark.parametrize("threshold", [-20, -10, 0])
|
||||
def test_limiter_peak_release(fs, rt, threshold):
|
||||
# Release time test bads on Figure 2 in Guy McNally's "Dynamic Range
|
||||
# Control of Digital Audio Signals"
|
||||
|
||||
if threshold >= dspg.HEADROOM_DB:
|
||||
pytest.skip("Threshold is above headroom")
|
||||
return
|
||||
|
||||
# Make a step down signal from 6 dB above the threshold to 3 dB below
|
||||
# threshold, make 2* length of release time plus 0.5 to keep the test quick
|
||||
x = np.ones(int((0.5+rt*2)*fs))
|
||||
x[:int(0.5*fs)] = utils.db2gain(threshold + 6)
|
||||
x[int(0.5*fs):] = utils.db2gain(threshold - 3)
|
||||
x = utils.saturate_float_array(x, dspg.Q_SIG)
|
||||
|
||||
t = np.arange(len(x))/fs
|
||||
|
||||
# fixed attack time of 0.01, so should have converged by 0.5s
|
||||
lt = drc.limiter_peak(fs, 1, threshold, 0.01, rt)
|
||||
|
||||
y = np.zeros_like(x)
|
||||
f = np.zeros_like(x)
|
||||
env = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y)):
|
||||
y[n], f[n], env[n] = lt.process(x[n])
|
||||
|
||||
# find when within 3 dB of target value after dropping below the threshold,
|
||||
# in this case the original value of 2 dB below the threshold.
|
||||
sig_3dB = np.argmax(utils.db(y[int(0.5*fs):]) > threshold - 2 - 3)
|
||||
|
||||
measured_rt = t[sig_3dB]
|
||||
print("target: %.3f, measured: %.3f" % (rt, measured_rt))
|
||||
print(measured_rt/rt)
|
||||
|
||||
# be somewhere vaugely near the spec
|
||||
assert measured_rt/rt > 0.8
|
||||
assert measured_rt/rt < 1.2
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("threshold", [0, -6, -12])
|
||||
@pytest.mark.parametrize("ratio", (1, 6, np.inf))
|
||||
@pytest.mark.parametrize("rt", [0.00000001])
|
||||
@pytest.mark.parametrize("at", [0.00000001])
|
||||
def test_comp_ratio(fs, at, rt, ratio, threshold):
|
||||
# make sure a fast compressor has the same perforance as a limiter
|
||||
# over a variety of ratios
|
||||
|
||||
drcut = drc.compressor_rms(fs, 1, ratio, threshold, at, rt)
|
||||
|
||||
signal = gen.log_chirp(fs, (0.1+(rt+at)*2), 1)
|
||||
signal = utils.saturate_float_array(signal, dspg.Q_SIG)
|
||||
|
||||
output_xcore = np.zeros(len(signal))
|
||||
output_flt = np.zeros(len(signal))
|
||||
|
||||
# limiter and compressor have 3 outputs
|
||||
for n in np.arange(len(signal)):
|
||||
output_xcore[n], _, _ = drcut.process_xcore(signal[n])
|
||||
drcut.reset_state()
|
||||
for n in np.arange(len(signal)):
|
||||
output_flt[n], _, _ = drcut.process(signal[n])
|
||||
|
||||
# lazy limiter
|
||||
ref_signal = np.copy(signal)
|
||||
over_thresh = utils.db(ref_signal) > threshold
|
||||
ref_signal[over_thresh] *= utils.db2gain((1 - 1/ratio)*(threshold - utils.db(ref_signal[over_thresh])))
|
||||
|
||||
np.testing.assert_allclose(ref_signal, output_flt, atol=3e-16, rtol=0)
|
||||
# threshold limited by 24b resolution in float32 mantissa
|
||||
np.testing.assert_allclose(output_flt, output_xcore, atol=2**-23, rtol=0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("at", [0.001, 0.01, 0.1, 0.5])
|
||||
@pytest.mark.parametrize("threshold", [-20, -10, 0])
|
||||
def comp_vs_limiter(fs, at, threshold):
|
||||
# check infinite ratio compressor is a limiter
|
||||
|
||||
# Make a constant signal at 6dB above the threshold, make 2* length of
|
||||
# attack time to keep the test quick
|
||||
x = np.ones(int(at*2*fs))
|
||||
x[:] = utils.db2gain(threshold + 6)
|
||||
x = utils.saturate_float_array(x, dspg.Q_SIG)
|
||||
|
||||
t = np.arange(len(x))/fs
|
||||
|
||||
rt = 0.3
|
||||
comp_type = "rms"
|
||||
comp_handle = getattr(drc, "compressor_%s" % comp_type)
|
||||
lim_handle = getattr(drc, "limiter_%s" % comp_type)
|
||||
|
||||
comp_thing = comp_handle(fs, 1, threshold, np.inf, at, rt)
|
||||
lim_thing = lim_handle(fs, 1, threshold, at, rt)
|
||||
|
||||
y_p = np.zeros_like(x)
|
||||
f_p = np.zeros_like(x)
|
||||
env_p = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_p)):
|
||||
y_p[n], f_p[n], env_p[n] = comp_thing.process(x[n])
|
||||
|
||||
y_r = np.zeros_like(x)
|
||||
f_r = np.zeros_like(x)
|
||||
env_r = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_r)):
|
||||
y_r[n], f_r[n], env_r[n] = lim_thing.process(x[n])
|
||||
|
||||
# limiter and infinite ratio compressor should be the same
|
||||
np.testing.assert_allclose(utils.db(y_p),
|
||||
utils.db(y_r),
|
||||
atol=0.002)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("at", [0.001, 0.01, 0.1, 0.5])
|
||||
@pytest.mark.parametrize("threshold", [-20, -10, 0])
|
||||
def test_peak_vs_rms(fs, at, threshold):
|
||||
# check peak and rms converge to same value
|
||||
|
||||
# Make a constant signal at 6dB above the threshold, make 2* length of
|
||||
# attack time to keep the test quick
|
||||
x = np.ones(int(at*10*fs))
|
||||
x[:] = utils.db2gain(threshold + 6)
|
||||
x = utils.saturate_float_array(x, dspg.Q_SIG)
|
||||
|
||||
t = np.arange(len(x))/fs
|
||||
|
||||
rt = 0.3
|
||||
comp_type = "limiter"
|
||||
peak_handle = getattr(drc, "%s_peak" % comp_type)
|
||||
rms_handle = getattr(drc, "%s_rms" % comp_type)
|
||||
|
||||
peak_thing = peak_handle(fs, 1, threshold, at, rt)
|
||||
rms_thing = rms_handle(fs, 1, threshold, at, rt)
|
||||
|
||||
y_p = np.zeros_like(x)
|
||||
f_p = np.zeros_like(x)
|
||||
env_p = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_p)):
|
||||
y_p[n], f_p[n], env_p[n] = peak_thing.process(x[n])
|
||||
|
||||
y_r = np.zeros_like(x)
|
||||
f_r = np.zeros_like(x)
|
||||
env_r = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_r)):
|
||||
y_r[n], f_r[n], env_r[n] = rms_thing.process(x[n])
|
||||
|
||||
# rms and peak limiter should converge to the same value
|
||||
np.testing.assert_allclose(utils.db(y_p[int(fs*at*5):]),
|
||||
utils.db(y_r[int(fs*at*5):]),
|
||||
atol=0.0022)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("at", [0.01])
|
||||
@pytest.mark.parametrize("threshold", [-10])
|
||||
def test_sidechain_mono_vs_comp(fs, at, threshold):
|
||||
# test a sidechain compressor is the same as a normal compressor
|
||||
# when the sidechain signal is the same as the input signal
|
||||
|
||||
ratio = 5
|
||||
|
||||
x = gen.sin(fs, 1, 1, 1)
|
||||
x = utils.saturate_float_array(x, dspg.Q_SIG)
|
||||
|
||||
t = np.arange(len(x))/fs
|
||||
|
||||
rt = 0.5
|
||||
comp_type = "compressor_rms"
|
||||
reg_handle = getattr(drc, "%s" % comp_type)
|
||||
side_handle = getattr(drc, "%s_sidechain_mono" % comp_type)
|
||||
|
||||
reg_thing = reg_handle(fs, 1, ratio, threshold, at, rt)
|
||||
side_thing = side_handle(fs, ratio, threshold, at, rt)
|
||||
|
||||
y_p = np.zeros_like(x)
|
||||
f_p = np.zeros_like(x)
|
||||
env_p = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_p)):
|
||||
y_p[n], f_p[n], env_p[n] = reg_thing.process(x[n])
|
||||
|
||||
y_r = np.zeros_like(x)
|
||||
f_r = np.zeros_like(x)
|
||||
env_r = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_r)):
|
||||
y_r[n], f_r[n], env_r[n] = side_thing.process(x[n], x[n])
|
||||
|
||||
# rms and peak limiter should converge to the same value
|
||||
np.testing.assert_array_equal(y_p, y_r)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("at", [0.01])
|
||||
@pytest.mark.parametrize("threshold", [-10])
|
||||
def test_sidechain_stereo(fs, at, threshold):
|
||||
# check peak and rms converge to same value
|
||||
|
||||
# Make a constant signal at 6dB above the threshold, make 2* length of
|
||||
# attack time to keep the test quick
|
||||
x = gen.sin(fs, 1, 1, 1)
|
||||
x = utils.saturate_float_array(x, dspg.Q_SIG)
|
||||
|
||||
x = np.stack([x, x], axis=0)
|
||||
t = np.arange(len(x))/fs
|
||||
|
||||
rt = 0.3
|
||||
comp_type = "compressor_rms"
|
||||
reg_handle = getattr(drc, "%s_stereo" % comp_type)
|
||||
side_handle = getattr(drc, "%s_sidechain_stereo" % comp_type)
|
||||
|
||||
reg_thing = reg_handle(fs, 1, threshold, at, rt)
|
||||
side_thing = side_handle(fs, 1, threshold, at, rt)
|
||||
|
||||
y_p = np.zeros_like(x)
|
||||
f_p = np.zeros_like(x)
|
||||
env_p = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_p)):
|
||||
y_p[:, n], f_p[:, n], env_p[:, n] = reg_thing.process_channels(x[:, n])
|
||||
|
||||
y_r = np.zeros_like(x)
|
||||
f_r = np.zeros_like(x)
|
||||
env_r = np.zeros_like(x)
|
||||
|
||||
# do the processing
|
||||
for n in range(len(y_r)):
|
||||
y_r[:, n], f_r[:, n], env_r[:, n] = side_thing.process_channels(x[:, n], x[:, n])
|
||||
|
||||
# rms and peak limiter should converge to the same value
|
||||
np.testing.assert_array_equal(y_p, y_r)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("component_mono, component_stereo, threshold, ratio", [("limiter_peak", "limiter_peak_stereo", -20, None),
|
||||
("limiter_peak", "limiter_peak_stereo", -6, None),
|
||||
("compressor_rms", "compressor_rms_stereo", 0, 6),
|
||||
("compressor_rms", "compressor_rms_stereo", 0, 2),
|
||||
("compressor_rms_sidechain_mono", "compressor_rms_sidechain_stereo", 0, 6),
|
||||
("compressor_rms_sidechain_mono", "compressor_rms_sidechain_stereo", 0, 2)])
|
||||
@pytest.mark.parametrize("rt", [0.2])
|
||||
@pytest.mark.parametrize("at", [0.001])
|
||||
def test_mono_vs_stereo(fs, component_mono, component_stereo, at, rt, threshold, ratio):
|
||||
# test the mono and stereo components have the same perforamnce when
|
||||
# fed a dual mono signal
|
||||
|
||||
signal = []
|
||||
length = 0.1 + (rt + at) * 2
|
||||
f = 997
|
||||
signal.append(gen.sin(fs, length, f, 1))
|
||||
signal.append(gen.sin(fs, length, f, 1))
|
||||
signal = np.stack(signal, axis=0)
|
||||
signal = utils.saturate_float_array(signal, dspg.Q_SIG)
|
||||
|
||||
if "sidechain" in component_stereo:
|
||||
sidechain_signal = np.zeros_like(signal)
|
||||
sidechain_signal[:, len(signal)//2:] = 1
|
||||
sidechain_signal = utils.saturate_float_array(sidechain_signal, dspg.Q_SIG)
|
||||
|
||||
stereo_component_handle = getattr(drc, component_stereo)
|
||||
mono_component_handle = getattr(drc, component_mono)
|
||||
if ratio is not None:
|
||||
drc_s = stereo_component_handle(fs, ratio, threshold, at, rt)
|
||||
if "sidechain" in component_mono:
|
||||
drc_m = mono_component_handle(fs, ratio, threshold, at, rt)
|
||||
else:
|
||||
drc_m = mono_component_handle(fs, 1, ratio, threshold, at, rt)
|
||||
|
||||
else:
|
||||
drc_s = stereo_component_handle(fs, threshold, at, rt)
|
||||
drc_m = mono_component_handle(fs, 1, threshold, at, rt)
|
||||
|
||||
|
||||
output_xcore_s = np.zeros(signal.shape)
|
||||
output_flt_s = np.zeros(signal.shape)
|
||||
|
||||
if "sidechain" in component_stereo:
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_xcore_s[:, n], _, _ = drc_s.process_channels_xcore(signal[:, n], sidechain_signal[:, n])
|
||||
drc_s.reset_state()
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_flt_s[:, n], _, _ = drc_s.process_channels(signal[:, n], sidechain_signal[:, n])
|
||||
|
||||
else:
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_xcore_s[:, n], _, _ = drc_s.process_channels_xcore(signal[:, n])
|
||||
drc_s.reset_state()
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_flt_s[:, n], _, _ = drc_s.process_channels(signal[:, n])
|
||||
drc_s.reset_state()
|
||||
|
||||
output_xcore_m = np.zeros(signal.shape)
|
||||
output_flt_m = np.zeros(signal.shape)
|
||||
|
||||
# write mono signal to both output channels, makes comparison to stereo easier
|
||||
if "sidechain" in component_mono:
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_xcore_m[:, n], _, _ = drc_m.process_xcore(signal[0, n], sidechain_signal[0, n])
|
||||
drc_m.reset_state()
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_flt_m[:, n], _, _ = drc_m.process(signal[0, n], sidechain_signal[0, n])
|
||||
else:
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_xcore_m[:, n], _, _ = drc_m.process_xcore(signal[0, n])
|
||||
drc_m.reset_state()
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_flt_m[:, n], _, _ = drc_m.process(signal[0, n])
|
||||
|
||||
# check stereo channels are the same
|
||||
np.testing.assert_array_equal(output_flt_s[0], output_flt_s[1])
|
||||
np.testing.assert_array_equal(output_xcore_s[0], output_xcore_s[1])
|
||||
|
||||
# check stereo equals mono
|
||||
#TODO this should be equal
|
||||
np.testing.assert_allclose(output_flt_s, output_flt_m, atol=2**-32)
|
||||
#TODO this should be array_equal
|
||||
np.testing.assert_allclose(output_xcore_s, output_xcore_m, atol=2**-31)
|
||||
|
||||
@pytest.mark.parametrize("component, threshold, ratio", [("noise_gate", -30, None),
|
||||
("noise_suppressor_expander", -30, 3)])
|
||||
def test_noise_gate(component, threshold, ratio):
|
||||
# test the noise gate performance on noisy speech
|
||||
signal, fs = make_noisy_speech()
|
||||
|
||||
test_len = int(6*fs)
|
||||
signal = signal[:test_len]
|
||||
signal = utils.saturate_float_array(signal, dspg.Q_SIG)
|
||||
|
||||
if ratio:
|
||||
drcut = drc.noise_suppressor_expander(fs, 1, ratio, threshold, 0.005, 0.2)
|
||||
else:
|
||||
drcut = drc.noise_gate(fs, 1, threshold, 0.005, 0.2)
|
||||
|
||||
|
||||
output_xcore = np.zeros(len(signal))
|
||||
output_flt = np.zeros(len(signal))
|
||||
gain_xcore = np.zeros(len(signal))
|
||||
gain_flt = np.zeros(len(signal))
|
||||
env_xcore = np.zeros(len(signal))
|
||||
env_flt = np.zeros(len(signal))
|
||||
|
||||
# noise gate has 3 outputs
|
||||
for n in np.arange(len(signal)):
|
||||
output_xcore[n], gain_xcore[n], env_xcore[n] = drcut.process_xcore(signal[n])
|
||||
drcut.reset_state()
|
||||
for n in np.arange(len(signal)):
|
||||
output_flt[n], gain_flt[n], env_flt[n] = drcut.process(signal[n])
|
||||
|
||||
sf.write("%s_test_in.wav" % component, signal, fs)
|
||||
sf.write("%s_test_out.wav" % component, output_flt, fs)
|
||||
|
||||
# small signals are always going to be ropey due to quantizing, so just check average error of top half
|
||||
top_half = utils.db(output_flt) > -50
|
||||
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("fs", [48000])
|
||||
@pytest.mark.parametrize("component, threshold, ratio", [("limiter_peak", 0, None),
|
||||
("limiter_rms", 0, None),
|
||||
("compressor_rms", 0, 6),
|
||||
("compressor_rms", 0, 2),
|
||||
("compressor_rms_softknee", 6, 6),
|
||||
("compressor_rms_softknee", 6, 2),
|
||||
("noise_gate", -1000, None),
|
||||
("noise_suppressor_expander", -1000, 5),
|
||||
("hard_limiter_peak", 0, None),
|
||||
("clipper", 0, None)])
|
||||
@pytest.mark.parametrize("rt, at", [[0.001, 0.2], [0.1, 0.5]])
|
||||
def test_drc_component_bypass(fs, component, at, rt, threshold, ratio):
|
||||
# test that a drc component is bit exact when the signal is below
|
||||
# the threshold (or above in the case of a noise gate).
|
||||
|
||||
component_handle = getattr(drc, component)
|
||||
|
||||
if threshold is not None:
|
||||
if ratio is not None:
|
||||
drcut = component_handle(fs, 1, ratio, threshold, at, rt)
|
||||
elif "clipper" in component:
|
||||
drcut = component_handle(fs, 1, threshold)
|
||||
else:
|
||||
drcut = component_handle(fs, 1, threshold, at, rt)
|
||||
else:
|
||||
drcut = component_handle(fs, 1, at, rt)
|
||||
|
||||
if "softknee" in component:
|
||||
# soft knee acts below threshold, so needs to be smaller for
|
||||
# bypass test
|
||||
signal = gen.log_chirp(fs, (0.1+(rt+at)*2), 0.5)
|
||||
else:
|
||||
signal = gen.log_chirp(fs, (0.1+(rt+at)*2), 1)
|
||||
signal = q_convert_flt(signal, 23, dspg.Q_SIG)
|
||||
signal = utils.saturate_float_array(signal, dspg.Q_SIG)
|
||||
|
||||
output_xcore = np.zeros(len(signal))
|
||||
output_flt = np.zeros(len(signal))
|
||||
|
||||
|
||||
if "envelope" in component or "clipper" in component:
|
||||
# envelope and clipper have 1 output
|
||||
for n in np.arange(len(signal)):
|
||||
output_xcore[n] = drcut.process_xcore(signal[n])
|
||||
if "clipper" not in component:
|
||||
drcut.reset_state()
|
||||
for n in np.arange(len(signal)):
|
||||
output_flt[n] = drcut.process(signal[n])
|
||||
else:
|
||||
# limiter and compressor have 3 outputs
|
||||
for n in np.arange(len(signal)):
|
||||
output_xcore[n], _, _ = drcut.process_xcore(signal[n])
|
||||
drcut.reset_state()
|
||||
for n in np.arange(len(signal)):
|
||||
output_flt[n], _, _ = drcut.process(signal[n])
|
||||
|
||||
np.testing.assert_array_equal(signal, output_flt)
|
||||
np.testing.assert_allclose(signal, output_xcore, atol=2**-32)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("component, threshold, ratio", [("limiter_peak", -20, None),
|
||||
("limiter_peak", 6, None),
|
||||
("limiter_rms", -20, None),
|
||||
("limiter_rms", 6, None),
|
||||
("envelope_detector_peak", None, None),
|
||||
("envelope_detector_rms", None, None),
|
||||
("compressor_rms", -20, 6),
|
||||
("compressor_rms", -20, 2),
|
||||
("compressor_rms", 6, 6),
|
||||
("compressor_rms", 6, 2),
|
||||
("compressor_rms_softknee", -20, 6),
|
||||
("compressor_rms_softknee", -20, 2),
|
||||
("compressor_rms_softknee", 6, 6),
|
||||
("compressor_rms_softknee", 6, 2),
|
||||
("noise_gate", -20, None),
|
||||
("noise_suppressor_expander", -20, 5),
|
||||
("hard_limiter_peak", -20, None),
|
||||
("hard_limiter_peak", 6, None),
|
||||
("clipper", -20, None),
|
||||
("clipper", 6, None)])
|
||||
@pytest.mark.parametrize("rt, at", [[0.001, 0.2], [0.1, 0.5], [0.001, 0.5], [0.1, 0.2]])
|
||||
def test_drc_component(fs, component, at, rt, threshold, ratio):
|
||||
# test the process_ functions of the drc components
|
||||
component_handle = getattr(drc, component)
|
||||
|
||||
if threshold is not None:
|
||||
if ratio is not None:
|
||||
drcut = component_handle(fs, 1, ratio, threshold, at, rt)
|
||||
elif "clipper" in component:
|
||||
drcut = component_handle(fs, 1, threshold)
|
||||
else:
|
||||
drcut = component_handle(fs, 1, threshold, at, rt)
|
||||
else:
|
||||
drcut = component_handle(fs, 1, at, rt)
|
||||
|
||||
signal = gen.log_chirp(fs, (0.1+(rt+at)*2), 1)
|
||||
len_sig = len(signal)
|
||||
|
||||
if threshold is not None:
|
||||
# If we are a limiter or compressor, have first half of signal above
|
||||
# the threshold and second half below
|
||||
signal[:len_sig//2] *= utils.db2gain(threshold + 6)
|
||||
signal[len_sig//2:] *= utils.db2gain(threshold - 3)
|
||||
else:
|
||||
# if we are an envelope detector, amplitude modulate with a sin to give
|
||||
# something to follow
|
||||
t = np.arange(len(signal))/fs
|
||||
signal *= np.sin(t*2*np.pi*0.5)
|
||||
|
||||
signal = q_convert_flt(signal, 23, dspg.Q_SIG)
|
||||
signal = utils.saturate_float_array(signal, dspg.Q_SIG)
|
||||
|
||||
output_xcore = np.zeros(len(signal))
|
||||
output_flt = np.zeros(len(signal))
|
||||
gain_xcore = np.zeros(len(signal))
|
||||
gain_flt = np.zeros(len(signal))
|
||||
env_xcore = np.zeros(len(signal))
|
||||
env_flt = np.zeros(len(signal))
|
||||
|
||||
if "envelope" in component or "clipper" in component:
|
||||
# envelope and clipper have 1 output
|
||||
for n in np.arange(len(signal)):
|
||||
output_xcore[n] = drcut.process_xcore(signal[n])
|
||||
if "clipper" not in component:
|
||||
drcut.reset_state()
|
||||
for n in np.arange(len(signal)):
|
||||
output_flt[n] = drcut.process(signal[n])
|
||||
else:
|
||||
# limiter and compressor have 3 outputs
|
||||
for n in np.arange(len(signal)):
|
||||
output_xcore[n], gain_xcore[n], env_xcore[n] = drcut.process_xcore(signal[n])
|
||||
drcut.reset_state()
|
||||
for n in np.arange(len(signal)):
|
||||
output_flt[n], gain_flt[n], env_flt[n] = drcut.process(signal[n])
|
||||
|
||||
# small signals are always going to be ropey due to quantizing, so just check average error of top half
|
||||
top_half = utils.db(output_flt) > -50
|
||||
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)))
|
||||
if "softknee" in component:
|
||||
# different implementation of knee adds more error
|
||||
if drcut.Q_sig > 27:
|
||||
pytest.skip("Soft knee breaks above Q4.27")
|
||||
else:
|
||||
assert mean_error_flt < 0.08
|
||||
else:
|
||||
assert mean_error_flt < 0.055
|
||||
|
||||
if "hard" in component or "clip" in component:
|
||||
# roudning error can occur in threshold
|
||||
assert np.all(output_xcore <= utils.db2gain(threshold) + 2**-(drcut.Q_sig + 1))
|
||||
assert np.all(output_flt <= utils.db2gain(threshold))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("component, threshold, ratio", [("limiter_peak", -20, None),
|
||||
("limiter_peak", 6, None),
|
||||
("limiter_rms", -20, None),
|
||||
("limiter_rms", 6, None),
|
||||
("envelope_detector_peak", None, None),
|
||||
("envelope_detector_rms", None, None),
|
||||
("compressor_rms", -20, 6),
|
||||
("compressor_rms", -20, 2),
|
||||
("compressor_rms", 6, 6),
|
||||
("compressor_rms", 6, 2),
|
||||
("compressor_rms_softknee", -20, 6),
|
||||
("compressor_rms_softknee", -20, 2),
|
||||
("compressor_rms_softknee", 6, 6),
|
||||
("compressor_rms_softknee", 6, 2),
|
||||
("noise_gate", -20, None),
|
||||
("noise_suppressor_expander", -20, 5),
|
||||
("hard_limiter_peak", -20, None),
|
||||
("hard_limiter_peak", 6, None),
|
||||
("clipper", -20, None),
|
||||
("clipper", 6, None)])
|
||||
@pytest.mark.parametrize("rt, at", [[0.001, 0.2], [0.1, 0.5]])
|
||||
@pytest.mark.parametrize("n_chans", [1, 2, 4])
|
||||
@pytest.mark.parametrize("q_format", [27, 31])
|
||||
def test_drc_component_frames(fs, component, at, rt, threshold, ratio, n_chans, q_format):
|
||||
# test the process_frame functions of the drc components
|
||||
|
||||
if q_format == 31 and rt != 0.2 and at != 0.001 and n_chans != 1:
|
||||
pytest.skip("Don't run all tests at Q0.31")
|
||||
|
||||
component_handle = getattr(drc, component)
|
||||
|
||||
if threshold is not None:
|
||||
if ratio is not None:
|
||||
drcut = component_handle(fs, n_chans, ratio, threshold, at, rt, Q_sig=q_format)
|
||||
elif "clipper" in component:
|
||||
drcut = component_handle(fs, n_chans, threshold, Q_sig=q_format)
|
||||
else:
|
||||
drcut = component_handle(fs, n_chans, threshold, at, rt, Q_sig=q_format)
|
||||
else:
|
||||
drcut = component_handle(fs, n_chans, at, rt)
|
||||
|
||||
signal = gen.log_chirp(fs, (0.1+(rt+at)*2), 1)
|
||||
len_sig = len(signal)
|
||||
if threshold is not None:
|
||||
signal[:len_sig//2] *= utils.db2gain(threshold + 6)
|
||||
signal[len_sig//2:] *= utils.db2gain(threshold - 3)
|
||||
else:
|
||||
t = np.arange(len(signal))/fs
|
||||
signal *= np.sin(t*2*np.pi*0.5)
|
||||
|
||||
signal = utils.saturate_float_array(signal, q_format)
|
||||
|
||||
signal = np.tile(signal, [n_chans, 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] = drcut.process_frame_xcore(signal_frames[n])
|
||||
if "clipper" not in component:
|
||||
drcut.reset_state()
|
||||
for n in range(len(signal_frames)):
|
||||
output_flt[:, n*frame_size:(n+1)*frame_size] = drcut.process_frame(signal_frames[n])
|
||||
|
||||
assert np.all(output_int[0, :] == output_int)
|
||||
assert np.all(output_flt[0, :] == output_flt)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs", [48000])
|
||||
@pytest.mark.parametrize("component, threshold, ratio", [("limiter_peak_stereo", -20, None),
|
||||
("limiter_peak_stereo", -6, None),
|
||||
("compressor_rms_stereo", 0, 6),
|
||||
("compressor_rms_stereo", 0, 2)])
|
||||
@pytest.mark.parametrize("rt, at", [[0.001, 0.2], [0.1, 0.5]])
|
||||
def test_stereo_components(fs, component, at, rt, threshold, ratio):
|
||||
# test the process_channels functions of the stereo drc components
|
||||
|
||||
component_handle = getattr(drc, component)
|
||||
if ratio is not None:
|
||||
drcut = component_handle(fs, ratio, threshold, at, rt)
|
||||
else:
|
||||
drcut = component_handle(fs, threshold, at, rt)
|
||||
|
||||
signal = []
|
||||
length = 0.1 + (rt + at) * 2
|
||||
f = 997
|
||||
signal.append(gen.sin(fs, length, f, 1))
|
||||
signal.append(gen.sin(fs, length, f, 0.5))
|
||||
signal = np.stack(signal, axis=0)
|
||||
signal = utils.saturate_float_array(signal, dspg.Q_SIG)
|
||||
|
||||
output_xcore = np.zeros(signal.shape)
|
||||
output_flt = np.zeros(signal.shape)
|
||||
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_xcore[:, n], _, _ = drcut.process_channels_xcore(signal[:, n])
|
||||
drcut.reset_state()
|
||||
for n in np.arange(signal.shape[1]):
|
||||
output_flt[:, n], _, _ = drcut.process_channels(signal[:, n])
|
||||
|
||||
error_flt = np.abs(utils.db(output_xcore)-utils.db(output_flt))
|
||||
mean_error_flt = utils.db(np.nanmean(utils.db2gain(error_flt)))
|
||||
assert mean_error_flt < 0.055
|
||||
|
||||
|
||||
# TODO more RMS limiter tests
|
||||
# TODO hard limiter test
|
||||
# TODO envelope detector tests
|
||||
# TODO compressor tests
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test_drc_component_frames(48000, "compressor_rms", 0.1, 0.5, 6, 6, 1, 31)
|
||||
# test_limiter_peak_attack(48000, 0.001, 0)
|
||||
# comp_vs_limiter(48000, 0.001, 0)
|
||||
# test_comp_ratio(48000, 0.00000001, 0.00000001, 2, 0)
|
||||
test_mono_vs_stereo(48000, "compressor_rms_sidechain_mono", "compressor_rms_sidechain_stereo", 0.001, 0.01, 0, 6)
|
||||
# test_sidechain_mono_vs_comp(16000, 0.05, -40)
|
||||
# test_noise_gate("noise_gate", -30, None)
|
||||
# test_drc_component_bypass(48000, "compressor_rms", 0.01, 0.2, 0, 6)
|
||||
Reference in New Issue
Block a user