【发布时间】:2015-10-20 11:45:15
【问题描述】:
硬编码的音频效果是一个重要的工具,但有时您想要通过每个样本可能不同且非常非线性甚至图灵完备的函数在样本级别不断重新定义声音,因此唯一的方法是写数字,在 -1 到 1 的范围内,直接进入缓冲区。
常说用javascript生成实时声音样本有计时问题。
由于它的实时声音取决于其他游戏控件,例如频率与鼠标位置或相机看到的内容成正比,我需要知道每次 javascript 再次运行时要预先计算和安排播放的缓冲区还有多少,可能在间隔为几毫秒的计时器上或在某些事件上(哪些事件可以做到这一点?)。
我已经在我的http://sourceforge.net/projects/jsoundcard java 软件中做到了这一点,该软件保留了麦克风缓冲区中剩余数据的统计信息和扬声器缓冲区中排队的数据,并使用它来不断调整消耗麦克风帧与产生扬声器的比率帧,大部分在 0.99 和 1.01 之间,以线性插值方式,所以当一个缓冲区变得有点太大时,它会更快地缩小,并且扬声器和麦克风之间的延迟,对于 Java 函数中定义的任何可能的声音效果,都会迅速缩小。您获得的延迟更少,因为它会观察时间并根据统计信息调整缓冲区使用情况。
如果可能的话,我想在 javascript 级别使用 Web Audio API 来做到这一点,或者如果他们计算声音的方式兼容,我会考虑将一些 JSoundCard 代码移植到该项目。
我应该怎么做才能访问缓冲区的使用量?
如何安排另一个缓冲区在播放我之前开始的缓冲区结束时立即继续播放?
我需要知道播放第一个缓冲区还剩下多少来选择要添加多少缓冲区,因为时间上的小错误会导致大延迟。
以下 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.');
};
【问题讨论】:
标签: javascript web-audio-api timing