@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,249 @@
|
|
|
1
|
+
package net.siteed.audiostudio
|
|
2
|
+
|
|
3
|
+
import org.junit.Test
|
|
4
|
+
import org.junit.Assert.*
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Unit tests for audio focus strategy configuration and logic.
|
|
8
|
+
* These tests verify that the RecordingConfig correctly handles audioFocusStrategy
|
|
9
|
+
* parameter and that the smart defaults work as expected.
|
|
10
|
+
*/
|
|
11
|
+
class AudioFocusStrategyTest {
|
|
12
|
+
|
|
13
|
+
@Test
|
|
14
|
+
fun testRecordingConfigWithExplicitBackgroundStrategy() {
|
|
15
|
+
val options = mapOf(
|
|
16
|
+
"sampleRate" to 44100,
|
|
17
|
+
"channels" to 1,
|
|
18
|
+
"encoding" to "pcm_16bit",
|
|
19
|
+
"android" to mapOf(
|
|
20
|
+
"audioFocusStrategy" to "background"
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
val result = RecordingConfig.fromMap(options)
|
|
25
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
26
|
+
|
|
27
|
+
val (config, _) = result.getOrThrow()
|
|
28
|
+
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
fun testRecordingConfigWithExplicitInteractiveStrategy() {
|
|
33
|
+
val options = mapOf(
|
|
34
|
+
"sampleRate" to 44100,
|
|
35
|
+
"channels" to 1,
|
|
36
|
+
"encoding" to "pcm_16bit",
|
|
37
|
+
"android" to mapOf(
|
|
38
|
+
"audioFocusStrategy" to "interactive"
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
val result = RecordingConfig.fromMap(options)
|
|
43
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
44
|
+
|
|
45
|
+
val (config, _) = result.getOrThrow()
|
|
46
|
+
assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Test
|
|
50
|
+
fun testRecordingConfigWithExplicitCommunicationStrategy() {
|
|
51
|
+
val options = mapOf(
|
|
52
|
+
"sampleRate" to 44100,
|
|
53
|
+
"channels" to 1,
|
|
54
|
+
"encoding" to "pcm_16bit",
|
|
55
|
+
"android" to mapOf(
|
|
56
|
+
"audioFocusStrategy" to "communication"
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
val result = RecordingConfig.fromMap(options)
|
|
61
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
62
|
+
|
|
63
|
+
val (config, _) = result.getOrThrow()
|
|
64
|
+
assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Test
|
|
68
|
+
fun testRecordingConfigWithExplicitNoneStrategy() {
|
|
69
|
+
val options = mapOf(
|
|
70
|
+
"sampleRate" to 44100,
|
|
71
|
+
"channels" to 1,
|
|
72
|
+
"encoding" to "pcm_16bit",
|
|
73
|
+
"android" to mapOf(
|
|
74
|
+
"audioFocusStrategy" to "none"
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
val result = RecordingConfig.fromMap(options)
|
|
79
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
80
|
+
|
|
81
|
+
val (config, _) = result.getOrThrow()
|
|
82
|
+
assertEquals("Audio focus strategy should be none", "none", config.audioFocusStrategy)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Test
|
|
86
|
+
fun testRecordingConfigWithoutAudioFocusStrategy() {
|
|
87
|
+
val options = mapOf(
|
|
88
|
+
"sampleRate" to 44100,
|
|
89
|
+
"channels" to 1,
|
|
90
|
+
"encoding" to "pcm_16bit"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
val result = RecordingConfig.fromMap(options)
|
|
94
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
95
|
+
|
|
96
|
+
val (config, _) = result.getOrThrow()
|
|
97
|
+
assertNull("Audio focus strategy should be null when not specified", config.audioFocusStrategy)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test
|
|
101
|
+
fun testRecordingConfigWithInvalidAudioFocusStrategy() {
|
|
102
|
+
val options = mapOf(
|
|
103
|
+
"sampleRate" to 44100,
|
|
104
|
+
"channels" to 1,
|
|
105
|
+
"encoding" to "pcm_16bit",
|
|
106
|
+
"android" to mapOf(
|
|
107
|
+
"audioFocusStrategy" to "invalid_strategy"
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
val result = RecordingConfig.fromMap(options)
|
|
112
|
+
assertTrue("Config creation should succeed even with invalid strategy", result.isSuccess)
|
|
113
|
+
|
|
114
|
+
val (config, _) = result.getOrThrow()
|
|
115
|
+
assertEquals("Invalid audio focus strategy should be preserved", "invalid_strategy", config.audioFocusStrategy)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test
|
|
119
|
+
fun testRecordingConfigWithNullAudioFocusStrategy() {
|
|
120
|
+
val options = mapOf(
|
|
121
|
+
"sampleRate" to 44100,
|
|
122
|
+
"channels" to 1,
|
|
123
|
+
"encoding" to "pcm_16bit",
|
|
124
|
+
"android" to mapOf(
|
|
125
|
+
"audioFocusStrategy" to null
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
val result = RecordingConfig.fromMap(options)
|
|
130
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
131
|
+
|
|
132
|
+
val (config, _) = result.getOrThrow()
|
|
133
|
+
assertNull("Audio focus strategy should be null", config.audioFocusStrategy)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
fun testRecordingConfigKeepAwakeAndBackgroundStrategy() {
|
|
138
|
+
val options = mapOf(
|
|
139
|
+
"sampleRate" to 44100,
|
|
140
|
+
"channels" to 1,
|
|
141
|
+
"encoding" to "pcm_16bit",
|
|
142
|
+
"keepAwake" to true,
|
|
143
|
+
"android" to mapOf(
|
|
144
|
+
"audioFocusStrategy" to "background"
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
val result = RecordingConfig.fromMap(options)
|
|
149
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
150
|
+
|
|
151
|
+
val (config, _) = result.getOrThrow()
|
|
152
|
+
assertTrue("keepAwake should be true", config.keepAwake)
|
|
153
|
+
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@Test
|
|
157
|
+
fun testRecordingConfigKeepAwakeFalseAndInteractiveStrategy() {
|
|
158
|
+
val options = mapOf(
|
|
159
|
+
"sampleRate" to 44100,
|
|
160
|
+
"channels" to 1,
|
|
161
|
+
"encoding" to "pcm_16bit",
|
|
162
|
+
"keepAwake" to false,
|
|
163
|
+
"android" to mapOf(
|
|
164
|
+
"audioFocusStrategy" to "interactive"
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
val result = RecordingConfig.fromMap(options)
|
|
169
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
170
|
+
|
|
171
|
+
val (config, _) = result.getOrThrow()
|
|
172
|
+
assertFalse("keepAwake should be false", config.keepAwake)
|
|
173
|
+
assertEquals("Audio focus strategy should be interactive", "interactive", config.audioFocusStrategy)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun testRecordingConfigWithAutoResumeAndBackgroundStrategy() {
|
|
178
|
+
val options = mapOf(
|
|
179
|
+
"sampleRate" to 44100,
|
|
180
|
+
"channels" to 1,
|
|
181
|
+
"encoding" to "pcm_16bit",
|
|
182
|
+
"autoResumeAfterInterruption" to true,
|
|
183
|
+
"android" to mapOf(
|
|
184
|
+
"audioFocusStrategy" to "background"
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
val result = RecordingConfig.fromMap(options)
|
|
189
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
190
|
+
|
|
191
|
+
val (config, _) = result.getOrThrow()
|
|
192
|
+
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
193
|
+
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@Test
|
|
197
|
+
fun testRecordingConfigWithCommunicationStrategyAndSpeechSampleRate() {
|
|
198
|
+
val options = mapOf(
|
|
199
|
+
"sampleRate" to 16000, // Common speech sample rate
|
|
200
|
+
"channels" to 1,
|
|
201
|
+
"encoding" to "pcm_16bit",
|
|
202
|
+
"android" to mapOf(
|
|
203
|
+
"audioFocusStrategy" to "communication"
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
val result = RecordingConfig.fromMap(options)
|
|
208
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
209
|
+
|
|
210
|
+
val (config, _) = result.getOrThrow()
|
|
211
|
+
assertEquals("Sample rate should be 16000", 16000, config.sampleRate)
|
|
212
|
+
assertEquals("Audio focus strategy should be communication", "communication", config.audioFocusStrategy)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
@Test
|
|
216
|
+
fun testDefaultRecordingConfigValues() {
|
|
217
|
+
val result = RecordingConfig.fromMap(null)
|
|
218
|
+
assertTrue("Config creation should succeed with null input", result.isSuccess)
|
|
219
|
+
|
|
220
|
+
val (config, _) = result.getOrThrow()
|
|
221
|
+
assertNull("Default audio focus strategy should be null", config.audioFocusStrategy)
|
|
222
|
+
assertTrue("Default keepAwake should be true", config.keepAwake)
|
|
223
|
+
assertFalse("Default autoResumeAfterInterruption should be false", config.autoResumeAfterInterruption)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@Test
|
|
227
|
+
fun testRecordingConfigCompleteAudioFocusConfiguration() {
|
|
228
|
+
val options = mapOf(
|
|
229
|
+
"sampleRate" to 44100,
|
|
230
|
+
"channels" to 1,
|
|
231
|
+
"encoding" to "pcm_16bit",
|
|
232
|
+
"keepAwake" to true,
|
|
233
|
+
"autoResumeAfterInterruption" to true,
|
|
234
|
+
"showNotification" to true,
|
|
235
|
+
"android" to mapOf(
|
|
236
|
+
"audioFocusStrategy" to "background"
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
val result = RecordingConfig.fromMap(options)
|
|
241
|
+
assertTrue("Config creation should succeed", result.isSuccess)
|
|
242
|
+
|
|
243
|
+
val (config, _) = result.getOrThrow()
|
|
244
|
+
assertEquals("Audio focus strategy should be background", "background", config.audioFocusStrategy)
|
|
245
|
+
assertTrue("keepAwake should be true", config.keepAwake)
|
|
246
|
+
assertTrue("autoResumeAfterInterruption should be true", config.autoResumeAfterInterruption)
|
|
247
|
+
assertTrue("showNotification should be true", config.showNotification)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
package net.siteed.audiostudio
|
|
2
|
+
|
|
3
|
+
import org.junit.Test
|
|
4
|
+
import org.junit.Assert.*
|
|
5
|
+
import org.junit.Before
|
|
6
|
+
import java.io.File
|
|
7
|
+
|
|
8
|
+
class AudioFormatTest {
|
|
9
|
+
private lateinit var tempDir: File
|
|
10
|
+
|
|
11
|
+
@Before
|
|
12
|
+
fun setUp() {
|
|
13
|
+
tempDir = File(System.getProperty("java.io.tmpdir"), "audio_format_test_${System.currentTimeMillis()}")
|
|
14
|
+
tempDir.mkdirs()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Test
|
|
18
|
+
fun testGetFileExtension_aacDefaultsToM4a() {
|
|
19
|
+
// Given
|
|
20
|
+
val config = RecordingConfig(
|
|
21
|
+
output = OutputConfig(
|
|
22
|
+
compressed = OutputConfig.CompressedOutput(
|
|
23
|
+
enabled = true,
|
|
24
|
+
format = "aac",
|
|
25
|
+
preferRawStream = false
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// When
|
|
31
|
+
val file = createTestFile(config, isCompressed = true)
|
|
32
|
+
|
|
33
|
+
// Then
|
|
34
|
+
assertTrue("AAC without preferRawStream should produce .m4a", file.name.endsWith(".m4a"))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Test
|
|
38
|
+
fun testGetFileExtension_aacWithPreferRawStreamProducesAac() {
|
|
39
|
+
// Given
|
|
40
|
+
val config = RecordingConfig(
|
|
41
|
+
output = OutputConfig(
|
|
42
|
+
compressed = OutputConfig.CompressedOutput(
|
|
43
|
+
enabled = true,
|
|
44
|
+
format = "aac",
|
|
45
|
+
preferRawStream = true
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
// When
|
|
51
|
+
val file = createTestFile(config, isCompressed = true)
|
|
52
|
+
|
|
53
|
+
// Then
|
|
54
|
+
assertTrue("AAC with preferRawStream should produce .aac", file.name.endsWith(".aac"))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@Test
|
|
58
|
+
fun testGetFileExtension_opusProducesOpus() {
|
|
59
|
+
// Given
|
|
60
|
+
val config = RecordingConfig(
|
|
61
|
+
output = OutputConfig(
|
|
62
|
+
compressed = OutputConfig.CompressedOutput(
|
|
63
|
+
enabled = true,
|
|
64
|
+
format = "opus"
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// When
|
|
70
|
+
val file = createTestFile(config, isCompressed = true)
|
|
71
|
+
|
|
72
|
+
// Then
|
|
73
|
+
assertTrue("Opus should produce .opus", file.name.endsWith(".opus"))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Test
|
|
77
|
+
fun testGetFileExtension_wavForUncompressed() {
|
|
78
|
+
// Given
|
|
79
|
+
val config = RecordingConfig()
|
|
80
|
+
|
|
81
|
+
// When
|
|
82
|
+
val file = createTestFile(config, isCompressed = false)
|
|
83
|
+
|
|
84
|
+
// Then
|
|
85
|
+
assertTrue("Uncompressed should produce .wav", file.name.endsWith(".wav"))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
fun testCompressedOutput_parseFromMap() {
|
|
90
|
+
// Given
|
|
91
|
+
val map = mapOf(
|
|
92
|
+
"enabled" to true,
|
|
93
|
+
"format" to "aac",
|
|
94
|
+
"bitrate" to 192000,
|
|
95
|
+
"preferRawStream" to true
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// When
|
|
99
|
+
val compressed = OutputConfig.CompressedOutput(
|
|
100
|
+
enabled = map["enabled"] as Boolean,
|
|
101
|
+
format = map["format"] as String,
|
|
102
|
+
bitrate = map["bitrate"] as Int,
|
|
103
|
+
preferRawStream = map["preferRawStream"] as Boolean
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
// Then
|
|
107
|
+
assertTrue("enabled should be true", compressed.enabled)
|
|
108
|
+
assertEquals("format should be aac", "aac", compressed.format)
|
|
109
|
+
assertEquals("bitrate should be 192000", 192000, compressed.bitrate)
|
|
110
|
+
assertTrue("preferRawStream should be true", compressed.preferRawStream)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Test
|
|
114
|
+
fun testCompressedOutput_defaultValues() {
|
|
115
|
+
// When
|
|
116
|
+
val compressed = OutputConfig.CompressedOutput()
|
|
117
|
+
|
|
118
|
+
// Then
|
|
119
|
+
assertFalse("enabled should default to false", compressed.enabled)
|
|
120
|
+
assertEquals("format should default to aac", "aac", compressed.format)
|
|
121
|
+
assertEquals("bitrate should default to 128000", 128000, compressed.bitrate)
|
|
122
|
+
assertFalse("preferRawStream should default to false", compressed.preferRawStream)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Helper function to simulate file creation logic from AudioRecorderManager
|
|
127
|
+
*/
|
|
128
|
+
private fun createTestFile(config: RecordingConfig, isCompressed: Boolean): File {
|
|
129
|
+
val baseFilename = config.filename?.let {
|
|
130
|
+
it.substringBeforeLast('.', it)
|
|
131
|
+
} ?: "test_recording"
|
|
132
|
+
|
|
133
|
+
val extension = if (isCompressed) {
|
|
134
|
+
when (config.output.compressed.format.lowercase()) {
|
|
135
|
+
"aac" -> {
|
|
136
|
+
if (config.output.compressed.preferRawStream) {
|
|
137
|
+
"aac" // Raw AAC stream
|
|
138
|
+
} else {
|
|
139
|
+
"m4a" // M4A container (new default)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
"opus" -> "opus" // Opus in OGG container
|
|
143
|
+
else -> config.output.compressed.format.lowercase()
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
"wav"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return File(tempDir, "$baseFilename.$extension")
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
package net.siteed.audiostudio
|
|
2
|
+
|
|
3
|
+
import org.junit.Test
|
|
4
|
+
import org.junit.Assert.*
|
|
5
|
+
import java.nio.ByteBuffer
|
|
6
|
+
import java.nio.ByteOrder
|
|
7
|
+
import kotlin.math.abs
|
|
8
|
+
|
|
9
|
+
class AudioFormatUtilsTest {
|
|
10
|
+
|
|
11
|
+
@Test
|
|
12
|
+
fun testConvertBitDepth_8to16() {
|
|
13
|
+
// Given - 8-bit PCM data (unsigned, centered at 128)
|
|
14
|
+
val input8bit = byteArrayOf(0, 64, 128.toByte(), 192.toByte(), 255.toByte())
|
|
15
|
+
|
|
16
|
+
// When
|
|
17
|
+
val output16bit = AudioFormatUtils.convertBitDepth(input8bit, 8, 16)
|
|
18
|
+
|
|
19
|
+
// Then
|
|
20
|
+
val buffer = ByteBuffer.wrap(output16bit).order(ByteOrder.LITTLE_ENDIAN)
|
|
21
|
+
val samples = ShortArray(output16bit.size / 2)
|
|
22
|
+
buffer.asShortBuffer().get(samples)
|
|
23
|
+
|
|
24
|
+
// Verify conversion (8-bit 128 = silence = 16-bit 0)
|
|
25
|
+
assertEquals("First sample should be -32768", -32768, samples[0].toInt())
|
|
26
|
+
assertEquals("Middle sample (128) should be 0", 0, samples[2].toInt())
|
|
27
|
+
assertEquals("Last sample should be 32767", 32767, samples[4].toInt())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Test
|
|
31
|
+
fun testConvertBitDepth_16to8() {
|
|
32
|
+
// Given - 16-bit PCM data
|
|
33
|
+
val buffer16 = ByteBuffer.allocate(10).order(ByteOrder.LITTLE_ENDIAN)
|
|
34
|
+
buffer16.putShort(-32768) // Min value
|
|
35
|
+
buffer16.putShort(-16384) // -0.5
|
|
36
|
+
buffer16.putShort(0) // Silence
|
|
37
|
+
buffer16.putShort(16384) // 0.5
|
|
38
|
+
buffer16.putShort(32767) // Max value
|
|
39
|
+
|
|
40
|
+
// When
|
|
41
|
+
val output8bit = AudioFormatUtils.convertBitDepth(buffer16.array(), 16, 8)
|
|
42
|
+
|
|
43
|
+
// Then
|
|
44
|
+
assertEquals("Should have 5 samples", 5, output8bit.size)
|
|
45
|
+
assertEquals("Min should convert to 0", 0, output8bit[0].toInt() and 0xFF)
|
|
46
|
+
assertEquals("Silence should convert to 128", 128, output8bit[2].toInt() and 0xFF)
|
|
47
|
+
assertEquals("Max should convert to 255", 255, output8bit[4].toInt() and 0xFF)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
fun testConvertBitDepth_16to32() {
|
|
52
|
+
// Given - 16-bit PCM data
|
|
53
|
+
val buffer16 = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN)
|
|
54
|
+
buffer16.putShort(-32768) // Min
|
|
55
|
+
buffer16.putShort(0) // Silence
|
|
56
|
+
buffer16.putShort(32767) // Max
|
|
57
|
+
|
|
58
|
+
// When
|
|
59
|
+
val output32bit = AudioFormatUtils.convertBitDepth(buffer16.array(), 16, 32)
|
|
60
|
+
|
|
61
|
+
// Then
|
|
62
|
+
val buffer32 = ByteBuffer.wrap(output32bit).order(ByteOrder.LITTLE_ENDIAN)
|
|
63
|
+
assertEquals("Should have 3 32-bit samples", 12, output32bit.size)
|
|
64
|
+
|
|
65
|
+
// Check values (scaled appropriately)
|
|
66
|
+
val sample1 = buffer32.getInt()
|
|
67
|
+
val sample2 = buffer32.getInt()
|
|
68
|
+
val sample3 = buffer32.getInt()
|
|
69
|
+
|
|
70
|
+
assertTrue("Min value should be negative", sample1 < 0)
|
|
71
|
+
assertEquals("Silence should be 0", 0, sample2)
|
|
72
|
+
assertTrue("Max value should be positive", sample3 > 0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Test
|
|
76
|
+
fun testConvertBitDepth_32to16() {
|
|
77
|
+
// Given - 32-bit PCM data
|
|
78
|
+
val buffer32 = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN)
|
|
79
|
+
buffer32.putInt(Int.MIN_VALUE) // Min
|
|
80
|
+
buffer32.putInt(0) // Silence
|
|
81
|
+
buffer32.putInt(Int.MAX_VALUE) // Max
|
|
82
|
+
|
|
83
|
+
// When
|
|
84
|
+
val output16bit = AudioFormatUtils.convertBitDepth(buffer32.array(), 32, 16)
|
|
85
|
+
|
|
86
|
+
// Then
|
|
87
|
+
val buffer16 = ByteBuffer.wrap(output16bit).order(ByteOrder.LITTLE_ENDIAN)
|
|
88
|
+
assertEquals("Should have 3 16-bit samples", 6, output16bit.size)
|
|
89
|
+
|
|
90
|
+
assertEquals("Min should convert to -32768", -32768, buffer16.getShort().toInt())
|
|
91
|
+
assertEquals("Silence should be 0", 0, buffer16.getShort().toInt())
|
|
92
|
+
assertEquals("Max should convert to 32767", 32767, buffer16.getShort().toInt())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
fun testConvertBitDepth_sameDepth() {
|
|
97
|
+
// Given
|
|
98
|
+
val input = byteArrayOf(1, 2, 3, 4, 5, 6)
|
|
99
|
+
|
|
100
|
+
// When - Convert 16 to 16 (no-op)
|
|
101
|
+
val output = AudioFormatUtils.convertBitDepth(input, 16, 16)
|
|
102
|
+
|
|
103
|
+
// Then
|
|
104
|
+
assertArrayEquals("Should return same data", input, output)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@Test
|
|
108
|
+
fun testConvertBitDepth_emptyData() {
|
|
109
|
+
// Given
|
|
110
|
+
val emptyData = byteArrayOf()
|
|
111
|
+
|
|
112
|
+
// When
|
|
113
|
+
val output = AudioFormatUtils.convertBitDepth(emptyData, 16, 32)
|
|
114
|
+
|
|
115
|
+
// Then
|
|
116
|
+
assertEquals("Should return empty array", 0, output.size)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Test
|
|
120
|
+
fun testConvertChannels_monoToStereo() {
|
|
121
|
+
// Given - Mono 16-bit data
|
|
122
|
+
val monoData = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).apply {
|
|
123
|
+
putShort(1000)
|
|
124
|
+
putShort(2000)
|
|
125
|
+
putShort(3000)
|
|
126
|
+
}.array()
|
|
127
|
+
|
|
128
|
+
// When
|
|
129
|
+
val stereoData = AudioFormatUtils.convertChannels(monoData, 1, 2, 16)
|
|
130
|
+
|
|
131
|
+
// Then
|
|
132
|
+
val buffer = ByteBuffer.wrap(stereoData).order(ByteOrder.LITTLE_ENDIAN)
|
|
133
|
+
assertEquals("Should have 6 samples (3 stereo pairs)", 12, stereoData.size)
|
|
134
|
+
|
|
135
|
+
// Each mono sample should be duplicated to both channels
|
|
136
|
+
assertEquals("L1", 1000, buffer.getShort().toInt())
|
|
137
|
+
assertEquals("R1", 1000, buffer.getShort().toInt())
|
|
138
|
+
assertEquals("L2", 2000, buffer.getShort().toInt())
|
|
139
|
+
assertEquals("R2", 2000, buffer.getShort().toInt())
|
|
140
|
+
assertEquals("L3", 3000, buffer.getShort().toInt())
|
|
141
|
+
assertEquals("R3", 3000, buffer.getShort().toInt())
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@Test
|
|
145
|
+
fun testConvertChannels_stereoToMono() {
|
|
146
|
+
// Given - Stereo 16-bit data
|
|
147
|
+
val stereoData = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).apply {
|
|
148
|
+
putShort(1000) // L1
|
|
149
|
+
putShort(2000) // R1
|
|
150
|
+
putShort(3000) // L2
|
|
151
|
+
putShort(4000) // R2
|
|
152
|
+
}.array()
|
|
153
|
+
|
|
154
|
+
// When
|
|
155
|
+
val monoData = AudioFormatUtils.convertChannels(stereoData, 2, 1, 16)
|
|
156
|
+
|
|
157
|
+
// Then
|
|
158
|
+
val buffer = ByteBuffer.wrap(monoData).order(ByteOrder.LITTLE_ENDIAN)
|
|
159
|
+
assertEquals("Should have 2 mono samples", 4, monoData.size)
|
|
160
|
+
|
|
161
|
+
// Each mono sample should be average of L+R
|
|
162
|
+
assertEquals("Sample 1", 1500, buffer.getShort().toInt()) // (1000+2000)/2
|
|
163
|
+
assertEquals("Sample 2", 3500, buffer.getShort().toInt()) // (3000+4000)/2
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@Test
|
|
167
|
+
fun testNormalizeAudio_quietSignal() {
|
|
168
|
+
// Given - Quiet 16-bit signal
|
|
169
|
+
val quietData = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).apply {
|
|
170
|
+
putShort(100)
|
|
171
|
+
putShort(-100)
|
|
172
|
+
putShort(50)
|
|
173
|
+
}.array()
|
|
174
|
+
|
|
175
|
+
// When
|
|
176
|
+
val normalized = AudioFormatUtils.normalizeAudio(quietData, 16)
|
|
177
|
+
|
|
178
|
+
// Then
|
|
179
|
+
val buffer = ByteBuffer.wrap(normalized).order(ByteOrder.LITTLE_ENDIAN)
|
|
180
|
+
val maxSample = abs(buffer.getShort().toInt())
|
|
181
|
+
buffer.rewind()
|
|
182
|
+
|
|
183
|
+
// The loudest sample should be close to max value
|
|
184
|
+
assertTrue("Should be normalized to near max", maxSample > 30000)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@Test
|
|
188
|
+
fun testNormalizeAudio_alreadyLoud() {
|
|
189
|
+
// Given - Already loud signal
|
|
190
|
+
val loudData = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).apply {
|
|
191
|
+
putShort(32000)
|
|
192
|
+
putShort(-32000)
|
|
193
|
+
}.array()
|
|
194
|
+
|
|
195
|
+
// When
|
|
196
|
+
val normalized = AudioFormatUtils.normalizeAudio(loudData, 16)
|
|
197
|
+
|
|
198
|
+
// Then
|
|
199
|
+
val buffer = ByteBuffer.wrap(normalized).order(ByteOrder.LITTLE_ENDIAN)
|
|
200
|
+
val sample1 = abs(buffer.getShort().toInt())
|
|
201
|
+
val sample2 = abs(buffer.getShort().toInt())
|
|
202
|
+
|
|
203
|
+
// Should be normalized but not clipped
|
|
204
|
+
assertTrue("Samples should be near max", sample1 > 32000 && sample2 > 32000)
|
|
205
|
+
assertTrue("Samples should not exceed max", sample1 <= 32767 && sample2 <= 32767)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@Test
|
|
209
|
+
fun testNormalizeAudio_silentSignal() {
|
|
210
|
+
// Given - Silent signal
|
|
211
|
+
val silentData = ByteBuffer.allocate(6).order(ByteOrder.LITTLE_ENDIAN).apply {
|
|
212
|
+
putShort(0)
|
|
213
|
+
putShort(0)
|
|
214
|
+
putShort(0)
|
|
215
|
+
}.array()
|
|
216
|
+
|
|
217
|
+
// When
|
|
218
|
+
val normalized = AudioFormatUtils.normalizeAudio(silentData, 16)
|
|
219
|
+
|
|
220
|
+
// Then
|
|
221
|
+
val buffer = ByteBuffer.wrap(normalized).order(ByteOrder.LITTLE_ENDIAN)
|
|
222
|
+
assertEquals("Silent should remain silent", 0, buffer.getShort().toInt())
|
|
223
|
+
assertEquals("Silent should remain silent", 0, buffer.getShort().toInt())
|
|
224
|
+
assertEquals("Silent should remain silent", 0, buffer.getShort().toInt())
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@Test
|
|
228
|
+
fun testResampleAudio_upsample() {
|
|
229
|
+
// Given - 8kHz mono audio
|
|
230
|
+
val samples8k = floatArrayOf(0.0f, 0.5f, 1.0f, 0.5f, 0.0f, -0.5f, -1.0f, -0.5f)
|
|
231
|
+
|
|
232
|
+
// When - Upsample to 16kHz
|
|
233
|
+
val samples16k = AudioFormatUtils.resampleAudio(samples8k, 8000, 16000)
|
|
234
|
+
|
|
235
|
+
// Then
|
|
236
|
+
assertEquals("Should have approximately double samples", 16, samples16k.size)
|
|
237
|
+
// First and last samples should match
|
|
238
|
+
assertEquals("First sample", samples8k[0], samples16k[0], 0.01f)
|
|
239
|
+
assertEquals("Last sample", samples8k.last(), samples16k.last(), 0.01f)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@Test
|
|
243
|
+
fun testResampleAudio_downsample() {
|
|
244
|
+
// Given - 16kHz mono audio
|
|
245
|
+
val samples16k = floatArrayOf(
|
|
246
|
+
0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 0.75f, 0.5f, 0.25f,
|
|
247
|
+
0.0f, -0.25f, -0.5f, -0.75f, -1.0f, -0.75f, -0.5f, -0.25f
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// When - Downsample to 8kHz
|
|
251
|
+
val samples8k = AudioFormatUtils.resampleAudio(samples16k, 16000, 8000)
|
|
252
|
+
|
|
253
|
+
// Then
|
|
254
|
+
assertEquals("Should have approximately half samples", 8, samples8k.size)
|
|
255
|
+
// Check general shape is preserved
|
|
256
|
+
val maxValue = samples8k.maxOrNull() ?: 0f
|
|
257
|
+
val minValue = samples8k.minOrNull() ?: 0f
|
|
258
|
+
assertTrue("Peak should be preserved", maxValue > 0.9f)
|
|
259
|
+
assertTrue("Trough should be preserved", minValue < -0.9f)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@Test
|
|
263
|
+
fun testResampleAudio_sameRate() {
|
|
264
|
+
// Given
|
|
265
|
+
val samples = floatArrayOf(0.1f, 0.2f, 0.3f, 0.4f, 0.5f)
|
|
266
|
+
|
|
267
|
+
// When - Same sample rate
|
|
268
|
+
val resampled = AudioFormatUtils.resampleAudio(samples, 44100, 44100)
|
|
269
|
+
|
|
270
|
+
// Then
|
|
271
|
+
assertArrayEquals("Should return same samples", samples, resampled, 0.001f)
|
|
272
|
+
}
|
|
273
|
+
}
|