跳到主要内容

创建音乐可视化

派生完整项目,了解更多细节。

本教程介绍如何使用 PlayCanvas 在 WebGL 中创建音乐可视化应用:从音频流提取频率数据,再将其绘制到 PlayCanvas 画布上。

音乐可视化由两个脚本组成。analyser 负责播放音频,并通过现代浏览器内置 Web Audio API 的 Analyser 节点提取数据;visualizer 获取分析器数据,将其渲染为屏幕上的动态图表。

分析器​

var Analyser = pc.createScript('analyser');

Analyser.attributes.add('fftsize', {
type: 'number'
});

// initialize code called once per entity
Analyser.prototype.initialize = function() {
var context = this.app.systems.sound.context;

// create analyser node and set up
this.analyser = context.createAnalyser();
this.analyser.smoothingTimeConstant = 0.6;
this.analyser.fftSize = this.fftsize;

this.freqData = new Float32Array(this.fftsize/2);
this.timeData = new Float32Array(this.fftsize/2);

var slot = this.entity.sound.slot("track");
slot.setExternalNodes(this.analyser);
};

// update code called every frame
Analyser.prototype.update = function(dt) {
this.analyser.getFloatFrequencyData(this.freqData);
this.analyser.getFloatTimeDomainData(this.timeData);
};

下面仔细看看代码。

首先取得 context,即应用的 AudioContext 实例,然后创建新的 AnalyserNode。AnalyserNode 是现代浏览器通用 Web Audio API 的一部分,可以每帧以数值数组的形式访问音频原始数据。smoothingTimeConstant 控制采样是否随时间平滑:0 表示不平滑,接近 1 表示高度平滑。fftSize 决定分析使用的采样点数,必须是 2 的幂;值越大,细节越多,CPU 开销也越高。

可以使用 Uint8Array 以整数形式访问 AnalyserNode 数据,或使用 Float32Array 以浮点形式访问。本例使用浮点数,因此分配两个 Float32Array,每个长度为 fftSize 的一半。

配置的最后一步是调用 PlayCanvas SoundSlot API 的 setExternalNodes,将新节点插入 Sound 组件的节点链。

随后在更新循环中调用 AnalyserNode 的 getFloatFrequencyData 和 getFloatTimeDomainData,用数据填充数组,供 Visualizer 脚本使用。

可视化器​

var Visualizer = pc.createScript('visualizer');

Visualizer.attributes.add('analyser', {
type: 'entity'
});

Visualizer.attributes.add('freqcolor', {
type: 'rgba'
});

Visualizer.attributes.add('timecolor', {
type: 'rgba'
});

Visualizer.attributes.add('heightScale', {
type: 'number',
default: 1
});

// initialize code called once per entity
Visualizer.prototype.initialize = function() {
this.lines = [];
var count = this.analyser.script.analyser.fftsize;
for (var i = 0; i < count; i++) {
this.lines.push(new pc.Vec3());
}

this.yScale = 1;
this.xScale = 0.10 * 2048 / count;

this.minDb = this.analyser.script.analyser.analyser.minDecibels;
this.maxDb = this.analyser.script.analyser.analyser.maxDecibels;
this.freqScale = 1 / (this.maxDb - this.minDb);
this.freqOffset = this.minDb;
};

// update code called every frame
Visualizer.prototype.update = function(dt) {
this.freqScale = 1 / (this.maxDb - this.minDb);
this.freqOffset = this.minDb;

this.renderData(this.analyser.script.analyser.freqData, this.freqcolor, this.freqScale, this.freqOffset);
this.renderData(this.analyser.script.analyser.timeData, this.timecolor, 0.5, 0);
};


Visualizer.prototype.renderData = function (data, color, scale, offset) {
var line = 0;
for (var i = 0; i < data.length; i++) {
if (line < this.lines.length) {
var h1 = scale * (data[i] - offset);
var h2 = scale * (data[i+1] - offset);

this.lines[line].set(i * this.xScale, this.heightScale*h1, 0);
this.lines[line+1].set((i+1) * this.xScale, this.heightScale*h2, 0);

line += 2;
}
}
this.app.renderLines(this.lines, color);
};

可视化脚本通过 this.app.renderLines API,将分析器数据渲染为折线图。

初始化时,为线段预先分配向量,每个数据点需要两个,分别用于线段起点和终点。接着设置缩放系数,确保线段显示在屏幕内。AnalyserNode 提供数据分贝值的最小和最大范围,虽然实际数据可能超出这个范围,但它仍可作为归一化的参考。

renderData 函数遍历所有数据,将预分配向量设置为当前数据点与下一数据点的位置。

更新循环同时渲染频率数据图与时域数据图。

更多想法​

这只是音乐可视化的一个起点。试着让三维柱体随音乐缩放,或同步调整颜色与亮度;也可以接入 SoundCloud,让用户选择曲目。还有很多可能性。