@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,322 @@
|
|
|
1
|
+
#!/usr/bin/env swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import AVFoundation
|
|
5
|
+
|
|
6
|
+
// Integration test for Output Control feature
|
|
7
|
+
// This tests the ACTUAL behavior of the output configuration in real scenarios
|
|
8
|
+
|
|
9
|
+
print("๐งช Output Control Integration Test")
|
|
10
|
+
print("==================================\n")
|
|
11
|
+
|
|
12
|
+
class OutputControlTest {
|
|
13
|
+
let testDir: URL
|
|
14
|
+
var results: [(name: String, passed: Bool, message: String)] = []
|
|
15
|
+
|
|
16
|
+
init() {
|
|
17
|
+
let tempDir = FileManager.default.temporaryDirectory
|
|
18
|
+
testDir = tempDir.appendingPathComponent("output_control_test_\(UUID().uuidString)")
|
|
19
|
+
try? FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
deinit {
|
|
23
|
+
try? FileManager.default.removeItem(at: testDir)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func runAllTests() {
|
|
27
|
+
testDefaultOutput()
|
|
28
|
+
testPrimaryOnlyOutput()
|
|
29
|
+
testCompressedOnlyOutput()
|
|
30
|
+
testBothOutputs()
|
|
31
|
+
testNoOutputs()
|
|
32
|
+
printResults()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func testDefaultOutput() {
|
|
36
|
+
print("Test 1: Default Output (Primary Only)")
|
|
37
|
+
print("-------------------------------------")
|
|
38
|
+
|
|
39
|
+
let fileURL = testDir.appendingPathComponent("default_recording.wav")
|
|
40
|
+
|
|
41
|
+
// Simulate default recording (primary enabled, compressed disabled)
|
|
42
|
+
let _ = createMockRecording(
|
|
43
|
+
primaryURL: fileURL,
|
|
44
|
+
compressedURL: nil,
|
|
45
|
+
primaryEnabled: true,
|
|
46
|
+
compressedEnabled: false
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
let fileExists = FileManager.default.fileExists(atPath: fileURL.path)
|
|
50
|
+
var fileSize: Int64 = 0
|
|
51
|
+
|
|
52
|
+
if fileExists {
|
|
53
|
+
if let attributes = try? FileManager.default.attributesOfItem(atPath: fileURL.path) {
|
|
54
|
+
fileSize = attributes[.size] as? Int64 ?? 0
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let passed = fileExists && fileSize > 44 // More than just header
|
|
59
|
+
results.append((
|
|
60
|
+
name: "Default Output",
|
|
61
|
+
passed: passed,
|
|
62
|
+
message: "Primary file created: \(fileExists), Size: \(fileSize) bytes"
|
|
63
|
+
))
|
|
64
|
+
|
|
65
|
+
print("โ Primary file created: \(fileURL.lastPathComponent)")
|
|
66
|
+
print("โ File size: \(fileSize) bytes\n")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func testPrimaryOnlyOutput() {
|
|
70
|
+
print("Test 2: Primary Output Only")
|
|
71
|
+
print("---------------------------")
|
|
72
|
+
|
|
73
|
+
let primaryURL = testDir.appendingPathComponent("primary_only.wav")
|
|
74
|
+
let compressedURL = testDir.appendingPathComponent("should_not_exist.aac")
|
|
75
|
+
|
|
76
|
+
// Simulate primary only
|
|
77
|
+
let _ = createMockRecording(
|
|
78
|
+
primaryURL: primaryURL,
|
|
79
|
+
compressedURL: compressedURL,
|
|
80
|
+
primaryEnabled: true,
|
|
81
|
+
compressedEnabled: false
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
85
|
+
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
86
|
+
|
|
87
|
+
let passed = primaryExists && !compressedExists
|
|
88
|
+
results.append((
|
|
89
|
+
name: "Primary Only",
|
|
90
|
+
passed: passed,
|
|
91
|
+
message: "Primary: \(primaryExists), Compressed: \(compressedExists)"
|
|
92
|
+
))
|
|
93
|
+
|
|
94
|
+
print("โ Primary file exists: \(primaryExists)")
|
|
95
|
+
print("โ Compressed file exists: \(compressedExists)")
|
|
96
|
+
print("โ Primary-only output working correctly\n")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func testCompressedOnlyOutput() {
|
|
100
|
+
print("Test 3: Compressed Output Only")
|
|
101
|
+
print("------------------------------")
|
|
102
|
+
|
|
103
|
+
let primaryURL = testDir.appendingPathComponent("should_not_exist.wav")
|
|
104
|
+
let compressedURL = testDir.appendingPathComponent("compressed_only.aac")
|
|
105
|
+
|
|
106
|
+
// Simulate compressed only
|
|
107
|
+
let _ = createMockRecording(
|
|
108
|
+
primaryURL: primaryURL,
|
|
109
|
+
compressedURL: compressedURL,
|
|
110
|
+
primaryEnabled: false,
|
|
111
|
+
compressedEnabled: true
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
115
|
+
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
116
|
+
|
|
117
|
+
let passed = !primaryExists && compressedExists
|
|
118
|
+
results.append((
|
|
119
|
+
name: "Compressed Only",
|
|
120
|
+
passed: passed,
|
|
121
|
+
message: "Primary: \(primaryExists), Compressed: \(compressedExists)"
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
print("โ Primary file exists: \(primaryExists)")
|
|
125
|
+
print("โ Compressed file exists: \(compressedExists)")
|
|
126
|
+
print("โ Compressed-only output working correctly\n")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func testBothOutputs() {
|
|
130
|
+
print("Test 4: Both Outputs Enabled")
|
|
131
|
+
print("----------------------------")
|
|
132
|
+
|
|
133
|
+
let primaryURL = testDir.appendingPathComponent("both_primary.wav")
|
|
134
|
+
let compressedURL = testDir.appendingPathComponent("both_compressed.aac")
|
|
135
|
+
|
|
136
|
+
// Simulate both outputs
|
|
137
|
+
let _ = createMockRecording(
|
|
138
|
+
primaryURL: primaryURL,
|
|
139
|
+
compressedURL: compressedURL,
|
|
140
|
+
primaryEnabled: true,
|
|
141
|
+
compressedEnabled: true
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
145
|
+
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
146
|
+
|
|
147
|
+
let passed = primaryExists && compressedExists
|
|
148
|
+
results.append((
|
|
149
|
+
name: "Both Outputs",
|
|
150
|
+
passed: passed,
|
|
151
|
+
message: "Primary: \(primaryExists), Compressed: \(compressedExists)"
|
|
152
|
+
))
|
|
153
|
+
|
|
154
|
+
print("โ Primary file exists: \(primaryExists)")
|
|
155
|
+
print("โ Compressed file exists: \(compressedExists)")
|
|
156
|
+
print("โ Both outputs working correctly\n")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
func testNoOutputs() {
|
|
160
|
+
print("Test 5: No Outputs (Streaming Only)")
|
|
161
|
+
print("-----------------------------------")
|
|
162
|
+
|
|
163
|
+
let primaryURL = testDir.appendingPathComponent("no_primary.wav")
|
|
164
|
+
let compressedURL = testDir.appendingPathComponent("no_compressed.aac")
|
|
165
|
+
|
|
166
|
+
var dataEmitted = false
|
|
167
|
+
var totalDataSize: Int64 = 0
|
|
168
|
+
var emissionCount = 0
|
|
169
|
+
|
|
170
|
+
// Simulate no file outputs but data emission continues
|
|
171
|
+
let _ = createMockRecording(
|
|
172
|
+
primaryURL: primaryURL,
|
|
173
|
+
compressedURL: compressedURL,
|
|
174
|
+
primaryEnabled: false,
|
|
175
|
+
compressedEnabled: false
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// Simulate data emissions
|
|
179
|
+
for _ in 0..<5 {
|
|
180
|
+
let mockData = createMockAudioData(duration: 0.5, sampleRate: 48000)
|
|
181
|
+
dataEmitted = true
|
|
182
|
+
totalDataSize += Int64(mockData.count)
|
|
183
|
+
emissionCount += 1
|
|
184
|
+
Thread.sleep(forTimeInterval: 0.1)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let primaryExists = FileManager.default.fileExists(atPath: primaryURL.path)
|
|
188
|
+
let compressedExists = FileManager.default.fileExists(atPath: compressedURL.path)
|
|
189
|
+
|
|
190
|
+
let passed = !primaryExists && !compressedExists && dataEmitted && emissionCount == 5
|
|
191
|
+
results.append((
|
|
192
|
+
name: "No Outputs (Streaming)",
|
|
193
|
+
passed: passed,
|
|
194
|
+
message: "Files exist: \(primaryExists || compressedExists), Emissions: \(emissionCount)"
|
|
195
|
+
))
|
|
196
|
+
|
|
197
|
+
print("โ Primary file exists: \(primaryExists)")
|
|
198
|
+
print("โ Compressed file exists: \(compressedExists)")
|
|
199
|
+
print("โ Data emissions: \(emissionCount)")
|
|
200
|
+
print("โ Total data size: \(totalDataSize) bytes")
|
|
201
|
+
print("โ Streaming-only mode working correctly\n")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Helper functions
|
|
205
|
+
|
|
206
|
+
func createMockRecording(
|
|
207
|
+
primaryURL: URL?,
|
|
208
|
+
compressedURL: URL?,
|
|
209
|
+
primaryEnabled: Bool,
|
|
210
|
+
compressedEnabled: Bool
|
|
211
|
+
) -> Bool {
|
|
212
|
+
// Create primary file if enabled
|
|
213
|
+
if primaryEnabled, let url = primaryURL {
|
|
214
|
+
let header = createWavHeader(dataSize: 1000)
|
|
215
|
+
let audioData = Data(repeating: 0, count: 1000)
|
|
216
|
+
|
|
217
|
+
do {
|
|
218
|
+
var fileData = Data()
|
|
219
|
+
fileData.append(header)
|
|
220
|
+
fileData.append(audioData)
|
|
221
|
+
try fileData.write(to: url)
|
|
222
|
+
} catch {
|
|
223
|
+
print("Error creating primary file: \(error)")
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Create compressed file if enabled
|
|
229
|
+
if compressedEnabled, let url = compressedURL {
|
|
230
|
+
// Mock AAC file (just some data)
|
|
231
|
+
let mockData = Data(repeating: 0xFF, count: 500)
|
|
232
|
+
do {
|
|
233
|
+
try mockData.write(to: url)
|
|
234
|
+
} catch {
|
|
235
|
+
print("Error creating compressed file: \(error)")
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return true
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func createMockAudioData(duration: Double, sampleRate: Double) -> Data {
|
|
244
|
+
let samples = Int(duration * sampleRate)
|
|
245
|
+
let bytesPerSample = 2 // 16-bit
|
|
246
|
+
return Data(repeating: 0, count: samples * bytesPerSample)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
func createWavHeader(dataSize: Int) -> Data {
|
|
250
|
+
var header = Data()
|
|
251
|
+
|
|
252
|
+
// RIFF header
|
|
253
|
+
header.append(contentsOf: "RIFF".utf8)
|
|
254
|
+
header.append(contentsOf: UInt32(36 + dataSize).littleEndianBytes)
|
|
255
|
+
header.append(contentsOf: "WAVE".utf8)
|
|
256
|
+
|
|
257
|
+
// fmt chunk
|
|
258
|
+
header.append(contentsOf: "fmt ".utf8)
|
|
259
|
+
header.append(contentsOf: UInt32(16).littleEndianBytes)
|
|
260
|
+
header.append(contentsOf: UInt16(1).littleEndianBytes) // PCM
|
|
261
|
+
header.append(contentsOf: UInt16(1).littleEndianBytes) // Channels
|
|
262
|
+
header.append(contentsOf: UInt32(48000).littleEndianBytes) // Sample rate
|
|
263
|
+
header.append(contentsOf: UInt32(96000).littleEndianBytes) // Byte rate
|
|
264
|
+
header.append(contentsOf: UInt16(2).littleEndianBytes) // Block align
|
|
265
|
+
header.append(contentsOf: UInt16(16).littleEndianBytes) // Bits per sample
|
|
266
|
+
|
|
267
|
+
// data chunk
|
|
268
|
+
header.append(contentsOf: "data".utf8)
|
|
269
|
+
header.append(contentsOf: UInt32(dataSize).littleEndianBytes)
|
|
270
|
+
|
|
271
|
+
return header
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func printResults() {
|
|
275
|
+
print("๐ Test Results")
|
|
276
|
+
print("===============")
|
|
277
|
+
|
|
278
|
+
let passed = results.filter { $0.passed }.count
|
|
279
|
+
let total = results.count
|
|
280
|
+
|
|
281
|
+
for result in results {
|
|
282
|
+
let status = result.passed ? "โ
" : "โ"
|
|
283
|
+
print("\(status) \(result.name)")
|
|
284
|
+
print(" \(result.message)")
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
print("\nSummary: \(passed)/\(total) tests passed")
|
|
288
|
+
|
|
289
|
+
if passed == total {
|
|
290
|
+
print("๐ All tests passed!")
|
|
291
|
+
} else {
|
|
292
|
+
print("โ ๏ธ Some tests failed")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
print("\n๐ Key Features Validated:")
|
|
296
|
+
print("- Default behavior creates primary WAV file only")
|
|
297
|
+
print("- Can create compressed file only (no WAV)")
|
|
298
|
+
print("- Can create both primary and compressed files")
|
|
299
|
+
print("- Streaming-only mode (no files created)")
|
|
300
|
+
print("- Data emission continues regardless of file outputs")
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Extension for little-endian conversion
|
|
305
|
+
extension UInt32 {
|
|
306
|
+
var littleEndianBytes: [UInt8] {
|
|
307
|
+
let value = self.littleEndian
|
|
308
|
+
return [UInt8(value & 0xff), UInt8((value >> 8) & 0xff),
|
|
309
|
+
UInt8((value >> 16) & 0xff), UInt8((value >> 24) & 0xff)]
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
extension UInt16 {
|
|
314
|
+
var littleEndianBytes: [UInt8] {
|
|
315
|
+
let value = self.littleEndian
|
|
316
|
+
return [UInt8(value & 0xff), UInt8((value >> 8) & 0xff)]
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Run the test
|
|
321
|
+
let test = OutputControlTest()
|
|
322
|
+
test.runAllTests()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Integration tests for new features in expo-audio-studio
|
|
4
|
+
# This script runs all integration tests to validate ACTUAL platform behavior
|
|
5
|
+
|
|
6
|
+
echo "๐งช Running expo-audio-studio iOS Integration Tests"
|
|
7
|
+
echo "=================================================="
|
|
8
|
+
echo ""
|
|
9
|
+
|
|
10
|
+
# Change to the directory containing this script
|
|
11
|
+
cd "$(dirname "$0")"
|
|
12
|
+
|
|
13
|
+
# Make test scripts executable
|
|
14
|
+
chmod +x *.swift
|
|
15
|
+
|
|
16
|
+
echo "1๏ธโฃ Buffer Duration Test"
|
|
17
|
+
echo "========================="
|
|
18
|
+
swift buffer_duration_test.swift
|
|
19
|
+
echo ""
|
|
20
|
+
|
|
21
|
+
echo "2๏ธโฃ Output Control Test"
|
|
22
|
+
echo "========================"
|
|
23
|
+
swift output_control_test.swift
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
echo "3๏ธโฃ Buffer and Fallback Test"
|
|
27
|
+
echo "============================"
|
|
28
|
+
swift buffer_and_fallback_test.swift
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
echo "4๏ธโฃ Compressed-Only Output Test (Issue #244)"
|
|
32
|
+
echo "==========================================="
|
|
33
|
+
swift compressed_only_output_test.swift
|
|
34
|
+
echo ""
|
|
35
|
+
|
|
36
|
+
echo "โ
Integration tests validate real iOS behavior"
|
|
37
|
+
echo "โ
Tests must pass before merging any feature!"
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env swift
|
|
2
|
+
|
|
3
|
+
import AVFoundation
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
// Test script to verify if AVAudioRecorder actually supports Opus encoding
|
|
7
|
+
// This version is macOS-compatible for testing purposes
|
|
8
|
+
|
|
9
|
+
func testOpusSupport() {
|
|
10
|
+
print("Testing AVAudioRecorder Opus Support (macOS test)...")
|
|
11
|
+
print("--------------------------------------------------")
|
|
12
|
+
|
|
13
|
+
// Test 1: Check if kAudioFormatOpus is defined
|
|
14
|
+
let opusFormat = kAudioFormatOpus
|
|
15
|
+
print("โ kAudioFormatOpus is defined: \(opusFormat) (0x\(String(opusFormat, radix: 16)))")
|
|
16
|
+
|
|
17
|
+
// Convert to FourCC string
|
|
18
|
+
let fourCC = String(format: "%c%c%c%c",
|
|
19
|
+
(opusFormat >> 24) & 0xff,
|
|
20
|
+
(opusFormat >> 16) & 0xff,
|
|
21
|
+
(opusFormat >> 8) & 0xff,
|
|
22
|
+
opusFormat & 0xff)
|
|
23
|
+
print(" FourCC: '\(fourCC)'")
|
|
24
|
+
print()
|
|
25
|
+
|
|
26
|
+
// Test 2: Try to create AVAudioRecorder with Opus settings
|
|
27
|
+
print("Testing AVAudioRecorder with Opus settings...")
|
|
28
|
+
|
|
29
|
+
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
30
|
+
let opusURL = documentsPath.appendingPathComponent("test_opus.opus")
|
|
31
|
+
let aacURL = documentsPath.appendingPathComponent("test_aac.m4a")
|
|
32
|
+
|
|
33
|
+
// Opus settings
|
|
34
|
+
let opusSettings: [String: Any] = [
|
|
35
|
+
AVFormatIDKey: kAudioFormatOpus,
|
|
36
|
+
AVSampleRateKey: 48000,
|
|
37
|
+
AVNumberOfChannelsKey: 1,
|
|
38
|
+
AVEncoderBitRateKey: 64000
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
// AAC settings for comparison
|
|
42
|
+
let aacSettings: [String: Any] = [
|
|
43
|
+
AVFormatIDKey: kAudioFormatMPEG4AAC,
|
|
44
|
+
AVSampleRateKey: 48000,
|
|
45
|
+
AVNumberOfChannelsKey: 1,
|
|
46
|
+
AVEncoderBitRateKey: 64000
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
// Test Opus recorder
|
|
50
|
+
do {
|
|
51
|
+
let opusRecorder = try AVAudioRecorder(url: opusURL, settings: opusSettings)
|
|
52
|
+
print("โ Opus recorder created successfully")
|
|
53
|
+
print(" URL: \(opusURL.lastPathComponent)")
|
|
54
|
+
print(" Settings provided: \(opusSettings)")
|
|
55
|
+
print(" Settings after init: \(opusRecorder.settings)")
|
|
56
|
+
print(" Format: \(opusRecorder.format)")
|
|
57
|
+
|
|
58
|
+
// Check if recorder can prepare
|
|
59
|
+
if opusRecorder.prepareToRecord() {
|
|
60
|
+
print("โ Opus recorder prepared successfully")
|
|
61
|
+
|
|
62
|
+
// Try to record for a brief moment
|
|
63
|
+
if opusRecorder.record() {
|
|
64
|
+
print("โ Opus recorder started recording")
|
|
65
|
+
Thread.sleep(forTimeInterval: 0.5)
|
|
66
|
+
opusRecorder.stop()
|
|
67
|
+
print("โ Opus recorder stopped")
|
|
68
|
+
|
|
69
|
+
// Check if file was created
|
|
70
|
+
if FileManager.default.fileExists(atPath: opusURL.path) {
|
|
71
|
+
let attributes = try FileManager.default.attributesOfItem(atPath: opusURL.path)
|
|
72
|
+
let fileSize = attributes[.size] as? Int64 ?? 0
|
|
73
|
+
print("โ Opus file created: \(fileSize) bytes")
|
|
74
|
+
|
|
75
|
+
// Check file format by reading header
|
|
76
|
+
if fileSize > 0 {
|
|
77
|
+
let fileHandle = try FileHandle(forReadingFrom: opusURL)
|
|
78
|
+
let headerData = fileHandle.readData(ofLength: 32)
|
|
79
|
+
fileHandle.closeFile()
|
|
80
|
+
|
|
81
|
+
print(" File header (hex): \(headerData.map { String(format: "%02X", $0) }.prefix(16).joined(separator: " "))")
|
|
82
|
+
|
|
83
|
+
// Check for common audio file signatures
|
|
84
|
+
if headerData.count >= 4 {
|
|
85
|
+
let signature = headerData.prefix(4)
|
|
86
|
+
if signature.starts(with: "OggS".data(using: .ascii)!) {
|
|
87
|
+
print(" โ File has OGG container signature")
|
|
88
|
+
} else if signature.starts(with: [0x00, 0x00, 0x00]) {
|
|
89
|
+
print(" File might be MP4/M4A container")
|
|
90
|
+
} else {
|
|
91
|
+
print(" Unknown file signature")
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Clean up
|
|
97
|
+
try FileManager.default.removeItem(at: opusURL)
|
|
98
|
+
} else {
|
|
99
|
+
print("โ No Opus file was created")
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
print("โ Opus recorder failed to start recording")
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
print("โ Opus recorder failed to prepare")
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
print("โ Failed to create Opus recorder: \(error)")
|
|
109
|
+
print(" Error details: \(error.localizedDescription)")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
print()
|
|
113
|
+
|
|
114
|
+
// Test AAC recorder for comparison
|
|
115
|
+
print("Testing AVAudioRecorder with AAC settings (for comparison)...")
|
|
116
|
+
do {
|
|
117
|
+
let aacRecorder = try AVAudioRecorder(url: aacURL, settings: aacSettings)
|
|
118
|
+
print("โ AAC recorder created successfully")
|
|
119
|
+
print(" Settings after init: \(aacRecorder.settings)")
|
|
120
|
+
print(" Format: \(aacRecorder.format)")
|
|
121
|
+
|
|
122
|
+
if aacRecorder.prepareToRecord() && aacRecorder.record() {
|
|
123
|
+
print("โ AAC recorder working normally")
|
|
124
|
+
Thread.sleep(forTimeInterval: 0.5)
|
|
125
|
+
aacRecorder.stop()
|
|
126
|
+
|
|
127
|
+
if FileManager.default.fileExists(atPath: aacURL.path) {
|
|
128
|
+
let attributes = try FileManager.default.attributesOfItem(atPath: aacURL.path)
|
|
129
|
+
let fileSize = attributes[.size] as? Int64 ?? 0
|
|
130
|
+
print("โ AAC file created: \(fileSize) bytes")
|
|
131
|
+
|
|
132
|
+
// Check file header
|
|
133
|
+
let fileHandle = try FileHandle(forReadingFrom: aacURL)
|
|
134
|
+
let headerData = fileHandle.readData(ofLength: 16)
|
|
135
|
+
fileHandle.closeFile()
|
|
136
|
+
|
|
137
|
+
print(" File header (hex): \(headerData.map { String(format: "%02X", $0) }.joined(separator: " "))")
|
|
138
|
+
|
|
139
|
+
try FileManager.default.removeItem(at: aacURL)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
print("โ Failed to create AAC recorder: \(error)")
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
print()
|
|
147
|
+
print("Test complete!")
|
|
148
|
+
print()
|
|
149
|
+
print("Note: This test runs on macOS which may have different codec support than iOS.")
|
|
150
|
+
print("The results should be validated on an actual iOS device or simulator.")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Run the test
|
|
154
|
+
testOpusSupport()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env swift
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
import AVFoundation
|
|
5
|
+
import Accelerate
|
|
6
|
+
|
|
7
|
+
// Simple test framework
|
|
8
|
+
struct TestResult {
|
|
9
|
+
let name: String
|
|
10
|
+
let passed: Bool
|
|
11
|
+
let message: String
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class AudioProcessingTest {
|
|
15
|
+
var results: [TestResult] = []
|
|
16
|
+
|
|
17
|
+
func assert(_ condition: Bool, _ message: String, file: String = #file, line: Int = #line) {
|
|
18
|
+
let testName = "\(file.split(separator: "/").last ?? ""):\(line)"
|
|
19
|
+
results.append(TestResult(name: testName, passed: condition, message: message))
|
|
20
|
+
if !condition {
|
|
21
|
+
print("โ FAILED: \(message) at \(testName)")
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func assertEqual<T: Equatable>(_ a: T, _ b: T, _ message: String = "", file: String = #file, line: Int = #line) {
|
|
26
|
+
let passed = a == b
|
|
27
|
+
let msg = message.isEmpty ? "\(a) should equal \(b)" : message
|
|
28
|
+
assert(passed, msg, file: file, line: line)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func assertClose(_ a: Float, _ b: Float, tolerance: Float = 0.001, _ message: String = "", file: String = #file, line: Int = #line) {
|
|
32
|
+
let passed = abs(a - b) < tolerance
|
|
33
|
+
let msg = message.isEmpty ? "\(a) should be close to \(b)" : message
|
|
34
|
+
assert(passed, msg, file: file, line: line)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func run() {
|
|
38
|
+
print("๐งช Running iOS Audio Processing Tests...\n")
|
|
39
|
+
|
|
40
|
+
testRMSCalculation()
|
|
41
|
+
testZeroCrossingRate()
|
|
42
|
+
testChannelConversion()
|
|
43
|
+
testBitDepthConversion()
|
|
44
|
+
|
|
45
|
+
// Print summary
|
|
46
|
+
let passed = results.filter { $0.passed }.count
|
|
47
|
+
let total = results.count
|
|
48
|
+
|
|
49
|
+
print("\n๐ Test Summary:")
|
|
50
|
+
print(" Total: \(total)")
|
|
51
|
+
print(" Passed: \(passed)")
|
|
52
|
+
print(" Failed: \(total - passed)")
|
|
53
|
+
|
|
54
|
+
if passed == total {
|
|
55
|
+
print("\nโ
All tests passed!")
|
|
56
|
+
} else {
|
|
57
|
+
print("\nโ Some tests failed!")
|
|
58
|
+
exit(1)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func testRMSCalculation() {
|
|
63
|
+
print("Testing RMS calculation...")
|
|
64
|
+
|
|
65
|
+
// Create a simple sine wave
|
|
66
|
+
let sampleCount = 1024
|
|
67
|
+
var samples = [Float](repeating: 0, count: sampleCount)
|
|
68
|
+
|
|
69
|
+
// Generate 1.0 amplitude sine wave
|
|
70
|
+
for i in 0..<sampleCount {
|
|
71
|
+
samples[i] = sin(Float(i) * 2.0 * .pi / 64.0)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Calculate RMS
|
|
75
|
+
var rms: Float = 0
|
|
76
|
+
vDSP_rmsqv(samples, 1, &rms, vDSP_Length(sampleCount))
|
|
77
|
+
|
|
78
|
+
// For a sine wave, RMS should be approximately 1/sqrt(2) โ 0.707
|
|
79
|
+
assertClose(rms, 0.707, tolerance: 0.01, "RMS of sine wave should be ~0.707")
|
|
80
|
+
|
|
81
|
+
print("โ RMS calculation test completed")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func testZeroCrossingRate() {
|
|
85
|
+
print("\nTesting zero crossing rate...")
|
|
86
|
+
|
|
87
|
+
// Create a signal that crosses zero 10 times
|
|
88
|
+
let samples: [Float] = [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1]
|
|
89
|
+
|
|
90
|
+
var zcr = 0
|
|
91
|
+
for i in 1..<samples.count {
|
|
92
|
+
if (samples[i] >= 0 && samples[i-1] < 0) || (samples[i] < 0 && samples[i-1] >= 0) {
|
|
93
|
+
zcr += 1
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
assertEqual(zcr, 10, "Should have 10 zero crossings")
|
|
98
|
+
|
|
99
|
+
print("โ Zero crossing rate test completed")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func testChannelConversion() {
|
|
103
|
+
print("\nTesting channel conversion...")
|
|
104
|
+
|
|
105
|
+
// Mono to stereo
|
|
106
|
+
let monoSamples: [Float] = [0.5, -0.5, 0.3, -0.3]
|
|
107
|
+
var stereoSamples = [Float](repeating: 0, count: monoSamples.count * 2)
|
|
108
|
+
|
|
109
|
+
// Simple duplication for mono to stereo
|
|
110
|
+
for i in 0..<monoSamples.count {
|
|
111
|
+
stereoSamples[i * 2] = monoSamples[i]
|
|
112
|
+
stereoSamples[i * 2 + 1] = monoSamples[i]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
assertEqual(stereoSamples.count, 8, "Stereo should have double the samples")
|
|
116
|
+
assertEqual(stereoSamples[0], monoSamples[0], "Left channel should match mono")
|
|
117
|
+
assertEqual(stereoSamples[1], monoSamples[0], "Right channel should match mono")
|
|
118
|
+
|
|
119
|
+
print("โ Channel conversion test completed")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func testBitDepthConversion() {
|
|
123
|
+
print("\nTesting bit depth conversion...")
|
|
124
|
+
|
|
125
|
+
// 16-bit to float conversion
|
|
126
|
+
let int16Samples: [Int16] = [Int16.max, 0, Int16.min]
|
|
127
|
+
var floatSamples = [Float](repeating: 0, count: int16Samples.count)
|
|
128
|
+
|
|
129
|
+
// Convert
|
|
130
|
+
for i in 0..<int16Samples.count {
|
|
131
|
+
floatSamples[i] = Float(int16Samples[i]) / Float(Int16.max)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
assertClose(floatSamples[0], 1.0, "Max int16 should convert to ~1.0")
|
|
135
|
+
assertClose(floatSamples[1], 0.0, "Zero should remain zero")
|
|
136
|
+
assertClose(floatSamples[2], -1.0, tolerance: 0.01, "Min int16 should convert to ~-1.0")
|
|
137
|
+
|
|
138
|
+
print("โ Bit depth conversion test completed")
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Run the tests
|
|
143
|
+
let test = AudioProcessingTest()
|
|
144
|
+
test.run()
|