音频可视化效果
[img]https://ooo.0o0.ooo/2016/10/22/580b5f50212f7.gif[/img]转自: [url=http://codepen.io/raurir/pen/ZQVrzg]http://codepen.io/raurir/pen/ZQVrzg[/url]
演示地址:
[url=https://a7d.000webhostapp.com/audio-visualiser/index_s_dread_rock.html]https://a7d.000webhostapp.com/audio-visualiser/index_s_dread_rock.html[/url][code]<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>Audio Visualiser</title>
<style>
body {
background-color: #000;
}
canvas {
margin: 50px auto;
display: block;
}
</style>
</head>
<body>
</body>
</html>
<script>
(function() {
var Alien, Aliencon, Analyse, Music, con,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Alien = (function() {
Alien.prototype.grid = null;
Alien.prototype.cellSize = 30;
Alien.prototype.numberOfRows = 10;
Alien.prototype.numberOfColumns = 10;
Alien.prototype.halfColumns = 0;
Alien.prototype.colReflected = 0;
Alien.prototype.ticks = 0;
Alien.prototype.canvas = null;
Alien.prototype.drawingContext = null;
Alien.prototype.canvasWidth = 0;
Alien.prototype.canvasHeight = 0;
function Alien() {
this.step = bind(this.step, this);
this.getAlien = bind(this.getAlien, this);
this.halfColumns = this.numberOfColumns / 2;
this.canvasWidth = this.cellSize * (this.numberOfColumns + 2);
this.canvasHeight = this.cellSize * (this.numberOfRows + 2);
this.canvas = document.createElement('canvas');
this.drawingContext = this.canvas.getContext('2d');
this.canvas.height = this.canvasWidth;
this.canvas.width = this.canvasHeight;
this.canvas.aliens = {
x: 0,
y: 0
};
}
Alien.prototype.clearCanvas = function() {
return this.drawingContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
};
Alien.prototype.seed = function() {
var column, k, ref, results, row, seedCell;
this.grid = [];
results = [];
for (row = k = 0, ref = this.numberOfRows; 0 <= ref ? k < ref : k > ref; row = 0 <= ref ? ++k : --k) {
this.grid[row] = [];
results.push((function() {
var n, ref1, results1;
results1 = [];
for (column = n = 0, ref1 = this.halfColumns; 0 <= ref1 ? n < ref1 : n > ref1; column = 0 <= ref1 ? ++n : --n) {
seedCell = this.createSeedCell(column);
results1.push(this.grid[row][column] = seedCell);
}
return results1;
}).call(this));
}
return results;
};
Alien.prototype.createSeedCell = function(probability) {
var chance, cutoff, tobe;
chance = Math.random();
cutoff = (probability + 1) / (this.halfColumns + 1);
tobe = chance < cutoff;
return tobe;
};
Alien.prototype.generateAlien = function() {
var q, r;
r = (function(_this) {
return function(m) {
return ~~(Math.random() * m + 1);
};
})(this);
q = (function(_this) {
return function() {
var a, g, i, j, l;
l = r(11);
g = r(5);
a = [];
i = 0;
while (i < l) {
j = i * g;
a.push(j);
if (i) {
a.unshift(-j);
}
i++;
}
return a;
};
})(this);
return {
x: q(),
y: q()
};
};
Alien.prototype.drawGrid = function(pixels) {
var b, b1, colourFill, colourLine, column, g, g1, k, n, o, r, r1, ref, ref1, ref2, results, row;
this.canvas.aliens = this.generateAlien();
r = ~~(Math.random() * 128 + 64);
g = ~~(Math.random() * 128 + 64);
b = ~~(Math.random() * 128 + 64);
colourLine = "rgba(" + r + ", " + g + ", " + b + ", 1)";
r1 = ~~(r - Math.random() * 64);
g1 = ~~(g - Math.random() * 64);
b1 = ~~(b - Math.random() * 64);
colourFill = "rgb(" + r1 + ", " + g1 + ", " + b1 + ")";
for (row = k = 0, ref = this.numberOfRows; 0 <= ref ? k < ref : k > ref; row = 0 <= ref ? ++k : --k) {
for (column = n = 0, ref1 = this.numberOfColumns; 0 <= ref1 ? n < ref1 : n > ref1; column = 0 <= ref1 ? ++n : --n) {
this.drawCell(row, column, colourLine, 10);
}
}
results = [];
for (row = o = 0, ref2 = this.numberOfRows; 0 <= ref2 ? o < ref2 : o > ref2; row = 0 <= ref2 ? ++o : --o) {
results.push((function() {
var p, ref3, results1;
results1 = [];
for (column = p = 0, ref3 = this.numberOfColumns; 0 <= ref3 ? p < ref3 : p > ref3; column = 0 <= ref3 ? ++p : --p) {
results1.push(this.drawCell(row, column, colourFill, 0));
}
return results1;
}).call(this));
}
return results;
};
Alien.prototype.drawCell = function(y, x, fillStyle, strokeWidth) {
var colReflected, isOn;
if (x >= this.halfColumns) {
colReflected = this.numberOfColumns - x - 1;
} else {
colReflected = x;
}
isOn = this.grid[y][colReflected];
if (isOn) {
this.drawingContext.fillStyle = fillStyle;
return this.drawingContext.fillRect((1 + x) * this.cellSize - strokeWidth, (1 + y) * this.cellSize - strokeWidth, this.cellSize + strokeWidth * 2, this.cellSize + strokeWidth * 2);
}
};
Alien.prototype.getAlien = function() {
return this.canvas;
};
Alien.prototype.step = function() {};
Alien.prototype.change = function() {
this.clearCanvas();
this.seed();
return this.drawGrid();
};
return Alien;
})();
window.Alien = Aliencon = console;
Analyse = (function() {
Analyse.prototype.waves = null;
Analyse.prototype.node = null;
Analyse.prototype.analyser = null;
Analyse.prototype.audio = null;
Analyse.prototype.frameBufferSize = Math.pow(2, 11);
Analyse.prototype.channels = 4;
Analyse.prototype.bufferSize = 0;
Analyse.prototype.signal = null;
Analyse.prototype.fft = null;
Analyse.prototype.amplitude = 0;
Analyse.prototype.spectrum = [];
function Analyse() {
this.step = bind(this.step, this);
this.processAudio = bind(this.processAudio, this);
this.bufferSize = this.frameBufferSize / this.channels;
this.signal = new Float32Array(this.bufferSize);
this.fft = new FFT(this.bufferSize, 44100);
this.newAudioContext();
// this.loadAudio('https://dl.dropboxusercontent.com/u/729503/Codepen/usee.mp3');
this.loadAudio('https://cf-media.sndcdn.com/CmhscBlMZl1W.128.mp3?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiKjovL2NmLW1lZGlhLnNuZGNkbi5jb20vQ21oc2NCbE1abDFXLjEyOC5tcDMiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE0NzcxMzk3MDN9fX1dfQ__&Signature=mCistR~h5En7Nk92Kq~bQIVWaSPmhz-PZ8CMB4OkboW-7Kg6zBGJ3Q5XQ~KDt-uMVYvelxaIZnmoB5rfyELS-ck0hX0WV4RRzHKsJXAAvQjEYES0SSpSueJFh4Vy6Oeh-xuwRK1gApq0S89Oo-km31w3cho4ix10WPcVu0Cgn0P57inrnDEq6NATn3T9iqNI47poxtqZk02VhL5GDr5DnxTvrWy7IqCTlci82LNKZEX1mY6quBP11R8EM7fUDd1BfbgEeZyjiP2IrrXsqpi9q3KDKOZtIIK24Kg2pWJWKzeHZBQu8zc8YQW-eeJYkAWpLDT132jvZ4CE3g1o3zSJow__&Key-Pair-Id=APKAJAGZ7VMH2PFPW6UQ');
con.log("Analyse constructor", this.signal.length);
}
Analyse.prototype.newAudioContext = function() {
var fok;
fok = (function(_this) {
return function(f) {
return window[f] != null;
};
})(this);
this.waves = false;
if (fok('AudioContext')) {
con.log("AudioContext");
this.waves = new AudioContext();
} else if (fok('webkitAudioContext')) {
con.log("webkitAudioContext");
this.waves = new webkitAudioContext();
}
return con.log("waves", this.waves);
};
Analyse.prototype.loadAudio = function(url) {
con.log("loadAudio", url, this.waves);
if (this.audio) {
this.audio.remove();
}
if (this.node) {
this.node.disconnect();
}
this.audio = new Audio();
this.audio.crossOrigin = "anonymous";
this.audio.preload = 'auto';
this.audio.controls = true;
document.body.appendChild(this.audio);
if (this.waves) {
con.log("waves created");
this.audio.addEventListener('canplay', (function(_this) {
return function(e) {
return _this.setupAudioNodes(e);
};
})(this));
} else {
}
return this.audio.src = url;
};
Analyse.prototype.setupAudioNodes = function(e) {
con.log("setupAudioNodes");
if (this.analyser == null) {
this.analyser = this.analyser || this.waves.createScriptProcessor(this.bufferSize, 2, 2);
this.analyser.onaudioprocess = this.processAudio;
this.node = this.waves.createMediaElementSource(this.audio);
this.node.connect(this.analyser);
this.analyser.connect(this.waves.destination);
}
return this.audio.play();
};
Analyse.prototype.processAudio = function(e) {
var i, inputArrayL, inputArrayR, k, outputArrayL, outputArrayR, ref;
inputArrayL = e.inputBuffer.getChannelData(0);
inputArrayR = e.inputBuffer.getChannelData(1);
outputArrayL = e.outputBuffer.getChannelData(0);
outputArrayR = e.outputBuffer.getChannelData(1);
for (i = k = 0, ref = this.bufferSize; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
outputArrayL[i] = inputArrayL[i];
outputArrayR[i] = inputArrayR[i];
this.signal[i] = (inputArrayL[i] + inputArrayR[i]) / 2;
}
this.fft.forward(this.signal);
return this.fftChanged();
};
Analyse.prototype.fftChanged = function() {
var i, k, magSum, ref, specLength;
specLength = this.fft.spectrum.length;
magSum = 0;
for (i = k = 0, ref = specLength; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
magSum += this.fft.spectrum[i];
}
return this.amplitude = 100 * magSum / specLength;
};
Analyse.prototype.step = function() {
var bandTotal, currentBand, i, k, ref, results, specLength, totalBands;
specLength = this.fft.spectrum.length;
totalBands = specLength / 32;
this.spectrum = [];
currentBand = -1;
results = [];
for (i = k = 0, ref = specLength; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
if (i % totalBands === 0) {
bandTotal = 0;
currentBand++;
}
bandTotal += this.fft.spectrum[i];
if (i % totalBands === totalBands - 1) {
results.push(this.spectrum[currentBand] = bandTotal);
} else {
results.push(void 0);
}
}
return results;
};
Analyse.prototype.getAnalysis = function() {
return {
spectrum: this.spectrum,
amplitude: this.amplitude,
waveform: this.signal
};
};
return Analyse;
})();
window.Analyse = Analyse;
//------------------------------------------//
//-------- STUFF FOR AUDIO ANALYSIS --------//
function FourierTransform(bufferSize, sampleRate)
{
this.bufferSize = bufferSize;
this.sampleRate = sampleRate;
this.bandwidth = bufferSize * sampleRate;
this.spectrum = new Float32Array(bufferSize/2);
this.real = new Float32Array(bufferSize);
this.imag = new Float32Array(bufferSize);
this.peakBand = 0;
this.peak = 0;
this.getBandFrequency = function(index)
{
return this.bandwidth * index + this.bandwidth / 2;
};
this.calculateSpectrum = function()
{
var spectrum = this.spectrum,
real = this.real,
imag = this.imag,
bSi = 2 / this.bufferSize,
rval, ival, mag;
this.peak = this.peakBand = 0;
for (var i = 0, N = bufferSize*0.5; i < N; i++)
{
rval = real[i];
ival = imag[i];
mag = bSi * Math.sqrt(rval * rval + ival * ival);
if (mag > this.peak)
{
this.peakBand = i;
this.peak = mag;
}
spectrum[i] = mag;
}
};
}
function FFT(bufferSize, sampleRate)
{
FourierTransform.call(this, bufferSize, sampleRate);
this.reverseTable = new Uint32Array(bufferSize);
var limit = 1;
var bit = bufferSize >> 1;
var i;
while (limit < bufferSize)
{
for (i = 0; i < limit; i++)
this.reverseTable[i + limit] = this.reverseTable[i] + bit;
limit = limit << 1;
bit = bit >> 1;
}
this.sinTable = new Float32Array(bufferSize);
this.cosTable = new Float32Array(bufferSize);
for (i = 0; i < bufferSize; i++)
{
this.sinTable[i] = Math.sin(-Math.PI/i);
this.cosTable[i] = Math.cos(-Math.PI/i);
}
}
FFT.prototype.forward = function(buffer)
{
var bufferSize = this.bufferSize,
cosTable = this.cosTable,
sinTable = this.sinTable,
reverseTable = this.reverseTable,
real = this.real,
imag = this.imag,
spectrum = this.spectrum;
var k = Math.floor(Math.log(bufferSize) / Math.LN2);
if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; }
if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; }
var halfSize = 1,
phaseShiftStepReal,
phaseShiftStepImag,
currentPhaseShiftReal,
currentPhaseShiftImag,
off,
tr,
ti,
tmpReal,
i;
for (i = 0; i < bufferSize; i++)
{
real[i] = buffer[reverseTable[i]];
imag[i] = 0;
}
while (halfSize < bufferSize)
{
phaseShiftStepReal = cosTable[halfSize];
phaseShiftStepImag = sinTable[halfSize];
currentPhaseShiftReal = 1;
currentPhaseShiftImag = 0;
for (var fftStep = 0; fftStep < halfSize; fftStep++)
{
i = fftStep;
while (i < bufferSize)
{
off = i + halfSize;
tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]);
ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]);
real[off] = real[i] - tr;
imag[off] = imag[i] - ti;
real[i] += tr;
imag[i] += ti;
i += halfSize << 1;
}
tmpReal = currentPhaseShiftReal;
currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag);
currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal);
}
halfSize = halfSize << 1;
}
return this.calculateSpectrum();
};
window.FFT = FFT;
;
con = console;
Music = (function() {
Music.prototype.analyser = null;
Music.prototype.aliendude = null;
Music.prototype.canvas = null;
Music.prototype.pixels = null;
Music.prototype.canvasWidth = 1000;
Music.prototype.canvasHeight = 500;
Music.prototype.centreX = 0;
Music.prototype.centreY = 0;
Music.prototype.scale = 1;
Music.prototype.float = 20;
function Music() {
this.step = bind(this.step, this);
con.log("Music constructor");
this.createCanvas();
this.analyser = new Analyse(this.pixels, this.centreX, this.centreY);
this.aliendude = new Alien();
if (window.requestAnimationFrame) {
con.log("native requestAnimationFrame");
} else {
con.log("creating requestAnimationFrame");
window.requestAnimationFrame = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame;
}
this.step();
}
Music.prototype.createCanvas = function() {
this.centreX = this.canvasWidth / 2;
this.centreY = this.canvasHeight / 2;
this.canvas = document.createElement('canvas');
document.body.appendChild(this.canvas);
this.pixels = this.canvas.getContext('2d');
this.canvas.width = this.canvasWidth;
return this.canvas.height = this.canvasHeight;
};
Music.prototype.clearCanvas = function() {
this.pixels.fillStyle = "rgb(0, 0, 0)";
return this.pixels.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
};
Music.prototype.alienScale = 2;
Music.prototype.alienRotate = 0.01;
Music.prototype.step = function() {
var amplitude, anal, b, bandWidth, c, g, i, img, k, len, len1, level, n, o, p, padding, r, ref, ref1, ref2, ref3, sc, specLength, spectrum, waveform, waveformLength, x, y;
this.clearCanvas();
this.analyser.step();
this.aliendude.step();
anal = this.analyser.getAnalysis();
spectrum = anal.spectrum;
amplitude = anal.amplitude;
waveform = anal.waveform;
this.pixels.fillStyle = "rgb(0, 20, 40)";
this.pixels.fillRect(0, this.canvasHeight, this.canvasWidth, -amplitude * this.canvasHeight);
specLength = spectrum.length;
bandWidth = this.canvasWidth / specLength;
padding = 5;
for (i = k = 0, ref = specLength; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
c = ~~(i / specLength * 55);
r = 100;
g = 200 + c;
b = 255 - c;
level = spectrum[i];
this.pixels.fillStyle = "rgba(" + r + ", " + g + ", " + b + ", 0.5)";
this.pixels.fillRect(i * bandWidth + padding, this.canvasHeight, bandWidth - padding * 2, -level * this.centreY);
}
waveformLength = waveform.length;
bandWidth = this.canvasWidth / waveformLength;
this.pixels.beginPath();
for (i = n = 0, ref1 = waveformLength; 0 <= ref1 ? n < ref1 : n > ref1; i = 0 <= ref1 ? ++n : --n) {
this.pixels.strokeStyle = '#0fe';
this.pixels.lineWidth = 1;
x = i * bandWidth;
y = this.centreY + waveform[i] * this.centreY;
if (i === 0) {
this.pixels.moveTo(x, y);
} else {
this.pixels.lineTo(x, y);
}
}
this.pixels.stroke();
this.float += this.alienRotate;
this.scale = spectrum[0];
if (this.scale > this.alienScale * 4) {
this.aliendude.change();
this.alienScale = this.scale;
this.alienRotate = (Math.random() - 0.5) * 0.1;
} else {
this.alienScale *= 0.9;
}
sc = this.alienScale * 1;
img = this.aliendude.getAlien();
this.pixels.save();
this.pixels.translate(this.centreX, this.centreY);
this.pixels.rotate(this.float);
this.pixels.scale(sc, sc);
this.pixels.translate(-img.width / 2, -img.height / 2);
ref2 = img.aliens.x;
for (o = 0, len = ref2.length; o < len; o++) {
x = ref2[o];
ref3 = img.aliens.y;
for (p = 0, len1 = ref3.length; p < len1; p++) {
y = ref3[p];
this.pixels.drawImage(img, img.width * x, img.height * y);
}
}
this.pixels.restore();
return requestAnimationFrame(this.step);
};
return Music;
})();
window.Music = Music;
new Music();
}).call(this);
</script>[/code] 居然是傅里叶变换 [i=s] 本帖最后由 523066680 于 2016-10-22 22:59 编辑 [/i]
要是结合分形想必是极美妙的
我觉得图像方面我可以玩的有趣一些,但是不懂音频解析,所以就当吹吹牛啦。模拟信号处理是不是一定会涉及博里叶变换?
恰巧就在今天看到知乎的一个帖子:有哪些计算机生成的精美图形?
某个回答里介绍了一款强大的音乐可视化插件——MilkDrop
[url]https://www.zhihu.com/question/19935130/answer/127674701[/url] [i=s] 本帖最后由 aa77dd@163.com 于 2016-10-22 23:01 编辑 [/i]
[b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=192085&ptid=42121]3#[/url] [i]523066680[/i] [/b]
自学过一点高数, 早忘光了, 不懂 傅里叶变换
想起了 N 年前 有增强版的 nullsoft winamp 就自带 MilkDrop plug-in [b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=192087&ptid=42121]4#[/url] [i]aa77dd@163.com[/i] [/b]
准备辞职深造,趁年轻。 [b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=192088&ptid=42121]5#[/url] [i]523066680[/i] [/b]
哈哈, 同意
MilkDrop 源码?
[url]http://forums.winamp.com/showthread.php?t=214971[/url] [b]回复 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=192085&ptid=42121]3#[/url] [i]523066680[/i] [/b]
回味经典 含 MilkDrop2 可视化效果插件
[url]http://meggamusic.co.uk/winamp/Winamp_Download.htm[/url]
Winamp 所属公司 Nullsoft 的吉祥物是 一只 大羊驼
[img]http://www.winamp.com/images/bg01.jpg[/img]
页:
[1]