首页 文章

使用AVAudioEngine实时检测音高是否可行?

提问于
浏览
0

我正在尝试编写一个音乐应用程序,其中检测音高是其中的核心 . 我已经看到了这个问题的解决方案以及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实时检测音高,还是应该采用另一种方式?

2 回答

  • 0

    这里有一些不同的概念 . AVAudioEngine只是为您提供原始PCM数据的引擎,您可以直接使用Novocaine,Core-Audio或其他选项 .

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

    就音高跟踪而言,有各种技术 . 需要注意的一点是频率检测与音调检测不同 .

    FFT哪个好,但无法检测缺少基本原理的信号的音高 . 您需要通过低通滤波器运行信号,以减少可能的频率混叠,然后将其传递给FFT之前的Nyquist Frequency然后window,这是为了减少spectral leakage . FFT将输出一系列区间内的频谱内容,具有最高值的区间被认为是信号中最强的频率 .

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

    最后,根据您想要检测的内容,需要考虑一些注意事项 . 像男声和某些乐器之类的东西可以通过在未经过预处理的缓冲器上运行的普通FFT来提供不正确的结果 .

    检查一下PITCH DETECTION METHODS REVIEW

    就Swift而言,它不适合实时,以性能为中心的系统 . 你可以查看old benchmarks of Swift vs C++

    enter image description here

    C FFT实现速度提高了24倍

  • 2

    我意识到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

相关问题