从Audio获取logarithmic byteFrequencyData

时间:2016-03-04 15:06:21

标签: javascript web-audio

我问了一个类似于此前的问题,但它没有解决我的问题并且解释得很差。 这次我做了插图,希望能更好地解释。

我的音频播放器有一个简单的频谱分析仪。频率存储在每个requestAnimationFrame更新的数组中,数组如下所示:

fbc_array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(fbc_array);

Read more about getByteFrequencyData here.

所以这很好用,但我希望频率在整个光谱范围内均匀分布。现在它显示线性频率:

enter image description here

正如您所看到的,这里的主要频率范围是高音(高端),而最主要的频率范围是低音范围(低端)。我希望我的分析仪具有均匀分布的频率范围,如下所示:

enter image description here

在这里,您可以看到分析仪上均匀分布的频率。这可能吗?

我用于生成分析器的代码如下所示:

// These variables are dynamically changed, ignore them.
var canbars = 737
var canmultiplier = 8
var canspace = 1

// The analyser
var canvas, ctx, source, context, analyser, fbc_array, bars, bar_x,
    bar_width, bar_height;

function audioAnalyserFrame() {
    'use strict';
    var i;
    canvas.width = $('analyser-').width();
    canvas.height = $('analyser-').height();
    ctx.imageSmoothingEnabled = false;
    fbc_array = new Uint8Array(analyser.frequencyBinCount);
    analyser.getByteFrequencyData(fbc_array);
    ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
    ctx.fillStyle = "white"; // Color of the bars
    bars = canbars;
    for (i = 0; i < bars; i += canmultiplier) {
        bar_x = i * canspace;
        bar_width = 2;
        bar_height = -3 - (fbc_array[i] / 2);
        ctx.fillRect(bar_x, canvas.height, bar_width, bar_height);
    }
    window.requestAnimationFrame(audioAnalyserFrame);
}

function audioAnalyserInitialize() {
    'use strict';
    var analyserElement = document.getElementById('analyzer');

    if (analyserElement !== null && audioViewIsCurrent() === true) {
        if (analyserInitialized === false) {
            context = new AudioContext();
            source = context.createMediaElementSource(audioSource);
        } else {
            analyser.disconnect();
        }
        analyser = context.createAnalyser();
        canvas = analyserElement;
        ctx = canvas.getContext('2d');
        source.connect(analyser);
        analyser.connect(context.destination);
        if (analyserInitialized === false) {
            audioAnalyserFrame();
        }
        analyserInitialized = true;
        analyser.smoothingTimeConstant = 0.7;
    }
}

请注意我在for循环中跳过8个小节(参见顶部的canmultiplier)(如果我不这样做,分析仪的另一半会在画布之外渲染,因为它&#39 ;太大了。)我不知道这是否也可能导致频率范围不一致。

6 个答案:

答案 0 :(得分:3)

如果我理解正确,我认为这对你有用,虽然远非完美。

你在for循环中所做的是每8个元素对数组进行一次采样。我要做的是以对数方式进行采样。

一个例子:

//Given a range, transforms a value from linear scale to log scale.
var toLog = function(value, min, max){
    var exp = (value-min) / (max-min);
  return min * Math.pow(max/min, exp);
}

//This would be the frequency array in a linear scale
var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];

//In this case i'm using a range from 1 to 20, you would use the size of your array. I'm incrementing 'i' by one each time, but you could also change that
for (var i = 1; i < 20; i += 1) {
  //I'm starting at 1 because 0 and logarithms dont get along
  var logindex = toLog(i,1,19); //the index we want to sample

  //As the logindex will probably be decimal, we need to interpolate (in this case linear interpolation)
  var low = Math.floor(logindex);
  var high = Math.ceil(logindex);
  var lv = arr[low];
  var hv = arr[high];
  var w = (logindex-low)/(high-low);
  var v = lv + (hv-lv)*w; //the interpolated value of the original array in the logindex index.
    document.write(v + "<br/>");  //In your case you should draw the bar here or save it in an array for later.
}

我希望我能很好地解释自己。在这里你有一个working demo,它有一些边界错误,但它可以正常工作。

答案 1 :(得分:2)

我相信我明白你的意思。问题不在于你的代码,而是在getByteFrequencyData下面的FFT。核心问题是音乐音符以对数方式间隔,而 FFT频率区间线性间隔

音符以对数间隔:连续低音符之间的差异,例如 A2 (110 Hz)和 A2#(116.5 Hz), 6.5 Hz,而相同2个音符在较高八度音阶 A3 (220 Hz)和 A3#(233.1 Hz)之间的差值为13.1 Hz。

FFT区间是线性间隔的:假设我们每秒处理44100个样本,FFT采用1024个样本(一个波形)的窗口,并且首先将它与波形相乘,只要1024个样本(我们称之为 wave1 ),因此1024/44100=0.023 seconds的句点为43.48 Hz,并将得到的幅度放在第一个bin中。然后它将它与频率为 wave1 * 2的波相乘,即86.95 Hz,然后 wave1 * 3 = 130.43 Hz。所以频率之间的差异是线性的;它总是相同= 43.48,不同于音符的变化。

这就是为什么近距离低频将被捆绑在同一个音箱中而高频近距离分离的原因。这是FFT频率分辨率的问题。它可以通过采用大于1024个样本的窗口来解决,但这将是时间分辨率的折衷。

答案 2 :(得分:1)

您必须手动平均值(或类似的值)才能将其转换为对数数组;这就是FFT算法的工作方式。

答案 3 :(得分:0)

另一种可能有效或无效的方法。打破信号,比如5个频段。应用低通和高通滤波器以及覆盖整个频率范围的3个带通滤波器。将所有滤波器(低通)除外的输出调制为降频0。为5种不同的信号中的每一种添加分析仪。绘制每个响应,并考虑到您已经将滤波器输出频率向下移动。

各个分析仪输出仍然是一致的,但结果可能足够接近。

(可以使用一个或两个增益节点调制低至0频率,其增益是来自振荡器节点的正弦或余弦波。)

答案 4 :(得分:0)

有些事情应该有效:

// These variables are dynamically changed, ignore them.
var canbars = 737
var canmultiplier = 8
var canspace = 1

// The analyser
var canvas, ctx, source, context, analyser, fbc_array, bars, bar_x,
    bar_width, bar_height;

function audioAnalyserFrame() {
    'use strict';
    var i;
    canvas.width = $('analyser-').width();
    canvas.height = $('analyser-').height();
    ctx.imageSmoothingEnabled = false;
    fbc_array = new Uint8Array(analyser.frequencyBinCount);
    analyser.getByteFrequencyData(fbc_array);
    ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
    ctx.fillStyle = "white"; // Color of the bars
    bars = canbars;
    //Find the center
    var center = Math.round(bars / 2) - 1;
    for (i = 0; i < fbc_array.length; i ++) {
        // Update the spectrum bars, spread evenly.
        bar_x = (center + (i % 2 == 0 ? -1 : 1) * Math.round(i / 2));
        bar_width = 2;
        bar_height = -3 - (fbc_array[i] / 2);
        ctx.fillRect(bar_x, canvas.height, bar_width, bar_height);
    }
    window.requestAnimationFrame(audioAnalyserFrame);
}

function audioAnalyserInitialize() {
    'use strict';
    var analyserElement = document.getElementById('analyzer');

    if (analyserElement !== null && audioViewIsCurrent() === true) {
        if (analyserInitialized === false) {
            context = new AudioContext();
            source = context.createMediaElementSource(audioSource);
        } else {
            analyser.disconnect();
        }
        analyser = context.createAnalyser();
        canvas = analyserElement;
        ctx = canvas.getContext('2d');
        source.connect(analyser);
        analyser.connect(context.destination);
        if (analyserInitialized === false) {
            audioAnalyserFrame();
        }
        analyserInitialized = true;
        analyser.smoothingTimeConstant = 0.7;
    }
}

改进了一步,将“更新”包装在一个函数

function audioAnalyserFrame() {
  'use strict';
  var i;
  canvas.width = $('analyser-').width();
  canvas.height = $('analyser-').height();
  ctx.imageSmoothingEnabled = false;
  fbc_array = new Uint8Array(analyser.frequencyBinCount);

  ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
  ctx.fillStyle = "white"; // Color of the bars
  bars = canbars;
  //Find the center
  var center = Math.round(bars / 2) - 1;
  (update = function() {
      window.requestAnimationFrame(update);
      analyser.getByteFrequencyData(fbc_array);
      for (i = 0; i < fbc_array.length; i++) {
        // Update the spectrum bars, spread evenly.
        bar_x = (center + (i % 2 == 0 ? -1 : 1) * Math.round(i / 2));
        bar_width = 2;
        bar_height = -3 - (fbc_array[i] / 2);
        ctx.fillRect(bar_x, canvas.height, bar_width, bar_height);
      }
    }();
  }

答案 5 :(得分:0)

在我看来,您可以通过将当前条的x位置乘以10 / i来简单地分隔条。我不确定这是否正确,但看起来是否正确。八度变化在图表中均匀分布,这是正确的。

请参阅我的Fourier Series可视化器版本,该可视化器还呈现所生成音频信号的fft分析器: https://editor.p5js.org/mohragk/sketches/BkMiw4KxV

分析器代码在drawAnalyser()中。