Web Audio API使用AnalyserNode创建峰值仪表

时间:2017-06-05 00:48:18

标签: javascript audio web-audio web-audio-api

使用网络音频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会更好吗?这种方法有何不同?

2 个答案:

答案 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要慢得多,但分辨率稍高。