// Основной класс приложения class DrumTrainerApp { constructor() { this.isPlaying = false; this.currentTempo = 120; this.currentPattern = []; this.currentLesson = null; this.favorites = JSON.parse(localStorage.getItem('drumFavorites')) || []; this.savedPatterns = JSON.parse(localStorage.getItem('drumPatterns')) || []; this.drumSounds = {}; this.metronomeSound = null; this.isMetronomeOn = true; this.isLooping = true; this.beatIndex = 0; this.totalBeats = 16; this.intervalId = null; this.initializeApp(); } async initializeApp() { // Инициализация звуков await this.initSounds(); // Загрузка интерфейса this.initUI(); this.loadLessons(); this.loadPatterns(); this.loadFavorites(); // Скрытие экрана загрузки setTimeout(() => { document.getElementById('loadScreen').style.opacity = '0'; setTimeout(() => { document.getElementById('loadScreen').style.display = 'none'; }, 500); }, 1500); // Проверка ориентации экрана this.checkOrientation(); } async initSounds() { // Инициализация Tone.js await Tone.start(); // Создание звуков барабанов const sounds = { 'kick': new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 10, oscillator: { type: 'sine' }, envelope: { attack: 0.001, decay: 0.4, sustain: 0.01, release: 1.4 } }).toDestination(), 'snare': new Tone.NoiseSynth({ noise: { type: 'white' }, envelope: { attack: 0.001, decay: 0.2, sustain: 0 } }).toDestination(), 'hihat': new Tone.MetalSynth({ frequency: 200, envelope: { attack: 0.001, decay: 0.1 }, harmonicity: 5.1, modulationIndex: 32, resonance: 4000, octaves: 1.5 }).toDestination(), 'crash': new Tone.MetalSynth({ frequency: 300, envelope: { attack: 0.001, decay: 0.5 }, harmonicity: 5.1, modulationIndex: 32, resonance: 4000, octaves: 1.5 }).toDestination(), 'tom1': new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 8, oscillator: { type: 'sine' }, envelope: { attack: 0.001, decay: 0.5 } }).toDestination(), 'tom2': new Tone.MembraneSynth({ pitchDecay: 0.05, octaves: 6, oscillator: { type: 'sine' }, envelope: { attack: 0.001, decay: 0.5 } }).toDestination() }; this.drumSounds = sounds; // Создание звука метронома this.metronomeSound = new Tone.Synth({ oscillator: { type: 'sine' } }).toDestination(); } initUI() { // Инициализация элементов управления this.initControls(); this.initPatternDisplay(); this.initEditor(); this.initEventListeners(); // Инициализация Canvas this.initCanvas(); } initControls() { // Связывание кнопок document.getElementById('playBtn').addEventListener('click', () => this.togglePlay()); document.getElementById('stopBtn').addEventListener('click', () => this.stop()); document.getElementById('prevBtn').addEventListener('click', () => this.prevPattern()); document.getElementById('nextBtn').addEventListener('click', () => this.nextPattern()); // Слайдеры document.getElementById('tempoSlider').addEventListener('input', (e) => { this.currentTempo = parseInt(e.target.value); document.getElementById('tempoValue').textContent = this.currentTempo; document.getElementById('tempoDisplay').textContent = `${this.currentTempo} BPM`; if (this.isPlaying) { this.stop(); this.start(); } }); // Барабанные пады document.querySelectorAll('.drum-pad').forEach(pad => { pad.addEventListener('click', () => { const sound = pad.dataset.sound; this.playSound(sound); this.animatePad(pad); }); }); // Клавиши для быстрой игры document.addEventListener('keydown', (e) => { const keyMap = { '1': 'kick', '2': 'snare', '3': 'hihat', '4': 'crash', '5': 'tom1', '6': 'tom2' }; if (keyMap[e.key]) { this.playSound(keyMap[e.key]); const pad = document.querySelector(`[data-sound="${keyMap[e.key]}"]`); if (pad) this.animatePad(pad); } // Пробел для play/pause if (e.code === 'Space') { e.preventDefault(); this.togglePlay(); } }); } initPatternDisplay() { const container = document.getElementById('patternDisplay'); container.innerHTML = ''; for (let i = 0; i < this.totalBeats; i++) { const cell = document.createElement('div'); cell.className = 'beat-cell'; cell.dataset.index = i; cell.addEventListener('click', () => this.toggleBeat(i)); container.appendChild(cell); } } initEditor() { const grid = document.getElementById('editorGrid'); grid.innerHTML = ''; // Заголовки const instruments = ['Kick', 'Snare', 'Hi-Hat', 'Crash', 'Tom 1', 'Tom 2']; // Создание сетки for (let row = 0; row < instruments.length; row++) { // Название инструмента const header = document.createElement('div'); header.className = 'grid-header'; header.textContent = instruments[row]; grid.appendChild(header); // Ячейки для каждого бита for (let col = 0; col < 16; col++) { const cell = document.createElement('div'); cell.className = 'grid-cell'; cell.dataset.row = row; cell.dataset.col = col; cell.addEventListener('click', () => { cell.classList.toggle('active'); this.updatePatternFromEditor(); }); grid.appendChild(cell); } } } initEventListeners() { // Навигация document.getElementById('homeBtn').addEventListener('click', () => this.showLessons()); document.querySelectorAll('.level-btn').forEach(btn => { btn.addEventListener('click', () => this.showLevel(btn.dataset.level)); }); document.getElementById('sandboxBtn').addEventListener('click', () => this.showSandbox()); document.getElementById('favoritesBtn').addEventListener('click', () => this.showFavorites()); document.getElementById('helpBtn').addEventListener('click', () => this.showHelp()); // Дополнительные кнопки document.getElementById('metronomeBtn').addEventListener('click', () => { this.isMetronomeOn = !this.isMetronomeOn; document.getElementById('metronomeBtn').textContent = `Метроном: ${this.isMetronomeOn ? 'ВКЛ' : 'ВЫКЛ'}`; }); document.getElementById('loopBtn').addEventListener('click', () => { this.isLooping = !this.isLooping; document.getElementById('loopBtn').textContent = `Повтор: ${this.isLooping ? 'ВКЛ' : 'ВЫКЛ'}`; }); // Кнопки редактора document.getElementById('clearBtn').addEventListener('click', () => this.clearEditor()); document.getElementById('savePatternBtn').addEventListener('click', () => this.saveCurrentPattern()); } initCanvas() { this.canvas = document.getElementById('drumCanvas'); this.ctx = this.canvas.getContext('2d'); this.animationFrame = null; } loadLessons() { // Примеры уроков const lessons = { easy: [ { name: 'Базовый рок-ритм', pattern: '1000100010001000', description: 'Основной ритм для рок-музыки' }, { name: 'Базовый фанк', pattern: '1010101010101010', description: 'Простой фанковый грув' }, { name: '4/4 с акцентами', pattern: '1000101010001010', description: 'Ритм с акцентами на слабые доли' } ], medium: [ { name: 'Синкопированный ритм', pattern: '1001001001001001', description: 'Ритм со смещенными акцентами' }, { name: 'Шаффл', pattern: '1011011011011011', description: 'Типичный шаффл-паттерн' }, { name: 'Латинский ритм', pattern: '1100110011001100', description: 'Основной латинский грув' } ], hard: [ { name: 'Двойные удары', pattern: '1111111111111111', description: 'Быстрые двойные удары' }, { name: 'Сложная синкопа', pattern: '1010010110100101', description: 'Сложный синкопированный ритм' }, { name: 'Полиритмия 3:2', pattern: '1001001001001001', description: 'Полиритмический паттерн' } ] }; // Загрузка уроков в интерфейс for (const level in lessons) { const container = document.getElementById(`${level}Lessons`); lessons[level].forEach((lesson, index) => { const lessonEl = document.createElement('div'); lessonEl.className = 'lesson-item'; lessonEl.innerHTML = `
${lesson.description}
`; lessonEl.addEventListener('click', () => this.loadLesson(level, index)); container.appendChild(lessonEl); }); } } loadPatterns() { // Загрузка паттернов из localStorage if (this.savedPatterns.length > 0) { const sandbox = document.getElementById('sandboxContainer'); const patternsList = document.createElement('div'); patternsList.className = 'saved-patterns'; patternsList.innerHTML = '${fav.description || 'Нет описания'}
`; favEl.querySelector('.remove-fav').addEventListener('click', (e) => { e.stopPropagation(); this.removeFromFavorites(index); }); favEl.addEventListener('click', () => this.loadFavorite(index)); container.appendChild(favEl); }); } togglePlay() { if (this.isPlaying) { this.stop(); } else { this.start(); } } start() { if (this.isPlaying) return; this.isPlaying = true; document.getElementById('playBtn').textContent = '⏸️'; const beatDuration = 60000 / this.currentTempo / 4; // Длительность 16-й ноты this.beatIndex = 0; this.intervalId = setInterval(() => { this.playBeat(); this.updateVisualization(); this.beatIndex = (this.beatIndex + 1) % this.totalBeats; if (this.beatIndex === 0 && !this.isLooping) { this.stop(); } }, beatDuration); } stop() { if (!this.isPlaying) return; this.isPlaying = false; document.getElementById('playBtn').textContent = '▶️'; if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } this.beatIndex = 0; this.updateProgress(0); } playBeat() { // Воспроизведение метронома if (this.isMetronomeOn) { if (this.beatIndex % 4 === 0) { // Акцент на первой доле this.metronomeSound.triggerAttackRelease('C5', '8n'); } else if (this.beatIndex % 2 === 0) { // Остальные сильные доли this.metronomeSound.triggerAttackRelease('C4', '8n'); } } // Обновление визуализации this.updatePatternDisplay(); this.updateCanvas(); } playSound(sound) { if (this.drumSounds[sound]) { this.drumSounds[sound].triggerAttackRelease('C2', '8n'); } } updateVisualization() { // Обновление прогресс-бара const progress = (this.beatIndex / this.totalBeats) * 100; this.updateProgress(progress); } updateProgress(percent) { document.getElementById('progressBar').style.width = `${percent}%`; } updatePatternDisplay() { document.querySelectorAll('.beat-cell').forEach((cell, index) => { if (index === this.beatIndex) { cell.style.transform = 'scale(1.2)'; cell.style.boxShadow = '0 0 10px #4ecdc4'; } else { cell.style.transform = 'scale(1)'; cell.style.boxShadow = 'none'; } }); } updateCanvas() { if (!this.ctx) return; this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // Отрисовка текущего бита const x = (this