From faf4425f20285226f021f516a3286c7496ca32d1 Mon Sep 17 00:00:00 2001 From: Martmists Date: Thu, 21 Oct 2021 15:18:26 +0200 Subject: [PATCH] Add N-order IIR Filter Signed-off-by: Martmists --- DIRECTORY.md | 4 ++ src/main/kotlin/audio_filters/IIRFilter.kt | 62 +++++++++++++++++++ .../kotlin/audio_filters/IIRFilterTest.kt | 31 ++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/main/kotlin/audio_filters/IIRFilter.kt create mode 100644 src/test/kotlin/audio_filters/IIRFilterTest.kt diff --git a/DIRECTORY.md b/DIRECTORY.md index 17951f0..85c7131 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -3,6 +3,8 @@ ## Src * Main * Kotlin + * Audio Filters + * [IIRFilter](https://github.com/TheAlgorithms/Kotlin/blob/master/src/main/kotlin/audio_filters/IIRFilter.kt) * Dynamic Programming * [Palindromepartitioning](https://github.com/TheAlgorithms/Kotlin/blob/master/src/main/kotlin/dynamic_programming/PalindromePartitioning.kt) * Dynamicprogramming @@ -38,6 +40,8 @@ * [Quicksort](https://github.com/TheAlgorithms/Kotlin/blob/master/src/main/kotlin/sort/QuickSort.kt) * [Selectionsort](https://github.com/TheAlgorithms/Kotlin/blob/master/src/main/kotlin/sort/SelectionSort.kt) * Test + * Audio Filters + * [IIRFilterTest](https://github.com/TheAlgorithms/Kotlin/blob/master/src/test/kotlin/audio_filters/IIRFilterTest.kt) * Dynamic Programming * [Palindromepartitioningtest](https://github.com/TheAlgorithms/Kotlin/blob/master/src/test/kotlin/dynamic_programming/PalindromePartitioningTest.kt) * Dynamicprogramming diff --git a/src/main/kotlin/audio_filters/IIRFilter.kt b/src/main/kotlin/audio_filters/IIRFilter.kt new file mode 100644 index 0000000..fa2379e --- /dev/null +++ b/src/main/kotlin/audio_filters/IIRFilter.kt @@ -0,0 +1,62 @@ +package audio_filters + +/** + * Implement N-order IIR Filter using 1st form + * Assumes inputs are normalized to [-1, 1] + * + * @param order order of the IIR Filter + */ +class IIRFilter(val order: Int) { + private val coeffsA = MutableList(order+1) { if (it == 0) 1.0 else 0.0 } + private val coeffsB = MutableList(order+1) { if (it == 0) 1.0 else 0.0 } + private val historyX = MutableList(order) { 0.0 } + private val historyY = MutableList(order) { 0.0 } + + /** + * Process a single sample + * + * @param sample Sample to process + * @return processed sample + */ + fun process(sample: Double): Double { + var result = 0.0 + + for (i in 1..order) { + result += coeffsB[i] * historyX[i-1] - coeffsA[i] * historyY[i-1] + } + + result = (result + coeffsB[0] * sample) / coeffsA[0] + + for (i in 1 until order) { + historyX[i] = historyX[i-1] + historyY[i] = historyY[i-1] + } + + historyX[0] = sample + historyY[0] = result + + return result + } + + /** + * Set the A/B coefficients, where B is the numerator and A is the denominator + */ + fun setCoeffs(coeffsA: List, coeffsB: List) { + if (coeffsA.size != order+1) { + throw IllegalArgumentException("Expected coeffsA to have ${order+1} values for $order-order IIR filter, got ${coeffsA.size}") + } + + if (coeffsA[0] == 0.0) { + throw IllegalArgumentException("coeffsA[0] cannot be 0.0!") + } + + if (coeffsB.size != order+1) { + throw IllegalArgumentException("Expected coeffsA to have ${order+1} values for $order-order IIR filter, got ${coeffsB.size}") + } + + for (i in 0..order) { + this.coeffsA[i] = coeffsA[i] + this.coeffsB[i] = coeffsB[i] + } + } +} diff --git a/src/test/kotlin/audio_filters/IIRFilterTest.kt b/src/test/kotlin/audio_filters/IIRFilterTest.kt new file mode 100644 index 0000000..1ec95ac --- /dev/null +++ b/src/test/kotlin/audio_filters/IIRFilterTest.kt @@ -0,0 +1,31 @@ +package audio_filters + +import org.junit.Test + +class IIRFilterTest { + @Test + fun iirFilterTestDefault() { + // Default behavior is to just return the sample + val filter = IIRFilter(10) + val sample = 0.5 + assert(filter.process(sample) == sample) + } + + @Test + fun iirFilterTestLowPass() { + // Butterworth low-pass filter with Fc=10_000 + val filter = IIRFilter(2) + filter.setCoeffs( + listOf(1.0, -0.3075651627667613, 0.18834053593286307), + listOf(0.22019384329152544, 0.4403876865830509, 0.22019384329152544) + ) + + val samples = listOf(0.5, 1.0, -0.3) + val expected = listOf(0.11009692164576272, 0.47424966420914927, 0.6095534171786035) + + for (i in samples.indices) { + val result = filter.process(samples[i]) + assert(result == expected[i]) { "Expected ${expected[i]}, got $result" } + } + } +}