代码之家  ›  专栏  ›  技术社区  ›  P i

检测音高的共振算法

  •  0
  • P i  · 技术社区  · 16 年前

    我一直在研究不同的方法来检测传声器中的音调。

    如果你按住钢琴上的踏板,然后在里面唱一个音调,(如果你足够接近它的一个音高),音符就会产生共鸣。

    我很希望能模仿这种行为。但我该怎么做呢?有人能帮我把这个往前推吗?

    6 回复  |  直到 16 年前
        1
  •  2
  •   midtiby    16 年前
        2
  •  2
  •   P i    15 年前

    我发现一个有趣的解决方案是将麦克风输入输入到一个Karplus强算法中。

    因此,Karplus Strong通过以下方式模拟拨弦:

    • 在-1和1之间充满静电
    • 绕着圆圈走,每次都将当前值设置为前两个值的平均值(并向扬声器发射当前值)

    现在,如果我们将麦克风流添加到此过程中,则:

    x = ( ringBuf[prev] + ring theBuf[prev2] ) * 0.5 * 0.998;
    micOut[moPtr++] = x;
    ringBuf[curr] = x + micIn[miPtr++];
    

    事实上,它能很准确地模拟吉他的演唱。如果你把音调调准了,它真的会哭。

    我看不出任何办法来解决这个问题。我认为必须放弃这种做法。

        3
  •  1
  •   Holstebroe    16 年前

    完全基于离散傅立叶变换(DFT)的算法有许多缺点。 另一个问题是离散对数频率分辨率的DFT可能不够好,一个基音检测器。毕竟,DFT只发现具有窗口大小的整数波长的波。

    一个稍微先进的算法可以这样做:

    1. 带通信号滤波隔离基音频率。
    2. 计算滤波信号中两个峰值之间的采样数。

    如果你想要比采样频率更高的分辨率,你可以在峰值附近的采样点上拟合一个函数,比如多项式。既然你已经抑制了其他频率,你应该能够做到。

    另一个答案是,你也可以使用自相关来找出信号中的最大信号重复。但是我要说的是,实现一个好的自相关基音检测并非易事。在不知道这一点的情况下,我假设吉他调谐器和类似的廉价电子设备将其算法建立在带通滤波器的基础上,并结合计算峰值之间的采样距离。

        4
  •  1
  •   CodesInChaos    16 年前

    你可以用一个阻尼的谐振子作为输入的驱动力。选择振荡器的参数,使其共振频率与你想要的频率相匹配。

        5
  •  1
  •   supercat    15 年前

    我发现一种有用的方法是产生两个相隔90度的参考波(我称之为“正弦”和“余弦”),并在输入的一些相当短(比如1/60秒)的范围内,取输入波形与这些参考波的点积。这将给你一个有点嘈杂的指标,你有多少输入频率,你的相位或相位与你的参考波(平方根的值产生的平方和使用两个参考波将振幅)。对于一个小窗口,你会注意到输出是相当嘈杂的,但是如果你用类似于简单的FIR或IIR滤波器的东西来过滤输出,你可能会得到一些相当合理的东西。

    两个振幅测量将经历相同的延迟,但第一个测量将比第二个测量更具选择性;因此,您可以非常清楚地知道频率是“正确开启”还是有点关闭。使用这种方法,可以快速检测DTMF音调,同时拒绝甚至几赫兹的音调(在“松散”检测器上,非音高音调的拾音比紧密的检测器上强得多)。

    示例代码:

    double sine_phase,sine_freq;
    void process_some_waves(double *input, int16 len, 
      double *sine_phase, double sine_freq, 
      double *sine_result, double *cosine_result)
    {
      int i;
      double phase, sin_tot,cos_tot;
      phase = *sine_phase;
      sin_tot = cos_tot = 0;
      for (i=0; len > i; i++)
      {
        sin_tot += input[i] * sin(phase);
        cos_tot += input[i] * cos(phase);
        phase += sine_freq;
      }
      *sine_result = sin_tot;
      *cosine_result = cos_tot;
      *sine_phase = phase;
    }
    
    /* Takes first element in buffer and 'smears' it through buffer with simple Gaussian resp. */
    void simple_fir_filter(double *buff, int buffsize)
    {
      int i;
    
      for (i=buffsize-1; i>=2; i--)
        buff[i] = (buff[i-1] + buff[i-2])/2;
    }
    
    #define FILTER_SIZE1 10
    #define FILTER_SIZE2 8
    #define SECTION_LENGTH 128
    #define FREQ whatever
    
    double sine_buff1[FILTER_SIZE1], sine_buff2[FILTER_SIZE2];
    double cos_buff1[FILTER_SIZE1], cos_buff2[FILTER_SIZE2];
    double combined_buff[FILTER_SIZE2];
    double tight_amplitude, loose_amplitude;
    double ref_phase;
    
    void handle_some_data(double *input)
    {
      /* Put results in first element of filter buffers */
      process_some_waves(input, SECTION_LENGTH, &ref_phase, FREQ, sine_buff1, cos_buff1);
    
      /* Run first stage of filtering */
    
      simple_fir_filter(sine_buff1, FILTER_SIZE1); 
      simple_fir_filter(cosine_buff1, FILTER_SIZE1);
    
      /* Last element of each array will hold results of filtering. */
      /* Now do second stage */
    
      sine_buff2[0] = sine_buff1[FILTER_SIZE1-1];
      cosine_buff2[0] = cosine_buff1[FILTER_SIZE1-1];
      combined_buff[0] = sine_buff2[0]*sine_buff2[0] + cosine_buff2[0]*cosine_buff2[0];
      simple_fir_filter(sine_buff2, FILTER_SIZE2); 
      simple_fir_filter(cosine_buff2, FILTER_SIZE2); 
      simple_fir_filter(combined_buff, FILTER_SIZE2); 
    
      tight_amplitude = sine_buff2[FILTER_SIZE2-1]*sine_buff2[FILTER_SIZE2-1] + 
                        cosine_buff2[FILTER_SIZE2-1]*cosine_buff2[FILTER_SIZE2-1];
      loose_amplitude = combined_buff2[FILTER_SIZE2-1];
    }
    

    这里的代码使用“double”表示除数组订阅之外的所有数学运算。实际上,几乎可以肯定的是,用整数数学代替某些数学会更快。在带有浮点的机器上,我希望最好的方法是将相位保持为32位整数,并使用一个约4096个“单”正弦值的表(RAM中的表越小,缓存一致性性能越好)。我在定点(整数)数字信号处理器上非常成功地使用了与上面类似的代码;在处理中的正弦和余弦计算中,有些波形是在单独的“循环”中完成的,每个“循环”被实现为带有“repeat”前缀的单个指令。

        6
  •  0
  •   P i    16 年前

    基本上,如果你想从一个信号中提取一个频率f,你只需输入正弦波频率f,将它与原始信号相乘,然后积分

    如果原始信号不包含任何频率f,你应该得到几乎为零。如果是这样的话,你就会得到一个信号在这个频率下能量的量度。

    这和我在钓鱼的目的有关。为了完成我上面的类比:要检查一架钢琴包含了什么音符,你只要踩下踏板,在其中唱一个升调,每当发生共鸣时,你可以记下钢琴有一个该频率的音符。

    当然,这并非没有缺点:如果你按住C1(这次没有踏板)并唱/弹C2,C1将以两倍于其基频的频率共振,产生C2声音。

    同样地,播放G2将使其在基频的三倍处产生共振