auvux-dsp 0.1.0.dev0__tar.gz → 0.1.0.dev2__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 (129) hide show
  1. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/.gitignore +5 -0
  2. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/CMakeLists.txt +20 -3
  3. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/PKG-INFO +11 -5
  4. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/README.md +8 -4
  5. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/benchmarks/benchmark.py +57 -58
  6. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/pyproject.toml +9 -1
  7. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/__init__.py +39 -1
  8. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_dispatch.py +39 -20
  9. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_functional.py +188 -1
  10. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_torch.py +22 -20
  11. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_transforms.py +282 -0
  12. auvux_dsp-0.1.0.dev2/python/auvux/dsp/_version.py +1 -0
  13. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/abi.hpp +1 -1
  14. auvux_dsp-0.1.0.dev2/src/bindings/bind_features.cpp +475 -0
  15. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/bind_stft.cpp +4 -5
  16. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/module.cpp +2 -0
  17. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/pooled_array.hpp +3 -3
  18. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/common/dlpack_bridge.hpp +3 -3
  19. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cuda/cuda_common.cu +2 -2
  20. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cuda/cuda_common.cuh +4 -2
  21. auvux_dsp-0.1.0.dev2/src/gpu/cuda/cuda_features.cu +1080 -0
  22. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cuda/cuda_stft.cu +2 -1
  23. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/gpu.hpp +28 -0
  24. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/gpu_stub.cpp +5 -0
  25. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/kernels/common.metal +14 -0
  26. auvux_dsp-0.1.0.dev2/src/gpu/metal/kernels/flux.metal +108 -0
  27. auvux_dsp-0.1.0.dev2/src/gpu/metal/kernels/moments.metal +219 -0
  28. auvux_dsp-0.1.0.dev2/src/gpu/metal/kernels/onset.metal +67 -0
  29. auvux_dsp-0.1.0.dev2/src/gpu/metal/kernels/rms.metal +92 -0
  30. auvux_dsp-0.1.0.dev2/src/gpu/metal/kernels/tempogram.metal +116 -0
  31. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/metal_common.h +8 -0
  32. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/metal_common.mm +21 -0
  33. auvux_dsp-0.1.0.dev2/src/gpu/metal/metal_flux.mm +187 -0
  34. auvux_dsp-0.1.0.dev2/src/gpu/metal/metal_moments.mm +176 -0
  35. auvux_dsp-0.1.0.dev2/src/gpu/metal/metal_onset.mm +158 -0
  36. auvux_dsp-0.1.0.dev2/src/gpu/metal/metal_rms.mm +158 -0
  37. auvux_dsp-0.1.0.dev2/src/gpu/metal/metal_tempogram.mm +169 -0
  38. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/chroma/chroma_cpu.cpp +19 -6
  39. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/cqt/cqt.hpp +6 -0
  40. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/cqt/cqt_cpu.cpp +102 -7
  41. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/cqt/cqt_filterbank.cpp +1 -1
  42. auvux_dsp-0.1.0.dev2/src/ops/db.hpp +119 -0
  43. auvux_dsp-0.1.0.dev2/src/ops/flux/flux.hpp +30 -0
  44. auvux_dsp-0.1.0.dev2/src/ops/flux/flux_cpu.cpp +141 -0
  45. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/mel/mel_cpu.cpp +12 -3
  46. auvux_dsp-0.1.0.dev2/src/ops/moments/moments.hpp +58 -0
  47. auvux_dsp-0.1.0.dev2/src/ops/moments/moments_cpu.cpp +276 -0
  48. auvux_dsp-0.1.0.dev2/src/ops/onset/onset.hpp +30 -0
  49. auvux_dsp-0.1.0.dev2/src/ops/onset/onset_cpu.cpp +77 -0
  50. auvux_dsp-0.1.0.dev2/src/ops/rms/rms.hpp +36 -0
  51. auvux_dsp-0.1.0.dev2/src/ops/rms/rms_cpu.cpp +77 -0
  52. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/stft/stft_cpu.cpp +7 -5
  53. auvux_dsp-0.1.0.dev2/src/ops/tempogram/tempogram.hpp +41 -0
  54. auvux_dsp-0.1.0.dev2/src/ops/tempogram/tempogram_cpu.cpp +147 -0
  55. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/window.hpp +1 -1
  56. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_adjoint.py +52 -0
  57. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_api.py +91 -0
  58. auvux_dsp-0.1.0.dev2/tests/test_dynamics.py +88 -0
  59. auvux_dsp-0.1.0.dev2/tests/test_gpu_features.py +248 -0
  60. auvux_dsp-0.1.0.dev2/tests/test_moments.py +87 -0
  61. auvux_dsp-0.1.0.dev2/tests/test_onset_tempo.py +100 -0
  62. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_vqt.py +8 -1
  63. auvux_dsp-0.1.0.dev2/tests/torch_refs.py +236 -0
  64. auvux_dsp-0.1.0.dev0/.env +0 -1
  65. auvux_dsp-0.1.0.dev0/python/auvux/dsp/_version.py +0 -1
  66. auvux_dsp-0.1.0.dev0/tests/torch_refs.py +0 -110
  67. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/.clang-format +0 -0
  68. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/.clangd +0 -0
  69. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/.github/workflows/ci.yml +0 -0
  70. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/.github/workflows/wheels.yml +0 -0
  71. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/LICENSE +0 -0
  72. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/THIRD_PARTY_LICENSES +0 -0
  73. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/cmake/embed_text.cmake +0 -0
  74. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_convert.py +0 -0
  75. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_filters.py +0 -0
  76. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/_transform.py +0 -0
  77. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/python/auvux/dsp/py.typed +0 -0
  78. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/scripts/dev-build.ps1 +0 -0
  79. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/scripts/dev-build.sh +0 -0
  80. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/bind_cqt.cpp +0 -0
  81. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/bind_fft.cpp +0 -0
  82. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/bind_mel.cpp +0 -0
  83. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/bindings/bind_util.cpp +0 -0
  84. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/common/host_pool.cpp +0 -0
  85. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/common/host_pool.hpp +0 -0
  86. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/common/threadpool.cpp +0 -0
  87. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/common/threadpool.hpp +0 -0
  88. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/fft/fft.cpp +0 -0
  89. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/fft/fft.hpp +0 -0
  90. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/fft/fft_impl.hpp +0 -0
  91. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/fft/fft_pffft.cpp +0 -0
  92. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/fft/fft_vdsp.cpp +0 -0
  93. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cqt_plan.cpp +0 -0
  94. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cqt_plan.hpp +0 -0
  95. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cuda/cuda_cqt.cu +0 -0
  96. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/cuda/cuda_mel.cu +0 -0
  97. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/gpu_common.hpp +0 -0
  98. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/kernels/cqt.metal +0 -0
  99. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/kernels/mel.metal +0 -0
  100. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/kernels/stft.metal +0 -0
  101. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/metal_cqt.mm +0 -0
  102. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/metal_mel.mm +0 -0
  103. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/gpu/metal/metal_stft.mm +0 -0
  104. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/chroma/chroma.hpp +0 -0
  105. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/cqt/cqt_filterbank.hpp +0 -0
  106. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/frame.hpp +0 -0
  107. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/istft/istft.hpp +0 -0
  108. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/istft/istft_cpu.cpp +0 -0
  109. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/mel/mel.hpp +0 -0
  110. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/mel/mel_filterbank.cpp +0 -0
  111. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/ola.cpp +0 -0
  112. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/ola.hpp +0 -0
  113. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/stft/stft.hpp +0 -0
  114. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/types.hpp +0 -0
  115. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/ops/window.cpp +0 -0
  116. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/third_party/dlpack.h +0 -0
  117. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/third_party/pffft.c +0 -0
  118. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/src/third_party/pffft.h +0 -0
  119. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_chroma.py +0 -0
  120. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_cqt.py +0 -0
  121. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_fft.py +0 -0
  122. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_gpu.py +0 -0
  123. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_grad.py +0 -0
  124. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_istft.py +0 -0
  125. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_mel.py +0 -0
  126. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_mfcc.py +0 -0
  127. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_namespace.py +0 -0
  128. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_resident.py +0 -0
  129. {auvux_dsp-0.1.0.dev0 → auvux_dsp-0.1.0.dev2}/tests/test_stft.py +0 -0
@@ -9,3 +9,8 @@ __pycache__/
9
9
  .mypy_cache/
10
10
  .ruff_cache/
11
11
  .DS_Store
12
+ .env
13
+ .env.*
14
+ *.env
15
+ .pypirc
16
+ *.pem
@@ -49,6 +49,11 @@ pybind11_add_module(_native
49
49
  src/ops/cqt/cqt_filterbank.cpp
50
50
  src/ops/cqt/cqt_cpu.cpp
51
51
  src/ops/chroma/chroma_cpu.cpp
52
+ src/ops/moments/moments_cpu.cpp
53
+ src/ops/rms/rms_cpu.cpp
54
+ src/ops/flux/flux_cpu.cpp
55
+ src/ops/onset/onset_cpu.cpp
56
+ src/ops/tempogram/tempogram_cpu.cpp
52
57
  src/gpu/gpu_stub.cpp
53
58
  src/gpu/cqt_plan.cpp
54
59
  src/third_party/pffft.c
@@ -57,6 +62,7 @@ pybind11_add_module(_native
57
62
  src/bindings/bind_stft.cpp
58
63
  src/bindings/bind_mel.cpp
59
64
  src/bindings/bind_cqt.cpp
65
+ src/bindings/bind_features.cpp
60
66
  src/bindings/bind_util.cpp)
61
67
  target_include_directories(_native PRIVATE src src/third_party)
62
68
  target_link_libraries(_native PRIVATE Threads::Threads)
@@ -90,7 +96,12 @@ if(AUVUX_GPU STREQUAL "metal")
90
96
  ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/common.metal
91
97
  ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/stft.metal
92
98
  ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/mel.metal
93
- ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/cqt.metal)
99
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/cqt.metal
100
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/rms.metal
101
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/moments.metal
102
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/flux.metal
103
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/onset.metal
104
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/metal/kernels/tempogram.metal)
94
105
  set(auvux_metal_gen ${CMAKE_CURRENT_BINARY_DIR}/generated/auvux_metal_kernels.h)
95
106
  string(JOIN "," auvux_metal_kernels_arg ${auvux_metal_kernels})
96
107
  add_custom_command(
@@ -104,7 +115,12 @@ if(AUVUX_GPU STREQUAL "metal")
104
115
  src/gpu/metal/metal_common.mm
105
116
  src/gpu/metal/metal_stft.mm
106
117
  src/gpu/metal/metal_mel.mm
107
- src/gpu/metal/metal_cqt.mm)
118
+ src/gpu/metal/metal_cqt.mm
119
+ src/gpu/metal/metal_rms.mm
120
+ src/gpu/metal/metal_moments.mm
121
+ src/gpu/metal/metal_flux.mm
122
+ src/gpu/metal/metal_onset.mm
123
+ src/gpu/metal/metal_tempogram.mm)
108
124
  target_sources(_native PRIVATE ${auvux_metal_srcs} ${auvux_metal_gen})
109
125
  set_source_files_properties(${auvux_metal_srcs} PROPERTIES COMPILE_OPTIONS "-fobjc-arc")
110
126
  target_include_directories(_native PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
@@ -125,7 +141,8 @@ elseif(AUVUX_GPU STREQUAL "cuda")
125
141
  src/gpu/cuda/cuda_common.cu
126
142
  src/gpu/cuda/cuda_stft.cu
127
143
  src/gpu/cuda/cuda_mel.cu
128
- src/gpu/cuda/cuda_cqt.cu)
144
+ src/gpu/cuda/cuda_cqt.cu
145
+ src/gpu/cuda/cuda_features.cu)
129
146
  target_compile_definitions(_native PRIVATE AUVUX_CUDA=1)
130
147
  if(NOT auvux_user_archs)
131
148
  # Distributable fatbin: SASS per supported generation plus PTX (the plain
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: auvux-dsp
3
- Version: 0.1.0.dev0
3
+ Version: 0.1.0.dev2
4
4
  Summary: Fast differentiable audio transforms (STFT, mel, MFCC, CQT, chroma) on CPU and GPU
5
5
  Keywords: audio,dsp,stft,mel,mfcc,cqt,chroma,spectrogram,gpu
6
6
  Author-Email: Peter Kiers <pkiers.1983@gmail.com>
@@ -17,15 +17,18 @@ Requires-Dist: pytest; extra == "test"
17
17
  Requires-Dist: librosa; extra == "test"
18
18
  Requires-Dist: soxr; extra == "test"
19
19
  Requires-Dist: torch; extra == "test"
20
+ Provides-Extra: wheeltest
21
+ Requires-Dist: pytest; extra == "wheeltest"
20
22
  Description-Content-Type: text/markdown
21
23
 
22
24
  # auvux-dsp
23
25
 
24
26
  Fast differentiable audio transforms (STFT, iSTFT, mel, MFCC, CQT/VQT, chroma)
25
- with native CPU backends (vDSP on macOS, PFFFT elsewhere) and GPU support
26
- (Metal, CUDA). Forward *and* backward passes run in native kernels; PyTorch
27
- autograd plugs in when you pass a torch tensor, and torch is never required
28
- otherwise.
27
+ plus differentiable scalar/temporal features (spectral moments & flatness, RMS,
28
+ spectral flux, onset strength, tempogram) with native CPU backends (vDSP on
29
+ macOS, PFFFT elsewhere) and GPU support (Metal, CUDA). Forward *and* backward
30
+ passes run in native kernels; PyTorch autograd plugs in when you pass a torch
31
+ tensor, and torch is never required otherwise.
29
32
 
30
33
  ```python
31
34
  import auvux.dsp as dsp
@@ -69,6 +72,9 @@ outputs (power/db/mel/...) are ordinary C-contiguous arrays.
69
72
  Status: under construction.
70
73
  - CPU (vDSP/PFFFT): STFT, ISTFT, MelSpectrogram, MFCC, CQT, VQT, Chroma —
71
74
  forward and native backward, librosa-parity tested, torch autograd built in.
75
+ - Differentiable features: SpectralMoments (centroid, bandwidth, skewness,
76
+ kurtosis, flatness), RMS, SpectralFlux, OnsetStrength, Tempogram — native
77
+ forward + analytic adjoint, librosa-parity / torch-grad tested.
72
78
  - Metal: all of the above on GPU (n_fft <= 4096), forward + backward,
73
79
  parity-tested against the CPU path. torch MPS tensors stay on the GPU end to
74
80
  end (DLPack), and backend="auto" routes them there — no flags needed.
@@ -1,10 +1,11 @@
1
1
  # auvux-dsp
2
2
 
3
3
  Fast differentiable audio transforms (STFT, iSTFT, mel, MFCC, CQT/VQT, chroma)
4
- with native CPU backends (vDSP on macOS, PFFFT elsewhere) and GPU support
5
- (Metal, CUDA). Forward *and* backward passes run in native kernels; PyTorch
6
- autograd plugs in when you pass a torch tensor, and torch is never required
7
- otherwise.
4
+ plus differentiable scalar/temporal features (spectral moments & flatness, RMS,
5
+ spectral flux, onset strength, tempogram) with native CPU backends (vDSP on
6
+ macOS, PFFFT elsewhere) and GPU support (Metal, CUDA). Forward *and* backward
7
+ passes run in native kernels; PyTorch autograd plugs in when you pass a torch
8
+ tensor, and torch is never required otherwise.
8
9
 
9
10
  ```python
10
11
  import auvux.dsp as dsp
@@ -48,6 +49,9 @@ outputs (power/db/mel/...) are ordinary C-contiguous arrays.
48
49
  Status: under construction.
49
50
  - CPU (vDSP/PFFFT): STFT, ISTFT, MelSpectrogram, MFCC, CQT, VQT, Chroma —
50
51
  forward and native backward, librosa-parity tested, torch autograd built in.
52
+ - Differentiable features: SpectralMoments (centroid, bandwidth, skewness,
53
+ kurtosis, flatness), RMS, SpectralFlux, OnsetStrength, Tempogram — native
54
+ forward + analytic adjoint, librosa-parity / torch-grad tested.
51
55
  - Metal: all of the above on GPU (n_fft <= 4096), forward + backward,
52
56
  parity-tested against the CPU path. torch MPS tensors stay on the GPU end to
53
57
  end (DLPack), and backend="auto" routes them there — no flags needed.
@@ -1,10 +1,10 @@
1
- """Benchmark auvux-dsp against librosa, torchaudio, and nnAudio.
1
+ """Benchmark auvux-dsp against librosa and torchaudio.
2
2
 
3
3
  Forward passes and torch training steps (forward + sum().backward()) per
4
4
  transform, implementation, and signal length. auvux runs on cpu, host-staged
5
- gpu, and gpu-resident torch tensors; torchaudio and nnAudio also run on the
6
- torch device when one is available. Progress prints as it runs; the summary
7
- tables are markdown, ready for the README.
5
+ gpu, and gpu-resident torch tensors; torchaudio also runs on the torch device
6
+ when one is available. Progress prints as it runs; the summary tables are
7
+ markdown, ready for the README.
8
8
 
9
9
  python benchmarks/benchmark.py # everything available
10
10
  python benchmarks/benchmark.py --quick # 1 repeat, short signals
@@ -95,16 +95,33 @@ def build_cases(y, sec, torch, device, gpu, resident):
95
95
  ("vqt", vqt_t),
96
96
  ("chroma", chroma_t),
97
97
  ]
98
+ # CPU-only differentiable features (no GPU kernels yet). librosa comparisons
99
+ # are registered below only for the 1:1-matching definitions.
100
+ feat = [
101
+ ("spectral_moments", dsp.SpectralMoments(sr=SR, n_fft=2048, hop_length=512)),
102
+ ("spectral_centroid",
103
+ dsp.SpectralMoments(sr=SR, features=("centroid",), n_fft=2048, hop_length=512)),
104
+ ("spectral_bandwidth",
105
+ dsp.SpectralMoments(sr=SR, features=("bandwidth",), n_fft=2048, hop_length=512)),
106
+ ("spectral_flatness",
107
+ dsp.SpectralMoments(sr=SR, features=("flatness",), n_fft=2048, hop_length=512)),
108
+ ("rms", dsp.RMS(sr=SR, frame_length=2048, hop_length=512)),
109
+ ("spectral_flux", dsp.SpectralFlux(sr=SR, n_fft=2048, hop_length=512)),
110
+ ("onset_strength", dsp.OnsetStrength(sr=SR, n_fft=2048, hop_length=512, n_mels=128)),
111
+ ("tempogram", dsp.Tempogram(sr=SR, n_fft=2048, hop_length=512, n_mels=128)),
112
+ ]
98
113
 
99
114
  for name, t in fwd:
100
115
  add("forward", name, "auvux cpu", lambda t=t: t(y))
116
+ for name, t in feat:
117
+ add("forward", name, "auvux cpu", lambda t=t: t(y))
101
118
  if gpu:
102
- for name, t in fwd:
119
+ for name, t in fwd + feat:
103
120
  add("forward", name, f"auvux {gpu} staged", lambda t=t: t(y, backend="gpu"))
104
121
  if resident:
105
122
  sync = syncer(torch, device)
106
123
  ytd = torch.from_numpy(y).to(device)
107
- for name, t in fwd:
124
+ for name, t in fwd + feat:
108
125
  add(
109
126
  "forward",
110
127
  name,
@@ -168,6 +185,25 @@ def build_cases(y, sec, torch, device, gpu, resident):
168
185
  "librosa",
169
186
  lambda: librosa.istft(Z, n_fft=2048, hop_length=512, length=n_out),
170
187
  )
188
+ # Features whose definitions match librosa 1:1, so the diff column is a
189
+ # real correctness check.
190
+ add("forward", "spectral_centroid", "librosa",
191
+ lambda: librosa.feature.spectral_centroid(y=y, sr=SR, n_fft=2048, hop_length=512))
192
+ add("forward", "spectral_bandwidth", "librosa",
193
+ lambda: librosa.feature.spectral_bandwidth(y=y, sr=SR, n_fft=2048, hop_length=512))
194
+ add("forward", "spectral_flatness", "librosa",
195
+ lambda: librosa.feature.spectral_flatness(y=y, n_fft=2048, hop_length=512))
196
+ add("forward", "rms", "librosa",
197
+ lambda: librosa.feature.rms(y=y, frame_length=2048, hop_length=512))
198
+ # Speed-only: our onset/tempogram match librosa's values but not its
199
+ # `center=True` envelope lag-shift (n_fft//(2*hop) frames), so the diff
200
+ # is suppressed (see _NO_DIFF) — correctness lives in the test suite.
201
+ # librosa has no standalone spectral_flux.
202
+ add("forward", "onset_strength", "librosa",
203
+ lambda: librosa.onset.onset_strength(
204
+ y=y, sr=SR, n_fft=2048, hop_length=512, n_mels=128))
205
+ add("forward", "tempogram", "librosa",
206
+ lambda: librosa.feature.tempogram(y=y, sr=SR, hop_length=512, win_length=384))
171
207
 
172
208
  if torch is not None and try_import("torchaudio"):
173
209
  import torchaudio.transforms as T
@@ -233,34 +269,6 @@ def build_cases(y, sec, torch, device, gpu, resident):
233
269
  lambda td=td: (td(ytd), sync()),
234
270
  )
235
271
 
236
- if torch is not None and try_import("nnAudio"):
237
- from nnAudio import features
238
-
239
- def nn_make():
240
- return {
241
- "mel_spectrogram": features.MelSpectrogram(
242
- sr=SR,
243
- n_fft=2048,
244
- hop_length=512,
245
- n_mels=128,
246
- pad_mode="constant",
247
- verbose=False,
248
- ),
249
- "cqt": features.CQT(
250
- sr=SR, hop_length=512, n_bins=84, pad_mode="constant", verbose=False
251
- ),
252
- }
253
-
254
- yt1 = torch.from_numpy(y)[None]
255
- for name, t in nn_make().items():
256
- add("forward", name, "nnAudio cpu", lambda t=t: t(yt1))
257
- if device:
258
- sync = syncer(torch, device)
259
- ytd1 = yt1.to(device)
260
- for name, t in nn_make().items():
261
- td = t.to(device)
262
- add("forward", name, f"nnAudio {device} resident", lambda td=td: (td(ytd1), sync()))
263
-
264
272
  if torch is None:
265
273
  return cases
266
274
 
@@ -287,16 +295,16 @@ def build_cases(y, sec, torch, device, gpu, resident):
287
295
  ("chroma", chroma_t),
288
296
  ]
289
297
 
290
- for name, t in train:
298
+ for name, t in train + feat:
291
299
  add("training step", name, "auvux cpu", step(t, yt, no_sync))
292
300
  if gpu:
293
- for name, t in train:
301
+ for name, t in train + feat:
294
302
  staged = step(lambda x, t=t: t(x, backend="gpu"), yt, no_sync)
295
303
  add("training step", name, f"auvux {gpu} staged", staged)
296
304
  if resident:
297
305
  sync = syncer(torch, device)
298
306
  ytd = torch.from_numpy(y).to(device)
299
- for name, t in train:
307
+ for name, t in train + feat:
300
308
  add(
301
309
  "training step",
302
310
  name,
@@ -384,25 +392,13 @@ def build_cases(y, sec, torch, device, gpu, resident):
384
392
  step(lambda x: inv_td(x, length=n_out), Ztd_ta, sync),
385
393
  )
386
394
 
387
- if try_import("nnAudio"):
388
- from nnAudio import features
395
+ return cases
389
396
 
390
- ncqt = features.CQT(sr=SR, hop_length=512, n_bins=84, pad_mode="constant", verbose=False)
391
- add("training step", "cqt", "nnAudio cpu", step(lambda x: ncqt(x[None]), yt, no_sync))
392
- if device:
393
- sync = syncer(torch, device)
394
- ncqt_d = features.CQT(
395
- sr=SR, hop_length=512, n_bins=84, pad_mode="constant", verbose=False
396
- ).to(device)
397
- ytd = torch.from_numpy(y).to(device)
398
- add(
399
- "training step",
400
- "cqt",
401
- f"nnAudio {device} resident",
402
- step(lambda x: ncqt_d(x[None]), ytd, sync),
403
- )
404
397
 
405
- return cases
398
+ # Transforms compared against librosa for speed only: their definitions match
399
+ # librosa's values but not its envelope lag-shift convention, so a relerr "diff"
400
+ # would read as a (misleading) failure rather than a correctness check.
401
+ _NO_DIFF = frozenset({"onset_strength", "tempogram"})
406
402
 
407
403
 
408
404
  def run_cases(cases, repeats, warmup):
@@ -440,7 +436,10 @@ def run_cases(cases, repeats, warmup):
440
436
  ts.append(time.perf_counter() - t0)
441
437
  if rep == repeats - 1:
442
438
  outs[id(c)] = r
443
- ref = next((to_numpy(outs[id(c)]) for c, _, _ in live if c.impl == "librosa"), None)
439
+ grp_tr = live[0][0].transform if live else ""
440
+ ref = None
441
+ if grp_tr not in _NO_DIFF:
442
+ ref = next((to_numpy(outs[id(c)]) for c, _, _ in live if c.impl == "librosa"), None)
444
443
  if ref is not None:
445
444
  for c, _, _ in live:
446
445
  if c.impl == "librosa":
@@ -466,7 +465,7 @@ def to_numpy(out):
466
465
  if hasattr(out, "detach"):
467
466
  out = out.detach().cpu().numpy()
468
467
  out = np.asarray(out)
469
- while out.ndim > 2 and out.shape[0] == 1: # nnAudio keeps a batch dim
468
+ while out.ndim > 2 and out.shape[0] == 1: # drop a leading singleton batch dim
470
469
  out = out[0]
471
470
  return out
472
471
 
@@ -521,7 +520,7 @@ def print_table(title, col_names, rows):
521
520
  print(line(r))
522
521
 
523
522
 
524
- _LIB_ORDER = ("auvux", "librosa", "torchaudio", "nnAudio")
523
+ _LIB_ORDER = ("auvux", "librosa", "torchaudio")
525
524
  _PATH_ORDER = ("cpu", "gpu staged", "gpu resident")
526
525
 
527
526
 
@@ -629,7 +628,7 @@ def main():
629
628
 
630
629
  torch, device, gpu, resident = detect_env()
631
630
  gpu_s = f"{gpu} ({dsp.backend_info(gpu)['device_name']})" if gpu else "none"
632
- rivals = ("librosa", "torchaudio", "nnAudio")
631
+ rivals = ("librosa", "torchaudio")
633
632
  libs = ", ".join(f"{n} {version_of(n)}" for n in rivals if try_import(n))
634
633
  print(f"auvux-dsp {dsp.__version__} benchmark")
635
634
  threads = dsp.get_num_threads() or "auto"
@@ -20,13 +20,21 @@ classifiers = [
20
20
  ]
21
21
 
22
22
  [project.optional-dependencies]
23
+ # Full parity/grad suite: librosa is the forward oracle, torch the autograd
24
+ # oracle. Used in dev and native-arch CI.
23
25
  test = ["pytest", "librosa", "soxr", "torch"]
26
+ # Lean smoke test for the per-wheel cibuildwheel run: just confirm the compiled
27
+ # extension loads and the native paths run. The librosa/torch oracle tests
28
+ # importorskip and skip cleanly without those (heavy, and unbuildable on some
29
+ # wheel targets — numba/llvmlite and torch have no x86_64-macOS wheels).
30
+ wheeltest = ["pytest"]
24
31
 
25
32
  [tool.scikit-build]
26
33
  minimum-version = "build-system.requires"
27
34
  wheel.packages = ["python/auvux"]
28
35
  cmake.version = ">=3.18"
29
36
  build-dir = "build/{wheel_tag}"
37
+ sdist.exclude = [".env", ".env.*", "*.env", ".pypirc", "*.pem", "wheelhouse", "dist"]
30
38
 
31
39
  [tool.scikit-build.metadata.version]
32
40
  provider = "scikit_build_core.metadata.regex"
@@ -35,7 +43,7 @@ input = "python/auvux/dsp/_version.py"
35
43
  [tool.cibuildwheel]
36
44
  build = "cp310-* cp311-* cp312-* cp313-*"
37
45
  skip = "*-musllinux* *_i686 *-win32"
38
- test-requires = "pytest"
46
+ test-extras = ["wheeltest"]
39
47
  test-command = "pytest {project}/tests -q"
40
48
 
41
49
  [tool.cibuildwheel.macos]
@@ -26,22 +26,50 @@ from auvux.dsp._functional import (
26
26
  istft,
27
27
  mel_spectrogram,
28
28
  mfcc,
29
+ onset_strength,
30
+ rms,
31
+ spectral_bandwidth,
32
+ spectral_centroid,
33
+ spectral_flatness,
34
+ spectral_flux,
35
+ spectral_kurtosis,
36
+ spectral_moments,
37
+ spectral_skewness,
29
38
  stft,
39
+ tempogram,
30
40
  vqt,
31
41
  )
32
42
  from auvux.dsp._transform import Transform
33
- from auvux.dsp._transforms import CQT, ISTFT, MFCC, STFT, VQT, Chroma, MelSpectrogram
43
+ from auvux.dsp._transforms import (
44
+ CQT,
45
+ ISTFT,
46
+ MFCC,
47
+ RMS,
48
+ STFT,
49
+ VQT,
50
+ Chroma,
51
+ MelSpectrogram,
52
+ OnsetStrength,
53
+ SpectralFlux,
54
+ SpectralMoments,
55
+ Tempogram,
56
+ )
34
57
  from auvux.dsp._version import __version__
35
58
 
36
59
  __all__ = [
37
60
  "CQT",
38
61
  "ISTFT",
39
62
  "MFCC",
63
+ "RMS",
40
64
  "STFT",
41
65
  "VQT",
42
66
  "BackendError",
43
67
  "Chroma",
44
68
  "MelSpectrogram",
69
+ "OnsetStrength",
70
+ "SpectralFlux",
71
+ "SpectralMoments",
72
+ "Tempogram",
45
73
  "Transform",
46
74
  "__version__",
47
75
  "amplitude_to_db",
@@ -65,8 +93,18 @@ __all__ = [
65
93
  "mel_spectrogram",
66
94
  "mel_to_hz",
67
95
  "mfcc",
96
+ "onset_strength",
68
97
  "power_to_db",
98
+ "rms",
69
99
  "set_num_threads",
100
+ "spectral_bandwidth",
101
+ "spectral_centroid",
102
+ "spectral_flatness",
103
+ "spectral_flux",
104
+ "spectral_kurtosis",
105
+ "spectral_moments",
106
+ "spectral_skewness",
70
107
  "stft",
108
+ "tempogram",
71
109
  "vqt",
72
110
  ]
@@ -17,7 +17,7 @@ class BackendError(RuntimeError):
17
17
  # Refuse to run against a compiled core older than these Python sources;
18
18
  # editable installs do not rebuild the extension on source changes.
19
19
  # Keep in lockstep with kAbiVersion in src/bindings/abi.hpp.
20
- _EXPECTED_ABI = 3
20
+ _EXPECTED_ABI = 12
21
21
  if getattr(_native, "abi_version", lambda: 0)() != _EXPECTED_ABI:
22
22
  raise ImportError(
23
23
  "auvux.dsp's compiled core is out of date with its Python sources; "
@@ -102,7 +102,10 @@ def backend_info(name: str) -> dict[str, Any]:
102
102
  "available": True,
103
103
  "device_name": "",
104
104
  "supports": {
105
- "transforms": ["stft", "istft", "mel_spectrogram", "mfcc", "cqt", "vqt", "chroma"],
105
+ "transforms": [
106
+ "stft", "istft", "mel_spectrogram", "mfcc", "cqt", "vqt", "chroma",
107
+ "spectral_moments", "rms", "spectral_flux", "onset_strength", "tempogram",
108
+ ],
106
109
  "n_fft": "any power of two >= 32",
107
110
  },
108
111
  }
@@ -115,7 +118,10 @@ def backend_info(name: str) -> dict[str, Any]:
115
118
  "supports": {
116
119
  # MFCC rides on the GPU mel; istft is resident-only (device
117
120
  # tensors in, device tensors out).
118
- "transforms": ["stft", "istft", "mel_spectrogram", "mfcc", "cqt", "vqt", "chroma"],
121
+ "transforms": [
122
+ "stft", "istft", "mel_spectrogram", "mfcc", "cqt", "vqt", "chroma", "rms",
123
+ "spectral_moments", "spectral_flux", "onset_strength", "tempogram",
124
+ ],
119
125
  "n_fft": "powers of two in [64, 4096]",
120
126
  "cqt": "hop_length divisible by 2**(n_octaves - 1)",
121
127
  },
@@ -198,12 +204,22 @@ def rewrap(out: np.ndarray, lead: tuple[int, ...], mod: str, device: Any = None)
198
204
  return out
199
205
 
200
206
 
201
- def _stream_for(kind: str) -> int:
202
- if kind == "cuda":
207
+ def _stream_for(t: Any, kind: str) -> int:
208
+ if kind != "cuda":
209
+ return 0
210
+ if array_module(t) == "torch":
203
211
  import torch
204
212
 
205
- return int(torch.cuda.current_stream().cuda_stream) or 1
206
- return 0
213
+ return int(torch.cuda.current_stream(getattr(t, "device", None)).cuda_stream) or 1
214
+ import cupy
215
+
216
+ return int(cupy.cuda.get_current_stream().ptr) or 1
217
+
218
+
219
+ def _dlpack_capsule(t: Any, kind: str, stream: int) -> Any:
220
+ if kind == "cuda":
221
+ return t.__dlpack__(stream=stream)
222
+ return t.__dlpack__()
207
223
 
208
224
 
209
225
  def _frame_major(strides: tuple[int, ...], shape: tuple[int, ...], elem: int) -> bool:
@@ -225,13 +241,14 @@ def export_dlpack(
225
241
  cplx: bool = False,
226
242
  sync: bool = True,
227
243
  frame_major: bool = False,
244
+ stream: int | None = None,
228
245
  ) -> Any:
229
- """GPU array (..., tail) -> (capsule, lead shape, keepalive) or None when
230
- the array library cannot export DLPack here (then callers stage to host).
231
- sync=False skips the device sync when an immediately preceding export
232
- already drained the queue. frame_major=True normalizes a (..., bins,
233
- frames) spectrum to the STFT/ISTFT engines' frame-major contract; other
234
- engines (CQT complex included) take compact tensors."""
246
+ """GPU array (..., tail) -> (capsule, lead shape, keepalive, stream) or
247
+ None when the array library cannot export DLPack here (then callers stage
248
+ to host). sync=False skips the device sync when an immediately preceding
249
+ export already drained the queue. frame_major=True normalizes a (...,
250
+ bins, frames) spectrum to the STFT/ISTFT engines' frame-major contract;
251
+ other engines (CQT complex included) take compact tensors."""
235
252
  m = array_module(t)
236
253
  lead = tuple(t.shape[:-trailing])
237
254
  tail = tuple(t.shape[-trailing:])
@@ -252,8 +269,9 @@ def export_dlpack(
252
269
  t2 = t2.contiguous()
253
270
  if kind == "metal" and sync:
254
271
  torch.mps.synchronize()
272
+ stream = _stream_for(t2, kind) if stream is None else stream
255
273
  try:
256
- return t2.__dlpack__(), lead, t2
274
+ return _dlpack_capsule(t2, kind, stream), lead, t2, stream
257
275
  except Exception:
258
276
  return None
259
277
  import cupy
@@ -265,8 +283,9 @@ def export_dlpack(
265
283
  t2 = cupy.ascontiguousarray(t2.transpose(0, 2, 1)).transpose(0, 2, 1)
266
284
  else:
267
285
  t2 = cupy.ascontiguousarray(t2)
286
+ stream = _stream_for(t2, kind) if stream is None else stream
268
287
  try:
269
- return t2.__dlpack__(), lead, t2
288
+ return _dlpack_capsule(t2, kind, stream), lead, t2, stream
270
289
  except Exception:
271
290
  return None
272
291
 
@@ -289,8 +308,8 @@ def resident_istft(engine: Any, Z: Any, length: int, kind: str) -> Any:
289
308
  exp = export_dlpack(Z, kind, trailing=2, cplx=True, frame_major=True)
290
309
  if exp is None:
291
310
  return None
292
- cap, lead, _keep = exp
293
- out_cap = engine.forward_dlpack(cap, length, _stream_for(kind))
311
+ cap, lead, _keep, stream = exp
312
+ out_cap = engine.forward_dlpack(cap, length, stream)
294
313
  out = import_dlpack(out_cap, array_module(Z))
295
314
  return out.reshape(lead + tuple(out.shape[1:]))
296
315
 
@@ -300,10 +319,10 @@ def resident_forward(engine: Any, y: Any, output: str | None, kind: str) -> Any:
300
319
  exp = export_dlpack(y, kind, trailing=1)
301
320
  if exp is None:
302
321
  return None
303
- cap, lead, _keep = exp
322
+ cap, lead, _keep, stream = exp
304
323
  if output is None:
305
- out_cap = engine.forward_dlpack(cap, _stream_for(kind))
324
+ out_cap = engine.forward_dlpack(cap, stream)
306
325
  else:
307
- out_cap = engine.forward_dlpack(cap, output, _stream_for(kind))
326
+ out_cap = engine.forward_dlpack(cap, output, stream)
308
327
  out = import_dlpack(out_cap, array_module(y))
309
328
  return out.reshape(lead + tuple(out.shape[1:]))