Skip to content

Instantly share code, notes, and snippets.

@moutend
Created March 27, 2021 01:10
Show Gist options
  • Save moutend/4f3a430e6d5a4cef4374d1947bbd3d73 to your computer and use it in GitHub Desktop.
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

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