@siteed/audio-studio 3.0.2 → 3.0.3

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 (86) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +7 -1
  3. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  4. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +10 -7
  5. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -1
  6. package/build/cjs/AudioAnalysis/audioFeaturesWasm.web.js +78 -97
  7. package/build/cjs/AudioAnalysis/audioFeaturesWasm.web.js.map +1 -1
  8. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +15 -12
  9. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
  10. package/build/cjs/AudioAnalysis/extractAudioData.js +144 -2
  11. package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -1
  12. package/build/cjs/AudioAnalysis/melSpectrogramWasm.web.js +9 -56
  13. package/build/cjs/AudioAnalysis/melSpectrogramWasm.web.js.map +1 -1
  14. package/build/cjs/AudioAnalysis/wasmConfig.js +4 -4
  15. package/build/cjs/AudioAnalysis/wasmConfig.js.map +1 -1
  16. package/build/cjs/AudioAnalysis/wasmLoader.web.js +78 -0
  17. package/build/cjs/AudioAnalysis/wasmLoader.web.js.map +1 -0
  18. package/build/cjs/AudioStudioModule.js +4 -599
  19. package/build/cjs/AudioStudioModule.js.map +1 -1
  20. package/build/cjs/trimAudio.js +227 -0
  21. package/build/cjs/trimAudio.js.map +1 -1
  22. package/build/cjs/utils/encodeCompressedAudio.web.js +65 -0
  23. package/build/cjs/utils/encodeCompressedAudio.web.js.map +1 -0
  24. package/build/cjs/utils/resampleAudioBuffer.web.js +25 -0
  25. package/build/cjs/utils/resampleAudioBuffer.web.js.map +1 -0
  26. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  27. package/build/esm/AudioAnalysis/audioFeaturesWasm.js +8 -5
  28. package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -1
  29. package/build/esm/AudioAnalysis/audioFeaturesWasm.web.js +76 -62
  30. package/build/esm/AudioAnalysis/audioFeaturesWasm.web.js.map +1 -1
  31. package/build/esm/AudioAnalysis/extractAudioAnalysis.js +15 -12
  32. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
  33. package/build/esm/AudioAnalysis/extractAudioData.js +144 -2
  34. package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -1
  35. package/build/esm/AudioAnalysis/melSpectrogramWasm.web.js +9 -23
  36. package/build/esm/AudioAnalysis/melSpectrogramWasm.web.js.map +1 -1
  37. package/build/esm/AudioAnalysis/wasmConfig.js +4 -4
  38. package/build/esm/AudioAnalysis/wasmConfig.js.map +1 -1
  39. package/build/esm/AudioAnalysis/wasmLoader.web.js +42 -0
  40. package/build/esm/AudioAnalysis/wasmLoader.web.js.map +1 -0
  41. package/build/esm/AudioStudioModule.js +4 -596
  42. package/build/esm/AudioStudioModule.js.map +1 -1
  43. package/build/esm/trimAudio.js +227 -0
  44. package/build/esm/trimAudio.js.map +1 -1
  45. package/build/esm/utils/encodeCompressedAudio.web.js +62 -0
  46. package/build/esm/utils/encodeCompressedAudio.web.js.map +1 -0
  47. package/build/esm/utils/resampleAudioBuffer.web.js +22 -0
  48. package/build/esm/utils/resampleAudioBuffer.web.js.map +1 -0
  49. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +11 -0
  50. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
  51. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +5 -9
  52. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -1
  53. package/build/types/AudioAnalysis/audioFeaturesWasm.web.d.ts +35 -16
  54. package/build/types/AudioAnalysis/audioFeaturesWasm.web.d.ts.map +1 -1
  55. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -1
  56. package/build/types/AudioAnalysis/extractAudioData.d.ts +2 -2
  57. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -1
  58. package/build/types/AudioAnalysis/melSpectrogramWasm.web.d.ts.map +1 -1
  59. package/build/types/AudioAnalysis/wasmLoader.web.d.ts +3 -0
  60. package/build/types/AudioAnalysis/wasmLoader.web.d.ts.map +1 -0
  61. package/build/types/AudioStudioModule.d.ts.map +1 -1
  62. package/build/types/trimAudio.d.ts.map +1 -1
  63. package/build/types/utils/encodeCompressedAudio.web.d.ts +10 -0
  64. package/build/types/utils/encodeCompressedAudio.web.d.ts.map +1 -0
  65. package/build/types/utils/resampleAudioBuffer.web.d.ts +2 -0
  66. package/build/types/utils/resampleAudioBuffer.web.d.ts.map +1 -0
  67. package/package.json +1 -1
  68. package/src/AudioAnalysis/AudioAnalysis.types.ts +12 -0
  69. package/src/AudioAnalysis/audioFeaturesWasm.ts +17 -22
  70. package/src/AudioAnalysis/audioFeaturesWasm.web.ts +102 -94
  71. package/src/AudioAnalysis/extractAudioAnalysis.ts +23 -20
  72. package/src/AudioAnalysis/extractAudioData.ts +186 -4
  73. package/src/AudioAnalysis/melSpectrogramWasm.web.ts +10 -27
  74. package/src/AudioAnalysis/wasmConfig.ts +4 -4
  75. package/src/AudioAnalysis/wasmLoader.web.ts +48 -0
  76. package/src/AudioStudioModule.ts +6 -854
  77. package/src/trimAudio.ts +337 -0
  78. package/src/utils/encodeCompressedAudio.web.ts +78 -0
  79. package/src/utils/resampleAudioBuffer.web.ts +39 -0
  80. package/build/cjs/AudioAnalysis/extractWaveform.js +0 -18
  81. package/build/cjs/AudioAnalysis/extractWaveform.js.map +0 -1
  82. package/build/esm/AudioAnalysis/extractWaveform.js +0 -11
  83. package/build/esm/AudioAnalysis/extractWaveform.js.map +0 -1
  84. package/build/types/AudioAnalysis/extractWaveform.d.ts +0 -8
  85. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  86. package/src/AudioAnalysis/extractWaveform.ts +0 -22
@@ -1,13 +1,195 @@
1
- import { ExtractAudioDataOptions } from '../AudioStudio.types'
1
+ import {
2
+ BitDepth,
3
+ ExtractAudioDataOptions,
4
+ ExtractedAudioData,
5
+ } from '../AudioStudio.types'
2
6
  import AudioStudioModule from '../AudioStudioModule'
3
7
  import { isWeb } from '../constants'
8
+ import { processAudioBuffer } from '../utils/audioProcessing'
4
9
  import { cleanNativeOptions } from '../utils/cleanNativeOptions'
10
+ import crc32 from '../utils/crc32'
11
+ import { writeWavHeader } from '../utils/writeWavHeader'
5
12
 
6
- export const extractAudioData = async (props: ExtractAudioDataOptions) => {
13
+ export const extractAudioData = async (
14
+ props: ExtractAudioDataOptions
15
+ ): Promise<ExtractedAudioData> => {
7
16
  if (isWeb) {
8
- // Web implementation handles logger natively in AudioStudioModule.ts
9
- return await AudioStudioModule.extractAudioData(props)
17
+ try {
18
+ const {
19
+ fileUri,
20
+ position,
21
+ length,
22
+ startTimeMs,
23
+ endTimeMs,
24
+ decodingOptions,
25
+ includeNormalizedData,
26
+ includeBase64Data,
27
+ includeWavHeader = false,
28
+ logger,
29
+ } = props
30
+
31
+ logger?.debug('EXTRACT AUDIO - Step 1: Initial request', {
32
+ fileUri,
33
+ extractionParams: {
34
+ position,
35
+ length,
36
+ startTimeMs,
37
+ endTimeMs,
38
+ },
39
+ decodingOptions: {
40
+ targetSampleRate:
41
+ decodingOptions?.targetSampleRate ?? 16000,
42
+ targetChannels: decodingOptions?.targetChannels ?? 1,
43
+ targetBitDepth: decodingOptions?.targetBitDepth ?? 16,
44
+ normalizeAudio: decodingOptions?.normalizeAudio ?? false,
45
+ },
46
+ outputOptions: {
47
+ includeNormalizedData,
48
+ includeBase64Data,
49
+ includeWavHeader,
50
+ },
51
+ })
52
+
53
+ // Process the audio using shared helper function
54
+ const processedBuffer = await processAudioBuffer({
55
+ fileUri,
56
+ targetSampleRate: decodingOptions?.targetSampleRate ?? 16000,
57
+ targetChannels: decodingOptions?.targetChannels ?? 1,
58
+ normalizeAudio: decodingOptions?.normalizeAudio ?? false,
59
+ position,
60
+ length,
61
+ startTimeMs,
62
+ endTimeMs,
63
+ logger,
64
+ })
65
+
66
+ logger?.debug('EXTRACT AUDIO - Step 2: Audio processing complete', {
67
+ processedData: {
68
+ samples: processedBuffer.samples,
69
+ sampleRate: processedBuffer.sampleRate,
70
+ channels: processedBuffer.channels,
71
+ durationMs: processedBuffer.durationMs,
72
+ },
73
+ })
74
+
75
+ const channelData = processedBuffer.channelData
76
+ const bitDepth = (decodingOptions?.targetBitDepth ?? 16) as BitDepth
77
+ const bytesPerSample = bitDepth / 8
78
+ const numSamples = processedBuffer.samples
79
+
80
+ logger?.debug('EXTRACT AUDIO - Step 3: PCM conversion setup', {
81
+ channelData: {
82
+ length: channelData.length,
83
+ first: channelData[0],
84
+ last: channelData[channelData.length - 1],
85
+ },
86
+ calculation: {
87
+ bitDepth,
88
+ bytesPerSample,
89
+ numSamples,
90
+ expectedBytes: numSamples * bytesPerSample,
91
+ },
92
+ })
93
+
94
+ // Create PCM data with correct length based on original byte length
95
+ const pcmData = new Uint8Array(numSamples * bytesPerSample)
96
+ let offset = 0
97
+
98
+ // Convert Float32 samples to PCM format
99
+ for (let i = 0; i < numSamples; i++) {
100
+ const sample = channelData[i]
101
+ const value = Math.max(-1, Math.min(1, sample))
102
+ // Convert to 16-bit signed integer
103
+ let intValue = Math.round(value * 32767)
104
+
105
+ // Handle negative values correctly
106
+ if (intValue < 0) {
107
+ intValue = 65536 + intValue
108
+ }
109
+
110
+ // Write as little-endian
111
+ pcmData[offset++] = intValue & 255 // Low byte
112
+ pcmData[offset++] = (intValue >> 8) & 255 // High byte
113
+ }
114
+
115
+ const durationMs = Math.round(
116
+ (numSamples / processedBuffer.sampleRate) * 1000
117
+ )
118
+
119
+ logger?.debug('EXTRACT AUDIO - Step 4: Final output', {
120
+ pcmData: {
121
+ length: pcmData.length,
122
+ first: pcmData[0],
123
+ last: pcmData[pcmData.length - 1],
124
+ },
125
+ timing: {
126
+ numSamples,
127
+ sampleRate: processedBuffer.sampleRate,
128
+ durationMs,
129
+ shouldBe3000ms: endTimeMs
130
+ ? endTimeMs - (startTimeMs ?? 0) === 3000
131
+ : undefined,
132
+ },
133
+ })
134
+
135
+ const result: ExtractedAudioData = {
136
+ pcmData: new Uint8Array(pcmData.buffer),
137
+ sampleRate: processedBuffer.sampleRate,
138
+ channels: processedBuffer.channels,
139
+ bitDepth,
140
+ durationMs,
141
+ format: `pcm_${bitDepth}bit` as const,
142
+ samples: numSamples,
143
+ }
144
+
145
+ // Add WAV header if requested
146
+ if (includeWavHeader) {
147
+ logger?.debug('EXTRACT AUDIO - Step 5: Adding WAV header', {
148
+ originalLength: pcmData.length,
149
+ newLength: result.pcmData.length,
150
+ firstBytes: Array.from(result.pcmData.slice(0, 44)), // WAV header is 44 bytes
151
+ })
152
+ const wavBuffer = writeWavHeader({
153
+ buffer: pcmData.buffer.slice(0, pcmData.length),
154
+ sampleRate: processedBuffer.sampleRate,
155
+ numChannels: processedBuffer.channels,
156
+ bitDepth,
157
+ })
158
+ result.pcmData = new Uint8Array(wavBuffer)
159
+ result.hasWavHeader = true
160
+ }
161
+
162
+ if (includeNormalizedData) {
163
+ result.normalizedData = channelData
164
+ }
165
+
166
+ if (includeBase64Data) {
167
+ result.base64Data = btoa(
168
+ String.fromCharCode(...new Uint8Array(pcmData.buffer))
169
+ )
170
+ }
171
+
172
+ if (props.computeChecksum) {
173
+ result.checksum = crc32.buf(pcmData)
174
+ }
175
+
176
+ logger?.debug('EXTRACT AUDIO - Step 3: PCM conversion complete', {
177
+ pcmStats: {
178
+ length: pcmData.length,
179
+ bytesPerSample,
180
+ totalSamples: numSamples,
181
+ firstBytes: Array.from(pcmData.slice(0, 16)),
182
+ lastBytes: Array.from(pcmData.slice(-16)),
183
+ },
184
+ })
185
+
186
+ return result
187
+ } catch (error) {
188
+ props.logger?.error('EXTRACT AUDIO - Error:', error)
189
+ throw error
190
+ }
10
191
  }
192
+
11
193
  // Native: only pass serializable fields — logger causes crash on Android
12
194
  const { logger: _logger, ...nativeOptions } = props
13
195
  // Clean undefined values to avoid Android Kotlin bridge crash
@@ -1,29 +1,5 @@
1
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(
16
- /* webpackIgnore: true */ /* @vite-ignore */ url
17
- )
18
- const factory = mod.default ?? mod
19
- return factory() as Promise<MelSpectrogramWasmModule>
20
- })().catch((err) => {
21
- modulePromise = null
22
- throw err
23
- })
24
- }
25
- return modulePromise
26
- }
2
+ import { getWasmModule } from './wasmLoader.web'
27
3
 
28
4
  // --- Streaming (per-frame) API for live mel spectrogram ---
29
5
 
@@ -46,7 +22,7 @@ export async function initMelStreamingWasm(
46
22
  fMin = 0,
47
23
  fMax = 0
48
24
  ): Promise<void> {
49
- const Module = await getModule()
25
+ const Module = (await getWasmModule()) as MelSpectrogramWasmModule
50
26
  streamingModule = Module
51
27
  const actualFMax = fMax > 0 ? fMax : sampleRate / 2
52
28
  Module._mel_spectrogram_init(
@@ -61,6 +37,13 @@ export async function initMelStreamingWasm(
61
37
  )
62
38
  streamingNMels = nMels
63
39
 
40
+ // Free frame buffer from previous session (if any) to avoid leak on re-init
41
+ if (streamingFramePtr) {
42
+ Module._free(streamingFramePtr)
43
+ streamingFramePtr = 0
44
+ streamingFrameCapacity = 0
45
+ }
46
+
64
47
  // Pre-allocate output buffer (fixed size)
65
48
  if (streamingMelPtr) Module._free(streamingMelPtr)
66
49
  streamingMelPtr = Module._malloc(nMels * 4)
@@ -120,7 +103,7 @@ export async function computeMelSpectrogramWasm(
120
103
  normalize: boolean,
121
104
  logScale: boolean
122
105
  ): Promise<number[][]> {
123
- const Module = await getModule()
106
+ const Module = (await getWasmModule()) as MelSpectrogramWasmModule
124
107
 
125
108
  const fftLength = 2048
126
109
  const windowTypeInt = windowType === 'hamming' ? 1 : 0
@@ -1,6 +1,6 @@
1
1
  // Version is inlined here — keep in sync with package.json when releasing.
2
2
  // The publish.sh script should bump this string alongside package.json.
3
- const WASM_VERSION = '3.0.2-beta.1'
3
+ const WASM_VERSION = '3.0.2'
4
4
  // jsDelivr syncs from npm automatically within ~5 min of publish.
5
5
  // GitHub release fallback (attach mel-spectrogram.js as a release asset):
6
6
  // https://github.com/deeeed/audiolab/releases/download/@siteed/audio-studio@VERSION/mel-spectrogram.js
@@ -8,15 +8,15 @@ const WASM_VERSION = '3.0.2-beta.1'
8
8
  const DEFAULT_WASM_CDN = `https://cdn.jsdelivr.net/npm/@siteed/audio-studio@${WASM_VERSION}/prebuilt/wasm/mel-spectrogram.js`
9
9
 
10
10
  let _wasmUrl: string = DEFAULT_WASM_CDN
11
- let _modulePromiseReset: (() => void) | null = null
11
+ const _resetListeners: Array<() => void> = []
12
12
 
13
13
  export function _registerModuleReset(fn: () => void): void {
14
- _modulePromiseReset = fn
14
+ _resetListeners.push(fn)
15
15
  }
16
16
 
17
17
  export function setMelSpectrogramWasmUrl(url: string): void {
18
18
  _wasmUrl = url
19
- _modulePromiseReset?.() // invalidate cached module so next call re-fetches
19
+ _resetListeners.forEach((fn) => fn())
20
20
  }
21
21
 
22
22
  export function getMelSpectrogramWasmUrl(): string {
@@ -0,0 +1,48 @@
1
+ import type { AudioFeaturesWasmModule } from './audio-features-wasm'
2
+ import { getMelSpectrogramWasmUrl, _registerModuleReset } from './wasmConfig'
3
+
4
+ // Global factory name for the shared WASM binary. Despite the name referring to
5
+ // mel spectrogram, this single binary also exports all audio-features functions.
6
+ const WASM_GLOBAL_NAME = 'createMelSpectrogramModule'
7
+ let modulePromise: Promise<AudioFeaturesWasmModule> | null = null
8
+
9
+ _registerModuleReset(() => {
10
+ modulePromise = null
11
+ })
12
+
13
+ function loadScriptTag(url: string): Promise<void> {
14
+ return new Promise((resolve, reject) => {
15
+ const script = document.createElement('script')
16
+ script.src = url
17
+ script.onload = () => resolve()
18
+ script.onerror = () => reject(new Error(`Failed to load script: ${url}`))
19
+ document.head.appendChild(script)
20
+ })
21
+ }
22
+
23
+ export function getWasmModule(): Promise<AudioFeaturesWasmModule> {
24
+ if (!modulePromise) {
25
+ modulePromise = (async () => {
26
+ const url = getMelSpectrogramWasmUrl()
27
+ // Try ESM import first; fall back to <script> tag for UMD modules
28
+ const mod = await import(/* webpackIgnore: true */ /* @vite-ignore */ url)
29
+ let factory: unknown = mod.default ?? mod
30
+ if (typeof factory !== 'function') {
31
+ // UMD fallback: load via <script> tag so the top-level `var` becomes a global and
32
+ // document.currentScript.src is set (Emscripten uses it to locate the .wasm binary).
33
+ await loadScriptTag(url)
34
+ factory = (globalThis as Record<string, unknown>)[WASM_GLOBAL_NAME]
35
+ }
36
+ if (typeof factory !== 'function') {
37
+ throw new TypeError(
38
+ `WASM factory '${WASM_GLOBAL_NAME}' not found after loading ${url}`
39
+ )
40
+ }
41
+ return (factory as () => Promise<AudioFeaturesWasmModule>)()
42
+ })().catch((err) => {
43
+ modulePromise = null
44
+ throw err
45
+ })
46
+ }
47
+ return modulePromise
48
+ }