Tuner.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. //
  2. // Tuner.swift
  3. // KulexiuForStudent
  4. //
  5. // Created by 王智 on 2022/10/13.
  6. //
  7. import Foundation
  8. import AudioKit
  9. private let flats = ["C", "D♭","D","E♭","E","F","G♭","G","A♭","A","B♭","B"]
  10. private let sharps = ["C", "C♯","D","D♯","E","F","F♯","G","G♯","A","A♯","B"]
  11. private let frequencies: [Float] = [
  12. 16.35, 17.32, 18.35, 19.45, 20.60, 21.83, 23.12, 24.50, 25.96, 27.50, 29.14, 30.87, // 0
  13. 32.70, 34.65, 36.71, 38.89, 41.20, 43.65, 46.25, 49.00, 51.91, 55.00, 58.27, 61.74, // 1
  14. 65.41, 69.30, 73.42, 77.78, 82.41, 87.31, 92.50, 98.00, 103.8, 110.0, 116.5, 123.5, // 2
  15. 130.8, 138.6, 146.8, 155.6, 164.8, 174.6, 185.0, 196.0, 207.7, 220.0, 233.1, 246.9, // 3
  16. 261.6, 277.2, 293.7, 311.1, 329.6, 349.2, 370.0, 392.0, 415.3, 440.0, 466.2, 493.9, // 4
  17. 523.3, 554.4, 587.3, 622.3, 659.3, 698.5, 740.0, 784.0, 830.6, 880.0, 932.3, 987.8, // 5
  18. 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, // 6
  19. 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, // 7
  20. 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902 // 8
  21. ]
  22. /**
  23. Types adopting the TunerDelegate protocol act as callbacks for Tuners and are
  24. the mechanism by which you may receive and respond to new information decoded
  25. by a Tuner.
  26. */
  27. @objc public protocol TunerDelegate {
  28. /**
  29. Called by a Tuner on each update.
  30. - parameter tuner: Tuner that performed the update.
  31. - parameter output: Contains information decoded by the Tuner.
  32. */
  33. func tunerDidUpdate(_ tuner: Tuner, output: TunerOutput)
  34. }
  35. // MARK:- TunerOutput
  36. /**
  37. Contains information decoded by a Tuner, such as frequency, octave, pitch, etc.
  38. */
  39. @objc public class TunerOutput: NSObject {
  40. /**
  41. The octave of the interpreted pitch.
  42. */
  43. @objc public fileprivate(set) var octave: Int = 0
  44. /**
  45. The interpreted pitch of the microphone audio.
  46. */
  47. @objc public fileprivate(set) var pitch: String = ""
  48. /**
  49. The difference between the frequency of the interpreted pitch and the actual
  50. frequency of the microphone audio.
  51. For example if the microphone audio has a frequency of 432Hz, the pitch will
  52. be interpreted as A4 (440Hz), thus making the distance -8Hz.
  53. */
  54. @objc public fileprivate(set) var distance: Float = 0.0
  55. /**
  56. The amplitude of the microphone audio.
  57. */
  58. @objc public fileprivate(set) var amplitude: Float = 0.0
  59. /**
  60. The frequency of the microphone audio.
  61. */
  62. @objc public fileprivate(set) var frequency: Float = 0.0
  63. fileprivate override init() {}
  64. }
  65. /**
  66. A Tuner uses the devices microphone and interprets the frequency, pitch, etc.
  67. */
  68. @objc public class Tuner: NSObject {
  69. fileprivate let smoothingBufferCount = 30
  70. fileprivate let threshold: Float
  71. fileprivate let smoothing: Float
  72. fileprivate var engine: AudioEngine?
  73. fileprivate var microphone: AudioEngine.InputNode?
  74. fileprivate var pitchTap: PitchTap?
  75. fileprivate var silence: Fader?
  76. fileprivate var smoothingBuffer: [Float] = []
  77. /**
  78. Object adopting the TunerDelegate protocol that should receive callbacks
  79. from this tuner.
  80. */
  81. @objc public var delegate: TunerDelegate?
  82. /**
  83. Initializes a new Tuner.
  84. - parameter threshold: The minimum amplitude to recognize, 0 < threshold < 1
  85. - parameter smoothing: Exponential smoothing factor, 0 < smoothing < 1
  86. */
  87. @objc public init(threshold: Float = 0.0, smoothing: Float = 0.25) {
  88. self.threshold = Float(min(abs(threshold), 1.0))
  89. self.smoothing = Float(min(abs(smoothing), 1.0))
  90. }
  91. /**
  92. Starts the tuner.
  93. */
  94. @objc public func start() {
  95. engine = AudioEngine()
  96. microphone = engine!.input
  97. silence = Fader(microphone!, gain: 0)
  98. /**
  99. 调大bufferSize ,可以降低采样频率
  100. 16384 (10次/秒)
  101. 8192 (20次/秒)
  102. 4096 (40次/秒) def
  103. */
  104. pitchTap = PitchTap(microphone!, bufferSize: 4096, handler: tap_handler)
  105. microphone!.start()
  106. pitchTap!.start()
  107. engine!.output = silence
  108. try? engine!.start()
  109. }
  110. /**
  111. Stops the tuner.
  112. */
  113. @objc public func stop() {
  114. microphone!.stop()
  115. pitchTap!.stop()
  116. engine!.stop()
  117. }
  118. func tap_handler(freq: [Float], amp: [Float]) -> Void {
  119. #if DEBUG
  120. print("freq -- real_\(freq[0]) -- imag_\(freq[1])" )
  121. print("amp -- real_\(amp[0]) -- imag_\(amp[1])")
  122. #endif
  123. if let d = self.delegate {
  124. if amp[0] > self.threshold
  125. {
  126. let amplitude = amp[0]
  127. let frequency = freq[0]
  128. let output = Tuner.newOutput(frequency, amplitude)
  129. DispatchQueue.main.async {
  130. d.tunerDidUpdate(self, output: output)
  131. }
  132. }
  133. }
  134. }
  135. /**
  136. Exponential smoothing:
  137. 指数平滑算法
  138. smoothing 平滑常数
  139. https://en.wikipedia.org/wiki/Exponential_smoothing
  140. */
  141. fileprivate func smooth(_ value: Float) -> Float {
  142. var frequency = value
  143. if smoothingBuffer.count > 0 {
  144. let last = smoothingBuffer.last!
  145. frequency = (smoothing * value) + (1.0 - smoothing) * last
  146. if smoothingBuffer.count > smoothingBufferCount {
  147. smoothingBuffer.removeFirst()
  148. }
  149. }
  150. smoothingBuffer.append(frequency)
  151. return frequency
  152. }
  153. /**
  154. Transfer frequency and amplitude to TunerOutput
  155. */
  156. static func newOutput(_ frequency: Float, _ amplitude: Float) -> TunerOutput {
  157. let output = TunerOutput()
  158. var norm = frequency
  159. while norm > frequencies[frequencies.count - 1] {
  160. norm = norm / 2.0
  161. }
  162. while norm < frequencies[0] {
  163. norm = norm * 2.0
  164. }
  165. var i = -1
  166. var min = Float.infinity
  167. for n in 0...frequencies.count-1 {
  168. let diff = frequencies[n] - norm
  169. if abs(diff) < abs(min) {
  170. min = diff
  171. i = n
  172. }
  173. }
  174. output.octave = i / 12
  175. output.frequency = frequency
  176. output.amplitude = amplitude
  177. output.distance = frequency - frequencies[i]
  178. output.pitch = String(format: "%@", sharps[i % sharps.count], flats[i % flats.count])
  179. return output
  180. }
  181. }