pyFDN 0.3.0__tar.gz → 0.4.1__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.3.0 → pyfdn-0.4.1}/CONTRIBUTING.rst +4 -4
- pyfdn-0.4.1/HISTORY.rst +101 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/PKG-INFO +4 -4
- {pyfdn-0.3.0 → pyfdn-0.4.1}/README.rst +4 -4
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/api_reference.rst +78 -12
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/conf.py +42 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/example_gallery.py +7 -4
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/index.rst +1 -0
- pyfdn-0.4.1/docs/training_losses.rst +99 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/usage.rst +4 -2
- {pyfdn-0.3.0 → pyfdn-0.4.1}/pyproject.toml +2 -2
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/__init__.py +107 -26
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/__init__.py +2 -2
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/acoustics.py +3 -211
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/coupled_rooms.py +5 -5
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/flamo.py +178 -60
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/flamo_graph.py +59 -24
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/marimo_utils.py +25 -2
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/plot.py +43 -30
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/utils.py +1 -1
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/build_io.py +45 -20
- pyfdn-0.4.1/src/pyFDN/eq/__init__.py +47 -0
- pyfdn-0.4.1/src/pyFDN/eq/_backend.py +34 -0
- pyfdn-0.4.1/src/pyFDN/eq/biquads.py +120 -0
- pyfdn-0.4.1/src/pyFDN/eq/design.py +176 -0
- pyfdn-0.4.1/src/pyFDN/eq/graphic_eq.py +187 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/SDN.py +15 -15
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/fdn_matrix_gallery.py +70 -31
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/process.py +1 -1
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d1.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d2.json +4 -3
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/td/operators.py +2 -2
- pyfdn-0.4.1/src/pyFDN/train/__init__.py +88 -0
- pyfdn-0.4.1/src/pyFDN/train/build.py +463 -0
- pyfdn-0.4.1/src/pyFDN/train/engine.py +207 -0
- pyfdn-0.4.1/src/pyFDN/train/filters.py +249 -0
- pyfdn-0.4.1/src/pyFDN/train/losses/__init__.py +46 -0
- pyfdn-0.4.1/src/pyFDN/train/losses/_targets.py +63 -0
- pyfdn-0.4.1/src/pyFDN/train/losses/base.py +151 -0
- pyfdn-0.4.1/src/pyFDN/train/losses/parameter.py +66 -0
- pyfdn-0.4.1/src/pyFDN/train/losses/spectral.py +356 -0
- pyfdn-0.4.1/src/pyFDN/train/losses/temporal.py +347 -0
- pyfdn-0.4.1/src/pyFDN/train/params.py +152 -0
- pyfdn-0.4.1/src/pyFDN/train/response.py +148 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/dss_to_flamo.py +59 -45
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/dss_to_impz.py +17 -21
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/dss_to_pr.py +2 -2
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/impz_to_res.py +1 -1
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN.egg-info/PKG-INFO +4 -4
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN.egg-info/SOURCES.txt +17 -9
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_api_reference.py +26 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_auxiliary_acoustics.py +0 -61
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_auxiliary_flamo.py +7 -14
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_auxiliary_plot.py +35 -14
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_auxiliary_utils.py +45 -11
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_build_io.py +14 -4
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_dss_to_pr.py +1 -1
- pyfdn-0.4.1/tests/test_eq_designs.py +352 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_flamo_graph.py +4 -4
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_flamo_one_pole_absorption.py +2 -2
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_flamo_to_pr.py +6 -6
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_generate.py +26 -23
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_process_extensions.py +54 -13
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_td_graph_processing.py +1 -1
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_td_operators.py +1 -1
- pyfdn-0.4.1/tests/test_train.py +1448 -0
- pyfdn-0.3.0/HISTORY.rst +0 -37
- pyfdn-0.3.0/src/pyFDN/graphicEQ/__init__.py +0 -17
- pyfdn-0.3.0/src/pyFDN/graphicEQ/absorption_geq.py +0 -54
- pyfdn-0.3.0/src/pyFDN/graphicEQ/bandpass_filter.py +0 -34
- pyfdn-0.3.0/src/pyFDN/graphicEQ/design_geq.py +0 -73
- pyfdn-0.3.0/src/pyFDN/graphicEQ/graphic_eq.py +0 -74
- pyfdn-0.3.0/src/pyFDN/graphicEQ/shelving_filter.py +0 -55
- pyfdn-0.3.0/src/pyFDN/train/__init__.py +0 -35
- pyfdn-0.3.0/src/pyFDN/train/build.py +0 -321
- pyfdn-0.3.0/src/pyFDN/train/engine.py +0 -182
- pyfdn-0.3.0/src/pyFDN/train/objectives.py +0 -207
- pyfdn-0.3.0/tests/test_graphic_eq.py +0 -129
- pyfdn-0.3.0/tests/test_train.py +0 -269
- {pyfdn-0.3.0 → pyfdn-0.4.1}/AUTHORS.rst +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/LICENSE +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/MANIFEST.in +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/Makefile +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/__init__.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/audio_gallery.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/authors.rst +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/contributing.rst +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/history.rst +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/installation.rst +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/logo/logo_pyFDN_3.png +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/logo/logo_pyFDN_4.png +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/logo/pyfdn_logo_basic.png +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/logo/pyfdn_logo_wave.png +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/make.bat +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/patch_rst.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/readme.rst +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/docs/references.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/setup.cfg +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/setup.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/allpass.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/audio.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/delay.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/math.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/poles.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/auxiliary/tiny_rotation_matrix.py +0 -0
- {pyfdn-0.3.0/src/pyFDN/graphicEQ → pyfdn-0.4.1/src/pyFDN/eq}/probe_sos.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/allpass_FDN/__init__.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/allpass_FDN/allpass_completion.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/allpass_FDN/homogeneous_allpass_fdn.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/allpass_FDN/rand_admissible_homogeneous_allpass.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/allpass_in_fdn.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/anderson_matrix.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/complete_orthogonal.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/construct_cascaded_paraunitary_matrix.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/construct_paraunitary_from_elementals.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/construct_velvet_feedback_matrix.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/degree_one_lossless.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/householder_matrix.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/is_almost_zero.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/nearest_orthogonal.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/nearest_sign_agnostic_orthogonal.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/random_matrix_shift.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/random_orthogonal.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/sample_delay_lengths.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/schroeder_reverberator.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/shift_matrix.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/generate/shift_matrix_distribute.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/presets.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/references.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/ATTRIBUTION.md +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/__init__.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/drums/drums.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/general/synth_dry.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/metadata.json +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/rir/s3_r4_o.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/speech/P1_Set1_8.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/speech/P20_Set1_8.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/strings/Kleiderschrank_Taylor_DI.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/audio/strings/Sommerhit2016_Taylor_DI.wav +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/licenses/diff-fdn-colorless-MIT.txt +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/resources/references.bib +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/td/__init__.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/td/connectors.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/dss_to_ss.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/dss_to_tf.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/flamo_to_pr.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/mtf_to_impz.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN/translate/pr_to_impz.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN.egg-info/dependency_links.txt +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN.egg-info/requires.txt +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/src/pyFDN.egg-info/top_level.txt +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/__init__.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/conftest.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/matloader.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/reference/README.md +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/reference/example_onePoleAbsorption.mat +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/reference/onePoleAbsorption_reference.mat +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_audio_gallery.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_auxiliary_delay.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_auxiliary_math.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_coupled_rooms.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_example_gallery.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_marimo_examples_run.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_packaged_resources.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_pyFDN.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_sdn.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_td_connectors.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_time_varying_matrix.py +0 -0
- {pyfdn-0.3.0 → pyfdn-0.4.1}/tests/test_translate.py +0 -0
|
@@ -150,11 +150,11 @@ documentation sources, and tests:
|
|
|
150
150
|
time-domain graph. ``process_fdn`` uses ``MatrixFIR`` for polynomial feedback
|
|
151
151
|
matrices; ``SOSBank`` implements per-delay-line filter cascades used for
|
|
152
152
|
frequency-dependent absorption.
|
|
153
|
-
* ``src/pyFDN/
|
|
154
|
-
|
|
153
|
+
* ``src/pyFDN/eq/`` contains every EQ and absorption-filter design, while
|
|
154
|
+
``src/pyFDN/translate/`` converts between delay state-space, transfer
|
|
155
155
|
function, pole-residue, impulse-response, and FLAMO representations.
|
|
156
|
-
* ``src/pyFDN/train/`` contains the trainable FDN construction,
|
|
157
|
-
and optimization loop. ``src/pyFDN/auxiliary/`` holds shared analysis,
|
|
156
|
+
* ``src/pyFDN/train/`` contains the trainable FDN construction, the composable
|
|
157
|
+
losses in ``train/losses/``, and the optimization loop. ``src/pyFDN/auxiliary/`` holds shared analysis,
|
|
158
158
|
plotting, audio, delay, and numerical helpers.
|
|
159
159
|
* ``examples/`` contains executable Marimo notebooks. Metadata at the top of
|
|
160
160
|
each ``example_*.py`` file drives the generated documentation gallery.
|
pyfdn-0.4.1/HISTORY.rst
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
=======
|
|
2
|
+
History
|
|
3
|
+
=======
|
|
4
|
+
|
|
5
|
+
0.4.1 (2026-08-24)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
* **Breaking:** rename the ``Fs`` parameter to ``fs`` everywhere it is still
|
|
9
|
+
spelled with a capital -- ``dss_to_flamo``, ``dss_to_pr``, ``delay_module``
|
|
10
|
+
and ``SDN``. Every other sampling-rate argument in the package was already
|
|
11
|
+
``fs``, and the odd one out forced callers to remember which spelling each
|
|
12
|
+
function wanted. ``SDN`` also renames its ``fs`` attribute and the ``"fs"``
|
|
13
|
+
key of the dictionary ``SDN.compute()`` returns.
|
|
14
|
+
|
|
15
|
+
0.4.0 (2026-08-23)
|
|
16
|
+
------------------
|
|
17
|
+
|
|
18
|
+
* **Breaking:** replace ``train_fdn``'s ``mode`` string, and the ``target``,
|
|
19
|
+
``criteria``, ``sparsity_alpha`` and ``mss_nfft`` arguments that went with
|
|
20
|
+
it, with a composed loss object. An objective is now written out --
|
|
21
|
+
``FlatMagnitude() + 0.2 * Sparsity(param(model, "feedback"))`` -- and every
|
|
22
|
+
loss is a function of the impulse response, carried as a ``Response``, so
|
|
23
|
+
losses own their reference data and one objective can fit two different
|
|
24
|
+
references. ``pyFDN.train.objectives`` and ``build_objective`` are gone.
|
|
25
|
+
* **Breaking:** collect every EQ design in ``pyFDN.eq`` (formerly
|
|
26
|
+
``pyFDN.graphicEQ``) behind one ``EQDesign`` interface, with ``GraphicEQ``,
|
|
27
|
+
``FirstOrderShelf`` and ``OnePole`` sharing a single implementation across
|
|
28
|
+
the numpy and torch backends.
|
|
29
|
+
* **Breaking:** remove ``absorption_filters`` and ``absorption_to_rt``; the FIR
|
|
30
|
+
absorption path they served has no callers left.
|
|
31
|
+
* **Breaking:** name the three filter hooks ``post_delay``, ``post_matrix`` and
|
|
32
|
+
``post_output`` everywhere -- on ``FDNBuild``, in the FLAMO graph, in the
|
|
33
|
+
plots and in the JSON schema -- and add the slot for the third. The build
|
|
34
|
+
schema is version 2; version 1 files no longer load.
|
|
35
|
+
* Fix colorless training on a lossless FDN, whose poles sit on the unit circle
|
|
36
|
+
and leave ``|H|`` unbounded. ``build_fdn`` now defaults ``alias_decay_db``
|
|
37
|
+
from ``rt`` (``LOSSLESS_ALIAS_DECAY_DB`` when ``rt`` is None) and
|
|
38
|
+
``trainable_from_build`` threads it into every module, so a magnitude
|
|
39
|
+
objective sees a bounded response.
|
|
40
|
+
* Train the decay: ``DecayFilter`` parametrizes the in-loop absorption filter
|
|
41
|
+
by reverberation time per band, so the loop stays contractive for every value
|
|
42
|
+
the parameter can take, and takes either one RT curve for the network or one
|
|
43
|
+
per delay line. ``OutputEQ`` trains the output filter outside the recursion,
|
|
44
|
+
the only part of an FDN that shapes the spectral envelope without touching
|
|
45
|
+
the decay. Both are passed as the ``post_delay`` and ``post_output`` hooks of
|
|
46
|
+
``trainable_from_build``.
|
|
47
|
+
* Add the losses that go with it: ``AsymmetricFlatMagnitude``,
|
|
48
|
+
``FlatSpectrogram``, ``MatchEnergyDecay``, ``MatchCumulativeEnergy``,
|
|
49
|
+
``MatchMagnitude``, ``MatchSpectrogram``, ``MatchMelSpectrogram``,
|
|
50
|
+
``MatchImpulseResponse``, ``Energy``, ``Sparsity``, ``L1`` and ``L2``.
|
|
51
|
+
* Resolve trainable parameters by name: ``param(model, "feedback")`` fails
|
|
52
|
+
where you write it with the list of available names, and ``params(model)``
|
|
53
|
+
enumerates them.
|
|
54
|
+
* Export the FLAMO graph builders (``assemble_fdn_core``, ``wrap_fdn_shell``,
|
|
55
|
+
``gain_module``, ``delay_module``, ``matrix_module``, ``fir_matrix_module``,
|
|
56
|
+
``sos_filter_module``, ``hook_module``, ``DecayFilter``, ``OutputEQ``) from
|
|
57
|
+
the top-level namespace.
|
|
58
|
+
* Make ``build_to_impz`` apply all three hooks, so it no longer rejects builds
|
|
59
|
+
that carry an output EQ, and make ``extract_build`` refuse a hook it cannot
|
|
60
|
+
bake rather than silently dropping it.
|
|
61
|
+
* Add an example notebook that fits every parameter of an FDN, decay included,
|
|
62
|
+
to a measured concert-hall RIR, and document the loss rationale and
|
|
63
|
+
measurements in ``docs/training_losses.rst``.
|
|
64
|
+
* Fix stereo orientation in ``labeled_audio``, which decoded a stereo render as
|
|
65
|
+
thousands of channels.
|
|
66
|
+
* Check packaging metadata in CI and fail the documentation build on notebook
|
|
67
|
+
cell errors.
|
|
68
|
+
|
|
69
|
+
0.3.0 (2026-08-17)
|
|
70
|
+
------------------
|
|
71
|
+
|
|
72
|
+
* **Breaking:** replace the ``pyFDN.dsp`` subpackage with ``pyFDN.td``. The
|
|
73
|
+
top-level ``FeedbackDelay``, ``FIRMatrixFilter`` and ``SOSFilterBank``
|
|
74
|
+
exports are gone; use ``pyFDN.td`` operators instead.
|
|
75
|
+
* **Breaking:** ship the packaged colorless-FDN presets as JSON rather than
|
|
76
|
+
MATLAB ``.mat`` files.
|
|
77
|
+
* Add ``pyFDN.td``, a time-domain block-based processing graph built on NumPy
|
|
78
|
+
alone (no torch, no FFT), with the ``Series``, ``Parallel`` and
|
|
79
|
+
``Recursion`` connectors and stateful ``Gain``, ``Delay``, ``SOSBank``,
|
|
80
|
+
``MatrixFIR``, ``MatrixConvolver`` and ``TimeVaryingMatrix`` operators.
|
|
81
|
+
* Add ``load_fdn_build``, ``save_fdn_build``, ``fdn_build_from_dict`` and
|
|
82
|
+
``fdn_build_to_dict`` for reading and writing FDN builds as JSON.
|
|
83
|
+
* Add an FDN-to-FAUST example that compiles a pyFDN design with adac, and a
|
|
84
|
+
reverberation-enhancement example.
|
|
85
|
+
* Improve the example gallery and stop tracking generated documentation in the
|
|
86
|
+
repository.
|
|
87
|
+
* Test on Python 3.14 in CI and the documentation build.
|
|
88
|
+
|
|
89
|
+
0.2.0 (2026-08-10)
|
|
90
|
+
------------------
|
|
91
|
+
|
|
92
|
+
* Package compact audio examples and predefined colorless-FDN coefficients so
|
|
93
|
+
browser-hosted tutorials work without repository-relative files.
|
|
94
|
+
* Add public APIs for packaged audio, FDN presets, and paper references.
|
|
95
|
+
* Add generated documentation galleries and wheel-content checks.
|
|
96
|
+
* Keep decay-estimation dependencies optional for a smaller core installation.
|
|
97
|
+
|
|
98
|
+
0.1.0 (2025-07-02)
|
|
99
|
+
------------------
|
|
100
|
+
|
|
101
|
+
* First release on PyPI.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyFDN
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -148,17 +148,17 @@ All main functions are accessible directly from the top-level ``pyFDN`` namespac
|
|
|
148
148
|
feedback = pyFDN.random_orthogonal(len(delays))
|
|
149
149
|
|
|
150
150
|
# one-pole absorption filters targeting RT of 1.2 s at DC and 0.9 s at Nyquist
|
|
151
|
-
absorption = pyFDN.
|
|
151
|
+
absorption = pyFDN.decay_to_one_pole(1.2, 0.9, delays, fs)
|
|
152
152
|
|
|
153
153
|
# convert delay state-space to standard state-space (A_ss, b, c, d)
|
|
154
154
|
A_ss, b, c, d = pyFDN.dss_to_ss(delays, feedback)
|
|
155
155
|
|
|
156
156
|
Alternatively, import specific functions directly::
|
|
157
157
|
|
|
158
|
-
from pyFDN import
|
|
158
|
+
from pyFDN import decay_to_one_pole, lin_to_db, random_orthogonal
|
|
159
159
|
|
|
160
160
|
feedback = random_orthogonal(4)
|
|
161
|
-
absorption =
|
|
161
|
+
absorption = decay_to_one_pole(1.2, 0.9, [100, 150, 200, 250], 48_000)
|
|
162
162
|
|
|
163
163
|
|
|
164
164
|
Development
|
|
@@ -78,17 +78,17 @@ All main functions are accessible directly from the top-level ``pyFDN`` namespac
|
|
|
78
78
|
feedback = pyFDN.random_orthogonal(len(delays))
|
|
79
79
|
|
|
80
80
|
# one-pole absorption filters targeting RT of 1.2 s at DC and 0.9 s at Nyquist
|
|
81
|
-
absorption = pyFDN.
|
|
81
|
+
absorption = pyFDN.decay_to_one_pole(1.2, 0.9, delays, fs)
|
|
82
82
|
|
|
83
83
|
# convert delay state-space to standard state-space (A_ss, b, c, d)
|
|
84
84
|
A_ss, b, c, d = pyFDN.dss_to_ss(delays, feedback)
|
|
85
85
|
|
|
86
86
|
Alternatively, import specific functions directly::
|
|
87
87
|
|
|
88
|
-
from pyFDN import
|
|
88
|
+
from pyFDN import decay_to_one_pole, lin_to_db, random_orthogonal
|
|
89
89
|
|
|
90
90
|
feedback = random_orthogonal(4)
|
|
91
|
-
absorption =
|
|
91
|
+
absorption = decay_to_one_pole(1.2, 0.9, [100, 150, 200, 250], 48_000)
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
Development
|
|
@@ -112,4 +112,4 @@ Helpful links
|
|
|
112
112
|
* `List of papers on Feedback Delay Networks <https://github.com/gdalsanto/delay-network-reverbs>`_ .
|
|
113
113
|
* `Multislope Estimation library <https://github.com/artificial-audio/multislope>`_ .
|
|
114
114
|
* `FLAMO (An Open-Source Library for Frequency-Domain Differentiable Audio Processing) <https://github.com/gdalsanto/flamo>`_ .
|
|
115
|
-
* `ADAC (Automatic Differentiable Audio Compilation) library <https://github.com/cucuwritescode/adac>`_ .
|
|
115
|
+
* `ADAC (Automatic Differentiable Audio Compilation) library <https://github.com/cucuwritescode/adac>`_ .
|
|
@@ -83,14 +83,9 @@ Acoustics & Absorption
|
|
|
83
83
|
:toctree: generated/
|
|
84
84
|
:nosignatures:
|
|
85
85
|
|
|
86
|
-
pyFDN.absorption_filters
|
|
87
|
-
pyFDN.first_order_absorption
|
|
88
|
-
pyFDN.first_order_shelving_eq
|
|
89
|
-
pyFDN.one_pole_absorption
|
|
90
86
|
pyFDN.sos_gain_per_sample_curves
|
|
91
87
|
pyFDN.echo_density
|
|
92
88
|
pyFDN.edc
|
|
93
|
-
pyFDN.absorption_to_rt
|
|
94
89
|
pyFDN.estimate_initial_level_bands
|
|
95
90
|
pyFDN.estimate_rt_bands
|
|
96
91
|
pyFDN.octave_bands
|
|
@@ -100,19 +95,35 @@ Acoustics & Absorption
|
|
|
100
95
|
pyFDN.slope_amplitude_to_level
|
|
101
96
|
pyFDN.slope_to_rt
|
|
102
97
|
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
EQ Design (``pyFDN.eq``)
|
|
99
|
+
-------------------------
|
|
100
|
+
|
|
101
|
+
Explicit functions map either decay targets or gain targets onto a named
|
|
102
|
+
filter design. The same functions run in NumPy or Torch; the trainable
|
|
103
|
+
:class:`pyFDN.DecayFilter` and :class:`pyFDN.OutputEQ` modules use these
|
|
104
|
+
mappings inside a training loop. ``EQDesign`` is the literal choice of
|
|
105
|
+
``"graphic_eq"``, ``"first_order_shelf"``, or ``"one_pole"`` used by those
|
|
106
|
+
modules.
|
|
105
107
|
|
|
106
108
|
.. autosummary::
|
|
107
109
|
:toctree: generated/
|
|
108
110
|
:nosignatures:
|
|
109
111
|
|
|
110
|
-
pyFDN.
|
|
111
|
-
pyFDN.
|
|
112
|
-
pyFDN.
|
|
112
|
+
pyFDN.EQDesign
|
|
113
|
+
pyFDN.decay_to_geq
|
|
114
|
+
pyFDN.decay_to_first_order_shelf
|
|
115
|
+
pyFDN.decay_to_one_pole
|
|
116
|
+
pyFDN.gain_to_geq
|
|
117
|
+
pyFDN.gain_to_bounded_geq
|
|
118
|
+
pyFDN.gain_to_first_order_shelf
|
|
119
|
+
pyFDN.gain_to_one_pole
|
|
120
|
+
pyFDN.geq_design_matrix
|
|
121
|
+
pyFDN.lowshelf_biquad
|
|
122
|
+
pyFDN.highshelf_biquad
|
|
123
|
+
pyFDN.peaking_biquad
|
|
124
|
+
pyFDN.first_order_shelf_biquad
|
|
125
|
+
pyFDN.one_pole_biquad
|
|
113
126
|
pyFDN.probe_sos
|
|
114
|
-
pyFDN.shelving_filter
|
|
115
|
-
pyFDN.bandpass_filter
|
|
116
127
|
|
|
117
128
|
Time-Domain Graph (``pyFDN.td``)
|
|
118
129
|
--------------------------------
|
|
@@ -151,6 +162,29 @@ Delay Utilities
|
|
|
151
162
|
pyFDN.flamo_time_response
|
|
152
163
|
pyFDN.flamo_freq_response
|
|
153
164
|
|
|
165
|
+
Building a FLAMO Graph
|
|
166
|
+
----------------------
|
|
167
|
+
|
|
168
|
+
An FDN as FLAMO modules, assembled from numpy values. The three filter hooks --
|
|
169
|
+
``post_delay`` inside the loop, ``post_matrix`` on the feedback path,
|
|
170
|
+
``post_output`` on the wet signal -- are the same three
|
|
171
|
+
:func:`pyFDN.process_fdn` takes, in the same positions and under the same names.
|
|
172
|
+
|
|
173
|
+
.. autosummary::
|
|
174
|
+
:toctree: generated/
|
|
175
|
+
:nosignatures:
|
|
176
|
+
|
|
177
|
+
pyFDN.assemble_fdn_core
|
|
178
|
+
pyFDN.wrap_fdn_shell
|
|
179
|
+
pyFDN.gain_module
|
|
180
|
+
pyFDN.delay_module
|
|
181
|
+
pyFDN.matrix_module
|
|
182
|
+
pyFDN.fir_matrix_module
|
|
183
|
+
pyFDN.sos_filter_module
|
|
184
|
+
pyFDN.hook_module
|
|
185
|
+
pyFDN.DecayFilter
|
|
186
|
+
pyFDN.OutputEQ
|
|
187
|
+
|
|
154
188
|
Polynomial & Matrix Maths
|
|
155
189
|
--------------------------
|
|
156
190
|
|
|
@@ -265,6 +299,38 @@ Training
|
|
|
265
299
|
pyFDN.Trainable
|
|
266
300
|
pyFDN.train_fdn
|
|
267
301
|
pyFDN.TrainLog
|
|
302
|
+
pyFDN.LOSSLESS_ALIAS_DECAY_DB
|
|
303
|
+
|
|
304
|
+
Training Objectives
|
|
305
|
+
-------------------
|
|
306
|
+
|
|
307
|
+
An objective is a weighted sum of losses, composed with ``+`` and ``*``. Losses
|
|
308
|
+
on the impulse response read a :class:`pyFDN.Response`; losses on a model
|
|
309
|
+
parameter take a :class:`pyFDN.ParamRef` from :func:`pyFDN.param`.
|
|
310
|
+
|
|
311
|
+
.. autosummary::
|
|
312
|
+
:toctree: generated/
|
|
313
|
+
:nosignatures:
|
|
314
|
+
|
|
315
|
+
pyFDN.Response
|
|
316
|
+
pyFDN.model_response
|
|
317
|
+
pyFDN.param
|
|
318
|
+
pyFDN.params
|
|
319
|
+
pyFDN.ParamRef
|
|
320
|
+
pyFDN.Loss
|
|
321
|
+
pyFDN.FlatMagnitude
|
|
322
|
+
pyFDN.AsymmetricFlatMagnitude
|
|
323
|
+
pyFDN.FlatSpectrogram
|
|
324
|
+
pyFDN.MatchMagnitude
|
|
325
|
+
pyFDN.MatchSpectrogram
|
|
326
|
+
pyFDN.MatchMelSpectrogram
|
|
327
|
+
pyFDN.MatchImpulseResponse
|
|
328
|
+
pyFDN.MatchEnergyDecay
|
|
329
|
+
pyFDN.MatchCumulativeEnergy
|
|
330
|
+
pyFDN.Energy
|
|
331
|
+
pyFDN.Sparsity
|
|
332
|
+
pyFDN.L1
|
|
333
|
+
pyFDN.L2
|
|
268
334
|
|
|
269
335
|
Plotting
|
|
270
336
|
--------
|
|
@@ -148,6 +148,17 @@ marimo_cache_notebooks = False
|
|
|
148
148
|
#
|
|
149
149
|
# This patch lives in conf.py (our repo), so the installed sphinx-marimo package
|
|
150
150
|
# stays pristine and deployment (RTD / GitHub Actions) needs no vendored changes.
|
|
151
|
+
#
|
|
152
|
+
# Cell errors are build failures: a notebook that raises still produces HTML,
|
|
153
|
+
# with the traceback baked in where the output should be, so a broken example
|
|
154
|
+
# reaches the published site looking like a rendered page. We record every
|
|
155
|
+
# notebook whose export exited non-zero and fail the build at the end (see
|
|
156
|
+
# ``setup`` below) instead of aborting on the first one, so a single run reports
|
|
157
|
+
# all of them. Set ``PYFDN_DOCS_ALLOW_NOTEBOOK_ERRORS=1`` to downgrade this back
|
|
158
|
+
# to a warning when you need a build out of a knowingly broken notebook.
|
|
159
|
+
_marimo_export_failures: "list[tuple[str, str]]" = []
|
|
160
|
+
|
|
161
|
+
|
|
151
162
|
def _patch_marimo_static_export():
|
|
152
163
|
import subprocess
|
|
153
164
|
|
|
@@ -173,8 +184,8 @@ def _patch_marimo_static_export():
|
|
|
173
184
|
# `html` (not `html-wasm`) executes the notebook server-side with the real
|
|
174
185
|
# venv and embeds the outputs. marimo returns a non-zero exit code when any
|
|
175
186
|
# cell raises during execution, but it still writes the HTML (with the error
|
|
176
|
-
# baked in), so we
|
|
177
|
-
#
|
|
187
|
+
# baked in), so we record the failure and keep going; the build-finished
|
|
188
|
+
# handler turns the collected failures into a build error.
|
|
178
189
|
proc = subprocess.run(
|
|
179
190
|
[
|
|
180
191
|
"marimo",
|
|
@@ -190,9 +201,10 @@ def _patch_marimo_static_export():
|
|
|
190
201
|
)
|
|
191
202
|
if proc.returncode != 0:
|
|
192
203
|
print(
|
|
193
|
-
f"[conf.py]
|
|
204
|
+
f"[conf.py] ERROR: '{relative_path}' had cell errors during static "
|
|
194
205
|
f"export (output still written):\n{proc.stderr.strip()}"
|
|
195
206
|
)
|
|
207
|
+
_marimo_export_failures.append((str(relative_path), proc.stderr.strip()))
|
|
196
208
|
|
|
197
209
|
return {
|
|
198
210
|
"name": output_name,
|
|
@@ -205,3 +217,30 @@ def _patch_marimo_static_export():
|
|
|
205
217
|
|
|
206
218
|
|
|
207
219
|
_patch_marimo_static_export()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _fail_on_marimo_export_errors(app, exception):
|
|
223
|
+
"""Fail the build if any notebook raised while being exported."""
|
|
224
|
+
# The build already failed for another reason; don't mask it.
|
|
225
|
+
if exception is not None or not _marimo_export_failures:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
report = "\n\n".join(
|
|
229
|
+
f"{path}:\n{stderr}" for path, stderr in _marimo_export_failures
|
|
230
|
+
)
|
|
231
|
+
if os.environ.get("PYFDN_DOCS_ALLOW_NOTEBOOK_ERRORS") == "1":
|
|
232
|
+
print(
|
|
233
|
+
f"[conf.py] WARNING: {len(_marimo_export_failures)} notebook(s) had cell "
|
|
234
|
+
f"errors; failing is disabled via PYFDN_DOCS_ALLOW_NOTEBOOK_ERRORS:\n{report}"
|
|
235
|
+
)
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
raise RuntimeError(
|
|
239
|
+
f"{len(_marimo_export_failures)} example notebook(s) raised during static "
|
|
240
|
+
f"export. The generated HTML contains the traceback instead of the intended "
|
|
241
|
+
f"output, so this must not be published:\n\n{report}"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def setup(app):
|
|
246
|
+
app.connect("build-finished", _fail_on_marimo_export_errors)
|
|
@@ -15,11 +15,14 @@ OUTPUT_FILE = DOCS_DIR / "examples_gallery.rst"
|
|
|
15
15
|
|
|
16
16
|
CATEGORY_ORDER = (
|
|
17
17
|
"Getting Started",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
18
|
+
"Feedback Matrices",
|
|
19
|
+
"Absorption & Decay",
|
|
20
|
+
"Analysis & Verification",
|
|
21
|
+
"Optimization",
|
|
22
|
+
"Allpass FDNs",
|
|
21
23
|
"Special FDNs",
|
|
22
|
-
"
|
|
24
|
+
"Representations",
|
|
25
|
+
"Export & Deployment",
|
|
23
26
|
)
|
|
24
27
|
|
|
25
28
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
===============================
|
|
2
|
+
Training losses: design notes
|
|
3
|
+
===============================
|
|
4
|
+
|
|
5
|
+
Why the losses in :mod:`pyFDN.train.losses` are shaped the way they are. The
|
|
6
|
+
reasoning is qualitative on purpose: what is stated here is a design choice and
|
|
7
|
+
its rationale, not a benchmark. Behaviour is FDN- and seed-dependent, so measure
|
|
8
|
+
your own case before assuming any of it transfers.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Flatness on the training grid depends on ``nfft``
|
|
12
|
+
=================================================
|
|
13
|
+
|
|
14
|
+
:class:`~pyFDN.FlatMagnitude` measures ``|H|`` at the model's DFT bins, so its
|
|
15
|
+
frequency resolution *is* the model's ``nfft``. The truncation to ``nfft``
|
|
16
|
+
samples is a rectangular window, so the peak-to-median range of ``|H|`` grows
|
|
17
|
+
with ``nfft``, and the mean-squared error weights the tallest modes ever more
|
|
18
|
+
heavily. Loss values are therefore comparable across runs at one ``nfft`` and
|
|
19
|
+
not across different ones. A multi-resolution loss
|
|
20
|
+
(:class:`~pyFDN.FlatSpectrogram`), whose analysis windows set their own
|
|
21
|
+
resolution, is the way out.
|
|
22
|
+
|
|
23
|
+
The optimization crosses long plateaus on ``FlatMagnitude``; ``train_fdn``'s
|
|
24
|
+
default ``patience=10`` stops inside one. Raise it for a converged fit.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Asymmetric flatness: peaks over dips
|
|
28
|
+
====================================
|
|
29
|
+
|
|
30
|
+
:class:`~pyFDN.AsymmetricFlatMagnitude` raises the two sides of the deviation
|
|
31
|
+
from flat to different powers so peaks cost more than dips. The exponent, not a
|
|
32
|
+
weight, is what makes it bite: at ``peak_power=4`` a peak twice as tall costs
|
|
33
|
+
sixteen times as much. Flat stays the unique minimum at every ``peak_power``
|
|
34
|
+
(normalizing by RMS forces ``<(1 + d)^2> = 1``), and the loss is gain-invariant.
|
|
35
|
+
|
|
36
|
+
It is deliberately **not** in decibels: ``∂dB/∂|H| ∝ 1/|H|``, so in dB the
|
|
37
|
+
deepest nulls dominate the gradient however lightly weighted. In linear
|
|
38
|
+
magnitude the gradient is ``∝ p·(d⁺)^(p-1)``, largest at the tallest peaks,
|
|
39
|
+
while a dip is bounded at ``d = -1``.
|
|
40
|
+
|
|
41
|
+
The cost is steps and steadiness: that same gradient vanishes near the optimum,
|
|
42
|
+
so a higher exponent takes longer to converge and varies more from seed to seed.
|
|
43
|
+
The advantage over ``FlatMagnitude`` is not unconditional. Loss values are not
|
|
44
|
+
comparable across ``peak_power``, or with ``FlatMagnitude``; compare the
|
|
45
|
+
responses.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Multi-resolution flatness: average over frames first
|
|
49
|
+
====================================================
|
|
50
|
+
|
|
51
|
+
:class:`~pyFDN.FlatSpectrogram` averages the short-time magnitudes over frames
|
|
52
|
+
into a Welch estimate *before* measuring flatness. Asking each individual frame
|
|
53
|
+
to be flat instead is actively harmful: an isolated echo inside a short frame
|
|
54
|
+
already has a flat frame spectrum, so that objective rewards an impulsive,
|
|
55
|
+
comb-filtered IR.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Energy decay vs. spectrogram distance
|
|
59
|
+
=====================================
|
|
60
|
+
|
|
61
|
+
:class:`~pyFDN.MatchEnergyDecay` compares octave-band Schroeder curves. A
|
|
62
|
+
magnitude spectrogram distance is not a substitute for fitting a decay: it
|
|
63
|
+
compares two signals frame by frame, and two rooms with identical decay have
|
|
64
|
+
uncorrelated fine structure, so predicting *silence* scores better there than
|
|
65
|
+
predicting the right amount of the wrong detail. Fitted to a measured room, a
|
|
66
|
+
spectrogram distance is minimized by an FDN whose RT falls well short of the
|
|
67
|
+
measurement, where an energy-decay loss is not.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Doubly-cumulated energy
|
|
71
|
+
=======================
|
|
72
|
+
|
|
73
|
+
:class:`~pyFDN.MatchCumulativeEnergy` integrates the short-time power spectrum
|
|
74
|
+
twice — backwards in time and along frequency — and scores the RMS difference of
|
|
75
|
+
the two surfaces after a compressive power.
|
|
76
|
+
|
|
77
|
+
**Why cumulate twice.** The time direction is Schroeder backward integration
|
|
78
|
+
(the reason ``MatchEnergyDecay`` exists). The frequency direction replaces
|
|
79
|
+
splitting into octave bands: band edges are an arbitrary quantization a fit can
|
|
80
|
+
satisfy on average while getting the shape wrong, whereas a cumulative sum is
|
|
81
|
+
the limit of ever-finer bands and is monotone and smooth in both axes, which a
|
|
82
|
+
gradient values. Read down the ``t = 0`` edge and you have the integrated
|
|
83
|
+
spectrum (colour); read across the ``f = 0`` edge and you have the full-band
|
|
84
|
+
decay; the interior ties them together band by band.
|
|
85
|
+
|
|
86
|
+
**Compression, not decibels.** The surface spans the whole dynamic range of the
|
|
87
|
+
decay, so a plain MSE would see only the first frames. Raising the normalized
|
|
88
|
+
surface to a fractional ``power`` (0.5 default) compresses that range while
|
|
89
|
+
staying bounded, its gradient ``∝ x^(p-1)`` finite everywhere the floor allows.
|
|
90
|
+
A logarithm is worse: it turns the silence *below* the response into an
|
|
91
|
+
unbounded penalty dominated by whichever bin is nearest zero.
|
|
92
|
+
|
|
93
|
+
**Which way the frequency cumulation runs.** Cumulating downwards alone — the
|
|
94
|
+
default, and the plain reading of "energy above this frequency" — leaves the
|
|
95
|
+
bottom octave with almost no gradient, and a fit that has to *find* a decay
|
|
96
|
+
tends to abandon it there. ``frequency="both"`` scores both directions and
|
|
97
|
+
averages, which recovers the low end; it is the setting to reach for when the
|
|
98
|
+
decay is not given. This is a matter of weighting rather than frequency
|
|
99
|
+
resolution, so a longer analysis window is not the fix.
|
|
@@ -7,12 +7,14 @@ All main functions are accessible directly from ``pyFDN``::
|
|
|
7
7
|
import pyFDN
|
|
8
8
|
|
|
9
9
|
feedback = pyFDN.random_orthogonal(4)
|
|
10
|
-
|
|
10
|
+
attenuation = pyFDN.decay_to_first_order_shelf(
|
|
11
|
+
1.2, 0.9, None, [100, 150, 200, 250], 48_000
|
|
12
|
+
)
|
|
11
13
|
gain_db = pyFDN.lin_to_db([0.5, 1.0, 2.0])
|
|
12
14
|
|
|
13
15
|
Or import specific functions::
|
|
14
16
|
|
|
15
|
-
from pyFDN import random_orthogonal,
|
|
17
|
+
from pyFDN import random_orthogonal, decay_to_first_order_shelf, dss_to_ss
|
|
16
18
|
|
|
17
19
|
feedback = random_orthogonal(4)
|
|
18
20
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyFDN"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
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.
|
|
116
|
+
current_version = "0.4.1"
|
|
117
117
|
allow_dirty = false
|
|
118
118
|
commit = true
|
|
119
119
|
message = "Bump version: {current_version} → {new_version}"
|