@siteed/audio-studio 3.0.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 (375) hide show
  1. package/CHANGELOG.md +535 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/android/build.gradle +143 -0
  5. package/android/src/androidTest/assets/chorus.wav +0 -0
  6. package/android/src/androidTest/assets/jfk.wav +0 -0
  7. package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
  8. package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
  9. package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +197 -0
  10. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +541 -0
  11. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +234 -0
  12. package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +332 -0
  13. package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +324 -0
  14. package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +253 -0
  15. package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +218 -0
  16. package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +120 -0
  17. package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +345 -0
  18. package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +340 -0
  19. package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +252 -0
  20. package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +95 -0
  21. package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +43 -0
  22. package/android/src/main/AndroidManifest.xml +30 -0
  23. package/android/src/main/CMakeLists.txt +29 -0
  24. package/android/src/main/java/net/siteed/audiostudio/AudioAnalysisData.kt +188 -0
  25. package/android/src/main/java/net/siteed/audiostudio/AudioDataEncoder.kt +9 -0
  26. package/android/src/main/java/net/siteed/audiostudio/AudioDeviceManager.kt +1741 -0
  27. package/android/src/main/java/net/siteed/audiostudio/AudioFeaturesNative.kt +26 -0
  28. package/android/src/main/java/net/siteed/audiostudio/AudioFileHandler.kt +136 -0
  29. package/android/src/main/java/net/siteed/audiostudio/AudioFormatUtils.kt +354 -0
  30. package/android/src/main/java/net/siteed/audiostudio/AudioNotificationsManager.kt +439 -0
  31. package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +2237 -0
  32. package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +2163 -0
  33. package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +167 -0
  34. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +1112 -0
  35. package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +1099 -0
  36. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +37 -0
  37. package/android/src/main/java/net/siteed/audiostudio/EventSender.kt +7 -0
  38. package/android/src/main/java/net/siteed/audiostudio/FFT.kt +100 -0
  39. package/android/src/main/java/net/siteed/audiostudio/Features.kt +98 -0
  40. package/android/src/main/java/net/siteed/audiostudio/LogUtils.kt +93 -0
  41. package/android/src/main/java/net/siteed/audiostudio/MelSpectrogramNative.kt +36 -0
  42. package/android/src/main/java/net/siteed/audiostudio/NotificationConfig.kt +72 -0
  43. package/android/src/main/java/net/siteed/audiostudio/PermissionUtils.kt +68 -0
  44. package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +59 -0
  45. package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +259 -0
  46. package/android/src/main/java/net/siteed/audiostudio/WaveformConfig.kt +19 -0
  47. package/android/src/main/java/net/siteed/audiostudio/WaveformRenderer.kt +159 -0
  48. package/android/src/main/jni/AudioFeaturesJNI.cpp +152 -0
  49. package/android/src/main/jni/MelSpectrogramJNI.cpp +165 -0
  50. package/android/src/main/res/drawable/ic_default_action_icon.xml +16 -0
  51. package/android/src/main/res/drawable/ic_microphone.xml +13 -0
  52. package/android/src/main/res/drawable/ic_pause.xml +10 -0
  53. package/android/src/main/res/drawable/ic_play.xml +10 -0
  54. package/android/src/main/res/drawable/ic_stop.xml +10 -0
  55. package/android/src/main/res/layout/notification_recording.xml +37 -0
  56. package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +279 -0
  57. package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +249 -0
  58. package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +151 -0
  59. package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +273 -0
  60. package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +140 -0
  61. package/android/src/test/resources/chorus.wav +0 -0
  62. package/android/src/test/resources/generate_test_audio.py +94 -0
  63. package/android/src/test/resources/jfk.wav +0 -0
  64. package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
  65. package/android/src/test/resources/recorder_hello_world.wav +0 -0
  66. package/app.plugin.js +3 -0
  67. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +4 -0
  68. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  69. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +164 -0
  70. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
  71. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +213 -0
  72. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  73. package/build/cjs/AudioAnalysis/extractAudioData.js +21 -0
  74. package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -0
  75. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +90 -0
  76. package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  77. package/build/cjs/AudioAnalysis/extractPreview.js +28 -0
  78. package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -0
  79. package/build/cjs/AudioAnalysis/extractWaveform.js +18 -0
  80. package/build/cjs/AudioAnalysis/extractWaveform.js.map +1 -0
  81. package/build/cjs/AudioAnalysis/melSpectrogramWasm.js +149 -0
  82. package/build/cjs/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
  83. package/build/cjs/AudioDeviceManager.js +688 -0
  84. package/build/cjs/AudioDeviceManager.js.map +1 -0
  85. package/build/cjs/AudioRecorder.provider.js +78 -0
  86. package/build/cjs/AudioRecorder.provider.js.map +1 -0
  87. package/build/cjs/AudioStudio.native.js +8 -0
  88. package/build/cjs/AudioStudio.native.js.map +1 -0
  89. package/build/cjs/AudioStudio.types.js +11 -0
  90. package/build/cjs/AudioStudio.types.js.map +1 -0
  91. package/build/cjs/AudioStudio.web.js +708 -0
  92. package/build/cjs/AudioStudio.web.js.map +1 -0
  93. package/build/cjs/AudioStudioModule.js +718 -0
  94. package/build/cjs/AudioStudioModule.js.map +1 -0
  95. package/build/cjs/WebRecorder.web.js +865 -0
  96. package/build/cjs/WebRecorder.web.js.map +1 -0
  97. package/build/cjs/constants/platformLimitations.js +99 -0
  98. package/build/cjs/constants/platformLimitations.js.map +1 -0
  99. package/build/cjs/constants.js +20 -0
  100. package/build/cjs/constants.js.map +1 -0
  101. package/build/cjs/events.js +29 -0
  102. package/build/cjs/events.js.map +1 -0
  103. package/build/cjs/hooks/useAudioDevices.js +179 -0
  104. package/build/cjs/hooks/useAudioDevices.js.map +1 -0
  105. package/build/cjs/index.js +64 -0
  106. package/build/cjs/index.js.map +1 -0
  107. package/build/cjs/trimAudio.js +76 -0
  108. package/build/cjs/trimAudio.js.map +1 -0
  109. package/build/cjs/useAudioRecorder.js +535 -0
  110. package/build/cjs/useAudioRecorder.js.map +1 -0
  111. package/build/cjs/utils/BlobFix.js +502 -0
  112. package/build/cjs/utils/BlobFix.js.map +1 -0
  113. package/build/cjs/utils/audioProcessing.js +136 -0
  114. package/build/cjs/utils/audioProcessing.js.map +1 -0
  115. package/build/cjs/utils/cleanNativeOptions.js +22 -0
  116. package/build/cjs/utils/cleanNativeOptions.js.map +1 -0
  117. package/build/cjs/utils/concatenateBuffers.js +25 -0
  118. package/build/cjs/utils/concatenateBuffers.js.map +1 -0
  119. package/build/cjs/utils/convertPCMToFloat32.js +124 -0
  120. package/build/cjs/utils/convertPCMToFloat32.js.map +1 -0
  121. package/build/cjs/utils/crc32.js +52 -0
  122. package/build/cjs/utils/crc32.js.map +1 -0
  123. package/build/cjs/utils/encodingToBitDepth.js +17 -0
  124. package/build/cjs/utils/encodingToBitDepth.js.map +1 -0
  125. package/build/cjs/utils/getWavFileInfo.js +96 -0
  126. package/build/cjs/utils/getWavFileInfo.js.map +1 -0
  127. package/build/cjs/utils/writeWavHeader.js +88 -0
  128. package/build/cjs/utils/writeWavHeader.js.map +1 -0
  129. package/build/cjs/workers/InlineFeaturesExtractor.web.js +294 -0
  130. package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +1 -0
  131. package/build/cjs/workers/inlineAudioWebWorker.web.js +190 -0
  132. package/build/cjs/workers/inlineAudioWebWorker.web.js.map +1 -0
  133. package/build/cjs/workers/wasmGlueString.web.js +27 -0
  134. package/build/cjs/workers/wasmGlueString.web.js.map +1 -0
  135. package/build/esm/AudioAnalysis/AudioAnalysis.types.js +3 -0
  136. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
  137. package/build/esm/AudioAnalysis/audioFeaturesWasm.js +126 -0
  138. package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
  139. package/build/esm/AudioAnalysis/extractAudioAnalysis.js +205 -0
  140. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
  141. package/build/esm/AudioAnalysis/extractAudioData.js +14 -0
  142. package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -0
  143. package/build/esm/AudioAnalysis/extractMelSpectrogram.js +86 -0
  144. package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
  145. package/build/esm/AudioAnalysis/extractPreview.js +25 -0
  146. package/build/esm/AudioAnalysis/extractPreview.js.map +1 -0
  147. package/build/esm/AudioAnalysis/extractWaveform.js +11 -0
  148. package/build/esm/AudioAnalysis/extractWaveform.js.map +1 -0
  149. package/build/esm/AudioAnalysis/melSpectrogramWasm.js +111 -0
  150. package/build/esm/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
  151. package/build/esm/AudioDeviceManager.js +681 -0
  152. package/build/esm/AudioDeviceManager.js.map +1 -0
  153. package/build/esm/AudioRecorder.provider.js +40 -0
  154. package/build/esm/AudioRecorder.provider.js.map +1 -0
  155. package/build/esm/AudioStudio.native.js +6 -0
  156. package/build/esm/AudioStudio.native.js.map +1 -0
  157. package/build/esm/AudioStudio.types.js +8 -0
  158. package/build/esm/AudioStudio.types.js.map +1 -0
  159. package/build/esm/AudioStudio.web.js +704 -0
  160. package/build/esm/AudioStudio.web.js.map +1 -0
  161. package/build/esm/AudioStudioModule.js +713 -0
  162. package/build/esm/AudioStudioModule.js.map +1 -0
  163. package/build/esm/WebRecorder.web.js +861 -0
  164. package/build/esm/WebRecorder.web.js.map +1 -0
  165. package/build/esm/constants/platformLimitations.js +90 -0
  166. package/build/esm/constants/platformLimitations.js.map +1 -0
  167. package/build/esm/constants.js +17 -0
  168. package/build/esm/constants.js.map +1 -0
  169. package/build/esm/events.js +21 -0
  170. package/build/esm/events.js.map +1 -0
  171. package/build/esm/hooks/useAudioDevices.js +176 -0
  172. package/build/esm/hooks/useAudioDevices.js.map +1 -0
  173. package/build/esm/index.js +23 -0
  174. package/build/esm/index.js.map +1 -0
  175. package/build/esm/trimAudio.js +69 -0
  176. package/build/esm/trimAudio.js.map +1 -0
  177. package/build/esm/useAudioRecorder.js +529 -0
  178. package/build/esm/useAudioRecorder.js.map +1 -0
  179. package/build/esm/utils/BlobFix.js +498 -0
  180. package/build/esm/utils/BlobFix.js.map +1 -0
  181. package/build/esm/utils/audioProcessing.js +133 -0
  182. package/build/esm/utils/audioProcessing.js.map +1 -0
  183. package/build/esm/utils/cleanNativeOptions.js +19 -0
  184. package/build/esm/utils/cleanNativeOptions.js.map +1 -0
  185. package/build/esm/utils/concatenateBuffers.js +21 -0
  186. package/build/esm/utils/concatenateBuffers.js.map +1 -0
  187. package/build/esm/utils/convertPCMToFloat32.js +120 -0
  188. package/build/esm/utils/convertPCMToFloat32.js.map +1 -0
  189. package/build/esm/utils/crc32.js +50 -0
  190. package/build/esm/utils/crc32.js.map +1 -0
  191. package/build/esm/utils/encodingToBitDepth.js +13 -0
  192. package/build/esm/utils/encodingToBitDepth.js.map +1 -0
  193. package/build/esm/utils/getWavFileInfo.js +92 -0
  194. package/build/esm/utils/getWavFileInfo.js.map +1 -0
  195. package/build/esm/utils/writeWavHeader.js +84 -0
  196. package/build/esm/utils/writeWavHeader.js.map +1 -0
  197. package/build/esm/workers/InlineFeaturesExtractor.web.js +291 -0
  198. package/build/esm/workers/InlineFeaturesExtractor.web.js.map +1 -0
  199. package/build/esm/workers/inlineAudioWebWorker.web.js +187 -0
  200. package/build/esm/workers/inlineAudioWebWorker.web.js.map +1 -0
  201. package/build/esm/workers/wasmGlueString.web.js +24 -0
  202. package/build/esm/workers/wasmGlueString.web.js.map +1 -0
  203. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +198 -0
  204. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
  205. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +24 -0
  206. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -0
  207. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts +74 -0
  208. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
  209. package/build/types/AudioAnalysis/extractAudioData.d.ts +3 -0
  210. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -0
  211. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts +20 -0
  212. package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
  213. package/build/types/AudioAnalysis/extractPreview.d.ts +11 -0
  214. package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -0
  215. package/build/types/AudioAnalysis/extractWaveform.d.ts +8 -0
  216. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +1 -0
  217. package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts +16 -0
  218. package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts.map +1 -0
  219. package/build/types/AudioDeviceManager.d.ts +187 -0
  220. package/build/types/AudioDeviceManager.d.ts.map +1 -0
  221. package/build/types/AudioRecorder.provider.d.ts +11 -0
  222. package/build/types/AudioRecorder.provider.d.ts.map +1 -0
  223. package/build/types/AudioStudio.native.d.ts +3 -0
  224. package/build/types/AudioStudio.native.d.ts.map +1 -0
  225. package/build/types/AudioStudio.types.d.ts +760 -0
  226. package/build/types/AudioStudio.types.d.ts.map +1 -0
  227. package/build/types/AudioStudio.web.d.ts +96 -0
  228. package/build/types/AudioStudio.web.d.ts.map +1 -0
  229. package/build/types/AudioStudioModule.d.ts +3 -0
  230. package/build/types/AudioStudioModule.d.ts.map +1 -0
  231. package/build/types/WebRecorder.web.d.ts +208 -0
  232. package/build/types/WebRecorder.web.d.ts.map +1 -0
  233. package/build/types/constants/platformLimitations.d.ts +40 -0
  234. package/build/types/constants/platformLimitations.d.ts.map +1 -0
  235. package/build/types/constants.d.ts +14 -0
  236. package/build/types/constants.d.ts.map +1 -0
  237. package/build/types/events.d.ts +29 -0
  238. package/build/types/events.d.ts.map +1 -0
  239. package/build/types/hooks/useAudioDevices.d.ts +15 -0
  240. package/build/types/hooks/useAudioDevices.d.ts.map +1 -0
  241. package/build/types/index.d.ts +21 -0
  242. package/build/types/index.d.ts.map +1 -0
  243. package/build/types/trimAudio.d.ts +25 -0
  244. package/build/types/trimAudio.d.ts.map +1 -0
  245. package/build/types/useAudioRecorder.d.ts +22 -0
  246. package/build/types/useAudioRecorder.d.ts.map +1 -0
  247. package/build/types/utils/BlobFix.d.ts +9 -0
  248. package/build/types/utils/BlobFix.d.ts.map +1 -0
  249. package/build/types/utils/audioProcessing.d.ts +24 -0
  250. package/build/types/utils/audioProcessing.d.ts.map +1 -0
  251. package/build/types/utils/cleanNativeOptions.d.ts +15 -0
  252. package/build/types/utils/cleanNativeOptions.d.ts.map +1 -0
  253. package/build/types/utils/concatenateBuffers.d.ts +8 -0
  254. package/build/types/utils/concatenateBuffers.d.ts.map +1 -0
  255. package/build/types/utils/convertPCMToFloat32.d.ts +13 -0
  256. package/build/types/utils/convertPCMToFloat32.d.ts.map +1 -0
  257. package/build/types/utils/crc32.d.ts +7 -0
  258. package/build/types/utils/crc32.d.ts.map +1 -0
  259. package/build/types/utils/encodingToBitDepth.d.ts +5 -0
  260. package/build/types/utils/encodingToBitDepth.d.ts.map +1 -0
  261. package/build/types/utils/getWavFileInfo.d.ts +26 -0
  262. package/build/types/utils/getWavFileInfo.d.ts.map +1 -0
  263. package/build/types/utils/writeWavHeader.d.ts +34 -0
  264. package/build/types/utils/writeWavHeader.d.ts.map +1 -0
  265. package/build/types/workers/InlineFeaturesExtractor.web.d.ts +2 -0
  266. package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
  267. package/build/types/workers/inlineAudioWebWorker.web.d.ts +2 -0
  268. package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
  269. package/build/types/workers/wasmGlueString.web.d.ts +2 -0
  270. package/build/types/workers/wasmGlueString.web.d.ts.map +1 -0
  271. package/cpp/AudioFeatures.cpp +274 -0
  272. package/cpp/AudioFeatures.h +85 -0
  273. package/cpp/AudioFeaturesBridge.cpp +146 -0
  274. package/cpp/AudioFeaturesBridge.h +47 -0
  275. package/cpp/MelSpectrogram.cpp +227 -0
  276. package/cpp/MelSpectrogram.h +82 -0
  277. package/cpp/MelSpectrogramBridge.cpp +112 -0
  278. package/cpp/MelSpectrogramBridge.h +33 -0
  279. package/cpp/kiss_fft/COPYING +11 -0
  280. package/cpp/kiss_fft/_kiss_fft_guts.h +167 -0
  281. package/cpp/kiss_fft/kiss_fft.c +424 -0
  282. package/cpp/kiss_fft/kiss_fft.h +160 -0
  283. package/cpp/kiss_fft/kiss_fft_log.h +36 -0
  284. package/cpp/kiss_fft/kiss_fftr.c +155 -0
  285. package/cpp/kiss_fft/kiss_fftr.h +54 -0
  286. package/expo-module.config.json +10 -0
  287. package/ios/AudioAnalysisData.swift +74 -0
  288. package/ios/AudioDeviceManager.swift +670 -0
  289. package/ios/AudioFeaturesWrapper.h +21 -0
  290. package/ios/AudioFeaturesWrapper.mm +63 -0
  291. package/ios/AudioNotificationManager.swift +154 -0
  292. package/ios/AudioProcessingHelpers.swift +797 -0
  293. package/ios/AudioProcessor.swift +1191 -0
  294. package/ios/AudioStreamError.swift +7 -0
  295. package/ios/AudioStreamManager.swift +2369 -0
  296. package/ios/AudioStreamManagerDelegate.swift +16 -0
  297. package/ios/AudioStudio.podspec +39 -0
  298. package/ios/AudioStudioModule.swift +1111 -0
  299. package/ios/AudioStudioTests/AudioFileHandlerTests.swift +338 -0
  300. package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +331 -0
  301. package/ios/AudioStudioTests/AudioTestHelpers.swift +130 -0
  302. package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +294 -0
  303. package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +105 -0
  304. package/ios/AudioStudioTests/Info.plist +22 -0
  305. package/ios/AudioStudioTests/README.md +39 -0
  306. package/ios/AudioStudioTests/SimpleAudioTest.swift +98 -0
  307. package/ios/AudioStudioTests/TestAudioGenerator.swift +75 -0
  308. package/ios/DataPoint.swift +54 -0
  309. package/ios/DecodingConfig.swift +59 -0
  310. package/ios/FFT.swift +62 -0
  311. package/ios/Features.swift +95 -0
  312. package/ios/ISSUE_IOS.md +68 -0
  313. package/ios/Logger.swift +39 -0
  314. package/ios/MelSpectrogramWrapper.h +30 -0
  315. package/ios/MelSpectrogramWrapper.mm +97 -0
  316. package/ios/NotificationExtension.swift +15 -0
  317. package/ios/RecordingResult.swift +22 -0
  318. package/ios/RecordingSettings.swift +311 -0
  319. package/ios/WaveformExtractor.swift +105 -0
  320. package/ios/tests/README.md +41 -0
  321. package/ios/tests/integration/buffer_and_fallback_test.swift +178 -0
  322. package/ios/tests/integration/buffer_duration_test.swift +185 -0
  323. package/ios/tests/integration/compressed_only_output_test.swift +271 -0
  324. package/ios/tests/integration/output_control_test.swift +322 -0
  325. package/ios/tests/integration/run_integration_tests.sh +37 -0
  326. package/ios/tests/opus_support_test_macos.swift +154 -0
  327. package/ios/tests/standalone/audio_processing_test.swift +144 -0
  328. package/ios/tests/standalone/audio_recording_test.swift +277 -0
  329. package/ios/tests/standalone/audio_streaming_test.swift +249 -0
  330. package/ios/tests/standalone/standalone_test.swift +144 -0
  331. package/package.json +146 -0
  332. package/plugin/build/index.cjs +194 -0
  333. package/plugin/build/index.d.cts +22 -0
  334. package/plugin/build/index.js +194 -0
  335. package/plugin/src/index.ts +285 -0
  336. package/plugin/tsconfig.json +10 -0
  337. package/plugin/tsconfig.tsbuildinfo +1 -0
  338. package/prebuilt/wasm/mel-spectrogram.js +18 -0
  339. package/src/AudioAnalysis/AudioAnalysis.types.ts +226 -0
  340. package/src/AudioAnalysis/audio-features-wasm.d.ts +37 -0
  341. package/src/AudioAnalysis/audioFeaturesWasm.ts +200 -0
  342. package/src/AudioAnalysis/extractAudioAnalysis.ts +350 -0
  343. package/src/AudioAnalysis/extractAudioData.ts +17 -0
  344. package/src/AudioAnalysis/extractMelSpectrogram.ts +140 -0
  345. package/src/AudioAnalysis/extractPreview.ts +34 -0
  346. package/src/AudioAnalysis/extractWaveform.ts +22 -0
  347. package/src/AudioAnalysis/mel-spectrogram-wasm.d.ts +48 -0
  348. package/src/AudioAnalysis/melSpectrogramWasm.ts +179 -0
  349. package/src/AudioDeviceManager.ts +800 -0
  350. package/src/AudioRecorder.provider.tsx +57 -0
  351. package/src/AudioStudio.native.ts +6 -0
  352. package/src/AudioStudio.types.ts +899 -0
  353. package/src/AudioStudio.web.ts +911 -0
  354. package/src/AudioStudioModule.ts +984 -0
  355. package/src/WebRecorder.web.ts +1114 -0
  356. package/src/constants/platformLimitations.ts +118 -0
  357. package/src/constants.ts +21 -0
  358. package/src/events.ts +63 -0
  359. package/src/hooks/useAudioDevices.ts +213 -0
  360. package/src/index.ts +67 -0
  361. package/src/trimAudio.ts +94 -0
  362. package/src/types/crc-32.d.ts +9 -0
  363. package/src/useAudioRecorder.tsx +784 -0
  364. package/src/utils/BlobFix.ts +561 -0
  365. package/src/utils/audioProcessing.ts +205 -0
  366. package/src/utils/cleanNativeOptions.ts +18 -0
  367. package/src/utils/concatenateBuffers.ts +24 -0
  368. package/src/utils/convertPCMToFloat32.ts +170 -0
  369. package/src/utils/crc32.ts +59 -0
  370. package/src/utils/encodingToBitDepth.ts +18 -0
  371. package/src/utils/getWavFileInfo.ts +132 -0
  372. package/src/utils/writeWavHeader.ts +115 -0
  373. package/src/workers/InlineFeaturesExtractor.web.tsx +291 -0
  374. package/src/workers/inlineAudioWebWorker.web.tsx +186 -0
  375. package/src/workers/wasmGlueString.web.ts +23 -0
@@ -0,0 +1,249 @@
1
+ package net.siteed.audiostudio
2
+
3
+ import org.junit.Test
4
+ import org.junit.Assert.*
5
+
6
+ /**
7
+ * Unit tests for audio focus strategy configuration and logic.
8
+ * These tests verify that the RecordingConfig correctly handles audioFocusStrategy
9
+ * parameter and that the smart defaults work as expected.
10
+ */
11
+ class AudioFocusStrategyTest {
12
+
13
+ @Test
14
+ fun testRecordingConfigWithExplicitBackgroundStrategy() {
15
+ val options = mapOf(
16
+ "sampleRate" to 44100,
17
+ "channels" to 1,
18
+ "encoding" to "pcm_16bit",
19
+ "android" to mapOf(
20
+ "audioFocusStrategy" to "background"
21
+ )
22
+ )
23
+
24
+ val result = RecordingConfig.fromMap(options)
25
+ assertTrue("Config creation should succeed", result.isSuccess)
26
+
27
+ val (config, _) = result.getOrThrow()
28
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
29
+ }
30
+
31
+ @Test
32
+ fun testRecordingConfigWithExplicitInteractiveStrategy() {
33
+ val options = mapOf(
34
+ "sampleRate" to 44100,
35
+ "channels" to 1,
36
+ "encoding" to "pcm_16bit",
37
+ "android" to mapOf(
38
+ "audioFocusStrategy" to "interactive"
39
+ )
40
+ )
41
+
42
+ val result = RecordingConfig.fromMap(options)
43
+ assertTrue("Config creation should succeed", result.isSuccess)
44
+
45
+ val (config, _) = result.getOrThrow()
46
+ assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
47
+ }
48
+
49
+ @Test
50
+ fun testRecordingConfigWithExplicitCommunicationStrategy() {
51
+ val options = mapOf(
52
+ "sampleRate" to 44100,
53
+ "channels" to 1,
54
+ "encoding" to "pcm_16bit",
55
+ "android" to mapOf(
56
+ "audioFocusStrategy" to "communication"
57
+ )
58
+ )
59
+
60
+ val result = RecordingConfig.fromMap(options)
61
+ assertTrue("Config creation should succeed", result.isSuccess)
62
+
63
+ val (config, _) = result.getOrThrow()
64
+ assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
65
+ }
66
+
67
+ @Test
68
+ fun testRecordingConfigWithExplicitNoneStrategy() {
69
+ val options = mapOf(
70
+ "sampleRate" to 44100,
71
+ "channels" to 1,
72
+ "encoding" to "pcm_16bit",
73
+ "android" to mapOf(
74
+ "audioFocusStrategy" to "none"
75
+ )
76
+ )
77
+
78
+ val result = RecordingConfig.fromMap(options)
79
+ assertTrue("Config creation should succeed", result.isSuccess)
80
+
81
+ val (config, _) = result.getOrThrow()
82
+ assertEquals("Audio focus strategy should be none", "none", config.audioFocusStrategy)
83
+ }
84
+
85
+ @Test
86
+ fun testRecordingConfigWithoutAudioFocusStrategy() {
87
+ val options = mapOf(
88
+ "sampleRate" to 44100,
89
+ "channels" to 1,
90
+ "encoding" to "pcm_16bit"
91
+ )
92
+
93
+ val result = RecordingConfig.fromMap(options)
94
+ assertTrue("Config creation should succeed", result.isSuccess)
95
+
96
+ val (config, _) = result.getOrThrow()
97
+ assertNull("Audio focus strategy should be null when not specified", config.audioFocusStrategy)
98
+ }
99
+
100
+ @Test
101
+ fun testRecordingConfigWithInvalidAudioFocusStrategy() {
102
+ val options = mapOf(
103
+ "sampleRate" to 44100,
104
+ "channels" to 1,
105
+ "encoding" to "pcm_16bit",
106
+ "android" to mapOf(
107
+ "audioFocusStrategy" to "invalid_strategy"
108
+ )
109
+ )
110
+
111
+ val result = RecordingConfig.fromMap(options)
112
+ assertTrue("Config creation should succeed even with invalid strategy", result.isSuccess)
113
+
114
+ val (config, _) = result.getOrThrow()
115
+ assertEquals("Invalid audio focus strategy should be preserved", "invalid_strategy", config.audioFocusStrategy)
116
+ }
117
+
118
+ @Test
119
+ fun testRecordingConfigWithNullAudioFocusStrategy() {
120
+ val options = mapOf(
121
+ "sampleRate" to 44100,
122
+ "channels" to 1,
123
+ "encoding" to "pcm_16bit",
124
+ "android" to mapOf(
125
+ "audioFocusStrategy" to null
126
+ )
127
+ )
128
+
129
+ val result = RecordingConfig.fromMap(options)
130
+ assertTrue("Config creation should succeed", result.isSuccess)
131
+
132
+ val (config, _) = result.getOrThrow()
133
+ assertNull("Audio focus strategy should be null", config.audioFocusStrategy)
134
+ }
135
+
136
+ @Test
137
+ fun testRecordingConfigKeepAwakeAndBackgroundStrategy() {
138
+ val options = mapOf(
139
+ "sampleRate" to 44100,
140
+ "channels" to 1,
141
+ "encoding" to "pcm_16bit",
142
+ "keepAwake" to true,
143
+ "android" to mapOf(
144
+ "audioFocusStrategy" to "background"
145
+ )
146
+ )
147
+
148
+ val result = RecordingConfig.fromMap(options)
149
+ assertTrue("Config creation should succeed", result.isSuccess)
150
+
151
+ val (config, _) = result.getOrThrow()
152
+ assertTrue("keepAwake should be true", config.keepAwake)
153
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
154
+ }
155
+
156
+ @Test
157
+ fun testRecordingConfigKeepAwakeFalseAndInteractiveStrategy() {
158
+ val options = mapOf(
159
+ "sampleRate" to 44100,
160
+ "channels" to 1,
161
+ "encoding" to "pcm_16bit",
162
+ "keepAwake" to false,
163
+ "android" to mapOf(
164
+ "audioFocusStrategy" to "interactive"
165
+ )
166
+ )
167
+
168
+ val result = RecordingConfig.fromMap(options)
169
+ assertTrue("Config creation should succeed", result.isSuccess)
170
+
171
+ val (config, _) = result.getOrThrow()
172
+ assertFalse("keepAwake should be false", config.keepAwake)
173
+ assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
174
+ }
175
+
176
+ @Test
177
+ fun testRecordingConfigWithAutoResumeAndBackgroundStrategy() {
178
+ val options = mapOf(
179
+ "sampleRate" to 44100,
180
+ "channels" to 1,
181
+ "encoding" to "pcm_16bit",
182
+ "autoResumeAfterInterruption" to true,
183
+ "android" to mapOf(
184
+ "audioFocusStrategy" to "background"
185
+ )
186
+ )
187
+
188
+ val result = RecordingConfig.fromMap(options)
189
+ assertTrue("Config creation should succeed", result.isSuccess)
190
+
191
+ val (config, _) = result.getOrThrow()
192
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
193
+ assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
194
+ }
195
+
196
+ @Test
197
+ fun testRecordingConfigWithCommunicationStrategyAndSpeechSampleRate() {
198
+ val options = mapOf(
199
+ "sampleRate" to 16000, // Common speech sample rate
200
+ "channels" to 1,
201
+ "encoding" to "pcm_16bit",
202
+ "android" to mapOf(
203
+ "audioFocusStrategy" to "communication"
204
+ )
205
+ )
206
+
207
+ val result = RecordingConfig.fromMap(options)
208
+ assertTrue("Config creation should succeed", result.isSuccess)
209
+
210
+ val (config, _) = result.getOrThrow()
211
+ assertEquals("Sample rate should be 16000", 16000, config.sampleRate)
212
+ assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
213
+ }
214
+
215
+ @Test
216
+ fun testDefaultRecordingConfigValues() {
217
+ val result = RecordingConfig.fromMap(null)
218
+ assertTrue("Config creation should succeed with null input", result.isSuccess)
219
+
220
+ val (config, _) = result.getOrThrow()
221
+ assertNull("Default audio focus strategy should be null", config.audioFocusStrategy)
222
+ assertTrue("Default keepAwake should be true", config.keepAwake)
223
+ assertFalse("Default autoResumeAfterInterruption should be false", config.autoResumeAfterInterruption)
224
+ }
225
+
226
+ @Test
227
+ fun testRecordingConfigCompleteAudioFocusConfiguration() {
228
+ val options = mapOf(
229
+ "sampleRate" to 44100,
230
+ "channels" to 1,
231
+ "encoding" to "pcm_16bit",
232
+ "keepAwake" to true,
233
+ "autoResumeAfterInterruption" to true,
234
+ "showNotification" to true,
235
+ "android" to mapOf(
236
+ "audioFocusStrategy" to "background"
237
+ )
238
+ )
239
+
240
+ val result = RecordingConfig.fromMap(options)
241
+ assertTrue("Config creation should succeed", result.isSuccess)
242
+
243
+ val (config, _) = result.getOrThrow()
244
+ assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
245
+ assertTrue("keepAwake should be true", config.keepAwake)
246
+ assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
247
+ assertTrue("showNotification should be true", config.showNotification)
248
+ }
249
+ }
@@ -0,0 +1,151 @@
1
+ package net.siteed.audiostudio
2
+
3
+ import org.junit.Test
4
+ import org.junit.Assert.*
5
+ import org.junit.Before
6
+ import java.io.File
7
+
8
+ class AudioFormatTest {
9
+ private lateinit var tempDir: File
10
+
11
+ @Before
12
+ fun setUp() {
13
+ tempDir = File(System.getProperty("java.io.tmpdir"), "audio_format_test_${System.currentTimeMillis()}")
14
+ tempDir.mkdirs()
15
+ }
16
+
17
+ @Test
18
+ fun testGetFileExtension_aacDefaultsToM4a() {
19
+ // Given
20
+ val config = RecordingConfig(
21
+ output = OutputConfig(
22
+ compressed = OutputConfig.CompressedOutput(
23
+ enabled = true,
24
+ format = "aac",
25
+ preferRawStream = false
26
+ )
27
+ )
28
+ )
29
+
30
+ // When
31
+ val file = createTestFile(config, isCompressed = true)
32
+
33
+ // Then
34
+ assertTrue("AAC without preferRawStream should produce .m4a", file.name.endsWith(".m4a"))
35
+ }
36
+
37
+ @Test
38
+ fun testGetFileExtension_aacWithPreferRawStreamProducesAac() {
39
+ // Given
40
+ val config = RecordingConfig(
41
+ output = OutputConfig(
42
+ compressed = OutputConfig.CompressedOutput(
43
+ enabled = true,
44
+ format = "aac",
45
+ preferRawStream = true
46
+ )
47
+ )
48
+ )
49
+
50
+ // When
51
+ val file = createTestFile(config, isCompressed = true)
52
+
53
+ // Then
54
+ assertTrue("AAC with preferRawStream should produce .aac", file.name.endsWith(".aac"))
55
+ }
56
+
57
+ @Test
58
+ fun testGetFileExtension_opusProducesOpus() {
59
+ // Given
60
+ val config = RecordingConfig(
61
+ output = OutputConfig(
62
+ compressed = OutputConfig.CompressedOutput(
63
+ enabled = true,
64
+ format = "opus"
65
+ )
66
+ )
67
+ )
68
+
69
+ // When
70
+ val file = createTestFile(config, isCompressed = true)
71
+
72
+ // Then
73
+ assertTrue("Opus should produce .opus", file.name.endsWith(".opus"))
74
+ }
75
+
76
+ @Test
77
+ fun testGetFileExtension_wavForUncompressed() {
78
+ // Given
79
+ val config = RecordingConfig()
80
+
81
+ // When
82
+ val file = createTestFile(config, isCompressed = false)
83
+
84
+ // Then
85
+ assertTrue("Uncompressed should produce .wav", file.name.endsWith(".wav"))
86
+ }
87
+
88
+ @Test
89
+ fun testCompressedOutput_parseFromMap() {
90
+ // Given
91
+ val map = mapOf(
92
+ "enabled" to true,
93
+ "format" to "aac",
94
+ "bitrate" to 192000,
95
+ "preferRawStream" to true
96
+ )
97
+
98
+ // When
99
+ val compressed = OutputConfig.CompressedOutput(
100
+ enabled = map["enabled"] as Boolean,
101
+ format = map["format"] as String,
102
+ bitrate = map["bitrate"] as Int,
103
+ preferRawStream = map["preferRawStream"] as Boolean
104
+ )
105
+
106
+ // Then
107
+ assertTrue("enabled should be true", compressed.enabled)
108
+ assertEquals("format should be aac", "aac", compressed.format)
109
+ assertEquals("bitrate should be 192000", 192000, compressed.bitrate)
110
+ assertTrue("preferRawStream should be true", compressed.preferRawStream)
111
+ }
112
+
113
+ @Test
114
+ fun testCompressedOutput_defaultValues() {
115
+ // When
116
+ val compressed = OutputConfig.CompressedOutput()
117
+
118
+ // Then
119
+ assertFalse("enabled should default to false", compressed.enabled)
120
+ assertEquals("format should default to aac", "aac", compressed.format)
121
+ assertEquals("bitrate should default to 128000", 128000, compressed.bitrate)
122
+ assertFalse("preferRawStream should default to false", compressed.preferRawStream)
123
+ }
124
+
125
+ /**
126
+ * Helper function to simulate file creation logic from AudioRecorderManager
127
+ */
128
+ private fun createTestFile(config: RecordingConfig, isCompressed: Boolean): File {
129
+ val baseFilename = config.filename?.let {
130
+ it.substringBeforeLast('.', it)
131
+ } ?: "test_recording"
132
+
133
+ val extension = if (isCompressed) {
134
+ when (config.output.compressed.format.lowercase()) {
135
+ "aac" -> {
136
+ if (config.output.compressed.preferRawStream) {
137
+ "aac" // Raw AAC stream
138
+ } else {
139
+ "m4a" // M4A container (new default)
140
+ }
141
+ }
142
+ "opus" -> "opus" // Opus in OGG container
143
+ else -> config.output.compressed.format.lowercase()
144
+ }
145
+ } else {
146
+ "wav"
147
+ }
148
+
149
+ return File(tempDir, "$baseFilename.$extension")
150
+ }
151
+ }
@@ -0,0 +1,273 @@
1
+ package net.siteed.audiostudio
2
+
3
+ import org.junit.Test
4
+ import org.junit.Assert.*
5
+ import java.nio.ByteBuffer
6
+ import java.nio.ByteOrder
7
+ import kotlin.math.abs
8
+
9
+ class AudioFormatUtilsTest {
10
+
11
+ @Test
12
+ fun testConvertBitDepth_8to16() {
13
+ // Given - 8-bit PCM data (unsigned, centered at 128)
14
+ val input8bit = byteArrayOf(0, 64, 128.toByte(), 192.toByte(), 255.toByte())
15
+
16
+ // When
17
+ val output16bit = AudioFormatUtils.convertBitDepth(input8bit, 8, 16)
18
+
19
+ // Then
20
+ val buffer = ByteBuffer.wrap(output16bit).order(ByteOrder.LITTLE_ENDIAN)
21
+ val samples = ShortArray(output16bit.size / 2)
22
+ buffer.asShortBuffer().get(samples)
23
+
24
+ // Verify conversion (8-bit 128 = silence = 16-bit 0)
25
+ assertEquals("First sample should be -32768", -32768, samples[0].toInt())
26
+ assertEquals("Middle sample (128) should be 0", 0, samples[2].toInt())
27
+ assertEquals("Last sample should be 32767", 32767, samples[4].toInt())
28
+ }
29
+
30
+ @Test
31
+ fun testConvertBitDepth_16to8() {
32
+ // Given - 16-bit PCM data
33
+ val buffer16 = ByteBuffer.allocate(10).order(ByteOrder.LITTLE_ENDIAN)
34
+ buffer16.putShort(-32768) // Min value
35
+ buffer16.putShort(-16384) // -0.5
36
+ buffer16.putShort(0) // Silence
37
+ buffer16.putShort(16384) // 0.5
38
+ buffer16.putShort(32767) // Max value
39
+
40
+ // When
41
+ val output8bit = AudioFormatUtils.convertBitDepth(buffer16.array(), 16, 8)
42
+
43
+ // Then
44
+ assertEquals("Should have 5 samples", 5, output8bit.size)
45
+ assertEquals("Min should convert to 0", 0, output8bit[0].toInt() and 0xFF)
46
+ assertEquals("Silence should convert to 128", 128, output8bit[2].toInt() and 0xFF)
47
+ assertEquals("Max should convert to 255", 255, output8bit[4].toInt() and 0xFF)
48
+ }
49
+
50
+ @Test
51
+ fun testConvertBitDepth_16to32() {
52
+ // Given - 16-bit PCM data
53
+ val buffer16 = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN)
54
+ buffer16.putShort(-32768) // Min
55
+ buffer16.putShort(0) // Silence
56
+ buffer16.putShort(32767) // Max
57
+
58
+ // When
59
+ val output32bit = AudioFormatUtils.convertBitDepth(buffer16.array(), 16, 32)
60
+
61
+ // Then
62
+ val buffer32 = ByteBuffer.wrap(output32bit).order(ByteOrder.LITTLE_ENDIAN)
63
+ assertEquals("Should have 3 32-bit samples", 12, output32bit.size)
64
+
65
+ // Check values (scaled appropriately)
66
+ val sample1 = buffer32.getInt()
67
+ val sample2 = buffer32.getInt()
68
+ val sample3 = buffer32.getInt()
69
+
70
+ assertTrue("Min value should be negative", sample1 < 0)
71
+ assertEquals("Silence should be 0", 0, sample2)
72
+ assertTrue("Max value should be positive", sample3 > 0)
73
+ }
74
+
75
+ @Test
76
+ fun testConvertBitDepth_32to16() {
77
+ // Given - 32-bit PCM data
78
+ val buffer32 = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN)
79
+ buffer32.putInt(Int.MIN_VALUE) // Min
80
+ buffer32.putInt(0) // Silence
81
+ buffer32.putInt(Int.MAX_VALUE) // Max
82
+
83
+ // When
84
+ val output16bit = AudioFormatUtils.convertBitDepth(buffer32.array(), 32, 16)
85
+
86
+ // Then
87
+ val buffer16 = ByteBuffer.wrap(output16bit).order(ByteOrder.LITTLE_ENDIAN)
88
+ assertEquals("Should have 3 16-bit samples", 6, output16bit.size)
89
+
90
+ assertEquals("Min should convert to -32768", -32768, buffer16.getShort().toInt())
91
+ assertEquals("Silence should be 0", 0, buffer16.getShort().toInt())
92
+ assertEquals("Max should convert to 32767", 32767, buffer16.getShort().toInt())
93
+ }
94
+
95
+ @Test
96
+ fun testConvertBitDepth_sameDepth() {
97
+ // Given
98
+ val input = byteArrayOf(1, 2, 3, 4, 5, 6)
99
+
100
+ // When - Convert 16 to 16 (no-op)
101
+ val output = AudioFormatUtils.convertBitDepth(input, 16, 16)
102
+
103
+ // Then
104
+ assertArrayEquals("Should return same data", input, output)
105
+ }
106
+
107
+ @Test
108
+ fun testConvertBitDepth_emptyData() {
109
+ // Given
110
+ val emptyData = byteArrayOf()
111
+
112
+ // When
113
+ val output = AudioFormatUtils.convertBitDepth(emptyData, 16, 32)
114
+
115
+ // Then
116
+ assertEquals("Should return empty array", 0, output.size)
117
+ }
118
+
119
+ @Test
120
+ fun testConvertChannels_monoToStereo() {
121
+ // Given - Mono 16-bit data
122
+ val monoData = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).apply {
123
+ putShort(1000)
124
+ putShort(2000)
125
+ putShort(3000)
126
+ }.array()
127
+
128
+ // When
129
+ val stereoData = AudioFormatUtils.convertChannels(monoData, 1, 2, 16)
130
+
131
+ // Then
132
+ val buffer = ByteBuffer.wrap(stereoData).order(ByteOrder.LITTLE_ENDIAN)
133
+ assertEquals("Should have 6 samples (3 stereo pairs)", 12, stereoData.size)
134
+
135
+ // Each mono sample should be duplicated to both channels
136
+ assertEquals("L1", 1000, buffer.getShort().toInt())
137
+ assertEquals("R1", 1000, buffer.getShort().toInt())
138
+ assertEquals("L2", 2000, buffer.getShort().toInt())
139
+ assertEquals("R2", 2000, buffer.getShort().toInt())
140
+ assertEquals("L3", 3000, buffer.getShort().toInt())
141
+ assertEquals("R3", 3000, buffer.getShort().toInt())
142
+ }
143
+
144
+ @Test
145
+ fun testConvertChannels_stereoToMono() {
146
+ // Given - Stereo 16-bit data
147
+ val stereoData = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).apply {
148
+ putShort(1000) // L1
149
+ putShort(2000) // R1
150
+ putShort(3000) // L2
151
+ putShort(4000) // R2
152
+ }.array()
153
+
154
+ // When
155
+ val monoData = AudioFormatUtils.convertChannels(stereoData, 2, 1, 16)
156
+
157
+ // Then
158
+ val buffer = ByteBuffer.wrap(monoData).order(ByteOrder.LITTLE_ENDIAN)
159
+ assertEquals("Should have 2 mono samples", 4, monoData.size)
160
+
161
+ // Each mono sample should be average of L+R
162
+ assertEquals("Sample 1", 1500, buffer.getShort().toInt()) // (1000+2000)/2
163
+ assertEquals("Sample 2", 3500, buffer.getShort().toInt()) // (3000+4000)/2
164
+ }
165
+
166
+ @Test
167
+ fun testNormalizeAudio_quietSignal() {
168
+ // Given - Quiet 16-bit signal
169
+ val quietData = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).apply {
170
+ putShort(100)
171
+ putShort(-100)
172
+ putShort(50)
173
+ }.array()
174
+
175
+ // When
176
+ val normalized = AudioFormatUtils.normalizeAudio(quietData, 16)
177
+
178
+ // Then
179
+ val buffer = ByteBuffer.wrap(normalized).order(ByteOrder.LITTLE_ENDIAN)
180
+ val maxSample = abs(buffer.getShort().toInt())
181
+ buffer.rewind()
182
+
183
+ // The loudest sample should be close to max value
184
+ assertTrue("Should be normalized to near max", maxSample > 30000)
185
+ }
186
+
187
+ @Test
188
+ fun testNormalizeAudio_alreadyLoud() {
189
+ // Given - Already loud signal
190
+ val loudData = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).apply {
191
+ putShort(32000)
192
+ putShort(-32000)
193
+ }.array()
194
+
195
+ // When
196
+ val normalized = AudioFormatUtils.normalizeAudio(loudData, 16)
197
+
198
+ // Then
199
+ val buffer = ByteBuffer.wrap(normalized).order(ByteOrder.LITTLE_ENDIAN)
200
+ val sample1 = abs(buffer.getShort().toInt())
201
+ val sample2 = abs(buffer.getShort().toInt())
202
+
203
+ // Should be normalized but not clipped
204
+ assertTrue("Samples should be near max", sample1 > 32000 && sample2 > 32000)
205
+ assertTrue("Samples should not exceed max", sample1 <= 32767 && sample2 <= 32767)
206
+ }
207
+
208
+ @Test
209
+ fun testNormalizeAudio_silentSignal() {
210
+ // Given - Silent signal
211
+ val silentData = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).apply {
212
+ putShort(0)
213
+ putShort(0)
214
+ putShort(0)
215
+ }.array()
216
+
217
+ // When
218
+ val normalized = AudioFormatUtils.normalizeAudio(silentData, 16)
219
+
220
+ // Then
221
+ val buffer = ByteBuffer.wrap(normalized).order(ByteOrder.LITTLE_ENDIAN)
222
+ assertEquals("Silent should remain silent", 0, buffer.getShort().toInt())
223
+ assertEquals("Silent should remain silent", 0, buffer.getShort().toInt())
224
+ assertEquals("Silent should remain silent", 0, buffer.getShort().toInt())
225
+ }
226
+
227
+ @Test
228
+ fun testResampleAudio_upsample() {
229
+ // Given - 8kHz mono audio
230
+ val samples8k = floatArrayOf(0.0f, 0.5f, 1.0f, 0.5f, 0.0f, -0.5f, -1.0f, -0.5f)
231
+
232
+ // When - Upsample to 16kHz
233
+ val samples16k = AudioFormatUtils.resampleAudio(samples8k, 8000, 16000)
234
+
235
+ // Then
236
+ assertEquals("Should have approximately double samples", 16, samples16k.size)
237
+ // First and last samples should match
238
+ assertEquals("First sample", samples8k[0], samples16k[0], 0.01f)
239
+ assertEquals("Last sample", samples8k.last(), samples16k.last(), 0.01f)
240
+ }
241
+
242
+ @Test
243
+ fun testResampleAudio_downsample() {
244
+ // Given - 16kHz mono audio
245
+ val samples16k = floatArrayOf(
246
+ 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 0.75f, 0.5f, 0.25f,
247
+ 0.0f, -0.25f, -0.5f, -0.75f, -1.0f, -0.75f, -0.5f, -0.25f
248
+ )
249
+
250
+ // When - Downsample to 8kHz
251
+ val samples8k = AudioFormatUtils.resampleAudio(samples16k, 16000, 8000)
252
+
253
+ // Then
254
+ assertEquals("Should have approximately half samples", 8, samples8k.size)
255
+ // Check general shape is preserved
256
+ val maxValue = samples8k.maxOrNull() ?: 0f
257
+ val minValue = samples8k.minOrNull() ?: 0f
258
+ assertTrue("Peak should be preserved", maxValue > 0.9f)
259
+ assertTrue("Trough should be preserved", minValue < -0.9f)
260
+ }
261
+
262
+ @Test
263
+ fun testResampleAudio_sameRate() {
264
+ // Given
265
+ val samples = floatArrayOf(0.1f, 0.2f, 0.3f, 0.4f, 0.5f)
266
+
267
+ // When - Same sample rate
268
+ val resampled = AudioFormatUtils.resampleAudio(samples, 44100, 44100)
269
+
270
+ // Then
271
+ assertArrayEquals("Should return same samples", samples, resampled, 0.001f)
272
+ }
273
+ }