@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,541 @@
1
+ package net.siteed.audiostudio
2
+
3
+ import android.Manifest
4
+ import android.content.Context
5
+ import android.media.AudioFormat
6
+ import android.media.AudioManager
7
+ import android.media.AudioTrack
8
+ import android.os.Bundle
9
+ import androidx.test.ext.junit.runners.AndroidJUnit4
10
+ import androidx.test.platform.app.InstrumentationRegistry
11
+ import androidx.test.rule.GrantPermissionRule
12
+ import expo.modules.kotlin.Promise
13
+ import org.junit.After
14
+ import org.junit.Assert.*
15
+ import org.junit.Before
16
+ import org.junit.Rule
17
+ import org.junit.Test
18
+ import org.junit.runner.RunWith
19
+ import java.io.File
20
+ import java.util.concurrent.CountDownLatch
21
+ import java.util.concurrent.TimeUnit
22
+ import kotlin.math.sin
23
+
24
+ /**
25
+ * Instrumented tests for AudioRecorderManager that test actual recording functionality.
26
+ * These tests run on an Android device/emulator and require microphone permissions.
27
+ */
28
+ @RunWith(AndroidJUnit4::class)
29
+ class AudioRecorderInstrumentedTest {
30
+
31
+ @get:Rule
32
+ val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
33
+ Manifest.permission.RECORD_AUDIO
34
+ )
35
+
36
+ private lateinit var context: Context
37
+ private lateinit var filesDir: File
38
+ private lateinit var audioRecorderManager: AudioRecorderManager
39
+ private lateinit var testEventSender: TestEventSender
40
+ private lateinit var permissionUtils: PermissionUtils
41
+ private lateinit var audioDataEncoder: AudioDataEncoder
42
+
43
+ // Test event sender to capture events
44
+ private class TestEventSender : EventSender {
45
+ val events = mutableListOf<Pair<String, Bundle>>()
46
+
47
+ override fun sendExpoEvent(eventName: String, params: Bundle) {
48
+ events.add(eventName to params)
49
+ }
50
+
51
+ fun clearEvents() {
52
+ events.clear()
53
+ }
54
+
55
+ fun getEventsOfType(eventName: String): List<Bundle> {
56
+ return events.filter { it.first == eventName }.map { it.second }
57
+ }
58
+ }
59
+
60
+ @Before
61
+ fun setUp() {
62
+ context = InstrumentationRegistry.getInstrumentation().targetContext
63
+ filesDir = context.filesDir
64
+ testEventSender = TestEventSender()
65
+ permissionUtils = PermissionUtils(context)
66
+ audioDataEncoder = AudioDataEncoder()
67
+
68
+ // Initialize AudioRecorderManager
69
+ audioRecorderManager = AudioRecorderManager.initialize(
70
+ context = context,
71
+ filesDir = filesDir,
72
+ permissionUtils = permissionUtils,
73
+ audioDataEncoder = audioDataEncoder,
74
+ eventSender = testEventSender,
75
+ enablePhoneStateHandling = false, // Disable for tests
76
+ enableBackgroundAudio = false // Disable for tests
77
+ )
78
+
79
+ // Clean up any existing audio files
80
+ cleanupAudioFiles()
81
+ }
82
+
83
+ @After
84
+ fun tearDown() {
85
+ // Stop any ongoing recording
86
+ if (audioRecorderManager.isRecording) {
87
+ stopRecordingSync()
88
+ }
89
+
90
+ // Clean up
91
+ AudioRecorderManager.destroy()
92
+ cleanupAudioFiles()
93
+ }
94
+
95
+ private fun cleanupAudioFiles() {
96
+ filesDir.listFiles()?.forEach { file ->
97
+ if (file.name.endsWith(".wav") || file.name.endsWith(".aac") || file.name.endsWith(".opus")) {
98
+ file.delete()
99
+ }
100
+ }
101
+ }
102
+
103
+ // ========== Basic Recording Tests ==========
104
+
105
+ @Test
106
+ fun testBasicRecording_createsWavFile() {
107
+ // Given
108
+ val recordingOptions = mapOf(
109
+ "sampleRate" to 16000,
110
+ "channels" to 1,
111
+ "encoding" to "pcm_16bit",
112
+ "interval" to 100,
113
+ "enableProcessing" to false,
114
+ "showNotification" to false
115
+ )
116
+
117
+ // When - Start recording
118
+ val startLatch = CountDownLatch(1)
119
+ var recordingResult: Map<String, Any>? = null
120
+
121
+ audioRecorderManager.startRecording(recordingOptions, object : Promise {
122
+ override fun resolve(value: Any?) {
123
+ when (value) {
124
+ is Bundle -> recordingResult = bundleToMap(value)
125
+ is Map<*, *> -> {
126
+ @Suppress("UNCHECKED_CAST")
127
+ recordingResult = value as? Map<String, Any>
128
+ }
129
+ else -> {
130
+ fail("Unexpected start result type: ${value?.javaClass?.name}")
131
+ }
132
+ }
133
+ startLatch.countDown()
134
+ }
135
+
136
+ override fun reject(code: String, message: String?, cause: Throwable?) {
137
+ fail("Recording start failed: $code - $message")
138
+ }
139
+ })
140
+
141
+ assertTrue("Recording should start within 2 seconds", startLatch.await(2, TimeUnit.SECONDS))
142
+ assertNotNull("Recording result should not be null", recordingResult)
143
+
144
+ // Record for 2 seconds
145
+ Thread.sleep(2000)
146
+
147
+ // Verify we received audio data events
148
+ val audioEvents = testEventSender.getEventsOfType(Constants.AUDIO_EVENT_NAME)
149
+ assertTrue("Should have received audio data events", audioEvents.isNotEmpty())
150
+
151
+ // Stop recording
152
+ val stopResult = stopRecordingSync()
153
+ assertNotNull("Stop result should not be null", stopResult)
154
+
155
+ // Verify the file was created
156
+ val fileUri = stopResult["fileUri"] as? String
157
+ assertNotNull("File URI should not be null", fileUri)
158
+
159
+ // Convert URI string to File - handle both file:// URIs and plain paths
160
+ val audioFile = when {
161
+ fileUri!!.startsWith("file://") -> File(java.net.URI(fileUri))
162
+ fileUri.startsWith("file:") -> File(java.net.URI(fileUri))
163
+ else -> File(fileUri)
164
+ }
165
+
166
+ assertTrue("Audio file should exist at ${audioFile.absolutePath}", audioFile.exists())
167
+ assertTrue("Audio file should have content", audioFile.length() > 44) // WAV header is 44 bytes
168
+
169
+ // Verify file is a valid WAV
170
+ val wavHeader = audioFile.inputStream().use { it.readNBytes(44) }
171
+ assertEquals("Should have RIFF header", "RIFF", String(wavHeader.sliceArray(0..3)))
172
+ assertEquals("Should have WAVE format", "WAVE", String(wavHeader.sliceArray(8..11)))
173
+ }
174
+
175
+ @Test
176
+ fun testRecordingWithAnalysis_generatesFeatures() {
177
+ // Given
178
+ val recordingOptions = mapOf(
179
+ "sampleRate" to 16000,
180
+ "channels" to 1,
181
+ "encoding" to "pcm_16bit",
182
+ "interval" to 100,
183
+ "intervalAnalysis" to 500,
184
+ "enableProcessing" to true,
185
+ "showNotification" to false,
186
+ "features" to mapOf(
187
+ "rms" to true,
188
+ "zcr" to true,
189
+ "energy" to true
190
+ )
191
+ )
192
+
193
+ // When - Start recording
194
+ startRecordingSync(recordingOptions)
195
+
196
+ // Record for 2 seconds to ensure we get analysis events
197
+ Thread.sleep(2000)
198
+
199
+ // Then - Verify analysis events
200
+ val analysisEvents = testEventSender.getEventsOfType(Constants.AUDIO_ANALYSIS_EVENT_NAME)
201
+ assertTrue("Should have received analysis events", analysisEvents.isNotEmpty())
202
+
203
+ val firstAnalysis = analysisEvents.first()
204
+ // Analysis data contains dataPoints array
205
+ assertTrue("Should have dataPoints", firstAnalysis.containsKey("dataPoints"))
206
+ val dataPoints = firstAnalysis.get("dataPoints") as? Array<*>
207
+ assertNotNull("DataPoints should not be null", dataPoints)
208
+ assertTrue("Should have at least one data point", dataPoints!!.isNotEmpty())
209
+
210
+ // Check the first data point
211
+ val firstDataPoint = dataPoints[0] as? Bundle
212
+ assertNotNull("First data point should be a Bundle", firstDataPoint)
213
+ assertTrue("Data point should have rms", firstDataPoint!!.containsKey("rms"))
214
+ assertTrue("Data point should have features", firstDataPoint.containsKey("features"))
215
+
216
+ // Check features in the data point
217
+ val features = firstDataPoint.get("features") as? Bundle
218
+ assertNotNull("Features should not be null", features)
219
+ assertTrue("Features should have rms", features!!.containsKey("rms"))
220
+ assertTrue("Features should have zcr", features.containsKey("zcr"))
221
+ assertTrue("Features should have energy", features.containsKey("energy"))
222
+
223
+ // Stop recording
224
+ stopRecordingSync()
225
+ }
226
+
227
+ @Test
228
+ fun testPauseResumeRecording() {
229
+ // Given
230
+ val recordingOptions = mapOf(
231
+ "sampleRate" to 16000,
232
+ "channels" to 1,
233
+ "encoding" to "pcm_16bit",
234
+ "interval" to 100,
235
+ "showNotification" to false
236
+ )
237
+
238
+ // Start recording
239
+ startRecordingSync(recordingOptions)
240
+ Thread.sleep(1000)
241
+
242
+ // Clear events before pause
243
+ testEventSender.clearEvents()
244
+
245
+ // When - Pause recording
246
+ val pauseLatch = CountDownLatch(1)
247
+ audioRecorderManager.pauseRecording(object : Promise {
248
+ override fun resolve(value: Any?) {
249
+ pauseLatch.countDown()
250
+ }
251
+
252
+ override fun reject(code: String, message: String?, cause: Throwable?) {
253
+ fail("Pause failed: $code - $message")
254
+ }
255
+ })
256
+
257
+ assertTrue("Pause should complete within 1 second", pauseLatch.await(1, TimeUnit.SECONDS))
258
+
259
+ // Give some time for any pending events to be processed
260
+ Thread.sleep(200)
261
+
262
+ // Clear events after pause to ensure we only check new events
263
+ testEventSender.clearEvents()
264
+
265
+ // Wait to verify no new audio events during pause
266
+ Thread.sleep(500)
267
+ val eventsDuringPause = testEventSender.getEventsOfType(Constants.AUDIO_EVENT_NAME)
268
+ assertTrue("Should not receive audio events while paused", eventsDuringPause.isEmpty())
269
+
270
+ // Resume recording
271
+ val resumeLatch = CountDownLatch(1)
272
+ audioRecorderManager.resumeRecording(object : Promise {
273
+ override fun resolve(value: Any?) {
274
+ resumeLatch.countDown()
275
+ }
276
+
277
+ override fun reject(code: String, message: String?, cause: Throwable?) {
278
+ fail("Resume failed: $code - $message")
279
+ }
280
+ })
281
+
282
+ assertTrue("Resume should complete within 1 second", resumeLatch.await(1, TimeUnit.SECONDS))
283
+
284
+ // Verify audio events resume
285
+ Thread.sleep(500)
286
+ val eventsAfterResume = testEventSender.getEventsOfType(Constants.AUDIO_EVENT_NAME)
287
+ assertTrue("Should receive audio events after resume", eventsAfterResume.isNotEmpty())
288
+
289
+ // Stop recording
290
+ stopRecordingSync()
291
+ }
292
+
293
+ @Test
294
+ fun testCompressedRecording_createsAacFile() {
295
+ // Skip test if API level is too low for compressed recording
296
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
297
+ println("Skipping compressed recording test - requires API 29+, current API: ${android.os.Build.VERSION.SDK_INT}")
298
+ return
299
+ }
300
+
301
+ // Given
302
+ val recordingOptions = mapOf(
303
+ "sampleRate" to 44100,
304
+ "channels" to 2,
305
+ "encoding" to "pcm_16bit",
306
+ "interval" to 100,
307
+ "showNotification" to false,
308
+ "output" to mapOf(
309
+ "compressed" to mapOf(
310
+ "enabled" to true,
311
+ "format" to "aac",
312
+ "bitrate" to 128000
313
+ )
314
+ )
315
+ )
316
+
317
+ // When - Record for 2 seconds
318
+ startRecordingSync(recordingOptions)
319
+ Thread.sleep(2000)
320
+ val result = stopRecordingSync()
321
+
322
+ // Debug: Print the result to understand its structure
323
+ println("Stop recording result keys: ${result.keys}")
324
+ println("Stop recording result: $result")
325
+
326
+ // Then - Verify both files exist
327
+ val wavUri = result["fileUri"] as? String
328
+ assertNotNull("WAV file URI should exist", wavUri)
329
+
330
+ // Check if compression info exists
331
+ val compression = when (val comp = result["compression"]) {
332
+ is Bundle -> bundleToMap(comp)
333
+ is Map<*, *> -> comp
334
+ else -> null
335
+ }
336
+
337
+ // The compressed file URI is inside the compression bundle
338
+ val compressedUri = compression?.get("compressedFileUri") as? String
339
+
340
+ // For debugging - print what we got
341
+ println("Compression info: $compression")
342
+
343
+ assertNotNull("Compression info should exist", compression)
344
+ assertNotNull("Compressed file URI should exist in compression info", compressedUri)
345
+
346
+ // Convert URI strings to Files
347
+ val wavFile = when {
348
+ wavUri!!.startsWith("file://") -> File(java.net.URI(wavUri))
349
+ wavUri.startsWith("file:") -> File(java.net.URI(wavUri))
350
+ else -> File(wavUri)
351
+ }
352
+
353
+ val aacFile = when {
354
+ compressedUri!!.startsWith("file://") -> File(java.net.URI(compressedUri))
355
+ compressedUri.startsWith("file:") -> File(java.net.URI(compressedUri))
356
+ else -> File(compressedUri)
357
+ }
358
+
359
+ assertTrue("WAV file should exist at ${wavFile.absolutePath}", wavFile.exists())
360
+ assertTrue("AAC file should exist at ${aacFile.absolutePath}", aacFile.exists())
361
+ assertTrue("AAC file should have content", aacFile.length() > 0)
362
+ assertTrue("AAC file should be smaller than WAV", aacFile.length() < wavFile.length())
363
+ }
364
+
365
+ @Test
366
+ fun testRecordingWithToneGeneration_verifiesAudioContent() {
367
+ // This test generates a tone and plays it while recording to verify
368
+ // that actual audio is being captured
369
+
370
+ // Given
371
+ val recordingOptions = mapOf(
372
+ "sampleRate" to 44100,
373
+ "channels" to 1,
374
+ "encoding" to "pcm_16bit",
375
+ "interval" to 100,
376
+ "showNotification" to false
377
+ )
378
+
379
+ // Start recording
380
+ startRecordingSync(recordingOptions)
381
+
382
+ // Play a 1kHz tone for 1 second
383
+ playTone(1000.0, 1000)
384
+
385
+ // Stop recording
386
+ val result = stopRecordingSync()
387
+
388
+ // Load and analyze the recorded file
389
+ val fileUri = result["fileUri"] as String
390
+ val audioFile = when {
391
+ fileUri.startsWith("file://") -> File(java.net.URI(fileUri))
392
+ fileUri.startsWith("file:") -> File(java.net.URI(fileUri))
393
+ else -> File(fileUri)
394
+ }
395
+
396
+ val audioProcessor = AudioProcessor(filesDir)
397
+ val audioData = audioProcessor.loadAudioFromAnyFormat(audioFile.absolutePath, null)
398
+
399
+ assertNotNull("Should load audio data", audioData)
400
+
401
+ // Analyze the audio to verify it contains the tone
402
+ val config = RecordingConfig(
403
+ sampleRate = 44100,
404
+ channels = 1,
405
+ encoding = "pcm_16bit",
406
+ features = mapOf(
407
+ "rms" to true,
408
+ "energy" to true,
409
+ "spectralCentroid" to true
410
+ )
411
+ )
412
+
413
+ val analysis = audioProcessor.processAudioData(audioData!!.data, config)
414
+
415
+ // Verify we captured audio with energy (not silence)
416
+ val dataPoints = analysis.dataPoints
417
+ assertTrue("Should have data points", dataPoints.isNotEmpty())
418
+
419
+ // Check that we have non-zero RMS values indicating captured audio
420
+ val avgRms = dataPoints.map { it.rms }.average()
421
+ assertTrue("Average RMS should indicate captured audio", avgRms > 0.01)
422
+
423
+ // Check that features were extracted
424
+ val firstPointWithFeatures = dataPoints.firstOrNull { it.features != null }
425
+ assertNotNull("Should have at least one data point with features", firstPointWithFeatures)
426
+
427
+ // The spectral centroid of a 1kHz tone should be around 1000Hz
428
+ // Note: spectral centroid can be affected by recording quality and background noise
429
+ val spectralCentroids = dataPoints.mapNotNull { it.features?.spectralCentroid }.filter { it > 0 }
430
+ assertTrue("Should have spectral centroid values", spectralCentroids.isNotEmpty())
431
+ val avgSpectralCentroid = spectralCentroids.average()
432
+
433
+ // Log the actual value for debugging
434
+ println("Average spectral centroid: $avgSpectralCentroid Hz")
435
+
436
+ // Be more lenient with the range as real device recording can have variations
437
+ // Just verify it's not silence (very low) or noise (very high)
438
+ assertTrue("Spectral centroid should indicate tonal content (was $avgSpectralCentroid Hz)",
439
+ avgSpectralCentroid > 100 && avgSpectralCentroid < 20000)
440
+ }
441
+
442
+ // ========== Helper Methods ==========
443
+
444
+ private fun startRecordingSync(options: Map<String, Any>) {
445
+ val latch = CountDownLatch(1)
446
+ audioRecorderManager.startRecording(options, object : Promise {
447
+ override fun resolve(value: Any?) {
448
+ latch.countDown()
449
+ }
450
+
451
+ override fun reject(code: String, message: String?, cause: Throwable?) {
452
+ fail("Recording start failed: $code - $message")
453
+ }
454
+ })
455
+
456
+ assertTrue("Recording should start within 2 seconds", latch.await(2, TimeUnit.SECONDS))
457
+ }
458
+
459
+ private fun stopRecordingSync(): Map<String, Any> {
460
+ val latch = CountDownLatch(1)
461
+ var result: Map<String, Any>? = null
462
+
463
+ audioRecorderManager.stopRecording(object : Promise {
464
+ override fun resolve(value: Any?) {
465
+ when (value) {
466
+ is Bundle -> {
467
+ // Convert Bundle to Map
468
+ result = bundleToMap(value)
469
+ }
470
+ is Map<*, *> -> {
471
+ @Suppress("UNCHECKED_CAST")
472
+ result = value as? Map<String, Any>
473
+ }
474
+ else -> {
475
+ fail("Unexpected result type: ${value?.javaClass?.name}")
476
+ }
477
+ }
478
+ latch.countDown()
479
+ }
480
+
481
+ override fun reject(code: String, message: String?, cause: Throwable?) {
482
+ fail("Recording stop failed: $code - $message")
483
+ }
484
+ })
485
+
486
+ assertTrue("Recording should stop within 2 seconds", latch.await(2, TimeUnit.SECONDS))
487
+ return result ?: throw AssertionError("Stop recording returned null result")
488
+ }
489
+
490
+ private fun bundleToMap(bundle: Bundle): Map<String, Any> {
491
+ val map = mutableMapOf<String, Any>()
492
+ for (key in bundle.keySet()) {
493
+ when (val value = bundle.get(key)) {
494
+ is Bundle -> map[key] = bundleToMap(value)
495
+ null -> { /* skip null values */ }
496
+ else -> map[key] = value
497
+ }
498
+ }
499
+ return map
500
+ }
501
+
502
+ private fun playTone(frequency: Double, durationMs: Int) {
503
+ val sampleRate = 44100
504
+ val numSamples = (sampleRate * durationMs / 1000.0).toInt()
505
+ val samples = ShortArray(numSamples)
506
+
507
+ // Generate sine wave
508
+ for (i in 0 until numSamples) {
509
+ val angle = 2.0 * Math.PI * i * frequency / sampleRate
510
+ samples[i] = (sin(angle) * Short.MAX_VALUE * 0.5).toInt().toShort()
511
+ }
512
+
513
+ // Play the tone
514
+ val audioTrack = AudioTrack.Builder()
515
+ .setAudioAttributes(
516
+ android.media.AudioAttributes.Builder()
517
+ .setUsage(android.media.AudioAttributes.USAGE_MEDIA)
518
+ .setContentType(android.media.AudioAttributes.CONTENT_TYPE_MUSIC)
519
+ .build()
520
+ )
521
+ .setAudioFormat(
522
+ AudioFormat.Builder()
523
+ .setSampleRate(sampleRate)
524
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
525
+ .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
526
+ .build()
527
+ )
528
+ .setBufferSizeInBytes(samples.size * 2)
529
+ .setTransferMode(AudioTrack.MODE_STATIC)
530
+ .build()
531
+
532
+ audioTrack.write(samples, 0, samples.size)
533
+ audioTrack.play()
534
+
535
+ // Wait for playback to complete
536
+ Thread.sleep(durationMs.toLong())
537
+
538
+ audioTrack.stop()
539
+ audioTrack.release()
540
+ }
541
+ }