【问题标题】:How to continuously generate raw audio samples in javascript using the web audio API?如何使用网络音频 API 在 javascript 中连续生成原始音频样本?
【发布时间】:2021-07-24 06:37:08
【问题描述】:

对于音乐应用,我需要能够使用网络音频 API 连续无缝地生成原始音频样本。经过搜索,我发现了AudioBuffer(https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer),看来这就是我需要的。但是,音频缓冲区只能播放一次(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 包来执行此操作。

【问题讨论】:

    标签: javascript audio web-audio-api webapi audiobuffer


    【解决方案1】:

    我认为您正在寻找的 API 是 AudioWorklet。这是一种直接在音频线程上运行代码的方法。它允许您在播放之前填充缓冲区。

    设置它通常有点复杂,因为您的处理器需要在单独的 JavaScript 文件中定义。但也可以使用Blob,如下所示。

    该示例基于生成随机样本的 sn-p。

    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);
    

    【讨论】:

    • 由于这段代码是直接在音频线程上运行的,请问有什么办法可以在主线程和音频线程之间交换数据吗?
    • 是的,您可以使用构造函数传递数据。或者您可以在运行时使用 MessagePort 发送数据。或者您可以使用 AudioParams 将数据发送到 process 函数。或者您可以使用 SharedArrayBuffer。 Chrome 团队发布了一些示例,展示了不同的选项。 googlechromelabs.github.io/web-audio-samples/audio-worklet
    • 感谢您的帮助。我尝试了它,它按预期工作,但有限的通信 api 对我的用例来说太笨拙了,因为我无法将函数传递给工作集。我会做一些 electron/node.js 魔术来让它工作。
    • 您也可以使用ScriptProcessorNode。它在主线程上运行,因此与其他一切竞争资源。它也已被弃用,但它可能永远不会消失。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 1970-01-01
    • 2015-02-10
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 2018-06-09
    相关资源
    最近更新 更多