使用网络音频API Peak Meter实现Logic 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
会更好吗?这种方法有何不同?
答案 0 :(得分:4)
如果您在一帧中取得getFloatFrequencyData()
的最大结果,那么您所测量的是单个频率的音频功率(无论哪个功率最大)。您实际想要测量的是任何频率的峰值 - 换句话说,您希望不使用频率数据,但未处理的样本未分离到频率分档中。
问题在于你必须自己计算分贝能力。这是一个相当简单的算法:你需要一些样本(一个或多个),对它们进行平方并对它们求平均值。请注意,即使是“峰值”仪表也可能正在进行平均 - 只是在更短的时间范围内。
这是一个完整的例子。 (警告:发出声音。)
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();
<p>
Short average
<meter id="avg-level" min="-100" max="10" value="-100"></meter>
<span id="avg-level-text">—</span> dB
<p>
Instantaneous
<meter id="inst-level" min="-100" max="10" value="-100"></meter>
<span id="inst-level-text">—</span> dB
答案 1 :(得分:3)
您是否采用最大值
对于峰值仪表,是的。对于VU表,在测量功率以及模拟仪表的弹道学方面有各种各样的考虑因素。还有RMS功率计量。
在数字领域,你会发现一个峰值表对许多任务最有用,而且到目前为止最容易计算。
任何给定样本集的峰值是集合中的最高绝对值。首先,您需要这组样本。如果您致电getFloatFrequencyData()
,那么您没有获得样本值,那么您就可以获得频谱。你想要的是getFloatTimeDomainData()
。该数据是样本的低分辨率表示。也就是说,您的窗口中可能有4096个样本,但您的分析器可能配置了256个桶...因此这些4096个样本将重新采样到256个样本。这对于计量任务通常是可以接受的。
从那里开始,只需Math.max(-Math.min(samples), Math.max(samples))
即可获得绝对值的最大值。
假设您需要更高分辨率的峰值仪表。为此,您需要获得所有原始样本。这就是ScriptProcessorNode派上用场的地方。您可以访问实际的样本数据。
基本上,对于此任务,AnalyserNode更快,但分辨率略低。 ScriptProcessorNode要慢得多,但分辨率稍高。