网页模拟钢琴键盘的实现方法

Author: Satori Satori

Posted: 2017-10-06 23:05:01

Category: HTML JavaScript 技术 钢琴键盘

Views: 923 Comments: 2

## 一、简介 这是键盘快捷键的第二个应用。这个想法最早来自一款叫做Mountain的steam游戏。这个游戏就是每个字母按键都对应一个音符,通过弹奏某些著名乐章的旋律来解锁一些成就。不够Mountain最大的问题是它只能弹C大调(或者A小调),也就是说,它只有C D E F G A B这7种音,根据十二平均律还少了5个半音,分别是C#(Db),D#(Eb),F#(Gb),G#(Ab),A#(Bb),因此我想实现一个能弹奏这十二种半音的钢琴键盘。 除了能弹奏十二种半音外,我还希望音域能宽一点,至少钢琴的88个半音都能涉及到。但是键盘按键又没有那么多,所以还需要两个按键来让键盘整体平移八度。如果能准备不同音色的乐器那就更好了。 ## 二、实现方法 ### 1.音的演奏。 一种做法是将所有的音作为静态文件进行播放,但是这样会有网络延迟带来的一系列问题,不是最佳的解决方法。幸好有JavaScript库可以使用。这里用的是[audiosynth][1],它通过调用简单的函数就能播放声音。 使用`Synth.setVolume`来调节播放音量,通过以下调用播放声音。 ``` Synth.play(instrument, note, oct + octave_base, note_span); ``` play函数接受4个参数,第一个是整形,表示乐器种类,这个库提供4种音色,0代表钢琴,1代表管风琴,2代表吉他,3的声音很迷,不知道是什么。第二个参数是演奏哪个音,是一个字符串,表示形如`C`,`C#`之类,降号需要转换成升号,比如`Db`就要转换为`C#`。第三个参数表示音是哪个八度的,整形,比如4就代表第4个八度。最后一个参数表示演奏的时间,单位是秒。 ### 2.键盘的对应 键盘的一个按键对应一个音。为了符合钢琴的键位,定义`q` `w` `e` `r` `t`这一行是低音`C` `D` `E` `F` `G`等等。`q`和`w`上面的按键为`2`,代表`C#`,`w`和`e`上面是`3`,代表`D#`等等。最后的`]`代表`G`,接着,键盘从`z` `x` `c` `v` `b`这一行开始,分别代表`A` `B` `C` `D` `E`这几个音,刚好和上一排对应上,最后一个音为`.`,也就是`B`。这样下来,这个键盘包含3个完整的8度。使用字典来记录这个规则。代码如下。 ``` var keymap = { 'q': 'C -1', 'w': 'D -1', 'e': 'E -1', 'r': 'F -1', 't': 'G -1', 'y': 'A -1', 'u': 'B -1', 'i': 'C 0', 'o': 'D 0', 'p': 'E 0', '[': 'F 0', ']': 'G 0', 'z': 'A 0', 'x': 'B 0', 'c': 'C 1', 'v': 'D 1', 'b': 'E 1', 'n': 'F 1', 'm': 'G 1', ',': 'A 1', '.': 'B 1', '2': 'C# -1', '3': 'D# -1', '5': 'F# -1', '6': 'G# -1', '7': 'A# -1', '9': 'C# 0', '0': 'D# 0', '=': 'F# 0', 'a': 'G# 0', 's': 'A# 0', 'f': 'C# 1', 'g': 'D# 1', 'j': 'F# 1', 'k': 'G# 1', 'l': 'A# 1' }; ``` 其中每一行的键为按键,值为字符串,前半部分表示音的种类,后半部分表示相对音高,相当于基准音的偏移。-1代表比基准低一个八度,1代表高一个八度。如果定义基准音高为4的话,`q`代表`C3`,`i`代表`C4`,`c`代表`C5`。 ### 3.重复按键的处理 重复按键问题主要出现在一直按着一个键不放的情况。比如一直按着`c`,就会一直不断演奏这个音,这显然不是我们想要的,我们想至少需要等到按键释放后再按才再次触发这个音的播放。于是按键处理这一块就需要用到高级函数了,需要我们自己定义按键按下和释放的处理函数。 最简单的思路就是记录每一个按键的状态,记录是否已经被按下,如果被按下了,就不再播放这个音。于是整个过程是这样。首先所有按键的状态都是没有被按下。当有一个按键被按下的时候,先判断这个按键是否已经被按下了,如果已经被按下了,说明之前那次按键还没有释放,直接跳过,如果没有被按下,就播放声音,并将那个按键的标志设为按下。当某一按键释放的时候,将那个键的状态设为没有被按下。整个过程代码如下。 ``` for (var key in keymap) { key_state[key] = false; key_func_map.push({ "keys": key, "on_keydown": function(event) { var key = event.key.toLowerCase(); if (key_state[key] === false) { var cur_str = keymap[key]; var note = cur_str.split(" ")[0]; var oct = parseInt(cur_str.split(" ")[1]); Synth.play(instrument, note, oct + octave_base, note_span); key_state[key] = true; } }, "on_keyup": function(event) { var key = event.key.toLowerCase(); key_state[key] = false; } }); } ``` 其中`key_state`记录按键状态,`key_func_map`存储按键映射,`octave_base`表示当前的音高。 ### 4.音高以及乐器的调整 因为想让键盘能弹出多个八度,因此需要将整体音域平移,需要两个按键,一个上移,一个下移。用方向键上下即可。同时还希望能改乐器,同理使用左右来切换乐器。当然需要处理边界条件,比如音的上界和下界,乐器的编号的上界和下界。同样使用`key_func_map`来记录按键映射,代码如下。 ``` key_func_map.push({ "keys": 'up', "on_keydown": function() { if (octave_base <= 6) { octave_base++; } } }); key_func_map.push({ "keys": 'down', "on_keydown": function() { if (octave_base >= 2) { octave_base--; } } }); key_func_map.push({ "keys": 'left', "on_keydown": function() { if (instrument >= 1) { instrument--; } } }); key_func_map.push({ "keys": 'right', "on_keydown": function() { if (instrument <= 2) { instrument++; } } }); ``` 其中`octave_base`记录当前基准音高,`instrument`记录当前乐器的编号。 ### 5.钢琴键盘的开启和关闭 首先使用一个listener将刚才`key_func_map`记录的按键映射进行监听,并进行初始化,比如先停止监听,设定初始播放音量等,代码如下。 ``` var instrument_listener = null; instrument_listener = new window.keypress.Listener; Synth.setVolume(0.4); instrument_listener.register_many(key_func_map); instrument_listener.stop_listening(); ``` 然后再用另一个listener来启动或者关闭这个钢琴键盘。同样使用按键序列来实现。通过一个变量`instrument_active`来记录钢琴键盘是否已经被激活,再根据激活状态来判断下面是激活还是无效钢琴键盘,代码如下。 ``` function toggle_keyboard() { if (instrument_active === false) { instrument_active = true; instrument_listener.listen(); } else if (instrument_active === true) { instrument_active = false; instrument_listener.stop_listening(); } } key_listener.sequence_combo("! @ #", toggle_keyboard, true); ``` 这样一来钢琴键盘的实现也就全部完成了。 [1]: https://github.com/keithwhor/audiosynth

LOGIN TO LEAVE A COMMENT
  1. vvvshane vvvshane Says:

    这就是能弹钢琴?

  2. hxs2580 hxs2580 Says:

    纪念国庆后第一次登陆