【问题标题】:Get logarithmic byteFrequencyData from Audio从音频中获取对数字节频率数据
【发布时间】:2016-06-18 09:06:06
【问题描述】:

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

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

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

Read more about getByteFrequencyData 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)(如果我不这样做,分析器的另一半会渲染到画布之外,因为它太大了。)我不'不知道这是否也是导致频率范围不一致的原因。

【问题讨论】:

    标签: javascript web-audio-api


    【解决方案1】:

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

    您在 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,它有一些边界错误,但我认为它可以按您的需要工作。

    【讨论】:

    • 请问为什么第一个和最后一个索引不是数字?
    • 这是一个简单的边界检查,我省略了以使代码更清晰。
    • 好的,让我快速尝试一下。我如何告诉它忽略数组的最后一个和第一个索引?
    • 在我的示例中,您可以从 2 到 19 运行 for 循环。更灵活的方法是检查 'low' 和 'high' 是否是数组的有效索引以及它们是否不同(如果它们相等,则除以零)。
    • array.length - 1 也可以,对吧?因为我们不知道数组的确切长度。
    【解决方案2】:

    我相信我完全理解您的意思。问题不在于您的代码,而在于底层的 FFT getByteFrequencyData。核心问题是音乐音符是对数间隔,而FFT频率区间是线性间隔

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

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

    这就是为什么靠近的低频会被捆绑在同一个 bin 中,而靠近的高频会被分开。这就是 FFT 的频率分辨率的问题。可以通过采用大于 1024 个样本的窗口来解决此问题,但这会以时间分辨率为代价。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • 是的,但是怎么做?这是一个每帧都会更改的数组,因此对其进行大量计算(我之前尝试过)会使我的浏览器停止爬行。
      • 另一种方法是使用一堆带通滤波器,其中心频率为您想要的频率。将每个过滤器的输出平方并绘制过滤器的输出(可能使用 AnalyserNode 来获取此数据)。
      【解决方案4】:

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

      查看我的傅立叶级数可视化器版本,它还呈现生成的音频信号的 fft 分析器: https://editor.p5js.org/mohragk/sketches/BkMiw4KxV

      分析器代码在drawAnalyser()

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

        • 必须有更简单的方法。当然你可以只处理频率 bin 数组?
        • 可能。只是不完全确定如何。你可以做一些事情,比如拿第一个垃圾箱。将下一个 2 相加,然后 4、8、16 等等。但是最后一个 bin 将有一半(左右)的频带。
        • 或者计算每三分之一(比如)八度音阶的边界。总结该范围内的所有垃圾箱。对于边界上的 bin,以某种方式分割边界之间的贡献。也许甚至线性就足够了。
        【解决方案6】:

        类似的东西应该可以工作:

        // 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);
              }
            }();
          }
        

        【讨论】:

          猜你喜欢
          • 2018-09-25
          • 2020-10-06
          • 1970-01-01
          • 1970-01-01
          • 2013-06-27
          • 1970-01-01
          • 1970-01-01
          • 2012-07-26
          • 2017-11-04
          相关资源
          最近更新 更多