跳到內容

音頻視覺化工具

這裡我們將深入了解一些 API 音頻屬性,並向您展示音頻視覺化工具建立的基礎知識。大多數視覺化工具都是基於相同的基本原則建立的,因此一點練習就能產生非常有趣的結果。

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" 代表頻率資料中完整的 200 個元素
var frequency = new Int8Array(engine.audio.freq)
// "reducedFreq" 將資料篩選為每四個結果以節省 CPU 負載
reducedFreq = frequency.filter((element, index) => {
return index % 4 === 0;
})
// 如果特效不存在,則建立它
if(effects.length < 1){
effects.push(new soundBars(20, 100))
}
// 背景顏色
DrawRect(0, 0, 320, 200, "black")
// 播放特效
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
// "height" 的資料處理。取每個元素的絕對值,然後翻轉為負
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++){
// 儲存 canvas 的當前狀態
ctx.save()
// 為您的條形添加圓形分量。此處「50」是圓的半徑
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
// 確定條形角度,使其垂直於圓
var rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2;
// 平移到圓心
ctx.translate(x, y)
// 旋轉 canvas
ctx.rotate(rotate)
// 平移回來以繪製
ctx.translate(-x, -y)
DrawRect(x, y, 5, height, "white")
// 繪製後恢復 canvas 的舊狀態以防止正反饋迴圈
ctx.restore()
}
}
}

密度使用起來很簡單。返回的值將是 0 到 1 之間的數字,代表音調的「純淨度」。數位音調將接近 0,模擬音調將接近 1。在此範例中,我將使用它來編輯聲音條的顏色,這將使您歌曲中不同的音調具有不同的顏色。

function soundBars (x, y){
this.x = x;
this.y = y;
this.rotate = 0;
this.draw = function(){
// 每個影格計算色相,結果將是 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
// 為了可見性略微編輯高度
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)
// 每個影格插入已編輯的色相值
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);
}
});
// 計算色相以匹配其餘視覺化工具
var hue = engine.audio.density * 360
// 音量乘以 10,然後加到 100。這是任意的,但此處某種組合的操作將給出您想要的結果
DrawCircle(160, 100, 50, `hsl(${hue}, 100%, ${100 + engine.audio.level * 10}%)`)
window.requestAnimationFrame(update);
}