Web Audio API如何获取已经播放的缓冲区数量并安排下一个缓冲区在连续播放后立即播放?

时间:2015-10-19 23:03:03

标签: javascript web-audio timing

硬编码的音频效果是一个重要的工具,但有时您想要的是通过每个样本可能不同且非常非线性甚至图灵完整的功能来连续重新定义样本级别的声音,因此唯一的方法是编写数字,在-1到1的范围内,直接进入缓冲区。

经常说在javascript中生成实时声音样本有时间问题。

由于它的实时声音取决于其他游戏控制,例如频率与鼠标位置或相机看到的频率成正比,我需要知道每次javascript再次运行时预计算和计划播放的缓冲区有多少,也许是在一个间隔几毫秒或某些事件的计时器上(以及哪些事件可以做到这一点?)。

我已经在我的http://sourceforge.net/projects/jsoundcard java软件中完成了这项工作,该软件保留了麦克风缓冲区中剩余数据的统计数据和扬声器缓冲区中排队的数据,并使用它来连续调整消耗麦克风帧的比率以线性插值的方式产生扬声器帧,大多数介于0.99和1.01之间,因此当一个缓冲器变得有点太大时,它会缩小得更快,扬声器和麦克风之间的延迟,对于Java功能中定义的任何可能的声音效果,都会很快收缩。您可以减少延迟,因为它会监视时间并通过统计信息调整缓冲区使用情况。

我希望使用Web Audio API,如果可能的话,在javascript级别,或者如果有必要,我会考虑将一些JSoundCard代码移植到该项目,如果他们的计算声音方式是兼容的。

我该怎么做才能访问已经使用了多少缓冲区?

如何安排另一个缓冲区在播放我之前开始的缓冲区结束时立即继续播放?

我需要知道播放第一个缓冲区还有多少剩余要添加多少缓冲区,因为时间上的小错误会导致大的延迟。

Heres javascript代码(适用于Windows 7中的Chrome,Firefox和Opera),可以在特定时间生成一次性声音(频率不断上升):

var audConType = window.AudioContext || window.webkitAudioContext;  
var context = new audConType();
var around = 0;
var aroundSpeed = .1;
var soundFunc = function(amplitudeIn){ //ignore amplitudeIn for now
    aroundSpeed += .000002;
    return Math.sin(around += aroundSpeed);
};
var channels = 1;
var howManyFrames = 50000;
var rate = 44100;
var buffer = context.createBuffer(channels, howManyFrames, rate);
for(var channel=0; channel<channels; channel++){
    var chanBuf = buffer.getChannelData(channel);
    for (var f=0; f<howManyFrames; f++){
        chanBuf[f] = soundFunc(0);
    }
}
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start();

编辑,cwilso回复后的新代码:

ScriptProcessorNode允许我使用javascript代码处理麦克风示例并将其发送到Firefox中的扬声器(为什么Chrome和Opera也不行?),但是还有1/3秒的延迟,这使得它对于现场音乐表演或者对于现场音乐表现来说是不切实际的游戏中的事件。有没有办法设置麦克风缓冲区大小?

var volume = .7;
var maxMicrophoneAmplitude = 1;
var soundFunc = function(ins, outs){
    var microphoneAmplitude = ins[0];
    var estimatedFramesPerSecond = 44100;
    var decaySeconds = .1;
    var decay = 1/(estimatedFramesPerSecond*decaySeconds);
    maxMicrophoneAmplitude = Math.max(maxMicrophoneAmplitude, microphoneAmplitude)*(1-decay);
    microphoneAmplitude/maxMicrophoneAmplitude * volume
    outs[0] = microphoneAmplitude/maxMicrophoneAmplitude * volume;
};
soundFunc.inSize = 1;
soundFunc.outSize = 1;
var inputNodes = {};
window.onload = function(){
    var context = new AudioContext();
    if(!navigator.getUserMedia && navigator['mozGetUserMedia']){
        navigator.getUserMedia = navigator['mozGetUserMedia'];
    }
    if(!navigator.getUserMedia && navigator['webkitGetUserMedia']){
        navigator.getUserMedia = navigator['webkitGetUserMedia'];
    }
    if(!navigator.getUserMedia && navigator['msGetUserMedia']){
        navigator.getUserMedia = navigator['msGetUserMedia'];
    }
    var framesPerBuffer = 1024;
    var scriptNode = context.createScriptProcessor(framesPerBuffer, 1, 1);
    scriptNode.onaudioprocess = function(audioProcessingEvent){
        var inputBuffer = audioProcessingEvent.inputBuffer;
        var outputBuffer = audioProcessingEvent.outputBuffer;
        var chansIn = [];
        var chansOut = [];
        for(var c=0; c<inputBuffer.numberOfChannels; c++){
            chansIn[c] = inputBuffer.getChannelData(c);
        }
        for(var c=0; c<outputBuffer.numberOfChannels; c++){
            chansOut[c] = outputBuffer.getChannelData(c);
        }
        var ins = new Float32Array(inputBuffer.numberOfChannels);
        var outs = new Float32Array(outputBuffer.numberOfChannels);
        for(var f=0; f<framesPerBuffer; f++){
            for(var c=0; c<inputBuffer.numberOfChannels; c++){
                ins[c] = chansIn[c][f];
            }
            soundFunc(ins, outs);
            for(var c=0; c<outputBuffer.numberOfChannels; c++){
                chansOut[c][f] = outs[c];
            }
        }

    }
    if(navigator.getUserMedia){
        navigator.getUserMedia(
            {'audio':true},
            function(stream){
                var input = context.createMediaStreamSource(stream);
                //http://stackoverflow.com/questions/22860468/html5-microphone-capture-stops-after-5-seconds-in-firefox says to save a reference to avoid sound ending after 5 seconds
                inputNodes.mic = input;
                input.connect(scriptNode);
                scriptNode.connect(context.destination);
                input.start();
            },
            function(e){ alert('Error capturing audio. e='+e); }
        );
    }else alert('getUserMedia not supported in this browser.'); 
};

1 个答案:

答案 0 :(得分:1)

执行此操作的最佳方法是不使用AudioBuffers,而是使用ScriptProcessor(当前;这将被AudioWorkers取代,但它们尚未在任何地方实现)。这将使您能够以动态方式完全控制音频位。