@glassly/bluetooth-sdk 0.1.0-dev.26
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/README.md +563 -0
- package/android/build.gradle +450 -0
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +212 -0
- package/android/lc3Lib/proguard-rules.pro +33 -0
- package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
- package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
- package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
- package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
- package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
- package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
- package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
- package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
- package/android/settings.gradle +4 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +50 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/AndroidManifest.xml +42 -0
- package/android/src/main/assets/G2Firmware/README.md +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/app.plugin.js +3 -0
- package/build/BluetoothSdk.types.d.ts +1354 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +41 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/_internal.d.ts +17 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +16 -0
- package/build/_internal.js.map +1 -0
- package/build/_private/BluetoothSdkModule.d.ts +172 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/_private/BluetoothSdkModule.js +300 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/_private/LocalNetworkModule.d.ts +38 -0
- package/build/_private/LocalNetworkModule.d.ts.map +1 -0
- package/build/_private/LocalNetworkModule.js +3 -0
- package/build/_private/LocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +32 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts +6 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +86 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/_private/sdkOtaManifest.d.ts +11 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +15 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +46 -0
- package/build/changelogs.js.map +1 -0
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +9 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +12 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +171 -0
- package/build/index.js.map +1 -0
- package/build/ota-server/OtaServer.types.d.ts +14 -0
- package/build/ota-server/OtaServer.types.d.ts.map +1 -0
- package/build/ota-server/OtaServer.types.js +2 -0
- package/build/ota-server/OtaServer.types.js.map +1 -0
- package/build/ota-server/OtaServerModule.d.ts +21 -0
- package/build/ota-server/OtaServerModule.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.js +3 -0
- package/build/ota-server/OtaServerModule.js.map +1 -0
- package/build/ota-server/OtaServerModule.web.d.ts +10 -0
- package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.web.js +12 -0
- package/build/ota-server/OtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.js +2 -0
- package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.js +3 -0
- package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetooth.d.ts +100 -0
- package/build/react/useBluetooth.d.ts.map +1 -0
- package/build/react/useBluetooth.js +167 -0
- package/build/react/useBluetooth.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/expo-module.config.json +14 -0
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +1034 -0
- package/ios/GlasslyBluetoothSDK.podspec +106 -0
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/LocalPhotoUploadServer.swift +754 -0
- package/ios/OtaServerModule.swift +147 -0
- package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
- package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
- package/ios/Packages/CoreObjC/attdet.c +92 -0
- package/ios/Packages/CoreObjC/attdet.h +44 -0
- package/ios/Packages/CoreObjC/bits.c +375 -0
- package/ios/Packages/CoreObjC/bits.h +315 -0
- package/ios/Packages/CoreObjC/bwdet.c +129 -0
- package/ios/Packages/CoreObjC/bwdet.h +69 -0
- package/ios/Packages/CoreObjC/common.h +151 -0
- package/ios/Packages/CoreObjC/energy.c +70 -0
- package/ios/Packages/CoreObjC/energy.h +43 -0
- package/ios/Packages/CoreObjC/fastmath.h +158 -0
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/lc3.c +704 -0
- package/ios/Packages/CoreObjC/lc3.h +313 -0
- package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
- package/ios/Packages/CoreObjC/lc3_private.h +163 -0
- package/ios/Packages/CoreObjC/ltpf.c +905 -0
- package/ios/Packages/CoreObjC/ltpf.h +111 -0
- package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
- package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
- package/ios/Packages/CoreObjC/makefile.mk +35 -0
- package/ios/Packages/CoreObjC/mdct.c +469 -0
- package/ios/Packages/CoreObjC/mdct.h +57 -0
- package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
- package/ios/Packages/CoreObjC/meson.build +61 -0
- package/ios/Packages/CoreObjC/plc.c +61 -0
- package/ios/Packages/CoreObjC/plc.h +57 -0
- package/ios/Packages/CoreObjC/rnnoise.h +114 -0
- package/ios/Packages/CoreObjC/sns.c +880 -0
- package/ios/Packages/CoreObjC/sns.h +103 -0
- package/ios/Packages/CoreObjC/spec.c +907 -0
- package/ios/Packages/CoreObjC/spec.h +119 -0
- package/ios/Packages/CoreObjC/tables.c +3457 -0
- package/ios/Packages/CoreObjC/tables.h +94 -0
- package/ios/Packages/CoreObjC/tns.c +457 -0
- package/ios/Packages/CoreObjC/tns.h +99 -0
- package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
- package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/PhotoReceiverModule.swift +129 -0
- package/ios/Source/BluetoothSDK.swift +2303 -0
- package/ios/Source/BluetoothSDKDebug.swift +12 -0
- package/ios/Source/BluetoothSdkDefaults.swift +29 -0
- package/ios/Source/Bridge.swift +708 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +2109 -0
- package/ios/Source/DeviceStore.swift +336 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/ObservableStore.swift +118 -0
- package/ios/Source/OtaManifest.swift +211 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/Resources/G2Firmware/README.md +10 -0
- package/ios/Source/audio/AudioModels.swift +178 -0
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +866 -0
- package/ios/Source/connection/ScanSession.swift +27 -0
- package/ios/Source/controllers/ControllerManager.swift +184 -0
- package/ios/Source/controllers/R1.swift +829 -0
- package/ios/Source/errors/BluetoothSdkError.swift +19 -0
- package/ios/Source/events/BluetoothEvents.swift +342 -0
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/internal/BluetoothAvailability.swift +78 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
- package/ios/Source/internal/ValueParsing.swift +99 -0
- package/ios/Source/requests/DisplayRequests.swift +82 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/Frame.swift +649 -0
- package/ios/Source/sgcs/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +6887 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
- package/ios/Source/sgcs/G2Flasher.swift +773 -0
- package/ios/Source/sgcs/Mach1.swift +541 -0
- package/ios/Source/sgcs/MentraLive.swift +7011 -0
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/MentraNex.swift +3221 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +608 -0
- package/ios/Source/sgcs/Simulated.swift +274 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
- package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
- package/ios/Source/status/DeviceStatus.swift +981 -0
- package/ios/Source/status/RuntimeState.swift +359 -0
- package/ios/Source/status/WifiHotspotStatus.swift +345 -0
- package/ios/Source/streaming/StreamModels.swift +693 -0
- package/ios/Source/stt/STTTools.swift +191 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
- package/ios/Source/tts/TTSTools.swift +171 -0
- package/ios/Source/types/DeviceModels.swift +177 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/BinaryCodec.swift +61 -0
- package/ios/Source/utils/BleJsonCompact.swift +405 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/Constants.swift +78 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2096 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
- package/ios/Source/utils/MessageChunker.swift +294 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
- package/ios/Source/utils/TarBz2Extractor.swift +257 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +129 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +3 -0
- package/plugin/build/withAndroid.js +190 -0
- package/plugin/build/withIos.d.ts +5 -0
- package/plugin/build/withIos.js +108 -0
- package/scripts/inject-ios-sdk-version.mjs +65 -0
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +45 -0
- package/scripts/write-release-metadata.mjs +152 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1623 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +684 -0
- package/src/_private/LocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +34 -0
- package/src/_private/photoRequestPayload.ts +90 -0
- package/src/_private/sdkOtaManifest.ts +18 -0
- package/src/changelogs.ts +46 -0
- package/src/debug.ts +9 -0
- package/src/generated/changelogCatalog.ts +13 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +321 -0
- package/src/ota-server/OtaServer.types.ts +10 -0
- package/src/ota-server/OtaServerModule.ts +23 -0
- package/src/ota-server/OtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
- package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
- package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/index.ts +21 -0
- package/src/react/useBluetooth.ts +309 -0
- package/src/react/useBluetoothEvent.ts +35 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +202 -0
- package/src/types/index.ts +12 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SpeakerIdEngine.swift
|
|
3
|
+
//
|
|
4
|
+
// On-device speaker identification over the canonical 16 kHz mono PCM stream.
|
|
5
|
+
//
|
|
6
|
+
// Taps DeviceManager.handlePcm (the single audio choke point), windows voiced
|
|
7
|
+
// audio with a local energy gate, computes a speaker embedding per
|
|
8
|
+
// utterance-ish window (sherpa-onnx CAM++ model), matches against enrolled
|
|
9
|
+
// profile centroids by cosine similarity, and emits `speaker_detected`
|
|
10
|
+
// bridge events. Raw audio never leaves the phone for speaker ID; enrollment
|
|
11
|
+
// audio is discarded as soon as its embeddings are computed.
|
|
12
|
+
//
|
|
13
|
+
// Profiles are pushed in from JS (`setSpeakerProfiles`) — MMKV in the engine
|
|
14
|
+
// layer is the source of truth; native holds them in memory only.
|
|
15
|
+
//
|
|
16
|
+
|
|
17
|
+
import Foundation
|
|
18
|
+
|
|
19
|
+
#if !SWIFT_PACKAGE || GLASSLY_FEATURE_LOCAL_STT
|
|
20
|
+
|
|
21
|
+
final class SpeakerIdEngine {
|
|
22
|
+
static let shared = SpeakerIdEngine()
|
|
23
|
+
|
|
24
|
+
struct Profile {
|
|
25
|
+
let id: String
|
|
26
|
+
let name: String
|
|
27
|
+
/// L2-normalized centroid.
|
|
28
|
+
let embedding: [Float]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
struct Match {
|
|
32
|
+
let profileId: String
|
|
33
|
+
let name: String
|
|
34
|
+
let similarity: Float
|
|
35
|
+
/// Epoch ms when the window producing this match ended.
|
|
36
|
+
let endMs: Int64
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// MARK: - Tuning (see notes/speaker-id-plan.md §2)
|
|
40
|
+
|
|
41
|
+
/// Cosine threshold for accepting a match (normalized embeddings).
|
|
42
|
+
/// Benchmarked cross-speaker ceiling for this model is ~0.46 (the MAX
|
|
43
|
+
/// over clean-audio pairs; typical impostors score far lower), while
|
|
44
|
+
/// on-device same-speaker scores run below the 0.67+ clean bench.
|
|
45
|
+
/// Owner-only voice recognition favors recall: a borderline accept just
|
|
46
|
+
/// labels a line "You", and the JS segment resolver's overlap voting +
|
|
47
|
+
/// conflict damping still filter isolated stray windows — so sit at the
|
|
48
|
+
/// edge of the impostor range rather than above it. (With one enrolled
|
|
49
|
+
/// profile the margin rule below is vacuous.)
|
|
50
|
+
private static let matchThreshold: Float = 0.45
|
|
51
|
+
/// Reject when the runner-up profile is within this margin of the best.
|
|
52
|
+
private static let matchMargin: Float = 0.08
|
|
53
|
+
/// Flush a window once this much voiced audio accumulated.
|
|
54
|
+
private static let windowVoicedTargetMs = 3000.0
|
|
55
|
+
/// End-of-speech hangover: flush after this much trailing silence.
|
|
56
|
+
private static let endOfSpeechSilenceMs = 500.0
|
|
57
|
+
/// Windows with less voiced audio than this are skipped (unreliable).
|
|
58
|
+
private static let minVoicedMs = 1000.0
|
|
59
|
+
/// Hard cap on buffered window samples (10 s) to bound memory.
|
|
60
|
+
private static let maxWindowSamples = 16000 * 10
|
|
61
|
+
/// RMS energy gate on [-1, 1] floats.
|
|
62
|
+
private static let voicedRmsThreshold: Float = 0.01
|
|
63
|
+
/// Enrollment chunks: pairwise cosine consistency floor.
|
|
64
|
+
private static let enrollConsistencyThreshold: Float = 0.6
|
|
65
|
+
private static let enrollChunkCount = 3
|
|
66
|
+
|
|
67
|
+
private static let sampleRate = 16000
|
|
68
|
+
private static let modelPathKey = "SpeakerIdModelPath"
|
|
69
|
+
|
|
70
|
+
// MARK: - State
|
|
71
|
+
|
|
72
|
+
private let queue = DispatchQueue(label: "com.glassly.speakerid.processor", qos: .utility)
|
|
73
|
+
|
|
74
|
+
/// Cheap gate read on the audio thread before hopping queues.
|
|
75
|
+
private let activeFlag = NSLock()
|
|
76
|
+
private var _identifyEnabled = false
|
|
77
|
+
private var _enrolling = false
|
|
78
|
+
private var isActive: Bool {
|
|
79
|
+
activeFlag.lock()
|
|
80
|
+
defer { activeFlag.unlock() }
|
|
81
|
+
return _identifyEnabled || _enrolling
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Everything below is touched on `queue` only.
|
|
85
|
+
private var extractor: SherpaOnnxSpeakerEmbeddingExtractorWrapper?
|
|
86
|
+
private var extractorLoadAttempted = false
|
|
87
|
+
private var profiles: [Profile] = []
|
|
88
|
+
|
|
89
|
+
private var windowSamples: [Float] = []
|
|
90
|
+
private var windowStartMs: Int64 = 0
|
|
91
|
+
private var windowVoicedMs = 0.0
|
|
92
|
+
private var trailingSilenceMs = 0.0
|
|
93
|
+
|
|
94
|
+
private struct EnrollmentSession {
|
|
95
|
+
var targetVoicedMs: Double
|
|
96
|
+
var source: String
|
|
97
|
+
var samples: [Float] = []
|
|
98
|
+
var voicedMs = 0.0
|
|
99
|
+
var lastProgressAt = Date(timeIntervalSince1970: 0)
|
|
100
|
+
var lastLevel: Float = 0
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private var enrollment: EnrollmentSession?
|
|
104
|
+
|
|
105
|
+
private let matchLock = NSLock()
|
|
106
|
+
private var _lastMatch: Match?
|
|
107
|
+
|
|
108
|
+
// MARK: - Public API (any thread unless noted)
|
|
109
|
+
|
|
110
|
+
func setIdentifyEnabled(_ enabled: Bool) {
|
|
111
|
+
activeFlag.lock()
|
|
112
|
+
_identifyEnabled = enabled
|
|
113
|
+
activeFlag.unlock()
|
|
114
|
+
Bridge.log("SpeakerID: identify \(enabled ? "enabled" : "disabled")")
|
|
115
|
+
queue.async { [weak self] in
|
|
116
|
+
guard let self else { return }
|
|
117
|
+
if enabled {
|
|
118
|
+
self.ensureExtractor()
|
|
119
|
+
} else {
|
|
120
|
+
self.resetWindow()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func setProfiles(_ profiles: [Profile]) {
|
|
126
|
+
queue.async { [weak self] in
|
|
127
|
+
self?.profiles = profiles
|
|
128
|
+
Bridge.log(
|
|
129
|
+
"SpeakerID: profiles set — \(profiles.count): "
|
|
130
|
+
+ profiles.map { "\($0.name)(\($0.embedding.count)d)" }.joined(separator: ", ")
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static func setModelPath(_ path: String) {
|
|
136
|
+
UserDefaults.standard.set(path, forKey: modelPathKey)
|
|
137
|
+
Bridge.log("SpeakerID: model path set → \(path)")
|
|
138
|
+
SpeakerIdEngine.shared.queue.async {
|
|
139
|
+
// Force a reload on next use — the model may have just arrived.
|
|
140
|
+
SpeakerIdEngine.shared.extractor = nil
|
|
141
|
+
SpeakerIdEngine.shared.extractorLoadAttempted = false
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static func isModelAvailable() -> Bool {
|
|
146
|
+
guard let path = resolvedModelPath() else { return false }
|
|
147
|
+
return FileManager.default.fileExists(atPath: path)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// `onEnded` fires (on an arbitrary queue) whenever the enrollment session
|
|
151
|
+
/// finishes or fails, so the caller can release the mic requirement.
|
|
152
|
+
func startEnrollment(targetSpeechSeconds: Double, source: String, onEnded: @escaping () -> Void) {
|
|
153
|
+
activeFlag.lock()
|
|
154
|
+
_enrolling = true
|
|
155
|
+
activeFlag.unlock()
|
|
156
|
+
queue.async { [weak self] in
|
|
157
|
+
guard let self else { return }
|
|
158
|
+
self.ensureExtractor()
|
|
159
|
+
guard self.extractor != nil else {
|
|
160
|
+
Bridge.log("SpeakerID: enrollment aborted — model unavailable")
|
|
161
|
+
self.finishEnrollment(ok: false, reason: "model_unavailable", embeddings: [], source: source)
|
|
162
|
+
onEnded()
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
Bridge.log("SpeakerID: enrollment started — target \(targetSpeechSeconds)s voiced, mic=\(source)")
|
|
166
|
+
self.enrollmentEnded = onEnded
|
|
167
|
+
self.enrollment = EnrollmentSession(
|
|
168
|
+
targetVoicedMs: max(5.0, targetSpeechSeconds) * 1000.0,
|
|
169
|
+
source: source
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private var enrollmentEnded: (() -> Void)?
|
|
175
|
+
|
|
176
|
+
func cancelEnrollment() {
|
|
177
|
+
activeFlag.lock()
|
|
178
|
+
_enrolling = false
|
|
179
|
+
activeFlag.unlock()
|
|
180
|
+
queue.async { [weak self] in
|
|
181
|
+
guard let self else { return }
|
|
182
|
+
self.enrollment = nil
|
|
183
|
+
let onEnded = self.enrollmentEnded
|
|
184
|
+
self.enrollmentEnded = nil
|
|
185
|
+
onEnded?()
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Latest accepted identification, if it ended within `withinMs` of now.
|
|
190
|
+
/// Used by the local STT path to stamp transcripts (native-side join).
|
|
191
|
+
func recentMatch(withinMs: Int64) -> Match? {
|
|
192
|
+
matchLock.lock()
|
|
193
|
+
defer { matchLock.unlock() }
|
|
194
|
+
guard let match = _lastMatch else { return nil }
|
|
195
|
+
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
196
|
+
return now - match.endMs <= withinMs ? match : nil
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Called from DeviceManager.handlePcm with 16 kHz mono s16le PCM.
|
|
200
|
+
func acceptAudio(pcm16le: Data) {
|
|
201
|
+
guard isActive, pcm16le.count >= 2 else { return }
|
|
202
|
+
queue.async { [weak self] in
|
|
203
|
+
self?.process(pcm16le)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// MARK: - Processing (queue only)
|
|
208
|
+
|
|
209
|
+
private func process(_ pcmData: Data) {
|
|
210
|
+
let samples = Self.toFloatArray(pcmData)
|
|
211
|
+
guard !samples.isEmpty else { return }
|
|
212
|
+
let chunkMs = Double(samples.count) / Double(Self.sampleRate) * 1000.0
|
|
213
|
+
let rms = Self.rms(samples)
|
|
214
|
+
let voiced = rms > Self.voicedRmsThreshold
|
|
215
|
+
|
|
216
|
+
if enrollment != nil {
|
|
217
|
+
processEnrollment(samples: samples, chunkMs: chunkMs, rms: rms, voiced: voiced)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
activeFlag.lock()
|
|
222
|
+
let identify = _identifyEnabled
|
|
223
|
+
activeFlag.unlock()
|
|
224
|
+
guard identify else { return }
|
|
225
|
+
processIdentification(samples: samples, chunkMs: chunkMs, voiced: voiced)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private func processIdentification(samples: [Float], chunkMs: Double, voiced: Bool) {
|
|
229
|
+
let nowMs = Int64(Date().timeIntervalSince1970 * 1000)
|
|
230
|
+
|
|
231
|
+
if windowSamples.isEmpty {
|
|
232
|
+
guard voiced else { return }
|
|
233
|
+
windowStartMs = nowMs - Int64(chunkMs)
|
|
234
|
+
windowVoicedMs = 0
|
|
235
|
+
trailingSilenceMs = 0
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Accumulate VOICED samples only — enrollment computes centroids from
|
|
239
|
+
// voiced-only audio, so identification must embed like-for-like.
|
|
240
|
+
// Windows padded with inter-phrase silence score far below the same
|
|
241
|
+
// speaker's centroid (observed 0.1–0.48 vs the 0.67+ bench).
|
|
242
|
+
if voiced {
|
|
243
|
+
if windowSamples.count < Self.maxWindowSamples {
|
|
244
|
+
windowSamples.append(contentsOf: samples)
|
|
245
|
+
}
|
|
246
|
+
windowVoicedMs += chunkMs
|
|
247
|
+
trailingSilenceMs = 0
|
|
248
|
+
} else {
|
|
249
|
+
trailingSilenceMs += chunkMs
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let endOfSpeech = trailingSilenceMs >= Self.endOfSpeechSilenceMs
|
|
253
|
+
let windowFull = windowVoicedMs >= Self.windowVoicedTargetMs
|
|
254
|
+
guard endOfSpeech || windowFull else { return }
|
|
255
|
+
|
|
256
|
+
let startMs = windowStartMs
|
|
257
|
+
let voicedMs = windowVoicedMs
|
|
258
|
+
let window = windowSamples
|
|
259
|
+
resetWindow()
|
|
260
|
+
|
|
261
|
+
guard voicedMs >= Self.minVoicedMs else {
|
|
262
|
+
Bridge.log("SpeakerID: window skipped — only \(Int(voicedMs))ms voiced (<\(Int(Self.minVoicedMs))ms)")
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
identify(window: window, startMs: startMs, endMs: nowMs, voicedMs: voicedMs)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private func identify(window: [Float], startMs: Int64, endMs: Int64, voicedMs: Double) {
|
|
269
|
+
ensureExtractor()
|
|
270
|
+
guard let extractor else {
|
|
271
|
+
Bridge.log("SpeakerID: window dropped — extractor not loaded (model missing?)")
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
guard !profiles.isEmpty else {
|
|
275
|
+
Bridge.log("SpeakerID: window dropped — no enrolled profiles pushed")
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let stream = extractor.createStream()
|
|
280
|
+
stream.acceptWaveform(samples: window, sampleRate: Self.sampleRate)
|
|
281
|
+
stream.inputFinished()
|
|
282
|
+
guard extractor.isReady(stream: stream) else {
|
|
283
|
+
Bridge.log("SpeakerID: window dropped — extractor not ready (\(window.count) samples)")
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
let embedding = Self.l2Normalize(extractor.compute(stream: stream))
|
|
287
|
+
guard !embedding.isEmpty else {
|
|
288
|
+
Bridge.log("SpeakerID: window dropped — empty embedding")
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
var best: (profile: Profile, similarity: Float)?
|
|
293
|
+
var second: Float = -1
|
|
294
|
+
for profile in profiles where profile.embedding.count == embedding.count {
|
|
295
|
+
let cos = Self.dot(profile.embedding, embedding)
|
|
296
|
+
if best == nil || cos > best!.similarity {
|
|
297
|
+
second = best?.similarity ?? -1
|
|
298
|
+
best = (profile, cos)
|
|
299
|
+
} else if cos > second {
|
|
300
|
+
second = cos
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
var matched: Profile?
|
|
305
|
+
var similarity: Float = 0
|
|
306
|
+
if let best, best.similarity >= Self.matchThreshold {
|
|
307
|
+
// Margin rule: reject ambiguous windows where the runner-up is
|
|
308
|
+
// close (overlapping talkers produce garbage embeddings).
|
|
309
|
+
if second < 0 || best.similarity - second >= Self.matchMargin {
|
|
310
|
+
matched = best.profile
|
|
311
|
+
similarity = best.similarity
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if let matched {
|
|
316
|
+
matchLock.lock()
|
|
317
|
+
_lastMatch = Match(profileId: matched.id, name: matched.name, similarity: similarity, endMs: endMs)
|
|
318
|
+
matchLock.unlock()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let durationMs = Int(endMs - startMs)
|
|
322
|
+
let speech = "speech=\(Int(voicedMs))ms window=\(durationMs)ms"
|
|
323
|
+
if let matched {
|
|
324
|
+
Bridge.log(
|
|
325
|
+
"SpeakerID: matched \(matched.name) cos=\(String(format: "%.3f", similarity)) "
|
|
326
|
+
+ "second=\(String(format: "%.3f", second)) \(speech)"
|
|
327
|
+
)
|
|
328
|
+
} else if let best {
|
|
329
|
+
let reason = best.similarity < Self.matchThreshold
|
|
330
|
+
? "below threshold \(Self.matchThreshold)"
|
|
331
|
+
: "margin to runner-up < \(Self.matchMargin)"
|
|
332
|
+
Bridge.log(
|
|
333
|
+
"SpeakerID: no match — best \(best.profile.name) cos=\(String(format: "%.3f", best.similarity)) "
|
|
334
|
+
+ "second=\(String(format: "%.3f", second)) (\(reason)) \(speech)"
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
Bridge.sendSpeakerDetected(
|
|
339
|
+
speakerId: matched?.id,
|
|
340
|
+
name: matched?.name,
|
|
341
|
+
similarity: Double(matched != nil ? similarity : (best?.similarity ?? 0)),
|
|
342
|
+
startMs: startMs,
|
|
343
|
+
endMs: endMs
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// MARK: - Enrollment (queue only)
|
|
348
|
+
|
|
349
|
+
private func processEnrollment(samples: [Float], chunkMs: Double, rms: Float, voiced: Bool) {
|
|
350
|
+
guard var session = enrollment else { return }
|
|
351
|
+
if voiced {
|
|
352
|
+
session.samples.append(contentsOf: samples)
|
|
353
|
+
session.voicedMs += chunkMs
|
|
354
|
+
}
|
|
355
|
+
session.lastLevel = rms
|
|
356
|
+
|
|
357
|
+
let now = Date()
|
|
358
|
+
if now.timeIntervalSince(session.lastProgressAt) >= 0.25 {
|
|
359
|
+
session.lastProgressAt = now
|
|
360
|
+
Bridge.sendSpeakerEnrollProgress(
|
|
361
|
+
voicedSeconds: session.voicedMs / 1000.0,
|
|
362
|
+
targetSeconds: session.targetVoicedMs / 1000.0,
|
|
363
|
+
level: Double(rms)
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if session.voicedMs >= session.targetVoicedMs {
|
|
368
|
+
enrollment = nil
|
|
369
|
+
activeFlag.lock()
|
|
370
|
+
_enrolling = false
|
|
371
|
+
activeFlag.unlock()
|
|
372
|
+
completeEnrollment(session)
|
|
373
|
+
} else {
|
|
374
|
+
enrollment = session
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private func completeEnrollment(_ session: EnrollmentSession) {
|
|
379
|
+
guard let extractor else {
|
|
380
|
+
finishEnrollment(ok: false, reason: "model_unavailable", embeddings: [], source: session.source)
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// One embedding per prompt-sized chunk; internal consistency check
|
|
385
|
+
// catches wind/noise/multiple talkers.
|
|
386
|
+
let chunkCount = Self.enrollChunkCount
|
|
387
|
+
let chunkSize = session.samples.count / chunkCount
|
|
388
|
+
guard chunkSize >= Self.sampleRate else {
|
|
389
|
+
finishEnrollment(ok: false, reason: "too_short", embeddings: [], source: session.source)
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
var embeddings: [[Float]] = []
|
|
394
|
+
for i in 0 ..< chunkCount {
|
|
395
|
+
let start = i * chunkSize
|
|
396
|
+
let end = i == chunkCount - 1 ? session.samples.count : start + chunkSize
|
|
397
|
+
let chunk = Array(session.samples[start ..< end])
|
|
398
|
+
let stream = extractor.createStream()
|
|
399
|
+
stream.acceptWaveform(samples: chunk, sampleRate: Self.sampleRate)
|
|
400
|
+
stream.inputFinished()
|
|
401
|
+
guard extractor.isReady(stream: stream) else { continue }
|
|
402
|
+
let embedding = Self.l2Normalize(extractor.compute(stream: stream))
|
|
403
|
+
if !embedding.isEmpty {
|
|
404
|
+
embeddings.append(embedding)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
guard embeddings.count >= 2 else {
|
|
409
|
+
finishEnrollment(ok: false, reason: "too_short", embeddings: [], source: session.source)
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
for i in 0 ..< embeddings.count {
|
|
414
|
+
for j in (i + 1) ..< embeddings.count {
|
|
415
|
+
let cos = Self.dot(embeddings[i], embeddings[j])
|
|
416
|
+
if cos < Self.enrollConsistencyThreshold {
|
|
417
|
+
Bridge.log(
|
|
418
|
+
"SpeakerID: enrollment chunks \(i)/\(j) inconsistent — cos=\(String(format: "%.3f", cos)) "
|
|
419
|
+
+ "< \(Self.enrollConsistencyThreshold) (noise / multiple talkers?)"
|
|
420
|
+
)
|
|
421
|
+
finishEnrollment(ok: false, reason: "inconsistent", embeddings: [], source: session.source)
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let encoded = embeddings.map { Self.base64(from: $0) }
|
|
428
|
+
finishEnrollment(ok: true, reason: nil, embeddings: encoded, source: session.source, dim: embeddings[0].count)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private func finishEnrollment(ok: Bool, reason: String?, embeddings: [String], source: String, dim: Int = 0) {
|
|
432
|
+
activeFlag.lock()
|
|
433
|
+
_enrolling = false
|
|
434
|
+
activeFlag.unlock()
|
|
435
|
+
Bridge.log(
|
|
436
|
+
ok
|
|
437
|
+
? "SpeakerID: enrollment ok — \(embeddings.count) embeddings (dim=\(dim), mic=\(source))"
|
|
438
|
+
: "SpeakerID: enrollment failed — \(reason ?? "unknown") (mic=\(source))"
|
|
439
|
+
)
|
|
440
|
+
Bridge.sendSpeakerEnrollResult(ok: ok, reason: reason, embeddings: embeddings, dim: dim, source: source)
|
|
441
|
+
let onEnded = enrollmentEnded
|
|
442
|
+
enrollmentEnded = nil
|
|
443
|
+
onEnded?()
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// MARK: - Model loading (queue only)
|
|
447
|
+
|
|
448
|
+
private func ensureExtractor() {
|
|
449
|
+
guard extractor == nil, !extractorLoadAttempted else { return }
|
|
450
|
+
extractorLoadAttempted = true
|
|
451
|
+
guard let modelPath = Self.resolvedModelPath(),
|
|
452
|
+
FileManager.default.fileExists(atPath: modelPath)
|
|
453
|
+
else {
|
|
454
|
+
Bridge.log("SpeakerID: model not downloaded yet — engine idle")
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
// Config and construction share one C-string scope; the config's
|
|
458
|
+
// `const char*` fields point at buffers owned by it (see toCPointer in
|
|
459
|
+
// SherpaOnnx.swift) and must not outlive it.
|
|
460
|
+
extractor = withSherpaOnnxCStrings {
|
|
461
|
+
var config = sherpaOnnxSpeakerEmbeddingExtractorConfig(model: modelPath, numThreads: 2)
|
|
462
|
+
return SherpaOnnxSpeakerEmbeddingExtractorWrapper(config: &config)
|
|
463
|
+
}
|
|
464
|
+
Bridge.log("SpeakerID: embedding extractor initialized (dim=\(extractor?.dim ?? -1))")
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/// Same Documents-relative reconstruction as the STT model path (the app
|
|
468
|
+
/// container id changes between dev builds).
|
|
469
|
+
private static func resolvedModelPath() -> String? {
|
|
470
|
+
guard let storedPath = UserDefaults.standard.string(forKey: modelPathKey) else { return nil }
|
|
471
|
+
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
472
|
+
if let range = storedPath.range(of: "/Documents/") {
|
|
473
|
+
let relativePath = String(storedPath[range.upperBound...])
|
|
474
|
+
return documentsURL.appendingPathComponent(relativePath).path
|
|
475
|
+
}
|
|
476
|
+
return storedPath
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// MARK: - Helpers
|
|
480
|
+
|
|
481
|
+
private func resetWindow() {
|
|
482
|
+
windowSamples = []
|
|
483
|
+
windowStartMs = 0
|
|
484
|
+
windowVoicedMs = 0
|
|
485
|
+
trailingSilenceMs = 0
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private static func toFloatArray(_ pcmData: Data) -> [Float] {
|
|
489
|
+
let count = pcmData.count / 2
|
|
490
|
+
var samples = [Float](repeating: 0, count: count)
|
|
491
|
+
pcmData.withUnsafeBytes { (bufferPointer: UnsafeRawBufferPointer) in
|
|
492
|
+
guard let address = bufferPointer.baseAddress else { return }
|
|
493
|
+
let int16Pointer = address.bindMemory(to: Int16.self, capacity: count)
|
|
494
|
+
for i in 0 ..< count {
|
|
495
|
+
samples[i] = Float(Int16(littleEndian: int16Pointer[i])) / 32768.0
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return samples
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private static func rms(_ samples: [Float]) -> Float {
|
|
502
|
+
guard !samples.isEmpty else { return 0 }
|
|
503
|
+
var sum: Float = 0
|
|
504
|
+
for s in samples { sum += s * s }
|
|
505
|
+
return (sum / Float(samples.count)).squareRoot()
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private static func dot(_ a: [Float], _ b: [Float]) -> Float {
|
|
509
|
+
guard a.count == b.count else { return 0 }
|
|
510
|
+
var sum: Float = 0
|
|
511
|
+
for i in 0 ..< a.count { sum += a[i] * b[i] }
|
|
512
|
+
return sum
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private static func l2Normalize(_ v: [Float]) -> [Float] {
|
|
516
|
+
var sum: Float = 0
|
|
517
|
+
for x in v { sum += x * x }
|
|
518
|
+
let norm = sum.squareRoot()
|
|
519
|
+
guard norm > 0 else { return v }
|
|
520
|
+
return v.map { $0 / norm }
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private static func base64(from embedding: [Float]) -> String {
|
|
524
|
+
embedding.withUnsafeBufferPointer { buffer in
|
|
525
|
+
Data(buffer: buffer).base64EncodedString()
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
static func embedding(fromBase64 base64: String) -> [Float]? {
|
|
530
|
+
guard let data = Data(base64Encoded: base64), data.count % 4 == 0, !data.isEmpty else { return nil }
|
|
531
|
+
let count = data.count / 4
|
|
532
|
+
var floats = [Float](repeating: 0, count: count)
|
|
533
|
+
_ = floats.withUnsafeMutableBytes { data.copyBytes(to: $0) }
|
|
534
|
+
return floats
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
#else
|
|
539
|
+
|
|
540
|
+
/// Stub for SWIFT_PACKAGE builds without GLASSLY_FEATURE_LOCAL_STT — the
|
|
541
|
+
/// sherpa-onnx runtime isn't linked, so speaker ID is unavailable.
|
|
542
|
+
final class SpeakerIdEngine {
|
|
543
|
+
static let shared = SpeakerIdEngine()
|
|
544
|
+
|
|
545
|
+
struct Profile {
|
|
546
|
+
let id: String
|
|
547
|
+
let name: String
|
|
548
|
+
let embedding: [Float]
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
struct Match {
|
|
552
|
+
let profileId: String
|
|
553
|
+
let name: String
|
|
554
|
+
let similarity: Float
|
|
555
|
+
let endMs: Int64
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
func setIdentifyEnabled(_: Bool) {}
|
|
559
|
+
func setProfiles(_: [Profile]) {}
|
|
560
|
+
static func setModelPath(_: String) {}
|
|
561
|
+
static func isModelAvailable() -> Bool { false }
|
|
562
|
+
func startEnrollment(targetSpeechSeconds _: Double, source: String, onEnded: @escaping () -> Void) {
|
|
563
|
+
Bridge.sendSpeakerEnrollResult(ok: false, reason: "unsupported", embeddings: [], dim: 0, source: source)
|
|
564
|
+
onEnded()
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
func cancelEnrollment() {}
|
|
568
|
+
func recentMatch(withinMs _: Int64) -> Match? { nil }
|
|
569
|
+
func acceptAudio(pcm16le _: Data) {}
|
|
570
|
+
static func embedding(fromBase64 _: String) -> [Float]? { nil }
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
#endif
|