package engine import ( "fmt" "voicesnap/internal/config" "voicesnap/internal/logger" "voicesnap/internal/model" "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) return NewWithModelID(model.DefaultModelID) } return NewWithModelID(cfg.SelectedModelID) } 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 resolved.BackendKind != model.BackendSenseVoice { return nil, fmt.Errorf("unsupported backend kind: %s", resolved.BackendKind) } logger.Info("Loading model %s from %s", resolved.ModelID, resolved.RootDir) return newPlatformEngine(resolved) } func resolveCurrentModel() (model.ResolvedModel, error) { cfg, err := config.Load() if err != nil { return model.ResolveModel(model.DefaultModelID) } return model.ResolveModel(model.NormalizeModelID(cfg.SelectedModelID)) }