使用Web 峰值计实现类似于逻辑Pro中的AnalyserNode
的正确方法是什么
我知道AnalyserNode.getFloatFrequencyData()
返回分贝值,但是如何组合这些值以获得表中显示的分贝值?您是否只取以下代码示例中的最大值( analyserData
来自getFloatFrequencyData()
)?
let peak = -Infinity;
for (let i = 0; i < analyserData.length; i++) {
const x = analyserData[i];
if (x > peak) {
peak = x;
}
}
检查一些输出只是取最大值,这看起来是不正确的方法。我说错了吗?
或者,用ScriptProcessorNode
代替是否是更好的主意?这种做法有何不同?
发布于 2017-06-04 18:01:22
如果在一个帧中取getFloatFrequencyData()
结果的最大值,那么您正在测量的是单个频率的音频功率(无论哪个频率最大)。实际上要测量的是任何频率上的峰值--换句话说,您不想使用频率数据,但是未处理的样本没有分离到频率箱中。
问题是,你必须自己计算分贝的功率。这是一个相当简单的算法:获取一些样本(一个或多个),对它们进行平方,并对它们进行平均值。请注意,即使是一个“峰值”表,可能是平均-只是在一个更短的时间尺度。
下面是一个完整的例子。(警告:发出声音。)
document.getElementById('start').addEventListener('click', () => {
const context = new(window.AudioContext || window.webkitAudioContext)();
const oscillator = context.createOscillator();
oscillator.type = 'square';
oscillator.frequency.value = 440;
oscillator.start();
const gain1 = context.createGain();
const analyser = context.createAnalyser();
// Reduce output level to not hurt your ears.
const gain2 = context.createGain();
gain2.gain.value = 0.01;
oscillator.connect(gain1);
gain1.connect(analyser);
analyser.connect(gain2);
gain2.connect(context.destination);
function displayNumber(id, value) {
const meter = document.getElementById(id + '-level');
const text = document.getElementById(id + '-level-text');
text.textContent = value.toFixed(2);
meter.value = isFinite(value) ? value : meter.min;
}
// Time domain samples are always provided with the count of
// fftSize even though there is no FFT involved.
// (Note that fftSize can only have particular values, not an
// arbitrary integer.)
analyser.fftSize = 2048;
const sampleBuffer = new Float32Array(analyser.fftSize);
function loop() {
// Vary power of input to analyser. Linear in amplitude, so
// nonlinear in dB power.
gain1.gain.value = 0.5 * (1 + Math.sin(Date.now() / 4e2));
analyser.getFloatTimeDomainData(sampleBuffer);
// Compute average power over the interval.
let sumOfSquares = 0;
for (let i = 0; i < sampleBuffer.length; i++) {
sumOfSquares += sampleBuffer[i] ** 2;
}
const avgPowerDecibels = 10 * Math.log10(sumOfSquares / sampleBuffer.length);
// Compute peak instantaneous power over the interval.
let peakInstantaneousPower = 0;
for (let i = 0; i < sampleBuffer.length; i++) {
const power = sampleBuffer[i] ** 2;
peakInstantaneousPower = Math.max(power, peakInstantaneousPower);
}
const peakInstantaneousPowerDecibels = 10 * Math.log10(peakInstantaneousPower);
// Note that you should then add or subtract as appropriate to
// get the _reference level_ suitable for your application.
// Display value.
displayNumber('avg', avgPowerDecibels);
displayNumber('inst', peakInstantaneousPowerDecibels);
requestAnimationFrame(loop);
}
loop();
});
<button id="start">Start</button>
<p>
Short average
<meter id="avg-level" min="-100" max="10" value="-100"></meter>
<span id="avg-level-text">—</span> dB
</p>
<p>
Instantaneous
<meter id="inst-level" min="-100" max="10" value="-100"></meter>
<span id="inst-level-text">—</span> dB
</p>
发布于 2017-06-04 17:43:36
你只是取最大值
对于峰值计,是的。对于VU计量器,在测量功率时有各种各样的考虑,以及模拟仪表的弹道。还有RMS的电能计量。
在数字土地上,你会发现一个峰值表对许多任务都是最有用的,而且到目前为止计算起来也是最容易的。
任何给定样本集的峰值是集合中的最高绝对值。但是,首先,您需要一组样本。如果你打电话给getFloatFrequencyData()
,你得到的不是样本值,而是光谱。相反,您需要的是getFloatTimeDomainData()
。此数据是样本的低分辨率表示。也就是说,您可能在您的窗口中有4096个样本,但是您的分析器可能配置有256个桶.因此,这4096个样本将被重新放大到256个样本。对于计量任务来说,这通常是可以接受的。
从这里,得到绝对值的最大值就是Math.max(-Math.min(samples), Math.max(samples))
。
假设你想要一个更高分辨率的峰值计。为此,你需要所有你能得到的原始样本。这就是ScriptProcessorNode派上用场的地方。您可以访问实际的示例数据。
基本上,对于这个任务,AnalyserNode要快得多,但分辨率要低一些。ScriptProcessorNode要慢得多,但分辨率要稍高一些。
https://stackoverflow.com/questions/44360301
复制