Аудиовизуализатор
Здесь рассматриваются некоторые аудиосвойства API и основы создания аудиовизуализаторов. Большинство визуализаторов строятся на одних и тех же базовых принципах, поэтому небольшая практика может дать очень интересные результаты.
Аудиосвойства SignalRGB
Заголовок раздела «Аудиосвойства SignalRGB»Доступ к аудиоданным SignalRGB в коде осуществляется через несколько свойств:
- engine.audio.level — возвращает число от -100 до 0, представляющее общую громкость трека. 0 — громко, -100 — очень тихо.
- engine.audio.density — возвращает число от 0 до 1, представляющее «чистоту» тона: цифровые тоны близки к 0, белый шум — к 1.
- engine.audio.freq — возвращает массив из 200 элементов с частотными данными трека.
Каждое свойство потребует различного уровня нормализации или корректировки перед использованием.
Начнём с базовой анимации частот.
Частота
Заголовок раздела «Частота»Частота представляет тональность воспринимаемого звука и является наиболее важным свойством для аудиовизуализаторов. Здесь берётся 200 срезов частотной волны за каждый кадр и преобразуется в визуальную форму. Основной процесс:
- Создать массив и заполнить его частотными данными.
- Отредактировать массив под свои нужды (фильтрация, отображение, сокращение и т. д.).
- Создать класс «звуковой полосы» для представления каждого элемента.
- Связывать данные с классом звуковой полосы при каждом кадре.
Важный момент с частотой: необходимо сделать две корректировки необработанных данных. Иногда элемент может иметь отрицательное значение, что визуально неприятно. Высота элементов также поступает некорректно для данного примера. Поскольку положительные значения в параметре «height» прямоугольника рисуются вниз от начала координат фигуры, нужно инвертировать их, чтобы получить вид типичного визуализатора.
Пример — необработанные данные:

Обработанные данные:

Пример кода с обработанными данными:
<head> <title>Visualizer Tutorial</title> <meta description="Basic Effects" /> <meta publisher="SignalRgb" /> </head>
<body style="margin: 0; padding: 0; background: #000;"> <canvas id="exCanvas" width="320" height="200"></canvas> </body>
<script> canvas = document.getElementById('exCanvas'); ctx = canvas.getContext('2d'); var effects = []; var reducedFreq = [];
function update() {
// "frequency" represents the full 200 elements from our frequency data var frequency = new Int8Array(engine.audio.freq) // "reducedFreq" filters the data down to every fourth result to save some CPU load reducedFreq = frequency.filter((element, index) => { return index % 4 === 0; })
// Create the effect if it does not yet exist if(effects.length < 1){ effects.push(new soundBars(20, 100)) }
// Background color DrawRect(0, 0, 320, 200, "black")
// Play effect effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime <= 0) { effects.splice(i, 1); } });
window.requestAnimationFrame(update); }
function soundBars (x, y){ this.x = x; this.y = y; this.draw = function(){ for(let i = 0; i < reducedFreq.length; i++){ var x = this.x + 5 * i var y = this.y // Data processing occurs for "height". Find the absolute value of each element, then flip negative var height = -Math.abs(reducedFreq[i]) DrawRect(x, y, 5, height, "white") } } }
function DrawRect(x, y, width, height, color) { ctx.beginPath(); ctx.fillStyle = color; ctx.fillRect(x, y, width, height); };
window.requestAnimationFrame(update); </script>С данным визуализатором всё ещё есть проблемы. Хотя музыка звучит сбалансированно через наушники, видно, что данные значительно предпочитают одни звуковые полосы и часто делают другие полностью невидимыми. С художественной точки зрения это не идеально, поэтому нужно «нормализовать» данные от SignalRGB. Нормализация предполагает равномерное распределение данных между максимальным и минимальным значениями. Определив максимум и минимум в массиве частот, уравнение простое: (x - min) / (max - min), где «x» — текущий элемент.
Нормализованные обработанные данные:

Теперь добавим эффектности, расположив звуковые полосы по кругу:
function soundBars (x, y){ this.x = x; this.y = y;
this.draw = function(){
var max = Math.max(...reducedFreq) var min = Math.min(...reducedFreq)
for(let i = 0; i < reducedFreq.length; i++){ //Save the current state of the canvas ctx.save() //Add the circular component to your bars. "50" here is the radius of the circle var x = this.x + Math.cos(i) * 50 var y = this.y + Math.sin(i) * 50 var height = ((Math.abs(reducedFreq[i])) - min) / (max - min) * -40 // Determine the angle of the bar, to make it perpindicular to the circle var rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2; //Translate to circle center ctx.translate(x, y) //Rotate the canvas ctx.rotate(rotate) //Translate back for drawing ctx.translate(-x, -y) DrawRect(x, y, 5, height, "white") //Restore the old state of the canvas after drawing to prevent positive feedback loops ctx.restore() } } }
Плотность
Заголовок раздела «Плотность»Плотность проста в использовании. Возвращаемое значение — число от 0 до 1, представляющее «чистоту» тона. Цифровые тоны будут ближе к 0, аналоговые — к 1. В этом примере она будет редактировать цвет звуковых полос, придавая различным тонам в музыке различную окраску.
function soundBars (x, y){
this.x = x; this.y = y; this.rotate = 0;
this.draw = function(){ //Hue is calculated each frame, the resulting amount will be a proper hue option for hsl var hue = engine.audio.density * 360
var max = Math.max(...reducedFreq) var min = Math.min(...reducedFreq) for(let i = 0; i < reducedFreq.length; i++){ ctx.save() var x = this.x + Math.cos(i) * 50 var y = this.y + Math.sin(i) * 50 //Height slightly edited for visability var height = ((Math.abs(reducedFreq[i])) - min) / (max - min) * -50 - 5 this.rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2; ctx.translate(x, y) ctx.rotate(this.rotate) ctx.translate(-x, -y) //Insert the edited hue value each frame DrawRect(x, y, 5, height, `hsl(${hue}, 100%, 50%)`) this.rotate = 0; ctx.restore() } } }
Уровень
Заголовок раздела «Уровень»Это свойство просто возвращает громкость трека в децибелах, но с особенностью: числа находятся в диапазоне от -100 до 0. -100 — очень тихо, 0 — очень громко. Потребуется небольшая корректировка данных. В этом примере уровень трека будет редактировать яркость внутреннего чёрного круга. Фигура рисуется в функции update.
function update() {
var frequency = new Int8Array(engine.audio.freq) reducedFreq = frequency.filter((element, index) => { return index % 4 === 0; })
if(effects.length < 1){ effects.push(new soundBars(160, 100)) }
DrawRect(0, 0, 320, 200, "black")
effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime <= 0) { effects.splice(i, 1); } });
// Hue calcualtion to match the rest of the visualizer var hue = engine.audio.density * 360 //The level has been multiplied by 10, then added to 100. This is arbitrary, but some combination of actions here will give you the result you want DrawCircle(160, 100, 50, `hsl(${hue}, 100%, ${100 + engine.audio.level * 10}%)`)
window.requestAnimationFrame(update); }