前言
前段时间一时兴起想学一下吉他,但是一门乐器要演奏成“能听”的程度也不是一天两天的事情,对我这种音乐基础为 0 的人来说学习周期太长了,不想耗费太多时间在学习乐器上面,于是想找个取巧的方法。
最终方案就是做了个简单粗陋的微信小程序 Demo 去弹奏吉他乐,勉强算是成功吧,可以很简单地弹奏出乐曲。
大概一小时的学习周期(含学习简谱时间),可以弹出传统乐器学习一两个月的效果。
但是这个小程序有一个 BUG ,导致它也只能算是一个失败品。
文章最后会附上开源代码地址,且容我先抛出这块砖,希望能引来玉吧。
使用
这里贴出微信小程序的二维码,小程序的名字叫冒牌吉他手,大家可以体验一下。
主界面
本小程序有四个模式:自由模式、简单模式、灵魂模式和自动模式。
在自由模式下,您可以按 1,2,3,4,5,6,7 等七个音符按键,分别对应 do(哆)、re(来)、mi(咪)、fa(发)、sol(唆)、la(拉)、si(西)。同时可以切换高低音和节拍按键来分别控制音高和音长。
在简单模式下,程序会自动根据简谱识别出音高和音长,您只需要按七个音符按键即可。默认设置的简谱是《成都》。
在灵魂模式下,程序会自动根据简谱识别出音高、音长和音符,您只需要按一个音符按键即可。
在自动模式下,程序会自动根据简谱弹奏音乐,您无需操作。
编辑简谱时,会自动定位到简谱最末尾的位置,且只支持新增和删除。
删除:点击删除按钮会直接删除最末尾的简谱符号。
新增:按下相应的音符键,会结合当前的高低音和节拍按键,新增对应的简谱符号。
另外编辑模式下会多出来两个新的按键,分别是音符按键最左侧的“0”键和音符按键最右侧的“|”键。
“0”键代表简谱上的休止符。
“|”键表示表示简谱的分隔符,只用来标识,不输入也是可以的。
方案与原理
这个小程序完全是前端代码实现,无需服务端,并且不需要音频资源文件。
之所以做成这样,是因为微信小程序上面现在放资源文件是要给腾讯打钱的,而单纯的代码是不需要的 o(∩_∩)o
技术上的实现说起来也很简单,使用 AudioContext 来实现,音频文件是预先将 mp3 进行 base64 编码,使用时再转换成AudioContext的 buffer 数组来实现。
在做这个之前我对AudioContext一无所知,网上这类材料也很少,算是非常冷门了,主要是参考了webaudiofont这个项目来实现。
但是这里有个问题,微信小程序的AudioContext是自己实现的一套机制,与 web 标准的AudioContext不一样,而且微信小程序这里另外提供了一个WebAudioContext的,不过还是与 AudioContext 标准接口有些差异。
所以我这里也做了一些兼容上的处理。
顺便说一下,在代码中简单修改一下音源文件 0255_GeneralUserGS_sf2_file.js 为其他音源文件,也可以弹奏其他的乐器,比如钢琴唢呐之类的。
关键代码
以下贴出播放音乐的关键代码:
import { _tone_0255_GeneralUserGS_sf2_file } from "../../utils/0255_GeneralUserGS_sf2_file";
let actx = wx.createWebAudioContext();
let player = new WebAudioFontPlayer();
player.adjustPreset(actx, _tone_0255_GeneralUserGS_sf2_file);
// 根据音高,音符,节拍来播放音乐
playMusic(type, char, pai, time = 0) {
if (char === "0") {
return;
}
const pitch = levels[type] * 12 + note[char - 1];
player.queueWaveTable(
actx,
actx.destination,
_tone_0255_GeneralUserGS_sf2_file,
time,
pitch,
TIME_RATE * parseFloat(pai),
1
);
},
在引入吉他音频源文件_tone_0255_GeneralUserGS_sf2_file后,我们先调用微信小程序的wx.createWebAudioContext函数,创建一个AudioContext实例,然后使用封装好的WebAudioFontPlayer类构建一个播放器实例player,然后调用player.adjustPreset函数预先解析音频源文件。
接着在具体播放音乐的函数中playMusic,我们使用player.queueWaveTable播放音乐。
传入的实参分别表示:
* actx // AudioContext实例
* actx.destination // AudioContext实例的渲染设备,一般就是扬声器之类的
* _tone_0255_GeneralUserGS_sf2_file // 音频源文件
* time // 音频响起的起始时间
* pitch // 音高
* TIME_RATE * parseFloat(pai) // 这里是根据传入的节拍计算出这个音调播放的持续时间
* 1 // 这里表示的是音量
另外注意一点:
const pitch = levels[type] * 12 + note[char - 1];
这行代码是我参考相关文件得出的计算音高的计算公式,保持固定就好了,其中涉及的魔法数字也是没办法的事情。
除了以上的关键代码,其他的代码比如输入简谱,自动定位当前的简谱,以及播放音乐的控制逻辑大多也不算难,所以就不单独列出了。
缺陷与失败的修复方案
这个小程序在自由模式、简单模式和灵魂模式下运行还算较好。
但是在真实手机上,自动模式是有问题的(在 PC 的开发者工具上自动模式没有问题)。
这个问题就是自动模式下,播放的声音有杂音。
自动模式尝试过两个方案:
一个方案使用的是 setTimeout,播放一个音调后再播放下一个音调,在 PC 上表现良好,在手机上会有延迟,杂音问题很小。
一个方案使用的是 queueWaveTable 自己的时间机制去播放,没有延迟,但是杂音问题严重。
由于这两套方案在开发者工具上都表现良好,只在真机上有问题,猜测只能是移动端性能不佳,或者是微信小程序的WebAudioContext实现有问题。
我实际发布的小程序采用的是第一种方案,不过第二种我也在代码中实现了,在代码中已注释。
其实我更倾向于第二种方案,因为 setTimeout 在真机上的延迟听起来像乱弹的,只是第二种的杂音问题实在太严重才不得不选第一种。
总的来说,在真机上,自动模式都不咋样。
总结
因为对这个方面实在没什么深入研究的欲望,随着兴趣减淡,也就偃旗息鼓了。
虽然自动模式确实存在问题,但是灵魂模式也不错了,也更好玩一点。
也算是做了一些东西的,如果有后来者可以解决这个问题,或者有感兴趣的可以在这个基础上再修复或者拓展吧。
这里给出开源的仓库地址:fake-guitarist
出处:https://www.cnblogs.com/vvjiang/
本博客文章均为作者原创,转载请注明作者和原文链接。