1

我正在尝试编写一个音乐应用程序,其中音高检测是这一切的核心。我已经看到了这个问题的解决方案以及 AppStore 上的应用程序。然而,它们中的大多数都已经过时了,我想做的是 Swift。我一直在寻找 AVAudioEngine 作为一种方法来做到这一点,但我发现缺少文档,或者我可能还不够努力。

我发现我可以像这样点击 inputNode 总线:

self.audioEngine = AVAudioEngine()
self.audioInputNode = self.audioEngine.inputNode!
self.audioInputNode.installTapOnBus(0, bufferSize:256, format: audioInputNode.outputFormatForBus(0), block: {(buffer, time) in
      self.analyzeBuffer(buffer)
})

总线每秒被点击 2-3 次,每个点击缓冲区包含超过 16000 个浮点数。这些幅度样本来自麦克风吗?

文档至少声称它是从节点输出的:“缓冲区参数是从 AVAudioNode 的输出中捕获的音频缓冲区。

是否可以使用 AVAudioEngine 实时检测音高,还是我应该以另一种方式进行?

4

2 回答 2

2

这里有几个不同的概念。AVAudioEngine 只是获取原始 PCM 数据的引擎,您可以直接使用 Novocaine、Core-Audio 或其他选项。

PCM 数据是来自麦克风的浮点样本。

就音高跟踪而言,有多种技术。需要注意的一件事是频率检测与音高检测不同。

FFT很好,但无法检测缺少基本信号的信号音高。您需要通过低通滤波器运行信号以减少可能高于奈奎斯特频率的频率混叠,然后在将其传递到 FFT 之前将其加,这是为了减少频谱泄漏。FFT 将在一系列 bin 内输出频谱内容,具有最高值的 bin 被称为信号中最强的频率。

自相关可以提供更好的结果。它基本上是与自身相关的信号。

最后取决于您想要检测的内容,有一些考虑因素需要考虑。通过在未经预处理的缓冲区上运行的正常 FFT,诸如男声和某些乐器之类的东西可能会给出不正确的结果。

检查此音高检测方法评论

就 Swift 而言,它不太适合实时的、注重性能的系统。您可以检查Swift 与 C++ 的旧基准

在此处输入图像描述

C++ FFT 实现速度快 24 倍以上

于 2016-02-29T07:56:03.767 回答
0

我意识到 Hellium3 确实让我了解了音高是什么,以及使用 Swift 做这些事情是否是个好主意。

我的问题最初是关于敲击 PCM 总线是否是从麦克风获取输入信号的方式。

自从问了这个问题后,我就做到了。使用通过点击 PCM 总线获得的数据并分析缓冲区窗口。

它工作得非常好,正是我对什么是 PCM 总线、缓冲区和采样频率缺乏了解,这让我首先提出了这个问题。

知道这三个可以更容易地看出这是正确的。

编辑:根据需要,我将粘贴我(已弃用)的 PitchDetector 实现。

class PitchDetector {
  var samplingFrequency: Float
  var harmonicConstant: Float

  init(harmonicConstant: Float, samplingFrequency: Float) {
    self.harmonicConstant = harmonicConstant
    self.samplingFrequency = samplingFrequency
  }

  //------------------------------------------------------------------------------
  // MARK: Signal processing
  //------------------------------------------------------------------------------

  func detectPitch(_ samples: [Float]) -> Pitch? {
    let snac = self.snac(samples)
    let (lags, peaks) = self.findKeyMaxima(snac)
    let (τBest, clarity) = self.findBestPeak(lags, peaks: peaks)
    if τBest > 0 {
      let frequency = self.samplingFrequency / τBest
      if PitchManager.sharedManager.inManageableRange(frequency) {
        return Pitch(measuredFrequency: frequency, clarity: clarity)
      }
    }

    return nil
  }

  // Returns a Special Normalision of the AutoCorrelation function array for various lags with values between -1 and 1
  private func snac(_ samples: [Float]) -> [Float] {
    let τMax = Int(self.samplingFrequency / PitchManager.sharedManager.noteFrequencies.first!) + 1
    var snac = [Float](repeating: 0.0, count: samples.count)
    let acf = self.acf(samples)
    let norm = self.m(samples)
    for τ in 1 ..< τMax {
      snac[τ] = 2 * acf[τ + acf.count / 2] / norm[τ]
    }

    return snac
  }

  // Auto correlation function
  private func acf(_ x: [Float]) -> [Float] {
    let resultSize = 2 * x.count - 1
    var result = [Float](repeating: 0, count: resultSize)
    let xPad = repeatElement(Float(0.0), count: x.count - 1)
    let xPadded = xPad + x + xPad
    vDSP_conv(xPadded, 1, x, 1, &result, 1, vDSP_Length(resultSize), vDSP_Length(x.count))

    return result
  }

  private func m(_ samples: [Float]) -> [Float] {
    var sum: Float = 0.0
    for i in 0 ..< samples.count {
      sum += 2.0 * samples[i] * samples[i]
    }
    var m = [Float](repeating: 0.0, count: samples.count)
    m[0] = sum
    for i in 1 ..< samples.count {
      m[i] = m[i - 1] - samples[i - 1] * samples[i - 1] - samples[samples.count - i - 1] * samples[samples.count - i - 1]
    }
    return m
  }

  /**
   * Finds the indices of all key maximum points in data
   */
  private func findKeyMaxima(_ data: [Float]) -> (lags: [Float], peaks: [Float]) {
    var keyMaximaLags: [Float] = []
    var keyMaximaPeaks: [Float] = []
    var newPeakIncoming = false
    var currentBestPeak: Float = 0.0
    var currentBestτ = -1
    for τ in 0 ..< data.count {
      newPeakIncoming = newPeakIncoming || ((data[τ] < 0) && (data[τ + 1] > 0))
      if newPeakIncoming {
        if data[τ] > currentBestPeak {
          currentBestPeak = data[τ]
          currentBestτ = τ
        }
        let zeroCrossing = (data[τ] > 0) && (data[τ + 1] < 0)
        if zeroCrossing {
          let (τEst, peakEst) = self.approximateTruePeak(currentBestτ, data: data)
          keyMaximaLags.append(τEst)
          keyMaximaPeaks.append(peakEst)
          newPeakIncoming = false
          currentBestPeak = 0.0
          currentBestτ = -1
        }
      }
    }

    if keyMaximaLags.count <= 1 {
      let unwantedPeakOfLowPitchTone = (keyMaximaLags.count == 1 && data[Int(keyMaximaLags[0])] < data.max()!)
      if unwantedPeakOfLowPitchTone {
        keyMaximaLags.removeAll()
        keyMaximaPeaks.removeAll()
      }
      let (τEst, peakEst) = self.approximateTruePeak(data.index(of: data.max()!)!, data: data)
      keyMaximaLags.append(τEst)
      keyMaximaPeaks.append(peakEst)
    }

    return (lags: keyMaximaLags, peaks: keyMaximaPeaks)
  }

  /**
   * Approximates the true peak according to https://www.dsprelated.com/freebooks/sasp/Quadratic_Interpolation_Spectral_Peaks.html
   */
  private func approximateTruePeak(_ τ: Int, data: [Float]) -> (τEst: Float, peakEst: Float) {
    let α = data[τ - 1]
    let β = data[τ]
    let γ = data[τ + 1]
    let p = 0.5 * ((α - γ) / (α - 2.0 * β + γ))
    let peakEst = min(1.0, β - 0.25 * (α - γ) * p)
    let τEst = Float(τ) + p

    return (τEst, peakEst)
  }

  private func findBestPeak(_ lags: [Float], peaks: [Float]) -> (τBest: Float, clarity: Float) {
    let threshold: Float = self.harmonicConstant * peaks.max()!
    for i in 0 ..< peaks.count {
      if peaks[i] > threshold {
        return (τBest: lags[i], clarity: peaks[i])
      }
    }

    return (τBest: lags[0], clarity: peaks[0])
  }
}

所有功劳归功于 Philip McLeod,他的研究用于我上面的实现。http://www.cs.otago.ac.nz/research/publications/oucs-2008-03.pdf

于 2016-04-18T07:21:41.597 回答