前言
前段時間一時興起想學一下吉他,但是一門樂器要演奏成“能聽”的程度也不是一天兩天的事情,對我這種音樂基礎為 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/
本博客文章均為作者原創,轉載請注明作者和原文鏈接,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/503402.html
標籤:JavaScript
