@mentra/bluetooth-sdk 0.1.9 → 0.1.11

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.
Files changed (147) hide show
  1. package/README.md +116 -18
  2. package/android/build.gradle +289 -16
  3. package/android/gradle.properties.example +9 -0
  4. package/android/lc3Lib/build.gradle +95 -6
  5. package/android/settings.gradle +3 -0
  6. package/android/silero/LICENSE.md +17 -0
  7. package/android/silero/build.gradle +42 -0
  8. package/android/silero/proguard-rules.pro +24 -0
  9. package/android/silero/src/androidTest/assets/hello.wav +0 -0
  10. package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
  11. package/android/silero/src/main/assets/LICENSE +21 -0
  12. package/android/silero/src/main/assets/silero_vad.onnx +0 -0
  13. package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
  14. package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
  15. package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
  16. package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
  17. package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
  18. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
  19. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
  20. package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
  21. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +6 -0
  22. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +195 -34
  23. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +103 -21
  24. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +210 -18
  25. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +30 -2
  26. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +787 -85
  27. package/android/src/main/java/com/mentra/bluetoothsdk/audio/AudioModels.kt +6 -2
  28. package/android/src/main/java/com/mentra/bluetoothsdk/camera/CameraModels.kt +125 -11
  29. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +12 -2
  30. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +8 -2
  31. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +33 -17
  32. package/android/src/main/java/com/mentra/bluetoothsdk/events/BluetoothEvents.kt +101 -0
  33. package/android/src/main/java/com/mentra/bluetoothsdk/internal/MapParsing.kt +8 -0
  34. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
  35. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +399 -74
  36. package/android/src/main/java/com/mentra/bluetoothsdk/photoreceiver/MentraPhotoReceiverModule.kt +100 -17
  37. package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +17 -0
  38. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +2 -2
  39. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +1293 -758
  40. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +2 -2
  41. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +271 -66
  42. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +197 -149
  43. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +12753 -27036
  44. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +64 -2
  45. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +16 -4
  46. package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +60 -3
  47. package/android/src/main/java/com/mentra/bluetoothsdk/streaming/StreamModels.kt +12 -3
  48. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +87 -36
  49. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +13 -4
  50. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
  51. package/android/src/main/java/com/mentra/bluetoothsdk/tts/TTSTools.kt +180 -0
  52. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AvifExifStripper.java +573 -0
  53. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +488 -50
  54. package/android/src/main/java/com/mentra/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
  55. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +18 -1
  56. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +64 -18
  57. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +48 -39
  58. package/android/src/test/java/com/mentra/bluetoothsdk/camera/PhotoRequestTest.kt +23 -0
  59. package/android/src/test/java/com/mentra/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
  60. package/android/src/test/java/com/mentra/bluetoothsdk/utils/AvifExifStripperTest.java +30 -0
  61. package/android/src/test/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +98 -0
  62. package/android/src/test/resources/avif_with_exif.avif +0 -0
  63. package/build/BluetoothSdk.types.d.ts +253 -44
  64. package/build/BluetoothSdk.types.d.ts.map +1 -1
  65. package/build/BluetoothSdk.types.js +11 -10
  66. package/build/BluetoothSdk.types.js.map +1 -1
  67. package/build/_private/BluetoothSdkModule.d.ts +37 -23
  68. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  69. package/build/_private/BluetoothSdkModule.js +43 -52
  70. package/build/_private/BluetoothSdkModule.js.map +1 -1
  71. package/build/_private/photoRequestPayload.d.ts +4 -0
  72. package/build/_private/photoRequestPayload.d.ts.map +1 -0
  73. package/build/_private/photoRequestPayload.js +25 -0
  74. package/build/_private/photoRequestPayload.js.map +1 -0
  75. package/build/index.d.ts +1 -1
  76. package/build/index.d.ts.map +1 -1
  77. package/build/index.js +25 -0
  78. package/build/index.js.map +1 -1
  79. package/build/react/useMentraBluetooth.d.ts +2 -2
  80. package/build/react/useMentraBluetooth.d.ts.map +1 -1
  81. package/build/react/useMentraBluetooth.js +3 -3
  82. package/build/react/useMentraBluetooth.js.map +1 -1
  83. package/ios/BluetoothSdkModule.swift +177 -70
  84. package/ios/LocalPhotoUploadServer.swift +370 -105
  85. package/ios/MentraBluetoothSDK.podspec +3 -1
  86. package/ios/MentraPhotoReceiverModule.swift +62 -10
  87. package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
  88. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +668 -26
  89. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +13 -9
  90. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  91. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  92. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  93. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4551 -0
  94. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +1745 -0
  95. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  96. package/ios/Source/BluetoothSdkDefaults.swift +6 -0
  97. package/ios/Source/Bridge.swift +68 -19
  98. package/ios/Source/DeviceManager.swift +167 -32
  99. package/ios/Source/DeviceStore.swift +24 -8
  100. package/ios/Source/MentraBluetoothSDK.swift +803 -47
  101. package/ios/Source/ObservableStore.swift +6 -0
  102. package/ios/Source/{Audio → audio}/AudioModels.swift +9 -7
  103. package/ios/Source/{Camera → camera}/CameraModels.swift +226 -16
  104. package/ios/Source/controllers/ControllerManager.swift +4 -3
  105. package/ios/Source/controllers/R1.swift +3 -3
  106. package/ios/Source/{Events → events}/BluetoothEvents.swift +183 -1
  107. package/ios/Source/{Internal → internal}/ValueParsing.swift +10 -1
  108. package/ios/Source/{Requests → requests}/DisplayRequests.swift +28 -4
  109. package/ios/Source/services/PhoneMic.swift +1 -0
  110. package/ios/Source/sgcs/G1.swift +5 -5
  111. package/ios/Source/sgcs/G2.swift +1494 -870
  112. package/ios/Source/sgcs/Mach1.swift +4 -4
  113. package/ios/Source/sgcs/MentraLive.swift +814 -321
  114. package/ios/Source/sgcs/MentraNex.swift +604 -244
  115. package/ios/Source/sgcs/SGCManager.swift +68 -5
  116. package/ios/Source/sgcs/Simulated.swift +9 -5
  117. package/ios/Source/sgcs/mentraos_ble.pb.swift +3073 -3134
  118. package/ios/Source/status/DeviceStatus.swift +974 -0
  119. package/ios/Source/{Status → status}/WifiHotspotStatus.swift +4 -4
  120. package/ios/Source/{Streaming → streaming}/StreamModels.swift +24 -9
  121. package/ios/Source/stt/STTTools.swift +27 -12
  122. package/ios/Source/stt/SherpaOnnxTranscriber.swift +31 -8
  123. package/ios/Source/tts/TTSTools.swift +164 -0
  124. package/ios/Source/utils/JSCExperiment.swift +7 -7
  125. package/ios/Source/utils/MessageChunkReassembler.swift +20 -1
  126. package/ios/Source/utils/MessageChunker.swift +78 -21
  127. package/ios/Source/utils/TarBz2Extractor.swift +53 -2
  128. package/package.json +1 -1
  129. package/plugin/build/withAndroid.js +68 -4
  130. package/src/BluetoothSdk.types.ts +344 -60
  131. package/src/_private/BluetoothSdkModule.ts +132 -102
  132. package/src/_private/photoRequestPayload.ts +28 -0
  133. package/src/index.ts +61 -15
  134. package/src/react/useMentraBluetooth.ts +6 -4
  135. package/android/.project +0 -28
  136. package/android/lc3Lib/.project +0 -28
  137. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +0 -162
  138. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +0 -1852
  139. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +0 -674
  140. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
  141. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
  142. package/ios/Source/Status/DeviceStatus.swift +0 -471
  143. /package/ios/Source/{Connection → connection}/ScanSession.swift +0 -0
  144. /package/ios/Source/{Errors → errors}/BluetoothError.swift +0 -0
  145. /package/ios/Source/{Internal → internal}/BluetoothAvailability.swift +0 -0
  146. /package/ios/Source/{Status → status}/RuntimeState.swift +0 -0
  147. /package/ios/Source/{Types → types}/DeviceModels.swift +0 -0
@@ -76,6 +76,14 @@ func sherpaOnnxOnlineNemoCtcModelConfig(
76
76
  )
77
77
  }
78
78
 
79
+ func sherpaOnnxOnlineToneCtcModelConfig(
80
+ model: String = ""
81
+ ) -> SherpaOnnxOnlineToneCtcModelConfig {
82
+ return SherpaOnnxOnlineToneCtcModelConfig(
83
+ model: toCPointer(model)
84
+ )
85
+ }
86
+
79
87
  /// Return an instance of SherpaOnnxOnlineModelConfig.
80
88
  ///
81
89
  /// Please refer to
@@ -101,7 +109,8 @@ func sherpaOnnxOnlineModelConfig(
101
109
  bpeVocab: String = "",
102
110
  tokensBuf: String = "",
103
111
  tokensBufSize: Int = 0,
104
- nemoCtc: SherpaOnnxOnlineNemoCtcModelConfig = sherpaOnnxOnlineNemoCtcModelConfig()
112
+ nemoCtc: SherpaOnnxOnlineNemoCtcModelConfig = sherpaOnnxOnlineNemoCtcModelConfig(),
113
+ toneCtc: SherpaOnnxOnlineToneCtcModelConfig = sherpaOnnxOnlineToneCtcModelConfig()
105
114
  ) -> SherpaOnnxOnlineModelConfig {
106
115
  return SherpaOnnxOnlineModelConfig(
107
116
  transducer: transducer,
@@ -116,7 +125,8 @@ func sherpaOnnxOnlineModelConfig(
116
125
  bpe_vocab: toCPointer(bpeVocab),
117
126
  tokens_buf: toCPointer(tokensBuf),
118
127
  tokens_buf_size: Int32(tokensBufSize),
119
- nemo_ctc: nemoCtc
128
+ nemo_ctc: nemoCtc,
129
+ t_one_ctc: toneCtc
120
130
  )
121
131
  }
122
132
 
@@ -361,6 +371,38 @@ func sherpaOnnxOfflineZipformerCtcModelConfig(
361
371
  )
362
372
  }
363
373
 
374
+ func sherpaOnnxOfflineWenetCtcModelConfig(
375
+ model: String = ""
376
+ ) -> SherpaOnnxOfflineWenetCtcModelConfig {
377
+ return SherpaOnnxOfflineWenetCtcModelConfig(
378
+ model: toCPointer(model)
379
+ )
380
+ }
381
+
382
+ func sherpaOnnxOfflineOmnilingualAsrCtcModelConfig(
383
+ model: String = ""
384
+ ) -> SherpaOnnxOfflineOmnilingualAsrCtcModelConfig {
385
+ return SherpaOnnxOfflineOmnilingualAsrCtcModelConfig(
386
+ model: toCPointer(model)
387
+ )
388
+ }
389
+
390
+ func sherpaOnnxOfflineMedAsrCtcModelConfig(
391
+ model: String = ""
392
+ ) -> SherpaOnnxOfflineMedAsrCtcModelConfig {
393
+ return SherpaOnnxOfflineMedAsrCtcModelConfig(
394
+ model: toCPointer(model)
395
+ )
396
+ }
397
+
398
+ func sherpaOnnxOfflineFireRedAsrCtcModelConfig(
399
+ model: String = ""
400
+ ) -> SherpaOnnxOfflineFireRedAsrCtcModelConfig {
401
+ return SherpaOnnxOfflineFireRedAsrCtcModelConfig(
402
+ model: toCPointer(model)
403
+ )
404
+ }
405
+
364
406
  func sherpaOnnxOfflineNemoEncDecCtcModelConfig(
365
407
  model: String = ""
366
408
  ) -> SherpaOnnxOfflineNemoEncDecCtcModelConfig {
@@ -382,14 +424,18 @@ func sherpaOnnxOfflineWhisperModelConfig(
382
424
  decoder: String = "",
383
425
  language: String = "",
384
426
  task: String = "transcribe",
385
- tailPaddings: Int = -1
427
+ tailPaddings: Int = -1,
428
+ enableTokenTimestamps: Bool = false,
429
+ enableSegmentTimestamps: Bool = false
386
430
  ) -> SherpaOnnxOfflineWhisperModelConfig {
387
431
  return SherpaOnnxOfflineWhisperModelConfig(
388
432
  encoder: toCPointer(encoder),
389
433
  decoder: toCPointer(decoder),
390
434
  language: toCPointer(language),
391
435
  task: toCPointer(task),
392
- tail_paddings: Int32(tailPaddings)
436
+ tail_paddings: Int32(tailPaddings),
437
+ enable_token_timestamps: enableTokenTimestamps ? 1 : 0,
438
+ enable_segment_timestamps: enableSegmentTimestamps ? 1 : 0
393
439
  )
394
440
  }
395
441
 
@@ -409,6 +455,22 @@ func sherpaOnnxOfflineCanaryModelConfig(
409
455
  )
410
456
  }
411
457
 
458
+ func sherpaOnnxOfflineCohereTranscribeModelConfig(
459
+ encoder: String = "",
460
+ decoder: String = "",
461
+ language: String = "",
462
+ usePunct: Bool = true,
463
+ useInverseTextNormalization: Bool = true
464
+ ) -> SherpaOnnxOfflineCohereTranscribeModelConfig {
465
+ return SherpaOnnxOfflineCohereTranscribeModelConfig(
466
+ encoder: toCPointer(encoder),
467
+ decoder: toCPointer(decoder),
468
+ language: toCPointer(language),
469
+ use_punct: usePunct ? 1 : 0,
470
+ use_itn: useInverseTextNormalization ? 1 : 0
471
+ )
472
+ }
473
+
412
474
  func sherpaOnnxOfflineFireRedAsrModelConfig(
413
475
  encoder: String = "",
414
476
  decoder: String = ""
@@ -419,17 +481,48 @@ func sherpaOnnxOfflineFireRedAsrModelConfig(
419
481
  )
420
482
  }
421
483
 
484
+ /// there are two versions of Moonshine
485
+ /// For v1, you need four models: preprocessor, encoder, uncachedDecoder, cachedDecoder
486
+ /// For v2, you need two models: encoder, mergedDecoder
422
487
  func sherpaOnnxOfflineMoonshineModelConfig(
423
488
  preprocessor: String = "",
424
489
  encoder: String = "",
425
490
  uncachedDecoder: String = "",
426
- cachedDecoder: String = ""
491
+ cachedDecoder: String = "",
492
+ mergedDecoder: String = ""
427
493
  ) -> SherpaOnnxOfflineMoonshineModelConfig {
428
494
  return SherpaOnnxOfflineMoonshineModelConfig(
429
495
  preprocessor: toCPointer(preprocessor),
430
496
  encoder: toCPointer(encoder),
431
497
  uncached_decoder: toCPointer(uncachedDecoder),
432
- cached_decoder: toCPointer(cachedDecoder)
498
+ cached_decoder: toCPointer(cachedDecoder),
499
+ merged_decoder: toCPointer(mergedDecoder)
500
+ )
501
+ }
502
+
503
+ func sherpaOnnxOfflineQwen3ASRModelConfig(
504
+ convFrontend: String = "",
505
+ encoder: String = "",
506
+ decoder: String = "",
507
+ tokenizer: String = "",
508
+ maxTotalLen: Int = 512,
509
+ maxNewTokens: Int = 128,
510
+ temperature: Float = 1e-6,
511
+ topP: Float = 0.8,
512
+ seed: Int = 42,
513
+ hotwords: String = ""
514
+ ) -> SherpaOnnxOfflineQwen3ASRModelConfig {
515
+ return SherpaOnnxOfflineQwen3ASRModelConfig(
516
+ conv_frontend: toCPointer(convFrontend),
517
+ encoder: toCPointer(encoder),
518
+ decoder: toCPointer(decoder),
519
+ tokenizer: toCPointer(tokenizer),
520
+ max_total_len: Int32(maxTotalLen),
521
+ max_new_tokens: Int32(maxNewTokens),
522
+ temperature: temperature,
523
+ top_p: topP,
524
+ seed: Int32(seed),
525
+ hotwords: toCPointer(hotwords)
433
526
  )
434
527
  }
435
528
 
@@ -463,6 +556,38 @@ func sherpaOnnxOfflineLMConfig(
463
556
  )
464
557
  }
465
558
 
559
+ func sherpaOnnxOfflineFunASRNanoModelConfig(
560
+ encoderAdaptor: String = "",
561
+ llm: String = "",
562
+ embedding: String = "",
563
+ tokenizer: String = "",
564
+ systemPrompt: String = "You are a helpful assistant.",
565
+ userPrompt: String = "语音转写:",
566
+ maxNewTokens: Int = 512,
567
+ temperature: Float = 1e-6,
568
+ topP: Float = 0.8,
569
+ seed: Int = 42,
570
+ language: String = "",
571
+ itn: Bool = true,
572
+ hotwords: String = ""
573
+ ) -> SherpaOnnxOfflineFunASRNanoModelConfig {
574
+ return SherpaOnnxOfflineFunASRNanoModelConfig(
575
+ encoder_adaptor: toCPointer(encoderAdaptor),
576
+ llm: toCPointer(llm),
577
+ embedding: toCPointer(embedding),
578
+ tokenizer: toCPointer(tokenizer),
579
+ system_prompt: toCPointer(systemPrompt),
580
+ user_prompt: toCPointer(userPrompt),
581
+ max_new_tokens: Int32(maxNewTokens),
582
+ temperature: temperature,
583
+ top_p: topP,
584
+ seed: Int32(seed),
585
+ language: toCPointer(language),
586
+ itn: itn ? 1 : 0,
587
+ hotwords: toCPointer(hotwords)
588
+ )
589
+ }
590
+
466
591
  func sherpaOnnxOfflineModelConfig(
467
592
  tokens: String,
468
593
  transducer: SherpaOnnxOfflineTransducerModelConfig = sherpaOnnxOfflineTransducerModelConfig(),
@@ -483,7 +608,21 @@ func sherpaOnnxOfflineModelConfig(
483
608
  dolphin: SherpaOnnxOfflineDolphinModelConfig = sherpaOnnxOfflineDolphinModelConfig(),
484
609
  zipformerCtc: SherpaOnnxOfflineZipformerCtcModelConfig =
485
610
  sherpaOnnxOfflineZipformerCtcModelConfig(),
486
- canary: SherpaOnnxOfflineCanaryModelConfig = sherpaOnnxOfflineCanaryModelConfig()
611
+ canary: SherpaOnnxOfflineCanaryModelConfig = sherpaOnnxOfflineCanaryModelConfig(),
612
+ wenetCtc: SherpaOnnxOfflineWenetCtcModelConfig =
613
+ sherpaOnnxOfflineWenetCtcModelConfig(),
614
+ omnilingual: SherpaOnnxOfflineOmnilingualAsrCtcModelConfig =
615
+ sherpaOnnxOfflineOmnilingualAsrCtcModelConfig(),
616
+ medasr: SherpaOnnxOfflineMedAsrCtcModelConfig =
617
+ sherpaOnnxOfflineMedAsrCtcModelConfig(),
618
+ funasrNano: SherpaOnnxOfflineFunASRNanoModelConfig =
619
+ sherpaOnnxOfflineFunASRNanoModelConfig(),
620
+ fireRedAsrCtc: SherpaOnnxOfflineFireRedAsrCtcModelConfig =
621
+ sherpaOnnxOfflineFireRedAsrCtcModelConfig(),
622
+ qwen3Asr: SherpaOnnxOfflineQwen3ASRModelConfig =
623
+ sherpaOnnxOfflineQwen3ASRModelConfig(),
624
+ cohereTranscribe: SherpaOnnxOfflineCohereTranscribeModelConfig =
625
+ sherpaOnnxOfflineCohereTranscribeModelConfig()
487
626
  ) -> SherpaOnnxOfflineModelConfig {
488
627
  return SherpaOnnxOfflineModelConfig(
489
628
  transducer: transducer,
@@ -504,7 +643,14 @@ func sherpaOnnxOfflineModelConfig(
504
643
  fire_red_asr: fireRedAsr,
505
644
  dolphin: dolphin,
506
645
  zipformer_ctc: zipformerCtc,
507
- canary: canary
646
+ canary: canary,
647
+ wenet_ctc: wenetCtc,
648
+ omnilingual: omnilingual,
649
+ medasr: medasr,
650
+ funasr_nano: funasrNano,
651
+ fire_red_asr_ctc: fireRedAsrCtc,
652
+ qwen3_asr: qwen3Asr,
653
+ cohere_transcribe: cohereTranscribe
508
654
  )
509
655
  }
510
656
 
@@ -550,6 +696,11 @@ class SherpaOnnxOfflineRecongitionResult {
550
696
  return (0 ..< result.pointee.count).map { p[Int($0)] }
551
697
  }()
552
698
 
699
+ private lazy var _durations: [Float] = {
700
+ guard let p = result.pointee.durations else { return [] }
701
+ return (0 ..< result.pointee.count).map { p[Int($0)] }
702
+ }()
703
+
553
704
  private lazy var _lang: String = {
554
705
  guard let cstr = result.pointee.lang else { return "" }
555
706
  return String(cString: cstr)
@@ -565,6 +716,24 @@ class SherpaOnnxOfflineRecongitionResult {
565
716
  return String(cString: cstr)
566
717
  }()
567
718
 
719
+ private lazy var _segmentTimestamps: [Float] = {
720
+ guard let p = result.pointee.segment_timestamps else { return [] }
721
+ return (0 ..< result.pointee.segment_count).map { p[Int($0)] }
722
+ }()
723
+
724
+ private lazy var _segmentDurations: [Float] = {
725
+ guard let p = result.pointee.segment_durations else { return [] }
726
+ return (0 ..< result.pointee.segment_count).map { p[Int($0)] }
727
+ }()
728
+
729
+ private lazy var _segmentTexts: [String] = {
730
+ guard let arr = result.pointee.segment_texts_arr else { return [] }
731
+ return (0 ..< result.pointee.segment_count).compactMap { idx -> String? in
732
+ guard let ptr = arr[Int(idx)] else { return nil }
733
+ return String(cString: ptr)
734
+ }
735
+ }()
736
+
568
737
  /// Return the actual recognition result.
569
738
  /// For English models, it contains words separated by spaces.
570
739
  /// For Chinese models, it contains Chinese words.
@@ -580,6 +749,11 @@ class SherpaOnnxOfflineRecongitionResult {
580
749
  _timestamps
581
750
  }
582
751
 
752
+ /// Non-empty for TDT models. Empty for all other non-TDT models
753
+ var durations: [Float] {
754
+ _durations
755
+ }
756
+
583
757
  /// For SenseVoice models, it can be zh, en, ja, yue, ko
584
758
  /// where zh is for Chinese
585
759
  /// en is for English
@@ -600,6 +774,23 @@ class SherpaOnnxOfflineRecongitionResult {
600
774
  _event
601
775
  }
602
776
 
777
+ /// Segment-level timestamps (for Whisper with segment timestamps enabled)
778
+ var segmentCount: Int {
779
+ Int(result.pointee.segment_count)
780
+ }
781
+
782
+ var segmentTimestamps: [Float] {
783
+ _segmentTimestamps
784
+ }
785
+
786
+ var segmentDurations: [Float] {
787
+ _segmentDurations
788
+ }
789
+
790
+ var segmentTexts: [String] {
791
+ _segmentTexts
792
+ }
793
+
603
794
  init(result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>) {
604
795
  self.result = result
605
796
  }
@@ -633,25 +824,54 @@ class SherpaOnnxOfflineRecognizer {
633
824
  /// - sampleRate: Sample rate of the input audio samples. Must match
634
825
  /// the one expected by the model.
635
826
  func decode(samples: [Float], sampleRate: Int = 16000) -> SherpaOnnxOfflineRecongitionResult {
827
+ let stream = createStream()
828
+ stream.acceptWaveform(samples: samples, sampleRate: sampleRate)
829
+ decode(stream: stream)
830
+ return getResult(stream: stream)
831
+ }
832
+
833
+ func setConfig(config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>) {
834
+ SherpaOnnxOfflineRecognizerSetConfig(recognizer, config)
835
+ }
836
+
837
+ func createStream() -> SherpaOnnxOfflineStreamWrapper {
636
838
  guard let stream = SherpaOnnxCreateOfflineStream(recognizer) else {
637
839
  fatalError("Failed to create offline stream")
638
840
  }
639
841
 
640
- defer { SherpaOnnxDestroyOfflineStream(stream) }
641
-
642
- SherpaOnnxAcceptWaveformOffline(stream, Int32(sampleRate), samples, Int32(samples.count))
842
+ return SherpaOnnxOfflineStreamWrapper(stream: stream)
843
+ }
643
844
 
644
- SherpaOnnxDecodeOfflineStream(recognizer, stream)
845
+ func decode(stream: SherpaOnnxOfflineStreamWrapper) {
846
+ SherpaOnnxDecodeOfflineStream(recognizer, stream.stream)
847
+ }
645
848
 
646
- guard let resultPtr = SherpaOnnxGetOfflineStreamResult(stream) else {
849
+ func getResult(stream: SherpaOnnxOfflineStreamWrapper) -> SherpaOnnxOfflineRecongitionResult {
850
+ guard let resultPtr = SherpaOnnxGetOfflineStreamResult(stream.stream) else {
647
851
  fatalError("Failed to get offline recognition result")
648
852
  }
649
853
 
650
854
  return SherpaOnnxOfflineRecongitionResult(result: resultPtr)
651
855
  }
856
+ }
652
857
 
653
- func setConfig(config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>) {
654
- SherpaOnnxOfflineRecognizerSetConfig(recognizer, config)
858
+ class SherpaOnnxOfflineStreamWrapper {
859
+ let stream: OpaquePointer
860
+
861
+ init(stream: OpaquePointer) {
862
+ self.stream = stream
863
+ }
864
+
865
+ deinit {
866
+ SherpaOnnxDestroyOfflineStream(stream)
867
+ }
868
+
869
+ func setOption(key: String, value: String) {
870
+ SherpaOnnxOfflineStreamSetOption(stream, toCPointer(key), toCPointer(value))
871
+ }
872
+
873
+ func acceptWaveform(samples: [Float], sampleRate: Int = 16000) {
874
+ SherpaOnnxAcceptWaveformOffline(stream, Int32(sampleRate), samples, Int32(samples.count))
655
875
  }
656
876
  }
657
877
 
@@ -910,6 +1130,74 @@ func sherpaOnnxOfflineTtsKittenModelConfig(
910
1130
  )
911
1131
  }
912
1132
 
1133
+ func sherpaOnnxOfflineTtsZipvoiceModelConfig(
1134
+ tokens: String = "",
1135
+ encoder: String = "",
1136
+ decoder: String = "",
1137
+ vocoder: String = "",
1138
+ dataDir: String = "",
1139
+ lexicon: String = "",
1140
+ featScale: Float = 0.1,
1141
+ tShift: Float = 0.5,
1142
+ targetRms: Float = 0.1,
1143
+ guidanceScale: Float = 1.0
1144
+ ) -> SherpaOnnxOfflineTtsZipvoiceModelConfig {
1145
+ return SherpaOnnxOfflineTtsZipvoiceModelConfig(
1146
+ tokens: toCPointer(tokens),
1147
+ encoder: toCPointer(encoder),
1148
+ decoder: toCPointer(decoder),
1149
+ vocoder: toCPointer(vocoder),
1150
+ data_dir: toCPointer(dataDir),
1151
+ lexicon: toCPointer(lexicon),
1152
+ feat_scale: featScale,
1153
+ t_shift: tShift,
1154
+ target_rms: targetRms,
1155
+ guidance_scale: guidanceScale
1156
+ )
1157
+ }
1158
+
1159
+ func sherpaOnnxOfflineTtsPocketModelConfig(
1160
+ lmFlow: String = "",
1161
+ lmMain: String = "",
1162
+ encoder: String = "",
1163
+ decoder: String = "",
1164
+ textConditioner: String = "",
1165
+ vocabJson: String = "",
1166
+ tokenScoresJson: String = "",
1167
+ voiceEmbeddingCacheCapacity: Int = 50
1168
+ ) -> SherpaOnnxOfflineTtsPocketModelConfig {
1169
+ return SherpaOnnxOfflineTtsPocketModelConfig(
1170
+ lm_flow: toCPointer(lmFlow),
1171
+ lm_main: toCPointer(lmMain),
1172
+ encoder: toCPointer(encoder),
1173
+ decoder: toCPointer(decoder),
1174
+ text_conditioner: toCPointer(textConditioner),
1175
+ vocab_json: toCPointer(vocabJson),
1176
+ token_scores_json: toCPointer(tokenScoresJson),
1177
+ voice_embedding_cache_capacity: Int32(voiceEmbeddingCacheCapacity)
1178
+ )
1179
+ }
1180
+
1181
+ func sherpaOnnxOfflineTtsSupertonicModelConfig(
1182
+ durationPredictor: String = "",
1183
+ textEncoder: String = "",
1184
+ vectorEstimator: String = "",
1185
+ vocoder: String = "",
1186
+ ttsJson: String = "",
1187
+ unicodeIndexer: String = "",
1188
+ voiceStyle: String = ""
1189
+ ) -> SherpaOnnxOfflineTtsSupertonicModelConfig {
1190
+ return SherpaOnnxOfflineTtsSupertonicModelConfig(
1191
+ duration_predictor: toCPointer(durationPredictor),
1192
+ text_encoder: toCPointer(textEncoder),
1193
+ vector_estimator: toCPointer(vectorEstimator),
1194
+ vocoder: toCPointer(vocoder),
1195
+ tts_json: toCPointer(ttsJson),
1196
+ unicode_indexer: toCPointer(unicodeIndexer),
1197
+ voice_style: toCPointer(voiceStyle)
1198
+ )
1199
+ }
1200
+
913
1201
  func sherpaOnnxOfflineTtsModelConfig(
914
1202
  vits: SherpaOnnxOfflineTtsVitsModelConfig = sherpaOnnxOfflineTtsVitsModelConfig(),
915
1203
  matcha: SherpaOnnxOfflineTtsMatchaModelConfig = sherpaOnnxOfflineTtsMatchaModelConfig(),
@@ -917,7 +1205,11 @@ func sherpaOnnxOfflineTtsModelConfig(
917
1205
  numThreads: Int = 1,
918
1206
  debug: Int = 0,
919
1207
  provider: String = "cpu",
920
- kitten: SherpaOnnxOfflineTtsKittenModelConfig = sherpaOnnxOfflineTtsKittenModelConfig()
1208
+ kitten: SherpaOnnxOfflineTtsKittenModelConfig = sherpaOnnxOfflineTtsKittenModelConfig(),
1209
+ zipvoice: SherpaOnnxOfflineTtsZipvoiceModelConfig = sherpaOnnxOfflineTtsZipvoiceModelConfig(),
1210
+ pocket: SherpaOnnxOfflineTtsPocketModelConfig = sherpaOnnxOfflineTtsPocketModelConfig(),
1211
+ supertonic: SherpaOnnxOfflineTtsSupertonicModelConfig =
1212
+ sherpaOnnxOfflineTtsSupertonicModelConfig()
921
1213
  ) -> SherpaOnnxOfflineTtsModelConfig {
922
1214
  return SherpaOnnxOfflineTtsModelConfig(
923
1215
  vits: vits,
@@ -926,7 +1218,10 @@ func sherpaOnnxOfflineTtsModelConfig(
926
1218
  provider: toCPointer(provider),
927
1219
  matcha: matcha,
928
1220
  kokoro: kokoro,
929
- kitten: kitten
1221
+ kitten: kitten,
1222
+ zipvoice: zipvoice,
1223
+ pocket: pocket,
1224
+ supertonic: supertonic
930
1225
  )
931
1226
  }
932
1227
 
@@ -1024,6 +1319,94 @@ typealias TtsCallbackWithArg = (
1024
1319
  ) -> Int32
1025
1320
  )?
1026
1321
 
1322
+ class SherpaOnnxCallbackPair {
1323
+ var cb: TtsCallbackWithArg
1324
+ var arg: UnsafeMutableRawPointer?
1325
+ init(cb: TtsCallbackWithArg, arg: UnsafeMutableRawPointer?) {
1326
+ self.cb = cb
1327
+ self.arg = arg
1328
+ }
1329
+ }
1330
+
1331
+ typealias TtsProgressCallbackWithArg =
1332
+ @convention(c) (
1333
+ UnsafePointer<Float>?, Int32, Float, UnsafeMutableRawPointer?
1334
+ ) -> Int32
1335
+
1336
+ struct SherpaOnnxGenerationConfigSwift {
1337
+ var silenceScale: Float = 0.2
1338
+ var speed: Float = 1.0
1339
+ var sid: Int = 0
1340
+ var referenceAudio: [Float] = []
1341
+ var referenceSampleRate: Int = 16000
1342
+ var referenceText: String = ""
1343
+ var numSteps: Int = 1
1344
+ var extra: [String: Any] = [:] // Any can be String, Int, Float, Double
1345
+
1346
+ /// Convert the extra dictionary into a JSON string
1347
+ func extraJsonString() -> String {
1348
+ var jsonCompatible: [String: Any] = [:]
1349
+
1350
+ for (key, value) in extra {
1351
+ switch value {
1352
+ case let v as String:
1353
+ jsonCompatible[key] = v
1354
+ case let v as Int:
1355
+ jsonCompatible[key] = v
1356
+ case let v as Float:
1357
+ jsonCompatible[key] = v
1358
+ case let v as Double:
1359
+ jsonCompatible[key] = v
1360
+ default:
1361
+ // ignore unsupported types
1362
+ print("Warning: unsupported type for key '\(key)' in extra")
1363
+ }
1364
+ }
1365
+
1366
+ guard let data = try? JSONSerialization.data(withJSONObject: jsonCompatible, options: []),
1367
+ let json = String(data: data, encoding: .utf8)
1368
+ else {
1369
+ return "{}"
1370
+ }
1371
+
1372
+ return json
1373
+ }
1374
+ }
1375
+
1376
+ final class SherpaOnnxGenerationConfigC {
1377
+ /// The underlying C struct
1378
+ var cConfig: SherpaOnnxGenerationConfig
1379
+
1380
+ /// Storage for reference audio so the pointer stays valid during the C call
1381
+ private let referenceAudioStorage: [Float]
1382
+
1383
+ /// Extra JSON string for C API
1384
+ let extraJson: String
1385
+
1386
+ init(_ swiftConfig: SherpaOnnxGenerationConfigSwift) {
1387
+ let referenceAudio = swiftConfig.referenceAudio
1388
+
1389
+ let extraJson = swiftConfig.extraJsonString()
1390
+ self.extraJson = extraJson
1391
+
1392
+ referenceAudioStorage = referenceAudio
1393
+
1394
+ cConfig = referenceAudioStorage.withUnsafeBufferPointer { buffer in
1395
+ SherpaOnnxGenerationConfig(
1396
+ silence_scale: swiftConfig.silenceScale,
1397
+ speed: swiftConfig.speed,
1398
+ sid: Int32(swiftConfig.sid),
1399
+ reference_audio: buffer.count > 0 ? buffer.baseAddress : nil,
1400
+ reference_audio_len: Int32(buffer.count),
1401
+ reference_sample_rate: Int32(swiftConfig.referenceSampleRate),
1402
+ reference_text: toCPointer(swiftConfig.referenceText),
1403
+ num_steps: Int32(swiftConfig.numSteps),
1404
+ extra: toCPointer(extraJson)
1405
+ )
1406
+ }
1407
+ }
1408
+ }
1409
+
1027
1410
  class SherpaOnnxOfflineTtsWrapper {
1028
1411
  /// A pointer to the underlying counterpart in C
1029
1412
  let tts: OpaquePointer!
@@ -1042,21 +1425,47 @@ class SherpaOnnxOfflineTtsWrapper {
1042
1425
  }
1043
1426
 
1044
1427
  func generate(text: String, sid: Int = 0, speed: Float = 1.0) -> SherpaOnnxGeneratedAudioWrapper {
1045
- let audio: UnsafePointer<SherpaOnnxGeneratedAudio>? = SherpaOnnxOfflineTtsGenerate(
1046
- tts, toCPointer(text), Int32(sid), speed
1047
- )
1048
-
1049
- return SherpaOnnxGeneratedAudioWrapper(audio: audio)
1428
+ let config = SherpaOnnxGenerationConfigSwift(speed: speed, sid: sid)
1429
+ return generateWithConfig(text: text, config: config, callback: nil, arg: nil)
1050
1430
  }
1051
1431
 
1052
1432
  func generateWithCallbackWithArg(
1053
1433
  text: String, callback: TtsCallbackWithArg, arg: UnsafeMutableRawPointer, sid: Int = 0,
1054
1434
  speed: Float = 1.0
1055
1435
  ) -> SherpaOnnxGeneratedAudioWrapper {
1436
+ let config = SherpaOnnxGenerationConfigSwift(speed: speed, sid: sid)
1437
+
1438
+ let pair = SherpaOnnxCallbackPair(cb: callback, arg: arg)
1439
+ let unmanaged = Unmanaged.passRetained(pair)
1440
+ let wrapper: TtsProgressCallbackWithArg = { samples, n, _, rawArg in
1441
+ let p = Unmanaged<SherpaOnnxCallbackPair>.fromOpaque(rawArg!).takeUnretainedValue()
1442
+ return p.cb!(samples, n, p.arg)
1443
+ }
1444
+ let result = generateWithConfig(
1445
+ text: text, config: config, callback: wrapper, arg: unmanaged.toOpaque()
1446
+ )
1447
+ unmanaged.release()
1448
+ return result
1449
+ }
1450
+
1451
+ func generateWithConfig(
1452
+ text: String,
1453
+ config: SherpaOnnxGenerationConfigSwift,
1454
+ callback: TtsProgressCallbackWithArg?,
1455
+ arg: UnsafeMutableRawPointer?
1456
+ ) -> SherpaOnnxGeneratedAudioWrapper {
1457
+ let bridge = SherpaOnnxGenerationConfigC(config)
1458
+
1056
1459
  let audio: UnsafePointer<SherpaOnnxGeneratedAudio>? =
1057
- SherpaOnnxOfflineTtsGenerateWithCallbackWithArg(
1058
- tts, toCPointer(text), Int32(sid), speed, callback, arg
1059
- )
1460
+ withUnsafePointer(to: &bridge.cConfig) { configPtr in
1461
+ SherpaOnnxOfflineTtsGenerateWithConfig(
1462
+ tts,
1463
+ toCPointer(text),
1464
+ configPtr,
1465
+ callback,
1466
+ arg
1467
+ )
1468
+ }
1060
1469
 
1061
1470
  return SherpaOnnxGeneratedAudioWrapper(audio: audio)
1062
1471
  }
@@ -1553,9 +1962,17 @@ func sherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig(model: String = "")
1553
1962
  return SherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig(model: toCPointer(model))
1554
1963
  }
1555
1964
 
1965
+ func sherpaOnnxOfflineSpeechDenoiserDpdfNetModelConfig(model: String = "")
1966
+ -> SherpaOnnxOfflineSpeechDenoiserDpdfNetModelConfig
1967
+ {
1968
+ return SherpaOnnxOfflineSpeechDenoiserDpdfNetModelConfig(model: toCPointer(model))
1969
+ }
1970
+
1556
1971
  func sherpaOnnxOfflineSpeechDenoiserModelConfig(
1557
1972
  gtcrn: SherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig =
1558
1973
  sherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig(),
1974
+ dpdfnet: SherpaOnnxOfflineSpeechDenoiserDpdfNetModelConfig =
1975
+ sherpaOnnxOfflineSpeechDenoiserDpdfNetModelConfig(),
1559
1976
  numThreads: Int = 1,
1560
1977
  provider: String = "cpu",
1561
1978
  debug: Int = 0
@@ -1564,7 +1981,8 @@ func sherpaOnnxOfflineSpeechDenoiserModelConfig(
1564
1981
  gtcrn: gtcrn,
1565
1982
  num_threads: Int32(numThreads),
1566
1983
  debug: Int32(debug),
1567
- provider: toCPointer(provider)
1984
+ provider: toCPointer(provider),
1985
+ dpdfnet: dpdfnet
1568
1986
  )
1569
1987
  }
1570
1988
 
@@ -1592,14 +2010,24 @@ class SherpaOnnxDenoisedAudioWrapper {
1592
2010
  }
1593
2011
 
1594
2012
  var n: Int32 {
2013
+ guard let audio else {
2014
+ return 0
2015
+ }
1595
2016
  return audio.pointee.n
1596
2017
  }
1597
2018
 
1598
2019
  var sampleRate: Int32 {
2020
+ guard let audio else {
2021
+ return 0
2022
+ }
1599
2023
  return audio.pointee.sample_rate
1600
2024
  }
1601
2025
 
1602
2026
  var samples: [Float] {
2027
+ guard let audio else {
2028
+ return []
2029
+ }
2030
+
1603
2031
  if let p = audio.pointee.samples {
1604
2032
  var samples: [Float] = []
1605
2033
  for index in 0 ..< n {
@@ -1612,6 +2040,9 @@ class SherpaOnnxDenoisedAudioWrapper {
1612
2040
  }
1613
2041
 
1614
2042
  func save(filename: String) -> Int32 {
2043
+ guard let audio else {
2044
+ return 0
2045
+ }
1615
2046
  return SherpaOnnxWriteWave(audio.pointee.samples, n, sampleRate, toCPointer(filename))
1616
2047
  }
1617
2048
  }
@@ -1646,6 +2077,53 @@ class SherpaOnnxOfflineSpeechDenoiserWrapper {
1646
2077
  }
1647
2078
  }
1648
2079
 
2080
+ func sherpaOnnxOnlineSpeechDenoiserConfig(
2081
+ model: SherpaOnnxOfflineSpeechDenoiserModelConfig =
2082
+ sherpaOnnxOfflineSpeechDenoiserModelConfig()
2083
+ ) -> SherpaOnnxOnlineSpeechDenoiserConfig {
2084
+ return SherpaOnnxOnlineSpeechDenoiserConfig(model: model)
2085
+ }
2086
+
2087
+ class SherpaOnnxOnlineSpeechDenoiserWrapper {
2088
+ let impl: OpaquePointer!
2089
+
2090
+ init(
2091
+ config: UnsafePointer<SherpaOnnxOnlineSpeechDenoiserConfig>!
2092
+ ) {
2093
+ impl = SherpaOnnxCreateOnlineSpeechDenoiser(config)
2094
+ }
2095
+
2096
+ deinit {
2097
+ if let impl {
2098
+ SherpaOnnxDestroyOnlineSpeechDenoiser(impl)
2099
+ }
2100
+ }
2101
+
2102
+ func run(samples: [Float], sampleRate: Int) -> SherpaOnnxDenoisedAudioWrapper {
2103
+ let audio: UnsafePointer<SherpaOnnxDenoisedAudio>? = SherpaOnnxOnlineSpeechDenoiserRun(
2104
+ impl, samples, Int32(samples.count), Int32(sampleRate)
2105
+ )
2106
+ return SherpaOnnxDenoisedAudioWrapper(audio: audio)
2107
+ }
2108
+
2109
+ func flush() -> SherpaOnnxDenoisedAudioWrapper {
2110
+ let audio: UnsafePointer<SherpaOnnxDenoisedAudio>? = SherpaOnnxOnlineSpeechDenoiserFlush(impl)
2111
+ return SherpaOnnxDenoisedAudioWrapper(audio: audio)
2112
+ }
2113
+
2114
+ func reset() {
2115
+ SherpaOnnxOnlineSpeechDenoiserReset(impl)
2116
+ }
2117
+
2118
+ var sampleRate: Int {
2119
+ return Int(SherpaOnnxOnlineSpeechDenoiserGetSampleRate(impl))
2120
+ }
2121
+
2122
+ var frameShiftInSamples: Int {
2123
+ return Int(SherpaOnnxOnlineSpeechDenoiserGetFrameShiftInSamples(impl))
2124
+ }
2125
+ }
2126
+
1649
2127
  func getSherpaOnnxVersion() -> String {
1650
2128
  return String(cString: SherpaOnnxGetVersionStr())
1651
2129
  }
@@ -1657,3 +2135,167 @@ func getSherpaOnnxGitSha1() -> String {
1657
2135
  func getSherpaOnnxGitDate() -> String {
1658
2136
  return String(cString: SherpaOnnxGetGitDate())
1659
2137
  }
2138
+
2139
+ // ---------------------------
2140
+ // Source separation
2141
+ // ---------------------------
2142
+
2143
+ struct AudioData {
2144
+ private enum Storage {
2145
+ case owned([Float])
2146
+ case wrapped(ManagedWave)
2147
+ }
2148
+
2149
+ private class ManagedWave {
2150
+ let pointer: UnsafePointer<SherpaOnnxMultiChannelWave>
2151
+ init(_ p: UnsafePointer<SherpaOnnxMultiChannelWave>) {
2152
+ pointer = p
2153
+ }
2154
+
2155
+ deinit { SherpaOnnxFreeMultiChannelWave(pointer) }
2156
+ }
2157
+
2158
+ private let storage: Storage
2159
+ let channelCount: Int
2160
+ let samplesPerChannel: Int
2161
+ let sampleRate: Int
2162
+
2163
+ init(samples: [Float], channelCount: Int, sampleRate: Int) {
2164
+ storage = .owned(samples)
2165
+ self.channelCount = channelCount
2166
+ self.sampleRate = sampleRate
2167
+ samplesPerChannel = channelCount > 0 ? samples.count / channelCount : 0
2168
+ }
2169
+
2170
+ init?(filename: String) {
2171
+ guard let ptr = SherpaOnnxReadWaveMultiChannel(filename) else { return nil }
2172
+ storage = .wrapped(ManagedWave(ptr))
2173
+ channelCount = Int(ptr.pointee.num_channels)
2174
+ samplesPerChannel = Int(ptr.pointee.num_samples)
2175
+ sampleRate = Int(ptr.pointee.sample_rate)
2176
+ }
2177
+
2178
+ func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Float>) -> R) -> R {
2179
+ switch storage {
2180
+ case let .owned(array):
2181
+ return array.withUnsafeBufferPointer(body)
2182
+ case let .wrapped(managed):
2183
+ let total = Int(managed.pointer.pointee.num_channels * managed.pointer.pointee.num_samples)
2184
+ // Ensure we start from the first channel's pointer
2185
+ return body(UnsafeBufferPointer(start: managed.pointer.pointee.samples[0], count: total))
2186
+ }
2187
+ }
2188
+
2189
+ @discardableResult
2190
+ func save(to filename: String) -> Bool {
2191
+ return withUnsafeBufferPointer { buf in
2192
+ guard let base = buf.baseAddress else { return false }
2193
+ // FIX: Explicitly type the array as Optional pointers to match C 'float* const*'
2194
+ var ptrs: [UnsafePointer<Float>?] = (0 ..< channelCount).map { base + ($0 * samplesPerChannel) }
2195
+
2196
+ return SherpaOnnxWriteWaveMultiChannel(
2197
+ &ptrs,
2198
+ Int32(samplesPerChannel),
2199
+ Int32(sampleRate),
2200
+ Int32(channelCount),
2201
+ filename
2202
+ ) == 1
2203
+ }
2204
+ }
2205
+ }
2206
+
2207
+ struct SourceSeparationConfig {
2208
+ struct Spleeter {
2209
+ var vocals: String
2210
+ var accompaniment: String
2211
+ }
2212
+
2213
+ struct Uvr { var model: String }
2214
+
2215
+ var spleeter: Spleeter?
2216
+ var uvr: Uvr?
2217
+ var numThreads: Int = 1
2218
+ var debug: Bool = false
2219
+ var provider: String = "cpu"
2220
+
2221
+ func withCConfig<R>(_ body: (UnsafePointer<SherpaOnnxOfflineSourceSeparationConfig>) -> R) -> R {
2222
+ var cConfig = SherpaOnnxOfflineSourceSeparationConfig()
2223
+ cConfig.model.num_threads = Int32(numThreads)
2224
+ cConfig.model.debug = debug ? 1 : 0
2225
+
2226
+ var s: [String: [Int8]] = [:]
2227
+ func b(_ k: String, _ v: String?) -> UnsafePointer<Int8>? {
2228
+ guard let v = v else { return nil }
2229
+ s[k] = Array(v.utf8CString)
2230
+ return s[k]!.withUnsafeBufferPointer { $0.baseAddress }
2231
+ }
2232
+
2233
+ cConfig.model.provider = b("provider", provider)
2234
+ cConfig.model.spleeter.vocals = b("spleeter.vocals", spleeter?.vocals)
2235
+ cConfig.model.spleeter.accompaniment = b("spleeter.accompaniment", spleeter?.accompaniment)
2236
+ cConfig.model.uvr.model = b("uvr.model", uvr?.model)
2237
+
2238
+ return body(&cConfig)
2239
+ }
2240
+ }
2241
+
2242
+ class SourceSeparator {
2243
+ private var engine: OpaquePointer?
2244
+
2245
+ init?(config: SourceSeparationConfig) {
2246
+ engine = config.withCConfig { SherpaOnnxCreateOfflineSourceSeparation($0) }
2247
+
2248
+ if engine == nil { return nil }
2249
+ }
2250
+
2251
+ deinit {
2252
+ if let e = engine {
2253
+ SherpaOnnxDestroyOfflineSourceSeparation(e)
2254
+ }
2255
+ }
2256
+
2257
+ func process(buffer: AudioData) -> [AudioData]? {
2258
+ guard let engine = engine else { return nil }
2259
+
2260
+ return buffer.withUnsafeBufferPointer { flatBuf in
2261
+ guard let base = flatBuf.baseAddress else { return nil }
2262
+ var ptrs: [UnsafePointer<Float>?] = (0 ..< buffer.channelCount).map {
2263
+ base + ($0 * buffer.samplesPerChannel)
2264
+ }
2265
+
2266
+ guard
2267
+ let raw = SherpaOnnxOfflineSourceSeparationProcess(
2268
+ engine,
2269
+ &ptrs,
2270
+ Int32(buffer.channelCount),
2271
+ Int32(buffer.samplesPerChannel),
2272
+ Int32(buffer.sampleRate)
2273
+ )
2274
+ else { return nil }
2275
+
2276
+ let stemCount = Int(raw.pointee.num_stems)
2277
+ let result = (0 ..< stemCount).map { i in
2278
+ let stem = raw.pointee.stems[i]
2279
+ let chs = Int(stem.num_channels)
2280
+ let n = Int(stem.n)
2281
+ var flat = [Float](repeating: 0, count: chs * n)
2282
+
2283
+ for c in 0 ..< chs {
2284
+ if let src = stem.samples[c] {
2285
+ let offset = c * n
2286
+ flat.withUnsafeMutableBufferPointer { dest in
2287
+ let destPtr = dest.baseAddress!.advanced(by: offset)
2288
+ destPtr.initialize(from: src, count: n)
2289
+ }
2290
+ }
2291
+ }
2292
+ return AudioData(
2293
+ samples: flat, channelCount: chs, sampleRate: Int(raw.pointee.sample_rate)
2294
+ )
2295
+ }
2296
+
2297
+ SherpaOnnxDestroySourceSeparationOutput(raw)
2298
+ return result
2299
+ }
2300
+ }
2301
+ }