@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,130 @@
1
+ import Foundation
2
+ import AVFoundation
3
+ import Accelerate
4
+
5
+ extension AVAudioPCMBuffer {
6
+
7
+ /// Convert buffer to Data
8
+ func toData() -> Data {
9
+ let audioFormat = self.format
10
+ let channelCount = Int(audioFormat.channelCount)
11
+ let frameLength = Int(self.frameLength)
12
+
13
+ var data = Data()
14
+
15
+ if let floatData = self.floatChannelData {
16
+ // Convert float samples to 16-bit PCM
17
+ for frame in 0..<frameLength {
18
+ for channel in 0..<channelCount {
19
+ let sample = floatData[channel][frame]
20
+ let int16Sample = Int16(max(-32768, min(32767, sample * 32767)))
21
+ data.append(contentsOf: withUnsafeBytes(of: int16Sample) { Array($0) })
22
+ }
23
+ }
24
+ } else if let int16Data = self.int16ChannelData {
25
+ // Already 16-bit, just copy
26
+ for frame in 0..<frameLength {
27
+ for channel in 0..<channelCount {
28
+ let sample = int16Data[channel][frame]
29
+ data.append(contentsOf: withUnsafeBytes(of: sample) { Array($0) })
30
+ }
31
+ }
32
+ }
33
+
34
+ return data
35
+ }
36
+
37
+ /// Calculate RMS (Root Mean Square) of the buffer
38
+ func rms() -> Float {
39
+ guard let channelData = self.floatChannelData else { return 0 }
40
+
41
+ let channelCount = Int(self.format.channelCount)
42
+ let frameLength = Int(self.frameLength)
43
+
44
+ var sum: Float = 0
45
+ var sampleCount = 0
46
+
47
+ for channel in 0..<channelCount {
48
+ for frame in 0..<frameLength {
49
+ let sample = channelData[channel][frame]
50
+ sum += sample * sample
51
+ sampleCount += 1
52
+ }
53
+ }
54
+
55
+ return sqrt(sum / Float(sampleCount))
56
+ }
57
+
58
+ /// Calculate energy of the buffer
59
+ func energy() -> Float {
60
+ guard let channelData = self.floatChannelData else { return 0 }
61
+
62
+ let channelCount = Int(self.format.channelCount)
63
+ let frameLength = Int(self.frameLength)
64
+
65
+ var sum: Float = 0
66
+
67
+ for channel in 0..<channelCount {
68
+ for frame in 0..<frameLength {
69
+ let sample = channelData[channel][frame]
70
+ sum += sample * sample
71
+ }
72
+ }
73
+
74
+ return sum
75
+ }
76
+ }
77
+
78
+ extension Data {
79
+
80
+ /// Convert PCM data to float array
81
+ func toFloatArray(bitDepth: Int = 16) -> [Float] {
82
+ var floats = [Float]()
83
+
84
+ switch bitDepth {
85
+ case 16:
86
+ let samples = self.withUnsafeBytes { $0.bindMemory(to: Int16.self) }
87
+ for sample in samples {
88
+ floats.append(Float(sample) / Float(Int16.max))
89
+ }
90
+ case 32:
91
+ let samples = self.withUnsafeBytes { $0.bindMemory(to: Int32.self) }
92
+ for sample in samples {
93
+ floats.append(Float(sample) / Float(Int32.max))
94
+ }
95
+ default:
96
+ break
97
+ }
98
+
99
+ return floats
100
+ }
101
+
102
+ /// Calculate RMS from PCM data
103
+ func rms(bitDepth: Int = 16) -> Float {
104
+ let floats = toFloatArray(bitDepth: bitDepth)
105
+ guard !floats.isEmpty else { return 0 }
106
+
107
+ let sum = floats.reduce(0) { $0 + $1 * $1 }
108
+ return sqrt(sum / Float(floats.count))
109
+ }
110
+
111
+ /// Calculate energy from PCM data
112
+ func energy(bitDepth: Int = 16) -> Float {
113
+ let floats = toFloatArray(bitDepth: bitDepth)
114
+ return floats.reduce(0) { $0 + $1 * $1 }
115
+ }
116
+ }
117
+
118
+ // Test assertion helpers
119
+ extension XCTestCase {
120
+
121
+ /// Assert two float values are approximately equal
122
+ func XCTAssertApproximatelyEqual(_ value1: Float, _ value2: Float, tolerance: Float = 0.0001, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
123
+ XCTAssertLessThanOrEqual(abs(value1 - value2), tolerance, message, file: file, line: line)
124
+ }
125
+
126
+ /// Assert two double values are approximately equal
127
+ func XCTAssertApproximatelyEqual(_ value1: Double, _ value2: Double, tolerance: Double = 0.0001, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
128
+ XCTAssertLessThanOrEqual(abs(value1 - value2), tolerance, message, file: file, line: line)
129
+ }
130
+ }
@@ -0,0 +1,294 @@
1
+ import XCTest
2
+ import AVFoundation
3
+ @testable import AudioStudio
4
+
5
+ class CompressedOnlyOutputTests: XCTestCase {
6
+
7
+ var audioManager: AudioStreamManager!
8
+ var testDelegate: TestAudioStreamDelegate!
9
+
10
+ override func setUp() {
11
+ super.setUp()
12
+ audioManager = AudioStreamManager()
13
+ testDelegate = TestAudioStreamDelegate()
14
+ audioManager.delegate = testDelegate
15
+ }
16
+
17
+ override func tearDown() {
18
+ audioManager.stopRecording()
19
+ audioManager = nil
20
+ testDelegate = nil
21
+ super.tearDown()
22
+ }
23
+
24
+ // MARK: - Test Compressed-Only Output (Issue #244)
25
+
26
+ func testCompressedOnlyOutputWithAAC() {
27
+ // Given: Recording settings with primary disabled and compressed enabled (AAC)
28
+ var settings = RecordingSettings(
29
+ sampleRate: 44100,
30
+ desiredSampleRate: 44100,
31
+ autoResumeAfterInterruption: false
32
+ )
33
+ settings.numberOfChannels = 1
34
+ settings.bitDepth = 16
35
+ settings.output.primary.enabled = false
36
+ settings.output.compressed.enabled = true
37
+ settings.output.compressed.format = "aac"
38
+ settings.output.compressed.bitrate = 128000
39
+
40
+ let expectation = self.expectation(description: "Recording should complete with compression info")
41
+ var capturedCompressionInfo: [String: Any]?
42
+ var capturedError: String?
43
+
44
+ // When: Start and stop recording
45
+ testDelegate.onAudioData = { data, recordingTime, totalDataSize, compressionInfo in
46
+ capturedCompressionInfo = compressionInfo
47
+ }
48
+
49
+ testDelegate.onError = { error in
50
+ capturedError = error
51
+ }
52
+
53
+ // Start recording returns a result that we can check
54
+ let startResult = audioManager.startRecording(settings: settings)
55
+ XCTAssertNotNil(startResult, "Start recording should return a result")
56
+
57
+ // Generate and process some test audio to ensure compression happens
58
+ let testBuffer = TestAudioGenerator.generateTone(frequency: 440, duration: 0.1, sampleRate: 44100)
59
+ if let buffer = testBuffer {
60
+ // Process multiple chunks to ensure we have enough data
61
+ for _ in 0..<5 {
62
+ audioManager.processAudioBuffer(buffer, time: AVAudioTime(hostTime: mach_absolute_time()))
63
+ Thread.sleep(forTimeInterval: 0.1)
64
+ }
65
+ }
66
+
67
+ // Stop recording and get the result
68
+ let recordingResult = audioManager.stopRecording()
69
+ expectation.fulfill()
70
+
71
+ waitForExpectations(timeout: 2.0) { error in
72
+ XCTAssertNil(error, "Recording should complete within timeout")
73
+ }
74
+
75
+ // Then: Verify compression info is returned
76
+ XCTAssertNil(capturedError, "No errors should occur during recording")
77
+ XCTAssertNotNil(recordingResult, "Recording result should not be nil")
78
+ XCTAssertNotNil(recordingResult?.compression, "Compression info should be included")
79
+
80
+ if let compression = recordingResult?.compression {
81
+ XCTAssertEqual(compression.format, "aac", "Format should be AAC")
82
+ XCTAssertEqual(compression.bitrate, 128000, "Bitrate should match settings")
83
+ XCTAssertFalse(compression.compressedFileUri.isEmpty, "Compressed file URI should not be empty")
84
+ XCTAssertGreaterThan(compression.size, 0, "Compressed file size should be greater than 0")
85
+ XCTAssertEqual(compression.mimeType, "audio/aac", "MIME type should be audio/aac")
86
+ }
87
+
88
+ // Verify main result uses compressed info when primary is disabled
89
+ XCTAssertEqual(recordingResult?.fileUri, recordingResult?.compression?.compressedFileUri,
90
+ "Main fileUri should use compressed URI when primary is disabled")
91
+ XCTAssertEqual(recordingResult?.mimeType, "audio/aac",
92
+ "Main mimeType should reflect compressed format")
93
+ }
94
+
95
+ func testCompressedOnlyOutputWithOpusFallback() {
96
+ // Given: Recording settings with primary disabled and compressed enabled (Opus)
97
+ var settings = RecordingSettings(
98
+ sampleRate: 48000,
99
+ desiredSampleRate: 48000,
100
+ autoResumeAfterInterruption: false
101
+ )
102
+ settings.numberOfChannels = 1
103
+ settings.bitDepth = 16
104
+ settings.output.primary.enabled = false
105
+ settings.output.compressed.enabled = true
106
+ settings.output.compressed.format = "opus" // Should fallback to AAC on iOS
107
+ settings.output.compressed.bitrate = 64000
108
+
109
+ let expectation = self.expectation(description: "Recording should complete with AAC fallback")
110
+
111
+ // Start recording
112
+ let startResult = audioManager.startRecording(settings: settings)
113
+ XCTAssertNotNil(startResult, "Start recording should return a result")
114
+
115
+ // Generate test audio
116
+ if let buffer = TestAudioGenerator.generateTone(frequency: 440, duration: 0.1, sampleRate: 48000) {
117
+ for _ in 0..<3 {
118
+ audioManager.processAudioBuffer(buffer, time: AVAudioTime(hostTime: mach_absolute_time()))
119
+ Thread.sleep(forTimeInterval: 0.1)
120
+ }
121
+ }
122
+
123
+ // Stop recording
124
+ let recordingResult = audioManager.stopRecording()
125
+ expectation.fulfill()
126
+
127
+ waitForExpectations(timeout: 2.0) { error in
128
+ XCTAssertNil(error, "Recording should complete within timeout")
129
+ }
130
+
131
+ // Then: Verify Opus falls back to AAC on iOS
132
+ XCTAssertNotNil(recordingResult?.compression, "Compression info should be included")
133
+ XCTAssertEqual(recordingResult?.compression?.format, "aac",
134
+ "Opus should fallback to AAC on iOS")
135
+ XCTAssertEqual(recordingResult?.compression?.bitrate, 64000,
136
+ "Bitrate should be preserved from original settings")
137
+ }
138
+
139
+ func testCompressedFileAccessibility() {
140
+ // Given: Recording with compressed output
141
+ var settings = RecordingSettings(
142
+ sampleRate: 44100,
143
+ desiredSampleRate: 44100,
144
+ autoResumeAfterInterruption: false
145
+ )
146
+ settings.numberOfChannels = 1
147
+ settings.bitDepth = 16
148
+ settings.output.primary.enabled = false
149
+ settings.output.compressed.enabled = true
150
+ settings.output.compressed.format = "aac"
151
+ settings.output.compressed.bitrate = 96000
152
+
153
+ let expectation = self.expectation(description: "Compressed file should be accessible")
154
+
155
+ // Start recording
156
+ let startResult = audioManager.startRecording(settings: settings)
157
+ XCTAssertNotNil(startResult, "Start recording should return a result")
158
+
159
+ // Generate substantial audio data to ensure file is created
160
+ if let buffer = TestAudioGenerator.generateTone(frequency: 440, duration: 0.2, sampleRate: 44100) {
161
+ for _ in 0..<5 {
162
+ audioManager.processAudioBuffer(buffer, time: AVAudioTime(hostTime: mach_absolute_time()))
163
+ Thread.sleep(forTimeInterval: 0.1)
164
+ }
165
+ }
166
+
167
+ // Stop recording
168
+ let recordingResult = audioManager.stopRecording()
169
+ expectation.fulfill()
170
+
171
+ waitForExpectations(timeout: 2.0) { error in
172
+ XCTAssertNil(error, "Recording should complete within timeout")
173
+ }
174
+
175
+ // Then: Verify compressed file is accessible
176
+ if let compression = recordingResult?.compression {
177
+ let fileURL = URL(string: compression.compressedFileUri)
178
+ XCTAssertNotNil(fileURL, "Compressed file URL should be valid")
179
+
180
+ if let url = fileURL {
181
+ let fileExists = FileManager.default.fileExists(atPath: url.path)
182
+ XCTAssertTrue(fileExists, "Compressed file should exist at the specified path")
183
+
184
+ // Verify file size matches reported size
185
+ if fileExists {
186
+ do {
187
+ let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
188
+ let actualSize = attributes[.size] as? Int64 ?? 0
189
+ XCTAssertEqual(actualSize, compression.size,
190
+ "Reported size should match actual file size")
191
+ } catch {
192
+ XCTFail("Failed to get file attributes: \(error)")
193
+ }
194
+ }
195
+ }
196
+ } else {
197
+ XCTFail("Compression info should not be nil")
198
+ }
199
+ }
200
+
201
+ func testStreamingOnlyWithCompression() {
202
+ // Given: Streaming configuration with compression
203
+ var settings = RecordingSettings(
204
+ sampleRate: 44100,
205
+ desiredSampleRate: 44100,
206
+ autoResumeAfterInterruption: false
207
+ )
208
+ settings.numberOfChannels = 1
209
+ settings.bitDepth = 16
210
+ settings.output.primary.enabled = false
211
+ settings.output.compressed.enabled = true
212
+ settings.output.compressed.format = "aac"
213
+ settings.output.compressed.bitrate = 128000
214
+ settings.interval = 100 // Enable streaming with 100ms intervals
215
+
216
+ let expectation = self.expectation(description: "Streaming should work with compressed output")
217
+ var dataEventCount = 0
218
+ var hasCompressionInfo = false
219
+
220
+ testDelegate.onAudioData = { data, recordingTime, totalDataSize, compressionInfo in
221
+ dataEventCount += 1
222
+ if compressionInfo != nil {
223
+ hasCompressionInfo = true
224
+ }
225
+ }
226
+
227
+ // Start recording
228
+ let startResult = audioManager.startRecording(settings: settings)
229
+ XCTAssertNotNil(startResult, "Start recording should return a result")
230
+
231
+ // Generate audio data
232
+ if let buffer = TestAudioGenerator.generateTone(frequency: 440, duration: 0.1, sampleRate: 44100) {
233
+ for _ in 0..<5 {
234
+ audioManager.processAudioBuffer(buffer, time: AVAudioTime(hostTime: mach_absolute_time()))
235
+ Thread.sleep(forTimeInterval: 0.1)
236
+ }
237
+ }
238
+
239
+ // Stop recording
240
+ let recordingResult = audioManager.stopRecording()
241
+ expectation.fulfill()
242
+
243
+ waitForExpectations(timeout: 2.0) { error in
244
+ XCTAssertNil(error, "Recording should complete within timeout")
245
+ }
246
+
247
+ // Then: Verify streaming worked and compression info is available
248
+ XCTAssertGreaterThan(dataEventCount, 0, "Should have received audio data events")
249
+ XCTAssertTrue(hasCompressionInfo, "Should have received compression info in data events")
250
+ XCTAssertNotNil(recordingResult?.compression, "Compression info should be available in final result")
251
+ }
252
+ }
253
+
254
+ // MARK: - Test Delegate
255
+
256
+ class TestAudioStreamDelegate: AudioStreamManagerDelegate {
257
+ var onAudioData: ((Data, TimeInterval, Int64, [String: Any]?) -> Void)?
258
+ var onError: ((String) -> Void)?
259
+ var onAnalysis: ((AudioAnalysisData?) -> Void)?
260
+
261
+ func audioStreamManager(
262
+ _ manager: AudioStreamManager,
263
+ didReceiveAudioData data: Data,
264
+ recordingTime: TimeInterval,
265
+ totalDataSize: Int64,
266
+ compressionInfo: [String: Any]?
267
+ ) {
268
+ onAudioData?(data, recordingTime, totalDataSize, compressionInfo)
269
+ }
270
+
271
+ func audioStreamManager(_ manager: AudioStreamManager, didReceiveProcessingResult result: AudioAnalysisData?) {
272
+ onAnalysis?(result)
273
+ }
274
+
275
+ func audioStreamManager(_ manager: AudioStreamManager, didPauseRecording pauseTime: Date) {
276
+ // Optional: Handle pause
277
+ }
278
+
279
+ func audioStreamManager(_ manager: AudioStreamManager, didResumeRecording resumeTime: Date) {
280
+ // Optional: Handle resume
281
+ }
282
+
283
+ func audioStreamManager(_ manager: AudioStreamManager, didUpdateNotificationState isPaused: Bool) {
284
+ // Optional: Handle notification state
285
+ }
286
+
287
+ func audioStreamManager(_ manager: AudioStreamManager, didReceiveInterruption info: [String: Any]) {
288
+ // Optional: Handle interruption
289
+ }
290
+
291
+ func audioStreamManager(_ manager: AudioStreamManager, didFailWithError error: String) {
292
+ onError?(error)
293
+ }
294
+ }
@@ -0,0 +1,105 @@
1
+ import XCTest
2
+ @testable import AudioStudio
3
+
4
+ class EventEmissionIntervalTests: XCTestCase {
5
+
6
+ var audioStreamManager: AudioStreamManager!
7
+
8
+ override func setUp() {
9
+ super.setUp()
10
+ audioStreamManager = AudioStreamManager()
11
+ }
12
+
13
+ override func tearDown() {
14
+ audioStreamManager = nil
15
+ super.tearDown()
16
+ }
17
+
18
+ func testIntervalClamping() {
19
+ // Test case 1: Interval below minimum (10ms)
20
+ let config1 = RecordingConfig()
21
+ config1.interval = 10
22
+ config1.intervalAnalysis = 10
23
+
24
+ audioStreamManager.prepareRecording(with: config1) { error in
25
+ XCTAssertNil(error, "Should prepare successfully")
26
+ }
27
+
28
+ // After our fix, this should be 10ms (0.01s), not 100ms (0.1s)
29
+ XCTAssertEqual(audioStreamManager.emissionInterval, 0.01, accuracy: 0.001)
30
+ XCTAssertEqual(audioStreamManager.emissionIntervalAnalysis, 0.01, accuracy: 0.001)
31
+
32
+ // Test case 2: Interval at old minimum (100ms)
33
+ let config2 = RecordingConfig()
34
+ config2.interval = 100
35
+ config2.intervalAnalysis = 100
36
+
37
+ audioStreamManager.prepareRecording(with: config2) { error in
38
+ XCTAssertNil(error, "Should prepare successfully")
39
+ }
40
+
41
+ XCTAssertEqual(audioStreamManager.emissionInterval, 0.1, accuracy: 0.001)
42
+ XCTAssertEqual(audioStreamManager.emissionIntervalAnalysis, 0.1, accuracy: 0.001)
43
+
44
+ // Test case 3: Interval above minimum (200ms)
45
+ let config3 = RecordingConfig()
46
+ config3.interval = 200
47
+ config3.intervalAnalysis = 200
48
+
49
+ audioStreamManager.prepareRecording(with: config3) { error in
50
+ XCTAssertNil(error, "Should prepare successfully")
51
+ }
52
+
53
+ XCTAssertEqual(audioStreamManager.emissionInterval, 0.2, accuracy: 0.001)
54
+ XCTAssertEqual(audioStreamManager.emissionIntervalAnalysis, 0.2, accuracy: 0.001)
55
+ }
56
+
57
+ func testEventEmissionTiming() {
58
+ let expectation = self.expectation(description: "Should emit events at correct intervals")
59
+ var eventTimestamps: [TimeInterval] = []
60
+ let testDuration: TimeInterval = 0.5 // 500ms
61
+
62
+ // Configure for 10ms intervals
63
+ let config = RecordingConfig()
64
+ config.interval = 10
65
+ config.intervalAnalysis = 10
66
+ config.enableProcessing = true
67
+ config.features = ["fft": true]
68
+
69
+ // Mock event handler to capture timestamps
70
+ audioStreamManager.onAudioData = { _ in
71
+ eventTimestamps.append(Date().timeIntervalSince1970)
72
+ }
73
+
74
+ audioStreamManager.startRecording(with: config) { error in
75
+ XCTAssertNil(error, "Should start recording successfully")
76
+
77
+ // Record for test duration
78
+ DispatchQueue.main.asyncAfter(deadline: .now() + testDuration) {
79
+ self.audioStreamManager.stopRecording()
80
+
81
+ // Analyze intervals
82
+ if eventTimestamps.count > 1 {
83
+ var intervals: [TimeInterval] = []
84
+ for i in 1..<eventTimestamps.count {
85
+ intervals.append((eventTimestamps[i] - eventTimestamps[i-1]) * 1000) // Convert to ms
86
+ }
87
+
88
+ let avgInterval = intervals.reduce(0, +) / Double(intervals.count)
89
+ let minInterval = intervals.min() ?? 0
90
+ let maxInterval = intervals.max() ?? 0
91
+
92
+ print("Event emission intervals - Avg: \(avgInterval)ms, Min: \(minInterval)ms, Max: \(maxInterval)ms")
93
+
94
+ // With the fix, average should be close to 10ms
95
+ XCTAssertLessThan(abs(avgInterval - 10), 5, "Average interval should be close to 10ms")
96
+ XCTAssertGreaterThan(minInterval, 5, "Minimum interval should be at least 5ms")
97
+ }
98
+
99
+ expectation.fulfill()
100
+ }
101
+ }
102
+
103
+ waitForExpectations(timeout: testDuration + 1.0)
104
+ }
105
+ }
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
7
+ <key>CFBundleExecutable</key>
8
+ <string>$(EXECUTABLE_NAME)</string>
9
+ <key>CFBundleIdentifier</key>
10
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11
+ <key>CFBundleInfoDictionaryVersion</key>
12
+ <string>6.0</string>
13
+ <key>CFBundleName</key>
14
+ <string>$(PRODUCT_NAME)</string>
15
+ <key>CFBundlePackageType</key>
16
+ <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
17
+ <key>CFBundleShortVersionString</key>
18
+ <string>1.0</string>
19
+ <key>CFBundleVersion</key>
20
+ <string>1</string>
21
+ </dict>
22
+ </plist>
@@ -0,0 +1,39 @@
1
+ # AudioStudio iOS Unit Tests
2
+
3
+ This directory contains unit tests for the AudioStudio iOS module.
4
+
5
+ ## Test Files
6
+
7
+ - `AudioTestHelpers.swift` - Common test utilities and extensions
8
+ - `AudioFormatUtilsTests.swift` - Tests for audio format utilities
9
+ - `AudioFileHandlerTests.swift` - Tests for file handling
10
+ - `SimpleAudioTest.swift` - Basic audio functionality tests
11
+ - `TestAudioGenerator.swift` - Audio generation utilities for testing
12
+ - `CompressedOnlyOutputTests.swift` - Tests for compressed-only output feature (Issue #244)
13
+
14
+ ## Running Tests
15
+
16
+ ### In Xcode
17
+ 1. Open the workspace/project containing AudioStudio
18
+ 2. Select the test target
19
+ 3. Press `Cmd+U` to run all tests or click on individual test methods
20
+
21
+ ### From Command Line
22
+ ```bash
23
+ # Run all tests
24
+ xcodebuild test -scheme AudioStudioTests -destination 'platform=iOS Simulator,name=iPhone 15'
25
+
26
+ # Run specific test class
27
+ xcodebuild test -scheme AudioStudioTests -destination 'platform=iOS Simulator,name=iPhone 15' -only-testing:AudioStudioTests/CompressedOnlyOutputTests
28
+ ```
29
+
30
+ ## Compressed-Only Output Tests
31
+
32
+ The `CompressedOnlyOutputTests.swift` file tests the fix for Issue #244, ensuring that:
33
+ - Compression info is properly returned when primary output is disabled
34
+ - AAC format works correctly
35
+ - Opus format falls back to AAC on iOS
36
+ - Compressed file URIs are accessible
37
+ - File sizes and metadata are correctly reported
38
+
39
+ These tests verify that users can access compressed audio files even when primary WAV output is disabled.
@@ -0,0 +1,98 @@
1
+ import XCTest
2
+ import AVFoundation
3
+
4
+ class SimpleAudioTest: XCTestCase {
5
+
6
+ func testCreateWAVHeader() {
7
+ // Test creating a basic WAV header
8
+ let sampleRate = 44100
9
+ let channels = 2
10
+ let bitsPerSample = 16
11
+ let dataSize = 1024
12
+
13
+ // Calculate expected values
14
+ let byteRate = sampleRate * channels * (bitsPerSample / 8)
15
+ let blockAlign = channels * (bitsPerSample / 8)
16
+
17
+ // Create header data manually (44 bytes)
18
+ var header = Data()
19
+
20
+ // RIFF chunk
21
+ header.append("RIFF".data(using: .ascii)!)
22
+ var fileSize = UInt32(dataSize + 36).littleEndian
23
+ header.append(Data(bytes: &fileSize, count: 4))
24
+ header.append("WAVE".data(using: .ascii)!)
25
+
26
+ // fmt chunk
27
+ header.append("fmt ".data(using: .ascii)!)
28
+ var fmtSize = UInt32(16).littleEndian
29
+ header.append(Data(bytes: &fmtSize, count: 4))
30
+ var audioFormat = UInt16(1).littleEndian // PCM
31
+ header.append(Data(bytes: &audioFormat, count: 2))
32
+ var numChannels = UInt16(channels).littleEndian
33
+ header.append(Data(bytes: &numChannels, count: 2))
34
+ var sampleRateValue = UInt32(sampleRate).littleEndian
35
+ header.append(Data(bytes: &sampleRateValue, count: 4))
36
+ var byteRateValue = UInt32(byteRate).littleEndian
37
+ header.append(Data(bytes: &byteRateValue, count: 4))
38
+ var blockAlignValue = UInt16(blockAlign).littleEndian
39
+ header.append(Data(bytes: &blockAlignValue, count: 2))
40
+ var bitsPerSampleValue = UInt16(bitsPerSample).littleEndian
41
+ header.append(Data(bytes: &bitsPerSampleValue, count: 2))
42
+
43
+ // data chunk
44
+ header.append("data".data(using: .ascii)!)
45
+ var dataSizeValue = UInt32(dataSize).littleEndian
46
+ header.append(Data(bytes: &dataSizeValue, count: 4))
47
+
48
+ // Verify header size
49
+ XCTAssertEqual(header.count, 44, "WAV header should be 44 bytes")
50
+
51
+ // Verify RIFF header
52
+ let riffHeader = String(data: header[0..<4], encoding: .ascii)
53
+ XCTAssertEqual(riffHeader, "RIFF")
54
+
55
+ // Verify WAVE format
56
+ let waveFormat = String(data: header[8..<12], encoding: .ascii)
57
+ XCTAssertEqual(waveFormat, "WAVE")
58
+
59
+ print("✅ Basic WAV header test passed!")
60
+ }
61
+
62
+ func testSimpleAudioBuffer() {
63
+ // Test creating a simple audio buffer
64
+ let sampleRate = 44100.0
65
+ let duration = 0.1 // 100ms
66
+ let frequency = 440.0 // A4 note
67
+
68
+ let frameCount = Int(sampleRate * duration)
69
+ let format = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!
70
+
71
+ guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)) else {
72
+ XCTFail("Failed to create audio buffer")
73
+ return
74
+ }
75
+
76
+ buffer.frameLength = AVAudioFrameCount(frameCount)
77
+
78
+ // Generate a simple sine wave
79
+ let channelData = buffer.floatChannelData![0]
80
+ for frame in 0..<frameCount {
81
+ let phase = 2.0 * Double.pi * frequency * Double(frame) / sampleRate
82
+ channelData[frame] = Float(sin(phase) * 0.5)
83
+ }
84
+
85
+ // Verify buffer properties
86
+ XCTAssertEqual(buffer.frameLength, AVAudioFrameCount(frameCount))
87
+ XCTAssertEqual(buffer.format.sampleRate, sampleRate)
88
+ XCTAssertEqual(buffer.format.channelCount, 1)
89
+
90
+ // Verify we have audio data
91
+ let firstSample = channelData[0]
92
+ let lastSample = channelData[frameCount - 1]
93
+ XCTAssertNotEqual(firstSample, 0.0, accuracy: 0.001)
94
+ XCTAssertNotEqual(lastSample, firstSample, accuracy: 0.001)
95
+
96
+ print("✅ Simple audio buffer test passed!")
97
+ }
98
+ }