@siteed/audio-studio 3.0.0 → 3.0.2-beta.1

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 (48) hide show
  1. package/CHANGELOG.md +356 -415
  2. package/README.md +1 -1
  3. package/android/src/main/CMakeLists.txt +3 -0
  4. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +7 -155
  5. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -1
  6. package/build/cjs/AudioAnalysis/audioFeaturesWasm.web.js +165 -0
  7. package/build/cjs/AudioAnalysis/audioFeaturesWasm.web.js.map +1 -0
  8. package/build/cjs/AudioAnalysis/melSpectrogramWasm.js +8 -140
  9. package/build/cjs/AudioAnalysis/melSpectrogramWasm.js.map +1 -1
  10. package/build/cjs/AudioAnalysis/melSpectrogramWasm.web.js +153 -0
  11. package/build/cjs/AudioAnalysis/melSpectrogramWasm.web.js.map +1 -0
  12. package/build/cjs/AudioAnalysis/wasmConfig.js +26 -0
  13. package/build/cjs/AudioAnalysis/wasmConfig.js.map +1 -0
  14. package/build/cjs/index.js +3 -1
  15. package/build/cjs/index.js.map +1 -1
  16. package/build/cjs/prebuilt/wasm/mel-spectrogram.js +18 -0
  17. package/build/esm/AudioAnalysis/audioFeaturesWasm.js +7 -122
  18. package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -1
  19. package/build/esm/AudioAnalysis/audioFeaturesWasm.web.js +127 -0
  20. package/build/esm/AudioAnalysis/audioFeaturesWasm.web.js.map +1 -0
  21. package/build/esm/AudioAnalysis/melSpectrogramWasm.js +8 -107
  22. package/build/esm/AudioAnalysis/melSpectrogramWasm.js.map +1 -1
  23. package/build/esm/AudioAnalysis/melSpectrogramWasm.web.js +115 -0
  24. package/build/esm/AudioAnalysis/melSpectrogramWasm.web.js.map +1 -0
  25. package/build/esm/AudioAnalysis/wasmConfig.js +21 -0
  26. package/build/esm/AudioAnalysis/wasmConfig.js.map +1 -0
  27. package/build/esm/index.js +1 -0
  28. package/build/esm/index.js.map +1 -1
  29. package/build/esm/prebuilt/wasm/mel-spectrogram.js +18 -0
  30. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +3 -15
  31. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -1
  32. package/build/types/AudioAnalysis/audioFeaturesWasm.web.d.ts +24 -0
  33. package/build/types/AudioAnalysis/audioFeaturesWasm.web.d.ts.map +1 -0
  34. package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts +3 -15
  35. package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts.map +1 -1
  36. package/build/types/AudioAnalysis/melSpectrogramWasm.web.d.ts +16 -0
  37. package/build/types/AudioAnalysis/melSpectrogramWasm.web.d.ts.map +1 -0
  38. package/build/types/AudioAnalysis/wasmConfig.d.ts +4 -0
  39. package/build/types/AudioAnalysis/wasmConfig.d.ts.map +1 -0
  40. package/build/types/index.d.ts +1 -0
  41. package/build/types/index.d.ts.map +1 -1
  42. package/package.json +3 -2
  43. package/src/AudioAnalysis/audioFeaturesWasm.ts +18 -179
  44. package/src/AudioAnalysis/audioFeaturesWasm.web.ts +201 -0
  45. package/src/AudioAnalysis/melSpectrogramWasm.ts +23 -169
  46. package/src/AudioAnalysis/melSpectrogramWasm.web.ts +184 -0
  47. package/src/AudioAnalysis/wasmConfig.ts +24 -0
  48. package/src/index.ts +2 -0
@@ -0,0 +1,201 @@
1
+ import type { AudioFeaturesWasmModule } from './audio-features-wasm'
2
+ import { getMelSpectrogramWasmUrl } from './wasmConfig'
3
+
4
+ export interface AudioFeaturesWasmResult {
5
+ spectralCentroid: number
6
+ spectralFlatness: number
7
+ spectralRolloff: number
8
+ spectralBandwidth: number
9
+ mfcc: number[]
10
+ chromagram: number[]
11
+ }
12
+
13
+ let modulePromise: Promise<AudioFeaturesWasmModule> | null = null
14
+
15
+ function getModule(): Promise<AudioFeaturesWasmModule> {
16
+ if (!modulePromise) {
17
+ modulePromise = (async () => {
18
+ const url = getMelSpectrogramWasmUrl()
19
+ // webpackIgnore + @vite-ignore prevent bundlers from trying to resolve the URL
20
+ const mod = await import(/* webpackIgnore: true */ /* @vite-ignore */ url)
21
+ const factory = mod.default ?? mod
22
+ return factory() as Promise<AudioFeaturesWasmModule>
23
+ })().catch((err) => {
24
+ modulePromise = null
25
+ throw err
26
+ })
27
+ }
28
+ return modulePromise
29
+ }
30
+
31
+ // --- Struct layout for CAudioFeaturesResult (wasm32) ---
32
+ // Offset 0: float spectralCentroid (4 bytes)
33
+ // Offset 4: float spectralFlatness (4 bytes)
34
+ // Offset 8: float spectralRolloff (4 bytes)
35
+ // Offset 12: float spectralBandwidth (4 bytes)
36
+ // Offset 16: float* mfcc (4 bytes pointer)
37
+ // Offset 20: int mfccCount (4 bytes)
38
+ // Offset 24: float* chromagram (4 bytes pointer)
39
+ // Offset 28: int chromagramCount (4 bytes)
40
+ const STRUCT_SIZE = 32
41
+
42
+ function readResult(
43
+ Module: AudioFeaturesWasmModule,
44
+ ptr: number
45
+ ): AudioFeaturesWasmResult {
46
+ const spectralCentroid = Module.getValue(ptr, 'float')
47
+ const spectralFlatness = Module.getValue(ptr + 4, 'float')
48
+ const spectralRolloff = Module.getValue(ptr + 8, 'float')
49
+ const spectralBandwidth = Module.getValue(ptr + 12, 'float')
50
+
51
+ const mfccPtr = Module.getValue(ptr + 16, 'i32')
52
+ const mfccCount = Module.getValue(ptr + 20, 'i32')
53
+ const chromaPtr = Module.getValue(ptr + 24, 'i32')
54
+ const chromaCount = Module.getValue(ptr + 28, 'i32')
55
+
56
+ const mfcc: number[] = []
57
+ if (mfccPtr && mfccCount > 0) {
58
+ const offset = mfccPtr >> 2
59
+ for (let i = 0; i < mfccCount; i++) {
60
+ mfcc.push(Module.HEAPF32[offset + i])
61
+ }
62
+ }
63
+
64
+ const chromagram: number[] = []
65
+ if (chromaPtr && chromaCount > 0) {
66
+ const offset = chromaPtr >> 2
67
+ for (let i = 0; i < chromaCount; i++) {
68
+ chromagram.push(Module.HEAPF32[offset + i])
69
+ }
70
+ }
71
+
72
+ return {
73
+ spectralCentroid,
74
+ spectralFlatness,
75
+ spectralRolloff,
76
+ spectralBandwidth,
77
+ mfcc,
78
+ chromagram,
79
+ }
80
+ }
81
+
82
+ // --- Streaming (per-frame) API ---
83
+
84
+ let streamingModule: AudioFeaturesWasmModule | null = null
85
+ let streamingFramePtr = 0
86
+ let streamingFrameCapacity = 0
87
+ let streamingResultPtr = 0
88
+
89
+ /**
90
+ * Initialise the WASM streaming audio features processor.
91
+ * Call once before computeAudioFeaturesFrameWasm().
92
+ */
93
+ export async function initAudioFeaturesWasm(
94
+ sampleRate: number,
95
+ fftLength = 1024,
96
+ nMfcc = 13,
97
+ nMelFilters = 26,
98
+ computeMfcc = true,
99
+ computeChroma = true
100
+ ): Promise<void> {
101
+ const Module = await getModule()
102
+ streamingModule = Module
103
+
104
+ Module._audio_features_init(
105
+ sampleRate,
106
+ fftLength,
107
+ nMfcc,
108
+ nMelFilters,
109
+ computeMfcc ? 1 : 0,
110
+ computeChroma ? 1 : 0
111
+ )
112
+
113
+ // Pre-allocate result struct on WASM heap
114
+ if (streamingResultPtr) Module._free(streamingResultPtr)
115
+ streamingResultPtr = Module._malloc(STRUCT_SIZE)
116
+ // Zero-initialize to prevent freeing garbage pointers on first use
117
+ Module.HEAPU8.fill(0, streamingResultPtr, streamingResultPtr + STRUCT_SIZE)
118
+
119
+ // Frame input buffer allocated on demand
120
+ streamingFrameCapacity = 0
121
+ streamingFramePtr = 0
122
+ }
123
+
124
+ /**
125
+ * Compute audio features for a single frame via WASM C++.
126
+ * Returns null if not initialised or on error.
127
+ */
128
+ export function computeAudioFeaturesFrameWasm(
129
+ samples: Float32Array
130
+ ): AudioFeaturesWasmResult | null {
131
+ if (!streamingModule || !streamingResultPtr) return null
132
+ const Module = streamingModule
133
+
134
+ // (Re-)allocate frame input buffer if needed
135
+ if (samples.length > streamingFrameCapacity) {
136
+ if (streamingFramePtr) Module._free(streamingFramePtr)
137
+ streamingFramePtr = Module._malloc(samples.length * 4)
138
+ streamingFrameCapacity = samples.length
139
+ }
140
+
141
+ // Copy samples to WASM heap
142
+ Module.HEAPF32.set(samples, streamingFramePtr >> 2)
143
+
144
+ const ok = Module._audio_features_compute_frame(
145
+ streamingFramePtr,
146
+ samples.length,
147
+ streamingResultPtr
148
+ )
149
+ if (!ok) return null
150
+
151
+ const result = readResult(Module, streamingResultPtr)
152
+
153
+ // Free internal arrays (mfcc, chromagram) allocated by C
154
+ Module._audio_features_free_arrays(streamingResultPtr)
155
+
156
+ return result
157
+ }
158
+
159
+ // --- Batch API ---
160
+
161
+ /**
162
+ * Compute audio features for a buffer of samples via WASM C++.
163
+ * Lazy-loads the WASM module on first call.
164
+ */
165
+ export async function computeAudioFeaturesWasm(
166
+ audioData: Float32Array,
167
+ sampleRate: number,
168
+ fftLength = 1024,
169
+ nMfcc = 13,
170
+ nMelFilters = 26,
171
+ computeMfcc = true,
172
+ computeChroma = true
173
+ ): Promise<AudioFeaturesWasmResult> {
174
+ const Module = await getModule()
175
+
176
+ const numSamples = audioData.length
177
+ const inputPtr = Module._malloc(numSamples * 4)
178
+ Module.HEAPF32.set(audioData, inputPtr >> 2)
179
+
180
+ const resultPtr = Module._audio_features_compute(
181
+ inputPtr,
182
+ numSamples,
183
+ sampleRate,
184
+ fftLength,
185
+ nMfcc,
186
+ nMelFilters,
187
+ computeMfcc ? 1 : 0,
188
+ computeChroma ? 1 : 0
189
+ )
190
+
191
+ Module._free(inputPtr)
192
+
193
+ if (resultPtr === 0) {
194
+ throw new Error('audio_features_compute returned null')
195
+ }
196
+
197
+ const result = readResult(Module, resultPtr)
198
+ Module._audio_features_free(resultPtr)
199
+
200
+ return result
201
+ }
@@ -1,179 +1,33 @@
1
- import type { MelSpectrogramWasmModule } from './mel-spectrogram-wasm'
1
+ // Native stub WASM mel spectrogram is web-only.
2
+ // These functions are only called in web contexts; on native, the C++ TurboModule handles mel spectrograms.
2
3
 
3
- let modulePromise: Promise<MelSpectrogramWasmModule> | null = null
4
-
5
- function getModule(): Promise<MelSpectrogramWasmModule> {
6
- if (!modulePromise) {
7
- modulePromise = (async () => {
8
- // Dynamic import of the prebuilt SINGLE_FILE Emscripten module
9
- // @ts-expect-error -- prebuilt Emscripten JS glue has no .d.ts
10
- const mod = await import('../../prebuilt/wasm/mel-spectrogram.js')
11
- const factory = mod.default ?? mod
12
- return factory() as Promise<MelSpectrogramWasmModule>
13
- })().catch((err) => {
14
- modulePromise = null
15
- throw err
16
- })
17
- }
18
- return modulePromise
19
- }
20
-
21
- // --- Streaming (per-frame) API for live mel spectrogram ---
22
-
23
- let streamingModule: MelSpectrogramWasmModule | null = null
24
- let streamingNMels = 0
25
- let streamingFramePtr = 0
26
- let streamingMelPtr = 0
27
- let streamingFrameCapacity = 0
28
-
29
- /**
30
- * Initialise the WASM streaming processor. Call once before computeMelFrame().
31
- * Re-initialises only when config changes.
32
- */
33
4
  export async function initMelStreamingWasm(
34
- sampleRate: number,
35
- nMels = 128,
36
- fftLength = 2048,
37
- windowSizeSamples = 400,
38
- hopLengthSamples = 160,
39
- fMin = 0,
40
- fMax = 0
5
+ _sampleRate: number,
6
+ _nMels?: number,
7
+ _fftLength?: number,
8
+ _windowSizeSamples?: number,
9
+ _hopLengthSamples?: number,
10
+ _fMin?: number,
11
+ _fMax?: number
41
12
  ): Promise<void> {
42
- const Module = await getModule()
43
- streamingModule = Module
44
- const actualFMax = fMax > 0 ? fMax : sampleRate / 2
45
- Module._mel_spectrogram_init(
46
- sampleRate,
47
- fftLength,
48
- windowSizeSamples,
49
- hopLengthSamples,
50
- nMels,
51
- fMin,
52
- actualFMax,
53
- 0 /* hann */
54
- )
55
- streamingNMels = nMels
56
-
57
- // Pre-allocate output buffer (fixed size)
58
- if (streamingMelPtr) Module._free(streamingMelPtr)
59
- streamingMelPtr = Module._malloc(nMels * 4)
60
-
61
- // Frame input buffer allocated on demand in computeMelFrame
62
- streamingFrameCapacity = 0
63
- streamingFramePtr = 0
13
+ throw new Error('WASM mel spectrogram is not available on native')
64
14
  }
65
15
 
66
- /**
67
- * Compute a single mel spectrogram frame from raw PCM samples via WASM C++.
68
- * Returns null if not initialised or on error.
69
- */
70
- export function computeMelFrameWasm(samples: Float32Array): number[] | null {
71
- if (!streamingModule || !streamingMelPtr) return null
72
- const Module = streamingModule
73
-
74
- // (Re-)allocate frame input buffer if needed
75
- if (samples.length > streamingFrameCapacity) {
76
- if (streamingFramePtr) Module._free(streamingFramePtr)
77
- streamingFramePtr = Module._malloc(samples.length * 4)
78
- streamingFrameCapacity = samples.length
79
- }
80
-
81
- // Copy samples to WASM heap
82
- Module.HEAPF32.set(samples, streamingFramePtr >> 2)
83
-
84
- const ok = Module._mel_spectrogram_compute_frame(
85
- streamingFramePtr,
86
- samples.length,
87
- streamingMelPtr
88
- )
89
- if (!ok) return null
90
-
91
- // Read mel output from WASM heap
92
- const offset = streamingMelPtr >> 2
93
- const result = new Array(streamingNMels)
94
- for (let i = 0; i < streamingNMels; i++) {
95
- result[i] = Module.HEAPF32[offset + i]
96
- }
97
- return result
16
+ export function computeMelFrameWasm(_samples: Float32Array): number[] | null {
17
+ return null
98
18
  }
99
19
 
100
- /**
101
- * Computes a mel spectrogram via the WASM-compiled C++ implementation.
102
- * Lazy-loads the WASM module on first call.
103
- */
104
20
  export async function computeMelSpectrogramWasm(
105
- audioData: Float32Array,
106
- sampleRate: number,
107
- nMels: number,
108
- windowSizeSamples: number,
109
- hopLengthSamples: number,
110
- fMin: number,
111
- fMax: number,
112
- windowType: 'hann' | 'hamming',
113
- normalize: boolean,
114
- logScale: boolean
21
+ _audioData: Float32Array,
22
+ _sampleRate: number,
23
+ _nMels: number,
24
+ _windowSizeSamples: number,
25
+ _hopLengthSamples: number,
26
+ _fMin: number,
27
+ _fMax: number,
28
+ _windowType: 'hann' | 'hamming',
29
+ _normalize: boolean,
30
+ _logScale: boolean
115
31
  ): Promise<number[][]> {
116
- const Module = await getModule()
117
-
118
- const fftLength = 2048
119
- const windowTypeInt = windowType === 'hamming' ? 1 : 0
120
-
121
- // Allocate input buffer on WASM heap
122
- const numSamples = audioData.length
123
- const inputPtr = Module._malloc(numSamples * 4) // 4 bytes per float
124
- Module.HEAPF32.set(audioData, inputPtr >> 2)
125
-
126
- // Call the C bridge
127
- const resultPtr = Module._mel_spectrogram_compute(
128
- inputPtr,
129
- numSamples,
130
- sampleRate,
131
- fftLength,
132
- windowSizeSamples,
133
- hopLengthSamples,
134
- nMels,
135
- fMin,
136
- fMax,
137
- windowTypeInt,
138
- logScale ? 1 : 0,
139
- normalize ? 1 : 0
140
- )
141
-
142
- // Free input buffer
143
- Module._free(inputPtr)
144
-
145
- if (resultPtr === 0) {
146
- throw new Error(
147
- 'mel_spectrogram_compute returned null (too few samples?)'
148
- )
149
- }
150
-
151
- // Read CMelSpectrogramResult struct (wasm32 pointers are 4 bytes)
152
- // struct layout: { float* data (offset 0), int timeSteps (offset 4), int nMels (offset 8) }
153
- const dataPtr = Module.getValue(resultPtr, 'i32')
154
- const timeSteps = Module.getValue(resultPtr + 4, 'i32')
155
- const resultNMels = Module.getValue(resultPtr + 8, 'i32')
156
-
157
- if (!dataPtr || timeSteps <= 0 || resultNMels <= 0) {
158
- Module._mel_spectrogram_free(resultPtr)
159
- throw new Error(
160
- 'mel_spectrogram_compute returned invalid result struct'
161
- )
162
- }
163
-
164
- // Copy spectrogram data to JS arrays
165
- const spectrogram: number[][] = []
166
- const heapOffset = dataPtr >> 2 // float32 offset into HEAPF32
167
- for (let t = 0; t < timeSteps; t++) {
168
- const row = new Array(resultNMels)
169
- for (let m = 0; m < resultNMels; m++) {
170
- row[m] = Module.HEAPF32[heapOffset + t * resultNMels + m]
171
- }
172
- spectrogram.push(row)
173
- }
174
-
175
- // Free the C result
176
- Module._mel_spectrogram_free(resultPtr)
177
-
178
- return spectrogram
32
+ throw new Error('WASM mel spectrogram is not available on native')
179
33
  }
@@ -0,0 +1,184 @@
1
+ import type { MelSpectrogramWasmModule } from './mel-spectrogram-wasm'
2
+ import { getMelSpectrogramWasmUrl, _registerModuleReset } from './wasmConfig'
3
+
4
+ let modulePromise: Promise<MelSpectrogramWasmModule> | null = null
5
+
6
+ _registerModuleReset(() => {
7
+ modulePromise = null
8
+ })
9
+
10
+ function getModule(): Promise<MelSpectrogramWasmModule> {
11
+ if (!modulePromise) {
12
+ modulePromise = (async () => {
13
+ const url = getMelSpectrogramWasmUrl()
14
+ // webpackIgnore + @vite-ignore prevent bundlers from trying to resolve the URL
15
+ const mod = await import(/* webpackIgnore: true */ /* @vite-ignore */ url)
16
+ const factory = mod.default ?? mod
17
+ return factory() as Promise<MelSpectrogramWasmModule>
18
+ })().catch((err) => {
19
+ modulePromise = null
20
+ throw err
21
+ })
22
+ }
23
+ return modulePromise
24
+ }
25
+
26
+ // --- Streaming (per-frame) API for live mel spectrogram ---
27
+
28
+ let streamingModule: MelSpectrogramWasmModule | null = null
29
+ let streamingNMels = 0
30
+ let streamingFramePtr = 0
31
+ let streamingMelPtr = 0
32
+ let streamingFrameCapacity = 0
33
+
34
+ /**
35
+ * Initialise the WASM streaming processor. Call once before computeMelFrame().
36
+ * Re-initialises only when config changes.
37
+ */
38
+ export async function initMelStreamingWasm(
39
+ sampleRate: number,
40
+ nMels = 128,
41
+ fftLength = 2048,
42
+ windowSizeSamples = 400,
43
+ hopLengthSamples = 160,
44
+ fMin = 0,
45
+ fMax = 0
46
+ ): Promise<void> {
47
+ const Module = await getModule()
48
+ streamingModule = Module
49
+ const actualFMax = fMax > 0 ? fMax : sampleRate / 2
50
+ Module._mel_spectrogram_init(
51
+ sampleRate,
52
+ fftLength,
53
+ windowSizeSamples,
54
+ hopLengthSamples,
55
+ nMels,
56
+ fMin,
57
+ actualFMax,
58
+ 0 /* hann */
59
+ )
60
+ streamingNMels = nMels
61
+
62
+ // Pre-allocate output buffer (fixed size)
63
+ if (streamingMelPtr) Module._free(streamingMelPtr)
64
+ streamingMelPtr = Module._malloc(nMels * 4)
65
+
66
+ // Frame input buffer allocated on demand in computeMelFrame
67
+ streamingFrameCapacity = 0
68
+ streamingFramePtr = 0
69
+ }
70
+
71
+ /**
72
+ * Compute a single mel spectrogram frame from raw PCM samples via WASM C++.
73
+ * Returns null if not initialised or on error.
74
+ */
75
+ export function computeMelFrameWasm(samples: Float32Array): number[] | null {
76
+ if (!streamingModule || !streamingMelPtr) return null
77
+ const Module = streamingModule
78
+
79
+ // (Re-)allocate frame input buffer if needed
80
+ if (samples.length > streamingFrameCapacity) {
81
+ if (streamingFramePtr) Module._free(streamingFramePtr)
82
+ streamingFramePtr = Module._malloc(samples.length * 4)
83
+ streamingFrameCapacity = samples.length
84
+ }
85
+
86
+ // Copy samples to WASM heap
87
+ Module.HEAPF32.set(samples, streamingFramePtr >> 2)
88
+
89
+ const ok = Module._mel_spectrogram_compute_frame(
90
+ streamingFramePtr,
91
+ samples.length,
92
+ streamingMelPtr
93
+ )
94
+ if (!ok) return null
95
+
96
+ // Read mel output from WASM heap
97
+ const offset = streamingMelPtr >> 2
98
+ const result = new Array(streamingNMels)
99
+ for (let i = 0; i < streamingNMels; i++) {
100
+ result[i] = Module.HEAPF32[offset + i]
101
+ }
102
+ return result
103
+ }
104
+
105
+ /**
106
+ * Computes a mel spectrogram via the WASM-compiled C++ implementation.
107
+ * Lazy-loads the WASM module on first call.
108
+ */
109
+ export async function computeMelSpectrogramWasm(
110
+ audioData: Float32Array,
111
+ sampleRate: number,
112
+ nMels: number,
113
+ windowSizeSamples: number,
114
+ hopLengthSamples: number,
115
+ fMin: number,
116
+ fMax: number,
117
+ windowType: 'hann' | 'hamming',
118
+ normalize: boolean,
119
+ logScale: boolean
120
+ ): Promise<number[][]> {
121
+ const Module = await getModule()
122
+
123
+ const fftLength = 2048
124
+ const windowTypeInt = windowType === 'hamming' ? 1 : 0
125
+
126
+ // Allocate input buffer on WASM heap
127
+ const numSamples = audioData.length
128
+ const inputPtr = Module._malloc(numSamples * 4) // 4 bytes per float
129
+ Module.HEAPF32.set(audioData, inputPtr >> 2)
130
+
131
+ // Call the C bridge
132
+ const resultPtr = Module._mel_spectrogram_compute(
133
+ inputPtr,
134
+ numSamples,
135
+ sampleRate,
136
+ fftLength,
137
+ windowSizeSamples,
138
+ hopLengthSamples,
139
+ nMels,
140
+ fMin,
141
+ fMax,
142
+ windowTypeInt,
143
+ logScale ? 1 : 0,
144
+ normalize ? 1 : 0
145
+ )
146
+
147
+ // Free input buffer
148
+ Module._free(inputPtr)
149
+
150
+ if (resultPtr === 0) {
151
+ throw new Error(
152
+ 'mel_spectrogram_compute returned null (too few samples?)'
153
+ )
154
+ }
155
+
156
+ // Read CMelSpectrogramResult struct (wasm32 pointers are 4 bytes)
157
+ // struct layout: { float* data (offset 0), int timeSteps (offset 4), int nMels (offset 8) }
158
+ const dataPtr = Module.getValue(resultPtr, 'i32')
159
+ const timeSteps = Module.getValue(resultPtr + 4, 'i32')
160
+ const resultNMels = Module.getValue(resultPtr + 8, 'i32')
161
+
162
+ if (!dataPtr || timeSteps <= 0 || resultNMels <= 0) {
163
+ Module._mel_spectrogram_free(resultPtr)
164
+ throw new Error(
165
+ 'mel_spectrogram_compute returned invalid result struct'
166
+ )
167
+ }
168
+
169
+ // Copy spectrogram data to JS arrays
170
+ const spectrogram: number[][] = []
171
+ const heapOffset = dataPtr >> 2 // float32 offset into HEAPF32
172
+ for (let t = 0; t < timeSteps; t++) {
173
+ const row = new Array(resultNMels)
174
+ for (let m = 0; m < resultNMels; m++) {
175
+ row[m] = Module.HEAPF32[heapOffset + t * resultNMels + m]
176
+ }
177
+ spectrogram.push(row)
178
+ }
179
+
180
+ // Free the C result
181
+ Module._mel_spectrogram_free(resultPtr)
182
+
183
+ return spectrogram
184
+ }
@@ -0,0 +1,24 @@
1
+ // Version is inlined here — keep in sync with package.json when releasing.
2
+ // The publish.sh script should bump this string alongside package.json.
3
+ const WASM_VERSION = '3.0.2'
4
+ // jsDelivr syncs from npm automatically within ~5 min of publish.
5
+ // GitHub release fallback (attach mel-spectrogram.js as a release asset):
6
+ // https://github.com/deeeed/audiolab/releases/download/@siteed/audio-studio@VERSION/mel-spectrogram.js
7
+ // To use the fallback: setMelSpectrogramWasmUrl('<url>') before any mel-spectrogram API call.
8
+ const DEFAULT_WASM_CDN = `https://cdn.jsdelivr.net/npm/@siteed/audio-studio@${WASM_VERSION}/prebuilt/wasm/mel-spectrogram.js`
9
+
10
+ let _wasmUrl: string = DEFAULT_WASM_CDN
11
+ let _modulePromiseReset: (() => void) | null = null
12
+
13
+ export function _registerModuleReset(fn: () => void): void {
14
+ _modulePromiseReset = fn
15
+ }
16
+
17
+ export function setMelSpectrogramWasmUrl(url: string): void {
18
+ _wasmUrl = url
19
+ _modulePromiseReset?.() // invalidate cached module so next call re-fetches
20
+ }
21
+
22
+ export function getMelSpectrogramWasmUrl(): string {
23
+ return _wasmUrl
24
+ }
package/src/index.ts CHANGED
@@ -43,6 +43,8 @@ export { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager'
43
43
  // Export useAudioDevices hook
44
44
  export { useAudioDevices } from './hooks/useAudioDevices'
45
45
 
46
+ export { setMelSpectrogramWasmUrl } from './AudioAnalysis/wasmConfig'
47
+
46
48
  export {
47
49
  AudioRecorderProvider,
48
50
  AudioStudioModule,