Skip to main content

FIR filter and object-oriented Swift

The Swift1 language is a successor to Apple’s Objective-C2. LadyBird3 is an open-source browser written in C++. Recently, I learned that its developers is looking for successor to C++. They compared Rust and Swift languages and chose the latter. In this interview4, Kling, the original author of LadyBird, says Swift is better at object-oriented programming (OOP) than Rust.

I have written an implementation of FIR filter in object-oriented C++5. So I thought I would rewrite it in object-oriented Swift for the development experience.

Here is the base class. It’s the direct form FIR6.

protocol FilterProtocol {
    func filter(input : Double) -> Double
}

class FIR : FilterProtocol {
    var coeffs : [Double]
    var delay_line : [Double]
    
    init(coeffs : [Double]) {
        self.coeffs = coeffs
        self.delay_line = [Double](repeating: 0.0, count: coeffs.count - 1)
    }
    
    func filter(input : Double) -> Double {
        var sum : Double = input * self.coeffs[0]
        for i in 0...self.delay_line.count-1 {
            sum += self.coeffs[i+1] * self.delay_line[i]
        }
        
        // Update delay line
        for i in stride(from: self.delay_line.count-1, through: 1, by: -1) {
            self.delay_line[i] = self.delay_line[i-1]
        }
        self.delay_line[0] = input
        
        return sum
    }
}

Here is the transposed form FIR6. I override the filter() function of the base class.

class FIRTransposed : FIR {
    override func filter(input : Double) -> Double {
        var product = [Double](repeating:0.0, count: coeffs.count)
        for i in 0...self.coeffs.count-1 {
            product[i] = input * self.coeffs[i]
        }
        let sum = product[0] + self.delay_line[self.delay_line.count-1]

        // Update delay line
        for i in 1...product.count-1 {
            let reverse = delay_line.count - i
            if reverse == 0 {
                self.delay_line[reverse] = product[i]
            } else {
                self.delay_line[reverse] = product[i] + self.delay_line[reverse-1]
            }
        }

        return sum
    }
}

Here is the test bench:

func sendImpulse(filter: FIR) {

    print("coefficients:", filter.coeffs)
    print("delay line:", filter.delay_line)

    var impulse = [Double](repeating: 0.0, count: coeffs.count)
    impulse[0] = 1.0
    var output = [Double](repeating: 0.0, count: coeffs.count)

    for i in 0...impulse.count-1 {
        output[i] = filter.filter(input: impulse[i])
    }

    print("impulse:", impulse)
    print("output:", output)
    print("delay line:", filter.delay_line)
}

var coeffs = [3.0, -2.0, 1.0]
var fir = FIR(coeffs: coeffs)
sendImpulse(filter: fir)

print("Transposed")
var firtranspose = FIRTransposed(coeffs: coeffs)
sendImpulse(filter: firtranspose)

Here is the output

coefficients: [3.0, -2.0, 1.0]
delay line: [0.0, 0.0]
impulse: [1.0, 0.0, 0.0]
output: [3.0, -2.0, 1.0]
delay line: [0.0, 0.0]
Transposed
coefficients: [3.0, -2.0, 1.0]
delay line: [0.0, 0.0]
impulse: [1.0, 0.0, 0.0]
output: [3.0, -2.0, 1.0]
delay line: [0.0, 0.0]
Program ended with exit code: 0

I wrote this program in Xcode on a Macbook. Writing in Swift feels more like writing in Python than in C++. There is a lot less boilerplate code. Unlike C++, Swift has native print support for arrays. Overall, it has the efficiency of writing in a scripting language and the speed of compiled executable.