Skip to content

Instantly share code, notes, and snippets.

@moutend
Created March 27, 2021 01:10
Show Gist options
  • Select an option

  • Save moutend/4f3a430e6d5a4cef4374d1947bbd3d73 to your computer and use it in GitHub Desktop.

Select an option

Save moutend/4f3a430e6d5a4cef4374d1947bbd3d73 to your computer and use it in GitHub Desktop.
[Swift] Equalizing Audio Signal with vDSP.Biquad
import Accelerate
import AVFoundation
class FilterParameter {
let b0: Double
let b1: Double
let b2: Double
let a1: Double
let a2: Double
init(_ b0: Double, _ b1: Double, _ b2: Double, _ a1: Double, _ a2: Double) {
self.b0 = b0
self.b1 = b1
self.b2 = b2
self.a1 = a1
self.a2 = a2
}
}
class LowPassFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, q: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let alpha: Double = sin(w0) / (2.0 * q)
let a0: Double = 1.0 + alpha
let a1: Double = -2.0 * cos(w0)
let a2: Double = 1.0 - alpha
let b0: Double = (1.0 - cos(w0)) / 2.0
let b1: Double = 1.0 - cos(w0)
let b2: Double = (1.0 - cos(w0)) / 2.0
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class HighPassFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, q: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let alpha: Double = sin(w0) / (2.0 * q)
let a0: Double = 1.0 + alpha
let a1: Double = -2.0 * cos(w0)
let a2: Double = 1.0 - alpha
let b0: Double = (1.0 + cos(w0)) / 2.0
let b1: Double = -1.0 * (1.0 + cos(w0))
let b2: Double = (1.0 + cos(w0)) / 2.0
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class AllPassFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, q: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let alpha: Double = sin(w0) / (2.0 * q)
let a0: Double = 1.0 + alpha
let a1: Double = -2.0 * cos(w0)
let a2: Double = 1.0 - alpha
let b0: Double = 1.0 - alpha
let b1: Double = -2.0 * cos(w0)
let b2: Double = 1.0 + alpha
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class BandPassFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, width: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let alpha: Double = sin(w0) * sinh(log(2.0) / 2.0 * width * w0 / sin(w0))
let a0: Double = 1.0 + alpha
let a1: Double = -2.0 * cos(w0)
let a2: Double = 1.0 - alpha
let b0: Double = alpha
let b1: Double = 0.0
let b2: Double = -1.0 * alpha
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class BandRejectFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, width: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let alpha: Double = sin(w0) * sinh(log(2.0)/2.0 * width * w0 / sin(w0))
let a0: Double = 1.0 + alpha
let a1: Double = -2.0 * cos(w0)
let a2: Double = 1.0 - alpha
let b0: Double = 1.0
let b1: Double = -2.0 * cos(w0)
let b2: Double = 1.0
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class LowShelfFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, q: Double, gain: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let a: Double = pow(10.0, (gain / 40.0))
let beta: Double = a.squareRoot() / q
let a0: Double = (a + 1.0) + (a - 1.0)*cos(w0) + beta * sin(w0)
let a1: Double = -2.0 * ((a - 1.0) + (a + 1.0)*cos(w0))
let a2: Double = (a + 1.0) + (a - 1.0)*cos(w0) - beta * sin(w0)
let b0: Double = a * ((a + 1.0) - (a - 1.0)*cos(w0) + beta * sin(w0))
let b1: Double = 2.0 * a * ((a - 1.0) - (a + 1.0)*cos(w0))
let b2:Double = a * ((a + 1.0) - (a - 1.0)*cos(w0) - beta * sin(w0))
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class HighShelfFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, q: Double, gain: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let a: Double = pow(10.0, (gain / 40.0))
let beta: Double = a.squareRoot() / q
let a0: Double = (a + 1.0) - (a - 1.0)*cos(w0) + beta * sin(w0)
let a1: Double = 2.0 * ((a - 1.0) - (a + 1.0)*cos(w0))
let a2: Double = (a + 1.0) - (a - 1.0)*cos(w0) - beta * sin(w0)
let b0: Double = a * ((a + 1.0) + (a - 1.0)*cos(w0) + beta * sin(w0))
let b1: Double = -2.0 * a * ((a - 1.0) + (a + 1.0)*cos(w0))
let b2: Double = a * ((a + 1.0) + (a - 1.0)*cos(w0) - beta * sin(w0))
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
class PeakingFilterParameter: FilterParameter {
init(sampleRate: Double, frequency: Double, width: Double, gain: Double) {
let w0: Double = 2.0 * Double.pi * frequency / sampleRate
let alpha: Double = sin(w0) * sinh(log(2.0)/2.0 * width * w0 / sin(w0))
let a: Double = pow(10.0, (gain / 40.0))
let a0: Double = 1.0 + alpha / a
let a1: Double = -2.0 * cos(w0)
let a2: Double = 1.0 - alpha / a
let b0: Double = 1.0 + alpha * a
let b1: Double = -2.0 * cos(w0)
let b2: Double = 1.0 - alpha * a
super.init(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0)
}
}
func readPCMBuffer(url: URL) -> AVAudioPCMBuffer? {
guard let input = try? AVAudioFile(forReading: url, commonFormat: .pcmFormatInt16, interleaved: false) else {
return nil
}
guard let buffer = AVAudioPCMBuffer(pcmFormat: input.processingFormat, frameCapacity: AVAudioFrameCount(input.length)) else {
return nil
}
do {
try input.read(into: buffer)
} catch {
return nil
}
return buffer
}
func writePCMBuffer(url: URL, buffer: AVAudioPCMBuffer) throws {
let settings: [String: Any] = [
AVFormatIDKey: buffer.format.settings[AVFormatIDKey] ?? kAudioFormatLinearPCM,
AVNumberOfChannelsKey: buffer.format.settings[AVNumberOfChannelsKey] ?? 2,
AVSampleRateKey: buffer.format.settings[AVSampleRateKey] ?? 44100,
AVLinearPCMBitDepthKey: buffer.format.settings[AVLinearPCMBitDepthKey] ?? 16
]
do {
let output = try AVAudioFile(forWriting: url, settings: settings, commonFormat: .pcmFormatInt16, interleaved: false)
try output.write(from: buffer)
} catch {
throw error
}
}
func applyLowPassFilter(from inputPath: String, to outputPath: String) {
guard let inputBuffer = readPCMBuffer(url: URL(string: inputPath)!) else {
fatalError("failed to read \(inputPath)")
}
guard let outputBuffer = AVAudioPCMBuffer(pcmFormat: inputBuffer.format, frameCapacity: inputBuffer.frameLength) else {
fatalError("failed to create a buffer for writing")
}
guard let inputInt16ChannelData = inputBuffer.int16ChannelData else {
fatalError("failed to obtain underlying input buffer")
}
guard let outputInt16ChannelData = outputBuffer.int16ChannelData else {
fatalError("failed to obtain underlying output buffer")
}
// Calculate a filter coefficients.
//
// Type ... Low-pass
// Cut off frequency ... 220.0 Hz
// Q-value ... 1.23
let parameter = LowPassFilterParameter(sampleRate: inputBuffer.format.sampleRate, frequency: 220.0, q: 1.23)
// Because the input might be stereo, create filters for each channels.
var filters = [vDSP.Biquad](repeating: vDSP.Biquad(coefficients: [parameter.b0, parameter.b1, parameter.b2, parameter.a1, parameter.a2], channelCount: 1, sectionCount: 1, ofType: Float.self)!, count: Int(inputBuffer.format.channelCount))
for channel in 0 ..< Int(inputBuffer.format.channelCount) {
let p1: UnsafeMutablePointer<Int16> = inputInt16ChannelData[channel]
let p2: UnsafeMutablePointer<Int16> = outputInt16ChannelData[channel]
var signal = [Float](repeating: 0.0, count: Int(inputBuffer.frameLength))
for i in 0 ..< Int(inputBuffer.frameLength) {
signal[i] = Float(p1[i]) / 32767.0
}
signal = filters[channel].apply(input: signal)
for i in 0 ..< Int(inputBuffer.frameLength) {
if signal[i] < -1.0 {
p2[i] = -32767
} else if signal[i] > 1.0 {
p2[i] = 32767
} else {
p2[i] = Int16(Int(signal[i] * 32767))
}
}
}
outputBuffer.frameLength = inputBuffer.frameLength
do {
try writePCMBuffer(url: URL(string: outputPath)!, buffer: outputBuffer)
} catch {
fatalError("failed to write \(outputPath)")
}
}
applyLowPassFilter(from: "file:///tmp/input.wav", to: "file:///tmp/output.wav")
@Kyanga25
Copy link
Copy Markdown

Kyanga25 commented Feb 2, 2024

Thanks so much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment