feat(audio): replace sound alerts with more pleasant tones (#145)

Replace the current jarring sound alerts with smoother, more pleasant
bell-like tones inspired by voice assistants like Siri and Gemini.

Changes:
- start_recording.wav: Ascending perfect fifth (A4→E5, 0.30s)
- stop_recording.wav: Descending perfect fifth (E5→A4, 0.25s)
- error.wav: Low gentle chime (E4, 0.35s)

Technical improvements:
- Smooth frequency sweeps (sigmoid curve transitions)
- Subtle vibrato (8Hz) for warmth and richness
- Harmonic overtones for fullness
- Elegant exponential decay envelopes
- Reduced file sizes (926KB → 80KB total)

The new sounds are shorter (0.25-0.35s vs 0.5-1s), quieter
(20-25% amplitude vs full volume), and more pleasant for frequent use.
This commit is contained in:
Jatin K Malik
2026-02-05 17:48:05 -08:00
committed by GitHub
parent 1e97f36d6c
commit a5b933a546
5 changed files with 157 additions and 1 deletions
+23 -1
View File
@@ -43,7 +43,7 @@
- 🔒 **Offline operation** for privacy and reliability (with VOSK)
- 🤖 **Optional Whisper AI** support for enhanced accuracy
- 🎨 **System tray integration** with visual status indicators
- 🔊 **Audio feedback** for recording status
- 🔊 **Pleasant audio feedback** - smooth gliding tones, headphone-friendly
- ⚙️ **Graphical settings** dialog for easy configuration
## 🚀 Quick Install
@@ -220,6 +220,8 @@ vocalinux/
│ ├── ui/ # GTK UI components
│ └── utils/ # Utility functions
├── tests/ # Test suite
├── scripts/ # Development utilities
│ └── generate_sounds.py # Sound generation script
├── resources/ # Icons and sounds
├── docs/ # Documentation
└── web/ # Website source
@@ -232,6 +234,26 @@ vocalinux/
- [User Guide](docs/USER_GUIDE.md) - Complete user documentation
- [Contributing](CONTRIBUTING.md) - Development setup and contribution guidelines
## 🔊 Sound Customization
Vocalinux uses smooth, pleasant gliding tones for audio feedback:
- **Start**: Ascending F4→A4 (0.6s) - positive, uplifting
- **Stop**: Descending A4→F4 (0.6s) - resolves completion
- **Error**: Lower descending E4→C4 (0.7s) - gentle but noticeable
All sounds use pure sine waves with smoothstep interpolation for buttery smooth pitch transitions - perfect for headphone use!
### Regenerate Sounds
To modify or regenerate the notification sounds:
```bash
python scripts/generate_sounds.py
```
This script generates all three sounds using the same smooth glide algorithm. You can edit the frequencies, durations, and amplitudes in the script to customize the sounds to your preference.
## 🗺️ Roadmap
- [x] ~~Custom icon design~~ ✅
Binary file not shown.
Binary file not shown.
Binary file not shown.
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""
Sound generation utility for Vocalinux.
This script generates the notification sounds used by Vocalinux for:
- Start recording (ascending pitch glide)
- Stop recording (descending pitch glide)
- Error notification (lower descending glide)
The sounds use smooth pitch glides with pure sine waves and soft envelopes
to create pleasant, headphone-friendly audio feedback.
Usage:
python scripts/generate_sounds.py
The generated sounds will be placed in resources/sounds/
Design Philosophy:
- Pure sine waves (no harsh harmonics)
- Smoothstep interpolation for pitch transitions
- Soft attack/decay envelopes
- Lower frequencies (F4-A4 range) for warmth
- Consistent glide aesthetic across all sounds
"""
import math
import os
import struct
import wave
def generate_glide_tone(
filename: str,
freq_start: float,
freq_end: float,
duration: float = 0.6,
amplitude: float = 0.16,
sample_rate: int = 44100,
) -> None:
"""
Generate a smooth pitch glide tone.
Args:
filename: Output WAV file path
freq_start: Starting frequency in Hz
freq_end: Ending frequency in Hz
duration: Duration in seconds
amplitude: Volume (0.0 to 1.0)
sample_rate: Audio sample rate
"""
num_samples = int(sample_rate * duration)
samples = []
for i in range(num_samples):
t = i / sample_rate
progress = i / num_samples
# Ultra-soft envelope: sine-squared with exponential decay
envelope = (math.sin(math.pi * progress) ** 2) * math.exp(-0.5 * progress)
# Smooth pitch glide using smoothstep interpolation
# This creates buttery smooth transitions with no abrupt changes
glide = progress ** 2 * (3 - 2 * progress)
freq_current = freq_start + (freq_end - freq_start) * glide
# Generate pure sine wave
phase = 2 * math.pi * freq_current * t
sample = amplitude * envelope * math.sin(phase)
samples.append(sample)
# Convert to 16-bit PCM
wav_samples = [int(sample * 32767) for sample in samples]
# Write WAV file
with wave.open(filename, "w") as wav_file:
wav_file.setnchannels(1) # Mono
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(sample_rate)
wav_file.writeframes(struct.pack("<" + "h" * len(wav_samples), *wav_samples))
print(f"Generated: {filename}")
def main():
"""Generate all Vocalinux notification sounds."""
# Determine output directory
script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.dirname(script_dir)
sounds_dir = os.path.join(repo_root, "resources", "sounds")
os.makedirs(sounds_dir, exist_ok=True)
print(f"Generating sounds in: {sounds_dir}\n")
# START: Ascending F4→A4 (positive, uplifting)
print("Generating start_recording.wav (ascending F4→A4)...")
generate_glide_tone(
os.path.join(sounds_dir, "start_recording.wav"),
freq_start=349.23, # F4
freq_end=440.00, # A4
duration=0.6,
amplitude=0.16,
)
# STOP: Descending A4→F4 (resolution, completion)
print("Generating stop_recording.wav (descending A4→F4)...")
generate_glide_tone(
os.path.join(sounds_dir, "stop_recording.wav"),
freq_start=440.00, # A4
freq_end=349.23, # F4
duration=0.6,
amplitude=0.16,
)
# ERROR: Lower descending E4→C4 (sad, concerning but gentle)
print("Generating error.wav (descending E4→C4)...")
generate_glide_tone(
os.path.join(sounds_dir, "error.wav"),
freq_start=329.63, # E4
freq_end=261.63, # C4
duration=0.7,
amplitude=0.14, # Slightly quieter
)
print("\n✓ All sounds generated successfully!")
print("\nSound characteristics:")
print("- Pure sine waves (headphone-friendly)")
print("- Smoothstep pitch interpolation (no jumps)")
print("- Soft envelopes (sine-squared with decay)")
print("- Lower frequencies (F4-A4 range, warm and pleasant)")
print("- Consistent glide aesthetic")
if __name__ == "__main__":
main()