「新春创意」用喜庆的音乐送自己一场音乐烟花show🎊🎊

前言

写个福字送给新年的自己后,为了让新春氛围更加浓厚,必不可少的就是烟花和 BGM 了,这篇文章我们就利用 p5.js 实现一个根据音乐来绽放烟花,为新春增色添彩~

效果预览

6.gif

什么是 p5.js

官网:p5.js 是个 JavaScript 创意编程程式库,其专注在让编程更易于使用及更加广泛的包容艺术家、设计师、教育家、初学者以及任何其他人!p5.js 是个免费及开源的软件因为我们相信所有人都应该能自由使用软件及用于学习软件的工具。p5.js 使用绘图的比喻并有一副完整的绘画功能。除此之外,您也不单限于您的绘图画布。您可以将您整个浏览器页面当作您的绘图,这包括了 HTML5 物件如文字、输入框、视屏、摄像头及音频。

简单来说,p5.js 可以利用 canvas 实现许许多多炫酷的浏览器效果,以及在浏览器中进行绘图。

实现

首先我们先引用 p5 的 cdn,因为我们要利用音频相关功能,所以再引用它的扩展包 p5.sound

1
2
<script src="https://cdn.bootcdn.net/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/p5.js/1.4.0/addons/p5.sound.min.js"></script>

接下来我们首先实现烟花的部分,然后再将音乐与烟花结合起来

创建画布

p5 的写法与一般情况不同,js 中直接定义 setup 函数即可实现初始化,draw 函数则用来对画布进行绘制:

1
2
3
4
5
6
function setup() {
createCanvas(window.innerWidth, window.innerHeight)
}
function draw() {
background(0, 0, 20)
}

在 vue 中如果想要定义 setup 函数则需要使用另一种声明方式:

1
2
3
4
5
6
7
8
const s = (sketch) => {
sketch.setup = () => {
createCanvas(window.innerWidth, window.innerHeight)
}
sketch.draw = () => {
background(0, 0, 20)
}
}

在本文中我们直接在 html 中编写代码所以采用第一种方式。

创建烟花

这里我们模拟的烟花由一个中心点和围绕在周围的随机点构成。 具体代码作用和含义在注释中标注

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// 目前接收一个爆炸半径参数。
function Fireworks(radius) {
var num = 64 // 一个烟花有多少个点

// 随机中心点位置,width和height为画布的宽高。
var cp = new p5.Vector(
random(0, width),
random(height / 2, height / 5),
random(-100, 100)
)

// 定义烟花发射的速度和加速度,目前高度写死,后续会根据音频相关数据进行动态修改。
var velocity = new p5.Vector(0, -15, 0)
var accel = new p5.Vector(0, 0.4, 0)

var img

var fp = []

var cosTheta
var sinTheta
var phi
var colorChange = random(0, 5)

// 初始化烟花的点
for (var i = 0; i < num; i++) {
cosTheta = random(0, 1) * 2 - 1
sinTheta = sqrt(1 - cosTheta * cosTheta)
phi = random(0, 1) * 2 * PI
fp[i] = new p5.Vector(
radius * sinTheta * cos(phi),
radius * sinTheta * sin(phi),
radius * cosTheta
) //1发烟花里各个点的位置计算
fp[i] = p5.Vector.mult(fp[i], 1.01)
}

// 为烟花进行随机上色
var side = 64
var center = side / 2.0

img = createImage(side, side)
img.loadPixels()
const r = random(0.5, 0.8)
const g = random(0.5, 0.8)
const b = random(0.5, 0.8)
for (var y = 0; y < side; y++) {
for (var x = 0; x < side; x++) {
var distance = (sq(center - x) + sq(center - y)) / 10.0
var r = int((255 * r) / distance)
var g = int((255 * g) / distance)
var b = int((255 * b) / distance)
// img.pixels[x + y * side] = color(r, g, b);
img.set(y, x, color(r, g, b))
}
}

img.updatePixels()

this.display = function () {
for (var i = 0; i < num; i++) {
push()
translate(cp.x, cp.y, cp.z)
translate(fp[i].x, fp[i].y, fp[i].z)
blendMode(ADD) //各个小点(发光源遮罩效果)
image(img, 0, 0)
pop()

fp[i] = p5.Vector.mult(fp[i], 1.015) //更新小点(发光源)扩散位置
}
}

//模拟重力加速度
this.update = function () {
radius = dist(
0,
0,
0,
fp[0].x,
fp[0].y,
fp[0].z
)
centerPosition.add(velocity)
velocity.add(accel)
}

// 判断是否需要销毁
this.needRemove = function () {
if (centerPosition.y - radius > height) {
return true
} else {
return false
}
}
}

现在基本的烟花类已经被定义好了,我们可以给画布绑定一个键盘事件,并修改 draw 函数中相关逻辑来看一下效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function keyPressed() {
//键盘事件添加一发烟花
fireworks.push(new Fireworks(20)) //80为烟花初始半径
}

function draw() {
background(0, 0, 20)
for (var i = 0; i < fireworks.length; i++) {
fireworks[i].display()
fireworks[i].update()
if (fireworks[i].needRemove()) {
fireworks.splice(i, 1)
}
}
}

7.gif

相关 API

这里将上述用到的相关 api 和类 单列出来方便查看

API 描述
createCanvas 创建画布
background 设置背景色
p5.Vector 创建包含幅度和方向的新 p5 向量
random 返回在作为参数给出的范围之间的随机浮点数
sqrt 获取任何输入数字的平方根
createImage 创建图片
translate 指定在显示窗口内放置对象的数量 x 参数用于指定左/右平移 y 参数用于指定上/下平移
blendMode 根据给定的混合模式混合两个像素
dist 测量计算在 2D 或 3D 中两点之间的距离

获取音频参数

首先我们需要添加一个audio标签来存放我们的音乐

1
<audio src="./1.mp3" autoplay controls>您的浏览器不支持 audio 标签。</audio>

接着改造我们的 setup 函数,让它能够获取到我们的音频资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fireworks = []
var highMap = [2, 5, 10, 15]
const ORI_NUM = 32
function setup() {
//processing初始化设置
createCanvas(window.innerWidth, window.innerHeight)
frameRate(60) // 每秒渲染次数
imageMode(CENTER)
audioContext_ = getAudioContext()
//取得声音dom
let el = document.querySelector('audio')
//根据声音dom创建web audio API source node, 并且连接主输出
const source = audioContext_.createMediaElementSource(el)
source.connect(p5.soundOut)
//0.8是变化程度,越小图案越抖
fft = new p5.FFT(0.8, ORI_NUM)
fft.setInput(source)
}

这里我们获取了音频资源的频谱,未来用来控制我们烟花的大小以及高度。当然 p5.sound 还可以获取音频的音量等等,以实现更炫酷的可视化效果。

接下来我们改造 draw 绘制函数,解析本地音频的频谱并利用它来搞事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function draw() {
//循环执行,达成画面渲染效果

audioContext_.resume()
background(0, 0, 20)
let spectrum = fft.analyze()

spectrum.forEach((item, j) => {
if (item !== 0 && j % 2) {
let a = highMap[0]
if (item >= 64 && item < 128) {
a = highMap[1]
} else if (item >= 128 && item < 192) {
a = highMap[2]
} else {
a = highMap[3]
}
const fire = new Fireworks(item, a)
fireworks.push(fire)
if (fireworks.length > 64) {
fireworks.shift()
}
}
})

for (var i = 0; i < fireworks.length; i++) {
fireworks[i].display()
fireworks[i].update()
if (fireworks[i].needRemove()) {
fireworks.splice(i, 1)
}
}
}

注意audioContext_.resume()是必须要添加的,因为谷歌浏览器默认不会自动播放音频。

spectrum 就是我们分析得到的频谱数值组成的数组,ORI_NUM 作为频谱的个数,最少需要 32 个。我们理论上可以放更多的烟花,但是由于本人电脑性能一般,所以直接取 16 个来放烟花。

代码中我们可以看到,Fireworks 类我们传了两个参数:频谱的值和我们定义的高度值,现在就可以对烟花类改造,以实现根据音频来控制烟花的爆炸半径和发射高度。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Fireworks(radius, h) {
var num = 64 //一发烟花里,有多少个点 (比较吃机器)

var cp = new p5.Vector(
random(0, width),
random(height / 2, height / 5),
random(-100, 100)
) //烟花的中心位置
var velocity = new p5.Vector(0, -h, 0)
var accel = new p5.Vector(0, 0.4, 0)

……
}

为了效果更真实,我们优化一下烟花的随机颜色,让它更亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
if (colorChange >= 3.5) {
img = makeColor(0.9, random(0.2, 0.5), random(0.2, 0.5))
} else if (colorChange > 3.2) {
img = makeColor(random(0.2, 0.5), 0.9, random(0.2, 0.5))
} else if (colorChange > 2) {
img = makeColor(random(0.2, 0.5), random(0.2, 0.5), 0.9)
} else {
img = makeColor(random(0.5, 0.8), random(0.5, 0.8), random(0.5, 0.8))
}

function makeColor(rr, gg, bb) {
var side = 64
var center = side / 2.0

var img = createImage(side, side)
img.loadPixels()
for (var y = 0; y < side; y++) {
for (var x = 0; x < side; x++) {
var distance = (sq(center - x) + sq(center - y)) / 10.0
var r = int((255 * rr) / distance)
var g = int((255 * gg) / distance)
var b = int((255 * bb) / distance)
// img.pixels[x + y * side] = color(r, g, b);
img.set(y, x, color(r, g, b))
}
}

img.updatePixels()
return img
}

效果就如文章开头那样绚烂多彩。

最后

至此音乐烟花就实现了,而 p5.js 也是我不经意间发现的一个库,发现用它可以做出各种强大的动画。本以为我会是第一个用 p5,js 来实现效果的人,而就在我把这个 demo 写出来的第二天,活动的第一天,就读到了北京哥的一场烟花盛宴 🎇,祝你新年快乐 🎉,实现思路不同,效果我觉得北京哥的也更美观真实~,心痛哈哈哈哈做不了第一那做个……第n也不错。


「新春创意」用喜庆的音乐送自己一场音乐烟花show🎊🎊
https://moewang0321.github.io/2022/01/10/「新春创意」用喜庆的音乐送自己一场音乐烟花show🎊🎊/
作者
Moe Wang
发布于
2022年1月10日
许可协议