pyFDN 0.1.0__tar.gz → 0.2.0__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 (226) hide show
  1. pyfdn-0.2.0/HISTORY.rst +17 -0
  2. {pyfdn-0.1.0 → pyfdn-0.2.0}/MANIFEST.in +1 -1
  3. {pyfdn-0.1.0 → pyfdn-0.2.0}/PKG-INFO +11 -2
  4. {pyfdn-0.1.0 → pyfdn-0.2.0}/README.rst +5 -0
  5. pyfdn-0.2.0/docs/__init__.py +1 -0
  6. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/api_reference.rst +30 -2
  7. pyfdn-0.2.0/docs/audio_gallery.py +150 -0
  8. pyfdn-0.2.0/docs/audio_gallery.rst +99 -0
  9. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/conf.py +3 -1
  10. pyfdn-0.2.0/docs/example_gallery.py +184 -0
  11. pyfdn-0.2.0/docs/examples_gallery.rst +133 -0
  12. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/index.rst +8 -1
  13. pyfdn-0.2.0/docs/patch_rst.py +41 -0
  14. pyfdn-0.2.0/docs/references.py +8 -0
  15. pyfdn-0.2.0/docs/usage.rst +34 -0
  16. {pyfdn-0.1.0 → pyfdn-0.2.0}/pyproject.toml +24 -9
  17. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/__init__.py +26 -5
  18. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/acoustics.py +187 -43
  19. pyfdn-0.2.0/src/pyFDN/auxiliary/audio.py +83 -0
  20. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/flamo_graph.py +1 -3
  21. pyfdn-0.2.0/src/pyFDN/auxiliary/marimo_utils.py +48 -0
  22. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/utils.py +23 -0
  23. pyfdn-0.2.0/src/pyFDN/presets.py +60 -0
  24. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/process.py +24 -16
  25. pyfdn-0.2.0/src/pyFDN/references.py +105 -0
  26. pyfdn-0.2.0/src/pyFDN/resources/ATTRIBUTION.md +31 -0
  27. pyfdn-0.2.0/src/pyFDN/resources/__init__.py +1 -0
  28. pyfdn-0.2.0/src/pyFDN/resources/audio/drums/drums.wav +0 -0
  29. {pyfdn-0.1.0/src/pyFDN/audio → pyfdn-0.2.0/src/pyFDN/resources/audio/general}/synth_dry.wav +0 -0
  30. pyfdn-0.2.0/src/pyFDN/resources/audio/metadata.json +79 -0
  31. pyfdn-0.2.0/src/pyFDN/resources/audio/rir/s3_r4_o.wav +0 -0
  32. pyfdn-0.2.0/src/pyFDN/resources/audio/speech/P1_Set1_8.wav +0 -0
  33. pyfdn-0.2.0/src/pyFDN/resources/audio/speech/P20_Set1_8.wav +0 -0
  34. pyfdn-0.2.0/src/pyFDN/resources/audio/strings/Kleiderschrank_Taylor_DI.wav +0 -0
  35. pyfdn-0.2.0/src/pyFDN/resources/audio/strings/Sommerhit2016_Taylor_DI.wav +0 -0
  36. pyfdn-0.2.0/src/pyFDN/resources/licenses/diff-fdn-colorless-MIT.txt +21 -0
  37. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N16_d1.mat +0 -0
  38. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N16_d2.mat +0 -0
  39. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N4_d1.mat +0 -0
  40. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N4_d2.mat +0 -0
  41. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N6_d1.mat +0 -0
  42. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N6_d2.mat +0 -0
  43. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N8_d1.mat +0 -0
  44. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_N8_d2.mat +0 -0
  45. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N16_d1.mat +0 -0
  46. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N16_d2.mat +0 -0
  47. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N4_d1.mat +0 -0
  48. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N4_d2.mat +0 -0
  49. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N6_d1.mat +0 -0
  50. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N6_d2.mat +0 -0
  51. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N8_d1.mat +0 -0
  52. pyfdn-0.2.0/src/pyFDN/resources/presets/colorless_FDN/param_init_N8_d2.mat +0 -0
  53. pyfdn-0.2.0/src/pyFDN/resources/references.bib +281 -0
  54. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/train/__init__.py +8 -2
  55. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/train/build.py +2 -2
  56. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/train/engine.py +2 -6
  57. pyfdn-0.2.0/src/pyFDN/translate/dss_to_impz.py +128 -0
  58. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN.egg-info/PKG-INFO +11 -2
  59. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN.egg-info/SOURCES.txt +39 -4
  60. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN.egg-info/requires.txt +3 -1
  61. pyfdn-0.2.0/tests/test_audio_gallery.py +17 -0
  62. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_auxiliary_acoustics.py +71 -5
  63. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_auxiliary_utils.py +33 -0
  64. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_example_gallery.py +2 -8
  65. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_marimo_examples_run.py +15 -0
  66. pyfdn-0.2.0/tests/test_packaged_resources.py +94 -0
  67. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_process_extensions.py +56 -1
  68. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_train.py +28 -15
  69. pyfdn-0.1.0/HISTORY.rst +0 -8
  70. pyfdn-0.1.0/docs/examples_gallery.rst +0 -131
  71. pyfdn-0.1.0/docs/usage.rst +0 -17
  72. pyfdn-0.1.0/src/pyFDN/audio/example_process_fdn_output.wav +0 -0
  73. pyfdn-0.1.0/src/pyFDN/audio/s3_r4_o.wav +0 -0
  74. pyfdn-0.1.0/src/pyFDN/auxiliary/audio.py +0 -54
  75. pyfdn-0.1.0/src/pyFDN/pyFDN.py +0 -1
  76. pyfdn-0.1.0/src/pyFDN/translate/dss_to_impz.py +0 -48
  77. {pyfdn-0.1.0 → pyfdn-0.2.0}/AUTHORS.rst +0 -0
  78. {pyfdn-0.1.0 → pyfdn-0.2.0}/CONTRIBUTING.rst +0 -0
  79. {pyfdn-0.1.0 → pyfdn-0.2.0}/LICENSE +0 -0
  80. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/Makefile +0 -0
  81. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/authors.rst +0 -0
  82. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/contributing.rst +0 -0
  83. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.DFiltMatrix.rst +0 -0
  84. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.FeedbackDelay.rst +0 -0
  85. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.FilterMatrix.rst +0 -0
  86. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.TFMatrix.rst +0 -0
  87. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.ZFIR.rst +0 -0
  88. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.ZSOS.rst +0 -0
  89. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.ZScalar.rst +0 -0
  90. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.ZTF.rst +0 -0
  91. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.absorption_filters.rst +0 -0
  92. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.absorption_to_rt.rst +0 -0
  93. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.construct_cascaded_paraunitary_matrix.rst +0 -0
  94. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.construct_velvet_feedback_matrix.rst +0 -0
  95. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.db_to_lin.rst +0 -0
  96. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.db_to_sq.rst +0 -0
  97. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.det_polynomial.rst +0 -0
  98. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.dss_to_impz.rst +0 -0
  99. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.dss_to_ss.rst +0 -0
  100. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.echo_density.rst +0 -0
  101. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.edc.rst +0 -0
  102. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.ensure_3d.rst +0 -0
  103. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.hertz_to_unit.rst +0 -0
  104. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.interpolate_orthogonal.rst +0 -0
  105. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.is_almost_zero.rst +0 -0
  106. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.is_bounding_curve.rst +0 -0
  107. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.is_orthogonal.rst +0 -0
  108. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.last_nonzero_indices.rst +0 -0
  109. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.lin_to_db.rst +0 -0
  110. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.matrix_convolution.rst +0 -0
  111. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.matrix_delay_approximation.rst +0 -0
  112. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.matrix_polyder.rst +0 -0
  113. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.matrix_polyval.rst +0 -0
  114. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.matrix_sqrt.rst +0 -0
  115. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.mgrpdelay.rst +0 -0
  116. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.ms_to_smp.rst +0 -0
  117. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.mulaw_decode.rst +0 -0
  118. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.mulaw_encode.rst +0 -0
  119. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.negpolyder.rst +0 -0
  120. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.one_pole_absorption.rst +0 -0
  121. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.outer_sum_approximation.rst +0 -0
  122. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.peak_normalize.rst +0 -0
  123. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.plot_impulse_response_matrix.rst +0 -0
  124. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.plot_system_matrix.rst +0 -0
  125. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.pole_boundaries.rst +0 -0
  126. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.poly_degree.rst +0 -0
  127. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.polyder_rational.rst +0 -0
  128. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.polydiag.rst +0 -0
  129. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.process_fdn.rst +0 -0
  130. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.random_matrix_shift.rst +0 -0
  131. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.random_orthogonal.rst +0 -0
  132. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.rt_to_gain_per_sample.rst +0 -0
  133. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.rt_to_slope.rst +0 -0
  134. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.shift_matrix.rst +0 -0
  135. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.shift_matrix_distribute.rst +0 -0
  136. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.skew.rst +0 -0
  137. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.slope_to_rt.rst +0 -0
  138. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.sq_to_db.rst +0 -0
  139. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/generated/pyFDN.tiny_rotation_matrix.rst +0 -0
  140. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/history.rst +0 -0
  141. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/installation.rst +0 -0
  142. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/logo/logo_pyFDN_3.png +0 -0
  143. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/logo/logo_pyFDN_4.png +0 -0
  144. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/logo/pyfdn_logo_basic.png +0 -0
  145. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/logo/pyfdn_logo_wave.png +0 -0
  146. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/make.bat +0 -0
  147. {pyfdn-0.1.0 → pyfdn-0.2.0}/docs/readme.rst +0 -0
  148. {pyfdn-0.1.0 → pyfdn-0.2.0}/setup.cfg +0 -0
  149. {pyfdn-0.1.0 → pyfdn-0.2.0}/setup.py +0 -0
  150. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/__init__.py +0 -0
  151. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/allpass.py +0 -0
  152. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/coupled_rooms.py +0 -0
  153. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/delay.py +0 -0
  154. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/flamo.py +0 -0
  155. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/math.py +0 -0
  156. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/plot.py +0 -0
  157. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/poles.py +0 -0
  158. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/auxiliary/tiny_rotation_matrix.py +0 -0
  159. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/dsp/__init__.py +0 -0
  160. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/dsp/dfilt_matrix.py +0 -0
  161. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/dsp/feedback_delay.py +0 -0
  162. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/dsp/sos_filter_bank.py +0 -0
  163. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/dsp/time_varying_matrix.py +0 -0
  164. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/SDN.py +0 -0
  165. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/allpass_FDN/__init__.py +0 -0
  166. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/allpass_FDN/allpass_completion.py +0 -0
  167. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/allpass_FDN/homogeneous_allpass_fdn.py +0 -0
  168. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/allpass_FDN/rand_admissible_homogeneous_allpass.py +0 -0
  169. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/allpass_in_fdn.py +0 -0
  170. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/anderson_matrix.py +0 -0
  171. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/complete_orthogonal.py +0 -0
  172. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/construct_cascaded_paraunitary_matrix.py +0 -0
  173. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/construct_paraunitary_from_elementals.py +0 -0
  174. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/construct_velvet_feedback_matrix.py +0 -0
  175. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/degree_one_lossless.py +0 -0
  176. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/fdn_matrix_gallery.py +0 -0
  177. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/householder_matrix.py +0 -0
  178. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/is_almost_zero.py +0 -0
  179. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/nearest_orthogonal.py +0 -0
  180. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/nearest_sign_agnostic_orthogonal.py +0 -0
  181. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/random_matrix_shift.py +0 -0
  182. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/random_orthogonal.py +0 -0
  183. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/sample_delay_lengths.py +0 -0
  184. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/schroeder_reverberator.py +0 -0
  185. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/shift_matrix.py +0 -0
  186. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/generate/shift_matrix_distribute.py +0 -0
  187. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/__init__.py +0 -0
  188. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/absorption_geq.py +0 -0
  189. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/bandpass_filter.py +0 -0
  190. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/design_geq.py +0 -0
  191. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/graphic_eq.py +0 -0
  192. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/probe_sos.py +0 -0
  193. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/graphicEQ/shelving_filter.py +0 -0
  194. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/train/objectives.py +0 -0
  195. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/dss_to_flamo.py +0 -0
  196. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/dss_to_pr.py +0 -0
  197. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/dss_to_ss.py +0 -0
  198. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/dss_to_tf.py +0 -0
  199. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/flamo_to_pr.py +0 -0
  200. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/impz_to_res.py +0 -0
  201. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/mtf_to_impz.py +0 -0
  202. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN/translate/pr_to_impz.py +0 -0
  203. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN.egg-info/dependency_links.txt +0 -0
  204. {pyfdn-0.1.0 → pyfdn-0.2.0}/src/pyFDN.egg-info/top_level.txt +0 -0
  205. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/__init__.py +0 -0
  206. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/conftest.py +0 -0
  207. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/matloader.py +0 -0
  208. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/reference/README.md +0 -0
  209. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/reference/example_onePoleAbsorption.mat +0 -0
  210. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/reference/onePoleAbsorption_reference.mat +0 -0
  211. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_api_reference.py +0 -0
  212. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_auxiliary_delay.py +0 -0
  213. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_auxiliary_flamo.py +0 -0
  214. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_auxiliary_math.py +0 -0
  215. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_auxiliary_plot.py +0 -0
  216. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_coupled_rooms.py +0 -0
  217. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_dss_to_pr.py +0 -0
  218. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_flamo_graph.py +0 -0
  219. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_flamo_one_pole_absorption.py +0 -0
  220. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_flamo_to_pr.py +0 -0
  221. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_generate.py +0 -0
  222. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_graphic_eq.py +0 -0
  223. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_pyFDN.py +0 -0
  224. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_sdn.py +0 -0
  225. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_time_varying_matrix.py +0 -0
  226. {pyfdn-0.1.0 → pyfdn-0.2.0}/tests/test_translate.py +0 -0
@@ -0,0 +1,17 @@
1
+ =======
2
+ History
3
+ =======
4
+
5
+ 0.2.0 (2026-08-10)
6
+ ------------------
7
+
8
+ * Package compact audio examples and predefined colorless-FDN coefficients so
9
+ browser-hosted tutorials work without repository-relative files.
10
+ * Add public APIs for packaged audio, FDN presets, and paper references.
11
+ * Add generated documentation galleries and wheel-content checks.
12
+ * Keep decay-estimation dependencies optional for a smaller core installation.
13
+
14
+ 0.1.0 (2025-07-02)
15
+ ------------------
16
+
17
+ * First release on PyPI.
@@ -8,4 +8,4 @@ recursive-include tests *
8
8
  recursive-exclude * __pycache__
9
9
  recursive-exclude * *.py[co]
10
10
 
11
- recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
11
+ recursive-include docs *.rst *.py Makefile make.bat *.jpg *.png *.gif
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyFDN
3
- Version: 0.1.0
3
+ Version: 0.2.0
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
@@ -16,20 +16,23 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Requires-Python: >=3.10
17
17
  Description-Content-Type: text/x-rst
18
18
  License-File: LICENSE
19
+ License-File: src/pyFDN/resources/ATTRIBUTION.md
20
+ License-File: src/pyFDN/resources/licenses/diff-fdn-colorless-MIT.txt
19
21
  Requires-Dist: numpy
20
22
  Requires-Dist: soundfile
21
23
  Requires-Dist: scipy
22
24
  Requires-Dist: flamo>=0.2.14
23
25
  Requires-Dist: flareverb
24
- Requires-Dist: pyroomacoustics
25
26
  Provides-Extra: examples
26
27
  Requires-Dist: marimo; extra == "examples"
27
28
  Requires-Dist: matplotlib>=3.5.0; extra == "examples"
28
29
  Requires-Dist: plotly>=5.0.0; extra == "examples"
30
+ Requires-Dist: multislope>=0.1.1; extra == "examples"
29
31
  Provides-Extra: test
30
32
  Requires-Dist: marimo; extra == "test"
31
33
  Requires-Dist: matplotlib>=3.5.0; extra == "test"
32
34
  Requires-Dist: plotly>=5.0.0; extra == "test"
35
+ Requires-Dist: multislope>=0.1.1; extra == "test"
33
36
  Requires-Dist: pytest>=7.0.0; extra == "test"
34
37
  Requires-Dist: pytest-cov>=4.0.0; extra == "test"
35
38
  Requires-Dist: coverage>=7.0.0; extra == "test"
@@ -56,6 +59,7 @@ Requires-Dist: ipykernel>=6.0.0; extra == "docs"
56
59
  Requires-Dist: pandoc>=2.0; extra == "docs"
57
60
  Requires-Dist: plotly>=5.0.0; extra == "docs"
58
61
  Requires-Dist: matplotlib>=3.5.0; extra == "docs"
62
+ Requires-Dist: multislope>=0.1.1; extra == "docs"
59
63
  Dynamic: license-file
60
64
 
61
65
  =====
@@ -89,6 +93,11 @@ Overview
89
93
 
90
94
  ``pyFDN`` provides building blocks for designing, simulating, and analysing Feedback Delay Networks (FDNs). The package focuses on reusable, tested helper functions that simplify typical FDN workflows such as creating orthogonal feedback matrices, designing loop filters, and inspecting pole locations. Using ``flamo`` as a dependency, ``pyFDN`` allows modular design of advanced FDN structure with DSP operations in time and frequency domain.
91
95
 
96
+ The source code is MIT-licensed. Small packaged tutorial resources retain the
97
+ separate terms recorded in ``src/pyFDN/resources/ATTRIBUTION.md``; in
98
+ particular, the Pori concert-hall impulse response is limited to
99
+ noncommercial use.
100
+
92
101
 
93
102
  Highlights
94
103
  ----------
@@ -29,6 +29,11 @@ Overview
29
29
 
30
30
  ``pyFDN`` provides building blocks for designing, simulating, and analysing Feedback Delay Networks (FDNs). The package focuses on reusable, tested helper functions that simplify typical FDN workflows such as creating orthogonal feedback matrices, designing loop filters, and inspecting pole locations. Using ``flamo`` as a dependency, ``pyFDN`` allows modular design of advanced FDN structure with DSP operations in time and frequency domain.
31
31
 
32
+ The source code is MIT-licensed. Small packaged tutorial resources retain the
33
+ separate terms recorded in ``src/pyFDN/resources/ATTRIBUTION.md``; in
34
+ particular, the Pori concert-hall impulse response is limited to
35
+ noncommercial use.
36
+
32
37
 
33
38
  Highlights
34
39
  ----------
@@ -0,0 +1 @@
1
+ """Documentation build helpers; this package is not distributed in the wheel."""
@@ -93,8 +93,11 @@ Acoustics & Absorption
93
93
  pyFDN.absorption_to_rt
94
94
  pyFDN.estimate_initial_level_bands
95
95
  pyFDN.estimate_rt_bands
96
+ pyFDN.octave_bands
97
+ pyFDN.octave_band_filterbank
96
98
  pyFDN.rt_to_gain_per_sample
97
99
  pyFDN.rt_to_slope
100
+ pyFDN.slope_amplitude_to_level
98
101
  pyFDN.slope_to_rt
99
102
 
100
103
  Graphic EQ
@@ -172,6 +175,7 @@ General Utilities
172
175
  pyFDN.lin_to_db
173
176
  pyFDN.sq_to_db
174
177
  pyFDN.ensure_3d
178
+ pyFDN.fade_out
175
179
  pyFDN.hertz_to_unit
176
180
  pyFDN.hertz_to_rad
177
181
  pyFDN.rad_to_hertz
@@ -181,10 +185,24 @@ General Utilities
181
185
  pyFDN.mulaw_decode
182
186
  pyFDN.mulaw_encode
183
187
  pyFDN.peak_normalize
184
- pyFDN.load_audio
185
188
  pyFDN.pole_boundaries
186
189
  pyFDN.skew
187
190
 
191
+ Packaged Examples & References
192
+ ------------------------------
193
+
194
+ .. autosummary::
195
+ :toctree: generated/
196
+ :nosignatures:
197
+
198
+ pyFDN.available_audio
199
+ pyFDN.audio_metadata
200
+ pyFDN.load_audio
201
+ pyFDN.available_fdn_presets
202
+ pyFDN.load_fdn_preset
203
+ pyFDN.paper_reference
204
+ pyFDN.paper_link
205
+
188
206
  State-Space Translators
189
207
  -----------------------
190
208
 
@@ -194,6 +212,7 @@ State-Space Translators
194
212
 
195
213
  pyFDN.dss_to_ss
196
214
  pyFDN.dss_to_impz
215
+ pyFDN.build_to_impz
197
216
  pyFDN.dss_to_tf
198
217
  pyFDN.dss_to_pr
199
218
  pyFDN.dss_to_flamo
@@ -225,7 +244,7 @@ Training
225
244
 
226
245
  pyFDN.build_fdn
227
246
  pyFDN.trainable_from_build
228
- pyFDN.with_decay
247
+ pyFDN.build_set_decay
229
248
  pyFDN.Trainable
230
249
  pyFDN.train_fdn
231
250
  pyFDN.TrainLog
@@ -252,6 +271,15 @@ Plotting
252
271
  pyFDN.downsample_minmax
253
272
  pyFDN.downsample_plotly_trace
254
273
 
274
+ Notebook Display
275
+ ----------------
276
+
277
+ .. autosummary::
278
+ :toctree: generated/
279
+ :nosignatures:
280
+
281
+ pyFDN.labeled_audio
282
+
255
283
  FLAMO Graph
256
284
  -----------
257
285
 
@@ -0,0 +1,150 @@
1
+ """Generate the audio gallery from the audio files packaged with pyFDN."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import html
6
+ import json
7
+ from collections import defaultdict
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from urllib.parse import quote
11
+
12
+ DOCS_DIR = Path(__file__).resolve().parent
13
+ PROJECT_ROOT = DOCS_DIR.parent
14
+ AUDIO_SOURCE_DIR = PROJECT_ROOT / "src" / "pyFDN" / "resources" / "audio"
15
+ AUDIO_REFERENCES_FILE = AUDIO_SOURCE_DIR / "metadata.json"
16
+ OUTPUT_FILE = DOCS_DIR / "audio_gallery.rst"
17
+ GITHUB_AUDIO_ROOT = (
18
+ "https://raw.githubusercontent.com/artificial-audio/pyFDN/"
19
+ "main/src/pyFDN/resources/audio/"
20
+ )
21
+
22
+ UNCATEGORIZED = "uncategorized"
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class AudioExample:
27
+ path: Path
28
+ category: str
29
+
30
+ @property
31
+ def filename(self) -> str:
32
+ return self.path.name
33
+
34
+ @property
35
+ def display_label(self) -> str:
36
+ return f"{self.filename}"
37
+
38
+ @property
39
+ def audio_url(self) -> str:
40
+ relative = self.path.relative_to(AUDIO_SOURCE_DIR)
41
+ return GITHUB_AUDIO_ROOT + quote(relative.as_posix())
42
+
43
+
44
+ def discover_audio_files() -> list[AudioExample]:
45
+ examples = []
46
+ for path in sorted(AUDIO_SOURCE_DIR.rglob("*.wav")):
47
+ relative = path.relative_to(AUDIO_SOURCE_DIR)
48
+ category = relative.parts[0] if len(relative.parts) > 1 else UNCATEGORIZED
49
+ examples.append(AudioExample(path, category))
50
+ return examples
51
+
52
+
53
+ def render_gallery(examples: list[AudioExample]) -> str:
54
+ # Load audio metadata
55
+ with open(AUDIO_REFERENCES_FILE, encoding="utf-8") as f:
56
+ audio_references = {item["filename"]: item for item in json.load(f)}
57
+
58
+ grouped: dict[str, list[AudioExample]] = defaultdict(list)
59
+ for example in examples:
60
+ grouped[example.category].append(example)
61
+
62
+ categories = sorted(
63
+ grouped, key=lambda name: (name == UNCATEGORIZED, name.casefold())
64
+ )
65
+
66
+ lines = [
67
+ ".. This file is generated by docs/audio_gallery.py. Do not edit it manually.",
68
+ "",
69
+ "============",
70
+ "Audio Gallery",
71
+ "============",
72
+ "",
73
+ "Audio examples demonstrating ``pyFDN``. The gallery is generated",
74
+ "automatically from every packaged ``.wav`` file, grouped by resource category.",
75
+ "",
76
+ ]
77
+
78
+ for category in categories:
79
+ entries = sorted(grouped[category], key=lambda item: item.filename.casefold())
80
+
81
+ lines.extend(
82
+ [
83
+ category,
84
+ "-" * len(category),
85
+ "",
86
+ ".. raw:: html",
87
+ "",
88
+ ]
89
+ )
90
+
91
+ for example in entries:
92
+ label_html = html.escape(example.display_label)
93
+ src = example.audio_url
94
+
95
+ if example.filename in audio_references:
96
+ audio_ref = audio_references[example.filename]
97
+
98
+ title = html.escape(audio_ref["source"])
99
+ url = html.escape(audio_ref["source_url"])
100
+ creator = html.escape(audio_ref["creator"])
101
+ license_name = html.escape(audio_ref["license"])
102
+ license_url = html.escape(audio_ref["license_url"])
103
+
104
+ lines.extend(
105
+ [
106
+ f" <p>{label_html}<br>",
107
+ f' Source: <a href="{url}">{title}</a> — {creator}<br>',
108
+ f' Terms: <a href="{license_url}">{license_name}</a><br>',
109
+ " <audio controls>",
110
+ f' <source src="{src}" type="audio/wav">',
111
+ " Your browser does not support the audio element.",
112
+ " </audio>",
113
+ " </p>",
114
+ ]
115
+ )
116
+ else:
117
+ lines.extend(
118
+ [
119
+ f" <p>{label_html}<br>",
120
+ " <audio controls>",
121
+ f' <source src="{src}" type="audio/wav">',
122
+ " Your browser does not support the audio element.",
123
+ " </audio>",
124
+ " </p>",
125
+ ]
126
+ )
127
+
128
+ lines.append("")
129
+
130
+ lines.extend(
131
+ [
132
+ "----",
133
+ "",
134
+ "All audio files can also be browsed in the repository's",
135
+ "`packaged audio directory <https://github.com/artificial-audio/pyFDN/tree/main/src/pyFDN/resources/audio>`_.",
136
+ "",
137
+ ]
138
+ )
139
+ return "\n".join(lines)
140
+
141
+
142
+ def generate_audio_gallery(output_file: Path = OUTPUT_FILE) -> str:
143
+ examples = discover_audio_files()
144
+ rendered = render_gallery(examples)
145
+ output_file.write_text(rendered, encoding="utf-8")
146
+ return rendered
147
+
148
+
149
+ if __name__ == "__main__":
150
+ generate_audio_gallery()
@@ -0,0 +1,99 @@
1
+ .. This file is generated by docs/audio_gallery.py. Do not edit it manually.
2
+
3
+ ============
4
+ Audio Gallery
5
+ ============
6
+
7
+ Audio examples demonstrating ``pyFDN``. The gallery is generated
8
+ automatically from every packaged ``.wav`` file, grouped by resource category.
9
+
10
+ drums
11
+ -----
12
+
13
+ .. raw:: html
14
+
15
+ <p>drums.wav<br>
16
+ Source: <a href="https://github.com/artificial-audio/pyFDN">Original pyFDN drum example</a> — Sebastian J. Schlecht<br>
17
+ Terms: <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0-1.0</a><br>
18
+ <audio controls>
19
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/drums/drums.wav" type="audio/wav">
20
+ Your browser does not support the audio element.
21
+ </audio>
22
+ </p>
23
+
24
+ general
25
+ -------
26
+
27
+ .. raw:: html
28
+
29
+ <p>synth_dry.wav<br>
30
+ Source: <a href="https://github.com/artificial-audio/pyFDN">Original pyFDN synthesizer example</a> — Sebastian J. Schlecht<br>
31
+ Terms: <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0-1.0</a><br>
32
+ <audio controls>
33
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/general/synth_dry.wav" type="audio/wav">
34
+ Your browser does not support the audio element.
35
+ </audio>
36
+ </p>
37
+
38
+ rir
39
+ ---
40
+
41
+ .. raw:: html
42
+
43
+ <p>s3_r4_o.wav<br>
44
+ Source: <a href="http://legacy.spa.aalto.fi/projects/poririrs/">Concert Hall Impulse Responses — Pori, Finland</a> — Juha Merimaa, Timo Peltonen, Tapio Lokki, Helsinki University of Technology, and Akukon Oy<br>
45
+ Terms: <a href="http://legacy.spa.aalto.fi/projects/poririrs/">PORIRIR noncommercial terms</a><br>
46
+ <audio controls>
47
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/rir/s3_r4_o.wav" type="audio/wav">
48
+ Your browser does not support the audio element.
49
+ </audio>
50
+ </p>
51
+
52
+ speech
53
+ ------
54
+
55
+ .. raw:: html
56
+
57
+ <p>P1_Set1_8.wav<br>
58
+ Source: <a href="https://zenodo.org/records/15034732">Dataset of Audiovisual Speech for AR Telepresence Studies</a> — Nils Meyer-Kahlen, Anja Hofmann, Sebastian J. Schlecht, and Tapio Lokki<br>
59
+ Terms: <a href="https://zenodo.org/records/15034732">Included with creator permission</a><br>
60
+ <audio controls>
61
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/speech/P1_Set1_8.wav" type="audio/wav">
62
+ Your browser does not support the audio element.
63
+ </audio>
64
+ </p>
65
+ <p>P20_Set1_8.wav<br>
66
+ Source: <a href="https://zenodo.org/records/15034732">Dataset of Audiovisual Speech for AR Telepresence Studies</a> — Nils Meyer-Kahlen, Anja Hofmann, Sebastian J. Schlecht, and Tapio Lokki<br>
67
+ Terms: <a href="https://zenodo.org/records/15034732">Included with creator permission</a><br>
68
+ <audio controls>
69
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/speech/P20_Set1_8.wav" type="audio/wav">
70
+ Your browser does not support the audio element.
71
+ </audio>
72
+ </p>
73
+
74
+ strings
75
+ -------
76
+
77
+ .. raw:: html
78
+
79
+ <p>Kleiderschrank_Taylor_DI.wav<br>
80
+ Source: <a href="https://phaidra.kug.ac.at/detail/o:68022">Gemeinfreie Gitarrenaufnahmen 1</a> — Franz Zotter and Daniel Rudrich<br>
81
+ Terms: <a href="https://creativecommons.org/publicdomain/mark/1.0/">PDM-1.0</a><br>
82
+ <audio controls>
83
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/strings/Kleiderschrank_Taylor_DI.wav" type="audio/wav">
84
+ Your browser does not support the audio element.
85
+ </audio>
86
+ </p>
87
+ <p>Sommerhit2016_Taylor_DI.wav<br>
88
+ Source: <a href="https://phaidra.kug.ac.at/detail/o:68022">Gemeinfreie Gitarrenaufnahmen 1</a> — Franz Zotter and Daniel Rudrich<br>
89
+ Terms: <a href="https://creativecommons.org/publicdomain/mark/1.0/">PDM-1.0</a><br>
90
+ <audio controls>
91
+ <source src="https://raw.githubusercontent.com/artificial-audio/pyFDN/main/src/pyFDN/resources/audio/strings/Sommerhit2016_Taylor_DI.wav" type="audio/wav">
92
+ Your browser does not support the audio element.
93
+ </audio>
94
+ </p>
95
+
96
+ ----
97
+
98
+ All audio files can also be browsed in the repository's
99
+ `packaged audio directory <https://github.com/artificial-audio/pyFDN/tree/main/src/pyFDN/resources/audio>`_.
@@ -9,12 +9,14 @@ sys.path.insert(
9
9
  0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))
10
10
  )
11
11
 
12
+ from audio_gallery import generate_audio_gallery
12
13
  from example_gallery import generate_gallery # noqa: E402
13
14
 
14
15
  import pyFDN # noqa: E402
15
16
 
16
- # Keep the gallery in sync with every marimo notebook before Sphinx reads it.
17
+ # Keep the gallery in sync with every marimo notebook and audio file before Sphinx reads it.
17
18
  generate_gallery()
19
+ generate_audio_gallery()
18
20
 
19
21
  # -- Project information ------------------------------------------------------
20
22
  project = "pyFDN"
@@ -0,0 +1,184 @@
1
+ """Generate the examples gallery from the marimo notebooks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import re
7
+ from collections import defaultdict
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ DOCS_DIR = Path(__file__).resolve().parent
12
+ PROJECT_ROOT = DOCS_DIR.parent
13
+ EXAMPLES_DIR = PROJECT_ROOT / "examples"
14
+ OUTPUT_FILE = DOCS_DIR / "examples_gallery.rst"
15
+
16
+ CATEGORY_ORDER = (
17
+ "Getting Started",
18
+ "Absorption & Filters",
19
+ "Translation Examples",
20
+ "Allpass FDN Examples",
21
+ "Special FDNs",
22
+ "FDN Design & Analysis",
23
+ )
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Example:
28
+ path: Path
29
+ title: str
30
+ description: str
31
+ category: str
32
+
33
+ @property
34
+ def output_name(self) -> str:
35
+ relative = self.path.relative_to(EXAMPLES_DIR).with_suffix("")
36
+ return "_".join(relative.parts)
37
+
38
+
39
+ def _markdown_blocks(path: Path) -> list[str]:
40
+ """Return literal Markdown strings passed to ``mo.md`` calls."""
41
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
42
+ blocks: list[str] = []
43
+ for node in ast.walk(tree):
44
+ if not isinstance(node, ast.Call) or not node.args:
45
+ continue
46
+ if not isinstance(node.func, ast.Attribute) or node.func.attr != "md":
47
+ continue
48
+ value = node.args[0]
49
+ if isinstance(value, ast.Constant) and isinstance(value.value, str):
50
+ blocks.append(value.value.strip())
51
+ return blocks
52
+
53
+
54
+ def _plain_text(markdown: str) -> str:
55
+ text = re.sub(r"\[([^]]+)]\([^)]+\)", r"\1", markdown)
56
+ text = re.sub(r"`+([^`]+?)`+", r"``\1``", text)
57
+ text = re.sub(r"\*\*([^*]+)\*\*", r"\1", text)
58
+ text = re.sub(r"(?<!\*)\*([^*]+)\*(?!\*)", r"\1", text)
59
+ text = re.sub(r"\s+", " ", text)
60
+ return text.strip()
61
+
62
+
63
+ def _title_and_description(path: Path) -> tuple[str, str]:
64
+ blocks = _markdown_blocks(path)
65
+ for block in blocks:
66
+ lines = block.splitlines()
67
+ for index, line in enumerate(lines):
68
+ if not line.startswith("# "):
69
+ continue
70
+ title = _plain_text(line[2:])
71
+ paragraphs = "\n".join(lines[index + 1 :]).strip().split("\n\n")
72
+ description = next(
73
+ (
74
+ _plain_text(paragraph)
75
+ for paragraph in paragraphs
76
+ if paragraph.strip() and not paragraph.lstrip().startswith("#")
77
+ ),
78
+ "Open the rendered marimo notebook.",
79
+ )
80
+ return title, description
81
+
82
+ fallback = path.stem.removeprefix("example_").replace("_", " ").title()
83
+ return fallback, "Open the rendered marimo notebook."
84
+
85
+
86
+ def _metadata(path: Path) -> dict[str, str]:
87
+ """Read ``# gallery_<key>: <value>`` comments."""
88
+ metadata: dict[str, str] = {}
89
+ for line in path.read_text(encoding="utf-8").splitlines()[:20]:
90
+ match = re.match(r"# gallery_(\w+):\s*(.+)", line)
91
+ if match:
92
+ metadata[match.group(1)] = match.group(2).strip()
93
+ return metadata
94
+
95
+
96
+ def _required_category(path: Path) -> str:
97
+ metadata = _metadata(path)
98
+ category = metadata.get("category")
99
+ if category:
100
+ return category
101
+ relative = path.relative_to(PROJECT_ROOT)
102
+ raise ValueError(f"{relative} is missing a required '# gallery_category: ...' tag")
103
+
104
+
105
+ def discover_examples() -> list[Example]:
106
+ examples = []
107
+ for path in sorted(EXAMPLES_DIR.rglob("example_*.py")):
108
+ title, description = _title_and_description(path)
109
+ examples.append(
110
+ Example(
111
+ path,
112
+ title,
113
+ description,
114
+ _required_category(path),
115
+ )
116
+ )
117
+ return examples
118
+
119
+
120
+ def render_gallery(examples: list[Example]) -> str:
121
+ grouped: dict[str, list[Example]] = defaultdict(list)
122
+ for example in examples:
123
+ grouped[example.category].append(example)
124
+
125
+ categories = list(CATEGORY_ORDER)
126
+ categories.extend(sorted(set(grouped) - set(categories)))
127
+
128
+ lines = [
129
+ ".. This file is generated by docs/example_gallery.py. Do not edit it manually.",
130
+ "",
131
+ "================",
132
+ "Examples Gallery",
133
+ "================",
134
+ "",
135
+ "Rendered Marimo notebooks demonstrating ``pyFDN``. The gallery is generated",
136
+ "automatically from every ``example_*.py`` file under ``examples/``.",
137
+ "",
138
+ ]
139
+
140
+ for category in categories:
141
+ entries = grouped.get(category, [])
142
+ if not entries:
143
+ continue
144
+ lines.extend(
145
+ [
146
+ category,
147
+ "-" * len(category),
148
+ "",
149
+ ".. list-table::",
150
+ " :widths: 35 65",
151
+ " :header-rows: 0",
152
+ "",
153
+ ]
154
+ )
155
+ for example in sorted(entries, key=lambda item: item.title.casefold()):
156
+ url = f"_static/marimo/notebooks/{example.output_name}.html"
157
+ lines.extend(
158
+ [
159
+ f" * - `{example.title} <{url}>`_",
160
+ f" - {example.description}",
161
+ ]
162
+ )
163
+ lines.append("")
164
+
165
+ lines.extend(
166
+ [
167
+ "----",
168
+ "",
169
+ "All notebooks can also be browsed in the repository's",
170
+ "`examples directory <https://github.com/artificial-audio/pyFDN/tree/main/examples>`_.",
171
+ "",
172
+ ]
173
+ )
174
+ return "\n".join(lines)
175
+
176
+
177
+ def generate_gallery(output_file: Path = OUTPUT_FILE) -> str:
178
+ rendered = render_gallery(discover_examples())
179
+ output_file.write_text(rendered, encoding="utf-8")
180
+ return rendered
181
+
182
+
183
+ if __name__ == "__main__":
184
+ generate_gallery()