summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-01 20:20:04 +0300
committerPaul Buetow <paul@buetow.org>2026-04-01 20:20:04 +0300
commit4f1d809b83c373aa27cd453bd7b3e04ab9719878 (patch)
treeda49fd3c9a73079eaf41d8fcefee24d16b85e8dc
parent4727a188102d8166e4acabc4fd62863245e6cdce (diff)
zg: extract shared audio voice lists
-rw-r--r--internal/audio/provider.go4
-rw-r--r--internal/audio/voices.go46
-rw-r--r--internal/audio/voices_test.go36
-rw-r--r--internal/gui/generator.go14
-rw-r--r--internal/processor/processor.go10
5 files changed, 91 insertions, 19 deletions
diff --git a/internal/audio/provider.go b/internal/audio/provider.go
index b961482..334e709 100644
--- a/internal/audio/provider.go
+++ b/internal/audio/provider.go
@@ -26,14 +26,14 @@ type Config struct {
// OpenAI-specific settings
OpenAIKey string
OpenAIModel string // "tts-1", "tts-1-hd", or "gpt-4o-mini-tts"
- OpenAIVoice string // "alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"
+ OpenAIVoice string // One of OpenAIVoices.
OpenAISpeed float64 // 0.25 to 4.0
OpenAIInstruction string // Voice instructions for gpt-4o-mini-tts model
// Gemini-specific settings
GoogleAPIKey string
GeminiTTSModel string // "gemini-2.5-flash-preview-tts"
- GeminiVoice string // Prebuilt Gemini TTS voice name, or empty for the model default
+ GeminiVoice string // One of GeminiVoices, or empty for the model default.
GeminiSpeed float64 // Prompt hint for desired speech speed
}
diff --git a/internal/audio/voices.go b/internal/audio/voices.go
new file mode 100644
index 0000000..9a96c76
--- /dev/null
+++ b/internal/audio/voices.go
@@ -0,0 +1,46 @@
+package audio
+
+// OpenAIVoices lists the OpenAI voices supported by the app.
+var OpenAIVoices = []string{
+ "alloy",
+ "ash",
+ "ballad",
+ "coral",
+ "echo",
+ "fable",
+ "onyx",
+ "nova",
+ "sage",
+ "shimmer",
+ "verse",
+}
+
+// GeminiVoices lists the Gemini prebuilt voices supported by the app.
+var GeminiVoices = []string{
+ "Zephyr",
+ "Puck",
+ "Charon",
+ "Kore",
+ "Fenrir",
+ "Leda",
+ "Orus",
+ "Aoede",
+ "Callirrhoe",
+ "Autonoe",
+ "Enceladus",
+ "Iapetus",
+ "Umbriel",
+ "Algieba",
+ "Despina",
+ "Erinome",
+ "Gacrux",
+ "Pulcherrima",
+ "Achernar",
+ "Rasalgethi",
+ "Laomedeia",
+ "Sadachbia",
+ "Schedar",
+ "Sulafat",
+ "Vindemiatrix",
+ "Zubenelgenubi",
+}
diff --git a/internal/audio/voices_test.go b/internal/audio/voices_test.go
new file mode 100644
index 0000000..121f328
--- /dev/null
+++ b/internal/audio/voices_test.go
@@ -0,0 +1,36 @@
+package audio
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestVoiceLists(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ got []string
+ want []string
+ }{
+ {
+ name: "openai",
+ got: OpenAIVoices,
+ want: []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"},
+ },
+ {
+ name: "gemini",
+ got: GeminiVoices,
+ want: []string{"Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus", "Umbriel", "Algieba", "Despina", "Erinome", "Gacrux", "Pulcherrima", "Achernar", "Rasalgethi", "Laomedeia", "Sadachbia", "Schedar", "Sulafat", "Vindemiatrix", "Zubenelgenubi"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+ if !reflect.DeepEqual(tt.got, tt.want) {
+ t.Fatalf("%s voice list mismatch\nwant: %#v\ngot: %#v", tt.name, tt.want, tt.got)
+ }
+ })
+ }
+}
diff --git a/internal/gui/generator.go b/internal/gui/generator.go
index 7964e72..3491db2 100644
--- a/internal/gui/generator.go
+++ b/internal/gui/generator.go
@@ -63,11 +63,8 @@ func (a *Application) generateAudio(ctx context.Context, word string, cardDir st
}
}
- // Always use random voice and speed
- allVoices := []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"}
-
// Select a random voice
- voice, speed := randomVoiceAndSpeed(allVoices)
+ voice, speed := randomVoiceAndSpeed(audio.OpenAIVoices)
// Create a copy of audio config with selected voice and speed
audioConfig := *a.audioConfig
@@ -127,8 +124,7 @@ func (a *Application) generateAudioFront(ctx context.Context, word string, cardD
return "", fmt.Errorf("card directory not provided")
}
- allVoices := []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"}
- voice, speed := randomVoiceAndSpeed(allVoices)
+ voice, speed := randomVoiceAndSpeed(audio.OpenAIVoices)
audioConfig := *a.audioConfig
audioConfig.OpenAIVoice = voice
@@ -169,8 +165,7 @@ func (a *Application) generateAudioBack(ctx context.Context, text string, cardDi
return "", fmt.Errorf("card directory not provided")
}
- allVoices := []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"}
- voice, speed := randomVoiceAndSpeed(allVoices)
+ voice, speed := randomVoiceAndSpeed(audio.OpenAIVoices)
audioConfig := *a.audioConfig
audioConfig.OpenAIVoice = voice
@@ -201,8 +196,7 @@ func (a *Application) generateAudioBgBg(ctx context.Context, front, back, cardDi
return "", "", fmt.Errorf("card directory not provided")
}
- allVoices := []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"}
- voice, speed := randomVoiceAndSpeed(allVoices)
+ voice, speed := randomVoiceAndSpeed(audio.OpenAIVoices)
audioConfig := *a.audioConfig
audioConfig.OpenAIVoice = voice
diff --git a/internal/processor/processor.go b/internal/processor/processor.go
index d7025a6..d2a380b 100644
--- a/internal/processor/processor.go
+++ b/internal/processor/processor.go
@@ -241,19 +241,17 @@ func (p *Processor) ProcessWordWithTranslationAndType(word, providedTranslation
// generateAudio generates audio files for a word
func (p *Processor) generateAudio(word string) error {
- allVoicesList := []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"}
-
// Get list of voices to use
var voices []string
if p.flags.AllVoices {
- voices = allVoicesList
+ voices = audio.OpenAIVoices
} else if p.flags.OpenAIVoice != "" {
// Use explicitly specified voice
voices = []string{p.flags.OpenAIVoice}
fmt.Printf(" Using specified voice: %s\n", p.flags.OpenAIVoice)
} else {
// Select a random voice
- randomVoice := allVoicesList[rand.Intn(len(allVoicesList))]
+ randomVoice := audio.OpenAIVoices[rand.Intn(len(audio.OpenAIVoices))]
voices = []string{randomVoice}
fmt.Printf(" Using random voice: %s\n", randomVoice)
}
@@ -273,10 +271,8 @@ func (p *Processor) generateAudio(word string) error {
// generateAudioBgBg generates audio files for both sides of a bg-bg card
func (p *Processor) generateAudioBgBg(front, back string) error {
- allVoicesList := []string{"alloy", "ash", "ballad", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer", "verse"}
-
// Select a random voice (same voice for both sides for consistency)
- voice := allVoicesList[rand.Intn(len(allVoicesList))]
+ voice := audio.OpenAIVoices[rand.Intn(len(audio.OpenAIVoices))]
if p.flags.OpenAIVoice != "" {
voice = p.flags.OpenAIVoice
}