@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,259 @@
|
|
|
1
|
+
package net.siteed.audiostudio
|
|
2
|
+
|
|
3
|
+
import android.media.AudioFormat
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import java.io.File
|
|
6
|
+
|
|
7
|
+
// New output configuration structure
|
|
8
|
+
data class OutputConfig(
|
|
9
|
+
val primary: PrimaryOutput = PrimaryOutput(),
|
|
10
|
+
val compressed: CompressedOutput = CompressedOutput()
|
|
11
|
+
) {
|
|
12
|
+
data class PrimaryOutput(
|
|
13
|
+
val enabled: Boolean = true,
|
|
14
|
+
val format: String = "wav"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
data class CompressedOutput(
|
|
18
|
+
val enabled: Boolean = false,
|
|
19
|
+
val format: String = "aac",
|
|
20
|
+
val bitrate: Int = 128000,
|
|
21
|
+
val preferRawStream: Boolean = false
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
companion object {
|
|
25
|
+
fun fromMap(map: Map<String, Any?>?): OutputConfig {
|
|
26
|
+
if (map == null) return OutputConfig()
|
|
27
|
+
|
|
28
|
+
val primaryMap = map.getTypedMap<Any?>("primary") { true }
|
|
29
|
+
val compressedMap = map.getTypedMap<Any?>("compressed") { true }
|
|
30
|
+
|
|
31
|
+
val primary = PrimaryOutput(
|
|
32
|
+
enabled = primaryMap.getBooleanOrDefault("enabled", true),
|
|
33
|
+
format = primaryMap.getStringOrDefault("format", "wav")
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
val compressed = CompressedOutput(
|
|
37
|
+
enabled = compressedMap.getBooleanOrDefault("enabled", false),
|
|
38
|
+
format = compressedMap.getStringOrDefault("format", "aac").lowercase(),
|
|
39
|
+
bitrate = compressedMap.getNumberOrDefault("bitrate", 128000),
|
|
40
|
+
preferRawStream = compressedMap.getBooleanOrDefault("preferRawStream", false)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return OutputConfig(primary = primary, compressed = compressed)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
data class RecordingConfig(
|
|
49
|
+
val sampleRate: Int = Constants.DEFAULT_SAMPLE_RATE,
|
|
50
|
+
val channels: Int = 1,
|
|
51
|
+
val encoding: String = "pcm_16bit",
|
|
52
|
+
val keepAwake: Boolean = true,
|
|
53
|
+
val interval: Long = Constants.DEFAULT_INTERVAL,
|
|
54
|
+
val intervalAnalysis: Long = Constants.DEFAULT_INTERVAL_ANALYSIS,
|
|
55
|
+
val enableProcessing: Boolean = false,
|
|
56
|
+
val segmentDurationMs: Int = 100,
|
|
57
|
+
val showNotification: Boolean = false,
|
|
58
|
+
val showWaveformInNotification: Boolean = false,
|
|
59
|
+
val notification: NotificationConfig = NotificationConfig(),
|
|
60
|
+
val features: Map<String, Boolean> = emptyMap(),
|
|
61
|
+
val output: OutputConfig = OutputConfig(),
|
|
62
|
+
val autoResumeAfterInterruption: Boolean = false,
|
|
63
|
+
val outputDirectory: String? = null,
|
|
64
|
+
val filename: String? = null,
|
|
65
|
+
val deviceId: String? = null,
|
|
66
|
+
val deviceDisconnectionBehavior: String? = null,
|
|
67
|
+
val audioFocusStrategy: String? = null,
|
|
68
|
+
val bufferDurationSeconds: Double? = null,
|
|
69
|
+
val streamFormat: String = "raw",
|
|
70
|
+
) {
|
|
71
|
+
companion object {
|
|
72
|
+
fun fromMap(options: Map<String, Any?>?): Result<Pair<RecordingConfig, AudioFormatInfo>> {
|
|
73
|
+
if (options == null) {
|
|
74
|
+
val defaultConfig = RecordingConfig()
|
|
75
|
+
val defaultFormat = AudioFormatInfo(
|
|
76
|
+
format = AudioFormat.ENCODING_PCM_16BIT,
|
|
77
|
+
mimeType = "audio/wav",
|
|
78
|
+
fileExtension = "wav"
|
|
79
|
+
)
|
|
80
|
+
return Result.success(Pair(defaultConfig, defaultFormat))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Extract features using type-safe helper
|
|
84
|
+
val features = options.getTypedMap<Boolean>("features") { it is Boolean }
|
|
85
|
+
|
|
86
|
+
// Parse notification config using type-safe helper
|
|
87
|
+
val notificationMap = options.getTypedMap<Any?>("notification") { true }
|
|
88
|
+
val notificationConfig = NotificationConfig.fromMap(notificationMap)
|
|
89
|
+
|
|
90
|
+
// Parse output config
|
|
91
|
+
val outputMap = options.getTypedMap<Any?>("output") { true }
|
|
92
|
+
val outputConfig = OutputConfig.fromMap(outputMap)
|
|
93
|
+
|
|
94
|
+
// Validate bitrate if compression is enabled
|
|
95
|
+
if (outputConfig.compressed.enabled) {
|
|
96
|
+
when {
|
|
97
|
+
outputConfig.compressed.bitrate < 8000 -> return Result.failure(
|
|
98
|
+
IllegalArgumentException("Bitrate must be at least 8000 bps")
|
|
99
|
+
)
|
|
100
|
+
outputConfig.compressed.bitrate > 960000 -> return Result.failure(
|
|
101
|
+
IllegalArgumentException("Bitrate cannot exceed 960000 bps")
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Only validate directory if it's provided
|
|
107
|
+
val outputDirectory = options["outputDirectory"] as? String
|
|
108
|
+
if (outputDirectory != null) {
|
|
109
|
+
// Clean up the directory path by removing file:// protocol and normalizing
|
|
110
|
+
val cleanDirectory = outputDirectory
|
|
111
|
+
.replace(Regex("^file://"), "")
|
|
112
|
+
.trim('/')
|
|
113
|
+
.replace("//", "/")
|
|
114
|
+
|
|
115
|
+
val directory = File(cleanDirectory)
|
|
116
|
+
if (!directory.exists()) {
|
|
117
|
+
return Result.failure(IllegalArgumentException("Directory does not exist: $cleanDirectory"))
|
|
118
|
+
}
|
|
119
|
+
if (!directory.isDirectory) {
|
|
120
|
+
return Result.failure(IllegalArgumentException("Path is not a directory: $cleanDirectory"))
|
|
121
|
+
}
|
|
122
|
+
if (!directory.canWrite()) {
|
|
123
|
+
return Result.failure(IllegalArgumentException("Directory is not writable: $cleanDirectory"))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Get device-related settings
|
|
128
|
+
val deviceId = options["deviceId"] as? String
|
|
129
|
+
val deviceDisconnectionBehavior = options["deviceDisconnectionBehavior"] as? String
|
|
130
|
+
|
|
131
|
+
// Get Android-specific settings
|
|
132
|
+
val androidConfig = options["android"] as? Map<String, Any>
|
|
133
|
+
val audioFocusStrategy = androidConfig?.get("audioFocusStrategy") as? String
|
|
134
|
+
|
|
135
|
+
// Initialize the recording configuration with cleaned directory path
|
|
136
|
+
val tempRecordingConfig = RecordingConfig(
|
|
137
|
+
sampleRate = options.getNumberOrDefault("sampleRate", Constants.DEFAULT_SAMPLE_RATE),
|
|
138
|
+
channels = options.getNumberOrDefault("channels", 1),
|
|
139
|
+
encoding = options.getStringOrDefault("encoding", "pcm_16bit"),
|
|
140
|
+
keepAwake = options.getBooleanOrDefault("keepAwake", true),
|
|
141
|
+
// Enforce minimum intervals to prevent excessive CPU usage
|
|
142
|
+
interval = maxOf(Constants.MIN_INTERVAL, options.getNumberOrDefault("interval", Constants.DEFAULT_INTERVAL)),
|
|
143
|
+
intervalAnalysis = maxOf(Constants.MIN_INTERVAL, options.getNumberOrDefault("intervalAnalysis", Constants.DEFAULT_INTERVAL_ANALYSIS)),
|
|
144
|
+
enableProcessing = options.getBooleanOrDefault("enableProcessing", false),
|
|
145
|
+
segmentDurationMs = options.getNumberOrDefault("segmentDurationMs", 100),
|
|
146
|
+
showNotification = options.getBooleanOrDefault("showNotification", false),
|
|
147
|
+
showWaveformInNotification = options.getBooleanOrDefault("showWaveformInNotification", false),
|
|
148
|
+
notification = notificationConfig,
|
|
149
|
+
features = features,
|
|
150
|
+
output = outputConfig,
|
|
151
|
+
autoResumeAfterInterruption = options.getBooleanOrDefault("autoResumeAfterInterruption", false),
|
|
152
|
+
outputDirectory = outputDirectory?.let {
|
|
153
|
+
it.replace(Regex("^file://"), "")
|
|
154
|
+
.trim('/')
|
|
155
|
+
.replace("//", "/")
|
|
156
|
+
},
|
|
157
|
+
filename = options["filename"] as? String,
|
|
158
|
+
deviceId = deviceId,
|
|
159
|
+
deviceDisconnectionBehavior = deviceDisconnectionBehavior,
|
|
160
|
+
audioFocusStrategy = audioFocusStrategy,
|
|
161
|
+
bufferDurationSeconds = (options["bufferDurationSeconds"] as? Number)?.toDouble(),
|
|
162
|
+
streamFormat = options.getStringOrDefault("streamFormat", "raw"),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// Validate sample rate and channels
|
|
166
|
+
if (tempRecordingConfig.sampleRate !in listOf(16000, 44100, 48000)) {
|
|
167
|
+
return Result.failure(
|
|
168
|
+
IllegalArgumentException("Sample rate must be one of 16000, 44100, or 48000 Hz")
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
if (tempRecordingConfig.channels !in 1..2) {
|
|
172
|
+
return Result.failure(
|
|
173
|
+
IllegalArgumentException("Channels must be either 1 (Mono) or 2 (Stereo)")
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Set encoding and file extension
|
|
178
|
+
val audioFormatInfo = when (tempRecordingConfig.encoding) {
|
|
179
|
+
"pcm_8bit" -> AudioFormatInfo(
|
|
180
|
+
format = AudioFormat.ENCODING_PCM_8BIT,
|
|
181
|
+
mimeType = "audio/wav",
|
|
182
|
+
fileExtension = "wav"
|
|
183
|
+
)
|
|
184
|
+
"pcm_16bit" -> AudioFormatInfo(
|
|
185
|
+
format = AudioFormat.ENCODING_PCM_16BIT,
|
|
186
|
+
mimeType = "audio/wav",
|
|
187
|
+
fileExtension = "wav"
|
|
188
|
+
)
|
|
189
|
+
"pcm_32bit" -> AudioFormatInfo(
|
|
190
|
+
format = AudioFormat.ENCODING_PCM_FLOAT,
|
|
191
|
+
mimeType = "audio/wav",
|
|
192
|
+
fileExtension = "wav"
|
|
193
|
+
)
|
|
194
|
+
"opus" -> {
|
|
195
|
+
if (Build.VERSION.SDK_INT < 29) {
|
|
196
|
+
return Result.failure(
|
|
197
|
+
IllegalArgumentException("Opus encoding not supported on this Android version.")
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
AudioFormatInfo(
|
|
201
|
+
format = if (Build.VERSION.SDK_INT >= 29) 20 else AudioFormat.ENCODING_DEFAULT, // 20 is ENCODING_OPUS
|
|
202
|
+
mimeType = "audio/opus",
|
|
203
|
+
fileExtension = "opus"
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
"aac_lc" -> AudioFormatInfo(
|
|
207
|
+
format = AudioFormat.ENCODING_AAC_LC,
|
|
208
|
+
mimeType = "audio/aac",
|
|
209
|
+
fileExtension = "aac"
|
|
210
|
+
)
|
|
211
|
+
else -> AudioFormatInfo(
|
|
212
|
+
format = AudioFormat.ENCODING_DEFAULT,
|
|
213
|
+
mimeType = "audio/wav",
|
|
214
|
+
fileExtension = "wav"
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return Result.success(Pair(tempRecordingConfig, audioFormatInfo))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Extension functions for type-safe map access
|
|
224
|
+
private inline fun <reified T> Map<String, Any?>.getTypedMap(
|
|
225
|
+
key: String,
|
|
226
|
+
predicate: (Any?) -> Boolean
|
|
227
|
+
): Map<String, T> {
|
|
228
|
+
return (this[key] as? Map<*, *>)?.mapNotNull { (k, v) ->
|
|
229
|
+
if (k is String && predicate(v)) {
|
|
230
|
+
k to (v as T)
|
|
231
|
+
} else null
|
|
232
|
+
}?.toMap() ?: emptyMap()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private fun Map<String, Any?>.getStringOrDefault(key: String, default: String): String {
|
|
236
|
+
return this[key] as? String ?: default
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private fun Map<String, Any?>.getBooleanOrDefault(key: String, default: Boolean): Boolean {
|
|
240
|
+
return this[key] as? Boolean ?: default
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Int): Int {
|
|
244
|
+
return (this[key] as? Number)?.toInt() ?: default
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Long): Long {
|
|
248
|
+
return (this[key] as? Number)?.toLong() ?: default
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private fun Map<String, Any?>.getNumberOrDefault(key: String, default: Double): Double {
|
|
252
|
+
return (this[key] as? Number)?.toDouble() ?: default
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
data class AudioFormatInfo(
|
|
256
|
+
val format: Int,
|
|
257
|
+
val mimeType: String,
|
|
258
|
+
val fileExtension: String
|
|
259
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package net.siteed.audiostudio
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for the notification waveform visualization
|
|
5
|
+
* @property color The color of the waveform (e.g., "#FFFFFF" for white)
|
|
6
|
+
* @property opacity Opacity of the waveform (0.0-1.0)
|
|
7
|
+
* @property strokeWidth Width of the waveform line (default: 1.5f)
|
|
8
|
+
* @property style Drawing style: "stroke" for outline, "fill" for solid
|
|
9
|
+
* @property mirror Whether to mirror the waveform (symmetrical display)
|
|
10
|
+
* @property height Height of the waveform view in dp (default: 64)
|
|
11
|
+
*/
|
|
12
|
+
data class WaveformConfig(
|
|
13
|
+
val color: String = "#FFFFFF",
|
|
14
|
+
val opacity: Float = 1.0f,
|
|
15
|
+
val strokeWidth: Float = 1.5f,
|
|
16
|
+
val style: String = "stroke", // "stroke" or "fill"
|
|
17
|
+
val mirror: Boolean = true,
|
|
18
|
+
val height: Int = 64
|
|
19
|
+
)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// WaveformRenderer.kt
|
|
2
|
+
package net.siteed.audiostudio
|
|
3
|
+
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.Canvas
|
|
6
|
+
import android.graphics.Color
|
|
7
|
+
import android.graphics.Paint
|
|
8
|
+
import android.graphics.Path
|
|
9
|
+
import kotlin.math.abs
|
|
10
|
+
|
|
11
|
+
class WaveformRenderer {
|
|
12
|
+
private enum class Style {
|
|
13
|
+
STROKE,
|
|
14
|
+
FILL
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private data class RenderConfig(
|
|
18
|
+
val color: Int = Color.WHITE,
|
|
19
|
+
val opacity: Float = 1.0f,
|
|
20
|
+
val strokeWidth: Float = 1.5f,
|
|
21
|
+
val style: Style = Style.STROKE,
|
|
22
|
+
val mirror: Boolean = true,
|
|
23
|
+
val height: Int = 64
|
|
24
|
+
) {
|
|
25
|
+
companion object {
|
|
26
|
+
fun fromConfig(config: WaveformConfig?): RenderConfig {
|
|
27
|
+
if (config == null) return RenderConfig()
|
|
28
|
+
|
|
29
|
+
return RenderConfig(
|
|
30
|
+
color = try {
|
|
31
|
+
Color.parseColor(config.color)
|
|
32
|
+
} catch (e: Exception) {
|
|
33
|
+
Color.WHITE
|
|
34
|
+
},
|
|
35
|
+
opacity = config.opacity.coerceIn(0f, 1f),
|
|
36
|
+
strokeWidth = config.strokeWidth.coerceAtLeast(0.5f),
|
|
37
|
+
style = when (config.style.lowercase()) {
|
|
38
|
+
"fill" -> Style.FILL
|
|
39
|
+
else -> Style.STROKE
|
|
40
|
+
},
|
|
41
|
+
mirror = config.mirror,
|
|
42
|
+
height = config.height.coerceIn(32, 128)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun generateWaveform(audioData: FloatArray, config: WaveformConfig?): Bitmap {
|
|
49
|
+
return generateWaveform(audioData, RenderConfig.fromConfig(config))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fun e(audioData: FloatArray): Bitmap {
|
|
53
|
+
return generateWaveform(audioData, RenderConfig())
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private fun generateWaveform(audioData: FloatArray, config: RenderConfig): Bitmap {
|
|
57
|
+
val width = 400 // Fixed width for notification
|
|
58
|
+
val height = config.height
|
|
59
|
+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
60
|
+
val canvas = Canvas(bitmap)
|
|
61
|
+
|
|
62
|
+
val paint = Paint().apply {
|
|
63
|
+
color = config.color
|
|
64
|
+
alpha = (config.opacity * 255).toInt()
|
|
65
|
+
strokeWidth = config.strokeWidth
|
|
66
|
+
isAntiAlias = true
|
|
67
|
+
style = when (config.style) {
|
|
68
|
+
Style.FILL -> Paint.Style.FILL
|
|
69
|
+
Style.STROKE -> Paint.Style.STROKE
|
|
70
|
+
}
|
|
71
|
+
strokeCap = Paint.Cap.ROUND
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
canvas.drawColor(Color.TRANSPARENT)
|
|
75
|
+
|
|
76
|
+
val centerY = height / 2f
|
|
77
|
+
val pointsPerSegment = (audioData.size / width.toFloat()).coerceAtLeast(1f)
|
|
78
|
+
val path = Path()
|
|
79
|
+
|
|
80
|
+
// Calculate max amplitude for scaling
|
|
81
|
+
val maxAmplitude = audioData.maxOf { abs(it) }.coerceAtLeast(0.01f)
|
|
82
|
+
val scaleFactor = (height * 0.4f) / maxAmplitude
|
|
83
|
+
|
|
84
|
+
drawWaveform(path, audioData, width, centerY, pointsPerSegment, scaleFactor)
|
|
85
|
+
|
|
86
|
+
if (config.mirror) {
|
|
87
|
+
drawMirroredWaveform(path, audioData, width, centerY, pointsPerSegment, scaleFactor)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Add shadow for filled style
|
|
91
|
+
if (config.style == Style.FILL) {
|
|
92
|
+
paint.setShadowLayer(4f, 0f, 2f, Color.argb(50, 0, 0, 0))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
canvas.drawPath(path, paint)
|
|
96
|
+
return bitmap
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun drawWaveform(
|
|
100
|
+
path: Path,
|
|
101
|
+
audioData: FloatArray,
|
|
102
|
+
width: Int,
|
|
103
|
+
centerY: Float,
|
|
104
|
+
pointsPerSegment: Float,
|
|
105
|
+
scaleFactor: Float
|
|
106
|
+
) {
|
|
107
|
+
var firstPoint = true
|
|
108
|
+
for (i in 0 until width) {
|
|
109
|
+
val startIdx = (i * pointsPerSegment).toInt()
|
|
110
|
+
val endIdx = ((i + 1) * pointsPerSegment).toInt().coerceAtMost(audioData.size)
|
|
111
|
+
|
|
112
|
+
val avgAmplitude = calculateAverageAmplitude(audioData, startIdx, endIdx, scaleFactor)
|
|
113
|
+
|
|
114
|
+
val x = i.toFloat()
|
|
115
|
+
val y = centerY - avgAmplitude
|
|
116
|
+
|
|
117
|
+
if (firstPoint) {
|
|
118
|
+
path.moveTo(x, y)
|
|
119
|
+
firstPoint = false
|
|
120
|
+
} else {
|
|
121
|
+
path.lineTo(x, y)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private fun drawMirroredWaveform(
|
|
127
|
+
path: Path,
|
|
128
|
+
audioData: FloatArray,
|
|
129
|
+
width: Int,
|
|
130
|
+
centerY: Float,
|
|
131
|
+
pointsPerSegment: Float,
|
|
132
|
+
scaleFactor: Float
|
|
133
|
+
) {
|
|
134
|
+
for (i in width - 1 downTo 0) {
|
|
135
|
+
val startIdx = (i * pointsPerSegment).toInt()
|
|
136
|
+
val endIdx = ((i + 1) * pointsPerSegment).toInt().coerceAtMost(audioData.size)
|
|
137
|
+
|
|
138
|
+
val avgAmplitude = calculateAverageAmplitude(audioData, startIdx, endIdx, scaleFactor)
|
|
139
|
+
path.lineTo(i.toFloat(), centerY + avgAmplitude)
|
|
140
|
+
}
|
|
141
|
+
path.close()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private fun calculateAverageAmplitude(
|
|
145
|
+
audioData: FloatArray,
|
|
146
|
+
startIdx: Int,
|
|
147
|
+
endIdx: Int,
|
|
148
|
+
scaleFactor: Float
|
|
149
|
+
): Float {
|
|
150
|
+
return if (endIdx > startIdx) {
|
|
151
|
+
audioData.slice(startIdx until endIdx)
|
|
152
|
+
.map { abs(it) }
|
|
153
|
+
.average()
|
|
154
|
+
.toFloat() * scaleFactor
|
|
155
|
+
} else {
|
|
156
|
+
0f
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#include <jni.h>
|
|
2
|
+
#include <android/log.h>
|
|
3
|
+
#include "AudioFeatures.h"
|
|
4
|
+
#include <memory>
|
|
5
|
+
#include <mutex>
|
|
6
|
+
|
|
7
|
+
#define LOG_TAG "AudioFeaturesJNI"
|
|
8
|
+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
|
9
|
+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
10
|
+
|
|
11
|
+
static std::unique_ptr<AudioFeaturesProcessor> cachedProcessor;
|
|
12
|
+
static std::mutex cachedMutex;
|
|
13
|
+
|
|
14
|
+
extern "C" JNIEXPORT void JNICALL
|
|
15
|
+
Java_net_siteed_audiostudio_AudioFeaturesNative_init(
|
|
16
|
+
JNIEnv* env, jobject /* thiz */,
|
|
17
|
+
jint sampleRate, jint fftLength, jint nMfcc, jint nMelFilters,
|
|
18
|
+
jboolean computeMfcc, jboolean computeChroma)
|
|
19
|
+
{
|
|
20
|
+
AudioFeaturesConfig config;
|
|
21
|
+
config.sampleRate = sampleRate;
|
|
22
|
+
config.fftLength = fftLength;
|
|
23
|
+
config.nMfcc = nMfcc;
|
|
24
|
+
config.nMelFilters = nMelFilters;
|
|
25
|
+
config.computeMfcc = computeMfcc;
|
|
26
|
+
config.computeChroma = computeChroma;
|
|
27
|
+
|
|
28
|
+
std::lock_guard<std::mutex> lock(cachedMutex);
|
|
29
|
+
if (!cachedProcessor || !(cachedProcessor->config() == config)) {
|
|
30
|
+
cachedProcessor = std::make_unique<AudioFeaturesProcessor>(config);
|
|
31
|
+
}
|
|
32
|
+
LOGI("init: sampleRate=%d, fftLength=%d, nMfcc=%d, nMelFilters=%d",
|
|
33
|
+
sampleRate, fftLength, nMfcc, nMelFilters);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
extern "C" JNIEXPORT jobject JNICALL
|
|
37
|
+
Java_net_siteed_audiostudio_AudioFeaturesNative_computeFrame(
|
|
38
|
+
JNIEnv* env, jobject /* thiz */,
|
|
39
|
+
jfloatArray jSamples, jint sampleRate, jint fftLength,
|
|
40
|
+
jint nMfcc, jint nMelFilters, jboolean computeMfcc, jboolean computeChroma)
|
|
41
|
+
{
|
|
42
|
+
jfloat* samples = env->GetFloatArrayElements(jSamples, nullptr);
|
|
43
|
+
if (!samples) {
|
|
44
|
+
LOGE("computeFrame: failed to get samples array");
|
|
45
|
+
return nullptr;
|
|
46
|
+
}
|
|
47
|
+
jint numSamples = env->GetArrayLength(jSamples);
|
|
48
|
+
|
|
49
|
+
AudioFeaturesConfig config;
|
|
50
|
+
config.sampleRate = sampleRate;
|
|
51
|
+
config.fftLength = fftLength;
|
|
52
|
+
config.nMfcc = nMfcc;
|
|
53
|
+
config.nMelFilters = nMelFilters;
|
|
54
|
+
config.computeMfcc = computeMfcc;
|
|
55
|
+
config.computeChroma = computeChroma;
|
|
56
|
+
|
|
57
|
+
std::lock_guard<std::mutex> lock(cachedMutex);
|
|
58
|
+
if (!cachedProcessor || !(cachedProcessor->config() == config)) {
|
|
59
|
+
cachedProcessor = std::make_unique<AudioFeaturesProcessor>(config);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
AudioFeaturesResult result = cachedProcessor->compute(samples, numSamples);
|
|
63
|
+
|
|
64
|
+
env->ReleaseFloatArrayElements(jSamples, samples, JNI_ABORT);
|
|
65
|
+
|
|
66
|
+
LOGI("computeFrame: centroid=%.2f, flatness=%.6f, rolloff=%.2f, bandwidth=%.2f, mfcc_size=%d, chroma_size=%d",
|
|
67
|
+
result.spectralCentroid, result.spectralFlatness,
|
|
68
|
+
result.spectralRolloff, result.spectralBandwidth,
|
|
69
|
+
(int)result.mfcc.size(), (int)result.chromagram.size());
|
|
70
|
+
|
|
71
|
+
// Build HashMap<String, Object>
|
|
72
|
+
jclass hashMapClass = env->FindClass("java/util/HashMap");
|
|
73
|
+
if (!hashMapClass || env->ExceptionCheck()) {
|
|
74
|
+
LOGE("computeFrame: failed to find HashMap class");
|
|
75
|
+
env->ExceptionClear();
|
|
76
|
+
return nullptr;
|
|
77
|
+
}
|
|
78
|
+
jmethodID hashMapInit = env->GetMethodID(hashMapClass, "<init>", "()V");
|
|
79
|
+
jmethodID hashMapPut = env->GetMethodID(hashMapClass, "put",
|
|
80
|
+
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
|
81
|
+
if (!hashMapInit || !hashMapPut || env->ExceptionCheck()) {
|
|
82
|
+
LOGE("computeFrame: failed to get HashMap methods");
|
|
83
|
+
env->ExceptionClear();
|
|
84
|
+
env->DeleteLocalRef(hashMapClass);
|
|
85
|
+
return nullptr;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
jobject map = env->NewObject(hashMapClass, hashMapInit);
|
|
89
|
+
if (!map) {
|
|
90
|
+
LOGE("computeFrame: failed to create HashMap");
|
|
91
|
+
env->DeleteLocalRef(hashMapClass);
|
|
92
|
+
return nullptr;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Helper: box a float as java.lang.Float
|
|
96
|
+
jclass floatClass = env->FindClass("java/lang/Float");
|
|
97
|
+
jmethodID floatValueOf = env->GetStaticMethodID(floatClass, "valueOf", "(F)Ljava/lang/Float;");
|
|
98
|
+
if (!floatClass || !floatValueOf || env->ExceptionCheck()) {
|
|
99
|
+
LOGE("computeFrame: failed to get Float class/method");
|
|
100
|
+
env->ExceptionClear();
|
|
101
|
+
if (floatClass) env->DeleteLocalRef(floatClass);
|
|
102
|
+
env->DeleteLocalRef(hashMapClass);
|
|
103
|
+
return map; // return partial map with no values
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Put scalar values
|
|
107
|
+
auto putFloat = [&](const char* key, float value) {
|
|
108
|
+
jstring jKey = env->NewStringUTF(key);
|
|
109
|
+
if (!jKey) return;
|
|
110
|
+
jobject jVal = env->CallStaticObjectMethod(floatClass, floatValueOf, value);
|
|
111
|
+
env->CallObjectMethod(map, hashMapPut, jKey, jVal);
|
|
112
|
+
env->DeleteLocalRef(jKey);
|
|
113
|
+
env->DeleteLocalRef(jVal);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
putFloat("spectralCentroid", result.spectralCentroid);
|
|
117
|
+
putFloat("spectralFlatness", result.spectralFlatness);
|
|
118
|
+
putFloat("spectralRolloff", result.spectralRolloff);
|
|
119
|
+
putFloat("spectralBandwidth", result.spectralBandwidth);
|
|
120
|
+
|
|
121
|
+
// Put MFCC array
|
|
122
|
+
if (!result.mfcc.empty()) {
|
|
123
|
+
jfloatArray jMfcc = env->NewFloatArray(static_cast<jsize>(result.mfcc.size()));
|
|
124
|
+
if (jMfcc) {
|
|
125
|
+
env->SetFloatArrayRegion(jMfcc, 0, static_cast<jsize>(result.mfcc.size()), result.mfcc.data());
|
|
126
|
+
jstring key = env->NewStringUTF("mfcc");
|
|
127
|
+
if (key) {
|
|
128
|
+
env->CallObjectMethod(map, hashMapPut, key, jMfcc);
|
|
129
|
+
env->DeleteLocalRef(key);
|
|
130
|
+
}
|
|
131
|
+
env->DeleteLocalRef(jMfcc);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Put chromagram array
|
|
136
|
+
if (!result.chromagram.empty()) {
|
|
137
|
+
jfloatArray jChroma = env->NewFloatArray(static_cast<jsize>(result.chromagram.size()));
|
|
138
|
+
if (jChroma) {
|
|
139
|
+
env->SetFloatArrayRegion(jChroma, 0, static_cast<jsize>(result.chromagram.size()), result.chromagram.data());
|
|
140
|
+
jstring key = env->NewStringUTF("chromagram");
|
|
141
|
+
if (key) {
|
|
142
|
+
env->CallObjectMethod(map, hashMapPut, key, jChroma);
|
|
143
|
+
env->DeleteLocalRef(key);
|
|
144
|
+
}
|
|
145
|
+
env->DeleteLocalRef(jChroma);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
env->DeleteLocalRef(floatClass);
|
|
150
|
+
env->DeleteLocalRef(hashMapClass);
|
|
151
|
+
return map;
|
|
152
|
+
}
|