pyFDN 0.4.1__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyfdn-0.4.1 → pyfdn-0.4.2}/HISTORY.rst +41 -2
- {pyfdn-0.4.1 → pyfdn-0.4.2}/PKG-INFO +2 -1
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/api_reference.rst +16 -3
- pyfdn-0.4.2/docs/usage.rst +102 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/pyproject.toml +2 -2
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/__init__.py +22 -7
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/allpass.py +26 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/flamo.py +1 -1
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/flamo_graph.py +2 -2
- pyfdn-0.4.1/src/pyFDN/build_io.py → pyfdn-0.4.2/src/pyFDN/build.py +50 -25
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/__init__.py +2 -0
- pyfdn-0.4.2/src/pyFDN/eq/_design_record.py +25 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/design.py +86 -27
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/graphic_eq.py +8 -1
- pyfdn-0.4.2/src/pyFDN/generate/fdn_build_gallery.py +305 -0
- pyfdn-0.4.2/src/pyFDN/generate/fdn_matrix_gallery.py +341 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/sample_delay_lengths.py +9 -4
- pyfdn-0.4.2/src/pyFDN/preset.py +195 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/ATTRIBUTION.md +11 -2
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/metadata.json +11 -0
- pyfdn-0.4.2/src/pyFDN/resources/audio/rir/meetingroom_to_hallway_290cm.wav +0 -0
- pyfdn-0.4.2/src/pyFDN/resources/licenses/coupled-rooms-CC-BY-4.0.txt +413 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d1.json +412 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d2.json +412 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d1.json +88 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d2.json +88 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d1.json +122 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d2.json +122 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d1.json +164 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d2.json +164 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d1.json +412 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d2.json +412 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d1.json +88 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d2.json +88 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d1.json +122 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d2.json +122 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d1.json +164 -0
- pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d2.json +164 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/references.bib +10 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/td/__init__.py +12 -0
- pyfdn-0.4.2/src/pyFDN/td/operators.py +1028 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/__init__.py +4 -2
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/build.py +137 -14
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/filters.py +64 -9
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/_targets.py +22 -4
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/spectral.py +16 -4
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/temporal.py +10 -4
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/params.py +1 -1
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_flamo.py +2 -2
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_impz.py +1 -1
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/PKG-INFO +2 -1
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/SOURCES.txt +9 -3
- pyfdn-0.4.2/tests/test_auxiliary_allpass.py +36 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_plot.py +3 -3
- pyfdn-0.4.1/tests/test_build_io.py → pyfdn-0.4.2/tests/test_build.py +3 -3
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_eq_designs.py +40 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_generate.py +83 -11
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_packaged_resources.py +11 -5
- pyfdn-0.4.2/tests/test_preset.py +298 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_process_extensions.py +1 -1
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_td_operators.py +231 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_train.py +68 -21
- pyfdn-0.4.1/docs/usage.rst +0 -41
- pyfdn-0.4.1/src/pyFDN/generate/fdn_matrix_gallery.py +0 -599
- pyfdn-0.4.1/src/pyFDN/presets.py +0 -50
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d1.json +0 -396
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d2.json +0 -396
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d1.json +0 -72
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d2.json +0 -72
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d1.json +0 -106
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d2.json +0 -106
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d1.json +0 -148
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d2.json +0 -148
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d1.json +0 -396
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d2.json +0 -396
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d1.json +0 -72
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d2.json +0 -72
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d1.json +0 -106
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d2.json +0 -106
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d1.json +0 -148
- pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d2.json +0 -148
- pyfdn-0.4.1/src/pyFDN/td/operators.py +0 -508
- {pyfdn-0.4.1 → pyfdn-0.4.2}/AUTHORS.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/CONTRIBUTING.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/LICENSE +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/MANIFEST.in +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/README.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/Makefile +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/__init__.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/audio_gallery.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/authors.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/conf.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/contributing.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/example_gallery.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/history.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/index.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/installation.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/logo_pyFDN_3.png +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/logo_pyFDN_4.png +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/pyfdn_logo_basic.png +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/pyfdn_logo_wave.png +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/make.bat +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/patch_rst.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/readme.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/references.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/training_losses.rst +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/setup.cfg +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/setup.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/__init__.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/acoustics.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/audio.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/coupled_rooms.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/delay.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/marimo_utils.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/math.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/plot.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/poles.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/tiny_rotation_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/utils.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/_backend.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/biquads.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/probe_sos.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/SDN.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/__init__.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/allpass_completion.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/homogeneous_allpass_fdn.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/rand_admissible_homogeneous_allpass.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_in_fdn.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/anderson_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/complete_orthogonal.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/construct_cascaded_paraunitary_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/construct_paraunitary_from_elementals.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/construct_velvet_feedback_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/degree_one_lossless.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/householder_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/is_almost_zero.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/nearest_orthogonal.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/nearest_sign_agnostic_orthogonal.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/random_matrix_shift.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/random_orthogonal.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/schroeder_reverberator.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/shift_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/shift_matrix_distribute.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/process.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/references.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/__init__.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/drums/drums.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/general/synth_dry.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/rir/s3_r4_o.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/speech/P1_Set1_8.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/speech/P20_Set1_8.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/strings/Kleiderschrank_Taylor_DI.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/strings/Sommerhit2016_Taylor_DI.wav +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/licenses/diff-fdn-colorless-MIT.txt +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/td/connectors.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/engine.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/__init__.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/base.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/parameter.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/response.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_pr.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_ss.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_tf.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/flamo_to_pr.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/impz_to_res.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/mtf_to_impz.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/pr_to_impz.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/dependency_links.txt +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/requires.txt +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/top_level.txt +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/__init__.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/conftest.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/matloader.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/reference/README.md +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/reference/example_onePoleAbsorption.mat +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/reference/onePoleAbsorption_reference.mat +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_api_reference.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_audio_gallery.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_acoustics.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_delay.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_flamo.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_math.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_utils.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_coupled_rooms.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_dss_to_pr.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_example_gallery.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_flamo_graph.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_flamo_one_pole_absorption.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_flamo_to_pr.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_marimo_examples_run.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_pyFDN.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_sdn.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_td_connectors.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_td_graph_processing.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_time_varying_matrix.py +0 -0
- {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_translate.py +0 -0
|
@@ -2,9 +2,44 @@
|
|
|
2
2
|
History
|
|
3
3
|
=======
|
|
4
4
|
|
|
5
|
+
0.4.2 (2026-08-27)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
* Add the ``example_multislope_rir_to_fdn`` notebook: a measured room-transition
|
|
9
|
+
response is fitted with two decay slopes per octave band using DecayFitNet
|
|
10
|
+
from the ``multislope`` package, and each slope is resynthesised by its own
|
|
11
|
+
FDN. The response it uses ships with the package as the
|
|
12
|
+
``meetingroom_to_hallway_290cm`` audio resource, taken from the coupled-rooms
|
|
13
|
+
transition dataset.
|
|
14
|
+
* Add nonlinear and pitch-shifting time-domain operators for shimmer
|
|
15
|
+
reverberation -- ``DCBlocker``, ``ControllableFullWaveRect``, ``SDFD``,
|
|
16
|
+
``RingModulator``, ``PitchShift`` and ``GranularPitchShift`` -- all usable as
|
|
17
|
+
``process_fdn`` hooks, plus the ``example_shimmer_fdn`` notebook that walks
|
|
18
|
+
through each one inside the feedback loop of the same 8-line FDN.
|
|
19
|
+
* ``is_uniallpass`` no longer solves a singular Lyapunov equation when ``A`` is
|
|
20
|
+
itself lossless (as in the allpass-in-FDN structure). The spectral radius is
|
|
21
|
+
checked first and such a system is reported as not uniallpass, instead of the
|
|
22
|
+
result depending on whether LAPACK chose to warn or raise on the ill-
|
|
23
|
+
conditioned solve.
|
|
24
|
+
* Fix a device mismatch when a loss built for one run is used in another: every
|
|
25
|
+
loss that holds a reference impulse response (``MatchCumulativeEnergy``,
|
|
26
|
+
``MatchEnergyDecay``, ``MatchMagnitude``, ``MatchImpulseResponse``,
|
|
27
|
+
``MatchSpectrogram``, ``MatchMelSpectrogram``) aligned its reference once, on
|
|
28
|
+
the first response it saw, and then handed that CPU tensor to a later CUDA
|
|
29
|
+
step -- the way a notebook cell that builds the loss but does not re-run on a
|
|
30
|
+
runtime switch would hit it. The aligned reference is now keyed on the
|
|
31
|
+
response's shape, device, dtype and sample rate, and rebuilt when any of them
|
|
32
|
+
changes. ``MatchSpectrogram`` and ``MatchMelSpectrogram`` also default their
|
|
33
|
+
``device`` to the response's rather than to the CPU, so FLAMO builds its
|
|
34
|
+
filterbanks where the model is.
|
|
35
|
+
|
|
5
36
|
0.4.1 (2026-08-24)
|
|
6
37
|
------------------
|
|
7
38
|
|
|
39
|
+
* Let ``fdn_build_gallery`` and the target-to-EQ functions optionally return
|
|
40
|
+
the design choices used to produce their coefficients, ready to store in an
|
|
41
|
+
``FDNPreset``. Build generation now also exposes the delay distribution and
|
|
42
|
+
coprimality options and lives separately from the matrix galleries.
|
|
8
43
|
* **Breaking:** rename the ``Fs`` parameter to ``fs`` everywhere it is still
|
|
9
44
|
spelled with a capital -- ``dss_to_flamo``, ``dss_to_pr``, ``delay_module``
|
|
10
45
|
and ``SDN``. Every other sampling-rate argument in the package was already
|
|
@@ -15,6 +50,10 @@ History
|
|
|
15
50
|
0.4.0 (2026-08-23)
|
|
16
51
|
------------------
|
|
17
52
|
|
|
53
|
+
* Add ``FDNPreset`` JSON documents: a baked ``FDNBuild`` plus catalog
|
|
54
|
+
metadata and a controlled vocabulary for delays, matrices, and the three
|
|
55
|
+
filter hooks. ``trainable_from_preset`` restores filter targets as meaningful
|
|
56
|
+
FLAMO parameters only when they reproduce the baked coefficients.
|
|
18
57
|
* **Breaking:** replace ``train_fdn``'s ``mode`` string, and the ``target``,
|
|
19
58
|
``criteria``, ``sparsity_alpha`` and ``mss_nfft`` arguments that went with
|
|
20
59
|
it, with a composed loss object. An objective is now written out --
|
|
@@ -37,7 +76,7 @@ History
|
|
|
37
76
|
from ``rt`` (``LOSSLESS_ALIAS_DECAY_DB`` when ``rt`` is None) and
|
|
38
77
|
``trainable_from_build`` threads it into every module, so a magnitude
|
|
39
78
|
objective sees a bounded response.
|
|
40
|
-
* Train the decay: ``
|
|
79
|
+
* Train the decay: ``AttenuationFilter`` parametrizes the in-loop absorption filter
|
|
41
80
|
by reverberation time per band, so the loop stays contractive for every value
|
|
42
81
|
the parameter can take, and takes either one RT curve for the network or one
|
|
43
82
|
per delay line. ``OutputEQ`` trains the output filter outside the recursion,
|
|
@@ -53,7 +92,7 @@ History
|
|
|
53
92
|
enumerates them.
|
|
54
93
|
* Export the FLAMO graph builders (``assemble_fdn_core``, ``wrap_fdn_shell``,
|
|
55
94
|
``gain_module``, ``delay_module``, ``matrix_module``, ``fir_matrix_module``,
|
|
56
|
-
``sos_filter_module``, ``hook_module``, ``
|
|
95
|
+
``sos_filter_module``, ``hook_module``, ``AttenuationFilter``, ``OutputEQ``) from
|
|
57
96
|
the top-level namespace.
|
|
58
97
|
* Make ``build_to_impz`` apply all three hooks, so it no longer rejects builds
|
|
59
98
|
that carry an output EQ, and make ``extract_build`` refuse a hook it cannot
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyFDN
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Python library for Feedback Delay Networks
|
|
5
5
|
Author: Sebastian J. Schlecht, Facundo Franchino, Jeremy Bai
|
|
6
6
|
Maintainer: Facundo Franchino, Sebastian J. Schlecht
|
|
@@ -17,6 +17,7 @@ Requires-Python: >=3.10
|
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
License-File: src/pyFDN/resources/ATTRIBUTION.md
|
|
20
|
+
License-File: src/pyFDN/resources/licenses/coupled-rooms-CC-BY-4.0.txt
|
|
20
21
|
License-File: src/pyFDN/resources/licenses/diff-fdn-colorless-MIT.txt
|
|
21
22
|
Requires-Dist: numpy
|
|
22
23
|
Requires-Dist: soundfile
|
|
@@ -100,10 +100,11 @@ EQ Design (``pyFDN.eq``)
|
|
|
100
100
|
|
|
101
101
|
Explicit functions map either decay targets or gain targets onto a named
|
|
102
102
|
filter design. The same functions run in NumPy or Torch; the trainable
|
|
103
|
-
:class:`pyFDN.
|
|
103
|
+
:class:`pyFDN.AttenuationFilter` and :class:`pyFDN.OutputEQ` modules use these
|
|
104
104
|
mappings inside a training loop. ``EQDesign`` is the literal choice of
|
|
105
105
|
``"graphic_eq"``, ``"first_order_shelf"``, or ``"one_pole"`` used by those
|
|
106
|
-
modules.
|
|
106
|
+
modules. The target-to-EQ functions accept ``return_design=True`` when their
|
|
107
|
+
JSON-compatible design record is also needed for an ``FDNPreset``.
|
|
107
108
|
|
|
108
109
|
.. autosummary::
|
|
109
110
|
:toctree: generated/
|
|
@@ -140,6 +141,12 @@ rendered with ``.process(signal)``. See :mod:`pyFDN.td`.
|
|
|
140
141
|
pyFDN.td.Gain
|
|
141
142
|
pyFDN.td.Delay
|
|
142
143
|
pyFDN.td.AbsoluteValue
|
|
144
|
+
pyFDN.td.DCBlocker
|
|
145
|
+
pyFDN.td.ControllableFullWaveRect
|
|
146
|
+
pyFDN.td.SDFD
|
|
147
|
+
pyFDN.td.RingModulator
|
|
148
|
+
pyFDN.td.PitchShift
|
|
149
|
+
pyFDN.td.GranularPitchShift
|
|
143
150
|
pyFDN.td.SOSBank
|
|
144
151
|
pyFDN.td.MatrixFIR
|
|
145
152
|
pyFDN.td.MatrixConvolver
|
|
@@ -182,7 +189,7 @@ An FDN as FLAMO modules, assembled from numpy values. The three filter hooks --
|
|
|
182
189
|
pyFDN.fir_matrix_module
|
|
183
190
|
pyFDN.sos_filter_module
|
|
184
191
|
pyFDN.hook_module
|
|
185
|
-
pyFDN.
|
|
192
|
+
pyFDN.AttenuationFilter
|
|
186
193
|
pyFDN.OutputEQ
|
|
187
194
|
|
|
188
195
|
Polynomial & Matrix Maths
|
|
@@ -246,6 +253,11 @@ Build Files, Packaged Examples & References
|
|
|
246
253
|
pyFDN.audio_metadata
|
|
247
254
|
pyFDN.load_audio
|
|
248
255
|
pyFDN.available_fdn_presets
|
|
256
|
+
pyFDN.get_fdn_preset
|
|
257
|
+
pyFDN.FDNPreset
|
|
258
|
+
pyFDN.fdn_preset_to_dict
|
|
259
|
+
pyFDN.fdn_preset_from_dict
|
|
260
|
+
pyFDN.save_fdn_preset
|
|
249
261
|
pyFDN.load_fdn_preset
|
|
250
262
|
pyFDN.fdn_build_to_dict
|
|
251
263
|
pyFDN.fdn_build_from_dict
|
|
@@ -295,6 +307,7 @@ Training
|
|
|
295
307
|
|
|
296
308
|
pyFDN.build_fdn
|
|
297
309
|
pyFDN.trainable_from_build
|
|
310
|
+
pyFDN.trainable_from_preset
|
|
298
311
|
pyFDN.build_set_decay
|
|
299
312
|
pyFDN.Trainable
|
|
300
313
|
pyFDN.train_fdn
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
=====
|
|
2
|
+
Usage
|
|
3
|
+
=====
|
|
4
|
+
|
|
5
|
+
All main functions are accessible directly from ``pyFDN``::
|
|
6
|
+
|
|
7
|
+
import pyFDN
|
|
8
|
+
|
|
9
|
+
feedback = pyFDN.random_orthogonal(4)
|
|
10
|
+
attenuation = pyFDN.decay_to_first_order_shelf(
|
|
11
|
+
1.2, 0.9, None, [100, 150, 200, 250], 48_000
|
|
12
|
+
)
|
|
13
|
+
gain_db = pyFDN.lin_to_db([0.5, 1.0, 2.0])
|
|
14
|
+
|
|
15
|
+
Or import specific functions::
|
|
16
|
+
|
|
17
|
+
from pyFDN import random_orthogonal, decay_to_first_order_shelf, dss_to_ss
|
|
18
|
+
|
|
19
|
+
feedback = random_orthogonal(4)
|
|
20
|
+
|
|
21
|
+
Packaged tutorial resources
|
|
22
|
+
---------------------------
|
|
23
|
+
|
|
24
|
+
The small resources used by the browser tutorials are available through the
|
|
25
|
+
public API, independent of the current working directory::
|
|
26
|
+
|
|
27
|
+
dry, fs = pyFDN.load_audio("synth_dry")
|
|
28
|
+
|
|
29
|
+
preset = pyFDN.get_fdn_preset("colorless_N8_d1")
|
|
30
|
+
reverberator = pyFDN.build_set_decay(preset.build, 1.5)
|
|
31
|
+
|
|
32
|
+
citation = pyFDN.paper_link("Allpass_Feedback_Delay_Networks")
|
|
33
|
+
|
|
34
|
+
Use ``pyFDN.available_audio()`` and ``pyFDN.available_fdn_presets()`` to list
|
|
35
|
+
the bundled choices. Attribution and license information for audio is returned
|
|
36
|
+
by ``pyFDN.audio_metadata(name)``.
|
|
37
|
+
|
|
38
|
+
FDN builds can also be exchanged as readable, versioned JSON files::
|
|
39
|
+
|
|
40
|
+
pyFDN.save_fdn_build("reverberator.json", reverberator)
|
|
41
|
+
restored = pyFDN.load_fdn_build("reverberator.json")
|
|
42
|
+
|
|
43
|
+
Preset documents
|
|
44
|
+
----------------
|
|
45
|
+
|
|
46
|
+
An ``FDNPreset`` keeps a baked ``FDNBuild`` together with catalog metadata and
|
|
47
|
+
the design choices that cannot be recovered reliably from its numbers. Unknown
|
|
48
|
+
design choices are left out rather than guessed. Generators and EQ design
|
|
49
|
+
functions return only their numerical result by default; opt into their design
|
|
50
|
+
record when preparing a preset::
|
|
51
|
+
|
|
52
|
+
build, design = pyFDN.fdn_build_gallery(
|
|
53
|
+
N=8,
|
|
54
|
+
fs=48_000,
|
|
55
|
+
delay_distribution="geometric",
|
|
56
|
+
coprime=True,
|
|
57
|
+
rt=1.2,
|
|
58
|
+
rt_nyquist=0.8,
|
|
59
|
+
return_design=True,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
A separately designed filter can return the same kind of record. The caller
|
|
63
|
+
only has to say which FDN hook receives it::
|
|
64
|
+
|
|
65
|
+
import dataclasses
|
|
66
|
+
|
|
67
|
+
post_matrix, post_matrix_design = pyFDN.gain_to_one_pole(
|
|
68
|
+
gain_db=[0.0] * 8,
|
|
69
|
+
gain_db_nyquist=[-3.0] * 8,
|
|
70
|
+
return_design=True,
|
|
71
|
+
)
|
|
72
|
+
build = dataclasses.replace(build, post_matrix=post_matrix)
|
|
73
|
+
design["post_matrix"] = post_matrix_design
|
|
74
|
+
|
|
75
|
+
Once all numerical and design parts are assembled, freeze that particular
|
|
76
|
+
realization as the preset::
|
|
77
|
+
|
|
78
|
+
preset = pyFDN.FDNPreset(
|
|
79
|
+
build=build,
|
|
80
|
+
metadata={
|
|
81
|
+
"name": "small-room",
|
|
82
|
+
"description": "A short, neutral room",
|
|
83
|
+
"authors": ["A. Author"],
|
|
84
|
+
"license": "CC0-1.0",
|
|
85
|
+
"tags": ["room", "short"],
|
|
86
|
+
},
|
|
87
|
+
design=design,
|
|
88
|
+
)
|
|
89
|
+
pyFDN.save_fdn_preset("small-room.json", preset)
|
|
90
|
+
restored = pyFDN.load_fdn_preset("small-room.json")
|
|
91
|
+
|
|
92
|
+
Metadata is an open dictionary because it is descriptive rather than part of
|
|
93
|
+
the sound. By convention, ``tags`` is a list of strings, so selecting presets
|
|
94
|
+
does not require a metadata class::
|
|
95
|
+
|
|
96
|
+
requested = {"room", "short"}
|
|
97
|
+
matches = requested <= set(restored.metadata.get("tags", []))
|
|
98
|
+
|
|
99
|
+
The numerical build, including its sample rate, is authoritative. Filter design
|
|
100
|
+
records may additionally carry RT or gain targets. In that case
|
|
101
|
+
``pyFDN.trainable_from_preset`` restores the meaningful FLAMO parameters and
|
|
102
|
+
checks that they reproduce the baked SOS coefficients before building a model.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyFDN"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "Python library for Feedback Delay Networks"
|
|
9
9
|
readme = "README.rst"
|
|
10
10
|
authors = [
|
|
@@ -113,7 +113,7 @@ include = ["pyFDN*"]
|
|
|
113
113
|
# triggers on. Usage: bump-my-version bump {major|minor|patch}
|
|
114
114
|
|
|
115
115
|
[tool.bumpversion]
|
|
116
|
-
current_version = "0.4.
|
|
116
|
+
current_version = "0.4.2"
|
|
117
117
|
allow_dirty = false
|
|
118
118
|
commit = true
|
|
119
119
|
message = "Bump version: {current_version} → {new_version}"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from importlib import import_module
|
|
4
4
|
|
|
5
5
|
__author__ = "Facundo Franchino"
|
|
6
|
-
__version__ = "0.4.
|
|
6
|
+
__version__ = "0.4.2"
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
# acoustics
|
|
@@ -36,7 +36,7 @@ __all__ = [
|
|
|
36
36
|
"fir_matrix_module",
|
|
37
37
|
"sos_filter_module",
|
|
38
38
|
"hook_module",
|
|
39
|
-
"
|
|
39
|
+
"AttenuationFilter",
|
|
40
40
|
"OutputEQ",
|
|
41
41
|
"audio_metadata",
|
|
42
42
|
"available_audio",
|
|
@@ -45,7 +45,12 @@ __all__ = [
|
|
|
45
45
|
"paper_link",
|
|
46
46
|
"paper_reference",
|
|
47
47
|
"available_fdn_presets",
|
|
48
|
+
"get_fdn_preset",
|
|
48
49
|
"load_fdn_preset",
|
|
50
|
+
"FDNPreset",
|
|
51
|
+
"fdn_preset_from_dict",
|
|
52
|
+
"fdn_preset_to_dict",
|
|
53
|
+
"save_fdn_preset",
|
|
49
54
|
"fdn_build_from_dict",
|
|
50
55
|
"fdn_build_to_dict",
|
|
51
56
|
"load_fdn_build",
|
|
@@ -148,6 +153,7 @@ __all__ = [
|
|
|
148
153
|
# training
|
|
149
154
|
"build_fdn",
|
|
150
155
|
"trainable_from_build",
|
|
156
|
+
"trainable_from_preset",
|
|
151
157
|
"LOSSLESS_ALIAS_DECAY_DB",
|
|
152
158
|
"build_set_decay",
|
|
153
159
|
"Trainable",
|
|
@@ -348,7 +354,8 @@ from .auxiliary.utils import (
|
|
|
348
354
|
skew,
|
|
349
355
|
sq_to_db,
|
|
350
356
|
)
|
|
351
|
-
from .
|
|
357
|
+
from .build import (
|
|
358
|
+
FDNBuild,
|
|
352
359
|
fdn_build_from_dict,
|
|
353
360
|
fdn_build_to_dict,
|
|
354
361
|
load_fdn_build,
|
|
@@ -403,10 +410,9 @@ from .generate.construct_paraunitary_from_elementals import (
|
|
|
403
410
|
)
|
|
404
411
|
from .generate.construct_velvet_feedback_matrix import construct_velvet_feedback_matrix
|
|
405
412
|
from .generate.degree_one_lossless import degree_one_lossless
|
|
413
|
+
from .generate.fdn_build_gallery import fdn_build_gallery
|
|
406
414
|
from .generate.fdn_matrix_gallery import (
|
|
407
|
-
FDNBuild,
|
|
408
415
|
FDNSystem,
|
|
409
|
-
fdn_build_gallery,
|
|
410
416
|
fdn_matrix_gallery,
|
|
411
417
|
fdn_system_gallery,
|
|
412
418
|
filter_matrix_gallery,
|
|
@@ -424,7 +430,15 @@ from .generate.schroeder_reverberator import schroeder_reverberator
|
|
|
424
430
|
from .generate.SDN import SDN
|
|
425
431
|
from .generate.shift_matrix import shift_matrix
|
|
426
432
|
from .generate.shift_matrix_distribute import shift_matrix_distribute
|
|
427
|
-
from .
|
|
433
|
+
from .preset import (
|
|
434
|
+
FDNPreset,
|
|
435
|
+
available_fdn_presets,
|
|
436
|
+
fdn_preset_from_dict,
|
|
437
|
+
fdn_preset_to_dict,
|
|
438
|
+
get_fdn_preset,
|
|
439
|
+
load_fdn_preset,
|
|
440
|
+
save_fdn_preset,
|
|
441
|
+
)
|
|
428
442
|
|
|
429
443
|
# fdn processing
|
|
430
444
|
from .process import process_fdn
|
|
@@ -436,7 +450,7 @@ from .train import (
|
|
|
436
450
|
L2,
|
|
437
451
|
LOSSLESS_ALIAS_DECAY_DB,
|
|
438
452
|
AsymmetricFlatMagnitude,
|
|
439
|
-
|
|
453
|
+
AttenuationFilter,
|
|
440
454
|
Energy,
|
|
441
455
|
FlatMagnitude,
|
|
442
456
|
FlatSpectrogram,
|
|
@@ -463,6 +477,7 @@ from .train import (
|
|
|
463
477
|
params,
|
|
464
478
|
train_fdn,
|
|
465
479
|
trainable_from_build,
|
|
480
|
+
trainable_from_preset,
|
|
466
481
|
)
|
|
467
482
|
|
|
468
483
|
# state-space translators
|
|
@@ -15,6 +15,10 @@ from scipy.linalg import solve_discrete_lyapunov
|
|
|
15
15
|
from pyFDN.auxiliary.math import general_char_poly
|
|
16
16
|
from pyFDN.generate.is_almost_zero import is_almost_zero
|
|
17
17
|
|
|
18
|
+
# How far inside the unit circle the eigenvalues of A must sit for the discrete
|
|
19
|
+
# Lyapunov equation solved by is_uniallpass to be well posed.
|
|
20
|
+
_STABILITY_TOL = 1e-9
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
def poletti_allpass(
|
|
20
24
|
g: float, U: np.ndarray
|
|
@@ -72,11 +76,33 @@ def is_uniallpass(
|
|
|
72
76
|
True if the system is uniallpass.
|
|
73
77
|
P : ndarray
|
|
74
78
|
Solution of discrete Lyapunov A P A' - P + B B' = 0; diagonal if uniallpass.
|
|
79
|
+
All-NaN when A is not strictly stable, see the note below.
|
|
80
|
+
|
|
81
|
+
Notes
|
|
82
|
+
-----
|
|
83
|
+
The Lyapunov equation only has a (unique, finite) solution when A is
|
|
84
|
+
strictly stable. If A itself is lossless -- as in the allpass-in-FDN
|
|
85
|
+
structure, where the feedback matrix has all its eigenvalues on the unit
|
|
86
|
+
circle -- no such P exists, and the linear system scipy solves is exactly
|
|
87
|
+
singular. Rather than let that surface as an ill-conditioned solve (which
|
|
88
|
+
raises or returns garbage depending on the LAPACK build), the spectral
|
|
89
|
+
radius is checked up front and the system reported as not uniallpass.
|
|
75
90
|
"""
|
|
76
91
|
A = np.asarray(A, dtype=float)
|
|
77
92
|
B = np.asarray(B, dtype=float)
|
|
78
93
|
C = np.asarray(C, dtype=float)
|
|
79
94
|
D = np.asarray(D, dtype=float)
|
|
95
|
+
# P exists only for a strictly stable A; on the unit circle the Lyapunov
|
|
96
|
+
# operator P -> A P A' - P is singular (eigenvalue pairs with λi·λj = 1).
|
|
97
|
+
spectral_radius = float(np.max(np.abs(np.linalg.eigvals(A)))) if A.size else 0.0
|
|
98
|
+
if spectral_radius >= 1.0 - _STABILITY_TOL:
|
|
99
|
+
warnings.warn(
|
|
100
|
+
f"A has spectral radius {spectral_radius:.6g} >= 1; the discrete "
|
|
101
|
+
"Lyapunov equation has no finite solution, so the system is not "
|
|
102
|
+
"uniallpass.",
|
|
103
|
+
stacklevel=2,
|
|
104
|
+
)
|
|
105
|
+
return False, np.full_like(A, np.nan)
|
|
80
106
|
# P = dlyap(A, B @ B')
|
|
81
107
|
P = solve_discrete_lyapunov(A, B @ B.T)
|
|
82
108
|
# Check P is diagonal
|
|
@@ -645,7 +645,7 @@ def assemble_fdn_core(
|
|
|
645
645
|
is ``Parallel(brA=fdn_branch, brB=direct)``.
|
|
646
646
|
post_delay : FLAMO module or None
|
|
647
647
|
In-loop filter after the delays (named ``post_delay``). Any module of
|
|
648
|
-
input/output size N: a :class:`~pyFDN.
|
|
648
|
+
input/output size N: a :class:`~pyFDN.AttenuationFilter` or a plain
|
|
649
649
|
:func:`sos_filter_module` for attenuation, or a whole nested core such as
|
|
650
650
|
a Schroeder allpass. Only an SOS filter here is extractable into an
|
|
651
651
|
:class:`~pyFDN.FDNBuild`.
|
|
@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
13
13
|
import numpy as np
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from pyFDN.
|
|
16
|
+
from pyFDN.build import FDNBuild
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
# Traversal uses type(module).__name__ and getattr; no need to import flamo here.
|
|
@@ -421,7 +421,7 @@ def extract_build(model: Any) -> FDNBuild:
|
|
|
421
421
|
if fs_value is None:
|
|
422
422
|
raise ValueError("FLAMO delay module exposes no sample rate; cannot extract fs")
|
|
423
423
|
|
|
424
|
-
from pyFDN.
|
|
424
|
+
from pyFDN.build import FDNBuild
|
|
425
425
|
|
|
426
426
|
return FDNBuild(
|
|
427
427
|
A=A,
|
|
@@ -1,24 +1,48 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Baked feedback-delay-network builds and their JSON representation."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from
|
|
6
|
+
from dataclasses import dataclass
|
|
7
7
|
from os import PathLike
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
|
|
13
|
-
from .generate.fdn_matrix_gallery import FDNBuild
|
|
14
|
-
|
|
15
13
|
FDN_BUILD_FORMAT = "pyfdn-fdn-build"
|
|
16
|
-
#: Current schema version. The three filter hooks are spelled ``post_delay``,
|
|
17
|
-
#: ``post_matrix`` and ``post_output``, the names they carry everywhere else in
|
|
18
|
-
#: pyFDN.
|
|
19
14
|
FDN_BUILD_VERSION = 2
|
|
20
15
|
|
|
21
16
|
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class FDNBuild:
|
|
19
|
+
"""Complete, renderable parameters of a vanilla FDN.
|
|
20
|
+
|
|
21
|
+
Every field is a plain NumPy value consumed by :func:`pyFDN.process_fdn`
|
|
22
|
+
and :func:`pyFDN.build_to_impz`. A build does not remember how its numbers
|
|
23
|
+
were designed; that optional information belongs to :class:`pyFDN.FDNPreset`.
|
|
24
|
+
|
|
25
|
+
The three optional SOS banks correspond directly to pyFDN's filter hooks:
|
|
26
|
+
|
|
27
|
+
* ``post_delay`` has shape ``(sections, 6, N)`` and sets the decay inside
|
|
28
|
+
the loop after the delays.
|
|
29
|
+
* ``post_matrix`` has shape ``(sections, 6, N)`` and filters the feedback
|
|
30
|
+
path after the feedback matrix.
|
|
31
|
+
* ``post_output`` has shape ``(sections, 6, outputs)`` and filters the wet
|
|
32
|
+
signal outside the recursion.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
A: np.ndarray
|
|
36
|
+
B: np.ndarray
|
|
37
|
+
C: np.ndarray
|
|
38
|
+
D: np.ndarray
|
|
39
|
+
delays: np.ndarray
|
|
40
|
+
fs: float
|
|
41
|
+
post_delay: np.ndarray | None = None
|
|
42
|
+
post_matrix: np.ndarray | None = None
|
|
43
|
+
post_output: np.ndarray | None = None
|
|
44
|
+
|
|
45
|
+
|
|
22
46
|
def _optional_array(value: Any, name: str) -> np.ndarray | None:
|
|
23
47
|
if value is None:
|
|
24
48
|
return None
|
|
@@ -29,10 +53,9 @@ def _optional_array(value: Any, name: str) -> np.ndarray | None:
|
|
|
29
53
|
|
|
30
54
|
|
|
31
55
|
def fdn_build_to_dict(
|
|
32
|
-
build: FDNBuild, *, metadata:
|
|
56
|
+
build: FDNBuild, *, metadata: dict[str, Any] | None = None
|
|
33
57
|
) -> dict[str, Any]:
|
|
34
|
-
"""Convert an :class:`FDNBuild` to
|
|
35
|
-
|
|
58
|
+
"""Convert an :class:`FDNBuild` to its JSON-compatible format."""
|
|
36
59
|
data: dict[str, Any] = {
|
|
37
60
|
"format": FDN_BUILD_FORMAT,
|
|
38
61
|
"version": FDN_BUILD_VERSION,
|
|
@@ -58,21 +81,16 @@ def fdn_build_to_dict(
|
|
|
58
81
|
}
|
|
59
82
|
if metadata:
|
|
60
83
|
data["metadata"] = dict(metadata)
|
|
61
|
-
# Validate shape, values, and schema before exposing or writing the data.
|
|
62
84
|
fdn_build_from_dict(data)
|
|
63
85
|
return data
|
|
64
86
|
|
|
65
87
|
|
|
66
|
-
def fdn_build_from_dict(
|
|
67
|
-
|
|
68
|
-
) -> FDNBuild:
|
|
69
|
-
"""Construct an :class:`FDNBuild` from its versioned dictionary format.
|
|
88
|
+
def fdn_build_from_dict(data: dict[str, Any], *, fs: float | None = None) -> FDNBuild:
|
|
89
|
+
"""Construct an :class:`FDNBuild` from its JSON-compatible dictionary.
|
|
70
90
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
fs: Optional sample-rate override in Hz.
|
|
91
|
+
``fs`` is an optional override retained for loading standalone legacy build
|
|
92
|
+
files. Preset loading always uses the sample rate stored in the build.
|
|
74
93
|
"""
|
|
75
|
-
|
|
76
94
|
if data.get("format") != FDN_BUILD_FORMAT:
|
|
77
95
|
raise ValueError(f"Expected format '{FDN_BUILD_FORMAT}'")
|
|
78
96
|
version = data.get("version")
|
|
@@ -123,8 +141,6 @@ def fdn_build_from_dict(
|
|
|
123
141
|
post_delay = _optional_array(data.get("post_delay"), "post_delay")
|
|
124
142
|
post_matrix = _optional_array(data.get("post_matrix"), "post_matrix")
|
|
125
143
|
post_output = _optional_array(data.get("post_output"), "post_output")
|
|
126
|
-
# The two in-loop hooks run on the delay lines; the output hook on the
|
|
127
|
-
# output channels.
|
|
128
144
|
for name, hook in (("post_delay", post_delay), ("post_matrix", post_matrix)):
|
|
129
145
|
if hook is not None and (
|
|
130
146
|
hook.ndim != 3 or hook.shape[1] != 6 or hook.shape[2] != num_delays
|
|
@@ -154,10 +170,9 @@ def save_fdn_build(
|
|
|
154
170
|
path: str | PathLike[str],
|
|
155
171
|
build: FDNBuild,
|
|
156
172
|
*,
|
|
157
|
-
metadata:
|
|
173
|
+
metadata: dict[str, Any] | None = None,
|
|
158
174
|
) -> None:
|
|
159
175
|
"""Write an :class:`FDNBuild` as indented, human-readable JSON."""
|
|
160
|
-
|
|
161
176
|
Path(path).write_text(
|
|
162
177
|
json.dumps(
|
|
163
178
|
fdn_build_to_dict(build, metadata=metadata), indent=2, allow_nan=False
|
|
@@ -168,9 +183,19 @@ def save_fdn_build(
|
|
|
168
183
|
|
|
169
184
|
|
|
170
185
|
def load_fdn_build(path: str | PathLike[str], *, fs: float | None = None) -> FDNBuild:
|
|
171
|
-
"""Load an :class:`FDNBuild` from
|
|
172
|
-
|
|
186
|
+
"""Load an :class:`FDNBuild` from its JSON format."""
|
|
173
187
|
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
174
188
|
if not isinstance(data, dict):
|
|
175
189
|
raise ValueError("FDN build JSON must contain an object")
|
|
176
190
|
return fdn_build_from_dict(data, fs=fs)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
__all__ = [
|
|
194
|
+
"FDNBuild",
|
|
195
|
+
"FDN_BUILD_FORMAT",
|
|
196
|
+
"FDN_BUILD_VERSION",
|
|
197
|
+
"fdn_build_from_dict",
|
|
198
|
+
"fdn_build_to_dict",
|
|
199
|
+
"load_fdn_build",
|
|
200
|
+
"save_fdn_build",
|
|
201
|
+
]
|
|
@@ -8,6 +8,7 @@ from .biquads import (
|
|
|
8
8
|
peaking_biquad,
|
|
9
9
|
)
|
|
10
10
|
from .design import (
|
|
11
|
+
EQ_DESIGNS,
|
|
11
12
|
EQDesign,
|
|
12
13
|
decay_to_first_order_shelf,
|
|
13
14
|
decay_to_geq,
|
|
@@ -29,6 +30,7 @@ __all__ = [
|
|
|
29
30
|
"BANDWIDTH_R",
|
|
30
31
|
"CENTER_FREQUENCIES",
|
|
31
32
|
"EQDesign",
|
|
33
|
+
"EQ_DESIGNS",
|
|
32
34
|
"SHELVING_CROSSOVER",
|
|
33
35
|
"decay_to_first_order_shelf",
|
|
34
36
|
"decay_to_geq",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Small helpers for optional JSON-compatible EQ design records."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
DesignRecord = dict[str, Any]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def design_value(value: Any) -> Any:
|
|
13
|
+
"""Detach an array-like design target and convert it to JSON values."""
|
|
14
|
+
if hasattr(value, "detach"):
|
|
15
|
+
value = value.detach().cpu().numpy()
|
|
16
|
+
array = np.asarray(value)
|
|
17
|
+
return array.item() if array.ndim == 0 else array.tolist()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def with_design(value: Any, design: DesignRecord, return_design: bool) -> Any:
|
|
21
|
+
"""Return a coefficient value alone or paired with its design record."""
|
|
22
|
+
return (value, design) if return_design else value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["DesignRecord", "design_value", "with_design"]
|