Skip to content

Mac App 如何使用 SherpaOnnx 接入 SenseVoice? #29

Description

@s1ntoneli

使用这个 issue 中的指引接入 SherpaOnnx:

k2-fsa/sherpa-onnx#1368

根据这个文档编译最新的 libonnxruntime.a sherpa-onnx.xcframework
https://k2-fsa.github.io/sherpa/onnx/swift-api/index.html

macOS 使用 swift-api-examples 作为示例,使用 build-swift-macos.sh 编译

下载模型:
https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models

以下是对应 2025.9.9 版本的模型格式接入文件 SherpaOnnx.swift,把它复制到你的项目里(第一个issue指引中有提到):

/// swift-api-examples/SherpaOnnx.swift
/// Copyright (c)  2023  Xiaomi Corporation

import Foundation // For NSString

/// Convert a String from swift to a `const char*` so that we can pass it to
/// the C language.
///
/// - Parameters:
///   - s: The String to convert.
/// - Returns: A pointer that can be passed to C as `const char*`

func toCPointer(_ s: String) -> UnsafePointer<Int8>! {
    let cs = (s as NSString).utf8String
    return UnsafePointer<Int8>(cs)
}

/// Return an instance of SherpaOnnxOnlineTransducerModelConfig.
///
/// Please refer to
/// https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-transducer/index.html
/// to download the required `.onnx` files.
///
/// - Parameters:
///   - encoder: Path to encoder.onnx
///   - decoder: Path to decoder.onnx
///   - joiner: Path to joiner.onnx
///
/// - Returns: Return an instance of SherpaOnnxOnlineTransducerModelConfig
func sherpaOnnxOnlineTransducerModelConfig(
    encoder: String = "",
    decoder: String = "",
    joiner: String = ""
) -> SherpaOnnxOnlineTransducerModelConfig {
    return SherpaOnnxOnlineTransducerModelConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder),
        joiner: toCPointer(joiner)
    )
}

/// Return an instance of SherpaOnnxOnlineParaformerModelConfig.
///
/// Please refer to
/// https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-paraformer/index.html
/// to download the required `.onnx` files.
///
/// - Parameters:
///   - encoder: Path to encoder.onnx
///   - decoder: Path to decoder.onnx
///
/// - Returns: Return an instance of SherpaOnnxOnlineParaformerModelConfig
func sherpaOnnxOnlineParaformerModelConfig(
    encoder: String = "",
    decoder: String = ""
) -> SherpaOnnxOnlineParaformerModelConfig {
    return SherpaOnnxOnlineParaformerModelConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder)
    )
}

func sherpaOnnxOnlineZipformer2CtcModelConfig(
    model: String = ""
) -> SherpaOnnxOnlineZipformer2CtcModelConfig {
    return SherpaOnnxOnlineZipformer2CtcModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOnlineNemoCtcModelConfig(
    model: String = ""
) -> SherpaOnnxOnlineNemoCtcModelConfig {
    return SherpaOnnxOnlineNemoCtcModelConfig(
        model: toCPointer(model)
    )
}

/**

 SHERPA_ONNX_API typedef struct SherpaOnnxOnlineToneCtcModelConfig {
   const char *model;
 } SherpaOnnxOnlineToneCtcModelConfig;
 */
func sherpaOnnxOnlineToneCtcModelConfig(
    model: String = ""
) -> SherpaOnnxOnlineToneCtcModelConfig {
    return SherpaOnnxOnlineToneCtcModelConfig(
        model: toCPointer(model)
    )
}

/// Return an instance of SherpaOnnxOnlineModelConfig.
///
/// Please refer to
/// https://k2-fsa.github.io/sherpa/onnx/pretrained_models/index.html
/// to download the required `.onnx` files.
///
/// - Parameters:
///   - tokens: Path to tokens.txt
///   - numThreads:  Number of threads to use for neural network computation.
///
/// - Returns: Return an instance of SherpaOnnxOnlineTransducerModelConfig
func sherpaOnnxOnlineModelConfig(
    tokens: String,
    transducer: SherpaOnnxOnlineTransducerModelConfig = sherpaOnnxOnlineTransducerModelConfig(),
    paraformer: SherpaOnnxOnlineParaformerModelConfig = sherpaOnnxOnlineParaformerModelConfig(),
    zipformer2Ctc: SherpaOnnxOnlineZipformer2CtcModelConfig =
        sherpaOnnxOnlineZipformer2CtcModelConfig(),
    numThreads: Int = 1,
    provider: String = "cpu",
    debug: Int = 0,
    modelType: String = "",
    modelingUnit: String = "cjkchar",
    bpeVocab: String = "",
    tokensBuf: String = "",
    tokensBufSize: Int = 0,
    nemoCtc: SherpaOnnxOnlineNemoCtcModelConfig = sherpaOnnxOnlineNemoCtcModelConfig(),
    t_oneCtc: SherpaOnnxOnlineToneCtcModelConfig =
        sherpaOnnxOnlineToneCtcModelConfig()
) -> SherpaOnnxOnlineModelConfig {
    return SherpaOnnxOnlineModelConfig(
        transducer: transducer,
        paraformer: paraformer,
        zipformer2_ctc: zipformer2Ctc,
        tokens: toCPointer(tokens),
        num_threads: Int32(numThreads),
        provider: toCPointer(provider),
        debug: Int32(debug),
        model_type: toCPointer(modelType),
        modeling_unit: toCPointer(modelingUnit),
        bpe_vocab: toCPointer(bpeVocab),
        tokens_buf: toCPointer(tokensBuf),
        tokens_buf_size: Int32(tokensBufSize),
        nemo_ctc: nemoCtc,
        t_one_ctc: t_oneCtc
    )
}

func sherpaOnnxFeatureConfig(
    sampleRate: Int = 16000,
    featureDim: Int = 80
) -> SherpaOnnxFeatureConfig {
    return SherpaOnnxFeatureConfig(
        sample_rate: Int32(sampleRate),
        feature_dim: Int32(featureDim)
    )
}

func sherpaOnnxOnlineCtcFstDecoderConfig(
    graph: String = "",
    maxActive: Int = 3000
) -> SherpaOnnxOnlineCtcFstDecoderConfig {
    return SherpaOnnxOnlineCtcFstDecoderConfig(
        graph: toCPointer(graph),
        max_active: Int32(maxActive)
    )
}

func sherpaOnnxHomophoneReplacerConfig(
    dictDir: String = "",
    lexicon: String = "",
    ruleFsts: String = ""
) -> SherpaOnnxHomophoneReplacerConfig {
    return SherpaOnnxHomophoneReplacerConfig(
        dict_dir: toCPointer(dictDir),
        lexicon: toCPointer(lexicon),
        rule_fsts: toCPointer(ruleFsts)
    )
}

func sherpaOnnxOnlineRecognizerConfig(
    featConfig: SherpaOnnxFeatureConfig,
    modelConfig: SherpaOnnxOnlineModelConfig,
    enableEndpoint: Bool = false,
    rule1MinTrailingSilence: Float = 2.4,
    rule2MinTrailingSilence: Float = 1.2,
    rule3MinUtteranceLength: Float = 30,
    decodingMethod: String = "greedy_search",
    maxActivePaths: Int = 4,
    hotwordsFile: String = "",
    hotwordsScore: Float = 1.5,
    ctcFstDecoderConfig: SherpaOnnxOnlineCtcFstDecoderConfig = sherpaOnnxOnlineCtcFstDecoderConfig(),
    ruleFsts: String = "",
    ruleFars: String = "",
    blankPenalty: Float = 0.0,
    hotwordsBuf: String = "",
    hotwordsBufSize: Int = 0,
    hr: SherpaOnnxHomophoneReplacerConfig = sherpaOnnxHomophoneReplacerConfig()
) -> SherpaOnnxOnlineRecognizerConfig {
    return SherpaOnnxOnlineRecognizerConfig(
        feat_config: featConfig,
        model_config: modelConfig,
        decoding_method: toCPointer(decodingMethod),
        max_active_paths: Int32(maxActivePaths),
        enable_endpoint: enableEndpoint ? 1 : 0,
        rule1_min_trailing_silence: rule1MinTrailingSilence,
        rule2_min_trailing_silence: rule2MinTrailingSilence,
        rule3_min_utterance_length: rule3MinUtteranceLength,
        hotwords_file: toCPointer(hotwordsFile),
        hotwords_score: hotwordsScore,
        ctc_fst_decoder_config: ctcFstDecoderConfig,
        rule_fsts: toCPointer(ruleFsts),
        rule_fars: toCPointer(ruleFars),
        blank_penalty: blankPenalty,
        hotwords_buf: toCPointer(hotwordsBuf),
        hotwords_buf_size: Int32(hotwordsBufSize),
        hr: hr
    )
}

/// Wrapper for recognition result.
///
/// Usage:
///
///  let result = recognizer.getResult()
///  print("text: \(result.text)")
///
class SherpaOnnxOnlineRecongitionResult {
    /// A pointer to the underlying counterpart in C
    private let result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>

    private lazy var _text: String = {
        guard let cstr = result.pointee.text else { return "" }
        return String(cString: cstr)
    }()

    private lazy var _tokens: [String] = {
        guard let tokensPointer = result.pointee.tokens_arr else { return [] }
        return (0 ..< count).compactMap { index in
            guard let ptr = tokensPointer[index] else { return nil }
            return String(cString: ptr)
        }
    }()

    private lazy var _timestamps: [Float] = {
        guard let timestampsPointer = result.pointee.timestamps else { return [] }
        return (0 ..< count).map { index in timestampsPointer[index] }
    }()

    init(result: UnsafePointer<SherpaOnnxOnlineRecognizerResult>) {
        self.result = result
    }

    deinit {
        SherpaOnnxDestroyOnlineRecognizerResult(result)
    }

    /// Return the actual recognition result.
    /// For English models, it contains words separated by spaces.
    /// For Chinese models, it contains Chinese words.
    var text: String { _text }

    var count: Int { Int(result.pointee.count) }

    var tokens: [String] { _tokens }

    var timestamps: [Float] { _timestamps }
}

class SherpaOnnxRecognizer {
    /// A pointer to the underlying counterpart in C
    private let recognizer: OpaquePointer
    private var stream: OpaquePointer
    private let lock = NSLock() // for thread-safe stream replacement

    /// Constructor taking a model config
    init(
        config: UnsafePointer<SherpaOnnxOnlineRecognizerConfig>
    ) {
        recognizer = SherpaOnnxCreateOnlineRecognizer(config)
        stream = SherpaOnnxCreateOnlineStream(recognizer)
    }

    deinit {
        SherpaOnnxDestroyOnlineStream(stream)
        SherpaOnnxDestroyOnlineRecognizer(recognizer)
    }

    /// Decode wave samples.
    ///
    /// - Parameters:
    ///   - samples: Audio samples normalized to the range [-1, 1]
    ///   - sampleRate: Sample rate of the input audio samples. Must match
    ///                 the one expected by the model.
    func acceptWaveform(samples: [Float], sampleRate: Int = 16000) {
        SherpaOnnxOnlineStreamAcceptWaveform(stream, Int32(sampleRate), samples, Int32(samples.count))
    }

    func isReady() -> Bool {
        return SherpaOnnxIsOnlineStreamReady(recognizer, stream) != 0
    }

    /// If there are enough number of feature frames, it invokes the neural
    /// network computation and decoding. Otherwise, it is a no-op.
    func decode() {
        SherpaOnnxDecodeOnlineStream(recognizer, stream)
    }

    /// Get the decoding results so far
    func getResult() -> SherpaOnnxOnlineRecongitionResult {
        guard let result = SherpaOnnxGetOnlineStreamResult(recognizer, stream) else {
            fatalError("SherpaOnnxGetOnlineStreamResult returned nil")
        }
        return SherpaOnnxOnlineRecongitionResult(result: result)
    }

    /// Reset the recognizer, which clears the neural network model state
    /// and the state for decoding.
    /// If hotwords is an empty string, it just recreates the decoding stream
    /// If hotwords is not empty, it will create a new decoding stream with
    /// the given hotWords appended to the default hotwords.
    func reset(hotwords: String? = nil) {
        guard let words = hotwords, !words.isEmpty else {
            SherpaOnnxOnlineStreamReset(recognizer, stream)
            return
        }

        words.withCString { cString in
            guard let newStream = SherpaOnnxCreateOnlineStreamWithHotwords(recognizer, cString) else {
                fatalError("SherpaOnnxCreateOnlineStreamWithHotwords returned nil")
            }
            lock.lock()
            // lock while release and replace stream
            SherpaOnnxDestroyOnlineStream(stream)
            stream = newStream
            lock.unlock()
        }
    }

    /// Signal that no more audio samples would be available.
    /// After this call, you cannot call acceptWaveform() any more.
    func inputFinished() {
        SherpaOnnxOnlineStreamInputFinished(stream)
    }

    /// Return true is an endpoint has been detected.
    func isEndpoint() -> Bool {
        return SherpaOnnxOnlineStreamIsEndpoint(recognizer, stream) != 0
    }
}

// For offline APIs

func sherpaOnnxOfflineTransducerModelConfig(
    encoder: String = "",
    decoder: String = "",
    joiner: String = ""
) -> SherpaOnnxOfflineTransducerModelConfig {
    return SherpaOnnxOfflineTransducerModelConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder),
        joiner: toCPointer(joiner)
    )
}

func sherpaOnnxOfflineParaformerModelConfig(
    model: String = ""
) -> SherpaOnnxOfflineParaformerModelConfig {
    return SherpaOnnxOfflineParaformerModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOfflineZipformerCtcModelConfig(
    model: String = ""
) -> SherpaOnnxOfflineZipformerCtcModelConfig {
    return SherpaOnnxOfflineZipformerCtcModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOfflineNemoEncDecCtcModelConfig(
    model: String = ""
) -> SherpaOnnxOfflineNemoEncDecCtcModelConfig {
    return SherpaOnnxOfflineNemoEncDecCtcModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOfflineDolphinModelConfig(
    model: String = ""
) -> SherpaOnnxOfflineDolphinModelConfig {
    return SherpaOnnxOfflineDolphinModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOfflineWenetCtcModelConfig(
    model: String = ""
) -> SherpaOnnxOfflineWenetCtcModelConfig {
    return SherpaOnnxOfflineWenetCtcModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOfflineWhisperModelConfig(
    encoder: String = "",
    decoder: String = "",
    language: String = "",
    task: String = "transcribe",
    tailPaddings: Int = -1
) -> SherpaOnnxOfflineWhisperModelConfig {
    return SherpaOnnxOfflineWhisperModelConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder),
        language: toCPointer(language),
        task: toCPointer(task),
        tail_paddings: Int32(tailPaddings)
    )
}

func sherpaOnnxOfflineCanaryModelConfig(
    encoder: String = "",
    decoder: String = "",
    srcLang: String = "en",
    tgtLang: String = "en",
    usePnc: Bool = true
) -> SherpaOnnxOfflineCanaryModelConfig {
    return SherpaOnnxOfflineCanaryModelConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder),
        src_lang: toCPointer(srcLang),
        tgt_lang: toCPointer(tgtLang),
        use_pnc: usePnc ? 1 : 0
    )
}

func sherpaOnnxOfflineFireRedAsrModelConfig(
    encoder: String = "",
    decoder: String = ""
) -> SherpaOnnxOfflineFireRedAsrModelConfig {
    return SherpaOnnxOfflineFireRedAsrModelConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder)
    )
}

func sherpaOnnxOfflineMoonshineModelConfig(
    preprocessor: String = "",
    encoder: String = "",
    uncachedDecoder: String = "",
    cachedDecoder: String = ""
) -> SherpaOnnxOfflineMoonshineModelConfig {
    return SherpaOnnxOfflineMoonshineModelConfig(
        preprocessor: toCPointer(preprocessor),
        encoder: toCPointer(encoder),
        uncached_decoder: toCPointer(uncachedDecoder),
        cached_decoder: toCPointer(cachedDecoder)
    )
}

func sherpaOnnxOfflineTdnnModelConfig(
    model: String = ""
) -> SherpaOnnxOfflineTdnnModelConfig {
    return SherpaOnnxOfflineTdnnModelConfig(
        model: toCPointer(model)
    )
}

func sherpaOnnxOfflineSenseVoiceModelConfig(
    model: String = "",
    language: String = "",
    useInverseTextNormalization: Bool = false
) -> SherpaOnnxOfflineSenseVoiceModelConfig {
    return SherpaOnnxOfflineSenseVoiceModelConfig(
        model: toCPointer(model),
        language: toCPointer(language),
        use_itn: useInverseTextNormalization ? 1 : 0
    )
}

func sherpaOnnxOfflineLMConfig(
    model: String = "",
    scale: Float = 1.0
) -> SherpaOnnxOfflineLMConfig {
    return SherpaOnnxOfflineLMConfig(
        model: toCPointer(model),
        scale: scale
    )
}

func sherpaOnnxOfflineModelConfig(
    tokens: String,
    transducer: SherpaOnnxOfflineTransducerModelConfig = sherpaOnnxOfflineTransducerModelConfig(),
    paraformer: SherpaOnnxOfflineParaformerModelConfig = sherpaOnnxOfflineParaformerModelConfig(),
    nemoCtc: SherpaOnnxOfflineNemoEncDecCtcModelConfig = sherpaOnnxOfflineNemoEncDecCtcModelConfig(),
    whisper: SherpaOnnxOfflineWhisperModelConfig = sherpaOnnxOfflineWhisperModelConfig(),
    tdnn: SherpaOnnxOfflineTdnnModelConfig = sherpaOnnxOfflineTdnnModelConfig(),
    numThreads: Int = 1,
    provider: String = "cpu",
    debug: Int = 0,
    modelType: String = "",
    modelingUnit: String = "cjkchar",
    bpeVocab: String = "",
    teleSpeechCtc: String = "",
    senseVoice: SherpaOnnxOfflineSenseVoiceModelConfig = sherpaOnnxOfflineSenseVoiceModelConfig(),
    moonshine: SherpaOnnxOfflineMoonshineModelConfig = sherpaOnnxOfflineMoonshineModelConfig(),
    fireRedAsr: SherpaOnnxOfflineFireRedAsrModelConfig = sherpaOnnxOfflineFireRedAsrModelConfig(),
    dolphin: SherpaOnnxOfflineDolphinModelConfig = sherpaOnnxOfflineDolphinModelConfig(),
    zipformerCtc: SherpaOnnxOfflineZipformerCtcModelConfig =
        sherpaOnnxOfflineZipformerCtcModelConfig(),
    canary: SherpaOnnxOfflineCanaryModelConfig = sherpaOnnxOfflineCanaryModelConfig(),
    wenetCtc: SherpaOnnxOfflineWenetCtcModelConfig = sherpaOnnxOfflineWenetCtcModelConfig()
) -> SherpaOnnxOfflineModelConfig {
    return SherpaOnnxOfflineModelConfig(
        transducer: transducer,
        paraformer: paraformer,
        nemo_ctc: nemoCtc,
        whisper: whisper,
        tdnn: tdnn,
        tokens: toCPointer(tokens),
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider),
        model_type: toCPointer(modelType),
        modeling_unit: toCPointer(modelingUnit),
        bpe_vocab: toCPointer(bpeVocab),
        telespeech_ctc: toCPointer(teleSpeechCtc),
        sense_voice: senseVoice,
        moonshine: moonshine,
        fire_red_asr: fireRedAsr,
        dolphin: dolphin,
        zipformer_ctc: zipformerCtc,
        canary: canary,
        wenet_ctc: wenetCtc
    )
}

func sherpaOnnxOfflineRecognizerConfig(
    featConfig: SherpaOnnxFeatureConfig,
    modelConfig: SherpaOnnxOfflineModelConfig,
    lmConfig: SherpaOnnxOfflineLMConfig = sherpaOnnxOfflineLMConfig(),
    decodingMethod: String = "greedy_search",
    maxActivePaths: Int = 4,
    hotwordsFile: String = "",
    hotwordsScore: Float = 1.5,
    ruleFsts: String = "",
    ruleFars: String = "",
    blankPenalty: Float = 0.0,
    hr: SherpaOnnxHomophoneReplacerConfig = sherpaOnnxHomophoneReplacerConfig()
) -> SherpaOnnxOfflineRecognizerConfig {
    return SherpaOnnxOfflineRecognizerConfig(
        feat_config: featConfig,
        model_config: modelConfig,
        lm_config: lmConfig,
        decoding_method: toCPointer(decodingMethod),
        max_active_paths: Int32(maxActivePaths),
        hotwords_file: toCPointer(hotwordsFile),
        hotwords_score: hotwordsScore,
        rule_fsts: toCPointer(ruleFsts),
        rule_fars: toCPointer(ruleFars),
        blank_penalty: blankPenalty,
        hr: hr
    )
}

class SherpaOnnxOfflineRecongitionResult {
    /// A pointer to the underlying counterpart in C
    let result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>

    private lazy var _text: String = {
        guard let cstr = result.pointee.text else { return "" }
        return String(cString: cstr)
    }()

    private lazy var _timestamps: [Float] = {
        guard let p = result.pointee.timestamps else { return [] }
        return (0 ..< result.pointee.count).map { p[Int($0)] }
    }()

    private lazy var _durations: [Float] = {
        guard let p = result.pointee.durations else { return [] }
        return (0 ..< result.pointee.count).map { p[Int($0)] }
    }()

    private lazy var _lang: String = {
        guard let cstr = result.pointee.lang else { return "" }
        return String(cString: cstr)
    }()

    private lazy var _json: String = {
        guard let cstr = result.pointee.json else { return "" }
        return String(cString: cstr)
    }()

    private lazy var _tokens: String = {
        guard let cstr = result.pointee.tokens else { return "" }
        return String(cString: cstr)
    }()
    
    private lazy var _tokensArr: [String] = {
        guard let base = result.pointee.tokens_arr else { return [] }
        var out: [String] = []
        var i = 0
        while i < result.pointee.count {
            if let cstr = base[i] {
                out.append(String(cString: cstr))
                i += 1
            } else {
                break
            }
        }
        return out
    }()


    private lazy var _emotion: String = {
        guard let cstr = result.pointee.emotion else { return "" }
        return String(cString: cstr)
    }()

    private lazy var _event: String = {
        guard let cstr = result.pointee.event else { return "" }
        return String(cString: cstr)
    }()

    /// Return the actual recognition result.
    /// For English models, it contains words separated by spaces.
    /// For Chinese models, it contains Chinese words.
    var text: String { _text }
    var count: Int { Int(result.pointee.count) }
    var timestamps: [Float] { _timestamps }
    var durations: [Float] { _durations }

    // For SenseVoice models, it can be zh, en, ja, yue, ko
    // where zh is for Chinese
    // en is for English
    // ja is for Japanese
    // yue is for Cantonese
    // ko is for Korean
    var lang: String { _lang }

    // for SenseVoice models
    var emotion: String { _emotion }

    // for SenseVoice models
    var event: String { _event }
    
    var json: String { _json }
    
    var tokens: String { _tokens }
    var tokensArr: [String] { _tokensArr }

    init(result: UnsafePointer<SherpaOnnxOfflineRecognizerResult>) {
        self.result = result
    }

    deinit {
        SherpaOnnxDestroyOfflineRecognizerResult(result)
    }
}

class SherpaOnnxOfflineRecognizer {
    /// A pointer to the underlying counterpart in C
    private let recognizer: OpaquePointer

    init(
        config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>
    ) {
        guard let ptr = SherpaOnnxCreateOfflineRecognizer(config) else {
            fatalError("Failed to create SherpaOnnxOfflineRecognizer")
        }
        recognizer = ptr
    }

    deinit {
        SherpaOnnxDestroyOfflineRecognizer(recognizer)
    }

    /// Decode wave samples.
    ///
    /// - Parameters:
    ///   - samples: Audio samples normalized to the range [-1, 1]
    ///   - sampleRate: Sample rate of the input audio samples. Must match
    ///                 the one expected by the model.
    func decode(samples: [Float], sampleRate: Int = 16000) -> SherpaOnnxOfflineRecongitionResult {
        guard let stream = SherpaOnnxCreateOfflineStream(recognizer) else {
            fatalError("Failed to create offline stream")
        }

        defer { SherpaOnnxDestroyOfflineStream(stream) }

        SherpaOnnxAcceptWaveformOffline(stream, Int32(sampleRate), samples, Int32(samples.count))

        SherpaOnnxDecodeOfflineStream(recognizer, stream)

        guard let resultPtr = SherpaOnnxGetOfflineStreamResult(stream) else {
            fatalError("Failed to get offline recognition result")
        }

        return SherpaOnnxOfflineRecongitionResult(result: resultPtr)
    }

    func setConfig(config: UnsafePointer<SherpaOnnxOfflineRecognizerConfig>) {
        SherpaOnnxOfflineRecognizerSetConfig(recognizer, config)
    }
}

func sherpaOnnxSileroVadModelConfig(
    model: String = "",
    threshold: Float = 0.5,
    minSilenceDuration: Float = 0.25,
    minSpeechDuration: Float = 0.5,
    windowSize: Int = 512,
    maxSpeechDuration: Float = 5.0
) -> SherpaOnnxSileroVadModelConfig {
    return SherpaOnnxSileroVadModelConfig(
        model: toCPointer(model),
        threshold: threshold,
        min_silence_duration: minSilenceDuration,
        min_speech_duration: minSpeechDuration,
        window_size: Int32(windowSize),
        max_speech_duration: maxSpeechDuration
    )
}

func sherpaOnnxTenVadModelConfig(
    model: String = "",
    threshold: Float = 0.5,
    minSilenceDuration: Float = 0.25,
    minSpeechDuration: Float = 0.5,
    windowSize: Int = 256,
    maxSpeechDuration: Float = 5.0
) -> SherpaOnnxTenVadModelConfig {
    return SherpaOnnxTenVadModelConfig(
        model: toCPointer(model),
        threshold: threshold,
        min_silence_duration: minSilenceDuration,
        min_speech_duration: minSpeechDuration,
        window_size: Int32(windowSize),
        max_speech_duration: maxSpeechDuration
    )
}

func sherpaOnnxVadModelConfig(
    sileroVad: SherpaOnnxSileroVadModelConfig = sherpaOnnxSileroVadModelConfig(),
    sampleRate: Int32 = 16000,
    numThreads: Int = 1,
    provider: String = "cpu",
    debug: Int = 0,
    tenVad: SherpaOnnxTenVadModelConfig = sherpaOnnxTenVadModelConfig()
) -> SherpaOnnxVadModelConfig {
    return SherpaOnnxVadModelConfig(
        silero_vad: sileroVad,
        sample_rate: sampleRate,
        num_threads: Int32(numThreads),
        provider: toCPointer(provider),
        debug: Int32(debug),
        ten_vad: tenVad
    )
}

class SherpaOnnxCircularBufferWrapper {
    private let buffer: OpaquePointer

    init(capacity: Int) {
        guard let ptr = SherpaOnnxCreateCircularBuffer(Int32(capacity)) else {
            fatalError("Failed to create SherpaOnnxCircularBuffer")
        }
        buffer = ptr
    }

    deinit {
        SherpaOnnxDestroyCircularBuffer(buffer)
    }

    func push(samples: [Float]) {
        guard !samples.isEmpty else { return }
        SherpaOnnxCircularBufferPush(buffer, samples, Int32(samples.count))
    }

    func get(startIndex: Int, n: Int) -> [Float] {
        guard startIndex >= 0 else { return [] }
        guard n > 0 else { return [] }

        guard let ptr = SherpaOnnxCircularBufferGet(buffer, Int32(startIndex), Int32(n)) else {
            return []
        }
        defer { SherpaOnnxCircularBufferFree(ptr) }

        return Array(UnsafeBufferPointer(start: ptr, count: n))
    }

    func pop(n: Int) {
        guard n > 0 else { return }
        SherpaOnnxCircularBufferPop(buffer, Int32(n))
    }

    func size() -> Int {
        return Int(SherpaOnnxCircularBufferSize(buffer))
    }

    func reset() {
        SherpaOnnxCircularBufferReset(buffer)
    }
}

class SherpaOnnxSpeechSegmentWrapper {
    private let p: UnsafePointer<SherpaOnnxSpeechSegment>

    init(p: UnsafePointer<SherpaOnnxSpeechSegment>) {
        self.p = p
    }

    deinit {
        SherpaOnnxDestroySpeechSegment(p)
    }

    var start: Int {
        Int(p.pointee.start)
    }

    var n: Int {
        Int(p.pointee.n)
    }

    lazy var samples: [Float] = Array(UnsafeBufferPointer(start: p.pointee.samples, count: n))
}

class SherpaOnnxVoiceActivityDetectorWrapper {
    /// A pointer to the underlying counterpart in C
    private let vad: OpaquePointer

    init(config: UnsafePointer<SherpaOnnxVadModelConfig>, buffer_size_in_seconds: Float) {
        guard let vad = SherpaOnnxCreateVoiceActivityDetector(config, buffer_size_in_seconds) else {
            fatalError("SherpaOnnxCreateVoiceActivityDetector returned nil")
        }
        self.vad = vad
    }

    deinit {
        SherpaOnnxDestroyVoiceActivityDetector(vad)
    }

    func acceptWaveform(samples: [Float]) {
        SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, samples, Int32(samples.count))
    }

    func isEmpty() -> Bool {
        return SherpaOnnxVoiceActivityDetectorEmpty(vad) == 1
    }

    func isSpeechDetected() -> Bool {
        return SherpaOnnxVoiceActivityDetectorDetected(vad) == 1
    }

    func pop() {
        SherpaOnnxVoiceActivityDetectorPop(vad)
    }

    func clear() {
        SherpaOnnxVoiceActivityDetectorClear(vad)
    }

    func front() -> SherpaOnnxSpeechSegmentWrapper {
        guard let p = SherpaOnnxVoiceActivityDetectorFront(vad) else {
            fatalError("SherpaOnnxVoiceActivityDetectorFront returned nil")
        }
        return SherpaOnnxSpeechSegmentWrapper(p: p)
    }

    func reset() {
        SherpaOnnxVoiceActivityDetectorReset(vad)
    }

    func flush() {
        SherpaOnnxVoiceActivityDetectorFlush(vad)
    }
}

// offline tts
func sherpaOnnxOfflineTtsVitsModelConfig(
    model: String = "",
    lexicon: String = "",
    tokens: String = "",
    dataDir: String = "",
    noiseScale: Float = 0.667,
    noiseScaleW: Float = 0.8,
    lengthScale: Float = 1.0,
    dictDir: String = ""
) -> SherpaOnnxOfflineTtsVitsModelConfig {
    return SherpaOnnxOfflineTtsVitsModelConfig(
        model: toCPointer(model),
        lexicon: toCPointer(lexicon),
        tokens: toCPointer(tokens),
        data_dir: toCPointer(dataDir),
        noise_scale: noiseScale,
        noise_scale_w: noiseScaleW,
        length_scale: lengthScale,
        dict_dir: toCPointer(dictDir)
    )
}

func sherpaOnnxOfflineTtsMatchaModelConfig(
    acousticModel: String = "",
    vocoder: String = "",
    lexicon: String = "",
    tokens: String = "",
    dataDir: String = "",
    noiseScale: Float = 0.667,
    lengthScale: Float = 1.0,
    dictDir: String = ""
) -> SherpaOnnxOfflineTtsMatchaModelConfig {
    return SherpaOnnxOfflineTtsMatchaModelConfig(
        acoustic_model: toCPointer(acousticModel),
        vocoder: toCPointer(vocoder),
        lexicon: toCPointer(lexicon),
        tokens: toCPointer(tokens),
        data_dir: toCPointer(dataDir),
        noise_scale: noiseScale,
        length_scale: lengthScale,
        dict_dir: toCPointer(dictDir)
    )
}

func sherpaOnnxOfflineTtsKokoroModelConfig(
    model: String = "",
    voices: String = "",
    tokens: String = "",
    dataDir: String = "",
    lengthScale: Float = 1.0,
    dictDir: String = "",
    lexicon: String = "",
    lang: String = ""
) -> SherpaOnnxOfflineTtsKokoroModelConfig {
    return SherpaOnnxOfflineTtsKokoroModelConfig(
        model: toCPointer(model),
        voices: toCPointer(voices),
        tokens: toCPointer(tokens),
        data_dir: toCPointer(dataDir),
        length_scale: lengthScale,
        dict_dir: toCPointer(dictDir),
        lexicon: toCPointer(lexicon),
        lang: toCPointer(lang)
    )
}

func sherpaOnnxOfflineTtsKittenModelConfig(
    model: String = "",
    voices: String = "",
    tokens: String = "",
    dataDir: String = "",
    lengthScale: Float = 1.0
) -> SherpaOnnxOfflineTtsKittenModelConfig {
    return SherpaOnnxOfflineTtsKittenModelConfig(
        model: toCPointer(model),
        voices: toCPointer(voices),
        tokens: toCPointer(tokens),
        data_dir: toCPointer(dataDir),
        length_scale: lengthScale
    )
}

/*
 const char *tokens;
 const char *text_model;
 const char *flow_matching_model;
 const char *vocoder;
 const char *data_dir;
 const char *pinyin_dict;
 float feat_scale;
 float t_shift;
 float target_rms;
 float guidance_scale;
 */
func sherpaOnnxOfflineTtsZipvoiceModelConfig(
    tokens: String = "",
    textModel: String = "",
    flowMatchingModel: String = "",
    vocoder: String = "",
    dataDir: String = "",
    pinyinDict: String = "",
    featScale: Float = 0.0,
    tShift: Float = 0.0,
    targetRms: Float = 0.0,
    guidanceScale: Float = 0.0
) -> SherpaOnnxOfflineTtsZipvoiceModelConfig {
    return SherpaOnnxOfflineTtsZipvoiceModelConfig(
        tokens: toCPointer(tokens),
        text_model: toCPointer(textModel),
        flow_matching_model: toCPointer(flowMatchingModel),
        vocoder: toCPointer(vocoder),
        data_dir: toCPointer(dataDir),
        pinyin_dict: toCPointer(pinyinDict),
        feat_scale: featScale,
        t_shift: tShift,
        target_rms: targetRms,
        guidance_scale: guidanceScale
    )
}

func sherpaOnnxOfflineTtsModelConfig(
    vits: SherpaOnnxOfflineTtsVitsModelConfig = sherpaOnnxOfflineTtsVitsModelConfig(),
    matcha: SherpaOnnxOfflineTtsMatchaModelConfig = sherpaOnnxOfflineTtsMatchaModelConfig(),
    kokoro: SherpaOnnxOfflineTtsKokoroModelConfig = sherpaOnnxOfflineTtsKokoroModelConfig(),
    numThreads: Int = 1,
    debug: Int = 0,
    provider: String = "cpu",
    kitten: SherpaOnnxOfflineTtsKittenModelConfig = sherpaOnnxOfflineTtsKittenModelConfig(),
    zipvoice: SherpaOnnxOfflineTtsZipvoiceModelConfig = sherpaOnnxOfflineTtsZipvoiceModelConfig()
) -> SherpaOnnxOfflineTtsModelConfig {
    return SherpaOnnxOfflineTtsModelConfig(
        vits: vits,
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider),
        matcha: matcha,
        kokoro: kokoro,
        kitten: kitten,
        zipvoice: zipvoice
    )
}

func sherpaOnnxOfflineTtsConfig(
    model: SherpaOnnxOfflineTtsModelConfig,
    ruleFsts: String = "",
    ruleFars: String = "",
    maxNumSentences: Int = 1,
    silenceScale: Float = 0.2
) -> SherpaOnnxOfflineTtsConfig {
    return SherpaOnnxOfflineTtsConfig(
        model: model,
        rule_fsts: toCPointer(ruleFsts),
        max_num_sentences: Int32(maxNumSentences),
        rule_fars: toCPointer(ruleFars),
        silence_scale: silenceScale
    )
}

class SherpaOnnxWaveWrapper {
    let wave: UnsafePointer<SherpaOnnxWave>!

    class func readWave(filename: String) -> SherpaOnnxWaveWrapper {
        let wave = SherpaOnnxReadWave(toCPointer(filename))
        return SherpaOnnxWaveWrapper(wave: wave)
    }

    init(wave: UnsafePointer<SherpaOnnxWave>!) {
        self.wave = wave
    }

    deinit {
        if let wave {
            SherpaOnnxFreeWave(wave)
        }
    }

    var numSamples: Int {
        return Int(wave.pointee.num_samples)
    }

    var sampleRate: Int {
        return Int(wave.pointee.sample_rate)
    }

    var samples: [Float] {
        if numSamples == 0 {
            return []
        } else {
            return [Float](UnsafeBufferPointer(start: wave.pointee.samples, count: numSamples))
        }
    }
}

class SherpaOnnxGeneratedAudioWrapper {
    /// A pointer to the underlying counterpart in C
    let audio: UnsafePointer<SherpaOnnxGeneratedAudio>!

    init(audio: UnsafePointer<SherpaOnnxGeneratedAudio>!) {
        self.audio = audio
    }

    deinit {
        if let audio {
            SherpaOnnxDestroyOfflineTtsGeneratedAudio(audio)
        }
    }

    var n: Int32 {
        return audio.pointee.n
    }

    var sampleRate: Int32 {
        return audio.pointee.sample_rate
    }

    var samples: [Float] {
        if let p = audio.pointee.samples {
            return [Float](UnsafeBufferPointer(start: p, count: Int(n)))
        } else {
            return []
        }
    }

    func save(filename: String) -> Int32 {
        return SherpaOnnxWriteWave(audio.pointee.samples, n, sampleRate, toCPointer(filename))
    }
}

typealias TtsCallbackWithArg = (
    @convention(c) (
        UnsafePointer<Float>?, // const float* samples
        Int32, // int32_t n
        UnsafeMutableRawPointer? // void *arg
    ) -> Int32
)?

class SherpaOnnxOfflineTtsWrapper {
    /// A pointer to the underlying counterpart in C
    let tts: OpaquePointer!

    /// Constructor taking a model config
    init(
        config: UnsafePointer<SherpaOnnxOfflineTtsConfig>!
    ) {
        tts = SherpaOnnxCreateOfflineTts(config)
    }

    deinit {
        if let tts {
            SherpaOnnxDestroyOfflineTts(tts)
        }
    }

    func generate(text: String, sid: Int = 0, speed: Float = 1.0) -> SherpaOnnxGeneratedAudioWrapper {
        let audio: UnsafePointer<SherpaOnnxGeneratedAudio>? = SherpaOnnxOfflineTtsGenerate(
            tts, toCPointer(text), Int32(sid), speed
        )

        return SherpaOnnxGeneratedAudioWrapper(audio: audio)
    }

    func generateWithCallbackWithArg(
        text: String, callback: TtsCallbackWithArg, arg: UnsafeMutableRawPointer, sid: Int = 0,
        speed: Float = 1.0
    ) -> SherpaOnnxGeneratedAudioWrapper {
        let audio: UnsafePointer<SherpaOnnxGeneratedAudio>? =
            SherpaOnnxOfflineTtsGenerateWithCallbackWithArg(
                tts, toCPointer(text), Int32(sid), speed, callback, arg
            )

        return SherpaOnnxGeneratedAudioWrapper(audio: audio)
    }
}

// spoken language identification

func sherpaOnnxSpokenLanguageIdentificationWhisperConfig(
    encoder: String,
    decoder: String,
    tailPaddings: Int = -1
) -> SherpaOnnxSpokenLanguageIdentificationWhisperConfig {
    return SherpaOnnxSpokenLanguageIdentificationWhisperConfig(
        encoder: toCPointer(encoder),
        decoder: toCPointer(decoder),
        tail_paddings: Int32(tailPaddings)
    )
}

func sherpaOnnxSpokenLanguageIdentificationConfig(
    whisper: SherpaOnnxSpokenLanguageIdentificationWhisperConfig,
    numThreads: Int = 1,
    debug: Int = 0,
    provider: String = "cpu"
) -> SherpaOnnxSpokenLanguageIdentificationConfig {
    return SherpaOnnxSpokenLanguageIdentificationConfig(
        whisper: whisper,
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider)
    )
}

class SherpaOnnxSpokenLanguageIdentificationResultWrapper {
    /// A pointer to the underlying counterpart in C
    let result: UnsafePointer<SherpaOnnxSpokenLanguageIdentificationResult>!

    /// Return the detected language.
    /// en for English
    /// zh for Chinese
    /// es for Spanish
    /// de for German
    /// etc.
    var lang: String {
        return String(cString: result.pointee.lang)
    }

    init(result: UnsafePointer<SherpaOnnxSpokenLanguageIdentificationResult>!) {
        self.result = result
    }

    deinit {
        if let result {
            SherpaOnnxDestroySpokenLanguageIdentificationResult(result)
        }
    }
}

class SherpaOnnxSpokenLanguageIdentificationWrapper {
    /// A pointer to the underlying counterpart in C
    let slid: OpaquePointer!

    init(
        config: UnsafePointer<SherpaOnnxSpokenLanguageIdentificationConfig>!
    ) {
        slid = SherpaOnnxCreateSpokenLanguageIdentification(config)
    }

    deinit {
        if let slid {
            SherpaOnnxDestroySpokenLanguageIdentification(slid)
        }
    }

    func decode(samples: [Float], sampleRate: Int = 16000)
        -> SherpaOnnxSpokenLanguageIdentificationResultWrapper
    {
        let stream: OpaquePointer! = SherpaOnnxSpokenLanguageIdentificationCreateOfflineStream(slid)
        SherpaOnnxAcceptWaveformOffline(stream, Int32(sampleRate), samples, Int32(samples.count))

        let result: UnsafePointer<SherpaOnnxSpokenLanguageIdentificationResult>? =
            SherpaOnnxSpokenLanguageIdentificationCompute(
                slid,
                stream
            )

        SherpaOnnxDestroyOfflineStream(stream)
        return SherpaOnnxSpokenLanguageIdentificationResultWrapper(result: result)
    }
}

// keyword spotting

class SherpaOnnxKeywordResultWrapper {
    /// A pointer to the underlying counterpart in C
    let result: UnsafePointer<SherpaOnnxKeywordResult>!

    var keyword: String {
        return String(cString: result.pointee.keyword)
    }

    var count: Int32 {
        return result.pointee.count
    }

    var tokens: [String] {
        if let tokensPointer = result.pointee.tokens_arr {
            var tokens: [String] = []
            for index in 0 ..< count {
                if let tokenPointer = tokensPointer[Int(index)] {
                    let token = String(cString: tokenPointer)
                    tokens.append(token)
                }
            }
            return tokens
        } else {
            let tokens: [String] = []
            return tokens
        }
    }

    init(result: UnsafePointer<SherpaOnnxKeywordResult>!) {
        self.result = result
    }

    deinit {
        if let result {
            SherpaOnnxDestroyKeywordResult(result)
        }
    }
}

func sherpaOnnxKeywordSpotterConfig(
    featConfig: SherpaOnnxFeatureConfig,
    modelConfig: SherpaOnnxOnlineModelConfig,
    keywordsFile: String,
    maxActivePaths: Int = 4,
    numTrailingBlanks: Int = 1,
    keywordsScore: Float = 1.0,
    keywordsThreshold: Float = 0.25,
    keywordsBuf: String = "",
    keywordsBufSize: Int = 0
) -> SherpaOnnxKeywordSpotterConfig {
    return SherpaOnnxKeywordSpotterConfig(
        feat_config: featConfig,
        model_config: modelConfig,
        max_active_paths: Int32(maxActivePaths),
        num_trailing_blanks: Int32(numTrailingBlanks),
        keywords_score: keywordsScore,
        keywords_threshold: keywordsThreshold,
        keywords_file: toCPointer(keywordsFile),
        keywords_buf: toCPointer(keywordsBuf),
        keywords_buf_size: Int32(keywordsBufSize)
    )
}

class SherpaOnnxKeywordSpotterWrapper {
    /// A pointer to the underlying counterpart in C
    let spotter: OpaquePointer!
    var stream: OpaquePointer!

    init(
        config: UnsafePointer<SherpaOnnxKeywordSpotterConfig>!
    ) {
        spotter = SherpaOnnxCreateKeywordSpotter(config)
        stream = SherpaOnnxCreateKeywordStream(spotter)
    }

    deinit {
        if let stream {
            SherpaOnnxDestroyOnlineStream(stream)
        }

        if let spotter {
            SherpaOnnxDestroyKeywordSpotter(spotter)
        }
    }

    func acceptWaveform(samples: [Float], sampleRate: Int = 16000) {
        SherpaOnnxOnlineStreamAcceptWaveform(stream, Int32(sampleRate), samples, Int32(samples.count))
    }

    func isReady() -> Bool {
        return SherpaOnnxIsKeywordStreamReady(spotter, stream) == 1 ? true : false
    }

    func decode() {
        SherpaOnnxDecodeKeywordStream(spotter, stream)
    }

    func reset() {
        SherpaOnnxResetKeywordStream(spotter, stream)
    }

    func getResult() -> SherpaOnnxKeywordResultWrapper {
        let result: UnsafePointer<SherpaOnnxKeywordResult>? = SherpaOnnxGetKeywordResult(
            spotter, stream
        )
        return SherpaOnnxKeywordResultWrapper(result: result)
    }

    /// Signal that no more audio samples would be available.
    /// After this call, you cannot call acceptWaveform() any more.
    func inputFinished() {
        SherpaOnnxOnlineStreamInputFinished(stream)
    }
}

// Punctuation

func sherpaOnnxOfflinePunctuationModelConfig(
    ctTransformer: String,
    numThreads: Int = 1,
    debug: Int = 0,
    provider: String = "cpu"
) -> SherpaOnnxOfflinePunctuationModelConfig {
    return SherpaOnnxOfflinePunctuationModelConfig(
        ct_transformer: toCPointer(ctTransformer),
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider)
    )
}

func sherpaOnnxOfflinePunctuationConfig(
    model: SherpaOnnxOfflinePunctuationModelConfig
) -> SherpaOnnxOfflinePunctuationConfig {
    return SherpaOnnxOfflinePunctuationConfig(
        model: model
    )
}

class SherpaOnnxOfflinePunctuationWrapper {
    /// A pointer to the underlying counterpart in C
    let ptr: OpaquePointer!

    /// Constructor taking a model config
    init(
        config: UnsafePointer<SherpaOnnxOfflinePunctuationConfig>!
    ) {
        ptr = SherpaOnnxCreateOfflinePunctuation(config)
    }

    deinit {
        if let ptr {
            SherpaOnnxDestroyOfflinePunctuation(ptr)
        }
    }

    func addPunct(text: String) -> String {
        let cText = SherpaOfflinePunctuationAddPunct(ptr, toCPointer(text))
        let ans = String(cString: cText!)
        SherpaOfflinePunctuationFreeText(cText)
        return ans
    }
}

func sherpaOnnxOnlinePunctuationModelConfig(
    cnnBiLstm: String,
    bpeVocab: String,
    numThreads: Int = 1,
    debug: Int = 0,
    provider: String = "cpu"
) -> SherpaOnnxOnlinePunctuationModelConfig {
    return SherpaOnnxOnlinePunctuationModelConfig(
        cnn_bilstm: toCPointer(cnnBiLstm),
        bpe_vocab: toCPointer(bpeVocab),
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider)
    )
}

func sherpaOnnxOnlinePunctuationConfig(
    model: SherpaOnnxOnlinePunctuationModelConfig
) -> SherpaOnnxOnlinePunctuationConfig {
    return SherpaOnnxOnlinePunctuationConfig(model: model)
}

class SherpaOnnxOnlinePunctuationWrapper {
    /// A pointer to the underlying counterpart in C
    let ptr: OpaquePointer!

    /// Constructor taking a model config
    init(
        config: UnsafePointer<SherpaOnnxOnlinePunctuationConfig>!
    ) {
        ptr = SherpaOnnxCreateOnlinePunctuation(config)
    }

    deinit {
        if let ptr {
            SherpaOnnxDestroyOnlinePunctuation(ptr)
        }
    }

    func addPunct(text: String) -> String {
        let cText = SherpaOnnxOnlinePunctuationAddPunct(ptr, toCPointer(text))
        let ans = String(cString: cText!)
        SherpaOnnxOnlinePunctuationFreeText(cText)
        return ans
    }
}

func sherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(model: String)
    -> SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig
{
    return SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig(model: toCPointer(model))
}

func sherpaOnnxOfflineSpeakerSegmentationModelConfig(
    pyannote: SherpaOnnxOfflineSpeakerSegmentationPyannoteModelConfig,
    numThreads: Int = 1,
    debug: Int = 0,
    provider: String = "cpu"
) -> SherpaOnnxOfflineSpeakerSegmentationModelConfig {
    return SherpaOnnxOfflineSpeakerSegmentationModelConfig(
        pyannote: pyannote,
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider)
    )
}

func sherpaOnnxFastClusteringConfig(numClusters: Int = -1, threshold: Float = 0.5)
    -> SherpaOnnxFastClusteringConfig
{
    return SherpaOnnxFastClusteringConfig(num_clusters: Int32(numClusters), threshold: threshold)
}

func sherpaOnnxSpeakerEmbeddingExtractorConfig(
    model: String,
    numThreads: Int = 1,
    debug: Int = 0,
    provider: String = "cpu"
) -> SherpaOnnxSpeakerEmbeddingExtractorConfig {
    return SherpaOnnxSpeakerEmbeddingExtractorConfig(
        model: toCPointer(model),
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider)
    )
}

func sherpaOnnxOfflineSpeakerDiarizationConfig(
    segmentation: SherpaOnnxOfflineSpeakerSegmentationModelConfig,
    embedding: SherpaOnnxSpeakerEmbeddingExtractorConfig,
    clustering: SherpaOnnxFastClusteringConfig,
    minDurationOn: Float = 0.3,
    minDurationOff: Float = 0.5
) -> SherpaOnnxOfflineSpeakerDiarizationConfig {
    return SherpaOnnxOfflineSpeakerDiarizationConfig(
        segmentation: segmentation,
        embedding: embedding,
        clustering: clustering,
        min_duration_on: minDurationOn,
        min_duration_off: minDurationOff
    )
}

struct SherpaOnnxOfflineSpeakerDiarizationSegmentWrapper {
    var start: Float = 0
    var end: Float = 0
    var speaker: Int = 0
}

class SherpaOnnxOfflineSpeakerDiarizationWrapper {
    /// A pointer to the underlying counterpart in C
    let impl: OpaquePointer!

    init(
        config: UnsafePointer<SherpaOnnxOfflineSpeakerDiarizationConfig>!
    ) {
        impl = SherpaOnnxCreateOfflineSpeakerDiarization(config)
    }

    deinit {
        if let impl {
            SherpaOnnxDestroyOfflineSpeakerDiarization(impl)
        }
    }

    var sampleRate: Int {
        return Int(SherpaOnnxOfflineSpeakerDiarizationGetSampleRate(impl))
    }

    // only config.clustering is used. All other fields are ignored
    func setConfig(config: UnsafePointer<SherpaOnnxOfflineSpeakerDiarizationConfig>!) {
        SherpaOnnxOfflineSpeakerDiarizationSetConfig(impl, config)
    }

    func process(samples: [Float]) -> [SherpaOnnxOfflineSpeakerDiarizationSegmentWrapper] {
        let result = SherpaOnnxOfflineSpeakerDiarizationProcess(
            impl, samples, Int32(samples.count)
        )

        if result == nil {
            return []
        }

        let numSegments = Int(SherpaOnnxOfflineSpeakerDiarizationResultGetNumSegments(result))

        let p: UnsafePointer<SherpaOnnxOfflineSpeakerDiarizationSegment>? =
            SherpaOnnxOfflineSpeakerDiarizationResultSortByStartTime(result)

        if p == nil {
            return []
        }

        var ans: [SherpaOnnxOfflineSpeakerDiarizationSegmentWrapper] = []
        for i in 0 ..< numSegments {
            ans.append(
                SherpaOnnxOfflineSpeakerDiarizationSegmentWrapper(
                    start: p![i].start, end: p![i].end, speaker: Int(p![i].speaker)
                ))
        }

        SherpaOnnxOfflineSpeakerDiarizationDestroySegment(p)
        SherpaOnnxOfflineSpeakerDiarizationDestroyResult(result)

        return ans
    }
}

class SherpaOnnxOnlineStreamWrapper {
    /// A pointer to the underlying counterpart in C
    let impl: OpaquePointer!
    init(impl: OpaquePointer!) {
        self.impl = impl
    }

    deinit {
        if let impl {
            SherpaOnnxDestroyOnlineStream(impl)
        }
    }

    func acceptWaveform(samples: [Float], sampleRate: Int = 16000) {
        SherpaOnnxOnlineStreamAcceptWaveform(impl, Int32(sampleRate), samples, Int32(samples.count))
    }

    func inputFinished() {
        SherpaOnnxOnlineStreamInputFinished(impl)
    }
}

class SherpaOnnxSpeakerEmbeddingExtractorWrapper {
    /// A pointer to the underlying counterpart in C
    let impl: OpaquePointer!

    init(
        config: UnsafePointer<SherpaOnnxSpeakerEmbeddingExtractorConfig>!
    ) {
        impl = SherpaOnnxCreateSpeakerEmbeddingExtractor(config)
    }

    deinit {
        if let impl {
            SherpaOnnxDestroySpeakerEmbeddingExtractor(impl)
        }
    }

    var dim: Int {
        return Int(SherpaOnnxSpeakerEmbeddingExtractorDim(impl))
    }

    func createStream() -> SherpaOnnxOnlineStreamWrapper {
        let newStream = SherpaOnnxSpeakerEmbeddingExtractorCreateStream(impl)
        return SherpaOnnxOnlineStreamWrapper(impl: newStream)
    }

    func isReady(stream: SherpaOnnxOnlineStreamWrapper) -> Bool {
        return SherpaOnnxSpeakerEmbeddingExtractorIsReady(impl, stream.impl) == 1 ? true : false
    }

    func compute(stream: SherpaOnnxOnlineStreamWrapper) -> [Float] {
        if !isReady(stream: stream) {
            return []
        }

        let p = SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(impl, stream.impl)

        defer {
            SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(p)
        }

        return [Float](UnsafeBufferPointer(start: p, count: dim))
    }
}

func sherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig(model: String = "")
    -> SherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig
{
    return SherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig(model: toCPointer(model))
}

func sherpaOnnxOfflineSpeechDenoiserModelConfig(
    gtcrn: SherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig =
        sherpaOnnxOfflineSpeechDenoiserGtcrnModelConfig(),
    numThreads: Int = 1,
    provider: String = "cpu",
    debug: Int = 0
) -> SherpaOnnxOfflineSpeechDenoiserModelConfig {
    return SherpaOnnxOfflineSpeechDenoiserModelConfig(
        gtcrn: gtcrn,
        num_threads: Int32(numThreads),
        debug: Int32(debug),
        provider: toCPointer(provider)
    )
}

func sherpaOnnxOfflineSpeechDenoiserConfig(
    model: SherpaOnnxOfflineSpeechDenoiserModelConfig =
        sherpaOnnxOfflineSpeechDenoiserModelConfig()
) -> SherpaOnnxOfflineSpeechDenoiserConfig {
    return SherpaOnnxOfflineSpeechDenoiserConfig(
        model: model)
}

class SherpaOnnxDenoisedAudioWrapper {
    /// A pointer to the underlying counterpart in C
    let audio: UnsafePointer<SherpaOnnxDenoisedAudio>!

    init(audio: UnsafePointer<SherpaOnnxDenoisedAudio>!) {
        self.audio = audio
    }

    deinit {
        if let audio {
            SherpaOnnxDestroyDenoisedAudio(audio)
        }
    }

    var n: Int32 {
        return audio.pointee.n
    }

    var sampleRate: Int32 {
        return audio.pointee.sample_rate
    }

    var samples: [Float] {
        if let p = audio.pointee.samples {
            var samples: [Float] = []
            for index in 0 ..< n {
                samples.append(p[Int(index)])
            }
            return samples
        } else {
            let samples: [Float] = []
            return samples
        }
    }

    func save(filename: String) -> Int32 {
        return SherpaOnnxWriteWave(audio.pointee.samples, n, sampleRate, toCPointer(filename))
    }
}

class SherpaOnnxOfflineSpeechDenoiserWrapper {
    /// A pointer to the underlying counterpart in C
    let impl: OpaquePointer!

    /// Constructor taking a model config
    init(
        config: UnsafePointer<SherpaOnnxOfflineSpeechDenoiserConfig>!
    ) {
        impl = SherpaOnnxCreateOfflineSpeechDenoiser(config)
    }

    deinit {
        if let impl {
            SherpaOnnxDestroyOfflineSpeechDenoiser(impl)
        }
    }

    func run(samples: [Float], sampleRate: Int) -> SherpaOnnxDenoisedAudioWrapper {
        let audio: UnsafePointer<SherpaOnnxDenoisedAudio>? = SherpaOnnxOfflineSpeechDenoiserRun(
            impl, samples, Int32(samples.count), Int32(sampleRate)
        )

        return SherpaOnnxDenoisedAudioWrapper(audio: audio)
    }

    var sampleRate: Int {
        return Int(SherpaOnnxOfflineSpeechDenoiserGetSampleRate(impl))
    }
}

func getSherpaOnnxVersion() -> String {
    return String(cString: SherpaOnnxGetVersionStr())
}

func getSherpaOnnxGitSha1() -> String {
    return String(cString: SherpaOnnxGetGitSha1())
}

func getSherpaOnnxGitDate() -> String {
    return String(cString: SherpaOnnxGetGitDate())
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions