Загрузка данных


<!DOCTYPE html>
<html>
<head>
    <style>
        body { 
            margin: 0; 
            background: transparent;
            overflow: hidden;
        }
        canvas { 
            display: block;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

canvas.width = 1920;
canvas.height = 1080;

let audioContext, analyser, dataArray, bufferLength;
let points = [];
const numPoints = 100; // Количество точек волны
const centerY = canvas.height / 2;

// Инициализация точек
for (let i = 0; i < numPoints; i++) {
    points.push({
        x: (canvas.width / (numPoints - 1)) * i,
        y: centerY,
        targetY: centerY,
        velocity: 0
    });
}

// Захват аудио
navigator.mediaDevices.getUserMedia({ audio: true })
    .then(stream => {
        audioContext = new AudioContext();
        analyser = audioContext.createAnalyser();
        const source = audioContext.createMediaStreamSource(stream);
        
        source.connect(analyser);
        analyser.fftSize = 512;
        analyser.smoothingTimeConstant = 0.85;
        
        bufferLength = analyser.frequencyBinCount;
        dataArray = new Uint8Array(bufferLength);
        
        animate();
    })
    .catch(err => {
        console.error('Ошибка доступа к микрофону:', err);
        // Анимация без звука для теста
        animate();
    });

function animate() {
    requestAnimationFrame(animate);
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    if (analyser) {
        analyser.getByteFrequencyData(dataArray);
    }
    
    // Обновление целевых позиций точек на основе аудио
    for (let i = 0; i < numPoints; i++) {
        const point = points[i];
        
        if (analyser) {
            // Берем данные из разных частот для разных точек
            const dataIndex = Math.floor((i / numPoints) * bufferLength);
            const amplitude = dataArray[dataIndex] || 0;
            
            // Нормализуем амплитуду (0-255 -> высота волны)
            const normalizedAmplitude = (amplitude / 255) * 300;
            
            // Добавляем синусоидальное движение для плавности
            const wave = Math.sin(i * 0.1 + Date.now() * 0.003) * 20;
            
            point.targetY = centerY + (Math.random() * normalizedAmplitude - normalizedAmplitude / 2) + wave;
        } else {
            // Тестовая анимация без звука
            const wave = Math.sin(i * 0.2 + Date.now() * 0.002) * 50;
            point.targetY = centerY + wave;
        }
        
        // Плавное движение к целевой позиции (пружинная физика)
        const dy = point.targetY - point.y;
        point.velocity += dy * 0.015; // Жесткость пружины
        point.velocity *= 0.85; // Затухание
        point.y += point.velocity;
    }
    
    // Рисование волны
    drawWave();
    
    // Рисование отражения (опционально)
    drawReflection();
}

function drawWave() {
    ctx.save();
    
    // Основная волна
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);
    
    // Используем кривые Безье для плавности
    for (let i = 0; i < points.length - 1; i++) {
        const point = points[i];
        const nextPoint = points[i + 1];
        
        const controlX = point.x + (nextPoint.x - point.x) / 2;
        const controlY1 = point.y;
        const controlY2 = nextPoint.y;
        
        ctx.quadraticCurveTo(controlX, controlY1, nextPoint.x, nextPoint.y);
    }
    
    // Стиль линии
    ctx.strokeStyle = '#FFDB4D'; // Желтый как в Яндекс.Музыке
    ctx.lineWidth = 4;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    
    // Свечение
    ctx.shadowBlur = 15;
    ctx.shadowColor = '#FFDB4D';
    
    ctx.stroke();
    
    // Заливка под волной (опционально)
    ctx.lineTo(canvas.width, canvas.height);
    ctx.lineTo(0, canvas.height);
    ctx.closePath();
    
    const gradient = ctx.createLinearGradient(0, centerY - 150, 0, canvas.height);
    gradient.addColorStop(0, 'rgba(255, 219, 77, 0.3)');
    gradient.addColorStop(1, 'rgba(255, 219, 77, 0)');
    ctx.fillStyle = gradient;
    ctx.fill();
    
    ctx.restore();
}

function drawReflection() {
    ctx.save();
    
    ctx.globalAlpha = 0.3;
    ctx.scale(1, -1);
    ctx.translate(0, -canvas.height);
    
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);
    
    for (let i = 0; i < points.length - 1; i++) {
        const point = points[i];
        const nextPoint = points[i + 1];
        
        const controlX = point.x + (nextPoint.x - point.x) / 2;
        const controlY1 = point.y;
        
        ctx.quadraticCurveTo(controlX, controlY1, nextPoint.x, nextPoint.y);
    }
    
    ctx.strokeStyle = '#FFDB4D';
    ctx.lineWidth = 4;
    ctx.lineCap = 'round';
    ctx.stroke();
    
    ctx.restore();
}

// Адаптация под размер окна OBS
window.addEventListener('resize', () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    
    // Пересчет позиций точек
    for (let i = 0; i < numPoints; i++) {
        points[i].x = (canvas.width / (numPoints - 1)) * i;
    }
});
</script>
</body>
</html>