@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,75 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import AVFoundation
|
|
3
|
+
import Accelerate
|
|
4
|
+
|
|
5
|
+
class TestAudioGenerator {
|
|
6
|
+
|
|
7
|
+
/// Generate a sine wave tone
|
|
8
|
+
static func generateTone(frequency: Double, duration: TimeInterval, sampleRate: Double = 44100) -> AVAudioPCMBuffer? {
|
|
9
|
+
let frameCount = AVAudioFrameCount(duration * sampleRate)
|
|
10
|
+
|
|
11
|
+
guard let buffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!, frameCapacity: frameCount) else {
|
|
12
|
+
return nil
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
buffer.frameLength = frameCount
|
|
16
|
+
|
|
17
|
+
let channelData = buffer.floatChannelData![0]
|
|
18
|
+
let angleIncrement = 2.0 * .pi * frequency / sampleRate
|
|
19
|
+
|
|
20
|
+
for frame in 0..<Int(frameCount) {
|
|
21
|
+
channelData[frame] = Float(sin(Double(frame) * angleIncrement))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return buffer
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Generate white noise
|
|
28
|
+
static func generateWhiteNoise(duration: TimeInterval, sampleRate: Double = 44100) -> AVAudioPCMBuffer? {
|
|
29
|
+
let frameCount = AVAudioFrameCount(duration * sampleRate)
|
|
30
|
+
|
|
31
|
+
guard let buffer = AVAudioPCMBuffer(pcmFormat: AVAudioFormat(standardFormatWithSampleRate: sampleRate, channels: 1)!, frameCapacity: frameCount) else {
|
|
32
|
+
return nil
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
buffer.frameLength = frameCount
|
|
36
|
+
|
|
37
|
+
let channelData = buffer.floatChannelData![0]
|
|
38
|
+
|
|
39
|
+
for frame in 0..<Int(frameCount) {
|
|
40
|
+
channelData[frame] = Float.random(in: -1...1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return buffer
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Load test asset from bundle
|
|
47
|
+
static func loadTestAsset(named name: String) -> AVAudioFile? {
|
|
48
|
+
guard let url = Bundle(for: TestAudioGenerator.self).url(forResource: name, withExtension: "wav") else {
|
|
49
|
+
return nil
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return try? AVAudioFile(forReading: url)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Convert AVAudioPCMBuffer to Data
|
|
56
|
+
static func bufferToData(_ buffer: AVAudioPCMBuffer) -> Data? {
|
|
57
|
+
guard let channelData = buffer.floatChannelData else { return nil }
|
|
58
|
+
|
|
59
|
+
let channelCount = Int(buffer.format.channelCount)
|
|
60
|
+
let frameLength = Int(buffer.frameLength)
|
|
61
|
+
let bytesPerFrame = 2 * channelCount // 16-bit audio
|
|
62
|
+
|
|
63
|
+
var data = Data(capacity: frameLength * bytesPerFrame)
|
|
64
|
+
|
|
65
|
+
for frame in 0..<frameLength {
|
|
66
|
+
for channel in 0..<channelCount {
|
|
67
|
+
let sample = channelData[channel][frame]
|
|
68
|
+
let int16Sample = Int16(max(-32768, min(32767, sample * 32767)))
|
|
69
|
+
data.append(contentsOf: withUnsafeBytes(of: int16Sample) { Array($0) })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return data
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DataPoint.swift
|
|
3
|
+
// AudioStudio
|
|
4
|
+
//
|
|
5
|
+
// Created by Arthur Breton on 23/6/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
public struct SpeechFeatures {
|
|
11
|
+
public var isActive: Bool
|
|
12
|
+
public var speakerId: Int?
|
|
13
|
+
|
|
14
|
+
func toDictionary() -> [String: Any] {
|
|
15
|
+
return [
|
|
16
|
+
"isActive": isActive,
|
|
17
|
+
"speakerId": speakerId as Any
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public struct DataPoint {
|
|
23
|
+
public var id: Int
|
|
24
|
+
public var amplitude: Float
|
|
25
|
+
public var rms: Float
|
|
26
|
+
public var dB: Float
|
|
27
|
+
public var silent: Bool
|
|
28
|
+
public var features: Features?
|
|
29
|
+
public var speech: SpeechFeatures?
|
|
30
|
+
public let startTime: Float // in seconds
|
|
31
|
+
public let endTime: Float // in seconds
|
|
32
|
+
public let startPosition: Int // byte position in audio file
|
|
33
|
+
public let endPosition: Int // byte position in audio file
|
|
34
|
+
public let samples: Int // number of samples in segment
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
extension DataPoint {
|
|
38
|
+
func toDictionary() -> [String: Any] {
|
|
39
|
+
return [
|
|
40
|
+
"id": id,
|
|
41
|
+
"amplitude": amplitude,
|
|
42
|
+
"rms": rms,
|
|
43
|
+
"dB": dB,
|
|
44
|
+
"silent": silent,
|
|
45
|
+
"features": features?.toDictionary() ?? [:],
|
|
46
|
+
"speech": speech?.toDictionary() ?? [:],
|
|
47
|
+
"startTime": startTime,
|
|
48
|
+
"endTime": endTime,
|
|
49
|
+
"startPosition": startPosition,
|
|
50
|
+
"endPosition": endPosition,
|
|
51
|
+
"samples": samples
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DecodingConfig.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Arthur Breton on 24/2/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import AVFoundation
|
|
9
|
+
|
|
10
|
+
public struct DecodingConfig {
|
|
11
|
+
public let targetSampleRate: Double?
|
|
12
|
+
public let targetChannels: Int?
|
|
13
|
+
public let targetBitDepth: Int?
|
|
14
|
+
public let normalizeAudio: Bool
|
|
15
|
+
|
|
16
|
+
public init(
|
|
17
|
+
targetSampleRate: Double?,
|
|
18
|
+
targetChannels: Int?,
|
|
19
|
+
targetBitDepth: Int?,
|
|
20
|
+
normalizeAudio: Bool = false
|
|
21
|
+
) {
|
|
22
|
+
self.targetSampleRate = targetSampleRate
|
|
23
|
+
self.targetChannels = targetChannels
|
|
24
|
+
self.targetBitDepth = targetBitDepth
|
|
25
|
+
self.normalizeAudio = normalizeAudio
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public static func fromDictionary(_ dict: [String: Any]?) -> DecodingConfig {
|
|
29
|
+
guard let dict = dict else {
|
|
30
|
+
return DecodingConfig.default
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return DecodingConfig(
|
|
34
|
+
targetSampleRate: dict["targetSampleRate"] as? Double,
|
|
35
|
+
targetChannels: dict["targetChannels"] as? Int,
|
|
36
|
+
targetBitDepth: dict["targetBitDepth"] as? Int,
|
|
37
|
+
normalizeAudio: dict["normalizeAudio"] as? Bool ?? false
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static var `default`: DecodingConfig {
|
|
42
|
+
return DecodingConfig(
|
|
43
|
+
targetSampleRate: nil,
|
|
44
|
+
targetChannels: nil,
|
|
45
|
+
targetBitDepth: nil,
|
|
46
|
+
normalizeAudio: false
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public func toAudioFormat(baseFormat: AVAudioFormat) -> AVAudioFormat {
|
|
51
|
+
let sampleRate = targetSampleRate ?? baseFormat.sampleRate
|
|
52
|
+
let channels = targetChannels ?? Int(baseFormat.channelCount)
|
|
53
|
+
|
|
54
|
+
return AVAudioFormat(
|
|
55
|
+
standardFormatWithSampleRate: sampleRate,
|
|
56
|
+
channels: AVAudioChannelCount(channels)
|
|
57
|
+
)!
|
|
58
|
+
}
|
|
59
|
+
}
|
package/ios/FFT.swift
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//
|
|
2
|
+
// FFT.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Arthur Breton on 20/2/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Accelerate
|
|
9
|
+
|
|
10
|
+
class FFT {
|
|
11
|
+
private let length: Int
|
|
12
|
+
private var setup: vDSP_DFT_Setup?
|
|
13
|
+
|
|
14
|
+
init(_ length: Int) {
|
|
15
|
+
self.length = length
|
|
16
|
+
self.setup = vDSP_DFT_zop_CreateSetup(
|
|
17
|
+
nil,
|
|
18
|
+
vDSP_Length(length),
|
|
19
|
+
vDSP_DFT_Direction.FORWARD
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
deinit {
|
|
24
|
+
if let setup = setup {
|
|
25
|
+
vDSP_DFT_DestroySetup(setup)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func realForward(_ data: inout [Float]) {
|
|
30
|
+
var realIn = data
|
|
31
|
+
var imagIn = [Float](repeating: 0.0, count: length)
|
|
32
|
+
var realOut = [Float](repeating: 0.0, count: length)
|
|
33
|
+
var imagOut = [Float](repeating: 0.0, count: length)
|
|
34
|
+
|
|
35
|
+
// Perform FFT
|
|
36
|
+
vDSP_DFT_Execute(setup!,
|
|
37
|
+
&realIn,
|
|
38
|
+
&imagIn,
|
|
39
|
+
&realOut,
|
|
40
|
+
&imagOut)
|
|
41
|
+
|
|
42
|
+
// Ensure data array has enough space for both real and imaginary parts
|
|
43
|
+
if data.count < 2 * length {
|
|
44
|
+
data.append(contentsOf: [Float](repeating: 0.0, count: 2 * length - data.count))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Combine real and imaginary parts
|
|
48
|
+
for i in 0..<length {
|
|
49
|
+
let j = i * 2
|
|
50
|
+
data[j] = realOut[i]
|
|
51
|
+
data[j + 1] = imagOut[i]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func processSegment(_ segment: [Float]) -> [Float] {
|
|
56
|
+
var fftData = segment.count < length ?
|
|
57
|
+
segment + [Float](repeating: 0, count: length - segment.count) :
|
|
58
|
+
Array(segment.prefix(length))
|
|
59
|
+
realForward(&fftData)
|
|
60
|
+
return fftData
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Features.swift
|
|
3
|
+
// AudioStudio
|
|
4
|
+
//
|
|
5
|
+
// Created by Arthur Breton on 23/6/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
public struct Features {
|
|
11
|
+
var energy: Float
|
|
12
|
+
var mfcc: [Float]
|
|
13
|
+
var rms: Float
|
|
14
|
+
var minAmplitude: Float
|
|
15
|
+
var maxAmplitude: Float
|
|
16
|
+
var zcr: Float
|
|
17
|
+
var spectralCentroid: Float
|
|
18
|
+
var spectralFlatness: Float
|
|
19
|
+
var spectralRolloff: Float?
|
|
20
|
+
var spectralBandwidth: Float?
|
|
21
|
+
var chromagram: [Float]?
|
|
22
|
+
var tempo: Float?
|
|
23
|
+
var hnr: Float?
|
|
24
|
+
var melSpectrogram: [Float]?
|
|
25
|
+
var spectralContrast: [Float]?
|
|
26
|
+
var tonnetz: [Float]?
|
|
27
|
+
var pitch: Float?
|
|
28
|
+
var crc32: UInt32?
|
|
29
|
+
|
|
30
|
+
init(
|
|
31
|
+
energy: Float = 0,
|
|
32
|
+
mfcc: [Float] = [],
|
|
33
|
+
rms: Float = 0,
|
|
34
|
+
minAmplitude: Float = 0,
|
|
35
|
+
maxAmplitude: Float = 0,
|
|
36
|
+
zcr: Float = 0,
|
|
37
|
+
spectralCentroid: Float = 0,
|
|
38
|
+
spectralFlatness: Float = 0,
|
|
39
|
+
spectralRolloff: Float? = nil,
|
|
40
|
+
spectralBandwidth: Float? = nil,
|
|
41
|
+
chromagram: [Float]? = nil,
|
|
42
|
+
tempo: Float? = nil,
|
|
43
|
+
hnr: Float? = nil,
|
|
44
|
+
melSpectrogram: [Float]? = nil,
|
|
45
|
+
spectralContrast: [Float]? = nil,
|
|
46
|
+
tonnetz: [Float]? = nil,
|
|
47
|
+
pitch: Float? = nil,
|
|
48
|
+
crc32: UInt32? = nil
|
|
49
|
+
) {
|
|
50
|
+
self.energy = energy
|
|
51
|
+
self.mfcc = mfcc
|
|
52
|
+
self.rms = rms
|
|
53
|
+
self.minAmplitude = minAmplitude
|
|
54
|
+
self.maxAmplitude = maxAmplitude
|
|
55
|
+
self.zcr = zcr
|
|
56
|
+
self.spectralCentroid = spectralCentroid
|
|
57
|
+
self.spectralFlatness = spectralFlatness
|
|
58
|
+
self.spectralRolloff = spectralRolloff
|
|
59
|
+
self.spectralBandwidth = spectralBandwidth
|
|
60
|
+
self.chromagram = chromagram
|
|
61
|
+
self.tempo = tempo
|
|
62
|
+
self.hnr = hnr
|
|
63
|
+
self.melSpectrogram = melSpectrogram
|
|
64
|
+
self.spectralContrast = spectralContrast
|
|
65
|
+
self.tonnetz = tonnetz
|
|
66
|
+
self.pitch = pitch
|
|
67
|
+
self.crc32 = crc32
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
extension Features {
|
|
72
|
+
func toDictionary() -> [String: Any] {
|
|
73
|
+
let dict: [String: Any] = [
|
|
74
|
+
"energy": energy,
|
|
75
|
+
"mfcc": mfcc,
|
|
76
|
+
"rms": rms,
|
|
77
|
+
"minAmplitude": minAmplitude,
|
|
78
|
+
"maxAmplitude": maxAmplitude,
|
|
79
|
+
"zcr": zcr,
|
|
80
|
+
"spectralCentroid": spectralCentroid,
|
|
81
|
+
"spectralFlatness": spectralFlatness,
|
|
82
|
+
"spectralRolloff": spectralRolloff ?? 0,
|
|
83
|
+
"spectralBandwidth": spectralBandwidth ?? 0,
|
|
84
|
+
"chromagram": chromagram ?? [],
|
|
85
|
+
"tempo": tempo ?? 0,
|
|
86
|
+
"hnr": hnr ?? 0,
|
|
87
|
+
"melSpectrogram": melSpectrogram ?? [],
|
|
88
|
+
"spectralContrast": spectralContrast ?? [],
|
|
89
|
+
"tonnetz": tonnetz ?? [],
|
|
90
|
+
"pitch": pitch ?? 0,
|
|
91
|
+
"crc32": crc32 ?? 0
|
|
92
|
+
]
|
|
93
|
+
return dict
|
|
94
|
+
}
|
|
95
|
+
}
|
package/ios/ISSUE_IOS.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# iOS Recording Issue: No WAV Data/Analysis When Resampling (e.g., 16kHz) - RESOLVED
|
|
2
|
+
|
|
3
|
+
## Problem Summary
|
|
4
|
+
|
|
5
|
+
Initially, the iOS implementation failed to record WAV audio data or perform real-time analysis when the requested `sampleRate` (e.g., 16,000 Hz) differed from the hardware's native sample rate (e.g., 48,000 Hz), requiring resampling. Recording at the native hardware sample rate worked correctly.
|
|
6
|
+
|
|
7
|
+
The primary symptom was that the tap installed on the `audioEngine.inputNode` using `installTap(onBus:bufferSize:format:)` was **not receiving any audio buffers** when resampling was required (i.e., requested rate != hardware rate). This resulted in:
|
|
8
|
+
|
|
9
|
+
* An empty WAV file (only the initial 44-byte header).
|
|
10
|
+
* No data being sent to the `AudioProcessor` for real-time analysis.
|
|
11
|
+
* No `AudioData` or `AudioAnalysis` events being emitted for the WAV stream.
|
|
12
|
+
|
|
13
|
+
Parallel compressed recording (e.g., AAC) functioned correctly even when the WAV stream failed, indicating the audio engine *was* capturing audio but not delivering it to the tap.
|
|
14
|
+
|
|
15
|
+
## Debugging History & Findings
|
|
16
|
+
|
|
17
|
+
1. **Initial State:** Empty WAV file at 16kHz, working AAC at 16kHz. Confirmed 48kHz WAV worked.
|
|
18
|
+
2. **Tap Installation Format:** Early attempts tried setting the tap format to the *requested* sample rate (16kHz), leading to `AVAudioIONodeImpl.mm:1334 Format mismatch` crashes because the tap format didn't match the actual hardware input format (often 48kHz).
|
|
19
|
+
3. **Using `inputNode.outputFormat`:** Switched to installing the tap using the format reported by `inputNode.outputFormat(forBus: 0)`, assuming this reflected the actual hardware format. Resampling was handled later in `processAudioBuffer`. This fixed the crash but **did not** fix the original issue – the tap still received no buffers at 16kHz.
|
|
20
|
+
4. **Race Condition:** Identified and fixed a race condition where `audioEngine.start()` was called before `isRecording` was set to `true`, causing the tap's initial guard check to fail. This allowed buffers to be processed *after* the flag was set, but the WAV file writing was still faulty (only the first buffer was written).
|
|
21
|
+
5. **Background File I/O Refactor:** Improved WAV file writing by keeping the `FileHandle` open during recording instead of opening/closing for each buffer in the background queue. This fixed the partial file writing issue but didn't solve the core "no buffers received at 16kHz" problem.
|
|
22
|
+
6. **Removing `setPreferredSampleRate`:** Tried removing the `session.setPreferredSampleRate` call, hoping the session would default to the hardware rate, allowing the 48kHz tap (based on `inputNode.outputFormat`) to receive buffers. This **worked** for the internal microphone but caused crashes with Bluetooth headsets, as the Bluetooth hardware *actually* operated at 16kHz, creating a new format mismatch.
|
|
23
|
+
7. **Using `session.sampleRate`:** Attempted to use `session.sampleRate` *after* session activation to determine the tap format. This also proved unreliable, sometimes reporting 16kHz for the session while `inputNode.outputFormat` still reported 48kHz, leading back to the format mismatch crash.
|
|
24
|
+
8. **Using `inputNode.inputFormat`:** After further analysis, discovered that `inputNode.inputFormat(forBus: 0)` gives the actual hardware input format, which may differ from the output format. This is the format that iOS strictly requires for a tap.
|
|
25
|
+
|
|
26
|
+
## Root Cause
|
|
27
|
+
|
|
28
|
+
The core issue stems from the unreliability and potential inconsistency between:
|
|
29
|
+
|
|
30
|
+
* `AVAudioSession.sampleRate` (especially after `setPreferredSampleRate` or device changes).
|
|
31
|
+
* `audioEngine.inputNode.outputFormat(forBus: 0)` (which might not immediately reflect the true hardware format).
|
|
32
|
+
* `audioEngine.inputNode.inputFormat(forBus: 0)` (which provides the hardware's actual input format).
|
|
33
|
+
* The actual sample rate the hardware is delivering to the audio engine.
|
|
34
|
+
|
|
35
|
+
Attempting to force a specific sample rate via `setPreferredSampleRate` or relying solely on `session.sampleRate` post-activation can lead to situations where the format used to install the tap does not match the format the audio engine expects/receives from the hardware input, causing either a crash (`Format mismatch`) or the tap simply not receiving any buffers.
|
|
36
|
+
|
|
37
|
+
## Final Solution
|
|
38
|
+
|
|
39
|
+
The most robust solution was found to be:
|
|
40
|
+
|
|
41
|
+
1. **Configure Session:** Set up the `AVAudioSession` category, mode, and options as required. **Do not** call `setPreferredSampleRate`. Let the session negotiate the rate with the hardware.
|
|
42
|
+
2. **Activate Session:** Activate the audio session.
|
|
43
|
+
3. **Query Hardware Input Format (Just-In-Time):** Immediately **before** calling `installTap`, query the hardware's input format using `audioEngine.inputNode.inputFormat(forBus: 0)`. This provides the actual format that the hardware is delivering and that iOS requires for the tap.
|
|
44
|
+
4. **Install Tap:** Install the tap using the exact `AVAudioFormat` obtained from `inputNode.inputFormat(forBus: 0)` in the previous step.
|
|
45
|
+
5. **Resample in Tap:** Inside the tap's processing closure (`processAudioBuffer`), check if the received `buffer.format.sampleRate` differs from the `settings.sampleRate` requested by the user. If they differ, perform the resampling explicitly using `AVAudioConverter` (or a similar method) before writing the WAV data or performing analysis.
|
|
46
|
+
|
|
47
|
+
This approach ensures the tap format always matches what the hardware requires, regardless of the device (internal mic, Bluetooth) or the requested output sample rate. Subsequent resampling handles the conversion to the user's desired format.
|
|
48
|
+
|
|
49
|
+
## Additional Findings: Device Disconnection Handling
|
|
50
|
+
|
|
51
|
+
During testing, we discovered an issue with device disconnection (particularly Bluetooth headsets):
|
|
52
|
+
|
|
53
|
+
1. When a recording device disconnects, iOS reports a route change notification.
|
|
54
|
+
2. If we attempt to resume recording after the device disconnection or switch to a new device, the app would crash with `Format mismatch: input hw <AVAudioFormat: 1 ch, 16000 Hz, Float32>, client format <AVAudioFormat: 1 ch, 48000 Hz, Float32>`.
|
|
55
|
+
|
|
56
|
+
We implemented a robust solution with the following components:
|
|
57
|
+
|
|
58
|
+
1. **Hardware Format Detection:** Created a shared `installTapWithHardwareFormat` method that always queries the current hardware input format using `inputNode.inputFormat(forBus: 0)` before installing a tap.
|
|
59
|
+
|
|
60
|
+
2. **Format Verification on Resume:** When resuming recording after a pause (potentially due to device disconnect), we reinstall the tap with the current hardware format.
|
|
61
|
+
|
|
62
|
+
3. **Fallback Device Handling:** Implemented a configurable device disconnection behavior:
|
|
63
|
+
- `pause`: Pause recording when the current device disconnects (default)
|
|
64
|
+
- `fallback`: Automatically switch to the default device (built-in mic) and continue recording
|
|
65
|
+
|
|
66
|
+
4. **Size Tracking Preservation:** Ensured that during device transitions, the total audio data size is preserved to maintain continuity in the recording.
|
|
67
|
+
|
|
68
|
+
These improvements ensure that when audio devices change during a recording session, the app can either pause gracefully or continue recording with a fallback device, without data loss or crashes due to format mismatches.
|
package/ios/Logger.swift
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class Logger {
|
|
2
|
+
// Similar to Android's TAG_PREFIX for consistent cross-platform logging
|
|
3
|
+
private static let TAG_PREFIX = "AudioStudio"
|
|
4
|
+
|
|
5
|
+
static func debug(_ className: String, _ message: @autoclosure () -> String) {
|
|
6
|
+
#if DEBUG
|
|
7
|
+
print("[\(TAG_PREFIX):\(className)] [DEBUG] \(message())")
|
|
8
|
+
#endif
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static func info(_ className: String, _ message: @autoclosure () -> String) {
|
|
12
|
+
print("[\(TAG_PREFIX):\(className)] [INFO] \(message())")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static func warn(_ className: String, _ message: @autoclosure () -> String) {
|
|
16
|
+
print("[\(TAG_PREFIX):\(className)] [WARN] ⚠️ \(message())")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static func error(_ className: String, _ message: @autoclosure () -> String) {
|
|
20
|
+
print("[\(TAG_PREFIX):\(className)] [ERROR] 🛑 \(message())")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// For backward compatibility with code that doesn't specify a class name
|
|
24
|
+
static func debug(_ message: @autoclosure () -> String) {
|
|
25
|
+
debug("General", message())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static func info(_ message: @autoclosure () -> String) {
|
|
29
|
+
info("General", message())
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static func warn(_ message: @autoclosure () -> String) {
|
|
33
|
+
warn("General", message())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static func error(_ message: @autoclosure () -> String) {
|
|
37
|
+
error("General", message())
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
@interface MelSpectrogramWrapper : NSObject
|
|
4
|
+
|
|
5
|
+
+ (nullable NSDictionary *)computeWithSamples:(const float *)samples
|
|
6
|
+
numSamples:(int)numSamples
|
|
7
|
+
sampleRate:(int)sampleRate
|
|
8
|
+
fftLength:(int)fftLength
|
|
9
|
+
windowSizeSamples:(int)windowSizeSamples
|
|
10
|
+
hopLengthSamples:(int)hopLengthSamples
|
|
11
|
+
nMels:(int)nMels
|
|
12
|
+
fMin:(float)fMin
|
|
13
|
+
fMax:(float)fMax
|
|
14
|
+
windowType:(int)windowType
|
|
15
|
+
logScale:(BOOL)logScale
|
|
16
|
+
normalize:(BOOL)normalize;
|
|
17
|
+
|
|
18
|
+
+ (void)initWithSampleRate:(int)sampleRate
|
|
19
|
+
fftLength:(int)fftLength
|
|
20
|
+
windowSizeSamples:(int)windowSizeSamples
|
|
21
|
+
hopLengthSamples:(int)hopLengthSamples
|
|
22
|
+
nMels:(int)nMels
|
|
23
|
+
fMin:(float)fMin
|
|
24
|
+
fMax:(float)fMax
|
|
25
|
+
windowType:(int)windowType;
|
|
26
|
+
|
|
27
|
+
+ (nullable NSArray<NSNumber *> *)computeFrameWithSamples:(const float *)samples
|
|
28
|
+
frameSize:(int)frameSize;
|
|
29
|
+
|
|
30
|
+
@end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#import "MelSpectrogramWrapper.h"
|
|
2
|
+
|
|
3
|
+
// Include C++ implementation directly since CocoaPods doesn't compile
|
|
4
|
+
// source files from outside the pod's root directory (../cpp/)
|
|
5
|
+
#include "kiss_fft/kiss_fft.c"
|
|
6
|
+
#include "kiss_fft/kiss_fftr.c"
|
|
7
|
+
#include "MelSpectrogram.cpp"
|
|
8
|
+
#include "MelSpectrogramBridge.cpp"
|
|
9
|
+
|
|
10
|
+
@implementation MelSpectrogramWrapper
|
|
11
|
+
|
|
12
|
+
+ (nullable NSDictionary *)computeWithSamples:(const float *)samples
|
|
13
|
+
numSamples:(int)numSamples
|
|
14
|
+
sampleRate:(int)sampleRate
|
|
15
|
+
fftLength:(int)fftLength
|
|
16
|
+
windowSizeSamples:(int)windowSizeSamples
|
|
17
|
+
hopLengthSamples:(int)hopLengthSamples
|
|
18
|
+
nMels:(int)nMels
|
|
19
|
+
fMin:(float)fMin
|
|
20
|
+
fMax:(float)fMax
|
|
21
|
+
windowType:(int)windowType
|
|
22
|
+
logScale:(BOOL)logScale
|
|
23
|
+
normalize:(BOOL)normalize
|
|
24
|
+
{
|
|
25
|
+
CMelSpectrogramResult* result = mel_spectrogram_compute(
|
|
26
|
+
samples, numSamples, sampleRate,
|
|
27
|
+
fftLength, windowSizeSamples, hopLengthSamples,
|
|
28
|
+
nMels, fMin, fMax,
|
|
29
|
+
windowType, logScale ? 1 : 0, normalize ? 1 : 0);
|
|
30
|
+
|
|
31
|
+
if (!result) {
|
|
32
|
+
return nil;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Convert flat float array to NSArray of NSArray<NSNumber>
|
|
36
|
+
NSMutableArray *spectrogram = [NSMutableArray arrayWithCapacity:result->timeSteps];
|
|
37
|
+
for (int i = 0; i < result->timeSteps; i++) {
|
|
38
|
+
NSMutableArray *row = [NSMutableArray arrayWithCapacity:result->nMels];
|
|
39
|
+
for (int j = 0; j < result->nMels; j++) {
|
|
40
|
+
[row addObject:@(result->data[i * result->nMels + j])];
|
|
41
|
+
}
|
|
42
|
+
[spectrogram addObject:row];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
NSDictionary *dict = @{
|
|
46
|
+
@"spectrogram": spectrogram,
|
|
47
|
+
@"timeSteps": @(result->timeSteps),
|
|
48
|
+
@"nMels": @(result->nMels)
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
mel_spectrogram_free(result);
|
|
52
|
+
|
|
53
|
+
return dict;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
+ (void)initWithSampleRate:(int)sampleRate
|
|
57
|
+
fftLength:(int)fftLength
|
|
58
|
+
windowSizeSamples:(int)windowSizeSamples
|
|
59
|
+
hopLengthSamples:(int)hopLengthSamples
|
|
60
|
+
nMels:(int)nMels
|
|
61
|
+
fMin:(float)fMin
|
|
62
|
+
fMax:(float)fMax
|
|
63
|
+
windowType:(int)windowType
|
|
64
|
+
{
|
|
65
|
+
mel_spectrogram_init(sampleRate, fftLength, windowSizeSamples,
|
|
66
|
+
hopLengthSamples, nMels, fMin, fMax, windowType);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
+ (nullable NSArray<NSNumber *> *)computeFrameWithSamples:(const float *)samples
|
|
70
|
+
frameSize:(int)frameSize
|
|
71
|
+
{
|
|
72
|
+
int nMels = mel_spectrogram_get_n_mels();
|
|
73
|
+
if (nMels <= 0) {
|
|
74
|
+
return nil;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
float* melOutput = (float*)malloc(nMels * sizeof(float));
|
|
78
|
+
if (!melOutput) {
|
|
79
|
+
return nil;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
int success = mel_spectrogram_compute_frame(samples, frameSize, melOutput);
|
|
83
|
+
if (!success) {
|
|
84
|
+
free(melOutput);
|
|
85
|
+
return nil;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
NSMutableArray<NSNumber *> *result = [NSMutableArray arrayWithCapacity:nMels];
|
|
89
|
+
for (int i = 0; i < nMels; i++) {
|
|
90
|
+
[result addObject:@(melOutput[i])];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
free(melOutput);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//
|
|
2
|
+
// NotificationExtension.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Arthur Breton on 27/10/2024.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
extension Notification.Name {
|
|
12
|
+
static let pauseRecording = Notification.Name("PAUSE_RECORDING")
|
|
13
|
+
static let resumeRecording = Notification.Name("RESUME_RECORDING")
|
|
14
|
+
static let notificationActionTriggered = Notification.Name("notificationActionTriggered")
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// RecordingResult.swift
|
|
2
|
+
|
|
3
|
+
struct RecordingResult {
|
|
4
|
+
var fileUri: String
|
|
5
|
+
var filename: String
|
|
6
|
+
var mimeType: String
|
|
7
|
+
var duration: Int64
|
|
8
|
+
var size: Int64
|
|
9
|
+
var channels: Int
|
|
10
|
+
var bitDepth: Int
|
|
11
|
+
var sampleRate: Double
|
|
12
|
+
var compression: CompressedRecordingInfo?
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
struct StartRecordingResult {
|
|
16
|
+
var fileUri: String
|
|
17
|
+
var mimeType: String
|
|
18
|
+
var channels: Int
|
|
19
|
+
var bitDepth: Int
|
|
20
|
+
var sampleRate: Double
|
|
21
|
+
var compression: CompressedRecordingInfo?
|
|
22
|
+
}
|