agentvibes 2.14.0 → 2.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  description: ElevenLabs TTS voice management commands
3
3
  ---
4
4
 
5
- # 🎤 ElevenLabs Voice Management
5
+ # 🎤 AgentVibes Voice Management
6
6
 
7
- Manage your ElevenLabs text-to-speech voices with these commands:
7
+ Manage your text-to-speech voices across multiple providers (ElevenLabs, Piper, macOS Say).
8
8
 
9
9
  ## Available Commands
10
10
 
@@ -49,9 +49,33 @@ Set a prefix word/phrase for all TTS messages
49
49
 
50
50
  Saved locally in `.agentvibes/config/agentvibes.json`
51
51
 
52
- ## Getting Voice IDs
52
+ ## Provider Management
53
53
 
54
- To add your own custom voices:
54
+ ### `/agent-vibes:provider list`
55
+ Show all available TTS providers
56
+
57
+ ### `/agent-vibes:provider switch <name>`
58
+ Switch between providers:
59
+ - `/agent-vibes:provider switch piper` - Free, offline (Linux/WSL)
60
+ - `/agent-vibes:provider switch elevenlabs` - Premium AI voices
61
+ - `/agent-vibes:provider switch macos` - Native macOS (Mac only)
62
+
63
+ ### `/agent-vibes:provider info <name>`
64
+ Get details about a specific provider
65
+
66
+ ## Providers
67
+
68
+ | Provider | Platform | Cost | Quality |
69
+ |----------|----------|------|---------|
70
+ | **macOS Say** | macOS only | Free (built-in) | ⭐⭐⭐⭐ |
71
+ | **Piper** | Linux/WSL | Free | ⭐⭐⭐⭐ |
72
+ | **ElevenLabs** | All | Free tier + paid | ⭐⭐⭐⭐⭐ |
73
+
74
+ On macOS, the native `say` provider is automatically detected and recommended.
75
+
76
+ ## Getting Voice IDs (ElevenLabs)
77
+
78
+ To add your own custom ElevenLabs voices:
55
79
  1. Go to https://elevenlabs.io/app/voice-library
56
80
  2. Select or create a voice
57
81
  3. Copy the voice ID (15-30 character alphanumeric string)
@@ -59,10 +83,10 @@ To add your own custom voices:
59
83
 
60
84
  ## Default Voices
61
85
 
62
- The system comes with these Character Voices from ElevenLabs:
63
- - Northern Terry, Grandpa Spuds Oxley, Ms. Walker
64
- - Ralf Eisend, Amy, Michael, Jessica Anne Bogart
65
- - Aria, Lutz Laugh, Dr. Von Fusion, Matthew Schmitz
66
- - Demon Monster, Cowboy Bob, Drill Sergeant
86
+ **ElevenLabs:** Northern Terry, Grandpa Spuds Oxley, Ms. Walker, Ralf Eisend, Amy, Michael, Jessica Anne Bogart, Aria, Lutz Laugh, Dr. Von Fusion, Matthew Schmitz, Demon Monster, Cowboy Bob, Drill Sergeant
87
+
88
+ **Piper:** en_US-lessac-medium, en_US-amy-medium, en_GB-alan-medium, and many more
89
+
90
+ **macOS Say:** Samantha, Alex, Daniel, Victoria, Karen, Moira, and 100+ more built-in voices
67
91
 
68
92
  Enjoy your TTS experience! 🎵
@@ -5,7 +5,7 @@ argument-hint: [command] [args...]
5
5
 
6
6
  # Provider Management Commands
7
7
 
8
- Manage TTS providers (ElevenLabs, Piper) - switch between providers, view details, and test.
8
+ Manage TTS providers (ElevenLabs, Piper, macOS Say) - switch between providers, view details, and test.
9
9
 
10
10
  ## Usage
11
11
 
@@ -30,8 +30,12 @@ Manage TTS providers (ElevenLabs, Piper) - switch between providers, view detail
30
30
  # Switch to ElevenLabs (premium quality)
31
31
  /agent-vibes:provider switch elevenlabs
32
32
 
33
+ # Switch to macOS Say (native macOS, free)
34
+ /agent-vibes:provider switch macos
35
+
33
36
  # Get info about a provider
34
37
  /agent-vibes:provider info piper
38
+ /agent-vibes:provider info macos
35
39
 
36
40
  # Test current provider
37
41
  /agent-vibes:provider test
@@ -42,12 +46,22 @@ Manage TTS providers (ElevenLabs, Piper) - switch between providers, view detail
42
46
 
43
47
  ## Provider Comparison
44
48
 
45
- | Feature | ElevenLabs | Piper |
46
- |---------|------------|-------|
47
- | Quality | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
48
- | Cost | Free tier + $5-22/mo | Free forever |
49
- | Offline | No | Yes |
50
- | Platform | All | WSL/Linux only |
49
+ | Feature | ElevenLabs | Piper | macOS Say |
50
+ |---------|------------|-------|-----------|
51
+ | Quality | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
52
+ | Cost | Free tier + $5-22/mo | Free forever | Free (built-in) |
53
+ | Offline | No | Yes | Yes |
54
+ | Platform | All | WSL/Linux only | macOS only |
55
+ | Setup | API key required | Auto-downloads | Zero setup |
56
+
57
+ **macOS Say** is automatically detected and recommended on macOS systems. It uses the native `say` command with voices like Samantha, Alex, Daniel, and many more. Perfect for Mac users who want free, offline TTS with zero configuration.
58
+
59
+ ### macOS Say Compatibility
60
+
61
+ - **Supported:** All macOS versions (Mac OS X 10.0+)
62
+ - **Voices:** 100+ built-in voices across 40+ languages
63
+ - **Enhanced voices:** Siri-quality voices available on macOS 10.14 Mojave and later
64
+ - **Note:** Available voices vary by macOS version; newer versions have more high-quality options
51
65
 
52
66
  Learn more: agentvibes.org/providers
53
67
 
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: .claude/hooks/macos-voice-manager.sh
4
+ #
5
+ # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
+ # Website: https://agentvibes.org
7
+ # Repository: https://github.com/paulpreibisch/AgentVibes
8
+ #
9
+ # Co-created by Paul Preibisch with Claude AI
10
+ # Copyright (c) 2025 Paul Preibisch
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+ # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # express or implied. Use at your own risk. See the Apache License for details.
26
+ #
27
+ # ---
28
+ #
29
+ # @fileoverview macOS Voice Manager - Voice discovery and management for macOS 'say' command
30
+ # @context Provides voice listing, validation, and info for macOS native TTS
31
+ # @architecture Helper functions for play-tts-macos.sh provider
32
+ # @dependencies macOS only (Darwin), say command (built-in)
33
+ # @entrypoints Called by voice-manager.sh for provider-aware voice operations
34
+ # @patterns Voice enumeration via 'say -v ?', language filtering, quality tiers
35
+ # @related play-tts-macos.sh, voice-manager.sh, provider-manager.sh
36
+ #
37
+
38
+ # Platform guard
39
+ if [[ "$(uname -s)" != "Darwin" ]]; then
40
+ echo "❌ Error: macOS voice manager only works on macOS"
41
+ exit 1
42
+ fi
43
+
44
+ # @function list_macos_voices
45
+ # @intent List all available macOS voices
46
+ # @returns Echoes voice names one per line
47
+ list_macos_voices() {
48
+ say -v ? 2>/dev/null | awk '{print $1}'
49
+ }
50
+
51
+ # @function list_macos_voices_detailed
52
+ # @intent List all voices with language info
53
+ # @returns Echoes "voice_name language_code" per line
54
+ list_macos_voices_detailed() {
55
+ say -v ? 2>/dev/null | while read -r line; do
56
+ local voice=$(echo "$line" | awk '{print $1}')
57
+ local lang=$(echo "$line" | awk '{print $2}')
58
+ echo "$voice $lang"
59
+ done
60
+ }
61
+
62
+ # @function list_english_voices
63
+ # @intent List only English voices
64
+ # @returns Echoes English voice names
65
+ list_english_voices() {
66
+ say -v ? 2>/dev/null | grep -i "en_" | awk '{print $1}'
67
+ }
68
+
69
+ # @function get_voice_language
70
+ # @intent Get the language code for a voice
71
+ # @param $1 Voice name
72
+ # @returns Echoes language code (e.g., "en_US")
73
+ get_voice_language() {
74
+ local voice="$1"
75
+ say -v ? 2>/dev/null | grep -i "^${voice} " | awk '{print $2}'
76
+ }
77
+
78
+ # @function validate_voice
79
+ # @intent Check if a voice exists on this system
80
+ # @param $1 Voice name
81
+ # @returns 0 if valid, 1 if not found
82
+ validate_voice() {
83
+ local voice="$1"
84
+ say -v ? 2>/dev/null | grep -qi "^${voice} "
85
+ }
86
+
87
+ # @function get_recommended_voices
88
+ # @intent Get list of recommended high-quality voices
89
+ # @returns Echoes recommended voice names
90
+ get_recommended_voices() {
91
+ # These are typically the highest quality voices on macOS
92
+ local recommended=("Samantha" "Alex" "Daniel" "Karen" "Moira" "Tessa" "Fiona" "Veena" "Victoria")
93
+
94
+ for voice in "${recommended[@]}"; do
95
+ if validate_voice "$voice"; then
96
+ echo "$voice"
97
+ fi
98
+ done
99
+ }
100
+
101
+ # @function get_voice_info
102
+ # @intent Get detailed info about a specific voice
103
+ # @param $1 Voice name
104
+ # @returns Echoes voice details
105
+ get_voice_info() {
106
+ local voice="$1"
107
+
108
+ if ! validate_voice "$voice"; then
109
+ echo "❌ Voice not found: $voice"
110
+ return 1
111
+ fi
112
+
113
+ local full_info=$(say -v ? 2>/dev/null | grep -i "^${voice} ")
114
+ local lang=$(echo "$full_info" | awk '{print $2}')
115
+
116
+ echo "Voice: $voice"
117
+ echo "Language: $lang"
118
+ echo "Provider: macOS Say (built-in)"
119
+
120
+ # Add description for known voices
121
+ case "$voice" in
122
+ Alex) echo "Description: American English male (enhanced)" ;;
123
+ Samantha) echo "Description: American English female (enhanced)" ;;
124
+ Victoria) echo "Description: American English female" ;;
125
+ Daniel) echo "Description: British English male (enhanced)" ;;
126
+ Karen) echo "Description: Australian English female (enhanced)" ;;
127
+ Moira) echo "Description: Irish English female (enhanced)" ;;
128
+ Tessa) echo "Description: South African English female (enhanced)" ;;
129
+ Fiona) echo "Description: Scottish English female (enhanced)" ;;
130
+ Veena) echo "Description: Indian English female (enhanced)" ;;
131
+ esac
132
+ }
133
+
134
+ # @function count_voices
135
+ # @intent Count total available voices
136
+ # @returns Echoes count
137
+ count_voices() {
138
+ say -v ? 2>/dev/null | wc -l | tr -d ' '
139
+ }
140
+
141
+ # @function count_english_voices
142
+ # @intent Count English voices
143
+ # @returns Echoes count
144
+ count_english_voices() {
145
+ say -v ? 2>/dev/null | grep -i "en_" | wc -l | tr -d ' '
146
+ }
147
+
148
+ # Command-line interface
149
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
150
+ case "${1:-}" in
151
+ list)
152
+ echo "🎤 Available macOS Voices:"
153
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
154
+ list_macos_voices_detailed | while read -r voice lang; do
155
+ printf " %-15s %s\n" "$voice" "$lang"
156
+ done
157
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
158
+ echo "Total: $(count_voices) voices"
159
+ ;;
160
+ list-english)
161
+ echo "🎤 English macOS Voices:"
162
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
163
+ list_english_voices
164
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
165
+ echo "Total: $(count_english_voices) English voices"
166
+ ;;
167
+ recommended)
168
+ echo "🌟 Recommended macOS Voices:"
169
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
170
+ get_recommended_voices
171
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
172
+ ;;
173
+ info)
174
+ if [[ -z "$2" ]]; then
175
+ echo "Usage: $0 info <voice_name>"
176
+ exit 1
177
+ fi
178
+ get_voice_info "$2"
179
+ ;;
180
+ validate)
181
+ if [[ -z "$2" ]]; then
182
+ echo "Usage: $0 validate <voice_name>"
183
+ exit 1
184
+ fi
185
+ if validate_voice "$2"; then
186
+ echo "✅ Voice '$2' is available"
187
+ exit 0
188
+ else
189
+ echo "❌ Voice '$2' is not available"
190
+ exit 1
191
+ fi
192
+ ;;
193
+ *)
194
+ echo "Usage: $0 {list|list-english|recommended|info|validate} [voice_name]"
195
+ echo ""
196
+ echo "Commands:"
197
+ echo " list - List all available voices"
198
+ echo " list-english - List English voices only"
199
+ echo " recommended - List recommended high-quality voices"
200
+ echo " info <voice> - Get details about a voice"
201
+ echo " validate <voice> - Check if voice exists"
202
+ exit 1
203
+ ;;
204
+ esac
205
+ fi
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # File: .claude/hooks/play-tts-macos.sh
4
+ #
5
+ # AgentVibes - Finally, your AI Agents can Talk Back! Text-to-Speech WITH personality for AI Assistants!
6
+ # Website: https://agentvibes.org
7
+ # Repository: https://github.com/paulpreibisch/AgentVibes
8
+ #
9
+ # Co-created by Paul Preibisch with Claude AI
10
+ # Copyright (c) 2025 Paul Preibisch
11
+ #
12
+ # Licensed under the Apache License, Version 2.0 (the "License");
13
+ # you may not use this file except in compliance with the License.
14
+ # You may obtain a copy of the License at
15
+ #
16
+ # http://www.apache.org/licenses/LICENSE-2.0
17
+ #
18
+ # Unless required by applicable law or agreed to in writing, software
19
+ # distributed under the License is distributed on an "AS IS" BASIS,
20
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ # See the License for the specific language governing permissions and
22
+ # limitations under the License.
23
+ #
24
+ # DISCLAIMER: This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ # express or implied. Use at your own risk. See the Apache License for details.
26
+ #
27
+ # ---
28
+ #
29
+ # @fileoverview macOS Say Provider Implementation - Native macOS TTS using the 'say' command
30
+ # @context Provides zero-dependency, offline TTS for macOS users using built-in Apple voices
31
+ # @architecture Implements provider interface contract for macOS 'say' command integration
32
+ # @dependencies macOS only (Darwin), say command (built-in), afplay (built-in)
33
+ # @entrypoints Called by play-tts.sh router when provider=macos
34
+ # @patterns Provider contract: text/voice → audio file path, voice validation, platform guard
35
+ # @related play-tts.sh, macos-voice-manager.sh, provider-manager.sh
36
+ #
37
+
38
+ # Platform guard - fail fast on non-macOS systems
39
+ if [[ "$(uname -s)" != "Darwin" ]]; then
40
+ echo "❌ Error: macOS provider only works on macOS"
41
+ echo " Current platform: $(uname -s)"
42
+ echo ""
43
+ echo " Switch to a different provider:"
44
+ echo " /agent-vibes:provider switch piper"
45
+ exit 1
46
+ fi
47
+
48
+ TEXT="$1"
49
+ VOICE_OVERRIDE="$2" # Optional: voice name (e.g., "Samantha", "Daniel")
50
+
51
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
52
+
53
+ # Default voice for macOS
54
+ DEFAULT_VOICE="Samantha"
55
+
56
+ # Common macOS voices with descriptions
57
+ # These are typically available on all macOS systems
58
+ declare -A MACOS_VOICE_INFO
59
+ MACOS_VOICE_INFO=(
60
+ ["Alex"]="American English male (enhanced)"
61
+ ["Samantha"]="American English female (enhanced)"
62
+ ["Victoria"]="American English female"
63
+ ["Daniel"]="British English male (enhanced)"
64
+ ["Karen"]="Australian English female (enhanced)"
65
+ ["Moira"]="Irish English female (enhanced)"
66
+ ["Tessa"]="South African English female (enhanced)"
67
+ ["Fiona"]="Scottish English female (enhanced)"
68
+ ["Veena"]="Indian English female (enhanced)"
69
+ )
70
+
71
+ # @function get_voice_file_path
72
+ # @intent Determine path to voice configuration file
73
+ # @returns Echoes path to tts-voice.txt
74
+ get_voice_file_path() {
75
+ local voice_file=""
76
+
77
+ if [[ -n "$CLAUDE_PROJECT_DIR" ]] && [[ -f "$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt" ]]; then
78
+ voice_file="$CLAUDE_PROJECT_DIR/.claude/tts-voice.txt"
79
+ elif [[ -f "$SCRIPT_DIR/../tts-voice.txt" ]]; then
80
+ voice_file="$SCRIPT_DIR/../tts-voice.txt"
81
+ elif [[ -f "$HOME/.claude/tts-voice.txt" ]]; then
82
+ voice_file="$HOME/.claude/tts-voice.txt"
83
+ fi
84
+
85
+ echo "$voice_file"
86
+ }
87
+
88
+ # @function determine_voice
89
+ # @intent Resolve which voice to use
90
+ # @returns Sets $VOICE_NAME global variable
91
+ VOICE_NAME=""
92
+
93
+ if [[ -n "$VOICE_OVERRIDE" ]]; then
94
+ VOICE_NAME="$VOICE_OVERRIDE"
95
+ echo "🎤 Using voice: $VOICE_OVERRIDE (session-specific)"
96
+ else
97
+ VOICE_FILE=$(get_voice_file_path)
98
+
99
+ if [[ -n "$VOICE_FILE" ]] && [[ -f "$VOICE_FILE" ]]; then
100
+ FILE_VOICE=$(cat "$VOICE_FILE" 2>/dev/null | tr -d '\n\r')
101
+ if [[ -n "$FILE_VOICE" ]]; then
102
+ VOICE_NAME="$FILE_VOICE"
103
+ fi
104
+ fi
105
+
106
+ # Fallback to default if no voice set
107
+ if [[ -z "$VOICE_NAME" ]]; then
108
+ VOICE_NAME="$DEFAULT_VOICE"
109
+ fi
110
+ fi
111
+
112
+ # @function validate_inputs
113
+ # @intent Check required parameters
114
+ if [[ -z "$TEXT" ]]; then
115
+ echo "Usage: $0 \"text to speak\" [voice_name]"
116
+ echo ""
117
+ echo "Available voices (run 'say -v ?' for full list):"
118
+ for voice in "${!MACOS_VOICE_INFO[@]}"; do
119
+ echo " $voice - ${MACOS_VOICE_INFO[$voice]}"
120
+ done | sort
121
+ exit 1
122
+ fi
123
+
124
+ # @function validate_voice
125
+ # @intent Check if the specified voice exists on this system
126
+ # @param $1 Voice name to validate
127
+ # @returns 0 if valid, 1 if not found
128
+ validate_voice() {
129
+ local voice="$1"
130
+ say -v ? 2>/dev/null | grep -qi "^${voice} "
131
+ }
132
+
133
+ # Validate voice exists (case-insensitive search)
134
+ if ! validate_voice "$VOICE_NAME"; then
135
+ echo "⚠️ Voice '$VOICE_NAME' not found on this system"
136
+ echo " Falling back to default: $DEFAULT_VOICE"
137
+ VOICE_NAME="$DEFAULT_VOICE"
138
+
139
+ # If default also doesn't exist, try to find any English voice
140
+ if ! validate_voice "$VOICE_NAME"; then
141
+ VOICE_NAME=$(say -v ? 2>/dev/null | grep -i "en_" | head -1 | awk '{print $1}')
142
+ if [[ -z "$VOICE_NAME" ]]; then
143
+ echo "❌ No English voices found on this system"
144
+ exit 2
145
+ fi
146
+ echo " Using first available English voice: $VOICE_NAME"
147
+ fi
148
+ fi
149
+
150
+ # @function determine_audio_directory
151
+ # @intent Find appropriate directory for audio file storage
152
+ if [[ -n "$CLAUDE_PROJECT_DIR" ]]; then
153
+ AUDIO_DIR="$CLAUDE_PROJECT_DIR/.claude/audio"
154
+ else
155
+ CURRENT_DIR="$PWD"
156
+ while [[ "$CURRENT_DIR" != "/" ]]; do
157
+ if [[ -d "$CURRENT_DIR/.claude" ]]; then
158
+ AUDIO_DIR="$CURRENT_DIR/.claude/audio"
159
+ break
160
+ fi
161
+ CURRENT_DIR=$(dirname "$CURRENT_DIR")
162
+ done
163
+ if [[ -z "$AUDIO_DIR" ]]; then
164
+ AUDIO_DIR="$HOME/.claude/audio"
165
+ fi
166
+ fi
167
+
168
+ mkdir -p "$AUDIO_DIR"
169
+
170
+ # Generate unique filename
171
+ TIMESTAMP=$(date +%s)
172
+ TEMP_FILE="$AUDIO_DIR/tts-${TIMESTAMP}.aiff"
173
+ FINAL_FILE="$AUDIO_DIR/tts-padded-${TIMESTAMP}.wav"
174
+
175
+ # @function get_speech_rate
176
+ # @intent Determine speech rate for synthesis
177
+ # @returns Speech rate value (words per minute, default ~175-200)
178
+ get_speech_rate() {
179
+ local rate_config=""
180
+
181
+ # Check for rate config file
182
+ if [[ -f "$SCRIPT_DIR/../config/tts-speech-rate.txt" ]]; then
183
+ rate_config="$SCRIPT_DIR/../config/tts-speech-rate.txt"
184
+ elif [[ -f "$HOME/.claude/config/tts-speech-rate.txt" ]]; then
185
+ rate_config="$HOME/.claude/config/tts-speech-rate.txt"
186
+ fi
187
+
188
+ if [[ -n "$rate_config" ]]; then
189
+ local user_speed=$(cat "$rate_config" 2>/dev/null | grep -v '^#' | grep -v '^$' | tail -1)
190
+ # Convert multiplier to words per minute (base ~200 WPM)
191
+ # User: 0.5=slower, 1.0=normal, 2.0=faster
192
+ echo "scale=0; 200 * $user_speed / 1" | bc -l 2>/dev/null || echo "200"
193
+ return
194
+ fi
195
+
196
+ # Default: 200 WPM (normal rate)
197
+ echo "200"
198
+ }
199
+
200
+ SPEECH_RATE=$(get_speech_rate)
201
+
202
+ # @function synthesize_with_say
203
+ # @intent Generate speech using macOS 'say' command
204
+ # @returns Creates audio file at $TEMP_FILE
205
+ echo "$TEXT" | say -v "$VOICE_NAME" -r "$SPEECH_RATE" -o "$TEMP_FILE" 2>/dev/null
206
+
207
+ if [[ ! -f "$TEMP_FILE" ]] || [[ ! -s "$TEMP_FILE" ]]; then
208
+ echo "❌ Failed to synthesize speech with macOS say command"
209
+ echo "Voice: $VOICE_NAME"
210
+ exit 3
211
+ fi
212
+
213
+ # @function convert_and_pad_audio
214
+ # @intent Convert AIFF to WAV and add silence padding for consistency
215
+ # @why Maintains consistent audio format across providers
216
+ if command -v ffmpeg &> /dev/null; then
217
+ # Add 200ms of silence at the beginning and convert to WAV
218
+ ffmpeg -f lavfi -i anullsrc=r=44100:cl=stereo:d=0.2 -i "$TEMP_FILE" \
219
+ -filter_complex "[0:a][1:a]concat=n=2:v=0:a=1[out]" \
220
+ -map "[out]" -y "$FINAL_FILE" 2>/dev/null
221
+
222
+ if [[ -f "$FINAL_FILE" ]]; then
223
+ rm -f "$TEMP_FILE"
224
+ TEMP_FILE="$FINAL_FILE"
225
+ fi
226
+ else
227
+ # No ffmpeg - use AIFF directly (rename for consistency)
228
+ FINAL_FILE="$AUDIO_DIR/tts-padded-${TIMESTAMP}.aiff"
229
+ mv "$TEMP_FILE" "$FINAL_FILE"
230
+ TEMP_FILE="$FINAL_FILE"
231
+ fi
232
+
233
+ # @function play_audio
234
+ # @intent Play generated audio using macOS native player
235
+ LOCK_FILE="/tmp/agentvibes-audio.lock"
236
+
237
+ # Wait for previous audio to finish (max 30 seconds)
238
+ for i in {1..60}; do
239
+ if [ ! -f "$LOCK_FILE" ]; then
240
+ break
241
+ fi
242
+ sleep 0.5
243
+ done
244
+
245
+ # Create lock and play audio
246
+ touch "$LOCK_FILE"
247
+
248
+ # Get audio duration for proper lock timing
249
+ if command -v ffprobe &> /dev/null; then
250
+ DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$TEMP_FILE" 2>/dev/null)
251
+ DURATION=${DURATION%.*} # Round to integer
252
+ else
253
+ # Estimate duration based on text length (~150 WPM)
254
+ WORD_COUNT=$(echo "$TEXT" | wc -w)
255
+ DURATION=$(( (WORD_COUNT * 60 / 150) + 1 ))
256
+ fi
257
+ DURATION=${DURATION:-2} # Default to 2 seconds if detection fails
258
+
259
+ # Play audio in background (skip if in test mode)
260
+ if [[ "${AGENTVIBES_TEST_MODE:-false}" != "true" ]]; then
261
+ afplay "$TEMP_FILE" >/dev/null 2>&1 &
262
+ PLAYER_PID=$!
263
+ fi
264
+
265
+ # Wait for audio to finish, then release lock
266
+ (sleep $DURATION; rm -f "$LOCK_FILE") &
267
+ disown
268
+
269
+ echo "🎵 Saved to: $TEMP_FILE"
270
+ echo "🎤 Voice used: $VOICE_NAME (macOS Say)"