Вопрос или проблема
Итак, я работаю над приложением, которое сравнивает вокал в стиле хэви-метал от популярных артистов (основная частота, тональность, децибелы и всё такое). Однако мой аудиозаписывающий устройство работает (что было одним из самых сложных), но мой аудиовизуализатор (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. Исходя из вашего описания, есть несколько моментов, которые стоит проверить и исправить.
Возможные проблемы и их решения:
-
CORS и Загрузка Аудиофайлов:
- Если вы сталкиваетесь с проблемами доступа к аудиофайлам из-за политики CORS, убедитесь, что ваш сервер Apache настроен для разрешения таких запросов. Для этого добавьте следующие строки в ваш файл конфигурации Apache (обычно
httpd.conf
или.htaccess
):Header set Access-Control-Allow-Origin "*"
- Это разрешит запросы из любых источников. Вы можете заменить
*
на конкретный домен, если требуется более строгая настройка.
- Если вы сталкиваетесь с проблемами доступа к аудиофайлам из-за политики CORS, убедитесь, что ваш сервер Apache настроен для разрешения таких запросов. Для этого добавьте следующие строки в ваш файл конфигурации Apache (обычно
-
Ошибки в JavaScript:
- Убедитесь, что все элементы, к которым вы обращаетесь через
document.getElementById
, корректно определены. Например, проверьте, правильно ли вы написали идентификаторы для кнопок и аудиодорожек. - Обратите внимание на ошибки синтаксиса. В вашем коде есть символы, которые могут вызывать проблемы (например, "<" и ">" вместо "<" и ">"). Убедитесь, что в коде JavaScript у вас действительно используются правильные символы.
- Убедитесь, что все элементы, к которым вы обращаетесь через
-
Анализ частот и визуализация:
- Проверьте, что вы инициализируете
AudioContext
иAnalyserNode
правильно. Убедитесь, что он активирован до начала обработки звука. - Убедитесь, что ваши функции для получения данных частоты и их визуализации корректны и не имеют ошибок. Например, при определении основного тона и гармоник в функции
calculateAudioSpecs
убедитесь, что вы правильно интерпретируете значения изdataArray
.
- Проверьте, что вы инициализируете
-
Логирование ошибок:
- Используйте
console.log
для проверки значений переменных и состояния выполнения кода. Это может помочь найти проблемные участки.
- Используйте
-
Обработка медиа потоков:
- Проверьте, что вы правильно обрабатываете запись и воспроизведение звука. Убедитесь, что
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);
Завершение:
Если вы выполнили все вышеперечисленные пункты и у вас все еще есть проблемы, попробуйте уточнить, в чем именно заключается сбой (например, выдаются ли ошибки в консоли браузера, звучит ли звук вообще и т.д.).
Я надеюсь, что эти рекомендации помогут вам разрешить ваши вопросы, и ваш проект по созданию аудиовизуализатора будет успешным. Если у вас есть дополнительные вопросы, не стесняйтесь их задать!