@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.
- package/CHANGELOG.md +535 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/android/build.gradle +143 -0
- package/android/src/androidTest/assets/chorus.wav +0 -0
- package/android/src/androidTest/assets/jfk.wav +0 -0
- package/android/src/androidTest/assets/osr_us_000_0010_8k.wav +0 -0
- package/android/src/androidTest/assets/recorder_hello_world.wav +0 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioProcessorInstrumentedTest.kt +197 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +541 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +234 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/AudioFocusStrategyIntegrationTest.kt +332 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/BufferDurationIntegrationTest.kt +324 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/CompressedOnlyOutputTest.kt +253 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/DeviceDisconnectionFallbackTest.kt +218 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/EventEmissionIntervalTest.kt +120 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/M4aFormatTest.kt +345 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/OutputControlIntegrationTest.kt +340 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/PcmStreamingDurationTest.kt +252 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/README.md +95 -0
- package/android/src/androidTest/java/net/siteed/audiostudio/integration/run_integration_tests.sh +43 -0
- package/android/src/main/AndroidManifest.xml +30 -0
- package/android/src/main/CMakeLists.txt +29 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioAnalysisData.kt +188 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioDataEncoder.kt +9 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioDeviceManager.kt +1741 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioFeaturesNative.kt +26 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioFileHandler.kt +136 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioFormatUtils.kt +354 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioNotificationsManager.kt +439 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +2237 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioRecorderManager.kt +2163 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioRecordingService.kt +167 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +1112 -0
- package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +1099 -0
- package/android/src/main/java/net/siteed/audiostudio/Constants.kt +37 -0
- package/android/src/main/java/net/siteed/audiostudio/EventSender.kt +7 -0
- package/android/src/main/java/net/siteed/audiostudio/FFT.kt +100 -0
- package/android/src/main/java/net/siteed/audiostudio/Features.kt +98 -0
- package/android/src/main/java/net/siteed/audiostudio/LogUtils.kt +93 -0
- package/android/src/main/java/net/siteed/audiostudio/MelSpectrogramNative.kt +36 -0
- package/android/src/main/java/net/siteed/audiostudio/NotificationConfig.kt +72 -0
- package/android/src/main/java/net/siteed/audiostudio/PermissionUtils.kt +68 -0
- package/android/src/main/java/net/siteed/audiostudio/RecordingActionReceiver.kt +59 -0
- package/android/src/main/java/net/siteed/audiostudio/RecordingConfig.kt +259 -0
- package/android/src/main/java/net/siteed/audiostudio/WaveformConfig.kt +19 -0
- package/android/src/main/java/net/siteed/audiostudio/WaveformRenderer.kt +159 -0
- package/android/src/main/jni/AudioFeaturesJNI.cpp +152 -0
- package/android/src/main/jni/MelSpectrogramJNI.cpp +165 -0
- package/android/src/main/res/drawable/ic_default_action_icon.xml +16 -0
- package/android/src/main/res/drawable/ic_microphone.xml +13 -0
- package/android/src/main/res/drawable/ic_pause.xml +10 -0
- package/android/src/main/res/drawable/ic_play.xml +10 -0
- package/android/src/main/res/drawable/ic_stop.xml +10 -0
- package/android/src/main/res/layout/notification_recording.xml +37 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFileHandlerTest.kt +279 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFocusStrategyTest.kt +249 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatTest.kt +151 -0
- package/android/src/test/java/net/siteed/audiostudio/AudioFormatUtilsTest.kt +273 -0
- package/android/src/test/java/net/siteed/audiostudio/DeviceDisconnectionFallbackUnitTest.kt +140 -0
- package/android/src/test/resources/chorus.wav +0 -0
- package/android/src/test/resources/generate_test_audio.py +94 -0
- package/android/src/test/resources/jfk.wav +0 -0
- package/android/src/test/resources/osr_us_000_0010_8k.wav +0 -0
- package/android/src/test/resources/recorder_hello_world.wav +0 -0
- package/app.plugin.js +3 -0
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js +4 -0
- package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +164 -0
- package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +213 -0
- package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractAudioData.js +21 -0
- package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractMelSpectrogram.js +90 -0
- package/build/cjs/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractPreview.js +28 -0
- package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -0
- package/build/cjs/AudioAnalysis/extractWaveform.js +18 -0
- package/build/cjs/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/cjs/AudioAnalysis/melSpectrogramWasm.js +149 -0
- package/build/cjs/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
- package/build/cjs/AudioDeviceManager.js +688 -0
- package/build/cjs/AudioDeviceManager.js.map +1 -0
- package/build/cjs/AudioRecorder.provider.js +78 -0
- package/build/cjs/AudioRecorder.provider.js.map +1 -0
- package/build/cjs/AudioStudio.native.js +8 -0
- package/build/cjs/AudioStudio.native.js.map +1 -0
- package/build/cjs/AudioStudio.types.js +11 -0
- package/build/cjs/AudioStudio.types.js.map +1 -0
- package/build/cjs/AudioStudio.web.js +708 -0
- package/build/cjs/AudioStudio.web.js.map +1 -0
- package/build/cjs/AudioStudioModule.js +718 -0
- package/build/cjs/AudioStudioModule.js.map +1 -0
- package/build/cjs/WebRecorder.web.js +865 -0
- package/build/cjs/WebRecorder.web.js.map +1 -0
- package/build/cjs/constants/platformLimitations.js +99 -0
- package/build/cjs/constants/platformLimitations.js.map +1 -0
- package/build/cjs/constants.js +20 -0
- package/build/cjs/constants.js.map +1 -0
- package/build/cjs/events.js +29 -0
- package/build/cjs/events.js.map +1 -0
- package/build/cjs/hooks/useAudioDevices.js +179 -0
- package/build/cjs/hooks/useAudioDevices.js.map +1 -0
- package/build/cjs/index.js +64 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/trimAudio.js +76 -0
- package/build/cjs/trimAudio.js.map +1 -0
- package/build/cjs/useAudioRecorder.js +535 -0
- package/build/cjs/useAudioRecorder.js.map +1 -0
- package/build/cjs/utils/BlobFix.js +502 -0
- package/build/cjs/utils/BlobFix.js.map +1 -0
- package/build/cjs/utils/audioProcessing.js +136 -0
- package/build/cjs/utils/audioProcessing.js.map +1 -0
- package/build/cjs/utils/cleanNativeOptions.js +22 -0
- package/build/cjs/utils/cleanNativeOptions.js.map +1 -0
- package/build/cjs/utils/concatenateBuffers.js +25 -0
- package/build/cjs/utils/concatenateBuffers.js.map +1 -0
- package/build/cjs/utils/convertPCMToFloat32.js +124 -0
- package/build/cjs/utils/convertPCMToFloat32.js.map +1 -0
- package/build/cjs/utils/crc32.js +52 -0
- package/build/cjs/utils/crc32.js.map +1 -0
- package/build/cjs/utils/encodingToBitDepth.js +17 -0
- package/build/cjs/utils/encodingToBitDepth.js.map +1 -0
- package/build/cjs/utils/getWavFileInfo.js +96 -0
- package/build/cjs/utils/getWavFileInfo.js.map +1 -0
- package/build/cjs/utils/writeWavHeader.js +88 -0
- package/build/cjs/utils/writeWavHeader.js.map +1 -0
- package/build/cjs/workers/InlineFeaturesExtractor.web.js +294 -0
- package/build/cjs/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/cjs/workers/inlineAudioWebWorker.web.js +190 -0
- package/build/cjs/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/build/cjs/workers/wasmGlueString.web.js +27 -0
- package/build/cjs/workers/wasmGlueString.web.js.map +1 -0
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js +3 -0
- package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -0
- package/build/esm/AudioAnalysis/audioFeaturesWasm.js +126 -0
- package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -0
- package/build/esm/AudioAnalysis/extractAudioAnalysis.js +205 -0
- package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -0
- package/build/esm/AudioAnalysis/extractAudioData.js +14 -0
- package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -0
- package/build/esm/AudioAnalysis/extractMelSpectrogram.js +86 -0
- package/build/esm/AudioAnalysis/extractMelSpectrogram.js.map +1 -0
- package/build/esm/AudioAnalysis/extractPreview.js +25 -0
- package/build/esm/AudioAnalysis/extractPreview.js.map +1 -0
- package/build/esm/AudioAnalysis/extractWaveform.js +11 -0
- package/build/esm/AudioAnalysis/extractWaveform.js.map +1 -0
- package/build/esm/AudioAnalysis/melSpectrogramWasm.js +111 -0
- package/build/esm/AudioAnalysis/melSpectrogramWasm.js.map +1 -0
- package/build/esm/AudioDeviceManager.js +681 -0
- package/build/esm/AudioDeviceManager.js.map +1 -0
- package/build/esm/AudioRecorder.provider.js +40 -0
- package/build/esm/AudioRecorder.provider.js.map +1 -0
- package/build/esm/AudioStudio.native.js +6 -0
- package/build/esm/AudioStudio.native.js.map +1 -0
- package/build/esm/AudioStudio.types.js +8 -0
- package/build/esm/AudioStudio.types.js.map +1 -0
- package/build/esm/AudioStudio.web.js +704 -0
- package/build/esm/AudioStudio.web.js.map +1 -0
- package/build/esm/AudioStudioModule.js +713 -0
- package/build/esm/AudioStudioModule.js.map +1 -0
- package/build/esm/WebRecorder.web.js +861 -0
- package/build/esm/WebRecorder.web.js.map +1 -0
- package/build/esm/constants/platformLimitations.js +90 -0
- package/build/esm/constants/platformLimitations.js.map +1 -0
- package/build/esm/constants.js +17 -0
- package/build/esm/constants.js.map +1 -0
- package/build/esm/events.js +21 -0
- package/build/esm/events.js.map +1 -0
- package/build/esm/hooks/useAudioDevices.js +176 -0
- package/build/esm/hooks/useAudioDevices.js.map +1 -0
- package/build/esm/index.js +23 -0
- package/build/esm/index.js.map +1 -0
- package/build/esm/trimAudio.js +69 -0
- package/build/esm/trimAudio.js.map +1 -0
- package/build/esm/useAudioRecorder.js +529 -0
- package/build/esm/useAudioRecorder.js.map +1 -0
- package/build/esm/utils/BlobFix.js +498 -0
- package/build/esm/utils/BlobFix.js.map +1 -0
- package/build/esm/utils/audioProcessing.js +133 -0
- package/build/esm/utils/audioProcessing.js.map +1 -0
- package/build/esm/utils/cleanNativeOptions.js +19 -0
- package/build/esm/utils/cleanNativeOptions.js.map +1 -0
- package/build/esm/utils/concatenateBuffers.js +21 -0
- package/build/esm/utils/concatenateBuffers.js.map +1 -0
- package/build/esm/utils/convertPCMToFloat32.js +120 -0
- package/build/esm/utils/convertPCMToFloat32.js.map +1 -0
- package/build/esm/utils/crc32.js +50 -0
- package/build/esm/utils/crc32.js.map +1 -0
- package/build/esm/utils/encodingToBitDepth.js +13 -0
- package/build/esm/utils/encodingToBitDepth.js.map +1 -0
- package/build/esm/utils/getWavFileInfo.js +92 -0
- package/build/esm/utils/getWavFileInfo.js.map +1 -0
- package/build/esm/utils/writeWavHeader.js +84 -0
- package/build/esm/utils/writeWavHeader.js.map +1 -0
- package/build/esm/workers/InlineFeaturesExtractor.web.js +291 -0
- package/build/esm/workers/InlineFeaturesExtractor.web.js.map +1 -0
- package/build/esm/workers/inlineAudioWebWorker.web.js +187 -0
- package/build/esm/workers/inlineAudioWebWorker.web.js.map +1 -0
- package/build/esm/workers/wasmGlueString.web.js +24 -0
- package/build/esm/workers/wasmGlueString.web.js.map +1 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +198 -0
- package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -0
- package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +24 -0
- package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts +74 -0
- package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractAudioData.d.ts +3 -0
- package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts +20 -0
- package/build/types/AudioAnalysis/extractMelSpectrogram.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractPreview.d.ts +11 -0
- package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -0
- package/build/types/AudioAnalysis/extractWaveform.d.ts +8 -0
- package/build/types/AudioAnalysis/extractWaveform.d.ts.map +1 -0
- package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts +16 -0
- package/build/types/AudioAnalysis/melSpectrogramWasm.d.ts.map +1 -0
- package/build/types/AudioDeviceManager.d.ts +187 -0
- package/build/types/AudioDeviceManager.d.ts.map +1 -0
- package/build/types/AudioRecorder.provider.d.ts +11 -0
- package/build/types/AudioRecorder.provider.d.ts.map +1 -0
- package/build/types/AudioStudio.native.d.ts +3 -0
- package/build/types/AudioStudio.native.d.ts.map +1 -0
- package/build/types/AudioStudio.types.d.ts +760 -0
- package/build/types/AudioStudio.types.d.ts.map +1 -0
- package/build/types/AudioStudio.web.d.ts +96 -0
- package/build/types/AudioStudio.web.d.ts.map +1 -0
- package/build/types/AudioStudioModule.d.ts +3 -0
- package/build/types/AudioStudioModule.d.ts.map +1 -0
- package/build/types/WebRecorder.web.d.ts +208 -0
- package/build/types/WebRecorder.web.d.ts.map +1 -0
- package/build/types/constants/platformLimitations.d.ts +40 -0
- package/build/types/constants/platformLimitations.d.ts.map +1 -0
- package/build/types/constants.d.ts +14 -0
- package/build/types/constants.d.ts.map +1 -0
- package/build/types/events.d.ts +29 -0
- package/build/types/events.d.ts.map +1 -0
- package/build/types/hooks/useAudioDevices.d.ts +15 -0
- package/build/types/hooks/useAudioDevices.d.ts.map +1 -0
- package/build/types/index.d.ts +21 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/trimAudio.d.ts +25 -0
- package/build/types/trimAudio.d.ts.map +1 -0
- package/build/types/useAudioRecorder.d.ts +22 -0
- package/build/types/useAudioRecorder.d.ts.map +1 -0
- package/build/types/utils/BlobFix.d.ts +9 -0
- package/build/types/utils/BlobFix.d.ts.map +1 -0
- package/build/types/utils/audioProcessing.d.ts +24 -0
- package/build/types/utils/audioProcessing.d.ts.map +1 -0
- package/build/types/utils/cleanNativeOptions.d.ts +15 -0
- package/build/types/utils/cleanNativeOptions.d.ts.map +1 -0
- package/build/types/utils/concatenateBuffers.d.ts +8 -0
- package/build/types/utils/concatenateBuffers.d.ts.map +1 -0
- package/build/types/utils/convertPCMToFloat32.d.ts +13 -0
- package/build/types/utils/convertPCMToFloat32.d.ts.map +1 -0
- package/build/types/utils/crc32.d.ts +7 -0
- package/build/types/utils/crc32.d.ts.map +1 -0
- package/build/types/utils/encodingToBitDepth.d.ts +5 -0
- package/build/types/utils/encodingToBitDepth.d.ts.map +1 -0
- package/build/types/utils/getWavFileInfo.d.ts +26 -0
- package/build/types/utils/getWavFileInfo.d.ts.map +1 -0
- package/build/types/utils/writeWavHeader.d.ts +34 -0
- package/build/types/utils/writeWavHeader.d.ts.map +1 -0
- package/build/types/workers/InlineFeaturesExtractor.web.d.ts +2 -0
- package/build/types/workers/InlineFeaturesExtractor.web.d.ts.map +1 -0
- package/build/types/workers/inlineAudioWebWorker.web.d.ts +2 -0
- package/build/types/workers/inlineAudioWebWorker.web.d.ts.map +1 -0
- package/build/types/workers/wasmGlueString.web.d.ts +2 -0
- package/build/types/workers/wasmGlueString.web.d.ts.map +1 -0
- package/cpp/AudioFeatures.cpp +274 -0
- package/cpp/AudioFeatures.h +85 -0
- package/cpp/AudioFeaturesBridge.cpp +146 -0
- package/cpp/AudioFeaturesBridge.h +47 -0
- package/cpp/MelSpectrogram.cpp +227 -0
- package/cpp/MelSpectrogram.h +82 -0
- package/cpp/MelSpectrogramBridge.cpp +112 -0
- package/cpp/MelSpectrogramBridge.h +33 -0
- package/cpp/kiss_fft/COPYING +11 -0
- package/cpp/kiss_fft/_kiss_fft_guts.h +167 -0
- package/cpp/kiss_fft/kiss_fft.c +424 -0
- package/cpp/kiss_fft/kiss_fft.h +160 -0
- package/cpp/kiss_fft/kiss_fft_log.h +36 -0
- package/cpp/kiss_fft/kiss_fftr.c +155 -0
- package/cpp/kiss_fft/kiss_fftr.h +54 -0
- package/expo-module.config.json +10 -0
- package/ios/AudioAnalysisData.swift +74 -0
- package/ios/AudioDeviceManager.swift +670 -0
- package/ios/AudioFeaturesWrapper.h +21 -0
- package/ios/AudioFeaturesWrapper.mm +63 -0
- package/ios/AudioNotificationManager.swift +154 -0
- package/ios/AudioProcessingHelpers.swift +797 -0
- package/ios/AudioProcessor.swift +1191 -0
- package/ios/AudioStreamError.swift +7 -0
- package/ios/AudioStreamManager.swift +2369 -0
- package/ios/AudioStreamManagerDelegate.swift +16 -0
- package/ios/AudioStudio.podspec +39 -0
- package/ios/AudioStudioModule.swift +1111 -0
- package/ios/AudioStudioTests/AudioFileHandlerTests.swift +338 -0
- package/ios/AudioStudioTests/AudioFormatUtilsTests.swift +331 -0
- package/ios/AudioStudioTests/AudioTestHelpers.swift +130 -0
- package/ios/AudioStudioTests/CompressedOnlyOutputTests.swift +294 -0
- package/ios/AudioStudioTests/EventEmissionIntervalTests.swift +105 -0
- package/ios/AudioStudioTests/Info.plist +22 -0
- package/ios/AudioStudioTests/README.md +39 -0
- package/ios/AudioStudioTests/SimpleAudioTest.swift +98 -0
- package/ios/AudioStudioTests/TestAudioGenerator.swift +75 -0
- package/ios/DataPoint.swift +54 -0
- package/ios/DecodingConfig.swift +59 -0
- package/ios/FFT.swift +62 -0
- package/ios/Features.swift +95 -0
- package/ios/ISSUE_IOS.md +68 -0
- package/ios/Logger.swift +39 -0
- package/ios/MelSpectrogramWrapper.h +30 -0
- package/ios/MelSpectrogramWrapper.mm +97 -0
- package/ios/NotificationExtension.swift +15 -0
- package/ios/RecordingResult.swift +22 -0
- package/ios/RecordingSettings.swift +311 -0
- package/ios/WaveformExtractor.swift +105 -0
- package/ios/tests/README.md +41 -0
- package/ios/tests/integration/buffer_and_fallback_test.swift +178 -0
- package/ios/tests/integration/buffer_duration_test.swift +185 -0
- package/ios/tests/integration/compressed_only_output_test.swift +271 -0
- package/ios/tests/integration/output_control_test.swift +322 -0
- package/ios/tests/integration/run_integration_tests.sh +37 -0
- package/ios/tests/opus_support_test_macos.swift +154 -0
- package/ios/tests/standalone/audio_processing_test.swift +144 -0
- package/ios/tests/standalone/audio_recording_test.swift +277 -0
- package/ios/tests/standalone/audio_streaming_test.swift +249 -0
- package/ios/tests/standalone/standalone_test.swift +144 -0
- package/package.json +146 -0
- package/plugin/build/index.cjs +194 -0
- package/plugin/build/index.d.cts +22 -0
- package/plugin/build/index.js +194 -0
- package/plugin/src/index.ts +285 -0
- package/plugin/tsconfig.json +10 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/prebuilt/wasm/mel-spectrogram.js +18 -0
- package/src/AudioAnalysis/AudioAnalysis.types.ts +226 -0
- package/src/AudioAnalysis/audio-features-wasm.d.ts +37 -0
- package/src/AudioAnalysis/audioFeaturesWasm.ts +200 -0
- package/src/AudioAnalysis/extractAudioAnalysis.ts +350 -0
- package/src/AudioAnalysis/extractAudioData.ts +17 -0
- package/src/AudioAnalysis/extractMelSpectrogram.ts +140 -0
- package/src/AudioAnalysis/extractPreview.ts +34 -0
- package/src/AudioAnalysis/extractWaveform.ts +22 -0
- package/src/AudioAnalysis/mel-spectrogram-wasm.d.ts +48 -0
- package/src/AudioAnalysis/melSpectrogramWasm.ts +179 -0
- package/src/AudioDeviceManager.ts +800 -0
- package/src/AudioRecorder.provider.tsx +57 -0
- package/src/AudioStudio.native.ts +6 -0
- package/src/AudioStudio.types.ts +899 -0
- package/src/AudioStudio.web.ts +911 -0
- package/src/AudioStudioModule.ts +984 -0
- package/src/WebRecorder.web.ts +1114 -0
- package/src/constants/platformLimitations.ts +118 -0
- package/src/constants.ts +21 -0
- package/src/events.ts +63 -0
- package/src/hooks/useAudioDevices.ts +213 -0
- package/src/index.ts +67 -0
- package/src/trimAudio.ts +94 -0
- package/src/types/crc-32.d.ts +9 -0
- package/src/useAudioRecorder.tsx +784 -0
- package/src/utils/BlobFix.ts +561 -0
- package/src/utils/audioProcessing.ts +205 -0
- package/src/utils/cleanNativeOptions.ts +18 -0
- package/src/utils/concatenateBuffers.ts +24 -0
- package/src/utils/convertPCMToFloat32.ts +170 -0
- package/src/utils/crc32.ts +59 -0
- package/src/utils/encodingToBitDepth.ts +18 -0
- package/src/utils/getWavFileInfo.ts +132 -0
- package/src/utils/writeWavHeader.ts +115 -0
- package/src/workers/InlineFeaturesExtractor.web.tsx +291 -0
- package/src/workers/inlineAudioWebWorker.web.tsx +186 -0
- 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()
|