@siteed/expo-audio-studio 2.4.1 → 2.6.0
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.
- package/CHANGELOG.md +14 -1
- package/README.md +25 -0
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +22 -0
- package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +1501 -0
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +10 -5
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +27 -25
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +73 -71
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +576 -252
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +17 -1
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +419 -155
- package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +65 -0
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +9 -1
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
- package/build/AudioDeviceManager.d.ts +107 -0
- package/build/AudioDeviceManager.d.ts.map +1 -0
- package/build/AudioDeviceManager.js +493 -0
- package/build/AudioDeviceManager.js.map +1 -0
- package/build/AudioRecorder.provider.d.ts.map +1 -1
- package/build/AudioRecorder.provider.js +3 -0
- package/build/AudioRecorder.provider.js.map +1 -1
- package/build/ExpoAudioStream.types.d.ts +104 -1
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js +7 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +37 -0
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +478 -62
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/ExpoAudioStreamModule.d.ts.map +1 -1
- package/build/ExpoAudioStreamModule.js +20 -0
- package/build/ExpoAudioStreamModule.js.map +1 -1
- package/build/WebRecorder.web.d.ts +74 -11
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +390 -74
- package/build/WebRecorder.web.js.map +1 -1
- package/build/hooks/useAudioDevices.d.ts +14 -0
- package/build/hooks/useAudioDevices.d.ts.map +1 -0
- package/build/hooks/useAudioDevices.js +151 -0
- package/build/hooks/useAudioDevices.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/index.js.map +1 -1
- package/build/useAudioRecorder.d.ts +1 -0
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.js +20 -1
- package/build/useAudioRecorder.js.map +1 -1
- package/build/utils/BlobFix.d.ts.map +1 -1
- package/build/utils/BlobFix.js +2 -2
- package/build/utils/BlobFix.js.map +1 -1
- package/build/utils/writeWavHeader.d.ts +3 -18
- package/build/utils/writeWavHeader.d.ts.map +1 -1
- package/build/utils/writeWavHeader.js +19 -26
- package/build/utils/writeWavHeader.js.map +1 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts +1 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
- package/build/workers/InlineFeaturesExtractor.web.js +27 -26
- package/build/workers/InlineFeaturesExtractor.web.js.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
- package/build/workers/inlineAudioWebWorker.web.js +25 -1
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
- package/ios/AudioDeviceManager.swift +654 -0
- package/ios/AudioStreamManager.swift +964 -760
- package/ios/ExpoAudioStreamModule.swift +174 -19
- package/ios/Features.swift +1 -1
- package/ios/ISSUE_IOS.md +45 -0
- package/ios/Logger.swift +13 -1
- package/ios/RecordingSettings.swift +12 -0
- package/package.json +2 -2
- package/src/AudioAnalysis/AudioAnalysis.types.ts +2 -2
- package/src/AudioDeviceManager.ts +571 -0
- package/src/AudioRecorder.provider.tsx +3 -0
- package/src/ExpoAudioStream.types.ts +113 -1
- package/src/ExpoAudioStream.web.ts +609 -69
- package/src/ExpoAudioStreamModule.ts +23 -0
- package/src/WebRecorder.web.ts +482 -92
- package/src/hooks/useAudioDevices.ts +180 -0
- package/src/index.ts +6 -0
- package/src/types/crc-32.d.ts +6 -6
- package/src/useAudioRecorder.tsx +27 -1
- package/src/utils/BlobFix.ts +6 -4
- package/src/utils/writeWavHeader.ts +26 -25
- package/src/workers/InlineFeaturesExtractor.web.tsx +27 -26
- package/src/workers/inlineAudioWebWorker.web.tsx +25 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { audioDeviceManager } from '../AudioDeviceManager'
|
|
4
|
+
import { AudioDevice } from '../ExpoAudioStream.types'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* React hook for managing audio input devices
|
|
8
|
+
*/
|
|
9
|
+
export function useAudioDevices() {
|
|
10
|
+
const [devices, setDevices] = useState<AudioDevice[]>([])
|
|
11
|
+
const [currentDevice, setCurrentDevice] = useState<AudioDevice | null>(null)
|
|
12
|
+
const [loading, setLoading] = useState(true)
|
|
13
|
+
const [error, setError] = useState<Error | null>(null)
|
|
14
|
+
|
|
15
|
+
// Load devices on mount
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
let isMounted = true
|
|
18
|
+
|
|
19
|
+
const loadDevices = async () => {
|
|
20
|
+
try {
|
|
21
|
+
setLoading(true)
|
|
22
|
+
setError(null)
|
|
23
|
+
|
|
24
|
+
// Load available devices
|
|
25
|
+
const availableDevices =
|
|
26
|
+
await audioDeviceManager.getAvailableDevices()
|
|
27
|
+
if (isMounted) setDevices(availableDevices)
|
|
28
|
+
|
|
29
|
+
// Get current device
|
|
30
|
+
const device = await audioDeviceManager.getCurrentDevice()
|
|
31
|
+
if (isMounted) setCurrentDevice(device)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error('Failed to load audio devices:', err)
|
|
34
|
+
if (isMounted)
|
|
35
|
+
setError(
|
|
36
|
+
err instanceof Error
|
|
37
|
+
? err
|
|
38
|
+
: new Error('Failed to load audio devices')
|
|
39
|
+
)
|
|
40
|
+
} finally {
|
|
41
|
+
if (isMounted) setLoading(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
loadDevices()
|
|
46
|
+
|
|
47
|
+
// Set up device change listener
|
|
48
|
+
const removeListener = audioDeviceManager.addDeviceChangeListener(
|
|
49
|
+
(updatedDevices: AudioDevice[]) => {
|
|
50
|
+
if (isMounted) {
|
|
51
|
+
setDevices(updatedDevices)
|
|
52
|
+
|
|
53
|
+
// If our current device is no longer available, update it
|
|
54
|
+
if (
|
|
55
|
+
currentDevice &&
|
|
56
|
+
!updatedDevices.some(
|
|
57
|
+
(d: AudioDevice) => d.id === currentDevice.id
|
|
58
|
+
)
|
|
59
|
+
) {
|
|
60
|
+
audioDeviceManager
|
|
61
|
+
.getCurrentDevice()
|
|
62
|
+
.then((newDevice: AudioDevice | null) => {
|
|
63
|
+
if (isMounted) setCurrentDevice(newDevice)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
isMounted = false
|
|
72
|
+
removeListener()
|
|
73
|
+
}
|
|
74
|
+
}, [])
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Select a specific audio input device
|
|
78
|
+
* @param deviceId The ID of the device to select
|
|
79
|
+
* @returns Promise resolving to a boolean indicating success
|
|
80
|
+
*/
|
|
81
|
+
const selectDevice = useCallback(
|
|
82
|
+
async (deviceId: string): Promise<boolean> => {
|
|
83
|
+
try {
|
|
84
|
+
setLoading(true)
|
|
85
|
+
setError(null)
|
|
86
|
+
|
|
87
|
+
const success = await audioDeviceManager.selectDevice(deviceId)
|
|
88
|
+
|
|
89
|
+
if (success) {
|
|
90
|
+
// Get the updated current device after selection
|
|
91
|
+
const device = await audioDeviceManager.getCurrentDevice()
|
|
92
|
+
setCurrentDevice(device)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return success
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error('Failed to select audio device:', err)
|
|
98
|
+
setError(
|
|
99
|
+
err instanceof Error
|
|
100
|
+
? err
|
|
101
|
+
: new Error('Failed to select audio device')
|
|
102
|
+
)
|
|
103
|
+
return false
|
|
104
|
+
} finally {
|
|
105
|
+
setLoading(false)
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
[]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Reset to the default audio input device
|
|
113
|
+
* @returns Promise resolving to a boolean indicating success
|
|
114
|
+
*/
|
|
115
|
+
const resetToDefaultDevice = useCallback(async (): Promise<boolean> => {
|
|
116
|
+
try {
|
|
117
|
+
setLoading(true)
|
|
118
|
+
setError(null)
|
|
119
|
+
|
|
120
|
+
const success = await audioDeviceManager.resetToDefaultDevice()
|
|
121
|
+
|
|
122
|
+
if (success) {
|
|
123
|
+
// Get the updated current device after reset
|
|
124
|
+
const device = await audioDeviceManager.getCurrentDevice()
|
|
125
|
+
setCurrentDevice(device)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return success
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error('Failed to reset to default audio device:', err)
|
|
131
|
+
setError(
|
|
132
|
+
err instanceof Error
|
|
133
|
+
? err
|
|
134
|
+
: new Error('Failed to reset to default audio device')
|
|
135
|
+
)
|
|
136
|
+
return false
|
|
137
|
+
} finally {
|
|
138
|
+
setLoading(false)
|
|
139
|
+
}
|
|
140
|
+
}, [])
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Refresh the list of available devices
|
|
144
|
+
*/
|
|
145
|
+
const refreshDevices = useCallback(async (): Promise<AudioDevice[]> => {
|
|
146
|
+
try {
|
|
147
|
+
setLoading(true)
|
|
148
|
+
setError(null)
|
|
149
|
+
|
|
150
|
+
const updatedDevices = await audioDeviceManager.refreshDevices()
|
|
151
|
+
setDevices(updatedDevices)
|
|
152
|
+
|
|
153
|
+
// Also refresh the current device
|
|
154
|
+
const device = await audioDeviceManager.getCurrentDevice()
|
|
155
|
+
setCurrentDevice(device)
|
|
156
|
+
|
|
157
|
+
return updatedDevices
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.error('Failed to refresh audio devices:', err)
|
|
160
|
+
setError(
|
|
161
|
+
err instanceof Error
|
|
162
|
+
? err
|
|
163
|
+
: new Error('Failed to refresh audio devices')
|
|
164
|
+
)
|
|
165
|
+
return []
|
|
166
|
+
} finally {
|
|
167
|
+
setLoading(false)
|
|
168
|
+
}
|
|
169
|
+
}, [])
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
devices,
|
|
173
|
+
currentDevice,
|
|
174
|
+
loading,
|
|
175
|
+
error,
|
|
176
|
+
selectDevice,
|
|
177
|
+
resetToDefaultDevice,
|
|
178
|
+
refreshDevices,
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,12 @@ export * from './utils/convertPCMToFloat32'
|
|
|
19
19
|
export * from './utils/getWavFileInfo'
|
|
20
20
|
export * from './utils/writeWavHeader'
|
|
21
21
|
|
|
22
|
+
// Export AudioDeviceManager
|
|
23
|
+
export { AudioDeviceManager, audioDeviceManager } from './AudioDeviceManager'
|
|
24
|
+
|
|
25
|
+
// Export useAudioDevices hook
|
|
26
|
+
export { useAudioDevices } from './hooks/useAudioDevices'
|
|
27
|
+
|
|
22
28
|
export {
|
|
23
29
|
AudioRecorderProvider,
|
|
24
30
|
ExpoAudioStreamModule,
|
package/src/types/crc-32.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
declare module 'crc-32' {
|
|
2
2
|
interface CRC32 {
|
|
3
|
-
(data: string | Uint8Array): number
|
|
4
|
-
buf(data: Uint8Array): number
|
|
3
|
+
(data: string | Uint8Array): number
|
|
4
|
+
buf(data: Uint8Array): number
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
const crc32: CRC32
|
|
8
|
-
export default crc32
|
|
9
|
-
}
|
|
6
|
+
|
|
7
|
+
const crc32: CRC32
|
|
8
|
+
export default crc32
|
|
9
|
+
}
|
package/src/useAudioRecorder.tsx
CHANGED
|
@@ -27,6 +27,7 @@ export interface UseAudioRecorderProps {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface UseAudioRecorderState {
|
|
30
|
+
prepareRecording: (_: RecordingConfig) => Promise<void>
|
|
30
31
|
startRecording: (_: RecordingConfig) => Promise<StartRecordingResult>
|
|
31
32
|
stopRecording: () => Promise<AudioRecording>
|
|
32
33
|
pauseRecording: () => Promise<void>
|
|
@@ -280,7 +281,7 @@ export function useAudioRecorder({
|
|
|
280
281
|
|
|
281
282
|
logger?.debug(
|
|
282
283
|
`[handleAudioAnalysis] Updated analysis data: durationMs=${savedAnalysisData.durationMs}`,
|
|
283
|
-
savedAnalysisData
|
|
284
|
+
{ dataPoints: savedAnalysisData.dataPoints.length }
|
|
284
285
|
)
|
|
285
286
|
|
|
286
287
|
// Call the onAudioAnalysis callback if it exists in the recording config
|
|
@@ -511,6 +512,30 @@ export function useAudioRecorder({
|
|
|
511
512
|
[handleAudioAnalysis, dispatch]
|
|
512
513
|
)
|
|
513
514
|
|
|
515
|
+
const prepareRecording = useCallback(
|
|
516
|
+
async (recordingOptions: RecordingConfig) => {
|
|
517
|
+
recordingConfigRef.current = recordingOptions
|
|
518
|
+
logger?.debug(`preparing recording`, recordingOptions)
|
|
519
|
+
|
|
520
|
+
analysisRef.current = { ...defaultAnalysis } // Reset analysis data
|
|
521
|
+
fullAnalysisRef.current = { ...defaultAnalysis }
|
|
522
|
+
const { onAudioStream, ...options } = recordingOptions
|
|
523
|
+
|
|
524
|
+
// Store onAudioStream for later use when recording starts
|
|
525
|
+
if (typeof onAudioStream === 'function') {
|
|
526
|
+
onAudioStreamRef.current = onAudioStream
|
|
527
|
+
} else {
|
|
528
|
+
logger?.warn(`onAudioStream is not a function`, onAudioStream)
|
|
529
|
+
onAudioStreamRef.current = null
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Call the native prepareRecording method
|
|
533
|
+
await ExpoAudioStream.prepareRecording(options)
|
|
534
|
+
logger?.debug(`recording prepared successfully`)
|
|
535
|
+
},
|
|
536
|
+
[]
|
|
537
|
+
)
|
|
538
|
+
|
|
514
539
|
const stopRecording = useCallback(async () => {
|
|
515
540
|
logger?.debug(`stoping recording`)
|
|
516
541
|
|
|
@@ -606,6 +631,7 @@ export function useAudioRecorder({
|
|
|
606
631
|
}, []) // Empty dependency array since we want this to run once
|
|
607
632
|
|
|
608
633
|
return {
|
|
634
|
+
prepareRecording,
|
|
609
635
|
startRecording,
|
|
610
636
|
stopRecording,
|
|
611
637
|
pauseRecording,
|
package/src/utils/BlobFix.ts
CHANGED
|
@@ -249,11 +249,13 @@ const sections: Record<number, Section> = {
|
|
|
249
249
|
class WebmBase<T> {
|
|
250
250
|
source?: Uint8Array
|
|
251
251
|
data?: T
|
|
252
|
+
name: string
|
|
253
|
+
type: string
|
|
252
254
|
|
|
253
|
-
constructor(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
constructor(name = 'Unknown', type = 'Unknown') {
|
|
256
|
+
this.name = name
|
|
257
|
+
this.type = type
|
|
258
|
+
}
|
|
257
259
|
|
|
258
260
|
updateBySource() {}
|
|
259
261
|
|
|
@@ -12,6 +12,8 @@ export interface WavHeaderOptions {
|
|
|
12
12
|
numChannels: number
|
|
13
13
|
/** The bit depth of the audio (e.g., 16, 24, or 32). */
|
|
14
14
|
bitDepth: number
|
|
15
|
+
/** Whether the audio data is in float format (only applies to 32-bit) */
|
|
16
|
+
isFloat?: boolean
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
@@ -30,30 +32,17 @@ export interface WavHeaderOptions {
|
|
|
30
32
|
* @returns An ArrayBuffer containing the WAV header, or the header combined with the provided audio data.
|
|
31
33
|
*
|
|
32
34
|
* @throws {Error} Throws an error if the provided options are invalid or if the buffer is too small.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* // Create a standalone WAV header
|
|
36
|
-
* const header = writeWavHeader({
|
|
37
|
-
* sampleRate: 44100,
|
|
38
|
-
* numChannels: 2,
|
|
39
|
-
* bitDepth: 16
|
|
40
|
-
* });
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* // Create a WAV header and combine it with audio data
|
|
44
|
-
* const completeWav = writeWavHeader({
|
|
45
|
-
* buffer: audioData,
|
|
46
|
-
* sampleRate: 44100,
|
|
47
|
-
* numChannels: 2,
|
|
48
|
-
* bitDepth: 16
|
|
49
|
-
* });
|
|
50
35
|
*/
|
|
51
36
|
export const writeWavHeader = ({
|
|
52
37
|
buffer,
|
|
53
38
|
sampleRate,
|
|
54
39
|
numChannels,
|
|
55
40
|
bitDepth,
|
|
41
|
+
isFloat = bitDepth === 32, // Default to float for 32-bit
|
|
56
42
|
}: WavHeaderOptions): ArrayBuffer => {
|
|
43
|
+
// For 32-bit float, we use format 3, otherwise format 1 for PCM
|
|
44
|
+
const audioFormat = isFloat ? 3 : 1 // 3 = IEEE float, 1 = PCM
|
|
45
|
+
|
|
57
46
|
const bytesPerSample = bitDepth / 8
|
|
58
47
|
const blockAlign = numChannels * bytesPerSample
|
|
59
48
|
const byteRate = sampleRate * blockAlign
|
|
@@ -67,22 +56,30 @@ export const writeWavHeader = ({
|
|
|
67
56
|
|
|
68
57
|
// Function to write or update the header
|
|
69
58
|
const writeHeader = (view: DataView, dataSize: number = 0xffffffff) => {
|
|
59
|
+
// RIFF chunk descriptor
|
|
70
60
|
writeString(view, 0, 'RIFF') // ChunkID
|
|
71
|
-
view.setUint32(4, 36 + dataSize, true) // ChunkSize
|
|
61
|
+
view.setUint32(4, 36 + dataSize, true) // ChunkSize: 4 + (8 + 16) + (8 + dataSize)
|
|
72
62
|
writeString(view, 8, 'WAVE') // Format
|
|
63
|
+
|
|
64
|
+
// "fmt " sub-chunk
|
|
73
65
|
writeString(view, 12, 'fmt ') // Subchunk1ID
|
|
74
|
-
view.setUint32(16, 16, true) // Subchunk1Size (16 for PCM)
|
|
75
|
-
view.setUint16(20,
|
|
66
|
+
view.setUint32(16, 16, true) // Subchunk1Size (16 for PCM/Float)
|
|
67
|
+
view.setUint16(20, audioFormat, true) // AudioFormat (3 for float, 1 for PCM)
|
|
76
68
|
view.setUint16(22, numChannels, true) // NumChannels
|
|
77
69
|
view.setUint32(24, sampleRate, true) // SampleRate
|
|
78
|
-
view.setUint32(28, byteRate, true) // ByteRate
|
|
79
|
-
view.setUint16(32, blockAlign, true) // BlockAlign
|
|
70
|
+
view.setUint32(28, byteRate, true) // ByteRate = SampleRate * NumChannels * BitsPerSample/8
|
|
71
|
+
view.setUint16(32, blockAlign, true) // BlockAlign = NumChannels * BitsPerSample/8
|
|
80
72
|
view.setUint16(34, bitDepth, true) // BitsPerSample
|
|
73
|
+
|
|
74
|
+
// "data" sub-chunk
|
|
81
75
|
writeString(view, 36, 'data') // Subchunk2ID
|
|
82
|
-
view.setUint32(40, dataSize, true) // Subchunk2Size
|
|
76
|
+
view.setUint32(40, dataSize, true) // Subchunk2Size = NumSamples * NumChannels * BitsPerSample/8
|
|
83
77
|
}
|
|
84
78
|
|
|
85
79
|
if (buffer) {
|
|
80
|
+
// Handle existing buffer
|
|
81
|
+
|
|
82
|
+
// Check for minimum size
|
|
86
83
|
if (buffer.byteLength < 44) {
|
|
87
84
|
throw new Error('Buffer is too small to contain a valid WAV header')
|
|
88
85
|
}
|
|
@@ -97,15 +94,19 @@ export const writeWavHeader = ({
|
|
|
97
94
|
writeHeader(view, buffer.byteLength - 44)
|
|
98
95
|
return buffer
|
|
99
96
|
} else {
|
|
100
|
-
//
|
|
97
|
+
// Create a new buffer with header + data
|
|
101
98
|
const newBuffer = new ArrayBuffer(44 + buffer.byteLength)
|
|
102
99
|
const newView = new DataView(newBuffer)
|
|
100
|
+
|
|
101
|
+
// Write header to new buffer
|
|
103
102
|
writeHeader(newView, buffer.byteLength)
|
|
103
|
+
|
|
104
|
+
// Copy audio data after header
|
|
104
105
|
new Uint8Array(newBuffer).set(new Uint8Array(buffer), 44)
|
|
105
106
|
return newBuffer
|
|
106
107
|
}
|
|
107
108
|
} else {
|
|
108
|
-
// Create
|
|
109
|
+
// Create standalone header
|
|
109
110
|
const headerBuffer = new ArrayBuffer(44)
|
|
110
111
|
const view = new DataView(headerBuffer)
|
|
111
112
|
writeHeader(view)
|
|
@@ -440,16 +440,36 @@ function estimatePitch(segment, sampleRate) {
|
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
// Unique ID counter
|
|
443
|
+
// Unique ID counter - the only state we need to maintain
|
|
444
444
|
let uniqueIdCounter = 0
|
|
445
|
-
let accumulatedDataPoints = []
|
|
446
445
|
let lastEmitTime = Date.now()
|
|
447
446
|
|
|
448
447
|
self.onmessage = function (event) {
|
|
448
|
+
// Extract enableLogging early so we can use it consistently
|
|
449
|
+
const enableLogging = event.data.enableLogging || false;
|
|
450
|
+
|
|
451
|
+
// Create consistent logger that only logs when enabled
|
|
452
|
+
const logger = enableLogging ? {
|
|
453
|
+
debug: (...args) => console.debug('[Worker]', ...args),
|
|
454
|
+
log: (...args) => console.log('[Worker]', ...args),
|
|
455
|
+
warn: (...args) => console.warn('[Worker]', ...args),
|
|
456
|
+
error: (...args) => console.error('[Worker]', ...args)
|
|
457
|
+
} : {
|
|
458
|
+
debug: () => {},
|
|
459
|
+
log: () => {},
|
|
460
|
+
warn: () => {},
|
|
461
|
+
error: () => {}
|
|
462
|
+
};
|
|
463
|
+
|
|
449
464
|
// Check if this is a reset command
|
|
450
465
|
if (event.data.command === 'resetCounter') {
|
|
451
|
-
|
|
452
|
-
|
|
466
|
+
const newValue = event.data.value;
|
|
467
|
+
logger.log('Reset counter request received with value:', newValue);
|
|
468
|
+
|
|
469
|
+
// Always respect explicit resets through the resetCounter command
|
|
470
|
+
uniqueIdCounter = typeof newValue === 'number' ? newValue : 0;
|
|
471
|
+
logger.log('Counter explicitly set to:', uniqueIdCounter);
|
|
472
|
+
|
|
453
473
|
return; // Exit early, don't process audio
|
|
454
474
|
}
|
|
455
475
|
|
|
@@ -464,34 +484,13 @@ self.onmessage = function (event) {
|
|
|
464
484
|
numberOfChannels,
|
|
465
485
|
features: _features,
|
|
466
486
|
intervalAnalysis = 500,
|
|
467
|
-
enableLogging,
|
|
468
|
-
resetCounter,
|
|
469
|
-
startCounterFrom,
|
|
470
487
|
} = event.data
|
|
471
488
|
|
|
472
|
-
// Also handle reset as part of regular message
|
|
473
|
-
if (resetCounter) {
|
|
474
|
-
uniqueIdCounter = startCounterFrom || 0;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
489
|
// Calculate subChunkStartTime safely, defaulting to 0 if fullAudioDurationMs is not a valid number
|
|
478
490
|
const subChunkStartTime = (typeof fullAudioDurationMs === 'number' && !isNaN(fullAudioDurationMs) && fullAudioDurationMs >= 0)
|
|
479
491
|
? fullAudioDurationMs / 1000
|
|
480
492
|
: 0;
|
|
481
493
|
|
|
482
|
-
|
|
483
|
-
// Create a simple logger that only logs when enabled
|
|
484
|
-
const logger = enableLogging ? {
|
|
485
|
-
debug: (...args) => console.debug('[Worker]', ...args),
|
|
486
|
-
log: (...args) => console.log('[Worker]', ...args),
|
|
487
|
-
error: (...args) => console.error('[Worker]', ...args)
|
|
488
|
-
} : {
|
|
489
|
-
debug: () => {},
|
|
490
|
-
log: () => {},
|
|
491
|
-
error: () => {}
|
|
492
|
-
}
|
|
493
|
-
logger.log('[Worker] START Feature Extractor - hasData: ' + (event.data ? true : false) + ', channelData: ' + (event.data.channelData ? event.data.channelData.length : 0) + ', fullAudioDurationMs: ' + (event.data.fullAudioDurationMs || 0) + ', sampleRate: ' + (event.data.sampleRate || 0) + ', segmentDurationMs: ' + (event.data.segmentDurationMs || 0) + ', algorithm: ' + (event.data.algorithm || 'none') + ', bitDepth: ' + (event.data.bitDepth || 0) + ', numberOfChannels: ' + (event.data.numberOfChannels || 0) + ', features: ' + (event.data.features ? Object.keys(event.data.features).length : 0) + ', intervalAnalysis: ' + (event.data.intervalAnalysis || 0) + ', dataKeys: ' + (event.data ? Object.keys(event.data).join(',') : ''));
|
|
494
|
-
|
|
495
494
|
const features = _features || {}
|
|
496
495
|
const bytesPerSample = bitDepth / 8; // Calculate bytes per sample
|
|
497
496
|
|
|
@@ -692,6 +691,7 @@ self.onmessage = function (event) {
|
|
|
692
691
|
|
|
693
692
|
var spectralFeatures = computeSpectralFeatures(channelData.slice(startIdx, endIdx), sampleRate, features);
|
|
694
693
|
|
|
694
|
+
// Simply use the counter, increment after assigning
|
|
695
695
|
const dataPoint = {
|
|
696
696
|
id: uniqueIdCounter++,
|
|
697
697
|
amplitude: maxAmp,
|
|
@@ -756,6 +756,7 @@ self.onmessage = function (event) {
|
|
|
756
756
|
|
|
757
757
|
var spectralFeatures = computeSpectralFeatures(channelData.slice(startIdx, endIdx), sampleRate, features);
|
|
758
758
|
|
|
759
|
+
// Simply use the counter, increment after assigning
|
|
759
760
|
const dataPoint = {
|
|
760
761
|
id: uniqueIdCounter++,
|
|
761
762
|
amplitude: maxAmp,
|
|
@@ -769,7 +770,7 @@ self.onmessage = function (event) {
|
|
|
769
770
|
samples: remainingSamples,
|
|
770
771
|
}
|
|
771
772
|
|
|
772
|
-
logger.
|
|
773
|
+
logger.debug('extractWaveform - dataPoint', dataPoint);
|
|
773
774
|
// Extract features if any are requested
|
|
774
775
|
const extractedFeatures = createFeaturesObject(
|
|
775
776
|
features,
|
|
@@ -17,6 +17,7 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
17
17
|
this.port.onmessage = this.handleMessage.bind(this)
|
|
18
18
|
this.enableLogging = false
|
|
19
19
|
this.exportIntervalSamples = 0
|
|
20
|
+
this.currentPosition = 0 // Track current position in seconds
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
handleMessage(event) {
|
|
@@ -36,6 +37,14 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
36
37
|
}
|
|
37
38
|
this.exportBitDepth =
|
|
38
39
|
event.data.exportBitDepth || this.recordBitDepth
|
|
40
|
+
|
|
41
|
+
// Handle position parameter for device switching
|
|
42
|
+
if (typeof event.data.position === 'number' && event.data.position > 0) {
|
|
43
|
+
this.currentPosition = event.data.position
|
|
44
|
+
if (this.enableLogging) {
|
|
45
|
+
console.log('AudioWorklet initialized with position:', this.currentPosition)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
39
48
|
break
|
|
40
49
|
|
|
41
50
|
case 'stop':
|
|
@@ -44,6 +53,14 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
44
53
|
this.processChunk()
|
|
45
54
|
}
|
|
46
55
|
break
|
|
56
|
+
|
|
57
|
+
case 'pause':
|
|
58
|
+
// Just a placeholder for pause handling
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
case 'resume':
|
|
62
|
+
// Just a placeholder for resume handling
|
|
63
|
+
break
|
|
47
64
|
}
|
|
48
65
|
}
|
|
49
66
|
|
|
@@ -138,14 +155,21 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
138
155
|
? this.convertBitDepth(resampledChunk, this.exportBitDepth)
|
|
139
156
|
: resampledChunk
|
|
140
157
|
|
|
141
|
-
//
|
|
158
|
+
// Calculate the duration in seconds
|
|
159
|
+
const chunkDuration = finalBuffer.length / this.exportSampleRate
|
|
160
|
+
|
|
161
|
+
// Send processed chunk with the current position
|
|
142
162
|
this.port.postMessage({
|
|
143
163
|
command: 'newData',
|
|
144
164
|
recordedData: finalBuffer,
|
|
145
165
|
sampleRate: this.exportSampleRate,
|
|
146
166
|
bitDepth: this.exportBitDepth,
|
|
147
167
|
numberOfChannels: this.numberOfChannels,
|
|
168
|
+
position: this.currentPosition,
|
|
148
169
|
})
|
|
170
|
+
|
|
171
|
+
// Update the position
|
|
172
|
+
this.currentPosition += chunkDuration
|
|
149
173
|
|
|
150
174
|
// Clear the current chunk
|
|
151
175
|
this.currentChunk = []
|