作为我在更现代的JavaScript中为remake my old Artsy demo努力的一部分,我决定从音乐播放器开始。 1993年的原始Amiga演示使用了SoundTracker MOD format,但是2013年我的翻拍使用了MP3文件,这有点作弊。播放MP3是有效的,需要几乎零代码,但这不是很有趣。另外,它需要加载几种兆字节的数据。因此,我决定仅使用Web Audio API
来尝试从头开始制作Mod播放器网络音频的当前状态
自2011年首次推出以来,Web音频API经历了许多更改。最初,它非常凌乱,需要大量的样板代码才能完成任何操作。此后,API已被清理,现在非常容易使用。不幸的是,仍然有很多博客和教程显示出旧的,弃用的代码。例如,ScriptProcessorNode(允许您以自2014年自2014年开始生成音频数据)的生成,但在许多示例中仍然使用过。今天,AudioWorklet接口是在JavaScript中生成声音的推荐方法。
时间很快
创建MOD播放器的第一步是发出一些噪音。做到这一点的一种方法是产生正弦波。正弦波是最简单的波形,几乎不需要制作代码。我将创建一个会产生恒定频率的正弦波的音频工作点。
第一步是创建一个AudioContext,这是Web音频API的主要接口。我还需要加载一个AudioWorklet (将稍后创建),将其连接到上下文,然后将消息发送给工作点以开始生成声音。在用户与页面进行交互之前,不允许上下文播放任何声音,因此我需要在恢复上下文之前等待click
事件。
// Create the audio context
const audio = new AudioContext();
// Load an audio worklet
await audio.audioWorklet.addModule('player-worklet.js');
// Create a player
const player = new AudioWorkletNode(audio, 'player-worklet');
// Connect the player to the audio context
player.connect(audio.destination);
// Start the player when the user clicks
window.addEventListener('click', () => {
audio.resume();
player.port.postMessage({
type: 'play'
});
});
由于上面的脚本称为异步函数,所以audio.audioWorklet.addModule
,我需要从html页面加载它作为ES6 module。如果您还没有将前端JavaScript作为ES6模块编写,那么您确实应该开始这样做。
<!DOCTYPE html>
<html>
<head>
<title>JS Mod Player by Anders Marzi Tornblad</title>
</head>
<body>
<script type="module" src="player.js"></script>
</body>
</html>
创建工作场代码的参与度更高,但这并不是太复杂了。我首先创建一个扩展AudioWorkletProcessor的类,这是所有音频工作点的基类。
工作点在单独的线程中运行,因此它无法访问主脚本中的任何变量。因此,每个Worklet实例都有一个port
属性,该属性是用于与主脚本通信的MessagePort。 Worklet可以使用port.postMessage
将消息发送到主脚本,并且可以使用port.onmessage
从主脚本接收消息。我通常在构造函数中放入一些代码,以将port.onmessage
事件处理程序连接到Worklet类中的onmessage
方法。
process method是Web音频框架所调用以生成声音的内容。它被反复调用,应该用音频数据填充outputs
阵列。该方法同时接收输入和输出,可以在音频图中的任何地方使用,例如为声音添加效果。因为我正在产生声音而不是操纵声音,所以我不需要使用输入,所以我只是忽略了输入。 outputs
数组是一个二维阵列,其中第一个维度是通道,第二维是样本。每个示例值都是-1和1之间的浮点数。process
方法必须返回true
,以表明它已经填充了outputs
数组的数据,并且Web音频框架应再次调用。如果该方法返回false
,则是Web音频框架的信号,表明工作场所正在生成声音,并且应该停止调用process
方法。
class PlayerWorklet extends AudioWorkletProcessor {
constructor() {
super();
this.port.onmessage = this.onmessage.bind(this);
this.playing = false;
}
onmessage(e) {
if (e.data.type === 'play') {
// Toggle between playing and silence
this.playing = !this.playing;
this.phase = 0;
}
}
process(inputs, outputs) {
const output = outputs[0];
const channel = output[0];
for (let i = 0; i < channel.length; ++i) {
if (this.playing) {
channel[i] = Math.sin(this.phase);
this.phase += 0.1;
} else {
channel[i] = 0;
}
}
return true;
}
}
registerProcessor('player-worklet', PlayerWorklet);
结论
就是这样!上面的代码是一个非常最小的示例,但是它应该使您了解如何创建工作点。下一步是将mod文件加载到工作点中,并使用它来生成声音。
您可以在atornblad.github.io/js-mod-player尝试此解决方案。
该代码的最新版本始终在the GitHub repository中可用。