A continuation of Janady13/Jennys-Drumste / drum-teacher-pro.html. The complete source identity, every recorded occurrence, and linked equations are on its first page.
Source transcript · continued
Download complete source text ↓1,802 lines · 61.3 KB
function playPattern(lesson) {
// Implementation for pattern playback
// This would coordinate with visual cues and timing checks
}
function toggleFreePlay() {
// Toggle free play mode
}
function toggleMetronome() {
// Toggle metronome
if (metronomeInterval) {
clearInterval(metronomeInterval);
metronomeInterval = null;
} else {
const interval = 60000 / bpm;
metronomeInterval = setInterval(playClick, interval);
}
}
function playClick() {
if (!audioContext || !clickEnabled) return;
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination);
osc.frequency.value = 1000;
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.05);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + 0.05);
}
function toggleClick() {
clickEnabled = !clickEnabled;
const toggle = document.getElementById('clickToggle');
toggle.classList.toggle('active', clickEnabled);
}
function updateTempo() {
bpm = parseInt(document.getElementById('tempoSlider').value);
document.getElementById('tempoDisplay').textContent = `${bpm} BPM`;
stats.maxBPM = Math.max(stats.maxBPM, bpm);
if (metronomeInterval) {
toggleMetronome();
toggleMetronome();
}
}
function toggleRecording() {
isRecording = !isRecording;
const btn = document.getElementById('recordBtn');
if (isRecording) {
recordedPattern = [];
btn.classList.add('recording');
btn.innerHTML = '⏹ Stop';
} else {
btn.classList.remove('recording');
btn.innerHTML = '⏺ Record';
document.getElementById('playbackBtn').disabled = recordedPattern.length === 0;
document.getElementById('saveBtn').disabled = recordedPattern.length === 0;
}
}
function playbackRecording() {
if (recordedPattern.length === 0) return;
const startTime = recordedPattern[0].time;
recordedPattern.forEach(hit => {
setTimeout(() => {
handleDrumHit(hit.note, hit.velocity);
}, hit.time - startTime);
});
}
function updateStats() {
document.getElementById('accuracy').textContent = stats.accuracy + '%';
document.getElementById('combo').textContent = stats.combo;
document.getElementById('score').textContent = stats.score.toLocaleString();
// Update level
const newLevel = Math.floor(stats.score / 1000) + 1;
if (newLevel > stats.level) {
stats.level = newLevel;
showLevelUp();
}
}
function showLevelUp() {
// Show level up animation
const modal = document.getElementById('modal');
document.getElementById('modalTitle').textContent = '🎉 Level Up!';
document.getElementById('modalBody').innerHTML = `
<p style="font-size: 1.5em; margin: 20px 0;">
You've reached <strong>Level ${stats.level}</strong>!
</p>
<p>Keep going to unlock more achievements!</p>
`;
modal.classList.add('active');
}
function startSessionTimer() {
setInterval(() => {
if (sessionStartTime) {
const elapsed = Math.floor((Date.now() - sessionStartTime) / 1000);
stats.sessionTime = elapsed;
const minutes = Math.floor(elapsed / 60);
const seconds = elapsed % 60;
document.getElementById('sessionTime').textContent =
`${minutes}:${seconds.toString().padStart(2, '0')}`;
checkAchievements();
}
}, 1000);
}
function checkAchievements() {
ACHIEVEMENTS.forEach(achievement => {
if (!stats.unlockedAchievements.has(achievement.id) && achievement.condition()) {
unlockAchievement(achievement);
}
});
}
function unlockAchievement(achievement) {
stats.unlockedAchievements.add(achievement.id);
const elem = document.querySelector(`.achievement[data-id="${achievement.id}"]`);
if (elem) {
elem.classList.add('unlocked');
showAchievementNotification(achievement);
}
}
function showAchievementNotification(achievement) {
// Show achievement popup
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: var(--dark);
padding: 20px;
border-radius: 10px;
font-weight: bold;
animation: slideIn 0.5s;
z-index: 2000;
`;
notification.innerHTML = `
<div>${achievement.icon} Achievement Unlocked!</div>
<div>${achievement.name}</div>
`;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
function showSessionResults() {
const modal = document.getElementById('modal');
document.getElementById('modalTitle').textContent = '📊 Session Complete!';
document.getElementById('modalBody').innerHTML = `
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin: 20px 0;">
<div>
<div style="color: var(--primary); font-size: 0.9em;">Accuracy</div>
<div style="font-size: 2em; font-weight: bold;">${stats.accuracy}%</div>
</div>
<div>
<div style="color: var(--primary); font-size: 0.9em;">Best Combo</div>
<div style="font-size: 2em; font-weight: bold;">${stats.maxCombo}</div>
</div>
<div>
<div style="color: var(--primary); font-size: 0.9em;">Score</div>
<div style="font-size: 2em; font-weight: bold;">${stats.score.toLocaleString()}</div>
</div>
<div>
<div style="color: var(--primary); font-size: 0.9em;">Perfect Hits</div>
<div style="font-size: 2em; font-weight: bold;">${stats.perfectHits}</div>
</div>
</div>
<p style="margin-top: 20px;">
${stats.accuracy >= 90 ? '🏆 Outstanding performance!' :
stats.accuracy >= 70 ? '👏 Great job! Keep practicing!' :
'💪 Good effort! Try again to improve!'}
</p>
`;
modal.classList.add('active');
}
function initPerformanceGraph() {
const canvas = document.getElementById('performanceGraph');
const ctx = canvas.getContext('2d');
function draw() {
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight;
ctx.clearRect(0, 0, width, height);
// Draw grid
ctx.strokeStyle = 'rgba(0, 255, 204, 0.1)';
ctx.lineWidth = 1;
for (let i = 0; i <= 4; i++) {
const y = (height / 4) * i;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
// Draw performance line
if (performanceHistory.length > 1) {
ctx.strokeStyle = 'rgba(0, 255, 204, 0.8)';
ctx.lineWidth = 2;
ctx.beginPath();
performanceHistory.forEach((value, index) => {
const x = (width / (performanceHistory.length - 1)) * index;
const y = height - (height * value / 100);
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
// Fill area under line
ctx.lineTo(width, height);
ctx.lineTo(0, height);
ctx.closePath();
ctx.fillStyle = 'rgba(0, 255, 204, 0.1)';
ctx.fill();
}
requestAnimationFrame(draw);
}
draw();
}
function playSuccessAnimation() {
// Success animation when connected
const particles = document.querySelectorAll('.particle');
particles.forEach((particle, index) => {
setTimeout(() => {
particle.style.background = 'var(--success)';
setTimeout(() => {
particle.style.background = 'var(--primary)';
}, 500);
}, index * 20);
});
}
function handleKeyboard(e) {
const keyMap = {
' ': 36, // Space: Kick
's': 38, // S: Snare
'h': 46, // H: Hi-hat
'c': 49, // C: Crash
'r': 51, // R: Ride
'1': 48, // 1: Tom 1
'2': 45, // 2: Tom 2
'3': 43, // 3: Tom 3
'4': 41 // 4: Floor Tom
};
const note = keyMap[e.key.toLowerCase()];
if (note) {
e.preventDefault();
handleDrumHit(note, 100);
}
}
function closeModal() {
document.getElementById('modal').classList.remove('active');
}
// Initialize combo bars animation
setInterval(() => {
const bars = document.getElementById('comboBars');
bars.innerHTML = '';
for (let i = 0; i < 10; i++) {
const bar = document.createElement('div');
bar.className = 'mini-bar';
const height = i < stats.combo ? 100 : Math.random() * 30;
bar.style.height = height + '%';
bars.appendChild(bar);
}
}, 500);
</script>
</body>
</html>