summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-09 14:39:40 +0300
committerPaul Buetow <paul@buetow.org>2026-04-09 14:39:40 +0300
commit4b4b3665230faafbd994f09894cb07c8f9d4b359 (patch)
tree4d2737404feb4494cf9ae66ae7539e43a2240f12
parent2e72f367b17c27c93672ec62c9806affbe3c4a55 (diff)
Release v0.28.1: convert Gemini TTS mono audio to stereov0.28.1
Duplicate each 16-bit PCM sample into both L/R channels so all audio output uses both speakers, and retroactively converted the 88 existing card MP3s to stereo via ffmpeg. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/audio/gemini_provider.go21
-rw-r--r--internal/audio/gemini_provider_test.go3
-rw-r--r--internal/version.go2
3 files changed, 23 insertions, 3 deletions
diff --git a/internal/audio/gemini_provider.go b/internal/audio/gemini_provider.go
index 74478bd..3b2872a 100644
--- a/internal/audio/gemini_provider.go
+++ b/internal/audio/gemini_provider.go
@@ -20,7 +20,8 @@ import (
const (
defaultGeminiTTSModel = "gemini-2.5-flash-preview-tts"
geminiTTSLanguageCode = "bg"
- geminiTTSChannels = 1
+ // Gemini returns mono PCM; we upsample to stereo so both channels carry audio.
+ geminiTTSChannels = 2
geminiTTSSampleRate = 24000
geminiTTSBitsPerSample = 16
)
@@ -230,7 +231,25 @@ func ensureOutputDirectory(outputFile string) error {
return nil
}
+// monoToStereo duplicates each 16-bit mono sample into both left and right
+// channels, producing interleaved stereo PCM. The output is twice as large.
+func monoToStereo(mono []byte) []byte {
+ bytesPerSample := geminiTTSBitsPerSample / 8
+ stereo := make([]byte, len(mono)*2)
+ for i := 0; i+bytesPerSample <= len(mono); i += bytesPerSample {
+ sample := mono[i : i+bytesPerSample]
+ // Write sample for left channel, then right channel.
+ dst := i * 2
+ copy(stereo[dst:], sample)
+ copy(stereo[dst+bytesPerSample:], sample)
+ }
+ return stereo
+}
+
func encodePCMAsWAV(pcmData []byte) ([]byte, error) {
+ // Gemini returns mono audio; convert to stereo so both speakers carry audio.
+ pcmData = monoToStereo(pcmData)
+
var buffer bytes.Buffer
if _, err := buffer.WriteString("RIFF"); err != nil {
diff --git a/internal/audio/gemini_provider_test.go b/internal/audio/gemini_provider_test.go
index 64b43b1..21b8a37 100644
--- a/internal/audio/gemini_provider_test.go
+++ b/internal/audio/gemini_provider_test.go
@@ -212,7 +212,8 @@ func TestWriteGeminiAudioFileWritesWAV(t *testing.T) {
t.Fatalf("output file does not look like WAV data: %q", fileData[:4])
}
- if got, want := len(fileData), 44+len(pcmData); got != want {
+ // Mono PCM is duplicated to stereo (both channels), so the data section doubles.
+ if got, want := len(fileData), 44+len(pcmData)*2; got != want {
t.Fatalf("len(output) = %d, want %d", got, want)
}
}
diff --git a/internal/version.go b/internal/version.go
index e6b624a..7f9bfc6 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -1,3 +1,3 @@
package internal
-const Version = "0.28.0"
+const Version = "0.28.1"