网络音频 API 使用 AnalyserNode 创建峰值计
Web Audio API creating a Peak Meter with AnalyserNode
实施 Peak Meter like those in Logic Pro with the Web Audio API AnalyserNode
的正确方法是什么?
我知道 AnalyserNode.getFloatFrequencyData()
returns 分贝值,但如何组合这些值才能在仪表中显示?您是否像下面的代码示例一样取最大值(其中 analyserData
来自 getFloatFrequencyData()
:
let peak = -Infinity;
for (let i = 0; i < analyserData.length; i++) {
const x = analyserData[i];
if (x > peak) {
peak = x;
}
}
检查仅取最大值的一些输出使得这看起来不是正确的方法。我错了吗?
或者,改用 ScriptProcessorNode
会更好吗?这种方法有何不同?
Do you just take the maximum value
对于峰值计,是的。对于 VU 表,在测量功率以及模拟表的弹道方面有各种各样的考虑因素。还有 RMS 功率计量。
在数字领域,您会发现峰值计对许多任务最有用,而且迄今为止最容易计算。
任何给定样本集的峰值是该集中的最高绝对值。不过,首先,您需要那组样本。如果您调用 getFloatFrequencyData()
,您得到的不是样本值,而是频谱。你想要的是 getFloatTimeDomainData()
。此数据是样本的低分辨率表示。也就是说,您的 window 中可能有 4096 个样本,但您的分析器可能配置了 256 个桶...因此这 4096 个样本将被重新采样为 256 个样本。这对于计量任务来说通常是可以接受的。
从那里开始,只需 Math.max(-Math.min(samples), Math.max(samples))
即可获得绝对值的最大值。
假设您想要更高分辨率的峰值计。为此,您需要获得所有原始样品。这就是 ScriptProcessorNode 派上用场的地方。您可以访问实际示例数据。
基本上,对于此任务,AnalyserNode 速度更快,但分辨率略低。 ScriptProcessorNode 慢得多,但分辨率稍高。
如果你在一帧中取getFloatFrequencyData()
的最大值,那么你测量的是音频功率在单一频率(取其有最强大)。您实际想要测量的是 any 频率处的峰值 — 换句话说,您想要 not 使用频率数据,但未处理的样本没有分成频率仓。
问题是您必须自己计算分贝功率。这是相当简单的算术:你取一些样本(一个或多个),对它们进行平方,然后取平均值。请注意,即使是“峰值”仪表也可能会进行平均 - 只是在更短的时间范围内。
这是一个完整的例子。 (警告:发出声音。)
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>
实施 Peak Meter like those in Logic Pro with the Web Audio API AnalyserNode
的正确方法是什么?
我知道 AnalyserNode.getFloatFrequencyData()
returns 分贝值,但如何组合这些值才能在仪表中显示?您是否像下面的代码示例一样取最大值(其中 analyserData
来自 getFloatFrequencyData()
:
let peak = -Infinity;
for (let i = 0; i < analyserData.length; i++) {
const x = analyserData[i];
if (x > peak) {
peak = x;
}
}
检查仅取最大值的一些输出使得这看起来不是正确的方法。我错了吗?
或者,改用 ScriptProcessorNode
会更好吗?这种方法有何不同?
Do you just take the maximum value
对于峰值计,是的。对于 VU 表,在测量功率以及模拟表的弹道方面有各种各样的考虑因素。还有 RMS 功率计量。
在数字领域,您会发现峰值计对许多任务最有用,而且迄今为止最容易计算。
任何给定样本集的峰值是该集中的最高绝对值。不过,首先,您需要那组样本。如果您调用 getFloatFrequencyData()
,您得到的不是样本值,而是频谱。你想要的是 getFloatTimeDomainData()
。此数据是样本的低分辨率表示。也就是说,您的 window 中可能有 4096 个样本,但您的分析器可能配置了 256 个桶...因此这 4096 个样本将被重新采样为 256 个样本。这对于计量任务来说通常是可以接受的。
从那里开始,只需 Math.max(-Math.min(samples), Math.max(samples))
即可获得绝对值的最大值。
假设您想要更高分辨率的峰值计。为此,您需要获得所有原始样品。这就是 ScriptProcessorNode 派上用场的地方。您可以访问实际示例数据。
基本上,对于此任务,AnalyserNode 速度更快,但分辨率略低。 ScriptProcessorNode 慢得多,但分辨率稍高。
如果你在一帧中取getFloatFrequencyData()
的最大值,那么你测量的是音频功率在单一频率(取其有最强大)。您实际想要测量的是 any 频率处的峰值 — 换句话说,您想要 not 使用频率数据,但未处理的样本没有分成频率仓。
问题是您必须自己计算分贝功率。这是相当简单的算术:你取一些样本(一个或多个),对它们进行平方,然后取平均值。请注意,即使是“峰值”仪表也可能会进行平均 - 只是在更短的时间范围内。
这是一个完整的例子。 (警告:发出声音。)
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>