Аудиовизуализатор/анализатор на чистом JavaScript: проблема или, возможно, CORS

Вопрос или проблема

Итак, я работаю над приложением, которое сравнивает вокал в стиле хэви-метал от популярных артистов (основная частота, тональность, децибелы и всё такое). Однако мой аудиозаписывающий устройство работает (что было одним из самых сложных), но мой аудиовизуализатор (canvas) и аудиоэлемент слева, загружаемый с помощью src=””, но я думаю, что что-то в моём JS вызывает проблемы, потому что сначала всё работало. Проблемы начались, когда я начал играть с гармониками, децибелами, тональностью, высотой звука и т. д. И это после того, как я забыл сохранить более работающую версию кода. Теперь я совершенно потерян. Также я запускаю сервер apache с XAMPP. Если у вас есть какие-либо вопросы о моем коде или о том, что я пытаюсь достичь, не стесняйтесь спрашивать. Также стоит упомянуть, что я сам написал половину кода, а для некоторых более технических вопросов использую chatGPT. Это также приводит к путанице в моем коде, поэтому я подумал, что снова зайду на этот сайт и посмотрю, смогу ли я получить помощь. Я очень увлечён звуковым инжинирингом и тяжёлой музыкой, поэтому мне действительно хотелось бы увидеть, как это приложение/сайт оживет, чтобы люди могли практиковать свои вокальные данные. Любая помощь была бы замечательна!!! Вот мой код.

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Аудио Визуализатор</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }

        @font-face {
            font-family: 'Deathcore';
            src: url('fonts/Faceless.ttf') format('truetype');
        }

        @font-face {
            font-family: 'Crunk';
            src: url('fonts/TheDark.ttf') format('truetype');
        }

        div#content-1 {
            display: block;
            width: 100%;
            height: 100%;
            background-color: #000000;
        }
        div#nav-bar {
            position: fixed;
            width: 100%;
            height: 50px;
            background-color: #131313;
            border-bottom: solid 1px #ff0000;
        }
        select {
            border-radius: 15px;
            border: 2px solid #ffffff;
            color: #ffffff;
            background-color: #000000;
            width: 175px;
            height: 36px;
            margin-top: 9px;
            margin-left: 20px;
        }
        img#profile-picture {
            margin-top: 30px;
            width: 350px;
            height: 300px;
            border: 1px solid #ffffff;
        }
        audio {
            height: 45px;
            border-radius: 0px;
            border: 2px solid #ffffff;
            background-color: #131313;
        }
        canvas {
            width: 48%;
            height: 135px;
            display: block;
            background-color: #000000;
        }
        #controls {
            position: absolute;
            bottom: 193px;
            right: 30px;
        }
        button {
            margin-left: 5px;
            padding: 10px;
            background-color: #ff0000;
            border: none;
            color: white;
            cursor: pointer;
            border-radius: 5px;
        }
        button:hover {
            background-color: #cc0000;
        }
        /* Новые стили для информационных div-ов */
        .info {
            font-family: Arial;
            position: absolute;
            width: 320px;
            height: 150px;
            background-color: #808080;
            color: #ffffff;
            padding: 10px;
            border-radius: 5px;
            font-size: 18px;
        }
        #info-artist {
            bottom: 235px;
            left: 30px;
        }
        #info-user {
            bottom: 235px;
            right: 30px;
        }

        div#bio {
            float: left;
        }
        div#name-info {
            margin-left: 20px;
            font-size: 30px;
            font-family: Deathcore;
            color: #ffffff;

        }
        div#band-info {
            margin-left: 28px;
            font-size: 20px;
            font-family: Arial;
            color: #ffffff;

        }
        div#lyrics {
            position: absolute;
            left: 500px;
            width: 930px;
            height: 350px;
            border-radius: 15px;
            background-color: #131313;
            color: #ffffff;
            font-family: Arial;
        }
    </style>
</head>
<body>
    <div id='content-1'>
        <div id='nav-bar'>
            <select id='vocal-style'>
                <option>Фрай</option>
                <option>Ложный Хор</option>
                <option>Гутуралы</option>
            </select>
            <select id='vocal-artist'>
                <option>Дэнни Уорсноп</option>
                <option>Алекс Террибл</option>
                <option>Cj McCreery</option>
            </select>
            <button id="generateSampleBtn">Сгенерировать Новый Пример</button>
            <button id="downloadSampleBtn">Скачать Пример</button>
        </div>
        <br>

        <div style="margin-top: 20px;margin-left: 10px;">
            <img id='profile-picture' src="https://stackoverflow.com/questions/79068504/profile_pics/CJ_McCreery.jpg">
        </div>
        <div id='bio'>
            <div id='name-info'>
                Алекс Террибл
            </div>
            <div id='band-info'>
                Группа: Slaughter to Prevail
            </div>
        </div>

        <div id="controls">
            <button id="recordBtn">Записать</button>
            <button id="stopBtn" disabled>Остановить</button>
            <button id="downloadBtn" disabled>Скачать</button>
            <button id="compareBtn" disabled>Сравнить Аудио</button>
        </div>
        <div style="position: absolute; bottom: 5px;width:100%;left: 30px;">
            <canvas style="margin-left: 932px; margin-bottom: -135px;" id='canvas-user'></canvas>
            <canvas style="margin-left: 2px;" id='canvas-artist'></canvas>
            <audio id='artist-audio' style="width:48%;" src="samples/false_chord/alex_terrible/sample-1.mp3" controls></audio>
            <audio id='user-audio' style="width:48%;" controls></audio>
        </div>

        <!-- Новые div-ы для отображения информации об аудио -->
        <div id="info-artist" class="info">
            <strong>Аудио Артиста:</strong>
            <div id="dB-artist">dB: </div>
            <div id="fundamental-artist">Основная Частота: </div>
            <div id="harmonics-artist">Гармоники: </div>
            <div id="pitch-artist">Высота Звука: </div>
            <div id="key-artist">Тональность: </div>
        </div>
        <div id="info-user" class="info">
            <strong>Аудио Пользователя:</strong>
            <div id="dB-user">dB: </div>
            <div id="fundamental-user">Основная Частота: </div>
            <div id="harmonics-user">Гармоники: </div>
            <div id="pitch-user">Высота Звука: </div>
            <div id="key-user">Тональность: </div>
        </div>
        <div id='lyrics'>
            Ад — это прямо здесь! Тебя оставили, тебя оставили! Ад — это прямо здесь! Королевство шторма и грома
        </div>
    </div>

    <script>
    window.onload = function () {
    // Весь твой JavaScript код здесь

    let isRecording = false; // Состояние записи
    let mediaRecorder;
    let recordedChunks = [];

    // Функциональность записи
    recordBtn.addEventListener('click', function () {
        recordedChunks = []; // Сброс записанных фрагментов
        navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
            mediaRecorder = new MediaRecorder(stream);
            mediaRecorder.start();
            isRecording = true;
            recordBtn.disabled = true;
            stopBtn.disabled = false;

            // Сохранение фрагментов аудиоданных
            mediaRecorder.ondataavailable = function (event) {
                recordedChunks.push(event.data);
            };

            // Обработка события остановки медиа рекордера
            mediaRecorder.onstop = function () {
                const blob = new Blob(recordedChunks, { type: 'audio/webm' });
                userAudio.src = URL.createObjectURL(blob);
                downloadBtn.disabled = false;
            };
        });
    });

    stopBtn.addEventListener('click', function () {
        if (isRecording) {
            mediaRecorder.stop();
            isRecording = false;
            recordBtn.disabled = false;
            stopBtn.disabled = true;
        }
    });

    // Функциональность загрузки
    downloadBtn.addEventListener('click', function () {
        const blob = new Blob(recordedChunks, { type: 'audio/webm' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.style.display = 'none';
        a.href = url;
        a.download = 'recording.webm';
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
    });

    const artistAudio = document.getElementById('artist-audio');
    const userAudio = document.getElementById('user-audio');
    const canvasArtist = document.getElementById('canvas-artist');
    const canvasUser = document.getElementById('canvas-user');
    const ctxArtist = canvasArtist.getContext('2d');
    const ctxUser = canvasUser.getContext('2d');

    // Установка размеров canvas на основе их разметки
    canvasArtist.width = canvasArtist.offsetWidth;
    canvasArtist.height = canvasArtist.offsetHeight;
    canvasUser.width = canvasUser.offsetWidth;
    canvasUser.height = canvasUser.offsetHeight;

    // Функция для вычисления и обновления информации об аудио
    function calculateAudioSpecs(analyser, dataArray, bufferLength, infoPrefix) {
        analyser.getByteFrequencyData(dataArray);

        let sum = 0;
        for (let i = 0; i < bufferLength; i++) {
            sum += dataArray[i] ** 2;
        }
        const rms = Math.sqrt(sum / bufferLength);
        const dB = 20 * Math.log10(rms / 255);
        document.getElementById(`dB-${infoPrefix}`).innerText = `dB: ${dB.toFixed(2)}`;

        let maxIndex = 0;
        let maxValue = -Infinity;
        for (let i = 0; i < bufferLength; i++) {
            if (dataArray[i] > maxValue) {
                maxValue = dataArray[i];
                maxIndex = i;
            }
        }
        const fundamentalFreq = maxIndex * analyser.context.sampleRate / analyser.fftSize;
        document.getElementById(`fundamental-${infoPrefix}`).innerText = `Основная Частота: ${fundamentalFreq.toFixed(2)} Гц`;

        const harmonics = maxValue / 255;
        document.getElementById(`harmonics-${infoPrefix}`).innerText = `Гармоники: ${harmonics.toFixed(2)}`;

        const pitch = fundamentalFreq;
        document.getElementById(`pitch-${infoPrefix}`).innerText = `Высота Звука: ${pitch.toFixed(2)} Гц`;

        const key = determineKey(fundamentalFreq);
        document.getElementById(`key-${infoPrefix}`).innerText = `Тональность: ${key}`;
    }

    // Функция для определения музыкальной тональности по частоте
    function determineKey(frequency) {
        const keys = [
            { note: "C", frequency: 261.63 },
            { note: "C#", frequency: 277.18 },
            { note: "D", frequency: 293.66 },
            { note: "D#", frequency: 311.13 },
            { note: "E", frequency: 329.63 },
            { note: "F", frequency: 349.23 },
            { note: "F#", frequency: 369.99 },
            { note: "G", frequency: 392.00 },
            { note: "G#", frequency: 415.30 },
            { note: "A", frequency: 440.00 },
            { note: "A#", frequency: 466.16 },
            { note: "B", frequency: 493.88 }
        ];

        let closestKey = keys[0].note;
        let closestFreqDiff = Math.abs(frequency - keys[0].frequency);

        keys.forEach(key => {
            const freqDiff = Math.abs(frequency - key.frequency);
            if (freqDiff < closestFreqDiff) {
                closestFreqDiff = freqDiff;
                closestKey = key.note;
            }
        });

        return closestKey;
    }

    // Настройка AudioContext для аудио Артиста
    const audioContextArtist = new (window.AudioContext || window.webkitAudioContext)();
    const analyserArtist = audioContextArtist.createAnalyser();
    const sourceArtist = audioContextArtist.createMediaElementSource(artistAudio);
    sourceArtist.connect(analyserArtist);
    analyserArtist.connect(audioContextArtist.destination);

    const bufferLengthArtist = analyserArtist.frequencyBinCount;
    const dataArrayArtist = new Uint8Array(bufferLengthArtist);
    analyserArtist.fftSize = 2048;

    // Настройка AudioContext для аудио Пользователя
    const audioContextUser = new (window.AudioContext || window.webkitAudioContext)();
    const analyserUser = audioContextUser.createAnalyser();
    const sourceUser = audioContextUser.createMediaElementSource(userAudio);
    sourceUser.connect(analyserUser);
    analyserUser.connect(audioContextUser.destination);

    const bufferLengthUser = analyserUser.frequencyBinCount;
    const dataArrayUser = new Uint8Array(bufferLengthUser);
    analyserUser.fftSize = 2048;

    // Функция для рисования визуализации и обновления информации об аудио
    function draw() {
        requestAnimationFrame(draw);

        // Очистка canvas для Артиста
        ctxArtist.clearRect(0, 0, canvasArtist.width, canvasArtist.height);
        analyserArtist.getByteFrequencyData(dataArrayArtist);

        ctxArtist.fillStyle="#ff0000";
        for (let i = 0; i < bufferLengthArtist; i++) {
            const barHeight = dataArrayArtist[i] / 2;
            ctxArtist.fillRect(i * 2, canvasArtist.height - barHeight, 1, barHeight);
        }

        // Очистка canvas для Пользователя
        ctxUser.clearRect(0, 0, canvasUser.width, canvasUser.height);
        analyserUser.getByteFrequencyData(dataArrayUser);

        ctxUser.fillStyle="#ff0000";
        for (let i = 0; i < bufferLengthUser; i++) {
            const barHeight = dataArrayUser[i] / 2;
            ctxUser.fillRect(i * 2, canvasUser.height - barHeight, 1, barHeight);
        }

        // Обновление информации об аудио для Артиста
        calculateAudioSpecs(analyserArtist, dataArrayArtist, bufferLengthArtist, 'artist');

        // Обновление информации об аудио для Пользователя
        calculateAudioSpecs(analyserUser, dataArrayUser, bufferLengthUser, 'user');
    }

    draw();
};

    </script>
</body>
</html>

Я пробовал разные подходы к загрузке аудиоэлемента, я пытался изменить заголовок cross-origin на своем сервере apache, я даже пытался использовать chatGPT, и теперь я здесь, и будем честными, я тоже не лучший программист, я лучше в области звукового инжиниринга и музыкального продюсирования, но подумал, что попробую.

Ответ или решение

Ваш вопрос связан с проблемами, которые могут возникнуть при разработке аудиовизуализатора/анализа звука с использованием Vanilla JavaScript и HTML5. Исходя из вашего описания, есть несколько моментов, которые стоит проверить и исправить.

Возможные проблемы и их решения:

  1. CORS и Загрузка Аудиофайлов:

    • Если вы сталкиваетесь с проблемами доступа к аудиофайлам из-за политики CORS, убедитесь, что ваш сервер Apache настроен для разрешения таких запросов. Для этого добавьте следующие строки в ваш файл конфигурации Apache (обычно httpd.conf или .htaccess):
      Header set Access-Control-Allow-Origin "*"
    • Это разрешит запросы из любых источников. Вы можете заменить * на конкретный домен, если требуется более строгая настройка.
  2. Ошибки в JavaScript:

    • Убедитесь, что все элементы, к которым вы обращаетесь через document.getElementById, корректно определены. Например, проверьте, правильно ли вы написали идентификаторы для кнопок и аудиодорожек.
    • Обратите внимание на ошибки синтаксиса. В вашем коде есть символы, которые могут вызывать проблемы (например, "<" и ">" вместо "<" и ">"). Убедитесь, что в коде JavaScript у вас действительно используются правильные символы.
  3. Анализ частот и визуализация:

    • Проверьте, что вы инициализируете AudioContext и AnalyserNode правильно. Убедитесь, что он активирован до начала обработки звука.
    • Убедитесь, что ваши функции для получения данных частоты и их визуализации корректны и не имеют ошибок. Например, при определении основного тона и гармоник в функции calculateAudioSpecs убедитесь, что вы правильно интерпретируете значения из dataArray.
  4. Логирование ошибок:

    • Используйте console.log для проверки значений переменных и состояния выполнения кода. Это может помочь найти проблемные участки.
  5. Обработка медиа потоков:

    • Проверьте, что вы правильно обрабатываете запись и воспроизведение звука. Убедитесь, что mediaRecorder корректно инициализирован и завершает запись, как ожидается.

Пример исправленного кода:

Пример того, как вы можете инициализировать ваш AudioContext и AnalyserNode:

const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioContext.createAnalyser();

// Связываем аудиовход с анализатором
const source = audioContext.createMediaElementSource(artistAudio);
source.connect(analyser);
analyser.connect(audioContext.destination);

// Устанавливаем FFT size
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

Завершение:

Если вы выполнили все вышеперечисленные пункты и у вас все еще есть проблемы, попробуйте уточнить, в чем именно заключается сбой (например, выдаются ли ошибки в консоли браузера, звучит ли звук вообще и т.д.).

Я надеюсь, что эти рекомендации помогут вам разрешить ваши вопросы, и ваш проект по созданию аудиовизуализатора будет успешным. Если у вас есть дополнительные вопросы, не стесняйтесь их задать!

Оцените материал
Добавить комментарий

Капча загружается...