@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,259 @@
1
+ package net.siteed.audiostudio
2
+
3
+ import android.media.AudioFormat
4
+ import android.os.Build
5
+ import java.io.File
6
+
7
+ // New output configuration structure
8
+ data class OutputConfig(
9
+ val primary: PrimaryOutput = PrimaryOutput(),
10
+ val compressed: CompressedOutput = CompressedOutput()
11
+ ) {
12
+ data class PrimaryOutput(
13
+ val enabled: Boolean = true,
14
+ val format: String = "wav"
15
+ )
16
+
17
+ data class CompressedOutput(
18
+ val enabled: Boolean = false,
19
+ val format: String = "aac",
20
+ val bitrate: Int = 128000,
21
+ val preferRawStream: Boolean = false
22
+ )
23
+
24
+ companion object {
25
+ fun fromMap(map: Map<String, Any?>?): OutputConfig {
26
+ if (map == null) return OutputConfig()
27
+
28
+ val primaryMap = map.getTypedMap<Any?>("primary") { true }
29
+ val compressedMap = map.getTypedMap<Any?>("compressed") { true }
30
+
31
+ val primary = PrimaryOutput(
32
+ enabled = primaryMap.getBooleanOrDefault("enabled", true),
33
+ format = primaryMap.getStringOrDefault("format", "wav")
34
+ )
35
+
36
+ val compressed = CompressedOutput(
37
+ enabled = compressedMap.getBooleanOrDefault("enabled", false),
38
+ format = compressedMap.getStringOrDefault("format", "aac").lowercase(),
39
+ bitrate = compressedMap.getNumberOrDefault("bitrate", 128000),
40
+ preferRawStream = compressedMap.getBooleanOrDefault("preferRawStream", false)
41
+ )
42
+
43
+ return OutputConfig(primary = primary, compressed = compressed)
44
+ }
45
+ }
46
+ }
47
+
48
+ data class RecordingConfig(
49
+ val sampleRate: Int = Constants.DEFAULT_SAMPLE_RATE,
50
+ val channels: Int = 1,
51
+ val encoding: String = "pcm_16bit",
52
+ val keepAwake: Boolean = true,
53
+ val interval: Long = Constants.DEFAULT_INTERVAL,
54
+ val intervalAnalysis: Long = Constants.DEFAULT_INTERVAL_ANALYSIS,
55
+ val enableProcessing: Boolean = false,
56
+ val segmentDurationMs: Int = 100,
57
+ val showNotification: Boolean = false,
58
+ val showWaveformInNotification: Boolean = false,
59
+ val notification: NotificationConfig = NotificationConfig(),
60
+ val features: Map<String, Boolean> = emptyMap(),
61
+ val output: OutputConfig = OutputConfig(),
62
+ val autoResumeAfterInterruption: Boolean = false,
63
+ val outputDirectory: String? = null,
64
+ val filename: String? = null,
65
+ val deviceId: String? = null,
66
+ val deviceDisconnectionBehavior: String? = null,
67
+ val audioFocusStrategy: String? = null,
68
+ val bufferDurationSeconds: Double? = null,
69
+ val streamFormat: String = "raw",
70
+ ) {
71
+ companion object {
72
+ fun fromMap(options: Map<String, Any?>?): Result<Pair<RecordingConfig, AudioFormatInfo>> {
73
+ if (options == null) {
74
+ val defaultConfig = RecordingConfig()
75
+ val defaultFormat = AudioFormatInfo(
76
+ format = AudioFormat.ENCODING_PCM_16BIT,
77
+ mimeType = "audio/wav",
78
+ fileExtension = "wav"
79
+ )
80
+ return Result.success(Pair(defaultConfig, defaultFormat))
81
+ }
82
+
83
+ // Extract features using type-safe helper
84
+ val features = options.getTypedMap<Boolean>("features") { it is Boolean }
85
+
86
+ // Parse notification config using type-safe helper
87
+ val notificationMap = options.getTypedMap<Any?>("notification") { true }
88
+ val notificationConfig = NotificationConfig.fromMap(notificationMap)
89
+
90
+ // Parse output config
91
+ val outputMap = options.getTypedMap<Any?>("output") { true }
92
+ val outputConfig = OutputConfig.fromMap(outputMap)
93
+
94
+ // Validate bitrate if compression is enabled
95
+ if (outputConfig.compressed.enabled) {
96
+ when {
97
+ outputConfig.compressed.bitrate < 8000 -> return Result.failure(
98
+ IllegalArgumentException("Bitrate must be at least 8000 bps")
99
+ )
100
+ outputConfig.compressed.bitrate > 960000 -> return Result.failure(
101
+ IllegalArgumentException("Bitrate cannot exceed 960000 bps")
102
+ )
103
+ }
104
+ }
105
+
106
+ // Only validate directory if it's provided
107
+ val outputDirectory = options["outputDirectory"] as? String
108
+ if (outputDirectory != null) {
109
+ // Clean up the directory path by removing file:// protocol and normalizing
110
+ val cleanDirectory = outputDirectory
111
+ .replace(Regex("^file://"), "")
112
+ .trim('/')
113
+ .replace("//", "/")
114
+
115
+ val directory = File(cleanDirectory)
116
+ if (!directory.exists()) {
117
+ return Result.failure(IllegalArgumentException("Directory does not exist: $cleanDirectory"))
118
+ }
119
+ if (!directory.isDirectory) {
120
+ return Result.failure(IllegalArgumentException("Path is not a directory: $cleanDirectory"))
121
+ }
122
+ if (!directory.canWrite()) {
123
+ return Result.failure(IllegalArgumentException("Directory is not writable: $cleanDirectory"))
124
+ }
125
+ }
126
+
127
+ // Get device-related settings
128
+ val deviceId = options["deviceId"] as? String
129
+ val deviceDisconnectionBehavior = options["deviceDisconnectionBehavior"] as? String
130
+
131
+ // Get Android-specific settings
132
+ val androidConfig = options["android"] as? Map<String, Any>
133
+ val audioFocusStrategy = androidConfig?.get("audioFocusStrategy") as? String
134
+
135
+ // Initialize the recording configuration with cleaned directory path
136
+ val tempRecordingConfig = RecordingConfig(
137
+ sampleRate = options.getNumberOrDefault("sampleRate", Constants.DEFAULT_SAMPLE_RATE),
138
+ channels = options.getNumberOrDefault("channels", 1),
139
+ encoding = options.getStringOrDefault("encoding", "pcm_16bit"),
140
+ keepAwake = options.getBooleanOrDefault("keepAwake", true),
141
+ // Enforce minimum intervals to prevent excessive CPU usage
142
+ interval = maxOf(Constants.MIN_INTERVAL, options.getNumberOrDefault("interval", Constants.DEFAULT_INTERVAL)),
143
+ intervalAnalysis = maxOf(Constants.MIN_INTERVAL, options.getNumberOrDefault("intervalAnalysis", Constants.DEFAULT_INTERVAL_ANALYSIS)),
144
+ enableProcessing = options.getBooleanOrDefault("enableProcessing", false),
145
+ segmentDurationMs = options.getNumberOrDefault("segmentDurationMs", 100),
146
+ showNotification = options.getBooleanOrDefault("showNotification", false),
147
+ showWaveformInNotification = options.getBooleanOrDefault("showWaveformInNotification", false),
148
+ notification = notificationConfig,
149
+ features = features,
150
+ output = outputConfig,
151
+ autoResumeAfterInterruption = options.getBooleanOrDefault("autoResumeAfterInterruption", false),
152
+ outputDirectory = outputDirectory?.let {
153
+ it.replace(Regex("^file://"), "")
154
+ .trim('/')
155
+ .replace("//", "/")
156
+ },
157
+ filename = options["filename"] as? String,
158
+ deviceId = deviceId,
159
+ deviceDisconnectionBehavior = deviceDisconnectionBehavior,
160
+ audioFocusStrategy = audioFocusStrategy,
161
+ bufferDurationSeconds = (options["bufferDurationSeconds"] as? Number)?.toDouble(),
162
+ streamFormat = options.getStringOrDefault("streamFormat", "raw"),
163
+ )
164
+
165
+ // Validate sample rate and channels
166
+ if (tempRecordingConfig.sampleRate !in listOf(16000, 44100, 48000)) {
167
+ return Result.failure(
168
+ IllegalArgumentException("Sample rate must be one of 16000, 44100, or 48000 Hz")
169
+ )
170
+ }
171
+ if (tempRecordingConfig.channels !in 1..2) {
172
+ return Result.failure(
173
+ IllegalArgumentException("Channels must be either 1 (Mono) or 2 (Stereo)")
174
+ )
175
+ }
176
+
177
+ // Set encoding and file extension
178
+ val audioFormatInfo = when (tempRecordingConfig.encoding) {
179
+ "pcm_8bit" -> AudioFormatInfo(
180
+ format = AudioFormat.ENCODING_PCM_8BIT,
181
+ mimeType = "audio/wav",
182
+ fileExtension = "wav"
183
+ )
184
+ "pcm_16bit" -> AudioFormatInfo(
185
+ format = AudioFormat.ENCODING_PCM_16BIT,
186
+ mimeType = "audio/wav",
187
+ fileExtension = "wav"
188
+ )
189
+ "pcm_32bit" -> AudioFormatInfo(
190
+ format = AudioFormat.ENCODING_PCM_FLOAT,
191
+ mimeType = "audio/wav",
192
+ fileExtension = "wav"
193
+ )
194
+ "opus" -> {
195
+ if (Build.VERSION.SDK_INT < 29) {
196
+ return Result.failure(
197
+ IllegalArgumentException("Opus encoding not supported on this Android version.")
198
+ )
199
+ }
200
+ AudioFormatInfo(
201
+ format = if (Build.VERSION.SDK_INT >= 29) 20 else AudioFormat.ENCODING_DEFAULT, // 20 is ENCODING_OPUS
202
+ mimeType = "audio/opus",
203
+ fileExtension = "opus"
204
+ )
205
+ }
206
+ "aac_lc" -> AudioFormatInfo(
207
+ format = AudioFormat.ENCODING_AAC_LC,
208
+ mimeType = "audio/aac",
209
+ fileExtension = "aac"
210
+ )
211
+ else -> AudioFormatInfo(
212
+ format = AudioFormat.ENCODING_DEFAULT,
213
+ mimeType = "audio/wav",
214
+ fileExtension = "wav"
215
+ )
216
+ }
217
+
218
+ return Result.success(Pair(tempRecordingConfig, audioFormatInfo))
219
+ }
220
+ }
221
+ }
222
+
223
+ // Extension functions for type-safe map access
224
+ private inline fun <reified T> Map<String, Any?>.getTypedMap(
225
+ key: String,
226
+ predicate: (Any?) -> Boolean
227
+ ): Map<String, T> {
228
+ return (this[key] as? Map<*, *>)?.mapNotNull { (k, v) ->
229
+ if (k is String && predicate(v)) {
230
+ k to (v as T)
231
+ } else null
232
+ }?.toMap() ?: emptyMap()
233
+ }
234
+
235
+ private fun Map<String, Any?>.getStringOrDefault(key: String, default: String): String {
236
+ return this[key] as? String ?: default
237
+ }
238
+
239
+ private fun Map<String, Any?>.getBooleanOrDefault(key: String, default: Boolean): Boolean {
240
+ return this[key] as? Boolean ?: default
241
+ }
242
+
243
+ private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Int): Int {
244
+ return (this[key] as? Number)?.toInt() ?: default
245
+ }
246
+
247
+ private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Long): Long {
248
+ return (this[key] as? Number)?.toLong() ?: default
249
+ }
250
+
251
+ private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Double): Double {
252
+ return (this[key] as? Number)?.toDouble() ?: default
253
+ }
254
+
255
+ data class AudioFormatInfo(
256
+ val format: Int,
257
+ val mimeType: String,
258
+ val fileExtension: String
259
+ )
@@ -0,0 +1,19 @@
1
+ package net.siteed.audiostudio
2
+
3
+ /**
4
+ * Configuration for the notification waveform visualization
5
+ * @property color The color of the waveform (e.g., "#FFFFFF" for white)
6
+ * @property opacity Opacity of the waveform (0.0-1.0)
7
+ * @property strokeWidth Width of the waveform line (default: 1.5f)
8
+ * @property style Drawing style: "stroke" for outline, "fill" for solid
9
+ * @property mirror Whether to mirror the waveform (symmetrical display)
10
+ * @property height Height of the waveform view in dp (default: 64)
11
+ */
12
+ data class WaveformConfig(
13
+ val color: String = "#FFFFFF",
14
+ val opacity: Float = 1.0f,
15
+ val strokeWidth: Float = 1.5f,
16
+ val style: String = "stroke", // "stroke" or "fill"
17
+ val mirror: Boolean = true,
18
+ val height: Int = 64
19
+ )
@@ -0,0 +1,159 @@
1
+ // WaveformRenderer.kt
2
+ package net.siteed.audiostudio
3
+
4
+ import android.graphics.Bitmap
5
+ import android.graphics.Canvas
6
+ import android.graphics.Color
7
+ import android.graphics.Paint
8
+ import android.graphics.Path
9
+ import kotlin.math.abs
10
+
11
+ class WaveformRenderer {
12
+ private enum class Style {
13
+ STROKE,
14
+ FILL
15
+ }
16
+
17
+ private data class RenderConfig(
18
+ val color: Int = Color.WHITE,
19
+ val opacity: Float = 1.0f,
20
+ val strokeWidth: Float = 1.5f,
21
+ val style: Style = Style.STROKE,
22
+ val mirror: Boolean = true,
23
+ val height: Int = 64
24
+ ) {
25
+ companion object {
26
+ fun fromConfig(config: WaveformConfig?): RenderConfig {
27
+ if (config == null) return RenderConfig()
28
+
29
+ return RenderConfig(
30
+ color = try {
31
+ Color.parseColor(config.color)
32
+ } catch (e: Exception) {
33
+ Color.WHITE
34
+ },
35
+ opacity = config.opacity.coerceIn(0f, 1f),
36
+ strokeWidth = config.strokeWidth.coerceAtLeast(0.5f),
37
+ style = when (config.style.lowercase()) {
38
+ "fill" -> Style.FILL
39
+ else -> Style.STROKE
40
+ },
41
+ mirror = config.mirror,
42
+ height = config.height.coerceIn(32, 128)
43
+ )
44
+ }
45
+ }
46
+ }
47
+
48
+ fun generateWaveform(audioData: FloatArray, config: WaveformConfig?): Bitmap {
49
+ return generateWaveform(audioData, RenderConfig.fromConfig(config))
50
+ }
51
+
52
+ fun e(audioData: FloatArray): Bitmap {
53
+ return generateWaveform(audioData, RenderConfig())
54
+ }
55
+
56
+ private fun generateWaveform(audioData: FloatArray, config: RenderConfig): Bitmap {
57
+ val width = 400 // Fixed width for notification
58
+ val height = config.height
59
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
60
+ val canvas = Canvas(bitmap)
61
+
62
+ val paint = Paint().apply {
63
+ color = config.color
64
+ alpha = (config.opacity * 255).toInt()
65
+ strokeWidth = config.strokeWidth
66
+ isAntiAlias = true
67
+ style = when (config.style) {
68
+ Style.FILL -> Paint.Style.FILL
69
+ Style.STROKE -> Paint.Style.STROKE
70
+ }
71
+ strokeCap = Paint.Cap.ROUND
72
+ }
73
+
74
+ canvas.drawColor(Color.TRANSPARENT)
75
+
76
+ val centerY = height / 2f
77
+ val pointsPerSegment = (audioData.size / width.toFloat()).coerceAtLeast(1f)
78
+ val path = Path()
79
+
80
+ // Calculate max amplitude for scaling
81
+ val maxAmplitude = audioData.maxOf { abs(it) }.coerceAtLeast(0.01f)
82
+ val scaleFactor = (height * 0.4f) / maxAmplitude
83
+
84
+ drawWaveform(path, audioData, width, centerY, pointsPerSegment, scaleFactor)
85
+
86
+ if (config.mirror) {
87
+ drawMirroredWaveform(path, audioData, width, centerY, pointsPerSegment, scaleFactor)
88
+ }
89
+
90
+ // Add shadow for filled style
91
+ if (config.style == Style.FILL) {
92
+ paint.setShadowLayer(4f, 0f, 2f, Color.argb(50, 0, 0, 0))
93
+ }
94
+
95
+ canvas.drawPath(path, paint)
96
+ return bitmap
97
+ }
98
+
99
+ private fun drawWaveform(
100
+ path: Path,
101
+ audioData: FloatArray,
102
+ width: Int,
103
+ centerY: Float,
104
+ pointsPerSegment: Float,
105
+ scaleFactor: Float
106
+ ) {
107
+ var firstPoint = true
108
+ for (i in 0 until width) {
109
+ val startIdx = (i * pointsPerSegment).toInt()
110
+ val endIdx = ((i + 1) * pointsPerSegment).toInt().coerceAtMost(audioData.size)
111
+
112
+ val avgAmplitude = calculateAverageAmplitude(audioData, startIdx, endIdx, scaleFactor)
113
+
114
+ val x = i.toFloat()
115
+ val y = centerY - avgAmplitude
116
+
117
+ if (firstPoint) {
118
+ path.moveTo(x, y)
119
+ firstPoint = false
120
+ } else {
121
+ path.lineTo(x, y)
122
+ }
123
+ }
124
+ }
125
+
126
+ private fun drawMirroredWaveform(
127
+ path: Path,
128
+ audioData: FloatArray,
129
+ width: Int,
130
+ centerY: Float,
131
+ pointsPerSegment: Float,
132
+ scaleFactor: Float
133
+ ) {
134
+ for (i in width - 1 downTo 0) {
135
+ val startIdx = (i * pointsPerSegment).toInt()
136
+ val endIdx = ((i + 1) * pointsPerSegment).toInt().coerceAtMost(audioData.size)
137
+
138
+ val avgAmplitude = calculateAverageAmplitude(audioData, startIdx, endIdx, scaleFactor)
139
+ path.lineTo(i.toFloat(), centerY + avgAmplitude)
140
+ }
141
+ path.close()
142
+ }
143
+
144
+ private fun calculateAverageAmplitude(
145
+ audioData: FloatArray,
146
+ startIdx: Int,
147
+ endIdx: Int,
148
+ scaleFactor: Float
149
+ ): Float {
150
+ return if (endIdx > startIdx) {
151
+ audioData.slice(startIdx until endIdx)
152
+ .map { abs(it) }
153
+ .average()
154
+ .toFloat() * scaleFactor
155
+ } else {
156
+ 0f
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,152 @@
1
+ #include <jni.h>
2
+ #include <android/log.h>
3
+ #include "AudioFeatures.h"
4
+ #include <memory>
5
+ #include <mutex>
6
+
7
+ #define LOG_TAG "AudioFeaturesJNI"
8
+ #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
9
+ #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
10
+
11
+ static std::unique_ptr<AudioFeaturesProcessor> cachedProcessor;
12
+ static std::mutex cachedMutex;
13
+
14
+ extern "C" JNIEXPORT void JNICALL
15
+ Java_net_siteed_audiostudio_AudioFeaturesNative_init(
16
+ JNIEnv* env, jobject /* thiz */,
17
+ jint sampleRate, jint fftLength, jint nMfcc, jint nMelFilters,
18
+ jboolean computeMfcc, jboolean computeChroma)
19
+ {
20
+ AudioFeaturesConfig config;
21
+ config.sampleRate = sampleRate;
22
+ config.fftLength = fftLength;
23
+ config.nMfcc = nMfcc;
24
+ config.nMelFilters = nMelFilters;
25
+ config.computeMfcc = computeMfcc;
26
+ config.computeChroma = computeChroma;
27
+
28
+ std::lock_guard<std::mutex> lock(cachedMutex);
29
+ if (!cachedProcessor || !(cachedProcessor->config() == config)) {
30
+ cachedProcessor = std::make_unique<AudioFeaturesProcessor>(config);
31
+ }
32
+ LOGI("init: sampleRate=%d, fftLength=%d, nMfcc=%d, nMelFilters=%d",
33
+ sampleRate, fftLength, nMfcc, nMelFilters);
34
+ }
35
+
36
+ extern "C" JNIEXPORT jobject JNICALL
37
+ Java_net_siteed_audiostudio_AudioFeaturesNative_computeFrame(
38
+ JNIEnv* env, jobject /* thiz */,
39
+ jfloatArray jSamples, jint sampleRate, jint fftLength,
40
+ jint nMfcc, jint nMelFilters, jboolean computeMfcc, jboolean computeChroma)
41
+ {
42
+ jfloat* samples = env->GetFloatArrayElements(jSamples, nullptr);
43
+ if (!samples) {
44
+ LOGE("computeFrame: failed to get samples array");
45
+ return nullptr;
46
+ }
47
+ jint numSamples = env->GetArrayLength(jSamples);
48
+
49
+ AudioFeaturesConfig config;
50
+ config.sampleRate = sampleRate;
51
+ config.fftLength = fftLength;
52
+ config.nMfcc = nMfcc;
53
+ config.nMelFilters = nMelFilters;
54
+ config.computeMfcc = computeMfcc;
55
+ config.computeChroma = computeChroma;
56
+
57
+ std::lock_guard<std::mutex> lock(cachedMutex);
58
+ if (!cachedProcessor || !(cachedProcessor->config() == config)) {
59
+ cachedProcessor = std::make_unique<AudioFeaturesProcessor>(config);
60
+ }
61
+
62
+ AudioFeaturesResult result = cachedProcessor->compute(samples, numSamples);
63
+
64
+ env->ReleaseFloatArrayElements(jSamples, samples, JNI_ABORT);
65
+
66
+ LOGI("computeFrame: centroid=%.2f, flatness=%.6f, rolloff=%.2f, bandwidth=%.2f, mfcc_size=%d, chroma_size=%d",
67
+ result.spectralCentroid, result.spectralFlatness,
68
+ result.spectralRolloff, result.spectralBandwidth,
69
+ (int)result.mfcc.size(), (int)result.chromagram.size());
70
+
71
+ // Build HashMap<String, Object>
72
+ jclass hashMapClass = env->FindClass("java/util/HashMap");
73
+ if (!hashMapClass || env->ExceptionCheck()) {
74
+ LOGE("computeFrame: failed to find HashMap class");
75
+ env->ExceptionClear();
76
+ return nullptr;
77
+ }
78
+ jmethodID hashMapInit = env->GetMethodID(hashMapClass, "<init>", "()V");
79
+ jmethodID hashMapPut = env->GetMethodID(hashMapClass, "put",
80
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
81
+ if (!hashMapInit || !hashMapPut || env->ExceptionCheck()) {
82
+ LOGE("computeFrame: failed to get HashMap methods");
83
+ env->ExceptionClear();
84
+ env->DeleteLocalRef(hashMapClass);
85
+ return nullptr;
86
+ }
87
+
88
+ jobject map = env->NewObject(hashMapClass, hashMapInit);
89
+ if (!map) {
90
+ LOGE("computeFrame: failed to create HashMap");
91
+ env->DeleteLocalRef(hashMapClass);
92
+ return nullptr;
93
+ }
94
+
95
+ // Helper: box a float as java.lang.Float
96
+ jclass floatClass = env->FindClass("java/lang/Float");
97
+ jmethodID floatValueOf = env->GetStaticMethodID(floatClass, "valueOf", "(F)Ljava/lang/Float;");
98
+ if (!floatClass || !floatValueOf || env->ExceptionCheck()) {
99
+ LOGE("computeFrame: failed to get Float class/method");
100
+ env->ExceptionClear();
101
+ if (floatClass) env->DeleteLocalRef(floatClass);
102
+ env->DeleteLocalRef(hashMapClass);
103
+ return map; // return partial map with no values
104
+ }
105
+
106
+ // Put scalar values
107
+ auto putFloat = [&](const char* key, float value) {
108
+ jstring jKey = env->NewStringUTF(key);
109
+ if (!jKey) return;
110
+ jobject jVal = env->CallStaticObjectMethod(floatClass, floatValueOf, value);
111
+ env->CallObjectMethod(map, hashMapPut, jKey, jVal);
112
+ env->DeleteLocalRef(jKey);
113
+ env->DeleteLocalRef(jVal);
114
+ };
115
+
116
+ putFloat("spectralCentroid", result.spectralCentroid);
117
+ putFloat("spectralFlatness", result.spectralFlatness);
118
+ putFloat("spectralRolloff", result.spectralRolloff);
119
+ putFloat("spectralBandwidth", result.spectralBandwidth);
120
+
121
+ // Put MFCC array
122
+ if (!result.mfcc.empty()) {
123
+ jfloatArray jMfcc = env->NewFloatArray(static_cast<jsize>(result.mfcc.size()));
124
+ if (jMfcc) {
125
+ env->SetFloatArrayRegion(jMfcc, 0, static_cast<jsize>(result.mfcc.size()), result.mfcc.data());
126
+ jstring key = env->NewStringUTF("mfcc");
127
+ if (key) {
128
+ env->CallObjectMethod(map, hashMapPut, key, jMfcc);
129
+ env->DeleteLocalRef(key);
130
+ }
131
+ env->DeleteLocalRef(jMfcc);
132
+ }
133
+ }
134
+
135
+ // Put chromagram array
136
+ if (!result.chromagram.empty()) {
137
+ jfloatArray jChroma = env->NewFloatArray(static_cast<jsize>(result.chromagram.size()));
138
+ if (jChroma) {
139
+ env->SetFloatArrayRegion(jChroma, 0, static_cast<jsize>(result.chromagram.size()), result.chromagram.data());
140
+ jstring key = env->NewStringUTF("chromagram");
141
+ if (key) {
142
+ env->CallObjectMethod(map, hashMapPut, key, jChroma);
143
+ env->DeleteLocalRef(key);
144
+ }
145
+ env->DeleteLocalRef(jChroma);
146
+ }
147
+ }
148
+
149
+ env->DeleteLocalRef(floatClass);
150
+ env->DeleteLocalRef(hashMapClass);
151
+ return map;
152
+ }