测量唱歌音符的颤音速率

信息处理 信号分析 声音的 频谱
2022-02-01 18:38:58

我正在做一个项目,我需要在各种条件下测量唱歌音符中颤音循环的速率。刺激将是一个几秒钟长的音频样本,该样本是一个带有颤音的无伴奏无伴奏音符输出应该是颤音重复的速率(例如 4Hz、10.5Hz 等)。将专门为这项研究捕获音频;我不会对现有音频进行采样,也不需要处理干扰信号。音符的基本音高可以是任何需要的,尽管精确度可能是不可能的。

这是一个示例声波图:

演唱颤音的示例声波图

理想情况下,这将使用 Linux 命令行链或 shell 脚本来完成,但如果我需要编写一些 Python 也可以。

2个回答

您可能会对本出版物感兴趣:

Jonathan Driedger、Stefan Balke、Sebastian Ewert 和 Meinard Müller:基于模板的音乐信号颤音分析,载于:国际音乐信息检索会议论文集,纽约,美国,2016 年,第 239-245 页。[ PDF ]

此外,本文还有一个随附的网站

我使用以下开源工具实现了一个简单的颤音测量算法:

  • 用于音频捕获的sox

  • 用于定期推导基频的aubiopitch

  • 用于编写上述脚本并处理结果的Python

aubioitch 是 aubio 库的一部分是这里的关键。它接收一个音频文件并输出一个时间和基本频率列表,间隔大约每 6ms(aubiopitch 使用 2048 点FFT,但步进 256 个样本,音频采样率为 44.1kHz)。然后 Python 脚本按如下方式处理:

  • 保留两个运行平均值,最后 5 个频率样本中的一个短的,最后 10 个频率样本中的一个长的
  • 如果短期平均线大于长期平均线,那么频率正在上升;否则它正在下降
  • 注意频率斜率何时改变符号。计算转换次数,跟踪第一次转换和最后一次转换之间的时间
  • 将转换次数除以时间,再除以 2(每个周期两次转换),您就得到了以 Hz 为单位的颤音速率。

Python源代码:

import subprocess
import re
import os

TEMP_SOUND = "/tmp/vibrato.delme.wav"
TEMP_DATA = "/tmp/vibrato.delme.txt"
DURATION = 2
VERBOSE = False


print("Recording for {} seconds...".format(DURATION))
subprocess.call(
    "/opt/local/bin/sox -d {} trim 0 {} >/dev/null 2>&1".format(TEMP_SOUND, DURATION), shell=True)
print("   ... done. Analyzing...")
subprocess.call(
    "/usr/local/bin/aubiopitch {}  | tail -n +10 > {}".format(TEMP_SOUND, TEMP_DATA), shell=True)


short_list = []
short_max = 5
long_list = []
long_max = 10
last_slope = None
first_crossing_time = None
time_freq_re = re.compile(r'([^ ]+) ([^ ]+)')
samples = 0

with open(TEMP_DATA, mode="rU") as f:
  for line in f:
    line = line.strip()
    time_freq_match = time_freq_re.match(line)
    if not time_freq_match:
      print("Whoa!")
      exit(1)
    time = float(time_freq_match.group(1))
    freq = float(time_freq_match.group(2))
    short_list.append(freq)
    if len(short_list) > short_max:
      short_list.pop(0)
    long_list.append(freq)
    if len(long_list) > long_max:
      long_list.pop(0)
    short_average = sum(short_list) / short_max
    long_average = sum(long_list) / long_max
    # If we've collected enough data to analyze
    if len(long_list) == long_max:
      slope = (short_average > long_average)
      # If this is the first time through, remember the slope
      if last_slope == None:
        last_slope = slope
      samples += 1
      # Crossing?
      if slope != last_slope:
        last_slope = slope
        # First crossing?
        if first_crossing_time == None:
          first_crossing_time = time
          samples = 0
          crossings = 0
        else:
          crossings += 1
        last_crossing_time = time
        last_crossing_samples = samples
        if VERBOSE:
          print("**** crossing ****")
      if VERBOSE:
        print("short {}, long {}, slope {}".format(short_average, long_average, slope))

  total_time = last_crossing_time - first_crossing_time
  print("total time {:.4}, samples {}, sample period {:.4} ms, crossings {}, vibrato rate {:.4} Hz".format(
      total_time, last_crossing_samples, total_time * 1000 / last_crossing_samples, crossings, crossings / (total_time * 2)))

os.remove(TEMP_SOUND)
os.remove(TEMP_DATA)

示例输出:

DanMBPE:Vibrato griscom$ python ./vibrato.py
Recording for 2 seconds...
   ... done. Analyzing...
total time 1.84, samples 317, sample period 5.805 ms, crossings 17, vibrato rate 4.619 Hz
DanMBPE:Vibrato griscom$

欢迎提出建议!