Ariver
2026-06-03 d04184c47264bd740d85bc2100656ffdb0ff98e5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//go:build darwin
 
package engine
 
import (
    "fmt"
    "voicesnap/internal/logger"
    "voicesnap/internal/model"
 
    _ "github.com/k2-fsa/sherpa-onnx-go-macos"
    sherpa "github.com/k2-fsa/sherpa-onnx-go/sherpa_onnx"
)
 
type sherpaEngine struct {
    recognizer *sherpa.OfflineRecognizer
    hwInfo     string
}
 
func newPlatformEngine(resolved model.ResolvedModel) (Engine, error) {
    if !isSupportedBackend(resolved.BackendKind) {
        return nil, fmt.Errorf("unsupported backend kind: %s", resolved.BackendKind)
    }
 
    providers := darwinProviders(resolved.ProviderOrder)
 
    for _, p := range providers {
        config, err := offlineConfigForResolvedModel(resolved, p.provider)
        if err != nil {
            return nil, err
        }
 
        recognizer := sherpa.NewOfflineRecognizer(&config)
        if recognizer != nil {
            info := fmt.Sprintf("%s ยท %s", resolved.Profile.DisplayName, p.name)
            logger.Info("Engine initialized: %s", info)
            return &sherpaEngine{
                recognizer: recognizer,
                hwInfo:     info,
            }, nil
        }
        logger.Info("Failed to init with %s, trying next provider", p.name)
    }
 
    return nil, fmt.Errorf("failed to initialize sherpa-onnx with any provider")
}
 
func offlineConfigForResolvedModel(resolved model.ResolvedModel, provider string) (sherpa.OfflineRecognizerConfig, error) {
    config := sherpa.OfflineRecognizerConfig{}
    config.FeatConfig.SampleRate = 16000
    config.FeatConfig.FeatureDim = 80
    config.ModelConfig.Tokens = resolved.Files["tokens"]
    config.ModelConfig.NumThreads = resolved.Profile.NumThreads
    config.ModelConfig.Provider = provider
    config.DecodingMethod = "greedy_search"
 
    switch resolved.BackendKind {
    case model.BackendSenseVoice:
        config.ModelConfig.SenseVoice.Model = resolved.Files["model"]
        config.ModelConfig.SenseVoice.UseInverseTextNormalization = 1
    case model.BackendMoonshine:
        config.ModelConfig.Moonshine.Preprocessor = resolved.Files["preprocessor"]
        config.ModelConfig.Moonshine.Encoder = resolved.Files["encoder"]
        config.ModelConfig.Moonshine.UncachedDecoder = resolved.Files["uncached_decoder"]
        config.ModelConfig.Moonshine.CachedDecoder = resolved.Files["cached_decoder"]
    case model.BackendNemoTransducer:
        config.ModelConfig.Transducer.Encoder = resolved.Files["encoder"]
        config.ModelConfig.Transducer.Decoder = resolved.Files["decoder"]
        config.ModelConfig.Transducer.Joiner = resolved.Files["joiner"]
        config.ModelConfig.ModelType = model.BackendNemoTransducer
    default:
        return sherpa.OfflineRecognizerConfig{}, fmt.Errorf("unsupported backend kind: %s", resolved.BackendKind)
    }
 
    return config, nil
}
 
func darwinProviders(providerOrder []string) []struct {
    name     string
    provider string
} {
    names := map[string]string{
        "coreml": "CoreML (Apple Neural Engine)",
        "cpu":    "CPU",
    }
    providers := make([]struct {
        name     string
        provider string
    }, 0, len(providerOrder))
    for _, provider := range providerOrder {
        name, ok := names[provider]
        if !ok {
            continue
        }
        providers = append(providers, struct {
            name     string
            provider string
        }{name: name, provider: provider})
    }
    if len(providers) == 0 {
        providers = append(providers, struct {
            name     string
            provider string
        }{name: "CPU", provider: "cpu"})
    }
    return providers
}
 
func (e *sherpaEngine) Recognize(samples []float32) (string, error) {
    stream := sherpa.NewOfflineStream(e.recognizer)
    defer sherpa.DeleteOfflineStream(stream)
 
    stream.AcceptWaveform(16000, samples)
 
    e.recognizer.Decode(stream)
    result := stream.GetResult()
 
    return result.Text, nil
}
 
func (e *sherpaEngine) HardwareInfo() string {
    return e.hwInfo
}
 
func (e *sherpaEngine) Close() {
    if e.recognizer != nil {
        sherpa.DeleteOfflineRecognizer(e.recognizer)
        e.recognizer = nil
    }
}