簡體   English   中英

使用AVAudioEngine實時檢測音高是否可行?

[英]Is it feasable to use AVAudioEngine to detect pitch in real time?

我正在嘗試編寫一個音樂應用程序,其中音調檢測是所有程序的核心。 我已經看到了針對此問題的解決方案以及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個浮點。 這些幅度樣本來自麥克風嗎?

文檔至少聲稱它是從節點輸出的:“ buffer參數是從AVAudioNode的輸出捕獲的音頻的緩沖區。

是否可以使用AVAudioEngine實時檢測音高,還是應該采用其他方式?

這里有一些不同的概念。 AVAudioEngine只是獲取原始PCM數據的引擎,您可以直接使用Novocaine,Core-Audio或其他選項。

PCM數據是來自麥克風的浮點采樣。

至於音高跟蹤,有各種各樣的技術。 要注意的一件事是頻率檢測與音高檢測不同。

FFT很好,但無法檢測到缺少基本信號的音調。 您可能需要通過低通濾波器運行信號,以減少高於奈奎斯特頻率的頻率可能出現的混疊,然后在將其傳遞給FFT之前對其進行窗口化 ,以減少頻譜泄漏 FFT將在一系列倉中輸出頻譜內容,具有最高值的倉被認為是信號中最強的頻率。

自相關可以提供更好的結果。 基本上是與自身相關的信號。

最后,取決於您要檢測的內容,需要考慮一些注意事項。 諸如男性聲音之類的東西和某些樂器可能會通過在未經預處理的緩沖區上運行常規FFT來給出錯誤的結果。

檢查此間距檢測方法評論

就Swift而言,它不太適合實時的,注重性能的系統。 您可以查看Swift與C ++舊基准測試

在此處輸入圖片說明

C ++ FFT的執行速度提高了24倍以上

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM