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.
Files changed (196) hide show
  1. {pyfdn-0.4.1 → pyfdn-0.4.2}/HISTORY.rst +41 -2
  2. {pyfdn-0.4.1 → pyfdn-0.4.2}/PKG-INFO +2 -1
  3. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/api_reference.rst +16 -3
  4. pyfdn-0.4.2/docs/usage.rst +102 -0
  5. {pyfdn-0.4.1 → pyfdn-0.4.2}/pyproject.toml +2 -2
  6. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/__init__.py +22 -7
  7. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/allpass.py +26 -0
  8. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/flamo.py +1 -1
  9. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/flamo_graph.py +2 -2
  10. pyfdn-0.4.1/src/pyFDN/build_io.py → pyfdn-0.4.2/src/pyFDN/build.py +50 -25
  11. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/__init__.py +2 -0
  12. pyfdn-0.4.2/src/pyFDN/eq/_design_record.py +25 -0
  13. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/design.py +86 -27
  14. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/graphic_eq.py +8 -1
  15. pyfdn-0.4.2/src/pyFDN/generate/fdn_build_gallery.py +305 -0
  16. pyfdn-0.4.2/src/pyFDN/generate/fdn_matrix_gallery.py +341 -0
  17. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/sample_delay_lengths.py +9 -4
  18. pyfdn-0.4.2/src/pyFDN/preset.py +195 -0
  19. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/ATTRIBUTION.md +11 -2
  20. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/metadata.json +11 -0
  21. pyfdn-0.4.2/src/pyFDN/resources/audio/rir/meetingroom_to_hallway_290cm.wav +0 -0
  22. pyfdn-0.4.2/src/pyFDN/resources/licenses/coupled-rooms-CC-BY-4.0.txt +413 -0
  23. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d1.json +412 -0
  24. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d2.json +412 -0
  25. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d1.json +88 -0
  26. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d2.json +88 -0
  27. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d1.json +122 -0
  28. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d2.json +122 -0
  29. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d1.json +164 -0
  30. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d2.json +164 -0
  31. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d1.json +412 -0
  32. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d2.json +412 -0
  33. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d1.json +88 -0
  34. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d2.json +88 -0
  35. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d1.json +122 -0
  36. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d2.json +122 -0
  37. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d1.json +164 -0
  38. pyfdn-0.4.2/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d2.json +164 -0
  39. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/references.bib +10 -0
  40. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/td/__init__.py +12 -0
  41. pyfdn-0.4.2/src/pyFDN/td/operators.py +1028 -0
  42. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/__init__.py +4 -2
  43. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/build.py +137 -14
  44. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/filters.py +64 -9
  45. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/_targets.py +22 -4
  46. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/spectral.py +16 -4
  47. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/temporal.py +10 -4
  48. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/params.py +1 -1
  49. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_flamo.py +2 -2
  50. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_impz.py +1 -1
  51. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/PKG-INFO +2 -1
  52. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/SOURCES.txt +9 -3
  53. pyfdn-0.4.2/tests/test_auxiliary_allpass.py +36 -0
  54. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_plot.py +3 -3
  55. pyfdn-0.4.1/tests/test_build_io.py → pyfdn-0.4.2/tests/test_build.py +3 -3
  56. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_eq_designs.py +40 -0
  57. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_generate.py +83 -11
  58. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_packaged_resources.py +11 -5
  59. pyfdn-0.4.2/tests/test_preset.py +298 -0
  60. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_process_extensions.py +1 -1
  61. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_td_operators.py +231 -0
  62. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_train.py +68 -21
  63. pyfdn-0.4.1/docs/usage.rst +0 -41
  64. pyfdn-0.4.1/src/pyFDN/generate/fdn_matrix_gallery.py +0 -599
  65. pyfdn-0.4.1/src/pyFDN/presets.py +0 -50
  66. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d1.json +0 -396
  67. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N16_d2.json +0 -396
  68. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d1.json +0 -72
  69. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N4_d2.json +0 -72
  70. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d1.json +0 -106
  71. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N6_d2.json +0 -106
  72. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d1.json +0 -148
  73. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_N8_d2.json +0 -148
  74. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d1.json +0 -396
  75. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N16_d2.json +0 -396
  76. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d1.json +0 -72
  77. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N4_d2.json +0 -72
  78. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d1.json +0 -106
  79. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N6_d2.json +0 -106
  80. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d1.json +0 -148
  81. pyfdn-0.4.1/src/pyFDN/resources/presets/colorless_FDN/colorless_init_N8_d2.json +0 -148
  82. pyfdn-0.4.1/src/pyFDN/td/operators.py +0 -508
  83. {pyfdn-0.4.1 → pyfdn-0.4.2}/AUTHORS.rst +0 -0
  84. {pyfdn-0.4.1 → pyfdn-0.4.2}/CONTRIBUTING.rst +0 -0
  85. {pyfdn-0.4.1 → pyfdn-0.4.2}/LICENSE +0 -0
  86. {pyfdn-0.4.1 → pyfdn-0.4.2}/MANIFEST.in +0 -0
  87. {pyfdn-0.4.1 → pyfdn-0.4.2}/README.rst +0 -0
  88. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/Makefile +0 -0
  89. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/__init__.py +0 -0
  90. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/audio_gallery.py +0 -0
  91. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/authors.rst +0 -0
  92. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/conf.py +0 -0
  93. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/contributing.rst +0 -0
  94. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/example_gallery.py +0 -0
  95. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/history.rst +0 -0
  96. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/index.rst +0 -0
  97. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/installation.rst +0 -0
  98. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/logo_pyFDN_3.png +0 -0
  99. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/logo_pyFDN_4.png +0 -0
  100. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/pyfdn_logo_basic.png +0 -0
  101. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/logo/pyfdn_logo_wave.png +0 -0
  102. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/make.bat +0 -0
  103. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/patch_rst.py +0 -0
  104. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/readme.rst +0 -0
  105. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/references.py +0 -0
  106. {pyfdn-0.4.1 → pyfdn-0.4.2}/docs/training_losses.rst +0 -0
  107. {pyfdn-0.4.1 → pyfdn-0.4.2}/setup.cfg +0 -0
  108. {pyfdn-0.4.1 → pyfdn-0.4.2}/setup.py +0 -0
  109. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/__init__.py +0 -0
  110. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/acoustics.py +0 -0
  111. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/audio.py +0 -0
  112. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/coupled_rooms.py +0 -0
  113. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/delay.py +0 -0
  114. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/marimo_utils.py +0 -0
  115. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/math.py +0 -0
  116. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/plot.py +0 -0
  117. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/poles.py +0 -0
  118. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/tiny_rotation_matrix.py +0 -0
  119. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/auxiliary/utils.py +0 -0
  120. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/_backend.py +0 -0
  121. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/biquads.py +0 -0
  122. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/eq/probe_sos.py +0 -0
  123. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/SDN.py +0 -0
  124. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/__init__.py +0 -0
  125. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/allpass_completion.py +0 -0
  126. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/homogeneous_allpass_fdn.py +0 -0
  127. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_FDN/rand_admissible_homogeneous_allpass.py +0 -0
  128. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/allpass_in_fdn.py +0 -0
  129. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/anderson_matrix.py +0 -0
  130. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/complete_orthogonal.py +0 -0
  131. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/construct_cascaded_paraunitary_matrix.py +0 -0
  132. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/construct_paraunitary_from_elementals.py +0 -0
  133. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/construct_velvet_feedback_matrix.py +0 -0
  134. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/degree_one_lossless.py +0 -0
  135. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/householder_matrix.py +0 -0
  136. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/is_almost_zero.py +0 -0
  137. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/nearest_orthogonal.py +0 -0
  138. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/nearest_sign_agnostic_orthogonal.py +0 -0
  139. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/random_matrix_shift.py +0 -0
  140. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/random_orthogonal.py +0 -0
  141. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/schroeder_reverberator.py +0 -0
  142. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/shift_matrix.py +0 -0
  143. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/generate/shift_matrix_distribute.py +0 -0
  144. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/process.py +0 -0
  145. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/references.py +0 -0
  146. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/__init__.py +0 -0
  147. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/drums/drums.wav +0 -0
  148. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/general/synth_dry.wav +0 -0
  149. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/rir/s3_r4_o.wav +0 -0
  150. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/speech/P1_Set1_8.wav +0 -0
  151. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/speech/P20_Set1_8.wav +0 -0
  152. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/strings/Kleiderschrank_Taylor_DI.wav +0 -0
  153. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/audio/strings/Sommerhit2016_Taylor_DI.wav +0 -0
  154. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/resources/licenses/diff-fdn-colorless-MIT.txt +0 -0
  155. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/td/connectors.py +0 -0
  156. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/engine.py +0 -0
  157. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/__init__.py +0 -0
  158. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/base.py +0 -0
  159. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/losses/parameter.py +0 -0
  160. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/train/response.py +0 -0
  161. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_pr.py +0 -0
  162. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_ss.py +0 -0
  163. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/dss_to_tf.py +0 -0
  164. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/flamo_to_pr.py +0 -0
  165. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/impz_to_res.py +0 -0
  166. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/mtf_to_impz.py +0 -0
  167. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN/translate/pr_to_impz.py +0 -0
  168. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/dependency_links.txt +0 -0
  169. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/requires.txt +0 -0
  170. {pyfdn-0.4.1 → pyfdn-0.4.2}/src/pyFDN.egg-info/top_level.txt +0 -0
  171. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/__init__.py +0 -0
  172. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/conftest.py +0 -0
  173. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/matloader.py +0 -0
  174. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/reference/README.md +0 -0
  175. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/reference/example_onePoleAbsorption.mat +0 -0
  176. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/reference/onePoleAbsorption_reference.mat +0 -0
  177. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_api_reference.py +0 -0
  178. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_audio_gallery.py +0 -0
  179. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_acoustics.py +0 -0
  180. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_delay.py +0 -0
  181. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_flamo.py +0 -0
  182. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_math.py +0 -0
  183. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_auxiliary_utils.py +0 -0
  184. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_coupled_rooms.py +0 -0
  185. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_dss_to_pr.py +0 -0
  186. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_example_gallery.py +0 -0
  187. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_flamo_graph.py +0 -0
  188. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_flamo_one_pole_absorption.py +0 -0
  189. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_flamo_to_pr.py +0 -0
  190. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_marimo_examples_run.py +0 -0
  191. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_pyFDN.py +0 -0
  192. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_sdn.py +0 -0
  193. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_td_connectors.py +0 -0
  194. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_td_graph_processing.py +0 -0
  195. {pyfdn-0.4.1 → pyfdn-0.4.2}/tests/test_time_varying_matrix.py +0 -0
  196. {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: ``DecayFilter`` parametrizes the in-loop absorption filter
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``, ``DecayFilter``, ``OutputEQ``) from
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.1
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.DecayFilter` and :class:`pyFDN.OutputEQ` modules use these
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.DecayFilter
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.1"
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.1"
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.1"
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
- "DecayFilter",
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 .build_io import (
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 .presets import available_fdn_presets, load_fdn_preset
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
- DecayFilter,
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.DecayFilter` or a plain
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.generate.fdn_matrix_gallery import FDNBuild
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.generate.fdn_matrix_gallery import FDNBuild
424
+ from pyFDN.build import FDNBuild
425
425
 
426
426
  return FDNBuild(
427
427
  A=A,
@@ -1,24 +1,48 @@
1
- """Readable, versioned serialization for :class:`pyFDN.FDNBuild`."""
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 collections.abc import Mapping
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: Mapping[str, Any] | None = None
56
+ build: FDNBuild, *, metadata: dict[str, Any] | None = None
33
57
  ) -> dict[str, Any]:
34
- """Convert an :class:`FDNBuild` to the versioned JSON-compatible format."""
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
- data: Mapping[str, Any], *, fs: float | None = None
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
- Args:
72
- data: Parsed JSON-compatible build dictionary.
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: Mapping[str, Any] | None = None,
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 the versioned JSON format."""
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"]