From 2c799a2e85c3b0b9321845e6c5ddd76a29309b4e Mon Sep 17 00:00:00 2001 From: Nathan Lee Date: Thu, 1 Aug 2024 07:22:37 +0000 Subject: [PATCH] cras: dsp: Port 'drc_math.c' to rust code Rewrite 'drc_math.c' in rust for future use by 'drc_kernel' add unitests to verify the correctness. BUG=b:352433455 TEST=bazel test //... Disallow-Recycled-Builds: chromite-cq, test-failures Change-Id: Ic2691b56ace0b922d1a595dcae55c25c1666ff93 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/5755686 Reviewed-by: Li-Yu Yu Tested-by: chromeos-cop-builder@chromeos-cop.iam.gserviceaccount.com Commit-Queue: Nathan Lee --- Cargo.Bazel.lock | 95 +++++++-- Cargo.lock | 15 +- cras/src/dsp/rust/Cargo.toml | 2 + cras/src/dsp/rust/src/drc_math.rs | 311 ++++++++++++++++++++++++++++++ cras/src/dsp/rust/src/lib.rs | 1 + 5 files changed, 407 insertions(+), 17 deletions(-) create mode 100644 cras/src/dsp/rust/src/drc_math.rs diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index 9dbffaebc..ad5528a81 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "f7de0e8907328c83d03a76fc913f7c148657ad17050d3dd859022acd55e3057f", + "checksum": "eaf4e3f491c579c0e41f9c9773f4ecdd4fc3d809efff4591ad83af92f5a58a83", "crates": { "addr2line 0.20.0": { "name": "addr2line", @@ -2185,7 +2185,7 @@ "target": "nix" }, { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -2253,7 +2253,7 @@ "target": "log" }, { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -2353,7 +2353,7 @@ "target": "log" }, { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -2441,7 +2441,7 @@ "target": "log" }, { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -2665,6 +2665,10 @@ ], "deps": { "common": [ + { + "id": "float-cmp 0.9.0", + "target": "float_cmp" + }, { "id": "itertools 0.11.0", "target": "itertools" @@ -2676,6 +2680,10 @@ { "id": "nix 0.28.0", "target": "nix" + }, + { + "id": "num-traits 0.2.15", + "target": "num_traits" } ], "selects": {} @@ -3054,6 +3062,61 @@ ], "license_file": "LICENSE-APACHE" }, + "float-cmp 0.9.0": { + "name": "float-cmp", + "version": "0.9.0", + "package_url": "https://github.com/mikedilger/float-cmp", + "repository": { + "Http": { + "url": "https://static.crates.io/crates/float-cmp/0.9.0/download", + "sha256": "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + } + }, + "targets": [ + { + "Library": { + "crate_name": "float_cmp", + "crate_root": "src/lib.rs", + "srcs": { + "allow_empty": false, + "include": [ + "**/*.rs" + ] + } + } + } + ], + "library_target_name": "float_cmp", + "common_attrs": { + "compile_data_glob": [ + "**" + ], + "crate_features": { + "common": [ + "default", + "num-traits", + "ratio" + ], + "selects": {} + }, + "deps": { + "common": [ + { + "id": "num-traits 0.2.15", + "target": "num_traits" + } + ], + "selects": {} + }, + "edition": "2018", + "version": "0.9.0" + }, + "license": "MIT", + "license_ids": [ + "MIT" + ], + "license_file": "LICENSE" + }, "foreign-types 0.3.2": { "name": "foreign-types", "version": "0.3.2", @@ -5881,14 +5944,14 @@ ], "license_file": "LICENSE-APACHE" }, - "once_cell 1.17.0": { + "once_cell 1.19.0": { "name": "once_cell", - "version": "1.17.0", + "version": "1.19.0", "package_url": "https://github.com/matklad/once_cell", "repository": { "Http": { - "url": "https://static.crates.io/crates/once_cell/1.17.0/download", - "sha256": "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + "url": "https://static.crates.io/crates/once_cell/1.19.0/download", + "sha256": "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" } }, "targets": [ @@ -5920,7 +5983,7 @@ "selects": {} }, "edition": "2021", - "version": "1.17.0" + "version": "1.19.0" }, "license": "MIT OR Apache-2.0", "license_ids": [ @@ -5995,7 +6058,7 @@ "target": "libc" }, { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -6560,7 +6623,7 @@ "deps": { "common": [ { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -6628,7 +6691,7 @@ "target": "anyhow" }, { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" }, { @@ -9230,7 +9293,7 @@ "selects": { "cfg(windows)": [ { - "id": "once_cell 1.17.0", + "id": "once_cell 1.19.0", "target": "once_cell" } ] @@ -11921,6 +11984,7 @@ "dbus 0.9.7", "dbus-tokio 0.7.6", "fixedbitset 0.4.2", + "float-cmp 0.9.0", "futures 0.3.28", "getrandom 0.2.10", "hound 3.5.0", @@ -11928,7 +11992,8 @@ "libc 0.2.155", "log 0.4.20", "nix 0.28.0", - "once_cell 1.17.0", + "num-traits 0.2.15", + "once_cell 1.19.0", "openssl 0.10.62", "protobuf 3.2.0", "protobuf-codegen 3.2.0", diff --git a/Cargo.lock b/Cargo.lock index e1f7a7cf1..d58e01660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,9 +455,11 @@ dependencies = [ name = "dsp_rust" version = "0.1.0" dependencies = [ + "float-cmp", "itertools", "libc", "nix 0.28.0", + "num-traits", ] [[package]] @@ -515,6 +517,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -929,9 +940,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" diff --git a/cras/src/dsp/rust/Cargo.toml b/cras/src/dsp/rust/Cargo.toml index 4f81c5825..de63ed7a6 100644 --- a/cras/src/dsp/rust/Cargo.toml +++ b/cras/src/dsp/rust/Cargo.toml @@ -5,6 +5,8 @@ authors = ["The Chromium OS Authors"] edition = "2021" [dependencies] +float-cmp = "0.9.0" libc = "0.2" nix = "0.28.0" +num-traits = "0.2.15" itertools = "0.11.0" diff --git a/cras/src/dsp/rust/src/drc_math.rs b/cras/src/dsp/rust/src/drc_math.rs new file mode 100644 index 000000000..7375c254a --- /dev/null +++ b/cras/src/dsp/rust/src/drc_math.rs @@ -0,0 +1,311 @@ +// Copyright 2024 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use std::f64::consts::FRAC_2_PI; +use std::f64::consts::FRAC_PI_2; + +use num_traits::Float; + +#[allow(dead_code)] +const NEG_TWO_DB: f64 = 0.7943282347242815; // -2dB = 10^(-2/20) + +pub fn db_to_linear_table() -> [f32; 201] { + let mut arr: [f32; 201] = [0.; 201]; + for (i, j) in (-100..=100).enumerate() { + arr[i] = 10_f64.powf((j as f64) / 20.) as f32; + } + arr +} + +#[allow(non_upper_case_globals)] +pub fn db_to_linear(db: usize) -> f32 { + thread_local! { + static table: [f32; 201] = db_to_linear_table(); + } + table.with(|x| x[db]) +} + +#[allow(dead_code)] +pub fn isbadf(x: f32) -> bool { + let bits: u32 = x.to_bits(); + let exp = (bits >> 23) & 0xff; + exp == 0xff +} + +#[allow(dead_code)] +pub mod slow { + use super::Float; + use super::FRAC_2_PI; + use super::FRAC_PI_2; + + pub fn decibels_to_linear(decibels: f32) -> f32 { + // 10^(x/20) = e^(x * log(10^(1/20))) + (0.1151292546497022 * decibels).exp() + } + + pub fn frexpf(x: f32, e: &mut i32) -> f32 { + if x == 0. { + *e = 0; + return 0.; + } + let (man, exp, sign) = Float::integer_decode(x); + *e = (man as f32).log2().floor() as i32 + exp as i32 + 1; + sign as f32 * 2_f32.powf(((man as f64).log2().fract() - 1.) as f32) + } + + pub fn linear_to_decibels(linear: f32) -> f32 { + if linear <= 0. { + return -1000.; + } + // 20 * log10(x) = 20 / log(10) * log(x) + 8.6858896380650366 * linear.ln() + } + + pub fn warp_sinf(x: f32) -> f32 { + ((FRAC_PI_2 as f32) * x).sin() + } + + pub fn warp_asinf(x: f32) -> f32 { + x.asin() * (FRAC_2_PI as f32) + } + + pub fn knee_expf(input: f32) -> f32 { + input.exp() + } +} + +#[allow(non_snake_case, dead_code)] +pub mod fast { + use super::db_to_linear; + use super::FRAC_2_PI; + + pub fn decibels_to_linear(decibels: f32) -> f32 { + let fi: f32 = decibels.round(); + let x: f32 = decibels - fi; + let i: i32 = (fi as i32).min(100).max(-100); + + /* Coefficients obtained from: + * fpminimax(10^(x/20), [|1,2,3|], [|SG...|], [-0.5;0.5], 1, absolute); + * max error ~= 7.897e-8 + */ + let A3: f32 = 2.54408805631101131439208984375e-4; + let A2: f32 = 6.628888659179210662841796875e-3; + let A1: f32 = 0.11512924730777740478515625; + let A0: f32 = 1.; + + let x2: f32 = x * x; + ((A3 * x + A2) * x2 + (A1 * x + A0)) * db_to_linear((i + 100) as usize) + } + + pub fn frexpf(x: f32, e: &mut i32) -> f32 { + if x == 0. { + *e = 0; + return 0.; + } + let bits: u32 = x.to_bits(); + let neg: u32 = bits >> 31; + let exp: i32 = ((bits >> 23) & 0xff) as i32; + let man: u32 = bits & 0x7fffff; + *e = (exp - 126) as i32; + f32::from_bits(neg << 31 | 126 << 23 | man) + } + + pub fn linear_to_decibels(linear: f32) -> f32 { + if linear <= 0. { + return -1000.; + } + let mut e: i32 = 0; + let mut x: f32 = frexpf(linear, &mut e); + let mut exp: f32 = e as f32; + + if x > 0.707106781186548 { + x *= 0.707106781186548; + exp += 0.5; + } + + /* Coefficients obtained from: + * fpminimax(log10(x), 5, [|SG...|], [1/2;sqrt(2)/2], absolute); + * max err ~= 6.088e-8 + */ + let A5: f32 = 1.131880283355712890625; + let A4: f32 = -4.258677959442138671875; + let A3: f32 = 6.81631565093994140625; + let A2: f32 = -6.1185703277587890625; + let A1: f32 = 3.6505267620086669921875; + let A0: f32 = -1.217894077301025390625; + + let x2: f32 = x * x; + let x4: f32 = x2 * x2; + ((A5 * x + A4) * x4 + (A3 * x + A2) * x2 + (A1 * x + A0)) * 20. + exp * 6.0205999132796239 + } + + pub fn warp_sinf(x: f32) -> f32 { + let A7: f32 = -4.3330336920917034149169921875e-3; + let A5: f32 = 7.9434238374233245849609375e-2; + let A3: f32 = -0.645892798900604248046875; + let A1: f32 = 1.5707910060882568359375; + + let x2: f32 = x * x; + let x4: f32 = x2 * x2; + x * ((A7 * x2 + A5) * x4 + (A3 * x2 + A1)) + } + + pub fn warp_asinf(x: f32) -> f32 { + x.asin() * (FRAC_2_PI as f32) + } + + pub fn knee_expf(input: f32) -> f32 { + // exp(x) = decibels_to_linear(20*log10(e)*x) + decibels_to_linear(8.685889638065044 * input) + } +} + +#[allow(unused_imports)] +pub use fast::*; + +#[cfg(test)] +mod tests { + use float_cmp::assert_approx_eq; + + use crate::drc_math::fast; + use crate::drc_math::slow; + + // The functions in the slow module are built with library functions, and + // the functions in the fast module are approximating functions we + // implemented. We compare the output of the same functions in fast module + // and slow module to verify the correctness of functions in fast module. + + #[test] + fn decibels_to_linear_test() { + assert_approx_eq!( + f32, + fast::decibels_to_linear(0.), + slow::decibels_to_linear(0.) + ); + assert_approx_eq!( + f32, + fast::decibels_to_linear(5.), + slow::decibels_to_linear(5.) + ); + assert_approx_eq!( + f32, + fast::decibels_to_linear(10.), + slow::decibels_to_linear(10.) + ); + assert_approx_eq!( + f32, + fast::decibels_to_linear(15.), + slow::decibels_to_linear(15.) + ); + assert_approx_eq!( + f32, + fast::decibels_to_linear(20.), + slow::decibels_to_linear(20.) + ); + } + + #[test] + fn frexpf_test() { + let mut e1: i32 = 0; + let mut e2: i32 = 0; + assert_approx_eq!(f32, fast::frexpf(0., &mut e1), slow::frexpf(0., &mut e2)); + assert_eq!(e1, e2); + assert_approx_eq!( + f32, + fast::frexpf(0.05, &mut e1), + slow::frexpf(0.05, &mut e2) + ); + assert_eq!(e1, e2); + assert_approx_eq!( + f32, + fast::frexpf(-0.05, &mut e1), + slow::frexpf(-0.05, &mut e2) + ); + assert_eq!(e1, e2); + assert_approx_eq!( + f32, + fast::frexpf(500., &mut e1), + slow::frexpf(500., &mut e2) + ); + assert_eq!(e1, e2); + assert_approx_eq!( + f32, + fast::frexpf(10000., &mut e1), + slow::frexpf(10000., &mut e2) + ); + assert_eq!(e1, e2); + } + + #[test] + fn linear_to_decibels_test() { + assert_approx_eq!( + f32, + fast::linear_to_decibels(0.), + slow::linear_to_decibels(0.) + ); + assert_approx_eq!( + f32, + fast::linear_to_decibels(0.05), + slow::linear_to_decibels(0.05) + ); + assert_approx_eq!( + f32, + fast::linear_to_decibels(-0.05), + slow::linear_to_decibels(-0.05) + ); + assert_approx_eq!( + f32, + fast::linear_to_decibels(500.), + slow::linear_to_decibels(500.) + ); + assert_approx_eq!( + f32, + fast::linear_to_decibels(10000.), + slow::linear_to_decibels(10000.) + ); + } + + #[test] + fn warp_sinf_test() { + assert_approx_eq!( + f32, + fast::warp_sinf(0.), + slow::warp_sinf(0.), + epsilon = 0.00001 + ); + assert_approx_eq!( + f32, + fast::warp_sinf(0.2), + slow::warp_sinf(0.2), + epsilon = 0.00001 + ); + assert_approx_eq!( + f32, + fast::warp_sinf(0.4), + slow::warp_sinf(0.4), + epsilon = 0.00001 + ); + assert_approx_eq!( + f32, + fast::warp_sinf(0.6), + slow::warp_sinf(0.6), + epsilon = 0.00001 + ); + assert_approx_eq!( + f32, + fast::warp_sinf(0.8), + slow::warp_sinf(0.8), + epsilon = 0.00001 + ); + } + + #[test] + fn knee_expf_test() { + assert_approx_eq!(f32, fast::knee_expf(0.), slow::knee_expf(0.)); + assert_approx_eq!(f32, fast::knee_expf(1.), slow::knee_expf(1.)); + assert_approx_eq!(f32, fast::knee_expf(0.5), slow::knee_expf(0.5)); + assert_approx_eq!(f32, fast::knee_expf(-2.), slow::knee_expf(-2.)); + assert_approx_eq!(f32, fast::knee_expf(5.), slow::knee_expf(5.)); + } +} diff --git a/cras/src/dsp/rust/src/lib.rs b/cras/src/dsp/rust/src/lib.rs index 2fe9ac174..22c8a9519 100644 --- a/cras/src/dsp/rust/src/lib.rs +++ b/cras/src/dsp/rust/src/lib.rs @@ -10,6 +10,7 @@ mod crossover2_bindings; mod crossover_bindings; mod dcblock; mod dcblock_bindings; +mod drc_math; mod eq; mod eq2; mod eq2_bindings;