diff --git a/c_sources/common.h b/c_sources/common.h
new file mode 100644
--- /dev/null
+++ b/c_sources/common.h
@@ -0,0 +1,276 @@
+/*
+ * Common functions used by filters, decimators and resamplers. 
+ * Includes real and complex dot products and SIMD horizontal additions.
+ * TODO: better performance might be possible by avoiding unaligned memory accesses.
+ */
+
+#include <x86intrin.h>
+
+/*
+ * Real horizontal addition
+ */
+static inline __m128 sse_hadd_R(__m128 in){
+    __m128 accum;
+    accum = _mm_hadd_ps(in, in);
+    return _mm_hadd_ps(accum, accum);
+}
+
+static inline __m128 avx_hadd_R(__m256 in){
+    __m128 res1 = _mm256_extractf128_ps(in, 0);
+    __m128 res2 = _mm256_extractf128_ps(in, 1);
+
+    res1 = _mm_hadd_ps(res1, res1);
+    res1 = _mm_hadd_ps(res1, res1);
+
+    res2 = _mm_hadd_ps(res2, res2);
+    res2 = _mm_hadd_ps(res2, res2);
+
+    return _mm_add_ss(res1, res2);
+}
+
+/*
+ * Real dot products
+ */
+static inline float dotprod_R(int num, float *a, float *b){
+    int i;
+    float accum = 0;
+    for(i=0; i<num; i++){
+        accum += a[i] * b[i];
+    }
+    return accum;
+}
+
+static inline __m128 sse_dotprod_R(int num, float *a, float *b){
+    int i;
+    __m128 accum = _mm_setzero_ps();
+
+    for(i=0; i<num; i+=4){
+        //Load the needed vectors
+        __m128 coeff = _mm_loadu_ps(a + i);
+        __m128 val   = _mm_loadu_ps(b + i);
+
+        //Multiply and acumulate
+        accum = _mm_add_ps(accum, _mm_mul_ps(coeff, val));
+    }
+    return accum;
+}
+
+static inline __m256 avx_dotprod_R(int num, float *a, float *b){
+    int i;
+    __m256 accum = _mm256_setzero_ps();
+
+    for(i=0; i<num; i+=8){
+
+        //Load the needed vectors
+        __m256 coeff = _mm256_loadu_ps(a + i);
+        __m256 val   = _mm256_loadu_ps(b + i);
+
+        //Multiply and acumulate
+        accum = _mm256_add_ps(accum, _mm256_mul_ps(coeff, val));
+    }
+    return accum;
+}
+
+/*
+ * Complex horizontal addition
+ */
+static inline __m128 sse_hadd_C(__m128 in) {
+    __m128 accum = _mm_shuffle_ps(in, in, 0b11011000);
+    return _mm_hadd_ps(accum, accum);
+}
+
+static inline __m128 avx_hadd_C(__m256 in) {
+    __m256 accum    = _mm256_permute_ps(in, _MM_SHUFFLE(3, 1, 2, 0));
+    __m128 accum_hi = _mm256_extractf128_ps(accum, 1);
+    __m128 accum_lo = _mm256_extractf128_ps(accum, 0);
+    __m128 added    = _mm_hadd_ps(accum_lo, accum_hi);
+    added           = _mm_permute_ps(added, _MM_SHUFFLE(3, 1, 2, 0));
+    added           = _mm_hadd_ps(added, added);
+    return added;
+}
+
+/*
+ * Complex dot products
+ */
+static inline float dotprod_C(int num, float *a, float *b, float *result){
+    int i;
+    float real = 0;
+    float imag = 0;
+    for(i=0; i<num; i++){
+        real += b[2*i] * a[i];
+        imag += b[2*i+1] * a[i];
+    }
+    result[0] = real;
+    result[1] = imag;
+}
+
+static inline __m128 sse_dotprod_C(int num, float *coeffs, float *startPtr){
+    int i;
+    __m128 accum1 = _mm_setzero_ps();
+    __m128 accum2 = _mm_setzero_ps();
+
+    for(i=0; i<num; i+=4){
+
+        //Load the needed vectors
+        __m128 coeff  = _mm_loadu_ps(coeffs + i);
+        __m128 coeff1 = _mm_shuffle_ps(coeff, coeff, 0x50);
+        __m128 coeff2 = _mm_shuffle_ps(coeff, coeff, 0xfa);
+        __m128 val1   = _mm_loadu_ps(startPtr + 2 * i);
+        __m128 val2   = _mm_loadu_ps(startPtr + 2 * i + 4);
+
+        //Multiply and acumulate
+        accum1 = _mm_add_ps(accum1, _mm_mul_ps(coeff1, val1));
+        accum2 = _mm_add_ps(accum2, _mm_mul_ps(coeff2, val2));
+    }
+    __m128 accum = _mm_add_ps(accum1, accum2);
+    return accum;
+}
+
+static inline __m256 avx_dotprod_C(int num, float *a, float *b){
+    int i;
+    __m256 accum1 = _mm256_setzero_ps();
+    __m256 accum2 = _mm256_setzero_ps();
+
+    float *startPtr = a;
+    for(i=0; i<num; i+=8){
+
+        //Load the needed vectors
+        __m256 coeff  = _mm256_loadu_ps(a + i);
+
+        __m256 coeffa = _mm256_shuffle_ps(coeff, coeff, 0x50);
+        __m256 coeffb = _mm256_shuffle_ps(coeff, coeff, 0xfa);
+
+        __m256 coeff1 = _mm256_permute2f128_ps(coeffa, coeffb, 0x20);
+        __m256 coeff2 = _mm256_permute2f128_ps(coeffa, coeffb, 0x31);
+
+        __m256 val1   = _mm256_loadu_ps(b + 2 * i);
+        __m256 val2   = _mm256_loadu_ps(b + 2 * i + 8);
+
+        //Multiply and acumulate
+        accum1 = _mm256_add_ps(accum1, _mm256_mul_ps(coeff1, val1));
+        accum2 = _mm256_add_ps(accum2, _mm256_mul_ps(coeff2, val2));
+    }
+
+    return _mm256_add_ps(accum1, accum2);
+}
+
+/*
+ * Real symmetric dot products
+ */
+static inline __m128 sse_sym_dotprod_R(int num, float *a, float *b){
+    int i;
+    __m128 accum = _mm_setzero_ps();
+
+    float *startPtr = b;
+    float *endPtr = b + num * 2 - 4;
+    for(i=0; i<num; i+=4){
+
+        //Load the needed vectors
+        __m128 coeff = _mm_loadu_ps(a + i);
+        __m128 val1  = _mm_loadu_ps(startPtr + i);
+        __m128 val2  = _mm_loadu_ps(endPtr   - i);
+        val2         = _mm_permute_ps(val2, _MM_SHUFFLE(0, 1, 2, 3));
+
+        //Multiply and acumulate
+        accum = _mm_add_ps(accum, _mm_mul_ps(coeff, _mm_add_ps(val1, val2)));
+    }
+
+    return accum;
+}
+
+static inline __m256 avx_sym_dotprod_R(int num, float *a, float *b){
+    int i;
+    __m256 accum = _mm256_setzero_ps();
+
+    float *startPtr = b;
+    float *endPtr   = b + num * 2 - 8;
+    for(i=0; i<num; i+=8){
+
+        //Load the needed vectors
+        __m256 coeff = _mm256_loadu_ps(a + i);
+        __m256 val1  = _mm256_loadu_ps(startPtr + i);
+        __m256 val2  = _mm256_loadu_ps(endPtr   - i);
+        val2         = _mm256_permute2f128_ps(val2, val2, 0x01);
+        val2         = _mm256_permute_ps(val2, _MM_SHUFFLE(0, 1, 2, 3));
+
+        //Multiply and acumulate
+        accum = _mm256_add_ps(accum, _mm256_mul_ps(coeff, _mm256_add_ps(val1, val2)));
+    }
+
+    return accum;
+}
+
+/*
+ * Complex symmetric dot products
+ */
+static inline __m128 sse_sym_dotprod_C(int num, float *a, float *b){
+    int i;
+    __m128 accum1 = _mm_setzero_ps();
+    __m128 accum2 = _mm_setzero_ps();
+
+    float *startPtr = b;
+    float *endPtr   = b + num * 4 - 4;
+    for(i=0; i<num; i+=4){
+
+        //Load the needed vectors
+        __m128 coeff  = _mm_loadu_ps(a + i);
+        __m128 coeff1 = _mm_shuffle_ps(coeff, coeff, 0x50);
+        __m128 coeff2 = _mm_shuffle_ps(coeff, coeff, 0xfa);
+
+        __m128 val1   = _mm_loadu_ps(startPtr + 2*i);
+        __m128 val2   = _mm_loadu_ps(startPtr + 2*i + 4);
+        __m128 val3   = _mm_loadu_ps(endPtr   - 2*i - 4);
+        val3          = _mm_shuffle_ps(val3, val3, _MM_SHUFFLE(1, 0, 3, 2));
+        __m128 val4   = _mm_loadu_ps(endPtr   - 2*i);
+        val4          = _mm_shuffle_ps(val4, val4, _MM_SHUFFLE(1, 0, 3, 2));
+
+        //Multiply and acumulate
+        accum1 = _mm_add_ps(accum1, _mm_mul_ps(coeff1, _mm_add_ps(val1, val4)));
+        accum2 = _mm_add_ps(accum2, _mm_mul_ps(coeff2, _mm_add_ps(val2, val3)));
+    }
+
+    return _mm_add_ps(accum1, accum2);
+}
+
+static inline __m256 avx_sym_dotprod_C(int num, float *a, float *b){
+    int i;
+    __m256 accum1 = _mm256_setzero_ps();
+    __m256 accum2 = _mm256_setzero_ps();
+
+    float *startPtr = b;
+    float *endPtr   = b + num * 4 - 8;
+    for(i=0; i<num; i+=8){
+        //
+        //Load the needed vectors
+        __m256 coeff  = _mm256_loadu_ps(a + i);
+        __m256 coeffa = _mm256_shuffle_ps(coeff, coeff, 0x50);
+        __m256 coeffb = _mm256_shuffle_ps(coeff, coeff, 0xfa);
+        __m256 coeff1 = _mm256_permute2f128_ps(coeffa, coeffb, 0x20);
+        __m256 coeff2 = _mm256_permute2f128_ps(coeffa, coeffb, 0x31);
+
+        __m256 val1  = _mm256_loadu_ps(startPtr + 2*i);
+        __m256 val2  = _mm256_loadu_ps(startPtr + 2*i + 8);
+        __m256 val3  = _mm256_loadu_ps(endPtr   - 2*i - 8);
+        val3         = _mm256_permute_ps(val3, _MM_SHUFFLE(1, 0, 3, 2));
+        val3         = _mm256_permute2f128_ps(val3, val3, 0x01);
+        __m256 val4  = _mm256_loadu_ps(endPtr   - 2*i);
+        val4         = _mm256_permute_ps(val4, _MM_SHUFFLE(1, 0, 3, 2));
+        val4         = _mm256_permute2f128_ps(val4, val4, 0x01);
+
+        //Multiply and acumulate
+        accum1 = _mm256_add_ps(accum1, _mm256_mul_ps(coeff1, _mm256_add_ps(val1, val4)));
+        accum2 = _mm256_add_ps(accum2, _mm256_mul_ps(coeff2, _mm256_add_ps(val2, val3)));
+    }
+
+    return _mm256_add_ps(accum1, accum2);
+}
+
+/*
+ * Storing complex numbers
+ */
+static inline void store_complex(float *loc, __m128 val){
+    _mm_store_ss(loc, val);
+    val = _mm_shuffle_ps(val, val, 0b00000001);
+    _mm_store_ss(loc + 1, val);
+}
+
diff --git a/hs_sources/SDR/PipeUtils.hs b/hs_sources/SDR/PipeUtils.hs
--- a/hs_sources/SDR/PipeUtils.hs
+++ b/hs_sources/SDR/PipeUtils.hs
@@ -20,7 +20,7 @@
         lift $ yield res
         lift $ lift $ yield res
 
--- | Combime two consumers into a single consumer
+-- | Combine two consumers into a single consumer
 combine :: Monad m => Consumer a m r -> Consumer a m r -> Consumer a m r
 combine x y = runEffect $ runEffect (fork func >-> hoist (lift . lift) x) >-> hoist lift y
     where
diff --git a/sdr.cabal b/sdr.cabal
--- a/sdr.cabal
+++ b/sdr.cabal
@@ -1,5 +1,5 @@
 name:                sdr
-version:             0.1.0.0
+version:             0.1.0.1
 synopsis:            A software defined radio library
 description:         
     Write software defined radio applications in Haskell.
@@ -95,6 +95,7 @@
     default-language:    Haskell2010
     ghc-options:         -O2
     includes:            c_sources/common.h
+    install-includes:    c_sources/common.h
     c-sources:           
         c_sources/filter.c, 
         c_sources/decimate.c, 
