基于 Web Audio API 实现一个 Tuner(调音器)

基于 Web Audio API 实现一个 Tuner(调音器)_第1张图片

这个调音器是一年前做的,现在想起来写个文章分享实现的过程。

一个简单调音器的实现有两个关键点:实时音频录制及处理 + 音高识别算法。需要注意的是,为了让代码更容易理解,以下展示的代码没有做任何兼容、容错处理,只能保证在 chrome 正常运行。完整的项目请看:在线演示,Github。

通过 Web Audio 获取实时录音数据

数据来源是我们首要考虑的,上一个时代其实就已经有可以运行在浏览器的调音器,只不过是用 flash 实现的。现在,我们很高兴地看到 Web Audio API 已经成为了标准,在浏览器里获取实时录音数据是可行的,兼容性尚且可以,所有最新主流浏览器基本都支持。相关链接:

  • Web Audio API
  • AudioRecorder Demo
  • Can I use stream

接下来,我们来实践一下。

AudioContext = window.AudioContext || window.webkitAudioContext

const audioContext = new AudioContext()
const analyser = audioContext.createAnalyser()
const scriptProcessor = audioContext.createScriptProcessor(8192, 1, 1)

navigator.mediaDevices.getUserMedia({audio: true}).then(streamSource => {
  // connect 的顺序一定要 streamSource => analyser => scriptProcessor
  audioContext.createMediaStreamSource(streamSource).connect(analyser)
  analyser.connect(scriptProcessor)
  scriptProcessor.connect(audioContext.destination)

  scriptProcessor.addEventListener('audioprocess', event => {
    const data = event.inputBuffer.getChannelData(0)
    // 为了避免卡住浏览器,只打印一些简单的数据
    console.log(`${data.length}, ${data[0]}`)
  })
})

在线演示

可以看到,从浏览器获取录音数据还是很简单的,当然实际中你要处理 AudioContextgetUserMedia 的兼容性,以及必要的错误处理。

音高检测(Pitch detection)

音高检测算法已经很成熟了,不乏论文和资料,诸如 java、c/c++ 都有现成的库可用,而 js 在这方面显然是有缺失的。因为我的目的是快速实现一个调音器,所以我是基于一个现有的 c 音频库 aubio 用 emscripten 编译成 js,以便在浏览器里运行。

我们来试试效果:

AudioContext = window.AudioContext || window.webkitAudioContext

const bufferSize = 8192
const audioContext = new AudioContext()
const analyser = audioContext.createAnalyser()
const scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1)

const pitchDetector = new (Module().AubioPitch)(
    'default', bufferSize, 1, audioContext.sampleRate)

navigator.mediaDevices.getUserMedia({audio: true}).then(streamSource => {
  // connect 的顺序一定要 streamSource => analyser => scriptProcessor
  audioContext.createMediaStreamSource(streamSource).connect(analyser)
  analyser.connect(scriptProcessor)
  scriptProcessor.connect(audioContext.destination)

  scriptProcessor.addEventListener('audioprocess', event => {
    const frequency = self.pitchDetector.do(event.inputBuffer.getChannelData(0))
    if (frequency) {
      console.log(frequency)
    }
  })
})

在线演示

基于 Web Audio API 实现一个 Tuner(调音器)_第2张图片
这是我的吉他6弦拨出来的声音,可以看到,在末尾识别成了第二泛音,现实中确实会出现第二泛音比第一泛音强的情况,但我们认为这是错误的结果,是需要优化避免的

实现调音器界面

我的计划是做一个表盘式的调音器,首先,我需要把音高频率转成音名及对应的八度,比如 82.41 Hz 对应 E2。音符 - 维基百科 里有详细的描述及公式。

这里我们就用标准的 MIDI 转换公式,基于十二平均律,设定标准音高为 440 Hz:


其次,为了描述音的偏移距离,还需要引入音分的概念,其公式:

写成代码就是:

const MIDDLE_A = 440
const SEMITONE = 69

/**
 * get musical note from frequency
 *
 * @param {float} frequency
 * @returns {int}
 */
function getNote(frequency) {
  var note = 12 * (Math.log(frequency / MIDDLE_A) / Math.log(2))
  return Math.round(note) + SEMITONE
}

/**
 * get the musical note's standard frequency
 *
 * @param note
 * @returns {number}
 */
function getStandardFrequency(note) {
  return MIDDLE_A * Math.pow(2, (note - SEMINETON) / 12)
}

/**
 * get cents difference between given frequency and musical note's standard frequency
 *
 * @param {float} frequency
 * @param {int} note
 * @returns {int}
 */
function getCents(frequency, note) {
  return Math.floor(1200 * Math.log(frequency / getStandardFrequency(note)) / Math.log(2))
}

表盘的实现

我们可以用 css 的 transform: rotate,只要把音分差值转换成旋转角度:


基于 Web Audio API 实现一个 Tuner(调音器)_第3张图片
$pointer.style.transform = 'rotate(' + (cents / 50 * 45) + 'deg)'

到这里,一个可用的调音器已经可以基本实现了,更多实现细节可以看项目代码。

你可能感兴趣的:(基于 Web Audio API 实现一个 Tuner(调音器))