Ariver
2026-06-03 ce96a0e5272d6bcdf732dd0fc1ff8a9d9c834a0c
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
package engine
 
import (
    "fmt"
    "voicesnap/internal/config"
    "voicesnap/internal/language"
    "voicesnap/internal/logger"
    "voicesnap/internal/model"
    "voicesnap/internal/modelselection"
    "voicesnap/internal/paths"
)
 
// Engine is the interface for ASR engines.
type Engine interface {
    // Recognize takes float32 PCM samples (16kHz mono) and returns the recognized text.
    Recognize(samples []float32) (string, error)
    // HardwareInfo returns a human-readable description of the hardware backend being used.
    HardwareInfo() string
    // Close releases engine resources.
    Close()
}
 
// ModelDir returns the path to the sensevoice model directory.
func ModelDir() string {
    resolved, err := resolveCurrentModel()
    if err == nil && resolved.IsUsable() {
        return resolved.RootDir
    }
    return paths.ModelDir()
}
 
// ModelPath returns the path to the ONNX model file (prefers int8).
func ModelPath() string {
    resolved, err := resolveCurrentModel()
    if err == nil && resolved.IsUsable() {
        return resolved.Files["model"]
    }
    return ""
}
 
// TokensPath returns the path to the tokens.txt file.
func TokensPath() string {
    resolved, err := resolveCurrentModel()
    if err == nil && resolved.IsUsable() {
        return resolved.Files["tokens"]
    }
    return ""
}
 
// ModelExists checks if both the model and tokens files exist.
func ModelExists() bool {
    resolved, err := resolveCurrentModel()
    if err != nil {
        return false
    }
    return resolved.IsUsable()
}
 
// New creates a new platform-specific ASR engine.
// Returns an error if the model files are not found.
func New() (Engine, error) {
    cfg, err := config.Load()
    if err != nil {
        logger.Error("Failed to load config for model selection: %v", err)
        cfg = config.Default()
    }
    current := modelselection.Resolve(cfg, language.NewSystemDetector())
    return NewWithModelID(current.ModelID)
}
 
func NewWithModelID(modelID string) (Engine, error) {
    resolved, err := model.ResolveModel(model.NormalizeModelID(modelID))
    if err != nil {
        return nil, err
    }
    return NewWithResolvedModel(resolved)
}
 
func NewWithResolvedModel(resolved model.ResolvedModel) (Engine, error) {
    if !resolved.IsUsable() {
        return nil, fmt.Errorf("model %s is not usable: %s missing=%v problems=%v", resolved.ModelID, resolved.Status, resolved.Missing, resolved.Problems)
    }
    if !isSupportedBackend(resolved.BackendKind) {
        return nil, fmt.Errorf("unsupported backend kind: %s", resolved.BackendKind)
    }
 
    logger.Info("Loading model %s backend=%s from %s", resolved.ModelID, resolved.BackendKind, resolved.RootDir)
    return newPlatformEngine(resolved)
}
 
func isSupportedBackend(backend string) bool {
    switch backend {
    case model.BackendSenseVoice, model.BackendMoonshine, model.BackendNemoTransducer:
        return true
    default:
        return false
    }
}
 
func resolveCurrentModel() (model.ResolvedModel, error) {
    cfg, err := config.Load()
    if err != nil {
        cfg = config.Default()
    }
    current := modelselection.Resolve(cfg, language.NewSystemDetector())
    return model.ResolveModel(current.ModelID)
}