VoiceGen – Free Text to Speech

* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: ‘Segoe UI’, Tahoma, Geneva, Verdana, sans-serif;
}

body {
background: linear-gradient(135deg, #D32F2F, #FFCDD2);
min-height: 100vh;
color: #B71C1C;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}

.container {
width: 100%;
max-width: 800px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
margin-top: 20px;
}

h1 {
text-align: center;
margin-bottom: 10px;
color: #FFFFFF;
font-size: 2.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.subtitle {
text-align: center;
margin-bottom: 30px;
color: #FFFFFF;
font-weight: 300;
}

textarea {
width: 100%;
height: 200px;
padding: 15px;
border-radius: 10px;
border: 2px solid rgba(183, 28, 28, 0.3);
background: rgba(255, 255, 255, 0.9);
margin-bottom: 20px;
resize: vertical;
color: #B71C1C;
font-size: 1rem;
transition: all 0.3s ease;
}

textarea:focus {
outline: none;
border-color: #D32F2F;
box-shadow: 0 0 10px rgba(211, 47, 47, 0.5);
}

.controls {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
}

select, .btn {
padding: 12px 20px;
border-radius: 8px;
border: none;
background: rgba(255, 255, 255, 0.9);
color: #B71C1C;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
}

select {
flex: 1;
min-width: 150px;
}

.btn {
background: #D32F2F;
color: white;
font-weight: bold;
flex: 1;
min-width: 120px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}

.btn:hover {
background: #B71C1C;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(183, 28, 28, 0.3);
}

.btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}

.settings-row {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}

.setting {
display: flex;
flex-direction: column;
flex: 1;
min-width: 120px;
}

.setting label {
margin-bottom: 5px;
color: white;
}

input[type=”range”] {
width: 100%;
-webkit-appearance: none;
height: 8px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.3);
outline: none;
}

input[type=”range”]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #D32F2F;
cursor: pointer;
}

.audio-player {
width: 100%;
margin-top: 20px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.2);
padding: 10px;
display: none;
}

audio {
width: 100%;
}

.character-count {
text-align: right;
color: white;
margin-top: -15px;
margin-bottom: 15px;
font-size: 0.9rem;
}

.loader {
display: none;
width: 24px;
height: 24px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
to { transform: rotate(360deg); }
}

.feature-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
margin-top: 30px;
gap: 20px;
}

.feature-item {
flex-basis: calc(50% – 10px);
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 15px;
display: flex;
align-items: center;
gap: 15px;
}

.feature-icon {
background: rgba(211, 47, 47, 0.2);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.2rem;
}

.feature-text h3 {
color: white;
margin-bottom: 5px;
}

.feature-text p {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}

footer {
margin-top: 40px;
text-align: center;
color: white;
font-size: 0.9rem;
opacity: 0.8;
}

.badge {
display: inline-block;
padding: 5px 10px;
border-radius: 30px;
background: #FFCDD2;
color: #B71C1C;
font-size: 0.8rem;
font-weight: bold;
margin-left: 10px;
vertical-align: middle;
}

.language-group {
margin-bottom: 5px;
font-weight: bold;
color: #D32F2F;
background-color: rgba(255, 255, 255, 0.8);
padding: 3px 8px;
border-radius: 4px;
}

@media (max-width: 768px) {
.container {
padding: 20px;
}

h1 {
font-size: 2rem;
}

.feature-item {
flex-basis: 100%;
}

.controls {
flex-direction: column;
}

.btn, select {
width: 100%;
}
}

VoiceGen Free

Transform your text to natural-sounding speech

0/5000 characters

English (US)
English (UK)
Hindi
Spanish
French
German
Italian
Japanese
Korean
Chinese (Simplified)
Chinese (Traditional)
Russian
Portuguese (Brazil)
Arabic
Bengali
Telugu
Tamil

Loading voices…




🔊

Multiple Voices

Choose from a variety of natural-sounding voices

🌐

Multiple Languages

Support for 15+ languages including Hindi, Bengali & more

⚡

Instant Preview

Hear your text immediately with real-time preview

💾

MP3 Download

Save your audio for offline use in MP3 format

© 2025 VoiceGen | All Rights Reserved

// Initialize speech synthesis and variables
const synth = window.speechSynthesis;
let voices = [];
let currentSpeech = null;
let audioBlob = null;

// DOM elements
const textInput = document.getElementById(‘text-input’);
const voiceSelect = document.getElementById(‘voice-select’);
const languageSelect = document.getElementById(‘language-select’);
const rateSlider = document.getElementById(‘rate-slider’);
const pitchSlider = document.getElementById(‘pitch-slider’);
const rateValue = document.getElementById(‘rate-value’);
const pitchValue = document.getElementById(‘pitch-value’);
const playBtn = document.getElementById(‘play-btn’);
const pauseBtn = document.getElementById(‘pause-btn’);
const stopBtn = document.getElementById(‘stop-btn’);
const downloadBtn = document.getElementById(‘download-btn’);
const audioPlayer = document.getElementById(‘audio-player’);
const audioElement = document.getElementById(‘audio-element’);
const charCount = document.getElementById(‘char-count’);
const downloadLoader = document.getElementById(‘download-loader’);

// Language name mapping
const languageNames = {
‘en’: ‘English’,
‘es’: ‘Spanish’,
‘fr’: ‘French’,
‘de’: ‘German’,
‘it’: ‘Italian’,
‘ja’: ‘Japanese’,
‘ko’: ‘Korean’,
‘zh’: ‘Chinese’,
‘ru’: ‘Russian’,
‘pt’: ‘Portuguese’,
‘ar’: ‘Arabic’,
‘hi’: ‘Hindi’,
‘bn’: ‘Bengali’,
‘te’: ‘Telugu’,
‘ta’: ‘Tamil’
};

// Load available voices
function loadVoices() {
voices = synth.getVoices();
voiceSelect.innerHTML = ”;

const currentLang = languageSelect.value;
const langCode = currentLang.split(‘-‘)[0]; // Get primary language code (e.g., ‘en’ from ‘en-US’)

// Group voices by language
const voicesByLang = {};

voices.forEach(voice => {
const voiceLangCode = voice.lang.split(‘-‘)[0];
if (!voicesByLang[voiceLangCode]) {
voicesByLang[voiceLangCode] = [];
}
voicesByLang[voiceLangCode].push(voice);
});

// First add voices for the selected language
if (voicesByLang[langCode] && voicesByLang[langCode].length > 0) {
const langName = languageNames[langCode] || langCode.toUpperCase();
const langGroup = document.createElement(‘optgroup’);
langGroup.label = `${langName} Voices`;

voicesByLang[langCode].forEach(voice => {
const option = document.createElement(‘option’);
option.textContent = `${voice.name}`;
option.value = voices.indexOf(voice);
langGroup.appendChild(option);
});

voiceSelect.appendChild(langGroup);
} else {
// If no voices for selected language, show a message
const option = document.createElement(‘option’);
option.textContent = `No specific ${currentLang} voices found`;
option.value = ‘default’;
option.disabled = true;
voiceSelect.appendChild(option);
}

// Then add other language voices
Object.keys(voicesByLang).forEach(code => {
if (code !== langCode) {
const langName = languageNames[code] || code.toUpperCase();
const langGroup = document.createElement(‘optgroup’);
langGroup.label = `${langName} Voices`;

voicesByLang[code].forEach(voice => {
const option = document.createElement(‘option’);
option.textContent = `${voice.name}`;
option.value = voices.indexOf(voice);
langGroup.appendChild(option);
});

voiceSelect.appendChild(langGroup);
}
});

// Select first voice if available
if (voiceSelect.options.length > 0 && voiceSelect.options[0].value !== ‘default’) {
voiceSelect.selectedIndex = 0;
}
}

// On language change, update available voices
languageSelect.addEventListener(‘change’, loadVoices);

// Initialize voice list when available
if (synth.onvoiceschanged !== undefined) {
synth.onvoiceschanged = loadVoices;
} else {
// For browsers that don’t fire onvoiceschanged
setTimeout(loadVoices, 1000);
}

// Update sliders value display
rateSlider.addEventListener(‘input’, () => {
rateValue.textContent = rateSlider.value;
});

pitchSlider.addEventListener(‘input’, () => {
pitchValue.textContent = pitchSlider.value;
});

// Update character count
textInput.addEventListener(‘input’, () => {
const text = textInput.value;
charCount.textContent = text.length;

// Limit to 5000 characters
if (text.length > 5000) {
textInput.value = text.substring(0, 5000);
charCount.textContent = 5000;
}
});

// Play button functionality
playBtn.addEventListener(‘click’, () => {
// Stop any currently playing speech
if (synth.speaking) {
synth.cancel();
}

const text = textInput.value.trim();
if (!text) {
alert(‘Please enter some text to convert to speech.’);
return;
}

// Create speech utterance
const utterance = new SpeechSynthesisUtterance(text);

// Set selected voice
if (voices.length && voiceSelect.value !== ‘loading’ && voiceSelect.value !== ‘default’) {
utterance.voice = voices[parseInt(voiceSelect.value)];
}

// Set speech properties
utterance.rate = parseFloat(rateSlider.value);
utterance.pitch = parseFloat(pitchSlider.value);
utterance.lang = languageSelect.value;

// Store current speech
currentSpeech = utterance;

// Set event handlers
utterance.onstart = () => {
playBtn.disabled = true;
pauseBtn.disabled = false;
stopBtn.disabled = false;
playBtn.innerHTML = ‘▶ Playing…’;
};

utterance.onend = () => {
resetPlayButtons();
};

utterance.onerror = (e) => {
console.error(‘Speech synthesis error:’, e);
resetPlayButtons();
alert(‘Error playing speech. Please try again.’);
};

// Start speaking
synth.speak(utterance);
});

// Pause button functionality
pauseBtn.addEventListener(‘click’, () => {
if (synth.speaking && !synth.paused) {
synth.pause();
pauseBtn.innerHTML = ‘▶ Resume’;
} else if (synth.paused) {
synth.resume();
pauseBtn.innerHTML = ‘⏸ Pause’;
}
});

// Stop button functionality
stopBtn.addEventListener(‘click’, () => {
if (synth.speaking) {
synth.cancel();
resetPlayButtons();
}
});

// Reset play button states
function resetPlayButtons() {
playBtn.disabled = false;
pauseBtn.disabled = true;
stopBtn.disabled = true;
playBtn.innerHTML = ‘▶ Preview’;
pauseBtn.innerHTML = ‘⏸ Pause’;
}

// Download as MP3 functionality
downloadBtn.addEventListener(‘click’, async () => {
const text = textInput.value.trim();
if (!text) {
alert(‘Please enter some text to convert to speech.’);
return;
}

downloadBtn.disabled = true;
downloadLoader.style.display = ‘inline-block’;

try {
// Use Web Speech API to generate audio
// Note: This is a simplified approach. In a production app,
// you might want to use a dedicated TTS API for better quality.
await generateAndDownloadAudio(text);
} catch (error) {
console.error(‘Error generating audio:’, error);
alert(‘Error generating audio. Please try again.’);
} finally {
downloadBtn.disabled = false;
downloadLoader.style.display = ‘none’;
}
});

// Generate audio using MediaRecorder API (note: this is a workaround)
async function generateAndDownloadAudio(text) {
return new Promise((resolve, reject) => {
// Create an audio context
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const mediaStreamDestination = audioContext.createMediaStreamDestination();

// Create a media recorder to capture the audio
const mediaRecorder = new MediaRecorder(mediaStreamDestination.stream);
const audioChunks = [];

// Set up the recorder
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};

mediaRecorder.onstop = () => {
// Create blob from audio chunks
audioBlob = new Blob(audioChunks, { type: ‘audio/mp3’ });

// Create download link
const url = URL.createObjectURL(audioBlob);
audioElement.src = url;
audioPlayer.style.display = ‘block’;

// Trigger download
const a = document.createElement(‘a’);
a.href = url;
a.download = ‘voicegen-speech.mp3’;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);

resolve();
};

// Start recording (this is just to create an MP3 container – the actual speech is generated by Web Speech API)
mediaRecorder.start();

// Generate speech with Web Speech API
const utterance = new SpeechSynthesisUtterance(text);

// Set selected voice
if (voices.length && voiceSelect.value !== ‘loading’ && voiceSelect.value !== ‘default’) {
utterance.voice = voices[parseInt(voiceSelect.value)];
}

// Set speech properties
utterance.rate = parseFloat(rateSlider.value);
utterance.pitch = parseFloat(pitchSlider.value);
utterance.lang = languageSelect.value;

utterance.onend = () => {
// Stop recording when speech ends
oscillator.connect(mediaStreamDestination);
oscillator.start();
setTimeout(() => {
oscillator.stop();
mediaRecorder.stop();
}, 100);
};

utterance.onerror = (e) => {
console.error(‘Speech synthesis error:’, e);
reject(e);
};

// Start speaking
synth.speak(utterance);
});
}