voice-mode 4.2.0__tar.gz → 4.3.1__tar.gz

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.
Files changed (134) hide show
  1. {voice_mode-4.2.0 → voice_mode-4.3.1}/CHANGELOG.md +8 -0
  2. {voice_mode-4.2.0 → voice_mode-4.3.1}/PKG-INFO +1 -1
  3. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/__version__.py +1 -1
  4. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli.py +24 -6
  5. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli_commands/hook.py +33 -21
  6. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/config.py +89 -0
  7. {voice_mode-4.2.0 → voice_mode-4.3.1}/.gitignore +0 -0
  8. {voice_mode-4.2.0 → voice_mode-4.3.1}/README.md +0 -0
  9. {voice_mode-4.2.0 → voice_mode-4.3.1}/build_hooks.py +0 -0
  10. {voice_mode-4.2.0 → voice_mode-4.3.1}/pyproject.toml +0 -0
  11. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/__init__.py +0 -0
  12. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/__main__.py +0 -0
  13. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli_commands/__init__.py +0 -0
  14. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli_commands/claude.py +0 -0
  15. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli_commands/exchanges.py +0 -0
  16. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli_commands/pronounce_commands.py +0 -0
  17. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/cli_commands/transcribe.py +0 -0
  18. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/conversation_logger.py +0 -0
  19. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/core.py +0 -0
  20. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/data/default_pronunciation.yaml +0 -0
  21. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/data/versions.json +0 -0
  22. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/__init__.py +0 -0
  23. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/conversations.py +0 -0
  24. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/filters.py +0 -0
  25. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/formatters.py +0 -0
  26. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/models.py +0 -0
  27. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/reader.py +0 -0
  28. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/exchanges/stats.py +0 -0
  29. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/README.md +0 -0
  30. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/app/api/connection-details/route.ts +0 -0
  31. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/app/favicon.ico +0 -0
  32. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/app/globals.css +0 -0
  33. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/app/layout.tsx +0 -0
  34. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/app/page.tsx +0 -0
  35. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/components/CloseIcon.tsx +0 -0
  36. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/components/NoAgentNotification.tsx +0 -0
  37. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/components/TranscriptionView.tsx +0 -0
  38. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/hooks/useCombinedTranscriptions.ts +0 -0
  39. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/hooks/useLocalMicTrack.ts +0 -0
  40. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/next-env.d.ts +0 -0
  41. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/next.config.mjs +0 -0
  42. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/package-lock.json +0 -0
  43. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/package.json +0 -0
  44. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/pnpm-lock.yaml +0 -0
  45. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/postcss.config.mjs +0 -0
  46. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/tailwind.config.ts +0 -0
  47. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/frontend/tsconfig.json +0 -0
  48. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/prompts/README.md +0 -0
  49. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/prompts/__init__.py +0 -0
  50. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/prompts/converse.py +0 -0
  51. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/prompts/release_notes.py +0 -0
  52. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/prompts/services.py +0 -0
  53. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/pronounce.py +0 -0
  54. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/provider_discovery.py +0 -0
  55. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/providers.py +0 -0
  56. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/__init__.py +0 -0
  57. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/audio_files.py +0 -0
  58. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/changelog.py +0 -0
  59. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/configuration.py +0 -0
  60. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/statistics.py +0 -0
  61. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/version.py +0 -0
  62. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/resources/whisper_models.py +0 -0
  63. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/server.py +0 -0
  64. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/shared.py +0 -0
  65. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/simple_failover.py +0 -0
  66. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/statistics.py +0 -0
  67. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/streaming.py +0 -0
  68. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/__init__.py +0 -0
  69. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/launchd/com.voicemode.frontend.plist +0 -0
  70. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/launchd/com.voicemode.kokoro.plist +0 -0
  71. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/launchd/com.voicemode.livekit.plist +0 -0
  72. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/launchd/com.voicemode.whisper.plist +0 -0
  73. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/launchd/start-kokoro-with-health-check.sh +0 -0
  74. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/launchd/start-whisper-with-health-check.sh +0 -0
  75. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/scripts/__init__.py +0 -0
  76. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/scripts/start-whisper-server.sh +0 -0
  77. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/systemd/voicemode-frontend.service +0 -0
  78. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/systemd/voicemode-kokoro.service +0 -0
  79. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/systemd/voicemode-livekit.service +0 -0
  80. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/templates/systemd/voicemode-whisper.service +0 -0
  81. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/__init__.py +0 -0
  82. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/claude_thinking.py +0 -0
  83. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/configuration_management.py +0 -0
  84. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/converse.py +0 -0
  85. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/dependencies.py +0 -0
  86. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/devices.py +0 -0
  87. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/diagnostics.py +0 -0
  88. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/pronounce.py +0 -0
  89. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/providers.py +0 -0
  90. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/service.py +0 -0
  91. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/kokoro/install.py +0 -0
  92. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/kokoro/uninstall.py +0 -0
  93. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/list_versions.py +0 -0
  94. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/livekit/__init__.py +0 -0
  95. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/livekit/frontend.py +0 -0
  96. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/livekit/install.py +0 -0
  97. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/livekit/production_server.py +0 -0
  98. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/livekit/uninstall.py +0 -0
  99. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/version_info.py +0 -0
  100. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/__init__.py +0 -0
  101. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/install.py +0 -0
  102. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/list_models.py +0 -0
  103. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/model_active.py +0 -0
  104. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/model_benchmark.py +0 -0
  105. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/model_install.py +0 -0
  106. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/model_remove.py +0 -0
  107. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/models.py +0 -0
  108. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/services/whisper/uninstall.py +0 -0
  109. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/sound_fonts/__init__.py +0 -0
  110. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/sound_fonts/audio_player.py +0 -0
  111. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/sound_fonts/hook_handler.py +0 -0
  112. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/sound_fonts/player.py +0 -0
  113. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/statistics.py +0 -0
  114. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/transcription/__init__.py +0 -0
  115. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/transcription/backends.py +0 -0
  116. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/transcription/core.py +0 -0
  117. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/transcription/formats.py +0 -0
  118. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/transcription/types.py +0 -0
  119. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/tools/voice_registry.py +0 -0
  120. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/__init__.py +0 -0
  121. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/audio_diagnostics.py +0 -0
  122. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/event_logger.py +0 -0
  123. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/ffmpeg_check.py +0 -0
  124. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/format_migration.py +0 -0
  125. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/gpu_detection.py +0 -0
  126. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/migration_helpers.py +0 -0
  127. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/services/common.py +0 -0
  128. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/services/coreml_setup.py +0 -0
  129. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/services/kokoro_helpers.py +0 -0
  130. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/services/livekit_helpers.py +0 -0
  131. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/services/whisper_helpers.py +0 -0
  132. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/services/whisper_version.py +0 -0
  133. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/utils/version_helpers.py +0 -0
  134. {voice_mode-4.2.0 → voice_mode-4.3.1}/voice_mode/version.py +0 -0
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [4.3.1] - 2025-09-03
11
+
12
+ ## [4.3.0] - 2025-09-03
13
+
10
14
  ## [4.2.0] - 2025-09-03
11
15
 
12
16
  ### Added
@@ -23,6 +27,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
27
  - Claude Code integration via stdin-receiver for hook-based audio
24
28
  - CLI command `play-sound` with theme, action, and sound selection
25
29
  - Enhances user experience with auditory feedback during operations
30
+ - MP3 support added for 90% file size reduction over WAV
31
+ - Recursive directory copying for complete sound font structure
32
+ - Three Bears sound fonts for baby-bear, mama-bear, and papa-bear agents
33
+ - Sound fonts disabled by default (VOICEMODE_SOUNDFONTS_ENABLED=false)
26
34
 
27
35
  - **🎭 Claude Code Deep Integration**
28
36
  - Extract and analyze Claude's conversation logs in real-time
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voice-mode
3
- Version: 4.2.0
3
+ Version: 4.3.1
4
4
  Summary: VoiceMode - Voice interaction capabilities for AI assistants (formerly voice-mcp)
5
5
  Project-URL: Homepage, https://github.com/mbailey/voicemode
6
6
  Project-URL: Repository, https://github.com/mbailey/voicemode
@@ -1,3 +1,3 @@
1
1
  # This file is automatically updated by 'make release'
2
2
  # Do not edit manually
3
- __version__ = "4.2.0"
3
+ __version__ = "4.3.1"
@@ -57,6 +57,14 @@ def voice_mode() -> None:
57
57
  voice_mode_main_cli()
58
58
 
59
59
 
60
+ # Audio group for audio-related commands
61
+ @voice_mode_main_cli.group()
62
+ @click.help_option('-h', '--help', help='Show this message and exit')
63
+ def audio():
64
+ """Audio transcription and playback commands."""
65
+ pass
66
+
67
+
60
68
  # Service group commands
61
69
  @voice_mode_main_cli.group()
62
70
  @click.help_option('-h', '--help', help='Show this message and exit')
@@ -1372,15 +1380,25 @@ cli.add_command(claude.claude_group)
1372
1380
 
1373
1381
  # Add exchanges to main CLI
1374
1382
  voice_mode_main_cli.add_command(exchanges_cmd.exchanges)
1375
- voice_mode_main_cli.add_command(pronounce_commands.pronounce_group)
1376
1383
  voice_mode_main_cli.add_command(claude.claude_group)
1377
1384
 
1378
- # Add transcribe to main CLI
1379
- voice_mode_main_cli.add_command(transcribe_cmd.transcribe)
1385
+ # Note: We'll add these commands after the groups are defined
1386
+ # audio group will get transcribe and play commands
1387
+ # claude group will get hook command
1388
+ # config group will get pronounce command
1389
+
1390
+
1391
+ # Now add the subcommands to their respective groups
1392
+ # Add transcribe command to audio group
1393
+ transcribe_audio_cmd = transcribe_cmd.transcribe.commands['audio']
1394
+ transcribe_audio_cmd.name = 'transcribe'
1395
+ audio.add_command(transcribe_audio_cmd)
1380
1396
 
1381
- # Add hook command to main CLI
1382
- voice_mode_main_cli.add_command(hook_cmd.hook)
1397
+ # Add hook command under claude group
1398
+ claude.claude_group.add_command(hook_cmd.hook)
1383
1399
 
1400
+ # Add pronounce under config group
1401
+ config.add_command(pronounce_commands.pronounce_group)
1384
1402
 
1385
1403
  # Converse command - direct voice conversation from CLI
1386
1404
  @voice_mode_main_cli.command()
@@ -1759,7 +1777,7 @@ def update(force):
1759
1777
 
1760
1778
 
1761
1779
  # Sound Fonts command
1762
- @voice_mode_main_cli.command("play-sound")
1780
+ @audio.command("play")
1763
1781
  @click.help_option('-h', '--help')
1764
1782
  @click.option('-t', '--tool', help='Tool name for direct command-line usage')
1765
1783
  @click.option('-a', '--action', default='start', type=click.Choice(['start', 'end']), help='Action type')
@@ -86,25 +86,32 @@ def stdin_receiver(tool_name, action, subagent_type, event, debug):
86
86
  print(f"[DEBUG] Processing: event={event_name}, tool={tool_name}, "
87
87
  f"action={action}, subagent={subagent_type}", file=sys.stderr)
88
88
 
89
- # Find sound file using filesystem conventions
90
- sound_file = find_sound_file(event_name, tool_name, subagent_type)
89
+ # Check if sound fonts are enabled
90
+ from voice_mode.config import SOUNDFONTS_ENABLED
91
91
 
92
- if sound_file:
92
+ if not SOUNDFONTS_ENABLED:
93
93
  if debug:
94
- print(f"[DEBUG] Found sound file: {sound_file}", file=sys.stderr)
95
-
96
- # Play the sound
97
- player = Player()
98
- success = player.play(str(sound_file))
99
-
100
- if debug:
101
- if success:
102
- print(f"[DEBUG] Sound played successfully", file=sys.stderr)
103
- else:
104
- print(f"[DEBUG] Failed to play sound", file=sys.stderr)
94
+ print(f"[DEBUG] Sound fonts are disabled (VOICEMODE_SOUNDFONTS_ENABLED=false)", file=sys.stderr)
105
95
  else:
106
- if debug:
107
- print(f"[DEBUG] No sound file found for this event", file=sys.stderr)
96
+ # Find sound file using filesystem conventions
97
+ sound_file = find_sound_file(event_name, tool_name, subagent_type)
98
+
99
+ if sound_file:
100
+ if debug:
101
+ print(f"[DEBUG] Found sound file: {sound_file}", file=sys.stderr)
102
+
103
+ # Play the sound
104
+ player = Player()
105
+ success = player.play(str(sound_file))
106
+
107
+ if debug:
108
+ if success:
109
+ print(f"[DEBUG] Sound played successfully", file=sys.stderr)
110
+ else:
111
+ print(f"[DEBUG] Failed to play sound", file=sys.stderr)
112
+ else:
113
+ if debug:
114
+ print(f"[DEBUG] No sound file found for this event", file=sys.stderr)
108
115
 
109
116
  # Always exit 0 to not disrupt Claude Code
110
117
  sys.exit(0)
@@ -114,11 +121,11 @@ def find_sound_file(event: str, tool: str, subagent: Optional[str] = None) -> Op
114
121
  """
115
122
  Find sound file using filesystem conventions.
116
123
 
117
- Tries paths in order:
118
- 1. Most specific: {event}/{tool}/subagent/{subagent}.wav (Task tool only)
119
- 2. Tool default: {event}/{tool}/default.wav
120
- 3. Event default: {event}/default.wav
121
- 4. Global fallback: fallback.wav
124
+ Tries paths in order (mp3 preferred over wav for size):
125
+ 1. Most specific: {event}/{tool}/subagent/{subagent}.{mp3,wav} (Task tool only)
126
+ 2. Tool default: {event}/{tool}/default.{mp3,wav}
127
+ 3. Event default: {event}/default.{mp3,wav}
128
+ 4. Global fallback: fallback.{mp3,wav}
122
129
 
123
130
  Args:
124
131
  event: Event name (PreToolUse, PostToolUse)
@@ -157,15 +164,20 @@ def find_sound_file(event: str, tool: str, subagent: Optional[str] = None) -> Op
157
164
 
158
165
  # 1. Most specific: subagent sound (Task tool only)
159
166
  if tool == 'task' and subagent:
167
+ # Try mp3 first (smaller), then wav
168
+ paths_to_try.append(base_path / event_dir / tool / 'subagent' / f'{subagent}.mp3')
160
169
  paths_to_try.append(base_path / event_dir / tool / 'subagent' / f'{subagent}.wav')
161
170
 
162
171
  # 2. Tool-specific default
172
+ paths_to_try.append(base_path / event_dir / tool / 'default.mp3')
163
173
  paths_to_try.append(base_path / event_dir / tool / 'default.wav')
164
174
 
165
175
  # 3. Event-level default
176
+ paths_to_try.append(base_path / event_dir / 'default.mp3')
166
177
  paths_to_try.append(base_path / event_dir / 'default.wav')
167
178
 
168
179
  # 4. Global fallback
180
+ paths_to_try.append(base_path / 'fallback.mp3')
169
181
  paths_to_try.append(base_path / 'fallback.wav')
170
182
 
171
183
  # Find first existing file
@@ -101,6 +101,9 @@ def load_voicemode_env():
101
101
  # Enable audio feedback (true/false)
102
102
  # VOICEMODE_AUDIO_FEEDBACK=true
103
103
 
104
+ # Enable sound fonts for tool use hooks (true/false)
105
+ # VOICEMODE_SOUNDFONTS_ENABLED=false
106
+
104
107
  #############
105
108
  # Provider Configuration
106
109
  #############
@@ -371,6 +374,11 @@ FRONTEND_PORT = int(os.getenv("VOICEMODE_FRONTEND_PORT", "3000"))
371
374
  # Auto-enable services after installation
372
375
  SERVICE_AUTO_ENABLE = env_bool("VOICEMODE_SERVICE_AUTO_ENABLE", False)
373
376
 
377
+ # ==================== SOUND FONTS CONFIGURATION ====================
378
+
379
+ # Sound fonts are disabled by default to avoid annoying users with unexpected sounds
380
+ SOUNDFONTS_ENABLED = env_bool("VOICEMODE_SOUNDFONTS_ENABLED", False)
381
+
374
382
  # ==================== AUDIO CONFIGURATION ====================
375
383
 
376
384
  # Audio parameters
@@ -555,6 +563,87 @@ def initialize_directories():
555
563
  # Create events log directory
556
564
  if EVENT_LOG_ENABLED:
557
565
  Path(EVENT_LOG_DIR).mkdir(parents=True, exist_ok=True)
566
+
567
+ # Initialize sound fonts if not present
568
+ initialize_soundfonts()
569
+
570
+ # ==================== SOUND FONTS INITIALIZATION ====================
571
+
572
+ def initialize_soundfonts():
573
+ """Install default sound fonts from package data if not present."""
574
+ import shutil
575
+ import importlib.resources
576
+
577
+ soundfonts_dir = BASE_DIR / "soundfonts"
578
+ default_soundfont_dir = soundfonts_dir / "default"
579
+ current_symlink = soundfonts_dir / "current"
580
+
581
+ # Skip if soundfonts already exist (user has customized them)
582
+ if default_soundfont_dir.exists():
583
+ # Ensure symlink exists if directory exists
584
+ if not current_symlink.exists():
585
+ try:
586
+ current_symlink.symlink_to(default_soundfont_dir.resolve())
587
+ except OSError:
588
+ # Symlinks might not work on all systems
589
+ pass
590
+ return
591
+
592
+ try:
593
+ # Create soundfonts directory
594
+ soundfonts_dir.mkdir(exist_ok=True)
595
+
596
+ # Copy default soundfonts from package data
597
+ try:
598
+ # For Python 3.9+
599
+ from importlib.resources import files
600
+ package_soundfonts = files("voice_mode.data.soundfonts.default")
601
+
602
+ if package_soundfonts.is_dir():
603
+ # Create the default directory
604
+ default_soundfont_dir.mkdir(exist_ok=True)
605
+
606
+ # Recursively copy all files from package data
607
+ def copy_tree(src, dst):
608
+ """Recursively copy directory tree from package data."""
609
+ dst.mkdir(exist_ok=True)
610
+ for item in src.iterdir():
611
+ if item.is_file():
612
+ target = dst / item.name
613
+ target.write_bytes(item.read_bytes())
614
+ elif item.is_dir():
615
+ copy_tree(item, dst / item.name)
616
+
617
+ # Copy entire tree structure
618
+ copy_tree(package_soundfonts, default_soundfont_dir)
619
+ except ImportError:
620
+ # Fallback for older Python versions
621
+ import pkg_resources
622
+
623
+ # Create the default directory
624
+ default_soundfont_dir.mkdir(exist_ok=True)
625
+
626
+ # List all resources in the soundfonts directory
627
+ resource_dir = "data/soundfonts/default"
628
+ if pkg_resources.resource_exists("voice_mode", resource_dir):
629
+ # This is a bit more complex with pkg_resources
630
+ # We'll need to manually copy the structure
631
+ pass
632
+
633
+ # Create symlink to current soundfont (points to default)
634
+ if default_soundfont_dir.exists() and not current_symlink.exists():
635
+ try:
636
+ current_symlink.symlink_to(default_soundfont_dir.resolve())
637
+ except OSError:
638
+ # Symlinks might not work on all systems (e.g., Windows without admin)
639
+ pass
640
+
641
+ except Exception as e:
642
+ # Don't fail initialization if soundfonts can't be installed
643
+ # They're optional and disabled by default
644
+ if DEBUG:
645
+ import logging
646
+ logging.getLogger("voicemode").debug(f"Could not initialize soundfonts: {e}")
558
647
 
559
648
  # ==================== UTILITY FUNCTIONS ====================
560
649
 
File without changes
File without changes
File without changes
File without changes