@siteed/expo-audio-stream 1.0.0 → 1.0.2

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 (85) hide show
  1. package/README.md +7 -18
  2. package/android/build.gradle +5 -0
  3. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +120 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +34 -4
  5. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +635 -0
  6. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +194 -79
  7. package/android/src/main/java/net/siteed/audiostream/Constants.kt +1 -0
  8. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +48 -2
  9. package/android/src/main/java/net/siteed/audiostream/FFT.kt +44 -0
  10. package/android/src/main/java/net/siteed/audiostream/Features.kt +56 -0
  11. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +12 -0
  12. package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +56 -0
  13. package/app.plugin.js +1 -1
  14. package/build/AudioRecorder.provider.js +1 -1
  15. package/build/AudioRecorder.provider.js.map +1 -1
  16. package/build/ExpoAudioStream.native.d.ts +3 -0
  17. package/build/ExpoAudioStream.native.d.ts.map +1 -0
  18. package/build/ExpoAudioStream.native.js +6 -0
  19. package/build/ExpoAudioStream.native.js.map +1 -0
  20. package/build/ExpoAudioStream.types.d.ts +79 -6
  21. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  22. package/build/ExpoAudioStream.types.js.map +1 -1
  23. package/build/ExpoAudioStream.web.d.ts +41 -0
  24. package/build/ExpoAudioStream.web.d.ts.map +1 -0
  25. package/build/ExpoAudioStream.web.js +184 -0
  26. package/build/ExpoAudioStream.web.js.map +1 -0
  27. package/build/ExpoAudioStreamModule.d.ts +2 -2
  28. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  29. package/build/ExpoAudioStreamModule.js +12 -3
  30. package/build/ExpoAudioStreamModule.js.map +1 -1
  31. package/build/WebRecorder.d.ts +47 -0
  32. package/build/WebRecorder.d.ts.map +1 -0
  33. package/build/WebRecorder.js +243 -0
  34. package/build/WebRecorder.js.map +1 -0
  35. package/build/index.d.ts +14 -5
  36. package/build/index.d.ts.map +1 -1
  37. package/build/index.js +106 -7
  38. package/build/index.js.map +1 -1
  39. package/build/inlineAudioWebWorker.d.ts +3 -0
  40. package/build/inlineAudioWebWorker.d.ts.map +1 -0
  41. package/build/inlineAudioWebWorker.js +340 -0
  42. package/build/inlineAudioWebWorker.js.map +1 -0
  43. package/build/useAudioRecording.d.ts +24 -9
  44. package/build/useAudioRecording.d.ts.map +1 -1
  45. package/build/useAudioRecording.js +107 -29
  46. package/build/useAudioRecording.js.map +1 -1
  47. package/build/utils.d.ts +31 -0
  48. package/build/utils.d.ts.map +1 -0
  49. package/build/utils.js +143 -0
  50. package/build/utils.js.map +1 -0
  51. package/expo-module.config.json +13 -4
  52. package/ios/AudioAnalysisData.swift +39 -0
  53. package/ios/AudioProcessingHelpers.swift +59 -0
  54. package/ios/AudioProcessor.swift +317 -0
  55. package/ios/AudioStreamError.swift +7 -0
  56. package/ios/AudioStreamManager.swift +204 -52
  57. package/ios/AudioStreamManagerDelegate.swift +4 -0
  58. package/ios/DataPoint.swift +41 -0
  59. package/ios/ExpoAudioStreamModule.swift +188 -6
  60. package/ios/Features.swift +44 -0
  61. package/ios/RecordingResult.swift +19 -0
  62. package/ios/RecordingSettings.swift +13 -0
  63. package/ios/WaveformExtractor.swift +105 -0
  64. package/package.json +9 -9
  65. package/plugin/tsconfig.json +13 -8
  66. package/publish.sh +8 -0
  67. package/src/AudioRecorder.provider.tsx +1 -1
  68. package/src/ExpoAudioStream.native.ts +6 -0
  69. package/src/ExpoAudioStream.types.ts +97 -11
  70. package/src/ExpoAudioStream.web.ts +228 -0
  71. package/src/ExpoAudioStreamModule.ts +17 -3
  72. package/src/WebRecorder.ts +364 -0
  73. package/src/index.ts +166 -20
  74. package/src/inlineAudioWebWorker.tsx +340 -0
  75. package/src/useAudioRecording.tsx +410 -0
  76. package/src/utils.ts +189 -0
  77. package/build/ExpoAudioStreamModule.web.d.ts +0 -37
  78. package/build/ExpoAudioStreamModule.web.d.ts.map +0 -1
  79. package/build/ExpoAudioStreamModule.web.js +0 -156
  80. package/build/ExpoAudioStreamModule.web.js.map +0 -1
  81. package/docs/demo.gif +0 -0
  82. package/release-it.js +0 -18
  83. package/src/ExpoAudioStreamModule.web.ts +0 -181
  84. package/src/useAudioRecording.ts +0 -268
  85. package/yarn-error.log +0 -7793
@@ -7,12 +7,7 @@
7
7
 
8
8
  import Foundation
9
9
  import AVFoundation
10
-
11
- struct RecordingSettings {
12
- var sampleRate: Double
13
- var numberOfChannels: Int = 1
14
- var bitDepth: Int = 16
15
- }
10
+ import Accelerate
16
11
 
17
12
  // Helper to convert to little-endian byte array
18
13
  extension UInt32 {
@@ -29,41 +24,13 @@ extension UInt16 {
29
24
  }
30
25
  }
31
26
 
32
-
33
- struct RecordingResult {
34
- var fileUri: String
35
- var mimeType: String
36
- var duration: Int64
37
- var size: Int64
38
- var channels: Int
39
- var bitDepth: Int
40
- var sampleRate: Double
41
- }
42
-
43
- struct StartRecordingResult {
44
- var fileUri: String
45
- var mimeType: String
46
- var channels: Int
47
- var bitDepth: Int
48
- var sampleRate: Double
49
- }
50
-
51
- protocol AudioStreamManagerDelegate: AnyObject {
52
- func audioStreamManager(_ manager: AudioStreamManager, didReceiveAudioData data: Data, recordingTime: TimeInterval, totalDataSize: Int64)
53
- }
54
-
55
- enum AudioStreamError: Error {
56
- case audioSessionSetupFailed(String)
57
- case fileCreationFailed(URL)
58
- case audioProcessingError(String)
59
- }
60
-
61
27
  class AudioStreamManager: NSObject {
62
28
  private let audioEngine = AVAudioEngine()
63
29
  private var inputNode: AVAudioInputNode {
64
30
  return audioEngine.inputNode
65
31
  }
66
32
  internal var recordingFileURL: URL?
33
+ private var audioProcessor: AudioProcessor?
67
34
  private var startTime: Date?
68
35
  internal var lastEmissionTime: Date?
69
36
  internal var lastEmittedSize: Int64 = 0
@@ -78,13 +45,17 @@ class AudioStreamManager: NSObject {
78
45
  internal var mimeType: String = "audio/wav"
79
46
  private var lastBufferTime: AVAudioTime?
80
47
  private var accumulatedData = Data()
48
+ private var recentData = [Float]() // This property stores the recent audio data
81
49
 
82
50
  weak var delegate: AudioStreamManagerDelegate? // Define the delegate here
83
51
 
52
+ /// Initializes the AudioStreamManager
84
53
  override init() {
85
54
  super.init()
86
55
  }
87
56
 
57
+ /// Handles audio session interruptions.
58
+ /// - Parameter notification: The notification object containing interruption information.
88
59
  @objc func handleAudioSessionInterruption(notification: Notification) {
89
60
  guard let info = notification.userInfo,
90
61
  let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
@@ -107,6 +78,8 @@ class AudioStreamManager: NSObject {
107
78
  }
108
79
  }
109
80
 
81
+ /// Creates a new recording file.
82
+ /// - Returns: The URL of the newly created recording file, or nil if creation failed.
110
83
  private func createRecordingFile() -> URL? {
111
84
  let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
112
85
  recordingUUID = UUID()
@@ -120,6 +93,9 @@ class AudioStreamManager: NSObject {
120
93
  return fileURL
121
94
  }
122
95
 
96
+ /// Creates a WAV header for the given data size.
97
+ /// - Parameter dataSize: The size of the audio data.
98
+ /// - Returns: A Data object containing the WAV header.
123
99
  private func createWavHeader(dataSize: Int) -> Data {
124
100
  var header = Data()
125
101
 
@@ -152,7 +128,8 @@ class AudioStreamManager: NSObject {
152
128
  return header
153
129
  }
154
130
 
155
-
131
+ /// Gets the current status of the recording.
132
+ /// - Returns: A dictionary containing the recording status information.
156
133
  func getStatus() -> [String: Any] {
157
134
  // let currentTime = Date()
158
135
  // let totalRecordedTime = startTime != nil ? Int(currentTime.timeIntervalSince(startTime!)) - pausedDuration : 0
@@ -170,7 +147,7 @@ class AudioStreamManager: NSObject {
170
147
  let durationInMilliseconds = Int(durationInSeconds * 1000)
171
148
 
172
149
  return [
173
- "duration": durationInMilliseconds,
150
+ "durationMs": durationInMilliseconds,
174
151
  "isRecording": isRecording,
175
152
  "isPaused": isPaused,
176
153
  "mimeType": mimeType,
@@ -180,7 +157,12 @@ class AudioStreamManager: NSObject {
180
157
 
181
158
  }
182
159
 
183
- func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> StartRecordingResult? {
160
+ /// Starts a new audio recording with the specified settings and interval.
161
+ /// - Parameters:
162
+ /// - settings: The recording settings to use.
163
+ /// - intervalMilliseconds: The interval in milliseconds for emitting audio data.
164
+ /// - Returns: A StartRecordingResult object if recording starts successfully, or nil otherwise.
165
+ func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> StartRecordingResult? {
184
166
  guard !isRecording else {
185
167
  Logger.debug("Debug: Recording is already in progress.")
186
168
  return nil
@@ -191,11 +173,11 @@ class AudioStreamManager: NSObject {
191
173
  return nil
192
174
  }
193
175
 
194
- recordingSettings = settings
176
+ var newSettings = settings // Make settings mutable
195
177
 
196
178
  // Determine the commonFormat based on bitDepth
197
179
  let commonFormat: AVAudioCommonFormat
198
- switch settings.bitDepth {
180
+ switch newSettings.bitDepth {
199
181
  case 16:
200
182
  commonFormat = .pcmFormatInt16
201
183
  case 32:
@@ -203,7 +185,7 @@ class AudioStreamManager: NSObject {
203
185
  default:
204
186
  Logger.debug("Unsupported bit depth. Defaulting to 16-bit PCM")
205
187
  commonFormat = .pcmFormatInt16
206
- recordingSettings?.bitDepth = 16
188
+ newSettings.bitDepth = 16
207
189
  }
208
190
 
209
191
  emissionInterval = max(100.0, Double(intervalMilliseconds)) / 1000.0
@@ -214,11 +196,32 @@ class AudioStreamManager: NSObject {
214
196
  let session = AVAudioSession.sharedInstance()
215
197
  do {
216
198
  Logger.debug("Debug: Configuring audio session with sample rate: \(settings.sampleRate) Hz")
199
+
200
+ // Create an audio format with the desired sample rate
201
+ let desiredFormat = AVAudioFormat(commonFormat: commonFormat, sampleRate: newSettings.sampleRate, channels: UInt32(newSettings.numberOfChannels), interleaved: true)
202
+
203
+ // Check if the input node supports the desired format
204
+ let inputNode = audioEngine.inputNode
205
+ let hardwareFormat = inputNode.inputFormat(forBus: 0)
206
+ if hardwareFormat.sampleRate != newSettings.sampleRate {
207
+ Logger.debug("Debug: Preferred sample rate not supported. Falling back to hardware sample rate \(session.sampleRate).")
208
+ newSettings.sampleRate = session.sampleRate
209
+ }
210
+
211
+ try session.setCategory(.playAndRecord)
212
+ try session.setMode(.default)
217
213
  try session.setPreferredSampleRate(settings.sampleRate)
218
214
  try session.setPreferredIOBufferDuration(1024 / settings.sampleRate)
219
- try session.setCategory(.playAndRecord)
220
215
  try session.setActive(true)
221
216
  Logger.debug("Debug: Audio session activated successfully.")
217
+
218
+ let actualSampleRate = session.sampleRate
219
+ if actualSampleRate != newSettings.sampleRate {
220
+ Logger.debug("Debug: Preferred sample rate not set. Falling back to hardware sample rate: \(actualSampleRate) Hz")
221
+ newSettings.sampleRate = actualSampleRate
222
+ }
223
+
224
+ recordingSettings = newSettings // Update the class property with the new settings
222
225
  } catch {
223
226
  Logger.debug("Error: Failed to set up audio session with preferred settings: \(error.localizedDescription)")
224
227
  return nil
@@ -227,11 +230,21 @@ class AudioStreamManager: NSObject {
227
230
  NotificationCenter.default.addObserver(self, selector: #selector(handleAudioSessionInterruption), name: AVAudioSession.interruptionNotification, object: nil)
228
231
 
229
232
  // Correct the format to use 16-bit integer (PCM)
230
- guard let audioFormat = AVAudioFormat(commonFormat: commonFormat, sampleRate: settings.sampleRate, channels: UInt32(settings.numberOfChannels), interleaved: true) else {
233
+ guard let audioFormat = AVAudioFormat(commonFormat: commonFormat, sampleRate: newSettings.sampleRate, channels: UInt32(newSettings.numberOfChannels), interleaved: true) else {
231
234
  Logger.debug("Error: Failed to create audio format with the specified bit depth.")
232
235
  return nil
233
236
  }
234
237
 
238
+ if newSettings.enableProcessing == true {
239
+ // Initialize the AudioProcessor for buffer-based processing
240
+ self.audioProcessor = AudioProcessor(resolve: { result in
241
+ // Handle the result here if needed
242
+ }, reject: { code, message in
243
+ // Handle the rejection here if needed
244
+ })
245
+ Logger.debug("AudioProcessor activated successfully.")
246
+ }
247
+
235
248
  audioEngine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: audioFormat) { [weak self] (buffer, time) in
236
249
  guard let self = self, let fileURL = self.recordingFileURL else {
237
250
  Logger.debug("Error: File URL or self is nil during buffer processing.")
@@ -270,6 +283,10 @@ class AudioStreamManager: NSObject {
270
283
  }
271
284
  }
272
285
 
286
+
287
+ /// Describes the format of the given audio format.
288
+ /// - Parameter format: The AVAudioFormat object to describe.
289
+ /// - Returns: A string description of the audio format.
273
290
  func describeAudioFormat(_ format: AVAudioFormat) -> String {
274
291
  let sampleRate = format.sampleRate
275
292
  let channelCount = format.channelCount
@@ -291,13 +308,15 @@ class AudioStreamManager: NSObject {
291
308
  return "Sample Rate: \(sampleRate), Channels: \(channelCount), Format: \(bitDepth)"
292
309
  }
293
310
 
311
+ /// Stops the current audio recording.
312
+ /// - Returns: A RecordingResult object if the recording stopped successfully, or nil otherwise.
294
313
  func stopRecording() -> RecordingResult? {
295
314
  audioEngine.stop()
296
315
  audioEngine.inputNode.removeTap(onBus: 0)
297
316
  isRecording = false
298
317
 
299
318
  guard let fileURL = recordingFileURL, let startTime = startTime, let settings = recordingSettings else {
300
- print("Recording or file URL is nil.")
319
+ Logger.debug("Recording or file URL is nil.")
301
320
  return nil
302
321
  }
303
322
 
@@ -335,11 +354,93 @@ class AudioStreamManager: NSObject {
335
354
 
336
355
  return result
337
356
  } catch {
338
- print("Failed to fetch file attributes: \(error)")
357
+ Logger.debug("Failed to fetch file attributes: \(error)")
339
358
  return nil
340
359
  }
341
360
  }
342
361
 
362
+ /// Resamples the audio buffer using vDSP. If it fails, falls back to manual resampling.
363
+ /// - Parameters:
364
+ /// - buffer: The original audio buffer to be resampled.
365
+ /// - originalSampleRate: The sample rate of the original audio buffer.
366
+ /// - targetSampleRate: The desired sample rate to resample to.
367
+ /// - Returns: A new audio buffer resampled to the target sample rate, or nil if resampling fails.
368
+ private func resampleAudioBuffer(_ buffer: AVAudioPCMBuffer, from originalSampleRate: Double, to targetSampleRate: Double) -> AVAudioPCMBuffer? {
369
+ guard let channelData = buffer.floatChannelData else { return nil }
370
+
371
+ let sourceFrameCount = Int(buffer.frameLength)
372
+ let sourceChannels = Int(buffer.format.channelCount)
373
+
374
+ // Calculate the number of frames in the target buffer
375
+ let targetFrameCount = Int(Double(sourceFrameCount) * targetSampleRate / originalSampleRate)
376
+
377
+ // Create a new audio buffer for the resampled data
378
+ guard let targetBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, frameCapacity: AVAudioFrameCount(targetFrameCount)) else { return nil }
379
+ targetBuffer.frameLength = AVAudioFrameCount(targetFrameCount)
380
+
381
+ let resamplingFactor = Float(targetSampleRate / originalSampleRate) // Factor to resample the audio
382
+
383
+ for channel in 0..<sourceChannels {
384
+ let input = UnsafeBufferPointer(start: channelData[channel], count: sourceFrameCount) // Original channel data
385
+ let output = UnsafeMutableBufferPointer(start: targetBuffer.floatChannelData![channel], count: targetFrameCount) // Buffer for resampled data
386
+
387
+ var y: [Float] = Array(repeating: 0, count: targetFrameCount) // Temporary array for resampled data
388
+
389
+ // Resample using vDSP_vgenp which performs interpolation
390
+ vDSP_vgenp(input.baseAddress!, vDSP_Stride(1), [Float](stride(from: 0, to: Float(sourceFrameCount), by: resamplingFactor)), vDSP_Stride(1), &y, vDSP_Stride(1), vDSP_Length(targetFrameCount), vDSP_Length(sourceFrameCount))
391
+
392
+ for i in 0..<targetFrameCount {
393
+ output[i] = y[i]
394
+ }
395
+ }
396
+ return targetBuffer
397
+ }
398
+
399
+ /// Manually resamples the audio buffer using linear interpolation.
400
+ /// - Parameters:
401
+ /// - buffer: The original audio buffer to be resampled.
402
+ /// - originalSampleRate: The sample rate of the original audio buffer.
403
+ /// - targetSampleRate: The desired sample rate to resample to.
404
+ /// - Returns: A new audio buffer resampled to the target sample rate, or nil if resampling fails.
405
+ private func manualResampleAudioBuffer(_ buffer: AVAudioPCMBuffer, from originalSampleRate: Double, to targetSampleRate: Double) -> AVAudioPCMBuffer? {
406
+ guard let channelData = buffer.floatChannelData else { return nil }
407
+
408
+ let sourceFrameCount = Int(buffer.frameLength)
409
+ let sourceChannels = Int(buffer.format.channelCount)
410
+ let targetFrameCount = Int(Double(sourceFrameCount) * targetSampleRate / originalSampleRate)
411
+
412
+ guard let targetBuffer = AVAudioPCMBuffer(pcmFormat: buffer.format, frameCapacity: AVAudioFrameCount(targetFrameCount)) else { return nil }
413
+ targetBuffer.frameLength = AVAudioFrameCount(targetFrameCount)
414
+
415
+ let resamplingFactor = Float(targetSampleRate / originalSampleRate)
416
+
417
+ for channel in 0..<sourceChannels {
418
+ let input = UnsafeBufferPointer(start: channelData[channel], count: sourceFrameCount)
419
+ let output = UnsafeMutableBufferPointer(start: targetBuffer.floatChannelData![channel], count: targetFrameCount)
420
+
421
+ var y = Array(repeating: Float(0), count: targetFrameCount)
422
+ for i in 0..<targetFrameCount {
423
+ let index = Float(i) / resamplingFactor
424
+ let low = Int(floor(index))
425
+ let high = min(low + 1, sourceFrameCount - 1)
426
+ let weight = index - Float(low)
427
+ y[i] = (1 - weight) * input[low] + weight * input[high]
428
+ }
429
+
430
+ for i in 0..<targetFrameCount {
431
+ output[i] = y[i]
432
+ }
433
+ }
434
+
435
+ return targetBuffer
436
+ }
437
+
438
+
439
+
440
+ /// Updates the WAV header with the correct file size.
441
+ /// - Parameters:
442
+ /// - fileURL: The URL of the WAV file.
443
+ /// - totalDataSize: The total size of the audio data.
343
444
  private func updateWavHeader(fileURL: URL, totalDataSize: Int64) {
344
445
  do {
345
446
  let fileHandle = try FileHandle(forUpdating: fileURL)
@@ -360,19 +461,39 @@ class AudioStreamManager: NSObject {
360
461
  fileHandle.write(Data(dataSizeBytes))
361
462
 
362
463
  } catch let error {
363
- print("Error updating WAV header: \(error)")
464
+ Logger.debug("Error updating WAV header: \(error)")
364
465
  }
365
466
  }
366
467
 
468
+ /// Processes the audio buffer and writes data to the file. Also handles audio processing if enabled.
469
+ /// - Parameters:
470
+ /// - buffer: The audio buffer to process.
471
+ /// - fileURL: The URL of the file to write the data to.
367
472
  private func processAudioBuffer(_ buffer: AVAudioPCMBuffer, fileURL: URL) {
368
473
  guard let fileHandle = try? FileHandle(forWritingTo: fileURL) else {
369
- print("Failed to open file handle for URL: \(fileURL)")
474
+ Logger.debug("Failed to open file handle for URL: \(fileURL)")
370
475
  return
371
476
  }
372
477
 
373
- let audioData = buffer.audioBufferList.pointee.mBuffers
478
+ let targetSampleRate = recordingSettings?.desiredSampleRate ?? buffer.format.sampleRate
479
+ let finalBuffer: AVAudioPCMBuffer
480
+
481
+ if buffer.format.sampleRate != targetSampleRate {
482
+ // Resample the audio buffer if the target sample rate is different from the input sample rate
483
+ if let resampledBuffer = resampleAudioBuffer(buffer, from: buffer.format.sampleRate, to: targetSampleRate) {
484
+ finalBuffer = resampledBuffer
485
+ } else {
486
+ Logger.debug("Failed to resample audio buffer. Using original buffer.")
487
+ finalBuffer = buffer
488
+ }
489
+ } else {
490
+ // Use the original buffer if the sample rates are the same
491
+ finalBuffer = buffer
492
+ }
493
+
494
+ let audioData = finalBuffer.audioBufferList.pointee.mBuffers
374
495
  guard let bufferData = audioData.mData else {
375
- print("Buffer data is nil.")
496
+ Logger.debug("Buffer data is nil.")
376
497
  return
377
498
  }
378
499
  var data = Data(bytes: bufferData, count: Int(audioData.mDataByteSize))
@@ -399,13 +520,44 @@ class AudioStreamManager: NSObject {
399
520
  if let lastEmissionTime = lastEmissionTime, currentTime.timeIntervalSince(lastEmissionTime) >= emissionInterval {
400
521
  if let startTime = startTime {
401
522
  let recordingTime = currentTime.timeIntervalSince(startTime)
402
- // print("Emitting data: Recording time \(recordingTime) seconds, Data size \(totalDataSize) bytes")
403
- self.delegate?.audioStreamManager(self, didReceiveAudioData: accumulatedData, recordingTime: recordingTime, totalDataSize: totalDataSize)
523
+ // Copy accumulated data for processing
524
+ let dataToProcess = accumulatedData
525
+
526
+ // Emit the processed audio data
527
+ self.delegate?.audioStreamManager(self, didReceiveAudioData: dataToProcess, recordingTime: recordingTime, totalDataSize: totalDataSize)
528
+
529
+ if recordingSettings?.enableProcessing == true {
530
+ // Process the copied data and emit result
531
+ DispatchQueue.global().async {
532
+ if let processor = self.audioProcessor, let settings = self.recordingSettings {
533
+ Logger.debug("processAudioBuffer with dataToProcess size --> \(dataToProcess.count)")
534
+
535
+ let processingResult = processor.processAudioBuffer(
536
+ data: dataToProcess,
537
+ sampleRate: Float(settings.sampleRate),
538
+ pointsPerSecond: settings.pointsPerSecond ?? 10,
539
+ algorithm: settings.algorithm ?? "rms",
540
+ featureOptions: settings.featureOptions ?? ["rms": true, "zcr": true],
541
+ bitDepth: settings.bitDepth,
542
+ numberOfChannels: settings.numberOfChannels
543
+ )
544
+ Logger.debug("processingResult \(String(describing: processingResult))")
545
+
546
+ DispatchQueue.main.async {
547
+ if let result = processingResult {
548
+ self.delegate?.audioStreamManager(self, didReceiveProcessingResult: result)
549
+ } else {
550
+ Logger.debug("Processing failed or returned nil.")
551
+ }
552
+ }
553
+ }
554
+ }
555
+ }
556
+
404
557
  self.lastEmissionTime = currentTime // Update last emission time
405
558
  self.lastEmittedSize = totalDataSize
406
559
  accumulatedData.removeAll() // Reset accumulated data after emission
407
560
  }
408
561
  }
409
562
  }
410
-
411
563
  }
@@ -0,0 +1,4 @@
1
+ protocol AudioStreamManagerDelegate: AnyObject {
2
+ func audioStreamManager(_ manager: AudioStreamManager, didReceiveAudioData data: Data, recordingTime: TimeInterval, totalDataSize: Int64)
3
+ func audioStreamManager(_ manager: AudioStreamManager, didReceiveProcessingResult result: AudioAnalysisData?)
4
+ }
@@ -0,0 +1,41 @@
1
+ //
2
+ // DataPoint.swift
3
+ // ExpoAudioStream
4
+ //
5
+ // Created by Arthur Breton on 23/6/2024.
6
+ //
7
+
8
+ import Foundation
9
+
10
+
11
+ public struct DataPoint {
12
+ public var id: Int
13
+ public var amplitude: Float
14
+ public var activeSpeech: Bool?
15
+ public var dB: Float?
16
+ public var silent: Bool?
17
+ public var features: Features?
18
+ public var startTime: Float?
19
+ public var endTime: Float?
20
+ public var startPosition: Int?
21
+ public var endPosition: Int?
22
+ public var speaker: Int?
23
+ }
24
+
25
+ extension DataPoint {
26
+ func toDictionary() -> [String: Any] {
27
+ return [
28
+ "id": id,
29
+ "amplitude": amplitude,
30
+ "activeSpeech": activeSpeech ?? false,
31
+ "dB": dB ?? 0,
32
+ "silent": silent ?? false,
33
+ "features": features?.toDictionary() ?? [:],
34
+ "startTime": startTime ?? 0,
35
+ "endTime": endTime ?? 0,
36
+ "startPosition": startPosition ?? 0,
37
+ "endPosition": endPosition ?? 0,
38
+ "speaker": speaker ?? 0
39
+ ]
40
+ }
41
+ }