【笔记】JS实现无源蜂鸣器

前言

JS实现无源蜂鸣器,从而实现伪钢琴音乐

源代码

  • 为指定class的标签设置触摸事件,每当该标签被触摸时,都会发出一次蜂鸣器声音

由于浏览器安全限制,需要用户在站点进行任意操作后才会触发声音,否则直接触摸该标签不会发出声音
例如:先点击一下该标签,之后每次触摸都会发出声音

  • 有两种方式实现打歌

第一种方式是原代码作者方案:该代码原作者将所有蜂鸣器振动频率放到了一个以空格分开的字符串中,然后将字符串切割为数组
该方案的案例是歌曲《天空之城》

t:存放无源蜂鸣器振动频率的数组

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
// 为指定class的标签设置触摸事件
element_classname = ["site-title"];

window.AudioContext = window.AudioContext || window.webkitAudioContext,
function () {
if (window.AudioContext) {
let e = new AudioContext;
let t = "880 987 1046 987 1046 1318 987 659 659 880 784 880 1046 784 659 659 698 659 698 1046 659 1046 1046 1046 987 698 987 987 880 987 1046 987 1046 1318 987 659 659 880 784 880 1046 784 659 698 1046 987 1046 1174 1174 1174 1046 1046 880 987 784 880 1046 1174 1318 1174 1318 1567 1046 987 1046 1318 1318 1174 784 784 880 1046 987 1174 1046 784 784 1396 1318 1174 659 1318 1046 1318 1760 1567 1567 1318 1174 1046 1046 1174 1046 1174 1567 1318 1318 1760 1567 1318 1174 1046 1046 1174 1046 1174 987 880 880 987 880".split(" "); //天空之城
let i = 0;
let o = 1;
for (const item of element_classname) {
document.getElementsByClassName(item)[0].addEventListener("mouseenter", function () {
let r = t[i];
r || (i = 0, r = t[i]), i += o;
let c = e.createOscillator();
let l = e.createGain();
c.connect(l);
l.connect(e.destination);
c.type = "sine";
c.frequency.value = r;
l.gain.setValueAtTime(0, e.currentTime);
l.gain.linearRampToValueAtTime(1, e.currentTime + .01);
c.start(e.currentTime);
l.gain.exponentialRampToValueAtTime(.001, e.currentTime + 1);
c.stop(e.currentTime + 1);
n = !1;
});
}
}
}();

第二种方式是我的方案:由于之前学习单片机时,学习过压缩的方式加载振动频率值,所以可以采用压缩的方式,先存储一个基本音数组,然后再存储曲谱数组,遍历曲谱数组中的音调转换为基本音的振动频率值数组
该方案的案例是歌曲《天空之城》优化版(优化了所有连续音符,将连续音符改为单音符,使其更适合JS蜂鸣器)

t:存放无源蜂鸣器振动频率的数组

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
// 为指定class的标签设置触摸事件
element_classname = ["site-title"];

// C大调钢琴音调
const pitch = [
262, 294, 330, 349, 392, 440, 494,
523, 587, 659, 698, 784, 880, 988,
1046, 1175, 1318, 1397, 1568, 1760, 1976,
];

// 根据曲谱打歌
const tone = [
12, 13, 14, 13, 14, 16, 13,
9, 9, 12, 11, 12, 14, 11,
9, 9, 10, 9, 10, 14, 9,
14, 14, 13, 10, 10, 13, 13,
12, 13, 14, 13, 14, 16, 13,
9, 9, 12, 11, 12, 14, 11,
8, 9, 10, 14, 13, 14, 15, 16, 14,
14, 13, 12, 13, 11, 12,
14, 15, 16, 15, 16, 18, 15,
11, 11, 14, 13, 14, 16, 16,
12, 13, 14, 13, 15, 15, 14, 11, 11,
17, 16, 15, 14, 16,
16, 19, 19, 18, 18, 16, 15, 14,
14, 15, 14, 15, 18, 16,
16, 19, 19, 18, 18, 16, 15, 14,
14, 15, 14, 15, 13, 12,
];

// 将曲谱转换为钢琴音数组
let music_list = [];
for (let i = 0; i < tone.length; i++) {
music_list.push(pitch[tone[i]]);
}
// console.log(music_list)

window.AudioContext = window.AudioContext || window.webkitAudioContext,
function () {
if (window.AudioContext) {
let e = new AudioContext;
let t = music_list;
let i = 0;
let o = 1;
for (const item of element_classname) {
document.getElementsByClassName(item)[0].addEventListener("mouseenter", function () {
let r = t[i];
r || (i = 0, r = t[i]), i += o;
let c = e.createOscillator();
let l = e.createGain();
c.connect(l);
l.connect(e.destination);
c.type = "sine";
c.frequency.value = r;
l.gain.setValueAtTime(0, e.currentTime);
l.gain.linearRampToValueAtTime(1, e.currentTime + .01);
c.start(e.currentTime);
l.gain.exponentialRampToValueAtTime(.001, e.currentTime + 1);
c.stop(e.currentTime + 1);
n = !1;
});
}
}
}();

完成

参考文献

雾时之森
歌谱简谱网