使用Web音频API将多个ArrayBuffer合并/层叠成一个AudioBuffer

6

我需要对.wav音频轨道进行分层,最终需要能够开启和关闭,并保持同步。

首先,我加载这些音频轨道,并停止BufferLoader将加载的arraybuffer转换为AudioBuffer(因此使用了false)。

        function loadTracks(data) {
            for (var i = 0; i < data.length; i++) {
                trackUrls.push(data[i]['url']);
            };
            bufferLoader = new BufferLoader(context, trackUrls, finishedLoading);
            bufferLoader.load(false);
            return loaderDefered.promise;
        }

当您在屏幕上点击按钮时,它会调用startStop()函数。
    function startStop(index, name, isPlaying) {
        if(!activeBuffer) {
            activeBuffer = bufferList[index];
        }else{
            activeBuffer = appendBuffer(activeBuffer, bufferList[index]);
        }
        context.decodeAudioData(activeBuffer, function(buffer){
            audioBuffer = buffer;
            play();
        })


    function play() {
        var scheduledTime = 0.015;
        try {
            audioSource.stop(scheduledTime);
        } catch (e) {}

        audioSource = context.createBufferSource();
        audioSource.buffer = audioBuffer;
        audioSource.loop = true;
        audioSource.connect(context.destination);
        var currentTime = context.currentTime + 0.010 || 0;
        audioSource.start(scheduledTime - 0.005, currentTime, audioBuffer.duration - currentTime);
        audioSource.playbackRate.value = 1;
    }

我发现的大部分代码都是从这个人的github上找到的。

在演示中,你可以听到他正在叠加 AudioBuffers。

我尝试了同样的东西在我的托管上

忽略 argularJS 部分,Web Audio 的内容在 service.js 中进行:

/js/angular/service.js 

如果您打开控制台并点击按钮,您可以看到 activeBuffer.byteLength(类型为ArrayBuffer)正在增加,但是即使通过context.decodeAudioData方法进行解码,它仍然只播放您单击的第一个声音而不是合并的AudioBuffer

2个回答

7
我不太确定我完全理解您的情况 - 您不希望这些同时播放吗?(例如低音加在鼓上)。
您当前的代码会在每次点击该文件的按钮时尝试连接另一个音频文件。您不能只是连接音频文件(以其编码形式),然后再通过解码进行处理 - decodeAudioData方法正在解码数组缓冲区中的第一个完整声音,然后停止运行(因为它已经完成了声音的解码)。
您应该改变逻辑,从生成的AudioBuffers中连接缓冲区数据(见下文)。即使这种逻辑还不完全正确 - 它仍会缓存编码音频文件并且每次点击按钮时都进行解码。相反地,您应该缓存解码的音频缓冲区,然后只需连接即可。
function startStop(index, name, isPlaying) {

    // Note we're decoding just the new sound
    context.decodeAudioData( bufferList[index], function(buffer){
        // We have a decoded buffer - now we need to concatenate it
        audioBuffer = buffer;

        if(!audioBuffer) {
            audioBuffer = buffer;
        }else{
            audioBuffer = concatenateAudioBuffers(audioBuffer, buffer);
        }

        play();
    })
}

function concatenateAudioBuffers(buffer1, buffer2) {
    if (!buffer1 || !buffer2) {
        console.log("no buffers!");
        return null;
    }

    if (buffer1.numberOfChannels != buffer2.numberOfChannels) {
        console.log("number of channels is not the same!");
        return null;
    }

    if (buffer1.sampleRate != buffer2.sampleRate) {
        console.log("sample rates don't match!");
        return null;
    }

    var tmp = context.createBuffer(buffer1.numberOfChannels, buffer1.length + buffer2.length, buffer1.sampleRate);

    for (var i=0; i<tmp.numberOfChannels; i++) {
        var data = tmp.getChannelData(i);
        data.set(buffer1.getChannelData(i));
        data.set(buffer2.getChannelData(i),buffer1.length);
    }
    return tmp;
};

嗨,我们使用 data.set(buffer1.getChannelData(i)); data.set(buffer2.getChannelData(i),buffer1.length); 来追加两个缓冲区。如果我们想要合并它们,以便同时播放,该怎么办呢? - AbsarAkram
请回答这个问题。 - Zane Hitchcox
如果你想将它们分层 - 就像,真的只是同时播放它们 - 我个人会将它们保持为单独的AudioBuffers,并创建两个在同一时间start()的AudioBufferSourceNodes。但如果你真的想叠加,只需遍历并总结缓冲区数组中的每个值(不要使用"Buffer.set()",而是直接执行data[i] = buf[i]+buf2[i];)。 - cwilso

-1

已解决:

要同时播放多个相同持续时间的循环,并在随机启动和停止它们时保持同步。

首先,创建所有缓冲区源,其中bufferList是一个AudioBuffers数组,第一个声音是您将从中读取并用其他声音覆盖的声音。

    function createAllBufferSources() {
        for (var i = 0; i < bufferList.length; i++) {
            var source = context.createBufferSource();
            source.buffer = bufferList[i];
            source.loop = true;
            bufferSources.push(source);
        };
        console.log(bufferSources)
    }

然后:

    function start() {
        var rewrite = bufferSources[0];
        rewrite.connect(context.destination);
        var processNode = context.createScriptProcessor(2048, 2, 2);
        rewrite.connect(processNode)
        processNode.onaudioprocess = function(e) {
                            //getting the left and right of the sound we want to overwrite
            var left = rewrite.buffer.getChannelData(0);
            var right = rewrite.buffer.getChannelData(1);
            var overL = [],
                overR = [],
                i, a, b, l;

            l = bufferList.length,
                            //storing all the loops channel data
            for (i = 0; i < l; i++) {
                    overL[i] = bufferList[i].getChannelData(0);
                    overR[i] = bufferList[i].getChannelData(1);
            }
                            //looping through the channel data of the sound we are going to overwrite
            a = 0, b = overL.length, l = left.length;
            for (i = 0; i < l; i++) {
                            //making sure its a blank before we start to write
                left[i] -= left[i];
                right[i] -= right[i];
                            //looping through all the sounds we want to add and assigning the bytes to the old sound, both at the same position
                for (a = 0; a < b; a++) {
                    left[i] += overL[a][i];
                    right[i] += overR[a][i];
                }
                left[i] /= b;
                right[i] /= b);
            }
        };

        processNode.connect(context.destination);
        rewrite.start(0)
    }

如果您从bufferList中删除AudioBuffer并在任何时候再次添加它,则始终会保持同步。
编辑:
请记住: -处理器节点的垃圾收集方式很奇怪。 -这非常耗费资源,可能需要考虑以某种方式使用WebWorkers。

啊!你不需要使用ScriptProcessorNode来做这个;这样会导致大量的线程间通信和潜在的故障,而你可以很容易地连接缓冲区(见上文)或者在适当的偏移时间调用start()函数。 - cwilso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接