@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +25 -0
  3. package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +22 -0
  4. package/android/src/main/java/net/siteed/audiostream/AudioDeviceManager.kt +1501 -0
  5. package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +10 -5
  6. package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +27 -25
  7. package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +73 -71
  8. package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +576 -252
  9. package/android/src/main/java/net/siteed/audiostream/Constants.kt +17 -1
  10. package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +419 -155
  11. package/android/src/main/java/net/siteed/audiostream/LogUtils.kt +65 -0
  12. package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +9 -1
  13. package/build/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  14. package/build/AudioDeviceManager.d.ts +107 -0
  15. package/build/AudioDeviceManager.d.ts.map +1 -0
  16. package/build/AudioDeviceManager.js +493 -0
  17. package/build/AudioDeviceManager.js.map +1 -0
  18. package/build/AudioRecorder.provider.d.ts.map +1 -1
  19. package/build/AudioRecorder.provider.js +3 -0
  20. package/build/AudioRecorder.provider.js.map +1 -1
  21. package/build/ExpoAudioStream.types.d.ts +104 -1
  22. package/build/ExpoAudioStream.types.d.ts.map +1 -1
  23. package/build/ExpoAudioStream.types.js +7 -1
  24. package/build/ExpoAudioStream.types.js.map +1 -1
  25. package/build/ExpoAudioStream.web.d.ts +37 -0
  26. package/build/ExpoAudioStream.web.d.ts.map +1 -1
  27. package/build/ExpoAudioStream.web.js +478 -62
  28. package/build/ExpoAudioStream.web.js.map +1 -1
  29. package/build/ExpoAudioStreamModule.d.ts.map +1 -1
  30. package/build/ExpoAudioStreamModule.js +20 -0
  31. package/build/ExpoAudioStreamModule.js.map +1 -1
  32. package/build/WebRecorder.web.d.ts +74 -11
  33. package/build/WebRecorder.web.d.ts.map +1 -1
  34. package/build/WebRecorder.web.js +390 -74
  35. package/build/WebRecorder.web.js.map +1 -1
  36. package/build/hooks/useAudioDevices.d.ts +14 -0
  37. package/build/hooks/useAudioDevices.d.ts.map +1 -0
  38. package/build/hooks/useAudioDevices.js +151 -0
  39. package/build/hooks/useAudioDevices.js.map +1 -0
  40. package/build/index.d.ts +2 -0
  41. package/build/index.d.ts.map +1 -1
  42. package/build/index.js +4 -0
  43. package/build/index.js.map +1 -1
  44. package/build/useAudioRecorder.d.ts +1 -0
  45. package/build/useAudioRecorder.d.ts.map +1 -1
  46. package/build/useAudioRecorder.js +20 -1
  47. package/build/useAudioRecorder.js.map +1 -1
  48. package/build/utils/BlobFix.d.ts.map +1 -1
  49. package/build/utils/BlobFix.js +2 -2
  50. package/build/utils/BlobFix.js.map +1 -1
  51. package/build/utils/writeWavHeader.d.ts +3 -18
  52. package/build/utils/writeWavHeader.d.ts.map +1 -1
  53. package/build/utils/writeWavHeader.js +19 -26
  54. package/build/utils/writeWavHeader.js.map +1 -1
  55. package/build/workers/InlineFeaturesExtractor.web.d.ts +1 -1
  56. package/build/workers/InlineFeaturesExtractor.web.d.ts.map +1 -1
  57. package/build/workers/InlineFeaturesExtractor.web.js +27 -26
  58. package/build/workers/InlineFeaturesExtractor.web.js.map +1 -1
  59. package/build/workers/inlineAudioWebWorker.web.d.ts +1 -1
  60. package/build/workers/inlineAudioWebWorker.web.d.ts.map +1 -1
  61. package/build/workers/inlineAudioWebWorker.web.js +25 -1
  62. package/build/workers/inlineAudioWebWorker.web.js.map +1 -1
  63. package/ios/AudioDeviceManager.swift +654 -0
  64. package/ios/AudioStreamManager.swift +964 -760
  65. package/ios/ExpoAudioStreamModule.swift +174 -19
  66. package/ios/Features.swift +1 -1
  67. package/ios/ISSUE_IOS.md +45 -0
  68. package/ios/Logger.swift +13 -1
  69. package/ios/RecordingSettings.swift +12 -0
  70. package/package.json +2 -2
  71. package/src/AudioAnalysis/AudioAnalysis.types.ts +2 -2
  72. package/src/AudioDeviceManager.ts +571 -0
  73. package/src/AudioRecorder.provider.tsx +3 -0
  74. package/src/ExpoAudioStream.types.ts +113 -1
  75. package/src/ExpoAudioStream.web.ts +609 -69
  76. package/src/ExpoAudioStreamModule.ts +23 -0
  77. package/src/WebRecorder.web.ts +482 -92
  78. package/src/hooks/useAudioDevices.ts +180 -0
  79. package/src/index.ts +6 -0
  80. package/src/types/crc-32.d.ts +6 -6
  81. package/src/useAudioRecorder.tsx +27 -1
  82. package/src/utils/BlobFix.ts +6 -4
  83. package/src/utils/writeWavHeader.ts +26 -25
  84. package/src/workers/InlineFeaturesExtractor.web.tsx +27 -26
  85. package/src/workers/inlineAudioWebWorker.web.tsx +25 -1
@@ -15,6 +15,7 @@ import android.media.MediaCodec
15
15
  import java.io.FileInputStream
16
16
  import java.io.RandomAccessFile
17
17
  import java.util.zip.CRC32
18
+ import net.siteed.audiostream.LogUtils
18
19
 
19
20
  data class DecodingConfig(
20
21
  val targetSampleRate: Int? = null, // Optional target sample rate
@@ -34,6 +35,7 @@ class AudioProcessor(private val filesDir: File) {
34
35
  const val DCT_SQRT_DIVISOR = 2.0
35
36
  private const val N_FFT = 1024
36
37
  private const val N_CHROMA = 12
38
+ private const val CLASS_NAME = "AudioProcessor" // Add class name constant for logging
37
39
 
38
40
  private val uniqueIdCounter = AtomicLong(0L) // Keep as companion object property to maintain during pause/resume cycles
39
41
 
@@ -50,11 +52,11 @@ class AudioProcessor(private val filesDir: File) {
50
52
  private fun loadAudioFile(filePath: String): AudioData? {
51
53
  try {
52
54
  val fileUri = filePath.removePrefix("file://")
53
- Log.d("AudioProcessor", "Processing WAV file: $fileUri")
55
+ LogUtils.d(CLASS_NAME, "Processing WAV file: $fileUri")
54
56
 
55
57
  val file = File(fileUri).takeIf { it.exists() } ?: File(filesDir, File(fileUri).name).takeIf { it.exists() }
56
58
  ?: run {
57
- Log.e("AudioProcessor", "File not found: $fileUri")
59
+ LogUtils.e(CLASS_NAME, "File not found: $fileUri")
58
60
  return null
59
61
  }
60
62
 
@@ -64,14 +66,14 @@ class AudioProcessor(private val filesDir: File) {
64
66
  // Read RIFF header
65
67
  val riffHeader = ByteArray(4).apply { raf.readFully(this) }
66
68
  if (String(riffHeader) != "RIFF") {
67
- Log.e("AudioProcessor", "Invalid RIFF header")
69
+ LogUtils.e(CLASS_NAME, "Invalid RIFF header")
68
70
  return null
69
71
  }
70
72
 
71
73
  // Read WAVE header
72
74
  val waveHeader = ByteArray(4).apply { raf.readFully(this) }
73
75
  if (String(waveHeader) != "WAVE") {
74
- Log.e("AudioProcessor", "Invalid WAVE header")
76
+ LogUtils.e(CLASS_NAME, "Invalid WAVE header")
75
77
  return null
76
78
  }
77
79
 
@@ -89,12 +91,12 @@ class AudioProcessor(private val filesDir: File) {
89
91
  val chunkSizeBytes = ByteArray(4).apply { raf.readFully(this) }
90
92
  val chunkSize = ByteBuffer.wrap(chunkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).int.toLong() and 0xFFFFFFFFL
91
93
 
92
- Log.d("AudioProcessor", "Found chunk: $chunkId ($chunkSize bytes)")
94
+ LogUtils.d(CLASS_NAME, "Found chunk: $chunkId ($chunkSize bytes)")
93
95
 
94
96
  when (chunkId) {
95
97
  "fmt " -> {
96
98
  if (chunkSize < 16) {
97
- Log.e("AudioProcessor", "Invalid fmt chunk size")
99
+ LogUtils.e(CLASS_NAME, "Invalid fmt chunk size")
98
100
  return null
99
101
  }
100
102
 
@@ -109,11 +111,11 @@ class AudioProcessor(private val filesDir: File) {
109
111
  val blockAlign = formatBuffer.short
110
112
  bitDepth = formatBuffer.short.toInt() and 0xFFFF
111
113
 
112
- Log.d("AudioProcessor", "Raw format data: ${formatData.joinToString(", ")}")
113
- Log.d("AudioProcessor", "Format chunk: audioFormat=$audioFormat, channels=$channels, sampleRate=$sampleRate, bitDepth=$bitDepth, byteRate=$byteRate, blockAlign=$blockAlign")
114
+ LogUtils.d(CLASS_NAME, "Raw format data: ${formatData.joinToString(", ")}")
115
+ LogUtils.d(CLASS_NAME, "Format chunk: audioFormat=$audioFormat, channels=$channels, sampleRate=$sampleRate, bitDepth=$bitDepth, byteRate=$byteRate, blockAlign=$blockAlign")
114
116
 
115
117
  if (bitDepth !in listOf(8, 16, 32)) {
116
- Log.e("AudioProcessor", "Invalid bit depth: $bitDepth")
118
+ LogUtils.e(CLASS_NAME, "Invalid bit depth: $bitDepth")
117
119
  return null
118
120
  }
119
121
 
@@ -141,17 +143,17 @@ class AudioProcessor(private val filesDir: File) {
141
143
  }
142
144
 
143
145
  if (!fmtChunkFound || !dataChunkFound) {
144
- Log.e("AudioProcessor", "Missing essential chunks (fmt=$fmtChunkFound, data=$dataChunkFound)")
146
+ LogUtils.e(CLASS_NAME, "Missing essential chunks (fmt=$fmtChunkFound, data=$dataChunkFound)")
145
147
  return null
146
148
  }
147
149
 
148
150
  // Calculate actual data size if it seems wrong
149
151
  if (dataSize <= 0 || dataSize > fileSize - dataOffset) {
150
152
  dataSize = fileSize - dataOffset
151
- Log.d("AudioProcessor", "Adjusted data size to: $dataSize")
153
+ LogUtils.d(CLASS_NAME, "Adjusted data size to: $dataSize")
152
154
  }
153
155
 
154
- Log.d("AudioProcessor", "Reading PCM data: offset=$dataOffset, size=$dataSize")
156
+ LogUtils.d(CLASS_NAME, "Reading PCM data: offset=$dataOffset, size=$dataSize")
155
157
 
156
158
  val wavData = ByteArray(dataSize.toInt())
157
159
  raf.seek(dataOffset)
@@ -163,7 +165,7 @@ class AudioProcessor(private val filesDir: File) {
163
165
  val numFrames = wavData.size / bytesPerFrame
164
166
  val durationMs = (numFrames * 1000L) / sampleRate
165
167
 
166
- Log.d(Constants.TAG, "WAV duration calculation: size=${wavData.size}, bytesPerFrame=$bytesPerFrame, numFrames=$numFrames, sampleRate=$sampleRate, duration=${durationMs}ms")
168
+ LogUtils.d(CLASS_NAME, "WAV duration calculation: size=${wavData.size}, bytesPerFrame=$bytesPerFrame, numFrames=$numFrames, sampleRate=$sampleRate, duration=${durationMs}ms")
167
169
 
168
170
  return AudioData(
169
171
  data = wavData,
@@ -173,7 +175,7 @@ class AudioProcessor(private val filesDir: File) {
173
175
  durationMs = durationMs
174
176
  )
175
177
  } catch (e: Exception) {
176
- Log.e(Constants.TAG, "Failed to load WAV file: ${e.message}")
178
+ LogUtils.e(CLASS_NAME, "Failed to load WAV file: ${e.message}", e)
177
179
  return null
178
180
  }
179
181
  }
@@ -186,7 +188,7 @@ class AudioProcessor(private val filesDir: File) {
186
188
  */
187
189
  fun processAudioData(data: ByteArray, config: RecordingConfig): AudioAnalysisData {
188
190
  if (data.isEmpty()) {
189
- Log.e("AudioProcessor", "Received empty audio data")
191
+ LogUtils.e(CLASS_NAME, "Received empty audio data")
190
192
  return AudioAnalysisData(
191
193
  segmentDurationMs = config.segmentDurationMs,
192
194
  durationMs = 0,
@@ -216,12 +218,12 @@ class AudioProcessor(private val filesDir: File) {
216
218
  val samplesPerSegment = ((config.segmentDurationMs / 1000.0) * sampleRate).toInt()
217
219
  val totalPoints = ceil(totalSamples.toDouble() / samplesPerSegment).toInt()
218
220
 
219
- Log.d("AudioProcessor", "Extracting waveform totalSize=${data.size} with $totalSamples samples --> $totalPoints points")
220
- Log.d("AudioProcessor", "segmentDuration: ${config.segmentDurationMs}ms, samplesPerSegment: $samplesPerSegment")
221
+ LogUtils.d(CLASS_NAME, "Extracting waveform totalSize=${data.size} with $totalSamples samples --> $totalPoints points")
222
+ LogUtils.d(CLASS_NAME, "segmentDuration: ${config.segmentDurationMs}ms, samplesPerSegment: $samplesPerSegment")
221
223
 
222
224
  // Remove expectedPoints calculation since it used pointsPerSecond
223
225
  val samplesPerPoint = ceil(channelData.size / totalPoints.toDouble()).toInt()
224
- Log.d("AudioProcessor", "Extracting waveform with samplesPerPoints=$samplesPerPoint")
226
+ LogUtils.d(CLASS_NAME, "Extracting waveform with samplesPerPoints=$samplesPerPoint")
225
227
 
226
228
  val dataPoints = mutableListOf<DataPoint>()
227
229
  var minAmplitude = Float.MAX_VALUE
@@ -372,21 +374,21 @@ class AudioProcessor(private val filesDir: File) {
372
374
  val mfcc = try {
373
375
  if (featureOptions["mfcc"] == true) computeMFCC(segmentData, sampleRate) else emptyList()
374
376
  } catch (e: Exception) {
375
- Log.e("AudioProcessor", "Failed to extract MFCC: ${e.message}", e)
377
+ LogUtils.e(CLASS_NAME, "Failed to extract MFCC: ${e.message}", e)
376
378
  emptyList()
377
379
  }
378
380
 
379
381
  val melSpectrogram = try {
380
382
  if (featureOptions["melSpectrogram"] == true) computeMelSpectrogram(segmentData, sampleRate) else emptyList()
381
383
  } catch (e: Exception) {
382
- Log.e("AudioProcessor", "Failed to compute mel spectrogram: ${e.message}", e)
384
+ LogUtils.e(CLASS_NAME, "Failed to compute mel spectrogram: ${e.message}", e)
383
385
  emptyList()
384
386
  }
385
387
 
386
388
  val chroma = try {
387
389
  if (featureOptions["chromagram"] == true) computeChroma(segmentData, sampleRate) else emptyList()
388
390
  } catch (e: Exception) {
389
- Log.e("AudioProcessor", "Failed to compute chroma: ${e.message}", e)
391
+ LogUtils.e(CLASS_NAME, "Failed to compute chroma: ${e.message}", e)
390
392
  emptyList()
391
393
  }
392
394
 
@@ -402,28 +404,28 @@ class AudioProcessor(private val filesDir: File) {
402
404
  val tempo = try {
403
405
  if (featureOptions["tempo"] == true) extractTempo(segmentData, sampleRate) else 0f
404
406
  } catch (e: Exception) {
405
- Log.e("AudioProcessor", "Failed to extract tempo: ${e.message}", e)
407
+ LogUtils.e(CLASS_NAME, "Failed to extract tempo: ${e.message}", e)
406
408
  0f
407
409
  }
408
410
 
409
411
  val hnr = try {
410
412
  if (featureOptions["hnr"] == true) extractHNR(segmentData) else 0f
411
413
  } catch (e: Exception) {
412
- Log.e("AudioProcessor", "Failed to extract HNR: ${e.message}", e)
414
+ LogUtils.e(CLASS_NAME, "Failed to extract HNR: ${e.message}", e)
413
415
  0f
414
416
  }
415
417
 
416
418
  val spectralContrast = try {
417
419
  if (featureOptions["spectralContrast"] == true) computeSpectralContrast(segmentData, sampleRate) else emptyList()
418
420
  } catch (e: Exception) {
419
- Log.e("AudioProcessor", "Failed to compute spectral contrast: ${e.message}", e)
421
+ LogUtils.e(CLASS_NAME, "Failed to compute spectral contrast: ${e.message}", e)
420
422
  emptyList()
421
423
  }
422
424
 
423
425
  val tonnetz = try {
424
426
  if (featureOptions["tonnetz"] == true) computeTonnetz(segmentData, sampleRate) else emptyList()
425
427
  } catch (e: Exception) {
426
- Log.e("AudioProcessor", "Failed to compute tonnetz: ${e.message}", e)
428
+ LogUtils.e(CLASS_NAME, "Failed to compute tonnetz: ${e.message}", e)
427
429
  emptyList()
428
430
  }
429
431
 
@@ -661,7 +663,7 @@ class AudioProcessor(private val filesDir: File) {
661
663
  )
662
664
 
663
665
  if (melFilters.any { it.size != powerSpectrum.size }) {
664
- Log.e("AudioProcessor", "Mel filter size (${melFilters[0].size}) does not match power spectrum size (${powerSpectrum.size})")
666
+ LogUtils.e(CLASS_NAME, "Mel filter size (${melFilters[0].size}) does not match power spectrum size (${powerSpectrum.size})")
665
667
  return emptyList()
666
668
  }
667
669
 
@@ -812,14 +814,14 @@ class AudioProcessor(private val filesDir: File) {
812
814
  val cleanUri = fileUri.removePrefix("file://")
813
815
  val file = File(cleanUri).takeIf { it.exists() } ?: File(filesDir, File(cleanUri).name).takeIf { it.exists() }
814
816
  ?: run {
815
- Log.e("AudioProcessor", "File not found in any location: $cleanUri")
817
+ LogUtils.e(CLASS_NAME, "File not found in any location: $cleanUri")
816
818
  return null
817
819
  }
818
820
 
819
821
  // First try MediaExtractor
820
822
  val extractor = MediaExtractor()
821
823
  try {
822
- Log.d("AudioProcessor", "Attempting MediaExtractor with path: ${file.absolutePath}")
824
+ LogUtils.d(CLASS_NAME, "Attempting MediaExtractor with path: ${file.absolutePath}")
823
825
  extractor.setDataSource(file.absolutePath)
824
826
 
825
827
  // Find the first audio track
@@ -838,10 +840,10 @@ class AudioProcessor(private val filesDir: File) {
838
840
  } catch (e: Exception) {
839
841
  (format.getString(MediaFormat.KEY_DURATION) ?: "-1").toLong()
840
842
  }
841
- Log.d("AudioProcessor", "Raw duration from format: ${totalDurationUs}us")
843
+ LogUtils.d(CLASS_NAME, "Raw duration from format: ${totalDurationUs}us")
842
844
 
843
845
  val totalDurationMs = totalDurationUs / 1000
844
- Log.d("AudioProcessor", "Final duration: ${totalDurationMs}ms")
846
+ LogUtils.d(CLASS_NAME, "Final duration: ${totalDurationMs}ms")
845
847
 
846
848
  // Process using MediaExtractor
847
849
  val pcmData = decodeAudioToPCM(extractor, format)
@@ -867,14 +869,14 @@ class AudioProcessor(private val filesDir: File) {
867
869
  )
868
870
  }
869
871
  } catch (e: Exception) {
870
- Log.d("AudioProcessor", "MediaExtractor failed, attempting WAV parser: ${e.message}")
872
+ LogUtils.d(CLASS_NAME, "MediaExtractor failed, attempting WAV parser: ${e.message}")
871
873
  } finally {
872
874
  extractor.release()
873
875
  }
874
876
 
875
877
  // If MediaExtractor failed and file is WAV, try WAV parser
876
878
  if (file.name.lowercase().endsWith(".wav")) {
877
- Log.d("AudioProcessor", "Falling back to WAV parser")
879
+ LogUtils.d(CLASS_NAME, "Falling back to WAV parser")
878
880
  return loadAudioFile(file.absolutePath)?.let { wavData ->
879
881
  if (decodingConfig != null) {
880
882
  val processedData = processAudio(
@@ -898,7 +900,7 @@ class AudioProcessor(private val filesDir: File) {
898
900
  }
899
901
  }
900
902
 
901
- Log.e("AudioProcessor", "Failed to process audio file with both MediaExtractor and WAV parser")
903
+ LogUtils.e(CLASS_NAME, "Failed to process audio file with both MediaExtractor and WAV parser")
902
904
  return null
903
905
  }
904
906
 
@@ -1081,11 +1083,11 @@ class AudioProcessor(private val filesDir: File) {
1081
1083
  raf.readFully(bytes)
1082
1084
  }
1083
1085
 
1084
- Log.d("AudioProcessor", "WAV Header Bytes: ${bytes.joinToString(", ") { String.format("%02X", it) }}")
1085
- Log.d("AudioProcessor", "ASCII: ${bytes.map { it.toInt().toChar() }.joinToString("")}")
1086
+ LogUtils.d(CLASS_NAME, "WAV Header Bytes: ${bytes.joinToString(", ") { String.format("%02X", it) }}")
1087
+ LogUtils.d(CLASS_NAME, "ASCII: ${bytes.map { it.toInt().toChar() }.joinToString("")}")
1086
1088
 
1087
1089
  val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN)
1088
- Log.d("AudioProcessor", """
1090
+ LogUtils.d(CLASS_NAME, """
1089
1091
  RIFF header: ${String(bytes, 0, 4)}
1090
1092
  File size: ${buffer.getInt(4)}
1091
1093
  WAVE header: ${String(bytes, 8, 4)}
@@ -1099,7 +1101,7 @@ class AudioProcessor(private val filesDir: File) {
1099
1101
  Bits per sample: ${buffer.getShort(34)}
1100
1102
  """.trimIndent())
1101
1103
  } catch (e: Exception) {
1102
- Log.e("AudioProcessor", "Failed to debug WAV header: ${e.message}")
1104
+ LogUtils.e(CLASS_NAME, "Failed to debug WAV header: ${e.message}", e)
1103
1105
  }
1104
1106
  }
1105
1107
 
@@ -1112,7 +1114,7 @@ class AudioProcessor(private val filesDir: File) {
1112
1114
  ): AudioAnalysisData {
1113
1115
  val totalDurationMs = audioData.durationMs
1114
1116
 
1115
- Log.d(Constants.TAG, "Total audio duration: ${totalDurationMs}ms")
1117
+ LogUtils.d(CLASS_NAME, "Total audio duration: ${totalDurationMs}ms")
1116
1118
 
1117
1119
  // Validate time range
1118
1120
  if (startTimeMs != null) {
@@ -1123,7 +1125,7 @@ class AudioProcessor(private val filesDir: File) {
1123
1125
  if (endTimeMs != null) {
1124
1126
  require(endTimeMs >= 0) { "endTime must be non-negative, got: $endTimeMs" }
1125
1127
  if (endTimeMs > totalDurationMs) {
1126
- Log.w(Constants.TAG, "endTime ($endTimeMs) is beyond audio duration ($totalDurationMs), clamping to duration")
1128
+ LogUtils.w(CLASS_NAME, "endTime ($endTimeMs) is beyond audio duration ($totalDurationMs), clamping to duration")
1127
1129
  }
1128
1130
  if (startTimeMs != null) {
1129
1131
  require(startTimeMs < endTimeMs) { "startTime ($startTimeMs) must be less than endTime ($endTimeMs)" }
@@ -1135,7 +1137,7 @@ class AudioProcessor(private val filesDir: File) {
1135
1137
  val effectiveEndMs = (endTimeMs ?: totalDurationMs).coerceAtMost(totalDurationMs)
1136
1138
  val durationMs = effectiveEndMs - effectiveStartMs
1137
1139
 
1138
- Log.d(Constants.TAG, "Preview range: ${effectiveStartMs}ms to ${effectiveEndMs}ms (${durationMs}ms)")
1140
+ LogUtils.d(CLASS_NAME, "Preview range: ${effectiveStartMs}ms to ${effectiveEndMs}ms (${durationMs}ms)")
1139
1141
 
1140
1142
  // Calculate sample range
1141
1143
  val startSampleIndex = ((effectiveStartMs * audioData.sampleRate) / 1000).toInt()
@@ -1199,7 +1201,7 @@ class AudioProcessor(private val filesDir: File) {
1199
1201
  samples = segmentData.size
1200
1202
  ))
1201
1203
  } catch (e: Exception) {
1202
- Log.e(Constants.TAG, "Error processing segment $i: ${e.message}")
1204
+ LogUtils.e(CLASS_NAME, "Error processing segment $i: ${e.message}")
1203
1205
  throw IllegalStateException("Failed to process audio segment: ${e.message}", e)
1204
1206
  }
1205
1207
  }
@@ -1260,17 +1262,17 @@ class AudioProcessor(private val filesDir: File) {
1260
1262
 
1261
1263
  // If it's a WAV file (by extension and header verification)
1262
1264
  return if (isWavByExtension && headerSize != null) {
1263
- Log.d(Constants.TAG, "Loading WAV range with header size: $headerSize bytes")
1265
+ LogUtils.d(CLASS_NAME, "Loading WAV range with header size: $headerSize bytes")
1264
1266
  loadWavRange(fileUri, startTimeMs, endTimeMs, effectiveConfig, headerSize)
1265
1267
  } else {
1266
1268
  if (isWavByExtension) {
1267
- Log.w(Constants.TAG, "File has .wav extension but invalid header, falling back to compressed loader")
1269
+ LogUtils.w(CLASS_NAME, "File has .wav extension but invalid header, falling back to compressed loader")
1268
1270
  }
1269
- Log.d(Constants.TAG, "Loading compressed audio range")
1271
+ LogUtils.d(CLASS_NAME, "Loading compressed audio range")
1270
1272
  loadCompressedAudioRange(fileUri, startTimeMs, endTimeMs, effectiveConfig)
1271
1273
  }
1272
1274
  } catch (e: Exception) {
1273
- Log.e(Constants.TAG, "Failed to load audio range: ${e.message}", e)
1275
+ LogUtils.e(CLASS_NAME, "Failed to load audio range: ${e.message}", e)
1274
1276
  return null
1275
1277
  }
1276
1278
  }
@@ -1297,7 +1299,7 @@ class AudioProcessor(private val filesDir: File) {
1297
1299
  val startByte = headerSize + startByteOffset
1298
1300
  val endByte = headerSize + endByteOffset
1299
1301
 
1300
- Log.d(Constants.TAG, """
1302
+ LogUtils.d(CLASS_NAME, """
1301
1303
  Loading WAV range:
1302
1304
  - headerSize: $headerSize
1303
1305
  - startByte: $startByte
@@ -1320,7 +1322,7 @@ class AudioProcessor(private val filesDir: File) {
1320
1322
  config.targetBitDepth
1321
1323
  )
1322
1324
  effectiveBitDepth = config.targetBitDepth
1323
- Log.d(Constants.TAG, "Converted bit depth from ${format.bitDepth} to ${config.targetBitDepth}")
1325
+ LogUtils.d(CLASS_NAME, "Converted bit depth from ${format.bitDepth} to ${config.targetBitDepth}")
1324
1326
  }
1325
1327
 
1326
1328
  return AudioData(
@@ -1331,7 +1333,7 @@ class AudioProcessor(private val filesDir: File) {
1331
1333
  durationMs = endTimeMs - startTimeMs
1332
1334
  )
1333
1335
  } catch (e: Exception) {
1334
- Log.e(Constants.TAG, "Failed to load WAV range: ${e.message}", e)
1336
+ LogUtils.e(CLASS_NAME, "Failed to load WAV range: ${e.message}", e)
1335
1337
  return null
1336
1338
  }
1337
1339
  }
@@ -1357,10 +1359,10 @@ class AudioProcessor(private val filesDir: File) {
1357
1359
  } catch (e: Exception) {
1358
1360
  (format.getString(MediaFormat.KEY_DURATION) ?: "-1").toLong()
1359
1361
  }
1360
- Log.d("AudioProcessor", "Raw duration from format: ${totalDurationUs}us")
1362
+ LogUtils.d(CLASS_NAME, "Raw duration from format: ${totalDurationUs}us")
1361
1363
 
1362
1364
  val totalDurationMs = totalDurationUs / 1000
1363
- Log.d("AudioProcessor", "Final duration: ${totalDurationMs}ms")
1365
+ LogUtils.d(CLASS_NAME, "Final duration: ${totalDurationMs}ms")
1364
1366
 
1365
1367
  // Calculate valid time range
1366
1368
  val validStartMs = startTimeMs.coerceIn(0, totalDurationMs) ?: 0
@@ -1385,7 +1387,7 @@ class AudioProcessor(private val filesDir: File) {
1385
1387
  val samplesPerSecond = targetSampleRate * targetChannels
1386
1388
  val totalBytes = (effectiveDurationMs * samplesPerSecond * bytesPerSample) / 1000
1387
1389
 
1388
- Log.d(Constants.TAG, """
1390
+ LogUtils.d(CLASS_NAME, """
1389
1391
  Loading audio range:
1390
1392
  - start: ${validStartMs}ms
1391
1393
  - end: ${validEndMs}ms
@@ -1454,10 +1456,10 @@ class AudioProcessor(private val filesDir: File) {
1454
1456
  bitDepth = targetBitDepth,
1455
1457
  durationMs = endTimeMs - startTimeMs // Use the actual time range
1456
1458
  ).also {
1457
- Log.d(Constants.TAG, "Loaded compressed audio with duration: ${effectiveDurationMs}ms")
1459
+ LogUtils.d(CLASS_NAME, "Loaded compressed audio with duration: ${effectiveDurationMs}ms")
1458
1460
  }
1459
1461
  } catch (e: Exception) {
1460
- Log.e(Constants.TAG, "Failed to load compressed audio range: ${e.message}", e)
1462
+ LogUtils.e(CLASS_NAME, "Failed to load compressed audio range: ${e.message}", e)
1461
1463
  return null
1462
1464
  } finally {
1463
1465
  decoder?.stop()
@@ -1489,7 +1491,7 @@ class AudioProcessor(private val filesDir: File) {
1489
1491
 
1490
1492
  val durationMs = (endTimeMs - startTimeMs).toInt()
1491
1493
 
1492
- Log.d(Constants.TAG, """
1494
+ LogUtils.d(CLASS_NAME, """
1493
1495
  Trimming audio:
1494
1496
  - start: ${startTimeMs}ms
1495
1497
  - end: ${endTimeMs}ms
@@ -1546,7 +1548,7 @@ class AudioProcessor(private val filesDir: File) {
1546
1548
  bitDepth = audioData.bitDepth
1547
1549
  )
1548
1550
  } catch (e: Exception) {
1549
- Log.e(Constants.TAG, "Failed to trim audio: ${e.message}", e)
1551
+ LogUtils.e(CLASS_NAME, "Failed to trim audio: ${e.message}", e)
1550
1552
  return null
1551
1553
  }
1552
1554
  }
@@ -1910,7 +1912,7 @@ class AudioProcessor(private val filesDir: File) {
1910
1912
  try {
1911
1913
  fft.realForward(padded)
1912
1914
  } catch (e: Exception) {
1913
- Log.e("AudioProcessor", "FFT forward transform failed: ${e.message}")
1915
+ LogUtils.e(CLASS_NAME, "FFT forward transform failed: ${e.message}")
1914
1916
  return 0.0f
1915
1917
  }
1916
1918
 
@@ -1929,7 +1931,7 @@ class AudioProcessor(private val filesDir: File) {
1929
1931
  powerSpectrum[fftLength - i] = powerSpectrum[i] // Mirror for inverse FFT
1930
1932
  }
1931
1933
  } catch (e: Exception) {
1932
- Log.e("AudioProcessor", "Power spectrum computation failed: ${e.message}")
1934
+ LogUtils.e(CLASS_NAME, "Power spectrum computation failed: ${e.message}")
1933
1935
  return 0.0f
1934
1936
  }
1935
1937
 
@@ -1938,7 +1940,7 @@ class AudioProcessor(private val filesDir: File) {
1938
1940
  try {
1939
1941
  fft.realInverse(powerSpectrum, autocorrelation)
1940
1942
  } catch (e: Exception) {
1941
- Log.e("AudioProcessor", "FFT inverse transform failed: ${e.message}")
1943
+ LogUtils.e(CLASS_NAME, "FFT inverse transform failed: ${e.message}")
1942
1944
  return 0.0f
1943
1945
  }
1944
1946
 
@@ -2011,7 +2013,7 @@ class AudioProcessor(private val filesDir: File) {
2011
2013
  val cleanUri = fileUri.removePrefix("file://")
2012
2014
  val file = File(cleanUri).takeIf { it.exists() } ?: File(filesDir, File(cleanUri).name).takeIf { it.exists() }
2013
2015
  ?: run {
2014
- Log.e(Constants.TAG, "File not found: $cleanUri")
2016
+ LogUtils.e(CLASS_NAME, "File not found: $cleanUri")
2015
2017
  return null
2016
2018
  }
2017
2019
 
@@ -2025,7 +2027,7 @@ class AudioProcessor(private val filesDir: File) {
2025
2027
  bitDepth = 16 // Most compressed formats decode to 16-bit PCM
2026
2028
  )
2027
2029
  } catch (e: Exception) {
2028
- Log.e(Constants.TAG, "Failed to get audio format: ${e.message}")
2030
+ LogUtils.e(CLASS_NAME, "Failed to get audio format: ${e.message}", e)
2029
2031
  return null
2030
2032
  } finally {
2031
2033
  extractor.release()
@@ -2064,7 +2066,7 @@ class AudioProcessor(private val filesDir: File) {
2064
2066
  val cleanUri = fileUri.removePrefix("file://")
2065
2067
  val file = File(cleanUri).takeIf { it.exists() } ?: File(filesDir, File(cleanUri).name).takeIf { it.exists() }
2066
2068
  ?: run {
2067
- Log.e(Constants.TAG, "File not found: $cleanUri")
2069
+ LogUtils.e(CLASS_NAME, "File not found: $cleanUri")
2068
2070
  return null
2069
2071
  }
2070
2072
 
@@ -2074,13 +2076,13 @@ class AudioProcessor(private val filesDir: File) {
2074
2076
 
2075
2077
  // Read RIFF header
2076
2078
  if (inputStream.read(buffer) != 12) {
2077
- Log.e(Constants.TAG, "Failed to read RIFF header")
2079
+ LogUtils.e(CLASS_NAME, "Failed to read RIFF header")
2078
2080
  return null
2079
2081
  }
2080
2082
 
2081
2083
  // Verify RIFF header
2082
2084
  if (String(buffer, 0, 4) != "RIFF" || String(buffer, 8, 4) != "WAVE") {
2083
- Log.e(Constants.TAG, "Invalid WAV file format")
2085
+ LogUtils.e(CLASS_NAME, "Invalid WAV file format")
2084
2086
  return null
2085
2087
  }
2086
2088
 
@@ -2090,7 +2092,7 @@ class AudioProcessor(private val filesDir: File) {
2090
2092
  // Read chunks until we find the data chunk
2091
2093
  while (true) {
2092
2094
  if (inputStream.read(buffer, 0, 8) != 8) {
2093
- Log.e(Constants.TAG, "Unexpected end of file while reading chunks")
2095
+ LogUtils.e(CLASS_NAME, "Unexpected end of file while reading chunks")
2094
2096
  break
2095
2097
  }
2096
2098
 
@@ -2100,11 +2102,11 @@ class AudioProcessor(private val filesDir: File) {
2100
2102
  (buffer[4].toInt() and 0xFF)
2101
2103
 
2102
2104
  val chunkId = String(buffer, 0, 4)
2103
- Log.d(Constants.TAG, "Found chunk: $chunkId, size: $chunkSize")
2105
+ LogUtils.d(CLASS_NAME, "Found chunk: $chunkId, size: $chunkSize")
2104
2106
 
2105
2107
  if (chunkId == "data") {
2106
2108
  headerSize += 8 // Add chunk header size
2107
- Log.d(Constants.TAG, "Found data chunk at offset: $headerSize")
2109
+ LogUtils.d(CLASS_NAME, "Found data chunk at offset: $headerSize")
2108
2110
  break
2109
2111
  }
2110
2112
 
@@ -2113,11 +2115,11 @@ class AudioProcessor(private val filesDir: File) {
2113
2115
  }
2114
2116
 
2115
2117
  inputStream.close()
2116
- Log.d(Constants.TAG, "Total WAV header size: $headerSize bytes")
2118
+ LogUtils.d(CLASS_NAME, "Total WAV header size: $headerSize bytes")
2117
2119
  return headerSize
2118
2120
 
2119
2121
  } catch (e: Exception) {
2120
- Log.e(Constants.TAG, "Error calculating WAV header size: ${e.message}")
2122
+ LogUtils.e(CLASS_NAME, "Error calculating WAV header size: ${e.message}", e)
2121
2123
  return null
2122
2124
  }
2123
2125
  }
@@ -2215,20 +2217,20 @@ class AudioProcessor(private val filesDir: File) {
2215
2217
  durationMs = endTimeMs - startTimeMs
2216
2218
  )
2217
2219
  } catch (e: Exception) {
2218
- Log.e(Constants.TAG, "Failed to decode audio range: ${e.message}", e)
2220
+ LogUtils.e(CLASS_NAME, "Failed to decode audio range: ${e.message}", e)
2219
2221
  return null
2220
2222
  } finally {
2221
2223
  try {
2222
2224
  decoder?.stop()
2223
2225
  decoder?.release()
2224
2226
  } catch (e: Exception) {
2225
- Log.w(Constants.TAG, "Error releasing decoder: ${e.message}")
2227
+ LogUtils.w(CLASS_NAME, "Error releasing decoder: ${e.message}")
2226
2228
  }
2227
2229
 
2228
2230
  try {
2229
2231
  extractor.release()
2230
2232
  } catch (e: Exception) {
2231
- Log.w(Constants.TAG, "Error releasing extractor: ${e.message}")
2233
+ LogUtils.w(CLASS_NAME, "Error releasing extractor: ${e.message}")
2232
2234
  }
2233
2235
  }
2234
2236
  }