After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.

This commit is contained in:
Storm Dragon committed 2026-10-27 20:30:55 -04:00
1 parent c018f5fd29
commit 0e50de4c76
1047 files changed
+58364 -63747

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"errors"
"fmt"
"log/slog"
"time"
"git.stormux.org/storm/fenrir/internal/config"
"git.stormux.org/storm/fenrir/internal/speech"
)
type speechChangeResult struct {
err error
done func(error)
}
func same_speech_selection(a, b config.Speech) bool {
return a.Driver == b.Driver && a.Module == b.Module && a.Language == b.Language && a.Voice == b.Voice
}
func copy_speech_selection(target *config.Speech, source config.Speech) {
target.Module, target.Language, target.Voice = source.Module, source.Language, source.Voice
}
func prepare_speech(selected config.Speech) (speech.Transport, error) {
return speech.Prepare(selected.Driver, speech_options(selected))
}
func speech_options(selected config.Speech) speech.Options {
return speech.Options{Rate: selected.Rate, Pitch: &selected.Pitch, Volume: &selected.Volume, Module: selected.Module, Language: selected.Language, Voice: selected.Voice, Hardware: speech.HardwareOptions{Device: selected.HardwareDevice, BaudRate: selected.HardwareBaudRate}}
}
func (a *application) replace_speech(prepared speech.Transport) error {
if service, ok := a.voice.Speaker.(interface{ Replace(speech.Transport) error }); ok {
return service.Replace(prepared)
}
prepared.Close()
return fmt.Errorf("speech service does not support live selection")
}
// Only the result callback mutates reader state. Preparing a silent candidate
// and committing it through the speech worker never blocks the input loop.
func (a *application) change_speech(selected config.Speech, done func(error)) {
if a.speechPending || a.saver.busy {
done(fmt.Errorf("settings are busy"))
return
}
if !a.settings.speechAvailable {
done(fmt.Errorf("speech was disabled at startup"))
return
}
a.speechPending = true
go func() {
prepared, err := a.prepareSpeech(selected)
if err == nil {
err = a.replace_speech(prepared)
}
select {
case a.speechResults <- speechChangeResult{err: err, done: done}:
case <-a.ctx.Done():
}
}()
}
func speech_choice(source string) bool {
return source == "speech-module" || source == "speech-language" || source == "speech-voice"
}
func (a *application) speech_result(result speechChangeResult) {
a.speechPending = false
result.done(result.err)
// Reapply numeric overrides made while the silent candidate was prepared.
a.voice.Configure(speech_parameters(a.settings.live))
}
type savedSpeechError struct{ err error }
func (e *savedSpeechError) Error() string {
return "settings saved; speech selection was not applied: " + e.err.Error()
}
func (e *savedSpeechError) Unwrap() error { return e.err }
func settings_committed(err error) bool {
var saved *savedSpeechError
return err == nil || errors.As(err, &saved)
}
func open_speech(selected config.Speech) (speech.Transport, config.Speech, error) {
options := speech_options(selected)
if selected.Module == "" && selected.Language == "" && selected.Voice == "" {
client, err := speech.Open_driver(selected.Driver, options)
return client, selected, err
}
client, err := prepare_speech(selected)
if err == nil {
return client, selected, nil
}
slog.Warn("saved speech selection unavailable; using server default", "error", err, "at", time.Now())
selected.Module, selected.Language, selected.Voice = "", "", ""
options.Module, options.Language, options.Voice = "", "", ""
client, err = speech.Open_driver(selected.Driver, options)
return client, selected, err
}