@siteed/expo-audio-stream 1.8.0 → 1.9.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 +18 -1
- package/build/ExpoAudioStream.types.d.ts +1 -14
- package/build/ExpoAudioStream.types.d.ts.map +1 -1
- package/build/ExpoAudioStream.types.js.map +1 -1
- package/build/ExpoAudioStream.web.d.ts +5 -3
- package/build/ExpoAudioStream.web.d.ts.map +1 -1
- package/build/ExpoAudioStream.web.js +52 -57
- package/build/ExpoAudioStream.web.js.map +1 -1
- package/build/WebRecorder.web.d.ts +7 -5
- package/build/WebRecorder.web.d.ts.map +1 -1
- package/build/WebRecorder.web.js +150 -197
- package/build/WebRecorder.web.js.map +1 -1
- package/build/useAudioRecorder.d.ts +2 -2
- package/build/useAudioRecorder.d.ts.map +1 -1
- package/build/useAudioRecorder.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 +65 -160
- package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoAudioStream.types.ts +1 -15
- package/src/ExpoAudioStream.web.ts +55 -58
- package/src/WebRecorder.web.ts +182 -228
- package/src/useAudioRecorder.tsx +1 -2
- package/src/workers/inlineAudioWebWorker.web.tsx +65 -160
|
@@ -5,23 +5,23 @@ const DEFAULT_SAMPLE_RATE = 44100
|
|
|
5
5
|
class RecorderProcessor extends AudioWorkletProcessor {
|
|
6
6
|
constructor() {
|
|
7
7
|
super()
|
|
8
|
-
this.
|
|
9
|
-
this.newRecBuffer = [] // Float32Array
|
|
10
|
-
this.resampledBuffer = [] // Float32Array
|
|
11
|
-
this.exportIntervalSamples = 0
|
|
8
|
+
this.currentChunk = [] // Float32Array
|
|
12
9
|
this.samplesSinceLastExport = 0
|
|
13
|
-
this.recordSampleRate = DEFAULT_SAMPLE_RATE
|
|
14
|
-
this.exportSampleRate = DEFAULT_SAMPLE_RATE
|
|
15
|
-
this.recordBitDepth = DEFAULT_BIT_DEPTH
|
|
16
|
-
this.exportBitDepth = DEFAULT_BIT_DEPTH
|
|
17
|
-
this.numberOfChannels = 1
|
|
10
|
+
this.recordSampleRate = DEFAULT_SAMPLE_RATE
|
|
11
|
+
this.exportSampleRate = DEFAULT_SAMPLE_RATE
|
|
12
|
+
this.recordBitDepth = DEFAULT_BIT_DEPTH
|
|
13
|
+
this.exportBitDepth = DEFAULT_BIT_DEPTH
|
|
14
|
+
this.numberOfChannels = 1
|
|
18
15
|
this.isRecording = true
|
|
19
16
|
this.port.onmessage = this.handleMessage.bind(this)
|
|
17
|
+
this.logger = undefined
|
|
18
|
+
this.exportIntervalSamples = 0
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
handleMessage(event) {
|
|
23
22
|
switch (event.data.command) {
|
|
24
23
|
case 'init':
|
|
24
|
+
this.logger = event.data.logger
|
|
25
25
|
this.recordSampleRate = event.data.recordSampleRate
|
|
26
26
|
this.exportSampleRate =
|
|
27
27
|
event.data.exportSampleRate || event.data.recordSampleRate
|
|
@@ -34,28 +34,14 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
34
34
|
this.recordBitDepth = event.data.recordBitDepth
|
|
35
35
|
}
|
|
36
36
|
this.exportBitDepth =
|
|
37
|
-
event.data.exportBitDepth ||
|
|
38
|
-
this.recordBitDepth ||
|
|
39
|
-
DEFAULT_BIT_DEPTH
|
|
37
|
+
event.data.exportBitDepth || this.recordBitDepth
|
|
40
38
|
break
|
|
39
|
+
|
|
41
40
|
case 'stop':
|
|
42
41
|
this.isRecording = false
|
|
43
|
-
this.
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
command: 'recordedData',
|
|
47
|
-
recordedData: fullRecordedData,
|
|
48
|
-
bitDepth: this.exportBitDepth,
|
|
49
|
-
sampleRate: this.exportSampleRate,
|
|
50
|
-
})
|
|
51
|
-
return fullRecordedData
|
|
52
|
-
})
|
|
53
|
-
.catch((error) => {
|
|
54
|
-
console.error(
|
|
55
|
-
'RecorderProcessor Error extracting recorded data:',
|
|
56
|
-
error
|
|
57
|
-
)
|
|
58
|
-
})
|
|
42
|
+
if (this.currentChunk.length > 0) {
|
|
43
|
+
this.processChunk()
|
|
44
|
+
}
|
|
59
45
|
break
|
|
60
46
|
}
|
|
61
47
|
}
|
|
@@ -65,12 +51,11 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
65
51
|
const input = inputs[0]
|
|
66
52
|
if (input.length > 0) {
|
|
67
53
|
const newBuffer = new Float32Array(input[0])
|
|
68
|
-
this.
|
|
69
|
-
this.recordedBuffers.push(newBuffer)
|
|
54
|
+
this.currentChunk.push(newBuffer)
|
|
70
55
|
this.samplesSinceLastExport += newBuffer.length
|
|
71
56
|
|
|
72
57
|
if (this.samplesSinceLastExport >= this.exportIntervalSamples) {
|
|
73
|
-
this.
|
|
58
|
+
this.processChunk()
|
|
74
59
|
this.samplesSinceLastExport = 0
|
|
75
60
|
}
|
|
76
61
|
}
|
|
@@ -87,38 +72,15 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
87
72
|
return result
|
|
88
73
|
}
|
|
89
74
|
|
|
90
|
-
|
|
91
|
-
const output = new Int16Array(input.length)
|
|
92
|
-
for (let i = 0; i < input.length; i++) {
|
|
93
|
-
const s = Math.max(-1, Math.min(1, input[i]))
|
|
94
|
-
output[i] = s < 0 ? s * 0x8000 : s * 0x7fff
|
|
95
|
-
}
|
|
96
|
-
console.debug(
|
|
97
|
-
'RecorderProcessor Float to 16-bit PCM conversion complete. Output byte length:',
|
|
98
|
-
output.byteLength
|
|
99
|
-
)
|
|
100
|
-
return output
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
floatTo32BitPCM(input) {
|
|
104
|
-
const output = new Int32Array(input.length)
|
|
105
|
-
for (let i = 0; i < input.length; i++) {
|
|
106
|
-
const s = Math.max(-1, Math.min(1, input[i]))
|
|
107
|
-
output[i] = s < 0 ? s * 0x80000000 : s * 0x7fffffff
|
|
108
|
-
}
|
|
109
|
-
console.debug(
|
|
110
|
-
'RecorderProcessor Float to 32-bit PCM conversion complete. Output byte length:',
|
|
111
|
-
output.byteLength
|
|
112
|
-
)
|
|
113
|
-
return output
|
|
114
|
-
}
|
|
115
|
-
|
|
75
|
+
// Keep basic resampling for sample rate conversion
|
|
116
76
|
resample(samples, targetSampleRate) {
|
|
117
77
|
if (this.recordSampleRate === targetSampleRate) {
|
|
118
78
|
return samples
|
|
119
79
|
}
|
|
120
80
|
const resampledBuffer = new Float32Array(
|
|
121
|
-
(
|
|
81
|
+
Math.ceil(
|
|
82
|
+
(samples.length * targetSampleRate) / this.recordSampleRate
|
|
83
|
+
)
|
|
122
84
|
)
|
|
123
85
|
const ratio = this.recordSampleRate / targetSampleRate
|
|
124
86
|
let offset = 0
|
|
@@ -130,119 +92,62 @@ class RecorderProcessor extends AudioWorkletProcessor {
|
|
|
130
92
|
accum += samples[j]
|
|
131
93
|
count++
|
|
132
94
|
}
|
|
133
|
-
resampledBuffer[i] = accum / count
|
|
95
|
+
resampledBuffer[i] = count > 0 ? accum / count : 0
|
|
134
96
|
offset = nextOffset
|
|
135
97
|
}
|
|
136
98
|
return resampledBuffer
|
|
137
99
|
}
|
|
138
100
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
const offlineContext = new OfflineAudioContext(
|
|
148
|
-
this.numberOfChannels,
|
|
149
|
-
buffer.length,
|
|
150
|
-
this.recordSampleRate
|
|
151
|
-
)
|
|
152
|
-
const sourceBuffer = offlineContext.createBuffer(
|
|
153
|
-
this.numberOfChannels,
|
|
154
|
-
buffer.length,
|
|
155
|
-
this.recordSampleRate
|
|
156
|
-
)
|
|
157
|
-
sourceBuffer.copyToChannel(buffer, 0)
|
|
158
|
-
|
|
159
|
-
const bufferSource = offlineContext.createBufferSource()
|
|
160
|
-
bufferSource.buffer = sourceBuffer
|
|
161
|
-
bufferSource.connect(offlineContext.destination)
|
|
162
|
-
bufferSource.start()
|
|
163
|
-
|
|
164
|
-
const renderedBuffer = await offlineContext.startRendering()
|
|
165
|
-
|
|
166
|
-
const resampledBuffer = new Float32Array(renderedBuffer.length)
|
|
167
|
-
renderedBuffer.copyFromChannel(resampledBuffer, 0)
|
|
168
|
-
|
|
169
|
-
return resampledBuffer
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async exportNewData() {
|
|
173
|
-
// Calculate the total length of the new recorded buffers
|
|
174
|
-
const length = this.newRecBuffer.reduce(
|
|
175
|
-
(acc, buffer) => acc + buffer.length,
|
|
176
|
-
0
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
// Merge all new recorded buffers into a single buffer
|
|
180
|
-
const mergedBuffer = this.mergeBuffers(this.newRecBuffer, length)
|
|
181
|
-
|
|
182
|
-
const resampledBuffer = await this.resampleBuffer(
|
|
183
|
-
mergedBuffer,
|
|
184
|
-
this.exportSampleRate
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
let finalBuffer = resampledBuffer // Float32Array
|
|
188
|
-
if (this.recordBitDepth !== this.exportBitDepth) {
|
|
189
|
-
if (this.exportBitDepth === 16) {
|
|
190
|
-
finalBuffer = this.floatTo16BitPCM(resampledBuffer)
|
|
191
|
-
} else if (this.exportBitDepth === 32) {
|
|
192
|
-
finalBuffer = this.floatTo32BitPCM(resampledBuffer)
|
|
101
|
+
// Keep bit depth conversion if needed
|
|
102
|
+
convertBitDepth(input, targetBitDepth) {
|
|
103
|
+
if (targetBitDepth === 32) {
|
|
104
|
+
const output = new Int32Array(input.length)
|
|
105
|
+
for (let i = 0; i < input.length; i++) {
|
|
106
|
+
const s = Math.max(-1, Math.min(1, input[i]))
|
|
107
|
+
output[i] = s < 0 ? s * 0x80000000 : s * 0x7fffffff
|
|
193
108
|
}
|
|
109
|
+
return output
|
|
110
|
+
} else if (targetBitDepth === 16) {
|
|
111
|
+
const output = new Int16Array(input.length)
|
|
112
|
+
for (let i = 0; i < input.length; i++) {
|
|
113
|
+
const s = Math.max(-1, Math.min(1, input[i]))
|
|
114
|
+
output[i] = s < 0 ? s * 0x8000 : s * 0x7fff
|
|
115
|
+
}
|
|
116
|
+
return output
|
|
194
117
|
}
|
|
195
|
-
|
|
196
|
-
const originalSize = mergedBuffer.byteLength
|
|
197
|
-
const resampledSize = resampledBuffer.byteLength
|
|
198
|
-
const finalSize = finalBuffer.byteLength
|
|
199
|
-
|
|
200
|
-
// Clear the new recorded buffers after they have been processed
|
|
201
|
-
this.newRecBuffer.length = 0
|
|
202
|
-
|
|
203
|
-
// Post the message to the main thread
|
|
204
|
-
// The first argument is the message data, containing the encoded WAV buffer
|
|
205
|
-
// The second argument is the transfer list, which transfers ownership of the ArrayBuffer
|
|
206
|
-
// to the main thread, avoiding the need to copy the buffer and improving performance
|
|
207
|
-
// this.port.postMessage({ recordedData: encodedWav.buffer, sampleRate: this.recordSampleRate }, [encodedWav.buffer]);
|
|
208
|
-
this.port.postMessage(
|
|
209
|
-
{
|
|
210
|
-
command: 'newData',
|
|
211
|
-
recordedData: finalBuffer,
|
|
212
|
-
sampleRate: this.exportSampleRate,
|
|
213
|
-
bitDepth: this.exportBitDepth,
|
|
214
|
-
},
|
|
215
|
-
[]
|
|
216
|
-
)
|
|
118
|
+
return input
|
|
217
119
|
}
|
|
218
120
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
121
|
+
processChunk() {
|
|
122
|
+
if (this.currentChunk.length === 0) return
|
|
123
|
+
|
|
124
|
+
// Merge buffers
|
|
125
|
+
const chunkLength = this.currentChunk.reduce(
|
|
126
|
+
(acc, buf) => acc + buf.length,
|
|
222
127
|
0
|
|
223
128
|
)
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Convert
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
129
|
+
const mergedChunk = this.mergeBuffers(this.currentChunk, chunkLength)
|
|
130
|
+
|
|
131
|
+
// Resample if needed
|
|
132
|
+
const resampledChunk = this.resample(mergedChunk, this.exportSampleRate)
|
|
133
|
+
|
|
134
|
+
// Convert bit depth if needed
|
|
135
|
+
const finalBuffer =
|
|
136
|
+
this.recordBitDepth !== this.exportBitDepth
|
|
137
|
+
? this.convertBitDepth(resampledChunk, this.exportBitDepth)
|
|
138
|
+
: resampledChunk
|
|
139
|
+
|
|
140
|
+
// Send processed chunk
|
|
141
|
+
this.port.postMessage({
|
|
142
|
+
command: 'newData',
|
|
143
|
+
recordedData: finalBuffer,
|
|
144
|
+
sampleRate: this.exportSampleRate,
|
|
145
|
+
bitDepth: this.exportBitDepth,
|
|
146
|
+
numberOfChannels: this.numberOfChannels,
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
// Clear the current chunk
|
|
150
|
+
this.currentChunk = []
|
|
246
151
|
}
|
|
247
152
|
}
|
|
248
153
|
|