-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Speech to Numbers</title> | ||
<style> | ||
body { | ||
font-family: Arial, sans-serif; | ||
text-align: center; | ||
margin: 0; | ||
padding: 0; | ||
background-color: #f4f4f9; | ||
color: #333; | ||
} | ||
|
||
header { | ||
background-color: #6200ea; | ||
color: white; | ||
padding: 1rem; | ||
margin-bottom: 20px; | ||
} | ||
|
||
h1 { | ||
margin: 0; | ||
} | ||
|
||
#last-spoken { | ||
font-size: 1.5rem; | ||
margin-top: 10px; | ||
} | ||
|
||
#numbers { | ||
font-size: 2rem; | ||
margin-top: 20px; | ||
} | ||
|
||
#ball-container { | ||
display: flex; | ||
flex-wrap: wrap; | ||
justify-content: center; | ||
margin-top: 20px; | ||
} | ||
|
||
.ball { | ||
width: 50px; | ||
height: 50px; | ||
border-radius: 50%; | ||
margin: 5px; | ||
} | ||
|
||
button { | ||
background-color: #6200ea; | ||
color: white; | ||
border: none; | ||
padding: 10px 20px; | ||
font-size: 1rem; | ||
cursor: pointer; | ||
border-radius: 5px; | ||
margin-top: 20px; | ||
} | ||
|
||
button:disabled { | ||
background-color: #ccc; | ||
cursor: not-allowed; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<header> | ||
<h1>Speech to Numbers</h1> | ||
</header> | ||
<main> | ||
<p>Click the button below and start speaking. Any numbers you say will appear here:</p> | ||
<div id="last-spoken">Last spoken: None</div> | ||
<div id="numbers">Last number: No numbers detected yet.</div> | ||
<div id="ball-container"></div> | ||
<button id="start-btn">Start Listening</button> | ||
</main> | ||
<script> | ||
const startButton = document.getElementById('start-btn'); | ||
const lastSpokenDiv = document.getElementById('last-spoken'); | ||
const numbersDiv = document.getElementById('numbers'); | ||
const ballContainer = document.getElementById('ball-container'); | ||
|
||
const numberWords = { | ||
one: 1, | ||
two: 2, | ||
three: 3, | ||
four: 4, | ||
five: 5, | ||
six: 6, | ||
seven: 7, | ||
eight: 8, | ||
nine: 9, | ||
ten: 10 | ||
}; | ||
|
||
let ballCounts = []; | ||
let lastProcessedResult = ""; // Store last processed result to avoid duplicates | ||
let isRecognitionStopped = false; | ||
let isAnswerPhase = false; // Toggle to differentiate between normal and answer phases | ||
|
||
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) { | ||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | ||
const recognition = new SpeechRecognition(); | ||
|
||
recognition.lang = 'en-US'; | ||
recognition.interimResults = true; | ||
recognition.continuous = true; | ||
|
||
let isListening = false; | ||
|
||
startButton.addEventListener('click', () => { | ||
if (isListening) { | ||
recognition.stop(); | ||
startButton.textContent = 'Start Listening'; | ||
isListening = false; | ||
} else { | ||
try { | ||
lastProcessedResult = ""; // Reset processed result tracking | ||
recognition.start(); | ||
startButton.textContent = 'Listening... Click to Stop'; | ||
isListening = true; | ||
} catch (error) { | ||
console.error('Error starting recognition:', error); | ||
alert('Please allow microphone permissions and reload the page.'); | ||
} | ||
} | ||
}); | ||
|
||
recognition.onresult = (event) => { | ||
let interimTranscript = ""; | ||
|
||
for (let i = event.resultIndex; i < event.results.length; i++) { | ||
const result = event.results[i]; | ||
const transcript = result[0].transcript.toLowerCase(); | ||
|
||
// Update "Last spoken" regardless of phase | ||
updateLastSpoken(transcript, result.isFinal); | ||
|
||
if (result.isFinal) { | ||
// Handle either normal or answer phase | ||
if (!isAnswerPhase) { | ||
if (transcript !== lastProcessedResult) { | ||
lastProcessedResult = transcript; // Prevent duplicates | ||
processTranscript(transcript); | ||
} | ||
} else { | ||
handleAnswer(transcript); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
function updateLastSpoken(text, isFinal) { | ||
const prefix = isFinal ? "Last spoken" : "Last spoken (interim)"; | ||
lastSpokenDiv.textContent = `${prefix}: ${text}`; | ||
} | ||
|
||
function processTranscript(transcript) { | ||
// Convert number words to digits | ||
transcript = transcript.replace(/\b(one|two|three|four|five|six|seven|eight|nine|ten)\b/g, (match) => { | ||
return numberWords[match]; | ||
}); | ||
|
||
const matches = transcript.match(/\b\d+\b/g); | ||
|
||
if (matches) { | ||
const latestNumber = matches[matches.length - 1]; | ||
numbersDiv.textContent = `Last number: ${latestNumber}`; | ||
const count = parseInt(latestNumber); | ||
|
||
if (!isNaN(count) && count > 0 && ballCounts.length < 2) { | ||
ballCounts.push(count); | ||
addBalls(count); | ||
|
||
if (ballCounts.length === 2) { | ||
setTimeout(() => askQuestion(), 500); | ||
} | ||
} | ||
} | ||
} | ||
|
||
function addBalls(count) { | ||
const colors = ['#ff5733', '#33ff57', '#3357ff', '#ff33a8', '#a833ff', '#33fff3', '#ffa733']; | ||
const color = colors[Math.floor(Math.random() * colors.length)]; | ||
|
||
for (let i = 0; i < count; i++) { | ||
const ball = document.createElement('div'); | ||
ball.className = 'ball'; | ||
ball.style.backgroundColor = color; | ||
ballContainer.appendChild(ball); | ||
} | ||
} | ||
|
||
function askQuestion() { | ||
const total = ballCounts.reduce((a, b) => a + b, 0); | ||
const question = `How many balls are there in total?`; | ||
|
||
const synth = window.speechSynthesis; | ||
const utterance = new SpeechSynthesisUtterance(question); | ||
|
||
isAnswerPhase = true; // Enter answer phase | ||
isRecognitionStopped = true; | ||
recognition.stop(); | ||
synth.speak(utterance); | ||
|
||
utterance.onend = () => { | ||
isRecognitionStopped = false; | ||
recognition.start(); | ||
}; | ||
} | ||
|
||
function handleAnswer(transcript) { | ||
const total = ballCounts.reduce((a, b) => a + b, 0); | ||
const answer = parseInt(transcript); | ||
const synth = window.speechSynthesis; | ||
|
||
if (!isNaN(answer)) { | ||
if (answer === total) { | ||
const congrats = `Good job! You figured out that ${ballCounts[0]} plus ${ballCounts[1]} is ${total}.`; | ||
const congratsUtterance = new SpeechSynthesisUtterance(congrats); | ||
synth.speak(congratsUtterance); | ||
congratsUtterance.onend = () => { | ||
resetToListeningMode(); | ||
}; | ||
} else { | ||
const hint = `Not quite! Here's a hint: ${ballCounts[0]} plus ${ballCounts[1]} equals ?`; | ||
const hintUtterance = new SpeechSynthesisUtterance(hint); | ||
synth.speak(hintUtterance); | ||
} | ||
} | ||
} | ||
|
||
function resetToListeningMode() { | ||
ballCounts = []; | ||
isAnswerPhase = false; | ||
lastProcessedResult = ""; // Reset processed results | ||
} | ||
|
||
recognition.onerror = (event) => { | ||
console.error('Speech recognition error:', event.error); | ||
alert('Error with speech recognition. Please reload the page.'); | ||
isListening = false; | ||
startButton.textContent = 'Start Listening'; | ||
}; | ||
|
||
recognition.onend = () => { | ||
if (isListening && !isRecognitionStopped) { | ||
recognition.start(); | ||
} else { | ||
startButton.textContent = 'Start Listening'; | ||
} | ||
}; | ||
} else { | ||
alert('Sorry, your browser does not support the Web Speech API. Please use a modern browser like Chrome or Edge.'); | ||
startButton.disabled = true; | ||
} | ||
</script> | ||
</body> | ||
</html> |