@siteed/expo-audio-stream 2.1.0 → 2.2.1-beta.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.
Files changed (189) hide show
  1. package/README.md +23 -260
  2. package/build/index.d.ts +11 -15
  3. package/build/index.js +54 -14
  4. package/build/src/index.d.ts +11 -0
  5. package/build/src/index.js +54 -0
  6. package/package.json +49 -110
  7. package/src/index.ts +18 -32
  8. package/CHANGELOG.md +0 -206
  9. package/android/build.gradle +0 -105
  10. package/android/src/main/AndroidManifest.xml +0 -27
  11. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
  12. package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
  13. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
  14. package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
  15. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
  16. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2235
  17. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
  18. package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -152
  19. package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
  20. package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -21
  21. package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
  22. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -739
  23. package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
  24. package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
  25. package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
  26. package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
  27. package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
  28. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
  29. package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
  30. package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
  31. package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
  32. package/android/src/main/res/drawable/ic_microphone.xml +0 -13
  33. package/android/src/main/res/drawable/ic_pause.xml +0 -10
  34. package/android/src/main/res/drawable/ic_play.xml +0 -10
  35. package/android/src/main/res/drawable/ic_stop.xml +0 -10
  36. package/android/src/main/res/layout/notification_recording.xml +0 -37
  37. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
  38. package/app.plugin.js +0 -1
  39. package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -179
  40. package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
  41. package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
  42. package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
  43. package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -68
  44. package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
  45. package/build/AudioAnalysis/extractAudioAnalysis.js +0 -203
  46. package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
  47. package/build/AudioAnalysis/extractAudioData.d.ts +0 -3
  48. package/build/AudioAnalysis/extractAudioData.d.ts.map +0 -1
  49. package/build/AudioAnalysis/extractAudioData.js +0 -5
  50. package/build/AudioAnalysis/extractAudioData.js.map +0 -1
  51. package/build/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
  52. package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
  53. package/build/AudioAnalysis/extractMelSpectrogram.js +0 -85
  54. package/build/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
  55. package/build/AudioAnalysis/extractPreview.d.ts +0 -11
  56. package/build/AudioAnalysis/extractPreview.d.ts.map +0 -1
  57. package/build/AudioAnalysis/extractPreview.js +0 -25
  58. package/build/AudioAnalysis/extractPreview.js.map +0 -1
  59. package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
  60. package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  61. package/build/AudioAnalysis/extractWaveform.js +0 -11
  62. package/build/AudioAnalysis/extractWaveform.js.map +0 -1
  63. package/build/AudioRecorder.provider.d.ts +0 -11
  64. package/build/AudioRecorder.provider.d.ts.map +0 -1
  65. package/build/AudioRecorder.provider.js +0 -37
  66. package/build/AudioRecorder.provider.js.map +0 -1
  67. package/build/ExpoAudioStream.native.d.ts +0 -3
  68. package/build/ExpoAudioStream.native.d.ts.map +0 -1
  69. package/build/ExpoAudioStream.native.js +0 -6
  70. package/build/ExpoAudioStream.native.js.map +0 -1
  71. package/build/ExpoAudioStream.types.d.ts +0 -532
  72. package/build/ExpoAudioStream.types.d.ts.map +0 -1
  73. package/build/ExpoAudioStream.types.js +0 -2
  74. package/build/ExpoAudioStream.types.js.map +0 -1
  75. package/build/ExpoAudioStream.web.d.ts +0 -59
  76. package/build/ExpoAudioStream.web.d.ts.map +0 -1
  77. package/build/ExpoAudioStream.web.js +0 -285
  78. package/build/ExpoAudioStream.web.js.map +0 -1
  79. package/build/ExpoAudioStreamModule.d.ts +0 -3
  80. package/build/ExpoAudioStreamModule.d.ts.map +0 -1
  81. package/build/ExpoAudioStreamModule.js +0 -693
  82. package/build/ExpoAudioStreamModule.js.map +0 -1
  83. package/build/WebRecorder.web.d.ts +0 -119
  84. package/build/WebRecorder.web.d.ts.map +0 -1
  85. package/build/WebRecorder.web.js +0 -436
  86. package/build/WebRecorder.web.js.map +0 -1
  87. package/build/constants.d.ts +0 -11
  88. package/build/constants.d.ts.map +0 -1
  89. package/build/constants.js +0 -14
  90. package/build/constants.js.map +0 -1
  91. package/build/events.d.ts +0 -26
  92. package/build/events.d.ts.map +0 -1
  93. package/build/events.js +0 -21
  94. package/build/events.js.map +0 -1
  95. package/build/index.d.ts.map +0 -1
  96. package/build/index.js.map +0 -1
  97. package/build/trimAudio.d.ts +0 -25
  98. package/build/trimAudio.d.ts.map +0 -1
  99. package/build/trimAudio.js +0 -67
  100. package/build/trimAudio.js.map +0 -1
  101. package/build/useAudioRecorder.d.ts +0 -21
  102. package/build/useAudioRecorder.d.ts.map +0 -1
  103. package/build/useAudioRecorder.js +0 -427
  104. package/build/useAudioRecorder.js.map +0 -1
  105. package/build/utils/BlobFix.d.ts +0 -9
  106. package/build/utils/BlobFix.d.ts.map +0 -1
  107. package/build/utils/BlobFix.js +0 -498
  108. package/build/utils/BlobFix.js.map +0 -1
  109. package/build/utils/audioProcessing.d.ts +0 -24
  110. package/build/utils/audioProcessing.d.ts.map +0 -1
  111. package/build/utils/audioProcessing.js +0 -133
  112. package/build/utils/audioProcessing.js.map +0 -1
  113. package/build/utils/concatenateBuffers.d.ts +0 -8
  114. package/build/utils/concatenateBuffers.d.ts.map +0 -1
  115. package/build/utils/concatenateBuffers.js +0 -21
  116. package/build/utils/concatenateBuffers.js.map +0 -1
  117. package/build/utils/convertPCMToFloat32.d.ts +0 -13
  118. package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
  119. package/build/utils/convertPCMToFloat32.js +0 -120
  120. package/build/utils/convertPCMToFloat32.js.map +0 -1
  121. package/build/utils/encodingToBitDepth.d.ts +0 -5
  122. package/build/utils/encodingToBitDepth.d.ts.map +0 -1
  123. package/build/utils/encodingToBitDepth.js +0 -13
  124. package/build/utils/encodingToBitDepth.js.map +0 -1
  125. package/build/utils/getWavFileInfo.d.ts +0 -26
  126. package/build/utils/getWavFileInfo.d.ts.map +0 -1
  127. package/build/utils/getWavFileInfo.js +0 -92
  128. package/build/utils/getWavFileInfo.js.map +0 -1
  129. package/build/utils/writeWavHeader.d.ts +0 -49
  130. package/build/utils/writeWavHeader.d.ts.map +0 -1
  131. package/build/utils/writeWavHeader.js +0 -91
  132. package/build/utils/writeWavHeader.js.map +0 -1
  133. package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
  134. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
  135. package/build/workers/InlineFeaturesExtractor.web.js +0 -828
  136. package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
  137. package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
  138. package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
  139. package/build/workers/inlineAudioWebWorker.web.js +0 -157
  140. package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
  141. package/expo-module.config.json +0 -9
  142. package/ios/AudioAnalysisData.swift +0 -74
  143. package/ios/AudioNotificationManager.swift +0 -135
  144. package/ios/AudioProcessingHelpers.swift +0 -743
  145. package/ios/AudioProcessor.swift +0 -1313
  146. package/ios/AudioStreamError.swift +0 -7
  147. package/ios/AudioStreamManager.swift +0 -1708
  148. package/ios/AudioStreamManagerDelegate.swift +0 -16
  149. package/ios/DataPoint.swift +0 -54
  150. package/ios/DecodingConfig.swift +0 -47
  151. package/ios/ExpoAudioStream.podspec +0 -27
  152. package/ios/ExpoAudioStreamModule.swift +0 -805
  153. package/ios/FFT.swift +0 -62
  154. package/ios/Features.swift +0 -95
  155. package/ios/Logger.swift +0 -7
  156. package/ios/NotificationExtension.swift +0 -15
  157. package/ios/RecordingResult.swift +0 -22
  158. package/ios/RecordingSettings.swift +0 -265
  159. package/ios/WaveformExtractor.swift +0 -105
  160. package/plugin/build/index.d.ts +0 -21
  161. package/plugin/build/index.js +0 -191
  162. package/plugin/src/index.ts +0 -278
  163. package/plugin/tsconfig.json +0 -10
  164. package/plugin/tsconfig.tsbuildinfo +0 -1
  165. package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -202
  166. package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -333
  167. package/src/AudioAnalysis/extractAudioData.ts +0 -6
  168. package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -144
  169. package/src/AudioAnalysis/extractPreview.ts +0 -34
  170. package/src/AudioAnalysis/extractWaveform.ts +0 -22
  171. package/src/AudioRecorder.provider.tsx +0 -54
  172. package/src/ExpoAudioStream.native.ts +0 -6
  173. package/src/ExpoAudioStream.types.ts +0 -641
  174. package/src/ExpoAudioStream.web.ts +0 -359
  175. package/src/ExpoAudioStreamModule.ts +0 -967
  176. package/src/WebRecorder.web.ts +0 -580
  177. package/src/constants.ts +0 -18
  178. package/src/events.ts +0 -60
  179. package/src/trimAudio.ts +0 -90
  180. package/src/useAudioRecorder.tsx +0 -620
  181. package/src/utils/BlobFix.ts +0 -559
  182. package/src/utils/audioProcessing.ts +0 -205
  183. package/src/utils/concatenateBuffers.ts +0 -24
  184. package/src/utils/convertPCMToFloat32.ts +0 -170
  185. package/src/utils/encodingToBitDepth.ts +0 -18
  186. package/src/utils/getWavFileInfo.ts +0 -132
  187. package/src/utils/writeWavHeader.ts +0 -114
  188. package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
  189. package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
@@ -1,205 +0,0 @@
1
- // packages/expo-audio-stream/src/utils/audioProcessing.ts
2
- import { Platform } from 'react-native'
3
-
4
- import { ConsoleLike } from '../ExpoAudioStream.types'
5
-
6
- export interface ProcessAudioBufferOptions {
7
- arrayBuffer?: ArrayBuffer
8
- fileUri?: string
9
- targetSampleRate: number
10
- targetChannels: number
11
- normalizeAudio: boolean
12
- startTimeMs?: number
13
- endTimeMs?: number
14
- position?: number
15
- length?: number
16
- audioContext?: AudioContext
17
- logger?: ConsoleLike
18
- }
19
-
20
- export interface ProcessedAudioData {
21
- channelData: Float32Array
22
- samples: number
23
- durationMs: number
24
- sampleRate: number
25
- channels: number
26
- buffer: AudioBuffer
27
- }
28
-
29
- export async function processAudioBuffer({
30
- arrayBuffer,
31
- fileUri,
32
- targetSampleRate,
33
- targetChannels,
34
- normalizeAudio,
35
- startTimeMs,
36
- endTimeMs,
37
- position,
38
- length,
39
- audioContext,
40
- logger,
41
- }: ProcessAudioBufferOptions): Promise<ProcessedAudioData> {
42
- if (Platform.OS !== 'web') {
43
- throw new Error('processAudioBuffer is only supported on web')
44
- }
45
-
46
- let ctx: AudioContext | undefined
47
- let buffer: AudioBuffer | undefined
48
-
49
- try {
50
- // Log initial parameters
51
- logger?.debug('Process audio buffer - Initial params:', {
52
- hasArrayBuffer: !!arrayBuffer,
53
- fileUri,
54
- targetSampleRate,
55
- targetChannels,
56
- normalizeAudio,
57
- startTimeMs,
58
- endTimeMs,
59
- position,
60
- length,
61
- })
62
-
63
- // Get the audio data
64
- let audioData: ArrayBuffer
65
- if (arrayBuffer) {
66
- audioData = arrayBuffer
67
- } else if (fileUri) {
68
- const response = await fetch(fileUri)
69
- if (!response.ok) {
70
- throw new Error(
71
- `Failed to fetch fileUri: ${response.statusText}`
72
- )
73
- }
74
- audioData = await response.arrayBuffer()
75
- } else {
76
- throw new Error('Either arrayBuffer or fileUri must be provided')
77
- }
78
-
79
- logger?.debug('Audio data loaded:', {
80
- byteLength: audioData.byteLength,
81
- firstBytes: Array.from(new Uint8Array(audioData.slice(0, 16))),
82
- })
83
-
84
- // Create context at original sample rate first
85
- ctx =
86
- audioContext ||
87
- new (window.AudioContext || (window as any).webkitAudioContext)()
88
- buffer = await ctx.decodeAudioData(audioData)
89
-
90
- logger?.debug('Decoded audio buffer:', {
91
- originalChannels: buffer.numberOfChannels,
92
- originalSampleRate: buffer.sampleRate,
93
- originalDuration: buffer.duration,
94
- originalLength: buffer.length,
95
- })
96
-
97
- // Calculate time range
98
- const startSample =
99
- startTimeMs !== undefined
100
- ? Math.floor((startTimeMs / 1000) * buffer.sampleRate)
101
- : position !== undefined
102
- ? Math.floor(position / 2)
103
- : 0
104
-
105
- // Fix: Adjust position calculation based on original sample rate
106
- // When position is provided in bytes, we need to account for the original sample rate
107
- const bytesPerSample = 2 // 16-bit audio = 2 bytes per sample
108
- const adjustedStartSample =
109
- position !== undefined
110
- ? Math.floor(
111
- (position / bytesPerSample) *
112
- (buffer.sampleRate / targetSampleRate)
113
- )
114
- : startSample
115
-
116
- const samplesNeeded =
117
- length !== undefined
118
- ? Math.floor(
119
- (length / bytesPerSample) *
120
- (buffer.sampleRate / targetSampleRate)
121
- )
122
- : endTimeMs !== undefined && startTimeMs !== undefined
123
- ? Math.floor(
124
- ((endTimeMs - startTimeMs) / 1000) * buffer.sampleRate
125
- )
126
- : buffer.length - adjustedStartSample
127
-
128
- logger?.debug('Sample calculations (adjusted):', {
129
- originalStartSample: startSample,
130
- adjustedStartSample,
131
- samplesNeeded,
132
- originalSampleRate: buffer.sampleRate,
133
- targetSampleRate,
134
- conversionRatio: buffer.sampleRate / targetSampleRate,
135
- expectedDurationMs: (samplesNeeded / buffer.sampleRate) * 1000,
136
- })
137
-
138
- // Create temporary buffer for the segment
139
- const segmentBuffer = ctx.createBuffer(
140
- buffer.numberOfChannels,
141
- samplesNeeded,
142
- buffer.sampleRate
143
- )
144
-
145
- // Copy the segment
146
- for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
147
- const channelData = buffer.getChannelData(channel)
148
- const segmentData = segmentBuffer.getChannelData(channel)
149
- for (let i = 0; i < samplesNeeded; i++) {
150
- segmentData[i] = channelData[adjustedStartSample + i]
151
- }
152
- }
153
-
154
- // Create offline context for resampling
155
- const offlineCtx = new OfflineAudioContext(
156
- targetChannels,
157
- Math.ceil((samplesNeeded * targetSampleRate) / buffer.sampleRate),
158
- targetSampleRate
159
- )
160
-
161
- // Create source and connect
162
- const source = offlineCtx.createBufferSource()
163
- source.buffer = segmentBuffer
164
- source.connect(offlineCtx.destination)
165
-
166
- // Render at new sample rate
167
- source.start()
168
- const processedBuffer = await offlineCtx.startRendering()
169
-
170
- // Get the final audio data
171
- const channelData = processedBuffer.getChannelData(0)
172
- const durationMs = Math.round(
173
- (samplesNeeded / buffer.sampleRate) * 1000
174
- )
175
-
176
- logger?.debug('Final processed audio:', {
177
- outputSamples: channelData.length,
178
- outputSampleRate: targetSampleRate,
179
- durationMs,
180
- })
181
-
182
- return {
183
- buffer: processedBuffer,
184
- channelData,
185
- samples: channelData.length,
186
- durationMs,
187
- sampleRate: targetSampleRate,
188
- channels: processedBuffer.numberOfChannels,
189
- }
190
- } catch (error) {
191
- logger?.error('Failed to process audio buffer:', {
192
- error,
193
- position,
194
- length,
195
- startTimeMs,
196
- endTimeMs,
197
- bufferLength: buffer?.length,
198
- })
199
- throw error
200
- } finally {
201
- if (!audioContext && ctx) {
202
- await ctx.close()
203
- }
204
- }
205
- }
@@ -1,24 +0,0 @@
1
- /**
2
- * Concatenates an array of ArrayBuffers into a single ArrayBuffer.
3
- *
4
- * @param buffers - An array of ArrayBuffers to be concatenated.
5
- * @returns A single ArrayBuffer containing the concatenated data.
6
- */
7
- export const concatenateBuffers = (buffers: ArrayBuffer[]): ArrayBuffer => {
8
- // Filter out any undefined or null buffers
9
- const validBuffers = buffers.filter((buffer) => buffer)
10
- const totalLength = validBuffers.reduce(
11
- (sum, buffer) => sum + buffer.byteLength,
12
- 0
13
- )
14
- // Create a new Uint8Array to hold the concatenated result
15
- const result = new Uint8Array(totalLength)
16
- // Offset to keep track of the current position in the result array
17
- let offset = 0
18
-
19
- for (const buffer of validBuffers) {
20
- result.set(new Uint8Array(buffer), offset)
21
- offset += buffer.byteLength
22
- }
23
- return result.buffer
24
- }
@@ -1,170 +0,0 @@
1
- import { Platform } from 'react-native'
2
-
3
- import { ConsoleLike } from '../ExpoAudioStream.types'
4
- import { getWavFileInfo, WavFileInfo } from './getWavFileInfo'
5
-
6
- export const WAV_HEADER_SIZE = 44
7
-
8
- const convertSample = (
9
- dataView: DataView,
10
- offset: number,
11
- bitDepth: number
12
- ): number => {
13
- switch (bitDepth) {
14
- case 8:
15
- return (dataView.getUint8(offset) - 128) / 128
16
- case 16:
17
- return dataView.getInt16(offset, true) / 32768
18
- case 24:
19
- return (
20
- ((dataView.getUint8(offset) |
21
- (dataView.getUint8(offset + 1) << 8) |
22
- (dataView.getUint8(offset + 2) << 16)) /
23
- 8388608) *
24
- 2 -
25
- 1
26
- )
27
- case 32:
28
- return dataView.getFloat32(offset, true)
29
- default:
30
- throw new Error(`Unsupported bit depth: ${bitDepth}`)
31
- }
32
- }
33
-
34
- const convertSampleNative = (
35
- array: Uint8Array,
36
- startIndex: number,
37
- bitDepth: number
38
- ): number => {
39
- switch (bitDepth) {
40
- case 8:
41
- return (array[startIndex] - 128) / 128
42
- case 16: {
43
- // Handle 16-bit PCM using Uint8Array directly
44
- const low = array[startIndex]
45
- const high = array[startIndex + 1]
46
- const value = (high << 8) | low
47
- // Convert to signed 16-bit
48
- return (value > 32767 ? value - 65536 : value) / 32768
49
- }
50
- case 24: {
51
- const byte1 = array[startIndex]
52
- const byte2 = array[startIndex + 1]
53
- const byte3 = array[startIndex + 2]
54
- const value = (byte3 << 16) | (byte2 << 8) | byte1
55
- return (
56
- ((value > 8388607 ? value - 16777216 : value) / 8388608) * 2 - 1
57
- )
58
- }
59
- case 32: {
60
- // Assuming 32-bit float
61
- const view = new DataView(array.buffer, startIndex, 4)
62
- return view.getFloat32(0, true)
63
- }
64
- default:
65
- throw new Error(`Unsupported bit depth: ${bitDepth}`)
66
- }
67
- }
68
-
69
- export const convertPCMToFloat32 = async ({
70
- bitDepth,
71
- buffer,
72
- skipWavHeader = false,
73
- logger,
74
- }: {
75
- buffer: ArrayBuffer
76
- bitDepth: number
77
- skipWavHeader?: boolean
78
- logger?: ConsoleLike
79
- }): Promise<{ pcmValues: Float32Array; min: number; max: number }> => {
80
- try {
81
- logger?.debug(
82
- `Converting PCM to Float32: bitDepth: ${bitDepth}, buffer.byteLength: ${buffer.byteLength}`
83
- )
84
-
85
- let headerOffset = 0
86
- if (!skipWavHeader) {
87
- const wavFileInfo: WavFileInfo = await getWavFileInfo(buffer)
88
- headerOffset = wavFileInfo.dataChunkOffset
89
- logger?.debug(`Using WAV header offset: ${headerOffset}`)
90
- }
91
-
92
- // Convert ArrayBuffer to Uint8Array for more efficient native handling
93
- const uint8Array = new Uint8Array(buffer)
94
- const dataLength = buffer.byteLength - headerOffset
95
- const bytesPerSample = bitDepth / 8
96
- const sampleLength = Math.floor(dataLength / bytesPerSample)
97
-
98
- // Create result array using SharedArrayBuffer for better memory handling
99
- let float32Array: Float32Array
100
- try {
101
- // Try using SharedArrayBuffer first
102
- const sharedBuffer = new SharedArrayBuffer(sampleLength * 4)
103
- float32Array = new Float32Array(sharedBuffer)
104
- } catch (e) {
105
- // Fallback to regular ArrayBuffer if SharedArrayBuffer is not available
106
- float32Array = new Float32Array(sampleLength)
107
- }
108
-
109
- let min = Infinity
110
- let max = -Infinity
111
-
112
- // Process in smaller chunks
113
- const CHUNK_SIZE = Platform.OS === 'web' ? sampleLength : 4000 // Smaller chunks for native
114
- const numChunks = Math.ceil(sampleLength / CHUNK_SIZE)
115
-
116
- for (let chunk = 0; chunk < numChunks; chunk++) {
117
- const startSample = chunk * CHUNK_SIZE
118
- const endSample = Math.min((chunk + 1) * CHUNK_SIZE, sampleLength)
119
-
120
- // Process chunk
121
- for (let i = startSample; i < endSample; i++) {
122
- const startIndex = headerOffset + i * bytesPerSample
123
- if (startIndex + bytesPerSample <= uint8Array.length) {
124
- const value =
125
- Platform.OS === 'web'
126
- ? convertSample(
127
- new DataView(buffer),
128
- startIndex,
129
- bitDepth
130
- )
131
- : convertSampleNative(
132
- uint8Array,
133
- startIndex,
134
- bitDepth
135
- )
136
-
137
- if (isFinite(value)) {
138
- float32Array[i] = value
139
- min = Math.min(min, value)
140
- max = Math.max(max, value)
141
- }
142
- }
143
- }
144
-
145
- // Allow garbage collection between chunks on native
146
- if (Platform.OS !== 'web' && chunk < numChunks - 1) {
147
- await new Promise((resolve) => setTimeout(resolve, 0))
148
- }
149
- }
150
-
151
- logger?.debug(
152
- `Conversion complete. Length: ${float32Array.length}, Range: [${min}, ${max}]`
153
- )
154
-
155
- // Only log a small sample of values to avoid memory issues
156
- if (logger?.debug) {
157
- const sampleValues = Array.from(float32Array.slice(0, 5))
158
- logger.debug('Sample values:', sampleValues)
159
- }
160
-
161
- return {
162
- pcmValues: float32Array,
163
- min,
164
- max,
165
- }
166
- } catch (error: unknown) {
167
- logger?.error(`Error converting PCM to Float32`, error)
168
- throw error
169
- }
170
- }
@@ -1,18 +0,0 @@
1
- import { BitDepth, EncodingType } from '../ExpoAudioStream.types'
2
-
3
- export const encodingToBitDepth = ({
4
- encoding,
5
- }: {
6
- encoding: EncodingType
7
- }): BitDepth => {
8
- switch (encoding) {
9
- case 'pcm_32bit':
10
- return 32
11
- case 'pcm_16bit':
12
- return 16
13
- case 'pcm_8bit':
14
- return 8
15
- default:
16
- throw new Error(`Unsupported encoding type: ${encoding}`)
17
- }
18
- }
@@ -1,132 +0,0 @@
1
- // packages/expo-audio-stream/src/utils/getWavFileInfo.ts
2
-
3
- import { BitDepth, SampleRate } from '../ExpoAudioStream.types'
4
- import {
5
- DATA_CHUNK_ID,
6
- DEFAULT_BIT_DEPTH,
7
- DEFAULT_SAMPLE_RATE,
8
- FMT_CHUNK_ID,
9
- INFO_CHUNK_ID,
10
- RIFF_HEADER,
11
- WAVE_HEADER,
12
- } from '../constants'
13
-
14
- // Audio format descriptions
15
- const AUDIO_FORMATS: { [key: number]: string } = {
16
- 1: 'PCM',
17
- 3: 'IEEE float',
18
- 6: '8-bit ITU-T G.711 A-law',
19
- 7: '8-bit ITU-T G.711 µ-law',
20
- 65534: 'WAVE_FORMAT_EXTENSIBLE',
21
- }
22
-
23
- /**
24
- * Interface representing the metadata of a WAV file.
25
- */
26
- export interface WavFileInfo {
27
- sampleRate: SampleRate
28
- numChannels: number
29
- bitDepth: BitDepth
30
- size: number // in bytes
31
- durationMs: number // in ms
32
- audioFormatDescription: string // Description of the audio format
33
- byteRate: number // Average bytes per second
34
- blockAlign: number // Number of bytes for one sample including all channels
35
- creationDateTime?: string // Optional creation date and time
36
- comments?: string // Optional comments or tags
37
- compressionType?: string // Optional compression type
38
- dataChunkOffset: number // Position of the first data chunk
39
- }
40
-
41
- /**
42
- * Extracts metadata from a WAV buffer.
43
- *
44
- * @param arrayBuffer - The array buffer containing the WAV data.
45
- * @returns A promise that resolves to the extracted metadata.
46
- */
47
- export const getWavFileInfo = async (
48
- arrayBuffer: ArrayBuffer
49
- ): Promise<WavFileInfo> => {
50
- const view = new DataView(arrayBuffer)
51
-
52
- // Check if the file is a valid RIFF/WAVE file
53
- const riffHeader = view.getUint32(0, false)
54
- const waveHeader = view.getUint32(8, false)
55
- if (riffHeader !== RIFF_HEADER || waveHeader !== WAVE_HEADER) {
56
- throw new Error('Invalid WAV file')
57
- }
58
-
59
- // Initialize variables for the metadata
60
- let fmtChunkOffset = 12
61
- let sampleRate: SampleRate = DEFAULT_SAMPLE_RATE
62
- let numChannels = 0
63
- let bitDepth: BitDepth = DEFAULT_BIT_DEPTH
64
- let dataChunkSize = 0
65
- let audioFormat = 0
66
- let byteRate = 0
67
- let blockAlign = 0
68
- let creationDateTime = ''
69
- let comments = ''
70
- let dataChunkOffset = 0
71
-
72
- // Parse chunks to find the "fmt " and "data" chunks
73
- while (fmtChunkOffset < view.byteLength) {
74
- const chunkId = view.getUint32(fmtChunkOffset, false)
75
- const chunkSize = view.getUint32(fmtChunkOffset + 4, true)
76
- if (chunkId === FMT_CHUNK_ID) {
77
- // "fmt "
78
- audioFormat = view.getUint16(fmtChunkOffset + 8, true)
79
- if (!AUDIO_FORMATS[audioFormat]) {
80
- throw new Error('Unsupported WAV file format')
81
- }
82
- numChannels = view.getUint16(fmtChunkOffset + 10, true)
83
- sampleRate = view.getUint32(fmtChunkOffset + 12, true) as SampleRate
84
- byteRate = view.getUint32(fmtChunkOffset + 16, true)
85
- blockAlign = view.getUint16(fmtChunkOffset + 20, true)
86
- bitDepth = view.getUint16(fmtChunkOffset + 22, true) as BitDepth
87
- } else if (chunkId === DATA_CHUNK_ID) {
88
- // "data"
89
- dataChunkSize = chunkSize
90
- dataChunkOffset = fmtChunkOffset + 8 // Position after chunk header
91
- break
92
- } else if (chunkId === INFO_CHUNK_ID) {
93
- // "INFO"
94
- // Read INFO chunk (assuming it contains a text-based creation date/time and comments)
95
- const infoStart = fmtChunkOffset + 8
96
- const infoText = new TextDecoder().decode(
97
- new Uint8Array(
98
- arrayBuffer.slice(infoStart, infoStart + chunkSize)
99
- )
100
- )
101
- const infoParts = infoText.split('\0')
102
- creationDateTime = infoParts[0]
103
- comments = infoParts[1]
104
- }
105
- fmtChunkOffset += 8 + chunkSize
106
- }
107
-
108
- if (!sampleRate || !numChannels || !bitDepth || !dataChunkSize) {
109
- throw new Error('Incomplete WAV file information')
110
- }
111
-
112
- // Calculate duration
113
- const bytesPerSample = bitDepth / 8
114
- const numSamples = dataChunkSize / (numChannels * bytesPerSample)
115
- const durationMs = (numSamples / sampleRate) * 1000
116
-
117
- return {
118
- sampleRate,
119
- numChannels,
120
- bitDepth,
121
- size: arrayBuffer.byteLength,
122
- durationMs,
123
- audioFormatDescription: AUDIO_FORMATS[audioFormat],
124
- byteRate,
125
- blockAlign,
126
- creationDateTime: creationDateTime || undefined,
127
- comments: comments || undefined,
128
- compressionType:
129
- audioFormat === 1 ? 'None' : AUDIO_FORMATS[audioFormat],
130
- dataChunkOffset,
131
- }
132
- }
@@ -1,114 +0,0 @@
1
- // packages/expo-audio-stream/src/utils/writeWavHeader.ts
2
-
3
- /**
4
- * Options for creating a WAV header.
5
- */
6
- export interface WavHeaderOptions {
7
- /** Optional buffer containing audio data. If provided, it will be combined with the header. */
8
- buffer?: ArrayBuffer
9
- /** The sample rate of the audio in Hz (e.g., 44100). */
10
- sampleRate: number
11
- /** The number of audio channels (e.g., 1 for mono, 2 for stereo). */
12
- numChannels: number
13
- /** The bit depth of the audio (e.g., 16, 24, or 32). */
14
- bitDepth: number
15
- }
16
-
17
- /**
18
- * Writes or updates a WAV (RIFF) header based on the provided options.
19
- *
20
- * This function can be used in three ways:
21
- * 1. To create a standalone WAV header (when no buffer is provided).
22
- * 2. To create a WAV header and combine it with existing audio data (when a buffer without a header is provided).
23
- * 3. To update an existing WAV header in the provided buffer.
24
- *
25
- * For streaming audio where the final size is unknown, this function sets the size fields
26
- * to the maximum 32-bit value (0xFFFFFFFF). These can be updated later using the
27
- * `updateWavHeaderSize` function once the final size is known.
28
- *
29
- * @param options - The options for creating or updating the WAV header.
30
- * @returns An ArrayBuffer containing the WAV header, or the header combined with the provided audio data.
31
- *
32
- * @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
- */
51
- export const writeWavHeader = ({
52
- buffer,
53
- sampleRate,
54
- numChannels,
55
- bitDepth,
56
- }: WavHeaderOptions): ArrayBuffer => {
57
- const bytesPerSample = bitDepth / 8
58
- const blockAlign = numChannels * bytesPerSample
59
- const byteRate = sampleRate * blockAlign
60
-
61
- // Function to write a string to the DataView
62
- const writeString = (view: DataView, offset: number, string: string) => {
63
- for (let i = 0; i < string.length; i++) {
64
- view.setUint8(offset + i, string.charCodeAt(i))
65
- }
66
- }
67
-
68
- // Function to write or update the header
69
- const writeHeader = (view: DataView, dataSize: number = 0xffffffff) => {
70
- writeString(view, 0, 'RIFF') // ChunkID
71
- view.setUint32(4, 36 + dataSize, true) // ChunkSize
72
- writeString(view, 8, 'WAVE') // Format
73
- writeString(view, 12, 'fmt ') // Subchunk1ID
74
- view.setUint32(16, 16, true) // Subchunk1Size (16 for PCM)
75
- view.setUint16(20, bitDepth === 32 ? 3 : 1, true) // AudioFormat (3 for float, 1 for PCM)
76
- view.setUint16(22, numChannels, true) // NumChannels
77
- view.setUint32(24, sampleRate, true) // SampleRate
78
- view.setUint32(28, byteRate, true) // ByteRate
79
- view.setUint16(32, blockAlign, true) // BlockAlign
80
- view.setUint16(34, bitDepth, true) // BitsPerSample
81
- writeString(view, 36, 'data') // Subchunk2ID
82
- view.setUint32(40, dataSize, true) // Subchunk2Size
83
- }
84
-
85
- if (buffer) {
86
- if (buffer.byteLength < 44) {
87
- throw new Error('Buffer is too small to contain a valid WAV header')
88
- }
89
-
90
- const view = new DataView(buffer)
91
-
92
- // Check if the buffer already has a WAV header by looking for "RIFF" at the start
93
- const existingHeader = view.getUint32(0, false) === 0x52494646 // "RIFF" in ASCII
94
-
95
- if (existingHeader) {
96
- // Update the existing header
97
- writeHeader(view, buffer.byteLength - 44)
98
- return buffer
99
- } else {
100
- // Combine the new header with the existing buffer
101
- const newBuffer = new ArrayBuffer(44 + buffer.byteLength)
102
- const newView = new DataView(newBuffer)
103
- writeHeader(newView, buffer.byteLength)
104
- new Uint8Array(newBuffer).set(new Uint8Array(buffer), 44)
105
- return newBuffer
106
- }
107
- } else {
108
- // Create a standalone header
109
- const headerBuffer = new ArrayBuffer(44)
110
- const view = new DataView(headerBuffer)
111
- writeHeader(view)
112
- return headerBuffer
113
- }
114
- }