|
@@ -1,330 +0,0 @@
|
|
|
-//
|
|
|
-// Tuner.swift
|
|
|
-// KulexiuForStudent
|
|
|
-//
|
|
|
-// Created by 王智 on 2022/10/13.
|
|
|
-//
|
|
|
-
|
|
|
-import Foundation
|
|
|
-import AudioKit
|
|
|
-import AudioKitEX
|
|
|
-import SoundpipeAudioKit
|
|
|
-
|
|
|
-private let flats = ["C", "D♭","D","E♭","E","F","G♭","G","A♭","A","B♭","B"]
|
|
|
-private let sharps = ["C", "C♯","D","D♯","E","F","F♯","G","G♯","A","A♯","B"]
|
|
|
-private let frequencies: [Float] = [
|
|
|
- 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
|
|
|
- 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
|
|
|
- 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
|
|
|
- 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
|
|
|
- 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
|
|
|
- 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
|
|
|
- 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, // 6
|
|
|
- 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, // 7
|
|
|
- 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902 // 8
|
|
|
-]
|
|
|
-
|
|
|
-/**
|
|
|
- Types adopting the TunerDelegate protocol act as callbacks for Tuners and are
|
|
|
- the mechanism by which you may receive and respond to new information decoded
|
|
|
- by a Tuner.
|
|
|
- */
|
|
|
-@objc public protocol TunerDelegate {
|
|
|
-
|
|
|
- /**
|
|
|
- Called by a Tuner on each update.
|
|
|
-
|
|
|
- - parameter tuner: Tuner that performed the update.
|
|
|
- - parameter output: Contains information decoded by the Tuner.
|
|
|
- */
|
|
|
- func tunerDidUpdate(_ tuner: Tuner, output: TunerOutput)
|
|
|
-}
|
|
|
-
|
|
|
-// MARK:- TunerOutput
|
|
|
-/**
|
|
|
- Contains information decoded by a Tuner, such as frequency, octave, pitch, etc.
|
|
|
- */
|
|
|
-@objc public class TunerOutput: NSObject {
|
|
|
-
|
|
|
- /**
|
|
|
- The octave of the interpreted pitch.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var octave: Int = 0
|
|
|
-
|
|
|
- /**
|
|
|
- The interpreted pitch of the microphone audio.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var pitch: String = ""
|
|
|
-
|
|
|
- /**
|
|
|
- The octave of the interpreted pre pitch.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var preOctave: Int = 0
|
|
|
- /**
|
|
|
- The interpreted pre pitch of the microphone audio.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var prePitch: String = ""
|
|
|
- /**
|
|
|
- The octave of the interpreted next pitch.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var nextOctave: Int = 0
|
|
|
-
|
|
|
- /**
|
|
|
- The interpreted next pitch of the microphone audio.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var nextPitch: String = ""
|
|
|
-
|
|
|
- /**
|
|
|
- The frequance of the interpreted mid pitch.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var midFrequence: Float = 0.0
|
|
|
-
|
|
|
- /**
|
|
|
- The octave of the interpreted transfer pitch.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var transferOctave: Int = 0
|
|
|
-
|
|
|
- /**
|
|
|
- The interpreted transfer pitch of the microphone audio.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var transferPitch: String = ""
|
|
|
-
|
|
|
- /**
|
|
|
- The difference between the frequency of the interpreted pitch and the actual
|
|
|
- frequency of the microphone audio.
|
|
|
-
|
|
|
- For example if the microphone audio has a frequency of 432Hz, the pitch will
|
|
|
- be interpreted as A4 (440Hz), thus making the distance -8Hz.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var distance: Float = 0.0
|
|
|
-
|
|
|
- /**
|
|
|
- The amplitude of the microphone audio.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var amplitude: Float = 0.0
|
|
|
-
|
|
|
- /**
|
|
|
- The frequency of the microphone audio.
|
|
|
- */
|
|
|
- @objc public fileprivate(set) var frequency: Float = 0.0
|
|
|
-
|
|
|
- fileprivate override init() {}
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-/**
|
|
|
- A Tuner uses the devices microphone and interprets the frequency, pitch, etc.
|
|
|
- */
|
|
|
-@objc public class Tuner: NSObject {
|
|
|
-
|
|
|
- fileprivate let updateInterval: TimeInterval = 0.03
|
|
|
- fileprivate let smoothingBufferCount = 30
|
|
|
- // 设置的A4标准频率
|
|
|
- @objc public var A4Frequence: Int
|
|
|
- // 移调
|
|
|
- @objc public var toneChangeRate: Int
|
|
|
-
|
|
|
- fileprivate let threshold: Float
|
|
|
- fileprivate let smoothing: Float
|
|
|
- fileprivate var engine: AudioEngine?
|
|
|
- fileprivate var microphone: AudioEngine.InputNode?
|
|
|
- fileprivate var pitchTap: PitchTap?
|
|
|
- fileprivate var silence: Fader?
|
|
|
- fileprivate var smoothingBuffer: [Float] = []
|
|
|
-
|
|
|
- /**
|
|
|
- Object adopting the TunerDelegate protocol that should receive callbacks
|
|
|
- from this tuner.
|
|
|
- */
|
|
|
- @objc public weak var delegate: TunerDelegate?
|
|
|
-
|
|
|
- /**
|
|
|
- Initializes a new Tuner.
|
|
|
-
|
|
|
- - parameter threshold: The minimum amplitude to recognize, 0 < threshold < 1
|
|
|
- - parameter smoothing: Exponential smoothing factor, 0 < smoothing < 1
|
|
|
-
|
|
|
- */
|
|
|
- @objc public init(threshold: Float = 0.0, smoothing: Float = 0.25) {
|
|
|
- self.threshold = Float(min(abs(threshold), 1.0))
|
|
|
- self.smoothing = Float(min(abs(smoothing), 1.0))
|
|
|
- self.A4Frequence = 440
|
|
|
- self.toneChangeRate = 0;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- Starts the tuner.
|
|
|
- */
|
|
|
- @objc public func start() {
|
|
|
- engine = AudioEngine()
|
|
|
- microphone = engine!.input
|
|
|
- silence = Fader(microphone!, gain: 0)
|
|
|
- /**
|
|
|
- 调大bufferSize ,可以降低采样频率
|
|
|
- 16384 (10次/秒)
|
|
|
- 8192 (20次/秒)
|
|
|
- 4096 (40次/秒) def
|
|
|
- */
|
|
|
- pitchTap = PitchTap(microphone!, bufferSize: 6144, handler: tap_handler)
|
|
|
- microphone!.start()
|
|
|
- pitchTap!.start()
|
|
|
- engine!.output = silence
|
|
|
- try? engine!.start()
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- Stops the tuner.
|
|
|
- */
|
|
|
- @objc public func stop() {
|
|
|
- microphone!.stop()
|
|
|
- pitchTap!.stop()
|
|
|
- engine!.stop()
|
|
|
- }
|
|
|
-
|
|
|
- @objc public func freeTuner() {
|
|
|
- microphone!.stop()
|
|
|
- pitchTap!.stop()
|
|
|
- engine!.stop()
|
|
|
- microphone = nil
|
|
|
- pitchTap = nil
|
|
|
- silence = nil
|
|
|
- engine = nil
|
|
|
- }
|
|
|
-
|
|
|
- func tap_handler(freq: [Float], amp: [Float]) -> Void {
|
|
|
- // Reduces sensitivity to background noise to prevent random / fluctuating data.
|
|
|
- guard amp[0] > 0.1 else { return }
|
|
|
-#if DEBUG
|
|
|
- print("freq -- real_\(freq[0]) -- imag_\(freq[1])" )
|
|
|
- print("amp -- real_\(amp[0]) -- imag_\(amp[1])")
|
|
|
-#endif
|
|
|
- if let d = self.delegate {
|
|
|
- if amp[0] > self.threshold
|
|
|
- {
|
|
|
- let amplitude = amp[0]
|
|
|
- let frequency = freq[0]
|
|
|
-// let frequency = self.smooth(freq[0])
|
|
|
- let output = Tuner.newOutput(frequency, amplitude)
|
|
|
- DispatchQueue.main.async {
|
|
|
- d.tunerDidUpdate(self, output: output)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- Exponential smoothing:
|
|
|
- 指数平滑算法
|
|
|
- smoothing 平滑常数
|
|
|
- https://en.wikipedia.org/wiki/Exponential_smoothing
|
|
|
- */
|
|
|
- fileprivate func smooth(_ value: Float) -> Float {
|
|
|
- var frequency = value
|
|
|
- if smoothingBuffer.count > 0 {
|
|
|
- let last = smoothingBuffer.last!
|
|
|
- frequency = (smoothing * value) + (1.0 - smoothing) * last
|
|
|
- if smoothingBuffer.count > smoothingBufferCount {
|
|
|
- smoothingBuffer.removeFirst()
|
|
|
- }
|
|
|
- }
|
|
|
- smoothingBuffer.append(frequency)
|
|
|
- return frequency
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- Transfer frequency and amplitude to TunerOutput
|
|
|
- */
|
|
|
- static func newOutput(_ frequency: Float, _ amplitude: Float) -> TunerOutput {
|
|
|
- let output = TunerOutput()
|
|
|
-
|
|
|
- var norm = frequency
|
|
|
- while norm > frequencies[frequencies.count - 1] {
|
|
|
- norm = norm / 2.0
|
|
|
- }
|
|
|
- while norm < frequencies[0] {
|
|
|
- norm = norm * 2.0
|
|
|
- }
|
|
|
-
|
|
|
- var i = -1
|
|
|
- var min = Float.infinity
|
|
|
- for n in 0...frequencies.count-1 {
|
|
|
- let diff = frequencies[n] - norm
|
|
|
- if abs(diff) < abs(min) {
|
|
|
- min = diff
|
|
|
- i = n
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- output.octave = i / 12
|
|
|
- output.frequency = frequency
|
|
|
- output.amplitude = amplitude
|
|
|
- output.distance = frequency - frequencies[i]
|
|
|
- output.pitch = String(format: "%@", flats[i % flats.count], sharps[i % sharps.count])
|
|
|
-
|
|
|
- var index = i-1
|
|
|
- if i == 0 {
|
|
|
- index = 0
|
|
|
- } else if i+1 == frequencies.count-1 {
|
|
|
- index = frequencies.count-2
|
|
|
- }
|
|
|
-
|
|
|
- output.preOctave = (index) / 12
|
|
|
- output.prePitch = String(format: "%@", flats[(index) % flats.count], sharps[(index) % sharps.count])
|
|
|
-
|
|
|
- output.nextOctave = (index+2) / 12
|
|
|
- output.nextPitch = String(format: "%@", flats[(index+2) % flats.count], sharps[(index+2) % sharps.count])
|
|
|
-
|
|
|
- output.midFrequence = frequencies[index+1]
|
|
|
- return output
|
|
|
- }
|
|
|
-
|
|
|
- @objc public func getTransferPitch(A4Frequency: Float, toneChangeRate: Int, output: TunerOutput)->TunerOutput {
|
|
|
- let newOutput = TunerOutput()
|
|
|
-
|
|
|
- var norm = output.frequency
|
|
|
- while norm > frequencies[frequencies.count - 1] {
|
|
|
- norm = norm / 2.0
|
|
|
- }
|
|
|
- while norm < frequencies[0] {
|
|
|
- norm = norm * 2.0
|
|
|
- }
|
|
|
-
|
|
|
- var i = -1
|
|
|
- var min = Float.infinity
|
|
|
- for n in 0...frequencies.count-1 {
|
|
|
- let diff = frequencies[n] - norm
|
|
|
- if abs(diff) < abs(min) {
|
|
|
- min = diff
|
|
|
- i = n
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- var index = i-1+toneChangeRate
|
|
|
- if index <= 0 {
|
|
|
- index = 0
|
|
|
- } else if index+2 >= frequencies.count-1 {
|
|
|
- index = frequencies.count-2
|
|
|
- }
|
|
|
-
|
|
|
- newOutput.octave = output.octave
|
|
|
- newOutput.frequency = output.frequency
|
|
|
- newOutput.amplitude = output.amplitude
|
|
|
- newOutput.distance = output.frequency / A4Frequency * 440 - frequencies[i] // 将收到的频率转成 440hz频率后对比
|
|
|
- newOutput.pitch = output.pitch
|
|
|
-
|
|
|
- newOutput.preOctave = (index) / 12
|
|
|
- newOutput.prePitch = String(format: "%@", flats[(index) % flats.count],sharps[(index) % sharps.count])
|
|
|
-
|
|
|
- newOutput.nextOctave = (index+2) / 12
|
|
|
- newOutput.nextPitch = String(format: "%@", flats[(index+2) % flats.count],sharps[(index+2) % sharps.count])
|
|
|
-
|
|
|
- newOutput.midFrequence = frequencies[index+1-toneChangeRate] / 440 * A4Frequency
|
|
|
- // 移调之后的显示
|
|
|
- newOutput.transferOctave = (index+1) / 12
|
|
|
- newOutput.transferPitch = String(format: "%@", flats[(index+1) % flats.count],sharps[(index+1) % sharps.count])
|
|
|
-
|
|
|
- return newOutput
|
|
|
- }
|
|
|
-
|
|
|
-}
|