From a46dc45dc9491de56eeb23ec3a907db6cdab5d11 Mon Sep 17 00:00:00 2001 From: bglid Date: Wed, 30 Sep 2026 07:56:46 -0400 Subject: [PATCH 01/21] integration init with gitignore updates. --- .gitignore | 10 ++- .../setup_integration_data.py | 64 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/integration_tests/setup_integration_data.py diff --git a/.gitignore b/.gitignore index ec0a3be..c27906f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,14 @@ !python/adaptif/*.pyi !tests/ -!tests/**/ !tests/**/*.py +# integration tests specifics +!tests/integration_tests/python/ +!tests/integration_tests/python/**/*.py +!tests/integration_tests/rust/ +!tests/integration_tests/rust/**/*.rs + !examples/ !examples/**/ !examples/**/*.rs @@ -39,5 +44,6 @@ !.github/**/*.md !.pre-commit-config.yaml -# explitic excludes +# explicit excludes **/__pycache__/ +**/data diff --git a/tests/integration_tests/setup_integration_data.py b/tests/integration_tests/setup_integration_data.py new file mode 100644 index 0000000..b973a64 --- /dev/null +++ b/tests/integration_tests/setup_integration_data.py @@ -0,0 +1,64 @@ +import configparser +import subprocess +from pathlib import Path + +DATA_DIR = Path(__file__).parent / "data" +MS_SNSD = DATA_DIR / "MS-SNSD" +CONFIG = MS_SNSD / "noisyspeech_synthesizer.cfg" + + +def clone_dataset() -> None: + """Grab the repo only if it doesn't already exist in the integration test.""" + if MS_SNSD.exists(): + return + + DATA_DIR.mkdir(parents=True, exist_ok=True) + + subprocess.run( + [ + "git", + "clone", + "--depth", + "1", + "https://github.com/microsoft/MS-SNSD.git", + MS_SNSD, + ], + check=True, + ) + + +def configure_dataset() -> None: + """Configure the settings of the dataset generator.""" + config = configparser.ConfigParser() + proof = config.read(CONFIG) + # debuggin + print(proof) + + dataset = config["noisy_speech"] + + # editing the params of the dataset + dataset["audio_length"] = "5" + dataset["total_hours"] = "0.02" + dataset["snr_lower"] = "0" + dataset["snr_upper"] = "20" + dataset["total_snrlevels"] = "3" + + with CONFIG.open("w") as file: + config.write(file) + + +def generate_dataset() -> None: + """Generate the MS-SNSD dataset given the configured params in `configure_dataset()`""" + subprocess.run(["uv", "run", "noisyspeech_synthesizer.py"], cwd=MS_SNSD, check=True) + + +def main() -> None: + DATA_DIR.mkdir(parents=True, exist_ok=True) + + clone_dataset() + configure_dataset() + generate_dataset() + + +if __name__ == "__main__": + main() From 0de305c963fb20eefdba57c3db1cc6a52a165d5e Mon Sep 17 00:00:00 2001 From: bglid Date: Thu, 1 Oct 2026 08:06:19 -0400 Subject: [PATCH 02/21] Moving tests and setting up Make for integration vs unit tests --- .github/workflows/python-ci.yml | 8 +- .github/workflows/rust-ci.yml | 9 +- Makefile | 32 +++- pyproject.toml | 2 +- tests/README.md | 4 + tests/__init__.py | 0 .../setup_integration_data.py | 0 tests/test_bindings.py | 40 ----- tests/test_filters.py | 152 ------------------ 9 files changed, 43 insertions(+), 204 deletions(-) create mode 100644 tests/README.md delete mode 100644 tests/__init__.py rename tests/{integration_tests => }/setup_integration_data.py (100%) delete mode 100644 tests/test_bindings.py delete mode 100644 tests/test_filters.py diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index e312d4a..77ab998 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -47,9 +47,13 @@ jobs: - name: Run type checks run: uv run ty check - - name: Run tests + - name: Run unit tests run: | - uv run pytest + make test-py-unit + + - name: Run integration tests + run: | + make test-py-integration - name: Run safety checks run: uv audit diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 2dd5c11..a81f9bd 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -4,12 +4,14 @@ on: push: paths: - "src/**" + - "tests/rust/**" - ".github/workflows/rust-ci.yml" - "Cargo.toml" - "Cargo.lock" pull_request: paths: - "src/**" + - "tests/rust/**" - ".github/workflows/rust-ci.yml" - "Cargo.toml" - "Cargo.lock" @@ -51,8 +53,11 @@ jobs: - name: Build run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - name: Run unit tests + run: cargo test --lib --verbose + + - name: Run integration tests + run: make test-rs-integration - name: Audit dependencies run: cargo audit diff --git a/Makefile b/Makefile index a48a21c..f5427db 100644 --- a/Makefile +++ b/Makefile @@ -62,17 +62,35 @@ check-py: $(PY_BINDINGS) uv run ty check check-all: check-rs check-py +################################################## +# TESTS +################################################## + +.PHONY: \ + test-rs test-rs-unit test-rs-integration \ + test-py test-py-unit test-py-integration \ + test-unit test-integration test-all -# Run tests -.PHONY: test-rs test-py test-all -test-rs: check-rust-install +# Rust +test-rs-unit: check-rust-install # NOTE: `--all-features` currently produces a linker error, smth to do with PyO3. # Since we don't have any tests in the Python features, leaving it out for now. - cargo tarpaulin --frozen --skip-clean -test-py: $(PY_BINDINGS) - uv run pytest -test-all: test-rs test-py + cargo tarpaulin --lib --frozen --skip-clean +test-rs-integration: + cargo test --test rust --frozen +test-rs: test-rs-unit test-rs-integration + +# Python +test-py-unit: $(PY_BINDINGS) + uv run pytest python/tests +test-py-integration: $(PY_BINDINGS) + uv run pytest tests/python +test-py: test-py-unit test-py-integration +# Test type grouping +test-unit: test-rs-unit test-py-unit +test-integration: test-rs-integration test-py-integration +test-all: test-rs test-py # Audit dependencies .PHONY: audit diff --git a/pyproject.toml b/pyproject.toml index 02d53a0..d871bf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ include = [ [tool.pytest.ini_options] # https://docs.pytest.org/en/6.2.x/customize.html#pyproject-toml # Directories that are not visited by pytest collector: -norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__"] +norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__", "tests/data"] doctest_optionflags = ["NUMBER", "NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"] # Extra options: diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..3c50a4f --- /dev/null +++ b/tests/README.md @@ -0,0 +1,4 @@ +## Integration tests + + +*TODO: Add more information on tests and what they calculate...* diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/integration_tests/setup_integration_data.py b/tests/setup_integration_data.py similarity index 100% rename from tests/integration_tests/setup_integration_data.py rename to tests/setup_integration_data.py diff --git a/tests/test_bindings.py b/tests/test_bindings.py deleted file mode 100644 index c6d3f71..0000000 --- a/tests/test_bindings.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter - - -# TODO: update kwargs once default args are added (+ in other tests too) -@pytest.mark.parametrize( - ["filter_class", "kwargs"], - [ - (LMSFilter, {"mu": 1.0, "window_size": 1024}), - (NLMSFilter, {"mu": 1.0, "eps": 1e-8, "window_size": 1024}), - ( - RLSFilter, - {"forgetting_factor": 0.5, "p_init_scale": 1.0, "window_size": 1024}, - ), - ], -) -def test_filter_bindings(filter_class, kwargs): - filter = filter_class(**kwargs) - assert hasattr(filter_class, "from_weights") - assert hasattr(filter, "window_size") - assert hasattr(filter, "weights") - assert hasattr(filter, "adapt") - assert hasattr(filter, "filter") - assert not hasattr(filter, "block_size") - - -@pytest.mark.parametrize( - ["filter_class", "kwargs"], - [ - (BlockLMSFilter, {"mu": 1.0, "window_size": 1024, "block_size": 1024}), - ], -) -def test_block_filter_bindings(filter_class, kwargs): - filter = filter_class(**kwargs) - assert hasattr(filter_class, "from_weights") - assert hasattr(filter, "window_size") - assert hasattr(filter, "block_size") - assert hasattr(filter, "weights") - assert hasattr(filter, "adapt") - assert hasattr(filter, "filter") diff --git a/tests/test_filters.py b/tests/test_filters.py deleted file mode 100644 index d0ce279..0000000 --- a/tests/test_filters.py +++ /dev/null @@ -1,152 +0,0 @@ -import numpy as np -import pytest -from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter - - -@pytest.fixture( - params=[ - (LMSFilter, {"mu": 1.0, "window_size": 1024}), - (NLMSFilter, {"mu": 1.0, "eps": 1e-8, "window_size": 1024}), - ( - RLSFilter, - {"forgetting_factor": 0.5, "p_init_scale": 1.0, "window_size": 1024}, - ), - (BlockLMSFilter, {"mu": 1.0, "window_size": 1024, "block_size": 1024}), - ] -) -def filter(request): - filter_class, kwargs = request.param - return filter_class(**kwargs) - - -@pytest.mark.parametrize( - ["filter_class", "kwargs"], - [ - (LMSFilter, {"mu": 1.0}), - (NLMSFilter, {"mu": 1.0, "eps": 1e-8}), - (RLSFilter, {"forgetting_factor": 0.5, "p_init_scale": 1.0}), - (BlockLMSFilter, {"mu": 1.0, "block_size": 1024}), - ], -) -def test_window_size(filter_class, kwargs): - kwargs["window_size"] = 1 - filter = filter_class(**kwargs) - assert filter.window_size == 1 - - kwargs["window_size"] = -1 - with pytest.raises(OverflowError): - filter_class(**kwargs) - - kwargs["window_size"] = 0 - with pytest.raises(ValueError): - filter_class(**kwargs) - - -@pytest.mark.parametrize("filter_class", [BlockLMSFilter]) -def test_block_size(filter_class): - filter = filter_class(mu=0.1, window_size=1024, block_size=1) - assert filter.block_size == 1 - - with pytest.raises(OverflowError): - filter_class(mu=0.1, window_size=1024, block_size=-1) - with pytest.raises(ValueError): - filter_class(mu=0.1, window_size=1024, block_size=0) - - -def test_from_weights(): - weights = np.array([1.0, 2.0, 3.0]) - filter = LMSFilter.from_weights(1.0, weights) - assert filter.window_size == 3 - assert np.allclose(filter.weights, weights) - - with pytest.raises(ValueError): - LMSFilter.from_weights(1.0, np.array([])) - - -def test_weights(filter): - assert isinstance(filter.weights, np.ndarray) - assert np.allclose(filter.weights, np.zeros(filter.window_size)) - - -def test_adapt(filter): - if isinstance(filter, BlockLMSFilter): - # Block LMS doesn't update on the final block (see Rust code for reason) - # Since the test signals fit within one block we need a filter with a smaller - # block size to test if the weights update. - filter = BlockLMSFilter(mu=0.1, window_size=1, block_size=1) - input_signal = np.linspace(1, 5, 5) - noise_ref = np.linspace(0.5, 2.5, 5) - - output = filter.adapt(input_signal, noise_ref) - - assert isinstance(output, np.ndarray) - assert output.shape == input_signal.shape - assert not np.allclose(filter.weights, np.zeros(filter.window_size)) - - -def test_filter(filter): - input_signal = np.linspace(1, 5, 5) - noise_ref = np.linspace(0.5, 2.5, 5) - - output = filter.filter(input_signal, noise_ref) - - assert isinstance(output, np.ndarray) - assert output.shape == input_signal.shape - assert np.allclose(filter.weights, np.zeros(filter.window_size)) - - -@pytest.mark.parametrize( - "input, noise", - [ - (np.linspace(1, 5, 5), np.array([])), - (np.array([]), np.linspace(0.5, 2.5, 5)), - (np.array([]), np.array([])), - ], -) -def test_empty_input(filter, input, noise): - with pytest.raises(ValueError): - filter.adapt(input, noise) - with pytest.raises(ValueError): - filter.filter(input, noise) - - -@pytest.mark.parametrize("fn_name", ["adapt", "filter"]) -def test_signal_lengths(filter, fn_name): - filter_fn = getattr(filter, fn_name) - - short_input = np.linspace(1, 5, 4) - long_input = np.linspace(1, 5, 6) - noise_ref = np.linspace(0.5, 2.5, 5) - - short_output = filter_fn(short_input, noise_ref) - assert isinstance(short_output, np.ndarray) - assert short_output.shape == short_input.shape - - with pytest.raises(ValueError): - filter_fn(long_input, noise_ref) - - -# NOTE: this test is only needed if input signals are passed by reference -# If they are copied instead, the can be non-contiguous. -# @pytest.mark.parametrize("fn_name", ["adapt", "filter"]) -# def test_input_contiguous(filter, fn_name): -# filter_fn = getattr(filter, fn_name) -# -# input_signal = np.linspace(1, 5, 10) -# noise_ref = np.linspace(0.5, 2.5, 10) -# -# # regular slices are contiguous -> allowed -# filter_fn(input_signal[:5], noise_ref[:5]) -# -# # strided slices are not contiguous -> not allowed -# with pytest.raises(ValueError): -# filter_fn(input_signal[::2], noise_ref[:5]) -# with pytest.raises(ValueError): -# filter_fn(input_signal[:5], noise_ref[::2]) -# with pytest.raises(ValueError): -# filter_fn(input_signal[::2], noise_ref[::2]) -# -# # calling np.ascontiguousarray makes it possible to use non-contiguous arrays as input -# filter_fn( -# np.ascontiguousarray(input_signal[::2]), np.ascontiguousarray(noise_ref[::2]) -# ) From 6ae358f96cf2ff6d3fecea689195bdb83e255b67 Mon Sep 17 00:00:00 2001 From: bglid Date: Thu, 1 Oct 2026 08:09:25 -0400 Subject: [PATCH 03/21] updating the gitignore --- .gitignore | 13 +-- python/tests/__init__.py | 0 python/tests/test_bindings.py | 40 +++++++++ python/tests/test_filters.py | 152 ++++++++++++++++++++++++++++++++++ tests/rust/filters.rs | 14 ++++ tests/rust/main.rs | 1 + 6 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 python/tests/__init__.py create mode 100644 python/tests/test_bindings.py create mode 100644 python/tests/test_filters.py create mode 100644 tests/rust/filters.rs create mode 100644 tests/rust/main.rs diff --git a/.gitignore b/.gitignore index c27906f..337adef 100644 --- a/.gitignore +++ b/.gitignore @@ -10,14 +10,15 @@ !python/adaptif/*.py !python/adaptif/*.pyi -!tests/ -!tests/**/*.py +# python unit tests +!python/tests/ +!python/tests/**/*.py # integration tests specifics -!tests/integration_tests/python/ -!tests/integration_tests/python/**/*.py -!tests/integration_tests/rust/ -!tests/integration_tests/rust/**/*.rs +!tests/python/ +!tests/python/**/*.py +!tests/rust/ +!tests/rust/**/*.rs !examples/ !examples/**/ diff --git a/python/tests/__init__.py b/python/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/test_bindings.py b/python/tests/test_bindings.py new file mode 100644 index 0000000..c6d3f71 --- /dev/null +++ b/python/tests/test_bindings.py @@ -0,0 +1,40 @@ +import pytest +from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter + + +# TODO: update kwargs once default args are added (+ in other tests too) +@pytest.mark.parametrize( + ["filter_class", "kwargs"], + [ + (LMSFilter, {"mu": 1.0, "window_size": 1024}), + (NLMSFilter, {"mu": 1.0, "eps": 1e-8, "window_size": 1024}), + ( + RLSFilter, + {"forgetting_factor": 0.5, "p_init_scale": 1.0, "window_size": 1024}, + ), + ], +) +def test_filter_bindings(filter_class, kwargs): + filter = filter_class(**kwargs) + assert hasattr(filter_class, "from_weights") + assert hasattr(filter, "window_size") + assert hasattr(filter, "weights") + assert hasattr(filter, "adapt") + assert hasattr(filter, "filter") + assert not hasattr(filter, "block_size") + + +@pytest.mark.parametrize( + ["filter_class", "kwargs"], + [ + (BlockLMSFilter, {"mu": 1.0, "window_size": 1024, "block_size": 1024}), + ], +) +def test_block_filter_bindings(filter_class, kwargs): + filter = filter_class(**kwargs) + assert hasattr(filter_class, "from_weights") + assert hasattr(filter, "window_size") + assert hasattr(filter, "block_size") + assert hasattr(filter, "weights") + assert hasattr(filter, "adapt") + assert hasattr(filter, "filter") diff --git a/python/tests/test_filters.py b/python/tests/test_filters.py new file mode 100644 index 0000000..d0ce279 --- /dev/null +++ b/python/tests/test_filters.py @@ -0,0 +1,152 @@ +import numpy as np +import pytest +from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter + + +@pytest.fixture( + params=[ + (LMSFilter, {"mu": 1.0, "window_size": 1024}), + (NLMSFilter, {"mu": 1.0, "eps": 1e-8, "window_size": 1024}), + ( + RLSFilter, + {"forgetting_factor": 0.5, "p_init_scale": 1.0, "window_size": 1024}, + ), + (BlockLMSFilter, {"mu": 1.0, "window_size": 1024, "block_size": 1024}), + ] +) +def filter(request): + filter_class, kwargs = request.param + return filter_class(**kwargs) + + +@pytest.mark.parametrize( + ["filter_class", "kwargs"], + [ + (LMSFilter, {"mu": 1.0}), + (NLMSFilter, {"mu": 1.0, "eps": 1e-8}), + (RLSFilter, {"forgetting_factor": 0.5, "p_init_scale": 1.0}), + (BlockLMSFilter, {"mu": 1.0, "block_size": 1024}), + ], +) +def test_window_size(filter_class, kwargs): + kwargs["window_size"] = 1 + filter = filter_class(**kwargs) + assert filter.window_size == 1 + + kwargs["window_size"] = -1 + with pytest.raises(OverflowError): + filter_class(**kwargs) + + kwargs["window_size"] = 0 + with pytest.raises(ValueError): + filter_class(**kwargs) + + +@pytest.mark.parametrize("filter_class", [BlockLMSFilter]) +def test_block_size(filter_class): + filter = filter_class(mu=0.1, window_size=1024, block_size=1) + assert filter.block_size == 1 + + with pytest.raises(OverflowError): + filter_class(mu=0.1, window_size=1024, block_size=-1) + with pytest.raises(ValueError): + filter_class(mu=0.1, window_size=1024, block_size=0) + + +def test_from_weights(): + weights = np.array([1.0, 2.0, 3.0]) + filter = LMSFilter.from_weights(1.0, weights) + assert filter.window_size == 3 + assert np.allclose(filter.weights, weights) + + with pytest.raises(ValueError): + LMSFilter.from_weights(1.0, np.array([])) + + +def test_weights(filter): + assert isinstance(filter.weights, np.ndarray) + assert np.allclose(filter.weights, np.zeros(filter.window_size)) + + +def test_adapt(filter): + if isinstance(filter, BlockLMSFilter): + # Block LMS doesn't update on the final block (see Rust code for reason) + # Since the test signals fit within one block we need a filter with a smaller + # block size to test if the weights update. + filter = BlockLMSFilter(mu=0.1, window_size=1, block_size=1) + input_signal = np.linspace(1, 5, 5) + noise_ref = np.linspace(0.5, 2.5, 5) + + output = filter.adapt(input_signal, noise_ref) + + assert isinstance(output, np.ndarray) + assert output.shape == input_signal.shape + assert not np.allclose(filter.weights, np.zeros(filter.window_size)) + + +def test_filter(filter): + input_signal = np.linspace(1, 5, 5) + noise_ref = np.linspace(0.5, 2.5, 5) + + output = filter.filter(input_signal, noise_ref) + + assert isinstance(output, np.ndarray) + assert output.shape == input_signal.shape + assert np.allclose(filter.weights, np.zeros(filter.window_size)) + + +@pytest.mark.parametrize( + "input, noise", + [ + (np.linspace(1, 5, 5), np.array([])), + (np.array([]), np.linspace(0.5, 2.5, 5)), + (np.array([]), np.array([])), + ], +) +def test_empty_input(filter, input, noise): + with pytest.raises(ValueError): + filter.adapt(input, noise) + with pytest.raises(ValueError): + filter.filter(input, noise) + + +@pytest.mark.parametrize("fn_name", ["adapt", "filter"]) +def test_signal_lengths(filter, fn_name): + filter_fn = getattr(filter, fn_name) + + short_input = np.linspace(1, 5, 4) + long_input = np.linspace(1, 5, 6) + noise_ref = np.linspace(0.5, 2.5, 5) + + short_output = filter_fn(short_input, noise_ref) + assert isinstance(short_output, np.ndarray) + assert short_output.shape == short_input.shape + + with pytest.raises(ValueError): + filter_fn(long_input, noise_ref) + + +# NOTE: this test is only needed if input signals are passed by reference +# If they are copied instead, the can be non-contiguous. +# @pytest.mark.parametrize("fn_name", ["adapt", "filter"]) +# def test_input_contiguous(filter, fn_name): +# filter_fn = getattr(filter, fn_name) +# +# input_signal = np.linspace(1, 5, 10) +# noise_ref = np.linspace(0.5, 2.5, 10) +# +# # regular slices are contiguous -> allowed +# filter_fn(input_signal[:5], noise_ref[:5]) +# +# # strided slices are not contiguous -> not allowed +# with pytest.raises(ValueError): +# filter_fn(input_signal[::2], noise_ref[:5]) +# with pytest.raises(ValueError): +# filter_fn(input_signal[:5], noise_ref[::2]) +# with pytest.raises(ValueError): +# filter_fn(input_signal[::2], noise_ref[::2]) +# +# # calling np.ascontiguousarray makes it possible to use non-contiguous arrays as input +# filter_fn( +# np.ascontiguousarray(input_signal[::2]), np.ascontiguousarray(noise_ref[::2]) +# ) diff --git a/tests/rust/filters.rs b/tests/rust/filters.rs new file mode 100644 index 0000000..a3895de --- /dev/null +++ b/tests/rust/filters.rs @@ -0,0 +1,14 @@ +#![allow( + clippy::tests_outside_test_module, + reason = "This crate is exclusively an integration test target" +)] +use adaptif::{algorithms::Lms, filters}; + +// Just getting this setup +#[test] +fn lms_filters_signal() { + #[allow(clippy::unwrap_used, reason = "Tests")] + let lms_config = Lms::new(1.0).unwrap(); + let window_size = 1024; + let mut _lms = filters::LMSFilter::new(lms_config, window_size); +} diff --git a/tests/rust/main.rs b/tests/rust/main.rs new file mode 100644 index 0000000..175345e --- /dev/null +++ b/tests/rust/main.rs @@ -0,0 +1 @@ +mod filters; From 198b27eca8c21e4ee057ef061d166f3ce5494f5f Mon Sep 17 00:00:00 2001 From: bglid Date: Thu, 1 Oct 2026 08:10:26 -0400 Subject: [PATCH 04/21] updating the gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 337adef..b255c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ !python/tests/**/*.py # integration tests specifics +!tests/*.py !tests/python/ !tests/python/**/*.py !tests/rust/ From e0ca2f49542c39d9a046fda3628612ada32529b2 Mon Sep 17 00:00:00 2001 From: bglid Date: Fri, 2 Oct 2026 19:17:04 -0400 Subject: [PATCH 05/21] fixing MS bug --- tests/setup_integration_data.py | 34 ++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/setup_integration_data.py b/tests/setup_integration_data.py index b973a64..afe09cd 100644 --- a/tests/setup_integration_data.py +++ b/tests/setup_integration_data.py @@ -26,6 +26,25 @@ def clone_dataset() -> None: check=True, ) + # NOTE: Microsoft has a bug in their repo. See: https://github.com/microsoft/MS-SNSD/issues/16 + # The fix is essentially to fix the '*_snrlevels' typecast as int + # See: https://github.com/microsoft/MS-SNSD/pull/11 + + # fixes MS bug + synthesizer = MS_SNSD / "noisyspeech_synthesizer.py" + text = synthesizer.read_text() + # text = text.replace( + # 'snr_lower = float(cfg["snr_lower"])', 'snr_lower = int(cfg["snr_lower"])' + # ) + # text = text.replace( + # 'snr_upper = float(cfg["snr_upper"])', 'snr_upper = int(cfg["snr_upper"])' + # ) + text = text.replace( + 'total_snrlevels = float(cfg["total_snrlevels"])', + 'total_snrlevels = int(cfg["total_snrlevels"])', + ) + synthesizer.write_text(text) + def configure_dataset() -> None: """Configure the settings of the dataset generator.""" @@ -49,7 +68,20 @@ def configure_dataset() -> None: def generate_dataset() -> None: """Generate the MS-SNSD dataset given the configured params in `configure_dataset()`""" - subprocess.run(["uv", "run", "noisyspeech_synthesizer.py"], cwd=MS_SNSD, check=True) + subprocess.run( + [ + "uv", + "run", + "--no-project", + "--python", + "3.9", + "--with-requirements", + "requirements.txt", + "noisyspeech_synthesizer.py", + ], + cwd=MS_SNSD, + check=True, + ) def main() -> None: From 826d7bee235d74791832952e169ee0c49d805d51 Mon Sep 17 00:00:00 2001 From: bglid Date: Fri, 2 Oct 2026 19:17:22 -0400 Subject: [PATCH 06/21] cleanup --- tests/setup_integration_data.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/setup_integration_data.py b/tests/setup_integration_data.py index afe09cd..03643f5 100644 --- a/tests/setup_integration_data.py +++ b/tests/setup_integration_data.py @@ -33,12 +33,6 @@ def clone_dataset() -> None: # fixes MS bug synthesizer = MS_SNSD / "noisyspeech_synthesizer.py" text = synthesizer.read_text() - # text = text.replace( - # 'snr_lower = float(cfg["snr_lower"])', 'snr_lower = int(cfg["snr_lower"])' - # ) - # text = text.replace( - # 'snr_upper = float(cfg["snr_upper"])', 'snr_upper = int(cfg["snr_upper"])' - # ) text = text.replace( 'total_snrlevels = float(cfg["total_snrlevels"])', 'total_snrlevels = int(cfg["total_snrlevels"])', From ff9393368c14958f0681543b6567170d6e1fde10 Mon Sep 17 00:00:00 2001 From: bglid Date: Fri, 2 Oct 2026 19:43:00 -0400 Subject: [PATCH 07/21] fixing tests gitignore and adding soundfile for data load --- .gitignore | 3 + pyproject.toml | 1 + uv.lock | 146 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b255c4d..38337c2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,12 @@ !python/tests/**/*.py # integration tests specifics +!tests/ !tests/*.py + !tests/python/ !tests/python/**/*.py + !tests/rust/ !tests/rust/**/*.rs diff --git a/pyproject.toml b/pyproject.toml index d871bf8..9afb002 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ license-files = ["LICENSE-APACHE", "LICENSE-MIT"] requires-python = ">=3.10" dependencies = [ "numpy>=1.21.0,<2.0.0", + "soundfile>=0.14.0", ] classifiers = [ "Development Status :: 3 - Alpha", diff --git a/uv.lock b/uv.lock index aa03af0..a75d149 100644 --- a/uv.lock +++ b/uv.lock @@ -12,6 +12,7 @@ version = "0.2.0" source = { editable = "." } dependencies = [ { name = "numpy" }, + { name = "soundfile" }, ] [package.dev-dependencies] @@ -24,7 +25,10 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "numpy", specifier = ">=1.21.0,<2.0.0" }] +requires-dist = [ + { name = "numpy", specifier = ">=1.21.0,<2.0.0" }, + { name = "soundfile", specifier = ">=0.14.0" }, +] [package.metadata.requires-dev] dev = [ @@ -35,6 +39,116 @@ dev = [ { name = "ty", specifier = ">=0.0.75" }, ] +[[package]] +name = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/d2/2cde336b375f55c76ca670f0be3978cc048e31e24f3b4d7ce8473150a388/cffi-2.1.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be", size = 183779, upload-time = "2026-08-03T21:19:15.602Z" }, + { url = "https://files.pythonhosted.org/packages/94/1a/4b2f7c92293ba05cbd4a9a1b28faaf0326272d9488e6354657571c48a7aa/cffi-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b", size = 184178, upload-time = "2026-08-03T21:19:16.67Z" }, + { url = "https://files.pythonhosted.org/packages/17/0b/ba385d8ccedf926c3cd06e8e2f327027da5afe5f0eb30f1f7bc43ac55125/cffi-2.1.1-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004", size = 211037, upload-time = "2026-08-03T21:19:17.705Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b9/0f2e58b2cefa33255bff36935d42b13180fe559bba82596540eb404bde7d/cffi-2.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9", size = 218652, upload-time = "2026-08-03T21:19:18.735Z" }, + { url = "https://files.pythonhosted.org/packages/37/15/180e0dab27b9312c7479003d14c9e547634b7dcb934e2cc4650e1b131a7a/cffi-2.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98", size = 205422, upload-time = "2026-08-03T21:19:19.96Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/03026f0c850cbbaa9030750490225b4a7f4d524ea4df72c3cc740a90f4ef/cffi-2.1.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9", size = 205444, upload-time = "2026-08-03T21:19:21.246Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/60bebf6f818bec84210ac5b6979ce4eeadce6fbbaabc9c7ab23e506d1ce5/cffi-2.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6", size = 218742, upload-time = "2026-08-03T21:19:22.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ae/679bf47e73fd77b352171727f07de559a003f14de5d02b904a6ec1fa73ca/cffi-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf", size = 221054, upload-time = "2026-08-03T21:19:23.694Z" }, + { url = "https://files.pythonhosted.org/packages/09/b8/eefc0e06913b70aa153bf74c946094a18f58fd4aff11b7f372bfdfdca050/cffi-2.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659", size = 213489, upload-time = "2026-08-03T21:19:24.922Z" }, + { url = "https://files.pythonhosted.org/packages/6f/13/4e56852824a03cdf68523a35686f1c28eacd4bd30a7b0a78e682e6e6e1d3/cffi-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9", size = 220241, upload-time = "2026-08-03T21:19:26.214Z" }, + { url = "https://files.pythonhosted.org/packages/99/7f/040f9e163e4acac3ee3d85b02d00b2576e7ca980d8785f0a3a5f1a9bf7f5/cffi-2.1.1-cp310-cp310-win32.whl", hash = "sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41", size = 174578, upload-time = "2026-08-03T21:19:27.338Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0b/644a2ec1a4eaba49c2939410bb1eb1d25b09d6d0582f5d2f95c537043725/cffi-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1", size = 185082, upload-time = "2026-08-03T21:19:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/70/d2/16d99a0c4948febc0ebd133a13b2f688ff7f8cb04da971e1128872ce0c03/cffi-2.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12", size = 183838, upload-time = "2026-08-03T21:19:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/cd/95/31b535a9f0220ae9f357de4a08d57ce89cb417653c2fd9f075f50822a388/cffi-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1", size = 184168, upload-time = "2026-08-03T21:19:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" }, + { url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" }, + { url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" }, + { url = "https://files.pythonhosted.org/packages/db/e2/7e8109f65445bdc673a7b54f02c677de462db75674220fd1335efc8eb598/cffi-2.1.1-cp311-cp311-win32.whl", hash = "sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3", size = 174470, upload-time = "2026-08-03T21:19:41.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/c0/77ba02423c2f7d7091143c45cd49e0e6575c4c1967394bb542bd923a9b74/cffi-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0", size = 185096, upload-time = "2026-08-03T21:19:42.615Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/9f1f85f9672ceda4984dc6c4f8824e8558992a2972c3d3c81fb8eb28d4ba/cffi-2.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455", size = 179941, upload-time = "2026-08-03T21:19:43.747Z" }, + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" }, + { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" }, + { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/d9/99/c4b0c17cacdc9c3b8f280026286a9826d6a208c0f047591a3c3ce99b91fd/cffi-2.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54", size = 184964, upload-time = "2026-08-03T21:20:19.708Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a9/9db617d05d7367c1ad0ab00b3aa6e6f9281edd689b4ee9ea0e5a84e89c97/cffi-2.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72", size = 184962, upload-time = "2026-08-03T21:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" }, + { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/d0061c364cde06ee43168a0d076ac1da512cbc380d44767b844ba34fe2b6/cffi-2.1.1-cp314-cp314-win32.whl", hash = "sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c", size = 177682, upload-time = "2026-08-03T21:20:44.288Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1c3e01e3ba14c39f6d10bfbac52753b7e22259e38088e5cfe1d704918690/cffi-2.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48", size = 187949, upload-time = "2026-08-03T21:20:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/da4e39efe18eeb89cf580ea9cfc66b6a7c3eadb808fc0cc1d3a295cb5a5d/cffi-2.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836", size = 182947, upload-time = "2026-08-03T21:20:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/23/59/40338bf421c5accea1d45158170c87006ef1cd371b05c077e76476949728/cffi-2.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3", size = 188504, upload-time = "2026-08-03T21:20:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5ecf1023850036e674c77ec4de86182d309ae344e39e7cba984b7df5d647/cffi-2.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2", size = 188259, upload-time = "2026-08-03T21:20:31.291Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" }, + { url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" }, + { url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/3b7176cb570a1d3e27faf67b72f591af508036e0d8b2be2ef9af9e8c84bb/cffi-2.1.1-cp314-cp314t-win32.whl", hash = "sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4", size = 182868, upload-time = "2026-08-03T21:20:40.388Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/31f00c1bcd97c9bbf55f1bfdf5bc809a5de8887473e90bb9960dca825e80/cffi-2.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e", size = 194104, upload-time = "2026-08-03T21:20:41.725Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1b/58496f2ed0a35de575250c02a43ab3cc2c04d494a88fed31c1cabc0fd176/cffi-2.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5", size = 186402, upload-time = "2026-08-03T21:20:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8a/af668013284634733f02d683458a0728739c7d6ddb5e14cb0c20832266fe/cffi-2.1.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4", size = 184933, upload-time = "2026-08-03T21:20:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/0c/75/2f5207ff6d1a613133b23a5203cc0c2a628313b5eb3974d7956ae3c57950/cffi-2.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8", size = 185002, upload-time = "2026-08-03T21:20:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" }, + { url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/8debeb04f1ab9fe2a6963964cd6f1aaf7192627b83926586a6a4e089c9fa/cffi-2.1.1-cp315-cp315-win32.whl", hash = "sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac", size = 177683, upload-time = "2026-08-03T21:21:14.901Z" }, + { url = "https://files.pythonhosted.org/packages/e0/31/5158704cc474ab65c1647932e88be78dc0873f47130e253be38bcaf13d01/cffi-2.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960", size = 187897, upload-time = "2026-08-03T21:21:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/cc/4b/b3a2da8570c704ffc0f9762cdc3ec0f02c8573798e0b5cf7f11c82bbb70f/cffi-2.1.1-cp315-cp315-win_arm64.whl", hash = "sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1", size = 182935, upload-time = "2026-08-03T21:21:17.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ef/5443574510a1207e6f6bc38ba6e1f1de36cb48fef07b2728bb896a21f430/cffi-2.1.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc", size = 188464, upload-time = "2026-08-03T21:21:01.163Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ae/a56fa8c4686ad50e148fcbc8d3ae0d03915ff5c30d795058988c24118cef/cffi-2.1.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab", size = 188262, upload-time = "2026-08-03T21:21:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" }, + { url = "https://files.pythonhosted.org/packages/6d/cd/a361394c94b2129d604bb846f624a8e88255a3ee33129c434a00d715e64f/cffi-2.1.1-cp315-cp315t-win32.whl", hash = "sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66", size = 182707, upload-time = "2026-08-03T21:21:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/ba2b299993c26577d529b6ae29841f9e15b9fcf004d65f423f4fcf94ade9/cffi-2.1.1-cp315-cp315t-win_amd64.whl", hash = "sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3", size = 193772, upload-time = "2026-08-03T21:21:12.39Z" }, + { url = "https://files.pythonhosted.org/packages/aa/29/35e016098c814cd93de9cd320c66b5bfba14dc6ecedd3cb518fa7c408c69/cffi-2.1.1-cp315-cp315t-win_arm64.whl", hash = "sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692", size = 186360, upload-time = "2026-08-03T21:21:13.636Z" }, +] + [[package]] name = "cfgv" version = "3.5.0" @@ -209,6 +323,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/e2/bbb7129c9e7999a6b8ee9cca3b66486c25c423ab5a75f34071798b74ce94/pre_commit-4.6.2-py2.py3-none-any.whl", hash = "sha256:e2dde9a75d3bce11bd3831c26d134df00a2803c1d818be6a0383c3dcda25dc4e", size = 226202, upload-time = "2026-08-10T22:07:16.942Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pygments" version = "2.21.0" @@ -337,6 +460,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/e3/7df5a396e445b9ba49ce9a9437439a4d80042c61c0ade199abf8d16de1ac/ruff-0.16.5-py3-none-win_arm64.whl", hash = "sha256:a64abe90968719b851bb7cedffaa8753fbdbdadab483089682db623f3edc587e", size = 10391564, upload-time = "2026-08-27T16:34:16.064Z" }, ] +[[package]] +name = "soundfile" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/db/949331952a6fb1c5b12e9de80fd08747966c2039d1a61db4764fbd3981c2/soundfile-0.14.0.tar.gz", hash = "sha256:ba1c1a2d618bca5c406647c83b89f07cc8810fa506a50622a6993ba130c1de11", size = 47842, upload-time = "2026-06-06T08:58:47.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/d1/5e338af9ca6ed0786cd5bb03f6d60de1c325728c1189014f3b59aae7403c/soundfile-0.14.0-py2.py3-none-any.whl", hash = "sha256:8ba81ae3a89fd5ab3bef8a8eb481fbbe794e806309675a89b4df48b8d31908a8", size = 26799, upload-time = "2026-06-06T08:58:33.269Z" }, + { url = "https://files.pythonhosted.org/packages/7e/72/c6b21e58d3113596e7e8de0a08d6f1d95173492cfbca0a4db14148cbba2a/soundfile-0.14.0-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:19be05428da76ed61a4cad29b8e4bcf43a3e5c100089d2ec81dc961eed1b0dd4", size = 1144568, upload-time = "2026-06-06T08:58:35.231Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/dfdd6f8c748988427119f75eb860a3cedd858d1aea1fe28f39ad8559ef22/soundfile-0.14.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:d828d35a059626da52f1415b5faee610aeab393319cb3fc4a9aef47b619fc14c", size = 1103726, upload-time = "2026-06-06T08:58:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f8/fc39fad6f879633461d27394cd1ddaf1f769ffa0597dca35872f51b16461/soundfile-0.14.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e85724a90bc99a6e8062c0b4ddf725f53b2a3b70afd4da875e9d2cfc4e92f377", size = 1238050, upload-time = "2026-06-06T08:58:39.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a2/70fd4432b924684c372df8b0a45708c36c057ef3596c9eb53e0a806b980b/soundfile-0.14.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1e38bac1853412871318e82a1ba69a8be677619b56025bbfcccdb41b6cafe82d", size = 1315963, upload-time = "2026-06-06T08:58:41.716Z" }, + { url = "https://files.pythonhosted.org/packages/d9/34/c9e80783d83eab739a9531fdee03675d53e0bf1b2ccb4bb3af5844675046/soundfile-0.14.0-py2.py3-none-win32.whl", hash = "sha256:0a6ae43c50c71b4e020cc55382925cb89451c1ed1a0c3d0f5d802da269226849", size = 902199, upload-time = "2026-06-06T08:58:43.289Z" }, + { url = "https://files.pythonhosted.org/packages/ed/97/b39c18ac1df45e755ca22b8b00e872929da5d107998a207a5e4ac831bfda/soundfile-0.14.0-py2.py3-none-win_amd64.whl", hash = "sha256:299491d3499460fb1b74bb4bd78b57ffc2d243a5fafa7b6ec1b264875c78453e", size = 1021480, upload-time = "2026-06-06T08:58:45.016Z" }, + { url = "https://files.pythonhosted.org/packages/f4/83/55c65e61cf457805ce2ec157c1c6ae17715d0851aa2374422de0538838ca/soundfile-0.14.0-py2.py3-none-win_arm64.whl", hash = "sha256:e090704718e124e7c844695236f1fce8d18a5e761eaf7c82dfcd124620805f98", size = 888858, upload-time = "2026-06-06T08:58:46.593Z" }, +] + [[package]] name = "tomli" version = "2.4.1" From af35efad82b986c1100b65c8d76efa5f9436a4e2 Mon Sep 17 00:00:00 2001 From: bglid Date: Fri, 2 Oct 2026 20:08:50 -0400 Subject: [PATCH 08/21] first working integration tests python --- tests/__init__.py | 0 tests/python/__init__.py | 0 tests/python/test_filters.py | 31 +++++++++++++++++ tests/python/utils.py | 64 ++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/python/__init__.py create mode 100644 tests/python/test_filters.py create mode 100644 tests/python/utils.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/python/__init__.py b/tests/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py new file mode 100644 index 0000000..9d7d8a1 --- /dev/null +++ b/tests/python/test_filters.py @@ -0,0 +1,31 @@ +import numpy as np +from adaptif import LMSFilter + +from .utils import load_test_signals, mse, snr + + +# TODO: MAKE as a fixture to test across each filter +def test_lms_process_audio() -> None: + _, noisy_signal, noise_reference = load_test_signals() + + lms = LMSFilter(mu=0.01, window_size=32) + cleaned_signal = lms.adapt(input_signal=noisy_signal, noise_ref=noise_reference) + + assert cleaned_signal.shape == noisy_signal.shape + assert np.all(np.isfinite(cleaned_signal)) + + +# TODO: MAKE as a fixture to test across each filter +def test_lms_adapts_signal() -> None: + original_signal, noisy_signal, noise_reference = load_test_signals() + + lms = LMSFilter(mu=0.01, window_size=32) + cleaned_signal = lms.adapt(input_signal=noisy_signal, noise_ref=noise_reference) + + before_snr = snr(desired_signal=original_signal, noisy_signal=noisy_signal) + after_snr = snr(desired_signal=original_signal, noisy_signal=cleaned_signal) + assert after_snr > before_snr + + before_mse = mse(desired_signal=original_signal, input_signal=noisy_signal) + after_mse = mse(desired_signal=original_signal, input_signal=cleaned_signal) + assert after_mse < before_mse diff --git a/tests/python/utils.py b/tests/python/utils.py new file mode 100644 index 0000000..5eaa06e --- /dev/null +++ b/tests/python/utils.py @@ -0,0 +1,64 @@ +from pathlib import Path + +import numpy as np +import soundfile as sf +from numpy.typing import NDArray + +DATA = Path(__file__).parents[1] / "data" / "MS-SNSD" + + +def load_test_signals() -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Loads the test data for integration tests. + + Returns: + tuple(np.ndarray, np.ndarray, np.ndarray): Tuple of the clean signal, noisy signal, and noise. + + """ + clean_path = next((DATA / "CleanSpeech_training").glob("*.wav")) + noisy_path = next((DATA / "NoisySpeech_training").glob("*.wav")) + + clean_signal, clean_sr = sf.read(clean_path) + noisy_signal, noisy_sr = sf.read(noisy_path) + + assert clean_sr == noisy_sr + + n = min(len(clean_signal), len(noisy_signal)) + clean_signal = clean_signal[:n] + noisy_signal = noisy_signal[:n] + + noise_reference = noisy_signal - clean_signal + + return clean_signal, noisy_signal, noise_reference + + +def mse( + desired_signal: NDArray[np.float64], input_signal: NDArray[np.float64] +) -> np.float64: + """Calculates the Mean Squared Error = 1/n * sum(y - y_hat)**2 + + Args: + desired_signal (NDArray[np.float64]): Observed or desired value + input_signal (NDArray[np.float64]): Prediction of value + + Returns: + np.float64: Mean squared error + """ + return np.mean((desired_signal - input_signal) ** 2) + + +def snr( + desired_signal: NDArray[np.float64], noisy_signal: NDArray[np.float64] +) -> np.float64: + """Calculates the Signal to Noise Ratio in dB: SNR = (Power of Signal)/(Power of Noise). + + Args: + desired_signal (NDArray[np.float64]): Input signal + noisy_signal (NDArray[np.float64]): Input Noisy signal + + Returns: + np.float64: SNR in dB + """ + signal_power = np.sum(desired_signal**2) + noise_power = np.sum((desired_signal - noisy_signal) ** 2) + 1e-12 + snr = signal_power / noise_power + return 10 * np.log10(snr + 1e-12) From 03ac398cfa08a9cff550b1e73608928e1caa14cf Mon Sep 17 00:00:00 2001 From: bglid Date: Sat, 3 Oct 2026 09:45:48 -0400 Subject: [PATCH 09/21] Adding data preparation and integration job that's called --- .github/workflows/integration-ci.yml | 33 ++++++++++++++++++++++++++++ .github/workflows/python-ci.yml | 9 ++++++++ .github/workflows/rust-ci.yml | 17 ++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 .github/workflows/integration-ci.yml diff --git a/.github/workflows/integration-ci.yml b/.github/workflows/integration-ci.yml new file mode 100644 index 0000000..2c2f6d6 --- /dev/null +++ b/.github/workflows/integration-ci.yml @@ -0,0 +1,33 @@ +name: Integration CI + +on: + workflow_call: + push: + paths: + - "tests/setup_integration_data.py" + - ".github/workflows/integration-ci.yml" + pull_request: + paths: + - "tests/setup_integration_data.py" + - ".github/workflows/integration-ci.yml" + +jobs: + prepare-data: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v10.0.1 + + - name: Restore integration data + id: cache-data + uses: actions/cache@v4 + with: + path: tests/data + key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py')}} + + - name: Generate data + if: steps.cache-data.outputs.cache-hit != 'true' + run: uv run tests/setup_integration_data.py diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 77ab998..71d1436 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -8,6 +8,7 @@ on: - "pyproject.toml" - "uv.lock" - ".github/workflows/python-ci.yml" + - ".github/workflows/integration-ci.yml" pull_request: paths: - "python/**" @@ -15,10 +16,12 @@ on: - "pyproject.toml" - "uv.lock" - ".github/workflows/python-ci.yml" + - ".github/workflows/integration-ci.yml" jobs: build: runs-on: ubuntu-latest + needs: prepare-data strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] @@ -51,6 +54,12 @@ jobs: run: | make test-py-unit + - name: Restore integration data + uses: actions/cache@v4 + with: + path: tests/data + key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }} + - name: Run integration tests run: | make test-py-integration diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index a81f9bd..daa6d9b 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -20,8 +20,12 @@ env: CARGO_TERM_COLOR: always jobs: + prepare-data: + uses: ./.github/workflows/integration-ci.yml + build: name: Rust - latest + needs: prepare-data runs-on: ubuntu-latest strategy: matrix: @@ -56,6 +60,12 @@ jobs: - name: Run unit tests run: cargo test --lib --verbose + - name: Restore integration data + uses: actions/cache@v4 + with: + path: tests/data + key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }} + - name: Run integration tests run: make test-rs-integration @@ -65,6 +75,7 @@ jobs: coverage: if: github.event_name == 'pull_request' name: Rust coverage + needs: prepare-data runs-on: ubuntu-latest strategy: matrix: @@ -81,6 +92,12 @@ jobs: - name: rust-cache uses: Swatinem/rust-cache@v2 + - name: Restore integration data + uses: actions/cache@v4 + with: + path: tests/data + key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }} + - name: Install cargo-binstall uses: cargo-bins/cargo-binstall@main From 0cf579f12c7e33f4a4bc425c5073fac4a5925f79 Mon Sep 17 00:00:00 2001 From: bglid Date: Sat, 3 Oct 2026 09:54:01 -0400 Subject: [PATCH 10/21] fixing ubuntu run dep --- .github/workflows/integration-ci.yml | 18 +++++++++--------- .github/workflows/python-ci.yml | 10 ++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/workflows/integration-ci.yml b/.github/workflows/integration-ci.yml index 2c2f6d6..dbf5cea 100644 --- a/.github/workflows/integration-ci.yml +++ b/.github/workflows/integration-ci.yml @@ -2,14 +2,12 @@ name: Integration CI on: workflow_call: - push: - paths: - - "tests/setup_integration_data.py" - - ".github/workflows/integration-ci.yml" - pull_request: - paths: - - "tests/setup_integration_data.py" - - ".github/workflows/integration-ci.yml" + # push: + # paths: + # - "tests/setup_integration_data.py" + # pull_request: + # paths: + # - "tests/setup_integration_data.py" jobs: prepare-data: @@ -30,4 +28,6 @@ jobs: - name: Generate data if: steps.cache-data.outputs.cache-hit != 'true' - run: uv run tests/setup_integration_data.py + run: | + sudo apt-get update && sudo apt-get install -y libsndfile1 + uv run tests/setup_integration_data.py diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 71d1436..84bf28c 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -4,7 +4,7 @@ on: push: paths: - "python/**" - - "tests/**" + - "tests/python/**" - "pyproject.toml" - "uv.lock" - ".github/workflows/python-ci.yml" @@ -12,7 +12,7 @@ on: pull_request: paths: - "python/**" - - "tests/**" + - "tests/python/**" - "pyproject.toml" - "uv.lock" - ".github/workflows/python-ci.yml" @@ -51,8 +51,7 @@ jobs: run: uv run ty check - name: Run unit tests - run: | - make test-py-unit + run: make test-py-unit - name: Restore integration data uses: actions/cache@v4 @@ -61,8 +60,7 @@ jobs: key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }} - name: Run integration tests - run: | - make test-py-integration + run: make test-py-integration - name: Run safety checks run: uv audit From 6c69b340a8f4bc4519099b8206ac616ef4ffa430 Mon Sep 17 00:00:00 2001 From: bglid Date: Sat, 3 Oct 2026 10:03:17 -0400 Subject: [PATCH 11/21] Adjusting CIs smh --- .github/workflows/integration-ci.yml | 6 ------ .github/workflows/python-ci.yml | 3 +++ .github/workflows/rust-ci.yml | 4 ++++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-ci.yml b/.github/workflows/integration-ci.yml index dbf5cea..f9dcd7a 100644 --- a/.github/workflows/integration-ci.yml +++ b/.github/workflows/integration-ci.yml @@ -2,12 +2,6 @@ name: Integration CI on: workflow_call: - # push: - # paths: - # - "tests/setup_integration_data.py" - # pull_request: - # paths: - # - "tests/setup_integration_data.py" jobs: prepare-data: diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 84bf28c..30b7147 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -19,6 +19,9 @@ on: - ".github/workflows/integration-ci.yml" jobs: + prepare-data: + uses: ./.github/workflows/integration-ci.yml + build: runs-on: ubuntu-latest needs: prepare-data diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index daa6d9b..d8498f2 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -8,6 +8,8 @@ on: - ".github/workflows/rust-ci.yml" - "Cargo.toml" - "Cargo.lock" + - ".github/workflows/integration-ci.yml" + - "tests/setup_integration_data.py" pull_request: paths: - "src/**" @@ -15,6 +17,8 @@ on: - ".github/workflows/rust-ci.yml" - "Cargo.toml" - "Cargo.lock" + - ".github/workflows/integration-ci.yml" + - "tests/setup_integration_data.py" env: CARGO_TERM_COLOR: always From 6ae3c897884fb49479b66355701d6ec1c5e72a52 Mon Sep 17 00:00:00 2001 From: bglid Date: Sat, 3 Oct 2026 12:27:06 -0400 Subject: [PATCH 12/21] Pinning rust version and handling mr clippy --- .github/workflows/rust-ci.yml | 8 ++++---- Cargo.toml | 1 + Makefile | 5 +++-- src/python/mod.rs | 4 ++++ uv.lock | 7 ++++--- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index d8498f2..ce42d8b 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -34,14 +34,14 @@ jobs: strategy: matrix: toolchain: - - stable + - "1.99.0" steps: - name: Checkout uses: actions/checkout@v6 - name: Install latest Rust version - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - name: rust-cache uses: Swatinem/rust-cache@v2 @@ -84,14 +84,14 @@ jobs: strategy: matrix: toolchain: - - stable + - "1.99.0" steps: - name: Checkout uses: actions/checkout@v6 - name: Install latest Rust version - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} - name: rust-cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index fdddd53..2613a55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ exhaustive_structs = "allow" # Allows for exhaustive structs, which even Clippy # maybe missing_docs_in_private_items = "allow" +definition_in_module_root = "allow" # TODO: allowing on this branch, but working on removing. # Requires short functions to be explicitly inlined, because automatic inlining can't be done across crates w/o LTO. # This may be worth considering for the pure Rust API, but for PyO3 it's irrelevant. diff --git a/Makefile b/Makefile index f5427db..27614ee 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,10 @@ check-installs: check-rust-install check-uv-install # Install/update tooling and dependencies .PHONY: install-rust install-uv setup-rust setup-uv setup install-rust: - @command rustup --version >/dev/null 2>&1 && \ - rustup update || \ + @command rustup --version >/dev/null 2>&1 || \ { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh; } + rustup toolchain install 1.99.0 + rustup default 1.99.0 install-uv: @command uv --version >/dev/null 2>&1 && \ uv self update || \ diff --git a/src/python/mod.rs b/src/python/mod.rs index 1721895..bd48180 100644 --- a/src/python/mod.rs +++ b/src/python/mod.rs @@ -23,6 +23,10 @@ use crate::filters::{ }; use crate::types::signals::{InputSignal, NoiseReference}; +#[allow( + clippy::rest_pattern_accessible_field, + reason = "Fields are not used in pyerr. Rest pattern is fine here." +)] impl Error { fn to_pyerr(&self) -> PyErr { match *self { diff --git a/uv.lock b/uv.lock index a75d149..dd67d8e 100644 --- a/uv.lock +++ b/uv.lock @@ -571,16 +571,17 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.7.7" +version = "21.14.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, + { name = "packaging" }, { name = "platformdirs" }, { name = "python-discovery" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/41/c3f34799487924f2a6f43b8a8b7acd345a6c61aac2211d4bced8621ca4f1/virtualenv-21.7.7.tar.gz", hash = "sha256:6874376f99ba6b8d4e3ee8bde67f9285412400c7d5b29ba41ee6daa5e0221bdc", size = 5347022, upload-time = "2026-08-28T18:59:50.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/f9/f323b3b6058cff3853b31cf6a49c0425ed797bf9611572ec10d065e08bac/virtualenv-21.14.5.tar.gz", hash = "sha256:c4cb6c13e46b57225a999c7e22a09b163393878facc7ac4c059a57f46faa1647", size = 5467127, upload-time = "2026-10-02T17:42:25.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/f8/dcec8dc767b812193316c7debed1e2b53e586e59c267bfe517688ff90275/virtualenv-21.7.7-py3-none-any.whl", hash = "sha256:67a6a68fef3ad8ca16b8b89f33fd8f97996cc0bf0db31629d07ecf8dec539a2c", size = 5324620, upload-time = "2026-08-28T18:59:48.056Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/084f10ebbecdedf7902ed68fbecc903f0e9c5742155bfffb05eccbc071da/virtualenv-21.14.5-py3-none-any.whl", hash = "sha256:b0651e0174982bba17cc6f0aaa2ef496730cb85a3f35eb5d1119d1c904f7d1da", size = 5487482, upload-time = "2026-10-02T17:42:23.38Z" }, ] From 29a29a6777d9eb5ca6ab1132b5774e5d5093c310 Mon Sep 17 00:00:00 2001 From: bglid Date: Sat, 3 Oct 2026 13:24:52 -0400 Subject: [PATCH 13/21] Working python int tests, need to investigate more --- .github/workflows/python-ci.yml | 2 ++ .github/workflows/rust-ci.yml | 7 +++++-- tests/python/test_filters.py | 33 ++++++++++++++++++++++++--------- tests/setup_integration_data.py | 2 +- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 30b7147..e182e7b 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -9,6 +9,7 @@ on: - "uv.lock" - ".github/workflows/python-ci.yml" - ".github/workflows/integration-ci.yml" + - "tests/setup_integration_data.py" pull_request: paths: - "python/**" @@ -17,6 +18,7 @@ on: - "uv.lock" - ".github/workflows/python-ci.yml" - ".github/workflows/integration-ci.yml" + - "tests/setup_integration_data.py" jobs: prepare-data: diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index ce42d8b..b05ded5 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -40,8 +40,11 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Install latest Rust version - run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - name: Install Rust 1.99 + run: | + rustup toolchain install ${{ matrix.toolchain }} + rustup component add rustfmt clippy --toolchain ${{ matrix.toolchain }} + rustup default ${{ matrix.toolchain }} - name: rust-cache uses: Swatinem/rust-cache@v2 diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py index 9d7d8a1..741a8b6 100644 --- a/tests/python/test_filters.py +++ b/tests/python/test_filters.py @@ -1,26 +1,41 @@ import numpy as np -from adaptif import LMSFilter +import pytest +from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter from .utils import load_test_signals, mse, snr -# TODO: MAKE as a fixture to test across each filter -def test_lms_process_audio() -> None: +# placing in values that would actually be used +@pytest.fixture( + params=[ + (LMSFilter, {"mu": 0.01, "window_size": 16}), + (NLMSFilter, {"mu": 0.001, "eps": 1e-6, "window_size": 16}), + ( + RLSFilter, + {"forgetting_factor": 0.999, "p_init_scale": 0.001, "window_size": 16}, + ), + (BlockLMSFilter, {"mu": 1e-8, "window_size": 8, "block_size": 16}), + ], + ids=["lms", "nlms", "rls", "block-lms"], +) +def filter(request): + filter_class, kwargs = request.param + return filter_class(**kwargs) + + +def test_filter_process_audio(filter) -> None: _, noisy_signal, noise_reference = load_test_signals() - lms = LMSFilter(mu=0.01, window_size=32) - cleaned_signal = lms.adapt(input_signal=noisy_signal, noise_ref=noise_reference) + cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference) assert cleaned_signal.shape == noisy_signal.shape assert np.all(np.isfinite(cleaned_signal)) -# TODO: MAKE as a fixture to test across each filter -def test_lms_adapts_signal() -> None: +def test_filter_adapts_signal(filter) -> None: original_signal, noisy_signal, noise_reference = load_test_signals() - lms = LMSFilter(mu=0.01, window_size=32) - cleaned_signal = lms.adapt(input_signal=noisy_signal, noise_ref=noise_reference) + cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference) before_snr = snr(desired_signal=original_signal, noisy_signal=noisy_signal) after_snr = snr(desired_signal=original_signal, noisy_signal=cleaned_signal) diff --git a/tests/setup_integration_data.py b/tests/setup_integration_data.py index 03643f5..4998eb1 100644 --- a/tests/setup_integration_data.py +++ b/tests/setup_integration_data.py @@ -51,7 +51,7 @@ def configure_dataset() -> None: # editing the params of the dataset dataset["audio_length"] = "5" - dataset["total_hours"] = "0.02" + dataset["total_hours"] = "0.0015" # roughly 5 seconds of audio dataset["snr_lower"] = "0" dataset["snr_upper"] = "20" dataset["total_snrlevels"] = "3" From 51f6cf10350a16d9ca3fedef17d1a9145a3162c3 Mon Sep 17 00:00:00 2001 From: bglid Date: Sat, 3 Oct 2026 13:37:09 -0400 Subject: [PATCH 14/21] Removing block lms for now --- tests/python/test_filters.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py index 741a8b6..108b8b6 100644 --- a/tests/python/test_filters.py +++ b/tests/python/test_filters.py @@ -1,6 +1,6 @@ import numpy as np import pytest -from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter +from adaptif import LMSFilter, NLMSFilter, RLSFilter from .utils import load_test_signals, mse, snr @@ -14,9 +14,12 @@ RLSFilter, {"forgetting_factor": 0.999, "p_init_scale": 0.001, "window_size": 16}, ), - (BlockLMSFilter, {"mu": 1e-8, "window_size": 8, "block_size": 16}), ], - ids=["lms", "nlms", "rls", "block-lms"], + ids=[ + "lms", + "nlms", + "rls", + ], ) def filter(request): filter_class, kwargs = request.param From 12b8d21641d8730ff09a43e116007fed081c878e Mon Sep 17 00:00:00 2001 From: bglid Date: Sun, 4 Oct 2026 09:37:41 -0400 Subject: [PATCH 15/21] reverting to explicit commands for now in CIs --- .github/workflows/python-ci.yml | 4 ++-- .github/workflows/rust-ci.yml | 2 +- tests/python/test_filters.py | 9 +++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index e182e7b..24f5c7d 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -56,7 +56,7 @@ jobs: run: uv run ty check - name: Run unit tests - run: make test-py-unit + run: uv run pytest python/tests - name: Restore integration data uses: actions/cache@v4 @@ -65,7 +65,7 @@ jobs: key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }} - name: Run integration tests - run: make test-py-integration + run: uv run pytest tests/python - name: Run safety checks run: uv audit diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index b05ded5..19c4702 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -74,7 +74,7 @@ jobs: key: integration-data-v1-${{ hashFiles('tests/setup_integration_data.py') }} - name: Run integration tests - run: make test-rs-integration + run: cargo test --test rust --frozen - name: Audit dependencies run: cargo audit diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py index 108b8b6..4def722 100644 --- a/tests/python/test_filters.py +++ b/tests/python/test_filters.py @@ -1,6 +1,6 @@ import numpy as np import pytest -from adaptif import LMSFilter, NLMSFilter, RLSFilter +from adaptif import BlockLMSFilter, LMSFilter, NLMSFilter, RLSFilter from .utils import load_test_signals, mse, snr @@ -14,12 +14,9 @@ RLSFilter, {"forgetting_factor": 0.999, "p_init_scale": 0.001, "window_size": 16}, ), + (BlockLMSFilter, {"mu": 1e-10, "window_size": 8, "block_size": 16}), ], - ids=[ - "lms", - "nlms", - "rls", - ], + ids=["lms", "nlms", "rls", "block-lms"], ) def filter(request): filter_class, kwargs = request.param From 343861efd22464743bcc6d5f8f0d7fe839623550 Mon Sep 17 00:00:00 2001 From: bglid Date: Sun, 4 Oct 2026 11:04:37 -0400 Subject: [PATCH 16/21] working test rust integration tests --- tests/python/test_filters.py | 15 ++++----- tests/python/utils.py | 2 +- tests/rust/filters.rs | 47 +++++++++++++++++++++++----- tests/rust/main.rs | 1 + tests/rust/utils.rs | 54 +++++++++++++++++++++++++++++++++ tests/setup_integration_data.py | 2 +- 6 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 tests/rust/utils.rs diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py index 4def722..c6d0720 100644 --- a/tests/python/test_filters.py +++ b/tests/python/test_filters.py @@ -5,7 +5,7 @@ from .utils import load_test_signals, mse, snr -# placing in values that would actually be used +# placing in realistic values that would actually be used @pytest.fixture( params=[ (LMSFilter, {"mu": 0.01, "window_size": 16}), @@ -14,7 +14,7 @@ RLSFilter, {"forgetting_factor": 0.999, "p_init_scale": 0.001, "window_size": 16}, ), - (BlockLMSFilter, {"mu": 1e-10, "window_size": 8, "block_size": 16}), + (BlockLMSFilter, {"mu": 1e-4, "window_size": 32, "block_size": 32}), ], ids=["lms", "nlms", "rls", "block-lms"], ) @@ -33,14 +33,15 @@ def test_filter_process_audio(filter) -> None: def test_filter_adapts_signal(filter) -> None: - original_signal, noisy_signal, noise_reference = load_test_signals() + desired_signal, noisy_signal, noise_reference = load_test_signals() cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference) - before_snr = snr(desired_signal=original_signal, noisy_signal=noisy_signal) - after_snr = snr(desired_signal=original_signal, noisy_signal=cleaned_signal) + before_snr = snr(desired_signal=desired_signal, noisy_signal=noisy_signal) + after_snr = snr(desired_signal=desired_signal, noisy_signal=cleaned_signal) assert after_snr > before_snr - before_mse = mse(desired_signal=original_signal, input_signal=noisy_signal) - after_mse = mse(desired_signal=original_signal, input_signal=cleaned_signal) + # NOTE: point of this is that it doesn't only remove noise, but recovers the signal + before_mse = mse(desired_signal=desired_signal, input_signal=noisy_signal) + after_mse = mse(desired_signal=desired_signal, input_signal=cleaned_signal) assert after_mse < before_mse diff --git a/tests/python/utils.py b/tests/python/utils.py index 5eaa06e..52cbb88 100644 --- a/tests/python/utils.py +++ b/tests/python/utils.py @@ -59,6 +59,6 @@ def snr( np.float64: SNR in dB """ signal_power = np.sum(desired_signal**2) - noise_power = np.sum((desired_signal - noisy_signal) ** 2) + 1e-12 + noise_power = np.sum((desired_signal - noisy_signal) ** 2) + 1e-8 snr = signal_power / noise_power return 10 * np.log10(snr + 1e-12) diff --git a/tests/rust/filters.rs b/tests/rust/filters.rs index a3895de..f1c02b3 100644 --- a/tests/rust/filters.rs +++ b/tests/rust/filters.rs @@ -2,13 +2,46 @@ clippy::tests_outside_test_module, reason = "This crate is exclusively an integration test target" )] -use adaptif::{algorithms::Lms, filters}; -// Just getting this setup +use adaptif::algorithms::Lms; +use adaptif::filters::{AdaptiveFilter as _, FilterBase}; +use adaptif::types::signals::{InputSignal, NoiseReference}; + +use crate::utils::{mse, snr, test_signals}; + +#[test] +#[allow(clippy::unwrap_used, reason = "Integration test")] +fn filter_process_audio() { + let (_, noisy_wav, noise_ref_wav) = test_signals(); + let noisy_signal = InputSignal::new(noisy_wav.clone()).unwrap(); + let noise_reference = NoiseReference::new(noise_ref_wav).unwrap(); + let lms_config = Lms::new(0.01).unwrap(); + let window_size = 16; + let mut lms = FilterBase::new(lms_config, window_size).unwrap(); + + let cleaned_signal = lms.adapt(&noisy_signal, &noise_reference).unwrap(); + + assert_eq!(cleaned_signal.len(), noisy_wav.len()); + assert!(cleaned_signal.iter().all(|sample| sample.is_finite())); +} + #[test] -fn lms_filters_signal() { - #[allow(clippy::unwrap_used, reason = "Tests")] - let lms_config = Lms::new(1.0).unwrap(); - let window_size = 1024; - let mut _lms = filters::LMSFilter::new(lms_config, window_size); +#[allow(clippy::unwrap_used, reason = "Integration test")] +fn filter_adapts_signal() { + let (desired_wav, noisy_wav, noise_ref_wav) = test_signals(); + let noisy_signal = InputSignal::new(noisy_wav).unwrap(); + let noise_reference = NoiseReference::new(noise_ref_wav).unwrap(); + let lms_config = Lms::new(0.01).unwrap(); + let window_size = 16; + let mut lms = FilterBase::new(lms_config, window_size).unwrap(); + + let cleaned_signal = lms.adapt(&noisy_signal, &noise_reference).unwrap(); + + let before_mse = mse(&desired_wav, &noisy_signal); + let after_mse = mse(&desired_wav, &cleaned_signal); + assert!(after_mse < before_mse); + + let before_snr = snr(&desired_wav, &noisy_signal); + let after_snr = snr(&desired_wav, &cleaned_signal); + assert!(after_snr > before_snr); } diff --git a/tests/rust/main.rs b/tests/rust/main.rs index 175345e..fc5b71e 100644 --- a/tests/rust/main.rs +++ b/tests/rust/main.rs @@ -1 +1,2 @@ mod filters; +mod utils; diff --git a/tests/rust/utils.rs b/tests/rust/utils.rs new file mode 100644 index 0000000..9be989f --- /dev/null +++ b/tests/rust/utils.rs @@ -0,0 +1,54 @@ +use num_traits::cast::ToPrimitive as _; +use std::f64::consts::PI; + +// NOTE: PLEASE READ / TODO +// This one function is slop generated and is going to be replaced +// -> by a real .wav loader. Demo purposes. Will be removed +pub fn test_signals() -> (Vec, Vec, Vec) { + let sample_rate = 16_000.0; + let num_samples = 4_000; + + let clean: Vec = (0..num_samples) + .map(|n| { + let t = f64::from(n) / sample_rate; + (2.0 * PI * 440.0 * t).sin() + }) + .collect(); + + let noise_ref: Vec = (0..num_samples) + .map(|n| { + let t = f64::from(n) / sample_rate; + 0.2 * (2.0 * PI * 1000.0 * t).sin() + }) + .collect(); + + let noisy: Vec = clean + .iter() + .zip(&noise_ref) + .map(|(clean, noise)| clean + noise) + .collect(); + + (clean, noisy, noise_ref) +} + +#[allow(clippy::unwrap_used, clippy::indexing_slicing, reason = "Tests")] +pub fn mse(desired: &[f64], input: &[f64]) -> f64 { + desired + .iter() + .zip(input) + .map(|(d, i)| (d - i).powi(2)) + .sum::() + / desired.len().to_f64().unwrap() +} + +pub fn snr(desired: &[f64], input: &[f64]) -> f64 { + let signal_power = desired.iter().map(|d| d.powi(2)).sum::(); + let noise_power = desired + .iter() + .zip(input) + .map(|(d, i)| (d - i).powi(2)) + .sum::() + + 1e-8; + + 10.0 * (signal_power / noise_power).log10() +} diff --git a/tests/setup_integration_data.py b/tests/setup_integration_data.py index 4998eb1..e56f14a 100644 --- a/tests/setup_integration_data.py +++ b/tests/setup_integration_data.py @@ -51,7 +51,7 @@ def configure_dataset() -> None: # editing the params of the dataset dataset["audio_length"] = "5" - dataset["total_hours"] = "0.0015" # roughly 5 seconds of audio + dataset["total_hours"] = "0.003" # roughly 10 seconds of audio dataset["snr_lower"] = "0" dataset["snr_upper"] = "20" dataset["total_snrlevels"] = "3" From 708da78c92a8ba8480f2ad81a60847374b87c83c Mon Sep 17 00:00:00 2001 From: bglid Date: Sun, 4 Oct 2026 11:14:40 -0400 Subject: [PATCH 17/21] remove that lint now --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f3c4698..f459bd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,6 @@ rest_pattern_accessible_field = "allow" # conflicts with unneeded_field_pattern # maybe missing_docs_in_private_items = "allow" -definition_in_module_root = "allow" # TODO: allowing on this branch, but working on removing. # Requires short functions to be explicitly inlined, because automatic inlining can't be done across crates w/o LTO. # This may be worth considering for the pure Rust API, but for PyO3 it's irrelevant. From 7b0a78420e565f8f8e66710c4b72d83dfe3c6b0f Mon Sep 17 00:00:00 2001 From: bglid Date: Mon, 5 Oct 2026 07:36:10 -0400 Subject: [PATCH 18/21] switching rust pinning to rust-toolchain.toml --- .github/workflows/rust-ci.yml | 19 ++++++------------- .gitignore | 11 ++++------- Cargo.toml | 1 + Makefile | 4 ++-- pyproject.toml | 2 +- rust-toolchain.toml | 2 ++ uv.lock | 8 +++----- 7 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 19c4702..62e22c5 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -9,6 +9,7 @@ on: - "Cargo.toml" - "Cargo.lock" - ".github/workflows/integration-ci.yml" + - "rust-toolchain.toml" - "tests/setup_integration_data.py" pull_request: paths: @@ -18,6 +19,7 @@ on: - "Cargo.toml" - "Cargo.lock" - ".github/workflows/integration-ci.yml" + - "rust-toolchain.toml" - "tests/setup_integration_data.py" env: @@ -31,20 +33,15 @@ jobs: name: Rust - latest needs: prepare-data runs-on: ubuntu-latest - strategy: - matrix: - toolchain: - - "1.99.0" steps: - name: Checkout uses: actions/checkout@v6 - - name: Install Rust 1.99 + - name: Install Rust run: | - rustup toolchain install ${{ matrix.toolchain }} - rustup component add rustfmt clippy --toolchain ${{ matrix.toolchain }} - rustup default ${{ matrix.toolchain }} + rustup toolchain install + rustup component add rustfmt clippy - name: rust-cache uses: Swatinem/rust-cache@v2 @@ -84,17 +81,13 @@ jobs: name: Rust coverage needs: prepare-data runs-on: ubuntu-latest - strategy: - matrix: - toolchain: - - "1.99.0" steps: - name: Checkout uses: actions/checkout@v6 - name: Install latest Rust version - run: rustup toolchain install ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + run: rustup toolchain install - name: rust-cache uses: Swatinem/rust-cache@v2 diff --git a/.gitignore b/.gitignore index 38337c2..e040288 100644 --- a/.gitignore +++ b/.gitignore @@ -16,13 +16,9 @@ # integration tests specifics !tests/ -!tests/*.py - -!tests/python/ -!tests/python/**/*.py - -!tests/rust/ -!tests/rust/**/*.rs +!tests/**/ +!tests/**/*.rs +!tests/**/*.py !examples/ !examples/**/ @@ -30,6 +26,7 @@ !Cargo.toml !Cargo.lock +!rust-toolchain.toml # uv !pyproject.toml diff --git a/Cargo.toml b/Cargo.toml index f459bd3..b1d4ac8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "adaptif" version = "0.2.0" edition = "2024" +rust-version = "1.99.0" [dependencies] num-traits = "0.2.19" # TODO: use "libm" feature for no_std diff --git a/Makefile b/Makefile index 27614ee..0d801dd 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,8 @@ check-installs: check-rust-install check-uv-install install-rust: @command rustup --version >/dev/null 2>&1 || \ { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh; } - rustup toolchain install 1.99.0 - rustup default 1.99.0 + rustup toolchain install + rustup component add rustfmt clippy install-uv: @command uv --version >/dev/null 2>&1 && \ uv self update || \ diff --git a/pyproject.toml b/pyproject.toml index 9afb002..f0e3cc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ license-files = ["LICENSE-APACHE", "LICENSE-MIT"] requires-python = ">=3.10" dependencies = [ "numpy>=1.21.0,<2.0.0", - "soundfile>=0.14.0", ] classifiers = [ "Development Status :: 3 - Alpha", @@ -45,6 +44,7 @@ dev = [ "ruff>=0.16.4", "ty>=0.0.75", "maturin>=1.15.0", + "soundfile>=0.14.0", ] [tool.maturin] diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..b6cff89 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.99.0" diff --git a/uv.lock b/uv.lock index dd67d8e..537dd9c 100644 --- a/uv.lock +++ b/uv.lock @@ -12,7 +12,6 @@ version = "0.2.0" source = { editable = "." } dependencies = [ { name = "numpy" }, - { name = "soundfile" }, ] [package.dev-dependencies] @@ -21,14 +20,12 @@ dev = [ { name = "pre-commit" }, { name = "pytest" }, { name = "ruff" }, + { name = "soundfile" }, { name = "ty" }, ] [package.metadata] -requires-dist = [ - { name = "numpy", specifier = ">=1.21.0,<2.0.0" }, - { name = "soundfile", specifier = ">=0.14.0" }, -] +requires-dist = [{ name = "numpy", specifier = ">=1.21.0,<2.0.0" }] [package.metadata.requires-dev] dev = [ @@ -36,6 +33,7 @@ dev = [ { name = "pre-commit", specifier = ">=4.0.1,<5.0.0" }, { name = "pytest", specifier = ">=9.1.1" }, { name = "ruff", specifier = ">=0.16.4" }, + { name = "soundfile", specifier = ">=0.14.0" }, { name = "ty", specifier = ">=0.0.75" }, ] From 86124d68fdb4d2e844e649342566ec182c86f177 Mon Sep 17 00:00:00 2001 From: bglid Date: Mon, 5 Oct 2026 08:23:55 -0400 Subject: [PATCH 19/21] cleaning makefile --- Makefile | 18 ++++++------------ tests/setup_integration_data.py | 4 ++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 0d801dd..3bcad26 100644 --- a/Makefile +++ b/Makefile @@ -67,30 +67,24 @@ check-all: check-rs check-py # TESTS ################################################## -.PHONY: \ - test-rs test-rs-unit test-rs-integration \ - test-py test-py-unit test-py-integration \ - test-unit test-integration test-all +.PHONY: setup-data test-rs test-py test-all + +setup-data: + uv run tests/setup_integration_data.py # going to switch to .sh # Rust -test-rs-unit: check-rust-install +test-rs: check-rust-install setup-data # NOTE: `--all-features` currently produces a linker error, smth to do with PyO3. # Since we don't have any tests in the Python features, leaving it out for now. cargo tarpaulin --lib --frozen --skip-clean -test-rs-integration: cargo test --test rust --frozen -test-rs: test-rs-unit test-rs-integration # Python -test-py-unit: $(PY_BINDINGS) +test-py: $(PY_BINDINGS) setup-data uv run pytest python/tests -test-py-integration: $(PY_BINDINGS) uv run pytest tests/python -test-py: test-py-unit test-py-integration # Test type grouping -test-unit: test-rs-unit test-py-unit -test-integration: test-rs-integration test-py-integration test-all: test-rs test-py # Audit dependencies diff --git a/tests/setup_integration_data.py b/tests/setup_integration_data.py index e56f14a..450aa92 100644 --- a/tests/setup_integration_data.py +++ b/tests/setup_integration_data.py @@ -81,6 +81,10 @@ def generate_dataset() -> None: def main() -> None: DATA_DIR.mkdir(parents=True, exist_ok=True) + # early return if already setup + if MS_SNSD.exists(): + return + clone_dataset() configure_dataset() generate_dataset() From e179d03c0007d4a74f6602ec777b0b372f5aad96 Mon Sep 17 00:00:00 2001 From: bglid Date: Mon, 5 Oct 2026 18:58:20 -0400 Subject: [PATCH 20/21] utils naming updates, hound, and reorg rust tests --- .gitignore | 1 + Cargo.lock | 7 +++++++ Cargo.toml | 4 ++++ tests/python/test_filters.py | 10 +++++----- tests/python/utils.py | 18 +++++++++--------- tests/rust.rs | 4 ++++ tests/rust/filters.rs | 2 +- tests/rust/main.rs | 2 -- tests/rust/utils.rs | 16 ++++++++-------- 9 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 tests/rust.rs delete mode 100644 tests/rust/main.rs diff --git a/.gitignore b/.gitignore index e040288..a59973f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ !tests/**/ !tests/**/*.rs !tests/**/*.py +!tests/**/.wav !examples/ !examples/**/ diff --git a/Cargo.lock b/Cargo.lock index afb6ac6..57b04e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 4 name = "adaptif" version = "0.2.0" dependencies = [ + "hound", "num-traits", "numpy", "pyo3", @@ -23,6 +24,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "inventory" version = "0.3.24" diff --git a/Cargo.toml b/Cargo.toml index b1d4ac8..2c35409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ crate-type = ["cdylib", "rlib"] default = [] python = ["dep:pyo3", "dep:numpy"] + [lints.clippy] pedantic = {level = "warn", priority = -1} restriction = {level = "warn", priority = -1} @@ -63,3 +64,6 @@ missing_inline_in_public_items = "allow" # This one is supposed to skip test modules (#[cfg(test)]), but as of 1.98.0 it only works on nightly. inline_modules = "allow" +[dev-dependencies] +hound = "3.5.1" + diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py index c6d0720..9ea2263 100644 --- a/tests/python/test_filters.py +++ b/tests/python/test_filters.py @@ -33,15 +33,15 @@ def test_filter_process_audio(filter) -> None: def test_filter_adapts_signal(filter) -> None: - desired_signal, noisy_signal, noise_reference = load_test_signals() + original_signal, noisy_signal, noise_reference = load_test_signals() cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference) - before_snr = snr(desired_signal=desired_signal, noisy_signal=noisy_signal) - after_snr = snr(desired_signal=desired_signal, noisy_signal=cleaned_signal) + before_snr = snr(original_signal=original_signal, noisy_signal=noisy_signal) + after_snr = snr(original_signal=original_signal, noisy_signal=cleaned_signal) assert after_snr > before_snr # NOTE: point of this is that it doesn't only remove noise, but recovers the signal - before_mse = mse(desired_signal=desired_signal, input_signal=noisy_signal) - after_mse = mse(desired_signal=desired_signal, input_signal=cleaned_signal) + before_mse = mse(original_signal=original_signal, input_signal=noisy_signal) + after_mse = mse(original_signal=original_signal, input_signal=cleaned_signal) assert after_mse < before_mse diff --git a/tests/python/utils.py b/tests/python/utils.py index 52cbb88..86b2bd5 100644 --- a/tests/python/utils.py +++ b/tests/python/utils.py @@ -32,33 +32,33 @@ def load_test_signals() -> tuple[np.ndarray, np.ndarray, np.ndarray]: def mse( - desired_signal: NDArray[np.float64], input_signal: NDArray[np.float64] + original_signal: NDArray[np.float64], input_signal: NDArray[np.float64] ) -> np.float64: """Calculates the Mean Squared Error = 1/n * sum(y - y_hat)**2 Args: - desired_signal (NDArray[np.float64]): Observed or desired value - input_signal (NDArray[np.float64]): Prediction of value + original_signal (NDArray[np.float64]): Original signal/value + input_signal (NDArray[np.float64]): Prediction of signal/value Returns: np.float64: Mean squared error """ - return np.mean((desired_signal - input_signal) ** 2) + return np.mean((original_signal - input_signal) ** 2) def snr( - desired_signal: NDArray[np.float64], noisy_signal: NDArray[np.float64] + original_signal: NDArray[np.float64], noisy_signal: NDArray[np.float64] ) -> np.float64: """Calculates the Signal to Noise Ratio in dB: SNR = (Power of Signal)/(Power of Noise). Args: - desired_signal (NDArray[np.float64]): Input signal + original_signal (NDArray[np.float64]): Original clean signal noisy_signal (NDArray[np.float64]): Input Noisy signal Returns: np.float64: SNR in dB """ - signal_power = np.sum(desired_signal**2) - noise_power = np.sum((desired_signal - noisy_signal) ** 2) + 1e-8 + signal_power = np.sum(original_signal**2) + noise_power = np.sum((original_signal - noisy_signal) ** 2) + 1e-8 snr = signal_power / noise_power - return 10 * np.log10(snr + 1e-12) + return 10 * np.log10(snr) diff --git a/tests/rust.rs b/tests/rust.rs new file mode 100644 index 0000000..dbcf12a --- /dev/null +++ b/tests/rust.rs @@ -0,0 +1,4 @@ +mod rust { + pub mod filters; + pub mod utils; +} diff --git a/tests/rust/filters.rs b/tests/rust/filters.rs index f1c02b3..8f98563 100644 --- a/tests/rust/filters.rs +++ b/tests/rust/filters.rs @@ -7,7 +7,7 @@ use adaptif::algorithms::Lms; use adaptif::filters::{AdaptiveFilter as _, FilterBase}; use adaptif::types::signals::{InputSignal, NoiseReference}; -use crate::utils::{mse, snr, test_signals}; +use crate::rust::utils::{mse, snr, test_signals}; #[test] #[allow(clippy::unwrap_used, reason = "Integration test")] diff --git a/tests/rust/main.rs b/tests/rust/main.rs deleted file mode 100644 index fc5b71e..0000000 --- a/tests/rust/main.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod filters; -mod utils; diff --git a/tests/rust/utils.rs b/tests/rust/utils.rs index 9be989f..080a3be 100644 --- a/tests/rust/utils.rs +++ b/tests/rust/utils.rs @@ -32,20 +32,20 @@ pub fn test_signals() -> (Vec, Vec, Vec) { } #[allow(clippy::unwrap_used, clippy::indexing_slicing, reason = "Tests")] -pub fn mse(desired: &[f64], input: &[f64]) -> f64 { - desired +pub fn mse(original_signal: &[f64], input_signal: &[f64]) -> f64 { + original_signal .iter() - .zip(input) + .zip(input_signal) .map(|(d, i)| (d - i).powi(2)) .sum::() - / desired.len().to_f64().unwrap() + / original_signal.len().to_f64().unwrap() } -pub fn snr(desired: &[f64], input: &[f64]) -> f64 { - let signal_power = desired.iter().map(|d| d.powi(2)).sum::(); - let noise_power = desired +pub fn snr(original_signal: &[f64], input_signal: &[f64]) -> f64 { + let signal_power = original_signal.iter().map(|d| d.powi(2)).sum::(); + let noise_power = original_signal .iter() - .zip(input) + .zip(input_signal) .map(|(d, i)| (d - i).powi(2)) .sum::() + 1e-8; From b26f2b9d2019b5f9681cb1c24d10861dea8fe5bf Mon Sep 17 00:00:00 2001 From: bglid Date: Mon, 5 Oct 2026 19:05:08 -0400 Subject: [PATCH 21/21] quick refactor of tests to make them more compact --- .gitignore | 2 +- tests/python/test_filters.py | 10 ++-------- tests/rust/filters.rs | 17 ++--------------- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index a59973f..9183b86 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ !tests/**/ !tests/**/*.rs !tests/**/*.py -!tests/**/.wav +!tests/**/*.wav !examples/ !examples/**/ diff --git a/tests/python/test_filters.py b/tests/python/test_filters.py index 9ea2263..9168e2c 100644 --- a/tests/python/test_filters.py +++ b/tests/python/test_filters.py @@ -23,20 +23,14 @@ def filter(request): return filter_class(**kwargs) -def test_filter_process_audio(filter) -> None: - _, noisy_signal, noise_reference = load_test_signals() +def test_filter_adapts_signal(filter) -> None: + original_signal, noisy_signal, noise_reference = load_test_signals() cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference) assert cleaned_signal.shape == noisy_signal.shape assert np.all(np.isfinite(cleaned_signal)) - -def test_filter_adapts_signal(filter) -> None: - original_signal, noisy_signal, noise_reference = load_test_signals() - - cleaned_signal = filter.adapt(input_signal=noisy_signal, noise_ref=noise_reference) - before_snr = snr(original_signal=original_signal, noisy_signal=noisy_signal) after_snr = snr(original_signal=original_signal, noisy_signal=cleaned_signal) assert after_snr > before_snr diff --git a/tests/rust/filters.rs b/tests/rust/filters.rs index 8f98563..a42536e 100644 --- a/tests/rust/filters.rs +++ b/tests/rust/filters.rs @@ -11,8 +11,8 @@ use crate::rust::utils::{mse, snr, test_signals}; #[test] #[allow(clippy::unwrap_used, reason = "Integration test")] -fn filter_process_audio() { - let (_, noisy_wav, noise_ref_wav) = test_signals(); +fn filter_adapts_signal() { + let (desired_wav, noisy_wav, noise_ref_wav) = test_signals(); let noisy_signal = InputSignal::new(noisy_wav.clone()).unwrap(); let noise_reference = NoiseReference::new(noise_ref_wav).unwrap(); let lms_config = Lms::new(0.01).unwrap(); @@ -23,19 +23,6 @@ fn filter_process_audio() { assert_eq!(cleaned_signal.len(), noisy_wav.len()); assert!(cleaned_signal.iter().all(|sample| sample.is_finite())); -} - -#[test] -#[allow(clippy::unwrap_used, reason = "Integration test")] -fn filter_adapts_signal() { - let (desired_wav, noisy_wav, noise_ref_wav) = test_signals(); - let noisy_signal = InputSignal::new(noisy_wav).unwrap(); - let noise_reference = NoiseReference::new(noise_ref_wav).unwrap(); - let lms_config = Lms::new(0.01).unwrap(); - let window_size = 16; - let mut lms = FilterBase::new(lms_config, window_size).unwrap(); - - let cleaned_signal = lms.adapt(&noisy_signal, &noise_reference).unwrap(); let before_mse = mse(&desired_wav, &noisy_signal); let after_mse = mse(&desired_wav, &cleaned_signal);