@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.
@@ -5,23 +5,23 @@ const DEFAULT_SAMPLE_RATE = 44100
5
5
  class RecorderProcessor extends AudioWorkletProcessor {
6
6
  constructor() {
7
7
  super()
8
- this.recordedBuffers = [] // Float32Array
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 // To be overwritten
14
- this.exportSampleRate = DEFAULT_SAMPLE_RATE // To be overwritten
15
- this.recordBitDepth = DEFAULT_BIT_DEPTH // Default to 32-bit depth
16
- this.exportBitDepth = DEFAULT_BIT_DEPTH // To be overwritten
17
- this.numberOfChannels = 1 // Default to 1 channel (mono)
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.getAllRecordedData()
44
- .then((fullRecordedData) => {
45
- this.port.postMessage({
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.newRecBuffer.push(newBuffer)
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.exportNewData()
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
- floatTo16BitPCM(input) {
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
- (samples.length * targetSampleRate) / this.recordSampleRate
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
- async resampleBuffer(buffer, targetSampleRate) {
140
- if (typeof OfflineAudioContext === 'undefined') {
141
- return this.resample(buffer, targetSampleRate)
142
- }
143
-
144
- if (this.recordSampleRate === targetSampleRate) {
145
- return buffer
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
- async getAllRecordedData() {
220
- const length = this.recordedBuffers.reduce(
221
- (acc, buffer) => acc + buffer.length,
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 mergedBuffer = this.mergeBuffers(this.recordedBuffers, length)
225
- const resampledBuffer = await this.resampleBuffer(
226
- mergedBuffer,
227
- this.exportSampleRate
228
- )
229
- // Convert to the desired bit depth if necessary
230
- let finalBuffer = resampledBuffer
231
- if (this.recordBitDepth !== this.exportBitDepth) {
232
- if (this.exportBitDepth === 16) {
233
- finalBuffer = this.floatTo16BitPCM(resampledBuffer)
234
- } else if (this.exportBitDepth === 32) {
235
- finalBuffer = this.floatTo32BitPCM(resampledBuffer)
236
- }
237
- }
238
-
239
- const originalSize = mergedBuffer.byteLength
240
- const resampledSize = resampledBuffer.byteLength
241
- const finalSize = finalBuffer.byteLength
242
-
243
- this.recordedBuffers.length = 0 // Clear the buffers after extraction
244
-
245
- return finalBuffer
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