如何使用网络音频 API 在 javascript 中连续生成原始音频样本?
How to continuously generate raw audio samples in javascript using the web audio API?
对于音乐应用程序,我需要能够使用网络音频连续无缝地生成原始音频样本 API。搜索了一下,发现是AudioBuffer(https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer), and it seems that that's what I need. However, the audio buffer can only be played once(https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode),所以真的不能连续播放。我试过这个解决方法:
const buffer = audioCtx.createBuffer(1, 10000, audioCtx.sampleRate);
for(let i = 0; i < buffer.length; i++)
buffer.getChannelData(0)[i] = ((i / audioCtx.sampleRate) * 440) % 1;
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.onended = function() {
console.log(this);
const newSource = audioCtx.createBufferSource();
for(let i = 0; i < buffer.length; i++)
buffer.getChannelData(0)[i] = ((i / audioCtx.sampleRate) * 440) % 1;
newSource.buffer = buffer;
newSource.connect(audioCtx.destination);
newSource.onended = (this.onended as Function).bind(newSource);
newSource.start();
}
source.connect(audioCtx.destination);
source.start();
本质上,这段代码创建了一个缓冲区和源节点,播放缓冲区,当缓冲区结束时,它创建一个新的源和缓冲区并继续播放。但是,当缓冲区完成播放时,此方法会产生明显的静音。我假设这与 JS 事件循环有关,但我不确定。
理想情况下,我想要这样的东西:
audioCtx.createSampleStream(() => {
// generate samples here.
return Math.random() * 2 - 1;
})
希望我能够让它发挥作用。如果我不这样做,我可能会尝试编写一个带有 C++ 绑定的 npm 包来执行此操作。
我认为您要找的 API 是 AudioWorklet
。这是一种直接在音频线程上 运行 您的代码的方法。它允许您在播放之前立即填充缓冲区。
设置它通常有点复杂,因为您的处理器需要在单独的 JavaScript 文件中定义。但也可以使用如下所示的 Blob
。
该示例基于生成随机样本的代码段。
const blob = new Blob(
[`
class MyProcessor extends AudioWorkletProcessor {
process(_, outputs) {
for (const output of outputs) {
for (const channelData of output) {
for (let i = 0; i < channelData.length; i += 1) {
channelData[i] = Math.random() * 2 - 1;
}
}
}
return true;
}
}
registerProcessor('my-processor', MyProcessor);
`],
{ type: 'application/javascript' }
);
const url = URL.createObjectURL(blob);
const audioContext = new AudioContext();
await audioContext.audioWorklet.addModule(url);
const myAudioWorkletNode = new AudioWorkletNode(audioContext, 'my-processor');
myAudioWorkletNode.connect(audioContext.destination);
对于音乐应用程序,我需要能够使用网络音频连续无缝地生成原始音频样本 API。搜索了一下,发现是AudioBuffer(https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer), and it seems that that's what I need. However, the audio buffer can only be played once(https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode),所以真的不能连续播放。我试过这个解决方法:
const buffer = audioCtx.createBuffer(1, 10000, audioCtx.sampleRate);
for(let i = 0; i < buffer.length; i++)
buffer.getChannelData(0)[i] = ((i / audioCtx.sampleRate) * 440) % 1;
const source = audioCtx.createBufferSource();
source.buffer = buffer;
source.onended = function() {
console.log(this);
const newSource = audioCtx.createBufferSource();
for(let i = 0; i < buffer.length; i++)
buffer.getChannelData(0)[i] = ((i / audioCtx.sampleRate) * 440) % 1;
newSource.buffer = buffer;
newSource.connect(audioCtx.destination);
newSource.onended = (this.onended as Function).bind(newSource);
newSource.start();
}
source.connect(audioCtx.destination);
source.start();
本质上,这段代码创建了一个缓冲区和源节点,播放缓冲区,当缓冲区结束时,它创建一个新的源和缓冲区并继续播放。但是,当缓冲区完成播放时,此方法会产生明显的静音。我假设这与 JS 事件循环有关,但我不确定。
理想情况下,我想要这样的东西:
audioCtx.createSampleStream(() => {
// generate samples here.
return Math.random() * 2 - 1;
})
希望我能够让它发挥作用。如果我不这样做,我可能会尝试编写一个带有 C++ 绑定的 npm 包来执行此操作。
我认为您要找的 API 是 AudioWorklet
。这是一种直接在音频线程上 运行 您的代码的方法。它允许您在播放之前立即填充缓冲区。
设置它通常有点复杂,因为您的处理器需要在单独的 JavaScript 文件中定义。但也可以使用如下所示的 Blob
。
该示例基于生成随机样本的代码段。
const blob = new Blob(
[`
class MyProcessor extends AudioWorkletProcessor {
process(_, outputs) {
for (const output of outputs) {
for (const channelData of output) {
for (let i = 0; i < channelData.length; i += 1) {
channelData[i] = Math.random() * 2 - 1;
}
}
}
return true;
}
}
registerProcessor('my-processor', MyProcessor);
`],
{ type: 'application/javascript' }
);
const url = URL.createObjectURL(blob);
const audioContext = new AudioContext();
await audioContext.audioWorklet.addModule(url);
const myAudioWorkletNode = new AudioWorkletNode(audioContext, 'my-processor');
myAudioWorkletNode.connect(audioContext.destination);