@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,277 @@
1
+ #!/usr/bin/env swift
2
+
3
+ import Foundation
4
+ import AVFoundation
5
+
6
+ // Simple test framework
7
+ struct TestResult {
8
+ let name: String
9
+ let passed: Bool
10
+ let message: String
11
+ }
12
+
13
+ class AudioRecordingTest {
14
+ var results: [TestResult] = []
15
+ let testDir: URL
16
+ var audioRecorder: AVAudioRecorder?
17
+
18
+ init() {
19
+ // Create a temporary directory for test files
20
+ let tempDir = FileManager.default.temporaryDirectory
21
+ testDir = tempDir.appendingPathComponent("audio_recording_test_\(UUID().uuidString)")
22
+ try? FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)
23
+ }
24
+
25
+ deinit {
26
+ // Clean up test directory
27
+ try? FileManager.default.removeItem(at: testDir)
28
+ }
29
+
30
+ func assert(_ condition: Bool, _ message: String, file: String = #file, line: Int = #line) {
31
+ let testName = "\(file.split(separator: "/").last ?? ""):\(line)"
32
+ results.append(TestResult(name: testName, passed: condition, message: message))
33
+ if !condition {
34
+ print("โŒ FAILED: \(message) at \(testName)")
35
+ }
36
+ }
37
+
38
+ func assertEqual<T: Equatable>(_ a: T, _ b: T, _ message: String = "", file: String = #file, line: Int = #line) {
39
+ let passed = a == b
40
+ let msg = message.isEmpty ? "\(a) should equal \(b)" : message
41
+ assert(passed, msg, file: file, line: line)
42
+ }
43
+
44
+ func run() {
45
+ print("๐Ÿงช Running iOS Audio Recording Tests...\n")
46
+
47
+ // Request permission first (in a real app)
48
+ setupAudioSession()
49
+
50
+ testBasicWAVRecording()
51
+ testCompressedRecording()
52
+ testRecordingSettings()
53
+ testFileValidation()
54
+
55
+ // Print summary
56
+ let passed = results.filter { $0.passed }.count
57
+ let total = results.count
58
+
59
+ print("\n๐Ÿ“Š Test Summary:")
60
+ print(" Total: \(total)")
61
+ print(" Passed: \(passed)")
62
+ print(" Failed: \(total - passed)")
63
+
64
+ if passed == total {
65
+ print("\nโœ… All tests passed!")
66
+ } else {
67
+ print("\nโŒ Some tests failed!")
68
+ exit(1)
69
+ }
70
+ }
71
+
72
+ func setupAudioSession() {
73
+ #if os(iOS)
74
+ let session = AVAudioSession.sharedInstance()
75
+ do {
76
+ try session.setCategory(.playAndRecord, mode: .default)
77
+ try session.setActive(true)
78
+ print("โœ“ Audio session configured")
79
+ } catch {
80
+ print("โš ๏ธ Failed to setup audio session: \(error)")
81
+ }
82
+ #else
83
+ print("โœ“ Audio session setup skipped (macOS)")
84
+ #endif
85
+ }
86
+
87
+ func testBasicWAVRecording() {
88
+ print("\nTesting basic WAV recording...")
89
+
90
+ let wavURL = testDir.appendingPathComponent("test_recording.wav")
91
+
92
+ // Configure recording settings for WAV
93
+ let settings: [String: Any] = [
94
+ AVFormatIDKey: Int(kAudioFormatLinearPCM),
95
+ AVSampleRateKey: 44100.0,
96
+ AVNumberOfChannelsKey: 2,
97
+ AVLinearPCMBitDepthKey: 16,
98
+ AVLinearPCMIsBigEndianKey: false,
99
+ AVLinearPCMIsFloatKey: false
100
+ ]
101
+
102
+ do {
103
+ // Create recorder
104
+ audioRecorder = try AVAudioRecorder(url: wavURL, settings: settings)
105
+ assert(audioRecorder != nil, "Recorder should be created")
106
+
107
+ // Prepare and record
108
+ let prepared = audioRecorder!.prepareToRecord()
109
+ assert(prepared, "Recorder should prepare successfully")
110
+
111
+ let started = audioRecorder!.record()
112
+ assert(started, "Recording should start")
113
+
114
+ // Record for a short time
115
+ Thread.sleep(forTimeInterval: 0.5)
116
+
117
+ audioRecorder!.stop()
118
+
119
+ // Verify file exists and has content
120
+ assert(FileManager.default.fileExists(atPath: wavURL.path), "WAV file should exist")
121
+
122
+ let attributes = try FileManager.default.attributesOfItem(atPath: wavURL.path)
123
+ let fileSize = attributes[.size] as? Int64 ?? 0
124
+ assert(fileSize > 44, "WAV file should have content beyond header")
125
+
126
+ // Verify WAV header
127
+ let data = try Data(contentsOf: wavURL)
128
+ let riffHeader = String(data: data[0..<4], encoding: .ascii)
129
+ assertEqual(riffHeader, "RIFF", "Should have RIFF header")
130
+
131
+ let waveFormat = String(data: data[8..<12], encoding: .ascii)
132
+ assertEqual(waveFormat, "WAVE", "Should have WAVE format")
133
+
134
+ print("โœ“ Basic WAV recording test completed")
135
+
136
+ } catch {
137
+ assert(false, "Recording failed: \(error)")
138
+ }
139
+ }
140
+
141
+ func testCompressedRecording() {
142
+ print("\nTesting compressed recording (AAC)...")
143
+
144
+ let aacURL = testDir.appendingPathComponent("test_recording.m4a")
145
+
146
+ // Configure recording settings for AAC
147
+ let settings: [String: Any] = [
148
+ AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
149
+ AVSampleRateKey: 44100.0,
150
+ AVNumberOfChannelsKey: 2,
151
+ AVEncoderBitRateKey: 128000,
152
+ AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
153
+ ]
154
+
155
+ do {
156
+ // Create recorder
157
+ audioRecorder = try AVAudioRecorder(url: aacURL, settings: settings)
158
+ assert(audioRecorder != nil, "AAC recorder should be created")
159
+
160
+ // Record
161
+ let started = audioRecorder!.record()
162
+ assert(started, "AAC recording should start")
163
+
164
+ Thread.sleep(forTimeInterval: 0.5)
165
+ audioRecorder!.stop()
166
+
167
+ // Verify file
168
+ assert(FileManager.default.fileExists(atPath: aacURL.path), "AAC file should exist")
169
+
170
+ let attributes = try FileManager.default.attributesOfItem(atPath: aacURL.path)
171
+ let fileSize = attributes[.size] as? Int64 ?? 0
172
+ assert(fileSize > 0, "AAC file should have content")
173
+
174
+ // Verify it's a valid audio file by loading it
175
+ let audioFile = try AVAudioFile(forReading: aacURL)
176
+ assert(audioFile.length > 0, "AAC file should have audio frames")
177
+ assertEqual(Int(audioFile.fileFormat.sampleRate), 44100, "Sample rate should match")
178
+
179
+ print("โœ“ Compressed recording test completed")
180
+
181
+ } catch {
182
+ assert(false, "AAC recording failed: \(error)")
183
+ }
184
+ }
185
+
186
+ func testRecordingSettings() {
187
+ print("\nTesting various recording settings...")
188
+
189
+ // Test different sample rates
190
+ let sampleRates = [8000.0, 16000.0, 44100.0, 48000.0]
191
+
192
+ for sampleRate in sampleRates {
193
+ let url = testDir.appendingPathComponent("test_\(Int(sampleRate))hz.wav")
194
+
195
+ let settings: [String: Any] = [
196
+ AVFormatIDKey: Int(kAudioFormatLinearPCM),
197
+ AVSampleRateKey: sampleRate,
198
+ AVNumberOfChannelsKey: 1,
199
+ AVLinearPCMBitDepthKey: 16,
200
+ AVLinearPCMIsBigEndianKey: false,
201
+ AVLinearPCMIsFloatKey: false
202
+ ]
203
+
204
+ do {
205
+ let recorder = try AVAudioRecorder(url: url, settings: settings)
206
+ assert(recorder.prepareToRecord(), "Should prepare at \(sampleRate)Hz")
207
+
208
+ // Verify settings were applied
209
+ let appliedSettings = recorder.settings
210
+ let appliedRate = appliedSettings[AVSampleRateKey] as? Double ?? 0
211
+ assertEqual(appliedRate, sampleRate, "Sample rate should be \(sampleRate)")
212
+
213
+ } catch {
214
+ assert(false, "Failed to create recorder at \(sampleRate)Hz: \(error)")
215
+ }
216
+ }
217
+
218
+ print("โœ“ Recording settings test completed")
219
+ }
220
+
221
+ func testFileValidation() {
222
+ print("\nTesting file validation and properties...")
223
+
224
+ // Create a test recording
225
+ let url = testDir.appendingPathComponent("validation_test.wav")
226
+ let duration = 1.0 // 1 second
227
+
228
+ let settings: [String: Any] = [
229
+ AVFormatIDKey: Int(kAudioFormatLinearPCM),
230
+ AVSampleRateKey: 16000.0,
231
+ AVNumberOfChannelsKey: 1,
232
+ AVLinearPCMBitDepthKey: 16,
233
+ AVLinearPCMIsBigEndianKey: false,
234
+ AVLinearPCMIsFloatKey: false
235
+ ]
236
+
237
+ do {
238
+ audioRecorder = try AVAudioRecorder(url: url, settings: settings)
239
+ audioRecorder!.record()
240
+
241
+ // Record for the specified duration
242
+ Thread.sleep(forTimeInterval: duration)
243
+ audioRecorder!.stop()
244
+
245
+ // Load and validate the file
246
+ let audioFile = try AVAudioFile(forReading: url)
247
+
248
+ // Check duration (should be close to 1 second)
249
+ let recordedDuration = Double(audioFile.length) / audioFile.fileFormat.sampleRate
250
+ assert(abs(recordedDuration - duration) < 0.5, "Duration should be close to \(duration)s (got \(recordedDuration)s)")
251
+
252
+ // Check file format
253
+ assertEqual(Int(audioFile.fileFormat.sampleRate), 16000, "Sample rate should be 16kHz")
254
+ assertEqual(Int(audioFile.fileFormat.channelCount), 1, "Should be mono")
255
+
256
+ // Calculate expected file size
257
+ let expectedDataSize = Int(16000 * duration * 2) // 16kHz * 1s * 2 bytes per sample
258
+ let expectedFileSize = expectedDataSize + 44 // Plus WAV header
259
+
260
+ let attributes = try FileManager.default.attributesOfItem(atPath: url.path)
261
+ let actualFileSize = attributes[.size] as? Int64 ?? 0
262
+
263
+ // Allow some tolerance (macOS may add extra metadata)
264
+ assert(abs(Int(actualFileSize) - expectedFileSize) < 5000,
265
+ "File size should be close to expected (\(actualFileSize) vs \(expectedFileSize))")
266
+
267
+ print("โœ“ File validation test completed")
268
+
269
+ } catch {
270
+ assert(false, "File validation failed: \(error)")
271
+ }
272
+ }
273
+ }
274
+
275
+ // Run the tests
276
+ let test = AudioRecordingTest()
277
+ test.run()
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env swift
2
+
3
+ import Foundation
4
+ import AVFoundation
5
+
6
+ // Simple test framework
7
+ struct TestResult {
8
+ let name: String
9
+ let passed: Bool
10
+ let message: String
11
+ }
12
+
13
+ class AudioStreamingTest {
14
+ var results: [TestResult] = []
15
+ let testDir: URL
16
+ var audioEngine: AVAudioEngine?
17
+ var inputNode: AVAudioInputNode?
18
+
19
+ init() {
20
+ // Create a temporary directory for test files
21
+ let tempDir = FileManager.default.temporaryDirectory
22
+ testDir = tempDir.appendingPathComponent("audio_streaming_test_\(UUID().uuidString)")
23
+ try? FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)
24
+ }
25
+
26
+ deinit {
27
+ // Clean up
28
+ audioEngine?.stop()
29
+ try? FileManager.default.removeItem(at: testDir)
30
+ }
31
+
32
+ func assert(_ condition: Bool, _ message: String, file: String = #file, line: Int = #line) {
33
+ let testName = "\(file.split(separator: "/").last ?? ""):\(line)"
34
+ results.append(TestResult(name: testName, passed: condition, message: message))
35
+ if !condition {
36
+ print("โŒ FAILED: \(message) at \(testName)")
37
+ }
38
+ }
39
+
40
+ func assertEqual<T: Equatable>(_ a: T, _ b: T, _ message: String = "", file: String = #file, line: Int = #line) {
41
+ let passed = a == b
42
+ let msg = message.isEmpty ? "\(a) should equal \(b)" : message
43
+ assert(passed, msg, file: file, line: line)
44
+ }
45
+
46
+ func run() {
47
+ print("๐Ÿงช Running iOS Audio Streaming Tests...\n")
48
+
49
+ testAudioEngineSetup()
50
+ testRealtimeStreaming()
51
+ testBufferProcessing()
52
+ testMultipleFormats()
53
+
54
+ // Print summary
55
+ let passed = results.filter { $0.passed }.count
56
+ let total = results.count
57
+
58
+ print("\n๐Ÿ“Š Test Summary:")
59
+ print(" Total: \(total)")
60
+ print(" Passed: \(passed)")
61
+ print(" Failed: \(total - passed)")
62
+
63
+ if passed == total {
64
+ print("\nโœ… All tests passed!")
65
+ } else {
66
+ print("\nโŒ Some tests failed!")
67
+ exit(1)
68
+ }
69
+ }
70
+
71
+ func testAudioEngineSetup() {
72
+ print("Testing AVAudioEngine setup...")
73
+
74
+ audioEngine = AVAudioEngine()
75
+ inputNode = audioEngine!.inputNode
76
+
77
+ assert(audioEngine != nil, "Audio engine should be created")
78
+ assert(inputNode != nil, "Input node should be available")
79
+
80
+ // Check input format
81
+ let inputFormat = inputNode!.inputFormat(forBus: 0)
82
+ assert(inputFormat.sampleRate > 0, "Input format should have valid sample rate")
83
+ assert(inputFormat.channelCount > 0, "Input format should have channels")
84
+
85
+ print(" Sample rate: \(inputFormat.sampleRate) Hz")
86
+ print(" Channels: \(inputFormat.channelCount)")
87
+ print(" Format: \(inputFormat.commonFormat.rawValue)")
88
+
89
+ print("โœ“ Audio engine setup test completed")
90
+ }
91
+
92
+ func testRealtimeStreaming() {
93
+ print("\nTesting real-time audio streaming...")
94
+
95
+ guard let engine = audioEngine, let input = inputNode else {
96
+ assert(false, "Audio engine not initialized")
97
+ return
98
+ }
99
+
100
+ var bufferCount = 0
101
+ var totalFrames: AVAudioFrameCount = 0
102
+ let expectation = DispatchSemaphore(value: 0)
103
+
104
+ // Install tap on input
105
+ let format = input.outputFormat(forBus: 0)
106
+ input.installTap(onBus: 0, bufferSize: 1024, format: format) { buffer, time in
107
+ bufferCount += 1
108
+ totalFrames += buffer.frameLength
109
+
110
+ // Stop after collecting some buffers
111
+ if bufferCount >= 10 {
112
+ expectation.signal()
113
+ }
114
+ }
115
+
116
+ do {
117
+ try engine.start()
118
+ assert(engine.isRunning, "Engine should be running")
119
+
120
+ // Wait for buffers
121
+ let timeout = expectation.wait(timeout: .now() + 2.0)
122
+ assert(timeout == .success, "Should receive audio buffers within timeout")
123
+
124
+ engine.stop()
125
+ input.removeTap(onBus: 0)
126
+
127
+ assert(bufferCount >= 10, "Should have received at least 10 buffers")
128
+ assert(totalFrames > 0, "Should have received audio frames")
129
+
130
+ print(" Received \(bufferCount) buffers")
131
+ print(" Total frames: \(totalFrames)")
132
+
133
+ print("โœ“ Real-time streaming test completed")
134
+
135
+ } catch {
136
+ assert(false, "Failed to start audio engine: \(error)")
137
+ }
138
+ }
139
+
140
+ func testBufferProcessing() {
141
+ print("\nTesting buffer processing...")
142
+
143
+ guard let engine = audioEngine, let input = inputNode else {
144
+ assert(false, "Audio engine not initialized")
145
+ return
146
+ }
147
+
148
+ var processedBuffers = 0
149
+ var maxAmplitude: Float = 0
150
+ var totalRMS: Float = 0
151
+ let expectation = DispatchSemaphore(value: 0)
152
+
153
+ let format = input.outputFormat(forBus: 0)
154
+ input.installTap(onBus: 0, bufferSize: 2048, format: format) { buffer, time in
155
+ // Process buffer
156
+ if let channelData = buffer.floatChannelData {
157
+ let frameLength = Int(buffer.frameLength)
158
+ let channelCount = Int(buffer.format.channelCount)
159
+
160
+ for channel in 0..<channelCount {
161
+ let samples = channelData[channel]
162
+
163
+ // Find max amplitude
164
+ for i in 0..<frameLength {
165
+ let amplitude = abs(samples[i])
166
+ if amplitude > maxAmplitude {
167
+ maxAmplitude = amplitude
168
+ }
169
+ }
170
+
171
+ // Calculate RMS
172
+ var sum: Float = 0
173
+ for i in 0..<frameLength {
174
+ sum += samples[i] * samples[i]
175
+ }
176
+ let rms = sqrt(sum / Float(frameLength))
177
+ totalRMS += rms
178
+ }
179
+ }
180
+
181
+ processedBuffers += 1
182
+ if processedBuffers >= 5 {
183
+ expectation.signal()
184
+ }
185
+ }
186
+
187
+ do {
188
+ try engine.start()
189
+
190
+ let timeout = expectation.wait(timeout: .now() + 2.0)
191
+ assert(timeout == .success, "Should process buffers within timeout")
192
+
193
+ engine.stop()
194
+ input.removeTap(onBus: 0)
195
+
196
+ assert(processedBuffers >= 5, "Should have processed at least 5 buffers")
197
+ // Note: maxAmplitude might be 0 if there's silence
198
+ print(" Processed buffers: \(processedBuffers)")
199
+ print(" Max amplitude: \(maxAmplitude)")
200
+ print(" Average RMS: \(totalRMS / Float(processedBuffers))")
201
+
202
+ print("โœ“ Buffer processing test completed")
203
+
204
+ } catch {
205
+ assert(false, "Failed to process buffers: \(error)")
206
+ }
207
+ }
208
+
209
+ func testMultipleFormats() {
210
+ print("\nTesting multiple audio formats...")
211
+
212
+ // Test creating different format converters
213
+ let sampleRates = [8000.0, 16000.0, 44100.0, 48000.0]
214
+
215
+ for sampleRate in sampleRates {
216
+ // Create a format
217
+ guard let format = AVAudioFormat(
218
+ commonFormat: .pcmFormatFloat32,
219
+ sampleRate: sampleRate,
220
+ channels: 1,
221
+ interleaved: false
222
+ ) else {
223
+ assert(false, "Failed to create format at \(sampleRate)Hz")
224
+ continue
225
+ }
226
+
227
+ assert(format.sampleRate == sampleRate, "Format should have correct sample rate")
228
+ assert(format.channelCount == 1, "Format should be mono")
229
+ assert(format.commonFormat == .pcmFormatFloat32, "Format should be float32")
230
+
231
+ // Test creating a buffer with this format
232
+ guard let buffer = AVAudioPCMBuffer(
233
+ pcmFormat: format,
234
+ frameCapacity: AVAudioFrameCount(sampleRate * 0.1) // 100ms
235
+ ) else {
236
+ assert(false, "Failed to create buffer at \(sampleRate)Hz")
237
+ continue
238
+ }
239
+
240
+ assert(buffer.format.sampleRate == sampleRate, "Buffer format should match")
241
+ }
242
+
243
+ print("โœ“ Multiple formats test completed")
244
+ }
245
+ }
246
+
247
+ // Run the tests
248
+ let test = AudioStreamingTest()
249
+ test.run()
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env swift
2
+
3
+ import Foundation
4
+ import AVFoundation
5
+
6
+ // Simple test framework
7
+ struct TestResult {
8
+ let name: String
9
+ let passed: Bool
10
+ let message: String
11
+ }
12
+
13
+ class SimpleTest {
14
+ var results: [TestResult] = []
15
+
16
+ func assert(_ condition: Bool, _ message: String, file: String = #file, line: Int = #line) {
17
+ let testName = "\(file.split(separator: "/").last ?? ""):\(line)"
18
+ results.append(TestResult(name: testName, passed: condition, message: message))
19
+ if !condition {
20
+ print("โŒ FAILED: \(message) at \(testName)")
21
+ }
22
+ }
23
+
24
+ func assertEqual<T: Equatable>(_ a: T, _ b: T, _ message: String = "", file: String = #file, line: Int = #line) {
25
+ let passed = a == b
26
+ let msg = message.isEmpty ? "\(a) should equal \(b)" : message
27
+ assert(passed, msg, file: file, line: line)
28
+ }
29
+
30
+ func run() {
31
+ print("๐Ÿงช Running iOS Audio Tests...\n")
32
+
33
+ testWAVHeader()
34
+ testAudioBuffer()
35
+
36
+ // Print summary
37
+ let passed = results.filter { $0.passed }.count
38
+ let total = results.count
39
+
40
+ print("\n๐Ÿ“Š Test Summary:")
41
+ print(" Total: \(total)")
42
+ print(" Passed: \(passed)")
43
+ print(" Failed: \(total - passed)")
44
+
45
+ if passed == total {
46
+ print("\nโœ… All tests passed!")
47
+ } else {
48
+ print("\nโŒ Some tests failed!")
49
+ exit(1)
50
+ }
51
+ }
52
+
53
+ func testWAVHeader() {
54
+ print("Testing WAV header creation...")
55
+
56
+ let sampleRate = 44100
57
+ let channels = 2
58
+ let bitsPerSample = 16
59
+ let dataSize = 1024
60
+
61
+ // Create header
62
+ var header = Data()
63
+
64
+ // RIFF chunk
65
+ header.append("RIFF".data(using: .ascii)!)
66
+ var fileSize = UInt32(dataSize + 36).littleEndian
67
+ header.append(Data(bytes: &fileSize, count: 4))
68
+ header.append("WAVE".data(using: .ascii)!)
69
+
70
+ // fmt chunk
71
+ header.append("fmt ".data(using: .ascii)!)
72
+ var fmtSize = UInt32(16).littleEndian
73
+ header.append(Data(bytes: &fmtSize, count: 4))
74
+ var audioFormat = UInt16(1).littleEndian
75
+ header.append(Data(bytes: &audioFormat, count: 2))
76
+ var numChannels = UInt16(channels).littleEndian
77
+ header.append(Data(bytes: &numChannels, count: 2))
78
+ var sampleRateValue = UInt32(sampleRate).littleEndian
79
+ header.append(Data(bytes: &sampleRateValue, count: 4))
80
+ let byteRate = sampleRate * channels * (bitsPerSample / 8)
81
+ var byteRateValue = UInt32(byteRate).littleEndian
82
+ header.append(Data(bytes: &byteRateValue, count: 4))
83
+ let blockAlign = channels * (bitsPerSample / 8)
84
+ var blockAlignValue = UInt16(blockAlign).littleEndian
85
+ header.append(Data(bytes: &blockAlignValue, count: 2))
86
+ var bitsPerSampleValue = UInt16(bitsPerSample).littleEndian
87
+ header.append(Data(bytes: &bitsPerSampleValue, count: 2))
88
+
89
+ // data chunk
90
+ header.append("data".data(using: .ascii)!)
91
+ var dataSizeValue = UInt32(dataSize).littleEndian
92
+ header.append(Data(bytes: &dataSizeValue, count: 4))
93
+
94
+ // Tests
95
+ assertEqual(header.count, 44, "WAV header should be 44 bytes")
96
+
97
+ let riffHeader = String(data: header[0..<4], encoding: .ascii)
98
+ assertEqual(riffHeader, "RIFF", "Should have RIFF header")
99
+
100
+ let waveFormat = String(data: header[8..<12], encoding: .ascii)
101
+ assertEqual(waveFormat, "WAVE", "Should have WAVE format")
102
+
103
+ print("โœ“ WAV header test completed")
104
+ }
105
+
106
+ func testAudioBuffer() {
107
+ print("\nTesting audio buffer creation...")
108
+
109
+ let sampleRate = 44100.0
110
+ let duration = 0.1
111
+ let frequency = 440.0
112
+
113
+ let frameCount = Int(sampleRate * duration)
114
+ let format = AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!
115
+
116
+ guard let buffer = AVAudioPCMBuffer(pcmFormat: format, frameCapacity: AVAudioFrameCount(frameCount)) else {
117
+ assert(false, "Failed to create audio buffer")
118
+ return
119
+ }
120
+
121
+ buffer.frameLength = AVAudioFrameCount(frameCount)
122
+
123
+ // Generate sine wave
124
+ let channelData = buffer.floatChannelData![0]
125
+ for frame in 0..<frameCount {
126
+ let phase = 2.0 * Double.pi * frequency * Double(frame) / sampleRate
127
+ channelData[frame] = Float(sin(phase) * 0.5)
128
+ }
129
+
130
+ // Tests
131
+ assertEqual(Int(buffer.frameLength), frameCount, "Frame length should match")
132
+ assertEqual(buffer.format.sampleRate, sampleRate, "Sample rate should match")
133
+ assertEqual(Int(buffer.format.channelCount), 1, "Should have 1 channel")
134
+
135
+ let middleSample = channelData[frameCount / 4]
136
+ assert(abs(middleSample) > 0.001, "Middle sample should not be zero")
137
+
138
+ print("โœ“ Audio buffer test completed")
139
+ }
140
+ }
141
+
142
+ // Run the tests
143
+ let test = SimpleTest()
144
+ test.run()