Documentation
¶
Overview ¶
Package talkie is a voice front end for agents: wake word, voice activity detection, speech-to-text and text-to-speech with barge-in, on ONNX Runtime models (go-phonmatchnet, go-silero-vad, go-parakeet and go-kokoro) running in-process.
New loads the models; Run talks with the user through the audio devices, handing what they say to an Agent and speaking its Reply as it is written. Train measures a room once so Run can start tuned to it, and Config.OnEvent reports what the conversation is doing.
Example ¶
The README's quick start: an agent that repeats what it heard.
package main
import (
"context"
"log"
talkie "github.com/androiddrew/talkie-core"
)
func main() {
ctx := context.Background()
agent := talkie.AgentFunc(func(ctx context.Context, turn talkie.Turn, reply *talkie.Reply) error {
// Write the answer as it is generated; talkie speaks it a sentence at a time.
_, err := reply.WriteString("You said: " + turn.Text)
return err // ctx is cancelled if the user interrupts
})
t, err := talkie.New(ctx, talkie.Config{
ORTLibrary: "/opt/onnxruntime/lib/libonnxruntime.so",
CUDA: true,
Parakeet: talkie.ParakeetConfig{Dir: "/models/parakeet"},
Kokoro: talkie.KokoroConfig{
ModelPath: "/models/kokoro/kokoro-v1.0.onnx",
VoicesPath: "/models/kokoro/voices-v1.0.bin",
},
WakeWord: "hey computer", // also interrupts the reply
OnEvent: func(ev talkie.Event) {
if ev.Type == talkie.EventState {
log.Println(ev.State) // drive a status light
}
},
}, agent)
if err != nil {
log.Fatal(err)
}
defer t.Close()
if err := t.Run(ctx); err != nil { // the default microphone and speaker, until ctx is done
log.Fatal(err)
}
}
Output:
Index ¶
- Variables
- func DefaultProfileDir() (string, error)
- func ProfilePath(dir, input, output string) string
- func SaveProfile(path string, p Profile) error
- type Agent
- type AgentFunc
- type BargeIn
- type CancellerProfile
- type Config
- type Cues
- type EchoProfile
- type Event
- type EventType
- type InterruptedBy
- type Interruption
- type KokoroConfig
- type LatencyProfile
- type LatencySource
- type ParakeetConfig
- type Profile
- type Prompt
- type Reply
- type Sound
- type State
- type Step
- type Talkie
- type Then
- type Trigger
- type Turn
- type WakeProfile
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoProfile = errors.New("talkie: no room profile")
ErrNoProfile is returned by LoadProfile when there is no profile file.
Functions ¶
func DefaultProfileDir ¶
DefaultProfileDir is where Run looks for profiles unless Config.RoomProfileDir says otherwise: talkie/rooms in the user's configuration directory, such as ~/.config/talkie/rooms.
func ProfilePath ¶
ProfilePath is the file in dir for a microphone and speaker: a readable slug of their names plus a short hash, so similar names don't collide.
func SaveProfile ¶
SaveProfile writes p to path, creating its directory. It replaces any file there in one step, so a reader never sees half a profile.
Types ¶
type Agent ¶
Agent answers what the user says. talkie calls Respond once per transcript, on its own goroutine, and speaks what the agent writes to the reply as it is written.
ctx is cancelled when the user interrupts the reply (barge-in) or the session ends: stop generating and return. The next Turn's Interrupted then says how much of this reply the user heard.
type BargeIn ¶
type BargeIn int
BargeIn is how the user can interrupt the agent by voice, while it talks or thinks.
const ( // BargeInWakeWord, the default, interrupts only on the wake word: the // agent's own echo can't interrupt it, and the user needs the wake word // to. Without Config.WakeWord, nothing interrupts by voice. BargeInWakeWord BargeIn = iota // BargeInVoice also interrupts when the user talks over the agent. A // loud speaker can make the agent interrupt itself; room training says // when to turn it down. BargeInVoice // BargeInOff never interrupts by voice: replies play to the end unless // the application calls Talkie.Interrupt, as from a key or a button. BargeInOff )
type CancellerProfile ¶
type CancellerProfile struct {
// Snapshot is the canceller's state (go-speexdsp's echo.Canceller
// Snapshot); it restores only into talkie's canceller configuration.
Snapshot []byte
// ERLEDB is the echo it removed at the end of training, in dB.
ERLEDB float64
}
CancellerProfile is a trained echo canceller. Run restores it so the first reply starts with the echo path already learned.
type Config ¶
type Config struct {
// ORTLibrary is the ONNX Runtime library, a path or OS-loader name,
// shared by every model. A CUDA build is needed for CUDA.
ORTLibrary string
// CUDA runs speech-to-text and text-to-speech on GPU DeviceID. The wake
// word and VAD always run on the CPU.
CUDA bool
DeviceID int
Parakeet ParakeetConfig
Kokoro KokoroConfig
// WakeWord is the phrase that starts a session, such as "hey computer".
// Empty disables it: sessions start with Listen or when the agent speaks.
WakeWord string
// WakeFAR selects PhonMatchNet's operating point by its target
// false-accept rate; zero selects 0.01.
WakeFAR float64
// OutputRate is the speaker's sample rate; zero selects 48 kHz.
OutputRate int
// InputDevice and OutputDevice select the microphone and speaker by
// name, ignoring case: a device named exactly that, or else the first
// whose name contains it. PulseAudio's and PipeWire's monitor sources
// ("Monitor of ...") are skipped unless InputDevice says "monitor", so a
// USB headset's name selects its microphone. Empty selects the system
// defaults. OutputRate is the rate both run at.
InputDevice, OutputDevice string
// AudioLatency is the audio devices' round trip, from writing a sample
// to the speaker to hearing it at the microphone. Barge-in waits it out
// before judging a voice against the agent's echo, and the follow-up
// window ignores the agent's last words for that long. Zero assumes
// 100 ms; PipeWire and Bluetooth devices are often slower.
AudioLatency time.Duration
// RoomProfileDir is where Run looks for the room profile of its
// devices; empty selects DefaultProfileDir. IgnoreRoomProfile makes Run
// use only this Config.
RoomProfileDir string
IgnoreRoomProfile bool
// Logger, if set, receives the conversation's events at Info level and
// their details at Debug.
Logger *slog.Logger
// OnEvent, if set, receives the conversation's events as they happen:
// state changes, the wake word, transcripts, replies, interruptions,
// latency measurements and errors. It is called on Run's goroutine,
// which also runs the audio: return quickly, and hand anything slow to
// another goroutine.
OnEvent func(Event)
// DisableEchoCancellation turns off the built-in echo canceller
// (go-speexdsp), for a headset or a microphone that is already
// echo-cancelled, such as PipeWire's echo-cancel source. Barge-in still
// verifies interruptions against the predicted echo.
DisableEchoCancellation bool
// BargeIn sets how the user can interrupt the agent by voice; the zero
// value, BargeInWakeWord, allows only the wake word, so the agent's own
// echo can't interrupt it. Talkie.Interrupt works whatever it is set to.
BargeIn BargeIn
// Cues are sounds that say when talkie starts listening, while it
// thinks, and when the session ends, for a device without a screen; the
// zero value plays none. DefaultCues has built-in ones.
Cues Cues
// DisableReadyChime turns off the short chime Run plays when it starts.
// Run measures each session's audio latency from the chime's echo,
// since it can differ between device opens; without it, the latency
// comes from the room profile until the first reply measures it.
DisableReadyChime bool
}
Config selects the models. Every model runs in-process on one ONNX Runtime environment.
type Cues ¶ added in v0.2.0
type Cues struct {
// Listening plays when talkie starts listening at the user's request:
// the wake word, Talkie.Listen opening a session, or Talkie.Interrupt.
// Follow-up windows after a reply are silent.
Listening *Sound
// Thinking loops while the user waits: from ThinkingDelay after they
// stop talking, through transcription and the agent's work, until the
// reply starts playing, the user interrupts, or the session ends. It
// should loop seamlessly. ThinkingDelay is zero for 500 ms, so a quick
// reply doesn't blip.
Thinking *Sound
ThinkingDelay time.Duration
// Idle plays when the session ends and talkie waits for the wake word.
Idle *Sound
}
Cues are short sounds that say what talkie is doing, for a device without a screen. Each is optional: a nil Sound plays nothing. talkie plays them through its own output, so the echo canceller removes them from the microphone like the agent's voice; they don't count as the agent speaking.
func DefaultCues ¶ added in v0.2.0
func DefaultCues() Cues
DefaultCues returns talkie's built-in cues, at 48 kHz: two glassy notes a fifth up for Listening, the same down and a little quieter for Idle, and for Thinking a quiet fifth that swells and fades every 1.6 s.
type EchoProfile ¶
type EchoProfile struct {
// Heard is false when the microphone didn't hear the speaker, as with a
// headset; echo cancellation can then be turned off.
Heard bool
// GainDB is the echo's level relative to what the speaker was given,
// direct path and reverb. At or below -3 dB barge-in works; above 0 dB
// the echo is louder than the voice that made it, and the user can't be
// heard over it.
GainDB float64
// Clipped reports that the microphone reached full scale during a quiet
// chirp: its gain is far too high.
Clipped bool
// Advice says what to change in the room, or is empty.
Advice string
}
EchoProfile is how loud the speaker's sound is when it reaches the microphone.
type Event ¶
type Event struct {
Type EventType
// Time is when it happened; At is the same moment in the audio stream,
// since Run started.
Time time.Time
At time.Duration
// Session is the session it belongs to, empty outside one.
Session string
// State and From are the new and previous state, for EventState.
State, From State
// Trigger is what opened the session, listening window, utterance or
// transcript, for EventSessionStarted, EventListening,
// EventSpeechStarted and EventTranscript.
Trigger Trigger
// Reason says why, for EventSessionEnded, EventSpeechDiscarded and
// EventReplyStopped.
Reason string
// Timeout is how long the listening window stays open, for
// EventListening.
Timeout time.Duration
// Text is what the user said, without the wake phrase, for
// EventTranscript; STT is how long transcribing it took, and Speech how
// long the user spoke.
Text string
STT, Speech time.Duration
// Reply identifies the agent's reply, for the EventReply* events and
// EventInterrupted; Heard, Cut, Unspoken and Played are how far it
// played, as in Interruption.
Reply string
Heard, Cut, Unspoken string
Played time.Duration
// InterruptedBy and While are what interrupted the agent and what it
// was doing, StateSpeaking or StateThinking, for EventInterrupted.
InterruptedBy InterruptedBy
While State
// Latency is the speaker-to-microphone round trip, for EventLatency
// and EventRoomChanged; LatencySource says how it was measured, and
// EchoGainDB is the echo's level.
Latency time.Duration
LatencySource LatencySource
EchoGainDB float64
// Err is what failed, for EventError.
Err error
}
Event is something that happened in the conversation, for an application to show or measure: Config.OnEvent receives them. Type says which fields are set.
type EventType ¶
type EventType string
EventType names an event.
const ( EventSessionStarted EventType = "session_started" // Trigger EventSessionEnded EventType = "session_ended" // Reason: "timeout", "agent_timeout", "client", "agent" EventState EventType = "state" // State, From EventWake EventType = "wake" // the wake word was heard EventListening EventType = "listening" // Trigger, Timeout EventSpeechStarted EventType = "speech_started" // Trigger EventSpeechDiscarded EventType = "speech_discarded" // Reason: "too_short", "no_text", "stt_error", "session_ended" EventTranscript EventType = "transcript" // Text, Trigger, STT, Speech EventReplyStarted EventType = "reply_started" // Reply EventReplyFinished EventType = "reply_finished" // Reply, Heard, Played EventReplyStopped EventType = "reply_stopped" // Reply, Reason, Heard, Cut, Unspoken, Played EventInterrupted EventType = "interrupted" // InterruptedBy, While, and the reply's progress EventLatency EventType = "latency" // Latency, LatencySource, EchoGainDB EventRoomChanged EventType = "room_changed" // Latency: the room profile no longer fits EventError EventType = "error" // Err )
type InterruptedBy ¶
type InterruptedBy string
InterruptedBy is what interrupted the agent.
const ( InterruptedBySpeech InterruptedBy = "speech" // the user talked over it InterruptedByWake InterruptedBy = "wake_word" // the user said the wake word InterruptedByApp InterruptedBy = "app" // Talkie.Interrupt, as from a key )
type Interruption ¶
type Interruption struct {
Heard string // sentences that played completely
Cut string // the sentence playing when the user interrupted
Unspoken string // what the agent wrote that never played
// Played is how long the reply played; zero when the user spoke while
// the agent was still thinking.
Played time.Duration
}
Interruption is how much of a reply the user heard before cutting it off. The three texts are the agent's own words, split at sentence boundaries.
type KokoroConfig ¶
type KokoroConfig struct {
ModelPath string // kokoro-v1.0.onnx
VoicesPath string // voices-v1.0.bin
// Voice is a US (a*) or UK (b*) English voice; empty selects af_heart.
Voice string
// Speed is 0.5 to 2; zero selects 1.
Speed float32
// FirstChunkPhonemes and MaxChunkPhonemes limit each model run. A run in
// progress can't be interrupted, so shorter runs make barge-in quicker
// and start the first sentence sooner. Zero selects 80 and 160.
FirstChunkPhonemes, MaxChunkPhonemes int
}
KokoroConfig locates the Kokoro v1.0 files and sets the voice.
type LatencyProfile ¶
LatencyProfile is the round trip measured on several opens of the devices, which can differ by tens of milliseconds between opens.
func (LatencyProfile) Assumed ¶
func (l LatencyProfile) Assumed() time.Duration
Assumed is the latency Run uses: the middle of the measured range, which keeps barge-in working across it. Timing barge-in for the longest or the shortest run fails at the other end of the range.
type LatencySource ¶
type LatencySource string
LatencySource is how a latency was measured.
const ( LatencyFromChime LatencySource = "chime" // the ready chime, before the first reply LatencyFromReply LatencySource = "reply" // the echo of a reply )
type ParakeetConfig ¶
type ParakeetConfig struct {
// Dir holds nemo128.onnx, encoder-model.onnx (with its .onnx.data) and
// decoder_joint-model.onnx.
Dir string
// INT8 uses encoder-model.int8.onnx and decoder_joint-model.int8.onnx,
// smaller and faster on CPU.
INT8 bool
}
ParakeetConfig locates the Parakeet TDT 0.6B v3 ONNX files.
type Profile ¶
type Profile struct {
Trained time.Time
// Input and Output are the full names of the microphone and speaker, as
// the system reports them; Rate is the rate they were trained at.
Input, Output string
Rate int
// Latency is the speaker-to-microphone round trip.
Latency LatencyProfile
// Echo is how loud the speaker is at the microphone.
Echo EchoProfile
// NoiseDBFS is the room's background level at the microphone, in dB
// relative to full scale.
NoiseDBFS float64
// Canceller is the echo canceller as it learned the room, or nil when
// training didn't play speech.
Canceller *CancellerProfile
// Wake is the wake word tuned to the user, or nil when training had no
// wake word.
Wake *WakeProfile
}
Profile is what room training learned about one microphone and speaker. Run loads the profile for its devices and tunes itself with it. Train creates one; SaveProfile and LoadProfile store it.
func LoadProfile ¶
LoadProfile reads the profile at path.
func Train ¶
Train measures the room through cfg's devices: the background noise, then the speaker-to-microphone delay and level from quiet chirps. When cfg.Kokoro names the Kokoro files (with cfg.ORTLibrary), it then speaks a short paragraph while the echo canceller learns the room, so Run can start with it learned. When cfg.WakeWord is set (with cfg.ORTLibrary), the user then says the wake word a few times and reads a sentence, to tune the wake word's threshold to their voice. Save the result with SaveProfile at ProfilePath(dir, p.Input, p.Output) for Run to find it.
type Prompt ¶
Prompt shows a step's instruction and returns when the user is ready, or with an error to stop training. A terminal application prints and waits for Enter; a graphical one shows a dialog.
type Reply ¶
type Reply struct {
// contains filtered or unexported fields
}
Reply is where an agent writes its answer. Text is spoken as it arrives, a sentence at a time. Write returns an error once the reply is interrupted.
type Sound ¶ added in v0.2.0
Sound is mono audio for a cue: Samples in [-1, 1] at Rate samples per second. talkie resamples it to Config.OutputRate.
type State ¶
type State string
State is where the conversation is.
const ( StateIdle State = "idle" // no session: only the wake word is listened for StateListening State = "listening" // waiting for the user to talk StateHearing State = "hearing" // the user is talking StateTranscribing State = "transcribing" // the user stopped; transcribing StateThinking State = "thinking" // the agent has the transcript and hasn't spoken yet StateSpeaking State = "speaking" // the agent's reply is playing )
type Step ¶
type Step struct {
Name string // "noise", "echo", "canceller", "wake", "speech"
Instruction string // what to tell the user
}
Step is one stage of room training, for the application to present.
type Talkie ¶
type Talkie struct {
// contains filtered or unexported fields
}
Talkie is a loaded voice front end for an agent.
func New ¶
New loads the models and returns a front end that hands what the user says to agent. Close is mandatory.
func (*Talkie) Close ¶
Close releases the models. The ONNX Runtime environment stays loaded until the process exits.
func (*Talkie) EndSession ¶
func (t *Talkie) EndSession()
EndSession ends the conversation in progress, if any.
func (*Talkie) Interrupt ¶
func (t *Talkie) Interrupt()
Interrupt stops the agent, whether it is talking or thinking, and listens to the user without the wake word, whatever Config.BargeIn is set to: for a key or a button. When the agent isn't talking or thinking it listens, as Listen does. It takes effect on Run's goroutine.
func (*Talkie) Listen ¶
func (t *Talkie) Listen()
Listen opens a listening window as push-to-talk does: the user can speak without the wake word. It takes effect on Run's goroutine.
func (*Talkie) Run ¶
Run talks with the user through the audio devices until ctx is done. It opens Config.InputDevice and Config.OutputDevice, or the system defaults, at Config.OutputRate. Unless Config.DisableReadyChime, it starts with a short chime, from whose echo it measures this session's audio latency.
type Then ¶
type Then string
Then is what the conversation does after a reply has played.
const ( // ThenListen opens a follow-up window: the user can answer without the // wake word. It is the default. ThenListen Then = "listen" // ThenEnd ends the session; the user needs the wake word again. ThenEnd Then = "end" // ThenWait waits for the agent to speak again, for example after "let // me check". ThenWait Then = "wait" )
type Trigger ¶
type Trigger string
Trigger is what opened an utterance.
const ( TriggerWake Trigger = "wake_word" // the user said the wake word TriggerFollowup Trigger = "followup" // the user answered after a reply TriggerCommand Trigger = "command" // push-to-talk TriggerBargeIn Trigger = "barge_in" // the user interrupted the agent TriggerContinuation Trigger = "continuation" // the user kept talking after a pause )
type Turn ¶
type Turn struct {
Session string // the conversation it belongs to
Reply string // the reply's ID, as in Event.Reply
Text string // what the user said, with any leading wake phrase removed
Trigger Trigger // what opened the utterance
// Interrupted is set when the user cut off the previous reply to say
// this. Agents that keep a history should replace the reply they wrote
// with what was heard.
Interrupted *Interruption
}
Turn is one thing the user said.
type WakeProfile ¶
type WakeProfile struct {
// Keyword is the wake word it was trained for; Run uses Threshold only
// for the same wake word.
Keyword string
// Threshold is the PhonMatchNet score at or above which the wake word
// is heard, between the user saying it and the user's ordinary speech;
// zero when training couldn't separate them, and PhonMatchNet's default
// operating point is kept.
Threshold float64
// Positives are the scores of the times the user said the wake word;
// NegativeMax is the highest score anywhere in their ordinary speech.
// The threshold sits below all but one in five of the positives.
Positives []float64
NegativeMax float64
// Advice says what to change, or is empty.
Advice string
}
WakeProfile is the wake word tuned to the user's voice in the room.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
talkie-demo
command
Command talkie-demo talks with you through your microphone and speaker using talkie's models and a toy agent that repeats what it heard.
|
Command talkie-demo talks with you through your microphone and speaker using talkie's models and a toy agent that repeats what it heard. |
|
internal
|
|
|
audio
Package audio holds talkie's audio plumbing: a ring buffer addressed by absolute sample position and the player that owns the speaker.
|
Package audio holds talkie's audio plumbing: a ring buffer addressed by absolute sample position and the player that owns the speaker. |
|
calibrate
Package calibrate measures a room from what talkie plays and what the microphone hears: the speaker-to-microphone delay and level from a chirp, and the noise floor from silence.
|
Package calibrate measures a room from what talkie plays and what the microphone hears: the speaker-to-microphone delay and level from a chirp, and the noise floor from silence. |
|
device
Package device opens a full-duplex audio device with miniaudio (malgo): one callback per period receives the microphone samples and fills the speaker samples, so what was played and what was heard stay aligned for echo cancellation.
|
Package device opens a full-duplex audio device with miniaudio (malgo): one callback per period receives the microphone samples and fills the speaker samples, so what was played and what was heard stay aligned for echo cancellation. |
|
engine
Package engine is talkie's conversation state machine, ported from wonky-talkie's engine.
|
Package engine is talkie's conversation state machine, ported from wonky-talkie's engine. |
|
sim
Package sim is talkie's test bench: real speech clips, the real Silero VAD, a scripted wake word and a simulated room whose speaker echoes into the microphone.
|
Package sim is talkie's test bench: real speech clips, the real Silero VAD, a scripted wake word and a simulated room whose speaker echoes into the microphone. |
|
wake
Package wake slides a fixed window over 16 kHz audio, scores it every hop and debounces accepted windows into wake-word detections.
|
Package wake slides a fixed window over 16 kHz audio, scores it every hop and debounces accepted windows into wake-word detections. |