学过初中物理就会知道,声音是由空气振动产生的。振动产生波,所以声音就是不同振幅和频率的波构成的。振幅决定了声音的响度,频率决定了声音的音高。想更进一步了解的可以访问这个网页waveforms。
学过一点基础乐理的同学就知道,标准音(A4)的频率是440Hz,也就是说,每一个音其实是由它的频率决定的。在十二平均律中,一个音的八度音(及高八度后的音)的频率是这个音的两倍。而八度音跨了12个半音,所以每个半音平均相差2的1/12次方倍。例如,A4音的频率是440Hz,那么,B4的频率就是4402^(2/12)=493.88Hz(A和B跨两个半音)。以此类推,A5的频率就是4402=880Hz。
我们知道,吉他的六根弦分别是E2、A2、D3、G3、B3和E4。这四个音的音高可以根据上述的公示推到出来。那么当我们知道了吉他空弦的频率,我们就可以根据音频判断吉他的音高是否准确。但是有什么方法可以根据音名得到频率呢?
teoria是一个轻量级且快速的 JavaScript 音乐理论库,包括爵士乐和古典音乐。它旨在为音乐软件提供直观的编程界面。通过以下方法获取音频
const E2 = teoria.note('E2');
const fq = E2.fq();
// 82.41Hz
const name = E2.toString();
// E2
解决了吉他空弦频率问题,那么,当我们拨弦后,如何根据录音得到其对应的频率呢?
我们用到pitchy这个库。这个库可以根据音频计算出频率,采用了Philip McLeod 和Geoff Wyvill在文章A Smarter Way to Find Pitch设计的算法。用法如下
startRecord() {
const audioContext = new window.AudioContext();
const analyserNode = audioContext.createAnalyser();
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
// 得到音频流
this.audioStream = stream;
audioContext.createMediaStreamSource(stream).connect(analyserNode);
const detector = PitchDetector.forFloat32Array(analyserNode.fftSize);
const input = new Float32Array(detector.inputLength);
this.updatePitch(analyserNode, detector, input, audioContext.sampleRate);
});
},
updatePitch(analyserNode, detector, input, sampleRate) {
analyserNode.getFloatTimeDomainData(input);
// 得到音高频率和清晰度
const [pitch, clarity] = detector.findPitch(input, sampleRate);
// ...
// 每200ms检测一次频率
setTimeout(()=> this.updatePitch(analyserNode, detector, input, sampleRate), 200);
},
得到频率后,我们可以设计一个标准判定这个音已经调准了,例如,持续2s频率与标准频率差值小于2Hz。
updatePitch() {
// ...
this.delta = (this.pitch - this.currentNote.fq()).toFixed(2);
if (Math.abs(this.delta) < 2) {
if (firstCorrectTimeStamp === 0) {
firstCorrectTimeStamp = new Date().getTime();
} else {
// 进度条
this.correctProgress = (new Date().getTime() - firstCorrectTimeStamp) / 1000 / 2 * 100;
// 判定为已调准
if (this.correctProgress >= 100) {
this.correctNoteList.push(this.currentNote);
this.stopRecord();
return;
}
}
} else {
firstCorrectTimeStamp = 0;
this.correctProgress = 0;
}
// ....
}
这里我设计了一个时钟样式的进度条,如下图,当2s时长达到,进度100%。
用到了css样式的background的conic-gradient属性,可以自行了解下。
如果想再完善下,我们还可以在点击某个音时,播放这个音的音高。如何实现?
这里我使用了tone这个包。Tone.js 是一个网络音频框架,用于在浏览器中创建交互式音乐。 Tone.js 的架构旨在让创建基于 Web 的音频应用程序的音乐家和音频程序员都熟悉。由于直接使用默认方式的效果并不是吉他,我这边在另一个网站找到了这几个音的mp3音频,作为输入,代码如下
playPitch(note) {
this.sampler = new Tone.Sampler({
urls: {
"e2": "e2.mp3",
"a2": "a2.mp3",
"d3": "d3.mp3",
"g3": "g3.mp3",
"b3": "b3.mp3",
"e4": "e4.mp3",
},
baseUrl: IP,
}).toDestination();
Tone.loaded().then(() => {
this.sampler.triggerAttackRelease(note.toString(), 1);
});
},
链接:https://hougiser.gitee.io/music-score/
欢迎沟通~