Skip to content

Visualizer Audio

Di sini kita akan menyelami beberapa sifat audio API dan menunjukkan asas-asas penciptaan visualizer audio. Kebanyakan visualizer dibina daripada set prinsip asas yang sama, jadi sedikit latihan boleh menghasilkan hasil yang sangat menyeronokkan.

Data audio yang disediakan oleh SignalRGB boleh diakses melalui beberapa sifat dalam kod Anda:

  • engine.audio.level - mengembalikan nombor antara -100 dan 0 yang mewakili kelantangan keseluruhan trek. 0 adalah kuat, -100 adalah sangat perlahan.
  • engine.audio.density - mengembalikan nombor antara 0 dan 1 yang mewakili kekasaran nada, dengan nada ujian mengembalikan 0 dan hingar putih 1.
  • engine.audio.freq - mengembalikan susunan 200 elemen yang mengandungi data frekuensi trek.

Setiap sifat memerlukan tahap normalisasi atau pelarasan yang berbeza sebelum ia boleh digunakan dengan betul, yang akan saya masuki sebentar lagi.

Mari mulakan dengan animasi frekuensi asas.

Frekuensi mewakili pic bunyi yang kita dengar dan merupakan sifat yang paling penting untuk visualizer audio. Apa yang kita lakukan di sini adalah mengambil 200 hirisan gelombang frekuensi setiap bingkai dan menukarnya ke bentuk visual. Proses asasnya adalah:

  • Isytiharkan susunan dan isi dengan data frekuensi.
  • Edit susunan ini mengikut keperluan Anda (filter, map, reduce, dll.).
  • Tulis kelas “sound bar” untuk mewakili setiap elemen.
  • Hubungkan data ke kelas sound bar setiap bingkai.

Perkara penting dengan frekuensi ialah kita perlu membuat dua pelarasan pada data mentah. Kadang-kadang elemen akan masuk dengan nilai negatif, yang secara visual mengganggu. Ketinggian elemen juga masuk secara tidak betul untuk contoh ini. Memandangkan nilai positif dalam pilihan “ketinggian” segi empat tepat dilukis ke bawah dari asal bentuk, kita ingin membalikkannya untuk menyerupai visualizer purata Anda.

Contoh — data tidak diproses:

Data yang diproses:

Contoh kod dengan data yang diproses:

<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>

Namun masih terdapat beberapa isu dengan visualizer di atas. Walaupun lagu terdengar seimbang melalui fon telinga kita, kita dapat melihat bahawa data sangat memihak kepada beberapa sound bar kita dan sering meninggalkan yang lain sama sekali tidak kelihatan. Dari perspektif artistik, ini tidak ideal, jadi kita akan “menormalisasi” data yang diterima daripada SignalRGB. Normalisasi melibatkan pengagihan data secara seragam antara titik maksimum dan minimum untuk menggambarkan nilai mereka dalam hubungan antara satu sama lain dengan lebih baik. Setelah menentukan nilai maksimum dan minimum dalam susunan frekuensi, persamaannya cukup mudah: (x - min) / (max - min), di mana “x” mewakili elemen semasa.

Data yang dinormalisasi dan diproses:

Seterusnya, kita akan menambah sedikit kemewahan dengan menyusun sound bar dalam bulatan:

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()
}
}
}

Ketumpatan mudah digunakan. Nilai yang dikembalikan akan menjadi nombor antara 0 dan 1 yang mewakili “kebersihan” nada. Nada digital akan lebih hampir kepada 0, dan nada analog akan lebih hampir kepada 1. Untuk contoh ini, saya akan menggunakannya untuk mengedit warna sound bar, yang akan memberikan pewarnaan yang berbeza kepada nada yang berbeza dalam lagu Anda.

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()
}
}
}

Sifat ini hanya mengembalikan kelantangan trek dalam desibel, dengan trik bahawa ia menghasilkan nombor antara -100 dan 0. -100 adalah sangat perlahan, dan 0 adalah sangat kuat. Kita perlu melakukan sedikit pengeditan pada data ini untuk menggunakannya seperti yang saya mahu, dan saya akan melukis bentuk ini dalam fungsi kemaskini kita. Di sini, tahap trek akan mengedit kecerahan bulatan hitam dalaman.

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);
}